Skip to content

Commit 64b758b

Browse files
fix(PDiskPage): fix error boundary on failed restart
1 parent 74355a4 commit 64b758b

File tree

4 files changed

+113
-23
lines changed

4 files changed

+113
-23
lines changed

src/components/CriticalActionDialog/CriticalActionDialog.tsx

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,28 @@ import {Checkbox, Dialog, Icon} from '@gravity-ui/uikit';
66
import {ResultIssues} from '../../containers/Tenant/Query/Issues/Issues';
77
import type {IResponseError} from '../../types/api/error';
88
import {cn} from '../../utils/cn';
9+
import {isResponseError, isResponseErrorWithIssues} from '../../utils/response';
910

1011
import {criticalActionDialogKeyset} from './i18n';
1112

1213
import './CriticalActionDialog.scss';
1314

1415
const b = cn('ydb-critical-dialog');
1516

16-
const parseError = (error: IResponseError) => {
17-
if (error.data && 'issues' in error.data && error.data.issues) {
18-
return <ResultIssues hideSeverity data={error.data} />;
19-
}
20-
if (error.status === 403) {
21-
return criticalActionDialogKeyset('no-rights-error');
22-
}
23-
if (error.statusText) {
24-
return error.statusText;
17+
const parseError = (error: unknown) => {
18+
if (isResponseError(error)) {
19+
if (error.status === 403) {
20+
return criticalActionDialogKeyset('no-rights-error');
21+
}
22+
if (typeof error.data === 'string') {
23+
return error.data;
24+
}
25+
if (isResponseErrorWithIssues(error) && error.data) {
26+
return <ResultIssues hideSeverity data={error.data} />;
27+
}
28+
if (error.statusText) {
29+
return error.statusText;
30+
}
2531
}
2632

2733
return criticalActionDialogKeyset('default-error');

src/containers/Tenant/Query/utils/isQueryCancelledError.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import type {IResponseError} from '../../../../types/api/error';
21
import {isQueryErrorResponse, parseQueryError} from '../../../../utils/query';
2+
import {isResponseError} from '../../../../utils/response';
33

44
function isAbortError(error: unknown): error is {name: string} {
55
return (
@@ -10,10 +10,6 @@ function isAbortError(error: unknown): error is {name: string} {
1010
);
1111
}
1212

13-
function isResponseError(error: unknown): error is IResponseError {
14-
return typeof error === 'object' && error !== null && 'isCancelled' in error;
15-
}
16-
1713
export function isQueryCancelledError(error: unknown): boolean {
1814
if (isAbortError(error)) {
1915
return true;
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import {isResponseError, isResponseErrorWithIssues} from '../response';
2+
3+
describe('isResponseError', () => {
4+
test('should return false on incorrect data', () => {
5+
expect(isResponseError({})).toBe(false);
6+
expect(isResponseError([])).toBe(false);
7+
expect(isResponseError('some string')).toBe(false);
8+
expect(isResponseError(null)).toBe(false);
9+
expect(isResponseError(undefined)).toBe(false);
10+
});
11+
test('should return true if it is object with status or status text', () => {
12+
expect(isResponseError({status: 403})).toBe(true);
13+
expect(isResponseError({statusText: 'Gateway timeout'})).toBe(true);
14+
});
15+
test('should return true if it is cancelled', () => {
16+
expect(isResponseError({isCancelled: true})).toBe(true);
17+
});
18+
test('should return true if it has data', () => {
19+
expect(isResponseError({data: 'Everything is broken'})).toBe(true);
20+
});
21+
});
22+
23+
describe('isResponseErrorWithIssues', () => {
24+
test('should return false on incorrect data', () => {
25+
expect(
26+
isResponseErrorWithIssues({
27+
data: {},
28+
}),
29+
).toBe(false);
30+
expect(
31+
isResponseErrorWithIssues({
32+
data: [],
33+
}),
34+
).toBe(false);
35+
expect(
36+
isResponseErrorWithIssues({
37+
data: undefined,
38+
}),
39+
).toBe(false);
40+
expect(
41+
isResponseErrorWithIssues({
42+
data: null,
43+
}),
44+
).toBe(false);
45+
expect(
46+
isResponseErrorWithIssues({
47+
data: 'some string',
48+
}),
49+
).toBe(false);
50+
});
51+
test('should return true if it is an array of issues', () => {
52+
expect(
53+
isResponseErrorWithIssues({
54+
data: [{message: 'Some error'}],
55+
}),
56+
).toBe(false);
57+
});
58+
});

src/utils/response.ts

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,20 @@
11
import type {AxiosError, AxiosResponse} from 'axios';
22

3-
import type {NetworkError} from '../types/api/error';
3+
import type {IResponseError, NetworkError} from '../types/api/error';
4+
import type {TIssueMessage} from '../types/api/operations';
5+
import type {IssueMessage} from '../types/api/query';
6+
7+
export function isResponseError(error: unknown): error is IResponseError {
8+
if (!error || typeof error !== 'object') {
9+
return false;
10+
}
11+
const hasData = 'data' in error;
12+
const hasStatus = 'status' in error && typeof error.status === 'number';
13+
const hasStatusText = 'statusText' in error && typeof error.statusText === 'string';
14+
const isCancelled = 'isCancelled' in error && typeof error.isCancelled === 'boolean';
15+
16+
return hasData || hasStatus || hasStatusText || isCancelled;
17+
}
418

519
export const isNetworkError = (error: unknown): error is NetworkError => {
620
return Boolean(
@@ -26,24 +40,40 @@ export function isAxiosError(error: unknown): error is AxiosErrorObject {
2640
);
2741
}
2842

29-
export function isAccessError(error: unknown): error is {status: number} {
30-
return Boolean(
31-
error &&
32-
typeof error === 'object' &&
33-
'status' in error &&
34-
(error.status === 403 || error.status === 401),
35-
);
43+
export function isAccessError(error: unknown): error is IResponseError {
44+
return Boolean(isResponseError(error) && (error.status === 403 || error.status === 401));
3645
}
3746

3847
export function isRedirectToAuth(error: unknown): error is {status: 401; data: {authUrl: string}} {
3948
return Boolean(
4049
isAccessError(error) &&
4150
error.status === 401 &&
42-
'data' in error &&
4351
error.data &&
4452
typeof error.data === 'object' &&
4553
'authUrl' in error.data &&
4654
error.data.authUrl &&
4755
typeof error.data.authUrl === 'string',
4856
);
4957
}
58+
59+
export function isResponseErrorWithIssues(
60+
error: unknown,
61+
): error is IResponseError<TIssueMessage | IssueMessage> {
62+
return Boolean(
63+
isResponseError(error) &&
64+
error.data &&
65+
typeof error.data === 'object' &&
66+
'issues' in error.data &&
67+
isIssuesArray(error.data.issues),
68+
);
69+
}
70+
71+
export function isIssuesArray(arr: unknown): arr is TIssueMessage[] {
72+
return Boolean(Array.isArray(arr) && arr.length && arr.every(isIssueMessage));
73+
}
74+
75+
export function isIssueMessage(obj: unknown): obj is TIssueMessage {
76+
return Boolean(
77+
obj && typeof obj === 'object' && 'message' in obj && typeof obj.message === 'string',
78+
);
79+
}

0 commit comments

Comments
 (0)