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

msyavuz 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 cb88d886c7 fix(PropertiesModal): do not show validation errors while 
loading (#35215)
cb88d886c7 is described below

commit cb88d886c71066e4fdbb09c0aeaa9fbe1378d9fd
Author: Mehmet Salih Yavuz <[email protected]>
AuthorDate: Wed Sep 24 10:52:16 2025 +0300

    fix(PropertiesModal): do not show validation errors while loading (#35215)
---
 .../src/components/Modal/StandardModal.tsx         | 14 +++-
 .../PropertiesModal/PropertiesModal.test.tsx       | 24 +++++-
 .../dashboard/components/PropertiesModal/index.tsx | 18 ++--
 .../sections/BasicInfoSection.test.tsx             | 15 +---
 .../PropertiesModal/sections/BasicInfoSection.tsx  | 97 +++++++++++-----------
 5 files changed, 91 insertions(+), 77 deletions(-)

diff --git a/superset-frontend/src/components/Modal/StandardModal.tsx 
b/superset-frontend/src/components/Modal/StandardModal.tsx
index 43522baf22..49e0d31433 100644
--- a/superset-frontend/src/components/Modal/StandardModal.tsx
+++ b/superset-frontend/src/components/Modal/StandardModal.tsx
@@ -18,7 +18,7 @@
  */
 import { ReactNode } from 'react';
 import { styled, t } from '@superset-ui/core';
-import { Modal } from '@superset-ui/core/components';
+import { Modal, Loading, Flex } from '@superset-ui/core/components';
 import { ModalTitleWithIcon } from 'src/components/ModalTitleWithIcon';
 
 interface StandardModalProps {
@@ -39,6 +39,7 @@ interface StandardModalProps {
   destroyOnClose?: boolean;
   maskClosable?: boolean;
   wrapProps?: object;
+  contentLoading?: boolean;
 }
 
 // Standard modal widths
@@ -113,12 +114,13 @@ export function StandardModal({
   destroyOnClose = true,
   maskClosable = false,
   wrapProps,
+  contentLoading = false,
 }: StandardModalProps) {
   const primaryButtonName = saveText || (isEditMode ? t('Save') : t('Add'));
 
   return (
     <StyledModal
-      disablePrimaryButton={saveDisabled || saveLoading}
+      disablePrimaryButton={saveDisabled || saveLoading || contentLoading}
       primaryButtonLoading={saveLoading}
       primaryTooltipMessage={errorTooltip}
       onHandledPrimaryAction={onSave}
@@ -139,7 +141,13 @@ export function StandardModal({
         )
       }
     >
-      {children}
+      {contentLoading ? (
+        <Flex justify="center" align="center" style={{ minHeight: 200 }}>
+          <Loading />
+        </Flex>
+      ) : (
+        children
+      )}
     </StyledModal>
   );
 }
diff --git 
a/superset-frontend/src/dashboard/components/PropertiesModal/PropertiesModal.test.tsx
 
b/superset-frontend/src/dashboard/components/PropertiesModal/PropertiesModal.test.tsx
index 52ead26cb3..9298c6b09e 100644
--- 
a/superset-frontend/src/dashboard/components/PropertiesModal/PropertiesModal.test.tsx
+++ 
b/superset-frontend/src/dashboard/components/PropertiesModal/PropertiesModal.test.tsx
@@ -354,9 +354,19 @@ describe('PropertiesModal', () => {
     mockedIsFeatureEnabled.mockReturnValue(false);
     const props = createProps();
     props.onlyApply = false;
-    render(<PropertiesModal {...props} />, {
+    // Pass dashboardInfo to avoid loading state
+    const propsWithDashboardInfo = {
+      ...props,
+      dashboardInfo: {
+        ...dashboardInfo,
+        json_metadata: mockedJsonMetadata,
+      },
+    };
+    render(<PropertiesModal {...propsWithDashboardInfo} />, {
       useRedux: true,
     });
+
+    // Wait for the form to be visible
     expect(
       await screen.findByTestId('dashboard-edit-properties-form'),
     ).toBeInTheDocument();
@@ -379,9 +389,19 @@ describe('PropertiesModal', () => {
     mockedIsFeatureEnabled.mockReturnValue(false);
     const props = createProps();
     props.onlyApply = true;
-    render(<PropertiesModal {...props} />, {
+    // Pass dashboardInfo to avoid loading state
+    const propsWithDashboardInfo = {
+      ...props,
+      dashboardInfo: {
+        ...dashboardInfo,
+        json_metadata: mockedJsonMetadata,
+      },
+    };
+    render(<PropertiesModal {...propsWithDashboardInfo} />, {
       useRedux: true,
     });
+
+    // Wait for the form to be visible
     expect(
       await screen.findByTestId('dashboard-edit-properties-form'),
     ).toBeInTheDocument();
diff --git 
a/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx 
b/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx
index 040bc6ee2e..614f042ba5 100644
--- a/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx
+++ b/superset-frontend/src/dashboard/components/PropertiesModal/index.tsx
@@ -112,7 +112,7 @@ const PropertiesModal = ({
   const dispatch = useDispatch();
   const [form] = Form.useForm();
 
-  const [isLoading, setIsLoading] = useState(false);
+  const [isLoading, setIsLoading] = useState(true);
   const [isApplying, setIsApplying] = useState(false);
   const [colorScheme, setCurrentColorScheme] = useState(currentColorScheme);
   const [jsonMetadata, setJsonMetadata] = useState('');
@@ -207,7 +207,6 @@ const PropertiesModal = ({
   );
 
   const fetchDashboardDetails = useCallback(() => {
-    setIsLoading(true);
     // We fetch the dashboard details because not all code
     // that renders this component have all the values we need.
     // At some point when we have a more consistent frontend
@@ -382,10 +381,6 @@ const PropertiesModal = ({
     if (onlyApply) {
       setIsApplying(true);
       try {
-        console.log('Apply CSS debug:', {
-          css_being_sent: customCss,
-          onSubmitProps_css: onSubmitProps.css,
-        });
         onSubmit(onSubmitProps);
         onHide();
         addSuccessToast(t('Dashboard properties updated'));
@@ -422,10 +417,15 @@ const PropertiesModal = ({
 
   useEffect(() => {
     if (show) {
+      // Reset loading state when modal opens
+      setIsLoading(true);
+
       if (!currentDashboardInfo) {
         fetchDashboardDetails();
       } else {
         handleDashboardData(currentDashboardInfo);
+        // Data is immediately available, so we can stop loading
+        setIsLoading(false);
       }
 
       // Fetch themes (excluding system themes)
@@ -621,10 +621,9 @@ const PropertiesModal = ({
       }}
       title={t('Dashboard properties')}
       isEditMode
-      saveDisabled={
-        isLoading || dashboardInfo?.isManagedExternally || hasErrors
-      }
+      saveDisabled={dashboardInfo?.isManagedExternally || hasErrors}
       saveLoading={isApplying}
+      contentLoading={isLoading}
       errorTooltip={
         dashboardInfo?.isManagedExternally
           ? t(
@@ -665,7 +664,6 @@ const PropertiesModal = ({
               children: (
                 <BasicInfoSection
                   form={form}
-                  isLoading={isLoading}
                   validationStatus={validationStatus}
                 />
               ),
diff --git 
a/superset-frontend/src/dashboard/components/PropertiesModal/sections/BasicInfoSection.test.tsx
 
b/superset-frontend/src/dashboard/components/PropertiesModal/sections/BasicInfoSection.test.tsx
index 6f8052c9c5..df3c8f322d 100644
--- 
a/superset-frontend/src/dashboard/components/PropertiesModal/sections/BasicInfoSection.test.tsx
+++ 
b/superset-frontend/src/dashboard/components/PropertiesModal/sections/BasicInfoSection.test.tsx
@@ -21,8 +21,9 @@ import { Form } from '@superset-ui/core/components';
 import BasicInfoSection from './BasicInfoSection';
 
 const defaultProps = {
-  form: {} as any,
-  isLoading: false,
+  form: {
+    getFieldValue: jest.fn(() => 'Test Dashboard'),
+  } as any,
   validationStatus: {
     basic: { hasErrors: false, errors: [], name: 'Basic' },
   },
@@ -50,16 +51,6 @@ test('shows required asterisk for name field', () => {
   expect(screen.getByText('*')).toBeInTheDocument();
 });
 
-test('disables inputs when loading', () => {
-  render(
-    <Form>
-      <BasicInfoSection {...defaultProps} isLoading />
-    </Form>,
-  );
-
-  expect(screen.getByTestId('dashboard-title-input')).toBeDisabled();
-});
-
 test('shows error message when name is empty and has validation errors', () => 
{
   const mockForm = {
     getFieldValue: jest.fn(field => (field === 'title' ? '' : 'test')),
diff --git 
a/superset-frontend/src/dashboard/components/PropertiesModal/sections/BasicInfoSection.tsx
 
b/superset-frontend/src/dashboard/components/PropertiesModal/sections/BasicInfoSection.tsx
index cd11e71647..43047725be 100644
--- 
a/superset-frontend/src/dashboard/components/PropertiesModal/sections/BasicInfoSection.tsx
+++ 
b/superset-frontend/src/dashboard/components/PropertiesModal/sections/BasicInfoSection.tsx
@@ -23,62 +23,59 @@ import { ValidationObject } from 
'src/components/Modal/useModalValidation';
 
 interface BasicInfoSectionProps {
   form: FormInstance;
-  isLoading: boolean;
   validationStatus: ValidationObject;
 }
 
 const BasicInfoSection = ({
   form,
-  isLoading,
   validationStatus,
-}: BasicInfoSectionProps) => (
-  <>
-    <ModalFormField
-      label={t('Name')}
-      required
-      testId="dashboard-name-field"
-      error={
-        validationStatus.basic?.hasErrors &&
-        (!form.getFieldValue('title') ||
-          form.getFieldValue('title').trim().length === 0)
-          ? t('Dashboard name is required')
-          : undefined
-      }
-    >
-      <FormItem
-        name="title"
-        noStyle
-        rules={[
-          {
-            required: true,
-            message: t('Dashboard name is required'),
-            whitespace: true,
-          },
-        ]}
+}: BasicInfoSectionProps) => {
+  const titleValue = form.getFieldValue('title');
+  const hasError =
+    validationStatus.basic?.hasErrors &&
+    (!titleValue || titleValue.trim().length === 0);
+
+  return (
+    <>
+      <ModalFormField
+        label={t('Name')}
+        required
+        testId="dashboard-name-field"
+        error={hasError ? t('Dashboard name is required') : undefined}
+      >
+        <FormItem
+          name="title"
+          noStyle
+          rules={[
+            {
+              required: true,
+              message: t('Dashboard name is required'),
+              whitespace: true,
+            },
+          ]}
+        >
+          <Input
+            placeholder={t('The display name of your dashboard')}
+            data-test="dashboard-title-input"
+            type="text"
+          />
+        </FormItem>
+      </ModalFormField>
+      <ModalFormField
+        label={t('URL Slug')}
+        testId="dashboard-slug-field"
+        bottomSpacing={false}
       >
-        <Input
-          placeholder={t('The display name of your dashboard')}
-          data-test="dashboard-title-input"
-          type="text"
-          disabled={isLoading}
-        />
-      </FormItem>
-    </ModalFormField>
-    <ModalFormField
-      label={t('URL Slug')}
-      testId="dashboard-slug-field"
-      bottomSpacing={false}
-    >
-      <FormItem name="slug" noStyle>
-        <Input
-          placeholder={t('A readable URL for your dashboard')}
-          data-test="dashboard-slug-input"
-          type="text"
-          disabled={isLoading}
-        />
-      </FormItem>
-    </ModalFormField>
-  </>
-);
+        <FormItem name="slug" noStyle>
+          <Input
+            placeholder={t('A readable URL for your dashboard')}
+            data-test="dashboard-slug-input"
+            type="text"
+          />
+        </FormItem>
+      </ModalFormField>
+    </>
+  );
+};
 
 export default BasicInfoSection;

Reply via email to