mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-12 02:59:00 +08:00
feat: added right panel graphs in overview page (#6944)
* feat: added right panel graphs in overview page * feat: added right panel trace navigation * feat: implement search column wise and global table wise * feat: implemented filter on table with api filtering * feat: added allow clear to table filters * feat: fixed empty table issue * feat: fixed celery state - count display
This commit is contained in:
parent
7c85befc17
commit
35c61045c4
@ -409,10 +409,10 @@ const routes: AppRoutes[] = [
|
||||
isPrivate: true,
|
||||
},
|
||||
{
|
||||
path: ROUTES.MESSAGING_QUEUES_CELERY_OVERVIEW,
|
||||
path: ROUTES.MESSAGING_QUEUES_OVERVIEW,
|
||||
exact: true,
|
||||
component: MessagingQueues,
|
||||
key: 'MESSAGING_QUEUES_CELERY_OVERVIEW',
|
||||
key: 'MESSAGING_QUEUES_OVERVIEW',
|
||||
isPrivate: true,
|
||||
},
|
||||
{
|
||||
|
@ -10,6 +10,9 @@
|
||||
|
||||
.config-select-option {
|
||||
width: 100%;
|
||||
min-width: 160px;
|
||||
max-width: fit-content;
|
||||
|
||||
.ant-select-selector {
|
||||
display: flex;
|
||||
min-height: 32px;
|
||||
@ -23,6 +26,10 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.copy-url-btn {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import './CeleryOverviewConfigOptions.styles.scss';
|
||||
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Button, Select, Spin, Tooltip } from 'antd';
|
||||
import { Button, Row, Select, Spin, Tooltip } from 'antd';
|
||||
import {
|
||||
getValuesFromQueryParams,
|
||||
setQueryParamsFromOptions,
|
||||
@ -18,7 +18,7 @@ import { useCopyToClipboard } from 'react-use';
|
||||
interface SelectOptionConfig {
|
||||
placeholder: string;
|
||||
queryParam: QueryParams;
|
||||
filterType: 'serviceName' | 'spanName' | 'msgSystem';
|
||||
filterType: string | string[];
|
||||
}
|
||||
|
||||
function FilterSelect({
|
||||
@ -29,13 +29,14 @@ function FilterSelect({
|
||||
const { handleSearch, isFetching, options } = useCeleryFilterOptions(
|
||||
filterType,
|
||||
);
|
||||
|
||||
const urlQuery = useUrlQuery();
|
||||
const history = useHistory();
|
||||
const location = useLocation();
|
||||
|
||||
return (
|
||||
<Select
|
||||
key={filterType}
|
||||
key={filterType.toString()}
|
||||
placeholder={placeholder}
|
||||
showSearch
|
||||
mode="multiple"
|
||||
@ -44,6 +45,7 @@ function FilterSelect({
|
||||
className="config-select-option"
|
||||
onSearch={handleSearch}
|
||||
maxTagCount={4}
|
||||
allowClear
|
||||
maxTagPlaceholder={SelectMaxTagPlaceholder}
|
||||
value={getValuesFromQueryParams(queryParam, urlQuery) || []}
|
||||
notFoundContent={
|
||||
@ -74,16 +76,26 @@ function CeleryOverviewConfigOptions(): JSX.Element {
|
||||
queryParam: QueryParams.service,
|
||||
filterType: 'serviceName',
|
||||
},
|
||||
// {
|
||||
// placeholder: 'Span Name',
|
||||
// queryParam: QueryParams.spanName,
|
||||
// filterType: 'spanName',
|
||||
// },
|
||||
// {
|
||||
// placeholder: 'Msg System',
|
||||
// queryParam: QueryParams.msgSystem,
|
||||
// filterType: 'msgSystem',
|
||||
// },
|
||||
{
|
||||
placeholder: 'Span Name',
|
||||
queryParam: QueryParams.spanName,
|
||||
filterType: 'name',
|
||||
},
|
||||
{
|
||||
placeholder: 'Msg System',
|
||||
queryParam: QueryParams.msgSystem,
|
||||
filterType: 'messaging.system',
|
||||
},
|
||||
{
|
||||
placeholder: 'Destination',
|
||||
queryParam: QueryParams.destination,
|
||||
filterType: ['messaging.destination.name', 'messaging.destination'],
|
||||
},
|
||||
{
|
||||
placeholder: 'Kind',
|
||||
queryParam: QueryParams.kindString,
|
||||
filterType: 'kind_string',
|
||||
},
|
||||
];
|
||||
|
||||
const handleShareURL = (): void => {
|
||||
@ -96,16 +108,16 @@ function CeleryOverviewConfigOptions(): JSX.Element {
|
||||
|
||||
return (
|
||||
<div className="celery-overview-filters">
|
||||
<div className="celery-filters">
|
||||
<Row className="celery-filters">
|
||||
{selectConfigs.map((config) => (
|
||||
<FilterSelect
|
||||
key={config.filterType}
|
||||
key={config.filterType.toString()}
|
||||
placeholder={config.placeholder}
|
||||
queryParam={config.queryParam}
|
||||
filterType={config.filterType}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</Row>
|
||||
<Tooltip title="Share this" arrow={false}>
|
||||
<Button
|
||||
className="periscope-btn copy-url-btn"
|
||||
|
@ -1,8 +1,20 @@
|
||||
import './CeleryOverviewTable.styles.scss';
|
||||
|
||||
import { LoadingOutlined } from '@ant-design/icons';
|
||||
import { LoadingOutlined, SearchOutlined } from '@ant-design/icons';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Progress, Spin, TableColumnsType, Tooltip, Typography } from 'antd';
|
||||
import {
|
||||
Button,
|
||||
Input,
|
||||
InputRef,
|
||||
Progress,
|
||||
Space,
|
||||
Spin,
|
||||
TableColumnsType,
|
||||
TableColumnType,
|
||||
Tooltip,
|
||||
Typography,
|
||||
} from 'antd';
|
||||
import { FilterDropdownProps } from 'antd/lib/table/interface';
|
||||
import {
|
||||
getQueueOverview,
|
||||
QueueOverviewResponse,
|
||||
@ -10,10 +22,12 @@ import {
|
||||
import { isNumber } from 'chart.js/helpers';
|
||||
import { ResizeTable } from 'components/ResizeTable';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import useDragColumns from 'hooks/useDragColumns';
|
||||
import { getDraggedColumns } from 'hooks/useDragColumns/utils';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { isEmpty } from 'lodash-es';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useMutation } from 'react-query';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
@ -55,6 +69,74 @@ function ProgressRender(item: string | number): JSX.Element {
|
||||
);
|
||||
}
|
||||
|
||||
const getColumnSearchProps = (
|
||||
searchInput: React.RefObject<InputRef>,
|
||||
handleReset: (
|
||||
clearFilters: () => void,
|
||||
confirm: FilterDropdownProps['confirm'],
|
||||
) => void,
|
||||
handleSearch: (selectedKeys: string[], confirm: () => void) => void,
|
||||
dataIndex?: string,
|
||||
): TableColumnType<RowData> => ({
|
||||
filterDropdown: ({
|
||||
setSelectedKeys,
|
||||
selectedKeys,
|
||||
confirm,
|
||||
clearFilters,
|
||||
close,
|
||||
}): JSX.Element => (
|
||||
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
||||
<div style={{ padding: 8 }} onKeyDown={(e): void => e.stopPropagation()}>
|
||||
<Input
|
||||
ref={searchInput}
|
||||
placeholder={`Search ${dataIndex}`}
|
||||
value={selectedKeys[0]}
|
||||
onChange={(e): void =>
|
||||
setSelectedKeys(e.target.value ? [e.target.value] : [])
|
||||
}
|
||||
onPressEnter={(): void => handleSearch(selectedKeys as string[], confirm)}
|
||||
style={{ marginBottom: 8, display: 'block' }}
|
||||
/>
|
||||
<Space>
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
onClick={(): void => handleSearch(selectedKeys as string[], confirm)}
|
||||
icon={<SearchOutlined />}
|
||||
>
|
||||
Search
|
||||
</Button>
|
||||
<Button
|
||||
onClick={(): void => clearFilters && handleReset(clearFilters, confirm)}
|
||||
size="small"
|
||||
style={{ width: 90 }}
|
||||
>
|
||||
Reset
|
||||
</Button>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
onClick={(): void => {
|
||||
close();
|
||||
}}
|
||||
>
|
||||
close
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
),
|
||||
filterIcon: (filtered: boolean): JSX.Element => (
|
||||
<SearchOutlined
|
||||
style={{ color: filtered ? Color.BG_ROBIN_500 : undefined }}
|
||||
/>
|
||||
),
|
||||
onFilter: (value, record): boolean =>
|
||||
record[dataIndex || '']
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.includes((value as string).toLowerCase()),
|
||||
});
|
||||
|
||||
function getColumns(data: RowData[]): TableColumnsType<RowData> {
|
||||
if (data?.length === 0) {
|
||||
return [];
|
||||
@ -235,12 +317,11 @@ type FilterConfig = {
|
||||
|
||||
function makeFilters(urlQuery: URLSearchParams): Filter[] {
|
||||
const filterConfigs: FilterConfig[] = [
|
||||
{ paramName: 'destination', key: 'destination', operator: 'in' },
|
||||
{ paramName: 'queue', key: 'queue', operator: 'in' },
|
||||
{ paramName: 'kind_string', key: 'kind_string', operator: 'in' },
|
||||
{ paramName: 'service', key: 'service.name', operator: 'in' },
|
||||
{ paramName: 'span_name', key: 'span_name', operator: 'in' },
|
||||
{ paramName: 'messaging_system', key: 'messaging_system', operator: 'in' },
|
||||
{ paramName: QueryParams.destination, key: 'destination', operator: 'in' },
|
||||
{ paramName: QueryParams.msgSystem, key: 'queue', operator: 'in' },
|
||||
{ paramName: QueryParams.kindString, key: 'kind_string', operator: 'in' },
|
||||
{ paramName: QueryParams.service, key: 'service.name', operator: 'in' },
|
||||
{ paramName: QueryParams.spanName, key: 'name', operator: 'in' },
|
||||
];
|
||||
|
||||
return filterConfigs
|
||||
@ -260,7 +341,11 @@ function makeFilters(urlQuery: URLSearchParams): Filter[] {
|
||||
.filter((filter): filter is Filter => filter !== null);
|
||||
}
|
||||
|
||||
export default function CeleryOverviewTable(): JSX.Element {
|
||||
export default function CeleryOverviewTable({
|
||||
onRowClick,
|
||||
}: {
|
||||
onRowClick: (record: RowData) => void;
|
||||
}): JSX.Element {
|
||||
const [tableData, setTableData] = useState<RowData[]>([]);
|
||||
|
||||
const { minTime, maxTime } = useSelector<AppState, GlobalReducer>(
|
||||
@ -271,6 +356,8 @@ export default function CeleryOverviewTable(): JSX.Element {
|
||||
onSuccess: (data) => {
|
||||
if (data?.payload) {
|
||||
setTableData(getTableData(data?.payload));
|
||||
} else if (isEmpty(data?.payload)) {
|
||||
setTableData([]);
|
||||
}
|
||||
},
|
||||
});
|
||||
@ -293,11 +380,42 @@ export default function CeleryOverviewTable(): JSX.Element {
|
||||
LOCALSTORAGE.CELERY_OVERVIEW_COLUMNS,
|
||||
);
|
||||
|
||||
const [searchText, setSearchText] = useState('');
|
||||
const searchInput = useRef<InputRef>(null);
|
||||
|
||||
const handleSearch = (
|
||||
selectedKeys: string[],
|
||||
confirm: FilterDropdownProps['confirm'],
|
||||
): void => {
|
||||
confirm();
|
||||
setSearchText(selectedKeys[0]);
|
||||
};
|
||||
|
||||
const handleReset = (
|
||||
clearFilters: () => void,
|
||||
confirm: FilterDropdownProps['confirm'],
|
||||
): void => {
|
||||
clearFilters();
|
||||
setSearchText('');
|
||||
confirm();
|
||||
};
|
||||
|
||||
const columns = useMemo(
|
||||
() => getDraggedColumns<RowData>(getColumns(tableData), draggedColumns),
|
||||
() =>
|
||||
getDraggedColumns<RowData>(
|
||||
getColumns(tableData).map((item) => ({
|
||||
...item,
|
||||
...getColumnSearchProps(
|
||||
searchInput,
|
||||
handleReset,
|
||||
handleSearch,
|
||||
item.key?.toString(),
|
||||
),
|
||||
})),
|
||||
draggedColumns,
|
||||
),
|
||||
[tableData, draggedColumns],
|
||||
);
|
||||
|
||||
const handleDragColumn = useCallback(
|
||||
(fromIndex: number, toIndex: number) =>
|
||||
onDragColumns(columns, fromIndex, toIndex),
|
||||
@ -316,17 +434,44 @@ export default function CeleryOverviewTable(): JSX.Element {
|
||||
);
|
||||
|
||||
const handleRowClick = (record: RowData): void => {
|
||||
console.log(record);
|
||||
onRowClick(record);
|
||||
};
|
||||
|
||||
const getFilteredData = useCallback(
|
||||
(data: RowData[]): RowData[] => {
|
||||
if (!searchText) return data;
|
||||
|
||||
const searchLower = searchText.toLowerCase();
|
||||
return data.filter((record) =>
|
||||
Object.values(record).some(
|
||||
(value) =>
|
||||
value !== undefined &&
|
||||
value.toString().toLowerCase().includes(searchLower),
|
||||
),
|
||||
);
|
||||
},
|
||||
[searchText],
|
||||
);
|
||||
|
||||
const filteredData = useMemo(() => getFilteredData(tableData), [
|
||||
getFilteredData,
|
||||
tableData,
|
||||
]);
|
||||
|
||||
return (
|
||||
<div style={{ width: '100%' }}>
|
||||
<Input.Search
|
||||
placeholder="Search across all columns"
|
||||
onChange={(e): void => setSearchText(e.target.value)}
|
||||
value={searchText}
|
||||
allowClear
|
||||
/>
|
||||
<ResizeTable
|
||||
className="celery-overview-table"
|
||||
pagination={paginationConfig}
|
||||
size="middle"
|
||||
columns={columns}
|
||||
dataSource={tableData}
|
||||
dataSource={filteredData}
|
||||
bordered={false}
|
||||
loading={{
|
||||
spinning: isLoading,
|
||||
|
@ -7,7 +7,7 @@ import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
export interface Filters {
|
||||
searchText: string;
|
||||
attributeKey: string;
|
||||
attributeKey: string | string[];
|
||||
}
|
||||
|
||||
export interface GetAllFiltersResponse {
|
||||
@ -19,27 +19,36 @@ export function useGetAllFilters(props: Filters): GetAllFiltersResponse {
|
||||
const { searchText, attributeKey } = props;
|
||||
|
||||
const { data, isLoading } = useQuery(
|
||||
['attributesValues', searchText],
|
||||
['attributesValues', attributeKey, searchText],
|
||||
async () => {
|
||||
const { payload } = await getAttributesValues({
|
||||
aggregateOperator: 'noop',
|
||||
dataSource: DataSource.TRACES,
|
||||
aggregateAttribute: '',
|
||||
attributeKey,
|
||||
searchText: searchText ?? '',
|
||||
filterAttributeKeyDataType: DataTypes.String,
|
||||
tagType: 'tag',
|
||||
});
|
||||
const keys = Array.isArray(attributeKey) ? attributeKey : [attributeKey];
|
||||
|
||||
if (payload) {
|
||||
const values = Object.values(payload).find((el) => !!el) || [];
|
||||
const options: DefaultOptionType[] = values.map((val: string) => ({
|
||||
label: val,
|
||||
value: val,
|
||||
}));
|
||||
return options;
|
||||
}
|
||||
return [];
|
||||
const responses = await Promise.all(
|
||||
keys.map((key) =>
|
||||
getAttributesValues({
|
||||
aggregateOperator: 'noop',
|
||||
dataSource: DataSource.TRACES,
|
||||
aggregateAttribute: '',
|
||||
attributeKey: key,
|
||||
searchText: searchText ?? '',
|
||||
filterAttributeKeyDataType: DataTypes.String,
|
||||
tagType: 'tag',
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
const uniqueValues = [
|
||||
...new Set(
|
||||
responses.flatMap(
|
||||
({ payload }) => Object.values(payload || {}).find((el) => !!el) || [],
|
||||
),
|
||||
),
|
||||
];
|
||||
|
||||
return uniqueValues.map((val: string) => ({
|
||||
label: val,
|
||||
value: val,
|
||||
}));
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -4,14 +4,11 @@ import { Color, Spacing } from '@signozhq/design-tokens';
|
||||
import { Divider, Drawer, Typography } from 'antd';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import ROUTES from 'constants/routes';
|
||||
import dayjs from 'dayjs';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { RowData } from 'lib/query/createTableColumnsFromQuery';
|
||||
import { X } from 'lucide-react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import { UpdateTimeInterval } from 'store/actions';
|
||||
@ -19,12 +16,11 @@ import { AppState } from 'store/reducers';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { Query, TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { DataSource, MetricAggregateOperator } from 'types/common/queryBuilder';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import CeleryTaskGraph from '../CeleryTaskGraph/CeleryTaskGraph';
|
||||
import { useNavigateToTraces } from '../useNavigateToTraces';
|
||||
|
||||
export type CeleryTaskData = {
|
||||
entity: string;
|
||||
@ -143,46 +139,7 @@ export default function CeleryTaskDetail({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const { currentQuery } = useQueryBuilder();
|
||||
|
||||
const prepareQuery = useCallback(
|
||||
(selectedFilters: TagFilterItem[]): Query => ({
|
||||
...currentQuery,
|
||||
builder: {
|
||||
...currentQuery.builder,
|
||||
queryData: currentQuery.builder.queryData.map((item) => ({
|
||||
...item,
|
||||
dataSource: DataSource.TRACES,
|
||||
aggregateOperator: MetricAggregateOperator.NOOP,
|
||||
filters: {
|
||||
...item.filters,
|
||||
items: selectedFilters,
|
||||
},
|
||||
})),
|
||||
},
|
||||
}),
|
||||
[currentQuery],
|
||||
);
|
||||
|
||||
const navigateToTrace = (data: RowData): void => {
|
||||
const { entity, value } = taskData;
|
||||
const selectedFilters = createFiltersFromData({ ...data, [entity]: value });
|
||||
const urlParams = new URLSearchParams();
|
||||
urlParams.set(QueryParams.startTime, (minTime / 1000000).toString());
|
||||
urlParams.set(QueryParams.endTime, (maxTime / 1000000).toString());
|
||||
|
||||
const JSONCompositeQuery = encodeURIComponent(
|
||||
JSON.stringify(prepareQuery(selectedFilters)),
|
||||
);
|
||||
|
||||
const newTraceExplorerPath = `${
|
||||
ROUTES.TRACES_EXPLORER
|
||||
}?${urlParams.toString()}&${
|
||||
QueryParams.compositeQuery
|
||||
}=${JSONCompositeQuery}`;
|
||||
|
||||
window.open(newTraceExplorerPath, '_blank');
|
||||
};
|
||||
const navigateToTrace = useNavigateToTraces();
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
@ -223,7 +180,13 @@ export default function CeleryTaskDetail({
|
||||
panelType={PANEL_TYPES.TABLE}
|
||||
queryEnabled
|
||||
openTracesButton
|
||||
onOpenTraceBtnClick={navigateToTrace}
|
||||
onOpenTraceBtnClick={(rowData): void => {
|
||||
const filters = createFiltersFromData({
|
||||
...rowData,
|
||||
[taskData.entity]: taskData.value,
|
||||
});
|
||||
navigateToTrace(filters);
|
||||
}}
|
||||
/>
|
||||
</Drawer>
|
||||
);
|
||||
|
@ -140,7 +140,6 @@ function CeleryTaskBar({
|
||||
});
|
||||
|
||||
const customSeries = (data: QueryData[]): uPlot.Series[] => {
|
||||
console.log(data);
|
||||
const configurations: uPlot.Series[] = [
|
||||
{ label: 'Timestamp', stroke: 'purple' },
|
||||
];
|
||||
|
@ -24,7 +24,7 @@
|
||||
|
||||
.widget-graph-container {
|
||||
&.bar {
|
||||
height: calc(100% - 85px);
|
||||
height: calc(100% - 105px);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -45,9 +45,12 @@
|
||||
height: calc(100% - 18px);
|
||||
|
||||
.widget-graph-container {
|
||||
&.bar,
|
||||
&.bar {
|
||||
height: calc(100% - 105px);
|
||||
}
|
||||
|
||||
&.graph {
|
||||
height: calc(100% - 85px);
|
||||
height: calc(100% - 80px);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -93,7 +96,10 @@
|
||||
}
|
||||
|
||||
&__label-wrapper {
|
||||
margin-bottom: 8px;
|
||||
margin-bottom: 4px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
&__label {
|
||||
|
@ -917,3 +917,215 @@ export const celeryTimeSeriesTablesWidgetData = (
|
||||
columnUnits: { A: 'ns' },
|
||||
}),
|
||||
);
|
||||
|
||||
// State Count Widget
|
||||
export const celeryAllStateCountWidgetData = getWidgetQueryBuilder(
|
||||
getWidgetQuery({
|
||||
title: 'All State Count',
|
||||
description: 'Represents the all state count.',
|
||||
panelTypes: PANEL_TYPES.VALUE,
|
||||
queryData: [
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.EMPTY,
|
||||
id: '------false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: '',
|
||||
type: '',
|
||||
},
|
||||
aggregateOperator: 'count',
|
||||
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',
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
},
|
||||
functions: [],
|
||||
groupBy: [],
|
||||
having: [],
|
||||
legend: '',
|
||||
limit: null,
|
||||
orderBy: [],
|
||||
queryName: 'A',
|
||||
reduceTo: 'avg',
|
||||
spaceAggregation: 'sum',
|
||||
stepInterval: 60,
|
||||
timeAggregation: 'rate',
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
export const celerySuccessStateCountWidgetData = getWidgetQueryBuilder(
|
||||
getWidgetQuery({
|
||||
title: 'Successful State Count',
|
||||
description: 'Represents the successful state count.',
|
||||
panelTypes: PANEL_TYPES.VALUE,
|
||||
queryData: [
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.EMPTY,
|
||||
id: '------false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: '',
|
||||
type: '',
|
||||
},
|
||||
aggregateOperator: 'count',
|
||||
dataSource: DataSource.TRACES,
|
||||
disabled: false,
|
||||
expression: 'A',
|
||||
filters: {
|
||||
items: [
|
||||
{
|
||||
id: uuidv4(),
|
||||
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: [],
|
||||
having: [],
|
||||
legend: '',
|
||||
limit: null,
|
||||
orderBy: [],
|
||||
queryName: 'A',
|
||||
reduceTo: 'avg',
|
||||
spaceAggregation: 'sum',
|
||||
stepInterval: 60,
|
||||
timeAggregation: 'rate',
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
export const celeryFailedStateCountWidgetData = getWidgetQueryBuilder(
|
||||
getWidgetQuery({
|
||||
title: 'Failed State Count',
|
||||
description: 'Represents the failed state count.',
|
||||
panelTypes: PANEL_TYPES.VALUE,
|
||||
queryData: [
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.EMPTY,
|
||||
id: '------false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: '',
|
||||
type: '',
|
||||
},
|
||||
aggregateOperator: 'count',
|
||||
dataSource: DataSource.TRACES,
|
||||
disabled: false,
|
||||
expression: 'A',
|
||||
filters: {
|
||||
items: [
|
||||
{
|
||||
id: uuidv4(),
|
||||
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: [],
|
||||
having: [],
|
||||
legend: '',
|
||||
limit: null,
|
||||
orderBy: [],
|
||||
queryName: 'A',
|
||||
reduceTo: 'avg',
|
||||
spaceAggregation: 'sum',
|
||||
stepInterval: 60,
|
||||
timeAggregation: 'rate',
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
export const celeryRetryStateCountWidgetData = getWidgetQueryBuilder(
|
||||
getWidgetQuery({
|
||||
title: 'Retry State Count',
|
||||
description: 'Represents the retry state count.',
|
||||
panelTypes: PANEL_TYPES.VALUE,
|
||||
queryData: [
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.EMPTY,
|
||||
id: '------false',
|
||||
isColumn: false,
|
||||
key: '',
|
||||
type: '',
|
||||
},
|
||||
aggregateOperator: 'count',
|
||||
dataSource: DataSource.TRACES,
|
||||
disabled: false,
|
||||
expression: 'A',
|
||||
filters: {
|
||||
items: [
|
||||
{
|
||||
id: uuidv4(),
|
||||
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: [],
|
||||
having: [],
|
||||
legend: '',
|
||||
limit: null,
|
||||
orderBy: [],
|
||||
queryName: 'A',
|
||||
reduceTo: 'avg',
|
||||
spaceAggregation: 'sum',
|
||||
stepInterval: 60,
|
||||
timeAggregation: 'rate',
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
@ -1,8 +1,17 @@
|
||||
/* eslint-disable no-nested-ternary */
|
||||
import './CeleryTaskGraph.style.scss';
|
||||
|
||||
import { Col, Row } from 'antd';
|
||||
import { Dispatch, SetStateAction } from 'react';
|
||||
|
||||
import {
|
||||
celeryAllStateCountWidgetData,
|
||||
celeryFailedStateCountWidgetData,
|
||||
celeryRetryStateCountWidgetData,
|
||||
celerySuccessStateCountWidgetData,
|
||||
} from './CeleryTaskGraphUtils';
|
||||
import { useGetValueFromWidget } from './useGetValueFromWidget';
|
||||
|
||||
interface TabData {
|
||||
label: string;
|
||||
key: string;
|
||||
@ -33,6 +42,16 @@ function CeleryTaskStateGraphConfig({
|
||||
setBarState(key as CeleryTaskState);
|
||||
};
|
||||
|
||||
const { values, isLoading, isError } = useGetValueFromWidget(
|
||||
[
|
||||
celeryAllStateCountWidgetData,
|
||||
celeryFailedStateCountWidgetData,
|
||||
celeryRetryStateCountWidgetData,
|
||||
celerySuccessStateCountWidgetData,
|
||||
],
|
||||
['celery-task-states'],
|
||||
);
|
||||
|
||||
return (
|
||||
<Row className="celery-task-states">
|
||||
{tabs.map((tab, index) => (
|
||||
@ -46,6 +65,9 @@ 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]}
|
||||
</div>
|
||||
</div>
|
||||
{tab.key === barState && <div className="celery-task-states__indicator" />}
|
||||
</Col>
|
||||
|
@ -0,0 +1,63 @@
|
||||
import { ENTITY_VERSION_V4 } from 'constants/app';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
|
||||
import { getQueryPayloadFromWidgetsData } from 'pages/Celery/CeleryOverview/CeleryOverviewUtils';
|
||||
import { useCallback } from 'react';
|
||||
import { useQueries } from 'react-query';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
interface UseGetValueResult {
|
||||
values: string[];
|
||||
isLoading: boolean;
|
||||
isError: boolean;
|
||||
}
|
||||
|
||||
export const useGetValueFromWidget = (
|
||||
widgetsData: Widgets | Widgets[],
|
||||
queryKey: string[],
|
||||
): UseGetValueResult => {
|
||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
|
||||
const queryPayloads = useCallback(
|
||||
() =>
|
||||
getQueryPayloadFromWidgetsData({
|
||||
start: Math.floor(minTime / 1000000000),
|
||||
end: Math.floor(maxTime / 1000000000),
|
||||
widgetsData: Array.isArray(widgetsData) ? widgetsData : [widgetsData],
|
||||
panelType: PANEL_TYPES.VALUE,
|
||||
}),
|
||||
[minTime, maxTime, widgetsData],
|
||||
);
|
||||
|
||||
const queries = useQueries(
|
||||
queryPayloads().map((payload) => ({
|
||||
queryKey: [...queryKey, payload, ENTITY_VERSION_V4],
|
||||
queryFn: (): Promise<SuccessResponse<MetricRangePayloadProps>> =>
|
||||
GetMetricQueryRange(payload, ENTITY_VERSION_V4),
|
||||
enabled: !!payload,
|
||||
})),
|
||||
);
|
||||
|
||||
const isLoading = queries.some((query) => query.isLoading);
|
||||
const isError = queries.some((query) => query.isError);
|
||||
|
||||
const values = queries.map((query) => {
|
||||
if (query.isLoading) return 'Loading...';
|
||||
if (query.isError) return 'Error';
|
||||
|
||||
const value = parseFloat(
|
||||
query.data?.payload?.data?.newResult?.data?.result?.[0]?.series?.[0]
|
||||
?.values?.[0]?.value || 'NaN',
|
||||
);
|
||||
return Number.isNaN(value) ? 'NaN' : value.toFixed(2);
|
||||
});
|
||||
|
||||
return { values, isLoading, isError };
|
||||
};
|
@ -5,7 +5,7 @@ import { useState } from 'react';
|
||||
import { useGetAllFilters } from './CeleryTaskConfigOptions/useGetCeleryFilters';
|
||||
|
||||
export const useCeleryFilterOptions = (
|
||||
type: string,
|
||||
type: string | string[],
|
||||
): {
|
||||
searchText: string;
|
||||
handleSearch: (value: string) => void;
|
||||
|
65
frontend/src/components/CeleryTask/useNavigateToTraces.ts
Normal file
65
frontend/src/components/CeleryTask/useNavigateToTraces.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import { QueryParams } from 'constants/query';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useCallback } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { Query, TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { DataSource, MetricAggregateOperator } from 'types/common/queryBuilder';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
export function useNavigateToTraces(): (
|
||||
filters: TagFilterItem[],
|
||||
startTime?: number,
|
||||
endTime?: number,
|
||||
) => void {
|
||||
const { currentQuery } = useQueryBuilder();
|
||||
const { minTime, maxTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
|
||||
const prepareQuery = useCallback(
|
||||
(selectedFilters: TagFilterItem[]): Query => ({
|
||||
...currentQuery,
|
||||
builder: {
|
||||
...currentQuery.builder,
|
||||
queryData: currentQuery.builder.queryData.map((item) => ({
|
||||
...item,
|
||||
dataSource: DataSource.TRACES,
|
||||
aggregateOperator: MetricAggregateOperator.NOOP,
|
||||
filters: {
|
||||
...item.filters,
|
||||
items: selectedFilters,
|
||||
},
|
||||
})),
|
||||
},
|
||||
}),
|
||||
[currentQuery],
|
||||
);
|
||||
|
||||
return useCallback(
|
||||
(filters: TagFilterItem[], startTime?: number, endTime?: number): void => {
|
||||
const urlParams = new URLSearchParams();
|
||||
if (startTime && endTime) {
|
||||
urlParams.set(QueryParams.startTime, startTime.toString());
|
||||
urlParams.set(QueryParams.endTime, endTime.toString());
|
||||
} else {
|
||||
urlParams.set(QueryParams.startTime, (minTime / 1000000).toString());
|
||||
urlParams.set(QueryParams.endTime, (maxTime / 1000000).toString());
|
||||
}
|
||||
|
||||
const JSONCompositeQuery = encodeURIComponent(
|
||||
JSON.stringify(prepareQuery(filters)),
|
||||
);
|
||||
|
||||
const newTraceExplorerPath = `${
|
||||
ROUTES.TRACES_EXPLORER
|
||||
}?${urlParams.toString()}&${
|
||||
QueryParams.compositeQuery
|
||||
}=${JSONCompositeQuery}`;
|
||||
|
||||
window.open(newTraceExplorerPath, '_blank');
|
||||
},
|
||||
[minTime, maxTime, prepareQuery],
|
||||
);
|
||||
}
|
@ -44,4 +44,6 @@ export enum QueryParams {
|
||||
taskName = 'taskName',
|
||||
spanName = 'spanName',
|
||||
msgSystem = 'msgSystem',
|
||||
destination = 'destination',
|
||||
kindString = 'kindString',
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ const ROUTES = {
|
||||
INFRASTRUCTURE_MONITORING_HOSTS: '/infrastructure-monitoring/hosts',
|
||||
INFRASTRUCTURE_MONITORING_KUBERNETES: '/infrastructure-monitoring/kubernetes',
|
||||
MESSAGING_QUEUES_CELERY_TASK: '/messaging-queues/celery-task',
|
||||
MESSAGING_QUEUES_CELERY_OVERVIEW: '/messaging-queues/celery-overview',
|
||||
MESSAGING_QUEUES_OVERVIEW: '/messaging-queues/overview',
|
||||
} as const;
|
||||
|
||||
export default ROUTES;
|
||||
|
@ -290,7 +290,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
routeKey === 'MESSAGING_QUEUES' ||
|
||||
routeKey === 'MESSAGING_QUEUES_DETAIL' ||
|
||||
routeKey === 'MESSAGING_QUEUES_CELERY_TASK' ||
|
||||
routeKey === 'MESSAGING_QUEUES_CELERY_OVERVIEW';
|
||||
routeKey === 'MESSAGING_QUEUES_OVERVIEW';
|
||||
|
||||
const isDashboardListView = (): boolean => routeKey === 'ALL_DASHBOARD';
|
||||
const isAlertHistory = (): boolean => routeKey === 'ALERT_HISTORY';
|
||||
|
@ -52,7 +52,7 @@ export const routeConfig: Record<string, QueryParams[]> = {
|
||||
[ROUTES.MESSAGING_QUEUES]: [QueryParams.resourceAttributes],
|
||||
[ROUTES.MESSAGING_QUEUES_DETAIL]: [QueryParams.resourceAttributes],
|
||||
[ROUTES.MESSAGING_QUEUES_CELERY_TASK]: [QueryParams.resourceAttributes],
|
||||
[ROUTES.MESSAGING_QUEUES_CELERY_OVERVIEW]: [QueryParams.resourceAttributes],
|
||||
[ROUTES.MESSAGING_QUEUES_OVERVIEW]: [QueryParams.resourceAttributes],
|
||||
[ROUTES.INFRASTRUCTURE_MONITORING_HOSTS]: [QueryParams.resourceAttributes],
|
||||
[ROUTES.INFRASTRUCTURE_MONITORING_KUBERNETES]: [
|
||||
QueryParams.resourceAttributes,
|
||||
|
@ -215,7 +215,7 @@ export const routesToSkip = [
|
||||
ROUTES.MESSAGING_QUEUES,
|
||||
ROUTES.MESSAGING_QUEUES_DETAIL,
|
||||
ROUTES.MESSAGING_QUEUES_CELERY_TASK,
|
||||
ROUTES.MESSAGING_QUEUES_CELERY_OVERVIEW,
|
||||
ROUTES.MESSAGING_QUEUES_OVERVIEW,
|
||||
ROUTES.INFRASTRUCTURE_MONITORING_HOSTS,
|
||||
ROUTES.SOMETHING_WENT_WRONG,
|
||||
ROUTES.INFRASTRUCTURE_MONITORING_KUBERNETES,
|
||||
|
@ -1,20 +1,41 @@
|
||||
import './CeleryOverview.styles.scss';
|
||||
|
||||
import CeleryOverviewConfigOptions from 'components/CeleryOverview/CeleryOverviewConfigOptions/CeleryOverviewConfigOptions';
|
||||
import CeleryOverviewTable from 'components/CeleryOverview/CeleryOverviewTable/CeleryOverviewTable';
|
||||
import CeleryOverviewTable, {
|
||||
RowData,
|
||||
} from 'components/CeleryOverview/CeleryOverviewTable/CeleryOverviewTable';
|
||||
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
|
||||
import { useState } from 'react';
|
||||
|
||||
import CeleryOverviewDetails from './CeleryOverviewDetail/CeleryOverviewDetails';
|
||||
|
||||
export default function CeleryOverview(): JSX.Element {
|
||||
const [details, setDetails] = useState<RowData | null>(null);
|
||||
|
||||
const onRowClick = (record: RowData): void => {
|
||||
setDetails(record);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="celery-overview-container">
|
||||
<div className="celery-overview-content">
|
||||
<div className="celery-overview-content-header">
|
||||
<p className="celery-overview-content-header-title">Celery Overview</p>
|
||||
<p className="celery-overview-content-header-title">
|
||||
Messaging Queue Overview
|
||||
</p>
|
||||
<DateTimeSelectionV2 showAutoRefresh={false} hideShareModal />
|
||||
</div>
|
||||
<CeleryOverviewConfigOptions />
|
||||
<CeleryOverviewTable />
|
||||
<CeleryOverviewTable onRowClick={onRowClick} />
|
||||
</div>
|
||||
{details && (
|
||||
<CeleryOverviewDetails
|
||||
details={details}
|
||||
onClose={(): void => {
|
||||
setDetails(null);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -0,0 +1,30 @@
|
||||
.celery-overview-detail-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
padding: 16px 8px;
|
||||
}
|
||||
|
||||
.overview-right-panel-graph-card {
|
||||
border-radius: 8px;
|
||||
border: 1px solid #262626 !important;
|
||||
background: #0a0a0a;
|
||||
box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.1),
|
||||
0px 1px 2px -1px rgba(0, 0, 0, 0.1);
|
||||
|
||||
.request-rate-card,
|
||||
.error-rate-card,
|
||||
.avg-latency-card {
|
||||
height: 320px !important;
|
||||
padding: 10px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
border-bottom: 0.75px solid #262626;
|
||||
margin-bottom: 12px;
|
||||
position: relative;
|
||||
|
||||
.widget-graph-container {
|
||||
height: 260px;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
import './CeleryOverviewDetails.styles.scss';
|
||||
|
||||
import { Color, Spacing } from '@signozhq/design-tokens';
|
||||
import { Divider, Drawer, Typography } from 'antd';
|
||||
import { RowData } from 'components/CeleryOverview/CeleryOverviewTable/CeleryOverviewTable';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { X } from 'lucide-react';
|
||||
import { useMemo } from 'react';
|
||||
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import { getFiltersFromKeyValue } from '../CeleryOverviewUtils';
|
||||
import OverviewRightPanelGraph from './OverviewRightPanelGraph';
|
||||
import ValueInfo from './ValueInfo';
|
||||
|
||||
export default function CeleryOverviewDetails({
|
||||
details,
|
||||
onClose,
|
||||
}: {
|
||||
details: RowData;
|
||||
onClose: () => void;
|
||||
}): JSX.Element {
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
const filters: TagFilterItem[] = useMemo(() => {
|
||||
const keyValues = Object.entries(details).map(([key, value]) => {
|
||||
switch (key) {
|
||||
case 'service_name':
|
||||
return getFiltersFromKeyValue('service.name', value, 'resource');
|
||||
case 'span_name':
|
||||
return getFiltersFromKeyValue('name', value, '');
|
||||
case 'messaging_system':
|
||||
return getFiltersFromKeyValue('messaging.system', value, 'tag');
|
||||
case 'destination':
|
||||
return getFiltersFromKeyValue(
|
||||
details.messaging_system === 'celery'
|
||||
? 'messaging.destination'
|
||||
: 'messaging.destination.name',
|
||||
value,
|
||||
'tag',
|
||||
);
|
||||
case 'kind_string':
|
||||
return getFiltersFromKeyValue('kind_string', value, '');
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
|
||||
return keyValues.filter((item) => item !== undefined) as TagFilterItem[];
|
||||
}, [details]);
|
||||
|
||||
const groupByFilter = useMemo(
|
||||
() =>
|
||||
getFiltersFromKeyValue('messaging.destination.partition.id', '', 'tag').key,
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
width="60%"
|
||||
title={
|
||||
<div>
|
||||
<Typography.Text className="title">{details.service_name}</Typography.Text>
|
||||
<div>
|
||||
<Typography.Text className="subtitle">
|
||||
{details.span_name}
|
||||
</Typography.Text>
|
||||
<Divider type="vertical" />
|
||||
<Typography.Text className="subtitle">
|
||||
{details.messaging_system}
|
||||
</Typography.Text>
|
||||
<Divider type="vertical" />
|
||||
<Typography.Text className="subtitle">
|
||||
{details.destination}
|
||||
</Typography.Text>
|
||||
<Divider type="vertical" />
|
||||
<Typography.Text className="subtitle">
|
||||
{details.kind_string}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
placement="right"
|
||||
onClose={onClose}
|
||||
open={!!details}
|
||||
style={{
|
||||
overscrollBehavior: 'contain',
|
||||
background: isDarkMode ? Color.BG_INK_400 : Color.BG_VANILLA_100,
|
||||
}}
|
||||
className="celery-task-detail-drawer"
|
||||
destroyOnClose
|
||||
closeIcon={<X size={16} style={{ marginTop: Spacing.MARGIN_1 }} />}
|
||||
>
|
||||
<div className="celery-overview-detail-container">
|
||||
<ValueInfo filters={filters} />
|
||||
<OverviewRightPanelGraph filters={filters} groupByFilter={groupByFilter} />
|
||||
</div>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
@ -0,0 +1,169 @@
|
||||
import { Card } from 'antd';
|
||||
import { useNavigateToTraces } from 'components/CeleryTask/useNavigateToTraces';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { ViewMenuAction } from 'container/GridCardLayout/config';
|
||||
import GridCard from 'container/GridCardLayout/GridCard';
|
||||
import { Button } from 'container/MetricsApplication/Tabs/styles';
|
||||
import { onGraphClickHandler } from 'container/MetricsApplication/Tabs/util';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { OnClickPluginOpts } from 'lib/uPlotLib/plugins/onClickPlugin';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import { UpdateTimeInterval } from 'store/actions';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import {
|
||||
celeryOverviewAvgLatencyGraphData,
|
||||
celeryOverviewErrorRateGraphData,
|
||||
celeryOverviewRequestRateGraphData,
|
||||
} from '../CeleryOverviewGraphUtils';
|
||||
|
||||
export default function OverviewRightPanelGraph({
|
||||
groupByFilter,
|
||||
filters,
|
||||
}: {
|
||||
groupByFilter?: BaseAutocompleteData;
|
||||
filters?: TagFilterItem[];
|
||||
}): JSX.Element {
|
||||
const history = useHistory();
|
||||
const { pathname } = useLocation();
|
||||
const dispatch = useDispatch();
|
||||
const urlQuery = useUrlQuery();
|
||||
|
||||
const onDragSelect = useCallback(
|
||||
(start: number, end: number) => {
|
||||
const startTimestamp = Math.trunc(start);
|
||||
const endTimestamp = Math.trunc(end);
|
||||
|
||||
urlQuery.set(QueryParams.startTime, startTimestamp.toString());
|
||||
urlQuery.set(QueryParams.endTime, endTimestamp.toString());
|
||||
const generatedUrl = `${pathname}?${urlQuery.toString()}`;
|
||||
history.push(generatedUrl);
|
||||
|
||||
if (startTimestamp !== endTimestamp) {
|
||||
dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp]));
|
||||
}
|
||||
},
|
||||
[dispatch, history, pathname, urlQuery],
|
||||
);
|
||||
|
||||
const { minTime, maxTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
|
||||
const requestRateWidget = useMemo(
|
||||
() =>
|
||||
celeryOverviewRequestRateGraphData(minTime, maxTime, filters, groupByFilter),
|
||||
[minTime, maxTime, filters, groupByFilter],
|
||||
);
|
||||
|
||||
const errorRateWidget = useMemo(
|
||||
() =>
|
||||
celeryOverviewErrorRateGraphData(minTime, maxTime, filters, groupByFilter),
|
||||
[minTime, maxTime, filters, groupByFilter],
|
||||
);
|
||||
|
||||
const avgLatencyWidget = useMemo(
|
||||
() =>
|
||||
celeryOverviewAvgLatencyGraphData(minTime, maxTime, filters, groupByFilter),
|
||||
[minTime, maxTime, filters, groupByFilter],
|
||||
);
|
||||
const [selectedTimeStamp, setSelectedTimeStamp] = useState<number>(0);
|
||||
|
||||
const handleSetTimeStamp = useCallback((selectTime: number) => {
|
||||
setSelectedTimeStamp(selectTime);
|
||||
}, []);
|
||||
|
||||
const navigateToTraces = useNavigateToTraces();
|
||||
|
||||
const handleGraphClick = useCallback(
|
||||
(type: string): OnClickPluginOpts['onClick'] => (
|
||||
xValue,
|
||||
yValue,
|
||||
mouseX,
|
||||
mouseY,
|
||||
): Promise<void> =>
|
||||
onGraphClickHandler(handleSetTimeStamp)(
|
||||
xValue,
|
||||
yValue,
|
||||
mouseX,
|
||||
mouseY,
|
||||
type,
|
||||
),
|
||||
[handleSetTimeStamp],
|
||||
);
|
||||
|
||||
const goToTraces = useCallback(
|
||||
(widget: Widgets) => {
|
||||
const { stepInterval } = widget?.query?.builder?.queryData?.[0] ?? {};
|
||||
navigateToTraces(
|
||||
filters ?? [],
|
||||
selectedTimeStamp,
|
||||
selectedTimeStamp + (stepInterval ?? 60),
|
||||
);
|
||||
},
|
||||
[navigateToTraces, filters, selectedTimeStamp],
|
||||
);
|
||||
return (
|
||||
<Card className="overview-right-panel-graph-card">
|
||||
<div className="request-rate-card">
|
||||
<Button
|
||||
type="default"
|
||||
size="small"
|
||||
id="Celery_request_rate_button"
|
||||
onClick={(): void => goToTraces(requestRateWidget)}
|
||||
>
|
||||
View Traces
|
||||
</Button>
|
||||
<GridCard
|
||||
widget={requestRateWidget}
|
||||
headerMenuList={[...ViewMenuAction]}
|
||||
onDragSelect={onDragSelect}
|
||||
onClickHandler={handleGraphClick('Celery_request_rate')}
|
||||
/>
|
||||
</div>
|
||||
<div className="error-rate-card">
|
||||
<Button
|
||||
type="default"
|
||||
size="small"
|
||||
id="Celery_error_rate_button"
|
||||
onClick={(): void => goToTraces(errorRateWidget)}
|
||||
>
|
||||
View Traces
|
||||
</Button>
|
||||
<GridCard
|
||||
widget={errorRateWidget}
|
||||
headerMenuList={[...ViewMenuAction]}
|
||||
onDragSelect={onDragSelect}
|
||||
onClickHandler={handleGraphClick('Celery_error_rate')}
|
||||
/>
|
||||
</div>
|
||||
<div className="avg-latency-card">
|
||||
<Button
|
||||
type="default"
|
||||
size="small"
|
||||
id="Celery_avg_latency_button"
|
||||
onClick={(): void => goToTraces(avgLatencyWidget)}
|
||||
>
|
||||
View Traces
|
||||
</Button>
|
||||
<GridCard
|
||||
widget={avgLatencyWidget}
|
||||
headerMenuList={[...ViewMenuAction]}
|
||||
onDragSelect={onDragSelect}
|
||||
onClickHandler={handleGraphClick('Celery_avg_latency')}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
OverviewRightPanelGraph.defaultProps = {
|
||||
groupByFilter: undefined,
|
||||
filters: undefined,
|
||||
};
|
@ -0,0 +1,63 @@
|
||||
.value-info-card {
|
||||
border-radius: 8px;
|
||||
border: 1px solid #262626 !important;
|
||||
background: #0a0a0a;
|
||||
box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.1),
|
||||
0px 1px 2px -1px rgba(0, 0, 0, 0.1);
|
||||
|
||||
.metric-column {
|
||||
.metric-title {
|
||||
color: #fafafa;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.metric-value-container {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.metric-value {
|
||||
font-size: 24px;
|
||||
font-weight: 400;
|
||||
|
||||
&.red {
|
||||
color: #f87171;
|
||||
}
|
||||
|
||||
&.green {
|
||||
color: #4ade80;
|
||||
}
|
||||
|
||||
&.loading {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.metric-unit {
|
||||
color: #a3a3a3;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.metric-reference {
|
||||
color: #a3a3a3;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.trace-button {
|
||||
margin-top: 8px;
|
||||
background: #262626;
|
||||
border: none;
|
||||
|
||||
&:hover {
|
||||
background: #404040;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,209 @@
|
||||
import './ValueInfo.styles.scss';
|
||||
|
||||
import { FileSearchOutlined } from '@ant-design/icons';
|
||||
import { Button, Card, Col, Row } from 'antd';
|
||||
import { useNavigateToTraces } from 'components/CeleryTask/useNavigateToTraces';
|
||||
import { ENTITY_VERSION_V4 } from 'constants/app';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useQueries } from 'react-query';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import {
|
||||
celeryOverviewAvgLatencyWidgetData,
|
||||
celeryOverviewErrorRateWidgetData,
|
||||
celeryOverviewRequestRateWidgetData,
|
||||
} from '../CeleryOverviewGraphUtils';
|
||||
import { getQueryPayloadFromWidgetsData } from '../CeleryOverviewUtils';
|
||||
|
||||
export default function ValueInfo({
|
||||
filters,
|
||||
}: {
|
||||
filters?: TagFilterItem[];
|
||||
}): JSX.Element {
|
||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
|
||||
// get data from api
|
||||
const queryPayloads = useMemo(
|
||||
() =>
|
||||
getQueryPayloadFromWidgetsData({
|
||||
start: Math.floor(minTime / 1000000000),
|
||||
end: Math.floor(maxTime / 1000000000),
|
||||
widgetsData: [
|
||||
celeryOverviewRequestRateWidgetData(filters),
|
||||
celeryOverviewErrorRateWidgetData(filters),
|
||||
celeryOverviewAvgLatencyWidgetData(filters),
|
||||
],
|
||||
panelType: PANEL_TYPES.VALUE,
|
||||
}),
|
||||
[minTime, maxTime, filters],
|
||||
);
|
||||
|
||||
const queries = useQueries(
|
||||
queryPayloads.map((payload) => ({
|
||||
queryKey: [
|
||||
'overview-detail',
|
||||
payload,
|
||||
ENTITY_VERSION_V4,
|
||||
'overview-right-panel',
|
||||
],
|
||||
queryFn: (): Promise<SuccessResponse<MetricRangePayloadProps>> =>
|
||||
GetMetricQueryRange(payload, ENTITY_VERSION_V4),
|
||||
enabled: !!payload,
|
||||
})),
|
||||
);
|
||||
|
||||
const getValues = useCallback(
|
||||
() =>
|
||||
queries.map((query) => {
|
||||
const value = parseFloat(
|
||||
query.data?.payload?.data?.newResult?.data?.result?.[0]?.series?.[0]
|
||||
?.values?.[0]?.value || 'NaN',
|
||||
);
|
||||
return Number.isNaN(value) ? 'NaN' : value.toFixed(2);
|
||||
}),
|
||||
[queries],
|
||||
);
|
||||
|
||||
const isLoading = queries.some((query) => query.isLoading);
|
||||
const [requestRate, errorRate, avgLatency] = useMemo(
|
||||
() => (isLoading ? ['0', '0', '0'] : getValues()),
|
||||
[isLoading, getValues],
|
||||
);
|
||||
|
||||
const navigateToTrace = useNavigateToTraces();
|
||||
|
||||
const avgLatencyInMs = useMemo(() => {
|
||||
if (avgLatency === 'NaN') return 'NaN';
|
||||
const numericValue = parseFloat(avgLatency);
|
||||
return (numericValue / 1000000).toFixed(2);
|
||||
}, [avgLatency]);
|
||||
|
||||
const getColorBasedOnValue = (value: string): string => {
|
||||
const numericValue = parseFloat(value);
|
||||
if (value === 'NaN') return 'gray';
|
||||
if (numericValue < 3) return 'green';
|
||||
if (numericValue < 8) return 'yellow';
|
||||
return 'red';
|
||||
};
|
||||
|
||||
const getColorForLatency = (value: string): string => {
|
||||
const numericValue = parseFloat(value);
|
||||
if (value === 'NaN') return 'gray';
|
||||
if (numericValue < 100) return 'green';
|
||||
if (numericValue < 200) return 'yellow';
|
||||
return 'red';
|
||||
};
|
||||
|
||||
const getColorForErrorRate = (value: string): string => {
|
||||
const numericValue = parseFloat(value);
|
||||
if (value === 'NaN') return 'gray';
|
||||
if (numericValue < 60) return 'green';
|
||||
if (numericValue < 90) return 'yellow';
|
||||
return 'red';
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="value-info-card">
|
||||
<Row gutter={16}>
|
||||
<Col span={8} className="metric-column">
|
||||
<div className="metric-title">Request Rate</div>
|
||||
<div className="metric-value-container">
|
||||
<div
|
||||
className={`metric-value ${getColorBasedOnValue(requestRate)} ${
|
||||
isLoading ? 'loading' : ''
|
||||
}`}
|
||||
>
|
||||
{requestRate === 'NaN' ? '0' : requestRate}
|
||||
</div>
|
||||
<div className="metric-unit">req/s</div>
|
||||
</div>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<FileSearchOutlined />}
|
||||
className="trace-button"
|
||||
disabled={isLoading}
|
||||
onClick={(): void => navigateToTrace(filters ?? [])}
|
||||
>
|
||||
View Traces
|
||||
</Button>
|
||||
</Col>
|
||||
<Col span={8} className="metric-column">
|
||||
<div className="metric-title">Error Rate</div>
|
||||
<div className="metric-value-container">
|
||||
<div
|
||||
className={`metric-value ${getColorForErrorRate(errorRate)} ${
|
||||
isLoading ? 'loading' : ''
|
||||
}`}
|
||||
>
|
||||
{errorRate === 'NaN' ? '0' : errorRate}
|
||||
</div>
|
||||
<div className="metric-unit">%</div>
|
||||
</div>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<FileSearchOutlined />}
|
||||
className="trace-button"
|
||||
disabled={isLoading}
|
||||
onClick={(): void =>
|
||||
navigateToTrace([
|
||||
...(filters ?? []),
|
||||
{
|
||||
id: uuidv4(),
|
||||
key: {
|
||||
dataType: DataTypes.bool,
|
||||
id: 'has_error--bool----true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'has_error',
|
||||
type: '',
|
||||
},
|
||||
op: '=',
|
||||
value: 'true',
|
||||
},
|
||||
])
|
||||
}
|
||||
>
|
||||
View Traces
|
||||
</Button>
|
||||
</Col>
|
||||
<Col span={8} className="metric-column">
|
||||
<div className="metric-title">Average Latency</div>
|
||||
<div className="metric-value-container">
|
||||
<div
|
||||
className={`metric-value ${getColorForLatency(avgLatencyInMs)} ${
|
||||
isLoading ? 'loading' : ''
|
||||
}`}
|
||||
>
|
||||
{avgLatencyInMs === 'NaN' ? '0' : avgLatencyInMs}
|
||||
</div>
|
||||
<div className="metric-unit">ms</div>
|
||||
</div>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<FileSearchOutlined />}
|
||||
className="trace-button"
|
||||
disabled={isLoading}
|
||||
onClick={(): void => navigateToTrace(filters ?? [])}
|
||||
>
|
||||
View Traces
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
ValueInfo.defaultProps = {
|
||||
filters: undefined,
|
||||
};
|
@ -0,0 +1,407 @@
|
||||
/* eslint-disable sonarjs/no-duplicate-string */
|
||||
import { getStepInterval } from 'components/CeleryTask/CeleryTaskGraph/CeleryTaskGraphUtils';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { getWidgetQueryBuilder } from 'container/MetricsApplication/MetricsApplication.factory';
|
||||
import { getWidgetQuery } from 'pages/MessagingQueues/MQDetails/MetricPage/MetricPageUtil';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import {
|
||||
BaseAutocompleteData,
|
||||
DataTypes,
|
||||
} from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
export const celeryOverviewRequestRateWidgetData = (
|
||||
filters?: TagFilterItem[],
|
||||
): Widgets =>
|
||||
getWidgetQueryBuilder(
|
||||
getWidgetQuery({
|
||||
title: 'Request Rate',
|
||||
description: 'Represents request rate of the service',
|
||||
queryData: [
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.EMPTY,
|
||||
id: '------false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: '',
|
||||
type: '',
|
||||
},
|
||||
aggregateOperator: 'rate',
|
||||
dataSource: DataSource.TRACES,
|
||||
disabled: false,
|
||||
expression: 'A',
|
||||
filters: {
|
||||
items: filters ?? [],
|
||||
op: 'AND',
|
||||
},
|
||||
functions: [],
|
||||
groupBy: [],
|
||||
having: [],
|
||||
legend: '',
|
||||
limit: null,
|
||||
orderBy: [],
|
||||
queryName: 'A',
|
||||
reduceTo: 'avg',
|
||||
spaceAggregation: 'sum',
|
||||
stepInterval: 60,
|
||||
timeAggregation: 'rate',
|
||||
},
|
||||
],
|
||||
panelTypes: PANEL_TYPES.VALUE,
|
||||
}),
|
||||
);
|
||||
|
||||
export const celeryOverviewErrorRateWidgetData = (
|
||||
filters?: TagFilterItem[],
|
||||
): Widgets =>
|
||||
getWidgetQueryBuilder(
|
||||
getWidgetQuery({
|
||||
title: 'Error',
|
||||
description: 'Represents Error in the service',
|
||||
queryData: [
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.EMPTY,
|
||||
id: '------false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: '',
|
||||
type: '',
|
||||
},
|
||||
aggregateOperator: 'rate',
|
||||
dataSource: DataSource.TRACES,
|
||||
disabled: false,
|
||||
expression: 'A',
|
||||
filters: {
|
||||
items: [
|
||||
...(filters ?? []),
|
||||
{
|
||||
id: uuidv4(),
|
||||
key: {
|
||||
dataType: DataTypes.bool,
|
||||
id: 'has_error--bool----true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'has_error',
|
||||
type: '',
|
||||
},
|
||||
op: '=',
|
||||
value: 'true',
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
},
|
||||
functions: [],
|
||||
groupBy: [],
|
||||
having: [],
|
||||
legend: '',
|
||||
limit: null,
|
||||
orderBy: [],
|
||||
queryName: 'A',
|
||||
reduceTo: 'avg',
|
||||
spaceAggregation: 'sum',
|
||||
stepInterval: 60,
|
||||
timeAggregation: 'rate',
|
||||
},
|
||||
],
|
||||
panelTypes: PANEL_TYPES.VALUE,
|
||||
}),
|
||||
);
|
||||
|
||||
export const celeryOverviewAvgLatencyWidgetData = (
|
||||
filters?: TagFilterItem[],
|
||||
): Widgets =>
|
||||
getWidgetQueryBuilder(
|
||||
getWidgetQuery({
|
||||
title: 'Avg Latency',
|
||||
description: 'Represents Avg Latency of the service',
|
||||
queryData: [
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.Float64,
|
||||
id: 'duration_nano--float64----true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'duration_nano',
|
||||
type: '',
|
||||
},
|
||||
aggregateOperator: 'p95',
|
||||
dataSource: DataSource.TRACES,
|
||||
disabled: false,
|
||||
expression: 'A',
|
||||
filters: {
|
||||
items: filters ?? [],
|
||||
op: 'AND',
|
||||
},
|
||||
functions: [],
|
||||
groupBy: [],
|
||||
having: [],
|
||||
legend: 'p95',
|
||||
limit: null,
|
||||
orderBy: [],
|
||||
queryName: 'A',
|
||||
reduceTo: 'avg',
|
||||
spaceAggregation: 'sum',
|
||||
stepInterval: 60,
|
||||
timeAggregation: 'p95',
|
||||
},
|
||||
],
|
||||
panelTypes: PANEL_TYPES.VALUE,
|
||||
}),
|
||||
);
|
||||
|
||||
export const celeryOverviewRequestRateGraphData = (
|
||||
startTime: number,
|
||||
endTime: number,
|
||||
filters?: TagFilterItem[],
|
||||
groupByFilter?: BaseAutocompleteData,
|
||||
): Widgets =>
|
||||
getWidgetQueryBuilder(
|
||||
getWidgetQuery({
|
||||
title: 'Request rate',
|
||||
description: 'Represents Request rate of the service',
|
||||
queryData: [
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.EMPTY,
|
||||
id: '------false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: '',
|
||||
type: '',
|
||||
},
|
||||
aggregateOperator: 'rate',
|
||||
dataSource: DataSource.TRACES,
|
||||
disabled: false,
|
||||
expression: 'A',
|
||||
filters: {
|
||||
items: filters ?? [],
|
||||
op: 'AND',
|
||||
},
|
||||
functions: [],
|
||||
groupBy: groupByFilter ? [groupByFilter] : [],
|
||||
having: [],
|
||||
legend: 'Request Rate',
|
||||
limit: null,
|
||||
orderBy: [],
|
||||
queryName: 'A',
|
||||
reduceTo: 'avg',
|
||||
spaceAggregation: 'sum',
|
||||
stepInterval: getStepInterval(startTime, endTime),
|
||||
timeAggregation: 'rate',
|
||||
},
|
||||
],
|
||||
panelTypes: PANEL_TYPES.BAR,
|
||||
}),
|
||||
);
|
||||
|
||||
export const celeryOverviewErrorRateGraphData = (
|
||||
startTime: number,
|
||||
endTime: number,
|
||||
filters?: TagFilterItem[],
|
||||
groupByFilter?: BaseAutocompleteData,
|
||||
): Widgets =>
|
||||
getWidgetQueryBuilder(
|
||||
getWidgetQuery({
|
||||
title: 'Error',
|
||||
description: 'Represents Error in the service',
|
||||
queryData: [
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.EMPTY,
|
||||
id: '------false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: '',
|
||||
type: '',
|
||||
},
|
||||
aggregateOperator: 'rate',
|
||||
dataSource: DataSource.TRACES,
|
||||
disabled: false,
|
||||
expression: 'A',
|
||||
filters: {
|
||||
items: [
|
||||
...(filters ?? []),
|
||||
{
|
||||
id: uuidv4(),
|
||||
key: {
|
||||
dataType: DataTypes.bool,
|
||||
id: 'has_error--bool----true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'has_error',
|
||||
type: '',
|
||||
},
|
||||
op: '=',
|
||||
value: 'true',
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
},
|
||||
functions: [],
|
||||
groupBy: groupByFilter ? [groupByFilter] : [],
|
||||
having: [],
|
||||
legend: 'True',
|
||||
limit: null,
|
||||
orderBy: [],
|
||||
queryName: 'A',
|
||||
reduceTo: 'avg',
|
||||
spaceAggregation: 'sum',
|
||||
stepInterval: getStepInterval(startTime, endTime),
|
||||
timeAggregation: 'rate',
|
||||
},
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.EMPTY,
|
||||
id: '------false',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
key: '',
|
||||
type: '',
|
||||
},
|
||||
aggregateOperator: 'rate',
|
||||
dataSource: DataSource.TRACES,
|
||||
disabled: false,
|
||||
expression: 'B',
|
||||
filters: {
|
||||
items: [
|
||||
...(filters ?? []),
|
||||
{
|
||||
id: uuidv4(),
|
||||
key: {
|
||||
dataType: DataTypes.bool,
|
||||
id: 'has_error--bool----true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'has_error',
|
||||
type: '',
|
||||
},
|
||||
op: '=',
|
||||
value: 'false',
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
},
|
||||
functions: [],
|
||||
groupBy: groupByFilter ? [groupByFilter] : [],
|
||||
having: [],
|
||||
legend: 'False',
|
||||
limit: null,
|
||||
orderBy: [],
|
||||
queryName: 'B',
|
||||
reduceTo: 'avg',
|
||||
spaceAggregation: 'sum',
|
||||
stepInterval: getStepInterval(startTime, endTime),
|
||||
timeAggregation: 'rate',
|
||||
},
|
||||
],
|
||||
panelTypes: PANEL_TYPES.BAR,
|
||||
}),
|
||||
);
|
||||
|
||||
export const celeryOverviewAvgLatencyGraphData = (
|
||||
startTime: number,
|
||||
endTime: number,
|
||||
filters?: TagFilterItem[],
|
||||
groupByFilter?: BaseAutocompleteData,
|
||||
): Widgets =>
|
||||
getWidgetQueryBuilder(
|
||||
getWidgetQuery({
|
||||
title: 'Latency',
|
||||
description: 'Represents Latency of the service',
|
||||
queryData: [
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.ArrayFloat64,
|
||||
id: 'duration_nano--float64----true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'duration_nano',
|
||||
type: '',
|
||||
},
|
||||
aggregateOperator: 'p90',
|
||||
dataSource: DataSource.TRACES,
|
||||
disabled: false,
|
||||
expression: 'C',
|
||||
filters: {
|
||||
items: filters ?? [],
|
||||
op: 'AND',
|
||||
},
|
||||
functions: [],
|
||||
groupBy: groupByFilter ? [groupByFilter] : [],
|
||||
having: [],
|
||||
legend: 'p90',
|
||||
limit: null,
|
||||
orderBy: [],
|
||||
queryName: 'C',
|
||||
reduceTo: 'avg',
|
||||
spaceAggregation: 'sum',
|
||||
stepInterval: getStepInterval(startTime, endTime),
|
||||
timeAggregation: 'p90',
|
||||
},
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.ArrayFloat64,
|
||||
id: 'duration_nano--float64----true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'duration_nano',
|
||||
type: '',
|
||||
},
|
||||
aggregateOperator: 'p95',
|
||||
dataSource: DataSource.TRACES,
|
||||
disabled: false,
|
||||
expression: 'D',
|
||||
filters: {
|
||||
items: filters ?? [],
|
||||
op: 'AND',
|
||||
},
|
||||
functions: [],
|
||||
groupBy: groupByFilter ? [groupByFilter] : [],
|
||||
having: [],
|
||||
legend: 'p95',
|
||||
limit: null,
|
||||
orderBy: [],
|
||||
queryName: 'D',
|
||||
reduceTo: 'avg',
|
||||
spaceAggregation: 'sum',
|
||||
stepInterval: getStepInterval(startTime, endTime),
|
||||
timeAggregation: 'p95',
|
||||
},
|
||||
{
|
||||
aggregateAttribute: {
|
||||
dataType: DataTypes.ArrayFloat64,
|
||||
id: 'duration_nano--float64----true',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
key: 'duration_nano',
|
||||
type: '',
|
||||
},
|
||||
aggregateOperator: 'p99',
|
||||
dataSource: DataSource.TRACES,
|
||||
disabled: false,
|
||||
expression: 'E',
|
||||
filters: {
|
||||
items: filters ?? [],
|
||||
op: 'AND',
|
||||
},
|
||||
functions: [],
|
||||
groupBy: groupByFilter ? [groupByFilter] : [],
|
||||
having: [],
|
||||
legend: 'p99',
|
||||
limit: null,
|
||||
orderBy: [],
|
||||
queryName: 'E',
|
||||
reduceTo: 'avg',
|
||||
spaceAggregation: 'sum',
|
||||
stepInterval: getStepInterval(startTime, endTime),
|
||||
timeAggregation: 'p99',
|
||||
},
|
||||
],
|
||||
panelTypes: PANEL_TYPES.TIME_SERIES,
|
||||
yAxisUnit: 'ns',
|
||||
}),
|
||||
);
|
@ -0,0 +1,47 @@
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
export const getQueryPayloadFromWidgetsData = ({
|
||||
start,
|
||||
end,
|
||||
widgetsData,
|
||||
panelType,
|
||||
}: {
|
||||
start: number;
|
||||
end: number;
|
||||
widgetsData: Widgets[];
|
||||
panelType: PANEL_TYPES;
|
||||
}): GetQueryResultsProps[] =>
|
||||
widgetsData.map((widget) => ({
|
||||
start,
|
||||
end,
|
||||
graphType: panelType,
|
||||
query: widget.query,
|
||||
selectedTime: 'GLOBAL_TIME',
|
||||
formatForWeb: false,
|
||||
variables: {},
|
||||
}));
|
||||
|
||||
export const getFiltersFromKeyValue = (
|
||||
key: string,
|
||||
value: string | number,
|
||||
type?: string,
|
||||
op?: string,
|
||||
dataType?: DataTypes,
|
||||
): TagFilterItem => ({
|
||||
id: uuidv4(),
|
||||
key: {
|
||||
key,
|
||||
dataType: dataType || DataTypes.String,
|
||||
type: type || 'tag',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
id: `${key}--${dataType || DataTypes.String}--${type || 'tag'}--false`,
|
||||
},
|
||||
op: op || '=',
|
||||
value: value.toString(),
|
||||
});
|
@ -37,11 +37,11 @@ export const Overview: TabRoutes = {
|
||||
Component: CeleryOverview,
|
||||
name: (
|
||||
<div className="tab-item">
|
||||
<Rows3 size={16} /> Celery Overview
|
||||
<Rows3 size={16} /> Overview
|
||||
</div>
|
||||
),
|
||||
route: ROUTES.MESSAGING_QUEUES_CELERY_OVERVIEW,
|
||||
key: ROUTES.MESSAGING_QUEUES_CELERY_OVERVIEW,
|
||||
route: ROUTES.MESSAGING_QUEUES_OVERVIEW,
|
||||
key: ROUTES.MESSAGING_QUEUES_OVERVIEW,
|
||||
};
|
||||
|
||||
export default function MessagingQueuesMainPage(): JSX.Element {
|
||||
|
@ -109,5 +109,5 @@ export const routePermission: Record<keyof typeof ROUTES, ROLES[]> = {
|
||||
INFRASTRUCTURE_MONITORING_HOSTS: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
INFRASTRUCTURE_MONITORING_KUBERNETES: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
MESSAGING_QUEUES_CELERY_TASK: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
MESSAGING_QUEUES_CELERY_OVERVIEW: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
MESSAGING_QUEUES_OVERVIEW: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user