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

enzomartellucci 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 fe5d5fdae6 fix(chart-creation): use exact match when loading dataset 
from URL parameter (#36831)
fe5d5fdae6 is described below

commit fe5d5fdae6981e3044f70cb72a9b04fb93e8cf05
Author: Enzo Martellucci <[email protected]>
AuthorDate: Wed Dec 31 13:50:03 2025 +0100

    fix(chart-creation): use exact match when loading dataset from URL 
parameter (#36831)
---
 .../datasets/AddDataset/Footer/Footer.test.tsx     |   1 +
 .../features/datasets/AddDataset/Footer/index.tsx  |  10 +-
 .../src/pages/ChartCreation/ChartCreation.test.tsx | 227 +++++++++++++++++++++
 .../src/pages/ChartCreation/index.tsx              |  39 +++-
 4 files changed, 265 insertions(+), 12 deletions(-)

diff --git 
a/superset-frontend/src/features/datasets/AddDataset/Footer/Footer.test.tsx 
b/superset-frontend/src/features/datasets/AddDataset/Footer/Footer.test.tsx
index a89c5fd6f6..b0714fc90c 100644
--- a/superset-frontend/src/features/datasets/AddDataset/Footer/Footer.test.tsx
+++ b/superset-frontend/src/features/datasets/AddDataset/Footer/Footer.test.tsx
@@ -37,6 +37,7 @@ const mockCreateResource = jest.fn();
 jest.mock('src/views/CRUD/hooks', () => ({
   useSingleViewResource: () => ({
     createResource: mockCreateResource,
+    state: { loading: false },
   }),
   getDatabaseDocumentationLinks: () => ({
     support:
diff --git 
a/superset-frontend/src/features/datasets/AddDataset/Footer/index.tsx 
b/superset-frontend/src/features/datasets/AddDataset/Footer/index.tsx
index 2b23b728ee..9e41d8876a 100644
--- a/superset-frontend/src/features/datasets/AddDataset/Footer/index.tsx
+++ b/superset-frontend/src/features/datasets/AddDataset/Footer/index.tsx
@@ -63,11 +63,10 @@ function Footer({
 }: FooterProps) {
   const history = useHistory();
   const theme = useTheme();
-  const { createResource } = useSingleViewResource<Partial<DatasetObject>>(
-    'dataset',
-    t('dataset'),
-    addDangerToast,
-  );
+  const { createResource, state } = useSingleViewResource<
+    Partial<DatasetObject>
+  >('dataset', t('dataset'), addDangerToast);
+  const { loading } = state;
 
   const createLogAction = (dataset: Partial<DatasetObject>) => {
     let totalCount = 0;
@@ -149,6 +148,7 @@ function Footer({
       <DropdownButton
         type="primary"
         disabled={disabledCheck}
+        loading={loading}
         tooltip={!datasetObject?.table_name ? tooltipText : undefined}
         onClick={() => onSave(true)}
         popupRender={() => dropdownMenu}
diff --git a/superset-frontend/src/pages/ChartCreation/ChartCreation.test.tsx 
b/superset-frontend/src/pages/ChartCreation/ChartCreation.test.tsx
index 5caea4df88..e67f829bab 100644
--- a/superset-frontend/src/pages/ChartCreation/ChartCreation.test.tsx
+++ b/superset-frontend/src/pages/ChartCreation/ChartCreation.test.tsx
@@ -193,3 +193,230 @@ test('double-click viz type submits with formatted URL if 
datasource is selected
   const formattedUrl = '/explore/?viz_type=table&datasource=table_1__table';
   expect(history.push).toHaveBeenCalledWith(formattedUrl);
 });
+
+test('dropdown displays matching datasets when user types a search term', 
async () => {
+  fetchMock.reset();
+  fetchMock.get(/\/api\/v1\/dataset\/\?q=.*/, {
+    body: {
+      result: [
+        {
+          id: 'flights_1',
+          table_name: 'flights',
+          datasource_type: 'table',
+          database: { database_name: 'examples' },
+          schema: 'public',
+        },
+        {
+          id: 'flights_delayed_2',
+          table_name: 'flights_delayed',
+          datasource_type: 'table',
+          database: { database_name: 'examples' },
+          schema: 'public',
+        },
+      ],
+      count: 2,
+    },
+    status: 200,
+  });
+
+  await renderComponent();
+
+  const datasourceSelect = await screen.findByRole('combobox', {
+    name: 'Dataset',
+  });
+  userEvent.click(datasourceSelect);
+  userEvent.type(datasourceSelect, 'flight');
+
+  await screen.findByText('flights');
+  expect(screen.getByText('flights_delayed')).toBeInTheDocument();
+});
+
+test('handles special characters in dataset name from URL parameter', async () 
=> {
+  fetchMock.reset();
+  fetchMock.get(/\/api\/v1\/dataset\/\?q=.*/, {
+    body: {
+      result: [
+        {
+          id: 'special_1',
+          table_name: 'flightsÆ test',
+          datasource_type: 'table',
+          database: { database_name: 'test_db' },
+          schema: 'public',
+        },
+      ],
+      count: 1,
+    },
+    status: 200,
+  });
+
+  const originalLocation = window.location;
+  Object.defineProperty(window, 'location', {
+    value: {
+      ...originalLocation,
+      search: '?dataset=flights%C3%86%20test',
+    },
+    writable: true,
+  });
+
+  await renderComponent();
+
+  await screen.findByText('flightsÆ test');
+
+  Object.defineProperty(window, 'location', {
+    value: originalLocation,
+    writable: true,
+  });
+});
+
+test('pre-selects the dataset from URL parameter and shows it in dropdown', 
async () => {
+  fetchMock.reset();
+  fetchMock.get(/\/api\/v1\/dataset\/\?q=.*/, {
+    body: {
+      result: [
+        {
+          id: 'flights_123',
+          table_name: 'flights',
+          datasource_type: 'table',
+          database: { database_name: 'examples' },
+          schema: 'public',
+        },
+      ],
+      count: 1,
+    },
+    status: 200,
+  });
+
+  const originalLocation = window.location;
+  Object.defineProperty(window, 'location', {
+    value: { ...originalLocation, search: '?dataset=flights' },
+    writable: true,
+  });
+
+  await renderComponent();
+
+  await screen.findByText('flights');
+
+  Object.defineProperty(window, 'location', {
+    value: originalLocation,
+    writable: true,
+  });
+});
+
+test('shows loading spinner when dataset parameter is present in URL', async 
() => {
+  fetchMock.reset();
+  let resolveRequest: (value: unknown) => void;
+  const requestPromise = new Promise(resolve => {
+    resolveRequest = resolve;
+  });
+
+  fetchMock.get(/\/api\/v1\/dataset\/\?q=.*/, () =>
+    requestPromise.then(() => ({
+      body: {
+        result: [
+          {
+            id: 'flights_1',
+            table_name: 'flights',
+            datasource_type: 'table',
+            database: { database_name: 'examples' },
+            schema: 'public',
+          },
+        ],
+        count: 1,
+      },
+      status: 200,
+    })),
+  );
+
+  const originalLocation = window.location;
+  Object.defineProperty(window, 'location', {
+    value: { ...originalLocation, search: '?dataset=flights' },
+    writable: true,
+  });
+
+  render(
+    <ChartCreation
+      user={mockUser}
+      addSuccessToast={() => null}
+      theme={supersetTheme}
+      {...routeProps}
+    />,
+    {
+      useRedux: true,
+      useRouter: true,
+    },
+  );
+
+  expect(screen.getByRole('status')).toBeInTheDocument();
+
+  resolveRequest!(null);
+
+  await waitFor(() => {
+    expect(screen.queryByRole('status')).not.toBeInTheDocument();
+  });
+
+  Object.defineProperty(window, 'location', {
+    value: originalLocation,
+    writable: true,
+  });
+});
+
+test('shows only exact match when loading dataset from URL, not partial 
matches', async () => {
+  fetchMock.reset();
+  fetchMock.get(/\/api\/v1\/dataset\/\?q=.*/, url => {
+    if (url.includes('opr:eq')) {
+      return {
+        body: {
+          result: [
+            {
+              id: 'flights_1',
+              table_name: 'flights',
+              datasource_type: 'table',
+              database: { database_name: 'examples' },
+              schema: 'public',
+            },
+          ],
+          count: 1,
+        },
+        status: 200,
+      };
+    }
+    return {
+      body: {
+        result: [
+          {
+            id: 'flights_1',
+            table_name: 'flights',
+            datasource_type: 'table',
+            database: { database_name: 'examples' },
+            schema: 'public',
+          },
+          {
+            id: 'flights_delayed_2',
+            table_name: 'flights_delayed',
+            datasource_type: 'table',
+            database: { database_name: 'examples' },
+            schema: 'public',
+          },
+        ],
+        count: 2,
+      },
+      status: 200,
+    };
+  });
+
+  const originalLocation = window.location;
+  Object.defineProperty(window, 'location', {
+    value: { ...originalLocation, search: '?dataset=flights' },
+    writable: true,
+  });
+
+  await renderComponent();
+
+  await screen.findByText('flights');
+  expect(screen.queryByText('flights_delayed')).not.toBeInTheDocument();
+
+  Object.defineProperty(window, 'location', {
+    value: originalLocation,
+    writable: true,
+  });
+});
diff --git a/superset-frontend/src/pages/ChartCreation/index.tsx 
b/superset-frontend/src/pages/ChartCreation/index.tsx
index 118c78c3b7..3da0d7737e 100644
--- a/superset-frontend/src/pages/ChartCreation/index.tsx
+++ b/superset-frontend/src/pages/ChartCreation/index.tsx
@@ -24,7 +24,12 @@ import { withTheme, Theme } from '@emotion/react';
 import { getUrlParam } from 'src/utils/urlUtils';
 import { FilterPlugins, URL_PARAMS } from 'src/constants';
 import { Link, withRouter, RouteComponentProps } from 'react-router-dom';
-import { AsyncSelect, Button, Steps } from '@superset-ui/core/components';
+import {
+  AsyncSelect,
+  Button,
+  Loading,
+  Steps,
+} from '@superset-ui/core/components';
 import withToasts from 'src/components/MessageToasts/withToasts';
 
 import VizTypeGallery, {
@@ -50,6 +55,7 @@ export type ChartCreationState = {
   datasetName?: string | string[] | null;
   vizType: string | null;
   canCreateDataset: boolean;
+  loading: boolean;
 };
 
 const ESTIMATED_NAV_HEIGHT = 56;
@@ -172,6 +178,9 @@ export class ChartCreation extends PureComponent<
 > {
   constructor(props: ChartCreationProps) {
     super(props);
+    const hasDatasetParam = new URLSearchParams(window.location.search).has(
+      'dataset',
+    );
     this.state = {
       vizType: null,
       canCreateDataset: findPermission(
@@ -179,6 +188,7 @@ export class ChartCreation extends PureComponent<
         'Dataset',
         props.user.roles,
       ),
+      loading: hasDatasetParam,
     };
 
     this.changeDatasource = this.changeDatasource.bind(this);
@@ -191,10 +201,14 @@ export class ChartCreation extends PureComponent<
   componentDidMount() {
     const params = new URLSearchParams(window.location.search).get('dataset');
     if (params) {
-      this.loadDatasources(params, 0, 1).then(r => {
-        const datasource = r.data[0];
-        this.setState({ datasource });
-      });
+      this.loadDatasources(params, 0, 1, true)
+        .then(r => {
+          const datasource = r.data[0];
+          this.setState({ datasource, loading: false });
+        })
+        .catch(() => {
+          this.setState({ loading: false });
+        });
       this.props.addSuccessToast(t('The dataset has been saved'));
     }
   }
@@ -230,7 +244,12 @@ export class ChartCreation extends PureComponent<
     }
   }
 
-  loadDatasources(search: string, page: number, pageSize: number) {
+  loadDatasources(
+    search: string,
+    page: number,
+    pageSize: number,
+    exactMatch = false,
+  ) {
     const query = rison.encode({
       columns: [
         'id',
@@ -239,7 +258,9 @@ export class ChartCreation extends PureComponent<
         'database.database_name',
         'schema',
       ],
-      filters: [{ col: 'table_name', opr: 'ct', value: search }],
+      filters: [
+        { col: 'table_name', opr: exactMatch ? 'eq' : 'ct', value: search },
+      ],
       page,
       page_size: pageSize,
       order_column: 'table_name',
@@ -301,6 +322,10 @@ export class ChartCreation extends PureComponent<
       </span>
     );
 
+    if (this.state.loading) {
+      return <Loading />;
+    }
+
     return (
       <StyledContainer>
         <h3>{t('Create a new chart')}</h3>

Reply via email to