mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-14 05:36:03 +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
|
// API Monitoring Query Keys
|
||||||
GET_DOMAINS_LIST: 'GET_DOMAINS_LIST',
|
GET_DOMAINS_LIST: 'GET_DOMAINS_LIST',
|
||||||
GET_ENDPOINTS_LIST_BY_DOMAIN: 'GET_ENDPOINTS_LIST_BY_DOMAIN',
|
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_NESTED_ENDPOINTS_LIST: 'GET_NESTED_ENDPOINTS_LIST',
|
||||||
GET_ENDPOINT_METRICS_DATA: 'GET_ENDPOINT_METRICS_DATA',
|
GET_ENDPOINT_METRICS_DATA: 'GET_ENDPOINT_METRICS_DATA',
|
||||||
GET_ENDPOINT_STATUS_CODE_DATA: 'GET_ENDPOINT_STATUS_CODE_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 DomainMetrics from './components/DomainMetrics';
|
||||||
import { VIEW_TYPES, VIEWS } from './constants';
|
import { VIEW_TYPES, VIEWS } from './constants';
|
||||||
import EndPointDetailsWrapper from './EndPointDetailsWrapper';
|
import EndPointDetailsWrapper from './EndPointDetailsWrapper';
|
||||||
|
import TopErrors from './TopErrors';
|
||||||
|
|
||||||
const TimeRangeOffset = 1000000000;
|
const TimeRangeOffset = 1000000000;
|
||||||
|
|
||||||
@ -181,6 +182,14 @@ function DomainDetails({
|
|||||||
>
|
>
|
||||||
<div className="view-title">Endpoint Details</div>
|
<div className="view-title">Endpoint Details</div>
|
||||||
</Radio.Button>
|
</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>
|
</Radio.Group>
|
||||||
</div>
|
</div>
|
||||||
{selectedView === VIEW_TYPES.ALL_ENDPOINTS && (
|
{selectedView === VIEW_TYPES.ALL_ENDPOINTS && (
|
||||||
@ -203,6 +212,13 @@ function DomainDetails({
|
|||||||
timeRange={modalTimeRange}
|
timeRange={modalTimeRange}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{selectedView === VIEW_TYPES.TOP_ERRORS && (
|
||||||
|
<TopErrors
|
||||||
|
domainName={domainData.domainName}
|
||||||
|
timeRange={modalTimeRange}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Drawer>
|
</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 {
|
export enum VIEWS {
|
||||||
ALL_ENDPOINTS = 'all_endpoints',
|
ALL_ENDPOINTS = 'all_endpoints',
|
||||||
ENDPOINT_DETAILS = 'endpoint_details',
|
ENDPOINT_DETAILS = 'endpoint_details',
|
||||||
|
TOP_ERRORS = 'top_errors',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const VIEW_TYPES = {
|
export const VIEW_TYPES = {
|
||||||
ALL_ENDPOINTS: VIEWS.ALL_ENDPOINTS,
|
ALL_ENDPOINTS: VIEWS.ALL_ENDPOINTS,
|
||||||
ENDPOINT_DETAILS: VIEWS.ENDPOINT_DETAILS,
|
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 {
|
export interface EndPointsTableRowData {
|
||||||
key: string;
|
key: string;
|
||||||
endpointName: string;
|
endpointName: string;
|
||||||
@ -911,6 +1063,98 @@ export const formatEndPointsDataForTable = (
|
|||||||
return formattedData;
|
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 = (
|
export const createFiltersForSelectedRowData = (
|
||||||
selectedRowData: EndPointsTableRowData,
|
selectedRowData: EndPointsTableRowData,
|
||||||
currentFilters?: IBuilderQuery['filters'],
|
currentFilters?: IBuilderQuery['filters'],
|
||||||
|
Loading…
x
Reference in New Issue
Block a user