This is an automated email from the ASF dual-hosted git repository.

enzomartellucci pushed a commit to branch 
enxdev/feat/enhance-database-modal-validation
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to 
refs/heads/enxdev/feat/enhance-database-modal-validation by this push:
     new ad92ec683b wip: add validation loading state to SSH tunnel form fields
ad92ec683b is described below

commit ad92ec683becd3d8cc4a4508b3de8639c4553a34
Author: Enzo Martellucci <[email protected]>
AuthorDate: Wed Dec 31 17:35:50 2025 +0100

    wip: add validation loading state to SSH tunnel form fields
---
 .../databases/DatabaseModal/SSHTunnelForm.tsx      | 140 +++++++++++----------
 .../src/features/databases/DatabaseModal/index.tsx |   4 +-
 2 files changed, 77 insertions(+), 67 deletions(-)

diff --git 
a/superset-frontend/src/features/databases/DatabaseModal/SSHTunnelForm.tsx 
b/superset-frontend/src/features/databases/DatabaseModal/SSHTunnelForm.tsx
index 79e2a6fab7..3df9ddd2a2 100644
--- a/superset-frontend/src/features/databases/DatabaseModal/SSHTunnelForm.tsx
+++ b/superset-frontend/src/features/databases/DatabaseModal/SSHTunnelForm.tsx
@@ -17,19 +17,18 @@
  * under the License.
  */
 import { useState } from 'react';
-import { t } from '@superset-ui/core';
+import { t, JsonObject } from '@superset-ui/core';
 import { styled } from '@apache-superset/core/ui';
 import {
   Form,
   FormLabel,
   Col,
   Row,
-  Tooltip,
+  LabeledErrorBoundInput,
 } from '@superset-ui/core/components';
 import { Input } from '@superset-ui/core/components/Input';
 import { Radio } from '@superset-ui/core/components/Radio';
