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
This commit is contained in:
parent
9d67b46576
commit
d386e66f59
|
|
@ -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 });
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ module.exports = {
|
|||
testRegex: '(\\/spec|\\/src)\\/.*(_spec|\\.test)\\.(j|t)sx?$',
|
||||
moduleNameMapper: {
|
||||
'\\.(css|less)$': '<rootDir>/spec/__mocks__/styleMock.js',
|
||||
'\\.(gif|ttf|eot)$': '<rootDir>/spec/__mocks__/fileMock.js',
|
||||
'\\.(gif|ttf|eot|png)$': '<rootDir>/spec/__mocks__/fileMock.js',
|
||||
'\\.svg$': '<rootDir>/spec/__mocks__/svgrMock.js',
|
||||
'^src/(.*)$': '<rootDir>/src/$1',
|
||||
'^spec/(.*)$': '<rootDir>/spec/$1',
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ export default {
|
|||
id: 1234,
|
||||
slug: 'dashboardSlug',
|
||||
metadata: {
|
||||
filter_configuration: [
|
||||
native_filter_configuration: [
|
||||
{
|
||||
id: 'DefaultsID',
|
||||
filterType: 'filter_select',
|
||||
|
|
|
|||
|
|
@ -410,12 +410,14 @@ export const iconsRegistry: Record<
|
|||
|
||||
export interface IconProps extends SVGProps<SVGSVGElement> {
|
||||
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}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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) => (
|
||||
<Provider store={mockStore}>
|
||||
<FilterBar {...props} />
|
||||
</Provider>
|
||||
);
|
||||
|
||||
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(
|
||||
<Provider store={state ? getMockStore(state) : mockStore}>
|
||||
<FilterBar {...props} />
|
||||
</Provider>,
|
||||
);
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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<FCBProps> = ({
|
||||
createNewOnOpen,
|
||||
children,
|
||||
|
|
@ -38,14 +45,20 @@ export const FilterConfigurationLink: React.FC<FCBProps> = ({
|
|||
}
|
||||
|
||||
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 */}
|
||||
<div onClick={() => setOpen(true)}>{children}</div>
|
||||
<HeaderButton
|
||||
{...getFilterBarTestId('create-filter')}
|
||||
buttonStyle="link"
|
||||
buttonSize="xsmall"
|
||||
onClick={() => setOpen(true)}
|
||||
>
|
||||
{children}
|
||||
</HeaderButton>
|
||||
<FiltersConfigModal
|
||||
isOpen={isOpen}
|
||||
onSave={submit}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import { ActionButtons } from './Footer';
|
|||
import { useDataMask, useFilters, useFilterSets } from '../state';
|
||||
import { APPLY_FILTERS_HINT, findExistingFilterSet } from './utils';
|
||||
import { useFilterSetNameDuplicated } from './state';
|
||||
import { getFilterBarTestId } from '../index';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
display: grid;
|
||||
|
|
@ -156,7 +157,7 @@ const EditSection: FC<EditSectionProps> = ({
|
|||
htmlType="submit"
|
||||
buttonSize="small"
|
||||
onClick={handleSave}
|
||||
data-test="filter-set-edit-save"
|
||||
{...getFilterBarTestId('filter-set-edit-save')}
|
||||
>
|
||||
{t('Save')}
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -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<FilterSetUnitProps> = ({
|
|||
placement="bottomRight"
|
||||
trigger={['click']}
|
||||
>
|
||||
<EllipsisOutlined
|
||||
<HeaderButton
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}}
|
||||
/>
|
||||
{...getFilterBarTestId('filter-set-menu-button')}
|
||||
buttonStyle="link"
|
||||
buttonSize="xsmall"
|
||||
>
|
||||
<EllipsisOutlined />
|
||||
</HeaderButton>
|
||||
</Dropdown>
|
||||
)}
|
||||
</IconsBlock>
|
||||
|
|
|
|||
|
|
@ -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<FiltersHeaderProps> = ({ dataMask, filterSet }) => {
|
|||
<CaretDownOutlined rotate={isActive ? 0 : 180} />
|
||||
)}
|
||||
>
|
||||
<Collapse.Panel header={getFiltersHeader()} key="filters">
|
||||
<Collapse.Panel
|
||||
{...getFilterBarTestId('collapse-filter-set-description')}
|
||||
header={getFiltersHeader()}
|
||||
key="filters"
|
||||
>
|
||||
{resultFilters.map(getFilterRow)}
|
||||
</Collapse.Panel>
|
||||
</StyledCollapse>
|
||||
|
|
|
|||
|
|
@ -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<FooterProps> = ({
|
|||
htmlType="submit"
|
||||
buttonSize="small"
|
||||
onClick={onCreate}
|
||||
data-test="filter-set-create-button"
|
||||
{...getFilterBarTestId('create-filter-set-button')}
|
||||
>
|
||||
{t('Create')}
|
||||
</Button>
|
||||
|
|
@ -104,8 +105,8 @@ const Footer: FC<FooterProps> = ({
|
|||
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')}
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -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<FilterSetsProps> = ({
|
|||
if (isFilterSetChanged) {
|
||||
return;
|
||||
}
|
||||
|
||||
const foundFilterSet = findExistingFilterSet({
|
||||
dataMaskSelected,
|
||||
filterSetFilterValues,
|
||||
|
|
@ -246,7 +248,8 @@ const FilterSets: React.FC<FilterSetsProps> = ({
|
|||
)}
|
||||
{filterSetFilterValues.map(filterSet => (
|
||||
<FilterSetUnitWrapper
|
||||
selected={filterSet.id === selectedFiltersSetId}
|
||||
{...getFilterBarTestId('filter-set-wrapper')}
|
||||
data-selected={filterSet.id === selectedFiltersSetId}
|
||||
onClick={(e: MouseEvent<HTMLElement>) =>
|
||||
takeFilterSet(filterSet.id, e.target as HTMLElement)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<HeaderProps> = ({
|
|||
<span>{t('Filters')}</span>
|
||||
{canEdit && (
|
||||
<FilterConfigurationLink createNewOnOpen={filterValues.length === 0}>
|
||||
<Icon name="edit" role="button" data-test="create-filter" />
|
||||
<Icon name="edit" data-test="create-filter" />
|
||||
</FilterConfigurationLink>
|
||||
)}
|
||||
<Icon
|
||||
name="expand"
|
||||
role="button"
|
||||
<HeaderButton
|
||||
{...getFilterBarTestId('collapse-button')}
|
||||
buttonStyle="link"
|
||||
buttonSize="xsmall"
|
||||
onClick={() => toggleFiltersBar(false)}
|
||||
/>
|
||||
>
|
||||
<Icon name="expand" />
|
||||
</HeaderButton>
|
||||
</TitleArea>
|
||||
<ActionButtons>
|
||||
<Button
|
||||
|
|
@ -113,7 +121,7 @@ const Header: FC<HeaderProps> = ({
|
|||
buttonStyle="tertiary"
|
||||
buttonSize="small"
|
||||
onClick={handleClearAll}
|
||||
data-test="filter-reset-button"
|
||||
{...getFilterBarTestId('clear-button')}
|
||||
>
|
||||
{t('Clear all')}
|
||||
</Button>
|
||||
|
|
@ -123,7 +131,7 @@ const Header: FC<HeaderProps> = ({
|
|||
htmlType="submit"
|
||||
buttonSize="small"
|
||||
onClick={onApply}
|
||||
data-test="filter-apply-button"
|
||||
{...getFilterBarTestId('apply-button')}
|
||||
>
|
||||
{t('Apply')}
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -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<FiltersBarProps> = ({
|
|||
const filterValues = Object.values<Filter>(filters);
|
||||
const dataMaskApplied = useDataMask();
|
||||
const [isFilterSetChanged, setIsFilterSetChanged] = useState(false);
|
||||
|
||||
const cascadeChildren = useMemo(
|
||||
() => mapParentFiltersToChildren(filterValues),
|
||||
[filterValues],
|
||||
|
|
@ -210,13 +213,17 @@ const FilterBar: React.FC<FiltersBarProps> = ({
|
|||
!isInitialized || areObjectsEqual(dataMaskSelected, lastAppliedFilterData);
|
||||
|
||||
return (
|
||||
<BarWrapper data-test="filter-bar" className={cx({ open: filtersOpen })}>
|
||||
<BarWrapper {...getFilterBarTestId()} className={cx({ open: filtersOpen })}>
|
||||
<CollapsedBar
|
||||
{...getFilterBarTestId('collapsable')}
|
||||
className={cx({ open: !filtersOpen })}
|
||||
onClick={() => toggleFiltersBar(true)}
|
||||
>
|
||||
<StyledCollapseIcon name="collapse" />
|
||||
<Icon name="filter" />
|
||||
<StyledCollapseIcon
|
||||
name="collapse"
|
||||
{...getFilterBarTestId('expand-button')}
|
||||
/>
|
||||
<Icon name="filter" {...getFilterBarTestId('filter-icon')} />
|
||||
</CollapsedBar>
|
||||
<Bar className={cx({ open: filtersOpen })}>
|
||||
<Header
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ import ControlItems from './ControlItems';
|
|||
import FilterScope from './FilterScope/FilterScope';
|
||||
import RemovedFilter from './RemovedFilter';
|
||||
import DefaultValue from './DefaultValue';
|
||||
import { getFiltersConfigModalTestId } from '../FiltersConfigModal';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
|
|
@ -153,15 +154,15 @@ export const FiltersConfigForm: React.FC<FiltersConfigFormProps> = ({
|
|||
label={<StyledLabel>{t('Filter name')}</StyledLabel>}
|
||||
initialValue={filterToEdit?.name}
|
||||
rules={[{ required: !removed, message: t('Name is required') }]}
|
||||
data-test="name-input"
|
||||
>
|
||||
<Input />
|
||||
<Input {...getFiltersConfigModalTestId('name-input')} />
|
||||
</StyledFormItem>
|
||||
<StyledFormItem
|
||||
name={['filters', filterId, 'filterType']}
|
||||
rules={[{ required: !removed, message: t('Name is required') }]}
|
||||
initialValue={filterToEdit?.filterType || 'filter_select'}
|
||||
label={<StyledLabel>{t('Filter Type')}</StyledLabel>}
|
||||
{...getFiltersConfigModalTestId('filter-type')}
|
||||
>
|
||||
<Select
|
||||
options={nativeFilterVizTypes.map(filterType => ({
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import { t, styled } from '@superset-ui/core';
|
|||
import { Form } from 'src/common/components';
|
||||
import { StyledModal } from 'src/components/Modal';
|
||||
import ErrorBoundary from 'src/components/ErrorBoundary';
|
||||
import { testWithId } from 'src/utils/common';
|
||||
import { useFilterConfigMap, useFilterConfiguration } from '../state';
|
||||
import { FilterRemoval, NativeFiltersForm } from './types';
|
||||
import { FilterConfiguration } from '../types';
|
||||
|
|
@ -49,6 +50,11 @@ export const StyledForm = styled(Form)`
|
|||
width: 100%;
|
||||
`;
|
||||
|
||||
export const FILTERS_CONFIG_MODAL_TEST_ID = 'filters-config-modal';
|
||||
export const getFiltersConfigModalTestId = testWithId(
|
||||
FILTERS_CONFIG_MODAL_TEST_ID,
|
||||
);
|
||||
|
||||
export interface FiltersConfigModalProps {
|
||||
isOpen: boolean;
|
||||
initialFilterId?: string;
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ export const createHandleSave = (
|
|||
}
|
||||
return {
|
||||
id,
|
||||
controlValues: formInputs.controlValues,
|
||||
controlValues: formInputs.controlValues ?? {},
|
||||
name: formInputs.name,
|
||||
filterType: formInputs.filterType,
|
||||
// for now there will only ever be one target
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ import { Tooltip } from 'src/common/components/Tooltip';
|
|||
import { DEFAULT_TIME_RANGE } from 'src/explore/constants';
|
||||
import { useDebouncedEffect } from 'src/explore/exploreUtils';
|
||||
import { SLOW_DEBOUNCE } from 'src/constants';
|
||||
|
||||
import { testWithId } from 'src/utils/common';
|
||||
import { SelectOptionType, FrameType } from './types';
|
||||
import {
|
||||
COMMON_RANGE_VALUES_SET,
|
||||
|
|
@ -176,6 +176,11 @@ interface DateFilterControlProps {
|
|||
endpoints?: TimeRangeEndpoints;
|
||||
}
|
||||
|
||||
export const DATE_FILTER_CONTROL_TEST_ID = 'date-filter-control';
|
||||
export const getDateFilterControlTestId = testWithId(
|
||||
DATE_FILTER_CONTROL_TEST_ID,
|
||||
);
|
||||
|
||||
export default function DateFilterLabel(props: DateFilterControlProps) {
|
||||
const { value = DEFAULT_TIME_RANGE, endpoints, onChange } = props;
|
||||
const [actualTimeRange, setActualTimeRange] = useState<string>(value);
|
||||
|
|
@ -324,6 +329,7 @@ export default function DateFilterLabel(props: DateFilterControlProps) {
|
|||
disabled={!validTimeRange}
|
||||
key="apply"
|
||||
onClick={onSave}
|
||||
{...getDateFilterControlTestId('apply-button')}
|
||||
>
|
||||
{t('APPLY')}
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -125,3 +125,15 @@ export const detectOS = () => {
|
|||
|
||||
return 'Unknown OS';
|
||||
};
|
||||
|
||||
// Using bem standard
|
||||
export const testWithId = (prefix, idOnly = false) => id => {
|
||||
if (!id) {
|
||||
return idOnly ? prefix : { 'data-test': prefix };
|
||||
}
|
||||
if (!prefix) {
|
||||
return idOnly ? id : { 'data-test': id };
|
||||
}
|
||||
const newId = `${prefix}__${id}`;
|
||||
return idOnly ? newId : { 'data-test': newId };
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue