/** * 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 = {}; 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, }; };