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

amitmiran pushed a commit to branch 1.3
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 609463eb11f842f86304456764726d89f266049c
Author: Ville Brofeldt <[email protected]>
AuthorDate: Tue Jun 29 18:57:49 2021 +0300

    feat(native-filters): add support for preselect filters (#15427)
    
    * feat(native-filters): add support for sharing preselected filters
    
    * abc
    
    * add serialization
    
    (cherry picked from commit 4630abb5a891fa40962b58a11445b73570b1fa20)
---
 .../dashboard/util/getDashboardUrl_spec.js         | 45 ++++++++++++++++++----
 .../src/components/AnchorLink/index.jsx            |  8 ++--
 superset-frontend/src/constants.ts                 |  8 ++++
 superset-frontend/src/dashboard/actions/hydrate.js |  1 +
 .../dashboard/components/DashboardBuilder/state.ts |  7 +++-
 .../Header/HeaderActionsDropdown/index.jsx         | 27 +++++++------
 .../src/dashboard/components/Header/index.jsx      |  3 ++
 .../components/SliceHeaderControls/index.tsx       | 10 ++---
 .../FilterBar/FilterControls/FilterValue.tsx       | 10 ++++-
 .../components/nativeFilters/FilterBar/index.tsx   | 31 ++++++++++++++-
 .../components/nativeFilters/FilterBar/state.ts    | 16 +++++++-
 .../dashboard/components/nativeFilters/state.ts    | 11 ++++++
 .../src/dashboard/containers/DashboardHeader.jsx   |  4 ++
 superset-frontend/src/dashboard/types.ts           |  6 ++-
 .../src/dashboard/util/activeDashboardFilters.js   |  4 +-
 .../src/dashboard/util/getDashboardUrl.ts          | 36 ++++++++++++++---
 superset-frontend/src/utils/urlUtils.ts            | 13 ++++++-
 17 files changed, 199 insertions(+), 41 deletions(-)

diff --git 
a/superset-frontend/spec/javascripts/dashboard/util/getDashboardUrl_spec.js 
b/superset-frontend/spec/javascripts/dashboard/util/getDashboardUrl_spec.js
index 77a19c7..c986c6f 100644
--- a/superset-frontend/spec/javascripts/dashboard/util/getDashboardUrl_spec.js
+++ b/superset-frontend/spec/javascripts/dashboard/util/getDashboardUrl_spec.js
@@ -34,35 +34,64 @@ describe('getChartIdsFromLayout', () => {
   });
 
   it('should encode filters', () => {
-    const url = getDashboardUrl('path', filters);
+    const url = getDashboardUrl({ pathname: 'path', filters });
     expect(url).toBe(
       
'path?preselect_filters=%7B%2235%22%3A%7B%22key%22%3A%5B%22value%22%5D%7D%7D',
     );
   });
 
   it('should encode filters with hash', () => {
-    const urlWithHash = getDashboardUrl('path', filters, 'iamhashtag');
+    const urlWithHash = getDashboardUrl({
+      pathname: 'path',
+      filters,
+      hash: 'iamhashtag',
+    });
     expect(urlWithHash).toBe(
       
'path?preselect_filters=%7B%2235%22%3A%7B%22key%22%3A%5B%22value%22%5D%7D%7D#iamhashtag',
     );
   });
 
   it('should encode filters with standalone', () => {
-    const urlWithStandalone = getDashboardUrl(
-      'path',
+    const urlWithStandalone = getDashboardUrl({
+      pathname: 'path',
       filters,
-      '',
-      DashboardStandaloneMode.HIDE_NAV,
-    );
+      standalone: DashboardStandaloneMode.HIDE_NAV,
+    });
     expect(urlWithStandalone).toBe(
       
`path?preselect_filters=%7B%2235%22%3A%7B%22key%22%3A%5B%22value%22%5D%7D%7D&standalone=${DashboardStandaloneMode.HIDE_NAV}`,
     );
   });
 
   it('should encode filters with missing standalone', () => {
-    const urlWithStandalone = getDashboardUrl('path', filters, '', null);
+    const urlWithStandalone = getDashboardUrl({
+      pathname: 'path',
+      filters,
+      standalone: null,
+    });
     expect(urlWithStandalone).toBe(
       
'path?preselect_filters=%7B%2235%22%3A%7B%22key%22%3A%5B%22value%22%5D%7D%7D',
     );
   });
+
+  it('should encode native filters', () => {
+    const urlWithNativeFilters = getDashboardUrl({
+      pathname: 'path',
+      dataMask: {
+        'NATIVE_FILTER-foo123': {
+          filterState: {
+            label: 'custom label',
+            value: ['a', 'b'],
+          },
+        },
+        'NATIVE_FILTER-bar456': {
+          filterState: {
+            value: undefined,
+          },
+        },
+      },
+    });
+    expect(urlWithNativeFilters).toBe(
+      
'path?preselect_filters=%7B%7D&native_filters=%28NATIVE_FILTER-bar456%3A%21n%2CNATIVE_FILTER-foo123%3A%21%28a%2Cb%29%29',
+    );
+  });
 });
