Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Adds support to multiple dependencies to the native filters #18793

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,7 @@ import {
nativeFilters,
exploreView,
} from 'cypress/support/directories';
import {
testItems,
WORLD_HEALTH_CHARTS,
waitForChartLoad,
} from './dashboard.helper';
import { testItems } from './dashboard.helper';
import { DASHBOARD_LIST } from '../dashboard_list/dashboard_list.helper';
import { CHART_LIST } from '../chart_list/chart_list.helper';
import { FORM_DATA_DEFAULTS } from '../explore/visualizations/shared.helper';
Expand Down Expand Up @@ -263,7 +259,6 @@ describe('Nativefilters Sanity test', () => {
'Filter has default value',
'Can select multiple values',
'Filter value is required',
'Filter is hierarchical',
'Select first filter value by default',
'Inverse selection',
'Dynamically search all filter values',
Expand Down Expand Up @@ -534,114 +529,6 @@ describe('Nativefilters Sanity test', () => {
.contains('year')
.should('be.visible');
});
it('User can create parent filters using "Filter is hierarchical"', () => {
cy.get(nativeFilters.filterFromDashboardView.expand).click({ force: true });
// Create region filter
cy.get(nativeFilters.filterFromDashboardView.createFilterButton)
.should('be.visible')
.click();
cy.get(nativeFilters.filtersPanel.filterTypeInput)
.find(nativeFilters.filtersPanel.filterTypeItem)
.click({ force: true });
cy.get('[label="Value"]').click();
cy.get(nativeFilters.modal.container)
.find(nativeFilters.filtersPanel.datasetName)
.click({ force: true })
.within(() =>
cy
.get('input')
.type('wb_health_population{enter}', { delay: 50, force: true }),
);
cy.get(nativeFilters.modal.container)
.find(nativeFilters.filtersPanel.filterName)
.click({ force: true })
.clear()
.type('region', { scrollBehavior: false, force: true });
cy.wait(3000);
cy.get(nativeFilters.silentLoading).should('not.exist');
cy.get(nativeFilters.filtersPanel.filterInfoInput)
.last()
.should('be.visible')
.click({ force: true });
cy.get(nativeFilters.filtersPanel.filterInfoInput).last().type('region');
cy.get(nativeFilters.filtersPanel.inputDropdown)
.last()
.should('be.visible', { timeout: 20000 })
.click({ force: true });
// Create country filter
cy.get(nativeFilters.addFilterButton.button)
.first()
.click({ force: true })
.then(() => {
cy.get(nativeFilters.addFilterButton.dropdownItem)
.contains('Filter')
.click({ force: true });
});
cy.get(nativeFilters.filtersPanel.filterTypeInput)
.find(nativeFilters.filtersPanel.filterTypeItem)
.last()
.click({ force: true });
cy.get('[label="Value"]').last().click({ force: true });
cy.get(nativeFilters.modal.container)
.find(nativeFilters.filtersPanel.datasetName)
.last()
.click({ scrollBehavior: false })
.within(() =>
cy
.get('input')
.clear({ force: true })
.type('wb_health_population{enter}', {
delay: 50,
force: true,
scrollBehavior: false,
}),
);
cy.get(nativeFilters.filtersPanel.filterInfoInput)
.last()
.should('be.visible')
.click({ force: true });
cy.get(nativeFilters.filtersPanel.inputDropdown)
.should('be.visible', { timeout: 20000 })
.last()
.click();
cy.get(nativeFilters.modal.container)
.find(nativeFilters.filtersPanel.filterName)
.last()
.click({ force: true })
.type('country', { scrollBehavior: false, force: true });
cy.get(nativeFilters.silentLoading).should('not.exist');
cy.get(nativeFilters.filtersPanel.filterInfoInput)
.last()
.click({ force: true });
cy.get(nativeFilters.filtersPanel.filterInfoInput)
.last()
.type('country_name', { delay: 50, scrollBehavior: false, force: true });
cy.get(nativeFilters.filtersPanel.inputDropdown)
.last()
.should('be.visible', { timeout: 20000 })
.click({ force: true });
// Setup parent filter
cy.get(nativeFilters.filterConfigurationSections.displayedSection).within(
() => {
cy.contains('Filter is hierarchical').should('be.visible').click();
cy.wait(1000);
cy.get(nativeFilters.filterConfigurationSections.parentFilterInput)
.click()
.type('region{enter}', { delay: 30 });
},
);
cy.get(nativeFilters.modal.footer)
.contains('Save')
.should('be.visible')
.click();
WORLD_HEALTH_CHARTS.forEach(waitForChartLoad);
// assert that native filter is created
cy.get(nativeFilters.filterFromDashboardView.filterName)
.should('be.visible')
.contains('region');
cy.get(nativeFilters.filterIcon).click();
cy.contains('Select parent filters (2)').should('be.visible');
});
});

