feat: Add Dashboard Filter Support for Alert Reports (#32196)

Co-authored-by: Elizabeth Thompson <eschutho@gmail.com>
Co-authored-by: Hugh A Miles II <hugh@Mac.home>
diff --git a/superset-frontend/packages/superset-ui-core/src/utils/featureFlags.ts b/superset-frontend/packages/superset-ui-core/src/utils/featureFlags.ts
index 010c0cb..fb3ec30 100644
--- a/superset-frontend/packages/superset-ui-core/src/utils/featureFlags.ts
+++ b/superset-frontend/packages/superset-ui-core/src/utils/featureFlags.ts
@@ -26,6 +26,7 @@
   AlertReports = 'ALERT_REPORTS',
   AlertReportTabs = 'ALERT_REPORT_TABS',
   AlertReportSlackV2 = 'ALERT_REPORT_SLACK_V2',
+  AlertReportsFilter = 'ALERT_REPORTS_FILTER',
   AllowFullCsvExport = 'ALLOW_FULL_CSV_EXPORT',
   AvoidColorsCollision = 'AVOID_COLORS_COLLISION',
   ChartPluginsExperimental = 'CHART_PLUGINS_EXPERIMENTAL',
diff --git a/superset-frontend/src/features/alerts/AlertReportModal.test.tsx b/superset-frontend/src/features/alerts/AlertReportModal.test.tsx
index dbd7eef..ecffcc4 100644
--- a/superset-frontend/src/features/alerts/AlertReportModal.test.tsx
+++ b/superset-frontend/src/features/alerts/AlertReportModal.test.tsx
@@ -452,7 +452,7 @@
   expect(
     screen.getByRole('combobox', { name: /dashboard/i }),
   ).toBeInTheDocument();
-  expect(screen.getByText(/select tab/i)).toBeInTheDocument();
+  expect(screen.getAllByText(/select tab/i)).toHaveLength(1);
 });
 
 test('changes to content options when chart is selected', async () => {
@@ -666,3 +666,15 @@
     screen.getAllByRole('combobox', { name: /delivery method/i }).length,
   ).toBe(1);
 });
+
+test('renders dashboard filter dropdowns', async () => {
+  render(<AlertReportModal {...generateMockedProps(true, true)} />, {
+    useRedux: true,
+  });
+
+  userEvent.click(screen.getByTestId('contents-panel'));
+  const filterOptionDropdown = screen.getByRole('combobox', {
+    name: /select filter/i,
+  });
+  expect(filterOptionDropdown).toBeInTheDocument();
+});
diff --git a/superset-frontend/src/features/alerts/AlertReportModal.tsx b/superset-frontend/src/features/alerts/AlertReportModal.tsx
index 4b1d76d..cd923b6 100644
--- a/superset-frontend/src/features/alerts/AlertReportModal.tsx
+++ b/superset-frontend/src/features/alerts/AlertReportModal.tsx
@@ -39,11 +39,16 @@
 } from '@superset-ui/core';
 import rison from 'rison';
 import { useSingleViewResource } from 'src/views/CRUD/hooks';
+import withToasts from 'src/components/MessageToasts/withToasts';
+import Owner from 'src/types/Owner';
+// import { Form as AntdForm } from 'src/components/Form';
+import { propertyComparator } from '@superset-ui/core/components/Select/utils';
 import {
   AsyncSelect,
   Checkbox,
   Collapse,
   CollapseLabelInModal,
+  Form as AntdForm,
   InfoTooltip,
   Input,
   InputNumber,
@@ -52,10 +57,8 @@
   TreeSelect,
   type CheckboxChangeEvent,
 } from '@superset-ui/core/components';
+
 import TimezoneSelector from '@superset-ui/core/components/TimezoneSelector';
-import { propertyComparator } from '@superset-ui/core/components/Select/utils';
-import withToasts from 'src/components/MessageToasts/withToasts';
-import Owner from 'src/types/Owner';
 import TextAreaControl from 'src/explore/components/controls/TextAreaControl';
 import { useCommonConf } from 'src/features/databases/state';
 import {
@@ -75,9 +78,14 @@
   TabNode,
   SelectValue,
   ContentType,
+  ExtraNativeFilter,
+  NativeFilterObject,
 } from 'src/features/alerts/types';
+import { StatusMessage } from 'src/filters/components/common';
 import { useSelector } from 'react-redux';
 import { UserWithPermissionsAndRoles } from 'src/types/bootstrapTypes';
+import { getChartDataRequest } from 'src/components/Chart/chartAction';
+import DateFilterControl from 'src/explore/components/controls/DateFilterControl';
 import { Icons } from '@superset-ui/core/components/Icons';
 import { StandardModal, ModalFormField } from 'src/components/Modal';
 import NumberInput from './components/NumberInput';
@@ -92,6 +100,14 @@
   VizType.PairedTTest,
 ];
 
