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:
Palash 2022-07-13 19:49:27 +05:30 committed by GitHub
parent 4d1516e3fc
commit 5554cce379
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 481 additions and 141 deletions

View File

@ -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",

View File

@ -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,
})}`,
);

View File

@ -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,
})}`,
);

View File

@ -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,
})}`,
);

View 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;

View 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;

View File

@ -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: () =>
getAll({
end: maxTime,
start: minTime,
}),
});
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 (
<Typography>{dayjs(value).format('DD/MM/YYYY HH:mm:ss A')}</Typography>
);
};
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
}&timestamp=${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}
/>
);
}

View 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);
});
});

View 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;
};

View File

@ -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}&timestamp=${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;

View File

@ -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;

View File

@ -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 {

View File

@ -0,0 +1,8 @@
export const urlKey = {
serviceName: 'serviceName',
exceptionType: 'exceptionType',
groupId: 'groupId',
lastSeen: 'lastSeen',
errorId: 'errorId',
timestamp: 'timestamp',
};

View File

@ -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[];

View File

@ -0,0 +1,9 @@
import { PayloadProps as Prop } from './getByErrorTypeAndService';
export interface Props {
groupID: string;
errorID: string;
timestamp: string;
}
export type PayloadProps = Prop;

View File

@ -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;
}

View File

@ -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;

View 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;
};

View File

@ -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"