You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by am...@apache.org on 2021/07/04 08:40:31 UTC
[superset] 09/19: feat(native-filters): add support for preselect
filters (#15427)
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 <33...@users.noreply.github.com>
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;
}