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(native-filters): Add default first value to select filter #13726

Merged
merged 22 commits into from
Apr 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
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 @@ -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';
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
* under the License.
*/
import {
AppSection,
Behavior,
DataMask,
ensureIsArray,
Expand All @@ -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';

Expand All @@ -42,6 +43,7 @@ export default function PluginFilterSelect(props: PluginFilterSelectProps) {
width,
behaviors,
setDataMask,
appSection,
} = props;
const {
defaultValue,
Expand All @@ -51,39 +53,65 @@ 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];
const labelFormatter = getDataRecordFormatter({
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,
},
};

Expand All @@ -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,
amitmiran137 marked this conversation as resolved.
Show resolved Hide resolved
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,
Expand All @@ -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}
Expand Down
14 changes: 14 additions & 0 deletions superset-frontend/src/filters/components/Select/controlPanel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const {
enableEmptyFilter,
inverseSelection,
multiSelect,
defaultToFirstItem,
sortAscending,
} = DEFAULT_FORM_DATA;

Expand Down Expand Up @@ -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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -34,6 +42,7 @@ export default function transformProps(

return {
coltypeMap,
appSection,
width,
behaviors,
height,
Expand Down
11 changes: 9 additions & 2 deletions superset-frontend/src/filters/components/Select/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
* under the License.
*/
import {
AppSection,
ChartProps,
Behavior,
DataRecord,
Expand All @@ -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;
}
Expand All @@ -51,6 +56,7 @@ export type PluginFilterSelectProps = PluginFilterStylesProps & {
data: DataRecord[];
setDataMask: SetDataMaskHook;
behaviors: Behavior[];
appSection: AppSection;
formData: PluginFilterSelectQueryFormData;
};

Expand All @@ -59,6 +65,7 @@ export const DEFAULT_FORM_DATA: PluginFilterSelectCustomizeProps = {
currentValue: null,
enableEmptyFilter: false,
inverseSelection: false,
defaultToFirstItem: false,
multiSelect: true,
sortAscending: true,
};