diff --git a/shell/app/common/utils/go-to.tsx b/shell/app/common/utils/go-to.tsx index a81a1e9ebd..7063042c2d 100644 --- a/shell/app/common/utils/go-to.tsx +++ b/shell/app/common/utils/go-to.tsx @@ -286,6 +286,8 @@ export enum pages { // 微服务-具体事务分析页 mspServiceTransaction = '/{orgName}/msp/{projectId}/{env}/{tenantGroup}/monitor/{terminusKey}/service-analysis/{applicationId}/{serviceId}/{serviceName}/transaction', + mspServiceAnalysisTrace = '/{orgName}/msp/{projectId}/{env}/{tenantGroup}/monitor/{terminusKey}/service-analysis/trace', + mspGatewayIngress = '/{orgName}/msp/{projectId}/{env}/{tenantGroup}/synopsis/{terminusKey}/topology/gateway-ingress', mspExternalInsight = '/{orgName}/msp/{projectId}/{env}/{tenantGroup}/synopsis/{terminusKey}/topology/ei/{hostName}/affairs', diff --git a/shell/app/config-page/components/bubble-graph/bubble-graph.spec.d.ts b/shell/app/config-page/components/bubble-graph/bubble-graph.spec.d.ts new file mode 100644 index 0000000000..81658ba0ae --- /dev/null +++ b/shell/app/config-page/components/bubble-graph/bubble-graph.spec.d.ts @@ -0,0 +1,47 @@ +// Copyright (c) 2021 Terminus, Inc. +// +// This program is free software: you can use, redistribute, and/or modify +// it under the terms of the GNU Affero General Public License, version 3 +// or later ("AGPL"), as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +declare namespace CP_BUBBLE_GRAPH { + interface IProps { + theme: 'dark' | 'light'; + className?: string; + useRealSize?: boolean; + style: import('react').CSSProperties; + } + + interface Spec { + type: 'BubbleGraph'; + props: IProps; + data: { + title: string; + subTitle?: string; + list: { + dimension: string; + group: string; + size: { + value: number; + }; + x: { + unit: string; + value: number; + }; + y: { + unit: string; + value: number; + }; + }[]; + }; + } + + type Props = MakeProps; +} diff --git a/shell/app/config-page/components/bubble-graph/index.tsx b/shell/app/config-page/components/bubble-graph/index.tsx new file mode 100644 index 0000000000..63b4131760 --- /dev/null +++ b/shell/app/config-page/components/bubble-graph/index.tsx @@ -0,0 +1,153 @@ +// Copyright (c) 2021 Terminus, Inc. +// +// This program is free software: you can use, redistribute, and/or modify +// it under the terms of the GNU Affero General Public License, version 3 +// or later ("AGPL"), as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +import React from 'react'; +import { colorToRgb } from 'common/utils'; +import Echarts from 'charts/components/echarts'; +import { functionalColor } from 'common/constants'; +import { groupBy } from 'lodash'; + +const themeColor = { + dark: '#ffffff', + light: functionalColor.info, +}; + +const genCommonSize = (min: number, max: number, current: number, interval = 5) => { + const range = Math.ceil((max - min) / interval); + for (let i = 1; i <= interval; i++) { + const criticalValue = min + i * range; + if (current <= criticalValue) { + return criticalValue; + } + } + return current; +}; + +type ISerieData = [number, number, number, string, string]; + +const CP_BubbleGraph: React.FC = (props) => { + const { props: configProps, data, operations, execOperation, customOp } = props; + const color = themeColor[configProps.theme ?? 'light']; + const [option, onEvents] = React.useMemo(() => { + const dimensions = groupBy(data.list, 'dimension'); + const dimensionsArr = Object.keys(groupBy(data.list, 'dimension')); + const metaData = {}; + const xAxisData: Array = []; + const sizeArr: Array = []; + dimensionsArr.forEach((dimension) => { + const current = dimensions[dimension]; + current.forEach((item) => { + metaData[dimension] ||= []; + const xCoords = `${item.x.value}`; + const yCoords = `${item.y.value}`; + const size = item.size.value; + xAxisData.includes(xCoords) || xAxisData.push(xCoords); + sizeArr.includes(item.size.value) || sizeArr.push(item.size.value); + metaData[dimension].push([xCoords, yCoords, size, item]); + }); + }); + + const maxSize = Math.max(...sizeArr); + const minSize = Math.min(...sizeArr); + const series = Object.keys(metaData).map((dimension) => { + return { + name: dimension, + data: metaData[dimension], + type: 'scatter', + symbolSize: (meta: ISerieData) => { + return configProps.useRealSize ? meta[2] : genCommonSize(minSize, maxSize, meta[2]); + }, + emphasis: { + focus: 'series', + label: { + show: true, + formatter: (meta: { data: ISerieData }) => meta.data[2], + position: 'inside', + }, + }, + }; + }); + const chartOption = { + backgroundColor: 'transparent', + legend: { + data: (dimensionsArr ?? []).map((item) => ({ + name: item, + textStyle: { + color: colorToRgb(color, 0.6), + }, + })), + icon: 'reat', + itemWidth: 12, + itemHeight: 3, + type: 'scroll', + bottom: true, + }, + grid: { + containLabel: true, + left: '5%', + bottom: 30, + top: data.subTitle ? 25 : 10, + right: 0, + }, + xAxis: { + data: xAxisData, + axisLabel: { + color: colorToRgb(color, 0.6), + }, + splitLine: { + show: false, + }, + }, + yAxis: { + axisLabel: { + color: colorToRgb(color, 0.3), + }, + splitLine: { + show: true, + lineStyle: { + color: [colorToRgb(color, 0.1)], + }, + }, + scale: true, + }, + series, + }; + const chartEvent = {}; + if (customOp?.click || operations?.click) { + Object.assign(chartEvent, { + click: (params: { data: Array }) => { + customOp?.click && customOp.click(params.data); + operations?.click && execOperation(operations.click, params.data); + }, + }); + } + return [chartOption, chartEvent]; + }, [data.list, operations, customOp, configProps.useRealSize]); + + return ( +
+
+ {data.title} +
+
+ +
+
+ ); +}; + +export default CP_BubbleGraph; diff --git a/shell/app/config-page/components/index.tsx b/shell/app/config-page/components/index.tsx index 317c9739f9..d6c9a2c365 100644 --- a/shell/app/config-page/components/index.tsx +++ b/shell/app/config-page/components/index.tsx @@ -80,6 +80,7 @@ import TopN from './top-n'; import ScaleCard from './scale-card/scale-card'; import LineGraph from './line-graph'; import KV from './kv'; +import BubbleGraph from './bubble-graph'; export const containerMap = { Alert, @@ -154,4 +155,5 @@ export const containerMap = { DropdownSelect2, LineGraph, KV, + BubbleGraph, }; diff --git a/shell/app/config-page/components/table/v2/utils.tsx b/shell/app/config-page/components/table/v2/utils.tsx index 5d2dceae55..8b01fcad53 100644 --- a/shell/app/config-page/components/table/v2/utils.tsx +++ b/shell/app/config-page/components/table/v2/utils.tsx @@ -12,8 +12,9 @@ // along with this program. If not, see . import React from 'react'; import { Tooltip } from 'antd'; -import { ErdaIcon } from 'common'; +import { Copy, ErdaIcon, TagsRow } from 'common'; import { has } from 'lodash'; +import i18n from 'i18n'; export const convertTableData = (data?: CP_TABLE2.IData, haveBatchOp?: boolean, props?: CP_TABLE2.IProps) => { const { columnsMap: pColumnsMap, pageSizeOptions } = props || {}; @@ -94,6 +95,38 @@ export const getRender = (val: Obj, record: Obj) => { // Comp = // } break; + case 'text': + { + const value = (typeof data === 'string' ? data : data?.text) || '-'; + Comp = data.enableCopy ? ( + + + {value || i18n.t('copy')} + + + + + + ) : ( + value + ); + } + break; + case 'labels': + { + const { labels, showCount } = data; + // TODO showCount should be calculated based on the container width + Comp = ( + ({ + ...item, + label: item.title || item.id, + }))} + showCount={showCount ?? 2} + /> + ); + } + break; default: Comp = (typeof data === 'string' ? data : data?.text) || '-'; break; diff --git a/shell/app/config-page/mock/data-rank.mock.ts b/shell/app/config-page/mock/data-rank.mock.ts index 3e17932fdf..76ef1da391 100644 --- a/shell/app/config-page/mock/data-rank.mock.ts +++ b/shell/app/config-page/mock/data-rank.mock.ts @@ -25,165 +25,91 @@ export const mockData = { }, protocol: { hierarchy: { - root: 'dataRank', + root: 'BubbleGraph', structure: { - myPage: ['dataRank'], + myPage: ['BubbleGraph'], }, }, components: { myPage: { type: 'Container' }, - dataRank: { - type: 'TopN', + BubbleGraph: { + type: 'BubbleGraph', data: { list: [ { - title: '吞吐量最大Top5', - type: 'maximumThroughputTop5', - span: 6, - items: [ - { - id: '', // serviceId - name: '服务名称A', // serviceName - value: 300, - percent: 100, - unit: 'reqs/s', - }, - { - name: '服务名称b', - value: 80, - percent: 100, - unit: 'reqs/s', - }, - { - name: '服务名称c', - value: 77, - percent: 100, - unit: 'reqs/s', - }, - { - name: '服务名称d', - value: 50, - percent: 100, - unit: 'reqs/s', - }, - { - name: '服务名称很长的服务服务名称很长的服务服务名称很长的服务服务名称很长的服务', - value: 25, - percent: 100, - unit: 'reqs/s', - }, - ], + dimension: 'dimension A', + group: 'group A', + size: { + value: 10, + }, + x: { + unit: '', + value: 100, + }, + y: { + unit: '', + value: 100, + }, }, { - title: '吞吐量最小Top5', - type: 'minimumThroughputTop5', - span: 6, - items: [ - { - name: '服务名称A', - value: 100, - percent: 100, - unit: 'reqs/s', - }, - { - name: '服务名称b', - value: 80, - percent: 100, - unit: 'reqs/s', - }, - { - name: '服务名称c', - value: 77, - percent: 100, - unit: 'reqs/s', - }, - { - name: '服务名称d', - value: 50, - percent: 100, - unit: 'reqs/s', - }, - { - name: '服务名称很长的服务服务名称很长的服务服务名称很长的服务服务名称很长的服务', - value: 25, - percent: 100, - unit: 'reqs/s', - }, - ], + dimension: 'dimension A', + group: 'group B', + size: { + value: 15, + }, + x: { + unit: '', + value: 150, + }, + y: { + unit: '', + value: 200, + }, }, { - title: '平均延迟Top5', - type: 'averageDelayTop5', - span: 6, - items: [ - { - name: '服务名称A', - value: 100, - percent: 100, - unit: 'reqs/s', - }, - { - name: '服务名称b', - value: 80, - percent: 100, - unit: 'reqs/s', - }, - { - name: '服务名称c', - value: 77, - percent: 100, - unit: 'reqs/s', - }, - { - name: '服务名称d', - value: 50, - percent: 100, - unit: 'reqs/s', - }, - { - name: '服务名称很长的服务服务名称很长的服务服务名称很长的服务服务名称很长的服务', - value: 25, - percent: 100, - unit: 'reqs/s', - }, - ], + dimension: 'dimension B', + group: 'group A', + size: { + value: 25, + }, + x: { + unit: '', + value: 400, + }, + y: { + unit: '', + value: 300, + }, }, { - title: '错误率Top5', - type: 'errorRateTop5', - span: 6, - items: [ - { - name: '服务名称A', - value: 100, - percent: 100, - unit: 'reqs/s', - }, - { - name: '服务名称b', - value: 80, - percent: 100, - unit: 'reqs/s', - }, - { - name: '服务名称c', - value: 77, - percent: 100, - unit: 'reqs/s', - }, - { - name: '服务名称d', - value: 50, - percent: 100, - unit: 'reqs/s', - }, - { - name: '服务名称很长的服务服务名称很长的服务服务名称很长的服务服务名称很长的服务', - value: 25, - percent: 100, - unit: 'reqs/s', - }, - ], + dimension: 'dimension B', + group: 'group B', + size: { + value: 30, + }, + x: { + unit: '', + value: 1, + }, + y: { + unit: '', + value: 400, + }, + }, + { + dimension: 'dimension B', + group: 'group B', + size: { + value: 100, + }, + x: { + unit: '', + value: 100, + }, + y: { + unit: '', + value: 400, + }, }, ], }, diff --git a/shell/app/config-page/mock/index.tsx b/shell/app/config-page/mock/index.tsx index d475354711..bc25f8e0e0 100644 --- a/shell/app/config-page/mock/index.tsx +++ b/shell/app/config-page/mock/index.tsx @@ -11,7 +11,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -import { enhanceMock, mockData } from './crud.mock'; +import { enhanceMock, mockData } from './data-rank.mock'; export const useMock = (payload: Obj) => { if (process.env.NODE_ENV === 'production') { diff --git a/shell/app/config-page/mock/mock.tsx b/shell/app/config-page/mock/mock.tsx index f2164835c9..90a414d879 100644 --- a/shell/app/config-page/mock/mock.tsx +++ b/shell/app/config-page/mock/mock.tsx @@ -24,6 +24,13 @@ const Mock = () => { useMock={useMock} forceMock customProps={{ + BubbleGraph: { + op: { + click: (a: CP_DATA_RANK.IItem) => { + console.log(a); + }, + }, + }, dataRank: { op: { clickRow: (a: CP_DATA_RANK.IItem) => { diff --git a/shell/app/modules/msp/env-overview/service-list/pages/trace.tsx b/shell/app/modules/msp/env-overview/service-list/pages/trace.tsx new file mode 100644 index 0000000000..1025b96973 --- /dev/null +++ b/shell/app/modules/msp/env-overview/service-list/pages/trace.tsx @@ -0,0 +1,21 @@ +// Copyright (c) 2021 Terminus, Inc. +// +// This program is free software: you can use, redistribute, and/or modify +// it under the terms of the GNU Affero General Public License, version 3 +// or later ("AGPL"), as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +import React from 'react'; +import TraceSearch from 'trace-insight/pages/trace-querier/trace-search'; + +const Trace = () => { + return ; +}; + +export default Trace; diff --git a/shell/app/modules/msp/env-overview/service-list/pages/transaction.tsx b/shell/app/modules/msp/env-overview/service-list/pages/transaction.tsx index 3936423dae..c9854e83ea 100644 --- a/shell/app/modules/msp/env-overview/service-list/pages/transaction.tsx +++ b/shell/app/modules/msp/env-overview/service-list/pages/transaction.tsx @@ -201,6 +201,15 @@ const Transaction = () => { className: 'bg-white', }, }, + reqDistribution: { + props: { + style: { + width: '100%', + height: '170px', + minHeight: 0, + }, + }, + }, ...['rps', 'avgDuration'].reduce( (previousValue, currentValue) => ({ ...previousValue, diff --git a/shell/app/modules/msp/env-overview/service-list/router.ts b/shell/app/modules/msp/env-overview/service-list/router.ts index 3c1d22aad7..445e713a54 100644 --- a/shell/app/modules/msp/env-overview/service-list/router.ts +++ b/shell/app/modules/msp/env-overview/service-list/router.ts @@ -22,6 +22,7 @@ interface Tabs { const tabs = [ { key: 'overview', name: i18n.t('overview') }, { key: 'transaction', name: i18n.t('msp:call monitor') }, + { key: 'trace', name: i18n.t('msp:tracing query') }, { key: 'anomaly', name: i18n.t('msp:exception') }, { key: 'process', name: i18n.t('msp:process') }, ]; @@ -49,6 +50,21 @@ const serviceAnalysisRoutes = [ }, ], }, + { + path: 'trace', + tabs, + routes: [ + { + path: 'trace-detail/:traceId', + layout: { fullHeight: true }, + getComp: (cb: RouterGetComp) => cb(import('msp/monitor/trace-insight/pages/trace-querier/trace-search-detail')), + }, + { + layout: { noWrapper: true }, + getComp: (cb: RouterGetComp) => cb(import('msp/env-overview/service-list/pages/trace')), + }, + ], + }, { path: 'anomaly', tabs, diff --git a/shell/app/modules/msp/monitor/trace-insight/components/duration.scss b/shell/app/modules/msp/monitor/trace-insight/components/duration.scss new file mode 100644 index 0000000000..d130680570 --- /dev/null +++ b/shell/app/modules/msp/monitor/trace-insight/components/duration.scss @@ -0,0 +1,12 @@ +.trace-duration { + .ant-input, + .ant-input-group-addon { + background: transparent; + border: none; + } + + .ant-select { + background: transparent; + border: none; + } +} diff --git a/shell/app/modules/msp/monitor/trace-insight/components/duration.tsx b/shell/app/modules/msp/monitor/trace-insight/components/duration.tsx index fde64d63e4..175888ded5 100644 --- a/shell/app/modules/msp/monitor/trace-insight/components/duration.tsx +++ b/shell/app/modules/msp/monitor/trace-insight/components/duration.tsx @@ -13,7 +13,7 @@ import React from 'react'; import { Input, Select } from 'antd'; -import i18n from 'i18n'; +import './duration.scss'; const timeUnit = [ { @@ -26,7 +26,7 @@ const timeUnit = [ }, ]; -interface IValue { +export interface IValue { timer: number | string; unit: 'ms' | 's'; } diff --git a/shell/app/modules/msp/monitor/trace-insight/pages/trace-querier/trace-search-detail.tsx b/shell/app/modules/msp/monitor/trace-insight/pages/trace-querier/trace-search-detail.tsx index 87bd783707..06239f5573 100644 --- a/shell/app/modules/msp/monitor/trace-insight/pages/trace-querier/trace-search-detail.tsx +++ b/shell/app/modules/msp/monitor/trace-insight/pages/trace-querier/trace-search-detail.tsx @@ -12,7 +12,7 @@ // along with this program. If not, see . import React from 'react'; -import { Icon as CustomIcon, Copy, ErdaIcon } from 'common'; +import { Copy, ErdaIcon, Icon as CustomIcon } from 'common'; import { useUpdate } from 'common/use-hooks'; import PureTraceDetail from './trace-detail-new'; import monitorCommonStore from 'common/stores/monitorCommon'; @@ -83,6 +83,8 @@ export default ({ traceId, startTime }: { traceId?: string; startTime?: number } }); } else if (currentRoute?.path?.includes('trace/debug')) { goTo(goTo.pages.mspTraceDebug); + } else if (currentRoute?.path?.includes('service-analysis/trace')) { + goTo(goTo.pages.mspServiceAnalysisTrace); } else { goTo(goTo.pages.microTrace); } diff --git a/shell/app/modules/msp/monitor/trace-insight/pages/trace-querier/trace-search.tsx b/shell/app/modules/msp/monitor/trace-insight/pages/trace-querier/trace-search.tsx index baed943604..c9facaca77 100644 --- a/shell/app/modules/msp/monitor/trace-insight/pages/trace-querier/trace-search.tsx +++ b/shell/app/modules/msp/monitor/trace-insight/pages/trace-querier/trace-search.tsx @@ -12,28 +12,21 @@ // along with this program. If not, see . import React from 'react'; -import { debounce, isNumber } from 'lodash'; -import moment, { Moment } from 'moment'; -import { BoardGrid, ContractiveFilter, Copy, TagsRow } from 'common'; -import { useUpdate } from 'common/use-hooks'; -import { ColumnProps } from 'common/components/table/interface'; -import Table from 'common/components/table'; -import { message } from 'antd'; -import { useEffectOnce, useUpdateEffect } from 'react-use'; -import i18n from 'i18n'; -import { getDashboard } from 'msp/services'; -import { getFormatter } from 'charts/utils/formatter'; -import { useLoading } from 'core/stores/loading'; -import traceStore from '../../../../stores/trace'; -import TraceSearchDetail from './trace-search-detail'; -import { ICondition } from 'common/components/contractive-filter'; -import { getQueryConditions } from 'trace-insight/services/trace-querier'; -import Duration, { transformDuration } from 'trace-insight/components/duration'; import { TimeSelectWithStore } from 'msp/components/time-select'; -import monitorCommonStore from 'common/stores/monitorCommon'; +import { getTraceConditions } from 'msp/monitor/trace-insight/services/trace-querier'; +import ContractiveFilter, { ICondition } from 'common/components/contractive-filter'; +import Duration, { IValue, transformDuration } from 'trace-insight/components/duration'; +import i18n from 'i18n'; import routeInfoStore from 'core/stores/route'; - -const DashBoard = React.memo(BoardGrid.Pure); +import monitorCommonStore from 'common/stores/monitorCommon'; +import DiceConfigPage from 'config-page'; +import serviceAnalyticsStore from 'msp/stores/service-analytics'; +import NoServicesHolder from 'msp/env-overview/service-list/pages/no-services-holder'; +import { isNumber } from 'lodash'; +import { message } from 'antd'; +import TraceSearchDetail from 'trace-insight/pages/trace-querier/trace-search-detail'; +import { useUpdate } from 'common/use-hooks'; +import moment from 'moment'; const name = { sort: i18n.t('msp:sort method'), @@ -41,161 +34,110 @@ const name = { traceStatus: i18n.t('msp:tracking status'), }; -const convertData = ( - data: MONITOR_TRACE.TraceConditions, -): [any[], { [k in MONITOR_TRACE.IFixedConditionType]: string }] => { - const { others, ...rest } = data; - const list: ICondition[] = []; - const defaultValue = {}; - const fixC = Object.keys(rest); - fixC.forEach((key, index) => { - const option = data[key]; - defaultValue[key] = option?.[0]?.value; - list.push({ - type: 'select', - fixed: true, - showIndex: index + 2, - key, - label: name[key], - options: option.map((t) => ({ ...t, label: t.displayName })), - customProps: { - mode: 'single', - }, - }); - }); - others?.forEach(({ paramKey, displayName, type }) => { - list.push({ - type, - showIndex: 0, - fixed: false, - placeholder: i18n.t('please enter {name}', { name: displayName }), - key: paramKey, - label: displayName, - }); - }); - return [list, defaultValue]; -}; - -const initialFilter = [ - { - label: i18n.t('msp:duration'), - key: 'duration', - showIndex: 1, - fixed: true, - getComp: (props) => { - return ; - }, - }, -]; - interface IState { - filter: ICondition[]; traceId?: string; - defaultQuery: Obj; - query: Obj; - layout: DC.Layout; - startTime: number; + startTime?: number; + query: { + serviceName?: string; + rpcMethod?: string; + durationMin?: string; + durationMax?: string; + status?: string; + traceID?: string; + httpPath?: string; + }; } -type IQuery = { - [k in MONITOR_TRACE.IFixedConditionType]: string; -} & { - time: [Moment, Moment]; - duration: Array<{ timer: number; unit: 'ms' | 's' }>; -} & { - [k: string]: string; -}; +interface IProps { + scope?: 'serviceMonitor' | 'trace'; +} -const TraceSearch = () => { +interface ITableRow { + traceId: { + data: { text: string }; + }; + traceStartTime: { + data: { text: string }; + }; +} + +const TraceSearch: React.FC = ({ scope = 'trace' }) => { const range = monitorCommonStore.useStore((s) => s.globalTimeSelectSpan.range); - const [traceSummary] = traceStore.useStore((s) => [s.traceSummary]); - const { getTraceSummary } = traceStore; - const [loading] = useLoading(traceStore, ['getTraceSummary']); + const [requestCompleted, serviceName] = serviceAnalyticsStore.useStore((s) => [s.requestCompleted, s.serviceName]); + const tenantId = routeInfoStore.useStore((s) => s.params.terminusKey); const { setIsShowTraceDetail } = monitorCommonStore.reducers; - const [{ traceId, filter, defaultQuery, query, layout, startTime }, updater, update] = useUpdate({ - filter: [], + const conditions = getTraceConditions.useData(); + const [{ traceId, startTime, query }, updater, update] = useUpdate({ traceId: undefined, - defaultQuery: {}, query: {}, - layout: [], - startTime: 0, + startTime: undefined, }); - const [routeQuery, params] = routeInfoStore.useStore((s) => [s.query, s.params]); - const globalVariable = React.useMemo(() => { - return { - startTime: range.startTimeMs, - endTime: range.endTimeMs, - terminusKey: params.terminusKey, - durationLeft: query.durationMin ? `trace_duration::field>'${query.durationMin}'` : undefined, - durationRight: query.durationMax ? `trace_duration::field<'${query.durationMax}'` : undefined, - serviceName: query.serviceName ? `service_names::field='${query.serviceName}'` : undefined, - traceId: query.traceID ? `trace_id::tag='${query.traceID}'` : undefined, - rpcMethod: query.rpcMethod ? `rpc_methods::field='${query.rpcMethod}'` : undefined, - httpPath: query.httpPath ? `http_paths::field='${query.httpPath}'` : undefined, - statusSuccess: query.status === 'trace_success' ? `errors_sum::field='0'` : undefined, - statusError: query.status === 'trace_error' ? `errors_sum::field>'0'` : undefined, - }; - }, [ - params.terminusKey, - range, - query.rpcMethod, - query.durationMin, - query.durationMax, - query.status, - query.traceID, - query.serviceName, - query.httpPath, - ]); - - useEffectOnce(() => { - getQueryConditions().then((res) => { - if (res.success) { - const [list, defaultValue] = convertData(res.data); - const handleDefaultValue = { - ...defaultValue, - traceStatus: routeQuery?.status || defaultValue.traceStatus, - }; - update({ - defaultQuery: { - ...handleDefaultValue, + React.useEffect(() => { + getTraceConditions.fetch(); + }, []); + const [filter, defaultQuery] = React.useMemo<[ICondition[], Record]>(() => { + const list: ICondition[] = []; + const defaultValue: Record = {}; + if (conditions) { + const { others, sort, ...rest } = conditions; + const fixConditions = Object.keys(rest); + fixConditions.forEach((key, index) => { + const option = conditions[key]; + defaultValue[key] = option?.[0]?.value; + list.push({ + type: 'select', + fixed: true, + showIndex: index + 2, + key, + label: name[key], + options: option.map((t: SERVICE_ANALYTICS.IConditionItem) => ({ + value: t.value, + label: t.displayName, + icon: '', + })), + customProps: { + mode: 'single', }, - query: { - ...handleDefaultValue, - }, - filter: [...initialFilter, ...list], }); - getData({ - startTime: range.startTimeMs, - endTime: range.endTimeMs, - status: defaultValue.traceStatus, - ...handleDefaultValue, + }); + others + ?.filter((t) => (scope === 'serviceMonitor' ? t.paramKey !== 'serviceName' : true)) + .forEach(({ paramKey, displayName }) => { + list.push({ + type: 'input', + showIndex: 0, + fixed: false, + placeholder: i18n.t('please enter {name}', { name: displayName }), + key: paramKey, + label: displayName, + customProps: {}, + }); }); - } - }); - getDashboard({ type: 'trace_count' }).then(({ success, data }) => { - if (success) { - updater.layout(data?.viewConfig); - } - }); - }); - - useUpdateEffect(() => { - getData({ - ...query, - startTime: range.startTimeMs, - endTime: range.endTimeMs, - }); - }, [query, range]); - - const getData = React.useCallback( - debounce((obj: Omit) => { - getTraceSummary(obj); - }, 500), - [], - ); - - const handleSearch = (query: Partial) => { - const { duration, traceStatus, ...rest } = query; + } + updater.query(defaultValue); + return [ + [ + { + label: i18n.t('msp:duration'), + key: 'duration', + showIndex: 1, + fixed: true, + getComp: (props) => { + return ; + }, + }, + ...list, + ] as ICondition[], + defaultValue, + ]; + }, [conditions]); + + const handleSearch = (data: { + [key: string]: string | Array; + duration: Array; + traceStatus: string; + }) => { + const { duration, traceStatus, ...rest } = data; const durationMin = transformDuration(duration?.[0]); const durationMax = transformDuration(duration?.[1]); let durations = {}; @@ -216,71 +158,65 @@ const TraceSearch = () => { }); }; - const handleCheckTraceDetail = (id: string, time: number) => { - updater.traceId(id); - updater.startTime(time); - setIsShowTraceDetail(true); - }; - - const columns: Array> = [ - { - title: i18n.t('msp:trace id'), - dataIndex: 'id', - render: (id: string) => {id}, - }, - { - title: i18n.t('msp:duration'), - dataIndex: 'duration', - width: 240, - sorter: { - compare: (a: MS_MONITOR.ITraceSummary, b: MS_MONITOR.ITraceSummary) => a.duration - b.duration, - }, - render: (duration: number) => getFormatter('TIME', 'ns').format(duration), - }, - { - title: i18n.t('msp:start time'), - dataIndex: 'startTime', - width: 200, - render: (time: number) => moment(time).format('YYYY-MM-DD HH:mm:ss'), - }, - { - title: i18n.t('service'), - dataIndex: 'services', - width: 240, - render: (services: string[]) => ({ label: service }))} />, - }, - ]; - + const params = React.useMemo(() => { + return { + tenantId, + startTime: range.startTimeMs, + endTime: range.endTimeMs, + ...query, + serviceName: scope === 'serviceMonitor' ? serviceName : query.serviceName, + traceId: query.traceID, + }; + }, [tenantId, range, query, scope, serviceName]); + if (!serviceName && requestCompleted && scope == 'serviceMonitor') { + return ; + } return ( - <> -
-
- {filter.length > 0 ? ( - - ) : null} -
+
+
+ {filter.length > 1 ? ( + + ) : null}
-
- -
- { - return { - onClick: () => handleCheckTraceDetail(record.id, record.startTime), - }; - }} - scroll={{ x: 1100 }} - onChange={() => { - getData({ ...query, startTime: range.startTimeMs, endTime: range.endTimeMs }); - }} - /> + {tenantId ? ( + { + update({ + traceId: data.traceId.data.text, + startTime: moment(data.traceStartTime.data.text).valueOf(), + }); + setIsShowTraceDetail(true); + }, + }, + }, + reqDistribution: { + props: { + style: { + width: '100%', + height: '170px', + minHeight: 0, + }, + }, + }, + }} + /> + ) : null} - + ); }; diff --git a/shell/app/modules/msp/monitor/trace-insight/services/trace-querier.ts b/shell/app/modules/msp/monitor/trace-insight/services/trace-querier.ts index 83ba707aa8..3a0c298672 100644 --- a/shell/app/modules/msp/monitor/trace-insight/services/trace-querier.ts +++ b/shell/app/modules/msp/monitor/trace-insight/services/trace-querier.ts @@ -12,7 +12,7 @@ // along with this program. If not, see . import agent from 'agent'; -import { RES_BODY } from 'core/service'; +import { apiCreator } from 'core/service'; export const requestTrace = (data?: MONITOR_TRACE.ITraceRequestBody): { requestId: string } => { return agent @@ -83,6 +83,10 @@ export const getSpanDetailContent = ({ span, visible }: { span: any; visible: bo }; }; -export const getQueryConditions = (): Promise> => { - return agent.get('/api/msp/apm/trace/conditions').then((response: any) => response.body); +const apis = { + getTraceConditions: { + api: '/api/msp/apm/trace/conditions', + }, }; + +export const getTraceConditions = apiCreator<() => MONITOR_TRACE.TraceConditions>(apis.getTraceConditions); diff --git a/shell/app/modules/msp/services/service-analytics.ts b/shell/app/modules/msp/services/service-analytics.ts index 89e07cfabf..0457251fe8 100644 --- a/shell/app/modules/msp/services/service-analytics.ts +++ b/shell/app/modules/msp/services/service-analytics.ts @@ -18,7 +18,6 @@ const apis = { api: '/api/apm/topology/services', }, }; - export const getServiceList = apiCreator<(p: SERVICE_ANALYTICS.IServiceListQuery) => SERVICE_ANALYTICS.ServiceList>( apis.getService, );