You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by di...@apache.org on 2023/01/17 13:57:58 UTC
[superset] branch feat/cross-filters updated: Enable cross-filters by default
This is an automated email from the ASF dual-hosted git repository.
diegopucci pushed a commit to branch feat/cross-filters
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/feat/cross-filters by this push:
new 80a95b0663 Enable cross-filters by default
80a95b0663 is described below
commit 80a95b0663bdffa9ea9402f6865659e870fb0f50
Author: geido <di...@gmail.com>
AuthorDate: Tue Jan 17 14:57:40 2023 +0100
Enable cross-filters by default
---
.../integration/dashboard/nativeFilters.test.ts | 1 +
.../plugins/plugin-chart-echarts/src/types.ts | 2 +-
.../spec/fixtures/mockDashboardState.js | 2 +-
.../components/DropdownSelectableIcon/index.tsx | 6 +-
.../src/dashboard/actions/dashboardInfo.ts | 56 ++++++++++++++++
.../src/dashboard/actions/dashboardState.js | 4 +-
superset-frontend/src/dashboard/actions/hydrate.js | 2 +
.../dashboard/components/gridComponents/Chart.jsx | 12 +++-
.../FilterBarSettings/FilterBarSettings.test.tsx | 1 +
.../FilterBar/FilterBarSettings/index.tsx | 75 ++++++++++++++--------
.../src/dashboard/containers/Chart.jsx | 1 +
.../src/dashboard/reducers/dashboardInfo.js | 6 ++
superset-frontend/src/dashboard/types.ts | 1 +
superset/dashboards/dao.py | 1 +
superset/dashboards/schemas.py | 1 +
tests/integration_tests/dashboards/api_tests.py | 2 +-
tests/unit_tests/fixtures/assets_configs.py | 1 +
17 files changed, 138 insertions(+), 36 deletions(-)
diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilters.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilters.test.ts
index 365a7e4ecb..e1d888cdea 100644
--- a/superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilters.test.ts
+++ b/superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilters.test.ts
@@ -128,6 +128,7 @@ function prepareDashboardFilters(
label_colors: {},
shared_label_colors: {},
color_scheme_domain: [],
+ cross_filters_enabled: false,
positions: {
DASHBOARD_VERSION_KEY: 'v2',
ROOT_ID: { type: 'ROOT', id: 'ROOT_ID', children: ['GRID_ID'] },
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/types.ts b/superset-frontend/plugins/plugin-chart-echarts/src/types.ts
index faffb7a565..2f71b21af7 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/types.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/types.ts
@@ -134,7 +134,7 @@ export type CrossFilterTransformedProps = {
setControlValue?: HandlerFunction;
setDataMask: SetDataMaskHook;
selectedValues: Record<number, string>;
- emitCrossFilters: boolean;
+ emitCrossFilters?: boolean;
};
export type ContextMenuTransformedProps = {
diff --git a/superset-frontend/spec/fixtures/mockDashboardState.js b/superset-frontend/spec/fixtures/mockDashboardState.js
index b605443af6..0895ccf386 100644
--- a/superset-frontend/spec/fixtures/mockDashboardState.js
+++ b/superset-frontend/spec/fixtures/mockDashboardState.js
@@ -113,6 +113,6 @@ export const overwriteConfirmMetadata = {
slug: null,
owners: [],
json_metadata:
- '{"timed_refresh_immune_slices":[],"expanded_slices":{},"refresh_frequency":0,"default_filters":"{}","color_scheme":"supersetColors","label_colors":{"0":"#FCC700","1":"#A868B7","15":"#3CCCCB","30":"#A38F79","45":"#8FD3E4","age":"#1FA8C9","Yes,":"#1FA8C9","Female":"#454E7C","Prefer":"#5AC189","No,":"#FF7F44","Male":"#666666","Prefer not to say":"#E04355","Ph.D.":"#FCC700","associate\'s degree":"#A868B7","bachelor\'s degree":"#3CCCCB","high school diploma or equivalent (GED)":"#A38F7 [...]
+ '{"timed_refresh_immune_slices":[],"expanded_slices":{},"refresh_frequency":0,"default_filters":"{}","color_scheme":"supersetColors","label_colors":{"0":"#FCC700","1":"#A868B7","15":"#3CCCCB","30":"#A38F79","45":"#8FD3E4","age":"#1FA8C9","Yes,":"#1FA8C9","Female":"#454E7C","Prefer":"#5AC189","No,":"#FF7F44","Male":"#666666","Prefer not to say":"#E04355","Ph.D.":"#FCC700","associate\'s degree":"#A868B7","bachelor\'s degree":"#3CCCCB","high school diploma or equivalent (GED)":"#A38F7 [...]
},
};
diff --git a/superset-frontend/src/components/DropdownSelectableIcon/index.tsx b/superset-frontend/src/components/DropdownSelectableIcon/index.tsx
index 8d4926995e..c2488b45c3 100644
--- a/superset-frontend/src/components/DropdownSelectableIcon/index.tsx
+++ b/superset-frontend/src/components/DropdownSelectableIcon/index.tsx
@@ -26,7 +26,7 @@ import { css, Global } from '@emotion/react';
const { SubMenu } = Menu;
-type SubMenuItemProps = { key: string; label: React.ReactNode };
+type SubMenuItemProps = { key: string; label: string | React.ReactNode };
export interface DropDownSelectableProps extends Pick<MenuProps, 'onSelect'> {
ref?: RefObject<HTMLDivElement>;
@@ -34,7 +34,7 @@ export interface DropDownSelectableProps extends Pick<MenuProps, 'onSelect'> {
info?: string;
menuItems: {
key: string;
- label: React.ReactNode;
+ label: string | React.ReactNode;
children?: SubMenuItemProps[];
}[];
selectedKeys?: string[];
@@ -103,7 +103,7 @@ export default (props: DropDownSelectableProps) => {
)}
{menuItems.map(m =>
m.children?.length ? (
- <SubMenu title={m.label}>
+ <SubMenu title={m.label} key={m.key}>
{m.children.map(s => menuItem(s.label, s.key))}
</SubMenu>
) : (
diff --git a/superset-frontend/src/dashboard/actions/dashboardInfo.ts b/superset-frontend/src/dashboard/actions/dashboardInfo.ts
index dbec0cd1cc..3720df2bd5 100644
--- a/superset-frontend/src/dashboard/actions/dashboardInfo.ts
+++ b/superset-frontend/src/dashboard/actions/dashboardInfo.ts
@@ -131,6 +131,17 @@ export function setFilterBarOrientation(
return { type: SET_FILTER_BAR_ORIENTATION, filterBarOrientation };
}
+export const SET_CROSS_FILTERS_ENABLED = 'SET_CROSS_FILTERS_ENABLED';
+export interface SetCrossFiltersEnabled {
+ type: typeof SET_CROSS_FILTERS_ENABLED;
+ crossFiltersEnabled: boolean;
+}
+export function setCrossFiltersEnabled(
+ crossFiltersEnabled: boolean,
+) {
+ return { type: SET_CROSS_FILTERS_ENABLED, crossFiltersEnabled };
+}
+
export function saveFilterBarOrientation(orientation: FilterBarOrientation) {
return async (dispatch: Dispatch, getState: () => RootState) => {
const { id, metadata } = getState().dashboardInfo;
@@ -177,3 +188,48 @@ export function saveFilterBarOrientation(orientation: FilterBarOrientation) {
}
};
}
+
+export function saveCrossFiltersSetting(crossFiltersEnabled: boolean) {
+ return async (dispatch: Dispatch, getState: () => RootState) => {
+ const { id, metadata } = getState().dashboardInfo;
+ const updateDashboard = makeApi<
+ Partial<DashboardInfo>,
+ { result: Partial<DashboardInfo>; last_modified_time: number }
+ >({
+ method: 'PUT',
+ endpoint: `/api/v1/dashboard/${id}`,
+ });
+ try {
+ const response = await updateDashboard({
+ json_metadata: JSON.stringify({
+ ...metadata,
+ cross_filters_enabled: crossFiltersEnabled,
+ }),
+ });
+ const updatedDashboard = response.result;
+ const lastModifiedTime = response.last_modified_time;
+ if (updatedDashboard.json_metadata) {
+ const metadata = JSON.parse(updatedDashboard.json_metadata);
+ dispatch(setCrossFiltersEnabled(metadata.cross_filters_enabled));
+ }
+ if (lastModifiedTime) {
+ dispatch(onSave(lastModifiedTime));
+ }
+ } catch (errorObject) {
+ const { error, message } = await getClientErrorObject(errorObject);
+ let errorText = t('Sorry, an unknown error occurred.');
+
+ if (error) {
+ errorText = t(
+ 'Sorry, there was an error saving this dashboard: %s',
+ error,
+ );
+ }
+ if (typeof message === 'string' && message === 'Forbidden') {
+ errorText = t('You do not have permission to edit this dashboard');
+ }
+ dispatch(addDangerToast(errorText));
+ throw errorObject;
+ }
+ };
+}
\ No newline at end of file
diff --git a/superset-frontend/src/dashboard/actions/dashboardState.js b/superset-frontend/src/dashboard/actions/dashboardState.js
index 518dd7b5dc..3e4cc73650 100644
--- a/superset-frontend/src/dashboard/actions/dashboardState.js
+++ b/superset-frontend/src/dashboard/actions/dashboardState.js
@@ -242,7 +242,7 @@ export function saveDashboardRequest(data, id, saveType) {
} = data;
const hasId = item => item.id !== undefined;
-
+ const metadataCrossFiltersEnabled = data.metadata?.cross_filters_enabled;
// making sure the data is what the backend expects
const cleanedData = {
...data,
@@ -267,6 +267,8 @@ export function saveDashboardRequest(data, id, saveType) {
refresh_frequency: data.metadata?.refresh_frequency || 0,
timed_refresh_immune_slices:
data.metadata?.timed_refresh_immune_slices || [],
+ // cross-filters should be enabled by default
+ cross_filters_enabled: metadataCrossFiltersEnabled === undefined ? true : metadataCrossFiltersEnabled,
},
};
diff --git a/superset-frontend/src/dashboard/actions/hydrate.js b/superset-frontend/src/dashboard/actions/hydrate.js
index ed359a8cee..da6e1eecd2 100644
--- a/superset-frontend/src/dashboard/actions/hydrate.js
+++ b/superset-frontend/src/dashboard/actions/hydrate.js
@@ -433,6 +433,8 @@ export const hydrateDashboard =
(isFeatureEnabled(FeatureFlag.HORIZONTAL_FILTER_BAR) &&
metadata.filter_bar_orientation) ||
FilterBarOrientation.VERTICAL,
+ crossFiltersEnabled: (isFeatureEnabled(FeatureFlag.DASHBOARD_CROSS_FILTERS) &&
+ metadata.cross_filters_enabled === undefined || metadata.cross_filters_enabled) || false,
},
dataMask,
dashboardFilters,
diff --git a/superset-frontend/src/dashboard/components/gridComponents/Chart.jsx b/superset-frontend/src/dashboard/components/gridComponents/Chart.jsx
index 5891b9fa16..9612189f1b 100644
--- a/superset-frontend/src/dashboard/components/gridComponents/Chart.jsx
+++ b/superset-frontend/src/dashboard/components/gridComponents/Chart.jsx
@@ -97,6 +97,7 @@ const propTypes = {
postTransformProps: PropTypes.func,
datasetsStatus: PropTypes.oneOf(['loading', 'error', 'complete']),
isInView: PropTypes.bool,
+ emitCrossFilters: PropTypes.bool,
};
const defaultProps = {
@@ -175,6 +176,13 @@ class Chart extends React.Component {
return true;
}
+ // allow chart to update if enable/disable cross-filters.
+ if (
+ this.props?.emitCrossFilters !== nextProps?.emitCrossFilters
+ ) {
+ return true;
+ }
+
// allow chart update/re-render only if visible:
// under selected tab or no tab layout
if (nextProps.isComponentVisible) {
@@ -399,6 +407,7 @@ class Chart extends React.Component {
postTransformProps,
datasetsStatus,
isInView,
+ emitCrossFilters,
} = this.props;
const { width } = this.state;
@@ -428,6 +437,7 @@ class Chart extends React.Component {
filterId: id,
})
: {};
+
return (
<SliceContainer
className="chart-slice"
@@ -529,7 +539,7 @@ class Chart extends React.Component {
postTransformProps={postTransformProps}
datasetsStatus={datasetsStatus}
isInView={isInView}
- emitCrossFilters
+ emitCrossFilters={emitCrossFilters}
/>
</div>
</SliceContainer>
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBarSettings/FilterBarSettings.test.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBarSettings/FilterBarSettings.test.tsx
index 5f49076385..e003d1dc75 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBarSettings/FilterBarSettings.test.tsx
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBarSettings/FilterBarSettings.test.tsx
@@ -39,6 +39,7 @@ const initialState: { dashboardInfo: DashboardInfo } = {
color_scheme_domain: [],
label_colors: {},
shared_label_colors: {},
+ cross_filters_enabled: false,
},
json_metadata: '',
dash_edit_perm: true,
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBarSettings/index.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBarSettings/index.tsx
index 2c658442d2..1ea1880f5b 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBarSettings/index.tsx
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBarSettings/index.tsx
@@ -17,14 +17,14 @@
* under the License.
*/
-import React, { useCallback, useMemo, useState } from 'react';
+import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
-import { styled, t, useTheme } from '@superset-ui/core';
+import { FeatureFlag, isFeatureEnabled, styled, t, useTheme } from '@superset-ui/core';
import { MenuProps } from 'src/components/Menu';
import { FilterBarOrientation, RootState } from 'src/dashboard/types';
-import { saveFilterBarOrientation } from 'src/dashboard/actions/dashboardInfo';
+import { saveFilterBarOrientation, saveCrossFiltersSetting } from 'src/dashboard/actions/dashboardInfo';
import Icons from 'src/components/Icons';
-import DropdownSelectableIcon from 'src/components/DropdownSelectableIcon';
+import DropdownSelectableIcon, { DropDownSelectableProps } from 'src/components/DropdownSelectableIcon';
import Checkbox from 'src/components/Checkbox';
type SelectedKey = FilterBarOrientation | string | number;
@@ -38,18 +38,33 @@ const StyledMenuLabel = styled.span`
const FilterBarSettings = () => {
const dispatch = useDispatch();
const theme = useTheme();
+ const isCrossFiltersEnabled = useSelector<RootState, boolean>(
+ ({ dashboardInfo }) => dashboardInfo.crossFiltersEnabled,
+ );
const filterBarOrientation = useSelector<RootState, FilterBarOrientation>(
({ dashboardInfo }) => dashboardInfo.filterBarOrientation,
);
const [selectedFilterBarOrientation, setSelectedFilterBarOrientation] =
useState(filterBarOrientation);
+ const isCrossFiltersFeatureEnabled = isFeatureEnabled(
+ FeatureFlag.DASHBOARD_CROSS_FILTERS,
+ );
+ const shouldEnableCrossFilters = !!isCrossFiltersEnabled && isCrossFiltersFeatureEnabled;
const [crossFiltersEnabled, setCrossFiltersEnabled] =
- useState<boolean>(false);
+ useState<boolean>(shouldEnableCrossFilters);
const crossFiltersMenuKey = 'cross-filters-menu-key';
const isOrientation = (o: SelectedKey): o is FilterBarOrientation =>
o === FilterBarOrientation.VERTICAL ||
o === FilterBarOrientation.HORIZONTAL;
- const toggleFilterBarOrientation = useCallback(
+ const updateCrossFiltersSetting = useCallback(
+ async (isEnabled) => {
+ await dispatch(
+ saveCrossFiltersSetting(isEnabled),
+ );
+ },
+ [dispatch, crossFiltersEnabled],
+ );
+ const changeFilterBarSettings = useCallback(
async (
selection: Parameters<
Required<Pick<MenuProps, 'onSelect'>>['onSelect']
@@ -58,6 +73,7 @@ const FilterBarSettings = () => {
const selectedKey: SelectedKey = selection.key;
if (selectedKey === crossFiltersMenuKey) {
setCrossFiltersEnabled(!crossFiltersEnabled);
+ updateCrossFiltersSetting(!crossFiltersEnabled);
return;
}
if (isOrientation(selectedKey) && selectedKey !== filterBarOrientation) {
@@ -76,7 +92,6 @@ const FilterBarSettings = () => {
},
[dispatch, crossFiltersEnabled, filterBarOrientation],
);
-
const crossFiltersMenuItem = useMemo(
() => (
<StyledMenuLabel>
@@ -90,10 +105,33 @@ const FilterBarSettings = () => {
),
[crossFiltersEnabled],
);
+ const menuItems: DropDownSelectableProps["menuItems"] = [
+ {
+ key: 'placement',
+ label: t('Placement of the filter bar'),
+ children: [
+ {
+ key: FilterBarOrientation.VERTICAL,
+ label: t('Vertical (Left)'),
+ },
+ {
+ key: FilterBarOrientation.HORIZONTAL,
+ label: t('Horizontal (Top)'),
+ },
+ ],
+ },
+ ];
+
+ if (isCrossFiltersFeatureEnabled) {
+ menuItems.unshift({
+ key: crossFiltersMenuKey,
+ label: crossFiltersMenuItem,
+ });
+ }
return (
<DropdownSelectableIcon
- onSelect={toggleFilterBarOrientation}
+ onSelect={changeFilterBarSettings}
icon={
<Icons.Gear
name="gear"
@@ -101,26 +139,7 @@ const FilterBarSettings = () => {
data-test="filterbar-orientation-icon"
/>
}
- menuItems={[
- {
- key: crossFiltersMenuKey,
- label: crossFiltersMenuItem,
- },
- {
- key: 'placement',
- label: t('Placement of the filter bar'),
- children: [
- {
- key: FilterBarOrientation.VERTICAL,
- label: t('Vertical (Left)'),
- },
- {
- key: FilterBarOrientation.HORIZONTAL,
- label: t('Horizontal (Top)'),
- },
- ],
- },
- ]}
+ menuItems={menuItems}
selectedKeys={[selectedFilterBarOrientation]}
/>
);
diff --git a/superset-frontend/src/dashboard/containers/Chart.jsx b/superset-frontend/src/dashboard/containers/Chart.jsx
index 337b5fc017..167ec0a043 100644
--- a/superset-frontend/src/dashboard/containers/Chart.jsx
+++ b/superset-frontend/src/dashboard/containers/Chart.jsx
@@ -102,6 +102,7 @@ function mapStateToProps(
setControlValue,
filterboxMigrationState: dashboardState.filterboxMigrationState,
datasetsStatus,
+ emitCrossFilters: !!dashboardInfo.crossFiltersEnabled,
};
}
diff --git a/superset-frontend/src/dashboard/reducers/dashboardInfo.js b/superset-frontend/src/dashboard/reducers/dashboardInfo.js
index 030fd60250..166323af25 100644
--- a/superset-frontend/src/dashboard/reducers/dashboardInfo.js
+++ b/superset-frontend/src/dashboard/reducers/dashboardInfo.js
@@ -20,6 +20,7 @@
import {
DASHBOARD_INFO_UPDATED,
SET_FILTER_BAR_ORIENTATION,
+ SET_CROSS_FILTERS_ENABLED,
} from '../actions/dashboardInfo';
import { HYDRATE_DASHBOARD } from '../actions/hydrate';
@@ -43,6 +44,11 @@ export default function dashboardStateReducer(state = {}, action) {
...state,
filterBarOrientation: action.filterBarOrientation,
};
+ case SET_CROSS_FILTERS_ENABLED:
+ return {
+ ...state,
+ crossFiltersEnabled: action.crossFiltersEnabled,
+ };
default:
return state;
}
diff --git a/superset-frontend/src/dashboard/types.ts b/superset-frontend/src/dashboard/types.ts
index e24356be61..694372f1f5 100644
--- a/superset-frontend/src/dashboard/types.ts
+++ b/superset-frontend/src/dashboard/types.ts
@@ -109,6 +109,7 @@ export type DashboardInfo = {
label_colors: JsonObject;
shared_label_colors: JsonObject;
};
+ crossFiltersEnabled: boolean;
filterBarOrientation: FilterBarOrientation;
};
diff --git a/superset/dashboards/dao.py b/superset/dashboards/dao.py
index c6577f1b0e..3f0666266f 100644
--- a/superset/dashboards/dao.py
+++ b/superset/dashboards/dao.py
@@ -279,6 +279,7 @@ class DashboardDAO(BaseDAO):
md["label_colors"] = data.get("label_colors", {})
md["shared_label_colors"] = data.get("shared_label_colors", {})
md["color_scheme_domain"] = data.get("color_scheme_domain", [])
+ md["cross_filters_enabled"] = data.get("cross_filters_enabled", True)
dashboard.json_metadata = json.dumps(md)
if commit:
diff --git a/superset/dashboards/schemas.py b/superset/dashboards/schemas.py
index d3a9444980..f0d05445aa 100644
--- a/superset/dashboards/schemas.py
+++ b/superset/dashboards/schemas.py
@@ -130,6 +130,7 @@ class DashboardJSONMetadataSchema(Schema):
label_colors = fields.Dict()
shared_label_colors = fields.Dict()
color_scheme_domain = fields.List(fields.Str())
+ cross_filters_enabled = fields.Boolean(default=True)
# used for v0 import/export
import_time = fields.Integer()
remote_id = fields.Integer()
diff --git a/tests/integration_tests/dashboards/api_tests.py b/tests/integration_tests/dashboards/api_tests.py
index 7288889bf1..93dade83ff 100644
--- a/tests/integration_tests/dashboards/api_tests.py
+++ b/tests/integration_tests/dashboards/api_tests.py
@@ -72,7 +72,7 @@ class TestDashboardApi(SupersetTestCase, ApiOwnersTestCaseMixin, InsertChartMixi
"slug": "slug1_changed",
"position_json": '{"b": "B"}',
"css": "css_changed",
- "json_metadata": '{"refresh_frequency": 30, "timed_refresh_immune_slices": [], "expanded_slices": {}, "color_scheme": "", "label_colors": {}, "shared_label_colors": {}, "color_scheme_domain": []}',
+ "json_metadata": '{"refresh_frequency": 30, "timed_refresh_immune_slices": [], "expanded_slices": {}, "color_scheme": "", "label_colors": {}, "shared_label_colors": {}, "color_scheme_domain": [], "cross_filters_enabled": false}',
"published": False,
}
diff --git a/tests/unit_tests/fixtures/assets_configs.py b/tests/unit_tests/fixtures/assets_configs.py
index 14ff37cf61..6e78d9e562 100644
--- a/tests/unit_tests/fixtures/assets_configs.py
+++ b/tests/unit_tests/fixtures/assets_configs.py
@@ -177,6 +177,7 @@ dashboards_config_1: Dict[str, Any] = {
"show_native_filters": True,
"color_scheme_domain": [],
"shared_label_colors": {},
+ "cross_filters_enabled": False,
},
"version": "1.0.0",
},