mirror of https://github.com/apache/superset.git
This commit is contained in:
parent
ee1952e488
commit
6338ea5d42
|
@ -56,7 +56,6 @@ These features are **finished** but currently being tested. They are usable, but
|
|||
- DASHBOARD_FILTERS_EXPERIMENTAL
|
||||
- DASHBOARD_NATIVE_FILTERS
|
||||
- DYNAMIC_PLUGINS: [(docs)](https://superset.apache.org/docs/installation/running-on-kubernetes)
|
||||
- ENABLE_FILTER_BOX_MIGRATION
|
||||
- ENABLE_JAVASCRIPT_CONTROLS
|
||||
- GENERIC_CHART_AXES
|
||||
- GLOBAL_ASYNC_QUERIES [(docs)](https://github.com/apache/superset/blob/master/CONTRIBUTING.md#async-chart-queries)
|
||||
|
|
|
@ -44,7 +44,6 @@ export enum FeatureFlag {
|
|||
ENABLE_ADVANCED_DATA_TYPES = 'ENABLE_ADVANCED_DATA_TYPES',
|
||||
ENABLE_DND_WITH_CLICK_UX = 'ENABLE_DND_WITH_CLICK_UX',
|
||||
ENABLE_EXPLORE_DRAG_AND_DROP = 'ENABLE_EXPLORE_DRAG_AND_DROP',
|
||||
ENABLE_FILTER_BOX_MIGRATION = 'ENABLE_FILTER_BOX_MIGRATION',
|
||||
ENABLE_JAVASCRIPT_CONTROLS = 'ENABLE_JAVASCRIPT_CONTROLS',
|
||||
ENABLE_TEMPLATE_PROCESSING = 'ENABLE_TEMPLATE_PROCESSING',
|
||||
ENABLE_TEMPLATE_REMOVE_FILTERS = 'ENABLE_TEMPLATE_REMOVE_FILTERS',
|
||||
|
|
|
@ -59,7 +59,6 @@ const propTypes = {
|
|||
triggerRender: PropTypes.bool,
|
||||
force: PropTypes.bool,
|
||||
isFiltersInitialized: PropTypes.bool,
|
||||
isDeactivatedViz: PropTypes.bool,
|
||||
// state
|
||||
chartAlert: PropTypes.string,
|
||||
chartStatus: PropTypes.string,
|
||||
|
@ -94,7 +93,6 @@ const defaultProps = {
|
|||
triggerRender: false,
|
||||
dashboardId: null,
|
||||
chartStackTrace: null,
|
||||
isDeactivatedViz: false,
|
||||
force: false,
|
||||
isInView: true,
|
||||
};
|
||||
|
@ -140,25 +138,13 @@ class Chart extends React.PureComponent {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
// during migration, hold chart queries before user choose review or cancel
|
||||
if (
|
||||
this.props.triggerQuery &&
|
||||
this.props.filterboxMigrationState !== 'UNDECIDED'
|
||||
) {
|
||||
if (this.props.triggerQuery) {
|
||||
this.runQuery();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
// during migration, hold chart queries before user choose review or cancel
|
||||
if (
|
||||
this.props.triggerQuery &&
|
||||
this.props.filterboxMigrationState !== 'UNDECIDED'
|
||||
) {
|
||||
// if the chart is deactivated (filter_box), only load once
|
||||
if (this.props.isDeactivatedViz && this.props.queriesResponse) {
|
||||
return;
|
||||
}
|
||||
if (this.props.triggerQuery) {
|
||||
this.runQuery();
|
||||
}
|
||||
}
|
||||
|
@ -261,7 +247,6 @@ class Chart extends React.PureComponent {
|
|||
errorMessage,
|
||||
chartIsStale,
|
||||
queriesResponse = [],
|
||||
isDeactivatedViz = false,
|
||||
width,
|
||||
} = this.props;
|
||||
|
||||
|
@ -332,7 +317,7 @@ class Chart extends React.PureComponent {
|
|||
<Loading />
|
||||
)}
|
||||
</div>
|
||||
{isLoading && !isDeactivatedViz && <Loading />}
|
||||
{isLoading && <Loading />}
|
||||
</Styles>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
|
|
|
@ -27,10 +27,6 @@ export const BOOL_TRUE_DISPLAY = 'True';
|
|||
export const BOOL_FALSE_DISPLAY = 'False';
|
||||
|
||||
export const URL_PARAMS = {
|
||||
migrationState: {
|
||||
name: 'migration_state',
|
||||
type: 'string',
|
||||
},
|
||||
standalone: {
|
||||
name: 'standalone',
|
||||
type: 'number',
|
||||
|
|
|
@ -50,11 +50,9 @@ import newComponentFactory from 'src/dashboard/util/newComponentFactory';
|
|||
import { TIME_RANGE } from 'src/visualizations/FilterBox/FilterBox';
|
||||
import { URL_PARAMS } from 'src/constants';
|
||||
import { getUrlParam } from 'src/utils/urlUtils';
|
||||
import { FILTER_BOX_MIGRATION_STATES } from 'src/explore/constants';
|
||||
import { ResourceStatus } from 'src/hooks/apiResources/apiResources';
|
||||
import { FeatureFlag, isFeatureEnabled } from '../../featureFlags';
|
||||
import extractUrlParams from '../util/extractUrlParams';
|
||||
import getNativeFilterConfig from '../util/filterboxMigrationHelper';
|
||||
import { updateColorSchema } from './dashboardInfo';
|
||||
import { getChartIdsInFilterScope } from '../util/getChartIdsInFilterScope';
|
||||
import updateComponentParentsList from '../util/updateComponentParentsList';
|
||||
|
@ -63,14 +61,7 @@ import { FilterBarOrientation } from '../types';
|
|||
export const HYDRATE_DASHBOARD = 'HYDRATE_DASHBOARD';
|
||||
|
||||
export const hydrateDashboard =
|
||||
({
|
||||
history,
|
||||
dashboard,
|
||||
charts,
|
||||
filterboxMigrationState = FILTER_BOX_MIGRATION_STATES.NOOP,
|
||||
dataMask,
|
||||
activeTabs,
|
||||
}) =>
|
||||
({ history, dashboard, charts, dataMask, activeTabs }) =>
|
||||
(dispatch, getState) => {
|
||||
const { user, common, dashboardState } = getState();
|
||||
const { metadata, position_data: positionData } = dashboard;
|
||||
|
@ -232,25 +223,18 @@ export const hydrateDashboard =
|
|||
const componentId = chartIdToLayoutId[key];
|
||||
const directPathToFilter = (layout[componentId].parents || []).slice();
|
||||
directPathToFilter.push(componentId);
|
||||
if (
|
||||
[
|
||||
FILTER_BOX_MIGRATION_STATES.NOOP,
|
||||
FILTER_BOX_MIGRATION_STATES.SNOOZED,
|
||||
].includes(filterboxMigrationState)
|
||||
) {
|
||||
dashboardFilters[key] = {
|
||||
...dashboardFilter,
|
||||
chartId: key,
|
||||
componentId,
|
||||
datasourceId: slice.form_data.datasource,
|
||||
filterName: slice.slice_name,
|
||||
directPathToFilter,
|
||||
columns,
|
||||
labels,
|
||||
scopes: scopesByChartId,
|
||||
isDateFilter: Object.keys(columns).includes(TIME_RANGE),
|
||||
};
|
||||
}
|
||||
dashboardFilters[key] = {
|
||||
...dashboardFilter,
|
||||
chartId: key,
|
||||
componentId,
|
||||
datasourceId: slice.form_data.datasource,
|
||||
filterName: slice.slice_name,
|
||||
directPathToFilter,
|
||||
columns,
|
||||
labels,
|
||||
scopes: scopesByChartId,
|
||||
isDateFilter: Object.keys(columns).includes(TIME_RANGE),
|
||||
};
|
||||
}
|
||||
|
||||
// sync layout names with current slice names in case a slice was edited
|
||||
|
@ -319,28 +303,12 @@ export const hydrateDashboard =
|
|||
directPathToChild.push(directLinkComponentId);
|
||||
}
|
||||
|
||||
// should convert filter_box to filter component?
|
||||
let filterConfig = metadata?.native_filter_configuration || [];
|
||||
if (filterboxMigrationState === FILTER_BOX_MIGRATION_STATES.REVIEWING) {
|
||||
filterConfig = getNativeFilterConfig(
|
||||
charts,
|
||||
filterScopes,
|
||||
preselectFilters,
|
||||
);
|
||||
metadata.native_filter_configuration = filterConfig;
|
||||
metadata.show_native_filters = true;
|
||||
}
|
||||
const nativeFilters = getInitialNativeFilterState({
|
||||
filterConfig,
|
||||
filterConfig: metadata?.native_filter_configuration || [],
|
||||
});
|
||||
metadata.show_native_filters =
|
||||
dashboard?.metadata?.show_native_filters ??
|
||||
(isFeatureEnabled(FeatureFlag.DASHBOARD_NATIVE_FILTERS) &&
|
||||
[
|
||||
FILTER_BOX_MIGRATION_STATES.CONVERTED,
|
||||
FILTER_BOX_MIGRATION_STATES.REVIEWING,
|
||||
FILTER_BOX_MIGRATION_STATES.NOOP,
|
||||
].includes(filterboxMigrationState));
|
||||
metadata.show_native_filters = isFeatureEnabled(
|
||||
FeatureFlag.DASHBOARD_NATIVE_FILTERS,
|
||||
);
|
||||
|
||||
if (isFeatureEnabled(FeatureFlag.DASHBOARD_CROSS_FILTERS)) {
|
||||
// If user just added cross filter to dashboard it's not saving it scope on server,
|
||||
|
@ -465,7 +433,6 @@ export const hydrateDashboard =
|
|||
isRefreshing: false,
|
||||
isFiltersRefreshing: false,
|
||||
activeTabs: activeTabs || dashboardState?.activeTabs || [],
|
||||
filterboxMigrationState,
|
||||
datasetsStatus: ResourceStatus.LOADING,
|
||||
},
|
||||
dashboardLayout,
|
||||
|
|
|
@ -46,7 +46,6 @@ export function fetchAllSlicesFailed(error) {
|
|||
|
||||
export function fetchSlices(
|
||||
userId,
|
||||
excludeFilterBox,
|
||||
dispatch,
|
||||
filter_value,
|
||||
sortColumn = 'changed_on',
|
||||
|
@ -84,11 +83,7 @@ export function fetchSlices(
|
|||
})}`,
|
||||
})
|
||||
.then(({ json }) => {
|
||||
let { result } = json;
|
||||
// disable add filter_box viz to dashboard
|
||||
if (excludeFilterBox) {
|
||||
result = result.filter(slice => slice.viz_type !== 'filter_box');
|
||||
}
|
||||
const { result } = json;
|
||||
result.forEach(slice => {
|
||||
let form_data = JSON.parse(slice.params);
|
||||
form_data = {
|
||||
|
@ -135,46 +130,31 @@ export function fetchSlices(
|
|||
);
|
||||
}
|
||||
|
||||
export function fetchAllSlices(userId, excludeFilterBox = false) {
|
||||
export function fetchAllSlices(userId) {
|
||||
return (dispatch, getState) => {
|
||||
const { sliceEntities } = getState();
|
||||
if (sliceEntities.lastUpdated === 0) {
|
||||
dispatch(fetchAllSlicesStarted());
|
||||
return fetchSlices(userId, excludeFilterBox, dispatch, undefined);
|
||||
return fetchSlices(userId, dispatch, undefined);
|
||||
}
|
||||
|
||||
return dispatch(setAllSlices(sliceEntities.slices));
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchSortedSlices(
|
||||
userId,
|
||||
excludeFilterBox = false,
|
||||
order_column,
|
||||
) {
|
||||
export function fetchSortedSlices(userId, order_column) {
|
||||
return dispatch => {
|
||||
dispatch(fetchAllSlicesStarted());
|
||||
return fetchSlices(
|
||||
userId,
|
||||
excludeFilterBox,
|
||||
dispatch,
|
||||
undefined,
|
||||
order_column,
|
||||
);
|
||||
return fetchSlices(userId, dispatch, undefined, order_column);
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchFilteredSlices(
|
||||
userId,
|
||||
excludeFilterBox = false,
|
||||
filter_value,
|
||||
) {
|
||||
export function fetchFilteredSlices(userId, filter_value) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(fetchAllSlicesStarted());
|
||||
const { sliceEntities } = getState();
|
||||
return fetchSlices(
|
||||
userId,
|
||||
excludeFilterBox,
|
||||
dispatch,
|
||||
filter_value,
|
||||
undefined,
|
||||
|
|
|
@ -67,7 +67,7 @@ describe('slice entity actions', () => {
|
|||
describe('fetchFilteredSlices', () => {
|
||||
it('should dispatch an fetchAllSlicesStarted action', async () => {
|
||||
const { dispatch, getState } = setup();
|
||||
const thunk1 = fetchFilteredSlices('userId', false, 'filter_value');
|
||||
const thunk1 = fetchFilteredSlices('userId', 'filter_value');
|
||||
await thunk1(dispatch, getState);
|
||||
expect(dispatch.getCall(0).args[0]).toEqual({
|
||||
type: FETCH_ALL_SLICES_STARTED,
|
||||
|
@ -82,7 +82,7 @@ describe('slice entity actions', () => {
|
|||
sliceEntities: { slices: {}, lastUpdated: 1 },
|
||||
});
|
||||
|
||||
const thunk1 = fetchAllSlices('userId', false, 'filter_value');
|
||||
const thunk1 = fetchAllSlices('userId', 'filter_value');
|
||||
await thunk1(dispatch, getState);
|
||||
|
||||
expect(spy.get.callCount).toBe(0);
|
||||
|
|
|
@ -18,11 +18,10 @@
|
|||
*/
|
||||
import { useSelector } from 'react-redux';
|
||||
import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags';
|
||||
import { useCallback, useEffect, useState, useContext } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { URL_PARAMS } from 'src/constants';
|
||||
import { getUrlParam } from 'src/utils/urlUtils';
|
||||
import { RootState } from 'src/dashboard/types';
|
||||
import { MigrationContext } from 'src/dashboard/containers/DashboardPage';
|
||||
import {
|
||||
useFilters,
|
||||
useNativeFiltersDataMask,
|
||||
|
@ -30,7 +29,6 @@ import {
|
|||
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export const useNativeFilters = () => {
|
||||
const filterboxMigrationState = useContext(MigrationContext);
|
||||
const [isInitialized, setIsInitialized] = useState(false);
|
||||
const showNativeFilters = useSelector<RootState, boolean>(
|
||||
state =>
|
||||
|
@ -78,15 +76,13 @@ export const useNativeFilters = () => {
|
|||
useEffect(() => {
|
||||
if (
|
||||
expandFilters === false ||
|
||||
(filterValues.length === 0 &&
|
||||
nativeFiltersEnabled &&
|
||||
['CONVERTED', 'REVIEWING', 'NOOP'].includes(filterboxMigrationState))
|
||||
(filterValues.length === 0 && nativeFiltersEnabled)
|
||||
) {
|
||||
toggleDashboardFiltersOpen(false);
|
||||
} else {
|
||||
toggleDashboardFiltersOpen(true);
|
||||
}
|
||||
}, [filterValues.length, filterboxMigrationState]);
|
||||
}, [filterValues.length]);
|
||||
|
||||
useEffect(() => {
|
||||
if (showDashboard) {
|
||||
|
|
|
@ -35,7 +35,6 @@ import downloadAsImage from 'src/utils/downloadAsImage';
|
|||
import getDashboardUrl from 'src/dashboard/util/getDashboardUrl';
|
||||
import { getActiveFilters } from 'src/dashboard/util/activeDashboardFilters';
|
||||
import { getUrlParam } from 'src/utils/urlUtils';
|
||||
import { FILTER_BOX_MIGRATION_STATES } from 'src/explore/constants';
|
||||
import { LOG_ACTIONS_DASHBOARD_DOWNLOAD_AS_IMAGE } from 'src/logger/LogUtils';
|
||||
|
||||
const propTypes = {
|
||||
|
@ -70,11 +69,6 @@ const propTypes = {
|
|||
refreshLimit: PropTypes.number,
|
||||
refreshWarning: PropTypes.string,
|
||||
lastModifiedTime: PropTypes.number.isRequired,
|
||||
filterboxMigrationState: PropTypes.oneOf(
|
||||
Object.keys(FILTER_BOX_MIGRATION_STATES).map(
|
||||
key => FILTER_BOX_MIGRATION_STATES[key],
|
||||
),
|
||||
),
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
|
@ -82,7 +76,6 @@ const defaultProps = {
|
|||
colorScheme: undefined,
|
||||
refreshLimit: 0,
|
||||
refreshWarning: null,
|
||||
filterboxMigrationState: FILTER_BOX_MIGRATION_STATES.NOOP,
|
||||
};
|
||||
|
||||
const MENU_KEYS = {
|
||||
|
@ -230,7 +223,6 @@ class HeaderActionsDropdown extends React.PureComponent {
|
|||
lastModifiedTime,
|
||||
addSuccessToast,
|
||||
addDangerToast,
|
||||
filterboxMigrationState,
|
||||
setIsDropdownVisible,
|
||||
isDropdownVisible,
|
||||
...rest
|
||||
|
@ -378,15 +370,14 @@ class HeaderActionsDropdown extends React.PureComponent {
|
|||
</Menu>
|
||||
)
|
||||
) : null}
|
||||
{editMode &&
|
||||
filterboxMigrationState !== FILTER_BOX_MIGRATION_STATES.CONVERTED && (
|
||||
<Menu.Item key={MENU_KEYS.SET_FILTER_MAPPING}>
|
||||
<FilterScopeModal
|
||||
className="m-r-5"
|
||||
triggerNode={t('Set filter mapping')}
|
||||
/>
|
||||
</Menu.Item>
|
||||
)}
|
||||
{editMode && (
|
||||
<Menu.Item key={MENU_KEYS.SET_FILTER_MAPPING}>
|
||||
<FilterScopeModal
|
||||
className="m-r-5"
|
||||
triggerNode={t('Set filter mapping')}
|
||||
/>
|
||||
</Menu.Item>
|
||||
)}
|
||||
|
||||
<Menu.Item key={MENU_KEYS.AUTOREFRESH_MODAL}>
|
||||
<RefreshIntervalModal
|
||||
|
|
|
@ -53,7 +53,6 @@ import {
|
|||
import setPeriodicRunner, {
|
||||
stopPeriodicRender,
|
||||
} from 'src/dashboard/util/setPeriodicRunner';
|
||||
import { FILTER_BOX_MIGRATION_STATES } from 'src/explore/constants';
|
||||
import { PageHeaderWithActions } from 'src/components/PageHeaderWithActions';
|
||||
import { DashboardEmbedModal } from '../DashboardEmbedControls';
|
||||
import OverwriteConfirm from '../OverwriteConfirm';
|
||||
|
@ -463,18 +462,13 @@ class Header extends React.PureComponent {
|
|||
shouldPersistRefreshFrequency,
|
||||
setRefreshFrequency,
|
||||
lastModifiedTime,
|
||||
filterboxMigrationState,
|
||||
logEvent,
|
||||
} = this.props;
|
||||
|
||||
const userCanEdit =
|
||||
dashboardInfo.dash_edit_perm &&
|
||||
filterboxMigrationState !== FILTER_BOX_MIGRATION_STATES.REVIEWING &&
|
||||
!dashboardInfo.is_managed_externally;
|
||||
dashboardInfo.dash_edit_perm && !dashboardInfo.is_managed_externally;
|
||||
const userCanShare = dashboardInfo.dash_share_perm;
|
||||
const userCanSaveAs =
|
||||
dashboardInfo.dash_save_perm &&
|
||||
filterboxMigrationState !== FILTER_BOX_MIGRATION_STATES.REVIEWING;
|
||||
const userCanSaveAs = dashboardInfo.dash_save_perm;
|
||||
const userCanCurate =
|
||||
isFeatureEnabled(FeatureFlag.EMBEDDED_SUPERSET) &&
|
||||
findPermission('can_set_embedded', 'Dashboard', user.roles);
|
||||
|
@ -680,7 +674,6 @@ class Header extends React.PureComponent {
|
|||
refreshLimit={refreshLimit}
|
||||
refreshWarning={refreshWarning}
|
||||
lastModifiedTime={lastModifiedTime}
|
||||
filterboxMigrationState={filterboxMigrationState}
|
||||
isDropdownVisible={this.state.isDropdownVisible}
|
||||
setIsDropdownVisible={this.setIsDropdownVisible}
|
||||
logEvent={logEvent}
|
||||
|
|
|
@ -22,13 +22,7 @@ import PropTypes from 'prop-types';
|
|||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
import { FixedSizeList as List } from 'react-window';
|
||||
import { createFilter } from 'react-search-input';
|
||||
import {
|
||||
t,
|
||||
styled,
|
||||
isFeatureEnabled,
|
||||
FeatureFlag,
|
||||
css,
|
||||
} from '@superset-ui/core';
|
||||
import { t, styled, css } from '@superset-ui/core';
|
||||
import { Input } from 'src/components/Input';
|
||||
import { Select } from 'src/components';
|
||||
import Loading from 'src/components/Loading';
|
||||
|
@ -43,7 +37,6 @@ import {
|
|||
NEW_COMPONENTS_SOURCE_ID,
|
||||
} from 'src/dashboard/util/constants';
|
||||
import { slicePropShape } from 'src/dashboard/util/propShapes';
|
||||
import { FILTER_BOX_MIGRATION_STATES } from 'src/explore/constants';
|
||||
import _ from 'lodash';
|
||||
import AddSliceCard from './AddSliceCard';
|
||||
import AddSliceDragPreview from './dnd/AddSliceDragPreview';
|
||||
|
@ -58,7 +51,6 @@ const propTypes = {
|
|||
userId: PropTypes.string.isRequired,
|
||||
selectedSliceIds: PropTypes.arrayOf(PropTypes.number),
|
||||
editMode: PropTypes.bool,
|
||||
filterboxMigrationState: FILTER_BOX_MIGRATION_STATES,
|
||||
dashboardId: PropTypes.number,
|
||||
};
|
||||
|
||||
|
@ -66,7 +58,6 @@ const defaultProps = {
|
|||
selectedSliceIds: [],
|
||||
editMode: false,
|
||||
errorMessage: '',
|
||||
filterboxMigrationState: FILTER_BOX_MIGRATION_STATES.NOOP,
|
||||
};
|
||||
|
||||
const KEYS_TO_FILTERS = ['slice_name', 'viz_type', 'datasource_name'];
|
||||
|
@ -150,12 +141,7 @@ class SliceAdder extends React.Component {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { userId, filterboxMigrationState } = this.props;
|
||||
this.slicesRequest = this.props.fetchAllSlices(
|
||||
userId,
|
||||
isFeatureEnabled(FeatureFlag.ENABLE_FILTER_BOX_MIGRATION) &&
|
||||
filterboxMigrationState !== FILTER_BOX_MIGRATION_STATES.SNOOZED,
|
||||
);
|
||||
this.slicesRequest = this.props.fetchAllSlices(this.props.userId);
|
||||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||
|
@ -198,13 +184,8 @@ class SliceAdder extends React.Component {
|
|||
handleChange = _.debounce(value => {
|
||||
this.searchUpdated(value);
|
||||
|
||||
const { userId, filterboxMigrationState } = this.props;
|
||||
this.slicesRequest = this.props.fetchFilteredSlices(
|
||||
userId,
|
||||
isFeatureEnabled(FeatureFlag.ENABLE_FILTER_BOX_MIGRATION) &&
|
||||
filterboxMigrationState !== FILTER_BOX_MIGRATION_STATES.SNOOZED,
|
||||
value,
|
||||
);
|
||||
const { userId } = this.props;
|
||||
this.slicesRequest = this.props.fetchFilteredSlices(userId, value);
|
||||
}, 300);
|
||||
|
||||
searchUpdated(searchTerm) {
|
||||
|
@ -226,13 +207,8 @@ class SliceAdder extends React.Component {
|
|||
),
|
||||
}));
|
||||
|
||||
const { userId, filterboxMigrationState } = this.props;
|
||||
this.slicesRequest = this.props.fetchSortedSlices(
|
||||
userId,
|
||||
isFeatureEnabled(FeatureFlag.ENABLE_FILTER_BOX_MIGRATION) &&
|
||||
filterboxMigrationState !== FILTER_BOX_MIGRATION_STATES.SNOOZED,
|
||||
sortBy,
|
||||
);
|
||||
const { userId } = this.props;
|
||||
this.slicesRequest = this.props.fetchSortedSlices(userId, sortBy);
|
||||
}
|
||||
|
||||
rowRenderer({ key, index, style }) {
|
||||
|
|
|
@ -38,7 +38,6 @@ import {
|
|||
LOG_ACTIONS_FORCE_REFRESH_CHART,
|
||||
} from 'src/logger/LogUtils';
|
||||
import { areObjectsEqual } from 'src/reduxUtils';
|
||||
import { FILTER_BOX_MIGRATION_STATES } from 'src/explore/constants';
|
||||
import { postFormData } from 'src/explore/exploreUtils/formData';
|
||||
import { URL_PARAMS } from 'src/constants';
|
||||
|
||||
|
@ -70,11 +69,6 @@ const propTypes = {
|
|||
sliceName: PropTypes.string.isRequired,
|
||||
timeout: PropTypes.number.isRequired,
|
||||
maxRows: PropTypes.number.isRequired,
|
||||
filterboxMigrationState: PropTypes.oneOf(
|
||||
Object.keys(FILTER_BOX_MIGRATION_STATES).map(
|
||||
key => FILTER_BOX_MIGRATION_STATES[key],
|
||||
),
|
||||
),
|
||||
// all active filter fields in dashboard
|
||||
filters: PropTypes.object.isRequired,
|
||||
refreshChart: PropTypes.func.isRequired,
|
||||
|
@ -129,11 +123,6 @@ const ChartOverlay = styled.div`
|
|||
top: 0;
|
||||
left: 0;
|
||||
z-index: 5;
|
||||
|
||||
&.is-deactivated {
|
||||
opacity: 0.5;
|
||||
background-color: ${({ theme }) => theme.colors.grayscale.light1};
|
||||
}
|
||||
`;
|
||||
|
||||
const SliceContainer = styled.div`
|
||||
|
@ -405,7 +394,6 @@ class Chart extends React.Component {
|
|||
handleToggleFullSize,
|
||||
isFullSize,
|
||||
setControlValue,
|
||||
filterboxMigrationState,
|
||||
postTransformProps,
|
||||
datasetsStatus,
|
||||
isInView,
|
||||
|
@ -422,12 +410,6 @@ class Chart extends React.Component {
|
|||
|
||||
const { queriesResponse, chartUpdateEndTime, chartStatus } = chart;
|
||||
const isLoading = chartStatus === 'loading';
|
||||
const isDeactivatedViz =
|
||||
slice.viz_type === 'filter_box' &&
|
||||
[
|
||||
FILTER_BOX_MIGRATION_STATES.REVIEWING,
|
||||
FILTER_BOX_MIGRATION_STATES.CONVERTED,
|
||||
].includes(filterboxMigrationState);
|
||||
// eslint-disable-next-line camelcase
|
||||
const isCached = queriesResponse?.map(({ is_cached }) => is_cached) || [];
|
||||
const cachedDttm =
|
||||
|
@ -506,15 +488,15 @@ class Chart extends React.Component {
|
|||
isOverflowable && 'dashboard-chart--overflowable',
|
||||
)}
|
||||
>
|
||||
{(isLoading || isDeactivatedViz) && (
|
||||
{isLoading && (
|
||||
<ChartOverlay
|
||||
className={cx(isDeactivatedViz && 'is-deactivated')}
|
||||
style={{
|
||||
width,
|
||||
height: this.getChartHeight(),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<ChartContainer
|
||||
width={width}
|
||||
height={this.getChartHeight()}
|
||||
|
@ -538,8 +520,6 @@ class Chart extends React.Component {
|
|||
triggerQuery={chart.triggerQuery}
|
||||
vizType={slice.viz_type}
|
||||
setControlValue={setControlValue}
|
||||
isDeactivatedViz={isDeactivatedViz}
|
||||
filterboxMigrationState={filterboxMigrationState}
|
||||
postTransformProps={postTransformProps}
|
||||
datasetsStatus={datasetsStatus}
|
||||
isInView={isInView}
|
||||
|
|
|
@ -23,7 +23,6 @@ import { connect } from 'react-redux';
|
|||
import { LineEditableTabs } from 'src/components/Tabs';
|
||||
import { LOG_ACTIONS_SELECT_DASHBOARD_TAB } from 'src/logger/LogUtils';
|
||||
import { AntdModal } from 'src/components';
|
||||
import { FILTER_BOX_MIGRATION_STATES } from 'src/explore/constants';
|
||||
import DragDroppable from '../dnd/DragDroppable';
|
||||
import DragHandle from '../dnd/DragHandle';
|
||||
import DashboardComponent from '../../containers/DashboardComponent';
|
||||
|
@ -49,11 +48,6 @@ const propTypes = {
|
|||
renderHoverMenu: PropTypes.bool,
|
||||
directPathToChild: PropTypes.arrayOf(PropTypes.string),
|
||||
activeTabs: PropTypes.arrayOf(PropTypes.string),
|
||||
filterboxMigrationState: PropTypes.oneOf(
|
||||
Object.keys(FILTER_BOX_MIGRATION_STATES).map(
|
||||
key => FILTER_BOX_MIGRATION_STATES[key],
|
||||
),
|
||||
),
|
||||
|
||||
// actions (from DashboardComponent.jsx)
|
||||
logEvent: PropTypes.func.isRequired,
|
||||
|
@ -81,7 +75,6 @@ const defaultProps = {
|
|||
columnWidth: 0,
|
||||
activeTabs: [],
|
||||
directPathToChild: [],
|
||||
filterboxMigrationState: FILTER_BOX_MIGRATION_STATES.NOOP,
|
||||
setActiveTabs() {},
|
||||
onResizeStart() {},
|
||||
onResize() {},
|
||||
|
@ -136,10 +129,7 @@ export class Tabs extends React.PureComponent {
|
|||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (
|
||||
prevState.activeKey !== this.state.activeKey ||
|
||||
prevProps.filterboxMigrationState !== this.props.filterboxMigrationState
|
||||
) {
|
||||
if (prevState.activeKey !== this.state.activeKey) {
|
||||
this.props.setActiveTabs(this.state.activeKey, prevState.activeKey);
|
||||
}
|
||||
}
|
||||
|
@ -446,7 +436,6 @@ function mapStateToProps(state) {
|
|||
nativeFilters: state.nativeFilters,
|
||||
activeTabs: state.dashboardState.activeTabs,
|
||||
directPathToChild: state.dashboardState.directPathToChild,
|
||||
filterboxMigrationState: state.dashboardState.filterboxMigrationState,
|
||||
};
|
||||
}
|
||||
export default connect(mapStateToProps)(Tabs);
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
*/
|
||||
/* eslint-disable no-param-reassign */
|
||||
import { useSelector } from 'react-redux';
|
||||
import { filter, keyBy } from 'lodash';
|
||||
import {
|
||||
DataMaskState,
|
||||
DataMaskStateWithId,
|
||||
|
@ -27,10 +26,8 @@ import {
|
|||
Filters,
|
||||
FilterSets as FilterSetsType,
|
||||
} from '@superset-ui/core';
|
||||
import { useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { ChartsState, RootState } from 'src/dashboard/types';
|
||||
import { MigrationContext } from 'src/dashboard/containers/DashboardPage';
|
||||
import { FILTER_BOX_MIGRATION_STATES } from 'src/explore/constants';
|
||||
import { NATIVE_FILTER_PREFIX } from '../FiltersConfigModal/utils';
|
||||
|
||||
export const useFilterSets = () =>
|
||||
|
@ -102,30 +99,14 @@ export const useFilterUpdates = (
|
|||
export const useInitialization = () => {
|
||||
const [isInitialized, setIsInitialized] = useState<boolean>(false);
|
||||
const filters = useFilters();
|
||||
const filterboxMigrationState = useContext(MigrationContext);
|
||||
let charts = useSelector<RootState, ChartsState>(state => state.charts);
|
||||
const charts = useSelector<RootState, ChartsState>(state => state.charts);
|
||||
|
||||
// We need to know how much charts now shown on dashboard to know how many of all charts should be loaded
|
||||
let numberOfLoadingCharts = 0;
|
||||
if (!isInitialized) {
|
||||
// do not load filter_box in reviewing
|
||||
if (filterboxMigrationState === FILTER_BOX_MIGRATION_STATES.REVIEWING) {
|
||||
charts = keyBy(
|
||||
filter(charts, chart => chart.form_data?.viz_type !== 'filter_box'),
|
||||
'id',
|
||||
);
|
||||
const numberOfFilterbox = document.querySelectorAll(
|
||||
'[data-test-viz-type="filter_box"]',
|
||||
).length;
|
||||
|
||||
numberOfLoadingCharts =
|
||||
document.querySelectorAll('[data-ui-anchor="chart"]').length -
|
||||
numberOfFilterbox;
|
||||
} else {
|
||||
numberOfLoadingCharts = document.querySelectorAll(
|
||||
'[data-ui-anchor="chart"]',
|
||||
).length;
|
||||
}
|
||||
numberOfLoadingCharts = document.querySelectorAll(
|
||||
'[data-ui-anchor="chart"]',
|
||||
).length;
|
||||
}
|
||||
useEffect(() => {
|
||||
if (isInitialized) {
|
||||
|
|
|
@ -100,7 +100,6 @@ function mapStateToProps(
|
|||
filterState: dataMask[id]?.filterState,
|
||||
maxRows: common.conf.SQL_MAX_ROW,
|
||||
setControlValue,
|
||||
filterboxMigrationState: dashboardState.filterboxMigrationState,
|
||||
datasetsStatus,
|
||||
emitCrossFilters: !!dashboardInfo.crossFiltersEnabled,
|
||||
};
|
||||
|
|
|
@ -99,7 +99,6 @@ function mapStateToProps({
|
|||
slug: dashboardInfo.slug,
|
||||
metadata: dashboardInfo.metadata,
|
||||
reports,
|
||||
filterboxMigrationState: dashboardState.filterboxMigrationState,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React, { FC, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import React, { FC, useEffect, useMemo, useRef } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import {
|
||||
CategoricalColorNamespace,
|
||||
|
@ -25,14 +25,11 @@ import {
|
|||
isFeatureEnabled,
|
||||
SharedLabelColorSource,
|
||||
t,
|
||||
useTheme,
|
||||
} from '@superset-ui/core';
|
||||
import pick from 'lodash/pick';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { Global } from '@emotion/react';
|
||||
import { useToasts } from 'src/components/MessageToasts/withToasts';
|
||||
import Loading from 'src/components/Loading';
|
||||
import FilterBoxMigrationModal from 'src/dashboard/components/FilterBoxMigrationModal';
|
||||
import {
|
||||
useDashboard,
|
||||
useDashboardCharts,
|
||||
|
@ -42,37 +39,25 @@ import { hydrateDashboard } from 'src/dashboard/actions/hydrate';
|
|||
import { setDatasources } from 'src/dashboard/actions/datasources';
|
||||
import injectCustomCss from 'src/dashboard/util/injectCustomCss';
|
||||
import setupPlugins from 'src/setup/setupPlugins';
|
||||
import { UserWithPermissionsAndRoles } from 'src/types/bootstrapTypes';
|
||||
import { addWarningToast } from 'src/components/MessageToasts/actions';
|
||||
|
||||
import {
|
||||
getItem,
|
||||
LocalStorageKeys,
|
||||
setItem,
|
||||
} from 'src/utils/localStorageHelpers';
|
||||
import {
|
||||
FILTER_BOX_MIGRATION_STATES,
|
||||
FILTER_BOX_TRANSITION_SNOOZE_DURATION,
|
||||
} from 'src/explore/constants';
|
||||
import { URL_PARAMS } from 'src/constants';
|
||||
import { getUrlParam } from 'src/utils/urlUtils';
|
||||
import { canUserEditDashboard } from 'src/dashboard/util/permissionUtils';
|
||||
import { getFilterSets } from 'src/dashboard/actions/nativeFilters';
|
||||
import { setDatasetsStatus } from 'src/dashboard/actions/dashboardState';
|
||||
import {
|
||||
getFilterValue,
|
||||
getPermalinkValue,
|
||||
} from 'src/dashboard/components/nativeFilters/FilterBar/keyValue';
|
||||
import { filterCardPopoverStyle, headerStyles } from 'src/dashboard/styles';
|
||||
import { DashboardContextForExplore } from 'src/types/DashboardContextForExplore';
|
||||
import shortid from 'shortid';
|
||||
import { RootState } from '../types';
|
||||
import { getActiveFilters } from '../util/activeDashboardFilters';
|
||||
|
||||
export const MigrationContext = React.createContext(
|
||||
FILTER_BOX_MIGRATION_STATES.NOOP,
|
||||
);
|
||||
|
||||
export const DashboardPageIdContext = React.createContext('');
|
||||
|
||||
setupPlugins();
|
||||
|
@ -156,11 +141,7 @@ const useSyncDashboardStateWithLocalStorage = () => {
|
|||
|
||||
export const DashboardPage: FC<PageProps> = ({ idOrSlug }: PageProps) => {
|
||||
const dispatch = useDispatch();
|
||||
const theme = useTheme();
|
||||
const history = useHistory();
|
||||
const user = useSelector<any, UserWithPermissionsAndRoles>(
|
||||
state => state.user,
|
||||
);
|
||||
const dashboardPageId = useSyncDashboardStateWithLocalStorage();
|
||||
const { addDangerToast } = useToasts();
|
||||
const { result: dashboard, error: dashboardApiError } =
|
||||
|
@ -176,16 +157,7 @@ export const DashboardPage: FC<PageProps> = ({ idOrSlug }: PageProps) => {
|
|||
|
||||
const error = dashboardApiError || chartsApiError;
|
||||
const readyToRender = Boolean(dashboard && charts);
|
||||
const migrationStateParam = getUrlParam(
|
||||
URL_PARAMS.migrationState,
|
||||
) as FILTER_BOX_MIGRATION_STATES;
|
||||
const isMigrationEnabled = isFeatureEnabled(
|
||||
FeatureFlag.ENABLE_FILTER_BOX_MIGRATION,
|
||||
);
|
||||
const { dashboard_title, css, metadata, id = 0 } = dashboard || {};
|
||||
const [filterboxMigrationState, setFilterboxMigrationState] = useState(
|
||||
migrationStateParam || FILTER_BOX_MIGRATION_STATES.NOOP,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
// mark tab id as redundant when user closes browser tab - a new id will be
|
||||
|
@ -210,67 +182,6 @@ export const DashboardPage: FC<PageProps> = ({ idOrSlug }: PageProps) => {
|
|||
dispatch(setDatasetsStatus(status));
|
||||
}, [dispatch, status]);
|
||||
|
||||
useEffect(() => {
|
||||
// should convert filter_box to filter component?
|
||||
const hasFilterBox = charts?.some(
|
||||
chart => chart.form_data?.viz_type === 'filter_box',
|
||||
);
|
||||
const canEdit = dashboard && canUserEditDashboard(dashboard, user);
|
||||
|
||||
if (canEdit) {
|
||||
// can user edit dashboard?
|
||||
if (metadata?.native_filter_configuration) {
|
||||
setFilterboxMigrationState(
|
||||
isMigrationEnabled
|
||||
? FILTER_BOX_MIGRATION_STATES.CONVERTED
|
||||
: FILTER_BOX_MIGRATION_STATES.NOOP,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// set filterbox migration state if has filter_box in the dash:
|
||||
if (hasFilterBox) {
|
||||
if (isMigrationEnabled) {
|
||||
// has url param?
|
||||
if (
|
||||
migrationStateParam &&
|
||||
Object.values(FILTER_BOX_MIGRATION_STATES).includes(
|
||||
migrationStateParam,
|
||||
)
|
||||
) {
|
||||
setFilterboxMigrationState(migrationStateParam);
|
||||
return;
|
||||
}
|
||||
|
||||
// has cookie?
|
||||
const snoozeDash = getItem(
|
||||
LocalStorageKeys.filter_box_transition_snoozed_at,
|
||||
{},
|
||||
);
|
||||
if (
|
||||
Date.now() - (snoozeDash[id] || 0) <
|
||||
FILTER_BOX_TRANSITION_SNOOZE_DURATION
|
||||
) {
|
||||
setFilterboxMigrationState(FILTER_BOX_MIGRATION_STATES.SNOOZED);
|
||||
return;
|
||||
}
|
||||
|
||||
setFilterboxMigrationState(FILTER_BOX_MIGRATION_STATES.UNDECIDED);
|
||||
} else if (isFeatureEnabled(FeatureFlag.DASHBOARD_NATIVE_FILTERS)) {
|
||||
dispatch(
|
||||
addWarningToast(
|
||||
t(
|
||||
'filter_box will be deprecated ' +
|
||||
'in a future version of Superset. ' +
|
||||
'Please replace filter_box by dashboard filter components.',
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [readyToRender]);
|
||||
|
||||
useEffect(() => {
|
||||
// eslint-disable-next-line consistent-return
|
||||
async function getDataMaskApplied() {
|
||||
|
@ -308,7 +219,6 @@ export const DashboardPage: FC<PageProps> = ({ idOrSlug }: PageProps) => {
|
|||
dashboard,
|
||||
charts,
|
||||
activeTabs,
|
||||
filterboxMigrationState,
|
||||
dataMask,
|
||||
}),
|
||||
);
|
||||
|
@ -317,7 +227,7 @@ export const DashboardPage: FC<PageProps> = ({ idOrSlug }: PageProps) => {
|
|||
}
|
||||
if (id) getDataMaskApplied();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [readyToRender, filterboxMigrationState]);
|
||||
}, [readyToRender]);
|
||||
|
||||
useEffect(() => {
|
||||
if (dashboard_title) {
|
||||
|
@ -364,37 +274,9 @@ export const DashboardPage: FC<PageProps> = ({ idOrSlug }: PageProps) => {
|
|||
if (!readyToRender) return <Loading />;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Global styles={[filterCardPopoverStyle(theme), headerStyles(theme)]} />
|
||||
<FilterBoxMigrationModal
|
||||
show={filterboxMigrationState === FILTER_BOX_MIGRATION_STATES.UNDECIDED}
|
||||
hideFooter={!isMigrationEnabled}
|
||||
onHide={() => {
|
||||
// cancel button: only snooze this visit
|
||||
setFilterboxMigrationState(FILTER_BOX_MIGRATION_STATES.SNOOZED);
|
||||
}}
|
||||
onClickReview={() => {
|
||||
setFilterboxMigrationState(FILTER_BOX_MIGRATION_STATES.REVIEWING);
|
||||
}}
|
||||
onClickSnooze={() => {
|
||||
const snoozedDash = getItem(
|
||||
LocalStorageKeys.filter_box_transition_snoozed_at,
|
||||
{},
|
||||
);
|
||||
setItem(LocalStorageKeys.filter_box_transition_snoozed_at, {
|
||||
...snoozedDash,
|
||||
[id]: Date.now(),
|
||||
});
|
||||
setFilterboxMigrationState(FILTER_BOX_MIGRATION_STATES.SNOOZED);
|
||||
}}
|
||||
/>
|
||||
|
||||
<MigrationContext.Provider value={filterboxMigrationState}>
|
||||
<DashboardPageIdContext.Provider value={dashboardPageId}>
|
||||
<DashboardContainer />
|
||||
</DashboardPageIdContext.Provider>
|
||||
</MigrationContext.Provider>
|
||||
</>
|
||||
<DashboardPageIdContext.Provider value={dashboardPageId}>
|
||||
<DashboardContainer />
|
||||
</DashboardPageIdContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -40,7 +40,6 @@ function mapStateToProps(
|
|||
errorMessage: sliceEntities.errorMessage,
|
||||
lastUpdated: sliceEntities.lastUpdated,
|
||||
editMode: dashboardState.editMode,
|
||||
filterboxMigrationState: dashboardState.filterboxMigrationState,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,142 +0,0 @@
|
|||
/**
|
||||
* 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 getNativeFilterConfig from './filterboxMigrationHelper';
|
||||
|
||||
const regionFilter = {
|
||||
cache_timeout: null,
|
||||
changed_on: '2021-10-07 11:57:48.355047',
|
||||
description: null,
|
||||
description_markeddown: '',
|
||||
form_data: {
|
||||
compare_lag: '10',
|
||||
compare_suffix: 'o10Y',
|
||||
country_fieldtype: 'cca3',
|
||||
datasource: '1__table',
|
||||
date_filter: false,
|
||||
entity: 'country_code',
|
||||
filter_configs: [
|
||||
{
|
||||
asc: false,
|
||||
clearable: true,
|
||||
column: 'region',
|
||||
key: '2s98dfu',
|
||||
metric: 'sum__SP_POP_TOTL',
|
||||
multiple: false,
|
||||
},
|
||||
{
|
||||
asc: false,
|
||||
clearable: true,
|
||||
column: 'country_name',
|
||||
key: 'li3j2lk',
|
||||
metric: 'sum__SP_POP_TOTL',
|
||||
multiple: true,
|
||||
},
|
||||
],
|
||||
granularity_sqla: 'year',
|
||||
groupby: [],
|
||||
limit: '25',
|
||||
markup_type: 'markdown',
|
||||
row_limit: 50000,
|
||||
show_bubbles: true,
|
||||
slice_id: 32,
|
||||
time_range: '2014-01-01 : 2014-01-02',
|
||||
viz_type: 'filter_box',
|
||||
},
|
||||
modified: '<bound method AuditMixinNullable.modified of Region Filter>',
|
||||
slice_name: 'Region Filter',
|
||||
slice_url: '/explore/?form_data=%7B%22slice_id%22%3A%2032%7D',
|
||||
slice_id: 32,
|
||||
};
|
||||
const chart1 = {
|
||||
cache_timeout: null,
|
||||
changed_on: '2021-09-07 18:05:18.896212',
|
||||
description: null,
|
||||
description_markeddown: '',
|
||||
form_data: {
|
||||
compare_lag: '10',
|
||||
compare_suffix: 'over 10Y',
|
||||
country_fieldtype: 'cca3',
|
||||
datasource: '1__table',
|
||||
entity: 'country_code',
|
||||
granularity_sqla: 'year',
|
||||
groupby: [],
|
||||
limit: '25',
|
||||
markup_type: 'markdown',
|
||||
metric: 'sum__SP_POP_TOTL',
|
||||
row_limit: 50000,
|
||||
show_bubbles: true,
|
||||
slice_id: 33,
|
||||
time_range: '2000 : 2014-01-02',
|
||||
viz_type: 'big_number',
|
||||
},
|
||||
modified: "<bound method AuditMixinNullable.modified of World's Population>",
|
||||
slice_name: "World's Population",
|
||||
slice_url: '/explore/?form_data=%7B%22slice_id%22%3A%2033%7D',
|
||||
slice_id: 33,
|
||||
};
|
||||
const chartData = [regionFilter, chart1];
|
||||
const preselectedFilters = {
|
||||
'32': {
|
||||
region: ['East Asia & Pacific'],
|
||||
},
|
||||
};
|
||||
|
||||
test('should convert filter_box config to dashboard native filter config', () => {
|
||||
const filterConfig = getNativeFilterConfig(chartData, {}, {});
|
||||
// convert to 2 components
|
||||
expect(filterConfig.length).toEqual(2);
|
||||
|
||||
expect(filterConfig[0].id).toBeDefined();
|
||||
expect(filterConfig[0].filterType).toBe('filter_select');
|
||||
expect(filterConfig[0].name).toBe('region');
|
||||
expect(filterConfig[0].targets).toEqual([
|
||||
{ column: { name: 'region' }, datasetId: 1 },
|
||||
]);
|
||||
expect(filterConfig[0].scope).toEqual({
|
||||
excluded: [],
|
||||
rootPath: ['ROOT_ID'],
|
||||
});
|
||||
|
||||
expect(filterConfig[1].id).toBeDefined();
|
||||
expect(filterConfig[1].filterType).toBe('filter_select');
|
||||
expect(filterConfig[1].name).toBe('country_name');
|
||||
expect(filterConfig[1].targets).toEqual([
|
||||
{ column: { name: 'country_name' }, datasetId: 1 },
|
||||
]);
|
||||
expect(filterConfig[1].scope).toEqual({
|
||||
excluded: [],
|
||||
rootPath: ['ROOT_ID'],
|
||||
});
|
||||
});
|
||||
|
||||
test('should convert preselected filters', () => {
|
||||
const filterConfig = getNativeFilterConfig(chartData, {}, preselectedFilters);
|
||||
const { defaultDataMask } = filterConfig[0];
|
||||
expect(defaultDataMask.filterState).toEqual({
|
||||
value: ['East Asia & Pacific'],
|
||||
});
|
||||
expect(defaultDataMask.extraFormData?.filters).toEqual([
|
||||
{
|
||||
col: 'region',
|
||||
op: 'IN',
|
||||
val: ['East Asia & Pacific'],
|
||||
},
|
||||
]);
|
||||
});
|
|
@ -1,423 +0,0 @@
|
|||
/**
|
||||
* 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 shortid from 'shortid';
|
||||
import { find, isEmpty } from 'lodash';
|
||||
|
||||
import {
|
||||
FILTER_CONFIG_ATTRIBUTES,
|
||||
TIME_FILTER_LABELS,
|
||||
TIME_FILTER_MAP,
|
||||
} from 'src/explore/constants';
|
||||
import { DASHBOARD_FILTER_SCOPE_GLOBAL } from 'src/dashboard/reducers/dashboardFilters';
|
||||
import { Filter, NativeFilterType, TimeGranularity } from '@superset-ui/core';
|
||||
import { getChartIdsInFilterBoxScope } from './activeDashboardFilters';
|
||||
import getFilterConfigsFromFormdata from './getFilterConfigsFromFormdata';
|
||||
|
||||
interface FilterConfig {
|
||||
asc: boolean;
|
||||
clearable: boolean;
|
||||
column: string;
|
||||
defaultValue?: any;
|
||||
key: string;
|
||||
label?: string;
|
||||
metric: string;
|
||||
multiple: boolean;
|
||||
}
|
||||
|
||||
interface SliceData {
|
||||
slice_id: number;
|
||||
form_data: {
|
||||
adhoc_filters?: [];
|
||||
datasource: string;
|
||||
date_filter?: boolean;
|
||||
filter_configs?: FilterConfig[];
|
||||
granularity?: string;
|
||||
granularity_sqla?: string;
|
||||
time_grain_sqla?: string;
|
||||
time_range?: string;
|
||||
show_sqla_time_column?: boolean;
|
||||
show_sqla_time_granularity?: boolean;
|
||||
viz_type: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface FilterScopeType {
|
||||
scope: string[];
|
||||
immune: number[];
|
||||
}
|
||||
|
||||
interface FilterScopesMetadata {
|
||||
[key: string]: {
|
||||
[key: string]: FilterScopeType;
|
||||
};
|
||||
}
|
||||
|
||||
interface PreselectedFilterColumn {
|
||||
[key: string]: boolean | string | number | string[] | number[];
|
||||
}
|
||||
|
||||
interface PreselectedFiltersMetadata {
|
||||
[key: string]: PreselectedFilterColumn;
|
||||
}
|
||||
|
||||
interface FilterBoxToFilterComponentMap {
|
||||
[key: string]: {
|
||||
[key: string]: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface FilterBoxDependencyMap {
|
||||
[key: string]: {
|
||||
[key: string]: number[];
|
||||
};
|
||||
}
|
||||
|
||||
enum FILTER_COMPONENT_FILTER_TYPES {
|
||||
FILTER_TIME = 'filter_time',
|
||||
FILTER_TIMEGRAIN = 'filter_timegrain',
|
||||
FILTER_TIMECOLUMN = 'filter_timecolumn',
|
||||
FILTER_SELECT = 'filter_select',
|
||||
FILTER_RANGE = 'filter_range',
|
||||
}
|
||||
|
||||
const getPreselectedValuesFromDashboard =
|
||||
(preselectedFilters: PreselectedFiltersMetadata) =>
|
||||
(filterKey: string, column: string) => {
|
||||
if (preselectedFilters[filterKey]?.[column]) {
|
||||
// overwrite default values by dashboard default_filters
|
||||
return preselectedFilters[filterKey][column];
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const getFilterBoxDefaultValues = (config: FilterConfig) => {
|
||||
let defaultValues = config[FILTER_CONFIG_ATTRIBUTES.DEFAULT_VALUE];
|
||||
|
||||
// treat empty string as null (no default value)
|
||||
if (defaultValues === '') {
|
||||
defaultValues = null;
|
||||
}
|
||||
|
||||
// defaultValue could be ; separated values,
|
||||
// could be null or ''
|
||||
if (defaultValues && config[FILTER_CONFIG_ATTRIBUTES.MULTIPLE]) {
|
||||
defaultValues = config.defaultValue.split(';');
|
||||
}
|
||||
|
||||
return defaultValues;
|
||||
};
|
||||
|
||||
const setValuesInArray = (value1: any, value2: any) => {
|
||||
if (!isEmpty(value1)) {
|
||||
return [value1];
|
||||
}
|
||||
if (!isEmpty(value2)) {
|
||||
return [value2];
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
const getFilterboxDependencies = (filterScopes: FilterScopesMetadata) => {
|
||||
const filterFieldsDependencies: FilterBoxDependencyMap = {};
|
||||
const filterChartIds: number[] = Object.keys(filterScopes).map(key =>
|
||||
parseInt(key, 10),
|
||||
);
|
||||
Object.entries(filterScopes).forEach(([key, filterFields]) => {
|
||||
filterFieldsDependencies[key] = {};
|
||||
Object.entries(filterFields).forEach(([filterField, filterScope]) => {
|
||||
filterFieldsDependencies[key][filterField] = getChartIdsInFilterBoxScope({
|
||||
filterScope,
|
||||
}).filter(
|
||||
chartId => filterChartIds.includes(chartId) && String(chartId) !== key,
|
||||
);
|
||||
});
|
||||
});
|
||||
return filterFieldsDependencies;
|
||||
};
|
||||
|
||||
export default function getNativeFilterConfig(
|
||||
chartData: SliceData[] = [],
|
||||
filterScopes: FilterScopesMetadata = {},
|
||||
preselectFilters: PreselectedFiltersMetadata = {},
|
||||
): Filter[] {
|
||||
const filterConfig: Filter[] = [];
|
||||
const filterBoxToFilterComponentMap: FilterBoxToFilterComponentMap = {};
|
||||
|
||||
chartData.forEach(slice => {
|
||||
const key = String(slice.slice_id);
|
||||
|
||||
if (slice.form_data.viz_type === 'filter_box') {
|
||||
filterBoxToFilterComponentMap[key] = {};
|
||||
const configs = getFilterConfigsFromFormdata(slice.form_data);
|
||||
let { columns } = configs;
|
||||
if (preselectFilters[key]) {
|
||||
Object.keys(columns).forEach(col => {
|
||||
if (preselectFilters[key][col]) {
|
||||
columns = {
|
||||
...columns,
|
||||
[col]: preselectFilters[key][col],
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const scopesByChartId = Object.keys(columns).reduce((map, column) => {
|
||||
const scopeSettings = {
|
||||
...filterScopes[key],
|
||||
};
|
||||
const { scope, immune }: FilterScopeType = {
|
||||
...DASHBOARD_FILTER_SCOPE_GLOBAL,
|
||||
...scopeSettings[column],
|
||||
};
|
||||
|
||||
return {
|
||||
...map,
|
||||
[column]: {
|
||||
scope,
|
||||
immune,
|
||||
},
|
||||
};
|
||||
}, {});
|
||||
|
||||
const {
|
||||
adhoc_filters = [],
|
||||
datasource = '',
|
||||
date_filter = false,
|
||||
filter_configs = [],
|
||||
granularity_sqla,
|
||||
show_sqla_time_column = false,
|
||||
show_sqla_time_granularity = false,
|
||||
time_grain_sqla,
|
||||
time_range,
|
||||
} = slice.form_data;
|
||||
|
||||
const getDashboardDefaultValues =
|
||||
getPreselectedValuesFromDashboard(preselectFilters);
|
||||
|
||||
if (date_filter) {
|
||||
const { scope, immune }: FilterScopeType =
|
||||
scopesByChartId[TIME_FILTER_MAP.time_range] ||
|
||||
DASHBOARD_FILTER_SCOPE_GLOBAL;
|
||||
const timeRangeFilter: Filter = {
|
||||
id: `NATIVE_FILTER-${shortid.generate()}`,
|
||||
description: 'time range filter',
|
||||
controlValues: {},
|
||||
name: TIME_FILTER_LABELS.time_range,
|
||||
filterType: FILTER_COMPONENT_FILTER_TYPES.FILTER_TIME,
|
||||
targets: [{}],
|
||||
cascadeParentIds: [],
|
||||
defaultDataMask: {},
|
||||
type: NativeFilterType.NATIVE_FILTER,
|
||||
scope: {
|
||||
rootPath: scope,
|
||||
excluded: immune,
|
||||
},
|
||||
};
|
||||
filterBoxToFilterComponentMap[key][TIME_FILTER_MAP.time_range] =
|
||||
timeRangeFilter.id;
|
||||
const dashboardDefaultValues =
|
||||
getDashboardDefaultValues(key, TIME_FILTER_MAP.time_range) ||
|
||||
time_range;
|
||||
if (!isEmpty(dashboardDefaultValues)) {
|
||||
timeRangeFilter.defaultDataMask = {
|
||||
extraFormData: { time_range: dashboardDefaultValues as string },
|
||||
filterState: { value: dashboardDefaultValues },
|
||||
};
|
||||
}
|
||||
filterConfig.push(timeRangeFilter);
|
||||
|
||||
if (show_sqla_time_granularity) {
|
||||
const { scope, immune }: FilterScopeType =
|
||||
scopesByChartId[TIME_FILTER_MAP.time_grain_sqla] ||
|
||||
DASHBOARD_FILTER_SCOPE_GLOBAL;
|
||||
const timeGrainFilter: Filter = {
|
||||
id: `NATIVE_FILTER-${shortid.generate()}`,
|
||||
controlValues: {},
|
||||
description: 'time grain filter',
|
||||
name: TIME_FILTER_LABELS.time_grain_sqla,
|
||||
filterType: FILTER_COMPONENT_FILTER_TYPES.FILTER_TIMEGRAIN,
|
||||
targets: [
|
||||
{
|
||||
datasetId: parseInt(datasource.split('__')[0], 10),
|
||||
},
|
||||
],
|
||||
cascadeParentIds: [],
|
||||
defaultDataMask: {},
|
||||
type: NativeFilterType.NATIVE_FILTER,
|
||||
scope: {
|
||||
rootPath: scope,
|
||||
excluded: immune,
|
||||
},
|
||||
};
|
||||
filterBoxToFilterComponentMap[key][TIME_FILTER_MAP.time_grain_sqla] =
|
||||
timeGrainFilter.id;
|
||||
const dashboardDefaultValues = getDashboardDefaultValues(
|
||||
key,
|
||||
TIME_FILTER_MAP.time_grain_sqla,
|
||||
);
|
||||
if (!isEmpty(dashboardDefaultValues)) {
|
||||
timeGrainFilter.defaultDataMask = {
|
||||
extraFormData: {
|
||||
time_grain_sqla: (dashboardDefaultValues ||
|
||||
time_grain_sqla) as TimeGranularity,
|
||||
},
|
||||
filterState: {
|
||||
value: setValuesInArray(
|
||||
dashboardDefaultValues,
|
||||
time_grain_sqla,
|
||||
),
|
||||
},
|
||||
};
|
||||
}
|
||||
filterConfig.push(timeGrainFilter);
|
||||
}
|
||||
|
||||
if (show_sqla_time_column) {
|
||||
const { scope, immune }: FilterScopeType =
|
||||
scopesByChartId[TIME_FILTER_MAP.granularity_sqla] ||
|
||||
DASHBOARD_FILTER_SCOPE_GLOBAL;
|
||||
const timeColumnFilter: Filter = {
|
||||
id: `NATIVE_FILTER-${shortid.generate()}`,
|
||||
description: 'time column filter',
|
||||
controlValues: {},
|
||||
name: TIME_FILTER_LABELS.granularity_sqla,
|
||||
filterType: FILTER_COMPONENT_FILTER_TYPES.FILTER_TIMECOLUMN,
|
||||
targets: [
|
||||
{
|
||||
datasetId: parseInt(datasource.split('__')[0], 10),
|
||||
},
|
||||
],
|
||||
cascadeParentIds: [],
|
||||
defaultDataMask: {},
|
||||
type: NativeFilterType.NATIVE_FILTER,
|
||||
scope: {
|
||||
rootPath: scope,
|
||||
excluded: immune,
|
||||
},
|
||||
};
|
||||
filterBoxToFilterComponentMap[key][TIME_FILTER_MAP.granularity_sqla] =
|
||||
timeColumnFilter.id;
|
||||
const dashboardDefaultValues = getDashboardDefaultValues(
|
||||
key,
|
||||
TIME_FILTER_MAP.granularity_sqla,
|
||||
);
|
||||
if (!isEmpty(dashboardDefaultValues)) {
|
||||
timeColumnFilter.defaultDataMask = {
|
||||
extraFormData: {
|
||||
granularity_sqla: (dashboardDefaultValues ||
|
||||
granularity_sqla) as string,
|
||||
},
|
||||
filterState: {
|
||||
value: setValuesInArray(
|
||||
dashboardDefaultValues,
|
||||
granularity_sqla,
|
||||
),
|
||||
},
|
||||
};
|
||||
}
|
||||
filterConfig.push(timeColumnFilter);
|
||||
}
|
||||
}
|
||||
|
||||
filter_configs.forEach(config => {
|
||||
const { scope, immune }: FilterScopeType =
|
||||
scopesByChartId[config.column] || DASHBOARD_FILTER_SCOPE_GLOBAL;
|
||||
const entry: Filter = {
|
||||
id: `NATIVE_FILTER-${shortid.generate()}`,
|
||||
description: '',
|
||||
controlValues: {
|
||||
enableEmptyFilter: !config[FILTER_CONFIG_ATTRIBUTES.CLEARABLE],
|
||||
defaultToFirstItem: false,
|
||||
inverseSelection: false,
|
||||
multiSelect: config[FILTER_CONFIG_ATTRIBUTES.MULTIPLE],
|
||||
sortAscending: config[FILTER_CONFIG_ATTRIBUTES.SORT_ASCENDING],
|
||||
},
|
||||
name: config.label || config.column,
|
||||
filterType: FILTER_COMPONENT_FILTER_TYPES.FILTER_SELECT,
|
||||
targets: [
|
||||
{
|
||||
datasetId: parseInt(datasource.split('__')[0], 10),
|
||||
column: {
|
||||
name: config.column,
|
||||
},
|
||||
},
|
||||
],
|
||||
cascadeParentIds: [],
|
||||
defaultDataMask: {},
|
||||
type: NativeFilterType.NATIVE_FILTER,
|
||||
scope: {
|
||||
rootPath: scope,
|
||||
excluded: immune,
|
||||
},
|
||||
adhoc_filters,
|
||||
sortMetric: config[FILTER_CONFIG_ATTRIBUTES.SORT_METRIC],
|
||||
time_range,
|
||||
};
|
||||
filterBoxToFilterComponentMap[key][config.column] = entry.id;
|
||||
const defaultValues =
|
||||
getDashboardDefaultValues(key, config.column) ||
|
||||
getFilterBoxDefaultValues(config);
|
||||
if (!isEmpty(defaultValues)) {
|
||||
entry.defaultDataMask = {
|
||||
extraFormData: {
|
||||
filters: [{ col: config.column, op: 'IN', val: defaultValues }],
|
||||
},
|
||||
filterState: { value: defaultValues },
|
||||
};
|
||||
}
|
||||
filterConfig.push(entry);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const dependencies: FilterBoxDependencyMap =
|
||||
getFilterboxDependencies(filterScopes);
|
||||
Object.entries(dependencies).forEach(([key, filterFields]) => {
|
||||
Object.entries(filterFields).forEach(([field, childrenChartIds]) => {
|
||||
const parentComponentId = filterBoxToFilterComponentMap[key][field];
|
||||
childrenChartIds.forEach(childrenChartId => {
|
||||
const childComponentIds = Object.values(
|
||||
filterBoxToFilterComponentMap[childrenChartId],
|
||||
);
|
||||
childComponentIds.forEach(childComponentId => {
|
||||
const childComponent = find(
|
||||
filterConfig,
|
||||
({ id }) => id === childComponentId,
|
||||
);
|
||||
if (
|
||||
childComponent &&
|
||||
// time related filter components don't have parent
|
||||
[
|
||||
FILTER_COMPONENT_FILTER_TYPES.FILTER_SELECT,
|
||||
FILTER_COMPONENT_FILTER_TYPES.FILTER_RANGE,
|
||||
].includes(
|
||||
childComponent.filterType as FILTER_COMPONENT_FILTER_TYPES,
|
||||
)
|
||||
) {
|
||||
childComponent.cascadeParentIds =
|
||||
childComponent.cascadeParentIds || [];
|
||||
childComponent.cascadeParentIds.push(parentComponentId);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return filterConfig;
|
||||
}
|
|
@ -156,16 +156,6 @@ export const TIME_FILTER_MAP = {
|
|||
granularity: '__granularity',
|
||||
};
|
||||
|
||||
export enum FILTER_BOX_MIGRATION_STATES {
|
||||
CONVERTED = 'CONVERTED',
|
||||
NOOP = 'NOOP',
|
||||
REVIEWING = 'REVIEWING',
|
||||
SNOOZED = 'SNOOZED',
|
||||
UNDECIDED = 'UNDECIDED',
|
||||
}
|
||||
|
||||
export const FILTER_BOX_TRANSITION_SNOOZE_DURATION = 24 * 60 * 60 * 1000; // 24 hours
|
||||
|
||||
export const POPOVER_INITIAL_HEIGHT = 240;
|
||||
export const POPOVER_INITIAL_WIDTH = 320;
|
||||
export const UNRESIZABLE_POPOVER_WIDTH = 296;
|
||||
|
|
|
@ -444,7 +444,6 @@ DEFAULT_FEATURE_FLAGS: Dict[str, bool] = {
|
|||
"ALERT_REPORTS": False,
|
||||
"DASHBOARD_RBAC": False,
|
||||
"ENABLE_EXPLORE_DRAG_AND_DROP": True,
|
||||
"ENABLE_FILTER_BOX_MIGRATION": False,
|
||||
"ENABLE_ADVANCED_DATA_TYPES": False,
|
||||
"ENABLE_DND_WITH_CLICK_UX": True,
|
||||
# Enabling ALERTS_ATTACH_REPORTS, the system sends email and slack message
|
||||
|
|
|
@ -114,17 +114,9 @@ class Dashboard(BaseSupersetView):
|
|||
@expose("/new/")
|
||||
def new(self) -> FlaskResponse: # pylint: disable=no-self-use
|
||||
"""Creates a new, blank dashboard and redirects to it in edit mode"""
|
||||
metadata = {}
|
||||
if is_feature_enabled("ENABLE_FILTER_BOX_MIGRATION"):
|
||||
metadata = {
|
||||
"native_filter_configuration": [],
|
||||
"show_native_filters": True,
|
||||
}
|
||||
|
||||
new_dashboard = DashboardModel(
|
||||
dashboard_title="[ untitled dashboard ]",
|
||||
owners=[g.user],
|
||||
json_metadata=json.dumps(metadata, sort_keys=True),
|
||||
)
|
||||
db.session.add(new_dashboard)
|
||||
db.session.commit()
|
||||
|
|
Loading…
Reference in New Issue