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

commit 6aef573304aa8fa5ac0fc7d8b97fe97b74fd1902
Author: Enzo Martellucci <[email protected]>
AuthorDate: Wed Dec 31 17:23:13 2025 +0100

    feat(database): add validation loading state and duplicate name check
    
    - Add isValidating prop to TableCatalog, ValidatedInputField, and
      CommonParameters to show loading spinner during validation
    - Fix LabeledErrorBoundInput hasFeedback to display spinner while
      validating, not just on errors
    - Add duplicate database name validation to validate_parameters
      endpoint for real-time feedback before form submission
---
 .../src/components/Form/LabeledErrorBoundInput.tsx |  2 +-
 .../DatabaseConnectionForm/CommonParameters.tsx    |  2 ++
 .../DatabaseConnectionForm/TableCatalog.tsx        |  3 +++
 .../DatabaseConnectionForm/ValidatedInputField.tsx |  2 ++
 .../src/features/databases/DatabaseModal/index.tsx | 17 +++++++++------
 superset/commands/database/validate.py             | 24 +++++++++++++++++++++-
 6 files changed, 42 insertions(+), 8 deletions(-)

diff --git 
a/superset-frontend/packages/superset-ui-core/src/components/Form/LabeledErrorBoundInput.tsx
 
b/superset-frontend/packages/superset-ui-core/src/components/Form/LabeledErrorBoundInput.tsx
index b8f1dc1ef4..b2f1f5a34f 100644
--- 
a/superset-frontend/packages/superset-ui-core/src/components/Form/LabeledErrorBoundInput.tsx
+++ 
b/superset-frontend/packages/superset-ui-core/src/components/Form/LabeledErrorBoundInput.tsx
@@ -79,7 +79,7 @@ export const LabeledErrorBoundInput = ({
           isValidating ? 'validating' : hasError ? 'error' : 'success'
         }
         help={errorMessage || helpText}
-        hasFeedback={!!hasError}
+        hasFeedback={isValidating || !!hasError}
       >
         {visibilityToggle || props.name === 'password' ? (
           <StyledInputPassword
diff --git 
a/superset-frontend/src/features/databases/DatabaseModal/DatabaseConnectionForm/CommonParameters.tsx
 
b/superset-frontend/src/features/databases/DatabaseModal/DatabaseConnectionForm/CommonParameters.tsx
index 1f2993a134..6115715ec9 100644
--- 
a/superset-frontend/src/features/databases/DatabaseModal/DatabaseConnectionForm/CommonParameters.tsx
+++ 
b/superset-frontend/src/features/databases/DatabaseModal/DatabaseConnectionForm/CommonParameters.tsx
@@ -243,6 +243,7 @@ export const accessTokenField = ({
   validationErrors,
   db,
   isEditMode,
+  isValidating,
   default_value,
   description,
 }: FieldPropTypes) => (
@@ -250,6 +251,7 @@ export const accessTokenField = ({
     id="access_token"
     name="access_token"
     required={required}
+    isValidating={isValidating}
     visibilityToggle={!isEditMode}
     value={db?.parameters?.access_token}
     validationMethods={{ onBlur: getValidation }}
diff --git 
a/superset-frontend/src/features/databases/DatabaseModal/DatabaseConnectionForm/TableCatalog.tsx
 
b/superset-frontend/src/features/databases/DatabaseModal/DatabaseConnectionForm/TableCatalog.tsx
index 4798f5f3c0..f372291369 100644
--- 
a/superset-frontend/src/features/databases/DatabaseModal/DatabaseConnectionForm/TableCatalog.tsx
+++ 
b/superset-frontend/src/features/databases/DatabaseModal/DatabaseConnectionForm/TableCatalog.tsx
@@ -33,6 +33,7 @@ export const TableCatalog = ({
   getValidation,
   validationErrors,
   db,
+  isValidating,
 }: FieldPropTypes) => {
   const tableCatalog = db?.catalog || [];
   const catalogError = validationErrors || {};
@@ -51,6 +52,7 @@ export const TableCatalog = ({
               <ValidatedInput
                 className="catalog-name-input"
                 required={required}
+                isValidating={isValidating}
                 validationMethods={{ onBlur: getValidation }}
                 errorMessage={catalogError[idx]?.name}
                 placeholder={t('Enter a name for this sheet')}
@@ -84,6 +86,7 @@ export const TableCatalog = ({
             <ValidatedInput
               className="catalog-name-url"
               required={required}
+              isValidating={isValidating}
               validationMethods={{ onBlur: getValidation }}
               errorMessage={catalogError[idx]?.url}
               placeholder={t('Paste the shareable Google Sheet URL here')}
diff --git 
a/superset-frontend/src/features/databases/DatabaseModal/DatabaseConnectionForm/ValidatedInputField.tsx
 
b/superset-frontend/src/features/databases/DatabaseModal/DatabaseConnectionForm/ValidatedInputField.tsx
index 548ae77398..7b4571b5f6 100644
--- 
a/superset-frontend/src/features/databases/DatabaseModal/DatabaseConnectionForm/ValidatedInputField.tsx
+++ 
b/superset-frontend/src/features/databases/DatabaseModal/DatabaseConnectionForm/ValidatedInputField.tsx
@@ -49,11 +49,13 @@ export const validatedInputField = ({
   validationErrors,
   db,
   field,
+  isValidating,
 }: FieldPropTypes) => (
   <ValidatedInput
     id={field}
     name={field}
     required={required}
+    isValidating={isValidating}
     value={db?.parameters?.[field as keyof DatabaseParameters]}
     validationMethods={{ onBlur: getValidation }}
     errorMessage={validationErrors?.[field]}
diff --git a/superset-frontend/src/features/databases/DatabaseModal/index.tsx 
b/superset-frontend/src/features/databases/DatabaseModal/index.tsx
index bbcdfe1603..425d867689 100644
--- a/superset-frontend/src/features/databases/DatabaseModal/index.tsx
+++ b/superset-frontend/src/features/databases/DatabaseModal/index.tsx
@@ -790,6 +790,16 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> 
= ({
     [onChange],
   );
 
+  const handleTextChange = useCallback(
+    ({ target }: { target: HTMLInputElement }) => {
+      onChange(ActionType.TextChange, {
+        name: target.name,
+        value: target.value,
+      });
+    },
+    [onChange],
+  );
+
   const handleChangeWithValidation = useCallback(
     (
       actionType: ActionType,
@@ -1796,12 +1806,7 @@ const DatabaseModal: 
FunctionComponent<DatabaseModalProps> = ({
           });
         }}
         onParametersChange={handleParametersChange}
-        onChange={({ target }: { target: HTMLInputElement }) =>
-          handleChangeWithValidation(ActionType.TextChange, {
-            name: target.name,
-            value: target.value,
-          })
-        }
+        onChange={handleTextChange}
         getValidation={() => getValidation(db)}
         validationErrors={validationErrors}
         getPlaceholder={getPlaceholder}
diff --git a/superset/commands/database/validate.py 
b/superset/commands/database/validate.py
index 29c9497140..df431076af 100644
--- a/superset/commands/database/validate.py
+++ b/superset/commands/database/validate.py
@@ -139,5 +139,27 @@ class ValidateDatabaseParametersCommand(BaseCommand):
             )
 
     def validate(self) -> None:
-        if (database_id := self._properties.get("id")) is not None:
+        database_id = self._properties.get("id")
+        database_name = self._properties.get("database_name")
+
+        if database_id is not None:
             self._model = DatabaseDAO.find_by_id(database_id)
+
+        # Check for duplicate database name
+        if database_name:
+            is_unique = (
+                DatabaseDAO.validate_update_uniqueness(database_id, 
database_name)
+                if database_id is not None
+                else DatabaseDAO.validate_uniqueness(database_name)
+            )
+            if not is_unique:
+                raise InvalidParametersError(
+                    [
+                        SupersetError(
+                            message=__("A database with the same name already 
exists."),
+                            
error_type=SupersetErrorType.INVALID_PAYLOAD_SCHEMA_ERROR,
+                            level=ErrorLevel.ERROR,
+                            extra={"invalid": ["database_name"]},
+                        )
+                    ]
+                )

Reply via email to