diff --git a/superset-frontend/src/components/AnchorLink/index.jsx 
b/superset-frontend/src/components/AnchorLink/index.jsx
index 42f93c3..16be622 100644
--- a/superset-frontend/src/components/AnchorLink/index.jsx
+++ b/superset-frontend/src/components/AnchorLink/index.jsx
@@ -80,11 +80,11 @@ class AnchorLink extends React.PureComponent {
       <span className="anchor-link-container" id={anchorLinkId}>
         {showShortLinkButton && (
           <URLShortLinkButton
-            url={getDashboardUrl(
-              window.location.pathname,
+            url={getDashboardUrl({
+              pathname: window.location.pathname,
               filters,
-              anchorLinkId,
-            )}
+              hash: anchorLinkId,
+            })}
             emailSubject={t('Superset chart')}
             emailContent={t('Check out this chart in dashboard:')}
             placement={placement}
diff --git a/superset-frontend/src/constants.ts 
b/superset-frontend/src/constants.ts
index bfa7033..a525106 100644
--- a/superset-frontend/src/constants.ts
+++ b/superset-frontend/src/constants.ts
@@ -31,6 +31,14 @@ export const URL_PARAMS = {
     name: 'preselect_filters',
     type: 'object',
   },
+  nativeFilters: {
+    name: 'native_filters',
+    type: 'rison',
+  },
+  filterSet: {
+    name: 'filter_set',
+    type: 'string',
+  },
   showFilters: {
     name: 'show_filters',
     type: 'boolean',
diff --git a/superset-frontend/src/dashboard/actions/hydrate.js 
b/superset-frontend/src/dashboard/actions/hydrate.js
index 0cba0cd..c860e29 100644
--- a/superset-frontend/src/dashboard/actions/hydrate.js
+++ b/superset-frontend/src/dashboard/actions/hydrate.js
@@ -360,6 +360,7 @@ export const hydrateDashboard = (dashboardData, chartData, 
datasourcesData) => (
       dashboardFilters,
       nativeFilters,
       dashboardState: {
+        preselectNativeFilters: getUrlParam(URL_PARAMS.nativeFilters),
         sliceIds: Array.from(sliceIds),
         directPathToChild,
         directPathLastUpdated: Date.now(),
diff --git 
a/superset-frontend/src/dashboard/components/DashboardBuilder/state.ts 
b/superset-frontend/src/dashboard/components/DashboardBuilder/state.ts
index 874525d..e2d3b27 100644
--- a/superset-frontend/src/dashboard/components/DashboardBuilder/state.ts
+++ b/superset-frontend/src/dashboard/components/DashboardBuilder/state.ts
@@ -17,6 +17,7 @@
  * under the License.
  */
 import { useSelector } from 'react-redux';
+import { JsonObject } from '@superset-ui/core';
 import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags';
 import { useEffect, useState } from 'react';
 import { URL_PARAMS } from 'src/constants';
@@ -37,6 +38,9 @@ export const useNativeFilters = () => {
   const showNativeFilters = useSelector<RootState, boolean>(
     state => state.dashboardInfo.metadata?.show_native_filters,
   );
+  const preselectNativeFilters = useSelector<RootState, JsonObject>(
+    state => state.dashboardState?.preselectNativeFilters || {},
+  );
   const canEdit = useSelector<RootState, boolean>(
     ({ dashboardInfo }) => dashboardInfo.dash_edit_perm,
   );
@@ -50,7 +54,7 @@ export const useNativeFilters = () => {
     (canEdit || (!canEdit && filterValues.length !== 0));
 
   const requiredFirstFilter = filterValues.filter(
-    ({ requiredFirst }) => requiredFirst,
+    filter => filter.requiredFirst || preselectNativeFilters[filter.id],
   );
   const dataMask = useNativeFiltersDataMask();
   const showDashboard =
@@ -89,5 +93,6 @@ export const useNativeFilters = () => {
     dashboardFiltersOpen,
     toggleDashboardFiltersOpen,
     nativeFiltersEnabled,
+    preselectNativeFilters,
   };
 };
diff --git 
a/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/index.jsx
 
b/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/index.jsx
index 4f48797..e69d4a1 100644
--- 
a/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/index.jsx
+++ 
b/superset-frontend/src/dashboard/components/Header/HeaderActionsDropdown/index.jsx
@@ -42,6 +42,7 @@ const propTypes = {
   dashboardInfo: PropTypes.object.isRequired,
   dashboardId: PropTypes.number.isRequired,
   dashboardTitle: PropTypes.string.isRequired,
+  dataMask: PropTypes.object.isRequired,
   customCss: PropTypes.string.isRequired,
   colorNamespace: PropTypes.string,
   colorScheme: PropTypes.string,
@@ -164,12 +165,13 @@ class HeaderActionsDropdown extends React.PureComponent {
         break;
       }
       case MENU_KEYS.TOGGLE_FULLSCREEN: {
-        const url = getDashboardUrl(
-          window.location.pathname,
-          getActiveFilters(),
-          window.location.hash,
-          !getUrlParam(URL_PARAMS.standalone),
-        );
+        const url = getDashboardUrl({
+          dataMask: this.props.dataMask,
+          pathname: window.location.pathname,
+          filters: getActiveFilters(),
+          hash: window.location.hash,
+          standalone: !getUrlParam(URL_PARAMS.standalone),
+        });
         window.location.replace(url);
         break;
       }
@@ -183,6 +185,7 @@ class HeaderActionsDropdown extends React.PureComponent {
       dashboardTitle,
       dashboardId,
       dashboardInfo,
+      dataMask,
       refreshFrequency,
       shouldPersistRefreshFrequency,
       editMode,
@@ -206,11 +209,13 @@ class HeaderActionsDropdown extends React.PureComponent {
     const emailTitle = t('Superset dashboard');
     const emailSubject = `${emailTitle} ${dashboardTitle}`;
     const emailBody = t('Check out this dashboard: ');
-    const url = getDashboardUrl(
-      window.location.pathname,
-      getActiveFilters(),
-      window.location.hash,
-    );
+
+    const url = getDashboardUrl({
+      dataMask,
+      pathname: window.location.pathname,
+      filters: getActiveFilters(),
+      hash: window.location.hash,
+    });
 
     const menu = (
       <Menu
diff --git a/superset-frontend/src/dashboard/components/Header/index.jsx 
b/superset-frontend/src/dashboard/components/Header/index.jsx
index 6befcbb..3d9e58f 100644
--- a/superset-frontend/src/dashboard/components/Header/index.jsx
+++ b/superset-frontend/src/dashboard/components/Header/index.jsx
@@ -53,6 +53,7 @@ const propTypes = {
   addWarningToast: PropTypes.func.isRequired,
   dashboardInfo: PropTypes.object.isRequired,
   dashboardTitle: PropTypes.string.isRequired,
+  dataMask: PropTypes.object.isRequired,
   charts: PropTypes.objectOf(chartPropShape).isRequired,
   layout: PropTypes.object.isRequired,
   expandedSlices: PropTypes.object.isRequired,
@@ -353,6 +354,7 @@ class Header extends React.PureComponent {
       expandedSlices,
       customCss,
       colorNamespace,
+      dataMask,
       setColorSchemeAndUnsavedChanges,
       colorScheme,
       onUndo,
@@ -526,6 +528,7 @@ class Header extends React.PureComponent {
             dashboardId={dashboardInfo.id}
             dashboardTitle={dashboardTitle}
             dashboardInfo={dashboardInfo}
+            dataMask={dataMask}
             layout={layout}
             expandedSlices={expandedSlices}
             customCss={customCss}
diff --git 
a/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx 
b/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx
index 08e35b2..28d42dd 100644
--- a/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx
+++ b/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx
@@ -293,11 +293,11 @@ class SliceHeaderControls extends 
React.PureComponent<Props, State> {
 
         {supersetCanShare && (
           <ShareMenuItems
-            url={getDashboardUrl(
-              window.location.pathname,
-              getActiveFilters(),
-              componentId,
-            )}
+            url={getDashboardUrl({
+              pathname: window.location.pathname,
+              filters: getActiveFilters(),
+              hash: componentId,
+            })}
             copyMenuItemTitle={t('Copy chart URL')}
             emailMenuItemTitle={t('Share chart by email')}
             emailSubject={t('Superset chart')}
diff --git 
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterValue.tsx
 
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterValue.tsx
index e5f6727..1bd8cd4 100644
--- 
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterValue.tsx
+++ 
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterValue.tsx
@@ -43,6 +43,7 @@ import { ClientErrorObject } from 
'src/utils/getClientErrorObject';
 import { FilterProps } from './types';
 import { getFormData } from '../../utils';
 import { useCascadingFilters } from './state';
+import { usePreselectNativeFilter } from '../../state';
 
 const HEIGHT = 32;
 
@@ -80,7 +81,8 @@ const FilterValue: React.FC<FilterProps> = ({
   const { name: groupby } = column;
   const hasDataSource = !!datasetId;
   const [isLoading, setIsLoading] = useState<boolean>(hasDataSource);
-  const [isRefreshing, setIsRefreshing] = useState<boolean>(true);
+  const [isRefreshing, setIsRefreshing] = useState(true);
+  const preselection = usePreselectNativeFilter(filter.id);
   const dispatch = useDispatch();
 
   useEffect(() => {
@@ -195,6 +197,10 @@ const FilterValue: React.FC<FilterProps> = ({
       />
     );
   }
+  const filterState = { ...filter.dataMask?.filterState };
+  if (filterState.value === undefined && preselection) {
+    filterState.value = preselection;
+  }
 
   return (
     <StyledDiv data-test="form-item-value">
@@ -209,7 +215,7 @@ const FilterValue: React.FC<FilterProps> = ({
           queriesData={hasDataSource ? state : [{ data: [{}] }]}
           chartType={filterType}
           behaviors={[Behavior.NATIVE_FILTER]}
-          filterState={{ ...filter.dataMask?.filterState }}
+          filterState={filterState}
           ownState={filter.dataMask?.ownState}
           enableNoResults={metadata?.enableNoResults}
           isRefreshing={isRefreshing}
diff --git 
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx 
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx
index 1ec8773..f7d528c 100644
--- 
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx
+++ 
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx
@@ -33,6 +33,7 @@ import { testWithId } from 'src/utils/testUtils';
 import { Filter } from 'src/dashboard/components/nativeFilters/types';
 import Loading from 'src/components/Loading';
 import { getInitialDataMask } from 'src/dataMask/reducer';
+import { areObjectsEqual } from 'src/reduxUtils';
 import { checkIsApplyDisabled, TabIds } from './utils';
 import FilterSets from './FilterSets';
 import {
@@ -45,6 +46,7 @@ import {
 import EditSection from './FilterSets/EditSection';
 import Header from './Header';
 import FilterControls from './FilterControls/FilterControls';
+import { usePreselectNativeFilters } from '../state';
 
 export const FILTER_BAR_TEST_ID = 'filter-bar';
 export const getFilterBarTestId = testWithId(FILTER_BAR_TEST_ID);
@@ -156,6 +158,8 @@ const FilterBar: React.FC<FiltersBarProps> = ({
   const filterValues = Object.values<Filter>(filters);
   const dataMaskApplied: DataMaskStateWithId = useNativeFiltersDataMask();
   const [isFilterSetChanged, setIsFilterSetChanged] = useState(false);
+  const preselectNativeFilters = usePreselectNativeFilters();
+  const [initializedFilters, setInitializedFilters] = useState<any[]>([]);
 
   useEffect(() => {
     setDataMaskSelected(() => dataMaskApplied);
@@ -185,8 +189,33 @@ const FilterBar: React.FC<FiltersBarProps> = ({
   ) => {
     setIsFilterSetChanged(tab !== TabIds.AllFilters);
     setDataMaskSelected(draft => {
-      // force instant updating on initialization for filters with 
`requiredFirst` is true or instant filters
+      // check if a filter has preselect filters
       if (
+        preselectNativeFilters?.[filter.id] !== undefined &&
+        !initializedFilters.includes(filter.id)
+      ) {
+        /**
+         * since preselect filters don't have extraFormData, they need to 
iterate
+         * a few times to populate the full state necessary for proper 
filtering.
+         * Once both filterState and extraFormData are identical, we can 
coclude
+         * that the filter has been fully initialized.
+         */
+        if (
+          areObjectsEqual(
+            dataMask.filterState,
+            dataMaskSelected[filter.id]?.filterState,
+          ) &&
+          areObjectsEqual(
+            dataMask.extraFormData,
+            dataMaskSelected[filter.id]?.extraFormData,
+          )
+        ) {
+          setInitializedFilters(prevState => [...prevState, filter.id]);
+        }
+        dispatch(updateDataMask(filter.id, dataMask));
+      }
+      // force instant updating on initialization for filters with 
`requiredFirst` is true or instant filters
+      else if (
         (dataMaskSelected[filter.id] && filter.isInstant) ||
         // filterState.value === undefined - means that value not initialized
         (dataMask.filterState?.value !== undefined &&
diff --git 
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/state.ts 
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/state.ts
index aa4894f..12d7b6c 100644
--- 
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/state.ts
+++ 
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/state.ts
@@ -30,6 +30,7 @@ import {
 import { useEffect, useState } from 'react';
 import { ChartsState, RootState } from 'src/dashboard/types';
 import { NATIVE_FILTER_PREFIX } from '../FiltersConfigModal/utils';
+import { Filter } from '../types';
 
 export const useFilterSets = () =>
   useSelector<any, FilterSetsType>(
@@ -37,7 +38,20 @@ export const useFilterSets = () =>
   );
 
 export const useFilters = () =>
-  useSelector<any, Filters>(state => state.nativeFilters.filters);
+  useSelector<any, Filters>(state => {
+    const preselectNativeFilters =
+      state.dashboardState?.preselectNativeFilters || {};
+    return Object.entries(state.nativeFilters.filters).reduce(
+      (acc, [filterId, filter]: [string, Filter]) => ({
+        ...acc,
+        [filterId]: {
+          ...filter,
+          preselect: preselectNativeFilters[filterId],
+        },
+      }),
+      {} as Filters,
+    );
+  });
 
 export const useNativeFiltersDataMask = () => {
   const dataMask = useSelector<RootState, DataMaskStateWithId>(
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/state.ts 
b/superset-frontend/src/dashboard/components/nativeFilters/state.ts
index 280a203..165ee0f 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/state.ts
+++ b/superset-frontend/src/dashboard/components/nativeFilters/state.ts
@@ -18,6 +18,7 @@
  */
 import { useSelector } from 'react-redux';
 import { useMemo } from 'react';
+import { JsonObject } from '@superset-ui/core';
 import { Filter, FilterConfiguration } from './types';
 import { ActiveTabs, DashboardLayout, RootState } from '../../types';
 import { TAB_TYPE } from '../../util/componentTypes';
@@ -124,3 +125,13 @@ export function useSelectFiltersInScope(cascadeFilters: 
CascadeFilter[]) {
     return [filtersInScope, filtersOutOfScope];
   }, [cascadeFilters, dashboardHasTabs, isFilterInScope]);
 }
+
+export function usePreselectNativeFilters(): JsonObject | undefined {
+  return useSelector<RootState, any>(
+    state => state.dashboardState?.preselectNativeFilters,
+  );
+}
+
+export function usePreselectNativeFilter(id: string): JsonObject | undefined {
+  return usePreselectNativeFilters()?.[id];
+}
diff --git a/superset-frontend/src/dashboard/containers/DashboardHeader.jsx 
b/superset-frontend/src/dashboard/containers/DashboardHeader.jsx
index 6351561..2322ca7 100644
--- a/superset-frontend/src/dashboard/containers/DashboardHeader.jsx
+++ b/superset-frontend/src/dashboard/containers/DashboardHeader.jsx
@@ -19,6 +19,7 @@
 import { bindActionCreators } from 'redux';
 import { connect } from 'react-redux';
 
+import { updateDataMask } from 'src/dataMask/actions';
 import DashboardHeader from '../components/Header';
 import isDashboardLoading from '../util/isDashboardLoading';
 
@@ -61,6 +62,7 @@ function mapStateToProps({
   dashboardState,
   dashboardInfo,
   charts,
+  dataMask,
 }) {
   return {
     dashboardInfo,
@@ -77,6 +79,7 @@ function mapStateToProps({
     colorNamespace: dashboardState.colorNamespace,
     colorScheme: dashboardState.colorScheme,
     charts,
+    dataMask,
     userId: dashboardInfo.userId,
     isStarred: !!dashboardState.isStarred,
     isPublished: !!dashboardState.isPublished,
@@ -118,6 +121,7 @@ function mapDispatchToProps(dispatch) {
       setRefreshFrequency,
       dashboardInfoChanged,
       dashboardTitleChanged,
+      updateDataMask,
     },
     dispatch,
   );
diff --git a/superset-frontend/src/dashboard/types.ts 
b/superset-frontend/src/dashboard/types.ts
index 7c1edae..7c70ff4 100644
--- a/superset-frontend/src/dashboard/types.ts
+++ b/superset-frontend/src/dashboard/types.ts
@@ -52,6 +52,7 @@ export type ActiveTabs = string[];
 export type DashboardLayout = { [key: string]: LayoutItem };
 export type DashboardLayoutState = { present: DashboardLayout };
 export type DashboardState = {
+  preselectNativeFilters?: JsonObject;
   editMode: boolean;
   directPathToChild: string[];
   activeTabs: ActiveTabs;
@@ -63,7 +64,10 @@ export type DashboardInfo = {
   };
   userId: string;
   dash_edit_perm: boolean;
-  metadata: { show_native_filters: boolean; chart_configuration: JsonObject };
+  metadata: {
+    show_native_filters: boolean;
+    chart_configuration: JsonObject;
+  };
 };
 
 export type ChartsState = { [key: string]: Chart };
diff --git a/superset-frontend/src/dashboard/util/activeDashboardFilters.js 
b/superset-frontend/src/dashboard/util/activeDashboardFilters.js
index 30bdc25..96db2c3 100644
--- a/superset-frontend/src/dashboard/util/activeDashboardFilters.js
+++ b/superset-frontend/src/dashboard/util/activeDashboardFilters.js
@@ -33,7 +33,9 @@ let allComponents = {};
 
 // output: { [id_column]: { values, scope } }
 export function getActiveFilters() {
-  return activeFilters;
+  return {
+    ...activeFilters,
+  };
 }
 
 // currently filter_box is a chart,
diff --git a/superset-frontend/src/dashboard/util/getDashboardUrl.ts 
b/superset-frontend/src/dashboard/util/getDashboardUrl.ts
index 7eb817a..53eb826 100644
--- a/superset-frontend/src/dashboard/util/getDashboardUrl.ts
+++ b/superset-frontend/src/dashboard/util/getDashboardUrl.ts
@@ -16,15 +16,25 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+import rison from 'rison';
+import { JsonObject } from '@superset-ui/core';
 import { URL_PARAMS } from 'src/constants';
 import serializeActiveFilterValues from './serializeActiveFilterValues';
+import { DataMaskState } from '../../dataMask/types';
 
-export default function getDashboardUrl(
-  pathname: string,
+export default function getDashboardUrl({
+  pathname,
   filters = {},
   hash = '',
-  standalone?: number | null,
-) {
+  standalone,
+  dataMask,
+}: {
+  pathname: string;
+  filters: JsonObject;
+  hash: string;
+  standalone?: number | null;
+  dataMask?: DataMaskState;
+}) {
   const newSearchParams = new URLSearchParams();
 
   // convert flattened { [id_column]: values } object
@@ -38,7 +48,23 @@ export default function getDashboardUrl(
     newSearchParams.set(URL_PARAMS.standalone.name, standalone.toString());
   }
 
-  const hashSection = hash ? `#${hash}` : '';
+  if (dataMask) {
+    const filterStates = Object.entries(dataMask).reduce(
+      (agg, [key, value]) => {
+        const filterState = value?.filterState?.value;
+        return {
+          ...agg,
+          [key]: filterState || null,
+        };
+      },
+      {},
+    );
+    newSearchParams.set(
+      URL_PARAMS.nativeFilters.name,
+      rison.encode(filterStates),
+    );
+  }
 
+  const hashSection = hash ? `#${hash}` : '';
   return `${pathname}?${newSearchParams.toString()}${hashSection}`;
 }
diff --git a/superset-frontend/src/utils/urlUtils.ts 
b/superset-frontend/src/utils/urlUtils.ts
index ebb3a1d..f0d9fa8 100644
--- a/superset-frontend/src/utils/urlUtils.ts
+++ b/superset-frontend/src/utils/urlUtils.ts
@@ -17,15 +17,17 @@
  * under the License.
  */
 import { SupersetClient } from '@superset-ui/core';
+import rison from 'rison';
 import { getClientErrorObject } from './getClientErrorObject';
 import { URL_PARAMS } from '../constants';
 
-export type UrlParamType = 'string' | 'number' | 'boolean' | 'object';
+export type UrlParamType = 'string' | 'number' | 'boolean' | 'object' | 
'rison';
 export type UrlParam = typeof URL_PARAMS[keyof typeof URL_PARAMS];
 export function getUrlParam(param: UrlParam & { type: 'string' }): string;
 export function getUrlParam(param: UrlParam & { type: 'number' }): number;
 export function getUrlParam(param: UrlParam & { type: 'boolean' }): boolean;
 export function getUrlParam(param: UrlParam & { type: 'object' }): object;
+export function getUrlParam(param: UrlParam & { type: 'rison' }): object;
 export function getUrlParam({ name, type }: UrlParam): unknown {
   const urlParam = new URLSearchParams(window.location.search).get(name);
   switch (type) {
@@ -53,6 +55,15 @@ export function getUrlParam({ name, type }: UrlParam): 
unknown {
         return null;
       }
       return urlParam !== 'false' && urlParam !== '0';
+    case 'rison':
+      if (!urlParam) {
+        return null;
+      }
+      try {
+        return rison.decode(urlParam);
+      } catch {
+        return null;
+      }
     default:
       return urlParam;
   }

Reply via email to