mirror of
https://github.com/apache/superset.git
synced 2026-04-19 08:04:53 +00:00
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:
committed by
GitHub
parent
7faa5c6aff
commit
effcf3b50f
@@ -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 (
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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')}
|
||||
/>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user