mirror of
https://github.com/SigNoz/signoz.git
synced 2025-12-18 16:07:10 +00:00
* feat: custom hook to prevent redundant navigation and handle default params with URL comparison * feat: implement useSafeNavigation to QB, to ensure that the back navigation works properly * fix: handle syncing the relativeTime param with the time picker selected relative time * feat: add support for absolute and relative time sync with time picker component * refactor: integrate safeNavigate in LogsExplorerChart and deprecate the existing back navigation * feat: update pagination query params on pressing next/prev page * fix: fix the issue of NOOP getting converted to Count on coming back from alert creation page * refactor: replace history navigation with safeNavigate in DateTimeSelectionV2 component it also fixes the issue of relativeTime not being added to the url on mounting * feat: integrate useSafeNavigate across service details tabs * fix: fix duplicate redirections by converting the timestamp to milliseconds * fix: replace history navigation with useSafeNavigate in LogsExplorerViews and useUrlQueryData * fix: replace history navigation with useSafeNavigate across dashboard components * fix: use safeNavigate in alert components * fix: fix the issue of back navigation in alert table and sync the pagination with url param * fix: handle back navigation for resource filter and sync the state with url query * fix: fix the issue of double redirection from top operations to traces * fix: replace history.push with safeNavigate in TracesExplorer's updateDashboard * fix: prevent unnecessary query re-runs by checking stagedQuery before redirecting in NewWidget * chore: cleanup * fix: fix the failing tests * fix: fix the documentation redirection failing tests * test: mock useSafeNavigate hook in WidgetGraphComponent test * test: mock useSafeNavigate hook in ExplorerCard test
215 lines
5.6 KiB
TypeScript
215 lines
5.6 KiB
TypeScript
import './TopOperationsTable.styles.scss';
|
|
|
|
import { SearchOutlined } from '@ant-design/icons';
|
|
import { InputRef, Tooltip, Typography } from 'antd';
|
|
import { ColumnsType, ColumnType } from 'antd/lib/table';
|
|
import { ResizeTable } from 'components/ResizeTable';
|
|
import Download from 'container/Download/Download';
|
|
import { filterDropdown } from 'container/ServiceApplication/Filter/FilterDropdown';
|
|
import useResourceAttribute from 'hooks/useResourceAttribute';
|
|
import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils';
|
|
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
|
import { useRef } from 'react';
|
|
import { useSelector } from 'react-redux';
|
|
import { useParams } from 'react-router-dom';
|
|
import { AppState } from 'store/reducers';
|
|
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
|
import { Query, TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
|
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
|
import { v4 as uuid } from 'uuid';
|
|
|
|
import { IServiceName } from './Tabs/types';
|
|
import { useGetAPMToTracesQueries } from './Tabs/util';
|
|
import {
|
|
convertedTracesToDownloadData,
|
|
getErrorRate,
|
|
navigateToTrace,
|
|
} from './utils';
|
|
|
|
function TopOperationsTable({
|
|
data,
|
|
isLoading,
|
|
}: TopOperationsTableProps): JSX.Element {
|
|
const searchInput = useRef<InputRef>(null);
|
|
const { servicename: encodedServiceName } = useParams<IServiceName>();
|
|
const { safeNavigate } = useSafeNavigate();
|
|
const servicename = decodeURIComponent(encodedServiceName);
|
|
const { minTime, maxTime } = useSelector<AppState, GlobalReducer>(
|
|
(state) => state.globalTime,
|
|
);
|
|
const { queries } = useResourceAttribute();
|
|
|
|
const selectedTraceTags: string = JSON.stringify(
|
|
convertRawQueriesToTraceSelectedTags(queries) || [],
|
|
);
|
|
|
|
const apmToTraceQuery = useGetAPMToTracesQueries({ servicename });
|
|
|
|
const params = useParams<{ servicename: string }>();
|
|
|
|
const handleOnClick = (operation: string): void => {
|
|
const { servicename: encodedServiceName } = params;
|
|
const servicename = decodeURIComponent(encodedServiceName);
|
|
|
|
const opFilters: TagFilterItem[] = [
|
|
{
|
|
id: uuid().slice(0, 8),
|
|
key: {
|
|
key: 'name',
|
|
dataType: DataTypes.String,
|
|
type: 'tag',
|
|
isColumn: true,
|
|
isJSON: false,
|
|
id: 'name--string--tag--true',
|
|
},
|
|
op: 'in',
|
|
value: [operation],
|
|
},
|
|
];
|
|
|
|
const preparedQuery: Query = {
|
|
...apmToTraceQuery,
|
|
builder: {
|
|
...apmToTraceQuery.builder,
|
|
queryData: apmToTraceQuery.builder.queryData?.map((item) => ({
|
|
...item,
|
|
filters: {
|
|
...item.filters,
|
|
items: [...item.filters.items, ...opFilters],
|
|
},
|
|
})),
|
|
},
|
|
};
|
|
|
|
navigateToTrace({
|
|
servicename,
|
|
operation,
|
|
minTime,
|
|
maxTime,
|
|
selectedTraceTags,
|
|
apmToTraceQuery: preparedQuery,
|
|
safeNavigate,
|
|
});
|
|
};
|
|
|
|
const getSearchOption = (): ColumnType<TopOperationList> => ({
|
|
filterDropdown,
|
|
filterIcon: <SearchOutlined />,
|
|
onFilter: (value, record): boolean =>
|
|
record.name
|
|
.toString()
|
|
.toLowerCase()
|
|
.includes((value as string).toLowerCase()),
|
|
onFilterDropdownOpenChange: (visible): void => {
|
|
if (visible) {
|
|
setTimeout(() => searchInput.current?.select(), 100);
|
|
}
|
|
},
|
|
render: (text: string): JSX.Element => (
|
|
<Tooltip placement="topLeft" title={text}>
|
|
<Typography.Link onClick={(): void => handleOnClick(text)}>
|
|
{text}
|
|
</Typography.Link>
|
|
</Tooltip>
|
|
),
|
|
});
|
|
|
|
const columns: ColumnsType<TopOperationList> = [
|
|
{
|
|
title: 'Name',
|
|
dataIndex: 'name',
|
|
key: 'name',
|
|
width: 100,
|
|
...getSearchOption(),
|
|
},
|
|
{
|
|
title: 'P50 (in ms)',
|
|
dataIndex: 'p50',
|
|
key: 'p50',
|
|
width: 50,
|
|
sorter: (a: TopOperationList, b: TopOperationList): number => a.p50 - b.p50,
|
|
render: (value: number): string => (value / 1_000_000).toFixed(2),
|
|
},
|
|
{
|
|
title: 'P95 (in ms)',
|
|
dataIndex: 'p95',
|
|
key: 'p95',
|
|
width: 50,
|
|
sorter: (a: TopOperationList, b: TopOperationList): number => a.p95 - b.p95,
|
|
render: (value: number): string => (value / 1_000_000).toFixed(2),
|
|
},
|
|
{
|
|
title: 'P99 (in ms)',
|
|
dataIndex: 'p99',
|
|
key: 'p99',
|
|
width: 50,
|
|
sorter: (a: TopOperationList, b: TopOperationList): number => a.p99 - b.p99,
|
|
render: (value: number): string => (value / 1_000_000).toFixed(2),
|
|
},
|
|
{
|
|
title: 'Number of Calls',
|
|
dataIndex: 'numCalls',
|
|
key: 'numCalls',
|
|
width: 50,
|
|
sorter: (a: TopOperationList, b: TopOperationList): number =>
|
|
a.numCalls - b.numCalls,
|
|
},
|
|
{
|
|
title: 'Error Rate',
|
|
dataIndex: 'errorCount',
|
|
key: 'errorCount',
|
|
width: 50,
|
|
sorter: (first: TopOperationList, second: TopOperationList): number =>
|
|
getErrorRate(first) - getErrorRate(second),
|
|
render: (_, record: TopOperationList): string =>
|
|
`${getErrorRate(record).toFixed(2)} %`,
|
|
},
|
|
];
|
|
|
|
const downloadableData = convertedTracesToDownloadData(data);
|
|
|
|
const paginationConfig = {
|
|
pageSize: 10,
|
|
showSizeChanger: false,
|
|
hideOnSinglePage: true,
|
|
};
|
|
|
|
return (
|
|
<div className="top-operation">
|
|
<div className="top-operation--download">
|
|
<Download
|
|
data={downloadableData}
|
|
isLoading={isLoading}
|
|
fileName={`top-operations-${servicename}`}
|
|
/>
|
|
</div>
|
|
<ResizeTable
|
|
columns={columns}
|
|
loading={isLoading}
|
|
showHeader
|
|
title={(): string => 'Key Operations'}
|
|
tableLayout="fixed"
|
|
dataSource={data}
|
|
rowKey="name"
|
|
pagination={paginationConfig}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export interface TopOperationList {
|
|
p50: number;
|
|
p95: number;
|
|
p99: number;
|
|
numCalls: number;
|
|
name: string;
|
|
errorCount: number;
|
|
}
|
|
|
|
interface TopOperationsTableProps {
|
|
data: TopOperationList[];
|
|
isLoading: boolean;
|
|
}
|
|
|
|
export default TopOperationsTable;
|