feat: Programmatically open "more filters" dropdown in Horizontal Filter Bar (#22276)

Co-authored-by: Michael S. Molina <70410625+michael-s-molina@users.noreply.github.com>
This commit is contained in:
Geido 2022-12-02 15:03:01 +02:00 committed by GitHub
parent 7bc5f04368
commit df91664217
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 234 additions and 58 deletions

View File

@ -126,6 +126,7 @@ export type NativeFiltersState = {
filters: Filters; filters: Filters;
filterSets: FilterSets; filterSets: FilterSets;
focusedFilterId?: string; focusedFilterId?: string;
hoveredFilterId?: string;
}; };
export type DashboardComponentMetadata = { export type DashboardComponentMetadata = {

View File

@ -372,6 +372,28 @@ export function unsetFocusedNativeFilter(): UnsetFocusedNativeFilter {
}; };
} }
export const SET_HOVERED_NATIVE_FILTER = 'SET_HOVERED_NATIVE_FILTER';
export interface SetHoveredNativeFilter {
type: typeof SET_HOVERED_NATIVE_FILTER;
id: string;
}
export const UNSET_HOVERED_NATIVE_FILTER = 'UNSET_HOVERED_NATIVE_FILTER';
export interface UnsetHoveredNativeFilter {
type: typeof UNSET_HOVERED_NATIVE_FILTER;
}
export function setHoveredNativeFilter(id: string): SetHoveredNativeFilter {
return {
type: SET_HOVERED_NATIVE_FILTER,
id,
};
}
export function unsetHoveredNativeFilter(): UnsetHoveredNativeFilter {
return {
type: UNSET_HOVERED_NATIVE_FILTER,
};
}
export type AnyFilterAction = export type AnyFilterAction =
| SetFilterConfigBegin | SetFilterConfigBegin
| SetFilterConfigComplete | SetFilterConfigComplete
@ -383,6 +405,8 @@ export type AnyFilterAction =
| SetBootstrapData | SetBootstrapData
| SetFocusedNativeFilter | SetFocusedNativeFilter
| UnsetFocusedNativeFilter | UnsetFocusedNativeFilter
| SetHoveredNativeFilter
| UnsetHoveredNativeFilter
| CreateFilterSetBegin | CreateFilterSetBegin
| CreateFilterSetComplete | CreateFilterSetComplete
| CreateFilterSetFail | CreateFilterSetFail

View File

