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

jli 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 6a61baf5be8 fix(alerts): show friendly filter names in report edit 
modal (#38054)
6a61baf5be8 is described below

commit 6a61baf5be818821d3ce110fa8cdc5c459132e34
Author: Joe Li <[email protected]>
AuthorDate: Thu Feb 19 10:33:33 2026 -0800

    fix(alerts): show friendly filter names in report edit modal (#38054)
    
    Co-authored-by: Claude Opus 4.6 <[email protected]>
---
 .../src/features/alerts/AlertReportModal.test.tsx  | 284 +++++++++++++++++++--
 .../src/features/alerts/AlertReportModal.tsx       |   7 +
 2 files changed, 266 insertions(+), 25 deletions(-)

diff --git a/superset-frontend/src/features/alerts/AlertReportModal.test.tsx 
b/superset-frontend/src/features/alerts/AlertReportModal.test.tsx
index 91adaa3eeb4..585fd7c2bf3 100644
--- a/superset-frontend/src/features/alerts/AlertReportModal.test.tsx
+++ b/superset-frontend/src/features/alerts/AlertReportModal.test.tsx
@@ -18,6 +18,7 @@
  */
 import fetchMock from 'fetch-mock';
 import {
+  act,
   render,
   screen,
   userEvent,
@@ -104,9 +105,74 @@ const generateMockPayload = (dashboard = true) => {
 // mocking resource endpoints
 const FETCH_DASHBOARD_ENDPOINT = 'glob:*/api/v1/report/1';
 const FETCH_CHART_ENDPOINT = 'glob:*/api/v1/report/2';
+const FETCH_REPORT_WITH_FILTERS_ENDPOINT = 'glob:*/api/v1/report/3';
+const FETCH_REPORT_NO_FILTER_NAME_ENDPOINT = 'glob:*/api/v1/report/4';
+const FETCH_REPORT_OVERWRITE_ENDPOINT = 'glob:*/api/v1/report/5';
 
 fetchMock.get(FETCH_DASHBOARD_ENDPOINT, { result: generateMockPayload(true) });
 fetchMock.get(FETCH_CHART_ENDPOINT, { result: generateMockPayload(false) });
+fetchMock.get(FETCH_REPORT_WITH_FILTERS_ENDPOINT, {
+  result: {
+    ...generateMockPayload(true),
+    id: 3,
+    type: 'Report',
+    extra: {
+      dashboard: {
+        nativeFilters: [
+          {
+            nativeFilterId: 'NATIVE_FILTER-abc123',
+            filterName: 'Country',
+            filterType: 'filter_select',
+            columnName: 'country',
+            columnLabel: 'Country',
+            filterValues: ['USA'],
+          },
+        ],
+      },
+    },
+  },
+});
+fetchMock.get(FETCH_REPORT_NO_FILTER_NAME_ENDPOINT, {
+  result: {
+    ...generateMockPayload(true),
+    id: 4,
+    type: 'Report',
+    extra: {
+      dashboard: {
+        nativeFilters: [
+          {
+            nativeFilterId: 'NATIVE_FILTER-xyz789',
+            filterType: 'filter_select',
+            columnName: 'region',
+            columnLabel: 'Region',
+            filterValues: ['West'],
+          },
+        ],
+      },
+    },
+  },
+});
+fetchMock.get(FETCH_REPORT_OVERWRITE_ENDPOINT, {
+  result: {
+    ...generateMockPayload(true),
+    id: 5,
+    type: 'Report',
+    extra: {
+      dashboard: {
+        nativeFilters: [
+          {
+            nativeFilterId: 'NATIVE_FILTER-abc123',
+            filterName: 'Country',
+            filterType: 'filter_select',
+            columnName: 'country',
+            columnLabel: 'Country',
+            filterValues: ['USA'],
+          },
+        ],
+      },
+    },
+  },
+});
 
 // Related mocks
 const ownersEndpoint = 'glob:*/api/v1/alert/related/owners?*';
@@ -130,6 +196,38 @@ fetchMock.get(
   { name: tabsEndpoint },
 );
 
+// Restore the default tabs route and remove any test-specific overrides.
+// Called in afterEach so cleanup runs even when a test fails mid-way.
+const restoreDefaultTabsRoute = () => {
+  for (const name of [
+    'clear-icon-tabs',
+    'clear-icon-chart-data',
+    'deferred-tabs',
+    'overwrite-chart-data',
+  ]) {
+    try {
+      fetchMock.removeRoute(name);
+    } catch {
+      // route may not exist if the test that adds it didn't run
+    }
+  }
+  // Re-add the default empty tabs route if it was replaced
+  try {
+    fetchMock.removeRoute(tabsEndpoint);
+  } catch {
+    // already removed
+  }
+  fetchMock.get(
+    tabsEndpoint,
+    { result: { all_tabs: {}, tab_tree: [] } },
+    { name: tabsEndpoint },
+  );
+};
+
+afterEach(() => {
+  restoreDefaultTabsRoute();
+});
+
 // Create a valid alert with all required fields entered for validation check
 
 // @ts-expect-error will add id in factory function
@@ -713,34 +811,44 @@ test('filter reappears in dropdown after clearing with X 
icon', async () => {
   const chartDataEndpoint = 'glob:*/api/v1/chart/data*';
 
   fetchMock.removeRoute(tabsEndpoint);
-  fetchMock.get(tabsEndpoint, {
-    result: {
-      all_tabs: { tab1: 'Tab 1' },
-      tab_tree: [{ title: 'Tab 1', value: 'tab1' }],
-      native_filters: {
-        all: [
-          {
-            id: 'NATIVE_FILTER-test1',
-            name: 'Test Filter 1',
-            filterType: 'filter_select',
-            targets: [{ column: { name: 'test_column_1' } }],
-            adhoc_filters: [],
-          },
-        ],
-        tab1: [
-          {
-            id: 'NATIVE_FILTER-test2',
-            name: 'Test Filter 2',
-            filterType: 'filter_select',
-            targets: [{ column: { name: 'test_column_2' } }],
-            adhoc_filters: [],
-          },
-        ],
+  fetchMock.get(
+    tabsEndpoint,
+    {
+      result: {
+        all_tabs: { tab1: 'Tab 1' },
+        tab_tree: [{ title: 'Tab 1', value: 'tab1' }],
+        native_filters: {
+          all: [
+            {
+              id: 'NATIVE_FILTER-test1',
+              name: 'Test Filter 1',
+              filterType: 'filter_select',
+              targets: [{ column: { name: 'test_column_1' } }],
+              adhoc_filters: [],
+            },
+          ],
+          tab1: [
+            {
+              id: 'NATIVE_FILTER-test2',
+              name: 'Test Filter 2',
+              filterType: 'filter_select',
+              targets: [{ column: { name: 'test_column_2' } }],
+              adhoc_filters: [],
+            },
+          ],
+        },
       },
     },
-  });
+    { name: 'clear-icon-tabs' },
+  );
 
-  fetchMock.post(chartDataEndpoint, { result: [{ data: [] }] });
+  fetchMock.post(
+    chartDataEndpoint,
+    { result: [{ data: [] }] },
+    {
+      name: 'clear-icon-chart-data',
+    },
+  );
 
   render(<AlertReportModal {...generateMockedProps(true, true)} />, {
     useRedux: true,
@@ -793,3 +901,129 @@ test('filter reappears in dropdown after clearing with X 
icon', async () => {
     ).toBeInTheDocument();
   });
 });
+
+test('edit mode shows friendly filter names instead of raw IDs', async () => {
+  const props = generateMockedProps(true, true);
+  const editProps = {
+    ...props,
+    alert: { ...validAlert, id: 3 },
+  };
+
+  render(<AlertReportModal {...editProps} />, {
+    useRedux: true,
+  });
+
+  userEvent.click(screen.getByTestId('contents-panel'));
+
+  await waitFor(() => {
+    const selectionItem = document.querySelector(
+      '.ant-select-selection-item[title="Country"]',
+    );
+    expect(selectionItem).toBeInTheDocument();
+  });
+
+  expect(
+    document.querySelector(
+      '.ant-select-selection-item[title="NATIVE_FILTER-abc123"]',
+    ),
+  ).not.toBeInTheDocument();
+});
+
+test('edit mode falls back to raw ID when filterName is missing', async () => {
+  const props = generateMockedProps(true, true);
+  const editProps = {
+    ...props,
+    alert: { ...validAlert, id: 4 },
+  };
+
+  render(<AlertReportModal {...editProps} />, {
+    useRedux: true,
+  });
+
+  userEvent.click(screen.getByTestId('contents-panel'));
+
+  await waitFor(() => {
+    const selectionItem = document.querySelector(
+      '.ant-select-selection-item[title="NATIVE_FILTER-xyz789"]',
+    );
+    expect(selectionItem).toBeInTheDocument();
+  });
+});
+
+test('tabs metadata overwrites seeded filter options', async () => {
+  const chartDataEndpoint = 'glob:*/api/v1/chart/data*';
+
+  // Deferred promise to control when the tabs response resolves
+  let resolveTabsResponse!: (value: unknown) => void;
+  const deferredTabs = new Promise(resolve => {
+    resolveTabsResponse = resolve;
+  });
+
+  const tabsResult = {
+    result: {
+      all_tabs: { tab1: 'Tab 1' },
+      tab_tree: [{ title: 'Tab 1', value: 'tab1' }],
+      native_filters: {
+        all: [
+          {
+            id: 'NATIVE_FILTER-abc123',
+            name: 'Country (All Filters)',
+            filterType: 'filter_select',
+            targets: [{ column: { name: 'country' }, datasetId: 1 }],
+            adhoc_filters: [],
+          },
+        ],
+        tab1: [],
+      },
+    },
+  };
+
+  // Replace only the tabs route with a deferred version
+  fetchMock.removeRoute(tabsEndpoint);
+  fetchMock.get(tabsEndpoint, () => deferredTabs.then(() => tabsResult), {
+    name: 'deferred-tabs',
+  });
+  fetchMock.post(
+    chartDataEndpoint,
+    { result: [{ data: [] }] },
+    {
+      name: 'overwrite-chart-data',
+    },
+  );
+
+  const props = generateMockedProps(true, true);
+  const editProps = {
+    ...props,
+    alert: { ...validAlert, id: 5 },
+  };
+
+  render(<AlertReportModal {...editProps} />, {
+    useRedux: true,
+  });
+
+  userEvent.click(screen.getByTestId('contents-panel'));
+
+  // Seeded label from saved data appears before tabs respond
+  const filterSelect = screen.getByRole('combobox', {
+    name: /select filter/i,
+  });
+  const selectContainer = filterSelect.closest('.ant-select') as HTMLElement;
+  await waitFor(() => {
+    expect(within(selectContainer).getByTitle('Country')).toBeInTheDocument();
+  });
+
+  // Resolve the deferred tabs response
+  await act(async () => {
+    resolveTabsResponse(undefined);
+  });
+
+  // Tabs metadata overwrites the seeded label
+  await waitFor(() => {
+    expect(
+      within(selectContainer).getByTitle('Country (All Filters)'),
+    ).toBeInTheDocument();
+  });
+  expect(
+    within(selectContainer).queryByTitle('Country'),
+  ).not.toBeInTheDocument();
+});
diff --git a/superset-frontend/src/features/alerts/AlertReportModal.tsx 
b/superset-frontend/src/features/alerts/AlertReportModal.tsx
index 3974db42e33..fa25c7c75fd 100644
--- a/superset-frontend/src/features/alerts/AlertReportModal.tsx
+++ b/superset-frontend/src/features/alerts/AlertReportModal.tsx
@@ -1897,6 +1897,13 @@ const AlertReportModal: 
FunctionComponent<AlertReportModalProps> = ({
       if (resource.extra?.dashboard?.nativeFilters) {
         const filters = resource.extra.dashboard.nativeFilters;
         setNativeFilterData(filters);
+        // Seed options from saved data so names display while dashboard 
metadata loads
+        const savedOptions = filters
+          .filter(f => f.nativeFilterId && f.filterName)
+          .map(f => ({ value: f.nativeFilterId!, label: f.filterName! }));
+        if (savedOptions.length > 0) {
+          setNativeFilterOptions(savedOptions);
+        }
       }
 
       // Add notification settings

Reply via email to