This is an automated email from the ASF dual-hosted git repository.
kgabryje pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/master by this push:
new ce0e06a935 perf: Optimize native filters and cross filters (#31243)
ce0e06a935 is described below
commit ce0e06a9359feaf2181e33f71e42284058a09c35
Author: Kamil Gabryjelski <[email protected]>
AuthorDate: Mon Dec 2 15:42:34 2024 +0100
perf: Optimize native filters and cross filters (#31243)
---
.../DashboardBuilder/DashboardContainer.tsx | 9 +-
.../dashboard/components/FiltersBadge/index.tsx | 17 +-
.../ScopingModal/ScopingModal.test.tsx | 3 +
.../CrossFilters/ScopingModal/ScopingModal.tsx | 24 ++-
.../FilterBar/CrossFilters/Vertical.tsx | 18 +-
.../FilterBar/CrossFilters/selectors.ts | 27 +--
.../FilterBar/FilterControls/FilterControls.tsx | 201 +++++++++++----------
.../FilterBar/FilterControls/FilterValue.tsx | 2 +-
.../nativeFilters/FilterBar/Horizontal.tsx | 38 ++--
.../components/nativeFilters/FilterBar/index.tsx | 48 +++--
.../nativeFilters/FilterBar/useFilterOutlined.ts | 25 ++-
.../components/nativeFilters/FilterBar/utils.ts | 31 ++--
.../nativeFilters/FilterCard/FilterCard.test.tsx | 3 +
.../components/nativeFilters/selectors.ts | 32 ++--
.../dashboard/components/nativeFilters/state.ts | 55 +++---
.../components/nativeFilters/utils.test.ts | 6 +-
.../dashboard/components/nativeFilters/utils.ts | 93 ++--------
.../src/dashboard/util/charts/useChartIds.ts | 17 +-
.../src/dashboard/util/crossFilters.ts | 11 +-
.../src/dashboard/util/getChartIdsInFilterScope.ts | 5 +-
.../useChartIds.ts => useChartLayoutItems.ts} | 21 +--
.../util/useFilterFocusHighlightStyles.test.tsx | 50 -----
.../util/useFilterFocusHighlightStyles.ts | 64 ++-----
23 files changed, 354 insertions(+), 446 deletions(-)
diff --git
a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardContainer.tsx
b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardContainer.tsx
index c333e5318a..929c5af8fd 100644
---
a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardContainer.tsx
+++
b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardContainer.tsx
@@ -60,6 +60,7 @@ import {
ensureSyncedSharedLabelsColors,
ensureSyncedLabelsColorMap,
} from 'src/dashboard/actions/dashboardState';
+import { CHART_TYPE } from 'src/dashboard/util/componentTypes';
import { getColorNamespace, resetColors } from 'src/utils/colorScheme';
import { NATIVE_FILTER_DIVIDER_PREFIX } from
'../nativeFilters/FiltersConfigModal/utils';
import { findTabsWithChartsInScope } from '../nativeFilters/utils';
@@ -160,14 +161,18 @@ const DashboardContainer: FC<DashboardContainerProps> =
({ topLevelTabs }) => {
};
}
+ const chartLayoutItems = Object.values(dashboardLayout).filter(
+ item => item?.type === CHART_TYPE,
+ );
+
const chartsInScope: number[] = getChartIdsInFilterScope(
filterScope.scope,
chartIds,
- dashboardLayout,
+ chartLayoutItems,
);
const tabsInScope = findTabsWithChartsInScope(
- dashboardLayout,
+ chartLayoutItems,
chartsInScope,
);
return {
diff --git a/superset-frontend/src/dashboard/components/FiltersBadge/index.tsx
b/superset-frontend/src/dashboard/components/FiltersBadge/index.tsx
index 485879e959..57413ffd23 100644
--- a/superset-frontend/src/dashboard/components/FiltersBadge/index.tsx
+++ b/superset-frontend/src/dashboard/components/FiltersBadge/index.tsx
@@ -39,6 +39,7 @@ import {
} from '@superset-ui/core';
import Icons from 'src/components/Icons';
import { setDirectPathToChild } from 'src/dashboard/actions/dashboardState';
+import { useChartLayoutItems } from 'src/dashboard/util/useChartLayoutItems';
import Badge from 'src/components/Badge';
import DetailsPanelPopover from './DetailsPanel';
import {
@@ -47,7 +48,7 @@ import {
selectIndicatorsForChart,
selectNativeIndicatorsForChart,
} from '../nativeFilters/selectors';
-import { Chart, DashboardLayout, RootState } from '../../types';
+import { Chart, RootState } from '../../types';
export interface FiltersBadgeProps {
chartId: number;
@@ -126,9 +127,7 @@ export const FiltersBadge = ({ chartId }:
FiltersBadgeProps) => {
state => state.dashboardInfo.metadata?.chart_configuration,
);
const chart = useSelector<RootState, Chart>(state => state.charts[chartId]);
- const present = useSelector<RootState, DashboardLayout>(
- state => state.dashboardLayout.present,
- );
+ const chartLayoutItems = useChartLayoutItems();
const dataMask = useSelector<RootState, DataMaskStateWithId>(
state => state.dataMask,
);
@@ -207,7 +206,7 @@ export const FiltersBadge = ({ chartId }:
FiltersBadgeProps) => {
]);
const prevNativeFilters = usePrevious(nativeFilters);
- const prevDashboardLayout = usePrevious(present);
+ const prevChartLayoutItems = usePrevious(chartLayoutItems);
const prevDataMask = usePrevious(dataMask);
const prevChartConfig = usePrevious(chartConfiguration);
@@ -221,7 +220,7 @@ export const FiltersBadge = ({ chartId }:
FiltersBadgeProps) => {
chart?.queriesResponse?.[0]?.applied_filters !==
prevChart?.queriesResponse?.[0]?.applied_filters ||
nativeFilters !== prevNativeFilters ||
- present !== prevDashboardLayout ||
+ chartLayoutItems !== prevChartLayoutItems ||
dataMask !== prevDataMask ||
prevChartConfig !== chartConfiguration
) {
@@ -231,7 +230,7 @@ export const FiltersBadge = ({ chartId }:
FiltersBadgeProps) => {
dataMask,
chartId,
chart,
- present,
+ chartLayoutItems,
chartConfiguration,
),
);
@@ -244,14 +243,14 @@ export const FiltersBadge = ({ chartId }:
FiltersBadgeProps) => {
dataMask,
nativeFilters,
nativeIndicators.length,
- present,
prevChart?.queriesResponse,
prevChartConfig,
prevChartStatus,
- prevDashboardLayout,
prevDataMask,
prevNativeFilters,
showIndicators,
+ chartLayoutItems,
+ prevChartLayoutItems,
]);
const indicators = useMemo(
diff --git
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/ScopingModal/ScopingModal.test.tsx
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/ScopingModal/ScopingModal.test.tsx
index 392db3ae09..8914398c54 100644
---
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/ScopingModal/ScopingModal.test.tsx
+++
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/ScopingModal/ScopingModal.test.tsx
@@ -39,6 +39,9 @@ const INITIAL_STATE = {
3: { id: 3 },
4: { id: 4 },
},
+ dashboardState: {
+ sliceIds: [1, 2, 3, 4],
+ },
dashboardInfo: {
id: 1,
metadata: {
diff --git
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/ScopingModal/ScopingModal.tsx
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/ScopingModal/ScopingModal.tsx
index 61375f9ceb..e4530eacec 100644
---
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/ScopingModal/ScopingModal.tsx
+++
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/ScopingModal/ScopingModal.tsx
@@ -22,7 +22,6 @@ import { isDefined, NativeFilterScope, t } from
'@superset-ui/core';
import Modal from 'src/components/Modal';
import {
ChartConfiguration,
- Layout,
RootState,
isCrossFilterScopeGlobal,
GlobalChartCrossFilterConfig,
@@ -32,6 +31,7 @@ import { getChartIdsInFilterScope } from
'src/dashboard/util/getChartIdsInFilter
import { useChartIds } from 'src/dashboard/util/charts/useChartIds';
import { saveChartConfiguration } from 'src/dashboard/actions/dashboardInfo';
import { DEFAULT_CROSS_FILTER_SCOPING } from 'src/dashboard/constants';
+import { useChartLayoutItems } from 'src/dashboard/util/useChartLayoutItems';
import { ScopingModalContent } from './ScopingModalContent';
import { NEW_CHART_SCOPING_ID } from './constants';
@@ -76,9 +76,7 @@ export const ScopingModal = ({
closeModal,
}: ScopingModalProps) => {
const dispatch = useDispatch();
- const layout = useSelector<RootState, Layout>(
- state => state.dashboardLayout.present,
- );
+ const chartLayoutItems = useChartLayoutItems();
const chartIds = useChartIds();
const [currentChartId, setCurrentChartId] = useState(initialChartId);
const initialChartConfig = useSelector<RootState, ChartConfiguration>(
@@ -154,7 +152,11 @@ export const ScopingModal = ({
id: currentChartId,
crossFilters: {
scope,
- chartsInScope: getChartIdsInFilterScope(scope, chartIds, layout),
+ chartsInScope: getChartIdsInFilterScope(
+ scope,
+ chartIds,
+ chartLayoutItems,
+ ),
},
},
}));
@@ -162,7 +164,7 @@ export const ScopingModal = ({
const globalChartsInScope = getChartIdsInFilterScope(
scope,
chartIds,
- layout,
+ chartLayoutItems,
);
setGlobalChartConfig({
scope,
@@ -176,7 +178,7 @@ export const ScopingModal = ({
);
}
},
- [currentChartId, chartIds, layout],
+ [currentChartId, chartIds, chartLayoutItems],
);
const removeCustomScope = useCallback(
@@ -241,7 +243,11 @@ export const ScopingModal = ({
id: newChartId,
crossFilters: {
scope: newScope,
- chartsInScope: getChartIdsInFilterScope(newScope, chartIds,
layout),
+ chartsInScope: getChartIdsInFilterScope(
+ newScope,
+ chartIds,
+ chartLayoutItems,
+ ),
},
};
@@ -275,7 +281,7 @@ export const ScopingModal = ({
currentChartId,
globalChartConfig.chartsInScope,
globalChartConfig.scope,
- layout,
+ chartLayoutItems,
],
);
diff --git
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/Vertical.tsx
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/Vertical.tsx
index 2064d0814f..5deff0a988 100644
---
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/Vertical.tsx
+++
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/Vertical.tsx
@@ -17,9 +17,11 @@
* under the License.
*/
-import { DataMaskStateWithId, JsonObject } from '@superset-ui/core';
+import { DataMaskStateWithId } from '@superset-ui/core';
import { useSelector } from 'react-redux';
-import { DashboardLayout, RootState } from 'src/dashboard/types';
+import { RootState } from 'src/dashboard/types';
+import { useChartLayoutItems } from 'src/dashboard/util/useChartLayoutItems';
+import { useChartIds } from 'src/dashboard/util/charts/useChartIds';
import crossFiltersSelector from './selectors';
import VerticalCollapse from './VerticalCollapse';
import { useChartsVerboseMaps } from '../utils';
@@ -28,17 +30,13 @@ const CrossFiltersVertical = () => {
const dataMask = useSelector<RootState, DataMaskStateWithId>(
state => state.dataMask,
);
- const chartConfiguration = useSelector<RootState, JsonObject>(
- state => state.dashboardInfo.metadata?.chart_configuration,
- );
- const dashboardLayout = useSelector<RootState, DashboardLayout>(
- state => state.dashboardLayout.present,
- );
+ const chartIds = useChartIds();
+ const chartLayoutItems = useChartLayoutItems();
const verboseMaps = useChartsVerboseMaps();
const selectedCrossFilters = crossFiltersSelector({
dataMask,
- chartConfiguration,
- dashboardLayout,
+ chartIds,
+ chartLayoutItems,
verboseMaps,
});
diff --git
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/selectors.ts
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/selectors.ts
index c8fd8e2841..2c2b3ec753 100644
---
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/selectors.ts
+++
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/selectors.ts
@@ -21,36 +21,37 @@ import {
DataMaskStateWithId,
getColumnLabel,
isDefined,
- JsonObject,
} from '@superset-ui/core';
-import { DashboardLayout } from 'src/dashboard/types';
+import { LayoutItem } from 'src/dashboard/types';
import { CrossFilterIndicator, getCrossFilterIndicator } from
'../../selectors';
export const crossFiltersSelector = (props: {
dataMask: DataMaskStateWithId;
- chartConfiguration: JsonObject;
- dashboardLayout: DashboardLayout;
+ chartIds: number[];
+ chartLayoutItems: LayoutItem[];
verboseMaps: { [key: string]: Record<string, string> };
}): CrossFilterIndicator[] => {
- const { dataMask, chartConfiguration, dashboardLayout, verboseMaps } = props;
- const chartsIds = Object.keys(chartConfiguration || {});
+ const { dataMask, chartIds, chartLayoutItems, verboseMaps } = props;
- return chartsIds
+ return chartIds
.map(chartId => {
- const id = Number(chartId);
const filterIndicator = getCrossFilterIndicator(
- id,
- dataMask[id],
- dashboardLayout,
+ chartId,
+ dataMask[chartId],
+ chartLayoutItems,
);
if (
isDefined(filterIndicator.column) &&
isDefined(filterIndicator.value)
) {
const verboseColName =
- verboseMaps[id]?.[getColumnLabel(filterIndicator.column)] ||
+ verboseMaps[chartId]?.[getColumnLabel(filterIndicator.column)] ||
filterIndicator.column;
- return { ...filterIndicator, column: verboseColName, emitterId: id };
+ return {
+ ...filterIndicator,
+ column: verboseColName,
+ emitterId: chartId,
+ };
}
return null;
})
diff --git
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterControls.tsx
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterControls.tsx
index 09415047b1..d959026792 100644
---
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterControls.tsx
+++
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterControls.tsx
@@ -37,7 +37,6 @@ import {
isFeatureEnabled,
FeatureFlag,
isNativeFilterWithDataMask,
- JsonObject,
} from '@superset-ui/core';
import {
createHtmlPortalNode,
@@ -49,15 +48,13 @@ import {
useDashboardHasTabs,
useSelectFiltersInScope,
} from 'src/dashboard/components/nativeFilters/state';
-import {
- DashboardLayout,
- FilterBarOrientation,
- RootState,
-} from 'src/dashboard/types';
+import { FilterBarOrientation, RootState } from 'src/dashboard/types';
import DropdownContainer, {
Ref as DropdownContainerRef,
} from 'src/components/DropdownContainer';
import Icons from 'src/components/Icons';
+import { useChartIds } from 'src/dashboard/util/charts/useChartIds';
+import { useChartLayoutItems } from 'src/dashboard/util/useChartLayoutItems';
import { FiltersOutOfScopeCollapsible } from '../FiltersOutOfScopeCollapsible';
import { useFilterControlFactory } from '../useFilterControlFactory';
import { FiltersDropdownContent } from '../FiltersDropdownContent';
@@ -65,12 +62,15 @@ import crossFiltersSelector from
'../CrossFilters/selectors';
import CrossFilter from '../CrossFilters/CrossFilter';
import { useFilterOutlined } from '../useFilterOutlined';
import { useChartsVerboseMaps } from '../utils';
+import { CrossFilterIndicator } from '../../selectors';
type FilterControlsProps = {
dataMaskSelected: DataMaskStateWithId;
onFilterSelectionChange: (filter: Filter, dataMask: DataMask) => void;
};
+const EMPTY_ARRAY: CrossFilterIndicator[] = [];
+
const FilterControls: FC<FilterControlsProps> = ({
dataMaskSelected,
onFilterSelectionChange,
@@ -90,12 +90,8 @@ const FilterControls: FC<FilterControlsProps> = ({
const dataMask = useSelector<RootState, DataMaskStateWithId>(
state => state.dataMask,
);
- const chartConfiguration = useSelector<RootState, JsonObject>(
- state => state.dashboardInfo.metadata?.chart_configuration,
- );
- const dashboardLayout = useSelector<RootState, DashboardLayout>(
- state => state.dashboardLayout.present,
- );
+ const chartIds = useChartIds();
+ const chartLayoutItems = useChartLayoutItems();
const verboseMaps = useChartsVerboseMaps();
const isCrossFiltersEnabled = isFeatureEnabled(
@@ -106,12 +102,12 @@ const FilterControls: FC<FilterControlsProps> = ({
isCrossFiltersEnabled
? crossFiltersSelector({
dataMask,
- chartConfiguration,
- dashboardLayout,
+ chartIds,
+ chartLayoutItems,
verboseMaps,
})
- : [],
- [chartConfiguration, dashboardLayout, dataMask, isCrossFiltersEnabled],
+ : EMPTY_ARRAY,
+ [chartIds, chartLayoutItems, dataMask, isCrossFiltersEnabled, verboseMaps],
);
const { filterControlFactory, filtersWithValues } = useFilterControlFactory(
dataMaskSelected,
@@ -154,18 +150,27 @@ const FilterControls: FC<FilterControlsProps> = ({
[filtersWithValues, portalNodes],
);
- const renderVerticalContent = () => (
- <>
- {filtersInScope.map(renderer)}
- {showCollapsePanel && (
- <FiltersOutOfScopeCollapsible
- filtersOutOfScope={filtersOutOfScope}
- forceRender={hasRequiredFirst}
- hasTopMargin={filtersInScope.length > 0}
- renderer={renderer}
- />
- )}
- </>
+ const renderVerticalContent = useCallback(
+ () => (
+ <>
+ {filtersInScope.map(renderer)}
+ {showCollapsePanel && (
+ <FiltersOutOfScopeCollapsible
+ filtersOutOfScope={filtersOutOfScope}
+ forceRender={hasRequiredFirst}
+ hasTopMargin={filtersInScope.length > 0}
+ renderer={renderer}
+ />
+ )}
+ </>
+ ),
+ [
+ filtersInScope,
+ renderer,
+ showCollapsePanel,
+ filtersOutOfScope,
+ hasRequiredFirst,
+ ],
);
const overflowedFiltersInScope = useMemo(
@@ -230,70 +235,84 @@ const FilterControls: FC<FilterControlsProps> = ({
return [...crossFilters, ...nativeFiltersInScope];
}, [filtersInScope, renderer, rendererCrossFilter, selectedCrossFilters]);
- const renderHorizontalContent = () => (
- <div
- css={(theme: SupersetTheme) => css`
- padding: 0 ${theme.gridUnit * 4}px;
- min-width: 0;
- flex: 1;
- `}
- >
- <DropdownContainer
- items={items}
- dropdownTriggerIcon={
- <Icons.FilterSmall
- css={css`
- && {
- margin-right: -4px;
- display: flex;
- }
- `}
- />
- }
- dropdownTriggerText={t('More filters')}
- dropdownTriggerCount={activeOverflowedFiltersInScope.length}
- dropdownTriggerTooltip={
- activeOverflowedFiltersInScope.length === 0
- ? t('No applied filters')
- : t(
- 'Applied filters: %s',
- activeOverflowedFiltersInScope
- .map(filter => filter.name)
- .join(', '),
- )
- }
- dropdownContent={
- overflowedFiltersInScope.length ||
- overflowedCrossFilters.length ||
- (filtersOutOfScope.length && showCollapsePanel)
- ? () => (
- <FiltersDropdownContent
- overflowedCrossFilters={overflowedCrossFilters}
- filtersInScope={overflowedFiltersInScope}
- filtersOutOfScope={filtersOutOfScope}
- renderer={renderer}
- rendererCrossFilter={rendererCrossFilter}
- showCollapsePanel={showCollapsePanel}
- forceRenderOutOfScope={hasRequiredFirst}
- />
- )
- : undefined
- }
- forceRender={hasRequiredFirst}
- ref={popoverRef}
- onOverflowingStateChange={({ overflowed: nextOverflowedIds }) => {
- if (
- nextOverflowedIds.length !== overflowedIds.length ||
- overflowedIds.reduce(
- (a, b, i) => a || b !== nextOverflowedIds[i],
- false,
- )
- ) {
- setOverflowedIds(nextOverflowedIds);
+ const renderHorizontalContent = useCallback(
+ () => (
+ <div
+ css={(theme: SupersetTheme) => css`
+ padding: 0 ${theme.gridUnit * 4}px;
+ min-width: 0;
+ flex: 1;
+ `}
+ >
+ <DropdownContainer
+ items={items}
+ dropdownTriggerIcon={
+ <Icons.FilterSmall
+ css={css`
+ && {
+ margin-right: -4px;
+ display: flex;
+ }
+ `}
+ />
}
- }}
- />
- </div>
+ dropdownTriggerText={t('More filters')}
+ dropdownTriggerCount={activeOverflowedFiltersInScope.length}
+ dropdownTriggerTooltip={
+ activeOverflowedFiltersInScope.length === 0
+ ? t('No applied filters')
+ : t(
+ 'Applied filters: %s',
+ activeOverflowedFiltersInScope
+ .map(filter => filter.name)
+ .join(', '),
+ )
+ }
+ dropdownContent={
+ overflowedFiltersInScope.length ||
+ overflowedCrossFilters.length ||
+ (filtersOutOfScope.length && showCollapsePanel)
+ ? () => (
+ <FiltersDropdownContent
+ overflowedCrossFilters={overflowedCrossFilters}
+ filtersInScope={overflowedFiltersInScope}
+ filtersOutOfScope={filtersOutOfScope}
+ renderer={renderer}
+ rendererCrossFilter={rendererCrossFilter}
+ showCollapsePanel={showCollapsePanel}
+ forceRenderOutOfScope={hasRequiredFirst}
+ />
+ )
+ : undefined
+ }
+ forceRender={hasRequiredFirst}
+ ref={popoverRef}
+ onOverflowingStateChange={({ overflowed: nextOverflowedIds }) => {
+ if (
+ nextOverflowedIds.length !== overflowedIds.length ||
+ overflowedIds.reduce(
+ (a, b, i) => a || b !== nextOverflowedIds[i],
+ false,
+ )
+ ) {
+ setOverflowedIds(nextOverflowedIds);
+ }
+ }}
+ />
+ </div>
+ ),
+ [
+ items,
+ activeOverflowedFiltersInScope,
+ overflowedFiltersInScope,
+ overflowedCrossFilters,
+ filtersOutOfScope,
+ showCollapsePanel,
+ renderer,
+ rendererCrossFilter,
+ hasRequiredFirst,
+ overflowedIds,
+ ],
);
const overflowedByIndex = useMemo(() => {
diff --git
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterValue.tsx
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterValue.tsx
index 45ccd4dd41..454d906032 100644
---
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterValue.tsx
+++
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterValue.tsx
@@ -221,7 +221,7 @@ const FilterValue: FC<FilterControlProps> = ({
datasetId,
groupby,
handleFilterLoadFinish,
- JSON.stringify(filter),
+ filter,
hasDataSource,
isRefreshing,
shouldRefresh,
diff --git
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Horizontal.tsx
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Horizontal.tsx
index 2b96d9963f..e97a8768e0 100644
---
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Horizontal.tsx
+++
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Horizontal.tsx
@@ -17,18 +17,19 @@
* under the License.
*/
-import { FC, memo } from 'react';
+import { FC, memo, useMemo } from 'react';
import {
DataMaskStateWithId,
FeatureFlag,
isFeatureEnabled,
- JsonObject,
styled,
t,
} from '@superset-ui/core';
import Icons from 'src/components/Icons';
import Loading from 'src/components/Loading';
-import { DashboardLayout, RootState } from 'src/dashboard/types';
+import { RootState } from 'src/dashboard/types';
+import { useChartLayoutItems } from 'src/dashboard/util/useChartLayoutItems';
+import { useChartIds } from 'src/dashboard/util/charts/useChartIds';
import { useSelector } from 'react-redux';
import FilterControls from './FilterControls/FilterControls';
import { useChartsVerboseMaps, getFilterBarTestId } from './utils';
@@ -36,6 +37,7 @@ import { HorizontalBarProps } from './types';
import FilterBarSettings from './FilterBarSettings';
import FilterConfigurationLink from './FilterConfigurationLink';
import crossFiltersSelector from './CrossFilters/selectors';
+import { CrossFilterIndicator } from '../selectors';
const HorizontalBar = styled.div`
${({ theme }) => `
@@ -96,6 +98,7 @@ const FiltersLinkContainer = styled.div<{ hasFilters: boolean
}>`
`}
`;
+const EMPTY_ARRAY: CrossFilterIndicator[] = [];
const HorizontalFilterBar: FC<HorizontalBarProps> = ({
actions,
canEdit,
@@ -108,25 +111,26 @@ const HorizontalFilterBar: FC<HorizontalBarProps> = ({
const dataMask = useSelector<RootState, DataMaskStateWithId>(
state => state.dataMask,
);
- const chartConfiguration = useSelector<RootState, JsonObject>(
- state => state.dashboardInfo.metadata?.chart_configuration,
- );
- const dashboardLayout = useSelector<RootState, DashboardLayout>(
- state => state.dashboardLayout.present,
- );
+ const chartIds = useChartIds();
+ const chartLayoutItems = useChartLayoutItems();
const isCrossFiltersEnabled = isFeatureEnabled(
FeatureFlag.DashboardCrossFilters,
);
const verboseMaps = useChartsVerboseMaps();
- const selectedCrossFilters = isCrossFiltersEnabled
- ? crossFiltersSelector({
- dataMask,
- chartConfiguration,
- dashboardLayout,
- verboseMaps,
- })
- : [];
+ const selectedCrossFilters = useMemo(
+ () =>
+ isCrossFiltersEnabled
+ ? crossFiltersSelector({
+ dataMask,
+ chartIds,
+ chartLayoutItems,
+ verboseMaps,
+ })
+ : EMPTY_ARRAY,
+ [chartIds, chartLayoutItems, dataMask, isCrossFiltersEnabled, verboseMaps],
+ );
+
const hasFilters = filterValues.length > 0 || selectedCrossFilters.length >
0;
return (
diff --git
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx
index c25134715d..0b64d37a25 100644
---
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx
+++
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx
@@ -24,8 +24,8 @@ import {
useEffect,
useState,
useCallback,
- createContext,
useRef,
+ useMemo,
} from 'react';
import { useDispatch, useSelector } from 'react-redux';
@@ -129,7 +129,6 @@ const publishDataMask = debounce(
SLOW_DEBOUNCE,
);
-export const FilterBarScrollContext = createContext(false);
const FilterBar: FC<FiltersBarProps> = ({
orientation = FilterBarOrientation.Vertical,
verticalConfig,
@@ -144,8 +143,11 @@ const FilterBar: FC<FiltersBarProps> = ({
const tabId = useTabId();
const filters = useFilters();
const previousFilters = usePrevious(filters);
- const filterValues = Object.values(filters);
- const nativeFilterValues = filterValues.filter(isNativeFilter);
+ const filterValues = useMemo(() => Object.values(filters), [filters]);
+ const nativeFilterValues = useMemo(
+ () => filterValues.filter(isNativeFilter),
+ [filterValues],
+ );
const dashboardId = useSelector<any, number>(
({ dashboardInfo }) => dashboardInfo?.id,
);
@@ -212,14 +214,9 @@ const FilterBar: FC<FiltersBarProps> = ({
if (!isEmpty(updates)) {
setDataMaskSelected(draft => ({ ...draft, ...updates }));
- Object.keys(updates).forEach(key => dispatch(clearDataMask(key)));
}
}
- }, [
- JSON.stringify(filters),
- JSON.stringify(previousFilters),
- previousDashboardId,
- ]);
+ }, [dashboardId, filters, previousDashboardId, setDataMaskSelected]);
const dataMaskAppliedText = JSON.stringify(dataMaskApplied);
@@ -276,16 +273,27 @@ const FilterBar: FC<FiltersBarProps> = ({
);
const isInitialized = useInitialization();
- const actions = (
- <ActionButtons
- filterBarOrientation={orientation}
- width={verticalConfig?.width}
- onApply={handleApply}
- onClearAll={handleClearAll}
- dataMaskSelected={dataMaskSelected}
- dataMaskApplied={dataMaskApplied}
- isApplyDisabled={isApplyDisabled}
- />
+ const actions = useMemo(
+ () => (
+ <ActionButtons
+ filterBarOrientation={orientation}
+ width={verticalConfig?.width}
+ onApply={handleApply}
+ onClearAll={handleClearAll}
+ dataMaskSelected={dataMaskSelected}
+ dataMaskApplied={dataMaskApplied}
+ isApplyDisabled={isApplyDisabled}
+ />
+ ),
+ [
+ orientation,
+ verticalConfig?.width,
+ handleApply,
+ handleClearAll,
+ dataMaskSelected,
+ dataMaskAppliedText,
+ isApplyDisabled,
+ ],
);
const filterBarComponent =
diff --git
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/useFilterOutlined.ts
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/useFilterOutlined.ts
index 05c9b671f8..b2448fcce7 100644
---
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/useFilterOutlined.ts
+++
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/useFilterOutlined.ts
@@ -18,17 +18,26 @@
*/
import { useSelector } from 'react-redux';
+import { createSelector } from '@reduxjs/toolkit';
import { RootState } from 'src/dashboard/types';
import getChartAndLabelComponentIdFromPath from
'src/dashboard/util/getChartAndLabelComponentIdFromPath';
+const filterOutlinedSelector = createSelector(
+ [
+ (state: RootState) => state.dashboardState.directPathToChild,
+ (state: RootState) => state.dashboardState.directPathLastUpdated,
+ ],
+ (directPathToChild, directPathLastUpdated) => ({
+ outlinedFilterId: (
+ getChartAndLabelComponentIdFromPath(directPathToChild || []) as Record<
+ string,
+ string
+ >
+ )?.native_filter,
+ lastUpdated: directPathLastUpdated,
+ }),
+);
export const useFilterOutlined = () =>
useSelector<RootState, { outlinedFilterId: string; lastUpdated: number }>(
- state => ({
- outlinedFilterId: (
- getChartAndLabelComponentIdFromPath(
- state.dashboardState.directPathToChild || [],
- ) as Record<string, string>
- )?.native_filter,
- lastUpdated: state.dashboardState.directPathLastUpdated,
- }),
+ filterOutlinedSelector,
);
diff --git
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/utils.ts
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/utils.ts
index cdf488bc3d..6a6ca3a3e3 100644
---
a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/utils.ts
+++
b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/utils.ts
@@ -22,6 +22,7 @@ import { DataMaskStateWithId, Filter, FilterState } from
'@superset-ui/core';
import { testWithId } from 'src/utils/testUtils';
import { RootState } from 'src/dashboard/types';
import { useSelector } from 'react-redux';
+import { createSelector } from '@reduxjs/toolkit';
export const getOnlyExtraFormData = (data: DataMaskStateWithId) =>
Object.values(data).reduce(
@@ -64,20 +65,26 @@ export const checkIsApplyDisabled = (
);
};
+const chartsVerboseMapSelector = createSelector(
+ [
+ (state: RootState) => state.sliceEntities.slices,
+ (state: RootState) => state.datasources,
+ ],
+ (slices, datasources) =>
+ Object.keys(slices).reduce((chartsVerboseMaps, chartId) => {
+ const chartDatasource = slices[chartId]?.datasource
+ ? datasources[slices[chartId].datasource]
+ : undefined;
+ return {
+ ...chartsVerboseMaps,
+ [chartId]: chartDatasource ? chartDatasource.verbose_map : {},
+ };
+ }, {}),
+);
+
export const useChartsVerboseMaps = () =>
useSelector<RootState, { [chartId: string]: Record<string, string> }>(
- state => {
- const { charts, datasources } = state;
-
- return Object.keys(state.charts).reduce((chartsVerboseMaps, chartId) => {
- const chartDatasource =
- datasources[charts[chartId]?.form_data?.datasource];
- return {
- ...chartsVerboseMaps,
- [chartId]: chartDatasource ? chartDatasource.verbose_map : {},
- };
- }, {});
- },
+ chartsVerboseMapSelector,
);
export const FILTER_BAR_TEST_ID = 'filter-bar';
diff --git
a/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/FilterCard.test.tsx
b/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/FilterCard.test.tsx
index 319fecec0a..2f2f4e4525 100644
---
a/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/FilterCard.test.tsx
+++
b/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/FilterCard.test.tsx
@@ -86,6 +86,9 @@ const baseInitialState = {
id: 3,
},
},
+ dashboardState: {
+ sliceIds: [1, 2, 3],
+ },
dashboardLayout: {
past: [],
future: [],
diff --git
a/superset-frontend/src/dashboard/components/nativeFilters/selectors.ts
b/superset-frontend/src/dashboard/components/nativeFilters/selectors.ts
index 36f0fd1925..232ff57de2 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/selectors.ts
+++ b/superset-frontend/src/dashboard/components/nativeFilters/selectors.ts
@@ -32,11 +32,7 @@ import {
} from '@superset-ui/core';
import { TIME_FILTER_MAP } from 'src/explore/constants';
import { getChartIdsInFilterScope } from
'src/dashboard/util/activeDashboardFilters';
-import {
- ChartConfiguration,
- DashboardLayout,
- Layout,
-} from 'src/dashboard/types';
+import { ChartConfiguration, LayoutItem } from 'src/dashboard/types';
import { areObjectsEqual } from 'src/reduxUtils';
export enum IndicatorStatus {
@@ -170,7 +166,7 @@ export type CrossFilterIndicator = Indicator & { emitterId:
number };
export const getCrossFilterIndicator = (
chartId: number,
dataMask: DataMask,
- dashboardLayout: DashboardLayout,
+ chartLayoutItems: LayoutItem[],
) => {
const filterState = dataMask?.filterState;
const filters = dataMask?.extraFormData?.filters;
@@ -179,19 +175,17 @@ export const getCrossFilterIndicator = (
const column =
filters?.[0]?.col || (filtersState && Object.keys(filtersState)[0]);
- const dashboardLayoutItem = Object.values(dashboardLayout).find(
+ const chartLayoutItem = chartLayoutItems.find(
layoutItem => layoutItem?.meta?.chartId === chartId,
);
+
const filterObject: Indicator = {
column,
name:
- dashboardLayoutItem?.meta?.sliceNameOverride ||
- dashboardLayoutItem?.meta?.sliceName ||
+ chartLayoutItem?.meta?.sliceNameOverride ||
+ chartLayoutItem?.meta?.sliceName ||
'',
- path: [
- ...(dashboardLayoutItem?.parents ?? []),
- dashboardLayoutItem?.id || '',
- ],
+ path: [...(chartLayoutItem?.parents ?? []), chartLayoutItem?.id || ''],
value: label,
};
return filterObject;
@@ -288,7 +282,7 @@ const defaultChartConfig = {};
export const selectChartCrossFilters = (
dataMask: DataMaskStateWithId,
chartId: number,
- dashboardLayout: Layout,
+ chartLayoutItems: LayoutItem[],
chartConfiguration: ChartConfiguration = defaultChartConfig,
appliedColumns: Set<string>,
rejectedColumns: Set<string>,
@@ -312,7 +306,7 @@ export const selectChartCrossFilters = (
const filterIndicator = getCrossFilterIndicator(
Number(chartConfig.id),
dataMask[chartConfig.id],
- dashboardLayout,
+ chartLayoutItems,
);
const filterStatus = getStatus({
label: filterIndicator.value,
@@ -339,7 +333,7 @@ export const selectNativeIndicatorsForChart = (
dataMask: DataMaskStateWithId,
chartId: number,
chart: any,
- dashboardLayout: Layout,
+ chartLayoutItems: LayoutItem[],
chartConfiguration: ChartConfiguration = defaultChartConfig,
): Indicator[] => {
const appliedColumns = getAppliedColumns(chart);
@@ -351,7 +345,7 @@ export const selectNativeIndicatorsForChart = (
areObjectsEqual(cachedFilterData?.appliedColumns, appliedColumns) &&
areObjectsEqual(cachedFilterData?.rejectedColumns, rejectedColumns) &&
cachedFilterData?.nativeFilters === nativeFilters &&
- cachedFilterData?.dashboardLayout === dashboardLayout &&
+ cachedFilterData?.chartLayoutItems === chartLayoutItems &&
cachedFilterData?.chartConfiguration === chartConfiguration &&
cachedFilterData?.dataMask === dataMask
) {
@@ -389,7 +383,7 @@ export const selectNativeIndicatorsForChart = (
crossFilterIndicators = selectChartCrossFilters(
dataMask,
chartId,
- dashboardLayout,
+ chartLayoutItems,
chartConfiguration,
appliedColumns,
rejectedColumns,
@@ -399,7 +393,7 @@ export const selectNativeIndicatorsForChart = (
cachedNativeIndicatorsForChart[chartId] = indicators;
cachedNativeFilterDataForChart[chartId] = {
nativeFilters,
- dashboardLayout,
+ chartLayoutItems,
chartConfiguration,
dataMask,
appliedColumns,
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/state.ts
b/superset-frontend/src/dashboard/components/nativeFilters/state.ts
index 5bf71116c3..a17ebc15d8 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/state.ts
+++ b/superset-frontend/src/dashboard/components/nativeFilters/state.ts
@@ -17,7 +17,7 @@
* under the License.
*/
import { useSelector } from 'react-redux';
-import { useMemo } from 'react';
+import { useCallback, useMemo } from 'react';
import {
Filter,
FilterConfiguration,
@@ -25,7 +25,7 @@ import {
isFilterDivider,
} from '@superset-ui/core';
import { ActiveTabs, DashboardLayout, RootState } from '../../types';
-import { TAB_TYPE } from '../../util/componentTypes';
+import { CHART_TYPE, TAB_TYPE } from '../../util/componentTypes';
const defaultFilterConfiguration: Filter[] = [];
@@ -79,34 +79,45 @@ function useActiveDashboardTabs() {
function useSelectChartTabParents() {
const dashboardLayout = useDashboardLayout();
- return (chartId: number) => {
- const chartLayoutItem = Object.values(dashboardLayout).find(
- layoutItem => layoutItem.meta?.chartId === chartId,
- );
- return chartLayoutItem?.parents?.filter(
- (parent: string) => dashboardLayout[parent]?.type === TAB_TYPE,
- );
- };
+ const layoutChartItems = useMemo(
+ () =>
+ Object.values(dashboardLayout).filter(item => item.type === CHART_TYPE),
+ [dashboardLayout],
+ );
+ return useCallback(
+ (chartId: number) => {
+ const chartLayoutItem = layoutChartItems.find(
+ layoutItem => layoutItem.meta?.chartId === chartId,
+ );
+ return chartLayoutItem?.parents?.filter(
+ (parent: string) => dashboardLayout[parent]?.type === TAB_TYPE,
+ );
+ },
+ [dashboardLayout, layoutChartItems],
+ );
}
export function useIsFilterInScope() {
const activeTabs = useActiveDashboardTabs();
const selectChartTabParents = useSelectChartTabParents();
- // Filter is in scope if any of it's charts is visible.
+ // Filter is in scope if any of its charts is visible.
// Chart is visible if it's placed in an active tab tree or if it's not
attached to any tab.
- // Chart is in an active tab tree if all of it's ancestors of type TAB are
active
+ // Chart is in an active tab tree if all of its ancestors of type TAB are
active
// Dividers are always in scope
- return (filter: Filter | Divider) =>
- isFilterDivider(filter) ||
- ('chartsInScope' in filter &&
- filter.chartsInScope?.some((chartId: number) => {
- const tabParents = selectChartTabParents(chartId);
- return (
- tabParents?.length === 0 ||
- tabParents?.every(tab => activeTabs.includes(tab))
- );
- }));
+ return useCallback(
+ (filter: Filter | Divider) =>
+ isFilterDivider(filter) ||
+ ('chartsInScope' in filter &&
+ filter.chartsInScope?.some((chartId: number) => {
+ const tabParents = selectChartTabParents(chartId);
+ return (
+ tabParents?.length === 0 ||
+ tabParents?.every(tab => activeTabs.includes(tab))
+ );
+ })),
+ [selectChartTabParents, activeTabs],
+ );
}
export function useSelectFiltersInScope(filters: (Filter | Divider)[]) {
diff --git
a/superset-frontend/src/dashboard/components/nativeFilters/utils.test.ts
b/superset-frontend/src/dashboard/components/nativeFilters/utils.test.ts
index 095a9edaf3..db21db547d 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/utils.test.ts
+++ b/superset-frontend/src/dashboard/components/nativeFilters/utils.test.ts
@@ -19,6 +19,7 @@
import { Behavior, FeatureFlag } from '@superset-ui/core';
import * as uiCore from '@superset-ui/core';
import { DashboardLayout } from 'src/dashboard/types';
+import { CHART_TYPE } from 'src/dashboard/util/componentTypes';
import { nativeFilterGate, findTabsWithChartsInScope } from './utils';
let isFeatureEnabledMock: jest.MockInstance<boolean, [feature: FeatureFlag]>;
@@ -119,7 +120,10 @@ test('findTabsWithChartsInScope should handle a recursive
layout structure', ()
},
} as any as DashboardLayout;
- expect(Array.from(findTabsWithChartsInScope(dashboardLayout, []))).toEqual(
+ const chartLayoutItems = Object.values(dashboardLayout).filter(
+ item => item.type === CHART_TYPE,
+ );
+ expect(Array.from(findTabsWithChartsInScope(chartLayoutItems, []))).toEqual(
[],
);
});
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/utils.ts
b/superset-frontend/src/dashboard/components/nativeFilters/utils.ts
index 0ba28d0a3a..734e0ce91f 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/utils.ts
+++ b/superset-frontend/src/dashboard/components/nativeFilters/utils.ts
@@ -30,10 +30,9 @@ import {
QueryFormData,
t,
} from '@superset-ui/core';
-import { DashboardLayout } from 'src/dashboard/types';
+import { LayoutItem } from 'src/dashboard/types';
import extractUrlParams from 'src/dashboard/util/extractUrlParams';
-import { CHART_TYPE, TAB_TYPE } from '../../util/componentTypes';
-import { DASHBOARD_GRID_ID, DASHBOARD_ROOT_ID } from '../../util/constants';
+import { TAB_TYPE } from '../../util/componentTypes';
import getBootstrapData from '../../../utils/getBootstrapData';
const getDefaultRowLimit = (): number => {
@@ -156,84 +155,20 @@ export function nativeFilterGate(behaviors: Behavior[]):
boolean {
);
}
-const isComponentATab = (
- dashboardLayout: DashboardLayout,
- componentId: string,
-) => dashboardLayout?.[componentId]?.type === TAB_TYPE;
-
-const findTabsWithChartsInScopeHelper = (
- dashboardLayout: DashboardLayout,
- chartsInScope: number[],
- componentId: string,
- tabIds: string[],
- tabsToHighlight: Set<string>,
- visited: Set<string>,
-) => {
- if (visited.has(componentId)) {
- return;
- }
- visited.add(componentId);
- if (
- dashboardLayout?.[componentId]?.type === CHART_TYPE &&
- chartsInScope.includes(dashboardLayout[componentId]?.meta?.chartId)
- ) {
- tabIds.forEach(tabsToHighlight.add, tabsToHighlight);
- }
- if (
- dashboardLayout?.[componentId]?.children?.length === 0 ||
- (isComponentATab(dashboardLayout, componentId) &&
- tabsToHighlight.has(componentId))
- ) {
- return;
- }
- dashboardLayout[componentId]?.children.forEach(childId =>
- findTabsWithChartsInScopeHelper(
- dashboardLayout,
- chartsInScope,
- childId,
- isComponentATab(dashboardLayout, childId) ? [...tabIds, childId] :
tabIds,
- tabsToHighlight,
- visited,
- ),
- );
-};
-
export const findTabsWithChartsInScope = (
- dashboardLayout: DashboardLayout,
+ chartLayoutItems: LayoutItem[],
chartsInScope: number[],
-) => {
- const dashboardRoot = dashboardLayout[DASHBOARD_ROOT_ID];
- const rootChildId = dashboardRoot.children[0];
- const hasTopLevelTabs = rootChildId !== DASHBOARD_GRID_ID;
- const tabsInScope = new Set<string>();
- const visited = new Set<string>();
- if (hasTopLevelTabs) {
- dashboardLayout[rootChildId]?.children?.forEach(tabId =>
- findTabsWithChartsInScopeHelper(
- dashboardLayout,
- chartsInScope,
- tabId,
- [tabId],
- tabsInScope,
- visited,
- ),
- );
- } else {
- Object.values(dashboardLayout)
- .filter(element => element?.type === TAB_TYPE)
- .forEach(element =>
- findTabsWithChartsInScopeHelper(
- dashboardLayout,
- chartsInScope,
- element.id,
- [element.id],
- tabsInScope,
- visited,
- ),
- );
- }
- return tabsInScope;
-};
+) =>
+ new Set<string>(
+ chartsInScope
+ .map(chartId =>
+ chartLayoutItems
+ .find(item => item?.meta?.chartId === chartId)
+ ?.parents?.filter(parent => parent.startsWith(`${TAB_TYPE}-`)),
+ )
+ .filter(id => id !== undefined)
+ .flat() as string[],
+ );
export const getFilterValueForDisplay = (
value?: string[] | null | string | number | object,
diff --git a/superset-frontend/src/dashboard/util/charts/useChartIds.ts
b/superset-frontend/src/dashboard/util/charts/useChartIds.ts
index c95327a94a..9afc16d9f4 100644
--- a/superset-frontend/src/dashboard/util/charts/useChartIds.ts
+++ b/superset-frontend/src/dashboard/util/charts/useChartIds.ts
@@ -17,20 +17,7 @@
* under the License.
*/
import { useSelector } from 'react-redux';
-import { isEqual } from 'lodash';
-import { createSelector } from '@reduxjs/toolkit';
import { RootState } from 'src/dashboard/types';
-import { useMemoCompare } from 'src/hooks/useMemoCompare';
-const chartIdsSelector = createSelector(
- (state: RootState) => state.charts,
- charts => Object.values(charts).map(chart => chart.id),
-);
-
-export const useChartIds = () => {
- const chartIds = useSelector<RootState, number[]>(chartIdsSelector);
- return useMemoCompare(
- chartIds,
- (prev, next) => prev === next || isEqual(prev, next),
- );
-};
+export const useChartIds = () =>
+ useSelector<RootState, number[]>(state => state.dashboardState.sliceIds);
diff --git a/superset-frontend/src/dashboard/util/crossFilters.ts
b/superset-frontend/src/dashboard/util/crossFilters.ts
index 903a1f74fb..123435a430 100644
--- a/superset-frontend/src/dashboard/util/crossFilters.ts
+++ b/superset-frontend/src/dashboard/util/crossFilters.ts
@@ -33,6 +33,7 @@ import {
isCrossFilterScopeGlobal,
} from '../types';
import { DEFAULT_CROSS_FILTER_SCOPING } from '../constants';
+import { CHART_TYPE } from './componentTypes';
export const isCrossFiltersEnabled = (
metadataCrossFiltersEnabled: boolean | undefined,
@@ -52,13 +53,17 @@ export const getCrossFiltersConfiguration = (
return undefined;
}
+ const chartLayoutItems = Object.values(dashboardLayout).filter(
+ item => item?.type === CHART_TYPE,
+ );
+
const globalChartConfiguration = metadata.global_chart_configuration?.scope
? {
scope: metadata.global_chart_configuration.scope,
chartsInScope: getChartIdsInFilterScope(
metadata.global_chart_configuration.scope,
Object.values(charts).map(chart => chart.id),
- dashboardLayout,
+ chartLayoutItems,
),
}
: {
@@ -69,7 +74,7 @@ export const getCrossFiltersConfiguration = (
// If user just added cross filter to dashboard it's not saving its scope on
server,
// so we tweak it until user will update scope and will save it in server
const chartConfiguration = {};
- Object.values(dashboardLayout).forEach(layoutItem => {
+ chartLayoutItems.forEach(layoutItem => {
const chartId = layoutItem.meta?.chartId;
if (!isDefined(chartId)) {
@@ -105,7 +110,7 @@ export const getCrossFiltersConfiguration = (
: getChartIdsInFilterScope(
chartConfiguration[chartId].crossFilters.scope,
Object.values(charts).map(chart => chart.id),
- dashboardLayout,
+ chartLayoutItems,
);
}
});
diff --git a/superset-frontend/src/dashboard/util/getChartIdsInFilterScope.ts
b/superset-frontend/src/dashboard/util/getChartIdsInFilterScope.ts
index 7e4b7b1273..5bd37f26bc 100644
--- a/superset-frontend/src/dashboard/util/getChartIdsInFilterScope.ts
+++ b/superset-frontend/src/dashboard/util/getChartIdsInFilterScope.ts
@@ -18,14 +18,13 @@
*/
import { NativeFilterScope } from '@superset-ui/core';
import { CHART_TYPE } from './componentTypes';
-import { Layout } from '../types';
+import { LayoutItem } from '../types';
export function getChartIdsInFilterScope(
filterScope: NativeFilterScope,
chartIds: number[],
- layout: Layout,
+ layoutItems: LayoutItem[],
) {
- const layoutItems = Object.values(layout);
return chartIds.filter(
chartId =>
!filterScope.excluded.includes(chartId) &&
diff --git a/superset-frontend/src/dashboard/util/charts/useChartIds.ts
b/superset-frontend/src/dashboard/util/useChartLayoutItems.ts
similarity index 64%
copy from superset-frontend/src/dashboard/util/charts/useChartIds.ts
copy to superset-frontend/src/dashboard/util/useChartLayoutItems.ts
index c95327a94a..10635fc05b 100644
--- a/superset-frontend/src/dashboard/util/charts/useChartIds.ts
+++ b/superset-frontend/src/dashboard/util/useChartLayoutItems.ts
@@ -16,21 +16,14 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { useSelector } from 'react-redux';
-import { isEqual } from 'lodash';
import { createSelector } from '@reduxjs/toolkit';
-import { RootState } from 'src/dashboard/types';
-import { useMemoCompare } from 'src/hooks/useMemoCompare';
+import { useSelector } from 'react-redux';
+import { RootState } from '../types';
+import { CHART_TYPE } from './componentTypes';
-const chartIdsSelector = createSelector(
- (state: RootState) => state.charts,
- charts => Object.values(charts).map(chart => chart.id),
+const chartLayoutItemsSelector = createSelector(
+ (state: RootState) => state.dashboardLayout.present,
+ layout => Object.values(layout).filter(item => item?.type === CHART_TYPE),
);
-export const useChartIds = () => {
- const chartIds = useSelector<RootState, number[]>(chartIdsSelector);
- return useMemoCompare(
- chartIds,
- (prev, next) => prev === next || isEqual(prev, next),
- );
-};
+export const useChartLayoutItems = () => useSelector(chartLayoutItemsSelector);
diff --git
a/superset-frontend/src/dashboard/util/useFilterFocusHighlightStyles.test.tsx
b/superset-frontend/src/dashboard/util/useFilterFocusHighlightStyles.test.tsx
index 80e6038c07..a2047bf2aa 100644
---
a/superset-frontend/src/dashboard/util/useFilterFocusHighlightStyles.test.tsx
+++
b/superset-frontend/src/dashboard/util/useFilterFocusHighlightStyles.test.tsx
@@ -141,54 +141,4 @@ describe('useFilterFocusHighlightStyles', () => {
const styles = getComputedStyle(container);
expect(parseFloat(styles.opacity)).toBe(1);
});
-
- it('should return unfocused styles if focusedFilterField is targeting a
different chart', async () => {
- const chartId = 18;
- mockGetRelatedCharts.mockReturnValue([]);
- const store = createMockStore({
- dashboardState: {
- focusedFilterField: {
- chartId: 10,
- column: 'test',
- },
- },
- dashboardFilters: {
- 10: {
- scopes: {},
- },
- },
- });
- renderWrapper(chartId, store);
-
- const container = screen.getByTestId('test-component');
-
- const styles = getComputedStyle(container);
- expect(parseFloat(styles.opacity)).toBe(0.3);
- });
-
- it('should return focused styles if focusedFilterField chart equals our
own', async () => {
- const chartId = 18;
- mockGetRelatedCharts.mockReturnValue([chartId]);
- const store = createMockStore({
- dashboardState: {
- focusedFilterField: {
- chartId,
- column: 'test',
- },
- },
- dashboardFilters: {
- [chartId]: {
- scopes: {
- otherColumn: {},
- },
- },
- },
- });
- renderWrapper(chartId, store);
-
- const container = screen.getByTestId('test-component');
-
- const styles = getComputedStyle(container);
- expect(parseFloat(styles.opacity)).toBe(1);
- });
});
diff --git
a/superset-frontend/src/dashboard/util/useFilterFocusHighlightStyles.ts
b/superset-frontend/src/dashboard/util/useFilterFocusHighlightStyles.ts
index aa636cb1ee..3dea0d54ac 100644
--- a/superset-frontend/src/dashboard/util/useFilterFocusHighlightStyles.ts
+++ b/superset-frontend/src/dashboard/util/useFilterFocusHighlightStyles.ts
@@ -16,40 +16,30 @@
* specific language governing permissions and limitations
* under the License.
*/
+import { useMemo } from 'react';
import { Filter, useTheme } from '@superset-ui/core';
import { useSelector } from 'react-redux';
-import { getChartIdsInFilterScope } from
'src/dashboard/util/activeDashboardFilters';
-import { DashboardState, RootState } from 'src/dashboard/types';
+import { RootState } from 'src/dashboard/types';
import { getRelatedCharts } from './getRelatedCharts';
-const selectFocusedFilterScope = (
- dashboardState: DashboardState,
- dashboardFilters: any,
-) => {
- if (!dashboardState.focusedFilterField) return null;
- const { chartId, column } = dashboardState.focusedFilterField;
- return {
- chartId,
- scope: dashboardFilters[chartId].scopes[column],
- };
-};
+const unfocusedChartStyles = { opacity: 0.3, pointerEvents: 'none' };
+const EMPTY = {};
const useFilterFocusHighlightStyles = (chartId: number) => {
const theme = useTheme();
- const nativeFilters = useSelector((state: RootState) => state.nativeFilters);
- const dashboardState = useSelector(
- (state: RootState) => state.dashboardState,
+ const focusedChartStyles = useMemo(
+ () => ({
+ borderColor: theme.colors.primary.light2,
+ opacity: 1,
+ boxShadow: `0px 0px ${theme.gridUnit * 2}px
${theme.colors.primary.base}`,
+ pointerEvents: 'auto',
+ }),
+ [theme],
);
- const dashboardFilters = useSelector(
- (state: RootState) => state.dashboardFilters,
- );
- const focusedFilterScope = selectFocusedFilterScope(
- dashboardState,
- dashboardFilters,
- );
+ const nativeFilters = useSelector((state: RootState) => state.nativeFilters);
const slices =
useSelector((state: RootState) => state.sliceEntities.slices) || {};
@@ -57,8 +47,8 @@ const useFilterFocusHighlightStyles = (chartId: number) => {
const highlightedFilterId =
nativeFilters?.focusedFilterId || nativeFilters?.hoveredFilterId;
- if (!(focusedFilterScope || highlightedFilterId)) {
- return {};
+ if (!highlightedFilterId) {
+ return EMPTY;
}
const relatedCharts = getRelatedCharts(
@@ -67,29 +57,7 @@ const useFilterFocusHighlightStyles = (chartId: number) => {
slices,
);
- // we use local styles here instead of a conditionally-applied class,
- // because adding any conditional class to this container
- // causes performance issues in Chrome.
-
- // default to the "de-emphasized" state
- const unfocusedChartStyles = { opacity: 0.3, pointerEvents: 'none' };
- const focusedChartStyles = {
- borderColor: theme.colors.primary.light2,
- opacity: 1,
- boxShadow: `0px 0px ${theme.gridUnit * 2}px ${theme.colors.primary.base}`,
- pointerEvents: 'auto',
- };
-
- if (highlightedFilterId) {
- if (relatedCharts.includes(chartId)) {
- return focusedChartStyles;
- }
- } else if (
- chartId === focusedFilterScope?.chartId ||
- getChartIdsInFilterScope({
- filterScope: focusedFilterScope?.scope,
- }).includes(chartId)
- ) {
+ if (highlightedFilterId && relatedCharts.includes(chartId)) {
return focusedChartStyles;
}