diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBar.test.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBar.test.tsx index 449a58822..8578faee6 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBar.test.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBar.test.tsx @@ -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(FILTER_BAR_TEST_ID, true); diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/ColumnSelect.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/ColumnSelect.tsx index a45d3016d..719044303 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/ColumnSelect.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/ColumnSelect.tsx @@ -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; 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(); @@ -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 ( - ); } diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/DefaultValue.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/DefaultValue.tsx index 524ab83cd..0d16e6b45 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/DefaultValue.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/DefaultValue.tsx @@ -62,7 +62,7 @@ const DefaultValue: FC = ({ ) : ( 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={{t('Column')}} 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={ - {t('Pre-filter')} + {!hasTimeRange && } } /> @@ -778,6 +795,12 @@ const FiltersConfigForm = ( name={['filters', filterId, 'time_range']} label={{t('Time range')}} initialValue={filterToEdit?.time_range || 'No filter'} + required={!hasAdhoc} + rules={[ + { + validator: preFilterValidator, + }, + ]} > diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal.test.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal.test.tsx index 9713292d9..3ee4a9d88 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal.test.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal.test.tsx @@ -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(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( - - - , - ); - - 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, + initialState?: {}, +) { + return render(, { + 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 +*/ diff --git a/superset-frontend/src/filters/components/Time/TimeFilterPlugin.tsx b/superset-frontend/src/filters/components/Time/TimeFilterPlugin.tsx index f91bc83cf..73d04064f 100644 --- a/superset-frontend/src/filters/components/Time/TimeFilterPlugin.tsx +++ b/superset-frontend/src/filters/components/Time/TimeFilterPlugin.tsx @@ -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`