@ -239,9 +239,8 @@ const DashboardBuilder: FC<DashboardBuilderProps> = () => {
const canEdit = useSelector<RootState, boolean>( const canEdit = useSelector<RootState, boolean>(
({ dashboardInfo }) => dashboardInfo.dash_edit_perm, ({ dashboardInfo }) => dashboardInfo.dash_edit_perm,
); );
const directPathToChild = useSelector<RootState, string[]>( const nativeFilters = useSelector((state: RootState) => state.nativeFilters);
state => state.dashboardState.directPathToChild, const focusedFilterId = nativeFilters?.focusedFilterId;
);
const fullSizeChartId = useSelector<RootState, number | null>( const fullSizeChartId = useSelector<RootState, number | null>(
state => state.dashboardState.fullSizeChartId, state => state.dashboardState.fullSizeChartId,
); );
@ -369,7 +368,7 @@ const DashboardBuilder: FC<DashboardBuilderProps> = () => {
{showFilterBar && {showFilterBar &&
filterBarOrientation === FilterBarOrientation.HORIZONTAL && ( filterBarOrientation === FilterBarOrientation.HORIZONTAL && (
<FilterBar <FilterBar
directPathToChild={directPathToChild} focusedFilterId={focusedFilterId}
orientation={FilterBarOrientation.HORIZONTAL} orientation={FilterBarOrientation.HORIZONTAL}
/> />
)} )}
@ -401,7 +400,7 @@ const DashboardBuilder: FC<DashboardBuilderProps> = () => {
</div> </div>
), ),
[ [
directPathToChild, focusedFilterId,
nativeFiltersEnabled, nativeFiltersEnabled,
filterBarOrientation, filterBarOrientation,
editMode, editMode,
@ -437,7 +436,7 @@ const DashboardBuilder: FC<DashboardBuilderProps> = () => {
<StickyPanel ref={containerRef} width={filterBarWidth}> <StickyPanel ref={containerRef} width={filterBarWidth}>
<ErrorBoundary> <ErrorBoundary>
<FilterBar <FilterBar
directPathToChild={directPathToChild} focusedFilterId={focusedFilterId}
orientation={FilterBarOrientation.VERTICAL} orientation={FilterBarOrientation.VERTICAL}
verticalConfig={{ verticalConfig={{
filtersOpen: dashboardFiltersOpen, filtersOpen: dashboardFiltersOpen,

View File

@ -23,6 +23,7 @@ import cx from 'classnames';
import { DataMaskStateWithId, Filters } from '@superset-ui/core'; import { DataMaskStateWithId, Filters } from '@superset-ui/core';
import Icons from 'src/components/Icons'; import Icons from 'src/components/Icons';
import { usePrevious } from 'src/hooks/usePrevious'; import { usePrevious } from 'src/hooks/usePrevious';
import { setFocusedNativeFilter } from 'src/dashboard/actions/nativeFilters';
import DetailsPanelPopover from './DetailsPanel'; import DetailsPanelPopover from './DetailsPanel';
import { Pill } from './Styles'; import { Pill } from './Styles';
import { import {
@ -31,7 +32,6 @@ import {
selectIndicatorsForChart, selectIndicatorsForChart,
selectNativeIndicatorsForChart, selectNativeIndicatorsForChart,
} from './selectors'; } from './selectors';
import { setDirectPathToChild } from '../../actions/dashboardState';
import { import {
ChartsState, ChartsState,
DashboardInfo, DashboardInfo,
@ -87,7 +87,7 @@ export const FiltersBadge = ({ chartId }: FiltersBadgeProps) => {
const onHighlightFilterSource = useCallback( const onHighlightFilterSource = useCallback(
(path: string[]) => { (path: string[]) => {
dispatch(setDirectPathToChild(path)); dispatch(setFocusedNativeFilter(path[0]));
}, },
[dispatch], [dispatch],
); );

View File

@ -340,9 +340,10 @@ export class Tabs extends React.PureComponent {
const { tabIndex: selectedTabIndex, activeKey } = this.state; const { tabIndex: selectedTabIndex, activeKey } = this.state;
let tabsToHighlight; let tabsToHighlight;
if (nativeFilters?.focusedFilterId) { const highlightedFilterId =
tabsToHighlight = nativeFilters?.focusedFilterId || nativeFilters?.hoveredFilterId;
nativeFilters.filters[nativeFilters.focusedFilterId].tabsInScope; if (highlightedFilterId) {
tabsToHighlight = nativeFilters.filters[highlightedFilterId]?.tabsInScope;
} }
return ( return (
<DragDroppable <DragDroppable

View File

@ -217,7 +217,7 @@ const FilterControl = ({
filter, filter,
icon, icon,
onFilterSelectionChange, onFilterSelectionChange,
directPathToChild, focusedFilterId,
inView, inView,
showOverflow, showOverflow,
parentRef, parentRef,
@ -298,7 +298,7 @@ const FilterControl = ({
dataMaskSelected={dataMaskSelected} dataMaskSelected={dataMaskSelected}
filter={filter} filter={filter}
showOverflow={showOverflow} showOverflow={showOverflow}
directPathToChild={directPathToChild} focusedFilterId={focusedFilterId}
onFilterSelectionChange={onFilterSelectionChange} onFilterSelectionChange={onFilterSelectionChange}
inView={inView} inView={inView}
parentRef={parentRef} parentRef={parentRef}

View File

@ -16,7 +16,14 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import React, { FC, useCallback, useMemo, useState } from 'react'; import React, {
FC,
useEffect,
useCallback,
useMemo,
useRef,
useState,
} from 'react';
import { import {
DataMask, DataMask,
DataMaskStateWithId, DataMaskStateWithId,
@ -40,20 +47,22 @@ import {
useSelectFiltersInScope, useSelectFiltersInScope,
} from 'src/dashboard/components/nativeFilters/state'; } from 'src/dashboard/components/nativeFilters/state';
import { FilterBarOrientation, RootState } from 'src/dashboard/types'; import { FilterBarOrientation, RootState } from 'src/dashboard/types';
import DropdownContainer from 'src/components/DropdownContainer'; import DropdownContainer, {
Ref as DropdownContainerRef,
} from 'src/components/DropdownContainer';
import Icons from 'src/components/Icons'; import Icons from 'src/components/Icons';
import { FiltersOutOfScopeCollapsible } from '../FiltersOutOfScopeCollapsible'; import { FiltersOutOfScopeCollapsible } from '../FiltersOutOfScopeCollapsible';
import { useFilterControlFactory } from '../useFilterControlFactory'; import { useFilterControlFactory } from '../useFilterControlFactory';
import { FiltersDropdownContent } from '../FiltersDropdownContent'; import { FiltersDropdownContent } from '../FiltersDropdownContent';
type FilterControlsProps = { type FilterControlsProps = {
directPathToChild?: string[]; focusedFilterId?: string;
dataMaskSelected: DataMaskStateWithId; dataMaskSelected: DataMaskStateWithId;
onFilterSelectionChange: (filter: Filter, dataMask: DataMask) => void; onFilterSelectionChange: (filter: Filter, dataMask: DataMask) => void;
}; };
const FilterControls: FC<FilterControlsProps> = ({ const FilterControls: FC<FilterControlsProps> = ({
directPathToChild, focusedFilterId,
dataMaskSelected, dataMaskSelected,
onFilterSelectionChange, onFilterSelectionChange,
}) => { }) => {
@ -65,10 +74,11 @@ const FilterControls: FC<FilterControlsProps> = ({
); );
const [overflowedIds, setOverflowedIds] = useState<string[]>([]); const [overflowedIds, setOverflowedIds] = useState<string[]>([]);
const popoverRef = useRef<DropdownContainerRef>(null);
const { filterControlFactory, filtersWithValues } = useFilterControlFactory( const { filterControlFactory, filtersWithValues } = useFilterControlFactory(
dataMaskSelected, dataMaskSelected,
directPathToChild, focusedFilterId,
onFilterSelectionChange, onFilterSelectionChange,
); );
const portalNodes = useMemo(() => { const portalNodes = useMemo(() => {
@ -183,6 +193,7 @@ const FilterControls: FC<FilterControlsProps> = ({
) )
: undefined : undefined
} }
ref={popoverRef}
onOverflowingStateChange={({ overflowed: nextOverflowedIds }) => { onOverflowingStateChange={({ overflowed: nextOverflowedIds }) => {
if ( if (
nextOverflowedIds.length !== overflowedIds.length || nextOverflowedIds.length !== overflowedIds.length ||
@ -211,6 +222,12 @@ const FilterControls: FC<FilterControlsProps> = ({
); );
}, [filtersOutOfScope, filtersWithValues, overflowedFiltersInScope]); }, [filtersOutOfScope, filtersWithValues, overflowedFiltersInScope]);
useEffect(() => {
if (focusedFilterId && overflowedIds.includes(focusedFilterId)) {
popoverRef?.current?.open();
}
}, [focusedFilterId, popoverRef, overflowedIds]);
return ( return (
<> <>
{portalNodes {portalNodes

View File

@ -44,7 +44,8 @@ import { waitForAsyncData } from 'src/middleware/asyncEvent';
import { ClientErrorObject } from 'src/utils/getClientErrorObject'; import { ClientErrorObject } from 'src/utils/getClientErrorObject';
import { FilterBarOrientation, RootState } from 'src/dashboard/types'; import { FilterBarOrientation, RootState } from 'src/dashboard/types';
import { onFiltersRefreshSuccess } from 'src/dashboard/actions/dashboardState'; import { onFiltersRefreshSuccess } from 'src/dashboard/actions/dashboardState';
import { dispatchFocusAction } from './utils'; import { FAST_DEBOUNCE } from 'src/constants';
import { dispatchHoverAction, dispatchFocusAction } from './utils';
import { FilterControlProps } from './types'; import { FilterControlProps } from './types';
import { getFormData } from '../../utils'; import { getFormData } from '../../utils';
import { useFilterDependencies } from './state'; import { useFilterDependencies } from './state';
@ -78,7 +79,7 @@ const useShouldFilterRefresh = () => {
const FilterValue: React.FC<FilterControlProps> = ({ const FilterValue: React.FC<FilterControlProps> = ({
dataMaskSelected, dataMaskSelected,
filter, filter,
directPathToChild, focusedFilterId,
onFilterSelectionChange, onFilterSelectionChange,
inView = true, inView = true,
showOverflow, showOverflow,
@ -211,10 +212,12 @@ const FilterValue: React.FC<FilterControlProps> = ({
]); ]);
useEffect(() => { useEffect(() => {
if (directPathToChild?.[0] === filter.id) { if (focusedFilterId && focusedFilterId === filter.id) {
setTimeout(() => {
inputRef?.current?.focus(); inputRef?.current?.focus();
}, FAST_DEBOUNCE);
} }
}, [inputRef, directPathToChild, filter.id]); }, [inputRef, focusedFilterId, filter.id]);
const setDataMask = useCallback( const setDataMask = useCallback(
(dataMask: DataMask) => onFilterSelectionChange(filter, dataMask), (dataMask: DataMask) => onFilterSelectionChange(filter, dataMask),
@ -230,14 +233,32 @@ const FilterValue: React.FC<FilterControlProps> = ({
[dispatch], [dispatch],
); );
const setHoveredFilter = useCallback(
() => dispatchHoverAction(dispatch, id),
[dispatch, id],
);
const unsetHoveredFilter = useCallback(
() => dispatchHoverAction(dispatch),
[dispatch],
);
const hooks = useMemo( const hooks = useMemo(
() => ({ () => ({
setDataMask, setDataMask,
setHoveredFilter,
unsetHoveredFilter,
setFocusedFilter, setFocusedFilter,
unsetFocusedFilter, unsetFocusedFilter,
setFilterActive, setFilterActive,
}), }),
[setDataMask, setFilterActive, setFocusedFilter, unsetFocusedFilter], [
setDataMask,
setFilterActive,
setHoveredFilter,
unsetHoveredFilter,
setFocusedFilter,
unsetFocusedFilter,
],
); );
const isMissingRequiredValue = checkIsMissingRequiredValue( const isMissingRequiredValue = checkIsMissingRequiredValue(

View File

@ -36,7 +36,7 @@ export interface FilterControlProps extends BaseFilterProps {
dataMask?: DataMask; dataMask?: DataMask;
}; };
icon?: React.ReactElement; icon?: React.ReactElement;
directPathToChild?: string[]; focusedFilterId?: string;
onFilterSelectionChange: (filter: Filter, dataMask: DataMask) => void; onFilterSelectionChange: (filter: Filter, dataMask: DataMask) => void;
inView?: boolean; inView?: boolean;
showOverflow?: boolean; showOverflow?: boolean;

View File

@ -21,7 +21,21 @@ import { Dispatch } from 'react';
import { import {
setFocusedNativeFilter, setFocusedNativeFilter,
unsetFocusedNativeFilter, unsetFocusedNativeFilter,
setHoveredNativeFilter,
unsetHoveredNativeFilter,
} from 'src/dashboard/actions/nativeFilters'; } from 'src/dashboard/actions/nativeFilters';
import { FAST_DEBOUNCE } from 'src/constants';
export const dispatchHoverAction = debounce(
(dispatch: Dispatch<any>, id?: string) => {
if (id) {
dispatch(setHoveredNativeFilter(id));
} else {
dispatch(unsetHoveredNativeFilter());
}
},
FAST_DEBOUNCE,
);
export const dispatchFocusAction = debounce( export const dispatchFocusAction = debounce(
(dispatch: Dispatch<any>, id?: string) => { (dispatch: Dispatch<any>, id?: string) => {
@ -31,5 +45,5 @@ export const dispatchFocusAction = debounce(
dispatch(unsetFocusedNativeFilter()); dispatch(unsetFocusedNativeFilter());
} }
}, },
300, FAST_DEBOUNCE,
); );

View File

@ -93,7 +93,7 @@ const HorizontalFilterBar: React.FC<HorizontalBarProps> = ({
dataMaskSelected, dataMaskSelected,
filterValues, filterValues,
isInitialized, isInitialized,
directPathToChild, focusedFilterId,
onSelectionChange, onSelectionChange,
}) => { }) => {
const hasFilters = filterValues.length > 0; const hasFilters = filterValues.length > 0;
@ -124,7 +124,7 @@ const HorizontalFilterBar: React.FC<HorizontalBarProps> = ({
{hasFilters && ( {hasFilters && (
<FilterControls <FilterControls
dataMaskSelected={dataMaskSelected} dataMaskSelected={dataMaskSelected}
directPathToChild={directPathToChild} focusedFilterId={focusedFilterId}
onFilterSelectionChange={onSelectionChange} onFilterSelectionChange={onSelectionChange}
/> />
)} )}

View File

@ -141,7 +141,7 @@ const VerticalFilterBar: React.FC<VerticalBarProps> = ({
actions, actions,
canEdit, canEdit,
dataMaskSelected, dataMaskSelected,
directPathToChild, focusedFilterId,
filtersOpen, filtersOpen,
filterValues, filterValues,
height, height,
@ -258,7 +258,7 @@ const VerticalFilterBar: React.FC<VerticalBarProps> = ({
<FilterControlsWrapper> <FilterControlsWrapper>
<FilterControls <FilterControls
dataMaskSelected={dataMaskSelected} dataMaskSelected={dataMaskSelected}
directPathToChild={directPathToChild} focusedFilterId={focusedFilterId}
onFilterSelectionChange={onSelectionChange} onFilterSelectionChange={onSelectionChange}
/> />
</FilterControlsWrapper> </FilterControlsWrapper>
@ -300,7 +300,7 @@ const VerticalFilterBar: React.FC<VerticalBarProps> = ({
<FilterControlsWrapper> <FilterControlsWrapper>
<FilterControls <FilterControls
dataMaskSelected={dataMaskSelected} dataMaskSelected={dataMaskSelected}
directPathToChild={directPathToChild} focusedFilterId={focusedFilterId}
onFilterSelectionChange={onSelectionChange} onFilterSelectionChange={onSelectionChange}
/> />
</FilterControlsWrapper> </FilterControlsWrapper>

View File

@ -111,7 +111,7 @@ const publishDataMask = debounce(
export const FilterBarScrollContext = createContext(false); export const FilterBarScrollContext = createContext(false);
const FilterBar: React.FC<FiltersBarProps> = ({ const FilterBar: React.FC<FiltersBarProps> = ({
directPathToChild, focusedFilterId,
orientation = FilterBarOrientation.VERTICAL, orientation = FilterBarOrientation.VERTICAL,
verticalConfig, verticalConfig,
}) => { }) => {
@ -254,7 +254,7 @@ const FilterBar: React.FC<FiltersBarProps> = ({
canEdit={canEdit} canEdit={canEdit}
dashboardId={dashboardId} dashboardId={dashboardId}
dataMaskSelected={dataMaskSelected} dataMaskSelected={dataMaskSelected}
directPathToChild={directPathToChild} focusedFilterId={focusedFilterId}
filterValues={filterValues} filterValues={filterValues}
isInitialized={isInitialized} isInitialized={isInitialized}
onSelectionChange={handleFilterSelectionChange} onSelectionChange={handleFilterSelectionChange}
@ -264,7 +264,7 @@ const FilterBar: React.FC<FiltersBarProps> = ({
actions={actions} actions={actions}
canEdit={canEdit} canEdit={canEdit}
dataMaskSelected={dataMaskSelected} dataMaskSelected={dataMaskSelected}
directPathToChild={directPathToChild} focusedFilterId={focusedFilterId}
filtersOpen={verticalConfig.filtersOpen} filtersOpen={verticalConfig.filtersOpen}
filterValues={filterValues} filterValues={filterValues}
isInitialized={isInitialized} isInitialized={isInitialized}

View File

@ -28,7 +28,7 @@ interface CommonFiltersBarProps {
actions: React.ReactNode; actions: React.ReactNode;
canEdit: boolean; canEdit: boolean;
dataMaskSelected: DataMaskStateWithId; dataMaskSelected: DataMaskStateWithId;
directPathToChild?: string[]; focusedFilterId?: string;
filterValues: (Filter | Divider)[]; filterValues: (Filter | Divider)[];
isInitialized: boolean; isInitialized: boolean;
onSelectionChange: ( onSelectionChange: (
@ -46,7 +46,7 @@ interface VerticalBarConfig {
} }
export interface FiltersBarProps export interface FiltersBarProps
extends Pick<CommonFiltersBarProps, 'directPathToChild'> { extends Pick<CommonFiltersBarProps, 'focusedFilterId'> {
orientation: FilterBarOrientation; orientation: FilterBarOrientation;
verticalConfig?: VerticalBarConfig; verticalConfig?: VerticalBarConfig;
} }

View File

@ -33,7 +33,7 @@ import FilterDivider from './FilterControls/FilterDivider';
export const useFilterControlFactory = ( export const useFilterControlFactory = (
dataMaskSelected: DataMaskStateWithId, dataMaskSelected: DataMaskStateWithId,
directPathToChild: string[] | undefined, focusedFilterId: string | undefined,
onFilterSelectionChange: (filter: Filter, dataMask: DataMask) => void, onFilterSelectionChange: (filter: Filter, dataMask: DataMask) => void,
) => { ) => {
const filters = useFilters(); const filters = useFilters();
@ -68,7 +68,7 @@ export const useFilterControlFactory = (
<FilterControl <FilterControl
dataMaskSelected={dataMaskSelected} dataMaskSelected={dataMaskSelected}
filter={filter} filter={filter}
directPathToChild={directPathToChild} focusedFilterId={focusedFilterId}
onFilterSelectionChange={onFilterSelectionChange} onFilterSelectionChange={onFilterSelectionChange}
inView={false} inView={false}
orientation={filterBarOrientation} orientation={filterBarOrientation}
@ -79,7 +79,7 @@ export const useFilterControlFactory = (
[ [
filtersWithValues, filtersWithValues,
dataMaskSelected, dataMaskSelected,
directPathToChild, focusedFilterId,
onFilterSelectionChange, onFilterSelectionChange,
], ],
); );

View File

@ -19,9 +19,9 @@
import React, { useCallback, useMemo, useRef } from 'react'; import React, { useCallback, useMemo, useRef } from 'react';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { css, t, useTheme } from '@superset-ui/core'; import { css, t, useTheme } from '@superset-ui/core';
import { setDirectPathToChild } from 'src/dashboard/actions/dashboardState';
import Icons from 'src/components/Icons'; import Icons from 'src/components/Icons';
import { useTruncation } from 'src/hooks/useTruncation'; import { useTruncation } from 'src/hooks/useTruncation';
import { setFocusedNativeFilter } from 'src/dashboard/actions/nativeFilters';
import { import {
DependencyItem, DependencyItem,
Row, Row,
@ -40,7 +40,7 @@ const DependencyValue = ({
}: DependencyValueProps) => { }: DependencyValueProps) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const handleClick = useCallback(() => { const handleClick = useCallback(() => {
dispatch(setDirectPathToChild([dependency.id])); dispatch(setFocusedNativeFilter(dependency.id));
}, [dependency.id, dispatch]); }, [dependency.id, dispatch]);
return ( return (
<span> <span>

View File

@ -23,7 +23,7 @@ import { Filter, NativeFilterType } from '@superset-ui/core';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import { render, screen } from 'spec/helpers/testing-library'; import { render, screen } from 'spec/helpers/testing-library';
import { DASHBOARD_ROOT_ID } from 'src/dashboard/util/constants'; import { DASHBOARD_ROOT_ID } from 'src/dashboard/util/constants';
import { SET_DIRECT_PATH } from 'src/dashboard/actions/dashboardState'; import { SET_FOCUSED_NATIVE_FILTER } from 'src/dashboard/actions/nativeFilters';
import { FilterCardContent } from './FilterCardContent'; import { FilterCardContent } from './FilterCardContent';
const baseInitialState = { const baseInitialState = {
@ -298,8 +298,8 @@ describe('Filter Card', () => {
userEvent.click(screen.getByText('Native filter 2')); userEvent.click(screen.getByText('Native filter 2'));
expect(dummyDispatch).toHaveBeenCalledWith({ expect(dummyDispatch).toHaveBeenCalledWith({
type: SET_DIRECT_PATH, type: SET_FOCUSED_NATIVE_FILTER,
path: ['NATIVE_FILTER-2'], id: 'NATIVE_FILTER-2',
}); });
}); });
}); });

View File

@ -23,6 +23,8 @@ import {
SET_FILTER_SETS_COMPLETE, SET_FILTER_SETS_COMPLETE,
SET_FOCUSED_NATIVE_FILTER, SET_FOCUSED_NATIVE_FILTER,
UNSET_FOCUSED_NATIVE_FILTER, UNSET_FOCUSED_NATIVE_FILTER,
SET_HOVERED_NATIVE_FILTER,
UNSET_HOVERED_NATIVE_FILTER,
} from 'src/dashboard/actions/nativeFilters'; } from 'src/dashboard/actions/nativeFilters';
import { import {
FilterSet, FilterSet,
@ -102,6 +104,18 @@ export default function nativeFilterReducer(
...state, ...state,
focusedFilterId: undefined, focusedFilterId: undefined,
}; };
case SET_HOVERED_NATIVE_FILTER:
return {
...state,
hoveredFilterId: action.id,
};
case UNSET_HOVERED_NATIVE_FILTER:
return {
...state,
hoveredFilterId: undefined,
};
// TODO handle SET_FILTER_CONFIG_FAIL action // TODO handle SET_FILTER_CONFIG_FAIL action
default: default:
return state; return state;

View File

@ -79,6 +79,25 @@ describe('useFilterFocusHighlightStyles', () => {
expect(parseFloat(styles.opacity)).toBe(0.3); expect(parseFloat(styles.opacity)).toBe(0.3);
}); });
it('should return unfocused styles if chart is not in scope of hovered native filter', async () => {
const store = createMockStore({
nativeFilters: {
hoveredFilterId: 'test-filter',
filters: {
otherId: {
chartsInScope: [],
},
},
},
});
renderWrapper(10, store);
const container = screen.getByTestId('test-component');
const styles = getComputedStyle(container);
expect(parseFloat(styles.opacity)).toBe(0.3);
});
it('should return focused styles if chart is in scope of focused native filter', async () => { it('should return focused styles if chart is in scope of focused native filter', async () => {
const chartId = 18; const chartId = 18;
const store = createMockStore({ const store = createMockStore({
@ -99,6 +118,26 @@ describe('useFilterFocusHighlightStyles', () => {
expect(parseFloat(styles.opacity)).toBe(1); expect(parseFloat(styles.opacity)).toBe(1);
}); });
it('should return focused styles if chart is in scope of hovered native filter', async () => {
const chartId = 18;
const store = createMockStore({
nativeFilters: {
hoveredFilterId: 'testFilter',
filters: {
testFilter: {
chartsInScope: [chartId],
},
},
},
});
renderWrapper(chartId, store);
const container = screen.getByTestId('test-component');
const styles = getComputedStyle(container);
expect(parseFloat(styles.opacity)).toBe(1);
});
it('should return unfocused styles if focusedFilterField is targeting a different chart', async () => { it('should return unfocused styles if focusedFilterField is targeting a different chart', async () => {
const chartId = 18; const chartId = 18;
const store = createMockStore({ const store = createMockStore({

View File

@ -49,8 +49,9 @@ const useFilterFocusHighlightStyles = (chartId: number) => {
dashboardFilters, dashboardFilters,
); );
const focusedNativeFilterId = nativeFilters.focusedFilterId; const highlightedFilterId =
if (!(focusedFilterScope || focusedNativeFilterId)) { nativeFilters?.focusedFilterId || nativeFilters?.hoveredFilterId;
if (!(focusedFilterScope || highlightedFilterId)) {
return {}; return {};
} }
@ -67,9 +68,9 @@ const useFilterFocusHighlightStyles = (chartId: number) => {
pointerEvents: 'auto', pointerEvents: 'auto',
}; };
if (focusedNativeFilterId) { if (highlightedFilterId) {
if ( if (
nativeFilters.filters[focusedNativeFilterId]?.chartsInScope?.includes( nativeFilters.filters[highlightedFilterId]?.chartsInScope?.includes(
chartId, chartId,
) )
) { ) {

View File

@ -36,6 +36,8 @@ export default function PluginFilterGroupBy(props: PluginFilterGroupByProps) {
height, height,
width, width,
setDataMask, setDataMask,
setHoveredFilter,
unsetHoveredFilter,
setFocusedFilter, setFocusedFilter,
unsetFocusedFilter, unsetFocusedFilter,
setFilterActive, setFilterActive,
@ -116,6 +118,8 @@ export default function PluginFilterGroupBy(props: PluginFilterGroupByProps) {
onChange={handleChange} onChange={handleChange}
onBlur={unsetFocusedFilter} onBlur={unsetFocusedFilter}
onFocus={setFocusedFilter} onFocus={setFocusedFilter}
onMouseEnter={setHoveredFilter}
onMouseLeave={unsetHoveredFilter}
ref={inputRef} ref={inputRef}
options={options} options={options}
onDropdownVisibleChange={setFilterActive} onDropdownVisibleChange={setFilterActive}

View File

@ -33,6 +33,8 @@ export default function transformProps(chartProps: ChartProps) {
} = chartProps; } = chartProps;
const { const {
setDataMask = noOp, setDataMask = noOp,
setHoveredFilter = noOp,
unsetHoveredFilter = noOp,
setFocusedFilter = noOp, setFocusedFilter = noOp,
unsetFocusedFilter = noOp, unsetFocusedFilter = noOp,
setFilterActive = noOp, setFilterActive = noOp,
@ -48,6 +50,8 @@ export default function transformProps(chartProps: ChartProps) {
data, data,
formData: { ...DEFAULT_FORM_DATA, ...formData }, formData: { ...DEFAULT_FORM_DATA, ...formData },
setDataMask, setDataMask,
setHoveredFilter,
unsetHoveredFilter,
setFocusedFilter, setFocusedFilter,
unsetFocusedFilter, unsetFocusedFilter,
setFilterActive, setFilterActive,

View File

@ -171,6 +171,8 @@ export default function RangeFilterPlugin(props: PluginFilterRangeProps) {
setDataMask, setDataMask,
setFocusedFilter, setFocusedFilter,
unsetFocusedFilter, unsetFocusedFilter,
setHoveredFilter,
unsetHoveredFilter,
setFilterActive, setFilterActive,
filterState, filterState,
inputRef, inputRef,
@ -312,8 +314,8 @@ export default function RangeFilterPlugin(props: PluginFilterRangeProps) {
isOverflowing={isOverflowingFilterBar} isOverflowing={isOverflowingFilterBar}
onFocus={setFocusedFilter} onFocus={setFocusedFilter}
onBlur={unsetFocusedFilter} onBlur={unsetFocusedFilter}
onMouseEnter={setFocusedFilter} onMouseEnter={setHoveredFilter}
onMouseLeave={unsetFocusedFilter} onMouseLeave={unsetHoveredFilter}
onMouseDown={() => setFilterActive(true)} onMouseDown={() => setFilterActive(true)}
onMouseUp={() => setFilterActive(false)} onMouseUp={() => setFilterActive(false)}
> >

View File

@ -35,6 +35,8 @@ export default function transformProps(chartProps: ChartProps) {
setDataMask = noOp, setDataMask = noOp,
setFocusedFilter = noOp, setFocusedFilter = noOp,
unsetFocusedFilter = noOp, unsetFocusedFilter = noOp,
setHoveredFilter = noOp,
unsetHoveredFilter = noOp,
setFilterActive = noOp, setFilterActive = noOp,
} = hooks; } = hooks;
const { data } = queriesData[0]; const { data } = queriesData[0];
@ -47,6 +49,8 @@ export default function transformProps(chartProps: ChartProps) {
setDataMask, setDataMask,
filterState, filterState,
width, width,
setHoveredFilter,
unsetHoveredFilter,
setFocusedFilter, setFocusedFilter,
unsetFocusedFilter, unsetFocusedFilter,
setFilterActive, setFilterActive,

View File

@ -83,6 +83,8 @@ export default function PluginFilterSelect(props: PluginFilterSelectProps) {
isRefreshing, isRefreshing,
width, width,
setDataMask, setDataMask,
setHoveredFilter,
unsetHoveredFilter,
setFocusedFilter, setFocusedFilter,
unsetFocusedFilter, unsetFocusedFilter,
setFilterActive, setFilterActive,
@ -319,8 +321,9 @@ export default function PluginFilterSelect(props: PluginFilterSelectProps) {
onSearch={searchWrapper} onSearch={searchWrapper}
onSelect={clearSuggestionSearch} onSelect={clearSuggestionSearch}
onBlur={handleBlur} onBlur={handleBlur}
onMouseEnter={setFocusedFilter} onFocus={setFocusedFilter}
onMouseLeave={unsetFocusedFilter} onMouseEnter={setHoveredFilter}
onMouseLeave={unsetHoveredFilter}
// @ts-ignore // @ts-ignore
onChange={handleChange} onChange={handleChange}
ref={inputRef} ref={inputRef}

View File

@ -39,6 +39,8 @@ export default function transformProps(
const newFormData = { ...DEFAULT_FORM_DATA, ...formData }; const newFormData = { ...DEFAULT_FORM_DATA, ...formData };
const { const {
setDataMask = noOp, setDataMask = noOp,
setHoveredFilter = noOp,
unsetHoveredFilter = noOp,
setFocusedFilter = noOp, setFocusedFilter = noOp,
unsetFocusedFilter = noOp, unsetFocusedFilter = noOp,
setFilterActive = noOp, setFilterActive = noOp,
@ -61,6 +63,8 @@ export default function transformProps(
formData: newFormData, formData: newFormData,
isRefreshing, isRefreshing,
setDataMask, setDataMask,
setHoveredFilter,
unsetHoveredFilter,
setFocusedFilter, setFocusedFilter,
unsetFocusedFilter, unsetFocusedFilter,
setFilterActive, setFilterActive,

View File

@ -66,6 +66,8 @@ const ControlContainer = styled.div<{
export default function TimeFilterPlugin(props: PluginFilterTimeProps) { export default function TimeFilterPlugin(props: PluginFilterTimeProps) {
const { const {
setDataMask, setDataMask,
setHoveredFilter,
unsetHoveredFilter,
setFocusedFilter, setFocusedFilter,
unsetFocusedFilter, unsetFocusedFilter,
setFilterActive, setFilterActive,
@ -104,8 +106,8 @@ export default function TimeFilterPlugin(props: PluginFilterTimeProps) {
validateStatus={filterState.validateStatus} validateStatus={filterState.validateStatus}
onFocus={setFocusedFilter} onFocus={setFocusedFilter}
onBlur={unsetFocusedFilter} onBlur={unsetFocusedFilter}
onMouseEnter={setFocusedFilter} onMouseEnter={setHoveredFilter}
onMouseLeave={unsetFocusedFilter} onMouseLeave={unsetHoveredFilter}
> >
<DateFilterControl <DateFilterControl
value={filterState.value || NO_TIME_RANGE} value={filterState.value || NO_TIME_RANGE}

View File

@ -34,6 +34,8 @@ export default function transformProps(chartProps: ChartProps) {
} = chartProps; } = chartProps;
const { const {
setDataMask = noOp, setDataMask = noOp,
setHoveredFilter = noOp,
unsetHoveredFilter = noOp,
setFocusedFilter = noOp, setFocusedFilter = noOp,
unsetFocusedFilter = noOp, unsetFocusedFilter = noOp,
setFilterActive = noOp, setFilterActive = noOp,
@ -50,6 +52,8 @@ export default function transformProps(chartProps: ChartProps) {
height, height,
behaviors, behaviors,
setDataMask, setDataMask,
setHoveredFilter,
unsetHoveredFilter,
setFocusedFilter, setFocusedFilter,
unsetFocusedFilter, unsetFocusedFilter,
setFilterActive, setFilterActive,

View File

@ -38,6 +38,8 @@ export default function PluginFilterTimeColumn(
height, height,
width, width,
setDataMask, setDataMask,
setHoveredFilter,
unsetHoveredFilter,
setFocusedFilter, setFocusedFilter,
unsetFocusedFilter, unsetFocusedFilter,
setFilterActive, setFilterActive,
@ -114,8 +116,10 @@ export default function PluginFilterTimeColumn(
placeholder={placeholderText} placeholder={placeholderText}
// @ts-ignore // @ts-ignore
onChange={handleChange} onChange={handleChange}
onMouseEnter={setFocusedFilter} onBlur={unsetFocusedFilter}
onMouseLeave={unsetFocusedFilter} onFocus={setFocusedFilter}
onMouseEnter={setHoveredFilter}
onMouseLeave={unsetHoveredFilter}
ref={inputRef} ref={inputRef}
options={options} options={options}
onDropdownVisibleChange={setFilterActive} onDropdownVisibleChange={setFilterActive}

View File

@ -33,6 +33,8 @@ export default function transformProps(chartProps: ChartProps) {
} = chartProps; } = chartProps;
const { const {
setDataMask = noOp, setDataMask = noOp,
setHoveredFilter = noOp,
unsetHoveredFilter = noOp,
setFocusedFilter = noOp, setFocusedFilter = noOp,
unsetFocusedFilter = noOp, unsetFocusedFilter = noOp,
setFilterActive = noOp, setFilterActive = noOp,
@ -48,6 +50,8 @@ export default function transformProps(chartProps: ChartProps) {
data, data,
formData: { ...DEFAULT_FORM_DATA, ...formData }, formData: { ...DEFAULT_FORM_DATA, ...formData },
setDataMask, setDataMask,
setHoveredFilter,
unsetHoveredFilter,
setFocusedFilter, setFocusedFilter,
unsetFocusedFilter, unsetFocusedFilter,
setFilterActive, setFilterActive,

View File

@ -38,6 +38,8 @@ export default function PluginFilterTimegrain(
height, height,
width, width,
setDataMask, setDataMask,
setHoveredFilter,
unsetHoveredFilter,
setFocusedFilter, setFocusedFilter,
unsetFocusedFilter, unsetFocusedFilter,
setFilterActive, setFilterActive,
@ -124,8 +126,10 @@ export default function PluginFilterTimegrain(
placeholder={placeholderText} placeholder={placeholderText}
// @ts-ignore // @ts-ignore
onChange={handleChange} onChange={handleChange}
onMouseEnter={setFocusedFilter} onBlur={unsetFocusedFilter}
onMouseLeave={unsetFocusedFilter} onFocus={setFocusedFilter}
onMouseEnter={setHoveredFilter}
onMouseLeave={unsetHoveredFilter}
ref={inputRef} ref={inputRef}
options={options} options={options}
onDropdownVisibleChange={setFilterActive} onDropdownVisibleChange={setFilterActive}

View File

@ -25,6 +25,8 @@ export default function transformProps(chartProps: ChartProps) {
chartProps; chartProps;
const { const {
setDataMask = noOp, setDataMask = noOp,
setHoveredFilter = noOp,
unsetHoveredFilter = noOp,
setFocusedFilter = noOp, setFocusedFilter = noOp,
unsetFocusedFilter = noOp, unsetFocusedFilter = noOp,
setFilterActive = noOp, setFilterActive = noOp,
@ -39,6 +41,8 @@ export default function transformProps(chartProps: ChartProps) {
data, data,
formData: { ...DEFAULT_FORM_DATA, ...formData }, formData: { ...DEFAULT_FORM_DATA, ...formData },
setDataMask, setDataMask,
setHoveredFilter,
unsetHoveredFilter,
setFocusedFilter, setFocusedFilter,
unsetFocusedFilter, unsetFocusedFilter,
setFilterActive, setFilterActive,

View File

@ -30,5 +30,7 @@ export interface PluginFilterHooks {
setDataMask: SetDataMaskHook; setDataMask: SetDataMaskHook;
setFocusedFilter: () => void; setFocusedFilter: () => void;
unsetFocusedFilter: () => void; unsetFocusedFilter: () => void;
setHoveredFilter: () => void;
unsetHoveredFilter: () => void;
setFilterActive: (isActive: boolean) => void; setFilterActive: (isActive: boolean) => void;
} }