You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by vi...@apache.org on 2021/01/30 13:16:20 UTC
[superset] branch master updated: chore(native-filters): Connect
indicator magnifier with Filter Bar (#12812)
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 <47...@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 &