diff --git a/superset-frontend/src/dashboard/components/FiltersBadge/index.tsx b/superset-frontend/src/dashboard/components/FiltersBadge/index.tsx index b9c2b5f736f..317666b9146 100644 --- a/superset-frontend/src/dashboard/components/FiltersBadge/index.tsx +++ b/superset-frontend/src/dashboard/components/FiltersBadge/index.tsx @@ -57,6 +57,8 @@ const sortByStatus = (indicators: Indicator[]): Indicator[] => { ); }; +const indicatorsInitialState: Indicator[] = []; + export const FiltersBadge = ({ chartId }: FiltersBadgeProps) => { const dispatch = useDispatch(); const datasources = useSelector(state => state.datasources); @@ -77,9 +79,11 @@ export const FiltersBadge = ({ chartId }: FiltersBadgeProps) => { state => state.dataMask, ); - const [nativeIndicators, setNativeIndicators] = useState([]); + const [nativeIndicators, setNativeIndicators] = useState( + indicatorsInitialState, + ); const [dashboardIndicators, setDashboardIndicators] = useState( - [], + indicatorsInitialState, ); const onHighlightFilterSource = useCallback( @@ -90,46 +94,79 @@ export const FiltersBadge = ({ chartId }: FiltersBadgeProps) => { ); const chart = charts[chartId]; - const prevChartStatus = usePrevious(chart?.chartStatus); + const prevChart = usePrevious(chart); + const prevChartStatus = prevChart?.chartStatus; + const prevDashboardFilters = usePrevious(dashboardFilters); + const prevDatasources = usePrevious(datasources); + const showIndicators = + chart?.chartStatus && ['rendered', 'success'].includes(chart.chartStatus); - const showIndicators = useCallback( - () => - chart?.chartStatus && ['rendered', 'success'].includes(chart.chartStatus), - [chart.chartStatus], - ); useEffect(() => { - if (!showIndicators) { - setDashboardIndicators([]); - } - if (prevChartStatus !== 'success') { - setDashboardIndicators( - selectIndicatorsForChart(chartId, dashboardFilters, datasources, chart), - ); + if (!showIndicators && dashboardIndicators.length > 0) { + setDashboardIndicators(indicatorsInitialState); + } else if (prevChartStatus !== 'success') { + if ( + chart?.queriesResponse?.[0]?.rejected_filters !== + prevChart?.queriesResponse?.[0]?.rejected_filters || + chart?.queriesResponse?.[0]?.applied_filters !== + prevChart?.queriesResponse?.[0]?.applied_filters || + dashboardFilters !== prevDashboardFilters || + datasources !== prevDatasources + ) { + setDashboardIndicators( + selectIndicatorsForChart( + chartId, + dashboardFilters, + datasources, + chart, + ), + ); + } } }, [ chart, chartId, dashboardFilters, + dashboardIndicators.length, datasources, + prevChart?.queriesResponse, prevChartStatus, + prevDashboardFilters, + prevDatasources, showIndicators, ]); + const prevNativeFilters = usePrevious(nativeFilters); + const prevDashboardLayout = usePrevious(present); + const prevDataMask = usePrevious(dataMask); + const prevChartConfig = usePrevious( + dashboardInfo.metadata?.chart_configuration, + ); useEffect(() => { - if (!showIndicators) { - setNativeIndicators([]); - } - if (prevChartStatus !== 'success') { - setNativeIndicators( - selectNativeIndicatorsForChart( - nativeFilters, - dataMask, - chartId, - chart, - present, - dashboardInfo.metadata?.chart_configuration, - ), - ); + if (!showIndicators && nativeIndicators.length > 0) { + setNativeIndicators(indicatorsInitialState); + } else if (prevChartStatus !== 'success') { + if ( + chart?.queriesResponse?.[0]?.rejected_filters !== + prevChart?.queriesResponse?.[0]?.rejected_filters || + chart?.queriesResponse?.[0]?.applied_filters !== + prevChart?.queriesResponse?.[0]?.applied_filters || + nativeFilters !== prevNativeFilters || + present !== prevDashboardLayout || + dataMask !== prevDataMask || + prevChartConfig !== dashboardInfo.metadata?.chart_configuration + ) { + setNativeIndicators( + selectNativeIndicatorsForChart( + nativeFilters, + dataMask, + chartId, + chart, + present, + dashboardInfo.metadata?.chart_configuration, + ), + ); + } } }, [ chart, @@ -137,8 +174,14 @@ export const FiltersBadge = ({ chartId }: FiltersBadgeProps) => { dashboardInfo.metadata?.chart_configuration, dataMask, nativeFilters, + nativeIndicators.length, present, + prevChart?.queriesResponse, + prevChartConfig, prevChartStatus, + prevDashboardLayout, + prevDataMask, + prevNativeFilters, showIndicators, ]); @@ -155,17 +198,33 @@ export const FiltersBadge = ({ chartId }: FiltersBadgeProps) => { [dashboardIndicators, nativeIndicators], ); - const appliedCrossFilterIndicators = indicators.filter( - indicator => indicator.status === IndicatorStatus.CrossFilterApplied, + const appliedCrossFilterIndicators = useMemo( + () => + indicators.filter( + indicator => indicator.status === IndicatorStatus.CrossFilterApplied, + ), + [indicators], ); - const appliedIndicators = indicators.filter( - indicator => indicator.status === IndicatorStatus.Applied, + const appliedIndicators = useMemo( + () => + indicators.filter( + indicator => indicator.status === IndicatorStatus.Applied, + ), + [indicators], ); - const unsetIndicators = indicators.filter( - indicator => indicator.status === IndicatorStatus.Unset, + const unsetIndicators = useMemo( + () => + indicators.filter( + indicator => indicator.status === IndicatorStatus.Unset, + ), + [indicators], ); - const incompatibleIndicators = indicators.filter( - indicator => indicator.status === IndicatorStatus.Incompatible, + const incompatibleIndicators = useMemo( + () => + indicators.filter( + indicator => indicator.status === IndicatorStatus.Incompatible, + ), + [indicators], ); if ( diff --git a/superset-frontend/src/dashboard/components/FiltersBadge/selectors.ts b/superset-frontend/src/dashboard/components/FiltersBadge/selectors.ts index 34bdfce6d97..a6aec8ea591 100644 --- a/superset-frontend/src/dashboard/components/FiltersBadge/selectors.ts +++ b/superset-frontend/src/dashboard/components/FiltersBadge/selectors.ts @@ -16,16 +16,17 @@ * specific language governing permissions and limitations * under the License. */ -import { NO_TIME_RANGE, TIME_FILTER_MAP } from 'src/explore/constants'; -import { getChartIdsInFilterScope } from 'src/dashboard/util/activeDashboardFilters'; -import { ChartConfiguration, Filters } from 'src/dashboard/reducers/types'; -import { DataMaskStateWithId, DataMaskType } from 'src/dataMask/types'; import { ensureIsArray, FeatureFlag, FilterState, isFeatureEnabled, } from '@superset-ui/core'; +import { NO_TIME_RANGE, TIME_FILTER_MAP } from 'src/explore/constants'; +import { getChartIdsInFilterScope } from 'src/dashboard/util/activeDashboardFilters'; +import { ChartConfiguration, Filters } from 'src/dashboard/reducers/types'; +import { DataMaskStateWithId, DataMaskType } from 'src/dataMask/types'; +import { areObjectsEqual } from 'src/reduxUtils'; import { Layout } from '../../types'; import { getTreeCheckedItems } from '../nativeFilters/FiltersConfigModal/FiltersConfigForm/FilterScope/utils'; @@ -154,6 +155,8 @@ export type Indicator = { path?: string[]; }; +const cachedIndicatorsForChart = {}; +const cachedDashboardFilterDataForChart = {}; // inspects redux state to find what the filter indicators should be shown for a given chart export const selectIndicatorsForChart = ( chartId: number, @@ -165,37 +168,75 @@ export const selectIndicatorsForChart = ( // so grab the columns from the applied/rejected filters const appliedColumns = getAppliedColumns(chart); const rejectedColumns = getRejectedColumns(chart); + const matchingFilters = Object.values(filters).filter( + filter => filter.chartId !== chartId, + ); + const matchingDatasources = Object.entries(datasources) + .filter(([key]) => + matchingFilters.find(filter => filter.datasourceId === key), + ) + .map(([, datasource]) => datasource); - const indicators = Object.values(filters) - .filter(filter => filter.chartId !== chartId) - .reduce( - (acc, filter) => - acc.concat( - selectIndicatorsForChartFromFilter( - chartId, - filter, - datasources[filter.datasourceId] || {}, - appliedColumns, - rejectedColumns, - ), + const cachedFilterData = cachedDashboardFilterDataForChart[chartId]; + if ( + cachedIndicatorsForChart[chartId] && + areObjectsEqual(cachedFilterData?.appliedColumns, appliedColumns) && + areObjectsEqual(cachedFilterData?.rejectedColumns, rejectedColumns) && + areObjectsEqual(cachedFilterData?.matchingFilters, matchingFilters) && + areObjectsEqual(cachedFilterData?.matchingDatasources, matchingDatasources) + ) { + return cachedIndicatorsForChart[chartId]; + } + const indicators = matchingFilters.reduce( + (acc, filter) => + acc.concat( + selectIndicatorsForChartFromFilter( + chartId, + filter, + datasources[filter.datasourceId] || {}, + appliedColumns, + rejectedColumns, ), - [] as Indicator[], - ); + ), + [] as Indicator[], + ); indicators.sort((a, b) => a.name.localeCompare(b.name)); + cachedIndicatorsForChart[chartId] = indicators; + cachedDashboardFilterDataForChart[chartId] = { + appliedColumns, + rejectedColumns, + matchingFilters, + matchingDatasources, + }; return indicators; }; +const cachedNativeIndicatorsForChart = {}; +let cachedNativeFilterDataForChart: any = {}; +const defaultChartConfig = {}; export const selectNativeIndicatorsForChart = ( nativeFilters: Filters, dataMask: DataMaskStateWithId, chartId: number, chart: any, dashboardLayout: Layout, - chartConfiguration: ChartConfiguration = {}, + chartConfiguration: ChartConfiguration = defaultChartConfig, ): Indicator[] => { const appliedColumns = getAppliedColumns(chart); const rejectedColumns = getRejectedColumns(chart); + const cachedFilterData = cachedNativeFilterDataForChart[chartId]; + if ( + cachedNativeIndicatorsForChart[chartId] && + areObjectsEqual(cachedFilterData?.appliedColumns, appliedColumns) && + areObjectsEqual(cachedFilterData?.rejectedColumns, rejectedColumns) && + cachedNativeFilterDataForChart?.nativeFilters === nativeFilters && + cachedNativeFilterDataForChart?.dashboardLayout === dashboardLayout && + cachedNativeFilterDataForChart?.chartConfiguration === chartConfiguration && + cachedNativeFilterDataForChart?.dataMask === dataMask + ) { + return cachedNativeIndicatorsForChart[chartId]; + } const getStatus = ({ label, column, @@ -283,5 +324,18 @@ export const selectNativeIndicatorsForChart = ( }) .filter(filter => filter.status === IndicatorStatus.CrossFilterApplied); } - return crossFilterIndicators.concat(nativeFilterIndicators); + const indicators = crossFilterIndicators.concat(nativeFilterIndicators); + cachedNativeIndicatorsForChart[chartId] = indicators; + cachedNativeFilterDataForChart = { + ...cachedNativeFilterDataForChart, + nativeFilters, + dashboardLayout, + chartConfiguration, + dataMask, + }; + cachedNativeFilterDataForChart[chartId] = { + appliedColumns, + rejectedColumns, + }; + return indicators; }; diff --git a/superset-frontend/src/dashboard/components/SliceHeader/index.tsx b/superset-frontend/src/dashboard/components/SliceHeader/index.tsx index a0cdb94be80..9b42c81224c 100644 --- a/superset-frontend/src/dashboard/components/SliceHeader/index.tsx +++ b/superset-frontend/src/dashboard/components/SliceHeader/index.tsx @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import React, { FC } from 'react'; +import React, { FC, useMemo } from 'react'; import { styled, t } from '@superset-ui/core'; import { Tooltip } from 'src/components/Tooltip'; import { useDispatch, useSelector } from 'react-redux'; @@ -89,6 +89,14 @@ const SliceHeader: FC = ({ state => state.dataMask[slice?.slice_id]?.filterState?.value, ); + const indicator = useMemo( + () => ({ + value: crossFilterValue, + name: t('Emitted values'), + }), + [crossFilterValue], + ); + return (
@@ -139,10 +147,7 @@ const SliceHeader: FC = ({ placement="top" title={ }