This is an automated email from the ASF dual-hosted git repository. villebro 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 30be86f chore(native-filters): Connect indicator magnifier with Filter Bar (#12812) 30be86f is described below commit 30be86f81179d76fe245d20979b4f433cca405a5 Author: Agata Stawarz <47450693+agata...@users.noreply.github.com> AuthorDate: Sat Jan 30 14:15:44 2021 +0100 chore(native-filters): Connect indicator magnifier with Filter Bar (#12812) * Add direct path to child to native filter components * Implement focus for cascading filters * Remove empty line --- .../src/dashboard/components/DashboardBuilder.jsx | 2 + .../components/nativeFilters/CascadePopover.tsx | 60 ++++++++++++++++------ .../components/nativeFilters/FilterBar.tsx | 32 +++++++++++- .../filters/components/Range/AntdRangeFilter.tsx | 3 +- .../src/filters/components/Range/types.ts | 2 + .../filters/components/Select/AntdSelectFilter.tsx | 2 + .../src/filters/components/Select/types.ts | 2 + 7 files changed, 86 insertions(+), 17 deletions(-) diff --git a/superset-frontend/src/dashboard/components/DashboardBuilder.jsx b/superset-frontend/src/dashboard/components/DashboardBuilder.jsx index 3c764b7..f14c301 100644 --- a/superset-frontend/src/dashboard/components/DashboardBuilder.jsx +++ b/superset-frontend/src/dashboard/components/DashboardBuilder.jsx @@ -215,6 +215,7 @@ class DashboardBuilder extends React.Component { showBuilderPane, setColorSchemeAndUnsavedChanges, colorScheme, + directPathToChild, } = this.props; const { tabIndex } = this.state; const dashboardRoot = dashboardLayout[DASHBOARD_ROOT_ID]; @@ -290,6 +291,7 @@ class DashboardBuilder extends React.Component { <FilterBar filtersOpen={this.state.dashboardFiltersOpen} toggleFiltersBar={this.toggleDashboardFiltersOpen} + directPathToChild={directPathToChild} /> </ErrorBoundary> </StickyVerticalBar> diff --git a/superset-frontend/src/dashboard/components/nativeFilters/CascadePopover.tsx b/superset-frontend/src/dashboard/components/nativeFilters/CascadePopover.tsx index 2b92370..e12c933 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/CascadePopover.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/CascadePopover.tsx @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import React, { useCallback } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { ExtraFormData, styled, t } from '@superset-ui/core'; import Popover from 'src/common/components/Popover'; import Icon from 'src/components/Icon'; @@ -27,6 +27,7 @@ import { Filter, CascadeFilter } from './types'; interface CascadePopoverProps { filter: CascadeFilter; visible: boolean; + directPathToChild?: string[]; onVisibleChange: (visible: boolean) => void; onExtraFormDataChange: (filter: Filter, extraFormData: ExtraFormData) => void; } @@ -73,7 +74,18 @@ const CascadePopover: React.FC<CascadePopoverProps> = ({ visible, onVisibleChange, onExtraFormDataChange, + directPathToChild, }) => { + const [currentPathToChild, setCurrentPathToChild] = useState<string[]>(); + + useEffect(() => { + setCurrentPathToChild(directPathToChild); + // clear local copy of directPathToChild after 500ms + // to prevent triggering multiple focus + const timeout = setTimeout(() => setCurrentPathToChild(undefined), 500); + return () => clearTimeout(timeout); + }, [directPathToChild, setCurrentPathToChild]); + const getActiveChildren = useCallback((filter: CascadeFilter): | CascadeFilter[] | null => { @@ -95,30 +107,48 @@ const CascadePopover: React.FC<CascadePopoverProps> = ({ return null; }, []); + const getAllFilters = (filter: CascadeFilter): CascadeFilter[] => { + const children = filter.cascadeChildren || []; + const allChildren = children.flatMap(getAllFilters); + return [filter, ...allChildren]; + }; + + const allFilters = getAllFilters(filter); + const activeFilters = useMemo(() => getActiveChildren(filter) || [filter], [ + filter, + getActiveChildren, + ]); + + useEffect(() => { + const focusedFilterId = currentPathToChild?.[0]; + // filters not directly displayed in the Filter Bar + const inactiveFilters = allFilters.filter( + filterEl => !activeFilters.includes(filterEl), + ); + const focusedInactiveFilter = inactiveFilters.some( + cascadeChild => cascadeChild.id === focusedFilterId, + ); + + if (focusedInactiveFilter) { + onVisibleChange(true); + } + }, [currentPathToChild]); + if (!filter.cascadeChildren?.length) { return ( <FilterControl filter={filter} + directPathToChild={directPathToChild} onExtraFormDataChange={onExtraFormDataChange} /> ); } - const countFilters = (filter: CascadeFilter): number => { - let count = 1; - filter.cascadeChildren.forEach(child => { - count += countFilters(child); - }); - return count; - }; - - const totalChildren = countFilters(filter); - const title = ( <StyledTitleBox> <StyledTitle> <StyledIcon name="edit" /> - {t('Select parent filters')} ({totalChildren}) + {t('Select parent filters')} ({allFilters.length}) </StyledTitle> <StyledIcon name="close" onClick={() => onVisibleChange(false)} /> </StyledTitleBox> @@ -129,12 +159,11 @@ const CascadePopover: React.FC<CascadePopoverProps> = ({ data-test="cascade-filters-control" key={filter.id} filter={filter} + directPathToChild={visible ? currentPathToChild : undefined} onExtraFormDataChange={onExtraFormDataChange} /> ); - const activeFilters = getActiveChildren(filter) || [filter]; - return ( <Popover content={content} @@ -152,11 +181,12 @@ const CascadePopover: React.FC<CascadePopoverProps> = ({ key={activeFilter.id} filter={activeFilter} onExtraFormDataChange={onExtraFormDataChange} + directPathToChild={currentPathToChild} icon={ <> {filter.cascadeChildren.length !== 0 && ( <StyledPill onClick={() => onVisibleChange(true)}> - <Icon name="filter" /> {totalChildren} + <Icon name="filter" /> {allFilters.length} </StyledPill> )} </> diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar.tsx index a81e5d8..6922070 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar.tsx @@ -23,7 +23,13 @@ import { t, ExtraFormData, } from '@superset-ui/core'; -import React, { useState, useEffect, useMemo, useCallback } from 'react'; +import React, { + useState, + useEffect, + useMemo, + useCallback, + useRef, +} from 'react'; import { useSelector } from 'react-redux'; import cx from 'classnames'; import { Form } from 'src/common/components'; @@ -194,16 +200,19 @@ const StyledLoadingBox = styled.div` interface FilterProps { filter: Filter; icon?: React.ReactElement; + directPathToChild?: string[]; onExtraFormDataChange: (filter: Filter, extraFormData: ExtraFormData) => void; } interface FiltersBarProps { filtersOpen: boolean; toggleFiltersBar: any; + directPathToChild?: string[]; } const FilterValue: React.FC<FilterProps> = ({ filter, + directPathToChild, onExtraFormDataChange, }) => { const { @@ -219,6 +228,7 @@ const FilterValue: React.FC<FilterProps> = ({ const [state, setState] = useState([]); const [error, setError] = useState<boolean>(false); const [formData, setFormData] = useState<Partial<QueryFormData>>({}); + const inputRef = useRef<HTMLInputElement>(null); const [target] = targets; const { datasetId = 18, column } = target; const { name: groupby } = column; @@ -240,6 +250,7 @@ const FilterValue: React.FC<FilterProps> = ({ url_params: {}, viz_type: 'filter_select', defaultValues: currentValue || defaultValue || [], + inputRef, }); useEffect(() => { @@ -263,6 +274,17 @@ const FilterValue: React.FC<FilterProps> = ({ } }, [cascadingFilters, datasetId, groupby]); + useEffect(() => { + if (directPathToChild?.[0] === filter.id) { + // wait for Cascade Popover to open + const timeout = setTimeout(() => { + inputRef?.current?.focus(); + }, 200); + return () => clearTimeout(timeout); + } + return undefined; + }, [inputRef, directPathToChild, filter.id]); + const setExtraFormData = (extraFormData: ExtraFormData) => onExtraFormDataChange(filter, extraFormData); @@ -308,6 +330,7 @@ export const FilterControl: React.FC<FilterProps> = ({ filter, icon, onExtraFormDataChange, + directPathToChild, }) => { const { name = '<undefined>' } = filter; return ( @@ -318,6 +341,7 @@ export const FilterControl: React.FC<FilterProps> = ({ </StyledFilterControlTitleBox> <FilterValue filter={filter} + directPathToChild={directPathToChild} onExtraFormDataChange={onExtraFormDataChange} /> </StyledFilterControlContainer> @@ -326,11 +350,13 @@ export const FilterControl: React.FC<FilterProps> = ({ interface CascadeFilterControlProps { filter: CascadeFilter; + directPathToChild?: string[]; onExtraFormDataChange: (filter: Filter, extraFormData: ExtraFormData) => void; } export const CascadeFilterControl: React.FC<CascadeFilterControlProps> = ({ filter, + directPathToChild, onExtraFormDataChange, }) => ( <> @@ -338,6 +364,7 @@ export const CascadeFilterControl: React.FC<CascadeFilterControlProps> = ({ <StyledCaretIcon name="caret-down" /> <FilterControl filter={filter} + directPathToChild={directPathToChild} onExtraFormDataChange={onExtraFormDataChange} /> </StyledFilterControlBox> @@ -347,6 +374,7 @@ export const CascadeFilterControl: React.FC<CascadeFilterControlProps> = ({ <li key={childFilter.id}> <CascadeFilterControl filter={childFilter} + directPathToChild={directPathToChild} onExtraFormDataChange={onExtraFormDataChange} /> </li> @@ -358,6 +386,7 @@ export const CascadeFilterControl: React.FC<CascadeFilterControlProps> = ({ const FilterBar: React.FC<FiltersBarProps> = ({ filtersOpen, toggleFiltersBar, + directPathToChild, }) => { const [filterData, setFilterData] = useState<{ [id: string]: ExtraFormData }>( {}, @@ -493,6 +522,7 @@ const FilterBar: React.FC<FiltersBarProps> = ({ } filter={filter} onExtraFormDataChange={handleExtraFormDataChange} + directPathToChild={directPathToChild} /> ))} </FilterControls> diff --git a/superset-frontend/src/filters/components/Range/AntdRangeFilter.tsx b/superset-frontend/src/filters/components/Range/AntdRangeFilter.tsx index 33c0f83..5fb0471 100644 --- a/superset-frontend/src/filters/components/Range/AntdRangeFilter.tsx +++ b/superset-frontend/src/filters/components/Range/AntdRangeFilter.tsx @@ -29,7 +29,7 @@ const Styles = styled.div<AntdPluginFilterStylesProps>` `; export default function AntdRangeFilter(props: AntdPluginFilterRangeProps) { - const { data, formData, height, width, setExtraFormData } = props; + const { data, formData, height, width, setExtraFormData, inputRef } = props; const [row] = data; // @ts-ignore const { min, max }: { min: number; max: number } = row; @@ -50,6 +50,7 @@ export default function AntdRangeFilter(props: AntdPluginFilterRangeProps) { max={max} defaultValue={[min, max]} onChange={handleChange} + ref={inputRef} /> </Styles> ); diff --git a/superset-frontend/src/filters/components/Range/types.ts b/superset-frontend/src/filters/components/Range/types.ts index 63c9fbd..4b23262 100644 --- a/superset-frontend/src/filters/components/Range/types.ts +++ b/superset-frontend/src/filters/components/Range/types.ts @@ -21,6 +21,7 @@ import { QueryFormData, SetExtraFormDataHook, } from '@superset-ui/core'; +import { RefObject } from 'react'; import { AntdPluginFilterStylesProps } from '../types'; interface AntdPluginFilterSelectCustomizeProps { @@ -36,4 +37,5 @@ export type AntdPluginFilterRangeProps = AntdPluginFilterStylesProps & { data: DataRecord[]; formData: PluginFilterRangeQueryFormData; setExtraFormData: SetExtraFormDataHook; + inputRef: RefObject<any>; }; diff --git a/superset-frontend/src/filters/components/Select/AntdSelectFilter.tsx b/superset-frontend/src/filters/components/Select/AntdSelectFilter.tsx index c29b6aa..b97312c 100644 --- a/superset-frontend/src/filters/components/Select/AntdSelectFilter.tsx +++ b/superset-frontend/src/filters/components/Select/AntdSelectFilter.tsx @@ -41,6 +41,7 @@ export default function AntdPluginFilterSelect( multiSelect, showSearch, inverseSelection, + inputRef, } = { ...DEFAULT_FORM_DATA, ...formData, @@ -79,6 +80,7 @@ export default function AntdPluginFilterSelect( placeholder={placeholderText} // @ts-ignore onChange={handleChange} + ref={inputRef} > {(data || []).map(row => { const option = `${groupby.map(col => row[col])[0]}`; diff --git a/superset-frontend/src/filters/components/Select/types.ts b/superset-frontend/src/filters/components/Select/types.ts index bfa3117..d8f554b 100644 --- a/superset-frontend/src/filters/components/Select/types.ts +++ b/superset-frontend/src/filters/components/Select/types.ts @@ -21,6 +21,7 @@ import { DataRecord, SetExtraFormDataHook, } from '@superset-ui/core'; +import { RefObject } from 'react'; import { AntdPluginFilterStylesProps } from '../types'; interface AntdPluginFilterSelectCustomizeProps { @@ -30,6 +31,7 @@ interface AntdPluginFilterSelectCustomizeProps { inverseSelection: boolean; multiSelect: boolean; showSearch: boolean; + inputRef?: RefObject<HTMLInputElement>; } export type AntdPluginFilterSelectQueryFormData = QueryFormData &