diff --git a/frontend/src/components/CeleryTask/CeleryTaskDetail/CeleryTaskDetail.tsx b/frontend/src/components/CeleryTask/CeleryTaskDetail/CeleryTaskDetail.tsx index 05702a06d37..75f6fae6ea2 100644 --- a/frontend/src/components/CeleryTask/CeleryTaskDetail/CeleryTaskDetail.tsx +++ b/frontend/src/components/CeleryTask/CeleryTaskDetail/CeleryTaskDetail.tsx @@ -3,9 +3,12 @@ import './CeleryTaskDetail.style.scss'; 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 { useIsDarkMode } from 'hooks/useDarkMode'; import useUrlQuery from 'hooks/useUrlQuery'; +import { RowData } from 'lib/query/createTableColumnsFromQuery'; import { X } from 'lucide-react'; import { useEffect, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; @@ -24,16 +27,18 @@ export type CeleryTaskData = { timeRange: [number, number]; }; +export interface CaptureDataProps extends CeleryTaskData { + widgetData: Widgets; +} + export type CeleryTaskDetailProps = { onClose: () => void; - mainTitle: string; widgetData: Widgets; taskData: CeleryTaskData; drawerOpen: boolean; }; export default function CeleryTaskDetail({ - mainTitle, widgetData, taskData, onClose, @@ -50,7 +55,6 @@ export default function CeleryTaskDetail({ const [totalTask, setTotalTask] = useState(0); const getGraphData = (graphData?: MetricRangePayloadProps['data']): void => { - console.log(graphData); setTotalTask((graphData?.result?.[0] as any)?.table?.rows.length); }; @@ -100,12 +104,23 @@ export default function CeleryTaskDetail({ // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + const navigateToTrace = (data: RowData): void => { + console.log('navigateToTrace', data); + const urlParams = new URLSearchParams(); + urlParams.set(QueryParams.startTime, (minTime / 1000000).toString()); + urlParams.set(QueryParams.endTime, (maxTime / 1000000).toString()); + + const newTraceExplorerPath = `${ROUTES.TRACES_EXPLORER}`; // todo-sagar - add filters + + history.push(newTraceExplorerPath); + }; + return ( - {mainTitle} + {`Details - ${taskData.entity}`}
{`${formatTimestamp(taskData.timeRange[0])} ${ @@ -115,7 +130,7 @@ export default function CeleryTaskDetail({ }`} - task-details + {taskData.value}
} @@ -135,9 +150,11 @@ export default function CeleryTaskDetail({ > {}} getGraphData={getGraphData} + panelType={PANEL_TYPES.TABLE} queryEnabled + openTracesButton + onOpenTraceBtnClick={navigateToTrace} />
); diff --git a/frontend/src/components/CeleryTask/CeleryTaskGraph/CeleryTaskGraph.tsx b/frontend/src/components/CeleryTask/CeleryTaskGraph/CeleryTaskGraph.tsx index 09a84a9baf6..9aaf6208d23 100644 --- a/frontend/src/components/CeleryTask/CeleryTaskGraph/CeleryTaskGraph.tsx +++ b/frontend/src/components/CeleryTask/CeleryTaskGraph/CeleryTaskGraph.tsx @@ -5,6 +5,8 @@ import GridCard from 'container/GridCardLayout/GridCard'; import { Card } from 'container/GridCardLayout/styles'; import { useIsDarkMode } from 'hooks/useDarkMode'; import useUrlQuery from 'hooks/useUrlQuery'; +import { RowData } from 'lib/query/createTableColumnsFromQuery'; +import { getStartAndEndTimesInMilliseconds } from 'pages/MessagingQueues/MessagingQueuesUtils'; import { useCallback } from 'react'; import { useDispatch } from 'react-redux'; import { useHistory, useLocation } from 'react-router-dom'; @@ -12,18 +14,27 @@ import { UpdateTimeInterval } from 'store/actions'; import { Widgets } from 'types/api/dashboard/getAll'; import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; -import { CeleryTaskData } from '../CeleryTaskDetail/CeleryTaskDetail'; +import { CaptureDataProps } from '../CeleryTaskDetail/CeleryTaskDetail'; +import { celeryTimeSeriesTablesWidgetData } from './CeleryTaskGraphUtils'; function CeleryTaskGraph({ widgetData, onClick, getGraphData, queryEnabled, + rightPanelTitle, + panelType, + openTracesButton, + onOpenTraceBtnClick, }: { widgetData: Widgets; - onClick: (task: CeleryTaskData) => void; + onClick?: (task: CaptureDataProps) => void; getGraphData?: (graphData?: MetricRangePayloadProps['data']) => void; queryEnabled: boolean; + rightPanelTitle?: string; + panelType?: PANEL_TYPES; + openTracesButton?: boolean; + onOpenTraceBtnClick?: (record: RowData) => void; }): JSX.Element { const history = useHistory(); const { pathname } = useLocation(); @@ -51,19 +62,37 @@ function CeleryTaskGraph({ return ( { - console.log('clicked', arg); // todo-sagar: add logic to handle click - onClick(arg as any); + onClickHandler={(xValue, _yValue, _mouseX, _mouseY, data): void => { + const { start, end } = getStartAndEndTimesInMilliseconds(xValue); + + // Extract entity and value from data + const [firstDataPoint] = Object.entries(data || {}); + const [entity, value] = firstDataPoint || []; + + const widgetData = celeryTimeSeriesTablesWidgetData( + entity, + value, + rightPanelTitle || '', + ); + + onClick?.({ + entity, + value, + timeRange: [start, end], + widgetData, + }); }} getGraphData={getGraphData} isQueryEnabled={queryEnabled} + openTracesButton={openTracesButton} + onOpenTraceBtnClick={onOpenTraceBtnClick} /> ); @@ -71,6 +100,11 @@ function CeleryTaskGraph({ CeleryTaskGraph.defaultProps = { getGraphData: undefined, + onClick: undefined, + rightPanelTitle: undefined, + panelType: PANEL_TYPES.TIME_SERIES, + openTracesButton: false, + onOpenTraceBtnClick: undefined, }; export default CeleryTaskGraph; diff --git a/frontend/src/components/CeleryTask/CeleryTaskGraph/CeleryTaskGraphGrid.tsx b/frontend/src/components/CeleryTask/CeleryTaskGraph/CeleryTaskGraphGrid.tsx index ef022b20c87..d6dcfda13b1 100644 --- a/frontend/src/components/CeleryTask/CeleryTaskGraph/CeleryTaskGraphGrid.tsx +++ b/frontend/src/components/CeleryTask/CeleryTaskGraph/CeleryTaskGraphGrid.tsx @@ -1,6 +1,6 @@ import './CeleryTaskGraph.style.scss'; -import { CeleryTaskData } from '../CeleryTaskDetail/CeleryTaskDetail'; +import { CaptureDataProps } from '../CeleryTaskDetail/CeleryTaskDetail'; import CeleryTaskGraph from './CeleryTaskGraph'; import { celeryActiveTasksWidgetData, @@ -16,7 +16,7 @@ export default function CeleryTaskGraphGrid({ onClick, queryEnabled, }: { - onClick: (task: CeleryTaskData) => void; + onClick: (task: CaptureDataProps) => void; queryEnabled: boolean; }): JSX.Element { const bottomWidgetData = [ @@ -25,14 +25,19 @@ export default function CeleryTaskGraphGrid({ celeryLatencyByWorkerWidgetData, ]; + const rightPanelTitle = [ + 'Tasks/s by worker', + 'Error% by worker', + 'Latency by worker', + ]; + return (
- +
@@ -41,17 +46,17 @@ export default function CeleryTaskGraphGrid({
- {bottomWidgetData.map((widgetData) => ( + {bottomWidgetData.map((widgetData, index) => ( ))}
diff --git a/frontend/src/components/CeleryTask/CeleryTaskGraph/CeleryTaskGraphUtils.ts b/frontend/src/components/CeleryTask/CeleryTaskGraph/CeleryTaskGraphUtils.ts index 131cb7dc6c8..bd9dafc4b4e 100644 --- a/frontend/src/components/CeleryTask/CeleryTaskGraph/CeleryTaskGraphUtils.ts +++ b/frontend/src/components/CeleryTask/CeleryTaskGraph/CeleryTaskGraphUtils.ts @@ -5,6 +5,7 @@ import { getWidgetQuery } from 'pages/MessagingQueues/MQDetails/MetricPage/Metri import { Widgets } from 'types/api/dashboard/getAll'; import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; import { DataSource } from 'types/common/queryBuilder'; +import { v4 as uuidv4 } from 'uuid'; // State Graphs export const celeryAllStateWidgetData = getWidgetQueryBuilder( @@ -491,6 +492,61 @@ export const celeryWorkerOnlineWidgetData = getWidgetQueryBuilder( }), ); +// Task Latency +export const celeryTaskLatencyWidgetData = (type: string): Widgets => + getWidgetQueryBuilder( + getWidgetQuery({ + title: 'Task Latency', + description: 'Represents the latency of task execution.', + queryData: [ + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'duration_nano--float64----true', + isColumn: true, + isJSON: false, + key: 'duration_nano', + type: '', + }, + aggregateOperator: type || 'p99', + dataSource: DataSource.TRACES, + disabled: false, + expression: 'A', + filters: { + items: [], + op: 'AND', + }, + functions: [], + groupBy: [ + { + dataType: DataTypes.String, + id: 'celery.task_name--string--tag--false', + isColumn: false, + isJSON: false, + key: 'celery.task_name', + type: 'tag', + }, + ], + having: [], + legend: '', + limit: null, + orderBy: [ + { + columnName: '#SIGNOZ_VALUE', + order: 'asc', + }, + ], + queryName: 'A', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'p99', + }, + ], + yAxisUnit: 'ns', + }), + ); + // Tables export const celerySlowestTasksTableWidgetData = getWidgetQueryBuilder( getWidgetQuery({ @@ -542,6 +598,7 @@ export const celerySlowestTasksTableWidgetData = getWidgetQueryBuilder( timeAggregation: 'avg', }, ], + columnUnits: { A: 'ns' }, }), ); @@ -609,6 +666,7 @@ export const celeryRetryTasksTableWidgetData = getWidgetQueryBuilder( timeAggregation: 'avg', }, ], + columnUnits: { A: 'ns' }, }), ); @@ -617,6 +675,7 @@ export const celeryFailedTasksTableWidgetData = getWidgetQueryBuilder( title: 'Top 10 tasks in FAILED state', description: 'Represents the top 10 tasks in failed state.', panelTypes: PANEL_TYPES.TABLE, + columnUnits: { A: 'ns' }, queryData: [ { aggregateAttribute: { @@ -743,15 +802,20 @@ export const celerySuccessTasksTableWidgetData = getWidgetQueryBuilder( timeAggregation: 'avg', }, ], + columnUnits: { A: 'ns' }, }), ); -// Task Latency -export const celeryTaskLatencyWidgetData = (type: string): Widgets => +export const celeryTimeSeriesTablesWidgetData = ( + entity: string, + value: string | number, + rightPanelTitle: string, +): Widgets => getWidgetQueryBuilder( getWidgetQuery({ - title: 'Task Latency', - description: 'Represents the latency of task execution.', + title: rightPanelTitle, + description: '', + panelTypes: PANEL_TYPES.TABLE, queryData: [ { aggregateAttribute: { @@ -762,12 +826,26 @@ export const celeryTaskLatencyWidgetData = (type: string): Widgets => key: 'duration_nano', type: '', }, - aggregateOperator: type || 'p99', + aggregateOperator: 'avg', dataSource: DataSource.TRACES, disabled: false, expression: 'A', filters: { - items: [], + items: [ + { + id: uuidv4(), + key: { + dataType: DataTypes.String, + id: `${entity}--string--tag--false`, + isColumn: false, + isJSON: false, + key: `${entity}`, + type: 'tag', + }, + op: '=', + value, + }, + ], op: 'AND', }, functions: [], @@ -784,19 +862,14 @@ export const celeryTaskLatencyWidgetData = (type: string): Widgets => having: [], legend: '', limit: null, - orderBy: [ - { - columnName: '#SIGNOZ_VALUE', - order: 'asc', - }, - ], + orderBy: [], queryName: 'A', reduceTo: 'avg', spaceAggregation: 'sum', stepInterval: 60, - timeAggregation: 'p99', + timeAggregation: 'avg', }, ], - yAxisUnit: 'ns', + columnUnits: { A: 'ns' }, }), ); diff --git a/frontend/src/components/CeleryTask/CeleryTaskGraph/CeleryTaskHistogram.tsx b/frontend/src/components/CeleryTask/CeleryTaskGraph/CeleryTaskHistogram.tsx index 0651bba95ec..a9efa42f2b9 100644 --- a/frontend/src/components/CeleryTask/CeleryTaskGraph/CeleryTaskHistogram.tsx +++ b/frontend/src/components/CeleryTask/CeleryTaskGraph/CeleryTaskHistogram.tsx @@ -12,7 +12,7 @@ import { useDispatch } from 'react-redux'; import { useHistory, useLocation } from 'react-router-dom'; import { UpdateTimeInterval } from 'store/actions'; -import { CeleryTaskData } from '../CeleryTaskDetail/CeleryTaskDetail'; +// import { CaptureDataProps } from '../CeleryTaskDetail/CeleryTaskDetail'; import { celeryAllStateWidgetData, celeryFailedStateWidgetData, @@ -25,10 +25,10 @@ import { } from './CeleryTaskStateGraphConfig'; function CeleryTaskHistogram({ - onClick, + // onClick, queryEnabled, }: { - onClick: (task: CeleryTaskData) => void; + // onClick?: (task: CaptureDataProps) => void; queryEnabled: boolean; }): JSX.Element { @@ -59,10 +59,6 @@ function CeleryTaskHistogram({ CeleryTaskState.All, ); - const getGraphData = (graphData: any): void => { - console.log('graphData', graphData); - }; - return ( { - console.log('clicked', arg); - onClick(arg as any); - }} - getGraphData={getGraphData} isQueryEnabled={queryEnabled} /> )} @@ -92,11 +83,6 @@ function CeleryTaskHistogram({ widget={celeryFailedStateWidgetData} headerMenuList={[...ViewMenuAction]} onDragSelect={onDragSelect} - onClickHandler={(arg): void => { - console.log('clicked', arg); - onClick(arg as any); - }} - getGraphData={getGraphData} isQueryEnabled={queryEnabled} /> )} @@ -105,11 +91,6 @@ function CeleryTaskHistogram({ widget={celeryRetryStateWidgetData} headerMenuList={[...ViewMenuAction]} onDragSelect={onDragSelect} - onClickHandler={(arg): void => { - console.log('clicked', arg); - onClick(arg as any); - }} - getGraphData={getGraphData} isQueryEnabled={queryEnabled} /> )} @@ -118,11 +99,6 @@ function CeleryTaskHistogram({ widget={celerySuccessStateWidgetData} headerMenuList={[...ViewMenuAction]} onDragSelect={onDragSelect} - onClickHandler={(arg): void => { - console.log('clicked', arg); - onClick(arg as any); - }} - getGraphData={getGraphData} isQueryEnabled={queryEnabled} /> )} diff --git a/frontend/src/components/CeleryTask/CeleryTaskGraph/CeleryTaskLatencyGraph.tsx b/frontend/src/components/CeleryTask/CeleryTaskGraph/CeleryTaskLatencyGraph.tsx index 45215adf7cd..e135a1cfffe 100644 --- a/frontend/src/components/CeleryTask/CeleryTaskGraph/CeleryTaskLatencyGraph.tsx +++ b/frontend/src/components/CeleryTask/CeleryTaskGraph/CeleryTaskLatencyGraph.tsx @@ -8,13 +8,17 @@ import GridCard from 'container/GridCardLayout/GridCard'; import { Card } from 'container/GridCardLayout/styles'; import { useIsDarkMode } from 'hooks/useDarkMode'; import useUrlQuery from 'hooks/useUrlQuery'; +import { getStartAndEndTimesInMilliseconds } from 'pages/MessagingQueues/MessagingQueuesUtils'; import { useCallback, useState } from 'react'; import { useDispatch } from 'react-redux'; import { useHistory, useLocation } from 'react-router-dom'; import { UpdateTimeInterval } from 'store/actions'; -import { CeleryTaskData } from '../CeleryTaskDetail/CeleryTaskDetail'; -import { celeryTaskLatencyWidgetData } from './CeleryTaskGraphUtils'; +import { CaptureDataProps } from '../CeleryTaskDetail/CeleryTaskDetail'; +import { + celeryTaskLatencyWidgetData, + celeryTimeSeriesTablesWidgetData, +} from './CeleryTaskGraphUtils'; interface TabData { label: string; @@ -31,8 +35,7 @@ function CeleryTaskLatencyGraph({ onClick, queryEnabled, }: { - onClick: (task: CeleryTaskData) => void; - + onClick: (task: CaptureDataProps) => void; queryEnabled: boolean; }): JSX.Element { const history = useHistory(); @@ -72,8 +75,30 @@ function CeleryTaskLatencyGraph({ [dispatch, history, pathname, urlQuery], ); - const getGraphData = (graphData: any): void => { - console.log('graphData', graphData); + const onGraphClick = ( + xValue: number, + _yValue: number, + _mouseX: number, + _mouseY: number, + data?: { + [key: string]: string; + }, + ): void => { + const { start, end } = getStartAndEndTimesInMilliseconds(xValue); + + // Extract entity and value from data + const [firstDataPoint] = Object.entries(data || {}); + const [entity, value] = (firstDataPoint || ([] as unknown)) as [ + string, + string, + ]; + + onClick?.({ + entity, + value, + timeRange: [start, end], + widgetData: celeryTimeSeriesTablesWidgetData(entity, value, 'Task Latency'), + }); }; return ( @@ -109,11 +134,7 @@ function CeleryTaskLatencyGraph({ widget={celeryTaskLatencyWidgetData(graphState)} headerMenuList={[...ViewMenuAction]} onDragSelect={onDragSelect} - onClickHandler={(arg): void => { - console.log('clicked', arg); - onClick(arg as any); - }} - getGraphData={getGraphData} + onClickHandler={onGraphClick} isQueryEnabled={queryEnabled} /> )} @@ -123,11 +144,7 @@ function CeleryTaskLatencyGraph({ widget={celeryTaskLatencyWidgetData(graphState)} headerMenuList={[...ViewMenuAction]} onDragSelect={onDragSelect} - onClickHandler={(arg): void => { - console.log('clicked', arg); - onClick(arg as any); - }} - getGraphData={getGraphData} + onClickHandler={onGraphClick} isQueryEnabled={queryEnabled} /> )} @@ -136,11 +153,7 @@ function CeleryTaskLatencyGraph({ widget={celeryTaskLatencyWidgetData(graphState)} headerMenuList={[...ViewMenuAction]} onDragSelect={onDragSelect} - onClickHandler={(arg): void => { - console.log('clicked', arg); - onClick(arg as any); - }} - getGraphData={getGraphData} + onClickHandler={onGraphClick} isQueryEnabled={queryEnabled} /> )} diff --git a/frontend/src/container/GridCardLayout/GridCard/WidgetGraphComponent.tsx b/frontend/src/container/GridCardLayout/GridCard/WidgetGraphComponent.tsx index 6cb35817491..19fc789b25e 100644 --- a/frontend/src/container/GridCardLayout/GridCard/WidgetGraphComponent.tsx +++ b/frontend/src/container/GridCardLayout/GridCard/WidgetGraphComponent.tsx @@ -45,6 +45,8 @@ function WidgetGraphComponent({ onClickHandler, onDragSelect, customTooltipElement, + openTracesButton, + onOpenTraceBtnClick, }: WidgetGraphComponentProps): JSX.Element { const [deleteModal, setDeleteModal] = useState(false); const [hovered, setHovered] = useState(false); @@ -333,6 +335,8 @@ function WidgetGraphComponent({ tableProcessedDataRef={tableProcessedDataRef} customTooltipElement={customTooltipElement} searchTerm={searchTerm} + openTracesButton={openTracesButton} + onOpenTraceBtnClick={onOpenTraceBtnClick} /> )} diff --git a/frontend/src/container/GridCardLayout/GridCard/index.tsx b/frontend/src/container/GridCardLayout/GridCard/index.tsx index 82fc498c430..4bb0694ea04 100644 --- a/frontend/src/container/GridCardLayout/GridCard/index.tsx +++ b/frontend/src/container/GridCardLayout/GridCard/index.tsx @@ -38,6 +38,8 @@ function GridCardGraph({ customTooltipElement, dataAvailable, getGraphData, + openTracesButton, + onOpenTraceBtnClick, }: GridCardGraphProps): JSX.Element { const dispatch = useDispatch(); const [errorMessage, setErrorMessage] = useState(); @@ -250,6 +252,8 @@ function GridCardGraph({ onClickHandler={onClickHandler} onDragSelect={onDragSelect} customTooltipElement={customTooltipElement} + openTracesButton={openTracesButton} + onOpenTraceBtnClick={onOpenTraceBtnClick} /> )} diff --git a/frontend/src/container/GridCardLayout/GridCard/types.ts b/frontend/src/container/GridCardLayout/GridCard/types.ts index 2c2071900f0..63ff2b566bc 100644 --- a/frontend/src/container/GridCardLayout/GridCard/types.ts +++ b/frontend/src/container/GridCardLayout/GridCard/types.ts @@ -1,5 +1,6 @@ import { ToggleGraphProps } from 'components/Graph/types'; import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults'; +import { RowData } from 'lib/query/createTableColumnsFromQuery'; import { OnClickPluginOpts } from 'lib/uPlotLib/plugins/onClickPlugin'; import { Dispatch, MutableRefObject, ReactNode, SetStateAction } from 'react'; import { UseQueryResult } from 'react-query'; @@ -32,6 +33,8 @@ export interface WidgetGraphComponentProps { onClickHandler?: OnClickPluginOpts['onClick']; onDragSelect: (start: number, end: number) => void; customTooltipElement?: HTMLDivElement; + openTracesButton?: boolean; + onOpenTraceBtnClick?: (record: RowData) => void; } export interface GridCardGraphProps { @@ -46,6 +49,8 @@ export interface GridCardGraphProps { customTooltipElement?: HTMLDivElement; dataAvailable?: (isDataAvailable: boolean) => void; getGraphData?: (graphData?: MetricRangePayloadProps['data']) => void; + openTracesButton?: boolean; + onOpenTraceBtnClick?: (record: RowData) => void; } export interface GetGraphVisibilityStateOnLegendClickProps { diff --git a/frontend/src/container/GridTableComponent/GridTableComponent.styles.scss b/frontend/src/container/GridTableComponent/GridTableComponent.styles.scss index 80491e991a2..bdc21e164d7 100644 --- a/frontend/src/container/GridTableComponent/GridTableComponent.styles.scss +++ b/frontend/src/container/GridTableComponent/GridTableComponent.styles.scss @@ -3,3 +3,14 @@ max-height: 500px; overflow-y: auto; } + +.open-traces-button { + font-size: 11px; + border-radius: 4px; + border: none; + padding: 6px 8px; + cursor: pointer; + display: flex; + gap: 6px; + align-items: center; +} diff --git a/frontend/src/container/GridTableComponent/index.tsx b/frontend/src/container/GridTableComponent/index.tsx index 63084be5f32..db3d72a9749 100644 --- a/frontend/src/container/GridTableComponent/index.tsx +++ b/frontend/src/container/GridTableComponent/index.tsx @@ -1,3 +1,4 @@ +/* eslint-disable sonarjs/no-duplicate-string */ import './GridTableComponent.styles.scss'; import { ExclamationCircleFilled } from '@ant-design/icons'; @@ -7,9 +8,11 @@ import { Events } from 'constants/events'; import { QueryTable } from 'container/QueryTable'; import { RowData } from 'lib/query/createTableColumnsFromQuery'; import { cloneDeep, get, isEmpty } from 'lodash-es'; +import { Compass } from 'lucide-react'; import LineClampedText from 'periscope/components/LineClampedText/LineClampedText'; import { memo, ReactNode, useCallback, useEffect, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; +import styled from 'styled-components'; import { eventEmitter } from 'utils/getEventEmitter'; import { WrapperStyled } from './styles'; @@ -20,6 +23,23 @@ import { TableData, } from './utils'; +export const HoverButtonWrapper = styled.div` + position: absolute; + right: 0; + top: 50%; + transform: translateY(-50%); + opacity: 0; + transition: opacity 0.2s; +`; + +const RelativeWrapper = styled.div` + position: relative; + + &:hover ${HoverButtonWrapper} { + opacity: 1; + } +`; + function GridTableComponent({ data, query, @@ -27,6 +47,8 @@ function GridTableComponent({ columnUnits, tableProcessedDataRef, sticky, + openTracesButton, + onOpenTraceBtnClick, ...props }: GridTableComponentProps): JSX.Element { const { t } = useTranslation(['valueGraph']); @@ -161,6 +183,42 @@ function GridTableComponent({ }, })); + const columnDataWithOpenTracesButton = useMemo( + () => + newColumnData.map((column, index) => ({ + ...column, + render: (text: string): JSX.Element => { + const LineClampedTextComponent = ( + + ); + if (index !== 0) { + return
{LineClampedTextComponent}
; + } + + return ( + + {LineClampedTextComponent} + + + + + ); + }, + })), + [newColumnData], + ); + useEffect(() => { eventEmitter.emit(Events.TABLE_COLUMNS_DATA, { columns: newColumnData, @@ -174,9 +232,18 @@ function GridTableComponent({ query={query} queryTableData={data} loading={false} - columns={newColumnData} + columns={openTracesButton ? columnDataWithOpenTracesButton : newColumnData} dataSource={dataSource} sticky={sticky} + onRow={ + openTracesButton + ? (record): React.HTMLAttributes => ({ + onClick: (): void => { + onOpenTraceBtnClick?.(record); + }, + }) + : undefined + } // eslint-disable-next-line react/jsx-props-no-spreading {...props} /> diff --git a/frontend/src/container/GridTableComponent/types.ts b/frontend/src/container/GridTableComponent/types.ts index 883e280b38f..7e930d51c38 100644 --- a/frontend/src/container/GridTableComponent/types.ts +++ b/frontend/src/container/GridTableComponent/types.ts @@ -15,6 +15,8 @@ export type GridTableComponentProps = { tableProcessedDataRef?: React.MutableRefObject; sticky?: TableProps['sticky']; searchTerm?: string; + openTracesButton?: boolean; + onOpenTraceBtnClick?: (record: RowData) => void; } & Pick & Omit, 'columns' | 'dataSource'>; diff --git a/frontend/src/container/MetricsApplication/MetricsApplication.factory.ts b/frontend/src/container/MetricsApplication/MetricsApplication.factory.ts index 672bc118124..15eff76f8b7 100644 --- a/frontend/src/container/MetricsApplication/MetricsApplication.factory.ts +++ b/frontend/src/container/MetricsApplication/MetricsApplication.factory.ts @@ -10,6 +10,7 @@ export const getWidgetQueryBuilder = ({ yAxisUnit = '', fillSpans = false, id, + columnUnits, }: GetWidgetQueryBuilderProps): Widgets => ({ description: '', id: id || v4(), @@ -26,4 +27,5 @@ export const getWidgetQueryBuilder = ({ selectedLogFields: [], selectedTracesFields: [], fillSpans, + columnUnits, }); diff --git a/frontend/src/container/MetricsApplication/types.ts b/frontend/src/container/MetricsApplication/types.ts index 4b4cc2f4f13..60e7f269341 100644 --- a/frontend/src/container/MetricsApplication/types.ts +++ b/frontend/src/container/MetricsApplication/types.ts @@ -11,6 +11,7 @@ export interface GetWidgetQueryBuilderProps { yAxisUnit?: Widgets['yAxisUnit']; id?: Widgets['id']; fillSpans?: Widgets['fillSpans']; + columnUnits?: Widgets['columnUnits']; } export interface NavigateToTraceProps { diff --git a/frontend/src/container/PanelWrapper/HistogramPanelWrapper.tsx b/frontend/src/container/PanelWrapper/HistogramPanelWrapper.tsx index a6c80b01bde..9f69a7eef95 100644 --- a/frontend/src/container/PanelWrapper/HistogramPanelWrapper.tsx +++ b/frontend/src/container/PanelWrapper/HistogramPanelWrapper.tsx @@ -5,6 +5,7 @@ import { getLocalStorageGraphVisibilityState } from 'container/GridCardLayout/Gr import { useIsDarkMode } from 'hooks/useDarkMode'; import { useResizeObserver } from 'hooks/useDimensions'; import { getUplotHistogramChartOptions } from 'lib/uPlotLib/getUplotHistogramChartOptions'; +import _noop from 'lodash-es/noop'; import { useDashboard } from 'providers/Dashboard/Dashboard'; import { useEffect, useMemo, useRef } from 'react'; @@ -18,6 +19,7 @@ function HistogramPanelWrapper({ graphVisibility, isFullViewMode, onToggleModelHandler, + onClickHandler, }: PanelWrapperProps): JSX.Element { const graphRef = useRef(null); const { toScrollWidgetId, setToScrollWidgetId } = useDashboard(); @@ -67,6 +69,7 @@ function HistogramPanelWrapper({ setGraphsVisibilityStates: setGraphVisibility, graphsVisibilityStates: graphVisibility, mergeAllQueries: widget.mergeAllActiveQueries, + onClickHandler: onClickHandler || _noop, }), [ containerDimensions, @@ -78,6 +81,7 @@ function HistogramPanelWrapper({ widget.id, widget.mergeAllActiveQueries, widget.panelTypes, + onClickHandler, ], ); diff --git a/frontend/src/container/PanelWrapper/PanelWrapper.tsx b/frontend/src/container/PanelWrapper/PanelWrapper.tsx index 2f5b35485ee..646cff6f439 100644 --- a/frontend/src/container/PanelWrapper/PanelWrapper.tsx +++ b/frontend/src/container/PanelWrapper/PanelWrapper.tsx @@ -17,6 +17,8 @@ function PanelWrapper({ tableProcessedDataRef, customTooltipElement, searchTerm, + openTracesButton, + onOpenTraceBtnClick, }: PanelWrapperProps): JSX.Element { const Component = PanelTypeVsPanelWrapper[ selectedGraph || widget.panelTypes @@ -41,6 +43,8 @@ function PanelWrapper({ tableProcessedDataRef={tableProcessedDataRef} customTooltipElement={customTooltipElement} searchTerm={searchTerm} + openTracesButton={openTracesButton} + onOpenTraceBtnClick={onOpenTraceBtnClick} /> ); } diff --git a/frontend/src/container/PanelWrapper/TablePanelWrapper.tsx b/frontend/src/container/PanelWrapper/TablePanelWrapper.tsx index c5222e8d538..58ddaef5a8c 100644 --- a/frontend/src/container/PanelWrapper/TablePanelWrapper.tsx +++ b/frontend/src/container/PanelWrapper/TablePanelWrapper.tsx @@ -9,6 +9,8 @@ function TablePanelWrapper({ queryResponse, tableProcessedDataRef, searchTerm, + openTracesButton, + onOpenTraceBtnClick, }: PanelWrapperProps): JSX.Element { const panelData = (queryResponse.data?.payload?.data?.result?.[0] as any)?.table || []; @@ -22,6 +24,8 @@ function TablePanelWrapper({ tableProcessedDataRef={tableProcessedDataRef} sticky={widget.panelTypes === PANEL_TYPES.TABLE} searchTerm={searchTerm} + openTracesButton={openTracesButton} + onOpenTraceBtnClick={onOpenTraceBtnClick} // eslint-disable-next-line react/jsx-props-no-spreading {...GRID_TABLE_CONFIG} /> diff --git a/frontend/src/container/PanelWrapper/panelWrapper.types.ts b/frontend/src/container/PanelWrapper/panelWrapper.types.ts index 4778ffdb976..9c2ca0d7da5 100644 --- a/frontend/src/container/PanelWrapper/panelWrapper.types.ts +++ b/frontend/src/container/PanelWrapper/panelWrapper.types.ts @@ -25,6 +25,8 @@ export type PanelWrapperProps = { tableProcessedDataRef?: React.MutableRefObject; searchTerm?: string; customTooltipElement?: HTMLDivElement; + openTracesButton?: boolean; + onOpenTraceBtnClick?: (record: RowData) => void; }; export type TooltipData = { diff --git a/frontend/src/lib/uPlotLib/getUplotHistogramChartOptions.ts b/frontend/src/lib/uPlotLib/getUplotHistogramChartOptions.ts index 2ff0f3051e9..81750ff857f 100644 --- a/frontend/src/lib/uPlotLib/getUplotHistogramChartOptions.ts +++ b/frontend/src/lib/uPlotLib/getUplotHistogramChartOptions.ts @@ -4,12 +4,14 @@ import { themeColors } from 'constants/theme'; import { saveLegendEntriesToLocalStorage } from 'container/GridCardLayout/GridCard/FullView/utils'; import { Dimensions } from 'hooks/useDimensions'; import getLabelName from 'lib/getLabelName'; +import _noop from 'lodash-es/noop'; import { Dispatch, SetStateAction } from 'react'; import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; import { Query } from 'types/api/queryBuilder/queryBuilderData'; import { QueryData } from 'types/api/widgets/getQuery'; import uPlot from 'uplot'; +import onClickPlugin, { OnClickPluginOpts } from './plugins/onClickPlugin'; import tooltipPlugin from './plugins/tooltipPlugin'; import { drawStyles } from './utils/constants'; import { generateColor } from './utils/generateColor'; @@ -27,6 +29,7 @@ type GetUplotHistogramChartOptionsProps = { graphsVisibilityStates?: boolean[]; setGraphsVisibilityStates?: Dispatch>; mergeAllQueries?: boolean; + onClickHandler?: OnClickPluginOpts['onClick']; }; type GetHistogramSeriesProps = { @@ -119,6 +122,7 @@ export const getUplotHistogramChartOptions = ({ graphsVisibilityStates, setGraphsVisibilityStates, mergeAllQueries, + onClickHandler = _noop, }: GetUplotHistogramChartOptionsProps): uPlot.Options => ({ id, @@ -140,6 +144,10 @@ export const getUplotHistogramChartOptions = ({ isMergedSeries: mergeAllQueries, isDarkMode, }), + onClickPlugin({ + onClick: onClickHandler, + apiResponse, + }), ], scales: { x: { diff --git a/frontend/src/pages/Celery/CeleryTask/CeleryTask.tsx b/frontend/src/pages/Celery/CeleryTask/CeleryTask.tsx index 3c3008fb017..b33040ced18 100644 --- a/frontend/src/pages/Celery/CeleryTask/CeleryTask.tsx +++ b/frontend/src/pages/Celery/CeleryTask/CeleryTask.tsx @@ -2,19 +2,19 @@ import './CeleryTask.styles.scss'; import CeleryTaskConfigOptions from 'components/CeleryTask/CeleryTaskConfigOptions/CeleryTaskConfigOptions'; import CeleryTaskDetail, { - CeleryTaskData, + CaptureDataProps, } from 'components/CeleryTask/CeleryTaskDetail/CeleryTaskDetail'; import CeleryTaskGraphGrid from 'components/CeleryTask/CeleryTaskGraph/CeleryTaskGraphGrid'; -import { celerySlowestTasksTableWidgetData } from 'components/CeleryTask/CeleryTaskGraph/CeleryTaskGraphUtils'; import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2'; import { ListMinus } from 'lucide-react'; import { useState } from 'react'; export default function CeleryTask(): JSX.Element { - const [task, setTask] = useState(null); + const [task, setTask] = useState(null); - const onTaskClick = (task: CeleryTaskData): void => { - setTask(task); + const onTaskClick = (captureData: CaptureDataProps): void => { + setTask(captureData); + console.log(captureData); }; return ( @@ -36,13 +36,8 @@ export default function CeleryTask(): JSX.Element { onClose={(): void => { setTask(null); }} - mainTitle="Celery Task" - widgetData={celerySlowestTasksTableWidgetData} - taskData={{ - entity: 'task', - value: 'task', - timeRange: [1737569089000, 1737570889000], - }} + widgetData={task.widgetData} + taskData={task} drawerOpen={!!task} /> )} diff --git a/frontend/src/pages/MessagingQueues/MQDetails/MetricPage/MetricPageUtil.ts b/frontend/src/pages/MessagingQueues/MQDetails/MetricPage/MetricPageUtil.ts index 89bfaaa3eb2..f2f84c0f882 100644 --- a/frontend/src/pages/MessagingQueues/MQDetails/MetricPage/MetricPageUtil.ts +++ b/frontend/src/pages/MessagingQueues/MQDetails/MetricPage/MetricPageUtil.ts @@ -14,11 +14,13 @@ interface GetWidgetQueryProps { queryData: IBuilderQuery[]; panelTypes?: PANEL_TYPES; yAxisUnit?: string; + columnUnits?: Record; } interface GetWidgetQueryPropsReturn extends GetWidgetQueryBuilderProps { description?: string; nullZeroValues: string; + columnUnits?: Record; } export const getWidgetQueryBuilder = ({ @@ -51,7 +53,7 @@ export const getWidgetQueryBuilder = ({ export function getWidgetQuery( props: GetWidgetQueryProps, ): GetWidgetQueryPropsReturn { - const { title, description, panelTypes, yAxisUnit } = props; + const { title, description, panelTypes, yAxisUnit, columnUnits } = props; return { title, yAxisUnit: yAxisUnit || 'none', @@ -59,6 +61,7 @@ export function getWidgetQuery( fillSpans: false, description, nullZeroValues: 'zero', + columnUnits, query: { queryType: EQueryType.QUERY_BUILDER, promql: [],