Skip to content

Commit 31954ce

Browse files
authored
[Fixes #1428] Improve Sonar Cloud security hotspots metrics (#1436)
* Removes regex from compareVersions * Replaces regexes in reducers * Rewrite Version and compareVersions to expect undefined * Adds safeguard against strange cases * Fixes regression from another PR Co-authored-by: Damir Abdulganiev <[email protected]>
1 parent 205d8d0 commit 31954ce

File tree

10 files changed

+106
-121
lines changed

10 files changed

+106
-121
lines changed

kafka-ui-react-app/src/components/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ const App: React.FC = () => {
6161
<S.Hyperlink href="/ui">UI for Apache Kafka</S.Hyperlink>
6262

6363
<S.NavbarItem>
64-
<Version tag={GIT_TAG} commit={GIT_COMMIT} />
64+
{GIT_TAG && <Version tag={GIT_TAG} commit={GIT_COMMIT} />}
6565
</S.NavbarItem>
6666
</S.NavbarBrand>
6767
</S.Navbar>

kafka-ui-react-app/src/components/Topics/shared/Form/CustomParams/__tests__/CustomParamField.spec.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react';
2-
import {screen, waitFor, within} from '@testing-library/react';
2+
import { screen, waitFor, within } from '@testing-library/react';
33
import { render } from 'lib/testHelpers';
44
import CustomParamsField, {
55
Props,

kafka-ui-react-app/src/components/Topics/shared/Form/CustomParams/__tests__/CustomParams.spec.tsx

Lines changed: 66 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ import userEvent from '@testing-library/user-event';
99
import { TOPIC_CUSTOM_PARAMS } from 'lib/constants';
1010

1111
const selectOption = async (listbox: HTMLElement, option: string) => {
12-
await waitFor(() => userEvent.click(listbox));
13-
await waitFor(() => userEvent.click(screen.getByText(option)));
12+
await waitFor(() => {
13+
userEvent.click(listbox);
14+
userEvent.click(screen.getByText(option));
15+
});
1416
};
1517

1618
const expectOptionIsSelected = (listbox: HTMLElement, option: string) => {
@@ -19,17 +21,27 @@ const expectOptionIsSelected = (listbox: HTMLElement, option: string) => {
1921
expect(selectedOption[0]).toHaveTextContent(option);
2022
};
2123

22-
const expectOptionIsDisabled = async (
24+
const expectOptionAvailability = async (
2325
listbox: HTMLElement,
2426
option: string,
2527
disabled: boolean
2628
) => {
2729
await waitFor(() => userEvent.click(listbox));
28-
const selectedOption = within(listbox).getAllByText(option);
29-
expect(selectedOption[1]).toHaveStyleRule(
30+
const selectedOptions = within(listbox).getAllByText(option).reverse();
31+
// its either two or one nodes, we only need last one
32+
const selectedOption = selectedOptions[0];
33+
34+
if (disabled) {
35+
expect(selectedOption).toHaveAttribute('disabled');
36+
} else {
37+
expect(selectedOption).toBeEnabled();
38+
}
39+
40+
expect(selectedOption).toHaveStyleRule(
3041
'cursor',
3142
disabled ? 'not-allowed' : 'pointer'
3243
);
44+
await waitFor(() => userEvent.click(listbox));
3345
};
3446

3547
describe('CustomParams', () => {
@@ -51,15 +63,15 @@ describe('CustomParams', () => {
5163
});
5264

5365
it('renders with props', () => {
54-
const addParamButton = screen.getByRole('button');
55-
expect(addParamButton).toBeInTheDocument();
56-
expect(addParamButton).toHaveTextContent('Add Custom Parameter');
66+
const button = screen.getByRole('button');
67+
expect(button).toBeInTheDocument();
68+
expect(button).toHaveTextContent('Add Custom Parameter');
5769
});
5870

5971
describe('works with user inputs correctly', () => {
6072
it('button click creates custom param fieldset', async () => {
61-
const addParamButton = screen.getByRole('button');
62-
await waitFor(() => userEvent.click(addParamButton));
73+
const button = screen.getByRole('button');
74+
await waitFor(() => userEvent.click(button));
6375

6476
const listbox = screen.getByRole('listbox');
6577
expect(listbox).toBeInTheDocument();
@@ -69,38 +81,38 @@ describe('CustomParams', () => {
6981
});
7082

7183
it('can select option', async () => {
72-
const addParamButton = screen.getByRole('button');
73-
await waitFor(() => userEvent.click(addParamButton));
74-
84+
const button = screen.getByRole('button');
85+
await waitFor(() => userEvent.click(button));
7586
const listbox = screen.getByRole('listbox');
7687

7788
await selectOption(listbox, 'compression.type');
7889
expectOptionIsSelected(listbox, 'compression.type');
79-
expectOptionIsDisabled(listbox, 'compression.type', true);
90+
await expectOptionAvailability(listbox, 'compression.type', true);
8091

8192
const textbox = screen.getByRole('textbox');
8293
expect(textbox).toHaveValue(TOPIC_CUSTOM_PARAMS['compression.type']);
8394
});
8495

8596
it('when selected option changes disabled options update correctly', async () => {
86-
const addParamButton = screen.getByRole('button');
87-
await waitFor(() => userEvent.click(addParamButton));
97+
const button = screen.getByRole('button');
98+
await waitFor(() => userEvent.click(button));
8899

89100
const listbox = screen.getByRole('listbox');
90101

91102
await selectOption(listbox, 'compression.type');
92-
expectOptionIsDisabled(listbox, 'compression.type', true);
103+
expectOptionIsSelected(listbox, 'compression.type');
104+
await expectOptionAvailability(listbox, 'compression.type', true);
93105

94106
await selectOption(listbox, 'delete.retention.ms');
95-
expectOptionIsDisabled(listbox, 'delete.retention.ms', true);
96-
expectOptionIsDisabled(listbox, 'compression.type', false);
107+
await expectOptionAvailability(listbox, 'delete.retention.ms', true);
108+
await expectOptionAvailability(listbox, 'compression.type', false);
97109
});
98110

99111
it('multiple button clicks create multiple fieldsets', async () => {
100-
const addParamButton = screen.getByRole('button');
101-
await waitFor(() => userEvent.click(addParamButton));
102-
await waitFor(() => userEvent.click(addParamButton));
103-
await waitFor(() => userEvent.click(addParamButton));
112+
const button = screen.getByRole('button');
113+
await waitFor(() => userEvent.click(button));
114+
await waitFor(() => userEvent.click(button));
115+
await waitFor(() => userEvent.click(button));
104116

105117
const listboxes = screen.getAllByRole('listbox');
106118
expect(listboxes.length).toBe(3);
@@ -110,48 +122,64 @@ describe('CustomParams', () => {
110122
});
111123

112124
it("can't select already selected option", async () => {
113-
const addParamButton = screen.getByRole('button');
114-
userEvent.click(addParamButton);
115-
userEvent.click(addParamButton);
125+
const button = screen.getByRole('button');
126+
await waitFor(() => userEvent.click(button));
127+
await waitFor(() => userEvent.click(button));
116128

117129
const listboxes = screen.getAllByRole('listbox');
118130

119131
const firstListbox = listboxes[0];
120132
await selectOption(firstListbox, 'compression.type');
121-
expectOptionIsDisabled(firstListbox, 'compression.type', true);
133+
await expectOptionAvailability(firstListbox, 'compression.type', true);
122134

123135
const secondListbox = listboxes[1];
124-
expectOptionIsDisabled(secondListbox, 'compression.type', true);
136+
await expectOptionAvailability(secondListbox, 'compression.type', true);
125137
});
126138

127139
it('when fieldset with selected custom property type is deleted disabled options update correctly', async () => {
128-
const addParamButton = screen.getByRole('button');
129-
userEvent.click(addParamButton);
130-
userEvent.click(addParamButton);
131-
userEvent.click(addParamButton);
140+
const button = screen.getByRole('button');
141+
await waitFor(() => userEvent.click(button));
142+
await waitFor(() => userEvent.click(button));
143+
await waitFor(() => userEvent.click(button));
132144

133145
const listboxes = screen.getAllByRole('listbox');
134146

135147
const firstListbox = listboxes[0];
136148
await selectOption(firstListbox, 'compression.type');
137-
expectOptionIsDisabled(firstListbox, 'compression.type', true);
149+
await expectOptionAvailability(firstListbox, 'compression.type', true);
138150

139151
const secondListbox = listboxes[1];
140152
await selectOption(secondListbox, 'delete.retention.ms');
141-
expectOptionIsDisabled(secondListbox, 'delete.retention.ms', true);
153+
await expectOptionAvailability(
154+
secondListbox,
155+
'delete.retention.ms',
156+
true
157+
);
142158

143159
const thirdListbox = listboxes[2];
144160
await selectOption(thirdListbox, 'file.delete.delay.ms');
145-
expectOptionIsDisabled(thirdListbox, 'file.delete.delay.ms', true);
161+
await expectOptionAvailability(
162+
thirdListbox,
163+
'file.delete.delay.ms',
164+
true
165+
);
146166

147167
const deleteSecondFieldsetButton = screen.getByTitle(
148168
'Delete customParam field 1'
149169
);
150-
userEvent.click(deleteSecondFieldsetButton);
170+
await waitFor(() => userEvent.click(deleteSecondFieldsetButton));
151171
expect(secondListbox).not.toBeInTheDocument();
152172

153-
expectOptionIsDisabled(firstListbox, 'delete.retention.ms', false);
154-
expectOptionIsDisabled(thirdListbox, 'delete.retention.ms', false);
173+
await expectOptionAvailability(
174+
firstListbox,
175+
'delete.retention.ms',
176+
false
177+
);
178+
await expectOptionAvailability(
179+
thirdListbox,
180+
'delete.retention.ms',
181+
false
182+
);
155183
});
156184
});
157185
});

kafka-ui-react-app/src/components/Version/Version.tsx

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { GIT_REPO_LATEST_RELEASE_LINK } from 'lib/constants';
55
import compareVersions from './compareVersions';
66

77
export interface VesionProps {
8-
tag?: string;
8+
tag: string;
99
commit?: string;
1010
}
1111

@@ -15,20 +15,15 @@ const Version: React.FC<VesionProps> = ({ tag, commit }) => {
1515
latestTag: '',
1616
});
1717
useEffect(() => {
18-
if (tag) {
19-
fetch(GIT_REPO_LATEST_RELEASE_LINK)
20-
.then((response) => response.json())
21-
.then((data) => {
22-
setLatestVersionInfo({
23-
outdated: compareVersions(tag, data.tag_name) === -1,
24-
latestTag: data.tag_name,
25-
});
18+
fetch(GIT_REPO_LATEST_RELEASE_LINK)
19+
.then((response) => response.json())
20+
.then((data) => {
21+
setLatestVersionInfo({
22+
outdated: compareVersions(tag, data.tag_name) === -1,
23+
latestTag: data.tag_name,
2624
});
27-
}
25+
});
2826
}, [tag]);
29-
if (!tag) {
30-
return null;
31-
}
3227

3328
const { outdated, latestTag } = latestVersionInfo;
3429

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,22 @@
11
import React from 'react';
2-
import { mount } from 'enzyme';
3-
import Version from 'components/Version/Version';
2+
import Version, { VesionProps } from 'components/Version/Version';
3+
import { screen } from '@testing-library/react';
4+
import { render } from 'lib/testHelpers';
45

56
const tag = 'v1.0.1-SHAPSHOT';
67
const commit = '123sdf34';
78

89
describe('Version', () => {
9-
it('shows nothing if tag is not defined', () => {
10-
const component = mount(<Version />);
11-
expect(component.html()).toEqual(null);
12-
});
10+
const setupComponent = (props: VesionProps) => render(<Version {...props} />);
1311

14-
it('shows current tag when only tag is defined', () => {
15-
const component = mount(<Version tag={tag} />);
16-
expect(component.text()).toContain(tag);
12+
it('renders', () => {
13+
setupComponent({ tag });
14+
expect(screen.getByText('Version:')).toBeInTheDocument();
1715
});
1816

1917
it('shows current tag and commit', () => {
20-
const component = mount(<Version tag={tag} commit={commit} />);
21-
expect(component.text()).toContain(tag);
22-
expect(component.text()).toContain(commit);
23-
});
24-
25-
it('matches snapshot', () => {
26-
const component = mount(<Version tag={tag} commit={commit} />);
27-
expect(component).toMatchSnapshot();
18+
setupComponent({ tag, commit });
19+
expect(screen.getByText(tag)).toBeInTheDocument();
20+
expect(screen.getByText(commit)).toBeInTheDocument();
2821
});
2922
});

kafka-ui-react-app/src/components/Version/__tests__/__snapshots__/Version.spec.tsx.snap

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

kafka-ui-react-app/src/components/Version/__tests__/compareVersions.spec.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
2-
// @ts-nocheck
3-
41
import compareVersions from 'components/Version/compareVersions';
52

63
const runTests = (dataSet: [string, string, number][]) => {
@@ -63,10 +60,11 @@ describe('compareVersions function', () => {
6360
});
6461

6562
it('returns valid result (negative test cases)', () => {
63+
expect(compareVersions()).toEqual(0);
64+
expect(compareVersions('v0.0.0')).toEqual(0);
65+
// @ts-expect-error first arg is number
6666
expect(compareVersions(123, 'v0.0.0')).toEqual(0);
67-
expect(compareVersions(undefined, 'v0.0.0')).toEqual(0);
67+
// @ts-expect-error second arg is number
6868
expect(compareVersions('v0.0.0', 123)).toEqual(0);
69-
expect(compareVersions('v0.0.0', undefined)).toEqual(0);
70-
expect(compareVersions(undefined, undefined)).toEqual(0);
7169
});
7270
});

kafka-ui-react-app/src/components/Version/compareVersions.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
const split = (v: string): string[] => {
2-
const c = v.replace(/^v/, '').replace(/\+.*$/, '');
3-
return c.split('-')[0].split('.');
2+
const c = v.replace('v', '').split('-')[0];
3+
return c.split('.');
44
};
55

6-
const compareVersions = (v1: string, v2: string): number => {
6+
const compareVersions = (v1?: string, v2?: string): number => {
7+
if (!v1 || !v2) return 0;
8+
9+
// try..catch - is our safeguard for strange git tags (or usecases without network)
710
try {
811
const s1 = split(v1);
912
const s2 = split(v2);

kafka-ui-react-app/src/redux/reducers/alerts/reducer.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ export const initialState: AlertsState = {};
99
const reducer = (state = initialState, action: Action): AlertsState => {
1010
const { type } = action;
1111

12-
const matches = /(.*)__(FAILURE)$/.exec(type);
13-
if (matches && matches[2]) return addError(state, action);
12+
if (type.endsWith('__FAILURE')) return addError(state, action);
1413

1514
if (type === getType(dismissAlert)) {
1615
return removeAlert(state, action);

kafka-ui-react-app/src/redux/reducers/loader/reducer.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,17 @@ export const initialState: LoaderState = {};
44

55
const reducer = (state = initialState, action: Action): LoaderState => {
66
const { type } = action;
7-
const matches = /(.*)__(REQUEST|SUCCESS|FAILURE|CANCEL)$/.exec(type);
7+
const splitType = type.split('__');
8+
const requestState = splitType.pop();
9+
const requestName = splitType.join('__');
810

911
// not a *__REQUEST / *__SUCCESS / *__FAILURE / *__CANCEL actions, so we ignore them
10-
if (!matches) return state;
11-
12-
const [, requestName, requestState] = matches;
12+
if (
13+
requestState &&
14+
!['REQUEST', 'SUCCESS', 'FAILURE', 'CANCEL'].includes(requestState)
15+
) {
16+
return state;
17+
}
1318

1419
switch (requestState) {
1520
case 'REQUEST':

0 commit comments

Comments
 (0)