+const StyledDivider = styled.span`
+  margin: 0 ${({ theme }) => theme.sizeUnit * 3}px;
+  color: ${({ theme }) => theme.colorSplit};
+  font-weight: ${({ theme }) => theme.fontWeightStrong};
+  font-size: ${({ theme }) => theme.fontSize}px;
+  align-content: center;
+`;
+
 export interface AlertReportModalProps {
   addSuccessToast: (msg: string) => void;
   addDangerToast: (msg: string) => void;
@@ -274,6 +290,94 @@
     textarea {
       flex: 1 1 auto;
     }
+
+    input[disabled] {
+      color: ${theme.colorTextDisabled};
+    }
+
+    textarea {
+      height: 300px;
+      resize: none;
+    }
+
+    input::placeholder,
+    textarea::placeholder {
+      color: ${theme.colorTextPlaceholder};
+    }
+
+    textarea,
+    input[type='text'],
+    input[type='number'] {
+      padding: ${theme.sizeUnit}px ${theme.sizeUnit * 2}px;
+      border-style: none;
+      border: 1px solid ${theme.colorBorder};
+      border-radius: ${theme.borderRadius}px;
+
+      &[name='description'] {
+        flex: 1 1 auto;
+      }
+    }
+
+    .input-label {
+      margin-left: 10px;
+    }
+
+    .filters {
+      margin: ${theme.sizeUnit * 3}px 0;
+
+      .filters-container {
+        display: flex;
+        margin: ${theme.sizeUnit * 2}px 0;
+      }
+
+      .filters-dash-container {
+        display: flex;
+        flex-direction: column;
+        max-width: 174px;
+        flex: 1;
+        margin-right: ${theme.sizeUnit * 4}px;
+
+        .control-label {
+          flex: 1;
+          margin-bottom: ${theme.sizeUnit * 2}px;
+
+          .label-with-tooltip {
+            margin-right: ${theme.sizeUnit * 2}px;
+          }
+        }
+      }
+
+      .filters-dash-select {
+        flex: 1;
+      }
+
+      .filters-dashvalue-container {
+        display: flex;
+        flex-direction: column;
+        flex: 1;
+      }
+
+      .filters-delete {
+        display: flex;
+        margin-top: ${theme.sizeUnit * 8}px;
+        margin-left: ${theme.sizeUnit * 4}px;
+      }
+
+      .filters-trashcan {
+        width: ${theme.sizeUnit * 10}px;
+        display: 'flex';
+        color: ${theme.colorIcon};
+      }
+      .filters-add-container {
+        flex: '.25';
+        padding: '${theme.sizeUnit * 3} 0';
+
+        .filters-add-btn {
+          padding: ${theme.sizeUnit * 2}px;
+          color: ${theme.colorWhite};
+        }
+      }
+    }
   `}
 `;
 
@@ -334,6 +438,8 @@
   ERROR_TOOLTIP_MESSAGE: t(
     'Not all required fields are complete. Please provide the following:',
   ),
+  NATIVE_FILTER_COLUMN_ERROR_TEXT: t('Native filter column is required'),
+  NATIVE_FILTER_NO_VALUES_ERROR_TEXT: t('Native filter values has no values'),
 };
 
 const NotificationMethodAdd: FunctionComponent<NotificationMethodAddProps> = ({
@@ -400,6 +506,25 @@
   const [dashboardOptions, setDashboardOptions] = useState<MetaObject[]>([]);
   const [chartOptions, setChartOptions] = useState<MetaObject[]>([]);
   const [tabOptions, setTabOptions] = useState<TabNode[]>([]);
+  const [nativeFilterOptions, setNativeFilterOptions] = useState<
+    {
+      value: string;
+      label: string;
+    }[]
+  >([]);
+  const [tabNativeFilters, setTabNativeFilters] = useState<object>({});
+  const [nativeFilterData, setNativeFilterData] = useState<ExtraNativeFilter[]>(
+    [
+      {
+        nativeFilterId: null,
+        filterName: '',
+        filterType: '',
+        columnLabel: '',
+        columnName: '',
+        filterValues: [],
+      },
+    ],
+  );
 
   // Validation
   const [validationStatus, setValidationStatus] = useState<ValidationObject>({
@@ -452,6 +577,7 @@
   const formatOptionEnabled =
     isFeatureEnabled(FeatureFlag.AlertsAttachReports) || isReport;
   const tabsEnabled = isFeatureEnabled(FeatureFlag.AlertReportTabs);
+  const filtersEnabled = isFeatureEnabled(FeatureFlag.AlertReportsFilter);
 
   const [notificationAddState, setNotificationAddState] =
     useState<NotificationAddStatus>('active');
@@ -522,6 +648,116 @@
     grace_period: undefined,
   };
 
+  const fetchDashboardFilterValues = async (
+    dashboardId: number | string | undefined,
+    columnName: string,
+    datasetId: number | string,
+    vizType = 'filter_select',
+    adhocFilters = [],
+  ) => {
+    if (vizType === 'filter_time') {
+      return;
+    }
+
+    const filterValues = {
+      formData: {
+        datasource: `${datasetId}__table`,
+        groupby: [columnName],
+        metrics: ['count'],
+        row_limit: 1000,
+        showSearch: true,
+        viz_type: vizType,
+        type: 'NATIVE_FILTER',
+        dashboardId,
+        adhoc_filters: adhocFilters,
+      },
+      force: false,
+      ownState: {},
+    };
+
+    const data = await getChartDataRequest(filterValues).then(response => {
+      const rawData = response.json.result[0].data;
+      let filteredData = rawData;
+
+      if (vizType === 'filter_timecolumn') {
+        // filter for time columns types
+        filteredData = rawData.filter((item: any) => item.dtype === 2);
+      }
+
+      return filteredData.map((item: any) => {
+        if (vizType === 'filter_timegrain') {
+          return {
+            value: item.duration,
+            label: item.name,
+          };
+        }
+
+        if (vizType === 'filter_timecolumn') {
+          return {
+            value: item.column_name,
+            label: item.verbose_name || item.column_name,
+          };
+        }
+
+        return {
+          value: item[columnName],
+          label: item[columnName],
+        };
+      });
+    });
+
+    // eslint-disable-next-line consistent-return
+    return data;
+  };
+
+  const addNativeFilterOptions = (nativeFilters: NativeFilterObject[]) => {
+    nativeFilterData.map(nativeFilter => {
+      if (!nativeFilter.nativeFilterId) return;
+      const filter = nativeFilters.filter(
+        (f: any) => f.id === nativeFilter.nativeFilterId,
+      )[0];
+
+      const { datasetId } = filter.targets[0];
+      const filterName = filter.name;
+      const columnName = filter.targets[0].column?.name || filterName;
+      const dashboardId = currentAlert?.dashboard?.value;
+      const { filterType } = filter;
+
+      if (filterType === 'filter_time') {
+        return;
+      }
+
+      // eslint-disable-next-line consistent-return
+      return fetchDashboardFilterValues(
+        dashboardId,
+        columnName,
+        datasetId,
+        filterType,
+      ).then(optionFilterValues => {
+        setNativeFilterData(prev =>
+          prev.map(filter =>
+            filter.nativeFilterId === nativeFilter.nativeFilterId
+              ? {
+                  ...filter,
+                  filterType,
+                  filterName,
+                  optionFilterValues,
+                }
+              : filter,
+          ),
+        );
+      });
+    });
+  };
+
+  const filterNativeFilterOptions = () =>
+    nativeFilterOptions.filter(
+      option =>
+        !nativeFilterData.some(
+          filter => filter.nativeFilterId === option.value,
+        ),
+    );
+
   const updateNotificationSetting = (
     index: number,
     setting: NotificationSetting,
@@ -548,7 +784,6 @@
       setNotificationSettings(settings);
     }
   };
-
   const removeNotificationSetting = (index: number) => {
     const settings = notificationSettings.slice();
 
@@ -611,6 +846,37 @@
 
     const shouldEnableForceScreenshot =
       contentType === ContentType.Chart && !isReport;
+
+    if (currentAlert?.extra?.dashboard) {
+      // Filter out empty native filters (where both filter name and values are empty/null)
+      const validNativeFilters = nativeFilterData.filter(filter => {
+        const hasFilterName =
+          filter.filterName && filter.filterName.trim() !== '';
+        const hasFilterValues =
+          filter.filterValues && filter.filterValues.length > 0;
+        // Keep filter if it has either a name or values (or both)
+        return hasFilterName || hasFilterValues;
+      });
+
+      currentAlert.extra.dashboard.nativeFilters = validNativeFilters.map(
+        ({
+          columnName,
+          columnLabel,
+          nativeFilterId,
+          filterValues,
+          filterType,
+          filterName,
+        }) => ({
+          filterName,
+          filterType,
+          columnName,
+          columnLabel,
+          nativeFilterId,
+          filterValues,
+        }),
+      );
+    }
+
     const data: any = {
       ...currentAlert,
       type: isReport ? 'Report' : 'Alert',
@@ -771,7 +1037,11 @@
         endpoint: `/api/v1/dashboard/${dashboard.value}/tabs`,
       })
         .then(response => {
-          const { tab_tree: tabTree, all_tabs: allTabs } = response.json.result;
+          const {
+            tab_tree: tabTree,
+            all_tabs: allTabs,
+            native_filters: nativeFilters,
+          } = response.json.result;
           const allTabsWithOrder = tabTree.map(
             (tab: { value: string }) => tab.value,
           );
@@ -786,11 +1056,32 @@
           }
 
           setTabOptions(tabTree);
+          setTabNativeFilters(nativeFilters);
 
+          if (isEditMode && nativeFilters.all) {
+            // update options for all filters
+            addNativeFilterOptions(nativeFilters.all);
+            // Also set the available filter options for the add button
+            setNativeFilterOptions(
+              nativeFilters.all.map((filter: any) => ({
+                value: filter.id,
+                label: filter.name,
+              })),
+            );
+          }
           const anchor = currentAlert?.extra?.dashboard?.anchor;
           if (anchor) {
             try {
               const parsedAnchor = JSON.parse(anchor);
+              if (!Array.isArray(parsedAnchor)) {
+                // only show filters scoped to anchor
+                setNativeFilterOptions(
+                  nativeFilters[anchor].map((filter: any) => ({
+                    value: filter.id,
+                    label: filter.name,
+                  })),
+                );
+              }
               if (Array.isArray(parsedAnchor)) {
                 // Check if all elements in parsedAnchor list are in allTabs
                 const isValidSubset = parsedAnchor.every(tab => tab in allTabs);
@@ -805,9 +1096,16 @@
                 updateAnchorState(undefined);
               }
             }
+          } else if (nativeFilters.all) {
+            setNativeFilterOptions(
+              nativeFilters.all.map((filter: any) => ({
+                value: filter.id,
+                label: filter.name,
+              })),
+            );
           }
         })
-        .catch(() => {
+        .catch(e => {
           addDangerToast(t('There was an error retrieving dashboard tabs.'));
         });
     }
@@ -961,6 +1259,24 @@
     }
   };
 
