[Feature] Dashboard scoped filter (#8590)

* [WIP][dashboard scoped filter] part 1: scope selector modal (#8557)

* filter scope selector modal

* add single-field-edit in multi-edit mode switch

* fix code review comments (round 1)

* refactory after design review

* fix a few props initial value

* [WIP][dashboard scoped filter] part 2: add algorithm to convert checked ids to scope object (#8564)

* convert ids to scope object

* use lodash helpers to make code readable

* [WIP][dashboard scoped filter] part 3: merge filter scope settings into dashboard redux state (#8522)

* merge filter scope settings into dashboard redux state

* fix/add unit tests

* minor bug fixes

* fix save filter Scopes behavior

* resolve review comments

* fix save filter scope settings

* minor comments

* [dashboard scoped filter] Improve scrollbar inside modal (#8553)

* improve scroll inside modal

* make left pane and right pane scroll separately

* fix review comments

* force show filter_box as unchecked (#8587)
This commit is contained in:
Grace Guo
2019-11-18 13:02:25 -08:00
committed by GitHub
parent c87a140734
commit ff6ab10893
69 changed files with 2967 additions and 446 deletions

View File

@@ -24,7 +24,7 @@ import Dashboard from '../../../../src/dashboard/components/Dashboard';
import DashboardBuilder from '../../../../src/dashboard/containers/DashboardBuilder';
// mock data
import chartQueries, { sliceId as chartId } from '../fixtures/mockChartQueries';
import chartQueries from '../fixtures/mockChartQueries';
import datasources from '../../../fixtures/mockDatasource';
import dashboardInfo from '../fixtures/mockDashboardInfo';
import { dashboardLayout } from '../fixtures/mockDashboardLayout';
@@ -46,7 +46,7 @@ describe('Dashboard', () => {
dashboardState,
dashboardInfo,
charts: chartQueries,
filters: {},
activeFilters: {},
slices: sliceEntities.slices,
datasources,
layout: dashboardLayout.present,
@@ -61,10 +61,12 @@ describe('Dashboard', () => {
return wrapper;
}
// activeFilters map use id_column) as key
const OVERRIDE_FILTERS = {
1: { region: [] },
2: { country_name: ['USA'] },
3: { region: [], country_name: ['USA'] },
'1_region': [],
'2_country_name': ['USA'],
'3_region': [],
'3_country_name': ['USA'],
};
it('should render a DashboardBuilder', () => {
@@ -72,85 +74,6 @@ describe('Dashboard', () => {
expect(wrapper.find(DashboardBuilder)).toHaveLength(1);
});
describe('refreshExcept', () => {
const overrideDashboardInfo = {
...dashboardInfo,
metadata: {
...dashboardInfo.metadata,
filterImmuneSliceFields: { [chartQueries[chartId].id]: ['region'] },
},
};
const overrideCharts = {
...chartQueries,
1001: {
...chartQueries[chartId],
id: 1001,
},
};
const overrideSlices = {
...props.slices,
1001: {
...props.slices[chartId],
slice_id: 1001,
},
};
it('should call triggerQuery for all non-exempt slices', () => {
const wrapper = setup({ charts: overrideCharts, slices: overrideSlices });
const spy = sinon.spy(props.actions, 'triggerQuery');
wrapper.instance().refreshExcept('1001');
spy.restore();
expect(spy.callCount).toBe(Object.keys(overrideCharts).length - 1);
});
it('should not call triggerQuery for filterImmuneSlices', () => {
const wrapper = setup({
charts: overrideCharts,
dashboardInfo: {
...dashboardInfo,
metadata: {
...dashboardInfo.metadata,
filterImmuneSlices: Object.keys(overrideCharts).map(id =>
Number(id),
),
},
},
});
const spy = sinon.spy(props.actions, 'triggerQuery');
wrapper.instance().refreshExcept();
spy.restore();
expect(spy.callCount).toBe(0);
});
it('should not call triggerQuery for filterImmuneSliceFields', () => {
const wrapper = setup({
filters: OVERRIDE_FILTERS,
dashboardInfo: overrideDashboardInfo,
});
const spy = sinon.spy(props.actions, 'triggerQuery');
wrapper.instance().refreshExcept('1');
expect(spy.callCount).toBe(0);
spy.restore();
});
it('should call triggerQuery if filter has more filter-able fields', () => {
const wrapper = setup({
filters: OVERRIDE_FILTERS,
dashboardInfo: overrideDashboardInfo,
});
const spy = sinon.spy(props.actions, 'triggerQuery');
// if filter have additional fields besides immune ones,
// should apply filter.
wrapper.instance().refreshExcept('3');
expect(spy.callCount).toBe(1);
spy.restore();
});
});
describe('componentWillReceiveProps', () => {
const layoutWithExtraChart = {
...props.layout,
@@ -186,17 +109,17 @@ describe('Dashboard', () => {
describe('componentDidUpdate', () => {
let wrapper;
let prevProps;
let refreshExceptSpy;
let refreshSpy;
beforeEach(() => {
wrapper = setup({ filters: OVERRIDE_FILTERS });
wrapper = setup({ activeFilters: OVERRIDE_FILTERS });
wrapper.instance().appliedFilters = OVERRIDE_FILTERS;
prevProps = wrapper.instance().props;
refreshExceptSpy = sinon.spy(wrapper.instance(), 'refreshExcept');
refreshSpy = sinon.spy(wrapper.instance(), 'refreshCharts');
});
afterEach(() => {
refreshExceptSpy.restore();
refreshSpy.restore();
});
it('should not call refresh when is editMode', () => {
@@ -207,15 +130,15 @@ describe('Dashboard', () => {
},
});
wrapper.instance().componentDidUpdate(prevProps);
expect(refreshExceptSpy.callCount).toBe(0);
expect(refreshSpy.callCount).toBe(0);
});
it('should not call refresh when there is no change', () => {
wrapper.setProps({
filters: OVERRIDE_FILTERS,
activeFilters: OVERRIDE_FILTERS,
});
wrapper.instance().componentDidUpdate(prevProps);
expect(refreshExceptSpy.callCount).toBe(0);
expect(refreshSpy.callCount).toBe(0);
expect(wrapper.instance().appliedFilters).toBe(OVERRIDE_FILTERS);
});
@@ -224,12 +147,12 @@ describe('Dashboard', () => {
gender: ['boy', 'girl'],
};
wrapper.setProps({
filters: {
activeFilters: {
...OVERRIDE_FILTERS,
...newFilter,
},
});
expect(refreshExceptSpy.callCount).toBe(1);
expect(refreshSpy.callCount).toBe(1);
expect(wrapper.instance().appliedFilters).toEqual({
...OVERRIDE_FILTERS,
...newFilter,
@@ -238,23 +161,23 @@ describe('Dashboard', () => {
it('should call refresh if a filter is removed', () => {
wrapper.setProps({
filters: {},
activeFilters: {},
});
expect(refreshExceptSpy.callCount).toBe(1);
expect(refreshSpy.callCount).toBe(1);
expect(wrapper.instance().appliedFilters).toEqual({});
});
it('should call refresh if a filter is changed', () => {
wrapper.setProps({
filters: {
activeFilters: {
...OVERRIDE_FILTERS,
region: ['Canada'],
'1_region': ['Canada'],
},
});
expect(refreshExceptSpy.callCount).toBe(1);
expect(refreshSpy.callCount).toBe(1);
expect(wrapper.instance().appliedFilters).toEqual({
...OVERRIDE_FILTERS,
region: ['Canada'],
'1_region': ['Canada'],
});
});
});

View File

@@ -20,28 +20,34 @@ import React from 'react';
import { shallow } from 'enzyme';
import { dashboardFilters } from '../fixtures/mockDashboardFilters';
import { sliceId as chartId } from '../fixtures/mockChartQueries';
import { filterId, column } from '../fixtures/mockSliceEntities';
import FilterIndicatorsContainer from '../../../../src/dashboard/components/FilterIndicatorsContainer';
import FilterIndicator from '../../../../src/dashboard/components/FilterIndicator';
import * as colorMap from '../../../../src/dashboard/util/dashboardFiltersColorMap';
import { buildActiveFilters } from '../../../../src/dashboard/util/activeDashboardFilters';
import { getDashboardFilterKey } from '../../../../src/dashboard/util/getDashboardFilterKey';
import { DASHBOARD_ROOT_ID } from '../../../../src/dashboard/util/constants';
import { dashboardWithFilter } from '../fixtures/mockDashboardLayout';
describe('FilterIndicatorsContainer', () => {
const chartId = 1;
const mockedProps = {
dashboardFilters,
chartId,
chartStatus: 'success',
filterImmuneSlices: [],
filterImmuneSliceFields: {},
setDirectPathToChild: () => {},
filterFieldOnFocus: {},
};
colorMap.getFilterColorKey = jest.fn(() => 'id_column');
colorMap.getFilterColorMap = jest.fn(() => ({
id_column: 'badge-1',
[getDashboardFilterKey({ chartId, column })]: 'badge-1',
}));
buildActiveFilters({
dashboardFilters,
components: dashboardWithFilter,
});
function setup(overrideProps) {
return shallow(
<FilterIndicatorsContainer {...mockedProps} {...overrideProps} />,
@@ -58,18 +64,25 @@ describe('FilterIndicatorsContainer', () => {
expect(wrapper.find(FilterIndicator)).toHaveLength(0);
});
it('should not show indicator when chart is immune', () => {
const wrapper = setup({ filterImmuneSlices: [chartId] });
expect(wrapper.find(FilterIndicator)).toHaveLength(0);
});
it('should not show indicator when chart field is immune', () => {
const wrapper = setup({ filterImmuneSliceFields: { [chartId]: [column] } });
expect(wrapper.find(FilterIndicator)).toHaveLength(0);
});
it('should show indicator', () => {
const wrapper = setup();
expect(wrapper.find(FilterIndicator)).toHaveLength(1);
});
it('should not show indicator when chart is immune', () => {
const overwriteDashboardFilters = {
...dashboardFilters,
[filterId]: {
...dashboardFilters[filterId],
scopes: {
region: {
scope: [DASHBOARD_ROOT_ID],
immune: [chartId],
},
},
},
};
const wrapper = setup({ dashboardFilters: overwriteDashboardFilters });
expect(wrapper.find(FilterIndicator)).toHaveLength(0);
});
});