mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-12 12:28:59 +08:00
feat: exception page is updated (#1376)
* chore: all error utils is added * chore: error page list is added with total page and other handlings * test: unit test case for order is added
This commit is contained in:
parent
4d1516e3fc
commit
5554cce379
@ -81,6 +81,7 @@
|
||||
"style-loader": "1.3.0",
|
||||
"styled-components": "^5.2.1",
|
||||
"terser-webpack-plugin": "^5.2.5",
|
||||
"timestamp-nano": "^1.0.0",
|
||||
"ts-node": "^10.2.1",
|
||||
"tsconfig-paths-webpack-plugin": "^3.5.1",
|
||||
"typescript": "^4.0.5",
|
||||
|
@ -10,9 +10,8 @@ const getAll = async (
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`/errors?${createQueryParams({
|
||||
start: props.start.toString(),
|
||||
end: props.end.toString(),
|
||||
`/listErrors?${createQueryParams({
|
||||
...props,
|
||||
})}`,
|
||||
);
|
||||
|
||||
|
@ -10,11 +10,8 @@ const getByErrorType = async (
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`/errorWithType?${createQueryParams({
|
||||
start: props.start.toString(),
|
||||
end: props.end.toString(),
|
||||
serviceName: props.serviceName,
|
||||
errorType: props.errorType,
|
||||
`/errorFromGroupID?${createQueryParams({
|
||||
...props,
|
||||
})}`,
|
||||
);
|
||||
|
||||
|
@ -3,17 +3,15 @@ import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import createQueryParams from 'lib/createQueryParams';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/errors/getById';
|
||||
import { PayloadProps, Props } from 'types/api/errors/getByErrorId';
|
||||
|
||||
const getById = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`/errorWithId?${createQueryParams({
|
||||
start: props.start.toString(),
|
||||
end: props.end.toString(),
|
||||
errorId: props.errorId,
|
||||
`/errorFromErrorID?${createQueryParams({
|
||||
...props,
|
||||
})}`,
|
||||
);
|
||||
|
||||
|
29
frontend/src/api/errors/getErrorCounts.ts
Normal file
29
frontend/src/api/errors/getErrorCounts.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import createQueryParams from 'lib/createQueryParams';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/errors/getErrorCounts';
|
||||
|
||||
const getErrorCounts = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`/countErrors?${createQueryParams({
|
||||
...props,
|
||||
})}`,
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.message,
|
||||
payload: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default getErrorCounts;
|
29
frontend/src/api/errors/getNextPrevId.ts
Normal file
29
frontend/src/api/errors/getNextPrevId.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import createQueryParams from 'lib/createQueryParams';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/errors/getNextPrevId';
|
||||
|
||||
const getErrorCounts = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`/nextPrevErrorIDs?${createQueryParams({
|
||||
...props,
|
||||
})}`,
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.message,
|
||||
payload: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default getErrorCounts;
|
@ -1,31 +1,85 @@
|
||||
import { notification, Table, Tooltip, Typography } from 'antd';
|
||||
import { notification, Table, TableProps, Tooltip, Typography } from 'antd';
|
||||
import { ColumnsType } from 'antd/lib/table';
|
||||
import getAll from 'api/errors/getAll';
|
||||
import getErrorCounts from 'api/errors/getErrorCounts';
|
||||
import ROUTES from 'constants/routes';
|
||||
import dayjs from 'dayjs';
|
||||
import React, { useEffect } from 'react';
|
||||
import createQueryParams from 'lib/createQueryParams';
|
||||
import history from 'lib/history';
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQuery } from 'react-query';
|
||||
import { useQueries } from 'react-query';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { Exception } from 'types/api/errors/getAll';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { Exception, PayloadProps } from 'types/api/errors/getAll';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import {
|
||||
getDefaultOrder,
|
||||
getNanoSeconds,
|
||||
getOffSet,
|
||||
getOrder,
|
||||
getOrderParams,
|
||||
getUpdatePageSize,
|
||||
urlKey,
|
||||
} from './utils';
|
||||
|
||||
function AllErrors(): JSX.Element {
|
||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||
const { maxTime, minTime, loading } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
const { search, pathname } = useLocation();
|
||||
const params = useMemo(() => new URLSearchParams(search), [search]);
|
||||
|
||||
const { t } = useTranslation(['common']);
|
||||
|
||||
const { isLoading, data } = useQuery(['getAllError', [maxTime, minTime]], {
|
||||
queryFn: () =>
|
||||
const updatedOrder = getOrder(params.get(urlKey.order));
|
||||
const getUpdatedOffset = getOffSet(params.get(urlKey.offset));
|
||||
const getUpdatedParams = getOrderParams(params.get(urlKey.orderParam));
|
||||
const getUpdatedPageSize = getUpdatePageSize(params.get(urlKey.pageSize));
|
||||
|
||||
const updatedPath = useMemo(
|
||||
() =>
|
||||
`${pathname}?${createQueryParams({
|
||||
order: updatedOrder,
|
||||
offset: getUpdatedOffset,
|
||||
orderParam: getUpdatedParams,
|
||||
pageSize: getUpdatedPageSize,
|
||||
})}`,
|
||||
[
|
||||
pathname,
|
||||
updatedOrder,
|
||||
getUpdatedOffset,
|
||||
getUpdatedParams,
|
||||
getUpdatedPageSize,
|
||||
],
|
||||
);
|
||||
|
||||
const [{ isLoading, data }, errorCountResponse] = useQueries([
|
||||
{
|
||||
queryKey: ['getAllErrors', updatedPath, maxTime, minTime],
|
||||
queryFn: (): Promise<SuccessResponse<PayloadProps> | ErrorResponse> =>
|
||||
getAll({
|
||||
end: maxTime,
|
||||
start: minTime,
|
||||
order: updatedOrder,
|
||||
limit: getUpdatedPageSize,
|
||||
offset: getUpdatedOffset,
|
||||
orderParam: getUpdatedParams,
|
||||
}),
|
||||
});
|
||||
enabled: !loading,
|
||||
},
|
||||
{
|
||||
queryKey: ['getErrorCounts', maxTime, minTime],
|
||||
queryFn: (): Promise<ErrorResponse | SuccessResponse<number>> =>
|
||||
getErrorCounts({
|
||||
end: maxTime,
|
||||
start: minTime,
|
||||
}),
|
||||
},
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.error) {
|
||||
@ -35,11 +89,9 @@ function AllErrors(): JSX.Element {
|
||||
}
|
||||
}, [data?.error, data?.payload, t]);
|
||||
|
||||
const getDateValue = (value: string): JSX.Element => {
|
||||
return (
|
||||
const getDateValue = (value: string): JSX.Element => (
|
||||
<Typography>{dayjs(value).format('DD/MM/YYYY HH:mm:ss A')}</Typography>
|
||||
);
|
||||
};
|
||||
|
||||
const columns: ColumnsType<Exception> = [
|
||||
{
|
||||
@ -49,14 +101,22 @@ function AllErrors(): JSX.Element {
|
||||
render: (value, record): JSX.Element => (
|
||||
<Tooltip overlay={(): JSX.Element => value}>
|
||||
<Link
|
||||
to={`${ROUTES.ERROR_DETAIL}?serviceName=${record.serviceName}&errorType=${record.exceptionType}`}
|
||||
to={`${ROUTES.ERROR_DETAIL}?serviceName=${
|
||||
record.serviceName
|
||||
}&exceptionType=${record.exceptionType}&groupId=${
|
||||
record.groupID
|
||||
}×tamp=${getNanoSeconds(record.lastSeen)}`}
|
||||
>
|
||||
{value}
|
||||
</Link>
|
||||
</Tooltip>
|
||||
),
|
||||
sorter: (a, b): number =>
|
||||
a.exceptionType.charCodeAt(0) - b.exceptionType.charCodeAt(0),
|
||||
sorter: true,
|
||||
defaultSortOrder: getDefaultOrder(
|
||||
getUpdatedParams,
|
||||
updatedOrder,
|
||||
'exceptionType',
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Error Message',
|
||||
@ -78,39 +138,86 @@ function AllErrors(): JSX.Element {
|
||||
title: 'Count',
|
||||
dataIndex: 'exceptionCount',
|
||||
key: 'exceptionCount',
|
||||
sorter: (a, b): number => a.exceptionCount - b.exceptionCount,
|
||||
sorter: true,
|
||||
defaultSortOrder: getDefaultOrder(
|
||||
getUpdatedParams,
|
||||
updatedOrder,
|
||||
'exceptionCount',
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Last Seen',
|
||||
dataIndex: 'lastSeen',
|
||||
key: 'lastSeen',
|
||||
render: getDateValue,
|
||||
sorter: (a, b): number =>
|
||||
dayjs(b.lastSeen).isBefore(dayjs(a.lastSeen)) === true ? 1 : 0,
|
||||
sorter: true,
|
||||
defaultSortOrder: getDefaultOrder(
|
||||
getUpdatedParams,
|
||||
updatedOrder,
|
||||
'lastSeen',
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'First Seen',
|
||||
dataIndex: 'firstSeen',
|
||||
key: 'firstSeen',
|
||||
render: getDateValue,
|
||||
sorter: (a, b): number =>
|
||||
dayjs(b.firstSeen).isBefore(dayjs(a.firstSeen)) === true ? 1 : 0,
|
||||
sorter: true,
|
||||
defaultSortOrder: getDefaultOrder(
|
||||
getUpdatedParams,
|
||||
updatedOrder,
|
||||
'firstSeen',
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Application',
|
||||
dataIndex: 'serviceName',
|
||||
key: 'serviceName',
|
||||
sorter: (a, b): number =>
|
||||
a.serviceName.charCodeAt(0) - b.serviceName.charCodeAt(0),
|
||||
sorter: true,
|
||||
defaultSortOrder: getDefaultOrder(
|
||||
getUpdatedParams,
|
||||
updatedOrder,
|
||||
'serviceName',
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const onChangeHandler: TableProps<Exception>['onChange'] = (
|
||||
paginations,
|
||||
_,
|
||||
sorter,
|
||||
) => {
|
||||
if (!Array.isArray(sorter)) {
|
||||
const { current = 0, pageSize = 0 } = paginations;
|
||||
const { columnKey = '', order } = sorter;
|
||||
const updatedOrder = order === 'ascend' ? 'ascending' : 'descending';
|
||||
|
||||
history.replace(
|
||||
`${pathname}?${createQueryParams({
|
||||
order: updatedOrder,
|
||||
offset: current - 1,
|
||||
orderParam: columnKey,
|
||||
pageSize,
|
||||
})}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Table
|
||||
tableLayout="fixed"
|
||||
dataSource={data?.payload as Exception[]}
|
||||
columns={columns}
|
||||
loading={isLoading || false}
|
||||
rowKey="firstSeen"
|
||||
loading={isLoading || false || errorCountResponse.status === 'loading'}
|
||||
pagination={{
|
||||
pageSize: getUpdatedPageSize,
|
||||
responsive: true,
|
||||
current: getUpdatedOffset + 1,
|
||||
position: ['bottomLeft'],
|
||||
total: errorCountResponse.data?.payload || 0,
|
||||
}}
|
||||
onChange={onChangeHandler}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
28
frontend/src/container/AllError/utils.test.ts
Normal file
28
frontend/src/container/AllError/utils.test.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { isOrder, isOrderParams } from './utils';
|
||||
|
||||
describe('Error utils', () => {
|
||||
test('Valid OrderBy Params', () => {
|
||||
expect(isOrderParams('serviceName')).toBe(true);
|
||||
expect(isOrderParams('exceptionCount')).toBe(true);
|
||||
expect(isOrderParams('lastSeen')).toBe(true);
|
||||
expect(isOrderParams('firstSeen')).toBe(true);
|
||||
expect(isOrderParams('exceptionType')).toBe(true);
|
||||
});
|
||||
|
||||
test('Invalid OrderBy Params', () => {
|
||||
expect(isOrderParams('invalid')).toBe(false);
|
||||
expect(isOrderParams(null)).toBe(false);
|
||||
expect(isOrderParams('')).toBe(false);
|
||||
});
|
||||
|
||||
test('Valid Order', () => {
|
||||
expect(isOrder('ascending')).toBe(true);
|
||||
expect(isOrder('descending')).toBe(true);
|
||||
});
|
||||
|
||||
test('Invalid Order', () => {
|
||||
expect(isOrder('invalid')).toBe(false);
|
||||
expect(isOrder(null)).toBe(false);
|
||||
expect(isOrder('')).toBe(false);
|
||||
});
|
||||
});
|
89
frontend/src/container/AllError/utils.ts
Normal file
89
frontend/src/container/AllError/utils.ts
Normal file
@ -0,0 +1,89 @@
|
||||
import { SortOrder } from 'antd/lib/table/interface';
|
||||
import Timestamp from 'timestamp-nano';
|
||||
import { Order, OrderBy } from 'types/api/errors/getAll';
|
||||
|
||||
export const isOrder = (order: string | null): order is Order =>
|
||||
!!(order === 'ascending' || order === 'descending');
|
||||
|
||||
export const urlKey = {
|
||||
order: 'order',
|
||||
offset: 'offset',
|
||||
orderParam: 'orderParam',
|
||||
pageSize: 'pageSize',
|
||||
};
|
||||
|
||||
export const isOrderParams = (orderBy: string | null): orderBy is OrderBy => {
|
||||
return !!(
|
||||
orderBy === 'serviceName' ||
|
||||
orderBy === 'exceptionCount' ||
|
||||
orderBy === 'lastSeen' ||
|
||||
orderBy === 'firstSeen' ||
|
||||
orderBy === 'exceptionType'
|
||||
);
|
||||
};
|
||||
|
||||
export const getOrder = (order: string | null): Order => {
|
||||
if (isOrder(order)) {
|
||||
return order;
|
||||
}
|
||||
return 'ascending';
|
||||
};
|
||||
|
||||
export const getLimit = (limit: string | null): number => {
|
||||
if (limit) {
|
||||
return parseInt(limit, 10);
|
||||
}
|
||||
return 10;
|
||||
};
|
||||
|
||||
export const getOffSet = (offset: string | null): number => {
|
||||
if (offset && typeof offset === 'string') {
|
||||
return parseInt(offset, 10);
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
export const getOrderParams = (order: string | null): OrderBy => {
|
||||
if (isOrderParams(order)) {
|
||||
return order;
|
||||
}
|
||||
return 'serviceName';
|
||||
};
|
||||
|
||||
export const getDefaultOrder = (
|
||||
orderBy: OrderBy,
|
||||
order: Order,
|
||||
data: OrderBy,
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
): SortOrder | undefined => {
|
||||
if (orderBy === 'exceptionType' && data === 'exceptionType') {
|
||||
return order === 'ascending' ? 'ascend' : 'descend';
|
||||
}
|
||||
if (orderBy === 'serviceName' && data === 'serviceName') {
|
||||
return order === 'ascending' ? 'ascend' : 'descend';
|
||||
}
|
||||
if (orderBy === 'exceptionCount' && data === 'exceptionCount') {
|
||||
return order === 'ascending' ? 'ascend' : 'descend';
|
||||
}
|
||||
if (orderBy === 'lastSeen' && data === 'lastSeen') {
|
||||
return order === 'ascending' ? 'ascend' : 'descend';
|
||||
}
|
||||
if (orderBy === 'firstSeen' && data === 'firstSeen') {
|
||||
return order === 'ascending' ? 'ascend' : 'descend';
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const getNanoSeconds = (date: string): number => {
|
||||
return (
|
||||
parseInt((new Date(date).getTime() / 1e3).toString(), 10) * 1e9 +
|
||||
Timestamp.fromString(date).getNano()
|
||||
);
|
||||
};
|
||||
|
||||
export const getUpdatePageSize = (pageSize: string | null): number => {
|
||||
if (pageSize) {
|
||||
return parseInt(pageSize, 10);
|
||||
}
|
||||
return 10;
|
||||
};
|
@ -1,25 +1,49 @@
|
||||
import { Button, Divider, notification, Space, Table, Typography } from 'antd';
|
||||
import getNextPrevId from 'api/errors/getNextPrevId';
|
||||
import Editor from 'components/Editor';
|
||||
import { getNanoSeconds } from 'container/AllError/utils';
|
||||
import dayjs from 'dayjs';
|
||||
import history from 'lib/history';
|
||||
import { urlKey } from 'pages/ErrorDetails/utils';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQuery } from 'react-query';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { PayloadProps as GetByErrorTypeAndServicePayload } from 'types/api/errors/getByErrorTypeAndService';
|
||||
import { PayloadProps } from 'types/api/errors/getById';
|
||||
|
||||
import { DashedContainer, EditorContainer, EventContainer } from './styles';
|
||||
|
||||
function ErrorDetails(props: ErrorDetailsProps): JSX.Element {
|
||||
const { idPayload } = props;
|
||||
const [isLoading, setLoading] = useState<boolean>(false);
|
||||
const { t } = useTranslation(['errorDetails', 'common']);
|
||||
|
||||
const { search } = useLocation();
|
||||
const params = new URLSearchParams(search);
|
||||
const queryErrorId = params.get('errorId');
|
||||
const serviceName = params.get('serviceName');
|
||||
const errorType = params.get('errorType');
|
||||
|
||||
const params = useMemo(() => new URLSearchParams(search), [search]);
|
||||
|
||||
const errorId = params.get(urlKey.errorId);
|
||||
const serviceName = params.get(urlKey.serviceName);
|
||||
const errorType = params.get(urlKey.exceptionType);
|
||||
const timestamp = params.get(urlKey.timestamp);
|
||||
|
||||
const { data: nextPrevData, status: nextPrevStatus } = useQuery(
|
||||
[
|
||||
idPayload.errorId,
|
||||
idPayload.groupID,
|
||||
idPayload.timestamp,
|
||||
errorId,
|
||||
serviceName,
|
||||
errorType,
|
||||
timestamp,
|
||||
],
|
||||
{
|
||||
queryFn: () =>
|
||||
getNextPrevId({
|
||||
errorID: errorId || idPayload.errorId,
|
||||
groupID: idPayload.groupID,
|
||||
timestamp: timestamp || getNanoSeconds(idPayload.timestamp).toString(),
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
const errorDetail = idPayload;
|
||||
|
||||
@ -48,34 +72,34 @@ function ErrorDetails(props: ErrorDetailsProps): JSX.Element {
|
||||
'errorId',
|
||||
'timestamp',
|
||||
'exceptionMessage',
|
||||
'newerErrorId',
|
||||
'olderErrorId',
|
||||
'exceptionEscaped',
|
||||
],
|
||||
[],
|
||||
);
|
||||
|
||||
const onClickErrorIdHandler = async (id: string): Promise<void> => {
|
||||
const onClickErrorIdHandler = async (
|
||||
id: string,
|
||||
timespamp: string,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
if (id.length === 0) {
|
||||
notification.error({
|
||||
message: 'Error Id cannot be empty',
|
||||
});
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
|
||||
history.push(
|
||||
`${history.location.pathname}?errorId=${id}&serviceName=${serviceName}&errorType=${errorType}`,
|
||||
history.replace(
|
||||
`${history.location.pathname}?${urlKey.serviceName}=${serviceName}&${
|
||||
urlKey.exceptionType
|
||||
}=${errorType}&groupId=${idPayload.groupID}×tamp=${getNanoSeconds(
|
||||
timespamp,
|
||||
)}&errorId=${id}`,
|
||||
);
|
||||
} catch (error) {
|
||||
notification.error({
|
||||
message: t('something_went_wrong'),
|
||||
});
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
@ -106,25 +130,25 @@ function ErrorDetails(props: ErrorDetailsProps): JSX.Element {
|
||||
<div>
|
||||
<Space align="end" direction="horizontal">
|
||||
<Button
|
||||
loading={isLoading}
|
||||
disabled={
|
||||
errorDetail.olderErrorId.length === 0 ||
|
||||
queryErrorId === errorDetail.olderErrorId
|
||||
}
|
||||
loading={nextPrevStatus === 'loading'}
|
||||
disabled={nextPrevData?.payload?.prevErrorID.length === 0}
|
||||
onClick={(): Promise<void> =>
|
||||
onClickErrorIdHandler(errorDetail.olderErrorId)
|
||||
onClickErrorIdHandler(
|
||||
nextPrevData?.payload?.prevErrorID || '',
|
||||
nextPrevData?.payload?.prevTimestamp || '',
|
||||
)
|
||||
}
|
||||
>
|
||||
{t('older')}
|
||||
</Button>
|
||||
<Button
|
||||
loading={isLoading}
|
||||
disabled={
|
||||
errorDetail.newerErrorId.length === 0 ||
|
||||
queryErrorId === errorDetail.newerErrorId
|
||||
}
|
||||
loading={nextPrevStatus === 'loading'}
|
||||
disabled={nextPrevData?.payload?.nextErrorID.length === 0}
|
||||
onClick={(): Promise<void> =>
|
||||
onClickErrorIdHandler(errorDetail.newerErrorId)
|
||||
onClickErrorIdHandler(
|
||||
nextPrevData?.payload?.nextErrorID || '',
|
||||
nextPrevData?.payload?.nextTimestamp || '',
|
||||
)
|
||||
}
|
||||
>
|
||||
{t('newer')}
|
||||
@ -153,7 +177,7 @@ function ErrorDetails(props: ErrorDetailsProps): JSX.Element {
|
||||
}
|
||||
|
||||
interface ErrorDetailsProps {
|
||||
idPayload: PayloadProps;
|
||||
idPayload: GetByErrorTypeAndServicePayload;
|
||||
}
|
||||
|
||||
export default ErrorDetails;
|
||||
|
@ -1,6 +1,6 @@
|
||||
const createQueryParams = (params: { [x: string]: string }): string =>
|
||||
const createQueryParams = (params: { [x: string]: string | number }): string =>
|
||||
Object.keys(params)
|
||||
.map((k) => `${k}=${encodeURI(params[k])}`)
|
||||
.map((k) => `${k}=${encodeURI(String(params[k]))}`)
|
||||
.join('&');
|
||||
|
||||
export default createQueryParams;
|
||||
|
@ -4,107 +4,106 @@ import getById from 'api/errors/getById';
|
||||
import Spinner from 'components/Spinner';
|
||||
import ROUTES from 'constants/routes';
|
||||
import ErrorDetailsContainer from 'container/ErrorDetails';
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQuery } from 'react-query';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { Redirect, useLocation } from 'react-router-dom';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { PayloadProps } from 'types/api/errors/getById';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import { urlKey } from './utils';
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
function ErrorDetails(): JSX.Element {
|
||||
const { t } = useTranslation(['common']);
|
||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
const { search } = useLocation();
|
||||
const params = new URLSearchParams(search);
|
||||
const params = useMemo(() => new URLSearchParams(search), [search]);
|
||||
|
||||
const serviceName = params.get(urlKey.serviceName);
|
||||
const expectionType = params.get(urlKey.exceptionType);
|
||||
const groupId = params.get(urlKey.groupId);
|
||||
const errorId = params.get(urlKey.errorId);
|
||||
const timestamp = params.get(urlKey.timestamp);
|
||||
|
||||
const errorId = params.get('errorId');
|
||||
const errorType = params.get('errorType');
|
||||
const serviceName = params.get('serviceName');
|
||||
const defaultError = t('something_went_wrong');
|
||||
|
||||
const { data: IdData, status: IdStatus } = useQuery(
|
||||
[errorId, timestamp, groupId],
|
||||
{
|
||||
queryFn: () =>
|
||||
getById({
|
||||
errorID: errorId || '',
|
||||
groupID: groupId || '',
|
||||
timestamp: timestamp || '',
|
||||
}),
|
||||
enabled:
|
||||
errorId !== null &&
|
||||
groupId !== null &&
|
||||
timestamp !== null &&
|
||||
errorId.length !== 0 &&
|
||||
groupId.length !== 0 &&
|
||||
timestamp.length !== 0,
|
||||
},
|
||||
);
|
||||
|
||||
const { data, status } = useQuery(
|
||||
[
|
||||
'errorByType',
|
||||
errorType,
|
||||
'expectionType',
|
||||
expectionType,
|
||||
'serviceName',
|
||||
serviceName,
|
||||
maxTime,
|
||||
minTime,
|
||||
errorId,
|
||||
groupId,
|
||||
],
|
||||
{
|
||||
queryFn: () =>
|
||||
getByErrorType({
|
||||
end: maxTime,
|
||||
errorType: errorType || '',
|
||||
serviceName: serviceName || '',
|
||||
start: minTime,
|
||||
}),
|
||||
enabled: errorId === null && errorType !== null && serviceName !== null,
|
||||
cacheTime: 5000,
|
||||
},
|
||||
);
|
||||
|
||||
const { status: ErrorIdStatus, data: errorIdPayload } = useQuery(
|
||||
[
|
||||
'errorByType',
|
||||
errorType,
|
||||
'serviceName',
|
||||
serviceName,
|
||||
maxTime,
|
||||
minTime,
|
||||
'errorId',
|
||||
errorId,
|
||||
],
|
||||
{
|
||||
queryFn: () =>
|
||||
getById({
|
||||
end: maxTime,
|
||||
errorId: errorId || data?.payload?.errorId || '',
|
||||
start: minTime,
|
||||
groupID: groupId || '',
|
||||
timestamp: timestamp || '',
|
||||
}),
|
||||
enabled:
|
||||
(errorId !== null || status === 'success') &&
|
||||
errorType !== null &&
|
||||
serviceName !== null,
|
||||
cacheTime: 5000,
|
||||
!!expectionType && !!serviceName && !!groupId && IdStatus !== 'success',
|
||||
},
|
||||
);
|
||||
|
||||
// if errorType and serviceName is null redirecting to the ALL_ERROR page not now
|
||||
if (errorType === null || serviceName === null) {
|
||||
if (
|
||||
serviceName === null ||
|
||||
expectionType === null ||
|
||||
groupId === null ||
|
||||
timestamp === null
|
||||
) {
|
||||
return <Redirect to={ROUTES.ALL_ERROR} />;
|
||||
}
|
||||
|
||||
// when the api is in loading state
|
||||
if (status === 'loading' || ErrorIdStatus === 'loading') {
|
||||
if (status === 'loading' || IdStatus === 'loading') {
|
||||
return <Spinner tip="Loading.." />;
|
||||
}
|
||||
|
||||
// if any error occurred while loading
|
||||
if (status === 'error' || ErrorIdStatus === 'error') {
|
||||
return (
|
||||
<Typography>
|
||||
{data?.error || errorIdPayload?.error || defaultError}
|
||||
</Typography>
|
||||
);
|
||||
if (status === 'error' || IdStatus === 'error') {
|
||||
return <Typography>{data?.error || defaultError}</Typography>;
|
||||
}
|
||||
|
||||
const idPayload = data?.payload || IdData?.payload;
|
||||
|
||||
// if API is successfully but there is an error
|
||||
if (
|
||||
(status === 'success' && data?.statusCode >= 400) ||
|
||||
(ErrorIdStatus === 'success' && errorIdPayload.statusCode >= 400)
|
||||
(IdStatus === 'success' && IdData.statusCode >= 400) ||
|
||||
idPayload === null ||
|
||||
idPayload === undefined
|
||||
) {
|
||||
return <Typography>{data?.error || defaultError}</Typography>;
|
||||
}
|
||||
|
||||
return (
|
||||
<ErrorDetailsContainer idPayload={errorIdPayload?.payload as PayloadProps} />
|
||||
);
|
||||
return <ErrorDetailsContainer idPayload={idPayload} />;
|
||||
}
|
||||
|
||||
export interface ErrorDetailsParams {
|
||||
|
8
frontend/src/pages/ErrorDetails/utils.ts
Normal file
8
frontend/src/pages/ErrorDetails/utils.ts
Normal file
@ -0,0 +1,8 @@
|
||||
export const urlKey = {
|
||||
serviceName: 'serviceName',
|
||||
exceptionType: 'exceptionType',
|
||||
groupId: 'groupId',
|
||||
lastSeen: 'lastSeen',
|
||||
errorId: 'errorId',
|
||||
timestamp: 'timestamp',
|
||||
};
|
@ -1,8 +1,20 @@
|
||||
import { GlobalTime } from 'types/actions/globalTime';
|
||||
|
||||
export type Order = 'ascending' | 'descending';
|
||||
export type OrderBy =
|
||||
| 'serviceName'
|
||||
| 'exceptionCount'
|
||||
| 'lastSeen'
|
||||
| 'firstSeen'
|
||||
| 'exceptionType';
|
||||
|
||||
export interface Props {
|
||||
start: GlobalTime['minTime'];
|
||||
end: GlobalTime['maxTime'];
|
||||
order?: Order;
|
||||
orderParam?: OrderBy;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
}
|
||||
|
||||
export interface Exception {
|
||||
@ -12,6 +24,7 @@ export interface Exception {
|
||||
lastSeen: string;
|
||||
firstSeen: string;
|
||||
serviceName: string;
|
||||
groupID: string;
|
||||
}
|
||||
|
||||
export type PayloadProps = Exception[];
|
||||
|
9
frontend/src/types/api/errors/getByErrorId.ts
Normal file
9
frontend/src/types/api/errors/getByErrorId.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { PayloadProps as Prop } from './getByErrorTypeAndService';
|
||||
|
||||
export interface Props {
|
||||
groupID: string;
|
||||
errorID: string;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
export type PayloadProps = Prop;
|
@ -1,10 +1,6 @@
|
||||
import { GlobalTime } from 'types/actions/globalTime';
|
||||
|
||||
export interface Props {
|
||||
start: GlobalTime['minTime'];
|
||||
end: GlobalTime['maxTime'];
|
||||
serviceName: string;
|
||||
errorType: string;
|
||||
timestamp: string;
|
||||
groupID: string;
|
||||
}
|
||||
|
||||
export interface PayloadProps {
|
||||
@ -16,7 +12,6 @@ export interface PayloadProps {
|
||||
timestamp: string;
|
||||
spanID: string;
|
||||
traceID: string;
|
||||
serviceName: Props['serviceName'];
|
||||
newerErrorId: string;
|
||||
olderErrorId: string;
|
||||
serviceName: string;
|
||||
groupID: string;
|
||||
}
|
||||
|
@ -1,11 +1,8 @@
|
||||
import { GlobalTime } from 'types/actions/globalTime';
|
||||
|
||||
import { PayloadProps as Payload } from './getByErrorTypeAndService';
|
||||
|
||||
export type PayloadProps = Payload;
|
||||
|
||||
export type Props = {
|
||||
start: GlobalTime['minTime'];
|
||||
end: GlobalTime['minTime'];
|
||||
errorId: string;
|
||||
};
|
||||
|
||||
export type PayloadProps = number;
|
13
frontend/src/types/api/errors/getNextPrevId.ts
Normal file
13
frontend/src/types/api/errors/getNextPrevId.ts
Normal file
@ -0,0 +1,13 @@
|
||||
export type Props = {
|
||||
errorID: string;
|
||||
timestamp: string;
|
||||
groupID: string;
|
||||
};
|
||||
|
||||
export type PayloadProps = {
|
||||
prevErrorID: string;
|
||||
nextErrorID: string;
|
||||
groupID: string;
|
||||
nextTimestamp: string;
|
||||
prevTimestamp: string;
|
||||
};
|
@ -12408,6 +12408,11 @@ timed-out@^4.0.1:
|
||||
resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f"
|
||||
integrity sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=
|
||||
|
||||
timestamp-nano@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/timestamp-nano/-/timestamp-nano-1.0.0.tgz#03bf0b43c2bdcb913a6a02fbaae6f97d68650f3a"
|
||||
integrity sha512-NO/1CZigzlCWQiWdIGv8ebXt6Uk77zdLz2NE7KcZRU5Egj2+947lzUpk30xQUQlq5dRY25j7ZulG4RfA2DHYfA==
|
||||
|
||||
tiny-invariant@^1.0.2:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.2.0.tgz#a1141f86b672a9148c72e978a19a73b9b94a15a9"
|
||||
|
Loading…
x
Reference in New Issue
Block a user