-import { Icons } from '@superset-ui/core/components/Icons';
-import { DatabaseObject, FieldPropTypes } from '../types';
+import { DatabaseObject, CustomEventHandlerType } from '../types';
 import { AuthType } from '.';
 
 const StyledDiv = styled.div`
@@ -48,50 +47,60 @@ const StyledFormItem = styled(Form.Item)`
   margin-bottom: 0 !important;
 `;
 
-const StyledInputPassword = styled(Input.Password)`
-  margin: ${({ theme }) => `${theme.sizeUnit}px 0 ${theme.sizeUnit * 2}px`};
-`;
+interface SSHTunnelFormProps {
+  db: DatabaseObject | null;
+  onSSHTunnelParametersChange: CustomEventHandlerType;
+  setSSHTunnelLoginMethod: (method: AuthType) => void;
+  isValidating?: boolean;
+  validationErrors?: JsonObject | null;
+  getValidation?: () => void;
+}
 
 const SSHTunnelForm = ({
   db,
   onSSHTunnelParametersChange,
   setSSHTunnelLoginMethod,
-}: {
-  db: DatabaseObject | null;
-  onSSHTunnelParametersChange: 
FieldPropTypes['changeMethods']['onSSHTunnelParametersChange'];
-  setSSHTunnelLoginMethod: (method: AuthType) => void;
-}) => {
+  isValidating = false,
+  validationErrors,
+  getValidation,
+}: SSHTunnelFormProps) => {
   const [usePassword, setUsePassword] = useState<AuthType>(AuthType.Password);
+  const sshErrors = validationErrors?.ssh_tunnel || {};
 
   return (
     <Form>
       <StyledRow gutter={16}>
         <Col xs={24} md={12}>
           <StyledDiv>
-            <FormLabel htmlFor="server_address" required>
-              {t('SSH Host')}
-            </FormLabel>
-            <Input
+            <LabeledErrorBoundInput
+              id="server_address"
               name="server_address"
-              type="text"
+              label={t('SSH Host')}
+              required
               placeholder={t('e.g. 127.0.0.1')}
               value={db?.ssh_tunnel?.server_address || ''}
               onChange={onSSHTunnelParametersChange}
+              validationMethods={{ onBlur: getValidation }}
+              errorMessage={sshErrors?.server_address}
+              isValidating={isValidating}
               data-test="ssh-tunnel-server_address-input"
             />
           </StyledDiv>
         </Col>
         <Col xs={24} md={12}>
           <StyledDiv>
-            <FormLabel htmlFor="server_port" required>
-              {t('SSH Port')}
-            </FormLabel>
-            <Input
+            <LabeledErrorBoundInput
+              id="server_port"
               name="server_port"
+              label={t('SSH Port')}
+              required
               placeholder={t('22')}
               type="number"
               value={db?.ssh_tunnel?.server_port}
               onChange={onSSHTunnelParametersChange}
+              validationMethods={{ onBlur: getValidation }}
+              errorMessage={sshErrors?.server_port}
+              isValidating={isValidating}
               data-test="ssh-tunnel-server_port-input"
             />
           </StyledDiv>
@@ -100,15 +109,17 @@ const SSHTunnelForm = ({
       <StyledRow gutter={16}>
         <Col xs={24}>
           <StyledDiv>
-            <FormLabel htmlFor="username" required>
-              {t('Username')}
-            </FormLabel>
-            <Input
+            <LabeledErrorBoundInput
+              id="username"
               name="username"
-              type="text"
+              label={t('Username')}
+              required
               placeholder={t('e.g. Analytics')}
               value={db?.ssh_tunnel?.username || ''}
               onChange={onSSHTunnelParametersChange}
+              validationMethods={{ onBlur: getValidation }}
+              errorMessage={sshErrors?.username}
+              isValidating={isValidating}
               data-test="ssh-tunnel-username-input"
             />
           </StyledDiv>
@@ -148,27 +159,19 @@ const SSHTunnelForm = ({
         <StyledRow gutter={16}>
           <Col xs={24}>
             <StyledDiv>
-              <FormLabel htmlFor="password" required>
-                {t('SSH Password')}
-              </FormLabel>
-              <StyledInputPassword
+              <LabeledErrorBoundInput
+                id="password"
                 name="password"
+                label={t('SSH Password')}
+                required
+                visibilityToggle
                 placeholder={t('e.g. ********')}
                 value={db?.ssh_tunnel?.password || ''}
                 onChange={onSSHTunnelParametersChange}
+                validationMethods={{ onBlur: getValidation }}
+                errorMessage={sshErrors?.password}
+                isValidating={isValidating}
                 data-test="ssh-tunnel-password-input"
-                iconRender={visible =>
-                  visible ? (
-                    <Tooltip title="Hide password.">
-                      <Icons.EyeInvisibleOutlined />
-                    </Tooltip>
-                  ) : (
-                    <Tooltip title="Show password.">
-                      <Icons.EyeOutlined />
-                    </Tooltip>
-                  )
-                }
-                role="textbox"
               />
             </StyledDiv>
           </Col>
@@ -182,41 +185,46 @@ const SSHTunnelForm = ({
                 <FormLabel htmlFor="private_key" required>
                   {t('Private Key')}
                 </FormLabel>
-                <Input.TextArea
-                  name="private_key"
-                  placeholder={t('Paste Private Key here')}
-                  value={db?.ssh_tunnel?.private_key || ''}
-                  onChange={onSSHTunnelParametersChange}
-                  data-test="ssh-tunnel-private_key-input"
-                  rows={4}
-                />
+                <StyledFormItem
+                  validateStatus={
+                    isValidating
+                      ? 'validating'
+                      : sshErrors?.private_key
+                        ? 'error'
+                        : 'success'
+                  }
+                  help={sshErrors?.private_key}
+                  hasFeedback={isValidating || !!sshErrors?.private_key}
+                >
+                  <Input.TextArea
+                    name="private_key"
+                    placeholder={t('Paste Private Key here')}
+                    value={db?.ssh_tunnel?.private_key || ''}
+                    onChange={onSSHTunnelParametersChange}
+                    onBlur={getValidation}
+                    data-test="ssh-tunnel-private_key-input"
+                    rows={4}
+                  />
+                </StyledFormItem>
               </StyledDiv>
             </Col>
           </StyledRow>
           <StyledRow gutter={16}>
             <Col xs={24}>
               <StyledDiv>
-                <FormLabel htmlFor="private_key_password" required>
-                  {t('Private Key Password')}
-                </FormLabel>
-                <StyledInputPassword
+                <LabeledErrorBoundInput
+                  id="private_key_password"
                   name="private_key_password"
+                  label={t('Private Key Password')}
+                  required
+                  visibilityToggle
                   placeholder={t('e.g. ********')}
                   value={db?.ssh_tunnel?.private_key_password || ''}
                   onChange={onSSHTunnelParametersChange}
+                  validationMethods={{ onBlur: getValidation }}
+                  errorMessage={sshErrors?.private_key_password}
+                  isValidating={isValidating}
                   data-test="ssh-tunnel-private_key_password-input"
-                  iconRender={visible =>
-                    visible ? (
-                      <Tooltip title="Hide password.">
-                        <Icons.EyeInvisibleOutlined />
-                      </Tooltip>
-                    ) : (
-                      <Tooltip title="Show password.">
-                        <Icons.EyeOutlined />
-                      </Tooltip>
-                    )
-                  }
-                  role="textbox"
                 />
               </StyledDiv>
             </Col>
diff --git a/superset-frontend/src/features/databases/DatabaseModal/index.tsx 
b/superset-frontend/src/features/databases/DatabaseModal/index.tsx
index 425d867689..f77ce16db9 100644
--- a/superset-frontend/src/features/databases/DatabaseModal/index.tsx
+++ b/superset-frontend/src/features/databases/DatabaseModal/index.tsx
@@ -1730,7 +1730,6 @@ const DatabaseModal: 
FunctionComponent<DatabaseModalProps> = ({
           name: target.name,
           value: target.value,
         });
-        handleClearValidationErrors();
       }}
       setSSHTunnelLoginMethod={(method: AuthType) =>
         setDB({
@@ -1738,6 +1737,9 @@ const DatabaseModal: 
FunctionComponent<DatabaseModalProps> = ({
           payload: { login_method: method },
         })
       }
+      isValidating={isValidating}
+      validationErrors={validationErrors}
+      getValidation={() => getValidation(db)}
     />
   );
 

Reply via email to