feat: celery - misc feedback fixes (#7019)

* feat: celery - misc feedback fixes

* feat: enabled better sharing and auto-refresh

* feat: made task name filter more visible
This commit is contained in:
SagarRajput-7 2025-02-04 09:41:27 +05:30 committed by GitHub
parent 3b550c485d
commit cf95b15ba1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 352 additions and 298 deletions

View File

@ -1,7 +1,6 @@
import './CeleryOverviewConfigOptions.styles.scss';
import { Color } from '@signozhq/design-tokens';
import { Button, Row, Select, Spin, Tooltip } from 'antd';
import { Row, Select, Spin } from 'antd';
import {
getValuesFromQueryParams,
setQueryParamsFromOptions,
@ -10,10 +9,7 @@ import { useCeleryFilterOptions } from 'components/CeleryTask/useCeleryFilterOpt
import { SelectMaxTagPlaceholder } from 'components/MessagingQueues/MQCommon/MQCommon';
import { QueryParams } from 'constants/query';
import useUrlQuery from 'hooks/useUrlQuery';
import { Check, Share2 } from 'lucide-react';
import { useState } from 'react';
import { useHistory, useLocation } from 'react-router-dom';
import { useCopyToClipboard } from 'react-use';
interface SelectOptionConfig {
placeholder: string;
@ -66,10 +62,6 @@ function FilterSelect({
}
function CeleryOverviewConfigOptions(): JSX.Element {
const [isURLCopied, setIsURLCopied] = useState(false);
const [, handleCopyToClipboard] = useCopyToClipboard();
const selectConfigs: SelectOptionConfig[] = [
{
placeholder: 'Service Name',
@ -98,14 +90,6 @@ function CeleryOverviewConfigOptions(): JSX.Element {
},
];
const handleShareURL = (): void => {
handleCopyToClipboard(window.location.href);
setIsURLCopied(true);
setTimeout(() => {
setIsURLCopied(false);
}, 1000);
};
return (
<div className="celery-overview-filters">
<Row className="celery-filters">
@ -118,19 +102,6 @@ function CeleryOverviewConfigOptions(): JSX.Element {
/>
))}
</Row>
<Tooltip title="Share this" arrow={false}>
<Button
className="periscope-btn copy-url-btn"
onClick={handleShareURL}
icon={
isURLCopied ? (
<Check size={14} color={Color.BG_FOREST_500} />
) : (
<Share2 size={14} />
)
}
/>
</Tooltip>
</div>
);
}

View File

@ -37,7 +37,6 @@
font-weight: 600;
line-height: 18px; /* 163.636% */
letter-spacing: 0.44px;
text-transform: uppercase;
&::before {
background-color: transparent;

View File

@ -218,20 +218,26 @@ function getColumns(data: RowData[]): TableColumnsType<RowData> {
showTitle: false,
},
width: 200,
sorter: (a: RowData, b: RowData): number =>
String(a.error_percentage).localeCompare(String(b.error_percentage)),
sorter: (a: RowData, b: RowData): number => {
const aValue = Number(a.error_percentage);
const bValue = Number(b.error_percentage);
return aValue - bValue;
},
render: ProgressRender,
},
{
title: 'LATENCY (P95)',
title: 'LATENCY (P95) in ms',
dataIndex: 'p95_latency',
key: 'p95_latency',
ellipsis: {
showTitle: false,
},
width: 100,
sorter: (a: RowData, b: RowData): number =>
String(a.p95_latency).localeCompare(String(b.p95_latency)),
sorter: (a: RowData, b: RowData): number => {
const aValue = Number(a.p95_latency);
const bValue = Number(b.p95_latency);
return aValue - bValue;
},
render: (value: number | string): string => {
if (!isNumber(value)) return value.toString();
return (typeof value === 'string' ? parseFloat(value) : value).toFixed(3);
@ -245,8 +251,11 @@ function getColumns(data: RowData[]): TableColumnsType<RowData> {
showTitle: false,
},
width: 100,
sorter: (a: RowData, b: RowData): number =>
String(a.throughput).localeCompare(String(b.throughput)),
sorter: (a: RowData, b: RowData): number => {
const aValue = Number(a.throughput);
const bValue = Number(b.throughput);
return aValue - bValue;
},
render: (value: number | string): string => {
if (!isNumber(value)) return value.toString();
return (typeof value === 'string' ? parseFloat(value) : value).toFixed(3);

View File

@ -2,24 +2,16 @@ 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 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 { useState } from 'react';
import { Widgets } from 'types/api/dashboard/getAll';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { GlobalReducer } from 'types/reducer/globalTime';
import { v4 as uuidv4 } from 'uuid';
import CeleryTaskGraph from '../CeleryTaskGraph/CeleryTaskGraph';
import { createFiltersFromData } from '../CeleryUtils';
import { useNavigateToTraces } from '../useNavigateToTraces';
export type CeleryTaskData = {
@ -39,40 +31,6 @@ export type CeleryTaskDetailProps = {
drawerOpen: boolean;
};
const createFiltersFromData = (
data: Record<string, any>,
): Array<{
id: string;
key: {
key: string;
dataType: DataTypes;
type: string;
isColumn: boolean;
isJSON: boolean;
id: string;
};
op: string;
value: string;
}> => {
const excludeKeys = ['A', 'A_without_unit'];
return Object.entries(data)
.filter(([key]) => !excludeKeys.includes(key))
.map(([key, value]) => ({
id: uuidv4(),
key: {
key,
dataType: DataTypes.String,
type: 'tag',
isColumn: false,
isJSON: false,
id: `${key}--string--tag--false`,
},
op: '=',
value: value.toString(),
}));
};
export default function CeleryTaskDetail({
widgetData,
taskData,
@ -85,7 +43,7 @@ export default function CeleryTaskDetail({
!!taskData.entity && !!taskData.timeRange[0] && drawerOpen;
const formatTimestamp = (timestamp: number): string =>
dayjs(timestamp * 1000).format('MM-DD-YYYY hh:mm A');
dayjs(timestamp).format('DD-MM-YYYY hh:mm A');
const [totalTask, setTotalTask] = useState(0);
@ -93,52 +51,9 @@ export default function CeleryTaskDetail({
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 / 1e6, maxTime / 1e6]));
urlQuery.set(QueryParams.startTime, Math.floor(minTime / 1e6).toString());
urlQuery.set(QueryParams.endTime, Math.floor(maxTime / 1e6).toString());
}
const generatedUrl = `${location.pathname}?${urlQuery.toString()}`;
history.replace(generatedUrl);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const navigateToTrace = useNavigateToTraces();
return (
@ -149,10 +64,8 @@ export default function CeleryTaskDetail({
<Typography.Text className="title">{`Details - ${taskData.entity}`}</Typography.Text>
<div>
<Typography.Text className="subtitle">
{`${formatTimestamp(taskData.timeRange[0])} ${
taskData.timeRange[1]
? `- ${formatTimestamp(taskData.timeRange[1])}`
: ''
{`${formatTimestamp(startTime)} ${
endTime ? `- ${formatTimestamp(endTime)}` : ''
}`}
</Typography.Text>
<Divider type="vertical" />
@ -185,8 +98,10 @@ export default function CeleryTaskDetail({
...rowData,
[taskData.entity]: taskData.value,
});
navigateToTrace(filters);
navigateToTrace(filters, startTime, endTime);
}}
start={startTime}
end={endTime}
/>
</Drawer>
);

View File

@ -19,6 +19,10 @@ import { Widgets } from 'types/api/dashboard/getAll';
import { GlobalReducer } from 'types/reducer/globalTime';
import { CaptureDataProps } from '../CeleryTaskDetail/CeleryTaskDetail';
import {
applyCeleryFilterOnWidgetData,
getFiltersFromQueryParams,
} from '../CeleryUtils';
import { useGetGraphCustomSeries } from '../useGetGraphCustomSeries';
import {
celeryAllStateWidgetData,
@ -72,26 +76,60 @@ function CeleryTaskBar({
const [barState, setBarState] = useState<CeleryTaskState>(CeleryTaskState.All);
const selectedFilters = useMemo(
() =>
getFiltersFromQueryParams(
QueryParams.taskName,
urlQuery,
'celery.task_name',
),
[urlQuery],
);
const celeryAllStateData = useMemo(
() => celeryAllStateWidgetData(minTime, maxTime),
[minTime, maxTime],
);
const celeryAllStateFilteredData = useMemo(
() =>
applyCeleryFilterOnWidgetData(selectedFilters || [], celeryAllStateData),
[selectedFilters, celeryAllStateData],
);
const celeryFailedStateData = useMemo(
() => celeryFailedStateWidgetData(minTime, maxTime),
[minTime, maxTime],
);
const celeryFailedStateFilteredData = useMemo(
() =>
applyCeleryFilterOnWidgetData(selectedFilters || [], celeryFailedStateData),
[selectedFilters, celeryFailedStateData],
);
const celeryRetryStateData = useMemo(
() => celeryRetryStateWidgetData(minTime, maxTime),
[minTime, maxTime],
);
const celeryRetryStateFilteredData = useMemo(
() =>
applyCeleryFilterOnWidgetData(selectedFilters || [], celeryRetryStateData),
[selectedFilters, celeryRetryStateData],
);
const celerySuccessStateData = useMemo(
() => celerySuccessStateWidgetData(minTime, maxTime),
[minTime, maxTime],
);
const celerySuccessStateFilteredData = useMemo(
() =>
applyCeleryFilterOnWidgetData(selectedFilters || [], celerySuccessStateData),
[selectedFilters, celerySuccessStateData],
);
const onGraphClick = (
widgetData: Widgets,
xValue: number,
@ -141,7 +179,7 @@ function CeleryTaskBar({
<div className="celery-task-graph-grid-content">
{barState === CeleryTaskState.All && (
<GridCard
widget={celeryAllStateData}
widget={celeryAllStateFilteredData}
headerMenuList={[...ViewMenuAction]}
onDragSelect={onDragSelect}
isQueryEnabled={queryEnabled}
@ -153,7 +191,7 @@ function CeleryTaskBar({
)}
{barState === CeleryTaskState.Failed && (
<GridCard
widget={celeryFailedStateData}
widget={celeryFailedStateFilteredData}
headerMenuList={[...ViewMenuAction]}
onDragSelect={onDragSelect}
isQueryEnabled={queryEnabled}
@ -165,7 +203,7 @@ function CeleryTaskBar({
)}
{barState === CeleryTaskState.Retry && (
<GridCard
widget={celeryRetryStateData}
widget={celeryRetryStateFilteredData}
headerMenuList={[...ViewMenuAction]}
onDragSelect={onDragSelect}
isQueryEnabled={queryEnabled}
@ -177,7 +215,7 @@ function CeleryTaskBar({
)}
{barState === CeleryTaskState.Successful && (
<GridCard
widget={celerySuccessStateData}
widget={celerySuccessStateFilteredData}
headerMenuList={[...ViewMenuAction]}
onDragSelect={onDragSelect}
isQueryEnabled={queryEnabled}

View File

@ -117,7 +117,7 @@
.metric-page-grid {
display: grid;
grid-template-columns: 50% 50%;
grid-template-columns: 30% 19%;
align-items: flex-start;
gap: 10px;
width: 100%;
@ -144,6 +144,11 @@
gap: 16px;
align-items: center;
border: 1px dashed var(--bg-slate-50);
border-radius: 4px;
padding: 6px 24px 6px 12px;
width: max-content;
.configure-option-Info-text {
color: var(--bg-vanilla-400);
font-size: 12px;
@ -241,11 +246,13 @@
.lightMode {
.celery-task-graph-grid-container {
.celery-task-graph-grid {
.celery-task-graph-worker-count {
border: 1px solid var(--bg-vanilla-300);
background: unset;
}
.celery-task-graph-worker-count {
border: 1px solid var(--bg-vanilla-300);
background: unset;
}
.row-panel .row-panel-section .section-title {
color: var(--bg-ink-400);
}
}
@ -274,4 +281,8 @@
background-color: var(--bg-ink-400);
}
}
.configure-option-Info {
border: 1px dashed var(--bg-robin-400);
}
}

View File

@ -32,6 +32,9 @@ function CeleryTaskGraph({
openTracesButton,
onOpenTraceBtnClick,
applyCeleryTaskFilter,
customErrorMessage,
start,
end,
}: {
widgetData: Widgets;
onClick?: (task: CaptureDataProps) => void;
@ -42,6 +45,9 @@ function CeleryTaskGraph({
openTracesButton?: boolean;
onOpenTraceBtnClick?: (record: RowData) => void;
applyCeleryTaskFilter?: boolean;
customErrorMessage?: string;
start?: number;
end?: number;
}): JSX.Element {
const history = useHistory();
const { pathname } = useLocation();
@ -116,6 +122,9 @@ function CeleryTaskGraph({
openTracesButton={openTracesButton}
onOpenTraceBtnClick={onOpenTraceBtnClick}
version={ENTITY_VERSION_V4}
customErrorMessage={customErrorMessage}
start={start}
end={end}
/>
</Card>
);
@ -129,6 +138,9 @@ CeleryTaskGraph.defaultProps = {
openTracesButton: false,
onOpenTraceBtnClick: undefined,
applyCeleryTaskFilter: false,
customErrorMessage: undefined,
start: undefined,
end: undefined,
};
export default CeleryTaskGraph;

View File

@ -123,11 +123,12 @@ export default function CeleryTaskGraphGrid({
key={celeryActiveTasksData.id}
widgetData={celeryActiveTasksData}
queryEnabled={queryEnabled}
customErrorMessage="Enable Flower metrics to view this graph"
/>
<Card className="celery-task-graph-worker-count">
<div className="worker-count-header">
<Typography.Text className="worker-count-header-text">
Worker Count
Worker Online
</Typography.Text>
</div>
<div className="worker-count-text-container">
@ -173,7 +174,7 @@ export default function CeleryTaskGraphGrid({
{!collapsedSections.traceBasedGraphs && (
<>
<CeleryTaskBar queryEnabled={queryEnabled} onClick={onClick} />
<CeleryTaskLatencyGraph onClick={onClick} queryEnabled={queryEnabled} />
<CeleryTaskLatencyGraph queryEnabled={queryEnabled} />
<div className="celery-task-graph-grid-bottom">
{bottomWidgetData.map((widgetData, index) => (
<CeleryTaskGraph

View File

@ -42,21 +42,7 @@ export const celeryAllStateWidgetData = (
disabled: false,
expression: 'A',
filters: {
items: [
{
id: uuidv4(),
key: {
dataType: DataTypes.String,
id: 'celery.task_name--string--tag--false',
isColumn: false,
isJSON: false,
key: 'celery.task_name',
type: 'tag',
},
op: '=',
value: 'tasks.tasks.divide',
},
],
items: [],
op: 'AND',
},
functions: [],
@ -113,7 +99,7 @@ export const celeryRetryStateWidgetData = (
filters: {
items: [
{
id: '6d97eed3',
id: uuidv4(),
key: {
dataType: DataTypes.String,
id: 'celery.state--string--tag--false',
@ -179,7 +165,7 @@ export const celeryFailedStateWidgetData = (
filters: {
items: [
{
id: '5983eae2',
id: uuidv4(),
key: {
dataType: DataTypes.String,
id: 'celery.state--string--tag--false',
@ -245,7 +231,7 @@ export const celerySuccessStateWidgetData = (
filters: {
items: [
{
id: '000c5a93',
id: uuidv4(),
key: {
dataType: DataTypes.String,
id: 'celery.state--string--tag--false',
@ -602,7 +588,7 @@ export const celeryTaskLatencyWidgetData = (
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: getStepInterval(startTime, endTime),
timeAggregation: 'p99',
timeAggregation: type || 'p99',
},
],
yAxisUnit: 'ns',
@ -686,7 +672,7 @@ export const celeryRetryTasksTableWidgetData = getWidgetQueryBuilder(
filters: {
items: [
{
id: '9e09c9ed',
id: uuidv4(),
key: {
dataType: DataTypes.String,
id: 'celery.state--string--tag--false',
@ -755,7 +741,7 @@ export const celeryFailedTasksTableWidgetData = getWidgetQueryBuilder(
filters: {
items: [
{
id: '2330f906',
id: uuidv4(),
key: {
dataType: DataTypes.String,
id: 'celery.state--string--tag--false',
@ -822,7 +808,7 @@ export const celerySuccessTasksTableWidgetData = getWidgetQueryBuilder(
filters: {
items: [
{
id: 'ec3df7b7',
id: uuidv4(),
key: {
dataType: DataTypes.String,
id: 'celery.state--string--tag--false',
@ -945,33 +931,19 @@ export const celeryAllStateCountWidgetData = getWidgetQueryBuilder(
queryData: [
{
aggregateAttribute: {
dataType: DataTypes.EMPTY,
id: '------false',
isColumn: false,
dataType: DataTypes.String,
id: 'span_id--string----true',
isColumn: true,
isJSON: false,
key: '',
key: 'span_id',
type: '',
},
aggregateOperator: 'count',
aggregateOperator: 'count_distinct',
dataSource: DataSource.TRACES,
disabled: false,
expression: 'A',
filters: {
items: [
{
id: uuidv4(),
key: {
dataType: DataTypes.String,
id: 'celery.task_name--string--tag--false',
isColumn: false,
isJSON: false,
key: 'celery.task_name',
type: 'tag',
},
op: '=',
value: 'tasks.tasks.divide',
},
],
items: [],
op: 'AND',
},
functions: [],
@ -981,10 +953,10 @@ export const celeryAllStateCountWidgetData = getWidgetQueryBuilder(
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: 'avg',
reduceTo: 'last',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'rate',
timeAggregation: 'count_distinct',
},
],
}),
@ -998,14 +970,14 @@ export const celerySuccessStateCountWidgetData = getWidgetQueryBuilder(
queryData: [
{
aggregateAttribute: {
dataType: DataTypes.EMPTY,
id: '------false',
isColumn: false,
dataType: DataTypes.String,
id: 'span_id--string----true',
isColumn: true,
isJSON: false,
key: '',
key: 'span_id',
type: '',
},
aggregateOperator: 'count',
aggregateOperator: 'count_distinct',
dataSource: DataSource.TRACES,
disabled: false,
expression: 'A',
@ -1034,10 +1006,10 @@ export const celerySuccessStateCountWidgetData = getWidgetQueryBuilder(
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: 'avg',
reduceTo: 'last',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'rate',
timeAggregation: 'count_distinct',
},
],
}),
@ -1051,14 +1023,14 @@ export const celeryFailedStateCountWidgetData = getWidgetQueryBuilder(
queryData: [
{
aggregateAttribute: {
dataType: DataTypes.EMPTY,
id: '------false',
isColumn: false,
dataType: DataTypes.String,
id: 'span_id--string----true',
isColumn: true,
isJSON: false,
key: '',
key: 'span_id',
type: '',
},
aggregateOperator: 'count',
aggregateOperator: 'count_distinct',
dataSource: DataSource.TRACES,
disabled: false,
expression: 'A',
@ -1087,10 +1059,10 @@ export const celeryFailedStateCountWidgetData = getWidgetQueryBuilder(
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: 'avg',
reduceTo: 'last',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'rate',
timeAggregation: 'count_distinct',
},
],
}),
@ -1104,13 +1076,13 @@ export const celeryRetryStateCountWidgetData = getWidgetQueryBuilder(
queryData: [
{
aggregateAttribute: {
dataType: DataTypes.EMPTY,
id: '------false',
isColumn: false,
key: '',
dataType: DataTypes.String,
id: 'span_id--string----true',
isColumn: true,
key: 'span_id',
type: '',
},
aggregateOperator: 'count',
aggregateOperator: 'count_distinct',
dataSource: DataSource.TRACES,
disabled: false,
expression: 'A',
@ -1139,10 +1111,10 @@ export const celeryRetryStateCountWidgetData = getWidgetQueryBuilder(
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: 'avg',
reduceTo: 'last',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'rate',
timeAggregation: 'count_distinct',
},
],
}),

View File

@ -6,8 +6,11 @@ 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 { Button } from 'container/MetricsApplication/Tabs/styles';
import { onGraphClickHandler } from 'container/MetricsApplication/Tabs/util';
import { useIsDarkMode } from 'hooks/useDarkMode';
import useUrlQuery from 'hooks/useUrlQuery';
import { OnClickPluginOpts } from 'lib/uPlotLib/plugins/onClickPlugin';
import { getStartAndEndTimesInMilliseconds } from 'pages/MessagingQueues/MessagingQueuesUtils';
import { useCallback, useMemo, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
@ -16,15 +19,13 @@ import { UpdateTimeInterval } from 'store/actions';
import { AppState } from 'store/reducers';
import { GlobalReducer } from 'types/reducer/globalTime';
import { CaptureDataProps } from '../CeleryTaskDetail/CeleryTaskDetail';
import {
applyCeleryFilterOnWidgetData,
createFiltersFromData,
getFiltersFromQueryParams,
} from '../CeleryUtils';
import {
celeryTaskLatencyWidgetData,
celeryTimeSeriesTablesWidgetData,
} from './CeleryTaskGraphUtils';
import { useNavigateToTraces } from '../useNavigateToTraces';
import { celeryTaskLatencyWidgetData } from './CeleryTaskGraphUtils';
interface TabData {
label: string;
@ -38,10 +39,8 @@ export enum CeleryTaskGraphState {
}
function CeleryTaskLatencyGraph({
onClick,
queryEnabled,
}: {
onClick: (task: CaptureDataProps) => void;
queryEnabled: boolean;
}): JSX.Element {
const history = useHistory();
@ -106,31 +105,51 @@ function CeleryTaskLatencyGraph({
[celeryTaskLatencyData, selectedFilters],
);
const onGraphClick = (
xValue: number,
_yValue: number,
_mouseX: number,
_mouseY: number,
data?: {
[key: string]: string;
const [selectedTimeStamp, setSelectedTimeStamp] = useState<number>(0);
const [entityData, setEntityData] = useState<{
entity: string;
value: string;
}>();
const handleSetTimeStamp = useCallback((selectTime: number) => {
setSelectedTimeStamp(selectTime);
}, []);
const onGraphClick = useCallback(
(type: string): OnClickPluginOpts['onClick'] => (
xValue,
yValue,
mouseX,
mouseY,
data,
): Promise<void> => {
const [firstDataPoint] = Object.entries(data || {});
const [entity, value] = firstDataPoint;
setEntityData({
entity,
value,
});
return onGraphClickHandler(handleSetTimeStamp)(
xValue,
yValue,
mouseX,
mouseY,
type,
);
},
): void => {
const { start, end } = getStartAndEndTimesInMilliseconds(xValue);
[handleSetTimeStamp],
);
// Extract entity and value from data
const [firstDataPoint] = Object.entries(data || {});
const [entity, value] = (firstDataPoint || ([] as unknown)) as [
string,
string,
];
const navigateToTraces = useNavigateToTraces();
onClick?.({
entity,
value,
timeRange: [start, end],
widgetData: celeryTimeSeriesTablesWidgetData(entity, value, 'Task Latency'),
const goToTraces = useCallback(() => {
const { start, end } = getStartAndEndTimesInMilliseconds(selectedTimeStamp);
const filters = createFiltersFromData({
[entityData?.entity as string]: entityData?.value,
});
};
navigateToTraces(filters, start, end, true);
}, [entityData, navigateToTraces, selectedTimeStamp]);
return (
<Card
@ -161,32 +180,62 @@ function CeleryTaskLatencyGraph({
</Row>
<div className="celery-task-graph-grid-content">
{graphState === CeleryTaskGraphState.P99 && (
<GridCard
widget={updatedWidgetData}
headerMenuList={[...ViewMenuAction]}
onDragSelect={onDragSelect}
onClickHandler={onGraphClick}
isQueryEnabled={queryEnabled}
/>
<>
<Button
type="default"
size="small"
id="Celery_p99_latency_button"
onClick={goToTraces}
>
View Traces
</Button>
<GridCard
widget={updatedWidgetData}
headerMenuList={[...ViewMenuAction]}
onDragSelect={onDragSelect}
onClickHandler={onGraphClick('Celery_p99_latency')}
isQueryEnabled={queryEnabled}
/>
</>
)}
{graphState === CeleryTaskGraphState.P95 && (
<GridCard
widget={updatedWidgetData}
headerMenuList={[...ViewMenuAction]}
onDragSelect={onDragSelect}
onClickHandler={onGraphClick}
isQueryEnabled={queryEnabled}
/>
<>
<Button
type="default"
size="small"
id="Celery_p95_latency_button"
onClick={goToTraces}
>
View Traces
</Button>
<GridCard
widget={updatedWidgetData}
headerMenuList={[...ViewMenuAction]}
onDragSelect={onDragSelect}
onClickHandler={onGraphClick('Celery_p95_latency')}
isQueryEnabled={queryEnabled}
/>
</>
)}
{graphState === CeleryTaskGraphState.P90 && (
<GridCard
widget={updatedWidgetData}
headerMenuList={[...ViewMenuAction]}
onDragSelect={onDragSelect}
onClickHandler={onGraphClick}
isQueryEnabled={queryEnabled}
/>
<>
<Button
type="default"
size="small"
id="Celery_p90_latency_button"
onClick={goToTraces}
>
View Traces
</Button>
<GridCard
widget={updatedWidgetData}
headerMenuList={[...ViewMenuAction]}
onDragSelect={onDragSelect}
onClickHandler={onGraphClick('Celery_p90_latency')}
isQueryEnabled={queryEnabled}
/>
</>
)}
</div>
</Card>

View File

@ -2,8 +2,14 @@
import './CeleryTaskGraph.style.scss';
import { Col, Row } from 'antd';
import { Dispatch, SetStateAction } from 'react';
import { QueryParams } from 'constants/query';
import useUrlQuery from 'hooks/useUrlQuery';
import { Dispatch, SetStateAction, useMemo } from 'react';
import {
applyCeleryFilterOnWidgetData,
getFiltersFromQueryParams,
} from '../CeleryUtils';
import {
celeryAllStateCountWidgetData,
celeryFailedStateCountWidgetData,
@ -42,16 +48,29 @@ function CeleryTaskStateGraphConfig({
setBarState(key as CeleryTaskState);
};
const { values, isLoading, isError } = useGetValueFromWidget(
[
celeryAllStateCountWidgetData,
celeryFailedStateCountWidgetData,
celeryRetryStateCountWidgetData,
celerySuccessStateCountWidgetData,
],
['celery-task-states'],
const urlQuery = useUrlQuery();
const selectedFilters = useMemo(
() =>
getFiltersFromQueryParams(
QueryParams.taskName,
urlQuery,
'celery.task_name',
),
[urlQuery],
);
const widgetData = [
celeryAllStateCountWidgetData,
celeryFailedStateCountWidgetData,
celeryRetryStateCountWidgetData,
celerySuccessStateCountWidgetData,
].map((data) => applyCeleryFilterOnWidgetData(selectedFilters || [], data));
const { values, isLoading, isError } = useGetValueFromWidget(widgetData, [
'celery-task-states',
]);
return (
<Row className="celery-task-states">
{tabs.map((tab, index) => (
@ -66,7 +85,13 @@ function CeleryTaskStateGraphConfig({
<div className="celery-task-states__label-wrapper">
<div className="celery-task-states__label">{tab.label}</div>
<div className="celery-task-states__value">
{isLoading ? '-' : isError ? '-' : values[index]}
{isLoading
? '-'
: isError
? '-'
: Number.isNaN(values[index])
? '-'
: Math.round(Number(values[index]))}
</div>
</div>
{tab.key === barState && <div className="celery-task-states__indicator" />}

View File

@ -90,3 +90,40 @@ export const paths = (
return renderer(u, seriesIdx, idx0, idx1, extendGap, buildClip);
};
export const createFiltersFromData = (
data: Record<string, any>,
): Array<{
id: string;
key: {
key: string;
dataType: DataTypes;
type: string;
isColumn: boolean;
isJSON: boolean;
id: string;
};
op: string;
value: string;
}> => {
const excludeKeys = ['A', 'A_without_unit'];
return (
Object.entries(data)
.filter(([key]) => !excludeKeys.includes(key))
// eslint-disable-next-line sonarjs/no-identical-functions
.map(([key, value]) => ({
id: uuidv4(),
key: {
key,
dataType: DataTypes.String,
type: 'tag',
isColumn: false,
isJSON: false,
id: `${key}--string--tag--false`,
},
op: '=',
value: value.toString(),
}))
);
};

View File

@ -12,6 +12,7 @@ export function useNavigateToTraces(): (
filters: TagFilterItem[],
startTime?: number,
endTime?: number,
sameTab?: boolean,
) => void {
const { currentQuery } = useQueryBuilder();
const { minTime, maxTime } = useSelector<AppState, GlobalReducer>(
@ -38,7 +39,12 @@ export function useNavigateToTraces(): (
);
return useCallback(
(filters: TagFilterItem[], startTime?: number, endTime?: number): void => {
(
filters: TagFilterItem[],
startTime?: number,
endTime?: number,
sameTab?: boolean,
): void => {
const urlParams = new URLSearchParams();
if (startTime && endTime) {
urlParams.set(QueryParams.startTime, startTime.toString());
@ -58,7 +64,7 @@ export function useNavigateToTraces(): (
QueryParams.compositeQuery
}=${JSONCompositeQuery}`;
window.open(newTraceExplorerPath, '_blank');
window.open(newTraceExplorerPath, sameTab ? '_self' : '_blank');
},
[minTime, maxTime, prepareQuery],
);

View File

@ -48,6 +48,7 @@ function WidgetGraphComponent({
openTracesButton,
onOpenTraceBtnClick,
customSeries,
customErrorMessage,
}: WidgetGraphComponentProps): JSX.Element {
const [deleteModal, setDeleteModal] = useState(false);
const [hovered, setHovered] = useState(false);
@ -317,6 +318,13 @@ function WidgetGraphComponent({
setSearchTerm={setSearchTerm}
/>
</div>
{queryResponse.error && customErrorMessage && (
<div className="error-message-container">
<Typography.Text type="warning">{customErrorMessage}</Typography.Text>
</div>
)}
{queryResponse.isLoading && widget.panelTypes !== PANEL_TYPES.LIST && (
<Skeleton />
)}

View File

@ -41,9 +41,15 @@ function GridCardGraph({
openTracesButton,
onOpenTraceBtnClick,
customSeries,
customErrorMessage,
start,
end,
}: GridCardGraphProps): JSX.Element {
const dispatch = useDispatch();
const [errorMessage, setErrorMessage] = useState<string>();
const [isInternalServerError, setIsInternalServerError] = useState<boolean>(
false,
);
const {
toScrollWidgetId,
setToScrollWidgetId,
@ -178,6 +184,8 @@ function GridCardGraph({
variables: getDashboardVariables(variables),
selectedTime: widget.timePreferance || 'GLOBAL_TIME',
globalSelectedInterval,
start,
end,
},
version || DEFAULT_ENTITY_VERSION,
{
@ -207,6 +215,11 @@ function GridCardGraph({
refetchOnMount: false,
onError: (error) => {
setErrorMessage(error.message);
if (customErrorMessage) {
setIsInternalServerError(
String(error.message).includes('API responded with 500'),
);
}
setDashboardQueryRangeCalled(true);
},
onSettled: (data) => {
@ -256,6 +269,7 @@ function GridCardGraph({
openTracesButton={openTracesButton}
onOpenTraceBtnClick={onOpenTraceBtnClick}
customSeries={customSeries}
customErrorMessage={isInternalServerError ? customErrorMessage : undefined}
/>
)}
</div>

View File

@ -37,6 +37,7 @@ export interface WidgetGraphComponentProps {
openTracesButton?: boolean;
onOpenTraceBtnClick?: (record: RowData) => void;
customSeries?: (data: QueryData[]) => uPlot.Series[];
customErrorMessage?: string;
}
export interface GridCardGraphProps {
@ -54,6 +55,9 @@ export interface GridCardGraphProps {
openTracesButton?: boolean;
onOpenTraceBtnClick?: (record: RowData) => void;
customSeries?: (data: QueryData[]) => uPlot.Series[];
customErrorMessage?: string;
start?: number;
end?: number;
}
export interface GetGraphVisibilityStateOnLegendClickProps {

View File

@ -106,6 +106,16 @@
}
}
.error-message-container {
display: flex;
width: 100%;
height: 100%;
justify-content: center;
align-items: center;
padding-top: 0;
padding-bottom: 32px;
}
.row-settings {
.ant-popover-inner {
width: 191px;

View File

@ -23,7 +23,7 @@ export default function CeleryOverview(): JSX.Element {
<p className="celery-overview-content-header-title">
Messaging Queue Overview
</p>
<DateTimeSelectionV2 showAutoRefresh={false} hideShareModal />
<DateTimeSelectionV2 showAutoRefresh hideShareModal={false} />
</div>
<CeleryOverviewConfigOptions />
<CeleryOverviewTable onRowClick={onRowClick} />

View File

@ -1,16 +1,12 @@
import './CeleryTask.styles.scss';
import { Color } from '@signozhq/design-tokens';
import { Button, Tooltip } from 'antd';
import CeleryTaskConfigOptions from 'components/CeleryTask/CeleryTaskConfigOptions/CeleryTaskConfigOptions';
import CeleryTaskDetail, {
CaptureDataProps,
} from 'components/CeleryTask/CeleryTaskDetail/CeleryTaskDetail';
import CeleryTaskGraphGrid from 'components/CeleryTask/CeleryTaskGraph/CeleryTaskGraphGrid';
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
import { Check, Share2 } from 'lucide-react';
import { useState } from 'react';
import { useCopyToClipboard } from 'react-use';
export default function CeleryTask(): JSX.Element {
const [task, setTask] = useState<CaptureDataProps | null>(null);
@ -19,36 +15,13 @@ export default function CeleryTask(): JSX.Element {
setTask(captureData);
};
const [isURLCopied, setIsURLCopied] = useState(false);
const [, handleCopyToClipboard] = useCopyToClipboard();
return (
<div className="celery-task-container">
<div className="celery-content">
<div className="celery-content-header">
<p className="celery-content-header-title">Celery</p>
<div className="celery-content-header-right">
<DateTimeSelectionV2 showAutoRefresh={false} hideShareModal />
<Tooltip title="Share this" arrow={false}>
<Button
className="periscope-btn copy-url-btn"
onClick={(): void => {
handleCopyToClipboard(window.location.href);
setIsURLCopied(true);
setTimeout(() => {
setIsURLCopied(false);
}, 1000);
}}
icon={
isURLCopied ? (
<Check size={14} color={Color.BG_FOREST_500} />
) : (
<Share2 size={14} />
)
}
/>
</Tooltip>
<DateTimeSelectionV2 showAutoRefresh hideShareModal={false} />
</div>
</div>
<CeleryTaskGraphGrid