Repository: provectus/kafka-ui
Issue: Wizard: Cluster config validator doesn't switch focus to first field failing validation.
Labels: type/bug
scope/frontend
status/accepted
status/confirmed
severity/medium
area/wizard

**Describe the bug** (Actual behavior)
<!--(A clear and concise description of what the bug is.Use a list, if there is more than one problem)-->
On cluster creation or change, 'Validate' button doesn't switch focus to the field with an error.
Since the wizard is quite long, the validation errors might be outside visible page area.

**Expected behavior**
<!--(A clear and concise description of what you expected to happen.)-->
Focus should be set to the first field with a failed validation on 'Validate' button click.

**Set up**
<!--
WE MIGHT CLOSE THE ISSUE without further explanation IF YOU DON'T PROVIDE THIS INFORMATION.

How do you run the app? Please provide as much info as possible:
1. App version (docker image version or check commit hash in the top left corner in UI)
2. Helm chart version, if you use one
3. Any IAAC configs
-->
[dac023e](https://github.com/provectus/kafka-ui/commit/dac023e)
the 'wizard' branch

**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. -->

1. Set up the Kafka UI with a setting 
`DYNAMIC_CONFIG_ENABLED: true`
2. Open dashboard and click 'Configure' for and existing cluster or 'Configure new cluster'.
3. Leave out some fields empty, e.g. Truststore or Keystone
4. Click 'Validate' button.

**Screenshots**
<!--
(If applicable, add screenshots to help explain your problem)
-->
![image](https://user-images.githubusercontent.com/112083452/220970913-dae1a35f-f352-4c37-8d7b-03850c51ac6b.png)


**Additional context**
<!--
Add any other context about the problem here. E.g.: 
1. Are there any alternative scenarios (different data/methods/configuration/setup) you have tried? 
   Were they successfull or same issue occured? Please provide steps as well.
5. Related issues (if there are any).
6. Logs (if available)
7. Is there any serious impact or behaviour on the end-user because of this issue, that can be overlooked?
-->


Issue comments:

Pull request review comments:
This covers both Truststore and Keystore
with `undefined` as the first argument, `trigger` validates all fields provided in `useForm` config
get rid of required validation for password field

Patch that resolved the issue:
diff --git a/kafka-ui-react-app/src/components/common/Select/ControlledSelect.tsx b/kafka-ui-react-app/src/components/common/Select/ControlledSelect.tsx
index b3e15aacdf2..1ea90c356ae 100644
--- a/kafka-ui-react-app/src/components/common/Select/ControlledSelect.tsx
+++ b/kafka-ui-react-app/src/components/common/Select/ControlledSelect.tsx
@@ -45,6 +45,7 @@ const ControlledSelect: React.FC<ControlledSelectProps> = ({
               options={options}
               placeholder={placeholder}
               disabled={disabled}
+              ref={field.ref}
             />
           );
         }}
diff --git a/kafka-ui-react-app/src/components/common/Select/Select.tsx b/kafka-ui-react-app/src/components/common/Select/Select.tsx
index b56dbc4959d..a72660d2cb7 100644
--- a/kafka-ui-react-app/src/components/common/Select/Select.tsx
+++ b/kafka-ui-react-app/src/components/common/Select/Select.tsx
@@ -27,90 +27,99 @@ export interface SelectOption {
   isLive?: boolean;
 }
 
-const Select: React.FC<SelectProps> = ({
-  options = [],
-  value,
-  defaultValue,
-  selectSize = 'L',
-  placeholder = '',
-  isLive,
-  disabled = false,
-  onChange,
-  isThemeMode,
-  ...props
-}) => {
-  const [selectedOption, setSelectedOption] = useState(value);
-  const [showOptions, setShowOptions] = useState(false);
+const Select = React.forwardRef<HTMLUListElement, SelectProps>(
+  (
+    {
+      options = [],
+      value,
+      defaultValue,
+      selectSize = 'L',
+      placeholder = '',
+      isLive,
+      disabled = false,
+      onChange,
+      isThemeMode,
+      ...props
+    },
+    ref
+  ) => {
+    const [selectedOption, setSelectedOption] = useState(value);
+    const [showOptions, setShowOptions] = useState(false);
 
-  const showOptionsHandler = () => {
-    if (!disabled) setShowOptions(!showOptions);
-  };
+    const showOptionsHandler = () => {
+      if (!disabled) setShowOptions(!showOptions);
+    };
 
-  const selectContainerRef = useRef(null);
-  const clickOutsideHandler = () => setShowOptions(false);
-  useClickOutside(selectContainerRef, clickOutsideHandler);
+    const selectContainerRef = useRef(null);
+    const clickOutsideHandler = () => setShowOptions(false);
+    useClickOutside(selectContainerRef, clickOutsideHandler);
 
-  const updateSelectedOption = (option: SelectOption) => {
-    if (!option.disabled) {
-      setSelectedOption(option.value);
+    const updateSelectedOption = (option: SelectOption) => {
+      if (!option.disabled) {
+        setSelectedOption(option.value);
 
-      if (onChange) {
-        onChange(option.value);
+        if (onChange) {
+          onChange(option.value);
+        }
+
+        setShowOptions(false);
       }
+    };
 
-      setShowOptions(false);
-    }
-  };
+    React.useEffect(() => {
+      setSelectedOption(value);
+    }, [isLive, value]);
 
-  React.useEffect(() => {
-    setSelectedOption(value);
-  }, [isLive, value]);
+    return (
+      <div ref={selectContainerRef}>
+        <S.Select
+          role="listbox"
+          selectSize={selectSize}
+          isLive={isLive}
+          disabled={disabled}
+          onClick={showOptionsHandler}
+          onKeyDown={showOptionsHandler}
+          isThemeMode={isThemeMode}
+          ref={ref}
+          tabIndex={0}
+          {...props}
+        >
+          <S.SelectedOptionWrapper>
+            {isLive && <LiveIcon />}
+            <S.SelectedOption
+              role="option"
+              tabIndex={0}
+              isThemeMode={isThemeMode}
+            >
+              {options.find(
+                (option) => option.value === (defaultValue || selectedOption)
+              )?.label || placeholder}
+            </S.SelectedOption>
+          </S.SelectedOptionWrapper>
+          {showOptions && (
+            <S.OptionList>
+              {options?.map((option) => (
+                <S.Option
+                  value={option.value}
+                  key={option.value}
+                  disabled={option.disabled}
+                  onClick={() => updateSelectedOption(option)}
+                  tabIndex={0}
+                  role="option"
+                >
+                  {option.isLive && <LiveIcon />}
+                  {option.label}
+                </S.Option>
+              ))}
+            </S.OptionList>
+          )}
+          <DropdownArrowIcon isOpen={showOptions} />
+        </S.Select>
+      </div>
+    );
+  }
+);
 
-  return (
-    <div ref={selectContainerRef}>
-      <S.Select
-        role="listbox"
-        selectSize={selectSize}
-        isLive={isLive}
-        disabled={disabled}
-        onClick={showOptionsHandler}
-        onKeyDown={showOptionsHandler}
-        isThemeMode={isThemeMode}
-        {...props}
-      >
-        <S.SelectedOptionWrapper>
-          {isLive && <LiveIcon />}
-          <S.SelectedOption
-            role="option"
-            tabIndex={0}
-            isThemeMode={isThemeMode}
-          >
-            {options.find(
-              (option) => option.value === (defaultValue || selectedOption)
-            )?.label || placeholder}
-          </S.SelectedOption>
-        </S.SelectedOptionWrapper>
-        {showOptions && (
-          <S.OptionList>
-            {options?.map((option) => (
-              <S.Option
-                value={option.value}
-                key={option.value}
-                disabled={option.disabled}
-                onClick={() => updateSelectedOption(option)}
-                tabIndex={0}
-                role="option"
-              >
-                {option.isLive && <LiveIcon />}
-                {option.label}
-              </S.Option>
-            ))}
-          </S.OptionList>
-        )}
-        <DropdownArrowIcon isOpen={showOptions} />
-      </S.Select>
-    </div>
-  );
-};
+Select.displayName = 'Select';
 
 export default Select;
diff --git a/kafka-ui-react-app/src/widgets/ClusterConfigForm/index.tsx b/kafka-ui-react-app/src/widgets/ClusterConfigForm/index.tsx
index 8bd0c3f1257..2c636c29a27 100644
--- a/kafka-ui-react-app/src/widgets/ClusterConfigForm/index.tsx
+++ b/kafka-ui-react-app/src/widgets/ClusterConfigForm/index.tsx
@@ -75,7 +75,7 @@ const ClusterConfigForm: React.FC<ClusterConfigFormProps> = ({
   const onReset = () => methods.reset();
 
   const onValidate = async () => {
-    await trigger();
+    await trigger(undefined, { shouldFocus: true });
     if (!methods.formState.isValid) return;
     disableForm();
     const data = methods.getValues();


Test patch: