mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-12 22:59:01 +08:00
feat: added top errors tab in domain details
This commit is contained in:
parent
d5e2841083
commit
b86e65d2ca
@ -55,6 +55,7 @@ export const REACT_QUERY_KEY = {
|
||||
// API Monitoring Query Keys
|
||||
GET_DOMAINS_LIST: 'GET_DOMAINS_LIST',
|
||||
GET_ENDPOINTS_LIST_BY_DOMAIN: 'GET_ENDPOINTS_LIST_BY_DOMAIN',
|
||||
GET_TOP_ERRORS_BY_DOMAIN: 'GET_TOP_ERRORS_BY_DOMAIN',
|
||||
GET_NESTED_ENDPOINTS_LIST: 'GET_NESTED_ENDPOINTS_LIST',
|
||||
GET_ENDPOINT_METRICS_DATA: 'GET_ENDPOINT_METRICS_DATA',
|
||||
GET_ENDPOINT_STATUS_CODE_DATA: 'GET_ENDPOINT_STATUS_CODE_DATA',
|
||||
|
@ -21,6 +21,7 @@ import AllEndPoints from './AllEndPoints';
|
||||
import DomainMetrics from './components/DomainMetrics';
|
||||
import { VIEW_TYPES, VIEWS } from './constants';
|
||||
import EndPointDetailsWrapper from './EndPointDetailsWrapper';
|
||||
import TopErrors from './TopErrors';
|
||||
|
||||
const TimeRangeOffset = 1000000000;
|
||||
|
||||
@ -181,6 +182,14 @@ function DomainDetails({
|
||||
>
|
||||
<div className="view-title">Endpoint Details</div>
|
||||
</Radio.Button>
|
||||
<Radio.Button
|
||||
className={
|
||||
selectedView === VIEW_TYPES.TOP_ERRORS ? 'tab selected_view' : 'tab'
|
||||
}
|
||||
value={VIEW_TYPES.TOP_ERRORS}
|
||||
>
|
||||
<div className="view-title">Top Errors</div>
|
||||
</Radio.Button>
|
||||
</Radio.Group>
|
||||
</div>
|
||||
{selectedView === VIEW_TYPES.ALL_ENDPOINTS && (
|
||||
@ -203,6 +212,13 @@ function DomainDetails({
|
||||
timeRange={modalTimeRange}
|
||||
/>
|
||||
)}
|
||||
|
||||
{selectedView === VIEW_TYPES.TOP_ERRORS && (
|
||||
<TopErrors
|
||||
domainName={domainData.domainName}
|
||||
timeRange={modalTimeRange}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Drawer>
|
||||
|
@ -0,0 +1,120 @@
|
||||
import { LoadingOutlined } from '@ant-design/icons';
|
||||
import { Spin, Table, Typography } from 'antd';
|
||||
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
|
||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||
import {
|
||||
formatTopErrorsDataForTable,
|
||||
getTopErrorsColumnsConfig,
|
||||
getTopErrorsQueryPayload,
|
||||
TopErrorsResponseRow,
|
||||
} from 'container/ApiMonitoring/utils';
|
||||
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
|
||||
import { useMemo } from 'react';
|
||||
import { useQueries } from 'react-query';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
|
||||
import ErrorState from './components/ErrorState';
|
||||
|
||||
function TopErrors({
|
||||
domainName,
|
||||
timeRange,
|
||||
}: {
|
||||
domainName: string;
|
||||
timeRange: {
|
||||
startTime: number;
|
||||
endTime: number;
|
||||
};
|
||||
}): JSX.Element {
|
||||
const { startTime: minTime, endTime: maxTime } = timeRange;
|
||||
|
||||
const queryPayloads = useMemo(
|
||||
() => getTopErrorsQueryPayload(domainName, minTime, maxTime),
|
||||
[domainName, minTime, maxTime],
|
||||
);
|
||||
|
||||
// Since only one query here
|
||||
const topErrorsDataQueries = useQueries(
|
||||
queryPayloads.map((payload) => ({
|
||||
queryKey: [
|
||||
REACT_QUERY_KEY.GET_TOP_ERRORS_BY_DOMAIN,
|
||||
payload,
|
||||
DEFAULT_ENTITY_VERSION,
|
||||
],
|
||||
queryFn: (): Promise<SuccessResponse<MetricRangePayloadProps>> =>
|
||||
GetMetricQueryRange(payload, DEFAULT_ENTITY_VERSION),
|
||||
enabled: !!payload,
|
||||
staleTime: 60 * 1000, // 1 minute stale time : optimize this part
|
||||
})),
|
||||
);
|
||||
|
||||
const topErrorsDataQuery = topErrorsDataQueries[0];
|
||||
const {
|
||||
data: topErrorsData,
|
||||
isLoading,
|
||||
isRefetching,
|
||||
isError,
|
||||
refetch,
|
||||
} = topErrorsDataQuery;
|
||||
|
||||
const topErrorsColumnsConfig = useMemo(() => getTopErrorsColumnsConfig(), []);
|
||||
|
||||
const formattedTopErrorsData = useMemo(
|
||||
() =>
|
||||
formatTopErrorsDataForTable(
|
||||
topErrorsData?.payload?.data?.result as TopErrorsResponseRow[],
|
||||
),
|
||||
[topErrorsData],
|
||||
);
|
||||
|
||||
if (isError) {
|
||||
return (
|
||||
<div className="all-endpoints-error-state-wrapper">
|
||||
<ErrorState refetch={refetch} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
console.log('uncaught topErrors Data', formattedTopErrorsData);
|
||||
|
||||
return (
|
||||
<div className="all-endpoints-container">
|
||||
<div className="endpoints-table-container">
|
||||
<div className="endpoints-table-header">Top Errors</div>
|
||||
<Table
|
||||
columns={topErrorsColumnsConfig}
|
||||
loading={{
|
||||
spinning: isLoading || isRefetching,
|
||||
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
|
||||
}}
|
||||
dataSource={isLoading || isRefetching ? [] : formattedTopErrorsData}
|
||||
locale={{
|
||||
emptyText:
|
||||
isLoading || isRefetching ? null : (
|
||||
<div className="no-filtered-endpoints-message-container">
|
||||
<div className="no-filtered-endpoints-message-content">
|
||||
<img
|
||||
src="/Icons/emptyState.svg"
|
||||
alt="thinking-emoji"
|
||||
className="empty-state-svg"
|
||||
/>
|
||||
|
||||
<Typography.Text className="no-filtered-endpoints-message">
|
||||
This query had no results. Edit your query and try again!
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
}}
|
||||
scroll={{ x: true }}
|
||||
tableLayout="fixed"
|
||||
rowClassName={(_, index): string =>
|
||||
index % 2 === 0 ? 'table-row-dark' : 'table-row-light'
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default TopErrors;
|
@ -1,9 +1,11 @@
|
||||
export enum VIEWS {
|
||||
ALL_ENDPOINTS = 'all_endpoints',
|
||||
ENDPOINT_DETAILS = 'endpoint_details',
|
||||
TOP_ERRORS = 'top_errors',
|
||||
}
|
||||
|
||||
export const VIEW_TYPES = {
|
||||
ALL_ENDPOINTS: VIEWS.ALL_ENDPOINTS,
|
||||
ENDPOINT_DETAILS: VIEWS.ENDPOINT_DETAILS,
|
||||
TOP_ERRORS: VIEWS.TOP_ERRORS,
|
||||
};
|
||||
|
@ -633,6 +633,158 @@ export const getEndPointsQueryPayload = (
|
||||
];
|
||||
};
|
||||
|
||||
export const getTopErrorsQueryPayload = (
|
||||
domainName: string,
|
||||
start: number,
|
||||
end: number,
|
||||
): GetQueryResultsProps[] => [
|
||||
{
|
||||
selectedTime: 'GLOBAL_TIME',
|
||||
graphType: PANEL_TYPES.TABLE,
|
||||
query: {
|
||||
builder: {
|
||||
queryData: [
|
||||
{
|
||||
dataSource: DataSource.TRACES,
|
||||
queryName: 'A',
|
||||
aggregateOperator: 'count',
|
||||
aggregateAttribute: {
|
||||
id: '------false',
|
||||
dataType: DataTypes.String,
|
||||
key: '',
|
||||
isColumn: false,
|
||||
type: '',
|
||||
isJSON: false,
|
||||
},
|
||||
timeAggregation: 'rate',
|
||||
spaceAggregation: 'sum',
|
||||
functions: [],
|
||||
filters: {
|
||||
op: 'AND',
|
||||
items: [
|
||||
{
|
||||
id: '04da97bd',
|
||||
key: {
|
||||
key: 'kind_string',
|
||||
dataType: DataTypes.String,
|
||||
type: '',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
id: 'kind_string--string----true',
|
||||
},
|
||||
op: '=',
|
||||
value: 'Client',
|
||||
},
|
||||
{
|
||||
id: '75d65388',
|
||||
key: {
|
||||
key: 'status_message',
|
||||
dataType: DataTypes.String,
|
||||
type: '',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
id: 'status_message--string----true',
|
||||
},
|
||||
op: 'exists',
|
||||
value: '',
|
||||
},
|
||||
{
|
||||
id: 'b1af6bdb',
|
||||
key: {
|
||||
key: 'http.url',
|
||||
dataType: DataTypes.String,
|
||||
type: 'tag',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
id: 'http.url--string--tag--false',
|
||||
},
|
||||
op: 'exists',
|
||||
value: '',
|
||||
},
|
||||
{
|
||||
id: '4872bf91',
|
||||
key: {
|
||||
key: 'net.peer.name',
|
||||
dataType: DataTypes.String,
|
||||
type: 'tag',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
id: 'net.peer.name--string--tag--false',
|
||||
},
|
||||
op: '=',
|
||||
value: domainName,
|
||||
},
|
||||
],
|
||||
},
|
||||
expression: 'A',
|
||||
disabled: false,
|
||||
stepInterval: 60,
|
||||
having: [],
|
||||
limit: 10,
|
||||
orderBy: [
|
||||
{
|
||||
columnName: 'timestamp',
|
||||
order: 'desc',
|
||||
},
|
||||
],
|
||||
groupBy: [
|
||||
{
|
||||
key: 'http.url',
|
||||
dataType: DataTypes.String,
|
||||
type: 'tag',
|
||||
isColumn: false,
|
||||
isJSON: false,
|
||||
id: 'http.url--string--tag--false',
|
||||
},
|
||||
{
|
||||
key: 'status_code',
|
||||
dataType: DataTypes.Float64,
|
||||
type: '',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
id: 'status_code--float64----true',
|
||||
},
|
||||
{
|
||||
key: 'status_message',
|
||||
dataType: DataTypes.String,
|
||||
type: '',
|
||||
isColumn: true,
|
||||
isJSON: false,
|
||||
id: 'status_message--string----true',
|
||||
},
|
||||
],
|
||||
legend: '',
|
||||
reduceTo: 'avg',
|
||||
},
|
||||
],
|
||||
queryFormulas: [],
|
||||
},
|
||||
clickhouse_sql: [
|
||||
{
|
||||
disabled: false,
|
||||
legend: '',
|
||||
name: 'A',
|
||||
query: '',
|
||||
},
|
||||
],
|
||||
id: '315b15fa-ff0c-442f-89f8-2bf4fb1af2f2',
|
||||
promql: [
|
||||
{
|
||||
disabled: false,
|
||||
legend: '',
|
||||
name: 'A',
|
||||
query: '',
|
||||
},
|
||||
],
|
||||
queryType: EQueryType.QUERY_BUILDER,
|
||||
},
|
||||
variables: {},
|
||||
start,
|
||||
end,
|
||||
step: 240,
|
||||
},
|
||||
];
|
||||
|
||||
export interface EndPointsTableRowData {
|
||||
key: string;
|
||||
endpointName: string;
|
||||
@ -911,6 +1063,98 @@ export const formatEndPointsDataForTable = (
|
||||
return formattedData;
|
||||
};
|
||||
|
||||
export interface TopErrorsResponseRow {
|
||||
metric: {
|
||||
'http.url': string;
|
||||
status_code: string;
|
||||
status_message: string;
|
||||
};
|
||||
values: [number, string][];
|
||||
queryName: string;
|
||||
legend: string;
|
||||
}
|
||||
|
||||
export interface TopErrorsTableRowData {
|
||||
key: string;
|
||||
endpointName: string;
|
||||
statusCode: string;
|
||||
statusMessage: string;
|
||||
count: number | string;
|
||||
}
|
||||
|
||||
export const formatTopErrorsDataForTable = (
|
||||
data: TopErrorsResponseRow[] | undefined,
|
||||
): TopErrorsTableRowData[] => {
|
||||
if (!data) return [];
|
||||
|
||||
return data.map((row) => ({
|
||||
key: v4(),
|
||||
endpointName:
|
||||
row.metric['http.url'] === 'n/a' || row.metric['http.url'] === undefined
|
||||
? '-'
|
||||
: row.metric['http.url'],
|
||||
statusCode:
|
||||
row.metric.status_code === 'n/a' || row.metric.status_code === undefined
|
||||
? '-'
|
||||
: row.metric.status_code,
|
||||
statusMessage:
|
||||
row.metric.status_message === 'n/a' ||
|
||||
row.metric.status_message === undefined
|
||||
? '-'
|
||||
: row.metric.status_message,
|
||||
count:
|
||||
row.values &&
|
||||
row.values[0] &&
|
||||
row.values[0][1] !== undefined &&
|
||||
row.values[0][1] !== 'n/a'
|
||||
? row.values[0][1]
|
||||
: '-',
|
||||
}));
|
||||
};
|
||||
|
||||
export const getTopErrorsColumnsConfig = (): ColumnType<TopErrorsTableRowData>[] => [
|
||||
{
|
||||
title: <div className="endpoint-name-header">Endpoint</div>,
|
||||
dataIndex: 'endpointName',
|
||||
key: 'endpointName',
|
||||
width: 180,
|
||||
ellipsis: true,
|
||||
sorter: false,
|
||||
className: 'column',
|
||||
render: (text: string, record: TopErrorsTableRowData): React.ReactNode => (
|
||||
<div className="endpoint-name-value">{record.endpointName}</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: <div className="column-header">Status code</div>,
|
||||
dataIndex: 'statusCode',
|
||||
key: 'statusCode',
|
||||
width: 180,
|
||||
ellipsis: true,
|
||||
sorter: false,
|
||||
align: 'right',
|
||||
className: `column`,
|
||||
},
|
||||
{
|
||||
title: <div className="column-header">Status message</div>,
|
||||
dataIndex: 'statusMessage',
|
||||
key: 'statusMessage',
|
||||
width: 180,
|
||||
ellipsis: true,
|
||||
align: 'right',
|
||||
className: `column`,
|
||||
},
|
||||
{
|
||||
title: <div>Count</div>,
|
||||
dataIndex: 'count',
|
||||
key: 'count',
|
||||
width: 120,
|
||||
sorter: false,
|
||||
align: 'right',
|
||||
className: `column`,
|
||||
},
|
||||
];
|
||||
|
||||
export const createFiltersForSelectedRowData = (
|
||||
selectedRowData: EndPointsTableRowData,
|
||||
currentFilters?: IBuilderQuery['filters'],
|
||||
|
Loading…
x
Reference in New Issue
Block a user