mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-13 06:09:02 +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,
|
isPrivate: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: ROUTES.MESSAGING_QUEUES_CELERY_OVERVIEW,
|
path: ROUTES.MESSAGING_QUEUES_OVERVIEW,
|
||||||
exact: true,
|
exact: true,
|
||||||
component: MessagingQueues,
|
component: MessagingQueues,
|
||||||
key: 'MESSAGING_QUEUES_CELERY_OVERVIEW',
|
key: 'MESSAGING_QUEUES_OVERVIEW',
|
||||||
isPrivate: true,
|
isPrivate: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -10,6 +10,9 @@
|
|||||||
|
|
||||||
.config-select-option {
|
.config-select-option {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
min-width: 160px;
|
||||||
|
max-width: fit-content;
|
||||||
|
|
||||||
.ant-select-selector {
|
.ant-select-selector {
|
||||||
display: flex;
|
display: flex;
|
||||||
min-height: 32px;
|
min-height: 32px;
|
||||||
@ -23,6 +26,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.copy-url-btn {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.lightMode {
|
.lightMode {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import './CeleryOverviewConfigOptions.styles.scss';
|
import './CeleryOverviewConfigOptions.styles.scss';
|
||||||
|
|
||||||
import { Color } from '@signozhq/design-tokens';
|
import { Color } from '@signozhq/design-tokens';
|
||||||
import { Button, Select, Spin, Tooltip } from 'antd';
|
import { Button, Row, Select, Spin, Tooltip } from 'antd';
|
||||||
import {
|
import {
|
||||||
getValuesFromQueryParams,
|
getValuesFromQueryParams,
|
||||||
setQueryParamsFromOptions,
|
setQueryParamsFromOptions,
|
||||||
@ -18,7 +18,7 @@ import { useCopyToClipboard } from 'react-use';
|
|||||||
interface SelectOptionConfig {
|
interface SelectOptionConfig {
|
||||||
placeholder: string;
|
placeholder: string;
|
||||||
queryParam: QueryParams;
|
queryParam: QueryParams;
|
||||||
filterType: 'serviceName' | 'spanName' | 'msgSystem';
|
filterType: string | string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
function FilterSelect({
|
function FilterSelect({
|
||||||
@ -29,13 +29,14 @@ function FilterSelect({
|
|||||||
const { handleSearch, isFetching, options } = useCeleryFilterOptions(
|
const { handleSearch, isFetching, options } = useCeleryFilterOptions(
|
||||||
filterType,
|
filterType,
|
||||||
);
|
);
|
||||||
|
|
||||||
const urlQuery = useUrlQuery();
|
const urlQuery = useUrlQuery();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
key={filterType}
|
key={filterType.toString()}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
showSearch
|
showSearch
|
||||||
mode="multiple"
|
mode="multiple"
|
||||||
@ -44,6 +45,7 @@ function FilterSelect({
|
|||||||
className="config-select-option"
|
className="config-select-option"
|
||||||
onSearch={handleSearch}
|
onSearch={handleSearch}
|
||||||
maxTagCount={4}
|
maxTagCount={4}
|
||||||
|
allowClear
|
||||||
maxTagPlaceholder={SelectMaxTagPlaceholder}
|
maxTagPlaceholder={SelectMaxTagPlaceholder}
|
||||||
value={getValuesFromQueryParams(queryParam, urlQuery) || []}
|
value={getValuesFromQueryParams(queryParam, urlQuery) || []}
|
||||||
notFoundContent={
|
notFoundContent={
|
||||||
@ -74,16 +76,26 @@ function CeleryOverviewConfigOptions(): JSX.Element {
|
|||||||
queryParam: QueryParams.service,
|
queryParam: QueryParams.service,
|
||||||
filterType: 'serviceName',
|
filterType: 'serviceName',
|
||||||
},
|
},
|
||||||
// {
|
{
|
||||||
// placeholder: 'Span Name',
|
placeholder: 'Span Name',
|
||||||
// queryParam: QueryParams.spanName,
|
queryParam: QueryParams.spanName,
|
||||||
// filterType: 'spanName',
|
filterType: 'name',
|
||||||
// },
|
},
|
||||||
// {
|
{
|
||||||
// placeholder: 'Msg System',
|
placeholder: 'Msg System',
|
||||||
// queryParam: QueryParams.msgSystem,
|
queryParam: QueryParams.msgSystem,
|
||||||
// filterType: '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 => {
|
const handleShareURL = (): void => {
|
||||||
@ -96,16 +108,16 @@ function CeleryOverviewConfigOptions(): JSX.Element {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="celery-overview-filters">
|
<div className="celery-overview-filters">
|
||||||
<div className="celery-filters">
|
<Row className="celery-filters">
|
||||||
{selectConfigs.map((config) => (
|
{selectConfigs.map((config) => (
|
||||||
<FilterSelect
|
<FilterSelect
|
||||||
key={config.filterType}
|
key={config.filterType.toString()}
|
||||||
placeholder={config.placeholder}
|
placeholder={config.placeholder}
|
||||||
queryParam={config.queryParam}
|
queryParam={config.queryParam}
|
||||||
filterType={config.filterType}
|
filterType={config.filterType}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</Row>
|
||||||
<Tooltip title="Share this" arrow={false}>
|
<Tooltip title="Share this" arrow={false}>
|
||||||
<Button
|
<Button
|
||||||
className="periscope-btn copy-url-btn"
|
className="periscope-btn copy-url-btn"
|
||||||
|
@ -1,8 +1,20 @@
|
|||||||
import './CeleryOverviewTable.styles.scss';
|
import './CeleryOverviewTable.styles.scss';
|
||||||
|
|
||||||
import { LoadingOutlined } from '@ant-design/icons';
|
import { LoadingOutlined, SearchOutlined } from '@ant-design/icons';
|
||||||
import { Color } from '@signozhq/design-tokens';
|
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 {
|
import {
|
||||||
getQueueOverview,
|
getQueueOverview,
|
||||||
QueueOverviewResponse,
|
QueueOverviewResponse,
|
||||||
@ -10,10 +22,12 @@ import {
|
|||||||
import { isNumber } from 'chart.js/helpers';
|
import { isNumber } from 'chart.js/helpers';
|
||||||
import { ResizeTable } from 'components/ResizeTable';
|
import { ResizeTable } from 'components/ResizeTable';
|
||||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||||
|
import { QueryParams } from 'constants/query';
|
||||||
import useDragColumns from 'hooks/useDragColumns';
|
import useDragColumns from 'hooks/useDragColumns';
|
||||||
import { getDraggedColumns } from 'hooks/useDragColumns/utils';
|
import { getDraggedColumns } from 'hooks/useDragColumns/utils';
|
||||||
import useUrlQuery from 'hooks/useUrlQuery';
|
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 { useMutation } from 'react-query';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { AppState } from 'store/reducers';
|
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> {
|
function getColumns(data: RowData[]): TableColumnsType<RowData> {
|
||||||
if (data?.length === 0) {
|
if (data?.length === 0) {
|
||||||
return [];
|
return [];
|
||||||
@ -235,12 +317,11 @@ type FilterConfig = {
|
|||||||
|
|
||||||
function makeFilters(urlQuery: URLSearchParams): Filter[] {
|
function makeFilters(urlQuery: URLSearchParams): Filter[] {
|
||||||
const filterConfigs: FilterConfig[] = [
|
const filterConfigs: FilterConfig[] = [
|
||||||
{ paramName: 'destination', key: 'destination', operator: 'in' },
|
{ paramName: QueryParams.destination, key: 'destination', operator: 'in' },
|
||||||
{ paramName: 'queue', key: 'queue', operator: 'in' },
|
{ paramName: QueryParams.msgSystem, key: 'queue', operator: 'in' },
|
||||||
{ paramName: 'kind_string', key: 'kind_string', operator: 'in' },
|
{ paramName: QueryParams.kindString, key: 'kind_string', operator: 'in' },
|
||||||
{ paramName: 'service', key: 'service.name', operator: 'in' },
|
{ paramName: QueryParams.service, key: 'service.name', operator: 'in' },
|
||||||
{ paramName: 'span_name', key: 'span_name', operator: 'in' },
|
{ paramName: QueryParams.spanName, key: 'name', operator: 'in' },
|
||||||
{ paramName: 'messaging_system', key: 'messaging_system', operator: 'in' },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
return filterConfigs
|
return filterConfigs
|
||||||
@ -260,7 +341,11 @@ function makeFilters(urlQuery: URLSearchParams): Filter[] {
|
|||||||
.filter((filter): filter is Filter => filter !== null);
|
.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 [tableData, setTableData] = useState<RowData[]>([]);
|
||||||
|
|
||||||
const { minTime, maxTime } = useSelector<AppState, GlobalReducer>(
|
const { minTime, maxTime } = useSelector<AppState, GlobalReducer>(
|
||||||
@ -271,6 +356,8 @@ export default function CeleryOverviewTable(): JSX.Element {
|
|||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
if (data?.payload) {
|
if (data?.payload) {
|
||||||
setTableData(getTableData(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,
|
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(
|
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],
|
[tableData, draggedColumns],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleDragColumn = useCallback(
|
const handleDragColumn = useCallback(
|
||||||
(fromIndex: number, toIndex: number) =>
|
(fromIndex: number, toIndex: number) =>
|
||||||
onDragColumns(columns, fromIndex, toIndex),
|
onDragColumns(columns, fromIndex, toIndex),
|
||||||
@ -316,17 +434,44 @@ export default function CeleryOverviewTable(): JSX.Element {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleRowClick = (record: RowData): void => {
|
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 (
|
return (
|
||||||
<div style={{ width: '100%' }}>
|
<div style={{ width: '100%' }}>
|
||||||
|
<Input.Search
|
||||||
|
placeholder="Search across all columns"
|
||||||
|
onChange={(e): void => setSearchText(e.target.value)}
|
||||||
|
value={searchText}
|
||||||
|
allowClear
|
||||||
|
/>
|
||||||
<ResizeTable
|
<ResizeTable
|
||||||
className="celery-overview-table"
|
className="celery-overview-table"
|
||||||
pagination={paginationConfig}
|
pagination={paginationConfig}
|
||||||
size="middle"
|
size="middle"
|
||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={tableData}
|
dataSource={filteredData}
|
||||||
bordered={false}
|
bordered={false}
|
||||||
loading={{
|
loading={{
|
||||||
spinning: isLoading,
|
spinning: isLoading,
|
||||||
|
@ -7,7 +7,7 @@ import { DataSource } from 'types/common/queryBuilder';
|
|||||||
|
|
||||||
export interface Filters {
|
export interface Filters {
|
||||||
searchText: string;
|
searchText: string;
|
||||||
attributeKey: string;
|
attributeKey: string | string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GetAllFiltersResponse {
|
export interface GetAllFiltersResponse {
|
||||||
@ -19,27 +19,36 @@ export function useGetAllFilters(props: Filters): GetAllFiltersResponse {
|
|||||||
const { searchText, attributeKey } = props;
|
const { searchText, attributeKey } = props;
|
||||||
|
|
||||||
const { data, isLoading } = useQuery(
|
const { data, isLoading } = useQuery(
|
||||||
['attributesValues', searchText],
|
['attributesValues', attributeKey, searchText],
|
||||||
async () => {
|
async () => {
|
||||||
const { payload } = await getAttributesValues({
|
const keys = Array.isArray(attributeKey) ? attributeKey : [attributeKey];
|
||||||
|
|
||||||
|
const responses = await Promise.all(
|
||||||
|
keys.map((key) =>
|
||||||
|
getAttributesValues({
|
||||||
aggregateOperator: 'noop',
|
aggregateOperator: 'noop',
|
||||||
dataSource: DataSource.TRACES,
|
dataSource: DataSource.TRACES,
|
||||||
aggregateAttribute: '',
|
aggregateAttribute: '',
|
||||||
attributeKey,
|
attributeKey: key,
|
||||||
searchText: searchText ?? '',
|
searchText: searchText ?? '',
|
||||||
filterAttributeKeyDataType: DataTypes.String,
|
filterAttributeKeyDataType: DataTypes.String,
|
||||||
tagType: 'tag',
|
tagType: 'tag',
|
||||||
});
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
if (payload) {
|
const uniqueValues = [
|
||||||
const values = Object.values(payload).find((el) => !!el) || [];
|
...new Set(
|
||||||
const options: DefaultOptionType[] = values.map((val: string) => ({
|
responses.flatMap(
|
||||||
|
({ payload }) => Object.values(payload || {}).find((el) => !!el) || [],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
return uniqueValues.map((val: string) => ({
|
||||||
label: val,
|
label: val,
|
||||||
value: val,
|
value: val,
|
||||||
}));
|
}));
|
||||||
return options;
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -4,14 +4,11 @@ import { Color, Spacing } from '@signozhq/design-tokens';
|
|||||||
import { Divider, Drawer, Typography } from 'antd';
|
import { Divider, Drawer, Typography } from 'antd';
|
||||||
import { QueryParams } from 'constants/query';
|
import { QueryParams } from 'constants/query';
|
||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
import ROUTES from 'constants/routes';
|
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
|
||||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||||
import useUrlQuery from 'hooks/useUrlQuery';
|
import useUrlQuery from 'hooks/useUrlQuery';
|
||||||
import { RowData } from 'lib/query/createTableColumnsFromQuery';
|
|
||||||
import { X } from 'lucide-react';
|
import { X } from 'lucide-react';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { useHistory, useLocation } from 'react-router-dom';
|
import { useHistory, useLocation } from 'react-router-dom';
|
||||||
import { UpdateTimeInterval } from 'store/actions';
|
import { UpdateTimeInterval } from 'store/actions';
|
||||||
@ -19,12 +16,11 @@ import { AppState } from 'store/reducers';
|
|||||||
import { Widgets } from 'types/api/dashboard/getAll';
|
import { Widgets } from 'types/api/dashboard/getAll';
|
||||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
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 { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
import CeleryTaskGraph from '../CeleryTaskGraph/CeleryTaskGraph';
|
import CeleryTaskGraph from '../CeleryTaskGraph/CeleryTaskGraph';
|
||||||
|
import { useNavigateToTraces } from '../useNavigateToTraces';
|
||||||
|
|
||||||
export type CeleryTaskData = {
|
export type CeleryTaskData = {
|
||||||
entity: string;
|
entity: string;
|
||||||
@ -143,46 +139,7 @@ export default function CeleryTaskDetail({
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const { currentQuery } = useQueryBuilder();
|
const navigateToTrace = useNavigateToTraces();
|
||||||
|
|
||||||
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');
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Drawer
|
<Drawer
|
||||||
@ -223,7 +180,13 @@ export default function CeleryTaskDetail({
|
|||||||
panelType={PANEL_TYPES.TABLE}
|
panelType={PANEL_TYPES.TABLE}
|
||||||
queryEnabled
|
queryEnabled
|
||||||
openTracesButton
|
openTracesButton
|
||||||
onOpenTraceBtnClick={navigateToTrace}
|
onOpenTraceBtnClick={(rowData): void => {
|
||||||
|
const filters = createFiltersFromData({
|
||||||
|
...rowData,
|
||||||
|
[taskData.entity]: taskData.value,
|
||||||
|
});
|
||||||
|
navigateToTrace(filters);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
);
|
);
|
||||||
|
@ -140,7 +140,6 @@ function CeleryTaskBar({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const customSeries = (data: QueryData[]): uPlot.Series[] => {
|
const customSeries = (data: QueryData[]): uPlot.Series[] => {
|
||||||
console.log(data);
|
|
||||||
const configurations: uPlot.Series[] = [
|
const configurations: uPlot.Series[] = [
|
||||||
{ label: 'Timestamp', stroke: 'purple' },
|
{ label: 'Timestamp', stroke: 'purple' },
|
||||||
];
|
];
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
|
|
||||||
.widget-graph-container {
|
.widget-graph-container {
|
||||||
&.bar {
|
&.bar {
|
||||||
height: calc(100% - 85px);
|
height: calc(100% - 105px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -45,9 +45,12 @@
|
|||||||
height: calc(100% - 18px);
|
height: calc(100% - 18px);
|
||||||
|
|
||||||
.widget-graph-container {
|
.widget-graph-container {
|
||||||
&.bar,
|
&.bar {
|
||||||
|
height: calc(100% - 105px);
|
||||||
|
}
|
||||||
|
|
||||||
&.graph {
|
&.graph {
|
||||||
height: calc(100% - 85px);
|
height: calc(100% - 80px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -93,7 +96,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__label-wrapper {
|
&__label-wrapper {
|
||||||
margin-bottom: 8px;
|
margin-bottom: 4px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__label {
|
&__label {
|
||||||
|
@ -917,3 +917,215 @@ export const celeryTimeSeriesTablesWidgetData = (
|
|||||||
columnUnits: { A: 'ns' },
|
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 './CeleryTaskGraph.style.scss';
|
||||||
|
|
||||||
import { Col, Row } from 'antd';
|
import { Col, Row } from 'antd';
|
||||||
import { Dispatch, SetStateAction } from 'react';
|
import { Dispatch, SetStateAction } from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
celeryAllStateCountWidgetData,
|
||||||
|
celeryFailedStateCountWidgetData,
|
||||||
|
celeryRetryStateCountWidgetData,
|
||||||
|
celerySuccessStateCountWidgetData,
|
||||||
|
} from './CeleryTaskGraphUtils';
|
||||||
|
import { useGetValueFromWidget } from './useGetValueFromWidget';
|
||||||
|
|
||||||
interface TabData {
|
interface TabData {
|
||||||
label: string;
|
label: string;
|
||||||
key: string;
|
key: string;
|
||||||
@ -33,6 +42,16 @@ function CeleryTaskStateGraphConfig({
|
|||||||
setBarState(key as CeleryTaskState);
|
setBarState(key as CeleryTaskState);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { values, isLoading, isError } = useGetValueFromWidget(
|
||||||
|
[
|
||||||
|
celeryAllStateCountWidgetData,
|
||||||
|
celeryFailedStateCountWidgetData,
|
||||||
|
celeryRetryStateCountWidgetData,
|
||||||
|
celerySuccessStateCountWidgetData,
|
||||||
|
],
|
||||||
|
['celery-task-states'],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row className="celery-task-states">
|
<Row className="celery-task-states">
|
||||||
{tabs.map((tab, index) => (
|
{tabs.map((tab, index) => (
|
||||||
@ -46,6 +65,9 @@ function CeleryTaskStateGraphConfig({
|
|||||||
>
|
>
|
||||||
<div className="celery-task-states__label-wrapper">
|
<div className="celery-task-states__label-wrapper">
|
||||||
<div className="celery-task-states__label">{tab.label}</div>
|
<div className="celery-task-states__label">{tab.label}</div>
|
||||||
|
<div className="celery-task-states__value">
|
||||||
|
{isLoading ? '-' : isError ? '-' : values[index]}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{tab.key === barState && <div className="celery-task-states__indicator" />}
|
{tab.key === barState && <div className="celery-task-states__indicator" />}
|
||||||
</Col>
|
</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';
|
import { useGetAllFilters } from './CeleryTaskConfigOptions/useGetCeleryFilters';
|
||||||
|
|
||||||
export const useCeleryFilterOptions = (
|
export const useCeleryFilterOptions = (
|
||||||
type: string,
|
type: string | string[],
|
||||||
): {
|
): {
|
||||||
searchText: string;
|
searchText: string;
|
||||||
handleSearch: (value: string) => void;
|
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',
|
taskName = 'taskName',
|
||||||
spanName = 'spanName',
|
spanName = 'spanName',
|
||||||
msgSystem = 'msgSystem',
|
msgSystem = 'msgSystem',
|
||||||
|
destination = 'destination',
|
||||||
|
kindString = 'kindString',
|
||||||
}
|
}
|
||||||
|
@ -64,7 +64,7 @@ const ROUTES = {
|
|||||||
INFRASTRUCTURE_MONITORING_HOSTS: '/infrastructure-monitoring/hosts',
|
INFRASTRUCTURE_MONITORING_HOSTS: '/infrastructure-monitoring/hosts',
|
||||||
INFRASTRUCTURE_MONITORING_KUBERNETES: '/infrastructure-monitoring/kubernetes',
|
INFRASTRUCTURE_MONITORING_KUBERNETES: '/infrastructure-monitoring/kubernetes',
|
||||||
MESSAGING_QUEUES_CELERY_TASK: '/messaging-queues/celery-task',
|
MESSAGING_QUEUES_CELERY_TASK: '/messaging-queues/celery-task',
|
||||||
MESSAGING_QUEUES_CELERY_OVERVIEW: '/messaging-queues/celery-overview',
|
MESSAGING_QUEUES_OVERVIEW: '/messaging-queues/overview',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export default ROUTES;
|
export default ROUTES;
|
||||||
|
@ -290,7 +290,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
|||||||
routeKey === 'MESSAGING_QUEUES' ||
|
routeKey === 'MESSAGING_QUEUES' ||
|
||||||
routeKey === 'MESSAGING_QUEUES_DETAIL' ||
|
routeKey === 'MESSAGING_QUEUES_DETAIL' ||
|
||||||
routeKey === 'MESSAGING_QUEUES_CELERY_TASK' ||
|
routeKey === 'MESSAGING_QUEUES_CELERY_TASK' ||
|
||||||
routeKey === 'MESSAGING_QUEUES_CELERY_OVERVIEW';
|
routeKey === 'MESSAGING_QUEUES_OVERVIEW';
|
||||||
|
|
||||||
const isDashboardListView = (): boolean => routeKey === 'ALL_DASHBOARD';
|
const isDashboardListView = (): boolean => routeKey === 'ALL_DASHBOARD';
|
||||||
const isAlertHistory = (): boolean => routeKey === 'ALERT_HISTORY';
|
const isAlertHistory = (): boolean => routeKey === 'ALERT_HISTORY';
|
||||||
|
@ -52,7 +52,7 @@ export const routeConfig: Record<string, QueryParams[]> = {
|
|||||||
[ROUTES.MESSAGING_QUEUES]: [QueryParams.resourceAttributes],
|
[ROUTES.MESSAGING_QUEUES]: [QueryParams.resourceAttributes],
|
||||||
[ROUTES.MESSAGING_QUEUES_DETAIL]: [QueryParams.resourceAttributes],
|
[ROUTES.MESSAGING_QUEUES_DETAIL]: [QueryParams.resourceAttributes],
|
||||||
[ROUTES.MESSAGING_QUEUES_CELERY_TASK]: [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_HOSTS]: [QueryParams.resourceAttributes],
|
||||||
[ROUTES.INFRASTRUCTURE_MONITORING_KUBERNETES]: [
|
[ROUTES.INFRASTRUCTURE_MONITORING_KUBERNETES]: [
|
||||||
QueryParams.resourceAttributes,
|
QueryParams.resourceAttributes,
|
||||||
|
@ -215,7 +215,7 @@ export const routesToSkip = [
|
|||||||
ROUTES.MESSAGING_QUEUES,
|
ROUTES.MESSAGING_QUEUES,
|
||||||
ROUTES.MESSAGING_QUEUES_DETAIL,
|
ROUTES.MESSAGING_QUEUES_DETAIL,
|
||||||
ROUTES.MESSAGING_QUEUES_CELERY_TASK,
|
ROUTES.MESSAGING_QUEUES_CELERY_TASK,
|
||||||
ROUTES.MESSAGING_QUEUES_CELERY_OVERVIEW,
|
ROUTES.MESSAGING_QUEUES_OVERVIEW,
|
||||||
ROUTES.INFRASTRUCTURE_MONITORING_HOSTS,
|
ROUTES.INFRASTRUCTURE_MONITORING_HOSTS,
|
||||||
ROUTES.SOMETHING_WENT_WRONG,
|
ROUTES.SOMETHING_WENT_WRONG,
|
||||||
ROUTES.INFRASTRUCTURE_MONITORING_KUBERNETES,
|
ROUTES.INFRASTRUCTURE_MONITORING_KUBERNETES,
|
||||||
|
@ -1,20 +1,41 @@
|
|||||||
import './CeleryOverview.styles.scss';
|
import './CeleryOverview.styles.scss';
|
||||||
|
|
||||||
import CeleryOverviewConfigOptions from 'components/CeleryOverview/CeleryOverviewConfigOptions/CeleryOverviewConfigOptions';
|
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 DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
import CeleryOverviewDetails from './CeleryOverviewDetail/CeleryOverviewDetails';
|
||||||
|
|
||||||
export default function CeleryOverview(): JSX.Element {
|
export default function CeleryOverview(): JSX.Element {
|
||||||
|
const [details, setDetails] = useState<RowData | null>(null);
|
||||||
|
|
||||||
|
const onRowClick = (record: RowData): void => {
|
||||||
|
setDetails(record);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="celery-overview-container">
|
<div className="celery-overview-container">
|
||||||
<div className="celery-overview-content">
|
<div className="celery-overview-content">
|
||||||
<div className="celery-overview-content-header">
|
<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 />
|
<DateTimeSelectionV2 showAutoRefresh={false} hideShareModal />
|
||||||
</div>
|
</div>
|
||||||
<CeleryOverviewConfigOptions />
|
<CeleryOverviewConfigOptions />
|
||||||
<CeleryOverviewTable />
|
<CeleryOverviewTable onRowClick={onRowClick} />
|
||||||
</div>
|
</div>
|
||||||
|
{details && (
|
||||||
|
<CeleryOverviewDetails
|
||||||
|
details={details}
|
||||||
|
onClose={(): void => {
|
||||||
|
setDetails(null);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</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,
|
Component: CeleryOverview,
|
||||||
name: (
|
name: (
|
||||||
<div className="tab-item">
|
<div className="tab-item">
|
||||||
<Rows3 size={16} /> Celery Overview
|
<Rows3 size={16} /> Overview
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
route: ROUTES.MESSAGING_QUEUES_CELERY_OVERVIEW,
|
route: ROUTES.MESSAGING_QUEUES_OVERVIEW,
|
||||||
key: ROUTES.MESSAGING_QUEUES_CELERY_OVERVIEW,
|
key: ROUTES.MESSAGING_QUEUES_OVERVIEW,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function MessagingQueuesMainPage(): JSX.Element {
|
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_HOSTS: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||||
INFRASTRUCTURE_MONITORING_KUBERNETES: ['ADMIN', 'EDITOR', 'VIEWER'],
|
INFRASTRUCTURE_MONITORING_KUBERNETES: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||||
MESSAGING_QUEUES_CELERY_TASK: ['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