+  const handleAddFilterField = () => {
+    setNativeFilterData([
+      ...nativeFilterData,
+      {
+        nativeFilterId: null,
+        columnLabel: '',
+        columnName: '',
+        filterValues: [],
+      },
+    ]);
+  };
+
+  const handleRemoveFilterField = (filterIdx: number) => {
+    const filters = nativeFilterData || [];
+    filters.splice(filterIdx, 1);
+    setNativeFilterData(filters);
+  };
+
   const onCustomWidthChange = (value: number | string | null | undefined) => {
     const numValue =
       value === null ||
@@ -1005,8 +1321,21 @@
     updateAlertState('chart', null);
     if (tabsEnabled) {
       setTabOptions([]);
+      setNativeFilterOptions([]);
       updateAnchorState('');
     }
+    if (filtersEnabled) {
+      setNativeFilterData([
+        {
+          filterName: '',
+          filterType: '',
+          nativeFilterId: null,
+          columnLabel: '',
+          columnName: '',
+          filterValues: [],
+        },
+      ]);
+    }
   };
 
   const onChartChange = (chart: SelectValue) => {
@@ -1062,6 +1391,164 @@
     setForceScreenshot(e.target.checked);
   };
 
+  const onChangeDashboardFilter = (idx: number, nativeFilterId: string) => {
+    if (
+      !nativeFilterId ||
+      nativeFilterId === 'undefined' ||
+      nativeFilterId === 'null'
+    )
+      return;
+
+    // find specific filter tied to the selected filter
+    const filters = Object.values(tabNativeFilters).flat();
+    const filter = filters.filter((f: any) => f.id === nativeFilterId)[0];
+
+    const { filterType, adhoc_filters: adhocFilters } = filter;
+    const filterAlreadyExist = nativeFilterData.some(
+      filter => filter.nativeFilterId === nativeFilterId,
+    );
+
+    if (filterAlreadyExist) {
+      addDangerToast(t('This filter already exist on the report'));
+      return;
+    }
+
+    const filterName = filter.name;
+
+    let columnName: string;
+    if (
+      filterType === 'filter_time' ||
+      filterType === 'filter_timecolumn' ||
+      filterType === 'filter_timegrain'
+    ) {
+      columnName = filter.name;
+    } else {
+      columnName = filter.targets[0].column.name;
+    }
+
+    const datasetId = filter.targets[0].datasetId || null;
+
+    const columnLabel = nativeFilterOptions.filter(
+      filter => filter.value === nativeFilterId,
+    )[0].label;
+    const dashboardId = currentAlert?.dashboard?.value;
+
+    // Get values tied to the selected filter
+    const filterValues = {
+      formData: {
+        datasource: `${datasetId}__table`,
+        groupby: [columnName],
+        metrics: ['count'],
+        row_limit: 1000,
+        showSearch: true,
+        viz_type: 'filter_select',
+        type: 'NATIVE_FILTER',
+        dashboardId,
+        adhoc_filters: adhocFilters,
+      },
+      force: false,
+      ownState: {},
+    };
+
+    // todo(hugh): put this into another function
+    if (
+      filterType === 'filter_time' ||
+      filterType === 'filter_timecolumn' ||
+      filterType === 'filter_timegrain'
+    ) {
+      fetchDashboardFilterValues(
+        dashboardId,
+        columnName,
+        datasetId,
+        filterType,
+        adhocFilters,
+      ).then(optionFilterValues => {
+        setNativeFilterData(
+          nativeFilterData.map((filter, index) =>
+            index === idx
+              ? {
+                  ...filter,
+                  filterName,
+                  filterType,
+                  nativeFilterId,
+                  columnLabel,
+                  columnName,
+                  optionFilterValues,
+                  filterValues: [], // reset filter values on filter change
+                }
+              : filter,
+          ),
+        );
+      });
+
+      setNativeFilterData(
+        nativeFilterData.map((filter, index) =>
+          index === idx
+            ? {
+                ...filter,
+                filterName,
+                filterType,
+                nativeFilterId,
+                columnLabel,
+                columnName,
+                optionFilterValues: [],
+                filterValues: [], // reset filter values on filter change
+              }
+            : filter,
+        ),
+      );
+      return;
+    }
+
+    getChartDataRequest(filterValues).then(response => {
+      const newFilterValues = response.json.result[0].data.map((item: any) => ({
+        value: item[columnName],
+        label: item[columnName],
+      }));
+
+      setNativeFilterData(
+        nativeFilterData.map((filter, index) =>
+          index === idx
+            ? {
+                ...filter,
+                filterName,
+                filterType,
+                nativeFilterId,
+                columnLabel,
+                columnName,
+                optionFilterValues: newFilterValues,
+                filterValues: [], // reset filter values on filter change
+              }
+            : filter,
+        ),
+      );
+    });
+  };
+
+  const onChangeDashboardFilterValue = (
+    idx: number,
+    filterValues:
+      | SelectValue
+      | SelectValue[]
+      | string
+      | string[]
+      | number
+      | number[],
+  ) => {
+    let values: any;
+    if (typeof filterValues === 'string') {
+      values = [filterValues];
+    } else {
+      values = filterValues;
+    }
+
+    setNativeFilterData(
+      nativeFilterData.map((filter, index) =>
+        index === idx ? { ...filter, filterValues: values } : filter,
+      ),
+    );
+  };
+
   // Make sure notification settings has the required info
   const checkNotificationSettings = () => {
     if (!notificationSettings.length) {
@@ -1104,6 +1591,105 @@
     });
   };
 
