From d386e66f590c9df9b7063a15248700603b0f5e4d Mon Sep 17 00:00:00 2001 From: simcha90 <56388545+simcha90@users.noreply.github.com> Date: Tue, 20 Apr 2021 12:24:12 +0300 Subject: [PATCH] test(native-filters): add integration tests for filter bar (#14098) * test: add tests for filter bar * test: merge filter bar tests with master * test: add test for filter set * test: filter set tests * test: merge with master * test: fix tests for filter bar * fix: fix CR notes * fix: fix CR notes --- .../dashboard/nativeFilters.test.ts | 26 +- superset-frontend/jest.config.js | 2 +- .../spec/fixtures/mockDashboardInfo.js | 2 +- .../src/components/Icon/index.tsx | 4 +- .../FilterBar/FilterBar.test.tsx | 436 +++++++++++++++--- .../FilterConfigurationLink/index.tsx | 19 +- .../FilterBar/FilterSets/EditSection.tsx | 3 +- .../FilterBar/FilterSets/FilterSetUnit.tsx | 22 +- .../FilterBar/FilterSets/FiltersHeader.tsx | 7 +- .../FilterBar/FilterSets/Footer.tsx | 5 +- .../FilterBar/FilterSets/index.tsx | 9 +- .../nativeFilters/FilterBar/Header/index.tsx | 22 +- .../nativeFilters/FilterBar/index.tsx | 21 +- .../FilterScope/FilterScope.test.tsx} | 0 .../FiltersConfigForm/FiltersConfigForm.tsx | 5 +- .../FiltersConfigModal/FiltersConfigModal.tsx | 6 + .../nativeFilters/FiltersConfigModal/utils.ts | 2 +- .../DateFilterControl/DateFilterLabel.tsx | 8 +- superset-frontend/src/utils/common.js | 12 + 19 files changed, 498 insertions(+), 113 deletions(-) rename superset-frontend/{spec/javascripts/dashboard/components/nativeFilters/FilterScope_spec.tsx => src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FilterScope/FilterScope.test.tsx} (100%) diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilters.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilters.test.ts index fbb1aea9d..84e6f4ec5 100644 --- a/superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilters.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilters.test.ts @@ -52,12 +52,12 @@ describe('Nativefilters', () => { it('should show filter bar and allow user to create filters ', () => { cy.get('[data-test="filter-bar"]').should('be.visible'); - cy.get('[data-test="collapse"]').click(); - cy.get('[data-test="create-filter"]').click(); + cy.get('[data-test="filter-bar__expand-button"]').click(); + cy.get('[data-test="filter-bar__create-filter"]').click(); cy.get('.ant-modal').should('be.visible'); cy.get('.ant-modal') - .find('[data-test="name-input"]') + .find('[data-test="filters-config-modal__name-input"]') .click() .type('Country name'); @@ -90,14 +90,14 @@ describe('Nativefilters', () => { it('should filter dashboard with selected filter value', () => { cy.get('[data-test="form-item-value"]').should('be.visible').click(); cy.get('.ant-select-selection-search').type('Hong Kong{enter}'); - cy.get('[data-test="filter-apply-button"]').click(); + cy.get('[data-test="filter-bar__apply-button"]').click(); cy.get('.treemap').within(() => { cy.contains('HKG').should('be.visible'); cy.contains('USA').should('not.exist'); }); }); xit('default value is respected after revisit', () => { - cy.get('[data-test="create-filter"]').click(); + cy.get('[data-test="filter-bar__create-filter"]').click(); cy.get('.ant-modal').should('be.visible'); // TODO: replace with proper wait for filter to finish loading cy.wait(1000); @@ -118,7 +118,7 @@ describe('Nativefilters', () => { cy.contains('Sweden'); }); it('should allow for deleted filter restore', () => { - cy.get('[data-test="create-filter"]').click(); + cy.get('[data-test="filter-bar__create-filter"]').click(); cy.get('.ant-modal').should('be.visible'); cy.get('.ant-tabs-nav-list').within(() => { cy.get('.ant-tabs-tab-remove').click(); @@ -132,7 +132,7 @@ describe('Nativefilters', () => { }); it('should stop filtering when filter is removed', () => { - cy.get('[data-test="create-filter"]').click(); + cy.get('[data-test="filter-bar__create-filter"]').click(); cy.get('.ant-modal').should('be.visible'); cy.get('.ant-tabs-nav-list').within(() => { cy.get('.ant-tabs-tab-remove').click(); @@ -149,12 +149,12 @@ describe('Nativefilters', () => { describe('Parent Filters', () => { it('should allow for creating parent filters ', () => { cy.get('[data-test="filter-bar"]').should('be.visible'); - cy.get('[data-test="collapse"]').click(); - cy.get('[data-test="create-filter"]').click(); + cy.get('[data-test="filter-bar__expand-button"]').click(); + cy.get('[data-test="filter-bar__create-filter"]').click(); cy.get('.ant-modal').should('be.visible'); cy.get('.ant-modal') - .find('[data-test="name-input"]') + .find('[data-test="filters-config-modal__name-input"]') .click() .type('Country name'); @@ -180,12 +180,12 @@ describe('Nativefilters', () => { .should('be.visible') .click(); - cy.get('[data-test="create-filter"]').click(); + cy.get('[data-test="filter-bar__create-filter"]').click(); cy.get('.ant-modal').first().should('be.visible'); cy.get('[data-test=add-filter-button]').first().click(); cy.get('.ant-modal') - .find('[data-test="name-input"]') + .find('[data-test="filters-config-modal__name-input"]') .last() .click() .type('Region Name'); @@ -250,7 +250,7 @@ describe('Nativefilters', () => { }); it('should stop filtering when parent filter is removed', () => { - cy.get('[data-test="create-filter"]').click(); + cy.get('[data-test="filter-bar__create-filter"]').click(); cy.get('.ant-modal').should('be.visible'); cy.get('.ant-tabs-nav-list').within(() => { cy.get('.ant-tabs-tab-remove').click({ multiple: true }); diff --git a/superset-frontend/jest.config.js b/superset-frontend/jest.config.js index ce7226a58..aef9dd575 100644 --- a/superset-frontend/jest.config.js +++ b/superset-frontend/jest.config.js @@ -20,7 +20,7 @@ module.exports = { testRegex: '(\\/spec|\\/src)\\/.*(_spec|\\.test)\\.(j|t)sx?$', moduleNameMapper: { '\\.(css|less)$': '/spec/__mocks__/styleMock.js', - '\\.(gif|ttf|eot)$': '/spec/__mocks__/fileMock.js', + '\\.(gif|ttf|eot|png)$': '/spec/__mocks__/fileMock.js', '\\.svg$': '/spec/__mocks__/svgrMock.js', '^src/(.*)$': '/src/$1', '^spec/(.*)$': '/spec/$1', diff --git a/superset-frontend/spec/fixtures/mockDashboardInfo.js b/superset-frontend/spec/fixtures/mockDashboardInfo.js index 68f397440..4aaba5a52 100644 --- a/superset-frontend/spec/fixtures/mockDashboardInfo.js +++ b/superset-frontend/spec/fixtures/mockDashboardInfo.js @@ -20,7 +20,7 @@ export default { id: 1234, slug: 'dashboardSlug', metadata: { - filter_configuration: [ + native_filter_configuration: [ { id: 'DefaultsID', filterType: 'filter_select', diff --git a/superset-frontend/src/components/Icon/index.tsx b/superset-frontend/src/components/Icon/index.tsx index 707d9e389..693354d9a 100644 --- a/superset-frontend/src/components/Icon/index.tsx +++ b/superset-frontend/src/components/Icon/index.tsx @@ -410,12 +410,14 @@ export const iconsRegistry: Record< export interface IconProps extends SVGProps { name: IconName; + 'data-test'?: string; } const Icon = ({ name, color = '#666666', viewBox = '0 0 24 24', + 'data-test': dataTest, ...rest }: IconProps) => { const Component = iconsRegistry[name]; @@ -426,7 +428,7 @@ const Icon = ({ aria-label={name} color={color} viewBox={viewBox} - data-test={name} + data-test={dataTest ?? name} {...rest} /> ); 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 a942e2516..032133c0f 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBar.test.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBar.test.tsx @@ -16,71 +16,379 @@ * specific language governing permissions and limitations * under the License. */ + import React from 'react'; -import { render, screen } from 'spec/helpers/testing-library'; -import userEvent from '@testing-library/user-event'; -import { mockStore } from 'spec/fixtures/mockStore'; +import { render, screen, cleanup } from 'spec/helpers/testing-library'; import { Provider } from 'react-redux'; -import FilterBar, { FiltersBarProps } from '.'; +import userEvent from '@testing-library/user-event'; +import { getMockStore, mockStore } from 'spec/fixtures/mockStore'; +import * as mockCore from '@superset-ui/core'; +import { testWithId } from 'src/utils/common'; +import { FeatureFlag } from 'src/featureFlags'; +import { Preset } from '@superset-ui/core'; +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 FilterBar, { FILTER_BAR_TEST_ID } from '.'; +import { FILTERS_CONFIG_MODAL_TEST_ID } from '../FiltersConfigModal/FiltersConfigModal'; -const createProps = () => ({ - filtersOpen: false, - toggleFiltersBar: jest.fn(), -}); - -const setup = (props: FiltersBarProps) => ( - - - -); - -test('should render', () => { - const mockedProps = createProps(); - const { container } = render(setup(mockedProps)); - expect(container).toBeInTheDocument(); -}); - -test('should render the "Filters" heading', () => { - const mockedProps = createProps(); - render(setup(mockedProps)); - expect(screen.getByText('Filters')).toBeInTheDocument(); -}); - -test('should render the "Clear all" option', () => { - const mockedProps = createProps(); - render(setup(mockedProps)); - expect(screen.getByText('Clear all')).toBeInTheDocument(); -}); - -test('should render the "Apply" option', () => { - const mockedProps = createProps(); - render(setup(mockedProps)); - expect(screen.getByText('Apply')).toBeInTheDocument(); -}); - -test('should render the collapse icon', () => { - const mockedProps = createProps(); - render(setup(mockedProps)); - expect(screen.getByRole('img', { name: 'collapse' })).toBeInTheDocument(); -}); - -test('should render the filter icon', () => { - const mockedProps = createProps(); - render(setup(mockedProps)); - expect(screen.getByRole('img', { name: 'filter' })).toBeInTheDocument(); -}); - -test('should render the filter control name', () => { - const mockedProps = createProps(); - render(setup(mockedProps)); - expect(screen.getByText('test')).toBeInTheDocument(); -}); - -test('should toggle', () => { - const mockedProps = createProps(); - render(setup(mockedProps)); - const collapse = screen.getByRole('img', { name: 'collapse' }); - expect(mockedProps.toggleFiltersBar).not.toHaveBeenCalled(); - userEvent.click(collapse); - expect(mockedProps.toggleFiltersBar).toHaveBeenCalled(); +// @ts-ignore +mockCore.makeApi = jest.fn(); + +class MainPreset extends Preset { + constructor() { + super({ + name: 'Legacy charts', + plugins: [ + new TimeFilterPlugin().configure({ key: 'filter_time' }), + new SelectFilterPlugin().configure({ key: 'filter_select' }), + ], + }); + } +} + +const getTestId = testWithId(FILTER_BAR_TEST_ID, true); +const getModalTestId = testWithId(FILTERS_CONFIG_MODAL_TEST_ID, true); +const getDateControlTestId = testWithId(DATE_FILTER_CONTROL_TEST_ID, true); + +const FILTER_NAME = 'Time filter 1'; +const FILTER_SET_NAME = 'New filter set'; + +const addFilterFlow = () => { + // open filter config modal + userEvent.click(screen.getByTestId(getTestId('collapsable'))); + userEvent.click(screen.getByTestId(getTestId('create-filter'))); + // select filter + userEvent.click(screen.getByText('Select filter')); + userEvent.click(screen.getByText('Time filter')); + userEvent.type(screen.getByTestId(getModalTestId('name-input')), FILTER_NAME); + userEvent.click(screen.getByText('Save')); +}; + +const addFilterSetFlow = async () => { + // add filter set + userEvent.click(screen.getByText('Filter Sets (0)')); + expect(screen.getByTestId(getTestId('new-filter-set-button'))).toBeDisabled(); + + // check description + expect(screen.getByText('Filters (1)')).toBeInTheDocument(); + expect(screen.getByText(FILTER_NAME)).toBeInTheDocument(); + expect(screen.getAllByText('Last week').length).toBe(2); + + // apply filters + userEvent.click(screen.getByTestId(getTestId('apply-button'))); + expect(screen.getByTestId(getTestId('new-filter-set-button'))).toBeEnabled(); + + // create filter set + userEvent.click(screen.getByText('Create new filter set')); + userEvent.click(screen.getByText('Create')); + + // check filter set created + expect(await screen.findByRole('img', { name: 'check' })).toBeInTheDocument(); + expect(screen.getByTestId(getTestId('filter-set-wrapper'))).toHaveAttribute( + 'data-selected', + 'true', + ); +}; + +const changeFilterValue = async () => { + userEvent.click(screen.getAllByText('Last week')[0]); + userEvent.click(screen.getByDisplayValue('Last day')); + expect(await screen.findByText(/2021-04-13/)).toBeInTheDocument(); + userEvent.click(screen.getByTestId(getDateControlTestId('apply-button'))); +}; + +describe('FilterBar', () => { + const toggleFiltersBar = jest.fn(); + const closedBarProps = { + filtersOpen: false, + toggleFiltersBar, + }; + const openedBarProps = { + filtersOpen: true, + toggleFiltersBar, + }; + const noFiltersState = { + dashboardInfo: { + dash_edit_perm: true, + metadata: { + native_filter_configuration: [], + }, + }, + dataMask: {}, + nativeFilters: { filters: {}, filterSets: {} }, + }; + + const mockApi = jest.fn(async data => { + const json = JSON.parse(data.json_metadata); + const filterId = json.native_filter_configuration[0].id; + return { + id: 1234, + result: { + json_metadata: `{ + "label_colors":{"Girls":"#FF69B4","Boys":"#ADD8E6","girl":"#FF69B4","boy":"#ADD8E6"}, + "native_filter_configuration":[{ + "id":"${filterId}", + "name":"${FILTER_NAME}", + "filterType":"filter_time", + "targets":[{"datasetId":11,"column":{"name":"color"}}], + "defaultValue":null, + "controlValues":{}, + "cascadeParentIds":[], + "scope":{"rootPath":["ROOT_ID"],"excluded":[]}, + "isInstant":false + }], + "filter_sets_configuration":[{ + "name":"${FILTER_SET_NAME}", + "id":"${json.filter_sets_configuration?.[0].id}", + "nativeFilters":{ + "${filterId}":{ + "id":"${filterId}", + "name":"${FILTER_NAME}", + "filterType":"filter_time", + "targets":[{}], + "defaultValue":"Last week", + "controlValues":{}, + "cascadeParentIds":[], + "scope":{"rootPath":["ROOT_ID"],"excluded":[]}, + "isInstant":false + } + }, + "dataMask":{ + "${filterId}":{ + "extraFormData":{"override_form_data":{"time_range":"Last week"}}, + "filterState":{"value":"Last week"}, + "ownState":{}, + "id":"${filterId}" + } + } + }] + }`, + }, + }; + }); + + new MainPreset().register(); + beforeEach(() => { + toggleFiltersBar.mockClear(); + fetchMock.get( + 'http://localhost/api/v1/time_range/?q=%27Last%20day%27', + { + result: { + since: '2021-04-13T00:00:00', + until: '2021-04-14T00:00:00', + timeRange: 'Last day', + }, + }, + { overwriteRoutes: true }, + ); + fetchMock.get( + 'http://localhost/api/v1/time_range/?q=%27Last%20week%27', + { + result: { + since: '2021-04-07T00:00:00', + until: '2021-04-14T00:00:00', + timeRange: 'Last week', + }, + }, + { overwriteRoutes: true }, + ); + + // @ts-ignore + mockCore.makeApi = jest.fn(() => mockApi); + }); + + afterEach(() => { + cleanup(); + jest.clearAllMocks(); + }); + + const renderWrapper = (props = closedBarProps, state?: object) => + render( + + + , + ); + + it('should render', () => { + const { container } = renderWrapper(); + expect(container).toBeInTheDocument(); + }); + + it('should render the "Filters" heading', () => { + renderWrapper(); + expect(screen.getByText('Filters')).toBeInTheDocument(); + }); + + it('should render the "Clear all" option', () => { + renderWrapper(); + expect(screen.getByText('Clear all')).toBeInTheDocument(); + }); + + it('should render the "Apply" option', () => { + renderWrapper(); + expect(screen.getByText('Apply')).toBeInTheDocument(); + }); + + it('should render the collapse icon', () => { + renderWrapper(); + expect(screen.getByRole('img', { name: 'collapse' })).toBeInTheDocument(); + }); + + it('should render the filter icon', () => { + renderWrapper(); + expect(screen.getByRole('img', { name: 'filter' })).toBeInTheDocument(); + }); + + it('should render the filter control name', () => { + renderWrapper(); + expect(screen.getByText('test')).toBeInTheDocument(); + }); + + it('should toggle', () => { + renderWrapper(); + const collapse = screen.getByRole('img', { name: 'collapse' }); + expect(toggleFiltersBar).not.toHaveBeenCalled(); + userEvent.click(collapse); + expect(toggleFiltersBar).toHaveBeenCalled(); + }); + + it('open filter bar', () => { + renderWrapper(); + expect(screen.getByTestId(getTestId('filter-icon'))).toBeInTheDocument(); + expect(screen.getByTestId(getTestId('expand-button'))).toBeInTheDocument(); + + userEvent.click(screen.getByTestId(getTestId('collapsable'))); + expect(toggleFiltersBar).toHaveBeenCalledWith(true); + }); + + it('no edit filter button by disabled permissions', () => { + renderWrapper(openedBarProps, { + ...noFiltersState, + dashboardInfo: { metadata: {} }, + }); + + expect( + screen.queryByTestId(getTestId('create-filter')), + ).not.toBeInTheDocument(); + }); + + it('close filter bar', () => { + renderWrapper(openedBarProps); + const collapseButton = screen.getByTestId(getTestId('collapse-button')); + + expect(collapseButton).toBeInTheDocument(); + userEvent.click(collapseButton); + + expect(toggleFiltersBar).toHaveBeenCalledWith(false); + }); + + it('no filters', () => { + renderWrapper(openedBarProps, noFiltersState); + + expect(screen.getByTestId(getTestId('clear-button'))).toBeDisabled(); + expect(screen.getByTestId(getTestId('apply-button'))).toBeDisabled(); + }); + + it('create filter and apply it flow', async () => { + // @ts-ignore + global.featureFlags = { + [FeatureFlag.DASHBOARD_NATIVE_FILTERS_SET]: true, + [FeatureFlag.DASHBOARD_NATIVE_FILTERS]: true, + }; + renderWrapper(openedBarProps, noFiltersState); + expect(screen.getByTestId(getTestId('apply-button'))).toBeDisabled(); + + addFilterFlow(); + + await screen.findByText('All Filters (1)'); + + // apply filter + expect(screen.getByTestId(getTestId('apply-button'))).toBeEnabled(); + userEvent.click(screen.getByTestId(getTestId('apply-button'))); + expect(screen.getByTestId(getTestId('apply-button'))).toBeDisabled(); + }); + + it('add and apply filter set', async () => { + // @ts-ignore + global.featureFlags = { + [FeatureFlag.DASHBOARD_NATIVE_FILTERS_SET]: true, + }; + renderWrapper(openedBarProps, noFiltersState); + + addFilterFlow(); + + await screen.findByText('All Filters (1)'); + expect(screen.getByTestId(getTestId('apply-button'))).toBeEnabled(); + + await addFilterSetFlow(); + + // change filter + userEvent.click(screen.getByText('All Filters (1)')); + expect(screen.getByTestId(getTestId('apply-button'))).toBeDisabled(); + + await changeFilterValue(); + await waitFor(() => expect(screen.getAllByText('Last day').length).toBe(2)); + + // apply new filter value + expect(screen.getByTestId(getTestId('apply-button'))).toBeEnabled(); + userEvent.click(screen.getByTestId(getTestId('apply-button'))); + expect(screen.getByTestId(getTestId('apply-button'))).toBeDisabled(); + + // applying filter set + userEvent.click(screen.getByText('Filter Sets (1)')); + expect( + await screen.findByText('Create new filter set'), + ).toBeInTheDocument(); + expect( + screen.getByTestId(getTestId('filter-set-wrapper')), + ).not.toHaveAttribute('data-selected', 'true'); + userEvent.click(screen.getByTestId(getTestId('filter-set-wrapper'))); + expect(await screen.findByText('Last week')).toBeInTheDocument(); + expect(screen.getByTestId(getTestId('apply-button'))).toBeEnabled(); + userEvent.click(screen.getByTestId(getTestId('apply-button'))); + expect(screen.getByTestId(getTestId('apply-button'))).toBeDisabled(); + }); + + it('add and edit filter set', async () => { + // @ts-ignore + global.featureFlags = { + [FeatureFlag.DASHBOARD_NATIVE_FILTERS_SET]: true, + [FeatureFlag.DASHBOARD_NATIVE_FILTERS]: true, + }; + renderWrapper(openedBarProps, noFiltersState); + + addFilterFlow(); + + await screen.findByText('All Filters (1)'); + expect(screen.getByTestId(getTestId('apply-button'))).toBeEnabled(); + + await addFilterSetFlow(); + + userEvent.click(screen.getByTestId(getTestId('filter-set-menu-button'))); + userEvent.click(screen.getByText('Edit')); + + await changeFilterValue(); + await waitFor(() => expect(screen.getAllByText('Last day').length).toBe(1)); + + // apply new changes and save them + expect( + screen.getByTestId(getTestId('filter-set-edit-save')), + ).toBeDisabled(); + expect(screen.getByTestId(getTestId('apply-button'))).toBeEnabled(); + userEvent.click(screen.getByTestId(getTestId('apply-button'))); + expect(screen.getByTestId(getTestId('apply-button'))).toBeDisabled(); + + expect(screen.getByTestId(getTestId('filter-set-edit-save'))).toBeEnabled(); + userEvent.click(screen.getByTestId(getTestId('filter-set-edit-save'))); + expect(screen.queryByText('Save')).not.toBeInTheDocument(); + + expect( + Object.values( + JSON.parse(mockApi.mock.calls[2][0].json_metadata) + .filter_sets_configuration[0].dataMask as object, + )[0]?.filterState?.value, + ).toBe('Last day'); + }); }); diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterConfigurationLink/index.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterConfigurationLink/index.tsx index db125d796..71717aa14 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterConfigurationLink/index.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterConfigurationLink/index.tsx @@ -19,13 +19,20 @@ import React, { useState } from 'react'; import { useDispatch } from 'react-redux'; import { setFilterConfiguration } from 'src/dashboard/actions/nativeFilters'; +import Button from 'src/components/Button'; +import { styled } from '@superset-ui/core'; import { FilterConfiguration } from 'src/dashboard/components/nativeFilters/types'; import { FiltersConfigModal } from 'src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal'; +import { getFilterBarTestId } from '..'; export interface FCBProps { createNewOnOpen?: boolean; } +const HeaderButton = styled(Button)` + padding: 0; +`; + export const FilterConfigurationLink: React.FC = ({ createNewOnOpen, children, @@ -38,14 +45,20 @@ export const FilterConfigurationLink: React.FC = ({ } async function submit(filterConfig: FilterConfiguration) { - await dispatch(setFilterConfiguration(filterConfig)); + dispatch(await setFilterConfiguration(filterConfig)); close(); } return ( <> - {/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */} -
setOpen(true)}>{children}
+ setOpen(true)} + > + {children} + = ({ htmlType="submit" buttonSize="small" onClick={handleSave} - data-test="filter-set-edit-save" + {...getFilterBarTestId('filter-set-edit-save')} > {t('Save')} diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/FilterSetUnit.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/FilterSetUnit.tsx index 2f628933e..5132e420c 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/FilterSetUnit.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/FilterSetUnit.tsx @@ -23,7 +23,13 @@ import { DataMaskState } from 'src/dataMask/types'; import { CheckOutlined, EllipsisOutlined } from '@ant-design/icons'; import { HandlerFunction, styled, supersetTheme, t } from '@superset-ui/core'; import { Tooltip } from 'src/common/components/Tooltip'; +import Button from 'src/components/Button'; import FiltersHeader from './FiltersHeader'; +import { getFilterBarTestId } from '..'; + +const HeaderButton = styled(Button)` + padding: 0; +`; const TitleText = styled.div` display: flex; @@ -34,9 +40,10 @@ const TitleText = styled.div` const IconsBlock = styled.div` display: flex; justify-content: flex-end; - align-items: flex-start; - & > * { - ${({ theme }) => `padding-left: ${theme.gridUnit * 2}px`}; + align-items: center; + & > *, + & > button.superset-button { + ${({ theme }) => `margin-left: ${theme.gridUnit * 2}px`}; } `; @@ -102,12 +109,17 @@ const FilterSetUnit: FC = ({ placement="bottomRight" trigger={['click']} > - { e.stopPropagation(); e.preventDefault(); }} - /> + {...getFilterBarTestId('filter-set-menu-button')} + buttonStyle="link" + buttonSize="xsmall" + > + + )} diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/FiltersHeader.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/FiltersHeader.tsx index 3b4b91a86..4a3a626f2 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/FiltersHeader.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/FiltersHeader.tsx @@ -25,6 +25,7 @@ import { areObjectsEqual } from 'src/reduxUtils'; import { FilterSet } from 'src/dashboard/reducers/types'; import { getFilterValueForDisplay } from './utils'; import { useFilters } from '../state'; +import { getFilterBarTestId } from '../index'; const FilterHeader = styled.div` display: flex; @@ -118,7 +119,11 @@ const FiltersHeader: FC = ({ dataMask, filterSet }) => { )} > - + {resultFilters.map(getFilterRow)} diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/Footer.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/Footer.tsx index 5681c260a..e967f958c 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/Footer.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/Footer.tsx @@ -22,6 +22,7 @@ import Button from 'src/components/Button'; import { Tooltip } from 'src/common/components/Tooltip'; import { APPLY_FILTERS_HINT } from './utils'; import { useFilterSetNameDuplicated } from './state'; +import { getFilterBarTestId } from '..'; export type FooterProps = { filterSetName: string; @@ -90,7 +91,7 @@ const Footer: FC = ({ htmlType="submit" buttonSize="small" onClick={onCreate} - data-test="filter-set-create-button" + {...getFilterBarTestId('create-filter-set-button')} > {t('Create')} @@ -104,8 +105,8 @@ const Footer: FC = ({ disabled={disabled} buttonStyle="tertiary" buttonSize="small" - data-test="filter-set-create-new-button" onClick={onEdit} + {...getFilterBarTestId('new-filter-set-button')} > {t('Create new filter set')} diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/index.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/index.tsx index 6513bfaf4..8947cebb7 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/index.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/index.tsx @@ -29,6 +29,7 @@ import { Filter } from '../../types'; import { useFilters, useDataMask, useFilterSets } from '../state'; import Footer from './Footer'; import FilterSetUnit from './FilterSetUnit'; +import { getFilterBarTestId } from '..'; const FilterSetsWrapper = styled.div` display: grid; @@ -45,7 +46,7 @@ const FilterSetsWrapper = styled.div` const FilterSetUnitWrapper = styled.div<{ onClick?: HandlerFunction; - selected?: boolean; + 'data-selected'?: boolean; }>` display: grid; align-items: center; @@ -57,7 +58,7 @@ const FilterSetUnitWrapper = styled.div<{ border-bottom: 1px solid ${({ theme }) => theme.colors.grayscale.light2}; padding: ${({ theme }) => `${theme.gridUnit * 3}px ${theme.gridUnit * 2}px`}; cursor: ${({ onClick }) => (!onClick ? 'auto' : 'pointer')}; - ${({ theme, selected }) => + ${({ theme, 'data-selected': selected }) => `background: ${selected ? theme.colors.primary.light5 : 'transparent'}`}; `; @@ -97,6 +98,7 @@ const FilterSets: React.FC = ({ if (isFilterSetChanged) { return; } + const foundFilterSet = findExistingFilterSet({ dataMaskSelected, filterSetFilterValues, @@ -246,7 +248,8 @@ const FilterSets: React.FC = ({ )} {filterSetFilterValues.map(filterSet => ( ) => takeFilterSet(filterSet.id, e.target as HTMLElement) } diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Header/index.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Header/index.tsx index 28423ea35..f9844a9ff 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Header/index.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Header/index.tsx @@ -27,6 +27,7 @@ import { DataMaskState, DataMaskStateWithId } from 'src/dataMask/types'; import FilterConfigurationLink from 'src/dashboard/components/nativeFilters/FilterBar/FilterConfigurationLink'; import { useFilters } from 'src/dashboard/components/nativeFilters/FilterBar/state'; import { Filter } from 'src/dashboard/components/nativeFilters/types'; +import { getFilterBarTestId } from '..'; const TitleArea = styled.h4` display: flex; @@ -55,6 +56,10 @@ const ActionButtons = styled.div` } `; +const HeaderButton = styled(Button)` + padding: 0; +`; + type HeaderProps = { toggleFiltersBar: (arg0: boolean) => void; onApply: () => void; @@ -98,14 +103,17 @@ const Header: FC = ({ {t('Filters')} {canEdit && ( - + )} - toggleFiltersBar(false)} - /> + > + + @@ -123,7 +131,7 @@ const Header: FC = ({ htmlType="submit" buttonSize="small" onClick={onApply} - data-test="filter-apply-button" + {...getFilterBarTestId('apply-button')} > {t('Apply')} diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx index 87b296b59..eea6aabb7 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/index.tsx @@ -29,6 +29,7 @@ import { updateDataMask } from 'src/dataMask/actions'; import { DataMaskState } from 'src/dataMask/types'; import { useImmer } from 'use-immer'; import { areObjectsEqual } from 'src/reduxUtils'; +import { testWithId } from 'src/utils/common'; import { Filter } from 'src/dashboard/components/nativeFilters/types'; import { mapParentFiltersToChildren, TabIds } from './utils'; import FilterSets from './FilterSets'; @@ -43,7 +44,10 @@ import EditSection from './FilterSets/EditSection'; import Header from './Header'; import FilterControls from './FilterControls/FilterControls'; -const barWidth = `250px`; +const BAR_WIDTH = `250px`; + +export const FILTER_BAR_TEST_ID = 'filter-bar'; +export const getFilterBarTestId = testWithId(FILTER_BAR_TEST_ID); const BarWrapper = styled.div` width: ${({ theme }) => theme.gridUnit * 8}px; @@ -51,7 +55,7 @@ const BarWrapper = styled.div` margin: 0; } &.open { - width: ${barWidth}; // arbitrary... + width: ${BAR_WIDTH}; // arbitrary... } `; @@ -66,7 +70,7 @@ const Bar = styled.div` left: 0; flex-direction: column; flex-grow: 1; - width: ${barWidth}; // arbitrary... + width: ${BAR_WIDTH}; // arbitrary... background: ${({ theme }) => theme.colors.grayscale.light5}; border-right: 1px solid ${({ theme }) => theme.colors.grayscale.light2}; min-height: 100%; @@ -163,7 +167,6 @@ const FilterBar: React.FC = ({ const filterValues = Object.values(filters); const dataMaskApplied = useDataMask(); const [isFilterSetChanged, setIsFilterSetChanged] = useState(false); - const cascadeChildren = useMemo( () => mapParentFiltersToChildren(filterValues), [filterValues], @@ -210,13 +213,17 @@ const FilterBar: React.FC = ({ !isInitialized || areObjectsEqual(dataMaskSelected, lastAppliedFilterData); return ( - + toggleFiltersBar(true)} > - - + +
= ({ label={{t('Filter name')}} initialValue={filterToEdit?.name} rules={[{ required: !removed, message: t('Name is required') }]} - data-test="name-input" > - + {t('Filter Type')}} + {...getFiltersConfigModalTestId('filter-type')} >