mirror of
https://github.com/apache/superset.git
synced 2026-04-19 08:04:53 +00:00
feat(dashboards): Filter status indicators (#10936)
* Initial commit of new filters badge. * refactor applied/rejected filters code * finished filter indicators * filter badge tested * unnecessary imports * formatting and types * fixes * license * code quality tweaks * state management for showing focused filter scope * clean up filter key extraction code * remove unnecessary styles * temp css to demonstrate highlighting * fix focused filter logic * no more color badges * new toys for highlighting dash components (#11144) * tweak style for the filter chart when filter is focused * style: Filters p0 css2 (#11151) * nixing background tweak * src paths * another quick theme color * src paths, adjusting pill icon color, changing icons, showing applied/busted counts * linting stuff * fixing and tweaking tests * show filter indicator when filters are not active * chart title bar cleanup * open the right panel when popover opens * unused import * fix EditableTitle tests * margin on dashboard header * show the chart dropdown menu * fix blur filter breaking dropdowns * style tweak - no pointer events when irrelevant charts are blurred * fix box shadow on filter highlight * it's an array * attempt fixing e2e * style: filters p0 icon churn (#11215) * new filters icon * icon styling * bigger icons in list views * better sizing of table actions and favStars * more icon sizing... * fixing more button size jankiness * linting * Filters performance (#11255) * fixing time filter "ok" button * making unset filter menu collapsible * sort alphabetically * fix highlighting when removing items * try a flex layout (for browser render perf) * more specific transitioning * temp: comment out some code as a test * temp: comment out more code * temp: remove possibly expensive computations from ChartHolder * Revert "temp: comment out some code as a test" This reverts commit309b880e90. * Revert "temp: comment out more code" This reverts commit64c88b2cba. * Revert "temp: remove possibly expensive computations from ChartHolder" This reverts commit37ce0214f0. * experiment: upgrade react-select to v3 * Revert "experiment: upgrade react-select to v3" This reverts commitc3972ba486. * fix the damn problem * remove code used for testing purposes * awful hack to avoid adding a class to a container * approaching infinity... and not beyond! * fix ref forwarding * add theme to tests as necessary * fix(extra-filters): add logic for identifying applied extra filters (#11325) * fix: use dashboard id for stable cache key (#11293) * fix: button translations missing (#11187) * button translations missing * blank space before text * feat: update time_compare description and choices (#11294) * feat: update time_compare description and choices * Update sections.jsx * fix(extra-filters): add logic for identifying applied extra filters * lint Co-authored-by: Jesse Yang <jesse.yang@airbnb.com> Co-authored-by: rubenSastre <ruben.sastre@decathlon.com> Co-authored-by: Erik Ritter <erik.ritter@airbnb.com> * address design feedback * slight tweak to panel logic, keep panels open that user has opened * rearrange code to be more graceful * fix: bump superset-ui/core (#11385) * use is_dttm instead of is_temporal * types, names * only show unset filter panel if there are unset filters * fix highlighting the filter control * fix filterbox layout * translations * fix cypress * actually add the test attribute * Update superset-frontend/src/dashboard/components/DashboardBuilder.jsx Co-authored-by: Evan Rusackas <evan@preset.io> * Update superset-frontend/src/dashboard/components/DashboardBuilder.jsx Co-authored-by: Evan Rusackas <evan@preset.io> * formatting * add link comment to hack * Update superset-frontend/src/dashboard/components/gridComponents/ChartHolder.jsx Co-authored-by: Evan Rusackas <evan@preset.io> * stop importing lodash * Update superset-frontend/src/dashboard/components/gridComponents/ChartHolder.jsx Co-authored-by: Evan Rusackas <evan@preset.io> * Update superset-frontend/src/dashboard/components/FiltersBadge/Styles.tsx Co-authored-by: Evan Rusackas <evan@preset.io> * Update superset-frontend/src/dashboard/components/FiltersBadge/Styles.tsx Co-authored-by: Evan Rusackas <evan@preset.io> * Update superset-frontend/src/dashboard/components/FiltersBadge/Styles.tsx Co-authored-by: Evan Rusackas <evan@preset.io> * Update superset-frontend/src/dashboard/components/FiltersBadge/Styles.tsx Co-authored-by: Evan Rusackas <evan@preset.io> * skip broken test * Update superset-frontend/src/dashboard/components/FiltersBadge/Styles.tsx Co-authored-by: Evan Rusackas <evan@preset.io> * Update superset-frontend/src/dashboard/components/FiltersBadge/Styles.tsx Co-authored-by: Evan Rusackas <evan@preset.io> * adjust colors of titles * linting * no indicators when chart is loading * support all time fields * fix lock file Co-authored-by: Natalie Ruhe <natalie@preset.io> Co-authored-by: Evan Rusackas <evan@preset.io> Co-authored-by: Ville Brofeldt <33317356+villebro@users.noreply.github.com> Co-authored-by: Jesse Yang <jesse.yang@airbnb.com> Co-authored-by: rubenSastre <ruben.sastre@decathlon.com> Co-authored-by: Erik Ritter <erik.ritter@airbnb.com> Co-authored-by: Ville Brofeldt <ville.v.brofeldt@gmail.com>
This commit is contained in:
committed by
GitHub
parent
e9dba18466
commit
18658f45be
@@ -1,57 +0,0 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import FilterIndicatorGroup from 'src/dashboard/components/FilterIndicatorGroup';
|
||||
import FilterBadgeIcon from 'src/components/FilterBadgeIcon';
|
||||
|
||||
import { dashboardFilters } from '../fixtures/mockDashboardFilters';
|
||||
import { filterId, column } from '../fixtures/mockSliceEntities';
|
||||
|
||||
describe('FilterIndicatorGroup', () => {
|
||||
const mockedProps = {
|
||||
indicators: [
|
||||
{
|
||||
...dashboardFilters[filterId],
|
||||
colorCode: 'badge-1',
|
||||
name: column,
|
||||
values: ['a', 'b', 'c'],
|
||||
isFilterFieldActive: true,
|
||||
chartId: 1,
|
||||
componentId: 'foo',
|
||||
directPathToFilter: ['foo'],
|
||||
isDateFilter: false,
|
||||
isInstantFilter: false,
|
||||
label: 'foo',
|
||||
},
|
||||
],
|
||||
setDirectPathToChild: () => {},
|
||||
};
|
||||
|
||||
function setup(overrideProps) {
|
||||
return shallow(
|
||||
<FilterIndicatorGroup {...mockedProps} {...overrideProps} />,
|
||||
);
|
||||
}
|
||||
|
||||
it('should show indicator group with badge', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find(FilterBadgeIcon)).toExist();
|
||||
});
|
||||
});
|
||||
@@ -1,43 +0,0 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import FilterIndicatorTooltip from 'src/dashboard/components/FilterIndicatorTooltip';
|
||||
|
||||
describe('FilterIndicatorTooltip', () => {
|
||||
const label = 'region';
|
||||
const mockedProps = {
|
||||
colorCode: 'badge-1',
|
||||
label,
|
||||
values: [],
|
||||
clickIconHandler: jest.fn(),
|
||||
};
|
||||
|
||||
function setup(overrideProps) {
|
||||
return shallow(
|
||||
<FilterIndicatorTooltip {...mockedProps} {...overrideProps} />,
|
||||
);
|
||||
}
|
||||
|
||||
it('should show label', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find(`[htmlFor="filter-tooltip-${label}"]`)).toExist();
|
||||
});
|
||||
});
|
||||
@@ -1,63 +0,0 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import FilterIndicator from 'src/dashboard/components/FilterIndicator';
|
||||
import FilterBadgeIcon from 'src/components/FilterBadgeIcon';
|
||||
|
||||
import { dashboardFilters } from '../fixtures/mockDashboardFilters';
|
||||
import { filterId, column } from '../fixtures/mockSliceEntities';
|
||||
|
||||
describe('FilterIndicator', () => {
|
||||
const mockedProps = {
|
||||
indicator: {
|
||||
...dashboardFilters[filterId],
|
||||
colorCode: 'badge-1',
|
||||
name: column,
|
||||
label: column,
|
||||
values: ['a', 'b', 'c'],
|
||||
chartId: 1,
|
||||
componentId: 'foo',
|
||||
isDateFilter: false,
|
||||
isFilterFieldActive: true,
|
||||
isInstantFilter: false,
|
||||
},
|
||||
setDirectPathToChild: jest.fn(),
|
||||
};
|
||||
|
||||
function setup(overrideProps) {
|
||||
return shallow(<FilterIndicator {...mockedProps} {...overrideProps} />);
|
||||
}
|
||||
|
||||
it('should show indicator with badge', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find(FilterBadgeIcon)).toExist();
|
||||
});
|
||||
|
||||
it('should call setDirectPathToChild prop', () => {
|
||||
const wrapper = setup();
|
||||
const badge = wrapper.find('.filter-indicator');
|
||||
expect(badge).toHaveLength(1);
|
||||
|
||||
badge.simulate('click');
|
||||
expect(mockedProps.setDirectPathToChild).toHaveBeenCalledWith(
|
||||
dashboardFilters[filterId].directPathToFilter,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,107 +0,0 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
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 { dashboardFilters } from '../fixtures/mockDashboardFilters';
|
||||
import { sliceId as chartId } from '../fixtures/mockChartQueries';
|
||||
import { filterId, column } from '../fixtures/mockSliceEntities';
|
||||
import { dashboardWithFilter } from '../fixtures/mockDashboardLayout';
|
||||
|
||||
describe('FilterIndicatorsContainer', () => {
|
||||
const mockedProps = {
|
||||
dashboardFilters,
|
||||
chartId,
|
||||
chartStatus: 'success',
|
||||
setDirectPathToChild: () => {},
|
||||
filterFieldOnFocus: {},
|
||||
};
|
||||
|
||||
colorMap.getFilterColorMap = jest.fn(() => ({
|
||||
[getDashboardFilterKey({ chartId, column })]: 'badge-1',
|
||||
}));
|
||||
|
||||
buildActiveFilters({
|
||||
dashboardFilters,
|
||||
components: dashboardWithFilter,
|
||||
});
|
||||
|
||||
function setup(overrideProps) {
|
||||
return shallow(
|
||||
<FilterIndicatorsContainer {...mockedProps} {...overrideProps} />,
|
||||
);
|
||||
}
|
||||
|
||||
it('should not show indicator when chart is loading', () => {
|
||||
const wrapper = setup({ chartStatus: 'loading' });
|
||||
expect(wrapper.find(FilterIndicator)).not.toExist();
|
||||
});
|
||||
|
||||
it('should not show indicator for filter_box itself', () => {
|
||||
const wrapper = setup({ chartId: filterId });
|
||||
expect(wrapper.find(FilterIndicator)).not.toExist();
|
||||
});
|
||||
|
||||
it('should show indicator', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find(FilterIndicator)).toExist();
|
||||
});
|
||||
|
||||
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)).not.toExist();
|
||||
});
|
||||
|
||||
it('should show single number type value', () => {
|
||||
const overwriteDashboardFilters = {
|
||||
...dashboardFilters,
|
||||
[filterId]: {
|
||||
...dashboardFilters[filterId],
|
||||
columns: {
|
||||
testField: 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
const wrapper = setup({ dashboardFilters: overwriteDashboardFilters });
|
||||
expect(wrapper.find(FilterIndicator)).toExist();
|
||||
|
||||
const indicatorProps = wrapper.find(FilterIndicator).first().props()
|
||||
.indicator;
|
||||
expect(indicatorProps.label).toEqual('testField');
|
||||
expect(indicatorProps.values).toEqual([0]);
|
||||
});
|
||||
});
|
||||
@@ -1,68 +0,0 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { Overlay, Tooltip } from 'react-bootstrap';
|
||||
|
||||
import FilterTooltipWrapper from 'src/dashboard/components/FilterTooltipWrapper';
|
||||
import FilterIndicatorTooltip from 'src/dashboard/components/FilterIndicatorTooltip';
|
||||
|
||||
describe('FilterTooltipWrapper', () => {
|
||||
const mockedProps = {
|
||||
tooltip: (
|
||||
<FilterIndicatorTooltip
|
||||
label="region"
|
||||
values={['a', 'b', 'c']}
|
||||
clickIconHandler={jest.fn()}
|
||||
/>
|
||||
),
|
||||
};
|
||||
|
||||
function setup() {
|
||||
return shallow(
|
||||
<FilterTooltipWrapper {...mockedProps}>
|
||||
<div className="badge-1" />
|
||||
</FilterTooltipWrapper>,
|
||||
);
|
||||
}
|
||||
|
||||
it('should contain Overlay and Tooltip', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find(Overlay)).toExist();
|
||||
expect(wrapper.find(Tooltip)).toExist();
|
||||
});
|
||||
|
||||
it('should show tooltip on hover', async () => {
|
||||
const wrapper = setup();
|
||||
wrapper.instance().isHover = true;
|
||||
|
||||
wrapper.find('.indicator-container').simulate('mouseover');
|
||||
await new Promise(r => setTimeout(r, 101));
|
||||
expect(wrapper.state('show')).toBe(true);
|
||||
});
|
||||
|
||||
it('should hide tooltip on hover', async () => {
|
||||
const wrapper = setup();
|
||||
wrapper.instance().isHover = false;
|
||||
|
||||
wrapper.find('.indicator-container').simulate('mouseout');
|
||||
await new Promise(r => setTimeout(r, 101));
|
||||
expect(wrapper.state('show')).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { supersetTheme } from '@superset-ui/core';
|
||||
import * as SupersetUI from '@superset-ui/core';
|
||||
import { CHART_UPDATE_SUCCEEDED } from 'src/chart/chartAction';
|
||||
import { buildActiveFilters } from 'src/dashboard/util/activeDashboardFilters';
|
||||
import FiltersBadge from 'src/dashboard/containers/FiltersBadge';
|
||||
import { getMockStoreWithFilters } from '../fixtures/mockStore';
|
||||
import { sliceId } from '../fixtures/mockChartQueries';
|
||||
import { dashboardFilters } from '../fixtures/mockDashboardFilters';
|
||||
import { dashboardWithFilter } from '../fixtures/mockDashboardLayout';
|
||||
|
||||
describe('FiltersBadge', () => {
|
||||
// there's this bizarre "active filters" thing
|
||||
// that doesn't actually use any kind of state management.
|
||||
// Have to set variables in there.
|
||||
buildActiveFilters({
|
||||
dashboardFilters,
|
||||
components: dashboardWithFilter,
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// shallow rendering in enzyme doesn't propagate contexts correctly,
|
||||
// so we have to mock the hook.
|
||||
// See https://medium.com/7shifts-engineering-blog/testing-usecontext-react-hook-with-enzyme-shallow-da062140fc83
|
||||
jest.spyOn(SupersetUI, 'useTheme').mockImplementation(() => supersetTheme);
|
||||
});
|
||||
|
||||
it("doesn't show number when there are no active filters", () => {
|
||||
const store = getMockStoreWithFilters();
|
||||
// start with basic dashboard state, dispatch an event to simulate query completion
|
||||
store.dispatch({
|
||||
type: CHART_UPDATE_SUCCEEDED,
|
||||
key: sliceId,
|
||||
queryResponse: {
|
||||
status: 'success',
|
||||
applied_filters: [],
|
||||
rejected_filters: [],
|
||||
},
|
||||
dashboardFilters,
|
||||
});
|
||||
const wrapper = shallow(<FiltersBadge {...{ store }} chartId={sliceId} />);
|
||||
expect(
|
||||
wrapper.dive().find('[data-test="applied-filter-count"]'),
|
||||
).not.toExist();
|
||||
});
|
||||
|
||||
it('shows the indicator when filters have been applied', () => {
|
||||
const store = getMockStoreWithFilters();
|
||||
// start with basic dashboard state, dispatch an event to simulate query completion
|
||||
store.dispatch({
|
||||
type: CHART_UPDATE_SUCCEEDED,
|
||||
key: sliceId,
|
||||
queryResponse: {
|
||||
status: 'success',
|
||||
applied_filters: [{ column: 'region' }],
|
||||
rejected_filters: [],
|
||||
},
|
||||
dashboardFilters,
|
||||
});
|
||||
const wrapper = shallow(<FiltersBadge {...{ store }} chartId={sliceId} />);
|
||||
expect(wrapper.dive().find('DetailsPanelPopover')).toExist();
|
||||
expect(
|
||||
wrapper.dive().find('[data-test="applied-filter-count"]'),
|
||||
).toHaveText('1');
|
||||
expect(wrapper.dive().find('WarningFilled')).not.toExist();
|
||||
});
|
||||
|
||||
it("shows a warning when there's a rejected filter", () => {
|
||||
const store = getMockStoreWithFilters();
|
||||
// start with basic dashboard state, dispatch an event to simulate query completion
|
||||
store.dispatch({
|
||||
type: CHART_UPDATE_SUCCEEDED,
|
||||
key: sliceId,
|
||||
queryResponse: {
|
||||
status: 'success',
|
||||
applied_filters: [],
|
||||
rejected_filters: [{ column: 'region', reason: 'not_in_datasource' }],
|
||||
},
|
||||
dashboardFilters,
|
||||
});
|
||||
const wrapper = shallow(<FiltersBadge {...{ store }} chartId={sliceId} />);
|
||||
expect(wrapper.dive().find('DetailsPanelPopover')).toExist();
|
||||
expect(
|
||||
wrapper.dive().find('[data-test="applied-filter-count"]'),
|
||||
).toHaveText('0');
|
||||
expect(
|
||||
wrapper.dive().find('[data-test="incompatible-filter-count"]'),
|
||||
).toHaveText('1');
|
||||
// to look at the shape of the wrapper use:
|
||||
// console.log(wrapper.dive().debug())
|
||||
expect(wrapper.dive().find('Icon[name="alert-solid"]')).toExist();
|
||||
});
|
||||
});
|
||||
@@ -20,6 +20,7 @@ import { Provider } from 'react-redux';
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import sinon from 'sinon';
|
||||
import { supersetTheme, ThemeProvider } from '@superset-ui/core';
|
||||
|
||||
import Chart from 'src/dashboard/containers/Chart';
|
||||
import ChartHolder from 'src/dashboard/components/gridComponents/ChartHolder';
|
||||
@@ -61,6 +62,10 @@ describe('ChartHolder', () => {
|
||||
<ChartHolder {...props} {...overrideProps} />
|
||||
</WithDragDropContext>
|
||||
</Provider>,
|
||||
{
|
||||
wrappingComponent: ThemeProvider,
|
||||
wrappingComponentProps: { theme: supersetTheme },
|
||||
},
|
||||
);
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import { Provider } from 'react-redux';
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import sinon from 'sinon';
|
||||
import { supersetTheme, ThemeProvider } from '@superset-ui/core';
|
||||
|
||||
import BackgroundStyleDropdown from 'src/dashboard/components/menu/BackgroundStyleDropdown';
|
||||
import Column from 'src/dashboard/components/gridComponents/Column';
|
||||
@@ -69,6 +70,10 @@ describe('Column', () => {
|
||||
<Column {...props} {...overrideProps} />
|
||||
</WithDragDropContext>
|
||||
</Provider>,
|
||||
{
|
||||
wrappingComponent: ThemeProvider,
|
||||
wrappingComponentProps: { theme: supersetTheme },
|
||||
},
|
||||
);
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
@@ -86,7 +86,9 @@ describe('Header', () => {
|
||||
it('should render an EditableTitle with meta.text', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper.find(EditableTitle)).toExist();
|
||||
expect(wrapper.find('input').prop('value')).toBe(props.component.meta.text);
|
||||
expect(wrapper.find('.editable-title')).toHaveText(
|
||||
props.component.meta.text,
|
||||
);
|
||||
});
|
||||
|
||||
it('should call updateComponents when EditableTitle changes', () => {
|
||||
|
||||
@@ -30,6 +30,7 @@ import IconButton from 'src/dashboard/components/IconButton';
|
||||
import Row from 'src/dashboard/components/gridComponents/Row';
|
||||
import WithPopoverMenu from 'src/dashboard/components/menu/WithPopoverMenu';
|
||||
import { DASHBOARD_GRID_ID } from 'src/dashboard/util/constants';
|
||||
import { supersetTheme, ThemeProvider } from '@superset-ui/core';
|
||||
|
||||
import { mockStore } from '../../fixtures/mockStore';
|
||||
import { dashboardLayout as mockLayout } from '../../fixtures/mockDashboardLayout';
|
||||
@@ -65,6 +66,10 @@ describe('Row', () => {
|
||||
<Row {...props} {...overrideProps} />
|
||||
</WithDragDropContext>
|
||||
</Provider>,
|
||||
{
|
||||
wrappingComponent: ThemeProvider,
|
||||
wrappingComponentProps: { theme: supersetTheme },
|
||||
},
|
||||
);
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
@@ -81,7 +81,9 @@ describe('Tabs', () => {
|
||||
const wrapper = setup();
|
||||
const title = wrapper.find(EditableTitle);
|
||||
expect(title).toHaveLength(1);
|
||||
expect(title.find('input').prop('value')).toBe(props.component.meta.text);
|
||||
expect(title.find('.editable-title')).toHaveText(
|
||||
props.component.meta.text,
|
||||
);
|
||||
});
|
||||
|
||||
it('should call updateComponents when EditableTitle changes', () => {
|
||||
|
||||
@@ -21,6 +21,7 @@ import React from 'react';
|
||||
import { mount, shallow } from 'enzyme';
|
||||
import sinon from 'sinon';
|
||||
import { Tabs as BootstrapTabs, Tab as BootstrapTab } from 'react-bootstrap';
|
||||
import { supersetTheme, ThemeProvider } from '@superset-ui/core';
|
||||
|
||||
import DashboardComponent from 'src/dashboard/containers/DashboardComponent';
|
||||
import DeleteComponentButton from 'src/dashboard/components/DeleteComponentButton';
|
||||
@@ -64,6 +65,10 @@ describe('Tabs', () => {
|
||||
<Tabs {...props} {...overrideProps} />
|
||||
</WithDragDropContext>
|
||||
</Provider>,
|
||||
{
|
||||
wrappingComponent: ThemeProvider,
|
||||
wrappingComponentProps: { theme: supersetTheme },
|
||||
},
|
||||
);
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
@@ -27,5 +27,5 @@ export default {
|
||||
isStarred: true,
|
||||
isPublished: true,
|
||||
css: '',
|
||||
focusedFilterField: [],
|
||||
focusedFilterField: null,
|
||||
};
|
||||
|
||||
@@ -23,19 +23,54 @@ import rootReducer from 'src/dashboard/reducers/index';
|
||||
|
||||
import mockState from './mockState';
|
||||
import { dashboardLayoutWithTabs } from './mockDashboardLayout';
|
||||
import { sliceId } from './mockChartQueries';
|
||||
import { dashboardFilters } from './mockDashboardFilters';
|
||||
|
||||
export const mockStore = createStore(
|
||||
rootReducer,
|
||||
mockState,
|
||||
compose(applyMiddleware(thunk)),
|
||||
);
|
||||
export const getMockStore = () =>
|
||||
createStore(rootReducer, mockState, compose(applyMiddleware(thunk)));
|
||||
|
||||
export const mockStoreWithTabs = createStore(
|
||||
rootReducer,
|
||||
{
|
||||
export const mockStore = getMockStore();
|
||||
|
||||
export const getMockStoreWithTabs = () =>
|
||||
createStore(
|
||||
rootReducer,
|
||||
{
|
||||
...mockState,
|
||||
dashboardLayout: dashboardLayoutWithTabs,
|
||||
dashboardFilters: {},
|
||||
},
|
||||
compose(applyMiddleware(thunk)),
|
||||
);
|
||||
|
||||
export const mockStoreWithTabs = getMockStoreWithTabs();
|
||||
|
||||
export const sliceIdWithAppliedFilter = sliceId + 1;
|
||||
export const sliceIdWithRejectedFilter = sliceId + 2;
|
||||
|
||||
// has one chart with a filter that has been applied,
|
||||
// one chart with a filter that has been rejected,
|
||||
// and one chart with no filters set.
|
||||
export const getMockStoreWithFilters = () =>
|
||||
createStore(rootReducer, {
|
||||
...mockState,
|
||||
dashboardLayout: dashboardLayoutWithTabs,
|
||||
dashboardFilters: {},
|
||||
},
|
||||
compose(applyMiddleware(thunk)),
|
||||
);
|
||||
dashboardFilters,
|
||||
charts: {
|
||||
...mockState.charts,
|
||||
[sliceIdWithAppliedFilter]: {
|
||||
...mockState.charts[sliceId],
|
||||
queryResponse: {
|
||||
status: 'success',
|
||||
applied_filters: [{ column: 'region' }],
|
||||
rejected_filters: [],
|
||||
},
|
||||
},
|
||||
[sliceIdWithRejectedFilter]: {
|
||||
...mockState.charts[sliceId],
|
||||
queryResponse: {
|
||||
status: 'success',
|
||||
applied_filters: [],
|
||||
rejected_filters: [{ column: 'region', reason: 'not_in_datasource' }],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
SET_UNSAVED_CHANGES,
|
||||
TOGGLE_EXPAND_SLICE,
|
||||
TOGGLE_FAVE_STAR,
|
||||
UNSET_FOCUSED_FILTER_FIELD,
|
||||
} from 'src/dashboard/actions/dashboardState';
|
||||
|
||||
import dashboardStateReducer from 'src/dashboard/reducers/dashboardState';
|
||||
@@ -145,21 +146,34 @@ describe('dashboardState reducer', () => {
|
||||
).toBeGreaterThanOrEqual(lastModifiedTime);
|
||||
});
|
||||
|
||||
it('should clear focused filter field', () => {
|
||||
it('should clear the focused filter field', () => {
|
||||
const initState = {
|
||||
focusedFilterField: {
|
||||
chartId: 1,
|
||||
column: 'column_1',
|
||||
},
|
||||
};
|
||||
|
||||
const cleared = dashboardStateReducer(initState, {
|
||||
type: UNSET_FOCUSED_FILTER_FIELD,
|
||||
chartId: 1,
|
||||
column: 'column_1',
|
||||
});
|
||||
|
||||
expect(cleared.focusedFilterField).toBeNull();
|
||||
});
|
||||
|
||||
it('should only clear focused filter when the fields match', () => {
|
||||
// dashboard only has 1 focused filter field at a time,
|
||||
// but when user switch different filter boxes,
|
||||
// browser didn't always fire onBlur and onFocus events in order.
|
||||
// so in redux state focusedFilterField prop is a queue,
|
||||
// we always shift first element in the queue
|
||||
|
||||
// init state: has 1 focus field
|
||||
const initState = {
|
||||
focusedFilterField: [
|
||||
{
|
||||
chartId: 1,
|
||||
column: 'column_1',
|
||||
},
|
||||
],
|
||||
focusedFilterField: {
|
||||
chartId: 1,
|
||||
column: 'column_1',
|
||||
},
|
||||
};
|
||||
// when user switching filter,
|
||||
// browser focus on new filter first,
|
||||
@@ -170,10 +184,12 @@ describe('dashboardState reducer', () => {
|
||||
column: 'column_2',
|
||||
});
|
||||
const step2 = dashboardStateReducer(step1, {
|
||||
type: SET_FOCUSED_FILTER_FIELD,
|
||||
type: UNSET_FOCUSED_FILTER_FIELD,
|
||||
chartId: 1,
|
||||
column: 'column_1',
|
||||
});
|
||||
|
||||
expect(step2.focusedFilterField.slice(-1).pop()).toEqual({
|
||||
expect(step2.focusedFilterField).toEqual({
|
||||
chartId: 2,
|
||||
column: 'column_2',
|
||||
});
|
||||
|
||||
@@ -46,16 +46,15 @@ describe('EditableTitle', () => {
|
||||
expect(titleElement.props().value).toBe('my title');
|
||||
expect(titleElement.props().type).toBe('button');
|
||||
});
|
||||
it('should not render an input if it is not editable', () => {
|
||||
expect(notEditableWrapper.find('input')).not.toExist();
|
||||
});
|
||||
|
||||
describe('should handle click', () => {
|
||||
it('should change title', () => {
|
||||
editableWrapper.find('input').simulate('click');
|
||||
expect(editableWrapper.find('input').props().type).toBe('text');
|
||||
});
|
||||
it('should not change title', () => {
|
||||
notEditableWrapper.find('input').simulate('click');
|
||||
expect(notEditableWrapper.find('input').props().type).toBe('button');
|
||||
});
|
||||
});
|
||||
|
||||
describe('should handle change', () => {
|
||||
@@ -66,10 +65,6 @@ describe('EditableTitle', () => {
|
||||
editableWrapper.find('input').simulate('change', mockEvent);
|
||||
expect(editableWrapper.find('input').props().value).toBe('new title');
|
||||
});
|
||||
it('should not change title', () => {
|
||||
notEditableWrapper.find('input').simulate('change', mockEvent);
|
||||
expect(editableWrapper.find('input').props().value).toBe('my title');
|
||||
});
|
||||
});
|
||||
|
||||
describe('should handle blur', () => {
|
||||
|
||||
Reference in New Issue
Block a user