+  const renderFilterValueSelect = (filter: ExtraNativeFilter, idx: number) => {
+    if (!filter) return null;
+    const { filterType, filterValues } = filter;
+    let mode = 'multiple';
+    if (filterType === 'filter_time') {
+      return (
+        <DateFilterControl
+          name="time_range"
+          onChange={timeRange => {
+            setNativeFilterData(
+              nativeFilterData.map((f: any) =>
+                filter.nativeFilterId === f.nativeFilterId
+                  ? {
+                      ...f,
+                      filterValues: [timeRange],
+                    }
+                  : f,
+              ),
+            );
+          }}
+          value={filterValues?.[0]} // only showing first value in the array for filter_time
+        />
+      );
+    }
+    if (filterType === 'filter_range') {
+      const min = filterValues?.[0];
+      const max = filterValues?.[1];
+      return (
+        <div>
+          <div className="inline-container">
+            <InputNumber
+              value={min}
+              onChange={value => {
+                setNativeFilterData(
+                  nativeFilterData.map((f: any) =>
+                    f.nativeFilterId === filter.nativeFilterId
+                      ? { ...f, filterValues: [value, filterValues?.[1]] }
+                      : f,
+                  ),
+                );
+              }}
+            />
+            <StyledDivider>-</StyledDivider>
+            <InputNumber
+              value={max}
+              onChange={value => {
+                setNativeFilterData(
+                  nativeFilterData.map((f: any) =>
+                    f.nativeFilterId === filter.nativeFilterId
+                      ? { ...f, filterValues: [filterValues?.[0], value] }
+                      : f,
+                  ),
+                );
+              }}
+            />
+          </div>
+          <StatusMessage status="help">
+            {t('Enter minimum and maximum values for the range filter')}
+          </StatusMessage>
+        </div>
+      );
+    }
+
+    if (
+      filterType === 'filter_timegrain' ||
+      filterType === 'filter_timecolumn'
+    ) {
+      mode = 'single';
+    }
+
+    return (
+      <Select
+        ariaLabel={t('Select Value')}
+        placeholder={t('Select Value')}
+        disabled={!filter?.optionFilterValues}
+        value={filter?.filterValues}
+        options={filter?.optionFilterValues || []}
+        onChange={value =>
+          onChangeDashboardFilterValue(
+            idx,
+            value as
+              | string
+              | string[]
+              | number
+              | number[]
+              | SelectValue
+              | SelectValue[],
+          )
+        }
+        mode={mode as 'multiple' | 'single'}
+        onClear={() => {
+          // reset filter values on filter clear
+          onChangeDashboardFilterValue(idx, []);
+        }}
+        allowClear
+      />
+    );
+  };
+
   const validateGeneralSection = () => {
     const errors = [];
     if (!currentAlert?.name?.length) {
@@ -1124,6 +1710,28 @@
     ) {
       errors.push(TRANSLATIONS.CONTENT_ERROR_TEXT);
     }
+
+    // validate native filter
+    nativeFilterData.forEach(filter => {
+      const columnNameCheck = !filter.columnName || filter.columnName === '';
+      const filterValuesCheck =
+        !filter.filterValues || filter.filterValues.length === 0;
+
+      if (columnNameCheck && filterValuesCheck) {
+        // if both columnName and filterValues are null or empty, skip validation
+        return;
+      }
+
+      // check if native filter columnName is null or empty
+      if (columnNameCheck) {
+        errors.push(TRANSLATIONS.NATIVE_FILTER_COLUMN_ERROR_TEXT);
+      }
+      // check if native filter values is null or empty
+      if (filterValuesCheck) {
+        errors.push(TRANSLATIONS.NATIVE_FILTER_NO_VALUES_ERROR_TEXT);
+      }
+    });
+
     updateValidationStatus(Sections.Content, errors);
   };
   const validateAlertSection = () => {
@@ -1245,6 +1853,12 @@
 
   useEffect(() => {
     if (resource) {
+      // Add native filter settings
+      if (resource.extra?.dashboard?.nativeFilters) {
+        const filters = resource.extra.dashboard.nativeFilters;
+        setNativeFilterData(filters);
+      }
+
       // Add notification settings
       const settings = (resource.recipients || []).map(setting => {
         const config =
@@ -1336,6 +1950,7 @@
     currentAlertSafe.dashboard,
     currentAlertSafe.chart,
     contentType,
+    nativeFilterData,
     notificationSettings,
     conditionNotNull,
     emailError,
@@ -1481,8 +2096,9 @@
                 </div>
               ),
             },
-            ...(!isReport
-              ? [
+            ...(isReport
+              ? []
+              : [
                   {
                     key: 'condition',
                     label: (
@@ -1599,8 +2215,7 @@
                       </div>
                     ),
                   },
-                ]
-              : []),
+                ]),
             {
               key: 'contents',
               label: (
@@ -1733,6 +2348,111 @@
                       </>
                     </StyledInputContainer>
                   )}
+                  {filtersEnabled && contentType === ContentType.Dashboard && (
+                    <StyledInputContainer>
+                      <AntdForm
+                        className="filters"
+                        name="form"
+                        autoComplete="off"
+                      >
+                        <AntdForm.List
+                          name="filters"
+                          initialValue={nativeFilterData} // only show one filter field on create
+                        >
+                          {(fields, { add, remove }) => (
+                            <div>
+                              {fields.map(({ key, name: idx }) => (
+                                <div className="filters-container" key={key}>
+                                  <div className="filters-dash-container">
+                                    <div className="control-label">
+                                      <span className="label-with-tooltip">
+                                        {t('Dashboard Filter')}
+                                      </span>
+                                      <InfoTooltip
+                                        tooltip={t(
+                                          'Choose from existing dashboard filters and select a value to refine your report results.',
+                                        )}
+                                      />
+                                    </div>
+                                    <Select
+                                      disabled={
+                                        nativeFilterOptions?.length < 1 &&
+                                        !nativeFilterData[idx]?.filterName
+                                      }
+                                      ariaLabel={t('Select Filter')}
+                                      placeholder={t('Select Filter')}
+                                      value={nativeFilterData[idx]?.filterName}
+                                      options={filterNativeFilterOptions()}
+                                      onChange={value =>
+                                        onChangeDashboardFilter(
+                                          idx,
+                                          String(value),
+                                        )
+                                      }
+                                      onClear={() => {
+                                        // reset filter values on filter clear
+                                        nativeFilterData[idx].columnName = '';
+                                        nativeFilterData[idx].filterName = '';
+                                        nativeFilterData[idx].filterValues = [];
+                                      }}
+                                      css={css`
+                                        flex: 1;
+                                      `}
+                                      oneLine
+                                      allowClear
+                                    />
+                                  </div>
+                                  <div className="filters-dashvalue-container">
+                                    <div className="control-label">
+                                      {t('Value')}
+                                    </div>
+                                    {renderFilterValueSelect(
+                                      nativeFilterData[idx],
+                                      idx,
+                                    )}
+                                  </div>
+                                  {(idx !== 0 || isEditMode) && (
+                                    <div className="filters-delete">
+                                      <Icons.DeleteOutlined
+                                        iconSize="xl"
+                                        className="filters-trashcan"
+                                        onClick={() => {
+                                          handleRemoveFilterField(idx);
+                                          remove(idx);
+                                        }}
+                                      />
+                                    </div>
+                                  )}
+                                </div>
+                              ))}
+                              <div className="filters-add-container">
+                                {filterNativeFilterOptions().length > 0 && (
+                                  // eslint-disable-next-line jsx-a11y/anchor-is-valid
+                                  <a
+                                    className="filters-add-btn"
+                                    role="button"
+                                    tabIndex={0}
+                                    onClick={() => {
+                                      handleAddFilterField();
+                                      add();
+                                    }}
+                                    onKeyDown={e => {
+                                      if (e.key === 'Enter' || e.key === ' ') {
+                                        handleAddFilterField();
+                                        add();
+                                      }
+                                    }}
+                                  >
+                                    + {t('Apply another dashboard filter')}
+                                  </a>
+                                )}
+                              </div>
+                            </div>
+                          )}
+                        </AntdForm.List>
+                      </AntdForm>
+                    </StyledInputContainer>
+                  )}
                   {isScreenshot && (
                     <StyledInputContainer
                       css={
diff --git a/superset-frontend/src/features/alerts/types.ts b/superset-frontend/src/features/alerts/types.ts
index 0644969..4960910 100644
--- a/superset-frontend/src/features/alerts/types.ts
+++ b/superset-frontend/src/features/alerts/types.ts
@@ -92,6 +92,17 @@
   activeTabs?: Array<string>;
   dataMask?: Object;
   anchor?: string;
+  nativeFilters?: Array<ExtraNativeFilter>;
+};
+
+export type ExtraNativeFilter = {
+  filterName?: string;
+  filterType?: string;
+  columnName?: string;
+  columnLabel?: string;
+  filterValues?: Array<any> | [];
+  nativeFilterId?: string | null;
+  optionFilterValues?: Array<any> | [];
 };
 
 export type Extra = {
@@ -191,3 +202,36 @@
   Dashboard = 'dashboard',
   Chart = 'chart',
 }
+
+export type NativeFilterObject = {
+  cascadeParentIds: any[];
+  chartsInScope: number[];
+  controlValues: {
+    defaultToFirstItem: boolean;
+    enableEmptyFilter: boolean;
+    inverseSelection: boolean;
+    multiSelect: boolean;
+    searchAllOptions: boolean;
+  };
+  defaultDataMask: {
+    extraFormData: Record<string, any>;
+    filterState: Record<string, any>;
+    ownState: Record<string, any>;
+  };
+  description: string;
+  filterType: string;
+  id: string;
+  name: string;
+  scope: {
+    excluded: any[];
+    rootPath: string[];
+  };
+  tabsInScope: string[];
+  targets: Array<{
+    column: {
+      name: string;
+    };
+    datasetId: number;
+  }>;
+  type: string;
+};
diff --git a/superset/commands/report/execute.py b/superset/commands/report/execute.py
index 1b19738..e51b968 100644
--- a/superset/commands/report/execute.py
+++ b/superset/commands/report/execute.py
@@ -245,16 +245,34 @@
         Retrieve the URL for the dashboard tabs, or return the dashboard URL if no tabs are available.
         """  # noqa: E501
         force = "true" if self._report_schedule.force_screenshot else "false"
+
         if (
             dashboard_state := self._report_schedule.extra.get("dashboard")
         ) and feature_flag_manager.is_feature_enabled("ALERT_REPORT_TABS"):
+            native_filter_params = self._report_schedule.get_native_filters_params()
             if anchor := dashboard_state.get("anchor"):
                 try:
                     anchor_list: list[str] = json.loads(anchor)
-                    return self._get_tabs_urls(anchor_list, user_friendly=user_friendly)
+                    urls = self._get_tabs_urls(
+                        anchor_list,
+                        native_filter_params=native_filter_params,
+                        user_friendly=user_friendly,
+                    )
+                    return urls
                 except json.JSONDecodeError:
                     logger.debug("Anchor value is not a list, Fall back to single tab")
-            return [self._get_tab_url(dashboard_state)]
+
+            return [
+                self._get_tab_url(
+                    {
+                        "urlParams": [
+                            ["native_filters", native_filter_params]  # type: ignore
+                        ],
+                        **dashboard_state,
+                    },
+                    user_friendly=user_friendly,
+                )
+            ]
 
         dashboard = self._report_schedule.dashboard
         dashboard_id_or_slug = (
@@ -281,6 +299,7 @@
             dashboard_id=str(self._report_schedule.dashboard.uuid),
             state=dashboard_state,
         ).run()
+
         return get_url_path(
             "Superset.dashboard_permalink",
             key=permalink_key,
@@ -288,7 +307,10 @@
         )
 
     def _get_tabs_urls(
-        self, tab_anchors: list[str], user_friendly: bool = False
+        self,
+        tab_anchors: list[str],
+        native_filter_params: Optional[str] = None,
+        user_friendly: bool = False,
     ) -> list[str]:
         """
         Get multple tabs urls
@@ -299,7 +321,9 @@
                     "anchor": tab_anchor,
                     "dataMask": None,
                     "activeTabs": None,
-                    "urlParams": None,
+                    "urlParams": [
+                        ["native_filters", native_filter_params]  # type: ignore
+                    ],
                 },
                 user_friendly=user_friendly,
             )
@@ -338,7 +362,6 @@
             ]
         else:
             urls = self.get_dashboard_urls()
-
             window_width, window_height = app.config["WEBDRIVER_WINDOW"]["dashboard"]
             width = min(max_width, self._report_schedule.custom_width or window_width)
             height = self._report_schedule.custom_height or window_height
@@ -500,6 +523,7 @@
         error_text = None
         header_data = self._get_log_data()
         url = self._get_url(user_friendly=True)
+
         if (
             feature_flag_manager.is_feature_enabled("ALERTS_ATTACH_REPORTS")
             or self._report_schedule.type == ReportScheduleType.REPORT
diff --git a/superset/config.py b/superset/config.py
index dea23c8..856b604 100644
--- a/superset/config.py
+++ b/superset/config.py
@@ -535,6 +535,7 @@
     # Enables Alerts and reports new implementation
     "ALERT_REPORTS": False,
     "ALERT_REPORT_TABS": False,
+    "ALERT_REPORTS_FILTER": False,
     "ALERT_REPORT_SLACK_V2": False,
     "DASHBOARD_RBAC": False,
     "ENABLE_ADVANCED_DATA_TYPES": False,
diff --git a/superset/daos/dashboard.py b/superset/daos/dashboard.py
index c9c119c..1d6f366 100644
--- a/superset/daos/dashboard.py
+++ b/superset/daos/dashboard.py
@@ -17,6 +17,7 @@
 from __future__ import annotations
 
 import logging
+from collections import defaultdict
 from datetime import datetime
 from typing import Any
 
@@ -321,6 +322,23 @@
         return dash
 
     @classmethod
+    def get_native_filter_configuration(
+        cls, id: str
+    ) -> dict[str, list[dict[str, Any]]]:
+        dashboard = cls.get_by_id_or_slug(id)
+        metadata = json.loads(dashboard.json_metadata or "{}")
+        native_filter_configuration = metadata.get("native_filter_configuration", [])
+
+        tab_filters = defaultdict(list)
+        for filter in native_filter_configuration:
+            if tabs_in_scope := filter.get("tabsInScope", []):
+                for tab_key in tabs_in_scope:
+                    tab_filters[tab_key].append(filter)
+            tab_filters["all"].append(filter)
+
+        return tab_filters
+
+    @classmethod
     def update_native_filters_config(
         cls,
         dashboard: Dashboard | None = None,
diff --git a/superset/dashboards/api.py b/superset/dashboards/api.py
index c811e5a..79ce952 100644
--- a/superset/dashboards/api.py
+++ b/superset/dashboards/api.py
@@ -479,7 +479,11 @@
         """  # noqa: E501
         try:
             tabs = DashboardDAO.get_tabs_for_dashboard(id_or_slug)
+            native_filters = DashboardDAO.get_native_filter_configuration(id_or_slug)
+
             result = self.tab_schema.dump(tabs)
+            result["native_filters"] = native_filters
+
             return self.response(200, result=result)
 
         except (TypeError, ValueError) as err:
diff --git a/superset/reports/models.py b/superset/reports/models.py
index e4cdd7c..20d3389 100644
--- a/superset/reports/models.py
+++ b/superset/reports/models.py
@@ -16,6 +16,9 @@
 # under the License.
 """A collection of ORM sqlalchemy models for Superset"""
 
+from typing import Any, Optional
+
+import prison
 from cron_descriptor import get_description
 from flask_appbuilder import Model
 from flask_appbuilder.models.decorators import renders
@@ -183,6 +186,117 @@
     def crontab_humanized(self) -> str:
         return get_description(self.crontab)
 
+    def get_native_filters_params(self) -> str:
+        params: dict[str, Any] = {}
+        dashboard = self.extra.get("dashboard")
+        if dashboard and dashboard.get("nativeFilters"):
+            for filter in dashboard.get("nativeFilters") or []:  # type: ignore
+                params = {
+                    **params,
+                    **self._generate_native_filter(
+                        filter["nativeFilterId"],
+                        filter["filterType"],
+                        filter["columnName"],
+                        filter["filterValues"],
+                    ),
+                }
+        # hack(hughhh): workaround for escaping prison not handling quotes right
+        rison = prison.dumps(params)
+        rison = rison.replace("'", "%27")
+        return rison
+
+    def _generate_native_filter(
+        self,
+        native_filter_id: str,
+        filter_type: str,
+        column_name: str,
+        values: list[Optional[str]],
+    ) -> dict[str, Any]:
+        if filter_type == "filter_time":
+            # For select filters, we need to use the "IN" operator
+            return {
+                native_filter_id or "": {
+                    "id": native_filter_id or "",
+                    "extraFormData": {"time_range": values[0]},
+                    "filterState": {"value": values[0]},
+                    "ownState": {},
+                }
+            }
+        elif filter_type == "filter_timegrain":
+            return {
+                native_filter_id or "": {
+                    "id": native_filter_id or "",
+                    "extraFormData": {
+                        "time_grain_sqla": values[0],  # grain
+                    },
+                    "filterState": {
+                        # "label": "30 second", # grain_label
+                        "value": values  # grain
+                    },
+                    "ownState": {},
+                }
+            }
+
+        elif filter_type == "filter_timecolumn":
+            return {
+                native_filter_id or "": {
+                    "extraFormData": {
+                        "granularity_sqla": values[0]  # column_name
+                    },
+                    "filterState": {
+                        "value": values  # column_name
+                    },
+                }
+            }
+
+        elif filter_type == "filter_select":
+            return {
+                native_filter_id or "": {
+                    "id": native_filter_id or "",
+                    "extraFormData": {
+                        "filters": [
+                            {"col": column_name or "", "op": "IN", "val": values or []}
+                        ]
+                    },
+                    "filterState": {
+                        "label": column_name or "",
+                        "validateStatus": False,
+                        "value": values or [],
+                    },
+                    "ownState": {},
+                }
+            }
+        elif filter_type == "filter_range":
+            # For range filters, values should be [min, max] or [value] for single value
+            min_val = values[0] if len(values) > 0 else None
+            max_val = values[1] if len(values) > 1 else None
+
+            filters = []
+            if min_val is not None:
+                filters.append({"col": column_name or "", "op": ">=", "val": min_val})
+            if max_val is not None:
+                filters.append({"col": column_name or "", "op": "<=", "val": max_val})
+
+            return {
+                native_filter_id or "": {
+                    "id": native_filter_id or "",
+                    "extraFormData": {"filters": filters},
+                    "filterState": {
+                        "value": [min_val, max_val],
+                        "label": f"{min_val} ≤ x ≤ {max_val}"
+                        if min_val and max_val
+                        else f"x ≥ {min_val}"
+                        if min_val
+                        else f"x ≤ {max_val}"
+                        if max_val
+                        else "",
+                    },
+                    "ownState": {},
+                }
+            }
+
+        return {}
+
 
 class ReportRecipients(Model, AuditMixinNullable):
     """
diff --git a/superset/views/core.py b/superset/views/core.py
index d0c8d29..c495594 100755
--- a/superset/views/core.py
+++ b/superset/views/core.py
@@ -847,17 +847,24 @@
             return redirect(url_for("DashboardModelView.list"))
         if not value:
             return json_error_response(_("permalink state not found"), status=404)
+
         dashboard_id, state = value["dashboardId"], value.get("state", {})
         url = url_for(
             "Superset.dashboard", dashboard_id_or_slug=dashboard_id, permalink_key=key
         )
         if url_params := state.get("urlParams"):
-            params = parse.urlencode(url_params)
-            url = f"{url}&{params}"
+            for param_key, param_val in url_params:
+                if param_key == "native_filters":
+                    # native_filters doesnt need to be encoded here
+                    url = f"{url}&native_filters={param_val}"
+                else:
+                    params = parse.urlencode([param_key, param_val])  # type: ignore
+                    url = f"{url}&{params}"
         if original_params := request.query_string.decode():
             url = f"{url}&{original_params}"
         if hash_ := state.get("anchor", state.get("hash")):
             url = f"{url}#{hash_}"
+
         return redirect(url)
 
     @api
diff --git a/tests/integration_tests/dashboards/api_tests.py b/tests/integration_tests/dashboards/api_tests.py
index a655102..0ac973a 100644
--- a/tests/integration_tests/dashboards/api_tests.py
+++ b/tests/integration_tests/dashboards/api_tests.py
@@ -1177,6 +1177,7 @@
                     "TAB-hyTv5L7zz": "P1 - T2 - T2",
                     "TAB-qL7fSzr3jl": "Parent Tab 1",
                 },
+                "native_filters": {},
                 "tab_tree": [
                     {
                         "children": [
diff --git a/tests/integration_tests/reports/commands/execute_dashboard_report_tests.py b/tests/integration_tests/reports/commands/execute_dashboard_report_tests.py
index 318fd49..6c1b607 100644
--- a/tests/integration_tests/reports/commands/execute_dashboard_report_tests.py
+++ b/tests/integration_tests/reports/commands/execute_dashboard_report_tests.py
@@ -47,10 +47,12 @@
 ) -> None:
     dashboard_screenshot_mock.get_screenshot.return_value = b"test-image"
     current_app.config["ALERT_REPORTS_NOTIFICATION_DRY_RUN"] = False
-
     with create_dashboard_report(
         dashboard=tabbed_dashboard,
-        extra={"dashboard": {"active_tabs": ["TAB-L1B", "TAB-L2BB"]}},
+        extra={
+            "activeTabs": ["TAB-L1B", "TAB-L2BB"],
+            "urlParams": [["native_filters", "()"]],
+        },
         name="test report tabbed dashboard",
     ) as report_schedule:
         dashboard: Dashboard = report_schedule.dashboard
@@ -59,7 +61,7 @@
         ).run()
         dashboard_state = report_schedule.extra.get("dashboard", {})
         permalink_key = CreateDashboardPermalinkCommand(
-            str(dashboard.id), dashboard_state
+            str(dashboard.uuid), dashboard_state
         ).run()
 
         expected_url = get_url_path("Superset.dashboard_permalink", key=permalink_key)
@@ -90,7 +92,10 @@
 
     with create_dashboard_report(
         dashboard=tabbed_dashboard,
-        extra={"dashboard": {"active_tabs": ["TAB-L1B", "TAB-L2BB"]}},
+        extra={
+            "active_tabs": ["TAB-L1B", "TAB-L2BB"],
+            "urlParams": [["native_filters", "()"]],
+        },
         name="test report tabbed dashboard",
     ) as report_schedule:
         dashboard: Dashboard = report_schedule.dashboard
diff --git a/tests/integration_tests/reports/commands_tests.py b/tests/integration_tests/reports/commands_tests.py
index 63ff86c..ae1c855 100644
--- a/tests/integration_tests/reports/commands_tests.py
+++ b/tests/integration_tests/reports/commands_tests.py
@@ -1205,7 +1205,14 @@
             report_schedule = create_report_notification(
                 email_target="target@email.com",
                 dashboard=dashboard,
-                extra={"dashboard": {"anchor": "TAB-L2AB"}},
+                extra={
+                    "dashboard": {
+                        "anchor": "TAB-L2AB",
+                        "activeTabs": None,
+                        "dataMask": None,
+                        "urlParams": [["native_filters", "()"]],
+                    }
+                },
             )
             AsyncExecuteReportScheduleCommand(
                 TEST_ID, report_schedule.id, datetime.utcnow()
@@ -1254,7 +1261,14 @@
             report_schedule = create_report_notification(
                 email_target="target@email.com",
                 dashboard=dashboard,
-                extra={"dashboard": {"anchor": "TAB-L2AB"}},
+                extra={
+                    "dashboard": {
+                        "anchor": "TAB-L2AB",
+                        "activeTabs": None,
+                        "dataMask": None,
+                        "urlParams": [["native_filters", "()"]],
+                    }
+                },
             )
             AsyncExecuteReportScheduleCommand(
                 TEST_ID, report_schedule.id, datetime.utcnow()
diff --git a/tests/unit_tests/reports/model_test.py b/tests/unit_tests/reports/model_test.py
new file mode 100644
index 0000000..19e12e1
--- /dev/null
+++ b/tests/unit_tests/reports/model_test.py
@@ -0,0 +1,242 @@
+# 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 pytest
+
+from superset.reports.models import ReportSchedule
+
+
+def test_get_native_filters_params():
+    """
+    Test the ``get_native_filters_params`` method.
+    """
+    report_schedule = ReportSchedule()
+    report_schedule.extra = {
+        "dashboard": {
+            "nativeFilters": [
+                {
+                    "nativeFilterId": "filter_id",
+                    "columnName": "column_name",
+                    "filterType": "filter_select",
+                    "filterValues": ["value1", "value2"],
+                }
+            ]
+        }
+    }
+
+    assert report_schedule.get_native_filters_params() == (
+        "(filter_id:(extraFormData:(filters:!((col:column_name,op:IN,val:!(value1,value2)))),filterState:(label:column_name,validateStatus:!f,value:!(value1,value2)),id:filter_id,ownState:()))"
+    )
+
+
+def test_get_native_filters_params_multiple_filters():
+    """
+    Test the ``get_native_filters_params`` method with multiple native filters.
+    """
+    report_schedule = ReportSchedule()
+    report_schedule.extra = {
+        "dashboard": {
+            "nativeFilters": [
+                {
+                    "nativeFilterId": "filter_id_1",
+                    "filterType": "filter_select",
+                    "columnName": "column_name_1",
+                    "filterValues": ["value1", "value2"],
+                },
+                {
+                    "nativeFilterId": "filter_id_2",
+                    "filterType": "filter_select",
+                    "columnName": "column_name_2",
+                    "filterValues": ["value3", "value4"],
+                },
+            ]
+        }
+    }
+
+    assert report_schedule.get_native_filters_params() == (
+        "(filter_id_1:(extraFormData:(filters:!((col:column_name_1,op:IN,val:!(value1,value2)))),filterState:(label:column_name_1,validateStatus:!f,value:!(value1,value2)),id:filter_id_1,ownState:()),filter_id_2:(extraFormData:(filters:!((col:column_name_2,op:IN,val:!(value3,value4)))),filterState:(label:column_name_2,validateStatus:!f,value:!(value3,value4)),id:filter_id_2,ownState:()))"
+    )
+
+
+def test_report_generate_native_filter_no_values():
+    """
+    Test the ``_generate_native_filter`` method with no values.
+    """
+    report_schedule = ReportSchedule()
+    native_filter_id = "filter_id"
+    column_name = "column_name"
+    filter_type = "filter_select"
+    values = None
+
+    assert report_schedule._generate_native_filter(
+        native_filter_id, filter_type, column_name, values
+    ) == {
+        "filter_id": {
+            "id": "filter_id",
+            "extraFormData": {
+                "filters": [{"col": "column_name", "op": "IN", "val": []}]
+            },
+            "filterState": {
+                "label": "column_name",
+                "validateStatus": False,
+                "value": [],
+            },
+            "ownState": {},
+        }
+    }
+
+
+def test_get_native_filters_params_invalid_structure():
+    """
+    Test the ``get_native_filters_params`` method with invalid structure.
+    """
+    report_schedule = ReportSchedule()
+    report_schedule.extra = {
+        "dashboard": {
+            "nativeFilters": [
+                {
+                    "nativeFilterId": "filter_id",
+                    "columnName": "column_name",
+                    "filterType": "filter_select",
+                    # Missing "filterValues" key
+                }
+            ]
+        }
+    }
+
+    with pytest.raises(KeyError, match="'filterValues'"):
+        report_schedule.get_native_filters_params()
+
+
+# todo(hugh): how do we want to handle this case?
+# def test_report_generate_native_filter_invalid_filter_id():
+#     """
+#     Test the ``_generate_native_filter`` method with invalid filter id.
+#     """
+#     report_schedule = ReportSchedule()
+#     native_filter_id = None
+#     column_name = "column_name"
+#     values = ["value1", "value2"]
+
+#     assert report_schedule._generate_native_filter(
+#         native_filter_id, column_name, values
+#     ) == {}
+
+
+def test_report_generate_native_filter():
+    """
+    Test the ``_generate_native_filter`` method.
+    """
+    report_schedule = ReportSchedule()
+    native_filter_id = "filter_id"
+    filter_type = "filter_select"
+    column_name = "column_name"
+    values = ["value1", "value2"]
+
+    assert report_schedule._generate_native_filter(
+        native_filter_id, filter_type, column_name, values
+    ) == {
+        "filter_id": {
+            "extraFormData": {
+                "filters": [
+                    {"col": "column_name", "op": "IN", "val": ["value1", "value2"]}
+                ]
+            },
+            "filterState": {
+                "label": "column_name",
+                "validateStatus": False,
+                "value": ["value1", "value2"],
+            },
+            "id": "filter_id",
+            "ownState": {},
+        }
+    }
+
+
+def test_get_native_filters_params_empty():
+    """
+    Test the ``get_native_filters_params`` method with empty extra.
+    """
+    report_schedule = ReportSchedule()
+    report_schedule.extra = {}
+
+    assert report_schedule.get_native_filters_params() == "()"
+
+
+def test_get_native_filters_params_no_native_filters():
+    """
+    Test the ``get_native_filters_params`` method with no native filters.
+    """
+    report_schedule = ReportSchedule()
+    report_schedule.extra = {"dashboard": {"nativeFilters": []}}
+
+    assert report_schedule.get_native_filters_params() == "()"
+
+
+def test_report_generate_native_filter_empty_values():
+    """
+    Test the ``_generate_native_filter`` method with empty values.
+    """
+    report_schedule = ReportSchedule()
+    native_filter_id = "filter_id"
+    filter_type = "filter_select"
+    column_name = "column_name"
+    values = []
+
+    assert report_schedule._generate_native_filter(
+        native_filter_id, filter_type, column_name, values
+    ) == {
+        "filter_id": {
+            "extraFormData": {
+                "filters": [{"col": "column_name", "op": "IN", "val": []}]
+            },
+            "filterState": {
+                "label": "column_name",
+                "validateStatus": False,
+                "value": [],
+            },
+            "id": "filter_id",
+            "ownState": {},
+        }
+    }
+
+
+def test_report_generate_native_filter_no_column_name():
+    """
+    Test the ``_generate_native_filter`` method with no column name.
+    """
+    report_schedule = ReportSchedule()
+    native_filter_id = "filter_id"
+    filter_type = "filter_select"
+    column_name = ""
+    values = ["value1", "value2"]
+
+    assert report_schedule._generate_native_filter(
+        native_filter_id, filter_type, column_name, values
+    ) == {
+        "filter_id": {
+            "extraFormData": {
+                "filters": [{"col": "", "op": "IN", "val": ["value1", "value2"]}]
+            },
+            "filterState": {
+                "label": "",
+                "validateStatus": False,
+                "value": ["value1", "value2"],
+            },
+            "id": "filter_id",
+            "ownState": {},
+        }
+    }