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

diegopucci pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/master by this push:
     new 30695d75d7 fix(DatabaseModal): Resolve Connect button issue for 
SQLAlchemy URI database connections (#34112)
30695d75d7 is described below

commit 30695d75d7ca8350d619c787f6ecd151b8a00cac
Author: Enzo Martellucci <52219496+enx...@users.noreply.github.com>
AuthorDate: Fri Jul 11 13:03:36 2025 +0200

    fix(DatabaseModal): Resolve Connect button issue for SQLAlchemy URI 
database connections (#34112)
---
 .../components/DeleteModal/DeleteModal.test.tsx    |   6 +-
 .../src/components/Modal/Modal.tsx                 | 210 +++++++++++----------
 .../src/components/ErrorMessage/ErrorAlert.tsx     |   1 +
 .../PropertiesModal/PropertiesModal.test.tsx       |   2 +-
 .../src/features/databases/DatabaseModal/index.tsx |  26 ++-
 5 files changed, 134 insertions(+), 111 deletions(-)

diff --git 
a/superset-frontend/packages/superset-ui-core/src/components/DeleteModal/DeleteModal.test.tsx
 
b/superset-frontend/packages/superset-ui-core/src/components/DeleteModal/DeleteModal.test.tsx
index be2868afe9..dcf4cb508d 100644
--- 
a/superset-frontend/packages/superset-ui-core/src/components/DeleteModal/DeleteModal.test.tsx
+++ 
b/superset-frontend/packages/superset-ui-core/src/components/DeleteModal/DeleteModal.test.tsx
@@ -62,12 +62,12 @@ test('Calling "onHide"', async () => {
   expect(props.onConfirm).toHaveBeenCalledTimes(0);
 
   // type "del" in the input
-  await userEvent.type(screen.getByTestId('delete-modal-input'), 'del');
+  userEvent.type(screen.getByTestId('delete-modal-input'), 'del');
   expect(screen.getByTestId('delete-modal-input')).toHaveValue('del');
 
   // close the modal
-  expect(screen.getByText('×')).toBeInTheDocument();
-  await userEvent.click(screen.getByText('×'));
+  expect(screen.getByTestId('close-modal-btn')).toBeInTheDocument();
+  userEvent.click(screen.getByTestId('close-modal-btn'));
   expect(props.onHide).toHaveBeenCalledTimes(1);
   expect(props.onConfirm).toHaveBeenCalledTimes(0);
 
diff --git 
a/superset-frontend/packages/superset-ui-core/src/components/Modal/Modal.tsx 
b/superset-frontend/packages/superset-ui-core/src/components/Modal/Modal.tsx
index 5b9f1c2531..4b7125dea5 100644
--- a/superset-frontend/packages/superset-ui-core/src/components/Modal/Modal.tsx
+++ b/superset-frontend/packages/superset-ui-core/src/components/Modal/Modal.tsx
@@ -18,7 +18,7 @@
  */
 import { isValidElement, cloneElement, useMemo, useRef, useState } from 
'react';
 import { isNil } from 'lodash';
-import { css, styled, t } from '@superset-ui/core';
+import { css, styled, t, useTheme } from '@superset-ui/core';
 import { Modal as AntdModal, ModalProps as AntdModalProps } from 'antd';
 import { Resizable } from 're-resizable';
 import Draggable, {
@@ -26,6 +26,7 @@ import Draggable, {
   DraggableData,
   DraggableEvent,
 } from 'react-draggable';
+import { Icons } from '../Icons';
 import { Button } from '../Button';
 import type { ModalProps, StyledModalProps } from './types';
 
@@ -45,8 +46,16 @@ export const BaseModal = (props: AntdModalProps) => (
 );
 
 export const StyledModal = styled(BaseModal)<StyledModalProps>`
-  ${({ theme, responsive, maxWidth }) =>
-    responsive &&
+  ${({
+    theme,
+    responsive,
+    maxWidth,
+    resizable,
+    height,
+    draggable,
+    hideFooter,
+  }) => css`
+    ${responsive &&
     css`
       max-width: ${maxWidth ?? '900px'};
       padding-left: ${theme.sizeUnit * 3}px;
@@ -55,120 +64,120 @@ export const StyledModal = 
styled(BaseModal)<StyledModalProps>`
       top: 0;
     `}
 
-  .ant-modal-content {
-    background-color: ${({ theme }) => theme.colorBgContainer};
-    display: flex;
-    flex-direction: column;
-    max-height: ${({ theme }) => `calc(100vh - ${theme.sizeUnit * 8}px)`};
-    margin-bottom: ${({ theme }) => theme.sizeUnit * 4}px;
-    margin-top: ${({ theme }) => theme.sizeUnit * 4}px;
-    padding: 0;
-  }
-
-  .ant-modal-header {
-    flex: 0 0 auto;
-    border-radius: ${({ theme }) => theme.borderRadius}px
-      ${({ theme }) => theme.borderRadius}px 0 0;
-    padding: ${({ theme }) => theme.sizeUnit * 4}px
-      ${({ theme }) => theme.sizeUnit * 6}px;
-
-    .ant-modal-title {
-      font-weight: ${({ theme }) => theme.fontWeightStrong};
+    .ant-modal-content {
+      background-color: ${theme.colorBgContainer};
+      display: flex;
+      flex-direction: column;
+      max-height: calc(100vh - ${theme.sizeUnit * 8}px);
+      margin-bottom: ${theme.sizeUnit * 4}px;
+      margin-top: ${theme.sizeUnit * 4}px;
+      padding: 0;
+    }
+
+    .ant-modal-header {
+      flex: 0 0 auto;
+      border-radius: ${theme.borderRadius}px ${theme.borderRadius}px 0 0;
+      padding: ${theme.sizeUnit * 4}px ${theme.sizeUnit * 6}px;
+
+      .ant-modal-title {
+        font-weight: ${theme.fontWeightStrong};
+      }
+
+      .ant-modal-title h4 {
+        display: flex;
+        margin: 0;
+        align-items: center;
+      }
     }
 
-    .ant-modal-title h4 {
+    .ant-modal-close {
+      width: ${theme.sizeUnit * 14}px;
+      height: ${theme.sizeUnit * 14}px;
+      padding: ${theme.sizeUnit * 6}px ${theme.sizeUnit * 4}px
+        ${theme.sizeUnit * 4}px;
+      top: 0;
+      right: 0;
       display: flex;
-      margin: 0;
-      align-items: center;
+      justify-content: center;
     }
-  }
-
-  .ant-modal-close {
-    width: ${({ theme }) => theme.sizeUnit * 14}px;
-    height: ${({ theme }) => theme.sizeUnit * 14}px;
-    top: 0;
-    right: 0;
-  }
-
-  .ant-modal-close:hover {
-    background: transparent;
-  }
-
-  .ant-modal-close-x {
-    display: flex;
-    align-items: center;
-
-    .close {
-      flex: 1 1 auto;
-      margin-bottom: ${({ theme }) => theme.sizeUnit}px;
-      color: ${({ theme }) => theme.colorPrimaryText};
-      font-size: 32px;
-      font-weight: ${({ theme }) => theme.fontWeightLight};
+
+    .ant-modal-close:hover {
+      background: transparent;
     }
-  }
-
-  .ant-modal-body {
-    flex: 0 1 auto;
-    padding: ${({ theme }) => theme.sizeUnit * 4}px;
-    overflow: auto;
-    ${({ resizable, height }) => !resizable && height && `height: ${height};`}
-  }
-  .ant-modal-footer {
-    flex: 0 0 1;
-    border-top: ${({ theme }) => theme.sizeUnit / 4}px solid
-      ${({ theme }) => theme.colorSplit};
-    padding: ${({ theme }) => theme.sizeUnit * 4}px;
-    margin-top: 0;
-
-    .btn {
-      font-size: 12px;
+
+    .ant-modal-close-x {
+      display: flex;
+      align-items: center;
+      [data-test='close-modal-btn'] {
+        justify-content: center;
+      }
+      .close {
+        flex: 1 1 auto;
+        margin-bottom: ${theme.sizeUnit}px;
+        color: ${theme.colorPrimaryText};
+        font-weight: ${theme.fontWeightLight};
+      }
     }
 
-    .btn + .btn {
-      margin-left: ${({ theme }) => theme.sizeUnit * 2}px;
+    .ant-modal-body {
+      flex: 0 1 auto;
+      padding: ${theme.sizeUnit * 4}px;
+      overflow: auto;
+      ${!resizable && height && `height: ${height};`}
     }
-  }
 
-  &.no-content-padding .ant-modal-body {
-    padding: 0;
-  }
+    .ant-modal-footer {
+      flex: 0 0 1;
+      border-top: ${theme.sizeUnit / 4}px solid ${theme.colorSplit};
+      padding: ${theme.sizeUnit * 4}px;
+      margin-top: 0;
 
-  ${({ draggable, theme }) =>
-    draggable &&
-    `
-    .ant-modal-header {
+      .btn {
+        font-size: 12px;
+      }
+
+      .btn + .btn {
+        margin-left: ${theme.sizeUnit * 2}px;
+      }
+    }
+
+    &.no-content-padding .ant-modal-body {
       padding: 0;
-      .draggable-trigger {
+    }
+
+    ${draggable &&
+    css`
+      .ant-modal-header {
+        padding: 0;
+
+        .draggable-trigger {
           cursor: move;
           padding: ${theme.sizeUnit * 4}px;
           width: 100%;
         }
-    }
-  `};
+      }
+    `}
 
-  ${({ resizable, hideFooter }) =>
-    resizable &&
-    `
-    .resizable {
-      pointer-events: all;
+    ${resizable &&
+    css`
+      .resizable {
+        pointer-events: all;
 
-      .resizable-wrapper {
-        height: 100%;
-      }
+        .resizable-wrapper {
+          height: 100%;
+        }
 
-      .ant-modal-content {
-        height: 100%;
+        .ant-modal-content {
+          height: 100%;
 
-        .ant-modal-body {
-          /* 100% - header height - footer height */
-          height: ${
-            hideFooter
-              ? `calc(100% - ${MODAL_HEADER_HEIGHT}px);`
-              : `calc(100% - ${MODAL_HEADER_HEIGHT}px - 
${MODAL_FOOTER_HEIGHT}px);`
+          .ant-modal-body {
+            height: ${hideFooter
+              ? `calc(100% - ${MODAL_HEADER_HEIGHT}px)`
+              : `calc(100% - ${MODAL_HEADER_HEIGHT}px - 
${MODAL_FOOTER_HEIGHT}px)`};
           }
         }
       }
-    }
+    `}
   `}
 `;
 
@@ -221,6 +230,7 @@ const CustomModal = ({
   const draggableRef = useRef<HTMLDivElement>(null);
   const [bounds, setBounds] = useState<DraggableBounds>();
   const [dragDisabled, setDragDisabled] = useState<boolean>(true);
+  const theme = useTheme();
 
   const handleOnHide = () => {
     openerRef?.current?.focus();
@@ -312,9 +322,13 @@ const CustomModal = ({
       open={show}
       title={<ModalTitle />}
       closeIcon={
-        <span data-test="close-modal-btn" className="close" aria-hidden="true">
-          ×
-        </span>
+        <Icons.CloseOutlined
+          iconColor={theme.colorText}
+          iconSize="l"
+          data-test="close-modal-btn"
+          className="close"
+          aria-hidden="true"
+        />
       }
       footer={!hideFooter ? modalFooter : null}
       hideFooter={hideFooter}
diff --git a/superset-frontend/src/components/ErrorMessage/ErrorAlert.tsx 
b/superset-frontend/src/components/ErrorMessage/ErrorAlert.tsx
index 1c503de955..3985ef952d 100644
--- a/superset-frontend/src/components/ErrorMessage/ErrorAlert.tsx
+++ b/superset-frontend/src/components/ErrorMessage/ErrorAlert.tsx
@@ -70,6 +70,7 @@ export const ErrorAlert: React.FC<ErrorAlertProps> = ({
   const preStyle = {
     whiteSpace: 'pre-wrap',
     fontFamily: theme.fontFamilyCode,
+    margin: `${theme.sizeUnit}px 0`,
   };
   const renderDescription = () => (
     <div>
diff --git 
a/superset-frontend/src/explore/components/PropertiesModal/PropertiesModal.test.tsx
 
b/superset-frontend/src/explore/components/PropertiesModal/PropertiesModal.test.tsx
index 61e4d6c772..4bfa0cff85 100644
--- 
a/superset-frontend/src/explore/components/PropertiesModal/PropertiesModal.test.tsx
+++ 
b/superset-frontend/src/explore/components/PropertiesModal/PropertiesModal.test.tsx
@@ -173,7 +173,7 @@ test('Should have modal header', async () => {
 
   await waitFor(() => {
     expect(screen.getByText('Edit Chart Properties')).toBeVisible();
-    expect(screen.getByText('×')).toBeVisible();
+    expect(screen.getByTestId('close-modal-btn')).toBeVisible();
     expect(screen.getByRole('button', { name: 'Close' })).toBeVisible();
   });
 });
diff --git a/superset-frontend/src/features/databases/DatabaseModal/index.tsx 
b/superset-frontend/src/features/databases/DatabaseModal/index.tsx
index cbb6ced2a9..70484205aa 100644
--- a/superset-frontend/src/features/databases/DatabaseModal/index.tsx
+++ b/superset-frontend/src/features/databases/DatabaseModal/index.tsx
@@ -252,8 +252,9 @@ export type DBReducerActionType =
     };
 
 const StyledBtns = styled.div`
-  margin-bottom: ${({ theme }) => theme.sizeUnit * 3}px;
-  margin-left: ${({ theme }) => theme.sizeUnit * 3}px;
+  display: flex;
+  justify-content: center;
+  padding: ${({ theme }) => theme.sizeUnit * 5}px;
 `;
 
 export function dbReducer(
@@ -702,6 +703,7 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> 
= ({
 
   // Test Connection logic
   const testConnection = () => {
+    handleClearValidationErrors();
     if (!db?.sqlalchemy_uri) {
       addDangerToast(t('Please enter a SQLAlchemy URI to test'));
       return;
@@ -728,10 +730,12 @@ const DatabaseModal: 
FunctionComponent<DatabaseModalProps> = ({
       (errorMsg: string) => {
         setTestInProgress(false);
         addDangerToast(errorMsg);
+        setHasValidated(false);
       },
       (errorMsg: string) => {
         setTestInProgress(false);
         addSuccessToast(errorMsg);
+        setHasValidated(true);
       },
     );
   };
@@ -761,6 +765,7 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> 
= ({
   const handleClearValidationErrors = useCallback(() => {
     setValidationErrors(null);
     setHasValidated(false);
+    clearError();
   }, [setValidationErrors, setHasValidated]);
 
   const handleParametersChange = useCallback(
@@ -820,7 +825,7 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> 
= ({
   const onSave = async () => {
     let dbConfigExtraExtensionOnSaveError;
     setLoading(true);
-
+    setHasValidated(false);
     dbConfigExtraExtension
       ?.onSave(extraExtensionComponentState, db)
       .then(({ error }: { error: any }) => {
@@ -1166,6 +1171,7 @@ const DatabaseModal: 
FunctionComponent<DatabaseModalProps> = ({
   };
 
   const handleBackButtonOnConnect = () => {
+    handleClearValidationErrors();
     if (editNewDb) setHasConnectedDb(false);
     if (importingModal) setImportingModal(false);
     if (importErrored) {
@@ -1925,14 +1931,15 @@ const DatabaseModal: 
FunctionComponent<DatabaseModalProps> = ({
                         target,
                       }: {
                         target: HTMLInputElement;
-                      }) =>
+                      }) => {
+                        setHasValidated(false);
                         onChange(ActionType.InputChange, {
                           type: target.type,
                           name: target.name,
                           checked: target.checked,
                           value: target.value,
-                        })
-                      }
+                        });
+                      }}
                       conf={conf}
                       testConnection={testConnection}
                       testInProgress={testInProgress}
@@ -2156,7 +2163,8 @@ const DatabaseModal: 
FunctionComponent<DatabaseModalProps> = ({
                       <Button
                         data-test="sqla-connect-btn"
                         buttonStyle="link"
-                        onClick={() =>
+                        onClick={() => {
+                          handleClearValidationErrors();
                           setDB({
                             type: ActionType.ConfigMethodChange,
                             payload: {
@@ -2165,8 +2173,8 @@ const DatabaseModal: 
FunctionComponent<DatabaseModalProps> = ({
                                 ConfigurationMethod.SqlalchemyUri,
                               database_name: db.database_name,
                             },
-                          })
-                        }
+                          });
+                        }}
                         css={buttonLinkStyles}
                       >
                         {t(

Reply via email to