diff --git a/frontend/src/components/CeleryTask/CeleryTaskDetail/CeleryTaskDetail.style.scss b/frontend/src/components/CeleryTask/CeleryTaskDetail/CeleryTaskDetail.style.scss index 28168f71c6..7275b00cfb 100644 --- a/frontend/src/components/CeleryTask/CeleryTaskDetail/CeleryTaskDetail.style.scss +++ b/frontend/src/components/CeleryTask/CeleryTaskDetail/CeleryTaskDetail.style.scss @@ -1,5 +1,74 @@ .celery-task-detail-drawer { + .ant-drawer-wrapper-body { + background: var(--bg-ink-500); + border: 1px solid var(--bg-ink-300); + } + .ant-drawer-body { padding: 0px; + + .ant-card { + border: none; + .ant-card-body { + height: 100%; + background: var(--bg-ink-500); + + .ant-table { + background: var(--bg-ink-500); + } + } + } + } + + .ant-drawer-header { + border-bottom: 1px solid var(--bg-ink-300); + .ant-drawer-header-title { + .ant-drawer-close { + position: absolute; + right: 0; + } + + button > svg { + color: var(--bg-vanilla-100); + } + + .ant-drawer-title { + display: flex; + flex-direction: column; + align-items: flex-start; + + .title { + color: var(--bg-vanilla-100); + font-family: Inter; + font-size: 18px; + font-style: normal; + font-weight: 600; + line-height: 18px; + letter-spacing: -0.45px; + } + + .subtitle { + color: var(--bg-vanilla-400); + font-family: Inter; + font-size: 12px; + font-style: normal; + font-weight: 400; + line-height: 20px; + } + } + } + } + + .ant-drawer-footer { + border-top: 1px solid var(--bg-ink-300); + + .footer-text { + color: var(--bg-vanilla-400); + font-family: Inter; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 20px; + } } } diff --git a/frontend/src/components/CeleryTask/CeleryTaskDetail/CeleryTaskDetail.tsx b/frontend/src/components/CeleryTask/CeleryTaskDetail/CeleryTaskDetail.tsx index 253b12d118..7d3e47e4e4 100644 --- a/frontend/src/components/CeleryTask/CeleryTaskDetail/CeleryTaskDetail.tsx +++ b/frontend/src/components/CeleryTask/CeleryTaskDetail/CeleryTaskDetail.tsx @@ -2,43 +2,126 @@ import './CeleryTaskDetail.style.scss'; import { Color, Spacing } from '@signozhq/design-tokens'; import { Divider, Drawer, Typography } from 'antd'; +import { QueryParams } from 'constants/query'; +import dayjs from 'dayjs'; import { useIsDarkMode } from 'hooks/useDarkMode'; +import useUrlQuery from 'hooks/useUrlQuery'; import { X } from 'lucide-react'; +import { useEffect, 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 { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; +import { GlobalReducer } from 'types/reducer/globalTime'; import CeleryTaskGraph from '../CeleryTaskGraph/CeleryTaskGraph'; -import { celerySlowestTasksTableWidgetData } from '../CeleryTaskGraph/CeleryTaskGraphUtils'; export type CeleryTaskData = { - taskName: string; - taskId: string; - taskStatus: string; - taskCreatedAt: string; - taskCompletedAt: string; + entity: string; + value: string | number; + timeRange: [number, number]; }; export type CeleryTaskDetailProps = { - task: CeleryTaskData | null; onClose: () => void; + mainTitle: string; + widgetData: Widgets; + taskData: CeleryTaskData; + drawerOpen: boolean; }; export default function CeleryTaskDetail({ - task, + mainTitle, + widgetData, + taskData, onClose, + drawerOpen, }: CeleryTaskDetailProps): JSX.Element { const isDarkMode = useIsDarkMode(); + const shouldShowDrawer = + !!taskData.entity && !!taskData.timeRange[0] && drawerOpen; + + const formatTimestamp = (timestamp: number): string => + dayjs(timestamp * 1000).format('MM-DD-YYYY hh:mm A'); + + const [totalTask, setTotalTask] = useState(0); + + const getGraphData = (graphData?: MetricRangePayloadProps['data']): void => { + console.log(graphData); + setTotalTask((graphData?.result?.[0] as any)?.table?.rows.length); + }; + + // set time range + const { minTime, maxTime, selectedTime } = useSelector< + AppState, + GlobalReducer + >((state) => state.globalTime); + + const startTime = taskData.timeRange[0]; + const endTime = taskData.timeRange[1]; + + const urlQuery = useUrlQuery(); + const location = useLocation(); + const history = useHistory(); + const dispatch = useDispatch(); + + useEffect(() => { + urlQuery.delete(QueryParams.relativeTime); + urlQuery.set(QueryParams.startTime, startTime.toString()); + urlQuery.set(QueryParams.endTime, endTime.toString()); + + const generatedUrl = `${location.pathname}?${urlQuery.toString()}`; + history.replace(generatedUrl); + + if (startTime !== endTime) { + dispatch(UpdateTimeInterval('custom', [startTime, endTime])); + } + + return (): void => { + urlQuery.delete(QueryParams.relativeTime); + urlQuery.delete(QueryParams.startTime); + urlQuery.delete(QueryParams.endTime); + + if (selectedTime !== 'custom') { + dispatch(UpdateTimeInterval(selectedTime)); + urlQuery.set(QueryParams.relativeTime, selectedTime); + } else { + dispatch(UpdateTimeInterval('custom', [minTime, maxTime])); + urlQuery.set(QueryParams.startTime, minTime.toString()); + urlQuery.set(QueryParams.endTime, maxTime.toString()); + } + + const generatedUrl = `${location.pathname}?${urlQuery.toString()}`; + history.replace(generatedUrl); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + return ( - - {task?.taskName} - +
+ {mainTitle} +
+ + {`${formatTimestamp(taskData.timeRange[0])} ${ + taskData.timeRange[1] + ? `- ${formatTimestamp(taskData.timeRange[1])}` + : '' + }`} + + + task-details +
+
} placement="right" onClose={onClose} - open={!!task} + open={shouldShowDrawer} style={{ overscrollBehavior: 'contain', background: isDarkMode ? Color.BG_INK_400 : Color.BG_VANILLA_100, @@ -46,13 +129,16 @@ export default function CeleryTaskDetail({ className="celery-task-detail-drawer" destroyOnClose closeIcon={} + footer={ + {`Total Task: ${totalTask}`} + } > - {task && ( - {}} - /> - )} + {}} + getGraphData={getGraphData} + queryEnabled + />
); } diff --git a/frontend/src/components/CeleryTask/CeleryTaskGraph/CeleryTaskGraph.style.scss b/frontend/src/components/CeleryTask/CeleryTaskGraph/CeleryTaskGraph.style.scss index c2851fe269..9d86eb5e35 100644 --- a/frontend/src/components/CeleryTask/CeleryTaskGraph/CeleryTaskGraph.style.scss +++ b/frontend/src/components/CeleryTask/CeleryTaskGraph/CeleryTaskGraph.style.scss @@ -8,7 +8,7 @@ .celery-task-graph-grid { display: grid; - grid-template-columns: repeat(2, 1fr); + grid-template-columns: 60% 40%; align-items: flex-start; gap: 10px; width: 100%; @@ -23,8 +23,29 @@ height: calc(100% - 24px); .widget-graph-container { - &.bar { - height: calc(100% - 116px); + &.histogram { + height: calc(100% - 85px); + } + } + } + } + + .celery-task-graph-histogram { + height: 380px !important; + width: 100%; + box-sizing: border-box; + + .celery-task-graph-grid-content { + padding: 6px; + height: 100%; + } + + .ant-card-body { + height: calc(100% - 18px); + + .widget-graph-container { + &.histogram { + height: calc(100% - 85px); } } } diff --git a/frontend/src/components/CeleryTask/CeleryTaskGraph/CeleryTaskGraph.tsx b/frontend/src/components/CeleryTask/CeleryTaskGraph/CeleryTaskGraph.tsx index 754015e351..09a84a9baf 100644 --- a/frontend/src/components/CeleryTask/CeleryTaskGraph/CeleryTaskGraph.tsx +++ b/frontend/src/components/CeleryTask/CeleryTaskGraph/CeleryTaskGraph.tsx @@ -10,16 +10,20 @@ import { useDispatch } from 'react-redux'; import { useHistory, useLocation } from 'react-router-dom'; 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 { CeleryTaskGraphStates } from './CeleryTaskGraphStates'; function CeleryTaskGraph({ widgetData, onClick, + getGraphData, + queryEnabled, }: { widgetData: Widgets; onClick: (task: CeleryTaskData) => void; + getGraphData?: (graphData?: MetricRangePayloadProps['data']) => void; + queryEnabled: boolean; }): JSX.Element { const history = useHistory(); const { pathname } = useLocation(); @@ -50,7 +54,6 @@ function CeleryTaskGraph({ $panelType={PANEL_TYPES.TIME_SERIES} className="celery-task-graph" > - {widgetData.title === 'All' && } ); } +CeleryTaskGraph.defaultProps = { + getGraphData: undefined, +}; + export default CeleryTaskGraph; diff --git a/frontend/src/components/CeleryTask/CeleryTaskGraph/CeleryTaskGraphGrid.tsx b/frontend/src/components/CeleryTask/CeleryTaskGraph/CeleryTaskGraphGrid.tsx index 803880e7ce..1ad8a299e4 100644 --- a/frontend/src/components/CeleryTask/CeleryTaskGraph/CeleryTaskGraphGrid.tsx +++ b/frontend/src/components/CeleryTask/CeleryTaskGraph/CeleryTaskGraphGrid.tsx @@ -4,26 +4,21 @@ import { CeleryTaskData } from '../CeleryTaskDetail/CeleryTaskDetail'; import CeleryTaskGraph from './CeleryTaskGraph'; import { celeryActiveTasksWidgetData, - celeryAllStateWidgetData, celeryErrorByWorkerWidgetData, celeryLatencyByWorkerWidgetData, celeryTaskLatencyWidgetData, celeryTasksByWorkerWidgetData, celeryWorkerOnlineWidgetData, } from './CeleryTaskGraphUtils'; +import CeleryTaskHistogram from './CeleryTaskHistogram'; export default function CeleryTaskGraphGrid({ onClick, + queryEnabled, }: { onClick: (task: CeleryTaskData) => void; + queryEnabled: boolean; }): JSX.Element { - const widgetData = [ - celeryActiveTasksWidgetData, - celeryWorkerOnlineWidgetData, - celeryAllStateWidgetData, - celeryTaskLatencyWidgetData, - ]; - const bottomWidgetData = [ celeryTasksByWorkerWidgetData, celeryErrorByWorkerWidgetData, @@ -33,13 +28,36 @@ export default function CeleryTaskGraphGrid({ return (
- {widgetData.map((widget) => ( - - ))} + + +
+
+ +
- {bottomWidgetData.map((widget) => ( - + {bottomWidgetData.map((widgetData) => ( + ))}
diff --git a/frontend/src/components/CeleryTask/CeleryTaskGraph/CeleryTaskGraphStates.tsx b/frontend/src/components/CeleryTask/CeleryTaskGraph/CeleryTaskGraphStates.tsx deleted file mode 100644 index dadfb82f3c..0000000000 --- a/frontend/src/components/CeleryTask/CeleryTaskGraph/CeleryTaskGraphStates.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import './CeleryTaskGraph.style.scss'; - -import { Col, Row } from 'antd'; -import { useState } from 'react'; - -interface TabData { - label: string; - value: number; - key: string; -} - -function CeleryTaskGraphStates(): JSX.Element { - const [selectedTab, setSelectedTab] = useState('all'); - - const tabs: TabData[] = [ - { label: 'All Tasks', value: 1097, key: 'all' }, - { label: 'Failed', value: 11, key: 'failed' }, - { label: 'Pending', value: 59, key: 'pending' }, - { label: 'Successful', value: 1027, key: 'successful' }, - ]; - - const handleTabClick = (key: string): void => { - setSelectedTab(key); - }; - - return ( - - {tabs.map((tab, index) => ( - handleTabClick(tab.key)} - className={`celery-task-states__tab ${ - tab.key === selectedTab ? 'celery-task-states__tab--selected' : '' - }`} - data-last-tab={index === tabs.length - 1} - > -
-
{tab.label}
-
-
-
{tab.value}
-
- {tab.key === selectedTab && ( -
- )} - - ))} - - ); -} - -export { CeleryTaskGraphStates }; diff --git a/frontend/src/components/CeleryTask/CeleryTaskGraph/CeleryTaskGraphUtils.ts b/frontend/src/components/CeleryTask/CeleryTaskGraph/CeleryTaskGraphUtils.ts index b10cd8c60e..5d275475c6 100644 --- a/frontend/src/components/CeleryTask/CeleryTaskGraph/CeleryTaskGraphUtils.ts +++ b/frontend/src/components/CeleryTask/CeleryTaskGraph/CeleryTaskGraphUtils.ts @@ -1,10 +1,14 @@ /* eslint-disable sonarjs/no-duplicate-string */ import { PANEL_TYPES } from 'constants/queryBuilder'; import { getWidgetQueryBuilder } from 'container/MetricsApplication/MetricsApplication.factory'; +// import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults'; import { getWidgetQuery } from 'pages/MessagingQueues/MQDetails/MetricPage/MetricPageUtil'; import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; +// import { EQueryType } from 'types/common/dashboard'; import { DataSource } from 'types/common/queryBuilder'; +// import { v4 as uuidv4 } from 'uuid'; +// State Graphs export const celeryAllStateWidgetData = getWidgetQueryBuilder( getWidgetQuery({ queryData: [ @@ -64,7 +68,192 @@ export const celeryAllStateWidgetData = getWidgetQueryBuilder( title: 'All', description: 'Represents all states of task, including success, failed, and retry.', - panelTypes: PANEL_TYPES.BAR, // todo-sagar: ask shivanshu if BAR or Histogram + panelTypes: PANEL_TYPES.HISTOGRAM, + }), +); + +export const celeryRetryStateWidgetData = getWidgetQueryBuilder( + getWidgetQuery({ + title: 'Retry', + description: 'Represents the number of retry tasks.', + panelTypes: PANEL_TYPES.HISTOGRAM, + queryData: [ + { + aggregateAttribute: { + dataType: DataTypes.String, + id: '------false', + isColumn: false, + key: '', + type: '', + }, + aggregateOperator: 'count', + dataSource: DataSource.TRACES, + disabled: false, + expression: 'A', + filters: { + items: [ + { + id: '6d97eed3', + key: { + dataType: DataTypes.String, + id: 'celery.state--string--tag--false', + isColumn: false, + isJSON: false, + key: 'celery.state', + type: 'tag', + }, + op: '=', + value: 'RETRY', + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [ + { + dataType: DataTypes.String, + id: 'celery.hostname--string--tag--false', + isColumn: false, + isJSON: false, + key: 'celery.hostname', + type: 'tag', + }, + ], + having: [], + legend: '', + limit: null, + orderBy: [], + queryName: 'A', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'count', + }, + ], + }), +); + +export const celeryFailedStateWidgetData = getWidgetQueryBuilder( + getWidgetQuery({ + title: 'Failed', + description: 'Represents the number of failed tasks.', + panelTypes: PANEL_TYPES.HISTOGRAM, + queryData: [ + { + aggregateAttribute: { + dataType: DataTypes.String, + id: '------false', + isColumn: false, + isJSON: false, + key: '', + type: '', + }, + aggregateOperator: 'count', + dataSource: DataSource.TRACES, + disabled: false, + expression: 'A', + filters: { + items: [ + { + id: '5983eae2', + key: { + dataType: DataTypes.String, + id: 'celery.state--string--tag--false', + isColumn: false, + isJSON: false, + key: 'celery.state', + type: 'tag', + }, + op: '=', + value: 'FAILURE', + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [ + { + dataType: DataTypes.String, + id: 'celery.hostname--string--tag--false', + isColumn: false, + isJSON: false, + key: 'celery.hostname', + type: 'tag', + }, + ], + having: [], + legend: '', + limit: null, + orderBy: [], + queryName: 'A', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'rate', + }, + ], + }), +); + +export const celerySuccessStateWidgetData = getWidgetQueryBuilder( + getWidgetQuery({ + title: 'Success', + description: 'Represents the number of successful tasks.', + panelTypes: PANEL_TYPES.HISTOGRAM, + queryData: [ + { + aggregateAttribute: { + dataType: DataTypes.String, + id: '------false', + isColumn: false, + isJSON: false, + key: '', + type: '', + }, + aggregateOperator: 'count', + dataSource: DataSource.TRACES, + disabled: false, + expression: 'A', + filters: { + items: [ + { + id: '000c5a93', + key: { + dataType: DataTypes.String, + id: 'celery.state--string--tag--false', + isColumn: false, + isJSON: false, + key: 'celery.state', + type: 'tag', + }, + op: '=', + value: 'SUCCESS', + }, + ], + op: 'AND', + }, + functions: [], + groupBy: [ + { + dataType: DataTypes.String, + id: 'celery.hostname--string--tag--false', + isColumn: false, + isJSON: false, + key: 'celery.hostname', + type: 'tag', + }, + ], + having: [], + legend: '', + limit: null, + orderBy: [], + queryName: 'A', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'rate', + }, + ], }), ); @@ -408,3 +597,228 @@ export const celerySlowestTasksTableWidgetData = getWidgetQueryBuilder( ], }), ); + +export const celeryRetryTasksTableWidgetData = getWidgetQueryBuilder( + getWidgetQuery({ + title: 'Top 10 tasks in retry state', + description: 'Represents the top 10 tasks in retry state.', + panelTypes: PANEL_TYPES.TABLE, + queryData: [ + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'duration_nano--float64----true', + isColumn: true, + isJSON: false, + key: 'duration_nano', + type: '', + }, + aggregateOperator: 'avg', + dataSource: DataSource.TRACES, + disabled: false, + expression: 'A', + filters: { + items: [ + { + id: '9e09c9ed', + key: { + dataType: DataTypes.String, + id: 'celery.state--string--tag--false', + isColumn: false, + isJSON: false, + key: 'celery.state', + type: 'tag', + }, + op: '=', + value: 'RETRY', + }, + ], + 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: 10, + orderBy: [ + { + columnName: '#SIGNOZ_VALUE', + order: 'desc', + }, + ], + queryName: 'A', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'avg', + }, + ], + }), +); + +export const celeryFailedTasksTableWidgetData = getWidgetQueryBuilder( + getWidgetQuery({ + title: 'Top 10 tasks in FAILED state', + description: 'Represents the top 10 tasks in failed state.', + panelTypes: PANEL_TYPES.TABLE, + queryData: [ + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'duration_nano--float64----true', + isColumn: true, + isJSON: false, + key: 'duration_nano', + type: '', + }, + aggregateOperator: 'avg', + dataSource: DataSource.TRACES, + disabled: false, + expression: 'A', + filters: { + items: [ + { + id: '2330f906', + key: { + dataType: DataTypes.String, + id: 'celery.state--string--tag--false', + isColumn: false, + isJSON: false, + key: 'celery.state', + type: 'tag', + }, + op: '=', + value: 'FAILURE', + }, + ], + 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: 'desc', + }, + ], + queryName: 'A', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'avg', + }, + ], + }), +); + +export const celerySuccessTasksTableWidgetData = getWidgetQueryBuilder( + getWidgetQuery({ + title: 'Top 10 tasks in SUCCESS state', + description: 'Represents the top 10 tasks in success state.', + panelTypes: PANEL_TYPES.TABLE, + queryData: [ + { + aggregateAttribute: { + dataType: DataTypes.Float64, + id: 'duration_nano--float64----true', + isColumn: true, + isJSON: false, + key: 'duration_nano', + type: '', + }, + aggregateOperator: 'avg', + dataSource: DataSource.TRACES, + disabled: false, + expression: 'A', + filters: { + items: [ + { + id: 'ec3df7b7', + key: { + dataType: DataTypes.String, + id: 'celery.state--string--tag--false', + isColumn: false, + isJSON: false, + key: 'celery.state', + type: 'tag', + }, + op: '=', + value: 'SUCCESS', + }, + ], + 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: 'desc', + }, + ], + queryName: 'A', + reduceTo: 'avg', + spaceAggregation: 'sum', + stepInterval: 60, + timeAggregation: 'avg', + }, + ], + }), +); + +// export const getCeleryTaskStateQueryPayload = ({ +// start, +// end, +// }: { +// start: number; +// end: number; +// }): GetQueryResultsProps[] => { +// const widgetData = [ +// celeryRetryTasksTableWidgetData, +// celeryFailedTasksTableWidgetData, +// celerySuccessTasksTableWidgetData, +// ]; + +// return widgetData.map((widget) => ({ +// start, +// end, +// graphType: PANEL_TYPES.TABLE, +// query: widget.query, +// selectedTime: 'GLOBAL_TIME', +// formatForWeb: true, +// variables: {}, +// })); +// }; diff --git a/frontend/src/components/CeleryTask/CeleryTaskGraph/CeleryTaskHistogram.tsx b/frontend/src/components/CeleryTask/CeleryTaskGraph/CeleryTaskHistogram.tsx new file mode 100644 index 0000000000..1b1537de8f --- /dev/null +++ b/frontend/src/components/CeleryTask/CeleryTaskGraph/CeleryTaskHistogram.tsx @@ -0,0 +1,132 @@ +import { QueryParams } from 'constants/query'; +import { PANEL_TYPES } from 'constants/queryBuilder'; +import { ViewMenuAction } from 'container/GridCardLayout/config'; +import GridCard from 'container/GridCardLayout/GridCard'; +import { Card } from 'container/GridCardLayout/styles'; +import { useIsDarkMode } from 'hooks/useDarkMode'; +import useUrlQuery from 'hooks/useUrlQuery'; +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 { + celeryAllStateWidgetData, + celeryFailedStateWidgetData, + celeryRetryStateWidgetData, + celerySuccessStateWidgetData, +} from './CeleryTaskGraphUtils'; +import { + CeleryTaskState, + CeleryTaskStateGraphConfig, +} from './CeleryTaskStateGraphConfig'; + +function CeleryTaskHistogram({ + onClick, + queryEnabled, +}: { + onClick: (task: CeleryTaskData) => void; + + queryEnabled: boolean; +}): JSX.Element { + const history = useHistory(); + const { pathname } = useLocation(); + const dispatch = useDispatch(); + const urlQuery = useUrlQuery(); + const isDarkMode = useIsDarkMode(); + + 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 [histogramState, setHistogramState] = useState( + CeleryTaskState.All, + ); + + const getGraphData = (graphData: any): void => { + console.log('graphData', graphData); + }; + + return ( + + +
+ {histogramState === CeleryTaskState.All && ( + { + console.log('clicked', arg); + onClick(arg as any); + }} + getGraphData={getGraphData} + isQueryEnabled={queryEnabled} + /> + )} + {histogramState === CeleryTaskState.Failed && ( + { + console.log('clicked', arg); + onClick(arg as any); + }} + getGraphData={getGraphData} + isQueryEnabled={queryEnabled} + /> + )} + {histogramState === CeleryTaskState.Retry && ( + { + console.log('clicked', arg); + onClick(arg as any); + }} + getGraphData={getGraphData} + isQueryEnabled={queryEnabled} + /> + )} + {histogramState === CeleryTaskState.Successful && ( + { + console.log('clicked', arg); + onClick(arg as any); + }} + getGraphData={getGraphData} + isQueryEnabled={queryEnabled} + /> + )} +
+
+ ); +} + +export default CeleryTaskHistogram; diff --git a/frontend/src/components/CeleryTask/CeleryTaskGraph/CeleryTaskStateGraphConfig.tsx b/frontend/src/components/CeleryTask/CeleryTaskGraph/CeleryTaskStateGraphConfig.tsx new file mode 100644 index 0000000000..33efc72ec1 --- /dev/null +++ b/frontend/src/components/CeleryTask/CeleryTaskGraph/CeleryTaskStateGraphConfig.tsx @@ -0,0 +1,96 @@ +import './CeleryTaskGraph.style.scss'; + +import { Col, Row } from 'antd'; +// import { ENTITY_VERSION_V4 } from 'constants/app'; +// import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults'; +import { Dispatch, SetStateAction } 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 { getCeleryTaskStateQueryPayload } from './CeleryTaskGraphUtils'; + +interface TabData { + label: string; + value: number; + key: string; +} + +export enum CeleryTaskState { + All = 'all', + Failed = 'failed', + Retry = 'retry', + Successful = 'successful', +} + +function CeleryTaskStateGraphConfig({ + histogramState, + setHistogramState, +}: { + setHistogramState: Dispatch>; + histogramState: CeleryTaskState; +}): JSX.Element { + const tabs: TabData[] = [ + { label: 'All Tasks', value: 1097, key: CeleryTaskState.All }, + { label: 'Failed', value: 11, key: CeleryTaskState.Failed }, + { label: 'Retry', value: 59, key: CeleryTaskState.Retry }, + { label: 'Successful', value: 1027, key: CeleryTaskState.Successful }, + ]; + + // const { maxTime, minTime } = useSelector( + // (state) => state.globalTime, + // ); + + const handleTabClick = (key: CeleryTaskState): void => { + setHistogramState(key as CeleryTaskState); + }; + + // // get task count from api + // const queryPayloads = useMemo( + // () => + // getCeleryTaskStateQueryPayload({ + // start: Math.floor(minTime / 1000000000), + // end: Math.floor(maxTime / 1000000000), + // }), + // [minTime, maxTime], + // ); + + // const queries = useQueries( + // queryPayloads.map((payload) => ({ + // queryKey: ['host-metrics', payload, ENTITY_VERSION_V4, 'HOST'], + // queryFn: (): Promise> => + // GetMetricQueryRange(payload, ENTITY_VERSION_V4), + // enabled: !!payload, + // })), + // ); + + return ( + + {tabs.map((tab, index) => ( + handleTabClick(tab.key as CeleryTaskState)} + className={`celery-task-states__tab ${ + tab.key === histogramState ? 'celery-task-states__tab--selected' : '' + }`} + data-last-tab={index === tabs.length - 1} + > +
+
{tab.label}
+
+ {/*
+
{tab.value}
+
*/} + {tab.key === histogramState && ( +
+ )} + + ))} + + ); +} + +export { CeleryTaskStateGraphConfig }; diff --git a/frontend/src/container/GridCardLayout/GridCard/index.tsx b/frontend/src/container/GridCardLayout/GridCard/index.tsx index 09b6b65e1a..82fc498c43 100644 --- a/frontend/src/container/GridCardLayout/GridCard/index.tsx +++ b/frontend/src/container/GridCardLayout/GridCard/index.tsx @@ -37,6 +37,7 @@ function GridCardGraph({ onDragSelect, customTooltipElement, dataAvailable, + getGraphData, }: GridCardGraphProps): JSX.Element { const dispatch = useDispatch(); const [errorMessage, setErrorMessage] = useState(); @@ -209,6 +210,7 @@ function GridCardGraph({ dataAvailable?.( isDataAvailableByPanelType(data?.payload?.data, widget?.panelTypes), ); + getGraphData?.(data?.payload?.data); setDashboardQueryRangeCalled(true); }, }, diff --git a/frontend/src/container/GridCardLayout/GridCard/types.ts b/frontend/src/container/GridCardLayout/GridCard/types.ts index 05d3368096..2c2071900f 100644 --- a/frontend/src/container/GridCardLayout/GridCard/types.ts +++ b/frontend/src/container/GridCardLayout/GridCard/types.ts @@ -45,6 +45,7 @@ export interface GridCardGraphProps { onDragSelect: (start: number, end: number) => void; customTooltipElement?: HTMLDivElement; dataAvailable?: (isDataAvailable: boolean) => void; + getGraphData?: (graphData?: MetricRangePayloadProps['data']) => void; } export interface GetGraphVisibilityStateOnLegendClickProps { diff --git a/frontend/src/pages/Celery/CeleryTask/CeleryTask.tsx b/frontend/src/pages/Celery/CeleryTask/CeleryTask.tsx index e09c47f33e..3c3008fb01 100644 --- a/frontend/src/pages/Celery/CeleryTask/CeleryTask.tsx +++ b/frontend/src/pages/Celery/CeleryTask/CeleryTask.tsx @@ -5,6 +5,7 @@ import CeleryTaskDetail, { CeleryTaskData, } 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'; @@ -28,14 +29,23 @@ export default function CeleryTask(): JSX.Element {
- +
- { - setTask(null); - }} - /> + {!!task && ( + { + setTask(null); + }} + mainTitle="Celery Task" + widgetData={celerySlowestTasksTableWidgetData} + taskData={{ + entity: 'task', + value: 'task', + timeRange: [1737569089000, 1737570889000], + }} + drawerOpen={!!task} + /> + )} ); }