Repository: provectus/kafka-ui
Issue: [UI] KSQL: Empty field in Stream properties causes errors
Labels: type/bug
good first issue
scope/frontend
status/accepted
status/confirmed
hacktoberfest

<!--

Don't forget to check for existing issues/discussions regarding your proposal. We might already have it.
https://github.com/provectus/kafka-ui/issues
https://github.com/provectus/kafka-ui/discussions

-->

**Describe the bug**

When having empty field in stream properties "underfined" property is sent to backend.

**Set up**

kafka-ui master  


**Steps to Reproduce**
<!-- We'd like you to provide an example setup (via docker-compose, helm, etc.) 
to reproduce the problem, especially with a complex setups. -->
Steps to reproduce the behavior:

1. go to KSQL -> Execute Sql query
2. enter some query like `select * from GH_PRS_COUNTS_KSQL_TABLE_W;` and set stream property  like `ksql.query.pull.table.scan.enabled = true`. 
3. Press "Add Stream Property" button to create new empty field
4. Press Execute
=> 
```{"ksql":"select * from GH_PRS_COUNTS_KSQL_TABLE_W;","streamsProperties":{"ksql.query.pull.table.scan.enabled":"true","undefined":""}}``` is sent to backend, which is not right

**Expected behavior**
We only sending properties that were directly set.

### NOTE:
Within this ticket lets also prohibit adding more that one empty Stream Property input field - this looks very strange in UI when you press to "Add Stream Property" multiple times...

**Screenshots**
<!--
(If applicable, add screenshots to help explain your problem)
-->
<img width="1693" alt="Screenshot 2022-09-21 at 13 29 25" src="https://user-images.githubusercontent.com/702205/191471724-a5909ae1-380e-463b-9792-e5a95a9d3ada.png">


**Additional context**
<!--
(Add any other context about the problem here)
-->


Issue comments:

Pull request review comments:
there is no need in useCallback

Patch that resolved the issue:
diff --git a/kafka-ui-react-app/src/components/KsqlDb/Query/Query.tsx b/kafka-ui-react-app/src/components/KsqlDb/Query/Query.tsx
index efda3932175..7e875748636 100644
--- a/kafka-ui-react-app/src/components/KsqlDb/Query/Query.tsx
+++ b/kafka-ui-react-app/src/components/KsqlDb/Query/Query.tsx
@@ -167,7 +167,10 @@ const Query: FC = () => {
 
   const submitHandler = useCallback(
     (values: FormValues) => {
-      const streamsProperties = values.streamsProperties.reduce(
+      const filteredProperties = values.streamsProperties.filter(
+        (property) => property.key != null
+      );
+      const streamsProperties = filteredProperties.reduce(
         (acc, current) => ({
           ...acc,
           [current.key as keyof string]: current.value,
diff --git a/kafka-ui-react-app/src/components/KsqlDb/Query/QueryForm/QueryForm.tsx b/kafka-ui-react-app/src/components/KsqlDb/Query/QueryForm/QueryForm.tsx
index ee78877d225..9965ea5189a 100644
--- a/kafka-ui-react-app/src/components/KsqlDb/Query/QueryForm/QueryForm.tsx
+++ b/kafka-ui-react-app/src/components/KsqlDb/Query/QueryForm/QueryForm.tsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useCallback } from 'react';
 import { FormError } from 'components/common/Input/Input.styled';
 import { ErrorMessage } from '@hookform/error-message';
 import { useForm, Controller, useFieldArray } from 'react-hook-form';
@@ -46,6 +46,7 @@ const QueryForm: React.FC<Props> = ({
   const {
     handleSubmit,
     setValue,
+    getValues,
     control,
     formState: { errors },
   } = useForm<FormValues>({
@@ -64,6 +65,16 @@ const QueryForm: React.FC<Props> = ({
     name: 'streamsProperties',
   });
 
+  const handleAddNewProperty = useCallback(() => {
+    if (
+      getValues().streamsProperties.every((prop) => {
+        return prop.key;
+      })
+    ) {
+      append({ key: '', value: '' });
+    }
+  }, []);
+
   return (
     <S.QueryWrapper>
       <form onSubmit={handleSubmit(submitHandler)}>
@@ -164,7 +175,7 @@ const QueryForm: React.FC<Props> = ({
               type="button"
               buttonSize="M"
               buttonType="secondary"
-              onClick={() => append({ key: '', value: '' })}
+              onClick={handleAddNewProperty}
             >
               <PlusIcon />
               Add Stream Property
diff --git a/kafka-ui-react-app/src/components/KsqlDb/Query/QueryForm/__test__/QueryForm.spec.tsx b/kafka-ui-react-app/src/components/KsqlDb/Query/QueryForm/__test__/QueryForm.spec.tsx
index 12a61a5fa5f..0b8aa60b78a 100644
--- a/kafka-ui-react-app/src/components/KsqlDb/Query/QueryForm/__test__/QueryForm.spec.tsx
+++ b/kafka-ui-react-app/src/components/KsqlDb/Query/QueryForm/__test__/QueryForm.spec.tsx
@@ -157,7 +157,7 @@ describe('QueryForm', () => {
     expect(submitFn.mock.calls.length).toBe(1);
   });
 
-  it('add new property', async () => {
+  it('adds new property', async () => {
     renderComponent({
       fetching: false,
       hasResults: false,
@@ -166,6 +166,8 @@ describe('QueryForm', () => {
       submitHandler: jest.fn(),
     });
 
+    const textbox = screen.getByLabelText('key');
+    await userEvent.type(textbox, 'prop_name');
     await act(() => {
       userEvent.click(
         screen.getByRole('button', { name: 'Add Stream Property' })
@@ -174,6 +176,23 @@ describe('QueryForm', () => {
     expect(screen.getAllByRole('textbox', { name: 'key' }).length).toEqual(2);
   });
 
+  it("doesn't add new property", async () => {
+    renderComponent({
+      fetching: false,
+      hasResults: false,
+      handleClearResults: jest.fn(),
+      handleSSECancel: jest.fn(),
+      submitHandler: jest.fn(),
+    });
+
+    await act(() => {
+      userEvent.click(
+        screen.getByRole('button', { name: 'Add Stream Property' })
+      );
+    });
+    expect(screen.getAllByRole('textbox', { name: 'key' }).length).toEqual(1);
+  });
+
   it('delete stream property', async () => {
     await renderComponent({
       fetching: false,
@@ -184,6 +203,7 @@ describe('QueryForm', () => {
     });
 
     await act(() => {
+      userEvent.paste(screen.getByRole('textbox', { name: 'key' }), 'test');
       userEvent.click(
         screen.getByRole('button', { name: 'Add Stream Property' })
       );


Test patch: