chore: Improves the native filters UI/UX - iteration 7 (#15017)
This commit is contained in:
parent
8aaa6036d7
commit
1468026883
|
|
@ -34,10 +34,6 @@ import { TimeFilterPlugin, SelectFilterPlugin } from 'src/filters/components';
|
|||
import { DATE_FILTER_CONTROL_TEST_ID } from 'src/explore/components/controls/DateFilterControl/DateFilterLabel';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import { waitFor } from '@testing-library/react';
|
||||
import mockDatasource, {
|
||||
id as datasourceId,
|
||||
datasourceId as fullDatasourceId,
|
||||
} from 'spec/fixtures/mockDatasource';
|
||||
import FilterBar, { FILTER_BAR_TEST_ID } from '.';
|
||||
import { FILTERS_CONFIG_MODAL_TEST_ID } from '../FiltersConfigModal/FiltersConfigModal';
|
||||
|
||||
|
|
@ -57,8 +53,25 @@ class MainPreset extends Preset {
|
|||
}
|
||||
}
|
||||
|
||||
fetchMock.get(`glob:*/api/v1/dataset/${datasourceId}`, {
|
||||
result: [mockDatasource[fullDatasourceId]],
|
||||
fetchMock.get(`glob:*/api/v1/dataset/1`, {
|
||||
description_columns: {},
|
||||
id: 1,
|
||||
label_columns: {
|
||||
columns: 'Columns',
|
||||
table_name: 'Table Name',
|
||||
},
|
||||
result: {
|
||||
metrics: [],
|
||||
columns: [
|
||||
{
|
||||
column_name: 'Column A',
|
||||
id: 1,
|
||||
},
|
||||
],
|
||||
table_name: 'birth_names',
|
||||
id: 1,
|
||||
},
|
||||
show_columns: ['id', 'table_name'],
|
||||
});
|
||||
|
||||
const getTestId = testWithId<string>(FILTER_BAR_TEST_ID, true);
|
||||
|
|
|
|||
|
|
@ -16,27 +16,22 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React, { useCallback } from 'react';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { FormInstance } from 'antd/lib/form';
|
||||
import { SupersetClient, t } from '@superset-ui/core';
|
||||
import { useChangeEffect } from 'src/common/hooks/useChangeEffect';
|
||||
import { AsyncSelect } from 'src/components/Select';
|
||||
import { Select } from 'src/common/components';
|
||||
import { useToasts } from 'src/messageToasts/enhancers/withToasts';
|
||||
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
|
||||
import { cacheWrapper } from 'src/utils/cacheWrapper';
|
||||
import { NativeFiltersForm } from '../types';
|
||||
|
||||
type ColumnSelectValue = {
|
||||
value: string;
|
||||
label: string;
|
||||
};
|
||||
|
||||
interface ColumnSelectProps {
|
||||
form: FormInstance<NativeFiltersForm>;
|
||||
filterId: string;
|
||||
datasetId?: number | null | undefined;
|
||||
value?: ColumnSelectValue | null;
|
||||
onChange?: (value: ColumnSelectValue | null) => void;
|
||||
datasetId?: number;
|
||||
value?: string;
|
||||
onChange?: (value: string) => void;
|
||||
}
|
||||
|
||||
const localCache = new Map<string, any>();
|
||||
|
|
@ -56,6 +51,7 @@ export function ColumnSelect({
|
|||
value,
|
||||
onChange,
|
||||
}: ColumnSelectProps) {
|
||||
const [options, setOptions] = useState();
|
||||
const { addDangerToast } = useToasts();
|
||||
const resetColumnField = useCallback(() => {
|
||||
form.setFields([
|
||||
|
|
@ -67,45 +63,40 @@ export function ColumnSelect({
|
|||
if (previous != null) {
|
||||
resetColumnField();
|
||||
}
|
||||
if (datasetId != null) {
|
||||
cachedSupersetGet({
|
||||
endpoint: `/api/v1/dataset/${datasetId}`,
|
||||
}).then(
|
||||
({ json: { result } }) => {
|
||||
const columns = result.columns
|
||||
.map((col: any) => col.column_name)
|
||||
.sort((a: string, b: string) => a.localeCompare(b));
|
||||
if (!columns.includes(value)) {
|
||||
resetColumnField();
|
||||
}
|
||||
setOptions(
|
||||
columns.map((column: any) => ({ label: column, value: column })),
|
||||
);
|
||||
},
|
||||
async badResponse => {
|
||||
const { error, message } = await getClientErrorObject(badResponse);
|
||||
let errorText = message || error || t('An error has occurred');
|
||||
if (message === 'Forbidden') {
|
||||
errorText = t('You do not have permission to edit this dashboard');
|
||||
}
|
||||
addDangerToast(errorText);
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
function loadOptions() {
|
||||
if (datasetId == null) return [];
|
||||
return cachedSupersetGet({
|
||||
endpoint: `/api/v1/dataset/${datasetId}`,
|
||||
}).then(
|
||||
({ json: { result } }) => {
|
||||
const columns = result.columns
|
||||
.map((col: any) => col.column_name)
|
||||
.sort((a: string, b: string) => a.localeCompare(b));
|
||||
if (!columns.includes(value)) {
|
||||
resetColumnField();
|
||||
}
|
||||
return columns;
|
||||
},
|
||||
async badResponse => {
|
||||
const { error, message } = await getClientErrorObject(badResponse);
|
||||
let errorText = message || error || t('An error has occurred');
|
||||
if (message === 'Forbidden') {
|
||||
errorText = t('You do not have permission to edit this dashboard');
|
||||
}
|
||||
addDangerToast(errorText);
|
||||
return [];
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<AsyncSelect
|
||||
// "key" prop makes react render a new instance of the select whenever the dataset changes
|
||||
key={datasetId == null ? '*no dataset*' : datasetId}
|
||||
isDisabled={datasetId == null}
|
||||
<Select
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
isMulti={false}
|
||||
loadOptions={loadOptions}
|
||||
defaultOptions // load options on render
|
||||
cacheOptions
|
||||
options={options}
|
||||
placeholder={t('Select a column')}
|
||||
showSearch
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ const DefaultValue: FC<DefaultValueProps> = ({
|
|||
) : (
|
||||
<SuperChart
|
||||
height={25}
|
||||
width={250}
|
||||
width={formFilter?.filterType === 'filter_time' ? 350 : 250}
|
||||
appSection={AppSection.FILTER_CONFIG_MODAL}
|
||||
behaviors={[Behavior.NATIVE_FILTER]}
|
||||
formData={formData}
|
||||
|
|
|
|||
|
|
@ -164,6 +164,8 @@ const StyledCollapse = styled(Collapse)`
|
|||
const StyledTabs = styled(Tabs)`
|
||||
.ant-tabs-nav {
|
||||
position: sticky;
|
||||
margin-left: ${({ theme }) => theme.gridUnit * -4}px;
|
||||
margin-right: ${({ theme }) => theme.gridUnit * -4}px;
|
||||
top: 0px;
|
||||
background: white;
|
||||
z-index: 1;
|
||||
|
|
@ -180,8 +182,8 @@ const StyledTabs = styled(Tabs)`
|
|||
|
||||
const StyledAsterisk = styled.span`
|
||||
color: ${({ theme }) => theme.colors.error.base};
|
||||
font-family: SimSun, sans-serif;
|
||||
margin-right: ${({ theme }) => theme.gridUnit - 1}px;
|
||||
font-size: ${({ theme }) => theme.typography.sizes.s}px;
|
||||
margin-left: ${({ theme }) => theme.gridUnit - 1}px;
|
||||
&:before {
|
||||
content: '*';
|
||||
}
|
||||
|
|
@ -458,6 +460,28 @@ const FiltersConfigForm = (
|
|||
forceUpdate();
|
||||
};
|
||||
|
||||
const validatePreFilter = () =>
|
||||
setTimeout(
|
||||
() =>
|
||||
form.validateFields([
|
||||
['filters', filterId, 'adhoc_filters'],
|
||||
['filters', filterId, 'time_range'],
|
||||
]),
|
||||
0,
|
||||
);
|
||||
|
||||
const hasTimeRange =
|
||||
formFilter?.time_range && formFilter.time_range !== 'No filter';
|
||||
|
||||
const hasAdhoc = formFilter?.adhoc_filters?.length > 0;
|
||||
|
||||
const preFilterValidator = () => {
|
||||
if (hasTimeRange || hasAdhoc) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject(new Error(t('Pre-filter is required')));
|
||||
};
|
||||
|
||||
let hasCheckedAdvancedControl = hasParentFilter || hasPreFilter || hasSorting;
|
||||
if (!hasCheckedAdvancedControl) {
|
||||
hasCheckedAdvancedControl = Object.keys(controlItems)
|
||||
|
|
@ -567,7 +591,7 @@ const FiltersConfigForm = (
|
|||
initialValue={initColumn}
|
||||
label={<StyledLabel>{t('Column')}</StyledLabel>}
|
||||
rules={[
|
||||
{ required: !removed, message: t('Field is required') },
|
||||
{ required: !removed, message: t('Column is required') },
|
||||
]}
|
||||
data-test="field-input"
|
||||
>
|
||||
|
|
@ -626,7 +650,7 @@ const FiltersConfigForm = (
|
|||
},
|
||||
{
|
||||
validator: (rule, value) => {
|
||||
const hasValue = !!value.filterState?.value;
|
||||
const hasValue = !!value?.filterState?.value;
|
||||
if (
|
||||
hasValue ||
|
||||
// TODO: do more generic
|
||||
|
|
@ -730,14 +754,7 @@ const FiltersConfigForm = (
|
|||
checked={hasPreFilter}
|
||||
onChange={checked => {
|
||||
if (checked) {
|
||||
// execute after render
|
||||
setTimeout(
|
||||
() =>
|
||||
form.validateFields([
|
||||
['filters', filterId, 'adhoc_filters'],
|
||||
]),
|
||||
0,
|
||||
);
|
||||
validatePreFilter();
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
|
@ -747,8 +764,7 @@ const FiltersConfigForm = (
|
|||
required
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: t('Pre-filter is required'),
|
||||
validator: preFilterValidator,
|
||||
},
|
||||
]}
|
||||
>
|
||||
|
|
@ -765,11 +781,12 @@ const FiltersConfigForm = (
|
|||
adhoc_filters: filters,
|
||||
});
|
||||
forceUpdate();
|
||||
validatePreFilter();
|
||||
}}
|
||||
label={
|
||||
<span>
|
||||
<StyledAsterisk />
|
||||
<StyledLabel>{t('Pre-filter')}</StyledLabel>
|
||||
{!hasTimeRange && <StyledAsterisk />}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
|
|
@ -778,6 +795,12 @@ const FiltersConfigForm = (
|
|||
name={['filters', filterId, 'time_range']}
|
||||
label={<StyledLabel>{t('Time range')}</StyledLabel>}
|
||||
initialValue={filterToEdit?.time_range || 'No filter'}
|
||||
required={!hasAdhoc}
|
||||
rules={[
|
||||
{
|
||||
validator: preFilterValidator,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<DateFilterControl
|
||||
name="time_range"
|
||||
|
|
@ -786,6 +809,7 @@ const FiltersConfigForm = (
|
|||
time_range: timeRange,
|
||||
});
|
||||
forceUpdate();
|
||||
validatePreFilter();
|
||||
}}
|
||||
/>
|
||||
</StyledRowFormItem>
|
||||
|
|
|
|||
|
|
@ -17,198 +17,289 @@
|
|||
* under the License.
|
||||
*/
|
||||
import { Preset } from '@superset-ui/core';
|
||||
import { SelectFilterPlugin, TimeFilterPlugin } from 'src/filters/components';
|
||||
import { render, cleanup, screen } from 'spec/helpers/testing-library';
|
||||
import { Provider } from 'react-redux';
|
||||
import {
|
||||
getMockStore,
|
||||
mockStore,
|
||||
stateWithoutNativeFilters,
|
||||
} from 'spec/fixtures/mockStore';
|
||||
SelectFilterPlugin,
|
||||
RangeFilterPlugin,
|
||||
TimeFilterPlugin,
|
||||
TimeColumnFilterPlugin,
|
||||
TimeGrainFilterPlugin,
|
||||
} from 'src/filters/components';
|
||||
import { render, screen, waitFor } from 'spec/helpers/testing-library';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import React from 'react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { testWithId } from 'src/utils/testUtils';
|
||||
import { waitFor } from '@testing-library/react';
|
||||
import userEvent, { specialChars } from '@testing-library/user-event';
|
||||
import {
|
||||
FILTERS_CONFIG_MODAL_TEST_ID,
|
||||
FiltersConfigModal,
|
||||
FiltersConfigModalProps,
|
||||
} from './FiltersConfigModal';
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
class MainPreset extends Preset {
|
||||
constructor() {
|
||||
super({
|
||||
name: 'Legacy charts',
|
||||
plugins: [
|
||||
new TimeFilterPlugin().configure({ key: 'filter_time' }),
|
||||
new SelectFilterPlugin().configure({ key: 'filter_select' }),
|
||||
new RangeFilterPlugin().configure({ key: 'filter_range' }),
|
||||
new TimeFilterPlugin().configure({ key: 'filter_time' }),
|
||||
new TimeColumnFilterPlugin().configure({ key: 'filter_timecolumn' }),
|
||||
new TimeGrainFilterPlugin().configure({ key: 'filter_timegrain' }),
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const getTestId = testWithId<string>(FILTERS_CONFIG_MODAL_TEST_ID, true);
|
||||
const initialStoreState = {
|
||||
datasources: [{ id: 1, table_name: 'Datasource 1' }],
|
||||
};
|
||||
|
||||
describe('FilterConfigModal', () => {
|
||||
new MainPreset().register();
|
||||
const onSave = jest.fn();
|
||||
const newFilterProps = {
|
||||
isOpen: true,
|
||||
initialFilterId: undefined,
|
||||
createNewOnOpen: true,
|
||||
onSave,
|
||||
onCancel: jest.fn(),
|
||||
};
|
||||
fetchMock.get(
|
||||
'glob:*/api/v1/dataset/?q=*',
|
||||
{
|
||||
count: 1,
|
||||
ids: [11],
|
||||
label_columns: {
|
||||
id: 'Id',
|
||||
table_name: 'Table Name',
|
||||
fetchMock.get('glob:*/api/v1/dataset/1', {
|
||||
description_columns: {},
|
||||
id: 1,
|
||||
label_columns: {
|
||||
columns: 'Columns',
|
||||
table_name: 'Table Name',
|
||||
},
|
||||
result: {
|
||||
metrics: [],
|
||||
columns: [
|
||||
{
|
||||
column_name: 'Column A',
|
||||
id: 1,
|
||||
},
|
||||
list_columns: ['id', 'table_name'],
|
||||
order_columns: ['table_name'],
|
||||
result: [
|
||||
{
|
||||
id: 11,
|
||||
owners: [],
|
||||
table_name: 'birth_names',
|
||||
},
|
||||
],
|
||||
},
|
||||
{ overwriteRoutes: true },
|
||||
);
|
||||
fetchMock.get(
|
||||
'glob:*/api/v1/dataset/11',
|
||||
{
|
||||
description_columns: {},
|
||||
id: 3,
|
||||
label_columns: {
|
||||
columns: 'Columns',
|
||||
table_name: 'Table Name',
|
||||
},
|
||||
result: {
|
||||
columns: [
|
||||
{
|
||||
column_name: 'name',
|
||||
groupby: true,
|
||||
id: 334,
|
||||
},
|
||||
],
|
||||
table_name: 'birth_names',
|
||||
},
|
||||
show_columns: ['id', 'table_name'],
|
||||
},
|
||||
{ overwriteRoutes: true },
|
||||
);
|
||||
fetchMock.post(
|
||||
'glob:*/api/v1/chart/data',
|
||||
{
|
||||
result: [
|
||||
{
|
||||
status: 'success',
|
||||
data: [
|
||||
{ name: 'Aaron', count: 453 },
|
||||
{ name: 'Abigail', count: 228 },
|
||||
{ name: 'Adam', count: 454 },
|
||||
],
|
||||
applied_filters: [{ column: 'name' }],
|
||||
},
|
||||
],
|
||||
},
|
||||
{ overwriteRoutes: true },
|
||||
);
|
||||
|
||||
const renderWrapper = (
|
||||
props = newFilterProps,
|
||||
state: object = stateWithoutNativeFilters,
|
||||
) =>
|
||||
render(
|
||||
<Provider
|
||||
store={state ? getMockStore(stateWithoutNativeFilters) : mockStore}
|
||||
>
|
||||
<FiltersConfigModal {...props} />
|
||||
</Provider>,
|
||||
);
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
// TODO: fix and unskip
|
||||
it.skip('Create Select Filter (with datasource and columns) with specific filter scope', async () => {
|
||||
renderWrapper();
|
||||
|
||||
const FILTER_NAME = 'Select Filter 1';
|
||||
|
||||
// fill name
|
||||
userEvent.type(screen.getByTestId(getTestId('name-input')), FILTER_NAME);
|
||||
|
||||
// fill dataset
|
||||
await waitFor(() =>
|
||||
expect(screen.queryByText('Loading...')).not.toBeInTheDocument(),
|
||||
);
|
||||
userEvent.click(
|
||||
screen
|
||||
.getByTestId(getTestId('datasource-input'))
|
||||
.querySelector('.Select__indicators')!,
|
||||
);
|
||||
userEvent.click(screen.getByText('birth_names'));
|
||||
|
||||
// fill column
|
||||
userEvent.click(screen.getByText('Select...'));
|
||||
await waitFor(() =>
|
||||
expect(screen.queryByText('Loading...')).not.toBeInTheDocument(),
|
||||
);
|
||||
userEvent.click(screen.getByText('name'));
|
||||
|
||||
// fill controls
|
||||
expect(screen.getByText('Multiple select').parentElement!).toHaveAttribute(
|
||||
'class',
|
||||
'ant-checkbox-wrapper ant-checkbox-wrapper-checked',
|
||||
);
|
||||
userEvent.click(screen.getByText('Multiple select'));
|
||||
expect(
|
||||
screen.getByText('Multiple select').parentElement!,
|
||||
).not.toHaveAttribute(
|
||||
'class',
|
||||
'ant-checkbox-wrapper ant-checkbox-wrapper-checked',
|
||||
);
|
||||
|
||||
// choose default value
|
||||
userEvent.click(await screen.findByText('3 options'));
|
||||
userEvent.click(screen.getByTitle('Abigail'));
|
||||
|
||||
// fill scoping
|
||||
userEvent.click(screen.getByText('Apply to specific panels'));
|
||||
userEvent.click(screen.getByText('CHART_ID'));
|
||||
|
||||
// saving
|
||||
userEvent.click(screen.getByText('Save'));
|
||||
await waitFor(() =>
|
||||
expect(onSave.mock.calls[0][0][0]).toEqual(
|
||||
expect.objectContaining({
|
||||
cascadeParentIds: [],
|
||||
controlValues: {
|
||||
defaultToFirstItem: false,
|
||||
enableEmptyFilter: false,
|
||||
inverseSelection: false,
|
||||
multiSelect: false,
|
||||
sortAscending: true,
|
||||
},
|
||||
defaultValue: ['Abigail'],
|
||||
filterType: 'filter_select',
|
||||
isInstant: false,
|
||||
name: 'Select Filter 1',
|
||||
scope: { excluded: [], rootPath: [] },
|
||||
targets: [{ column: { name: 'name' }, datasetId: 11 }],
|
||||
}),
|
||||
),
|
||||
);
|
||||
});
|
||||
],
|
||||
table_name: 'birth_names',
|
||||
id: 1,
|
||||
},
|
||||
show_columns: ['id', 'table_name'],
|
||||
});
|
||||
|
||||
fetchMock.post('glob:*/api/v1/chart/data', {
|
||||
result: [
|
||||
{
|
||||
status: 'success',
|
||||
data: [
|
||||
{ name: 'Aaron', count: 453 },
|
||||
{ name: 'Abigail', count: 228 },
|
||||
{ name: 'Adam', count: 454 },
|
||||
],
|
||||
applied_filters: [{ column: 'name' }],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const FILTER_TYPE_REGEX = /^filter type$/i;
|
||||
const FILTER_NAME_REGEX = /^filter name$/i;
|
||||
const DATASET_REGEX = /^dataset$/i;
|
||||
const COLUMN_REGEX = /^column$/i;
|
||||
const VALUE_REGEX = /^value$/i;
|
||||
const NUMERICAL_RANGE_REGEX = /^numerical range$/i;
|
||||
const TIME_RANGE_REGEX = /^time range$/i;
|
||||
const TIME_COLUMN_REGEX = /^time column$/i;
|
||||
const TIME_GRAIN_REGEX = /^time grain$/i;
|
||||
const ADVANCED_REGEX = /^advanced$/i;
|
||||
const DEFAULT_VALUE_REGEX = /^filter has default value$/i;
|
||||
const MULTIPLE_REGEX = /^multiple select$/i;
|
||||
const REQUIRED_REGEX = /^required$/i;
|
||||
const APPLY_INSTANTLY_REGEX = /^apply changes instantly$/i;
|
||||
const HIERARCHICAL_REGEX = /^filter is hierarchical$/i;
|
||||
const FIRST_ITEM_REGEX = /^default to first item$/i;
|
||||
const INVERSE_SELECTION_REGEX = /^inverse selection$/i;
|
||||
const SEARCH_ALL_REGEX = /^search all filter options$/i;
|
||||
const PRE_FILTER_REGEX = /^pre-filter available values$/i;
|
||||
const SORT_REGEX = /^sort filter values$/i;
|
||||
const SAVE_REGEX = /^save$/i;
|
||||
const NAME_REQUIRED_REGEX = /^name is required$/i;
|
||||
const COLUMN_REQUIRED_REGEX = /^column is required$/i;
|
||||
const DEFAULT_VALUE_REQUIRED_REGEX = /^default value is required$/i;
|
||||
const PARENT_REQUIRED_REGEX = /^parent filter is required$/i;
|
||||
const PRE_FILTER_REQUIRED_REGEX = /^pre-filter is required$/i;
|
||||
const FILL_REQUIRED_FIELDS_REGEX = /fill all required fields to enable/;
|
||||
|
||||
const props: FiltersConfigModalProps = {
|
||||
isOpen: true,
|
||||
createNewOnOpen: true,
|
||||
onSave: jest.fn(),
|
||||
onCancel: jest.fn(),
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
new MainPreset().register();
|
||||
});
|
||||
|
||||
function defaultRender(
|
||||
overrides?: Partial<FiltersConfigModalProps>,
|
||||
initialState?: {},
|
||||
) {
|
||||
return render(<FiltersConfigModal {...props} {...overrides} />, {
|
||||
useRedux: true,
|
||||
initialState,
|
||||
});
|
||||
}
|
||||
|
||||
function getCheckbox(name: RegExp) {
|
||||
return screen.getByRole('checkbox', { name });
|
||||
}
|
||||
|
||||
function queryCheckbox(name: RegExp) {
|
||||
return screen.queryByRole('checkbox', { name });
|
||||
}
|
||||
|
||||
test('renders a value filter type', () => {
|
||||
defaultRender();
|
||||
|
||||
userEvent.click(screen.getByText(ADVANCED_REGEX));
|
||||
|
||||
expect(screen.getByText(FILTER_TYPE_REGEX)).toBeInTheDocument();
|
||||
expect(screen.getByText(FILTER_NAME_REGEX)).toBeInTheDocument();
|
||||
expect(screen.getByText(DATASET_REGEX)).toBeInTheDocument();
|
||||
expect(screen.getByText(COLUMN_REGEX)).toBeInTheDocument();
|
||||
|
||||
expect(getCheckbox(DEFAULT_VALUE_REGEX)).not.toBeChecked();
|
||||
expect(getCheckbox(REQUIRED_REGEX)).not.toBeChecked();
|
||||
expect(getCheckbox(APPLY_INSTANTLY_REGEX)).not.toBeChecked();
|
||||
expect(getCheckbox(HIERARCHICAL_REGEX)).not.toBeChecked();
|
||||
expect(getCheckbox(FIRST_ITEM_REGEX)).not.toBeChecked();
|
||||
expect(getCheckbox(INVERSE_SELECTION_REGEX)).not.toBeChecked();
|
||||
expect(getCheckbox(SEARCH_ALL_REGEX)).not.toBeChecked();
|
||||
expect(getCheckbox(PRE_FILTER_REGEX)).not.toBeChecked();
|
||||
expect(getCheckbox(SORT_REGEX)).not.toBeChecked();
|
||||
|
||||
expect(getCheckbox(MULTIPLE_REGEX)).toBeChecked();
|
||||
});
|
||||
|
||||
test('renders a numerical range filter type', () => {
|
||||
defaultRender();
|
||||
|
||||
userEvent.click(screen.getByText(VALUE_REGEX));
|
||||
userEvent.click(screen.getByText(NUMERICAL_RANGE_REGEX));
|
||||
userEvent.click(screen.getByText(ADVANCED_REGEX));
|
||||
|
||||
expect(screen.getByText(FILTER_TYPE_REGEX)).toBeInTheDocument();
|
||||
expect(screen.getByText(FILTER_NAME_REGEX)).toBeInTheDocument();
|
||||
expect(screen.getByText(DATASET_REGEX)).toBeInTheDocument();
|
||||
expect(screen.getByText(COLUMN_REGEX)).toBeInTheDocument();
|
||||
|
||||
expect(getCheckbox(DEFAULT_VALUE_REGEX)).not.toBeChecked();
|
||||
expect(getCheckbox(APPLY_INSTANTLY_REGEX)).not.toBeChecked();
|
||||
expect(getCheckbox(PRE_FILTER_REGEX)).not.toBeChecked();
|
||||
|
||||
expect(queryCheckbox(MULTIPLE_REGEX)).not.toBeInTheDocument();
|
||||
expect(queryCheckbox(REQUIRED_REGEX)).not.toBeInTheDocument();
|
||||
expect(queryCheckbox(HIERARCHICAL_REGEX)).not.toBeInTheDocument();
|
||||
expect(queryCheckbox(FIRST_ITEM_REGEX)).not.toBeInTheDocument();
|
||||
expect(queryCheckbox(INVERSE_SELECTION_REGEX)).not.toBeInTheDocument();
|
||||
expect(queryCheckbox(SEARCH_ALL_REGEX)).not.toBeInTheDocument();
|
||||
expect(queryCheckbox(SORT_REGEX)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('renders a time range filter type', () => {
|
||||
defaultRender();
|
||||
|
||||
userEvent.click(screen.getByText(VALUE_REGEX));
|
||||
userEvent.click(screen.getByText(TIME_RANGE_REGEX));
|
||||
|
||||
expect(screen.getByText(FILTER_TYPE_REGEX)).toBeInTheDocument();
|
||||
expect(screen.getByText(FILTER_NAME_REGEX)).toBeInTheDocument();
|
||||
expect(screen.queryByText(DATASET_REGEX)).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(COLUMN_REGEX)).not.toBeInTheDocument();
|
||||
|
||||
expect(getCheckbox(DEFAULT_VALUE_REGEX)).not.toBeChecked();
|
||||
expect(getCheckbox(APPLY_INSTANTLY_REGEX)).not.toBeChecked();
|
||||
|
||||
expect(screen.queryByText(ADVANCED_REGEX)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('renders a time column filter type', () => {
|
||||
defaultRender();
|
||||
|
||||
userEvent.click(screen.getByText(VALUE_REGEX));
|
||||
userEvent.click(screen.getByText(TIME_COLUMN_REGEX));
|
||||
|
||||
expect(screen.getByText(FILTER_TYPE_REGEX)).toBeInTheDocument();
|
||||
expect(screen.getByText(FILTER_NAME_REGEX)).toBeInTheDocument();
|
||||
expect(screen.getByText(DATASET_REGEX)).toBeInTheDocument();
|
||||
expect(screen.queryByText(COLUMN_REGEX)).not.toBeInTheDocument();
|
||||
|
||||
expect(getCheckbox(DEFAULT_VALUE_REGEX)).not.toBeChecked();
|
||||
expect(getCheckbox(APPLY_INSTANTLY_REGEX)).not.toBeChecked();
|
||||
|
||||
expect(screen.queryByText(ADVANCED_REGEX)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('renders a time grain filter type', () => {
|
||||
defaultRender();
|
||||
|
||||
userEvent.click(screen.getByText(VALUE_REGEX));
|
||||
userEvent.click(screen.getByText(TIME_GRAIN_REGEX));
|
||||
|
||||
expect(screen.getByText(FILTER_TYPE_REGEX)).toBeInTheDocument();
|
||||
expect(screen.getByText(FILTER_NAME_REGEX)).toBeInTheDocument();
|
||||
expect(screen.getByText(DATASET_REGEX)).toBeInTheDocument();
|
||||
expect(screen.queryByText(COLUMN_REGEX)).not.toBeInTheDocument();
|
||||
|
||||
expect(getCheckbox(DEFAULT_VALUE_REGEX)).not.toBeChecked();
|
||||
expect(getCheckbox(APPLY_INSTANTLY_REGEX)).not.toBeChecked();
|
||||
|
||||
expect(screen.queryByText(ADVANCED_REGEX)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('validates the name', async () => {
|
||||
defaultRender();
|
||||
userEvent.click(screen.getByRole('button', { name: SAVE_REGEX }));
|
||||
expect(await screen.findByText(NAME_REQUIRED_REGEX)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('validates the column', async () => {
|
||||
defaultRender();
|
||||
userEvent.click(screen.getByRole('button', { name: SAVE_REGEX }));
|
||||
expect(await screen.findByText(COLUMN_REQUIRED_REGEX)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/no-disabled-tests
|
||||
test.skip('validates the default value', async () => {
|
||||
defaultRender(undefined, initialStoreState);
|
||||
expect(await screen.findByText('birth_names')).toBeInTheDocument();
|
||||
userEvent.type(screen.getByRole('combobox'), `Column A${specialChars.enter}`);
|
||||
userEvent.click(getCheckbox(DEFAULT_VALUE_REGEX));
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.queryByText(FILL_REQUIRED_FIELDS_REGEX),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
expect(
|
||||
await screen.findByText(DEFAULT_VALUE_REQUIRED_REGEX),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('validates the hierarchical value', async () => {
|
||||
defaultRender();
|
||||
userEvent.click(screen.getByText(ADVANCED_REGEX));
|
||||
userEvent.click(getCheckbox(HIERARCHICAL_REGEX));
|
||||
expect(await screen.findByText(PARENT_REQUIRED_REGEX)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('validates the pre-filter value', async () => {
|
||||
defaultRender();
|
||||
userEvent.click(screen.getByText(ADVANCED_REGEX));
|
||||
userEvent.click(getCheckbox(PRE_FILTER_REGEX));
|
||||
expect(
|
||||
await screen.findByText(PRE_FILTER_REQUIRED_REGEX),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
/*
|
||||
TODO
|
||||
adds a new value filter type with all fields filled
|
||||
adds a new numerical range filter type with all fields filled
|
||||
adds a new time range filter type with all fields filled
|
||||
adds a new time column filter type with all fields filled
|
||||
adds a new time grain filter type with all fields filled
|
||||
collapsible controls opens by default when it is checked
|
||||
advanced section opens by default when it has an option checked
|
||||
deletes a filter
|
||||
disables the default value when default to first item is checked
|
||||
changes the default value options when the column changes
|
||||
switches to configuration tab when validation fails
|
||||
displays cancel message when there are pending operations
|
||||
do not displays cancel message when there are no pending operations
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import { Styles } from '../common';
|
|||
import { NO_TIME_RANGE } from '../../../explore/constants';
|
||||
|
||||
const TimeFilterStyles = styled(Styles)`
|
||||
overflow-x: scroll;
|
||||
overflow-x: auto;
|
||||
`;
|
||||
|
||||
const ControlContainer = styled.div`
|
||||
|
|
|
|||
Loading…
Reference in New Issue