xdescribe('Nativefilters', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ export type Props = Omit<SuperChartCoreProps, 'chartProps'> &
showOverflow?: boolean;
/** Prop for popovercontainer ref */
parentRef?: RefObject<any>;
/** Prop for chart ref */
inputRef?: RefObject<any>;
/** Chart width */
height?: number | string;
/** Chart height */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
/** Type checking is disabled for this file due to reselect only supporting
* TS declarations for selectors with up to 12 arguments. */
// @ts-nocheck
import { RefObject } from 'react';
import { createSelector } from 'reselect';
import {
AppSection,
Expand Down Expand Up @@ -90,6 +91,8 @@ export interface ChartPropsConfig {
appSection?: AppSection;
/** is the chart refreshing its contents */
isRefreshing?: boolean;
/** chart ref */
inputRef?: RefObject<any>;
}

const DEFAULT_WIDTH = 800;
Expand Down Expand Up @@ -128,6 +131,8 @@ export default class ChartProps<FormData extends RawFormData = RawFormData> {

isRefreshing?: boolean;

inputRef?: RefObject<any>;

constructor(config: ChartPropsConfig & { formData?: FormData } = {}) {
const {
annotationData = {},
Expand All @@ -143,6 +148,7 @@ export default class ChartProps<FormData extends RawFormData = RawFormData> {
height = DEFAULT_HEIGHT,
appSection,
isRefreshing,
inputRef,
} = config;
this.width = width;
this.height = height;
Expand All @@ -159,6 +165,7 @@ export default class ChartProps<FormData extends RawFormData = RawFormData> {
this.behaviors = behaviors;
this.appSection = appSection;
this.isRefreshing = isRefreshing;
this.inputRef = inputRef;
}
}

Expand All @@ -178,6 +185,7 @@ ChartProps.createSelector = function create(): ChartPropsSelector {
input => input.behaviors,
input => input.appSection,
input => input.isRefreshing,
input => input.inputRef,
(
annotationData,
datasource,
Expand All @@ -192,6 +200,7 @@ ChartProps.createSelector = function create(): ChartPropsSelector {
behaviors,
appSection,
isRefreshing,
inputRef,
) =>
new ChartProps({
annotationData,
Expand All @@ -207,6 +216,7 @@ ChartProps.createSelector = function create(): ChartPropsSelector {
behaviors,
appSection,
isRefreshing,
inputRef,
}),
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export type FilterSets = {
[filtersSetId: string]: FilterSet;
};

export interface Filter {
export type Filter = {
cascadeParentIds: string[];
defaultDataMask: DataMask;
id: string; // randomly generated at filter creation
Expand All @@ -90,19 +90,31 @@ export interface Filter {
chartsInScope?: number[];
type: typeof NativeFilterType.NATIVE_FILTER;
description: string;
}
};

export interface Divider {
export type Divider = Partial<Omit<Filter, 'id' | 'type'>> & {
id: string;
title: string;
description: string;
type: typeof NativeFilterType.DIVIDER;
};

export function isNativeFilter(
filterElement: Filter | Divider,
): filterElement is Filter {
return filterElement.type === NativeFilterType.NATIVE_FILTER;
}

export function isFilterDivider(
filterElement: Filter | Divider,
): filterElement is Divider {
return filterElement.type === NativeFilterType.DIVIDER;
}

export type FilterConfiguration = Array<Filter | Divider>;

export type Filters = {
[filterId: string]: Filter;
[filterId: string]: Filter | Divider;
};

export type NativeFiltersState = {
Expand Down
4 changes: 2 additions & 2 deletions superset-frontend/spec/fixtures/mockNativeFilters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,7 @@ export const mockQueryDataForCountries = [
export const buildNativeFilter = (
id: string,
name: string,
parents: string[],
dependencies: string[],
) => ({
id,
controlValues: {
Expand All @@ -481,7 +481,7 @@ export const buildNativeFilter = (
filterState: {},
ownState: {},
},
cascadeParentIds: parents,
cascadeParentIds: dependencies,
scope: {
rootPath: ['ROOT_ID'],
excluded: [],
Expand Down
63 changes: 37 additions & 26 deletions superset-frontend/src/components/Select/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
* under the License.
*/
import React, {
forwardRef,
ReactElement,
ReactNode,
RefObject,
Expand Down Expand Up @@ -59,6 +60,7 @@ type PickedSelectProps = Pick<
| 'onClear'
| 'onFocus'
| 'onBlur'
| 'onDropdownVisibleChange'
| 'placeholder'
| 'showSearch'
| 'value'
Expand Down Expand Up @@ -264,31 +266,35 @@ const getQueryCacheKey = (value: string, page: number, pageSize: number) =>
* Each of the categories come with different abilities. For a comprehensive guide please refer to
* the storybook in src/components/Select/Select.stories.tsx.
*/
const Select = ({
allowNewOptions = false,
ariaLabel,
fetchOnlyOnSearch,
filterOption = true,
header = null,
invertSelection = false,
labelInValue = false,
lazyLoading = true,
loading,
mode = 'single',
name,
notFoundContent,
onError,
onChange,
onClear,
optionFilterProps = ['label', 'value'],
options,
pageSize = DEFAULT_PAGE_SIZE,
placeholder = t('Select ...'),
showSearch = true,
sortComparator = defaultSortComparator,
value,
...props
}: SelectProps) => {
const Select = (
{
allowNewOptions = false,
ariaLabel,
fetchOnlyOnSearch,
filterOption = true,
header = null,
invertSelection = false,
labelInValue = false,
lazyLoading = true,
loading,
mode = 'single',
name,
notFoundContent,
onError,
onChange,
onClear,
onDropdownVisibleChange,
optionFilterProps = ['label', 'value'],
options,
pageSize = DEFAULT_PAGE_SIZE,
placeholder = t('Select ...'),
showSearch = true,
sortComparator = defaultSortComparator,
value,
...props
}: SelectProps,
ref: RefObject<HTMLInputElement>,
) => {
const isAsync = typeof options === 'function';
const isSingleMode = mode === 'single';
const shouldShowSearch = isAsync || allowNewOptions ? true : showSearch;
Expand Down Expand Up @@ -616,6 +622,10 @@ const Select = ({
if (!isSingleMode && isDropdownVisible) {
handleTopOptions(selectValue);
}

if (onDropdownVisibleChange) {
onDropdownVisibleChange(isDropdownVisible);
}
};

const dropdownRender = (
Expand Down Expand Up @@ -759,6 +769,7 @@ const Select = ({
<StyledCheckOutlined iconSize="m" />
)
}
ref={ref}
{...props}
>
{shouldUseChildrenOptions &&
Expand All @@ -779,4 +790,4 @@ const Select = ({
);
};

export default Select;
export default forwardRef(Select);
Loading