feat(native-filters): Add default first value to select filter (#13726)

* feat: native filters first default value

* fix: fix CR notes

* feat: add support for `Place` type

* refactor: sync with master

* feat: add first value to Select filters

* refactor: fix CR notes

* refactor: updates usage of `ownFilters` to `ownState`

* Revert "refactor: updates usage of `ownFilters` to `ownState`"

This reverts commit 58b91998

* fix: revert mocks

* fix: fix CR notes

* chore: update package json

* chore: update package json

* chore: update package json

* test: fix tests

* fix: fix BE empty metrics

* lint: fix lint

* fix: fix BE empty metrics

* refactor: fix Cr notes
This commit is contained in:
simcha90 2021-04-12 09:22:33 +03:00 committed by GitHub
parent 1c6173c7ee
commit 468638c5b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 88 additions and 18 deletions

View File

@ -17,7 +17,12 @@
* under the License.
*/
import React, { FC, useEffect, useState } from 'react';
import { Behavior, SetDataMaskHook, SuperChart } from '@superset-ui/core';
import {
Behavior,
SetDataMaskHook,
SuperChart,
AppSection,
} from '@superset-ui/core';
import { FormInstance } from 'antd/lib/form';
import Loading from 'src/components/Loading';
import { NativeFiltersForm } from '../types';
@ -56,6 +61,7 @@ const DefaultValue: FC<DefaultValueProps> = ({
<SuperChart
height={25}
width={250}
appSection={AppSection.FILTER_CONFIG_MODAL}
behaviors={[Behavior.NATIVE_FILTER]}
formData={formData}
// For charts that don't have datasource we need workaround for empty placeholder

View File

@ -17,6 +17,7 @@
* under the License.
*/
import {
AppSection,
Behavior,
DataMask,
ensureIsArray,
@ -27,7 +28,7 @@ import {
} from '@superset-ui/core';
import React, { useEffect, useState } from 'react';
import { Select } from 'src/common/components';
import { PluginFilterSelectProps } from './types';
import { FIRST_VALUE, PluginFilterSelectProps, SelectValue } from './types';
import { StyledSelect, Styles } from '../common';
import { getDataRecordFormatter, getSelectExtraFormData } from '../../utils';
@ -42,6 +43,7 @@ export default function PluginFilterSelect(props: PluginFilterSelectProps) {
width,
behaviors,
setDataMask,
appSection,
} = props;
const {
defaultValue,
@ -51,12 +53,28 @@ export default function PluginFilterSelect(props: PluginFilterSelectProps) {
currentValue,
inverseSelection,
inputRef,
defaultToFirstItem,
} = formData;
const [values, setValues] = useState<(string | number | boolean)[]>(
defaultValue ?? [],
);
const forceFirstValue =
appSection === AppSection.FILTER_CONFIG_MODAL && defaultToFirstItem;
const groupby = ensureIsArray<string>(formData.groupby);
// Correct initial value for Ant Select
const initSelectValue: SelectValue =
// `defaultValue` can be `FIRST_VALUE` if `defaultToFirstItem` is checked, so need convert it to correct value for Select
defaultValue === FIRST_VALUE ? [] : defaultValue ?? [];
const firstItem: SelectValue = data[0]
? (groupby.map(col => data[0][col]) as string[]) ?? initSelectValue
: initSelectValue;
// If we are in config modal we always need show empty select for `defaultToFirstItem`
const [values, setValues] = useState<SelectValue>(
defaultToFirstItem && appSection !== AppSection.FILTER_CONFIG_MODAL
? firstItem
: initSelectValue,
);
const [col] = groupby;
const datatype: GenericDataType = coltypeMap[col];
@ -64,26 +82,36 @@ export default function PluginFilterSelect(props: PluginFilterSelectProps) {
timeFormatter: smartDateDetailedFormatter,
});
const handleChange = (
value?: (number | string)[] | number | string | null,
) => {
const resultValue: (number | string)[] = ensureIsArray<number | string>(
const handleChange = (value?: SelectValue | number | string) => {
let selectValue: (number | string)[] = ensureIsArray<number | string>(
value,
);
setValues(resultValue);
let stateValue: SelectValue | typeof FIRST_VALUE = selectValue.length
? selectValue
: null;
if (value === FIRST_VALUE) {
selectValue = forceFirstValue ? [] : firstItem;
stateValue = FIRST_VALUE;
}
setValues(selectValue);
const emptyFilter =
enableEmptyFilter && !inverseSelection && resultValue?.length === 0;
enableEmptyFilter && !inverseSelection && selectValue?.length === 0;
const dataMask = {
extraFormData: getSelectExtraFormData(
col,
resultValue,
selectValue,
emptyFilter,
inverseSelection,
),
currentState: {
value: resultValue.length ? resultValue : null,
// We need to save in state `FIRST_VALUE` as some const and not as REAL value,
// because when FiltersBar check if all filters initialized it compares `defaultValue` with this value
// and because REAL value can be unpredictable for users that have different data for same dashboard we use `FIRST_VALUE`
value: stateValue,
},
};
@ -100,18 +128,23 @@ export default function PluginFilterSelect(props: PluginFilterSelectProps) {
};
useEffect(() => {
handleChange(currentValue ?? []);
// For currentValue we need set always `FIRST_VALUE` only if we in config modal for `defaultToFirstItem` mode
handleChange(forceFirstValue ? FIRST_VALUE : currentValue ?? []);
}, [
JSON.stringify(currentValue),
defaultToFirstItem,
multiSelect,
enableEmptyFilter,
inverseSelection,
]);
useEffect(() => {
handleChange(defaultValue ?? []);
// If we have `defaultToFirstItem` mode it means that default value always `FIRST_VALUE`
handleChange(defaultToFirstItem ? FIRST_VALUE : defaultValue);
}, [
JSON.stringify(defaultValue),
JSON.stringify(firstItem),
defaultToFirstItem,
multiSelect,
enableEmptyFilter,
inverseSelection,
@ -127,6 +160,7 @@ export default function PluginFilterSelect(props: PluginFilterSelectProps) {
allowClear
// @ts-ignore
value={values}
disabled={forceFirstValue}
showSearch={showSearch}
mode={multiSelect ? 'multiple' : undefined}
placeholder={placeholderText}

View File

@ -24,6 +24,7 @@ const {
enableEmptyFilter,
inverseSelection,
multiSelect,
defaultToFirstItem,
sortAscending,
} = DEFAULT_FORM_DATA;
@ -79,6 +80,19 @@ const config: ControlPanelConfig = {
},
},
],
[
{
name: 'defaultToFirstItem',
config: {
type: 'CheckboxControl',
label: t('Default to first item'),
default: defaultToFirstItem,
resetConfig: true,
renderTrigger: true,
description: t('Select first item by default'),
},
},
],
[
{
name: 'inverseSelection',

View File

@ -22,7 +22,15 @@ import { DEFAULT_FORM_DATA, PluginFilterSelectChartProps } from './types';
export default function transformProps(
chartProps: PluginFilterSelectChartProps,
) {
const { formData, height, hooks, queriesData, width, behaviors } = chartProps;
const {
formData,
height,
hooks,
queriesData,
width,
behaviors,
appSection,
} = chartProps;
const newFormData = { ...DEFAULT_FORM_DATA, ...formData };
const { setDataMask = () => {} } = hooks;
const [queryData] = queriesData;
@ -34,6 +42,7 @@ export default function transformProps(
return {
coltypeMap,
appSection,
width,
behaviors,
height,

View File

@ -17,6 +17,7 @@
* under the License.
*/
import {
AppSection,
ChartProps,
Behavior,
DataRecord,
@ -28,12 +29,16 @@ import {
import { RefObject } from 'react';
import { PluginFilterStylesProps } from '../types';
export const FIRST_VALUE = '__FIRST_VALUE__';
export type SelectValue = (number | string)[] | null;
interface PluginFilterSelectCustomizeProps {
defaultValue?: (string | number)[] | null;
currentValue?: (string | number)[] | null;
defaultValue?: SelectValue | typeof FIRST_VALUE;
currentValue?: SelectValue | typeof FIRST_VALUE;
enableEmptyFilter: boolean;
inverseSelection: boolean;
multiSelect: boolean;
defaultToFirstItem: boolean;
inputRef?: RefObject<HTMLInputElement>;
sortAscending: boolean;
}
@ -51,6 +56,7 @@ export type PluginFilterSelectProps = PluginFilterStylesProps & {
data: DataRecord[];
setDataMask: SetDataMaskHook;
behaviors: Behavior[];
appSection: AppSection;
formData: PluginFilterSelectQueryFormData;
};
@ -59,6 +65,7 @@ export const DEFAULT_FORM_DATA: PluginFilterSelectCustomizeProps = {
currentValue: null,
enableEmptyFilter: false,
inverseSelection: false,
defaultToFirstItem: false,
multiSelect: true,
sortAscending: true,
};