Skip to content

Commit 4cfdcf6

Browse files
fix: do not parse valid objects from meta (#2785)
1 parent ab99d1b commit 4cfdcf6

File tree

10 files changed

+241
-154
lines changed

10 files changed

+241
-154
lines changed

src/containers/Cluster/ClusterInfo/utils/__tests__/prepareLinks.test.ts

Lines changed: 0 additions & 51 deletions
This file was deleted.

src/containers/Cluster/ClusterInfo/utils/useClusterLinks.ts

Lines changed: 6 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -2,79 +2,32 @@ import React from 'react';
22

33
import {useClusterBaseInfo} from '../../../../store/reducers/cluster/cluster';
44
import type {ClusterLink} from '../../../../types/additionalProps';
5-
import {parseJson} from '../../../../utils/utils';
65
import i18n from '../../i18n';
76

8-
/**
9-
* parses stringified json in format {url: "href"}
10-
*/
11-
export function prepareClusterCoresLink(rawCoresString?: string) {
12-
try {
13-
const linkObject = parseJson(rawCoresString) as unknown;
14-
15-
if (
16-
linkObject &&
17-
typeof linkObject === 'object' &&
18-
'url' in linkObject &&
19-
typeof linkObject.url === 'string'
20-
) {
21-
return linkObject.url;
22-
}
23-
} catch {}
24-
25-
return undefined;
26-
}
27-
28-
/**
29-
* parses stringified json in format {url: "href", slo_logs_url: "href"}
30-
*/
31-
export function prepareClusterLoggingLinks(rawLoggingString?: string) {
32-
try {
33-
const linkObject = parseJson(rawLoggingString) as unknown;
34-
35-
if (linkObject && typeof linkObject === 'object') {
36-
const logsUrl =
37-
'url' in linkObject && typeof linkObject.url === 'string'
38-
? linkObject.url
39-
: undefined;
40-
const sloLogsUrl =
41-
'slo_logs_url' in linkObject && typeof linkObject.slo_logs_url === 'string'
42-
? linkObject.slo_logs_url
43-
: undefined;
44-
return {logsUrl, sloLogsUrl};
45-
}
46-
} catch {}
47-
48-
return {};
49-
}
50-
517
export function useClusterLinks() {
528
const {cores, logging} = useClusterBaseInfo();
539

5410
return React.useMemo(() => {
5511
const result: ClusterLink[] = [];
5612

57-
const coresUrl = prepareClusterCoresLink(cores);
58-
const {logsUrl, sloLogsUrl} = prepareClusterLoggingLinks(logging);
59-
60-
if (coresUrl) {
13+
if (cores?.url) {
6114
result.push({
6215
title: i18n('link_cores'),
63-
url: coresUrl,
16+
url: cores?.url,
6417
});
6518
}
6619

67-
if (logsUrl) {
20+
if (logging?.url) {
6821
result.push({
6922
title: i18n('link_logging'),
70-
url: logsUrl,
23+
url: logging?.url,
7124
});
7225
}
7326

74-
if (sloLogsUrl) {
27+
if (logging?.slo_logs_url) {
7528
result.push({
7629
title: i18n('link_slo-logs'),
77-
url: sloLogsUrl,
30+
url: logging.slo_logs_url,
7831
});
7932
}
8033

src/services/parsers/parseMetaCluster.ts

Lines changed: 0 additions & 16 deletions
This file was deleted.
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import {parseCoresUrl, parseLoggingUrls, parseTraceField} from '../parseFields';
2+
3+
describe('parseCoresUrl', () => {
4+
test('It should parse stringified json with cores url', () => {
5+
expect(parseCoresUrl('{"url":"https://coredumps.com?cluster=my_cluster"}')).toEqual({
6+
url: 'https://coredumps.com?cluster=my_cluster',
7+
});
8+
});
9+
10+
test('It should return undefined if input is undefined', () => {
11+
expect(parseCoresUrl(undefined)).toEqual(undefined);
12+
});
13+
test('It should return undefined if input is incorrect', () => {
14+
expect(parseCoresUrl('hello')).toEqual(undefined);
15+
});
16+
17+
test('It should return the object as-is if input is already an object', () => {
18+
const coresObject = {url: 'https://coredumps.com?cluster=my_cluster'};
19+
expect(parseCoresUrl(coresObject)).toEqual(coresObject);
20+
});
21+
});
22+
23+
describe('parseLoggingUrls', () => {
24+
test('It should parse stringified json with logging and slo logs urls', () => {
25+
expect(
26+
parseLoggingUrls(
27+
'{"url":"https://logging.com/logs?cluster=my_cluster","slo_logs_url":"https://logging.com/slo-logs?cluster=my_cluster"}',
28+
),
29+
).toEqual({
30+
url: 'https://logging.com/logs?cluster=my_cluster',
31+
slo_logs_url: 'https://logging.com/slo-logs?cluster=my_cluster',
32+
});
33+
});
34+
test('It should parse stringified json with only logging url', () => {
35+
expect(parseLoggingUrls('{"url":"https://logging.com/logs?cluster=my_cluster"}')).toEqual({
36+
url: 'https://logging.com/logs?cluster=my_cluster',
37+
});
38+
});
39+
test('It should parse stringified json with only slo logs url', () => {
40+
expect(
41+
parseLoggingUrls('{"slo_logs_url":"https://logging.com/slo-logs?cluster=my_cluster"}'),
42+
).toEqual({
43+
slo_logs_url: 'https://logging.com/slo-logs?cluster=my_cluster',
44+
});
45+
});
46+
test('It should return undefined if input is undefined', () => {
47+
expect(parseLoggingUrls(undefined)).toEqual(undefined);
48+
});
49+
test('It should return undefined if input is incorrect', () => {
50+
expect(parseLoggingUrls('hello')).toEqual(undefined);
51+
});
52+
53+
test('It should return the object as-is if input is already an object', () => {
54+
const loggingObject = {
55+
url: 'https://logging.com/logs?cluster=my_cluster',
56+
slo_logs_url: 'https://logging.com/slo-logs?cluster=my_cluster',
57+
};
58+
expect(parseLoggingUrls(loggingObject)).toEqual(loggingObject);
59+
});
60+
61+
test('It should return the object as-is if input is already an object with only url', () => {
62+
const loggingObject = {url: 'https://logging.com/logs?cluster=my_cluster'};
63+
expect(parseLoggingUrls(loggingObject)).toEqual(loggingObject);
64+
});
65+
66+
test('It should return the object as-is if input is already an object with only slo_logs_url', () => {
67+
const loggingObject = {slo_logs_url: 'https://logging.com/slo-logs?cluster=my_cluster'};
68+
expect(parseLoggingUrls(loggingObject)).toEqual(loggingObject);
69+
});
70+
});
71+
72+
describe('parseTraceField', () => {
73+
test('It should parse stringified json with trace view url', () => {
74+
expect(parseTraceField('{"url":"https://tracing.com/trace?cluster=my_cluster"}')).toEqual({
75+
url: 'https://tracing.com/trace?cluster=my_cluster',
76+
});
77+
});
78+
79+
test('It should return undefined if input is undefined', () => {
80+
expect(parseTraceField(undefined)).toEqual(undefined);
81+
});
82+
83+
test('It should return undefined if input is empty string', () => {
84+
expect(parseTraceField('')).toEqual(undefined);
85+
});
86+
87+
test('It should return undefined if input is incorrect json', () => {
88+
expect(parseTraceField('hello')).toEqual(undefined);
89+
});
90+
91+
test('It should return undefined if parsed json does not match schema', () => {
92+
expect(parseTraceField('{"invalid":"field"}')).toEqual(undefined);
93+
});
94+
95+
test('It should return the object as-is if input is already an object', () => {
96+
const traceObject = {url: 'https://tracing.com/trace?cluster=my_cluster'};
97+
expect(parseTraceField(traceObject)).toEqual(traceObject);
98+
});
99+
});

src/store/reducers/cluster/cluster.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import {skipToken} from '@reduxjs/toolkit/query';
44

55
import type {ClusterTab} from '../../../containers/Cluster/utils';
66
import {clusterTabsIds, isClusterTab} from '../../../containers/Cluster/utils';
7-
import {parseTraceFields} from '../../../services/parsers/parseMetaCluster';
87
import {isClusterInfoV2} from '../../../types/api/cluster';
98
import type {TClusterInfo} from '../../../types/api/cluster';
109
import type {TTabletStateInfo} from '../../../types/api/tablet';
@@ -16,6 +15,7 @@ import type {RootState} from '../../defaultStore';
1615
import {api} from '../api';
1716
import {selectNodesMap} from '../nodesList';
1817

18+
import {parseCoresUrl, parseLoggingUrls, parseTraceField} from './parseFields';
1919
import type {ClusterGroupsStats, ClusterState} from './types';
2020
import {
2121
createSelectClusterGroupsQuery,
@@ -147,7 +147,7 @@ export function useClusterBaseInfo() {
147147
skip: !isViewerUser,
148148
});
149149

150-
const {solomon: monitoring, name, title, trace_view: traceView, ...data} = currentData || {};
150+
const {solomon: monitoring, name, title, ...data} = currentData || {};
151151

152152
// name is used for requests, title is used for display
153153
// Example:
@@ -158,10 +158,15 @@ export function useClusterBaseInfo() {
158158

159159
return {
160160
...data,
161-
...parseTraceFields({traceView}),
161+
162+
monitoring,
163+
162164
name: clusterName,
163165
title: clusterTitle,
164-
monitoring,
166+
167+
traceView: parseTraceField(data.trace_view),
168+
cores: parseCoresUrl(data.cores),
169+
logging: parseLoggingUrls(data.logging),
165170
};
166171
}
167172

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import {z} from 'zod';
2+
3+
import type {
4+
MetaBaseClusterInfo,
5+
MetaClusterCoresUrl,
6+
MetaClusterLogsUrls,
7+
MetaClusterTraceView,
8+
} from '../../../types/api/meta';
9+
10+
const traceViewSchema = z.object({
11+
url: z.string().url(),
12+
});
13+
14+
export function parseTraceField(
15+
traceView: MetaBaseClusterInfo['trace_view'],
16+
): MetaClusterTraceView | undefined {
17+
if (traceView && typeof traceView === 'object') {
18+
return traceView;
19+
}
20+
try {
21+
return traceView ? traceViewSchema.parse(JSON.parse(traceView)) : undefined;
22+
} catch (e) {
23+
console.error('Error parsing trace_view field:', e);
24+
}
25+
26+
return undefined;
27+
}
28+
29+
const coresUrlSchema = z.object({
30+
url: z.string().url(),
31+
});
32+
33+
export function parseCoresUrl(
34+
cores: MetaBaseClusterInfo['cores'],
35+
): MetaClusterCoresUrl | undefined {
36+
if (cores && typeof cores === 'object') {
37+
return cores;
38+
}
39+
try {
40+
return cores ? coresUrlSchema.parse(JSON.parse(cores)) : undefined;
41+
} catch (e) {
42+
console.error('Error parsing cores field:', e);
43+
}
44+
45+
return undefined;
46+
}
47+
48+
const loggingUrlsSchema = z.object({
49+
url: z.string().url().optional(),
50+
slo_logs_url: z.string().url().optional(),
51+
monium_cluster: z.string().optional(),
52+
});
53+
54+
export function parseLoggingUrls(
55+
logging: MetaBaseClusterInfo['logging'],
56+
): MetaClusterLogsUrls | undefined {
57+
if (logging && typeof logging === 'object') {
58+
return logging;
59+
}
60+
try {
61+
return logging ? loggingUrlsSchema.parse(JSON.parse(logging)) : undefined;
62+
} catch (e) {
63+
console.error('Error parsing logging field:', e);
64+
}
65+
66+
return undefined;
67+
}

0 commit comments

Comments
 (0)