This is an automated email from the ASF dual-hosted git repository. graceguo pushed a commit to branch gg-ShowFilterBoxAsFilterComponent in repository https://gitbox.apache.org/repos/asf/superset.git
commit 71d0d7f48ccadabdb369bf3406c8326078dc7abc Author: Grace <[email protected]> AuthorDate: Sun Apr 25 22:28:29 2021 -0700 dispaly filter_box as native filters --- superset-frontend/src/dashboard/actions/hydrate.js | 306 +++++++++++++++++---- .../dashboard/components/gridComponents/Chart.jsx | 5 +- .../components/nativeFilters/FilterBar/index.tsx | 1 + superset-frontend/src/explore/constants.ts | 1 + superset-frontend/src/filters/utils.ts | 10 +- 5 files changed, 266 insertions(+), 57 deletions(-) diff --git a/superset-frontend/src/dashboard/actions/hydrate.js b/superset-frontend/src/dashboard/actions/hydrate.js index 0206d91..d4b9872 100644 --- a/superset-frontend/src/dashboard/actions/hydrate.js +++ b/superset-frontend/src/dashboard/actions/hydrate.js @@ -17,44 +17,34 @@ * under the License. */ /* eslint-disable camelcase */ -import { isString, keyBy } from 'lodash'; +import {isString, keyBy, isEmpty} from 'lodash'; import shortid from 'shortid'; -import { - Behavior, - CategoricalColorNamespace, - getChartMetadataRegistry, -} from '@superset-ui/core'; +import {Behavior, CategoricalColorNamespace, getChartMetadataRegistry,} from '@superset-ui/core'; import querystring from 'query-string'; -import { chart } from 'src/chart/chartReducer'; -import { initSliceEntities } from 'src/dashboard/reducers/sliceEntities'; -import { getInitialState as getInitialNativeFilterState } from 'src/dashboard/reducers/nativeFilters'; -import { getParam } from 'src/modules/utils'; -import { applyDefaultFormData } from 'src/explore/store'; -import { buildActiveFilters } from 'src/dashboard/util/activeDashboardFilters'; +import {chart} from 'src/chart/chartReducer'; +import {initSliceEntities} from 'src/dashboard/reducers/sliceEntities'; +import {getInitialState as getInitialNativeFilterState} from 'src/dashboard/reducers/nativeFilters'; +import {getParam} from 'src/modules/utils'; +import {applyDefaultFormData} from 'src/explore/store'; +import {buildActiveFilters} from 'src/dashboard/util/activeDashboardFilters'; import findPermission from 'src/dashboard/util/findPermission'; -import { - DASHBOARD_FILTER_SCOPE_GLOBAL, - dashboardFilter, -} from 'src/dashboard/reducers/dashboardFilters'; +import {DASHBOARD_FILTER_SCOPE_GLOBAL, dashboardFilter,} from 'src/dashboard/reducers/dashboardFilters'; import { DASHBOARD_HEADER_ID, - GRID_DEFAULT_CHART_WIDTH, - GRID_COLUMN_COUNT, DASHBOARD_ROOT_ID, + GRID_COLUMN_COUNT, + GRID_DEFAULT_CHART_WIDTH, } from 'src/dashboard/util/constants'; -import { - DASHBOARD_HEADER_TYPE, - CHART_TYPE, - ROW_TYPE, -} from 'src/dashboard/util/componentTypes'; +import {CHART_TYPE, DASHBOARD_HEADER_TYPE, ROW_TYPE,} from 'src/dashboard/util/componentTypes'; import findFirstParentContainerId from 'src/dashboard/util/findFirstParentContainer'; import getEmptyLayout from 'src/dashboard/util/getEmptyLayout'; import getFilterConfigsFromFormdata from 'src/dashboard/util/getFilterConfigsFromFormdata'; import getLocationHash from 'src/dashboard/util/getLocationHash'; import newComponentFactory from 'src/dashboard/util/newComponentFactory'; -import { TIME_RANGE } from 'src/visualizations/FilterBox/FilterBox'; -import { FeatureFlag, isFeatureEnabled } from '../../featureFlags'; +import {TIME_RANGE} from 'src/visualizations/FilterBox/FilterBox'; +import {FeatureFlag, isFeatureEnabled} from '../../featureFlags'; +import {FILTER_CONFIG_ATTRIBUTES, TIME_FILTER_LABELS, TIME_FILTER_MAP} from "../../explore/constants"; const reservedQueryParams = new Set(['standalone', 'edit']); @@ -77,6 +67,51 @@ const extractUrlParams = queryParams => return { ...acc, [key]: value }; }, {}); +const getPreselectedValuesFromDashboard = ( + preselectedFilters, +) => { + return (filterKey, column) => { + if (preselectedFilters[filterKey] && + preselectedFilters[filterKey].hasOwnProperty(column)) { + // overwrite default values by dashboard default_filters + return preselectedFilters[filterKey][column]; + } + } +} + +const getFilterBoxDefaultValues = (config) => { + 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 buildValuesInArray = (value1, value2) => { + if (!isEmpty(value1)) { + return [value1]; + } else if (isEmpty(value2)) { + return [value2]; + } else{ + return []; + } +} + +const FILTER_BOX_TRANSITION_MODE = { + test: 'TEST', + snooze: 'SNOOZE', + confirmed: 'CONFIRMED', +} + export const HYDRATE_DASHBOARD = 'HYDRATE_DASHBOARD'; export const hydrateDashboard = (dashboardData, chartData, datasourcesData) => ( @@ -143,6 +178,8 @@ export const hydrateDashboard = (dashboardData, chartData, datasourcesData) => ( let newSlicesContainerWidth = 0; const filterScopes = metadata?.filter_scopes || {}; + const filterConfig = metadata?.native_filter_configuration || []; + const isFilterBoxConverted = !!(metadata?.native_filter_configuration); const chartQueries = {}; const dashboardFilters = {}; @@ -210,7 +247,9 @@ export const hydrateDashboard = (dashboardData, chartData, datasourcesData) => ( } // build DashboardFilters for interactive filter features - if (slice.form_data.viz_type === 'filter_box') { + if (!isFilterBoxConverted && + slice.form_data.viz_type === 'filter_box' + ) { const configs = getFilterConfigsFromFormdata(slice.form_data); let { columns } = configs; const { labels } = configs; @@ -243,22 +282,195 @@ export const hydrateDashboard = (dashboardData, chartData, datasourcesData) => ( }; }, {}); - const componentId = chartIdToLayoutId[key]; - const directPathToFilter = (layout[componentId].parents || []).slice(); - directPathToFilter.push(componentId); - dashboardFilters[key] = { - ...dashboardFilter, - chartId: key, - componentId, - datasourceId: slice.form_data.datasource, - filterName: slice.slice_name, - directPathToFilter, - columns, - labels, - scopes: scopesByChartId, - isInstantFilter: !!slice.form_data.instant_filtering, - isDateFilter: Object.keys(columns).includes(TIME_RANGE), - }; + const { + date_filter = false, + datasource = '', + druid_time_origin, + filter_configs = [], + instant_filtering = false, + granularity, + granularity_sqla, + show_druid_time_granularity = false, + show_druid_time_origin = false, + 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} = scopesByChartId[TIME_FILTER_MAP['time_range']] || DASHBOARD_FILTER_SCOPE_GLOBAL; + const dashboardDefaultValues = getDashboardDefaultValues(key, TIME_FILTER_MAP['time_range']); + const timeRangeFilter = { + id: `NATIVE_FILTER-${shortid.generate()}`, + controlValues: {}, + name: TIME_FILTER_LABELS['time_range'], + filterType: "filter_time", + targets: [{}], + defaultValue: dashboardDefaultValues || time_range, + cascadeParentIds: [], + scope: { + "rootPath": scope, + "excluded": immune, + }, + isInstant: instant_filtering, + } + filterConfig.push(timeRangeFilter); + + if (show_sqla_time_granularity) { + const {scope, immune} = scopesByChartId['time_grain_sqla'] || DASHBOARD_FILTER_SCOPE_GLOBAL; + const dashboardDefaultValues = getDashboardDefaultValues(key, TIME_FILTER_MAP['time_grain_sqla']); + const timeGrainFilter = { + id: `NATIVE_FILTER-${shortid.generate()}`, + controlValues: {}, + name: TIME_FILTER_LABELS['time_grain_sqla'], + filterType: 'filter_timegrain', + targets: [ + { + datasetId: parseInt(datasource.split('__')[0], 10), + } + ], + defaultValue: buildValuesInArray(dashboardDefaultValues, time_grain_sqla), + cascadeParentIds: [], + scope: { + "rootPath": scope, + "excluded": immune, + }, + isInstant: instant_filtering, + } + filterConfig.push(timeGrainFilter); + } + + if (show_sqla_time_column) { + const {scope, immune} = scopesByChartId['granularity_sqla'] || DASHBOARD_FILTER_SCOPE_GLOBAL; + const dashboardDefaultValues = getDashboardDefaultValues(key, TIME_FILTER_MAP['granularity_sqla']); + const timeColumnFilter = { + id: `NATIVE_FILTER-${shortid.generate()}`, + controlValues: {}, + name: TIME_FILTER_LABELS['granularity_sqla'], + filterType: 'filter_timecolumn', + targets: [ + { + datasetId: parseInt(datasource.split('__')[0], 10), + } + ], + defaultValue: buildValuesInArray(dashboardDefaultValues, granularity_sqla), + cascadeParentIds: [], + scope: { + "rootPath": scope, + "excluded": immune, + }, + isInstant: instant_filtering, + } + filterConfig.push(timeColumnFilter); + } + + if (show_druid_time_granularity) { + const {scope, immune} = scopesByChartId['granularity'] || DASHBOARD_FILTER_SCOPE_GLOBAL; + const dashboardDefaultValues = getDashboardDefaultValues(key, TIME_FILTER_MAP['granularity']); + const druidGranularityFilter = { + id: `NATIVE_FILTER-${shortid.generate()}`, + controlValues: {}, + name: TIME_FILTER_LABELS['granularity'], + filterType: 'filter_timegrain', + targets: [ + { + datasetId: parseInt(datasource.split('__')[0], 10), + } + ], + defaultValue: buildValuesInArray(dashboardDefaultValues, granularity), + cascadeParentIds: [], + scope: { + "rootPath": scope, + "excluded": immune, + }, + isInstant: instant_filtering, + } + filterConfig.push(druidGranularityFilter); + } + + // TODO: test + if (show_druid_time_origin) { + const {scope, immune} = scopesByChartId['druid_time_origin'] || DASHBOARD_FILTER_SCOPE_GLOBAL; + const dashboardDefaultValues = getDashboardDefaultValues(key, TIME_FILTER_MAP['druid_time_origin']); + const druidOriginFilter = { + id: `NATIVE_FILTER-${shortid.generate()}`, + controlValues: {}, + name: TIME_FILTER_LABELS['druid_time_origin'], + filterType: 'filter_timegrain', + targets: [ + { + datasetId: parseInt(datasource.split('__')[0], 10), + } + ], + defaultValue: buildValuesInArray(dashboardDefaultValues, druid_time_origin), + cascadeParentIds: [], + scope: { + "rootPath": scope, + "excluded": immune, + }, + isInstant: instant_filtering, + } + filterConfig.push(druidOriginFilter); + } + } + filter_configs.forEach(config => { + const {scope, immune} = scopesByChartId[config.column] || DASHBOARD_FILTER_SCOPE_GLOBAL; + const defaultValues = getDashboardDefaultValues(key, config.column); + const entry = { + id: `NATIVE_FILTER-${config.key}`, + controlValues: { + // TODO: test + enableEmptyFilter: !config[FILTER_CONFIG_ATTRIBUTES.CLEARABLE], + defaultToFirstItem: false, // n/a + inverseSelection: false, // n/a + multiSelect: config[FILTER_CONFIG_ATTRIBUTES.MULTIPLE], + sortAscending: config[FILTER_CONFIG_ATTRIBUTES.SORT_ASCENDING], + }, + name: config.label || config.column, + filterType: "filter_select", + targets: [ + { + datasetId: parseInt(datasource.split('__')[0], 10), + column: { + name: config.column, + }, + } + ], + // TODO: test + defaultValue: defaultValues || getFilterBoxDefaultValues(config), + cascadeParentIds:[], + scope:{ + "rootPath": scope, + "excluded": immune, + }, + isInstant: instant_filtering, + } + filterConfig.push(entry); + }); + + metadata = metadata || {}; + metadata.native_filter_configuration = filterConfig; + // done + + // const componentId = chartIdToLayoutId[key]; + // const directPathToFilter = (layout[componentId].parents || []).slice(); + // directPathToFilter.push(componentId); + // dashboardFilters[key] = { + // ...dashboardFilter, + // chartId: key, + // componentId, + // datasourceId: slice.form_data.datasource, + // filterName: slice.slice_name, + // directPathToFilter, + // columns, + // labels, + // scopes: scopesByChartId, + // isInstantFilter: !!slice.form_data.instant_filtering, + // isDateFilter: Object.keys(columns).includes(TIME_RANGE), + // }; } // sync layout names with current slice names in case a slice was edited @@ -269,10 +481,10 @@ export const hydrateDashboard = (dashboardData, chartData, datasourcesData) => ( layout[layoutId].meta.sliceName = slice.slice_name; } }); - buildActiveFilters({ - dashboardFilters, - components: layout, - }); + // buildActiveFilters({ + // dashboardFilters, + // components: layout, + // }); // store the header as a layout component so we can undo/redo changes layout[DASHBOARD_HEADER_ID] = { @@ -298,7 +510,7 @@ export const hydrateDashboard = (dashboardData, chartData, datasourcesData) => ( } const nativeFilters = getInitialNativeFilterState({ - filterConfig: metadata?.native_filter_configuration || [], + filterConfig, filterSetsConfig: metadata?.filter_sets_configuration || [], }); diff --git a/superset-frontend/src/dashboard/components/gridComponents/Chart.jsx b/superset-frontend/src/dashboard/components/gridComponents/Chart.jsx index bc1f78f..831dfd3 100644 --- a/superset-frontend/src/dashboard/components/gridComponents/Chart.jsx +++ b/superset-frontend/src/dashboard/components/gridComponents/Chart.jsx @@ -93,6 +93,8 @@ const ChartOverlay = styled.div` top: 0; left: 0; z-index: 5; + opacity: 0.25; + background-color: #000; `; export default class Chart extends React.Component { @@ -279,6 +281,7 @@ export default class Chart extends React.Component { const { queriesResponse, chartUpdateEndTime, chartStatus } = chart; const isLoading = chartStatus === 'loading'; + const isDeactivated = slice.viz_type === 'filter_box' // eslint-disable-next-line camelcase const isCached = queriesResponse?.map(({ is_cached }) => is_cached) || []; const cachedDttm = @@ -350,7 +353,7 @@ export default class Chart extends React.Component { isOverflowable && 'dashboard-chart--overflowable', )} > - {isLoading && ( + {(isLoading || isDeactivated) && ( <ChartOverlay style={{ width, diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx index 4771f03..d5557d5 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx @@ -212,6 +212,7 @@ const FilterBar: React.FC<FiltersBarProps> = ({ const isApplyDisabled = !isInitialized || areObjectsEqual(dataMaskSelected, lastAppliedFilterData); + console.log('is filter change:', isFilterSetChanged) return ( <BarWrapper {...getFilterBarTestId()} className={cx({ open: filtersOpen })}> <CollapsedBar diff --git a/superset-frontend/src/explore/constants.ts b/superset-frontend/src/explore/constants.ts index 06fa62c..bd0de27 100644 --- a/superset-frontend/src/explore/constants.ts +++ b/superset-frontend/src/explore/constants.ts @@ -80,6 +80,7 @@ export const TIME_FILTER_LABELS = { }; export const FILTER_CONFIG_ATTRIBUTES = { + SORT_ASCENDING: 'asc', DEFAULT_VALUE: 'defaultValue', MULTIPLE: 'multiple', SEARCH_ALL_OPTIONS: 'searchAllOptions', diff --git a/superset-frontend/src/filters/utils.ts b/superset-frontend/src/filters/utils.ts index 6ca88dd..1df31db 100644 --- a/superset-frontend/src/filters/utils.ts +++ b/superset-frontend/src/filters/utils.ts @@ -33,15 +33,7 @@ export const getSelectExtraFormData = ( inverseSelection = false, ): ExtraFormData => { const extra: ExtraFormData = {}; - if (emptyFilter) { - extra.adhoc_filters = [ - { - expressionType: 'SQL', - clause: 'WHERE', - sqlExpression: '1 = 0', - }, - ]; - } else { + if (!emptyFilter) { extra.filters = value === undefined || value === null || value.length === 0 ? []
