From a4eebfbff5fbb4d2b3ab3e5e4e489ca479213599 Mon Sep 17 00:00:00 2001 From: SagarRajput-7 Date: Mon, 27 Jan 2025 23:16:31 +0530 Subject: [PATCH 1/7] feat: added right panel graphs in overview page --- frontend/src/AppRoutes/routes.ts | 4 +- .../CeleryOverviewTable.tsx | 8 +- frontend/src/constants/routes.ts | 2 +- frontend/src/container/AppLayout/index.tsx | 2 +- frontend/src/container/SideNav/config.ts | 2 +- .../TopNav/DateTimeSelectionV2/config.ts | 2 +- .../Celery/CeleryOverview/CeleryOverview.tsx | 27 +- .../CeleryOverviewDetails.styles.scss | 29 + .../CeleryOverviewDetails.tsx | 64 ++ .../OverviewRightPanelGraph.tsx | 88 ++ .../ValueInfo.styles.scss | 63 ++ .../CeleryOverviewDetail/ValueInfo.tsx | 161 +++ .../CeleryOverviewGraphUtils.ts | 982 ++++++++++++++++++ .../CeleryOverview/CeleryOverviewUtils.ts | 24 + .../MessagingQueuesMainPage.tsx | 6 +- frontend/src/utils/permission/index.ts | 2 +- 16 files changed, 1451 insertions(+), 15 deletions(-) create mode 100644 frontend/src/pages/Celery/CeleryOverview/CeleryOverviewDetail/CeleryOverviewDetails.styles.scss create mode 100644 frontend/src/pages/Celery/CeleryOverview/CeleryOverviewDetail/CeleryOverviewDetails.tsx create mode 100644 frontend/src/pages/Celery/CeleryOverview/CeleryOverviewDetail/OverviewRightPanelGraph.tsx create mode 100644 frontend/src/pages/Celery/CeleryOverview/CeleryOverviewDetail/ValueInfo.styles.scss create mode 100644 frontend/src/pages/Celery/CeleryOverview/CeleryOverviewDetail/ValueInfo.tsx create mode 100644 frontend/src/pages/Celery/CeleryOverview/CeleryOverviewGraphUtils.ts create mode 100644 frontend/src/pages/Celery/CeleryOverview/CeleryOverviewUtils.ts diff --git a/frontend/src/AppRoutes/routes.ts b/frontend/src/AppRoutes/routes.ts index a93fe6263f..bee46680c0 100644 --- a/frontend/src/AppRoutes/routes.ts +++ b/frontend/src/AppRoutes/routes.ts @@ -409,10 +409,10 @@ const routes: AppRoutes[] = [ isPrivate: true, }, { - path: ROUTES.MESSAGING_QUEUES_CELERY_OVERVIEW, + path: ROUTES.MESSAGING_QUEUES_OVERVIEW, exact: true, component: MessagingQueues, - key: 'MESSAGING_QUEUES_CELERY_OVERVIEW', + key: 'MESSAGING_QUEUES_OVERVIEW', isPrivate: true, }, { diff --git a/frontend/src/components/CeleryOverview/CeleryOverviewTable/CeleryOverviewTable.tsx b/frontend/src/components/CeleryOverview/CeleryOverviewTable/CeleryOverviewTable.tsx index ed2ed776be..35ed91468e 100644 --- a/frontend/src/components/CeleryOverview/CeleryOverviewTable/CeleryOverviewTable.tsx +++ b/frontend/src/components/CeleryOverview/CeleryOverviewTable/CeleryOverviewTable.tsx @@ -260,7 +260,11 @@ function makeFilters(urlQuery: URLSearchParams): Filter[] { .filter((filter): filter is Filter => filter !== null); } -export default function CeleryOverviewTable(): JSX.Element { +export default function CeleryOverviewTable({ + onRowClick, +}: { + onRowClick: (record: RowData) => void; +}): JSX.Element { const [tableData, setTableData] = useState([]); const { minTime, maxTime } = useSelector( @@ -316,7 +320,7 @@ export default function CeleryOverviewTable(): JSX.Element { ); const handleRowClick = (record: RowData): void => { - console.log(record); + onRowClick(record); }; return ( diff --git a/frontend/src/constants/routes.ts b/frontend/src/constants/routes.ts index 5818c82ce2..b90cd9b916 100644 --- a/frontend/src/constants/routes.ts +++ b/frontend/src/constants/routes.ts @@ -64,7 +64,7 @@ const ROUTES = { INFRASTRUCTURE_MONITORING_HOSTS: '/infrastructure-monitoring/hosts', INFRASTRUCTURE_MONITORING_KUBERNETES: '/infrastructure-monitoring/kubernetes', MESSAGING_QUEUES_CELERY_TASK: '/messaging-queues/celery-task', - MESSAGING_QUEUES_CELERY_OVERVIEW: '/messaging-queues/celery-overview', + MESSAGING_QUEUES_OVERVIEW: '/messaging-queues/overview', } as const; export default ROUTES; diff --git a/frontend/src/container/AppLayout/index.tsx b/frontend/src/container/AppLayout/index.tsx index 24d9c2b968..1b8ba43ac5 100644 --- a/frontend/src/container/AppLayout/index.tsx +++ b/frontend/src/container/AppLayout/index.tsx @@ -290,7 +290,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element { routeKey === 'MESSAGING_QUEUES' || routeKey === 'MESSAGING_QUEUES_DETAIL' || routeKey === 'MESSAGING_QUEUES_CELERY_TASK' || - routeKey === 'MESSAGING_QUEUES_CELERY_OVERVIEW'; + routeKey === 'MESSAGING_QUEUES_OVERVIEW'; const isDashboardListView = (): boolean => routeKey === 'ALL_DASHBOARD'; const isAlertHistory = (): boolean => routeKey === 'ALERT_HISTORY'; diff --git a/frontend/src/container/SideNav/config.ts b/frontend/src/container/SideNav/config.ts index 00c32604b2..875fb31b6b 100644 --- a/frontend/src/container/SideNav/config.ts +++ b/frontend/src/container/SideNav/config.ts @@ -52,7 +52,7 @@ export const routeConfig: Record = { [ROUTES.MESSAGING_QUEUES]: [QueryParams.resourceAttributes], [ROUTES.MESSAGING_QUEUES_DETAIL]: [QueryParams.resourceAttributes], [ROUTES.MESSAGING_QUEUES_CELERY_TASK]: [QueryParams.resourceAttributes], - [ROUTES.MESSAGING_QUEUES_CELERY_OVERVIEW]: [QueryParams.resourceAttributes], + [ROUTES.MESSAGING_QUEUES_OVERVIEW]: [QueryParams.resourceAttributes], [ROUTES.INFRASTRUCTURE_MONITORING_HOSTS]: [QueryParams.resourceAttributes], [ROUTES.INFRASTRUCTURE_MONITORING_KUBERNETES]: [ QueryParams.resourceAttributes, diff --git a/frontend/src/container/TopNav/DateTimeSelectionV2/config.ts b/frontend/src/container/TopNav/DateTimeSelectionV2/config.ts index 18a7b57ad8..f844d7f07d 100644 --- a/frontend/src/container/TopNav/DateTimeSelectionV2/config.ts +++ b/frontend/src/container/TopNav/DateTimeSelectionV2/config.ts @@ -215,7 +215,7 @@ export const routesToSkip = [ ROUTES.MESSAGING_QUEUES, ROUTES.MESSAGING_QUEUES_DETAIL, ROUTES.MESSAGING_QUEUES_CELERY_TASK, - ROUTES.MESSAGING_QUEUES_CELERY_OVERVIEW, + ROUTES.MESSAGING_QUEUES_OVERVIEW, ROUTES.INFRASTRUCTURE_MONITORING_HOSTS, ROUTES.SOMETHING_WENT_WRONG, ROUTES.INFRASTRUCTURE_MONITORING_KUBERNETES, diff --git a/frontend/src/pages/Celery/CeleryOverview/CeleryOverview.tsx b/frontend/src/pages/Celery/CeleryOverview/CeleryOverview.tsx index 134c897bc5..9d3728516e 100644 --- a/frontend/src/pages/Celery/CeleryOverview/CeleryOverview.tsx +++ b/frontend/src/pages/Celery/CeleryOverview/CeleryOverview.tsx @@ -1,20 +1,41 @@ import './CeleryOverview.styles.scss'; import CeleryOverviewConfigOptions from 'components/CeleryOverview/CeleryOverviewConfigOptions/CeleryOverviewConfigOptions'; -import CeleryOverviewTable from 'components/CeleryOverview/CeleryOverviewTable/CeleryOverviewTable'; +import CeleryOverviewTable, { + RowData, +} from 'components/CeleryOverview/CeleryOverviewTable/CeleryOverviewTable'; import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2'; +import { useState } from 'react'; + +import CeleryOverviewDetails from './CeleryOverviewDetail/CeleryOverviewDetails'; export default function CeleryOverview(): JSX.Element { + const [details, setDetails] = useState(null); + + const onRowClick = (record: RowData): void => { + setDetails(record); + }; + return (
-

Celery Overview

+

+ Messaging Queue Overview +

- +
+ {details && ( + { + setDetails(null); + }} + /> + )}
); } diff --git a/frontend/src/pages/Celery/CeleryOverview/CeleryOverviewDetail/CeleryOverviewDetails.styles.scss b/frontend/src/pages/Celery/CeleryOverview/CeleryOverviewDetail/CeleryOverviewDetails.styles.scss new file mode 100644 index 0000000000..81eed1a911 --- /dev/null +++ b/frontend/src/pages/Celery/CeleryOverview/CeleryOverviewDetail/CeleryOverviewDetails.styles.scss @@ -0,0 +1,29 @@ +.celery-overview-detail-container { + display: flex; + flex-direction: column; + gap: 16px; + padding: 16px 8px; +} + +.overview-right-panel-graph-card { + border-radius: 8px; + border: 1px solid #262626 !important; + background: #0a0a0a; + box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.1), + 0px 1px 2px -1px rgba(0, 0, 0, 0.1); + + .request-rate-card, + .error-rate-card, + .avg-latency-card { + height: 320px !important; + padding: 10px; + width: 100%; + box-sizing: border-box; + border-bottom: 0.75px solid #262626; + margin-bottom: 12px; + + .widget-graph-container { + height: 260px; + } + } +} diff --git a/frontend/src/pages/Celery/CeleryOverview/CeleryOverviewDetail/CeleryOverviewDetails.tsx b/frontend/src/pages/Celery/CeleryOverview/CeleryOverviewDetail/CeleryOverviewDetails.tsx new file mode 100644 index 0000000000..f93d9a748b --- /dev/null +++ b/frontend/src/pages/Celery/CeleryOverview/CeleryOverviewDetail/CeleryOverviewDetails.tsx @@ -0,0 +1,64 @@ +import './CeleryOverviewDetails.styles.scss'; + +import { Color, Spacing } from '@signozhq/design-tokens'; +import { Divider, Drawer, Typography } from 'antd'; +import { RowData } from 'components/CeleryOverview/CeleryOverviewTable/CeleryOverviewTable'; +import { useIsDarkMode } from 'hooks/useDarkMode'; +import { X } from 'lucide-react'; + +import OverviewRightPanelGraph from './OverviewRightPanelGraph'; +import ValueInfo from './ValueInfo'; + +export default function CeleryOverviewDetails({ + details, + onClose, +}: { + details: RowData; + onClose: () => void; +}): JSX.Element { + const isDarkMode = useIsDarkMode(); + console.log(details); + + return ( + + {details.service_name} +
+ + {details.span_name} + + + + {details.messaging_system} + + + + {details.destination} + + + + {details.kind_string} + +
+ + } + placement="right" + onClose={onClose} + open={!!details} + style={{ + overscrollBehavior: 'contain', + background: isDarkMode ? Color.BG_INK_400 : Color.BG_VANILLA_100, + }} + className="celery-task-detail-drawer" + destroyOnClose + closeIcon={} + > +
+ + +
+
+ ); +} diff --git a/frontend/src/pages/Celery/CeleryOverview/CeleryOverviewDetail/OverviewRightPanelGraph.tsx b/frontend/src/pages/Celery/CeleryOverview/CeleryOverviewDetail/OverviewRightPanelGraph.tsx new file mode 100644 index 0000000000..2df0f4309c --- /dev/null +++ b/frontend/src/pages/Celery/CeleryOverview/CeleryOverviewDetail/OverviewRightPanelGraph.tsx @@ -0,0 +1,88 @@ +import { Card } from 'antd'; +import { QueryParams } from 'constants/query'; +import { ViewMenuAction } from 'container/GridCardLayout/config'; +import GridCard from 'container/GridCardLayout/GridCard'; +import useUrlQuery from 'hooks/useUrlQuery'; +import { useCallback, useMemo } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { useHistory, useLocation } from 'react-router-dom'; +import { UpdateTimeInterval } from 'store/actions'; +import { AppState } from 'store/reducers'; +import { GlobalReducer } from 'types/reducer/globalTime'; + +import { + celeryOverviewAvgLatencyGraphData, + celeryOverviewErrorRateGraphData, + celeryOverviewRequestRateGraphData, +} from '../CeleryOverviewGraphUtils'; + +export default function OverviewRightPanelGraph(): JSX.Element { + const history = useHistory(); + const { pathname } = useLocation(); + const dispatch = useDispatch(); + const urlQuery = useUrlQuery(); + + const onDragSelect = useCallback( + (start: number, end: number) => { + const startTimestamp = Math.trunc(start); + const endTimestamp = Math.trunc(end); + + urlQuery.set(QueryParams.startTime, startTimestamp.toString()); + urlQuery.set(QueryParams.endTime, endTimestamp.toString()); + const generatedUrl = `${pathname}?${urlQuery.toString()}`; + history.push(generatedUrl); + + if (startTimestamp !== endTimestamp) { + dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp])); + } + }, + [dispatch, history, pathname, urlQuery], + ); + + const { minTime, maxTime } = useSelector( + (state) => state.globalTime, + ); + + const requestRateWidget = useMemo( + () => celeryOverviewRequestRateGraphData(minTime, maxTime), + [minTime, maxTime], + ); + + const errorRateWidget = useMemo( + () => celeryOverviewErrorRateGraphData(minTime, maxTime), + [minTime, maxTime], + ); + + const avgLatencyWidget = useMemo( + () => celeryOverviewAvgLatencyGraphData(minTime, maxTime), + [minTime, maxTime], + ); + return ( + +
+ console.log(...args)} + /> +
+
+ console.log(...args)} + /> +
+
+ console.log(...args)} + /> +
+
+ ); +} diff --git a/frontend/src/pages/Celery/CeleryOverview/CeleryOverviewDetail/ValueInfo.styles.scss b/frontend/src/pages/Celery/CeleryOverview/CeleryOverviewDetail/ValueInfo.styles.scss new file mode 100644 index 0000000000..8d42c449d6 --- /dev/null +++ b/frontend/src/pages/Celery/CeleryOverview/CeleryOverviewDetail/ValueInfo.styles.scss @@ -0,0 +1,63 @@ +.value-info-card { + border-radius: 8px; + border: 1px solid #262626 !important; + background: #0a0a0a; + box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.1), + 0px 1px 2px -1px rgba(0, 0, 0, 0.1); + + .metric-column { + .metric-title { + color: #fafafa; + font-size: 14px; + font-weight: 500; + } + + .metric-value-container { + display: flex; + align-items: baseline; + gap: 6px; + } + + .metric-value { + font-size: 24px; + font-weight: 400; + + &.red { + color: #f87171; + } + + &.green { + color: #4ade80; + } + + &.loading { + opacity: 0.5; + } + } + + .metric-unit { + color: #a3a3a3; + font-size: 14px; + } + + .metric-reference { + color: #a3a3a3; + font-size: 12px; + } + + .trace-button { + margin-top: 8px; + background: #262626; + border: none; + + &:hover { + background: #404040; + } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } + } + } +} diff --git a/frontend/src/pages/Celery/CeleryOverview/CeleryOverviewDetail/ValueInfo.tsx b/frontend/src/pages/Celery/CeleryOverview/CeleryOverviewDetail/ValueInfo.tsx new file mode 100644 index 0000000000..607ad07589 --- /dev/null +++ b/frontend/src/pages/Celery/CeleryOverview/CeleryOverviewDetail/ValueInfo.tsx @@ -0,0 +1,161 @@ +import './ValueInfo.styles.scss'; + +import { FileSearchOutlined } from '@ant-design/icons'; +import { Button, Card, Col, Row } from 'antd'; +import { ENTITY_VERSION_V4 } from 'constants/app'; +import { PANEL_TYPES } from 'constants/queryBuilder'; +import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults'; +import { useCallback, useMemo } from 'react'; +import { useQueries } from 'react-query'; +import { useSelector } from 'react-redux'; +import { AppState } from 'store/reducers'; +import { SuccessResponse } from 'types/api'; +import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; +import { GlobalReducer } from 'types/reducer/globalTime'; + +import { + celeryOverviewAvgLatencyWidgetData, + celeryOverviewErrorRateWidgetData, + celeryOverviewRequestRateWidgetData, +} from '../CeleryOverviewGraphUtils'; +import { getQueryPayloadFromWidgetsData } from '../CeleryOverviewUtils'; + +export default function ValueInfo(): JSX.Element { + const { maxTime, minTime } = useSelector( + (state) => state.globalTime, + ); + + // get data from api + const queryPayloads = useMemo( + () => + getQueryPayloadFromWidgetsData({ + start: Math.floor(minTime / 1000000000), + end: Math.floor(maxTime / 1000000000), + widgetsData: [ + celeryOverviewRequestRateWidgetData(), + celeryOverviewErrorRateWidgetData(), + celeryOverviewAvgLatencyWidgetData(), + ], + panelType: PANEL_TYPES.VALUE, + }), + [minTime, maxTime], + ); + + const queries = useQueries( + queryPayloads.map((payload) => ({ + queryKey: [ + 'overview-detail', + payload, + ENTITY_VERSION_V4, + 'overview-right-panel', + ], + queryFn: (): Promise> => + GetMetricQueryRange(payload, ENTITY_VERSION_V4), + enabled: !!payload, + })), + ); + + const getValues = useCallback( + () => + queries.map((query) => { + const value = parseFloat( + query.data?.payload?.data?.newResult?.data?.result?.[0]?.series?.[0] + ?.values?.[0]?.value || 'NaN', + ); + return Number.isNaN(value) ? 'NaN' : value.toFixed(2); + }), + [queries], + ); + + const isLoading = queries.some((query) => query.isLoading); + const [requestRate, errorRate, avgLatency] = useMemo( + () => (isLoading ? ['0', '0', '0'] : getValues()), + [isLoading, getValues], + ); + + const getColorBasedOnValue = (value: string): string => { + const numericValue = parseFloat(value); + if (value === 'NaN') return 'gray'; + if (numericValue < 3) return 'green'; + if (numericValue < 8) return 'yellow'; + return 'red'; + }; + + const getColorForErrorRate = (value: string): string => { + const numericValue = parseFloat(value); + if (value === 'NaN') return 'gray'; + if (numericValue < 60) return 'green'; + if (numericValue < 90) return 'yellow'; + return 'red'; + }; + + return ( + + + +
Request Rate
+
+
+ {requestRate === 'NaN' ? '0' : requestRate} +
+
req/s
+
+ + + +
Error Rate
+
+
+ {errorRate === 'NaN' ? '0' : errorRate} +
+
%
+
+ + + +
Average Latency
+
+
+ {avgLatency === 'NaN' ? '0' : avgLatency} +
+
ms
+
+ + +
+
+ ); +} diff --git a/frontend/src/pages/Celery/CeleryOverview/CeleryOverviewGraphUtils.ts b/frontend/src/pages/Celery/CeleryOverview/CeleryOverviewGraphUtils.ts new file mode 100644 index 0000000000..076497c26f --- /dev/null +++ b/frontend/src/pages/Celery/CeleryOverview/CeleryOverviewGraphUtils.ts @@ -0,0 +1,982 @@ +/* eslint-disable sonarjs/no-duplicate-string */ +import { getStepInterval } from 'components/CeleryTask/CeleryTaskGraph/CeleryTaskGraphUtils'; +import { PANEL_TYPES } from 'constants/queryBuilder'; +import { getWidgetQueryBuilder } from 'container/MetricsApplication/MetricsApplication.factory'; +import { getWidgetQuery } from 'pages/MessagingQueues/MQDetails/MetricPage/MetricPageUtil'; +import { Widgets } from 'types/api/dashboard/getAll'; +import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { DataSource } from 'types/common/queryBuilder'; + +export const celeryOverviewRequestRateWidgetData = (): Widgets => + getWidgetQueryBuilder( + getWidgetQuery({ + title: 'Request Rate', + description: 'Represents request rate of the service', + queryData: [ + { + aggregateAttribute: { + dataType: DataTypes.EMPTY, + id: '------false', + isColumn: false, + isJSON: false, + key: '', + type: '', + }, + aggregateOperator: 'rate', + dataSource: DataSource.TRACES, + disabled: false, + expression: 'A', + filters: { + items: [ + { + id: '66b09400', + key: { + dataType: DataTypes.String, + id: 'service.name--string--resource--true', + isColumn: true, + isJSON: false, + key: 'service.name', + type: 'resource', + }, + op: '=', + value: 'consumer-svc-2', + }, + { + id: '7c62322c', + key: { + dataType: DataTypes.String, + id: 'name--string----true', + isColumn: true, + isJSON: false, + key: 'name', + type: '', + }, + op: '=', + value: 'topic1 process', + }, + { + id: 'f428d54a', + key: { + dataType: DataTypes.String, + id: 'messaging.destination.name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'messaging.destination.name', + type: 'tag', + }, + op: '=', + value: 'topic1', + }, + { + id: '15202376', + key: { + dataType: DataTypes.String, + id: 'kind_string--string----true', + isColumn: true, + isJSON: false, + key: 'kind_string', + type: '', + }, + op: '=', + value: 'Consumer', + }, + { + id: 'ba4fdb16', + key: { + dataType: DataTypes.String, + id: 'messaging.system--string--tag--true', + isColumn: true, + isJSON: false, + key: 'messaging.system', + type: 'tag', + }, + op: '=', + value: 'kafka', + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [], + having: [], + legend: '', + limit: null, + orderBy: [], + queryName: 'A', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'rate', + }, + ], + panelTypes: PANEL_TYPES.VALUE, + }), + ); + +export const celeryOverviewErrorRateWidgetData = (): Widgets => + getWidgetQueryBuilder( + getWidgetQuery({ + title: 'Error Rate', + description: 'Represents Error rate of the service', + queryData: [ + { + aggregateAttribute: { + dataType: DataTypes.EMPTY, + id: '------false', + isColumn: false, + isJSON: false, + key: '', + type: '', + }, + aggregateOperator: 'rate', + dataSource: DataSource.TRACES, + disabled: false, + expression: 'A', + filters: { + items: [ + { + id: '0a44dbf3', + key: { + dataType: DataTypes.String, + id: 'service.name--string--resource--true', + isColumn: true, + isJSON: false, + key: 'service.name', + type: 'resource', + }, + op: '=', + value: 'worker1', + }, + { + id: '31b32f97', + key: { + dataType: DataTypes.String, + id: 'name--string----true', + isColumn: true, + isJSON: false, + key: 'name', + type: '', + }, + op: '=', + value: 'run/tasks.tasks.add', + }, + { + id: '7f20ee62', + key: { + dataType: DataTypes.String, + id: 'messaging.destination--string--tag--false', + isColumn: false, + isJSON: false, + key: 'messaging.destination', + type: 'tag', + }, + op: '=', + value: 'queue1', + }, + { + id: '76b02773', + key: { + dataType: DataTypes.String, + id: 'kind_string--string----true', + isColumn: true, + isJSON: false, + key: 'kind_string', + type: '', + }, + op: '=', + value: 'Consumer', + }, + { + id: 'a2dc0a85', + key: { + dataType: DataTypes.bool, + id: 'has_error--bool----true', + isColumn: true, + isJSON: false, + key: 'has_error', + type: '', + }, + op: '=', + value: 'true', + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [], + having: [], + legend: '', + limit: null, + orderBy: [], + queryName: 'A', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'rate', + }, + ], + panelTypes: PANEL_TYPES.VALUE, + }), + ); + +export const celeryOverviewAvgLatencyWidgetData = (): Widgets => + getWidgetQueryBuilder( + getWidgetQuery({ + title: 'Avg Latency', + description: 'Represents Avg Latency of the service', + queryData: [ + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'duration_nano--float64----true', + isColumn: true, + isJSON: false, + key: 'duration_nano', + type: '', + }, + aggregateOperator: 'p95', + dataSource: DataSource.TRACES, + disabled: false, + expression: 'A', + filters: { + items: [ + { + id: 'a2c0bfe2', + key: { + dataType: DataTypes.String, + id: 'service.name--string--resource--true', + isColumn: true, + isJSON: false, + key: 'service.name', + type: 'resource', + }, + op: '=', + value: 'worker1', + }, + { + id: '02e70326', + key: { + dataType: DataTypes.String, + id: 'name--string----true', + isColumn: true, + isJSON: false, + key: 'name', + type: '', + }, + op: '=', + value: 'run/tasks.tasks.add', + }, + { + id: 'ac15cbac', + key: { + dataType: DataTypes.String, + id: 'messaging.destination--string--tag--false', + isColumn: false, + isJSON: false, + key: 'messaging.destination', + type: 'tag', + }, + op: '=', + value: 'queue1', + }, + { + id: '59a7727a', + key: { + dataType: DataTypes.String, + id: 'kind_string--string----true', + isColumn: true, + isJSON: false, + key: 'kind_string', + type: '', + }, + op: '=', + value: 'Consumer', + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [], + having: [], + legend: '{{messaging.destination.partition.id}}', + limit: null, + orderBy: [], + queryName: 'A', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'p95', + }, + ], + panelTypes: PANEL_TYPES.VALUE, + }), + ); + +export const celeryOverviewRequestRateGraphData = ( + startTime: number, + endTime: number, +): Widgets => + getWidgetQueryBuilder( + getWidgetQuery({ + title: 'Request rate', + description: 'Represents Request rate of the service', + queryData: [ + { + aggregateAttribute: { + dataType: DataTypes.EMPTY, + id: '------false', + isColumn: false, + isJSON: false, + key: '', + type: '', + }, + aggregateOperator: 'rate', + dataSource: DataSource.TRACES, + disabled: false, + expression: 'A', + filters: { + items: [ + { + id: 'bfe7f9f6', + key: { + dataType: DataTypes.String, + id: 'service.name--string--resource--true', + isColumn: true, + isJSON: false, + key: 'service.name', + type: 'resource', + }, + op: '=', + value: 'consumer-svc-2', + }, + { + id: '0ae75c5b', + key: { + dataType: DataTypes.String, + id: 'messaging.destination.name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'messaging.destination.name', + type: 'tag', + }, + op: '=', + value: 'topic1', + }, + { + id: 'e694f23e', + key: { + dataType: DataTypes.String, + id: 'name--string----true', + isColumn: true, + isJSON: false, + key: 'name', + type: '', + }, + op: '=', + value: 'topic1 process', + }, + { + id: '21fffed6', + key: { + dataType: DataTypes.String, + id: 'kind_string--string----true', + isColumn: true, + isJSON: false, + key: 'kind_string', + type: '', + }, + op: '=', + value: 'Consumer', + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [ + { + dataType: DataTypes.String, + id: 'messaging.destination.partition.id--string--tag--false', + isColumn: false, + isJSON: false, + key: 'messaging.destination.partition.id', + type: 'tag', + }, + ], + having: [], + legend: '{{messaging.destination.partition.id}}', + limit: null, + orderBy: [], + queryName: 'A', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: getStepInterval(startTime, endTime), + timeAggregation: 'rate', + }, + ], + panelTypes: PANEL_TYPES.BAR, + }), + ); + +export const celeryOverviewErrorRateGraphData = ( + startTime: number, + endTime: number, +): Widgets => + getWidgetQueryBuilder( + getWidgetQuery({ + title: 'Error Rate', + description: 'Represents Error rate of the service', + queryData: [ + { + aggregateAttribute: { + dataType: DataTypes.EMPTY, + id: '------false', + isColumn: false, + isJSON: false, + key: '', + type: '', + }, + aggregateOperator: 'rate', + dataSource: DataSource.TRACES, + disabled: false, + expression: 'A', + filters: { + items: [ + { + id: '1a77c475', + key: { + dataType: DataTypes.String, + id: 'service.name--string--resource--true', + isColumn: true, + isJSON: false, + key: 'service.name', + type: 'resource', + }, + op: '=', + value: 'consumer-svc-2', + }, + { + id: '70630e97', + key: { + dataType: DataTypes.String, + id: 'name--string----true', + isColumn: true, + isJSON: false, + key: 'name', + type: '', + }, + op: '=', + value: 'topic1 process', + }, + { + id: 'f41ad482', + key: { + dataType: DataTypes.EMPTY, + isColumn: false, + isJSON: false, + key: 'messaging.destination.name', + type: '', + }, + op: '=', + value: 'topic1', + }, + { + id: '00637dde', + key: { + dataType: DataTypes.String, + id: 'kind_string--string----true', + isColumn: true, + isJSON: false, + key: 'kind_string', + type: '', + }, + op: '=', + value: 'Consumer', + }, + { + id: 'afa5fc10', + key: { + dataType: DataTypes.EMPTY, + isColumn: false, + isJSON: false, + key: 'messaging.system', + type: '', + }, + op: '=', + value: 'kafka', + }, + { + id: '9fdeebea', + key: { + dataType: DataTypes.bool, + id: 'has_error--bool----true', + isColumn: true, + isJSON: false, + key: 'has_error', + type: '', + }, + op: '=', + value: 'true', + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [ + { + dataType: DataTypes.String, + id: 'messaging.destination.partition.id--string--tag--false', + isColumn: false, + isJSON: false, + key: 'messaging.destination.partition.id', + type: 'tag', + }, + ], + having: [], + legend: '{{messaging.destination.partition.id}}', + limit: null, + orderBy: [], + queryName: 'A', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: getStepInterval(startTime, endTime), + timeAggregation: 'rate', + }, + { + aggregateAttribute: { + dataType: DataTypes.EMPTY, + id: '------false', + isColumn: false, + isJSON: false, + key: '', + type: '', + }, + aggregateOperator: 'rate', + dataSource: DataSource.TRACES, + disabled: false, + expression: 'B', + filters: { + items: [ + { + id: '93d85de1', + key: { + dataType: DataTypes.String, + id: 'service.name--string--resource--true', + isColumn: true, + isJSON: false, + key: 'service.name', + type: 'resource', + }, + op: '=', + value: 'consumer-svc-2', + }, + { + id: '57632b3f', + key: { + dataType: DataTypes.String, + id: 'name--string----true', + isColumn: true, + isJSON: false, + key: 'name', + type: '', + }, + op: '=', + value: 'topic1 process', + }, + { + id: '8d7f5e93', + key: { + dataType: DataTypes.EMPTY, + isColumn: false, + isJSON: false, + key: 'messaging.destination.name', + type: '', + }, + op: '=', + value: 'topic1', + }, + { + id: '64636499', + key: { + dataType: DataTypes.String, + id: 'kind_string--string----true', + isColumn: true, + isJSON: false, + key: 'kind_string', + type: '', + }, + op: '=', + value: 'Consumer', + }, + { + id: '22d01c6b', + key: { + dataType: DataTypes.EMPTY, + isColumn: false, + isJSON: false, + key: 'messaging.system', + type: '', + }, + op: '=', + value: 'kafka', + }, + { + id: 'd13a99c4', + key: { + dataType: DataTypes.bool, + id: 'has_error--bool----true', + isColumn: true, + isJSON: false, + key: 'has_error', + type: '', + }, + op: '=', + value: 'false', + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [ + { + dataType: DataTypes.String, + id: 'messaging.destination.partition.id--string--tag--false', + isColumn: false, + isJSON: false, + key: 'messaging.destination.partition.id', + type: 'tag', + }, + ], + having: [], + legend: '{{messaging.destination.partition.id}}', + limit: null, + orderBy: [], + queryName: 'B', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: getStepInterval(startTime, endTime), + timeAggregation: 'rate', + }, + ], + panelTypes: PANEL_TYPES.BAR, + }), + ); + +export const celeryOverviewAvgLatencyGraphData = ( + startTime: number, + endTime: number, +): Widgets => + getWidgetQueryBuilder( + getWidgetQuery({ + title: 'Latency', + description: 'Represents Latency of the service', + queryData: [ + { + aggregateAttribute: { + dataType: DataTypes.ArrayFloat64, + id: 'duration_nano--float64----true', + isColumn: true, + isJSON: false, + key: 'duration_nano', + type: '', + }, + aggregateOperator: 'p90', + dataSource: DataSource.TRACES, + disabled: false, + expression: 'C', + filters: { + items: [ + { + id: 'fec96858', + key: { + dataType: DataTypes.String, + id: 'service.name--string--resource--true', + isColumn: true, + isJSON: false, + key: 'service.name', + type: 'resource', + }, + op: '=', + value: 'consumer-svc-2', + }, + { + id: '36330bd8', + key: { + dataType: DataTypes.String, + id: 'name--string----true', + isColumn: true, + isJSON: false, + key: 'name', + type: '', + }, + op: '=', + value: 'topic1 process', + }, + { + id: '061f3047', + key: { + dataType: DataTypes.EMPTY, + isColumn: false, + isJSON: false, + key: 'messaging.destination.name', + type: '', + }, + op: '=', + value: 'topic1', + }, + { + id: '9fd15439', + key: { + dataType: DataTypes.String, + id: 'kind_string--string----true', + isColumn: true, + isJSON: false, + key: 'kind_string', + type: '', + }, + op: '=', + value: 'Consumer', + }, + { + id: '0f1af7b9', + key: { + dataType: DataTypes.EMPTY, + isColumn: false, + isJSON: false, + key: 'messaging.system', + type: '', + }, + op: '=', + value: 'kafka', + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [ + { + dataType: DataTypes.String, + id: 'messaging.destination.partition.id--string--tag--false', + isColumn: false, + isJSON: false, + key: 'messaging.destination.partition.id', + type: 'tag', + }, + ], + having: [], + legend: '{{messaging.destination.partition.id}}', + limit: null, + orderBy: [], + queryName: 'C', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: getStepInterval(startTime, endTime), + timeAggregation: 'p90', + }, + { + aggregateAttribute: { + dataType: DataTypes.ArrayFloat64, + id: 'duration_nano--float64----true', + isColumn: true, + isJSON: false, + key: 'duration_nano', + type: '', + }, + aggregateOperator: 'p95', + dataSource: DataSource.TRACES, + disabled: false, + expression: 'D', + filters: { + items: [ + { + id: 'f0143e91', + key: { + dataType: DataTypes.String, + id: 'service.name--string--resource--true', + isColumn: true, + isJSON: false, + key: 'service.name', + type: 'resource', + }, + op: '=', + value: 'consumer-svc-2', + }, + { + id: '0764eb8e', + key: { + dataType: DataTypes.String, + id: 'name--string----true', + isColumn: true, + isJSON: false, + key: 'name', + type: '', + }, + op: '=', + value: 'topic1 process', + }, + { + id: 'e6b592ec', + key: { + dataType: DataTypes.EMPTY, + isColumn: false, + isJSON: false, + key: 'messaging.destination.name', + type: '', + }, + op: '=', + value: 'topic1', + }, + { + id: '4ca3b0b4', + key: { + dataType: DataTypes.String, + id: 'kind_string--string----true', + isColumn: true, + isJSON: false, + key: 'kind_string', + type: '', + }, + op: '=', + value: 'Consumer', + }, + { + id: 'd81770c9', + key: { + dataType: DataTypes.EMPTY, + isColumn: false, + isJSON: false, + key: 'messaging.system', + type: '', + }, + op: '=', + value: 'kafka', + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [ + { + dataType: DataTypes.String, + id: 'messaging.destination.partition.id--string--tag--false', + isColumn: false, + isJSON: false, + key: 'messaging.destination.partition.id', + type: 'tag', + }, + ], + having: [], + legend: '{{messaging.destination.partition.id}}', + limit: null, + orderBy: [], + queryName: 'D', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: getStepInterval(startTime, endTime), + timeAggregation: 'p95', + }, + { + aggregateAttribute: { + dataType: DataTypes.ArrayFloat64, + id: 'duration_nano--float64----true', + isColumn: true, + isJSON: false, + key: 'duration_nano', + type: '', + }, + aggregateOperator: 'p99', + dataSource: DataSource.TRACES, + disabled: false, + expression: 'E', + filters: { + items: [ + { + id: 'a8f0c5e5', + key: { + dataType: DataTypes.String, + id: 'service.name--string--resource--true', + isColumn: true, + isJSON: false, + key: 'service.name', + type: 'resource', + }, + op: '=', + value: 'consumer-svc-2', + }, + { + id: '60c81993', + key: { + dataType: DataTypes.String, + id: 'name--string----true', + isColumn: true, + isJSON: false, + key: 'name', + type: '', + }, + op: '=', + value: 'topic1 process', + }, + { + id: '7f054fb2', + key: { + dataType: DataTypes.EMPTY, + isColumn: false, + isJSON: false, + key: 'messaging.destination.name', + type: '', + }, + op: '=', + value: 'topic1', + }, + { + id: 'e825e894', + key: { + dataType: DataTypes.String, + id: 'kind_string--string----true', + isColumn: true, + isJSON: false, + key: 'kind_string', + type: '', + }, + op: '=', + value: 'Consumer', + }, + { + id: '9ba10a06', + key: { + dataType: DataTypes.EMPTY, + isColumn: false, + isJSON: false, + key: 'messaging.system', + type: '', + }, + op: '=', + value: 'kafka', + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [ + { + dataType: DataTypes.String, + id: 'messaging.destination.partition.id--string--tag--false', + isColumn: false, + isJSON: false, + key: 'messaging.destination.partition.id', + type: 'tag', + }, + ], + having: [], + legend: '{{messaging.destination.partition.id}}', + limit: null, + orderBy: [], + queryName: 'E', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: getStepInterval(startTime, endTime), + timeAggregation: 'p99', + }, + ], + panelTypes: PANEL_TYPES.TIME_SERIES, + }), + ); diff --git a/frontend/src/pages/Celery/CeleryOverview/CeleryOverviewUtils.ts b/frontend/src/pages/Celery/CeleryOverview/CeleryOverviewUtils.ts new file mode 100644 index 0000000000..884b4b79ab --- /dev/null +++ b/frontend/src/pages/Celery/CeleryOverview/CeleryOverviewUtils.ts @@ -0,0 +1,24 @@ +import { PANEL_TYPES } from 'constants/queryBuilder'; +import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults'; +import { Widgets } from 'types/api/dashboard/getAll'; + +export const getQueryPayloadFromWidgetsData = ({ + start, + end, + widgetsData, + panelType, +}: { + start: number; + end: number; + widgetsData: Widgets[]; + panelType: PANEL_TYPES; +}): GetQueryResultsProps[] => + widgetsData.map((widget) => ({ + start, + end, + graphType: panelType, + query: widget.query, + selectedTime: 'GLOBAL_TIME', + formatForWeb: false, + variables: {}, + })); diff --git a/frontend/src/pages/MessagingQueues/MessagingQueuesMainPage.tsx b/frontend/src/pages/MessagingQueues/MessagingQueuesMainPage.tsx index 15fee0bb78..a3cd2bc6cb 100644 --- a/frontend/src/pages/MessagingQueues/MessagingQueuesMainPage.tsx +++ b/frontend/src/pages/MessagingQueues/MessagingQueuesMainPage.tsx @@ -37,11 +37,11 @@ export const Overview: TabRoutes = { Component: CeleryOverview, name: (
- Celery Overview + Overview
), - route: ROUTES.MESSAGING_QUEUES_CELERY_OVERVIEW, - key: ROUTES.MESSAGING_QUEUES_CELERY_OVERVIEW, + route: ROUTES.MESSAGING_QUEUES_OVERVIEW, + key: ROUTES.MESSAGING_QUEUES_OVERVIEW, }; export default function MessagingQueuesMainPage(): JSX.Element { diff --git a/frontend/src/utils/permission/index.ts b/frontend/src/utils/permission/index.ts index dcfe2f2524..02096cae9d 100644 --- a/frontend/src/utils/permission/index.ts +++ b/frontend/src/utils/permission/index.ts @@ -109,5 +109,5 @@ export const routePermission: Record = { INFRASTRUCTURE_MONITORING_HOSTS: ['ADMIN', 'EDITOR', 'VIEWER'], INFRASTRUCTURE_MONITORING_KUBERNETES: ['ADMIN', 'EDITOR', 'VIEWER'], MESSAGING_QUEUES_CELERY_TASK: ['ADMIN', 'EDITOR', 'VIEWER'], - MESSAGING_QUEUES_CELERY_OVERVIEW: ['ADMIN', 'EDITOR', 'VIEWER'], + MESSAGING_QUEUES_OVERVIEW: ['ADMIN', 'EDITOR', 'VIEWER'], }; From 8864171955782ab33610c49907070f48c160395c Mon Sep 17 00:00:00 2001 From: SagarRajput-7 Date: Tue, 28 Jan 2025 06:46:10 +0530 Subject: [PATCH 2/7] feat: added right panel trace navigation --- .../CeleryTaskDetail/CeleryTaskDetail.tsx | 57 +- .../CeleryTask/useNavigateToTraces.ts | 65 ++ .../container/MetricsApplication/Tabs/util.ts | 2 + .../CeleryOverviewDetails.styles.scss | 1 + .../CeleryOverviewDetails.tsx | 41 +- .../OverviewRightPanelGraph.tsx | 103 ++- .../CeleryOverviewDetail/ValueInfo.tsx | 62 +- .../CeleryOverviewGraphUtils.ts | 677 ++---------------- .../CeleryOverview/CeleryOverviewUtils.ts | 23 + 9 files changed, 337 insertions(+), 694 deletions(-) create mode 100644 frontend/src/components/CeleryTask/useNavigateToTraces.ts diff --git a/frontend/src/components/CeleryTask/CeleryTaskDetail/CeleryTaskDetail.tsx b/frontend/src/components/CeleryTask/CeleryTaskDetail/CeleryTaskDetail.tsx index 20989c57fc..ae5ba5ffb6 100644 --- a/frontend/src/components/CeleryTask/CeleryTaskDetail/CeleryTaskDetail.tsx +++ b/frontend/src/components/CeleryTask/CeleryTaskDetail/CeleryTaskDetail.tsx @@ -4,14 +4,11 @@ import { Color, Spacing } from '@signozhq/design-tokens'; import { Divider, Drawer, Typography } from 'antd'; import { QueryParams } from 'constants/query'; import { PANEL_TYPES } from 'constants/queryBuilder'; -import ROUTES from 'constants/routes'; import dayjs from 'dayjs'; -import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { useIsDarkMode } from 'hooks/useDarkMode'; import useUrlQuery from 'hooks/useUrlQuery'; -import { RowData } from 'lib/query/createTableColumnsFromQuery'; import { X } from 'lucide-react'; -import { useCallback, useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { useHistory, useLocation } from 'react-router-dom'; import { UpdateTimeInterval } from 'store/actions'; @@ -19,12 +16,11 @@ import { AppState } from 'store/reducers'; import { Widgets } from 'types/api/dashboard/getAll'; import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; -import { Query, TagFilterItem } from 'types/api/queryBuilder/queryBuilderData'; -import { DataSource, MetricAggregateOperator } from 'types/common/queryBuilder'; import { GlobalReducer } from 'types/reducer/globalTime'; import { v4 as uuidv4 } from 'uuid'; import CeleryTaskGraph from '../CeleryTaskGraph/CeleryTaskGraph'; +import { useNavigateToTraces } from '../useNavigateToTraces'; export type CeleryTaskData = { entity: string; @@ -143,46 +139,7 @@ export default function CeleryTaskDetail({ // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - const { currentQuery } = useQueryBuilder(); - - const prepareQuery = useCallback( - (selectedFilters: TagFilterItem[]): Query => ({ - ...currentQuery, - builder: { - ...currentQuery.builder, - queryData: currentQuery.builder.queryData.map((item) => ({ - ...item, - dataSource: DataSource.TRACES, - aggregateOperator: MetricAggregateOperator.NOOP, - filters: { - ...item.filters, - items: selectedFilters, - }, - })), - }, - }), - [currentQuery], - ); - - const navigateToTrace = (data: RowData): void => { - const { entity, value } = taskData; - const selectedFilters = createFiltersFromData({ ...data, [entity]: value }); - const urlParams = new URLSearchParams(); - urlParams.set(QueryParams.startTime, (minTime / 1000000).toString()); - urlParams.set(QueryParams.endTime, (maxTime / 1000000).toString()); - - const JSONCompositeQuery = encodeURIComponent( - JSON.stringify(prepareQuery(selectedFilters)), - ); - - const newTraceExplorerPath = `${ - ROUTES.TRACES_EXPLORER - }?${urlParams.toString()}&${ - QueryParams.compositeQuery - }=${JSONCompositeQuery}`; - - window.open(newTraceExplorerPath, '_blank'); - }; + const navigateToTrace = useNavigateToTraces(); return ( { + const filters = createFiltersFromData({ + ...rowData, + [taskData.entity]: taskData.value, + }); + navigateToTrace(filters); + }} /> ); diff --git a/frontend/src/components/CeleryTask/useNavigateToTraces.ts b/frontend/src/components/CeleryTask/useNavigateToTraces.ts new file mode 100644 index 0000000000..9bb8db9cf5 --- /dev/null +++ b/frontend/src/components/CeleryTask/useNavigateToTraces.ts @@ -0,0 +1,65 @@ +import { QueryParams } from 'constants/query'; +import ROUTES from 'constants/routes'; +import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; +import { useCallback } from 'react'; +import { useSelector } from 'react-redux'; +import { AppState } from 'store/reducers'; +import { Query, TagFilterItem } from 'types/api/queryBuilder/queryBuilderData'; +import { DataSource, MetricAggregateOperator } from 'types/common/queryBuilder'; +import { GlobalReducer } from 'types/reducer/globalTime'; + +export function useNavigateToTraces(): ( + filters: TagFilterItem[], + startTime?: number, + endTime?: number, +) => void { + const { currentQuery } = useQueryBuilder(); + const { minTime, maxTime } = useSelector( + (state) => state.globalTime, + ); + + const prepareQuery = useCallback( + (selectedFilters: TagFilterItem[]): Query => ({ + ...currentQuery, + builder: { + ...currentQuery.builder, + queryData: currentQuery.builder.queryData.map((item) => ({ + ...item, + dataSource: DataSource.TRACES, + aggregateOperator: MetricAggregateOperator.NOOP, + filters: { + ...item.filters, + items: selectedFilters, + }, + })), + }, + }), + [currentQuery], + ); + + return useCallback( + (filters: TagFilterItem[], startTime?: number, endTime?: number): void => { + const urlParams = new URLSearchParams(); + if (startTime && endTime) { + urlParams.set(QueryParams.startTime, startTime.toString()); + urlParams.set(QueryParams.endTime, endTime.toString()); + } else { + urlParams.set(QueryParams.startTime, (minTime / 1000000).toString()); + urlParams.set(QueryParams.endTime, (maxTime / 1000000).toString()); + } + + const JSONCompositeQuery = encodeURIComponent( + JSON.stringify(prepareQuery(filters)), + ); + + const newTraceExplorerPath = `${ + ROUTES.TRACES_EXPLORER + }?${urlParams.toString()}&${ + QueryParams.compositeQuery + }=${JSONCompositeQuery}`; + + window.open(newTraceExplorerPath, '_blank'); + }, + [minTime, maxTime, prepareQuery], + ); +} diff --git a/frontend/src/container/MetricsApplication/Tabs/util.ts b/frontend/src/container/MetricsApplication/Tabs/util.ts index e1f8164b09..966a3274e3 100644 --- a/frontend/src/container/MetricsApplication/Tabs/util.ts +++ b/frontend/src/container/MetricsApplication/Tabs/util.ts @@ -104,6 +104,8 @@ export function onGraphClickHandler( ): Promise => { const id = `${type}_button`; + console.log(xValue, yValue, mouseX, mouseY, type); + const buttonElement = document.getElementById(id); if (xValue) { diff --git a/frontend/src/pages/Celery/CeleryOverview/CeleryOverviewDetail/CeleryOverviewDetails.styles.scss b/frontend/src/pages/Celery/CeleryOverview/CeleryOverviewDetail/CeleryOverviewDetails.styles.scss index 81eed1a911..8196461828 100644 --- a/frontend/src/pages/Celery/CeleryOverview/CeleryOverviewDetail/CeleryOverviewDetails.styles.scss +++ b/frontend/src/pages/Celery/CeleryOverview/CeleryOverviewDetail/CeleryOverviewDetails.styles.scss @@ -21,6 +21,7 @@ box-sizing: border-box; border-bottom: 0.75px solid #262626; margin-bottom: 12px; + position: relative; .widget-graph-container { height: 260px; diff --git a/frontend/src/pages/Celery/CeleryOverview/CeleryOverviewDetail/CeleryOverviewDetails.tsx b/frontend/src/pages/Celery/CeleryOverview/CeleryOverviewDetail/CeleryOverviewDetails.tsx index f93d9a748b..7fd5ab0b47 100644 --- a/frontend/src/pages/Celery/CeleryOverview/CeleryOverviewDetail/CeleryOverviewDetails.tsx +++ b/frontend/src/pages/Celery/CeleryOverview/CeleryOverviewDetail/CeleryOverviewDetails.tsx @@ -5,7 +5,10 @@ import { Divider, Drawer, Typography } from 'antd'; import { RowData } from 'components/CeleryOverview/CeleryOverviewTable/CeleryOverviewTable'; import { useIsDarkMode } from 'hooks/useDarkMode'; import { X } from 'lucide-react'; +import { useMemo } from 'react'; +import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData'; +import { getFiltersFromKeyValue } from '../CeleryOverviewUtils'; import OverviewRightPanelGraph from './OverviewRightPanelGraph'; import ValueInfo from './ValueInfo'; @@ -17,7 +20,39 @@ export default function CeleryOverviewDetails({ onClose: () => void; }): JSX.Element { const isDarkMode = useIsDarkMode(); - console.log(details); + + const filters: TagFilterItem[] = useMemo(() => { + const keyValues = Object.entries(details).map(([key, value]) => { + switch (key) { + case 'service_name': + return getFiltersFromKeyValue('service.name', value, 'resource'); + case 'span_name': + return getFiltersFromKeyValue('name', value, ''); + case 'messaging_system': + return getFiltersFromKeyValue('messaging.system', value, 'tag'); + case 'destination': + return getFiltersFromKeyValue( + details.messaging_system === 'celery' + ? 'messaging.destination' + : 'messaging.destination.name', + value, + 'tag', + ); + case 'kind_string': + return getFiltersFromKeyValue('kind_string', value, ''); + default: + return undefined; + } + }); + + return keyValues.filter((item) => item !== undefined) as TagFilterItem[]; + }, [details]); + + const groupByFilter = useMemo( + () => + getFiltersFromKeyValue('messaging.destination.partition.id', '', 'tag').key, + [], + ); return ( } >
- - + +
); diff --git a/frontend/src/pages/Celery/CeleryOverview/CeleryOverviewDetail/OverviewRightPanelGraph.tsx b/frontend/src/pages/Celery/CeleryOverview/CeleryOverviewDetail/OverviewRightPanelGraph.tsx index 2df0f4309c..6516e1f39a 100644 --- a/frontend/src/pages/Celery/CeleryOverview/CeleryOverviewDetail/OverviewRightPanelGraph.tsx +++ b/frontend/src/pages/Celery/CeleryOverview/CeleryOverviewDetail/OverviewRightPanelGraph.tsx @@ -1,13 +1,20 @@ import { Card } from 'antd'; +import { useNavigateToTraces } from 'components/CeleryTask/useNavigateToTraces'; import { QueryParams } from 'constants/query'; import { ViewMenuAction } from 'container/GridCardLayout/config'; import GridCard from 'container/GridCardLayout/GridCard'; +import { Button } from 'container/MetricsApplication/Tabs/styles'; +import { onGraphClickHandler } from 'container/MetricsApplication/Tabs/util'; import useUrlQuery from 'hooks/useUrlQuery'; -import { useCallback, useMemo } from 'react'; +import { OnClickPluginOpts } from 'lib/uPlotLib/plugins/onClickPlugin'; +import { useCallback, useMemo, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { useHistory, useLocation } from 'react-router-dom'; import { UpdateTimeInterval } from 'store/actions'; import { AppState } from 'store/reducers'; +import { Widgets } from 'types/api/dashboard/getAll'; +import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData'; import { GlobalReducer } from 'types/reducer/globalTime'; import { @@ -16,7 +23,13 @@ import { celeryOverviewRequestRateGraphData, } from '../CeleryOverviewGraphUtils'; -export default function OverviewRightPanelGraph(): JSX.Element { +export default function OverviewRightPanelGraph({ + groupByFilter, + filters, +}: { + groupByFilter?: BaseAutocompleteData; + filters?: TagFilterItem[]; +}): JSX.Element { const history = useHistory(); const { pathname } = useLocation(); const dispatch = useDispatch(); @@ -44,45 +57,113 @@ export default function OverviewRightPanelGraph(): JSX.Element { ); const requestRateWidget = useMemo( - () => celeryOverviewRequestRateGraphData(minTime, maxTime), - [minTime, maxTime], + () => + celeryOverviewRequestRateGraphData(minTime, maxTime, filters, groupByFilter), + [minTime, maxTime, filters, groupByFilter], ); const errorRateWidget = useMemo( - () => celeryOverviewErrorRateGraphData(minTime, maxTime), - [minTime, maxTime], + () => + celeryOverviewErrorRateGraphData(minTime, maxTime, filters, groupByFilter), + [minTime, maxTime, filters, groupByFilter], ); const avgLatencyWidget = useMemo( - () => celeryOverviewAvgLatencyGraphData(minTime, maxTime), - [minTime, maxTime], + () => + celeryOverviewAvgLatencyGraphData(minTime, maxTime, filters, groupByFilter), + [minTime, maxTime, filters, groupByFilter], + ); + const [selectedTimeStamp, setSelectedTimeStamp] = useState(0); + + const handleSetTimeStamp = useCallback((selectTime: number) => { + setSelectedTimeStamp(selectTime); + }, []); + + const navigateToTraces = useNavigateToTraces(); + + const handleGraphClick = useCallback( + (type: string): OnClickPluginOpts['onClick'] => ( + xValue, + yValue, + mouseX, + mouseY, + ): Promise => + onGraphClickHandler(handleSetTimeStamp)( + xValue, + yValue, + mouseX, + mouseY, + type, + ), + [handleSetTimeStamp], + ); + + const goToTraces = useCallback( + (widget: Widgets) => { + const { stepInterval } = widget?.query?.builder?.queryData?.[0] ?? {}; + navigateToTraces( + filters ?? [], + selectedTimeStamp, + selectedTimeStamp + (stepInterval ?? 60), + ); + }, + [navigateToTraces, filters, selectedTimeStamp], ); return (
+ console.log(...args)} + onClickHandler={handleGraphClick('Celery_request_rate')} />
+ console.log(...args)} + onClickHandler={handleGraphClick('Celery_error_rate')} />
+ console.log(...args)} + onClickHandler={handleGraphClick('Celery_avg_latency')} />
); } + +OverviewRightPanelGraph.defaultProps = { + groupByFilter: undefined, + filters: undefined, +}; diff --git a/frontend/src/pages/Celery/CeleryOverview/CeleryOverviewDetail/ValueInfo.tsx b/frontend/src/pages/Celery/CeleryOverview/CeleryOverviewDetail/ValueInfo.tsx index 607ad07589..97ba9b752d 100644 --- a/frontend/src/pages/Celery/CeleryOverview/CeleryOverviewDetail/ValueInfo.tsx +++ b/frontend/src/pages/Celery/CeleryOverview/CeleryOverviewDetail/ValueInfo.tsx @@ -2,6 +2,7 @@ import './ValueInfo.styles.scss'; import { FileSearchOutlined } from '@ant-design/icons'; import { Button, Card, Col, Row } from 'antd'; +import { useNavigateToTraces } from 'components/CeleryTask/useNavigateToTraces'; import { ENTITY_VERSION_V4 } from 'constants/app'; import { PANEL_TYPES } from 'constants/queryBuilder'; import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults'; @@ -11,7 +12,10 @@ import { useSelector } from 'react-redux'; import { AppState } from 'store/reducers'; import { SuccessResponse } from 'types/api'; import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; +import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData'; import { GlobalReducer } from 'types/reducer/globalTime'; +import { v4 as uuidv4 } from 'uuid'; import { celeryOverviewAvgLatencyWidgetData, @@ -20,7 +24,11 @@ import { } from '../CeleryOverviewGraphUtils'; import { getQueryPayloadFromWidgetsData } from '../CeleryOverviewUtils'; -export default function ValueInfo(): JSX.Element { +export default function ValueInfo({ + filters, +}: { + filters?: TagFilterItem[]; +}): JSX.Element { const { maxTime, minTime } = useSelector( (state) => state.globalTime, ); @@ -32,13 +40,13 @@ export default function ValueInfo(): JSX.Element { start: Math.floor(minTime / 1000000000), end: Math.floor(maxTime / 1000000000), widgetsData: [ - celeryOverviewRequestRateWidgetData(), - celeryOverviewErrorRateWidgetData(), - celeryOverviewAvgLatencyWidgetData(), + celeryOverviewRequestRateWidgetData(filters), + celeryOverviewErrorRateWidgetData(filters), + celeryOverviewAvgLatencyWidgetData(filters), ], panelType: PANEL_TYPES.VALUE, }), - [minTime, maxTime], + [minTime, maxTime, filters], ); const queries = useQueries( @@ -73,6 +81,14 @@ export default function ValueInfo(): JSX.Element { [isLoading, getValues], ); + const navigateToTrace = useNavigateToTraces(); + + const avgLatencyInMs = useMemo(() => { + if (avgLatency === 'NaN') return 'NaN'; + const numericValue = parseFloat(avgLatency); + return (numericValue / 1000000).toFixed(2); + }, [avgLatency]); + const getColorBasedOnValue = (value: string): string => { const numericValue = parseFloat(value); if (value === 'NaN') return 'gray'; @@ -81,6 +97,14 @@ export default function ValueInfo(): JSX.Element { return 'red'; }; + const getColorForLatency = (value: string): string => { + const numericValue = parseFloat(value); + if (value === 'NaN') return 'gray'; + if (numericValue < 100) return 'green'; + if (numericValue < 200) return 'yellow'; + return 'red'; + }; + const getColorForErrorRate = (value: string): string => { const numericValue = parseFloat(value); if (value === 'NaN') return 'gray'; @@ -109,6 +133,7 @@ export default function ValueInfo(): JSX.Element { icon={} className="trace-button" disabled={isLoading} + onClick={(): void => navigateToTrace(filters ?? [])} > View Traces @@ -130,6 +155,24 @@ export default function ValueInfo(): JSX.Element { icon={} className="trace-button" disabled={isLoading} + onClick={(): void => + navigateToTrace([ + ...(filters ?? []), + { + id: uuidv4(), + key: { + dataType: DataTypes.bool, + id: 'has_error--bool----true', + isColumn: true, + isJSON: false, + key: 'has_error', + type: '', + }, + op: '=', + value: 'true', + }, + ]) + } > View Traces @@ -138,11 +181,11 @@ export default function ValueInfo(): JSX.Element {
Average Latency
- {avgLatency === 'NaN' ? '0' : avgLatency} + {avgLatencyInMs === 'NaN' ? '0' : avgLatencyInMs}
ms
@@ -151,6 +194,7 @@ export default function ValueInfo(): JSX.Element { icon={} className="trace-button" disabled={isLoading} + onClick={(): void => navigateToTrace(filters ?? [])} > View Traces @@ -159,3 +203,7 @@ export default function ValueInfo(): JSX.Element { ); } + +ValueInfo.defaultProps = { + filters: undefined, +}; diff --git a/frontend/src/pages/Celery/CeleryOverview/CeleryOverviewGraphUtils.ts b/frontend/src/pages/Celery/CeleryOverview/CeleryOverviewGraphUtils.ts index 076497c26f..ad66080bf8 100644 --- a/frontend/src/pages/Celery/CeleryOverview/CeleryOverviewGraphUtils.ts +++ b/frontend/src/pages/Celery/CeleryOverview/CeleryOverviewGraphUtils.ts @@ -4,10 +4,17 @@ import { PANEL_TYPES } from 'constants/queryBuilder'; import { getWidgetQueryBuilder } from 'container/MetricsApplication/MetricsApplication.factory'; import { getWidgetQuery } from 'pages/MessagingQueues/MQDetails/MetricPage/MetricPageUtil'; import { Widgets } from 'types/api/dashboard/getAll'; -import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { + BaseAutocompleteData, + DataTypes, +} from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData'; import { DataSource } from 'types/common/queryBuilder'; +import { v4 as uuidv4 } from 'uuid'; -export const celeryOverviewRequestRateWidgetData = (): Widgets => +export const celeryOverviewRequestRateWidgetData = ( + filters?: TagFilterItem[], +): Widgets => getWidgetQueryBuilder( getWidgetQuery({ title: 'Request Rate', @@ -27,73 +34,7 @@ export const celeryOverviewRequestRateWidgetData = (): Widgets => disabled: false, expression: 'A', filters: { - items: [ - { - id: '66b09400', - key: { - dataType: DataTypes.String, - id: 'service.name--string--resource--true', - isColumn: true, - isJSON: false, - key: 'service.name', - type: 'resource', - }, - op: '=', - value: 'consumer-svc-2', - }, - { - id: '7c62322c', - key: { - dataType: DataTypes.String, - id: 'name--string----true', - isColumn: true, - isJSON: false, - key: 'name', - type: '', - }, - op: '=', - value: 'topic1 process', - }, - { - id: 'f428d54a', - key: { - dataType: DataTypes.String, - id: 'messaging.destination.name--string--tag--false', - isColumn: false, - isJSON: false, - key: 'messaging.destination.name', - type: 'tag', - }, - op: '=', - value: 'topic1', - }, - { - id: '15202376', - key: { - dataType: DataTypes.String, - id: 'kind_string--string----true', - isColumn: true, - isJSON: false, - key: 'kind_string', - type: '', - }, - op: '=', - value: 'Consumer', - }, - { - id: 'ba4fdb16', - key: { - dataType: DataTypes.String, - id: 'messaging.system--string--tag--true', - isColumn: true, - isJSON: false, - key: 'messaging.system', - type: 'tag', - }, - op: '=', - value: 'kafka', - }, - ], + items: filters ?? [], op: 'AND', }, functions: [], @@ -113,11 +54,13 @@ export const celeryOverviewRequestRateWidgetData = (): Widgets => }), ); -export const celeryOverviewErrorRateWidgetData = (): Widgets => +export const celeryOverviewErrorRateWidgetData = ( + filters?: TagFilterItem[], +): Widgets => getWidgetQueryBuilder( getWidgetQuery({ - title: 'Error Rate', - description: 'Represents Error rate of the service', + title: 'Error', + description: 'Represents Error in the service', queryData: [ { aggregateAttribute: { @@ -134,60 +77,9 @@ export const celeryOverviewErrorRateWidgetData = (): Widgets => expression: 'A', filters: { items: [ + ...(filters ?? []), { - id: '0a44dbf3', - key: { - dataType: DataTypes.String, - id: 'service.name--string--resource--true', - isColumn: true, - isJSON: false, - key: 'service.name', - type: 'resource', - }, - op: '=', - value: 'worker1', - }, - { - id: '31b32f97', - key: { - dataType: DataTypes.String, - id: 'name--string----true', - isColumn: true, - isJSON: false, - key: 'name', - type: '', - }, - op: '=', - value: 'run/tasks.tasks.add', - }, - { - id: '7f20ee62', - key: { - dataType: DataTypes.String, - id: 'messaging.destination--string--tag--false', - isColumn: false, - isJSON: false, - key: 'messaging.destination', - type: 'tag', - }, - op: '=', - value: 'queue1', - }, - { - id: '76b02773', - key: { - dataType: DataTypes.String, - id: 'kind_string--string----true', - isColumn: true, - isJSON: false, - key: 'kind_string', - type: '', - }, - op: '=', - value: 'Consumer', - }, - { - id: 'a2dc0a85', + id: uuidv4(), key: { dataType: DataTypes.bool, id: 'has_error--bool----true', @@ -219,7 +111,9 @@ export const celeryOverviewErrorRateWidgetData = (): Widgets => }), ); -export const celeryOverviewAvgLatencyWidgetData = (): Widgets => +export const celeryOverviewAvgLatencyWidgetData = ( + filters?: TagFilterItem[], +): Widgets => getWidgetQueryBuilder( getWidgetQuery({ title: 'Avg Latency', @@ -239,66 +133,13 @@ export const celeryOverviewAvgLatencyWidgetData = (): Widgets => disabled: false, expression: 'A', filters: { - items: [ - { - id: 'a2c0bfe2', - key: { - dataType: DataTypes.String, - id: 'service.name--string--resource--true', - isColumn: true, - isJSON: false, - key: 'service.name', - type: 'resource', - }, - op: '=', - value: 'worker1', - }, - { - id: '02e70326', - key: { - dataType: DataTypes.String, - id: 'name--string----true', - isColumn: true, - isJSON: false, - key: 'name', - type: '', - }, - op: '=', - value: 'run/tasks.tasks.add', - }, - { - id: 'ac15cbac', - key: { - dataType: DataTypes.String, - id: 'messaging.destination--string--tag--false', - isColumn: false, - isJSON: false, - key: 'messaging.destination', - type: 'tag', - }, - op: '=', - value: 'queue1', - }, - { - id: '59a7727a', - key: { - dataType: DataTypes.String, - id: 'kind_string--string----true', - isColumn: true, - isJSON: false, - key: 'kind_string', - type: '', - }, - op: '=', - value: 'Consumer', - }, - ], + items: filters ?? [], op: 'AND', }, functions: [], groupBy: [], having: [], - legend: '{{messaging.destination.partition.id}}', + legend: 'p95', limit: null, orderBy: [], queryName: 'A', @@ -315,6 +156,8 @@ export const celeryOverviewAvgLatencyWidgetData = (): Widgets => export const celeryOverviewRequestRateGraphData = ( startTime: number, endTime: number, + filters?: TagFilterItem[], + groupByFilter?: BaseAutocompleteData, ): Widgets => getWidgetQueryBuilder( getWidgetQuery({ @@ -335,75 +178,13 @@ export const celeryOverviewRequestRateGraphData = ( disabled: false, expression: 'A', filters: { - items: [ - { - id: 'bfe7f9f6', - key: { - dataType: DataTypes.String, - id: 'service.name--string--resource--true', - isColumn: true, - isJSON: false, - key: 'service.name', - type: 'resource', - }, - op: '=', - value: 'consumer-svc-2', - }, - { - id: '0ae75c5b', - key: { - dataType: DataTypes.String, - id: 'messaging.destination.name--string--tag--false', - isColumn: false, - isJSON: false, - key: 'messaging.destination.name', - type: 'tag', - }, - op: '=', - value: 'topic1', - }, - { - id: 'e694f23e', - key: { - dataType: DataTypes.String, - id: 'name--string----true', - isColumn: true, - isJSON: false, - key: 'name', - type: '', - }, - op: '=', - value: 'topic1 process', - }, - { - id: '21fffed6', - key: { - dataType: DataTypes.String, - id: 'kind_string--string----true', - isColumn: true, - isJSON: false, - key: 'kind_string', - type: '', - }, - op: '=', - value: 'Consumer', - }, - ], + items: filters ?? [], op: 'AND', }, functions: [], - groupBy: [ - { - dataType: DataTypes.String, - id: 'messaging.destination.partition.id--string--tag--false', - isColumn: false, - isJSON: false, - key: 'messaging.destination.partition.id', - type: 'tag', - }, - ], + groupBy: groupByFilter ? [groupByFilter] : [], having: [], - legend: '{{messaging.destination.partition.id}}', + legend: 'Request Rate', limit: null, orderBy: [], queryName: 'A', @@ -420,11 +201,13 @@ export const celeryOverviewRequestRateGraphData = ( export const celeryOverviewErrorRateGraphData = ( startTime: number, endTime: number, + filters?: TagFilterItem[], + groupByFilter?: BaseAutocompleteData, ): Widgets => getWidgetQueryBuilder( getWidgetQuery({ - title: 'Error Rate', - description: 'Represents Error rate of the service', + title: 'Error', + description: 'Represents Error in the service', queryData: [ { aggregateAttribute: { @@ -441,71 +224,9 @@ export const celeryOverviewErrorRateGraphData = ( expression: 'A', filters: { items: [ + ...(filters ?? []), { - id: '1a77c475', - key: { - dataType: DataTypes.String, - id: 'service.name--string--resource--true', - isColumn: true, - isJSON: false, - key: 'service.name', - type: 'resource', - }, - op: '=', - value: 'consumer-svc-2', - }, - { - id: '70630e97', - key: { - dataType: DataTypes.String, - id: 'name--string----true', - isColumn: true, - isJSON: false, - key: 'name', - type: '', - }, - op: '=', - value: 'topic1 process', - }, - { - id: 'f41ad482', - key: { - dataType: DataTypes.EMPTY, - isColumn: false, - isJSON: false, - key: 'messaging.destination.name', - type: '', - }, - op: '=', - value: 'topic1', - }, - { - id: '00637dde', - key: { - dataType: DataTypes.String, - id: 'kind_string--string----true', - isColumn: true, - isJSON: false, - key: 'kind_string', - type: '', - }, - op: '=', - value: 'Consumer', - }, - { - id: 'afa5fc10', - key: { - dataType: DataTypes.EMPTY, - isColumn: false, - isJSON: false, - key: 'messaging.system', - type: '', - }, - op: '=', - value: 'kafka', - }, - { - id: '9fdeebea', + id: uuidv4(), key: { dataType: DataTypes.bool, id: 'has_error--bool----true', @@ -521,18 +242,9 @@ export const celeryOverviewErrorRateGraphData = ( op: 'AND', }, functions: [], - groupBy: [ - { - dataType: DataTypes.String, - id: 'messaging.destination.partition.id--string--tag--false', - isColumn: false, - isJSON: false, - key: 'messaging.destination.partition.id', - type: 'tag', - }, - ], + groupBy: groupByFilter ? [groupByFilter] : [], having: [], - legend: '{{messaging.destination.partition.id}}', + legend: 'True', limit: null, orderBy: [], queryName: 'A', @@ -556,71 +268,9 @@ export const celeryOverviewErrorRateGraphData = ( expression: 'B', filters: { items: [ + ...(filters ?? []), { - id: '93d85de1', - key: { - dataType: DataTypes.String, - id: 'service.name--string--resource--true', - isColumn: true, - isJSON: false, - key: 'service.name', - type: 'resource', - }, - op: '=', - value: 'consumer-svc-2', - }, - { - id: '57632b3f', - key: { - dataType: DataTypes.String, - id: 'name--string----true', - isColumn: true, - isJSON: false, - key: 'name', - type: '', - }, - op: '=', - value: 'topic1 process', - }, - { - id: '8d7f5e93', - key: { - dataType: DataTypes.EMPTY, - isColumn: false, - isJSON: false, - key: 'messaging.destination.name', - type: '', - }, - op: '=', - value: 'topic1', - }, - { - id: '64636499', - key: { - dataType: DataTypes.String, - id: 'kind_string--string----true', - isColumn: true, - isJSON: false, - key: 'kind_string', - type: '', - }, - op: '=', - value: 'Consumer', - }, - { - id: '22d01c6b', - key: { - dataType: DataTypes.EMPTY, - isColumn: false, - isJSON: false, - key: 'messaging.system', - type: '', - }, - op: '=', - value: 'kafka', - }, - { - id: 'd13a99c4', + id: uuidv4(), key: { dataType: DataTypes.bool, id: 'has_error--bool----true', @@ -636,18 +286,9 @@ export const celeryOverviewErrorRateGraphData = ( op: 'AND', }, functions: [], - groupBy: [ - { - dataType: DataTypes.String, - id: 'messaging.destination.partition.id--string--tag--false', - isColumn: false, - isJSON: false, - key: 'messaging.destination.partition.id', - type: 'tag', - }, - ], + groupBy: groupByFilter ? [groupByFilter] : [], having: [], - legend: '{{messaging.destination.partition.id}}', + legend: 'False', limit: null, orderBy: [], queryName: 'B', @@ -664,6 +305,8 @@ export const celeryOverviewErrorRateGraphData = ( export const celeryOverviewAvgLatencyGraphData = ( startTime: number, endTime: number, + filters?: TagFilterItem[], + groupByFilter?: BaseAutocompleteData, ): Widgets => getWidgetQueryBuilder( getWidgetQuery({ @@ -684,86 +327,13 @@ export const celeryOverviewAvgLatencyGraphData = ( disabled: false, expression: 'C', filters: { - items: [ - { - id: 'fec96858', - key: { - dataType: DataTypes.String, - id: 'service.name--string--resource--true', - isColumn: true, - isJSON: false, - key: 'service.name', - type: 'resource', - }, - op: '=', - value: 'consumer-svc-2', - }, - { - id: '36330bd8', - key: { - dataType: DataTypes.String, - id: 'name--string----true', - isColumn: true, - isJSON: false, - key: 'name', - type: '', - }, - op: '=', - value: 'topic1 process', - }, - { - id: '061f3047', - key: { - dataType: DataTypes.EMPTY, - isColumn: false, - isJSON: false, - key: 'messaging.destination.name', - type: '', - }, - op: '=', - value: 'topic1', - }, - { - id: '9fd15439', - key: { - dataType: DataTypes.String, - id: 'kind_string--string----true', - isColumn: true, - isJSON: false, - key: 'kind_string', - type: '', - }, - op: '=', - value: 'Consumer', - }, - { - id: '0f1af7b9', - key: { - dataType: DataTypes.EMPTY, - isColumn: false, - isJSON: false, - key: 'messaging.system', - type: '', - }, - op: '=', - value: 'kafka', - }, - ], + items: filters ?? [], op: 'AND', }, functions: [], - groupBy: [ - { - dataType: DataTypes.String, - id: 'messaging.destination.partition.id--string--tag--false', - isColumn: false, - isJSON: false, - key: 'messaging.destination.partition.id', - type: 'tag', - }, - ], + groupBy: groupByFilter ? [groupByFilter] : [], having: [], - legend: '{{messaging.destination.partition.id}}', + legend: 'p90', limit: null, orderBy: [], queryName: 'C', @@ -786,86 +356,13 @@ export const celeryOverviewAvgLatencyGraphData = ( disabled: false, expression: 'D', filters: { - items: [ - { - id: 'f0143e91', - key: { - dataType: DataTypes.String, - id: 'service.name--string--resource--true', - isColumn: true, - isJSON: false, - key: 'service.name', - type: 'resource', - }, - op: '=', - value: 'consumer-svc-2', - }, - { - id: '0764eb8e', - key: { - dataType: DataTypes.String, - id: 'name--string----true', - isColumn: true, - isJSON: false, - key: 'name', - type: '', - }, - op: '=', - value: 'topic1 process', - }, - { - id: 'e6b592ec', - key: { - dataType: DataTypes.EMPTY, - isColumn: false, - isJSON: false, - key: 'messaging.destination.name', - type: '', - }, - op: '=', - value: 'topic1', - }, - { - id: '4ca3b0b4', - key: { - dataType: DataTypes.String, - id: 'kind_string--string----true', - isColumn: true, - isJSON: false, - key: 'kind_string', - type: '', - }, - op: '=', - value: 'Consumer', - }, - { - id: 'd81770c9', - key: { - dataType: DataTypes.EMPTY, - isColumn: false, - isJSON: false, - key: 'messaging.system', - type: '', - }, - op: '=', - value: 'kafka', - }, - ], + items: filters ?? [], op: 'AND', }, functions: [], - groupBy: [ - { - dataType: DataTypes.String, - id: 'messaging.destination.partition.id--string--tag--false', - isColumn: false, - isJSON: false, - key: 'messaging.destination.partition.id', - type: 'tag', - }, - ], + groupBy: groupByFilter ? [groupByFilter] : [], having: [], - legend: '{{messaging.destination.partition.id}}', + legend: 'p95', limit: null, orderBy: [], queryName: 'D', @@ -888,86 +385,13 @@ export const celeryOverviewAvgLatencyGraphData = ( disabled: false, expression: 'E', filters: { - items: [ - { - id: 'a8f0c5e5', - key: { - dataType: DataTypes.String, - id: 'service.name--string--resource--true', - isColumn: true, - isJSON: false, - key: 'service.name', - type: 'resource', - }, - op: '=', - value: 'consumer-svc-2', - }, - { - id: '60c81993', - key: { - dataType: DataTypes.String, - id: 'name--string----true', - isColumn: true, - isJSON: false, - key: 'name', - type: '', - }, - op: '=', - value: 'topic1 process', - }, - { - id: '7f054fb2', - key: { - dataType: DataTypes.EMPTY, - isColumn: false, - isJSON: false, - key: 'messaging.destination.name', - type: '', - }, - op: '=', - value: 'topic1', - }, - { - id: 'e825e894', - key: { - dataType: DataTypes.String, - id: 'kind_string--string----true', - isColumn: true, - isJSON: false, - key: 'kind_string', - type: '', - }, - op: '=', - value: 'Consumer', - }, - { - id: '9ba10a06', - key: { - dataType: DataTypes.EMPTY, - isColumn: false, - isJSON: false, - key: 'messaging.system', - type: '', - }, - op: '=', - value: 'kafka', - }, - ], + items: filters ?? [], op: 'AND', }, functions: [], - groupBy: [ - { - dataType: DataTypes.String, - id: 'messaging.destination.partition.id--string--tag--false', - isColumn: false, - isJSON: false, - key: 'messaging.destination.partition.id', - type: 'tag', - }, - ], + groupBy: groupByFilter ? [groupByFilter] : [], having: [], - legend: '{{messaging.destination.partition.id}}', + legend: 'p99', limit: null, orderBy: [], queryName: 'E', @@ -978,5 +402,6 @@ export const celeryOverviewAvgLatencyGraphData = ( }, ], panelTypes: PANEL_TYPES.TIME_SERIES, + yAxisUnit: 'ns', }), ); diff --git a/frontend/src/pages/Celery/CeleryOverview/CeleryOverviewUtils.ts b/frontend/src/pages/Celery/CeleryOverview/CeleryOverviewUtils.ts index 884b4b79ab..98d735c4d3 100644 --- a/frontend/src/pages/Celery/CeleryOverview/CeleryOverviewUtils.ts +++ b/frontend/src/pages/Celery/CeleryOverview/CeleryOverviewUtils.ts @@ -1,6 +1,9 @@ import { PANEL_TYPES } from 'constants/queryBuilder'; import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults'; import { Widgets } from 'types/api/dashboard/getAll'; +import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData'; +import { v4 as uuidv4 } from 'uuid'; export const getQueryPayloadFromWidgetsData = ({ start, @@ -22,3 +25,23 @@ export const getQueryPayloadFromWidgetsData = ({ formatForWeb: false, variables: {}, })); + +export const getFiltersFromKeyValue = ( + key: string, + value: string | number, + type?: string, + op?: string, + dataType?: DataTypes, +): TagFilterItem => ({ + id: uuidv4(), + key: { + key, + dataType: dataType || DataTypes.String, + type: type || 'tag', + isColumn: false, + isJSON: false, + id: `${key}--${dataType || DataTypes.String}--${type || 'tag'}--false`, + }, + op: op || '=', + value: value.toString(), +}); From 57a3a06738cfc802e9ea19ceb423e85ed357193f Mon Sep 17 00:00:00 2001 From: SagarRajput-7 Date: Tue, 28 Jan 2025 07:19:11 +0530 Subject: [PATCH 3/7] feat: implement search column wise and global table wise --- .../CeleryOverviewTable.tsx | 148 +++++++++++++++++- 1 file changed, 142 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/CeleryOverview/CeleryOverviewTable/CeleryOverviewTable.tsx b/frontend/src/components/CeleryOverview/CeleryOverviewTable/CeleryOverviewTable.tsx index 35ed91468e..4c8f8f6623 100644 --- a/frontend/src/components/CeleryOverview/CeleryOverviewTable/CeleryOverviewTable.tsx +++ b/frontend/src/components/CeleryOverview/CeleryOverviewTable/CeleryOverviewTable.tsx @@ -1,8 +1,20 @@ import './CeleryOverviewTable.styles.scss'; -import { LoadingOutlined } from '@ant-design/icons'; +import { LoadingOutlined, SearchOutlined } from '@ant-design/icons'; import { Color } from '@signozhq/design-tokens'; -import { Progress, Spin, TableColumnsType, Tooltip, Typography } from 'antd'; +import { + Button, + Input, + InputRef, + Progress, + Space, + Spin, + TableColumnsType, + TableColumnType, + Tooltip, + Typography, +} from 'antd'; +import { FilterDropdownProps } from 'antd/lib/table/interface'; import { getQueueOverview, QueueOverviewResponse, @@ -13,7 +25,7 @@ import { LOCALSTORAGE } from 'constants/localStorage'; import useDragColumns from 'hooks/useDragColumns'; import { getDraggedColumns } from 'hooks/useDragColumns/utils'; import useUrlQuery from 'hooks/useUrlQuery'; -import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useMutation } from 'react-query'; import { useSelector } from 'react-redux'; import { AppState } from 'store/reducers'; @@ -55,6 +67,72 @@ function ProgressRender(item: string | number): JSX.Element { ); } +const getColumnSearchProps = ( + searchInput: React.RefObject, + handleReset: ( + clearFilters: () => void, + confirm: FilterDropdownProps['confirm'], + ) => void, + handleSearch: (selectedKeys: string[], confirm: () => void) => void, + dataIndex?: string, +): TableColumnType => ({ + filterDropdown: ({ + setSelectedKeys, + selectedKeys, + confirm, + clearFilters, + close, + }): JSX.Element => ( + // eslint-disable-next-line jsx-a11y/no-static-element-interactions +
e.stopPropagation()}> + + setSelectedKeys(e.target.value ? [e.target.value] : []) + } + onPressEnter={(): void => handleSearch(selectedKeys as string[], confirm)} + style={{ marginBottom: 8, display: 'block' }} + /> + + + + + +
+ ), + filterIcon: (filtered: boolean): JSX.Element => ( + + ), + onFilter: (value, record): boolean => + record[dataIndex || ''] + .toString() + .toLowerCase() + .includes((value as string).toLowerCase()), +}); + function getColumns(data: RowData[]): TableColumnsType { if (data?.length === 0) { return []; @@ -297,11 +375,42 @@ export default function CeleryOverviewTable({ LOCALSTORAGE.CELERY_OVERVIEW_COLUMNS, ); + const [searchText, setSearchText] = useState(''); + const searchInput = useRef(null); + + const handleSearch = ( + selectedKeys: string[], + confirm: FilterDropdownProps['confirm'], + ): void => { + confirm(); + setSearchText(selectedKeys[0]); + }; + + const handleReset = ( + clearFilters: () => void, + confirm: FilterDropdownProps['confirm'], + ): void => { + clearFilters(); + setSearchText(''); + confirm(); + }; + const columns = useMemo( - () => getDraggedColumns(getColumns(tableData), draggedColumns), + () => + getDraggedColumns( + getColumns(tableData).map((item) => ({ + ...item, + ...getColumnSearchProps( + searchInput, + handleReset, + handleSearch, + item.key?.toString(), + ), + })), + draggedColumns, + ), [tableData, draggedColumns], ); - const handleDragColumn = useCallback( (fromIndex: number, toIndex: number) => onDragColumns(columns, fromIndex, toIndex), @@ -323,14 +432,41 @@ export default function CeleryOverviewTable({ onRowClick(record); }; + const getFilteredData = useCallback( + (data: RowData[]): RowData[] => { + if (!searchText) return data; + + const searchLower = searchText.toLowerCase(); + return data.filter((record) => + Object.values(record).some( + (value) => + value !== undefined && + value.toString().toLowerCase().includes(searchLower), + ), + ); + }, + [searchText], + ); + + const filteredData = useMemo(() => getFilteredData(tableData), [ + getFilteredData, + tableData, + ]); + return (
+ setSearchText(e.target.value)} + value={searchText} + allowClear + /> Date: Tue, 28 Jan 2025 08:07:37 +0530 Subject: [PATCH 4/7] feat: implemented filter on table with api filtering --- .../CeleryOverviewConfigOptions.styles.scss | 7 +++ .../CeleryOverviewConfigOptions.tsx | 43 ++++++++++------ .../CeleryOverviewTable.tsx | 12 ++--- .../useGetCeleryFilters.ts | 51 +++++++++++-------- .../CeleryTask/useCeleryFilterOptions.ts | 2 +- frontend/src/constants/query.ts | 2 + 6 files changed, 73 insertions(+), 44 deletions(-) diff --git a/frontend/src/components/CeleryOverview/CeleryOverviewConfigOptions/CeleryOverviewConfigOptions.styles.scss b/frontend/src/components/CeleryOverview/CeleryOverviewConfigOptions/CeleryOverviewConfigOptions.styles.scss index 6d9be3a665..83dcabd43f 100644 --- a/frontend/src/components/CeleryOverview/CeleryOverviewConfigOptions/CeleryOverviewConfigOptions.styles.scss +++ b/frontend/src/components/CeleryOverview/CeleryOverviewConfigOptions/CeleryOverviewConfigOptions.styles.scss @@ -10,6 +10,9 @@ .config-select-option { width: 100%; + min-width: 160px; + max-width: fit-content; + .ant-select-selector { display: flex; min-height: 32px; @@ -23,6 +26,10 @@ } } } + + .copy-url-btn { + flex-shrink: 0; + } } .lightMode { diff --git a/frontend/src/components/CeleryOverview/CeleryOverviewConfigOptions/CeleryOverviewConfigOptions.tsx b/frontend/src/components/CeleryOverview/CeleryOverviewConfigOptions/CeleryOverviewConfigOptions.tsx index 22f2743410..456d22efa7 100644 --- a/frontend/src/components/CeleryOverview/CeleryOverviewConfigOptions/CeleryOverviewConfigOptions.tsx +++ b/frontend/src/components/CeleryOverview/CeleryOverviewConfigOptions/CeleryOverviewConfigOptions.tsx @@ -1,7 +1,7 @@ import './CeleryOverviewConfigOptions.styles.scss'; import { Color } from '@signozhq/design-tokens'; -import { Button, Select, Spin, Tooltip } from 'antd'; +import { Button, Row, Select, Spin, Tooltip } from 'antd'; import { getValuesFromQueryParams, setQueryParamsFromOptions, @@ -18,7 +18,7 @@ import { useCopyToClipboard } from 'react-use'; interface SelectOptionConfig { placeholder: string; queryParam: QueryParams; - filterType: 'serviceName' | 'spanName' | 'msgSystem'; + filterType: string | string[]; } function FilterSelect({ @@ -29,13 +29,14 @@ function FilterSelect({ const { handleSearch, isFetching, options } = useCeleryFilterOptions( filterType, ); + const urlQuery = useUrlQuery(); const history = useHistory(); const location = useLocation(); return (