chore: Improves the native filters UI/UX - iteration 3 (#14824)

This commit is contained in:
Michael S. Molina 2021-05-26 04:49:00 -03:00 committed by GitHub
parent 9fe0222009
commit 0c0eccb81a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 440 additions and 231 deletions

View File

@ -110,7 +110,7 @@ describe('FiltersConfigModal', () => {
function addFilter() {
act(() => {
wrapper.find('button[aria-label="Add tab"]').at(0).simulate('click');
wrapper.find('[aria-label="Add filter"]').at(0).simulate('click');
});
}

View File

@ -167,7 +167,9 @@ const FilterTabs: FC<FilterTabsProps> = ({
addIcon={
<StyledAddFilterBox>
<PlusOutlined />{' '}
<span data-test="add-filter-button">{t('Add filter')}</span>
<span data-test="add-filter-button" aria-label="Add filter">
{t('Add filter')}
</span>
</StyledAddFilterBox>
}
>

View File

@ -0,0 +1,70 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React, { ReactNode, useState } from 'react';
import { styled } from '@superset-ui/core';
import { Checkbox } from 'src/common/components';
interface CollapsibleControlProps {
checked?: boolean;
title: string;
children: ReactNode;
onChange?: (checked: boolean) => void;
}
const StyledContainer = styled.div<{ checked: boolean }>`
width: 100%;
display: flex;
flex-direction: column;
align-items: flex-start;
min-height: ${({ theme }) => theme.gridUnit * 10}px;
padding-top: ${({ theme }) => theme.gridUnit * 2 + 2}px;
.checkbox {
margin-bottom: ${({ theme, checked }) => (checked ? theme.gridUnit : 0)}px;
}
& > div {
margin-bottom: ${({ theme }) => theme.gridUnit * 2}px;
}
`;
const CollapsibleControl = (props: CollapsibleControlProps) => {
const { checked = false, title, children, onChange } = props;
const [isChecked, setIsChecked] = useState(checked);
return (
<StyledContainer checked={isChecked}>
<Checkbox
className="checkbox"
checked={isChecked}
onChange={e => {
const value = e.target.checked;
setIsChecked(value);
if (onChange) {
onChange(value);
}
}}
>
{title}
</Checkbox>
{isChecked && children}
</StyledContainer>
);
};
export { CollapsibleControl, CollapsibleControlProps };

View File

@ -1,111 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import {
CustomControlItem,
InfoTooltipWithTrigger,
} from '@superset-ui/chart-controls';
import React, { FC } from 'react';
import { Checkbox } from 'src/common/components';
import { FormInstance } from 'antd/lib/form';
import { getChartControlPanelRegistry, t } from '@superset-ui/core';
import { Tooltip } from 'src/components/Tooltip';
import { getControlItems, setNativeFilterFieldValues } from './utils';
import { NativeFiltersForm, NativeFiltersFormItem } from '../types';
import { StyledCheckboxFormItem } from './FiltersConfigForm';
import { Filter } from '../../types';
type ControlItemsProps = {
disabled: boolean;
filterId: string;
forceUpdate: Function;
filterToEdit?: Filter;
form: FormInstance<NativeFiltersForm>;
formFilter?: NativeFiltersFormItem;
};
const ControlItems: FC<ControlItemsProps> = ({
disabled,
forceUpdate,
form,
filterId,
filterToEdit,
formFilter,
}) => {
const filterType = formFilter?.filterType;
if (!filterType) return null;
const controlPanelRegistry = getChartControlPanelRegistry();
const controlItems =
getControlItems(controlPanelRegistry.get(filterType)) ?? [];
return (
<>
{controlItems
.filter(
(controlItem: CustomControlItem) =>
controlItem?.config?.renderTrigger,
)
.map(controlItem => (
<Tooltip
placement="left"
title={
controlItem.config.affectsDataMask &&
disabled &&
t('Populate "Default value" to enable this control')
}
>
<StyledCheckboxFormItem
key={controlItem.name}
name={['filters', filterId, 'controlValues', controlItem.name]}
initialValue={
filterToEdit?.controlValues?.[controlItem.name] ??
controlItem?.config?.default
}
valuePropName="checked"
colon={false}
>
<Checkbox
disabled={controlItem.config.affectsDataMask && disabled}
onChange={() => {
if (!controlItem.config.resetConfig) {
forceUpdate();
return;
}
setNativeFilterFieldValues(form, filterId, {
defaultDataMask: null,
});
forceUpdate();
}}
>
{controlItem.config.label}{' '}
{controlItem.config.description && (
<InfoTooltipWithTrigger
placement="top"
label={controlItem.config.name}
tooltip={controlItem.config.description}
/>
)}
</Checkbox>
</StyledCheckboxFormItem>
</Tooltip>
))}
</>
);
};
export default ControlItems;

View File

@ -32,7 +32,7 @@ import {
Metric,
} from '@superset-ui/chart-controls';
import { FormInstance } from 'antd/lib/form';
import React, { useCallback, useEffect, useState } from 'react';
import React, { useCallback, useEffect, useState, useMemo } from 'react';
import { useSelector } from 'react-redux';
import { Checkbox, Form, Input } from 'src/common/components';
import { Select } from 'src/components/Select';
@ -45,7 +45,6 @@ import { addDangerToast } from 'src/messageToasts/actions';
import { ClientErrorObject } from 'src/utils/getClientErrorObject';
import SelectControl from 'src/explore/components/controls/SelectControl';
import Collapse from 'src/components/Collapse';
import Button from 'src/components/Button';
import { getChartDataRequest } from 'src/chart/chartAction';
import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags';
import { waitForAsyncData } from 'src/middleware/asyncEvent';
@ -60,10 +59,11 @@ import {
import { useBackendFormUpdate } from './state';
import { getFormData } from '../../utils';
import { Filter } from '../../types';
import ControlItems from './ControlItems';
import getControlItemsMap from './getControlItemsMap';
import FilterScope from './FilterScope/FilterScope';
import RemovedFilter from './RemovedFilter';
import DefaultValue from './DefaultValue';
import { CollapsibleControl } from './CollapsibleControl';
import {
CASCADING_FILTERS,
getFiltersConfigModalTestId,
@ -77,19 +77,29 @@ const StyledContainer = styled.div`
justify-content: space-between;
`;
const StyledDatasetContainer = styled.div`
const StyledRowContainer = styled.div`
display: flex;
flex-direction: row;
justify-content: space-between;
width: 100%;
`;
export const StyledFormItem = styled(Form.Item)`
width: 49%;
margin-bottom: ${({ theme }) => theme.gridUnit * 4}px;
& .ant-form-item-control-input {
min-height: ${({ theme }) => theme.gridUnit * 10}px;
}
`;
export const StyledCheckboxFormItem = styled(Form.Item)`
margin-bottom: 0;
export const StyledRowFormItem = styled(Form.Item)`
margin-bottom: 0px;
min-width: 50%;
& .ant-form-item-control-input {
min-height: ${({ theme }) => theme.gridUnit * 10}px;
}
`;
export const StyledLabel = styled.span`
@ -120,6 +130,10 @@ const StyledCollapse = styled(Collapse)`
border: 0px;
}
.ant-collapse-content-box {
padding-top: ${({ theme }) => theme.gridUnit * 2}px;
}
&.ant-collapse > .ant-collapse-item {
border: 0px;
border-radius: 0px;
@ -130,6 +144,10 @@ const StyledTabs = styled(Tabs)`
.ant-tabs-nav-list {
padding: 0px;
}
.ant-form-item-label {
padding-bottom: 0px;
}
`;
const FilterTabs = {
@ -169,8 +187,11 @@ const FILTERS_WITHOUT_COLUMN = [
'filter_timecolumn',
'filter_groupby',
];
const FILTERS_WITH_ADHOC_FILTERS = ['filter_select', 'filter_range'];
const BASIC_CONTROL_ITEMS = ['enableEmptyFilter', 'multiSelect'];
/**
* The configuration form for a specific filter.
* Assigns field values to `filters[filterId]` in the form.
@ -184,9 +205,14 @@ export const FiltersConfigForm: React.FC<FiltersConfigFormProps> = ({
parentFilters,
}) => {
const [metrics, setMetrics] = useState<Metric[]>([]);
const [hasDefaultValue, setHasDefaultValue] = useState(
!!filterToEdit?.defaultDataMask?.filterState?.value,
);
const forceUpdate = useForceUpdate();
const [datasetDetails, setDatasetDetails] = useState<Record<string, any>>();
const formFilter = form.getFieldValue('filters')?.[filterId] || {};
const defaultFormFilter = useMemo(() => {}, []);
const formFilter =
form.getFieldValue('filters')?.[filterId] || defaultFormFilter;
const nativeFilterItems = getChartMetadataRegistry().items;
const nativeFilterVizTypes = Object.entries(nativeFilterItems)
// @ts-ignore
@ -243,7 +269,7 @@ export const FiltersConfigForm: React.FC<FiltersConfigFormProps> = ({
useBackendFormUpdate(form, filterId);
const refreshHandler = () => {
const refreshHandler = useCallback(() => {
if (!hasDataset || !formFilter?.dataset?.value) {
forceUpdate();
return;
@ -287,7 +313,7 @@ export const FiltersConfigForm: React.FC<FiltersConfigFormProps> = ({
forceUpdate();
}
});
};
}, [filterId, forceUpdate, form, formFilter, hasDataset]);
const defaultDatasetSelectOptions = Object.values(loadedDatasets).map(
datasetToSelectOption,
@ -304,6 +330,19 @@ export const FiltersConfigForm: React.FC<FiltersConfigFormProps> = ({
...formFilter,
});
useEffect(() => {
if (hasDataset && hasFilledDataset && hasDefaultValue && isDataDirty) {
refreshHandler();
}
}, [
hasDataset,
hasFilledDataset,
hasDefaultValue,
formFilter,
isDataDirty,
refreshHandler,
]);
const onDatasetSelectError = useCallback(
({ error, message }: ClientErrorObject) => {
let errorText = message || error || t('An error has occurred');
@ -324,8 +363,34 @@ export const FiltersConfigForm: React.FC<FiltersConfigFormProps> = ({
label: filter.title,
}));
const parentFilter = parentFilterOptions.find(
({ value }) => value === filterToEdit?.cascadeParentIds[0],
);
const showDefaultValue = !hasDataset || (!isDataDirty && hasFilledDataset);
const controlItems = formFilter
? getControlItemsMap({
disabled: !showDefaultValue,
forceUpdate,
form,
filterId,
filterType: formFilter.filterType,
filterToEdit,
})
: {};
const onSortChanged = (value: boolean | undefined) => {
const previous = form.getFieldValue('filters')?.[filterId].controlValues;
setNativeFilterFieldValues(form, filterId, {
controlValues: {
...previous,
sortAscending: value,
},
});
forceUpdate();
};
return (
<>
<StyledTabs defaultActiveKey={FilterTabs.configuration.key} centered>
@ -367,7 +432,7 @@ export const FiltersConfigForm: React.FC<FiltersConfigFormProps> = ({
</StyledFormItem>
</StyledContainer>
{hasDataset && (
<StyledDatasetContainer>
<StyledRowContainer>
<StyledFormItem
name={['filters', filterId, 'dataset']}
initialValue={{ value: initialDatasetId }}
@ -425,9 +490,9 @@ export const FiltersConfigForm: React.FC<FiltersConfigFormProps> = ({
/>
</StyledFormItem>
)}
</StyledDatasetContainer>
</StyledRowContainer>
)}
<StyledCollapse>
<StyledCollapse defaultActiveKey={FilterPanels.basic.key}>
<Collapse.Panel
header={FilterPanels.basic.name}
key={FilterPanels.basic.key}
@ -444,31 +509,12 @@ export const FiltersConfigForm: React.FC<FiltersConfigFormProps> = ({
hidden
initialValue={null}
/>
{isCascadingFilter && (
<StyledFormItem
name={['filters', filterId, 'parentFilter']}
label={<StyledLabel>{t('Parent filter')}</StyledLabel>}
initialValue={parentFilterOptions.find(
({ value }) => value === filterToEdit?.cascadeParentIds[0],
)}
data-test="parent-filter-input"
>
<Select
placeholder={t('None')}
options={parentFilterOptions}
isClearable
/>
</StyledFormItem>
)}
<StyledContainer>
<StyledFormItem className="bottom" label={<StyledLabel />}>
{hasDataset && hasFilledDataset && (
<Button onClick={refreshHandler}>
{isDataDirty ? t('Populate') : t('Refresh')}
</Button>
)}
</StyledFormItem>
<StyledFormItem
<CollapsibleControl
title={t('Filter has default value')}
checked={hasDefaultValue}
onChange={value => setHasDefaultValue(value)}
>
<StyledRowFormItem
name={['filters', filterId, 'defaultDataMask']}
initialValue={filterToEdit?.defaultDataMask}
data-test="default-input"
@ -488,14 +534,15 @@ export const FiltersConfigForm: React.FC<FiltersConfigFormProps> = ({
formData={newFormData}
enableNoResults={enableNoResults}
/>
) : hasFilledDataset ? (
t('Click "Populate" to get "Default Value" ->')
) : (
t('Fill all required fields to enable "Default Value"')
)}
</StyledFormItem>
</StyledContainer>
<StyledCheckboxFormItem
</StyledRowFormItem>
</CollapsibleControl>
{Object.keys(controlItems)
.filter(key => BASIC_CONTROL_ITEMS.includes(key))
.map(key => controlItems[key])}
<StyledRowFormItem
name={['filters', filterId, 'isInstant']}
initialValue={filterToEdit?.isInstant || false}
valuePropName="checked"
@ -504,24 +551,44 @@ export const FiltersConfigForm: React.FC<FiltersConfigFormProps> = ({
<Checkbox data-test="apply-changes-instantly-checkbox">
{t('Apply changes instantly')}
</Checkbox>
</StyledCheckboxFormItem>
<ControlItems
disabled={!showDefaultValue}
filterToEdit={filterToEdit}
formFilter={formFilter}
filterId={filterId}
form={form}
forceUpdate={forceUpdate}
/>
</StyledRowFormItem>
</Collapse.Panel>
{((hasDataset && hasAdditionalFilters) || hasMetrics) && (
<Collapse.Panel
header={FilterPanels.advanced.name}
key={FilterPanels.advanced.key}
>
{isCascadingFilter && (
<CollapsibleControl
title={t('Filter is hierarchical')}
checked={!!parentFilter}
>
<StyledRowFormItem
name={['filters', filterId, 'parentFilter']}
label={<StyledLabel>{t('Parent filter')}</StyledLabel>}
initialValue={parentFilter}
data-test="parent-filter-input"
>
<Select
placeholder={t('None')}
options={parentFilterOptions}
isClearable
/>
</StyledRowFormItem>
</CollapsibleControl>
)}
{Object.keys(controlItems)
.filter(key => !BASIC_CONTROL_ITEMS.includes(key))
.map(key => controlItems[key])}
{hasDataset && hasAdditionalFilters && (
<>
<StyledFormItem
<CollapsibleControl
title={t('Pre-filter available values')}
checked={
!!filterToEdit?.adhoc_filters ||
!!filterToEdit?.time_range
}
>
<StyledRowFormItem
name={['filters', filterId, 'adhoc_filters']}
initialValue={filterToEdit?.adhoc_filters}
>
@ -541,8 +608,8 @@ export const FiltersConfigForm: React.FC<FiltersConfigFormProps> = ({
}}
label={<StyledLabel>{t('Adhoc filters')}</StyledLabel>}
/>
</StyledFormItem>
<StyledFormItem
</StyledRowFormItem>
<StyledRowFormItem
name={['filters', filterId, 'time_range']}
label={<StyledLabel>{t('Time range')}</StyledLabel>}
initialValue={filterToEdit?.time_range || 'No filter'}
@ -556,37 +623,75 @@ export const FiltersConfigForm: React.FC<FiltersConfigFormProps> = ({
forceUpdate();
}}
/>
</StyledFormItem>
</>
</StyledRowFormItem>
</CollapsibleControl>
)}
{hasMetrics && (
<StyledFormItem
// don't show the column select unless we have a dataset
// style={{ display: datasetId == null ? undefined : 'none' }}
name={['filters', filterId, 'sortMetric']}
initialValue={filterToEdit?.sortMetric}
label={<StyledLabel>{t('Sort Metric')}</StyledLabel>}
data-test="field-input"
>
<SelectControl
form={form}
filterId={filterId}
name="sortMetric"
options={metrics.map((metric: Metric) => ({
value: metric.metric_name,
label: metric.verbose_name ?? metric.metric_name,
}))}
onChange={(value: string | null): void => {
if (value !== undefined) {
setNativeFilterFieldValues(form, filterId, {
sortMetric: value,
});
forceUpdate();
<CollapsibleControl
title={t('Sort filter values')}
onChange={checked => onSortChanged(checked || undefined)}
checked={
typeof filterToEdit?.controlValues?.sortAscending ===
'boolean'
}
>
<StyledRowContainer>
<StyledFormItem
name={[
'filters',
filterId,
'controlValues',
'sortAscending',
]}
initialValue={filterToEdit?.controlValues?.sortAscending}
label={<StyledLabel>{t('Sort type')}</StyledLabel>}
>
<Select
form={form}
filterId={filterId}
name="sortAscending"
options={[
{
value: true,
label: t('Sort ascending'),
},
{
value: false,
label: t('Sort descending'),
},
]}
onChange={({ value }: { value: boolean }) =>
onSortChanged(value)
}
}}
/>
</StyledFormItem>
)}
/>
</StyledFormItem>
{hasMetrics && (
<StyledFormItem
name={['filters', filterId, 'sortMetric']}
initialValue={filterToEdit?.sortMetric}
label={<StyledLabel>{t('Sort Metric')}</StyledLabel>}
data-test="field-input"
>
<SelectControl
form={form}
filterId={filterId}
name="sortMetric"
options={metrics.map((metric: Metric) => ({
value: metric.metric_name,
label: metric.verbose_name ?? metric.metric_name,
}))}
onChange={(value: string | null): void => {
if (value !== undefined) {
setNativeFilterFieldValues(form, filterId, {
sortMetric: value,
});
forceUpdate();
}
}}
/>
</StyledFormItem>
)}
</StyledRowContainer>
</CollapsibleControl>
</Collapse.Panel>
)}
</StyledCollapse>

View File

@ -19,23 +19,57 @@
import React from 'react';
import { render, screen } from 'spec/helpers/testing-library';
import userEvent from '@testing-library/user-event';
import { Filter } from 'src/dashboard/components/nativeFilters/types';
import { FormInstance } from 'src/common/components';
import { getControlItems, setNativeFilterFieldValues } from './utils';
import ControlItems from './ControlItems';
import getControlItemsMap, { ControlItemsProps } from './getControlItemsMap';
jest.mock('./utils', () => ({
getControlItems: jest.fn(),
setNativeFilterFieldValues: jest.fn(),
}));
const createProps = () => ({
forceUpdate: jest.fn(),
form: 'form',
filterId: 'filterId',
filterToEdit: '',
formFilter: {
filterType: 'filterType',
const formMock: FormInstance = {
__INTERNAL__: { itemRef: () => () => {} },
scrollToField: () => {},
getFieldInstance: () => {},
getFieldValue: () => {},
getFieldsValue: () => {},
getFieldError: () => [],
getFieldsError: () => [],
isFieldsTouched: () => false,
isFieldTouched: () => false,
isFieldValidating: () => false,
isFieldsValidating: () => false,
resetFields: () => {},
setFields: () => {},
setFieldsValue: () => {},
validateFields: () => Promise.resolve(),
submit: () => {},
};
const filterMock: Filter = {
cascadeParentIds: [],
defaultDataMask: {},
isInstant: false,
id: 'mock',
name: 'mock',
scope: {
rootPath: [],
excluded: [],
},
filterType: '',
targets: [{}],
controlValues: {},
};
const createProps: () => ControlItemsProps = () => ({
disabled: false,
forceUpdate: jest.fn(),
form: formMock,
filterId: 'filterId',
filterToEdit: filterMock,
filterType: 'filterType',
});
const createControlItems = () => [
@ -49,33 +83,32 @@ beforeEach(() => {
jest.clearAllMocks();
});
function renderControlItems(controlItemsMap: {}): any {
return render(<>{Object.values(controlItemsMap).map(value => value)}</>);
}
test('Should render null when has no "formFilter"', () => {
const defaultProps = createProps();
const props = {
forceUpdate: defaultProps.forceUpdate,
form: defaultProps.form,
filterId: defaultProps.filterId,
};
const { container } = render(<ControlItems {...(props as any)} />);
const props = createProps();
const controlItemsMap = getControlItemsMap(props);
const { container } = renderControlItems(controlItemsMap);
expect(container.children).toHaveLength(0);
});
test('Should render null when has no "formFilter.filterType" is falsy value', () => {
const defaultProps = createProps();
const props = {
forceUpdate: defaultProps.forceUpdate,
form: defaultProps.form,
filterId: defaultProps.filterId,
formFilter: { name: 'name', filterType: 'filterType' },
};
const { container } = render(<ControlItems {...(props as any)} />);
const props = createProps();
const controlItemsMap = getControlItemsMap({
...props,
filterType: 'filterType',
});
const { container } = renderControlItems(controlItemsMap);
expect(container.children).toHaveLength(0);
});
test('Should render null empty when "getControlItems" return []', () => {
const props = createProps();
(getControlItems as jest.Mock).mockReturnValue([]);
const { container } = render(<ControlItems {...(props as any)} />);
const controlItemsMap = getControlItemsMap(props);
const { container } = renderControlItems(controlItemsMap);
expect(container.children).toHaveLength(0);
});
@ -83,8 +116,8 @@ test('Should render null empty when "controlItems" are falsy', () => {
const props = createProps();
const controlItems = [null, false, {}, { config: { renderTrigger: false } }];
(getControlItems as jest.Mock).mockReturnValue(controlItems);
const { container } = render(<ControlItems {...(props as any)} />);
const controlItemsMap = getControlItemsMap(props);
const { container } = renderControlItems(controlItemsMap);
expect(container.children).toHaveLength(0);
});
@ -96,16 +129,16 @@ test('Should render render ControlItems', () => {
{ name: 'name_2', config: { renderTrigger: true } },
];
(getControlItems as jest.Mock).mockReturnValue(controlItems);
render(<ControlItems {...(props as any)} />);
const controlItemsMap = getControlItemsMap(props);
renderControlItems(controlItemsMap);
expect(screen.getAllByRole('checkbox')).toHaveLength(2);
});
test('Clickin on checkbox', () => {
const props = createProps();
(getControlItems as jest.Mock).mockReturnValue(createControlItems());
render(<ControlItems {...(props as any)} />);
const controlItemsMap = getControlItemsMap(props);
renderControlItems(controlItemsMap);
expect(props.forceUpdate).not.toBeCalled();
expect(setNativeFilterFieldValues).not.toBeCalled();
userEvent.click(screen.getByRole('checkbox'));
@ -118,8 +151,8 @@ test('Clickin on checkbox when resetConfig:flase', () => {
(getControlItems as jest.Mock).mockReturnValue([
{ name: 'name_1', config: { renderTrigger: true, resetConfig: false } },
]);
render(<ControlItems {...(props as any)} />);
const controlItemsMap = getControlItemsMap(props);
renderControlItems(controlItemsMap);
expect(props.forceUpdate).not.toBeCalled();
expect(setNativeFilterFieldValues).not.toBeCalled();
userEvent.click(screen.getByRole('checkbox'));

View File

@ -0,0 +1,110 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import {
CustomControlItem,
InfoTooltipWithTrigger,
} from '@superset-ui/chart-controls';
import React from 'react';
import { Checkbox } from 'src/common/components';
import { FormInstance } from 'antd/lib/form';
import { getChartControlPanelRegistry, t } from '@superset-ui/core';
import { Tooltip } from 'src/components/Tooltip';
import { getControlItems, setNativeFilterFieldValues } from './utils';
import { NativeFiltersForm } from '../types';
import { StyledRowFormItem } from './FiltersConfigForm';
import { Filter } from '../../types';
export interface ControlItemsProps {
disabled: boolean;
forceUpdate: Function;
form: FormInstance<NativeFiltersForm>;
filterId: string;
filterType: string;
filterToEdit?: Filter;
}
export default function getControlItemsMap({
disabled,
forceUpdate,
form,
filterId,
filterType,
filterToEdit,
}: ControlItemsProps) {
const controlPanelRegistry = getChartControlPanelRegistry();
const controlItems =
getControlItems(controlPanelRegistry.get(filterType)) ?? [];
const map = {};
controlItems
.filter(
(controlItem: CustomControlItem) =>
controlItem?.config?.renderTrigger &&
controlItem.name !== 'sortAscending',
)
.forEach(controlItem => {
const element = (
<Tooltip
key={controlItem.name}
placement="left"
title={
controlItem.config.affectsDataMask &&
disabled &&
t('Populate "Default value" to enable this control')
}
>
<StyledRowFormItem
key={controlItem.name}
name={['filters', filterId, 'controlValues', controlItem.name]}
initialValue={
filterToEdit?.controlValues?.[controlItem.name] ??
controlItem?.config?.default
}
valuePropName="checked"
colon={false}
>
<Checkbox
disabled={controlItem.config.affectsDataMask && disabled}
onChange={() => {
if (!controlItem.config.resetConfig) {
forceUpdate();
return;
}
setNativeFilterFieldValues(form, filterId, {
defaultDataMask: null,
});
forceUpdate();
}}
>
{controlItem.config.label}{' '}
{controlItem.config.description && (
<InfoTooltipWithTrigger
placement="top"
label={controlItem.config.name}
tooltip={controlItem.config.description}
/>
)}
</Checkbox>
</StyledRowFormItem>
</Tooltip>
);
map[controlItem.name] = element;
});
return map;
}

View File

@ -46,7 +46,7 @@ const StyledModalWrapper = styled(StyledModal)`
export const StyledModalBody = styled.div`
display: flex;
height: 500px;
height: 700px;
flex-direction: row;
.filters-list {
width: ${({ theme }) => theme.gridUnit * 50}px;