| /** |
| * 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 { isEqual } from 'lodash'; |
| import { |
| AdhocFilter, |
| ensureIsArray, |
| EXTRA_FORM_DATA_OVERRIDE_EXTRA_KEYS, |
| EXTRA_FORM_DATA_OVERRIDE_REGULAR_MAPPINGS, |
| isDefined, |
| isFreeFormAdhocFilter, |
| isSimpleAdhocFilter, |
| JsonObject, |
| NO_TIME_RANGE, |
| QueryFormData, |
| QueryObjectFilterClause, |
| SimpleAdhocFilter, |
| } from '@superset-ui/core'; |
| import { simpleFilterToAdhoc } from 'src/utils/simpleFilterToAdhoc'; |
| |
| const removeExtraFieldForNewCharts = ( |
| filters: AdhocFilter[], |
| isNewChart: boolean, |
| ) => |
| filters.map(filter => { |
| if (filter.isExtra) { |
| return { ...filter, isExtra: !isNewChart }; |
| } |
| return filter; |
| }); |
| |
| const removeAdhocFilterDuplicates = (filters: AdhocFilter[]) => { |
| const isDuplicate = ( |
| adhocFilter: AdhocFilter, |
| existingFilters: AdhocFilter[], |
| ) => |
| existingFilters.some( |
| (existingFilter: AdhocFilter) => |
| (isFreeFormAdhocFilter(existingFilter) && |
| isFreeFormAdhocFilter(adhocFilter) && |
| existingFilter.clause === adhocFilter.clause && |
| existingFilter.sqlExpression === adhocFilter.sqlExpression) || |
| (isSimpleAdhocFilter(existingFilter) && |
| isSimpleAdhocFilter(adhocFilter) && |
| existingFilter.operator === adhocFilter.operator && |
| existingFilter.subject === adhocFilter.subject && |
| ((!('comparator' in existingFilter) && |
| !('comparator' in adhocFilter)) || |
| ('comparator' in existingFilter && |
| 'comparator' in adhocFilter && |
| isEqual(existingFilter.comparator, adhocFilter.comparator)))), |
| ); |
| |
| return filters.reduce((acc, filter) => { |
| if (!isDuplicate(filter, acc)) { |
| acc.push(filter); |
| } |
| return acc; |
| }, [] as AdhocFilter[]); |
| }; |
| |
| const mergeFilterBoxToFormData = ( |
| exploreFormData: QueryFormData, |
| dashboardFormData: JsonObject, |
| ) => { |
| const dateColumns = { |
| __time_range: 'time_range', |
| __time_col: 'granularity_sqla', |
| __time_grain: 'time_grain_sqla', |
| __granularity: 'granularity', |
| }; |
| const appliedTimeExtras: Record<string, any> = {}; |
| |
| const filterBoxData: JsonObject = {}; |
| ensureIsArray(dashboardFormData.extra_filters).forEach(filter => { |
| if (dateColumns[filter.col as keyof typeof dateColumns]) { |
| if (filter.val !== NO_TIME_RANGE) { |
| filterBoxData[dateColumns[filter.col as keyof typeof dateColumns]] = |
| filter.val; |
| appliedTimeExtras[filter.col] = filter.val; |
| } |
| } else { |
| const adhocFilter = simpleFilterToAdhoc({ |
| ...(filter as QueryObjectFilterClause), |
| isExtra: true, |
| }); |
| filterBoxData.adhoc_filters = [ |
| ...ensureIsArray(filterBoxData.adhoc_filters), |
| adhocFilter, |
| ]; |
| } |
| }); |
| filterBoxData.applied_time_extras = appliedTimeExtras; |
| return filterBoxData; |
| }; |
| |
| const mergeNativeFiltersToFormData = ( |
| exploreFormData: QueryFormData, |
| dashboardFormData: JsonObject, |
| ) => { |
| const nativeFiltersData: JsonObject = {}; |
| const extraFormData = dashboardFormData.extra_form_data || {}; |
| const layerFilterScope = dashboardFormData?.layer_filter_scope; |
| const filterDataMapping = dashboardFormData?.filter_data_mapping; |
| Object.entries(EXTRA_FORM_DATA_OVERRIDE_REGULAR_MAPPINGS).forEach( |
| ([srcKey, targetKey]) => { |
| const val = extraFormData[srcKey]; |
| if (isDefined(val)) { |
| nativeFiltersData[targetKey] = val; |
| } |
| }, |
| ); |
| |
| if ('time_grain_sqla' in extraFormData) { |
| nativeFiltersData.time_grain_sqla = extraFormData.time_grain_sqla; |
| } |
| if ('granularity_sqla' in extraFormData) { |
| nativeFiltersData.granularity_sqla = extraFormData.granularity_sqla; |
| } |
| |
| const extras = dashboardFormData.extras || {}; |
| EXTRA_FORM_DATA_OVERRIDE_EXTRA_KEYS.forEach(key => { |
| const val = extraFormData[key]; |
| if (isDefined(val)) { |
| extras[key] = val; |
| } |
| }); |
| if (Object.keys(extras).length) { |
| nativeFiltersData.extras = extras; |
| } |
| |
| nativeFiltersData.adhoc_filters = ensureIsArray( |
| extraFormData.adhoc_filters, |
| ).map(filter => ({ |
| ...filter, |
| isExtra: true, |
| })); |
| |
| const appendFilters = ensureIsArray(extraFormData.filters).map(extraFilter => |
| simpleFilterToAdhoc({ |
| ...extraFilter, |
| isExtra: true, |
| filterDataMapping, |
| layerFilterScope, |
| }), |
| ); |
| Object.keys(exploreFormData).forEach(key => { |
| if (key.match(/adhoc_filter.*/)) { |
| nativeFiltersData[key] = [ |
| ...ensureIsArray(nativeFiltersData[key]), |
| ...appendFilters, |
| ]; |
| } |
| }); |
| |
| return nativeFiltersData; |
| }; |
| |
| const applyTimeRangeFilters = ( |
| dashboardFormData: JsonObject, |
| adhocFilters: AdhocFilter[], |
| ) => { |
| const extraFormData = dashboardFormData.extra_form_data || {}; |
| if ('time_range' in extraFormData) { |
| return adhocFilters.map((filter: SimpleAdhocFilter) => { |
| if (filter.operator === 'TEMPORAL_RANGE') { |
| return { |
| ...filter, |
| comparator: extraFormData.time_range, |
| isExtra: true, |
| }; |
| } |
| return filter; |
| }); |
| } |
| return adhocFilters; |
| }; |
| |
| export const getFormDataWithDashboardContext = ( |
| exploreFormData: QueryFormData, |
| dashboardContextFormData: JsonObject, |
| saveAction?: string | null, |
| ) => { |
| const filterBoxData = mergeFilterBoxToFormData( |
| exploreFormData, |
| dashboardContextFormData, |
| ); |
| |
| const nativeFiltersData = mergeNativeFiltersToFormData( |
| exploreFormData, |
| dashboardContextFormData, |
| ); |
| const isDeckGLChart = |
| exploreFormData.viz_type === 'deck_multi' || |
| dashboardContextFormData.viz_type === 'deck_multi'; |
| |
| const deckSlices = exploreFormData?.deck_slices; |
| |
| const adhocFilters = [ |
| ...Object.keys(exploreFormData), |
| ...Object.keys(filterBoxData), |
| ...Object.keys(nativeFiltersData), |
| ] |
| .filter(key => key.match(/adhoc_filter.*/)) |
| .reduce( |
| (acc, key) => ({ |
| ...acc, |
| [key]: (() => { |
| const beforeDuplicates = [ |
| ...ensureIsArray(exploreFormData[key]), |
| ...ensureIsArray(filterBoxData[key]), |
| ...ensureIsArray(nativeFiltersData[key]), |
| ]; |
| |
| const afterDuplicates = removeAdhocFilterDuplicates(beforeDuplicates); |
| |
| const final = removeExtraFieldForNewCharts( |
| applyTimeRangeFilters(dashboardContextFormData, afterDuplicates), |
| exploreFormData.slice_id === 0, |
| ); |
| |
| return final; |
| })(), |
| }), |
| {}, |
| ); |
| const ownColorScheme = exploreFormData.color_scheme; |
| const dashboardColorScheme = dashboardContextFormData.color_scheme; |
| const appliedColorScheme = dashboardColorScheme || ownColorScheme; |
| |
| const deckGLProperties: JsonObject = {}; |
| |
| if ( |
| isDeckGLChart && |
| isDefined(deckSlices) && |
| 'adhoc_filters' in adhocFilters && |
| Array.isArray(adhocFilters?.adhoc_filters) |
| ) { |
| const adhocFiltersWithDeckSlices = |
| adhocFilters?.adhoc_filters?.map((filter: AdhocFilter) => ({ |
| ...filter, |
| ...(Array.isArray(deckSlices) && |
| deckSlices?.length > 0 && |
| filter?.isExtra && { |
| deck_slices: deckSlices, |
| }), |
| })) || []; |
| |
| adhocFilters.adhoc_filters = adhocFiltersWithDeckSlices; |
| } |
| |
| if (isDeckGLChart) { |
| if (dashboardContextFormData.layer_filter_scope) { |
| deckGLProperties.layer_filter_scope = |
| dashboardContextFormData.layer_filter_scope; |
| } |
| if (dashboardContextFormData.filter_data_mapping) { |
| deckGLProperties.filter_data_mapping = |
| dashboardContextFormData.filter_data_mapping; |
| } |
| } |
| |
| if (saveAction === 'overwrite') { |
| return { |
| ...dashboardContextFormData, |
| ...filterBoxData, |
| ...nativeFiltersData, |
| ...adhocFilters, |
| ...exploreFormData, // Explore form data comes last to override |
| own_color_scheme: ownColorScheme, |
| color_scheme: appliedColorScheme, |
| dashboard_color_scheme: dashboardColorScheme, |
| ...deckGLProperties, |
| }; |
| } |
| |
| // Default behavior: Dashboard context overrides explore data, but adhoc filters, color schemes |
| // and specific properties from filterBox and native filters take final precedence |
| return { |
| ...exploreFormData, |
| ...dashboardContextFormData, |
| ...filterBoxData, |
| ...nativeFiltersData, |
| ...adhocFilters, |
| own_color_scheme: ownColorScheme, |
| color_scheme: appliedColorScheme, |
| dashboard_color_scheme: dashboardColorScheme, |
| ...deckGLProperties, |
| }; |
| }; |