diff --git a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardContainer.tsx b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardContainer.tsx index c333e5318ae29..929c5af8fdc2d 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 = ({ 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 485879e959543..57413ffd23a80 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(state => state.charts[chartId]); - const present = useSelector( - state => state.dashboardLayout.present, - ); + const chartLayoutItems = useChartLayoutItems(); const dataMask = useSelector( 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 392db3ae09d82..8914398c54a2d 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 61375f9ceb35d..e4530eacec639 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( - state => state.dashboardLayout.present, - ); + const chartLayoutItems = useChartLayoutItems(); const chartIds = useChartIds(); const [currentChartId, setCurrentChartId] = useState(initialChartId); const initialChartConfig = useSelector( @@ -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 2064d0814f037..5deff0a98872b 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( state => state.dataMask, ); - const chartConfiguration = useSelector( - state => state.dashboardInfo.metadata?.chart_configuration, - ); - const dashboardLayout = useSelector( - 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 c8fd8e2841c72..2c2b3ec7531ca 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 }; }): 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 09415047b11b8..d959026792a65 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 = ({ dataMaskSelected, onFilterSelectionChange, @@ -90,12 +90,8 @@ const FilterControls: FC = ({ const dataMask = useSelector( state => state.dataMask, ); - const chartConfiguration = useSelector( - state => state.dashboardInfo.metadata?.chart_configuration, - ); - const dashboardLayout = useSelector( - state => state.dashboardLayout.present, - ); + const chartIds = useChartIds(); + const chartLayoutItems = useChartLayoutItems(); const verboseMaps = useChartsVerboseMaps(); const isCrossFiltersEnabled = isFeatureEnabled( @@ -106,12 +102,12 @@ const FilterControls: FC = ({ 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 = ({ [filtersWithValues, portalNodes], ); - const renderVerticalContent = () => ( - <> - {filtersInScope.map(renderer)} - {showCollapsePanel && ( - 0} - renderer={renderer} - /> - )} - + const renderVerticalContent = useCallback( + () => ( + <> + {filtersInScope.map(renderer)} + {showCollapsePanel && ( + 0} + renderer={renderer} + /> + )} + + ), + [ + filtersInScope, + renderer, + showCollapsePanel, + filtersOutOfScope, + hasRequiredFirst, + ], ); const overflowedFiltersInScope = useMemo( @@ -230,70 +235,84 @@ const FilterControls: FC = ({ return [...crossFilters, ...nativeFiltersInScope]; }, [filtersInScope, renderer, rendererCrossFilter, selectedCrossFilters]); - const renderHorizontalContent = () => ( -
css` - padding: 0 ${theme.gridUnit * 4}px; - min-width: 0; - flex: 1; - `} - > - - } - 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) - ? () => ( - - ) - : 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( + () => ( +
css` + padding: 0 ${theme.gridUnit * 4}px; + min-width: 0; + flex: 1; + `} + > + } - }} - /> -
+ 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) + ? () => ( + + ) + : 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); + } + }} + /> +
+ ), + [ + 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 45ccd4dd4119a..454d9060321e9 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 = ({ 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 2b96d9963fc45..e97a8768e063b 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 = ({ actions, canEdit, @@ -108,25 +111,26 @@ const HorizontalFilterBar: FC = ({ const dataMask = useSelector( state => state.dataMask, ); - const chartConfiguration = useSelector( - state => state.dashboardInfo.metadata?.chart_configuration, - ); - const dashboardLayout = useSelector( - 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 c25134715d83e..0b64d37a25169 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 = ({ orientation = FilterBarOrientation.Vertical, verticalConfig, @@ -144,8 +143,11 @@ const FilterBar: FC = ({ 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( ({ dashboardInfo }) => dashboardInfo?.id, ); @@ -212,14 +214,9 @@ const FilterBar: FC = ({ 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 = ({ ); const isInitialized = useInitialization(); - const actions = ( - + const actions = useMemo( + () => ( + + ), + [ + 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 05c9b671f8dad..b2448fcce7b57 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( - state => ({ - outlinedFilterId: ( - getChartAndLabelComponentIdFromPath( - state.dashboardState.directPathToChild || [], - ) as Record - )?.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 cdf488bc3dda8..6a6ca3a3e3a5d 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 }>( - 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 319fecec0a423..2f2f4e4525e42 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 36f0fd1925dd9..232ff57de2ffe 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, rejectedColumns: Set, @@ -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 5bf71116c358e..a17ebc15d85f3 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 095a9edaf3de8..db21db547d597 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; @@ -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 0ba28d0a3a3f9..734e0ce91fe9d 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, - visited: Set, -) => { - 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(); - const visited = new Set(); - 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( + 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 c95327a94a559..9afc16d9f4e75 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(chartIdsSelector); - return useMemoCompare( - chartIds, - (prev, next) => prev === next || isEqual(prev, next), - ); -}; +export const useChartIds = () => + useSelector(state => state.dashboardState.sliceIds); diff --git a/superset-frontend/src/dashboard/util/crossFilters.ts b/superset-frontend/src/dashboard/util/crossFilters.ts index 903a1f74fbe55..123435a430a1f 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 7e4b7b1273121..5bd37f26bcbd6 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/useChartLayoutItems.ts b/superset-frontend/src/dashboard/util/useChartLayoutItems.ts new file mode 100644 index 0000000000000..10635fc05b41c --- /dev/null +++ b/superset-frontend/src/dashboard/util/useChartLayoutItems.ts @@ -0,0 +1,29 @@ +/** + * 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 { createSelector } from '@reduxjs/toolkit'; +import { useSelector } from 'react-redux'; +import { RootState } from '../types'; +import { CHART_TYPE } from './componentTypes'; + +const chartLayoutItemsSelector = createSelector( + (state: RootState) => state.dashboardLayout.present, + layout => Object.values(layout).filter(item => item?.type === CHART_TYPE), +); + +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 80e6038c0726a..a2047bf2aa618 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 aa636cb1ee55d..3dea0d54ac138 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; }