diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/DatasetSelect.test.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/DatasetSelect.test.tsx
new file mode 100644
index 000000000000..6e4afdb1398f
--- /dev/null
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/DatasetSelect.test.tsx
@@ -0,0 +1,203 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import {
+ render,
+ screen,
+ userEvent,
+ waitFor,
+ within,
+} from 'spec/helpers/testing-library';
+import fetchMock from 'fetch-mock';
+import DatasetSelect from './DatasetSelect';
+
+const DATASETS = [
+ {
+ id: 1,
+ table_name: 'birth_names',
+ database: { database_name: 'examples' },
+ schema: 'public',
+ },
+ {
+ id: 2,
+ table_name: 'energy_usage',
+ database: { database_name: 'examples' },
+ schema: 'public',
+ },
+ {
+ id: 3,
+ table_name: 'flights',
+ database: { database_name: 'examples' },
+ schema: 'main',
+ },
+ {
+ id: 4,
+ table_name: 'customers',
+ database: { database_name: 'sales_db' },
+ schema: 'dbo',
+ },
+];
+
+const mockOnChange = jest.fn();
+
+afterEach(() => {
+ fetchMock.restore();
+ jest.clearAllMocks();
+});
+
+const getSelect = () => screen.getByRole('combobox', { name: /dataset/i });
+
+const openSelect = () => {
+ userEvent.click(getSelect());
+};
+
+const typeIntoSelect = async (text: string) => {
+ const select = getSelect();
+ userEvent.clear(select);
+ return userEvent.type(select, text, { delay: 10 });
+};
+
+const findOption = (text: string) =>
+ waitFor(() => {
+ // eslint-disable-next-line testing-library/no-node-access
+ const virtualList = document.querySelector('.rc-virtual-list');
+ if (!virtualList) {
+ throw new Error('Virtual list not found');
+ }
+ return within(virtualList as HTMLElement).getByText(text);
+ });
+
+test('renders the dataset select component', () => {
+ fetchMock.get('glob:*/api/v1/dataset/*', {
+ result: [],
+ count: 0,
+ });
+
+ render();
+ expect(getSelect()).toBeInTheDocument();
+});
+
+test('loads and displays datasets when opened', async () => {
+ fetchMock.get('glob:*/api/v1/dataset/*', {
+ result: DATASETS,
+ count: DATASETS.length,
+ });
+
+ render();
+ openSelect();
+
+ expect(await findOption('birth_names')).toBeInTheDocument();
+ expect(await findOption('energy_usage')).toBeInTheDocument();
+});
+
+test('searches for datasets by table_name locally in loaded options', async () => {
+ fetchMock.get('glob:*/api/v1/dataset/*', {
+ result: DATASETS,
+ count: DATASETS.length,
+ });
+
+ render();
+ openSelect();
+
+ // Wait for all options to load
+ await findOption('flights');
+
+ // Now type to filter locally
+ await typeIntoSelect('flight');
+
+ // Should filter to show only flights
+ expect(await findOption('flights')).toBeInTheDocument();
+});
+
+test('uses optionFilterProps to enable table_name filtering', async () => {
+ // This test verifies that the optionFilterProps includes table_name
+ // which enables client-side filtering on that field
+ fetchMock.get('glob:*/api/v1/dataset/*', {
+ result: DATASETS,
+ count: DATASETS.length,
+ });
+
+ render();
+ openSelect();
+
+ // Load all options
+ await findOption('energy_usage');
+
+ // Search by table_name substring
+ await typeIntoSelect('energy');
+
+ // Should find the dataset by table_name
+ expect(await findOption('energy_usage')).toBeInTheDocument();
+});
+
+test('filters options case-insensitively on table_name', async () => {
+ fetchMock.get('glob:*/api/v1/dataset/*', {
+ result: DATASETS,
+ count: DATASETS.length,
+ });
+
+ render();
+ openSelect();
+
+ // Load options
+ await findOption('birth_names');
+
+ // Type in uppercase
+ await typeIntoSelect('BIRTH');
+
+ // Should still find it (case insensitive)
+ expect(await findOption('birth_names')).toBeInTheDocument();
+});
+
+test('calls onChange when a dataset is selected', async () => {
+ fetchMock.get('glob:*/api/v1/dataset/*', {
+ result: DATASETS,
+ count: DATASETS.length,
+ });
+
+ render();
+ openSelect();
+
+ const option = await findOption('birth_names');
+ userEvent.click(option);
+
+ await waitFor(() => {
+ expect(mockOnChange).toHaveBeenCalled();
+ const callArg = mockOnChange.mock.calls[0][0];
+ expect(callArg).toEqual({ key: 1, label: expect.anything(), value: 1 });
+ });
+});
+
+test('includes table_name field in option data structure', async () => {
+ fetchMock.get('glob:*/api/v1/dataset/*', {
+ result: [DATASETS[0]],
+ count: 1,
+ });
+
+ render();
+ openSelect();
+
+ const option = await findOption('birth_names');
+ userEvent.click(option);
+
+ await waitFor(() => {
+ expect(mockOnChange).toHaveBeenCalled();
+ const callArg = mockOnChange.mock.calls[0][1];
+ expect(callArg).toHaveProperty('table_name', 'birth_names');
+ });
+});
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/DatasetSelect.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/DatasetSelect.tsx
index 41abcb10bea4..c675f7ee5d17 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/DatasetSelect.tsx
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/DatasetSelect.tsx
@@ -74,9 +74,12 @@ const DatasetSelect = ({
const list: {
label: string | ReactNode;
value: string | number;
+ table_name: string;
}[] = filteredResult.map((item: Dataset) => ({
+ ...item,
label: DatasetSelectLabel(item),
value: item.id,
+ table_name: item.table_name,
}));
return {
data: list,
@@ -99,6 +102,7 @@ const DatasetSelect = ({
value={value}
options={loadDatasetOptions}
onChange={onChange}
+ optionFilterProps={['table_name']}
notFoundContent={t('No compatible datasets found')}
placeholder={t('Select a dataset')}
/>
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal.test.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal.test.tsx
index a419cd79362b..9a8f2e060ab0 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal.test.tsx
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal.test.tsx
@@ -348,10 +348,14 @@ test('validates the pre-filter value', async () => {
jest.useRealTimers();
}
+ jest.runOnlyPendingTimers();
+ jest.useRealTimers();
+
+ // Wait for validation to complete after timer switch
await waitFor(() => {
expect(screen.getByText(PRE_FILTER_REQUIRED_REGEX)).toBeInTheDocument();
});
-});
+}, 50000); // Slow-running test, increase timeout to 50 seconds.
// eslint-disable-next-line jest/no-disabled-tests
test.skip("doesn't render time range pre-filter if there are no temporal columns in datasource", async () => {
diff --git a/superset-frontend/src/pages/ChartCreation/index.tsx b/superset-frontend/src/pages/ChartCreation/index.tsx
index 34eaee7ec06d..2650e73f3f9d 100644
--- a/superset-frontend/src/pages/ChartCreation/index.tsx
+++ b/superset-frontend/src/pages/ChartCreation/index.tsx
@@ -257,11 +257,12 @@ export class ChartCreation extends PureComponent<
id: number;
label: string | ReactNode;
value: string;
+ table_name: string;
}[] = response.json.result.map((item: Dataset) => ({
id: item.id,
value: `${item.id}__${item.datasource_type}`,
label: DatasetSelectLabel(item),
- customLabel: item.table_name,
+ table_name: item.table_name,
}));
return {
data: list,
@@ -320,7 +321,7 @@ export class ChartCreation extends PureComponent<
name="select-datasource"
onChange={this.changeDatasource}
options={this.loadDatasources}
- optionFilterProps={['id', 'customLabel']}
+ optionFilterProps={['id', 'table_name']}
placeholder={t('Choose a dataset')}
showSearch
value={this.state.datasource}