perf(dashboard): decrease number of rerenders of FiltersBadge (#16545)

* perf(dashboard): decrease rerenders in FiltersBadge

* implement caching of dashboard filter indicators

* Implement caching for native filter indicators
This commit is contained in:
Kamil Gabryjelski
2021-09-07 12:03:43 +02:00
committed by GitHub
parent 7faa5c6aff
commit effcf3b50f
3 changed files with 180 additions and 62 deletions

View File

@@ -57,6 +57,8 @@ const sortByStatus = (indicators: Indicator[]): Indicator[] => {
);
};
const indicatorsInitialState: Indicator[] = [];
export const FiltersBadge = ({ chartId }: FiltersBadgeProps) => {
const dispatch = useDispatch();
const datasources = useSelector<RootState, any>(state => state.datasources);
@@ -77,9 +79,11 @@ export const FiltersBadge = ({ chartId }: FiltersBadgeProps) => {
state => state.dataMask,
);
const [nativeIndicators, setNativeIndicators] = useState<Indicator[]>([]);
const [nativeIndicators, setNativeIndicators] = useState<Indicator[]>(
indicatorsInitialState,
);
const [dashboardIndicators, setDashboardIndicators] = useState<Indicator[]>(
[],
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 (

View File

@@ -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;
};

View File

@@ -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<SliceHeaderProps> = ({
state => state.dataMask[slice?.slice_id]?.filterState?.value,
);
const indicator = useMemo(
() => ({
value: crossFilterValue,
name: t('Emitted values'),
}),
[crossFilterValue],
);
return (
<div className="chart-header" data-test="slice-header" ref={innerRef}>
<div className="header-title">
@@ -139,10 +147,7 @@ const SliceHeader: FC<SliceHeaderProps> = ({
placement="top"
title={
<FilterIndicator
indicator={{
value: crossFilterValue,
name: t('Emitted values'),
}}
indicator={indicator}
text={t('Click to clear emitted filters')}
/>
}