Skip to content

Commit c402f36

Browse files
NilumilakMgrdichVladSenyuta
authored
BE: FE: Topics: Allow custom topic params upon creation (#271)
Co-authored-by: Mgrdich <[email protected]> Co-authored-by: VladSenyuta <[email protected]>
1 parent 89d03cc commit c402f36

File tree

12 files changed

+536
-147
lines changed

12 files changed

+536
-147
lines changed

e2e-tests/src/main/java/io/kafbat/ui/screens/schemas/SchemaCreateForm.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import static com.codeborne.selenide.Selenide.$;
44
import static com.codeborne.selenide.Selenide.$$x;
55
import static com.codeborne.selenide.Selenide.$x;
6+
import static com.codeborne.selenide.Selenide.sleep;
67
import static org.openqa.selenium.By.id;
78

89
import com.codeborne.selenide.Condition;
@@ -96,6 +97,7 @@ public int getVersionsNumberFromList() {
9697
@Step
9798
public SchemaCreateForm selectVersionFromDropDown(int versionNumberDd) {
9899
$x(String.format(ddlElementLocator, versionNumberDd)).shouldBe(Condition.visible).click();
100+
sleep(1000);
99101
return this;
100102
}
101103

e2e-tests/src/main/java/io/kafbat/ui/screens/topics/TopicCreateEditForm.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,11 @@ public class TopicCreateEditForm extends BasePage {
2929
protected SelenideElement minInSyncReplicasField = $x("//input[@name='minInSyncReplicas']");
3030
protected SelenideElement cleanUpPolicyDdl = $x("//ul[@id='topicFormCleanupPolicy']");
3131
protected SelenideElement maxSizeOnDiscDdl = $x("//ul[@id='topicFormRetentionBytes']");
32-
protected SelenideElement customParameterDdl = $x("//ul[contains(@name,'customParams')]");
33-
protected SelenideElement deleteCustomParameterBtn = $x("//span[contains(@title,'Delete customParam')]");
34-
protected SelenideElement addCustomParameterTypeBtn = $x("//button[contains(text(),'Add Custom Parameter')]");
32+
protected SelenideElement customParameterDdl = $x("//input[contains(@name, 'customParams')][@role='listitem']");
33+
protected SelenideElement deleteCustomParameterBtn = $x("//span[contains(@title, 'Delete customParam')]");
34+
protected SelenideElement addCustomParameterTypeBtn = $x("//button[contains(text(), 'Add Custom Parameter')]");
3535
protected SelenideElement customParameterValueField = $x("//input[@placeholder='Value']");
36-
protected SelenideElement validationCustomParameterValueMsg = $x("//p[contains(text(),'Value is required')]");
36+
protected SelenideElement validationCustomParameterValueMsg = $x("//p[contains(text(), 'Value is required')]");
3737
protected String ddlElementLocator = "//li[@value='%s']";
3838
protected String btnTimeToRetainLocator = "//button[@class][text()='%s']";
3939
protected String customParamsElmCss = "ul[role=listbox][name^=customParams][name$=name]";

e2e-tests/src/test/java/io/kafbat/ui/smokesuite/brokers/BrokersTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ public void brokersConfigEditCheck() {
160160
configItem
161161
.setValue(String.valueOf(newValue))
162162
.clickCancelBtn();
163-
Assert.assertEquals(getIntegerFromString(configItem.getValue(), false), defaultValue,
163+
Assert.assertEquals(getIntegerFromString(configItem.getValue(), true), defaultValue,
164164
"configItem.getValue()");
165165
configItem
166166
.clickEditBtn()
@@ -173,7 +173,7 @@ public void brokersConfigEditCheck() {
173173
softly.assertFalse(configItem.getSaveBtn().isDisplayed(), "getSaveBtn().isDisplayed()");
174174
softly.assertFalse(configItem.getCancelBtn().isDisplayed(), "getCancelBtn().isDisplayed()");
175175
softly.assertTrue(configItem.getEditBtn().isDisplayed(), "getEditBtn().isDisplayed()");
176-
softly.assertEquals(getIntegerFromString(configItem.getValue(), false), newValue,
176+
softly.assertEquals(getIntegerFromString(configItem.getValue(), true), newValue,
177177
"configItem.getValue()");
178178
softly.assertAll();
179179
}

e2e-tests/src/test/java/io/kafbat/ui/smokesuite/schemas/SchemasTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ public void compareVersionsCheck() {
9090
.getVersionsNumberFromList();
9191
Assert.assertEquals(versionsNumberFromDdl, latestVersion, "Versions number is not matched");
9292
schemaCreateForm
93-
.selectVersionFromDropDown(1);
93+
.selectVersionFromDropDown(latestVersion);
9494
Assert.assertEquals(schemaCreateForm.getMarkedLinesNumber(), 42, "getMarkedLinesNumber()");
9595
}
9696

frontend/src/components/Topics/shared/Form/CustomParams/CustomParamField.tsx

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ import { TOPIC_CUSTOM_PARAMS } from 'lib/constants';
44
import { FieldArrayWithId, useFormContext, Controller } from 'react-hook-form';
55
import { InputLabel } from 'components/common/Input/InputLabel.styled';
66
import { FormError } from 'components/common/Input/Input.styled';
7-
import Select from 'components/common/Select/Select';
87
import Input from 'components/common/Input/Input';
98
import IconButtonWrapper from 'components/common/Icons/IconButtonWrapper';
109
import CloseCircleIcon from 'components/common/Icons/CloseCircleIcon';
1110
import * as C from 'components/Topics/shared/Form/TopicForm.styled';
1211
import { ConfigSource } from 'generated-sources';
12+
import InputWithOptions from 'components/common/InputWithOptions/InputWithOptions';
1313
import { TopicConfigParams, TopicFormData } from 'lib/interfaces/topic';
1414

1515
import * as S from './CustomParams.styled';
@@ -37,6 +37,7 @@ const CustomParamField: React.FC<Props> = ({
3737
formState: { errors },
3838
setValue,
3939
watch,
40+
trigger,
4041
control,
4142
} = useFormContext<TopicFormData>();
4243
const nameValue = watch(`customParams.${index}.name`);
@@ -76,17 +77,18 @@ const CustomParamField: React.FC<Props> = ({
7677
<InputLabel>Custom Parameter *</InputLabel>
7778
<Controller
7879
control={control}
79-
rules={{ required: 'Custom Parameter is required.' }}
8080
name={`customParams.${index}.name`}
81-
render={({ field: { value, name, onChange } }) => (
82-
<Select
83-
name={name}
84-
placeholder="Select"
85-
disabled={isDisabled}
86-
minWidth="270px"
87-
onChange={onChange}
81+
render={({ field: { name, onChange, value } }) => (
82+
<InputWithOptions
8883
value={value}
8984
options={options}
85+
name={name}
86+
onChange={(s) => {
87+
onChange(s);
88+
trigger('customParams');
89+
}}
90+
minWidth="270px"
91+
placeholder="Select"
9092
/>
9193
)}
9294
/>
@@ -101,9 +103,6 @@ const CustomParamField: React.FC<Props> = ({
101103
<InputLabel>Value *</InputLabel>
102104
<Input
103105
name={`customParams.${index}.value` as const}
104-
hookFormOptions={{
105-
required: 'Value is required.',
106-
}}
107106
placeholder="Value"
108107
defaultValue={field.value}
109108
autoComplete="off"

frontend/src/components/Topics/shared/Form/CustomParams/CustomParams.tsx

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { useFieldArray, useFormContext, useWatch } from 'react-hook-form';
33
import { Button } from 'components/common/Button/Button';
44
import { TOPIC_CUSTOM_PARAMS_PREFIX } from 'lib/constants';
55
import PlusIcon from 'components/common/Icons/PlusIcon';
6+
import { ErrorMessage } from '@hookform/error-message';
7+
import { FormError } from 'components/common/Input/Input.styled';
68
import { TopicConfigParams, TopicFormData } from 'lib/interfaces/topic';
79

810
import CustomParamField from './CustomParamField';
@@ -18,7 +20,12 @@ const CustomParams: React.FC<CustomParamsProps> = ({
1820
isSubmitting,
1921
config,
2022
}) => {
21-
const { control } = useFormContext<TopicFormData>();
23+
const {
24+
trigger,
25+
control,
26+
formState: { errors },
27+
} = useFormContext<TopicFormData>();
28+
2229
const { fields, append, remove } = useFieldArray({
2330
control,
2431
name: TOPIC_CUSTOM_PARAMS_PREFIX,
@@ -36,12 +43,14 @@ const CustomParams: React.FC<CustomParamsProps> = ({
3643
});
3744

3845
const [existingFields, setExistingFields] = React.useState<string[]>([]);
39-
4046
const removeField = (index: number): void => {
41-
setExistingFields(
42-
existingFields.filter((field) => field !== controlledFields[index].name)
43-
);
44-
remove(index);
47+
const itemIndex = existingFields.indexOf(controlledFields[index].name);
48+
if (itemIndex !== -1) {
49+
existingFields.splice(itemIndex, 1);
50+
setExistingFields(existingFields);
51+
remove(index);
52+
trigger('customParams');
53+
}
4554
};
4655

4756
return (
@@ -58,6 +67,9 @@ const CustomParams: React.FC<CustomParamsProps> = ({
5867
setExistingFields={setExistingFields}
5968
/>
6069
))}
70+
<FormError>
71+
<ErrorMessage errors={errors} name={`customParams` as const} />
72+
</FormError>
6173
<div>
6274
<Button
6375
type="button"

frontend/src/components/Topics/shared/Form/CustomParams/__test__/CustomParamField.spec.tsx

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, { PropsWithChildren } from 'react';
2-
import { act, screen, within } from '@testing-library/react';
2+
import { act, screen } from '@testing-library/react';
33
import { render } from 'lib/testHelpers';
44
import CustomParamsField, {
55
Props,
@@ -42,7 +42,14 @@ describe('CustomParamsField', () => {
4242
setExistingFields.mockClear();
4343
});
4444

45-
it('renders the component with its view correctly', () => {
45+
const getCustomParamInput = () => screen.getByRole('listitem');
46+
const getCustomParamsList = () => screen.getByRole('listbox');
47+
const getValueInput = () => screen.getByRole('textbox');
48+
const getRemoveButton = () => screen.getByRole('button');
49+
50+
const topicCustomParam1 = Object.keys(TOPIC_CUSTOM_PARAMS)[0];
51+
52+
it('renders the component with its view correctly', async () => {
4653
setupComponent({
4754
field,
4855
isDisabled,
@@ -51,9 +58,11 @@ describe('CustomParamsField', () => {
5158
existingFields,
5259
setExistingFields,
5360
});
54-
expect(screen.getByRole('listbox')).toBeInTheDocument();
55-
expect(screen.getByRole('textbox')).toBeInTheDocument();
56-
expect(screen.getByRole('button')).toBeInTheDocument();
61+
expect(getCustomParamInput()).toBeInTheDocument();
62+
expect(getValueInput()).toBeInTheDocument();
63+
expect(getRemoveButton()).toBeInTheDocument();
64+
await userEvent.click(getCustomParamInput());
65+
expect(getCustomParamsList()).toBeInTheDocument();
5766
});
5867

5968
describe('core functionality works', () => {
@@ -66,7 +75,7 @@ describe('CustomParamsField', () => {
6675
existingFields,
6776
setExistingFields,
6877
});
69-
await userEvent.click(screen.getByRole('button'));
78+
await userEvent.click(getRemoveButton());
7079
expect(remove).toHaveBeenCalledTimes(1);
7180
});
7281

@@ -79,7 +88,7 @@ describe('CustomParamsField', () => {
7988
existingFields,
8089
setExistingFields,
8190
});
82-
await userEvent.type(screen.getByRole('button'), SPACE_KEY);
91+
await userEvent.type(getRemoveButton(), SPACE_KEY);
8392
// userEvent.type triggers remove two times as at first it clicks on element and then presses space
8493
expect(remove).toHaveBeenCalledTimes(2);
8594
});
@@ -93,12 +102,10 @@ describe('CustomParamsField', () => {
93102
existingFields,
94103
setExistingFields,
95104
});
96-
const listbox = screen.getByRole('listbox');
97-
await selectOption(listbox, 'compression.type');
98-
99-
const selectedOption = within(listbox).getAllByRole('option');
100-
expect(selectedOption.length).toEqual(1);
101-
expect(selectedOption[0]).toHaveTextContent('compression.type');
105+
await userEvent.click(getCustomParamInput());
106+
await selectOption(getCustomParamsList(), topicCustomParam1);
107+
expect(screen.queryByRole('listbox')).not.toBeInTheDocument();
108+
expect(getCustomParamInput()).toHaveValue(topicCustomParam1);
102109
});
103110

104111
it('selecting option updates textbox value', async () => {
@@ -110,11 +117,12 @@ describe('CustomParamsField', () => {
110117
existingFields,
111118
setExistingFields,
112119
});
113-
const listbox = screen.getByRole('listbox');
114-
await selectOption(listbox, 'compression.type');
120+
await userEvent.click(getCustomParamInput());
121+
await selectOption(getCustomParamsList(), topicCustomParam1);
115122

116-
const textbox = screen.getByRole('textbox');
117-
expect(textbox).toHaveValue(TOPIC_CUSTOM_PARAMS['compression.type']);
123+
expect(getValueInput()).toHaveValue(
124+
TOPIC_CUSTOM_PARAMS[topicCustomParam1]
125+
);
118126
});
119127

120128
it('selecting option updates triggers setExistingFields', async () => {
@@ -126,8 +134,8 @@ describe('CustomParamsField', () => {
126134
existingFields,
127135
setExistingFields,
128136
});
129-
const listbox = screen.getByRole('listbox');
130-
await selectOption(listbox, 'compression.type');
137+
await userEvent.click(getCustomParamInput());
138+
await selectOption(getCustomParamsList(), topicCustomParam1);
131139

132140
expect(setExistingFields).toHaveBeenCalledTimes(1);
133141
});

0 commit comments

Comments
 (0)