diff --git a/superset-frontend/spec/javascripts/explore/components/CheckboxControl_spec.jsx b/superset-frontend/spec/javascripts/explore/components/CheckboxControl_spec.jsx index 9a0740cb01..6eec246029 100644 --- a/superset-frontend/spec/javascripts/explore/components/CheckboxControl_spec.jsx +++ b/superset-frontend/spec/javascripts/explore/components/CheckboxControl_spec.jsx @@ -19,8 +19,8 @@ /* eslint-disable no-unused-expressions */ import React from 'react'; import sinon from 'sinon'; -import { shallow, mount } from 'enzyme'; - +import { mount } from 'enzyme'; +import { ThemeProvider, supersetTheme } from '@superset-ui/core'; import CheckboxControl from 'src/explore/components/controls/CheckboxControl'; import ControlHeader from 'src/explore/components/ControlHeader'; import Checkbox from 'src/components/Checkbox'; @@ -36,20 +36,21 @@ describe('CheckboxControl', () => { let wrapper; beforeEach(() => { - wrapper = shallow(); + wrapper = mount( + + + , + ); }); it('renders a Checkbox', () => { - const controlHeader = wrapper.find(ControlHeader); + const controlHeader = wrapper.childAt(0).find(ControlHeader); expect(controlHeader).toHaveLength(1); - - const headerWrapper = controlHeader.shallow(); - expect(headerWrapper.find(Checkbox)).toHaveLength(1); + expect(controlHeader.find(Checkbox)).toHaveLength(1); }); it('Checks the box when the label is clicked', () => { - const fullComponent = mount(); - + const fullComponent = wrapper.childAt(0); const spy = sinon.spy(fullComponent.instance(), 'onChange'); fullComponent.instance().forceUpdate(); diff --git a/superset-frontend/src/components/Checkbox/CheckboxIcons.tsx b/superset-frontend/src/components/Checkbox/CheckboxIcons.tsx index 21ae153e4c..447524a0f9 100644 --- a/superset-frontend/src/components/Checkbox/CheckboxIcons.tsx +++ b/superset-frontend/src/components/Checkbox/CheckboxIcons.tsx @@ -17,51 +17,64 @@ * under the License. */ import React from 'react'; +import { useTheme } from '@superset-ui/core'; -export const CheckboxChecked = () => ( - - - - -); +export const CheckboxChecked = () => { + const theme = useTheme(); + return ( + + + + + ); +}; -export const CheckboxHalfChecked = () => ( - - - - -); +export const CheckboxHalfChecked = () => { + const theme = useTheme(); + return ( + + + + + ); +}; -export const CheckboxUnchecked = () => ( - - - - -); +export const CheckboxUnchecked = () => { + const theme = useTheme(); + return ( + + + + + ); +}; diff --git a/superset-frontend/src/dashboard/components/filterscope/FilterFieldItem.jsx b/superset-frontend/src/dashboard/components/filterscope/FilterFieldItem.jsx index d9d9409f77..b42d0f6a30 100644 --- a/superset-frontend/src/dashboard/components/filterscope/FilterFieldItem.jsx +++ b/superset-frontend/src/dashboard/components/filterscope/FilterFieldItem.jsx @@ -19,7 +19,6 @@ import React from 'react'; import PropTypes from 'prop-types'; import cx from 'classnames'; - import FormLabel from 'src/components/FormLabel'; const propTypes = { diff --git a/superset-frontend/src/dashboard/components/filterscope/FilterFieldTree.jsx b/superset-frontend/src/dashboard/components/filterscope/FilterFieldTree.jsx index f3d316fc4e..1a97b09a10 100644 --- a/superset-frontend/src/dashboard/components/filterscope/FilterFieldTree.jsx +++ b/superset-frontend/src/dashboard/components/filterscope/FilterFieldTree.jsx @@ -19,10 +19,9 @@ import React from 'react'; import PropTypes from 'prop-types'; import CheckboxTree from 'react-checkbox-tree'; - +import { filterScopeSelectorTreeNodePropShape } from 'src/dashboard/util/propShapes'; import treeIcons from './treeIcons'; import renderFilterFieldTreeNodes from './renderFilterFieldTreeNodes'; -import { filterScopeSelectorTreeNodePropShape } from '../../util/propShapes'; const propTypes = { activeKey: PropTypes.string, diff --git a/superset-frontend/src/dashboard/components/filterscope/FilterScope.test.tsx b/superset-frontend/src/dashboard/components/filterscope/FilterScope.test.tsx new file mode 100644 index 0000000000..920b5200b0 --- /dev/null +++ b/superset-frontend/src/dashboard/components/filterscope/FilterScope.test.tsx @@ -0,0 +1,373 @@ +/** + * 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 { supersetTheme } from '@superset-ui/core'; +import { render, screen } from 'spec/helpers/testing-library'; +import userEvent from '@testing-library/user-event'; +import FilterScopeSelector from './FilterScopeSelector'; + +const ROOT_ID = 'ROOT_ID'; +const GRID = 'GRID'; +const TABS = 'TABS'; +const TAB = 'TAB'; +const FILTER_A = 'FILTER_A'; +const FILTER_B = 'FILTER_B'; +const FILTER_C = 'FILTER_C'; +const TAB_A = 'TAB_A'; +const TAB_B = 'TAB_B'; +const CHART_A = 'CHART_A'; +const CHART_B = 'CHART_B'; +const CHART_C = 'CHART_C'; +const CHART_D = 'CHART_D'; + +const EXPAND_ALL = 'Expand all'; +const COLLAPSE_ALL = 'Collapse all'; +const CHECKED = 'checked'; +const UNCHECKED = 'unchecked'; +const INDETERMINATE = 'indeterminate'; +const ALL_FILTERS = 'All filters'; +const ALL_CHARTS = 'All charts'; + +const createProps = () => ({ + dashboardFilters: { + 1: { + chartId: 1, + componentId: 'component-id', + datasourceId: 'datasource-id', + directPathToFilter: [], + isDateFilter: false, + isInstantFilter: false, + filterName: FILTER_A, + columns: { column_b: undefined, column_c: undefined }, + labels: { column_b: FILTER_B, column_c: FILTER_C }, + scopes: { + column_b: { immune: [], scope: [ROOT_ID] }, + column_c: { immune: [], scope: [ROOT_ID] }, + }, + }, + }, + layout: { + ROOT_ID: { children: [GRID], id: ROOT_ID, type: 'ROOT' }, + GRID: { + children: [TABS], + id: GRID, + type: GRID, + parents: [ROOT_ID], + }, + TABS: { + children: [TAB_A, TAB_B], + id: TABS, + type: TABS, + parents: [ROOT_ID, GRID], + }, + TAB_A: { + meta: { text: TAB_A }, + children: [CHART_A, CHART_B], + id: TAB_A, + type: TAB, + parents: [ROOT_ID, GRID, TABS], + }, + TAB_B: { + meta: { text: TAB_B }, + children: [CHART_C, CHART_D], + id: TAB_B, + type: TAB, + parents: [ROOT_ID, GRID, TABS], + }, + CHART_A: { + meta: { + chartId: 2, + sliceName: CHART_A, + }, + children: [], + id: CHART_A, + type: 'CHART', + parents: [ROOT_ID, GRID, TABS, TAB_A], + }, + CHART_B: { + meta: { + chartId: 3, + sliceName: CHART_B, + }, + children: [], + id: CHART_B, + type: 'CHART', + parents: [ROOT_ID, GRID, TABS, TAB_A], + }, + CHART_C: { + meta: { + chartId: 4, + sliceName: CHART_C, + }, + children: [], + id: CHART_C, + type: 'CHART', + parents: [ROOT_ID, GRID, TABS, TAB_B], + }, + CHART_D: { + meta: { + chartId: 5, + sliceName: CHART_D, + }, + children: [], + id: CHART_D, + type: 'CHART', + parents: [ROOT_ID, GRID, TABS, TAB_B], + }, + }, + updateDashboardFiltersScope: jest.fn(), + setUnsavedChanges: jest.fn(), + onCloseModal: jest.fn(), +}); + +type CheckboxState = 'checked' | 'unchecked' | 'indeterminate'; + +/** + * Unfortunatelly react-checkbox-tree doesn't provide an easy way to + * access the checkbox icon. We need this function to find the element. + */ +function getCheckboxIcon(element: HTMLElement): Element { + const parent = element.parentElement!; + if (parent.classList.contains('rct-text')) { + return parent.children[1]; + } + return getCheckboxIcon(parent); +} + +/** + * Unfortunatelly when using react-checkbox-tree, the only perceived change of a + * checkbox state change is the fill color of the SVG icon. + */ +function getCheckboxState(name: string): CheckboxState { + const element = screen.getByRole('link', { name }); + const svgPath = getCheckboxIcon(element).children[1].children[0].children[0]; + const fill = svgPath.getAttribute('fill'); + return fill === supersetTheme.colors.primary.base + ? CHECKED + : fill === supersetTheme.colors.grayscale.light1 + ? INDETERMINATE + : UNCHECKED; +} + +function clickCheckbox(name: string) { + const element = screen.getByRole('link', { name }); + const checkboxLabel = getCheckboxIcon(element); + userEvent.click(checkboxLabel); +} + +test('renders with empty filters', () => { + render(, { + useRedux: true, + }); + expect(screen.getByText('Configure filter scopes')).toBeInTheDocument(); + expect( + screen.getByText('There are no filters in this dashboard.'), + ).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Close' })).toBeInTheDocument(); + expect( + screen.queryByRole('button', { name: 'Save' }), + ).not.toBeInTheDocument(); +}); + +test('renders with filters values', () => { + render(, { useRedux: true }); + expect(screen.getByRole('link', { name: FILTER_A })).toBeInTheDocument(); + expect(screen.getByRole('link', { name: FILTER_B })).toBeInTheDocument(); + expect(screen.getByRole('link', { name: FILTER_C })).toBeInTheDocument(); + expect(screen.getByRole('link', { name: TAB_A })).toBeInTheDocument(); + expect(screen.getByRole('link', { name: TAB_B })).toBeInTheDocument(); + expect(screen.queryByText(CHART_A)).not.toBeInTheDocument(); + expect(screen.queryByText(CHART_B)).not.toBeInTheDocument(); + expect(screen.queryByText(CHART_C)).not.toBeInTheDocument(); + expect(screen.queryByText(CHART_D)).not.toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Close' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Save' })).toBeInTheDocument(); +}); + +test('collapses/expands all filters', () => { + render(, { + useRedux: true, + }); + userEvent.click(screen.getAllByRole('button', { name: COLLAPSE_ALL })[0]); + expect(screen.getByRole('link', { name: ALL_FILTERS })).toBeInTheDocument(); + expect( + screen.queryByRole('link', { name: FILTER_A }), + ).not.toBeInTheDocument(); + expect( + screen.queryByRole('link', { name: FILTER_B }), + ).not.toBeInTheDocument(); + expect( + screen.queryByRole('link', { name: FILTER_C }), + ).not.toBeInTheDocument(); + userEvent.click(screen.getAllByRole('button', { name: EXPAND_ALL })[0]); + expect(screen.getByRole('link', { name: ALL_FILTERS })).toBeInTheDocument(); + expect(screen.getByRole('link', { name: FILTER_A })).toBeInTheDocument(); + expect(screen.getByRole('link', { name: FILTER_B })).toBeInTheDocument(); + expect(screen.getByRole('link', { name: FILTER_C })).toBeInTheDocument(); +}); + +test('collapses/expands all charts', () => { + render(, { + useRedux: true, + }); + userEvent.click(screen.getAllByRole('button', { name: COLLAPSE_ALL })[1]); + expect(screen.getByText(ALL_CHARTS)).toBeInTheDocument(); + expect(screen.queryByText(CHART_A)).not.toBeInTheDocument(); + expect(screen.queryByText(CHART_B)).not.toBeInTheDocument(); + expect(screen.queryByText(CHART_C)).not.toBeInTheDocument(); + expect(screen.queryByText(CHART_D)).not.toBeInTheDocument(); + userEvent.click(screen.getAllByRole('button', { name: EXPAND_ALL })[1]); + expect(screen.getByText(ALL_CHARTS)).toBeInTheDocument(); + expect(screen.getByRole('link', { name: CHART_A })).toBeInTheDocument(); + expect(screen.getByRole('link', { name: CHART_B })).toBeInTheDocument(); + expect(screen.getByRole('link', { name: CHART_C })).toBeInTheDocument(); + expect(screen.getByRole('link', { name: CHART_D })).toBeInTheDocument(); +}); + +test('searches for a chart', () => { + render(, { + useRedux: true, + }); + userEvent.type(screen.getByPlaceholderText('Search...'), CHART_C); + expect(screen.queryByRole('link', { name: CHART_A })).not.toBeInTheDocument(); + expect(screen.queryByRole('link', { name: CHART_B })).not.toBeInTheDocument(); + expect(screen.getByRole('link', { name: CHART_C })).toBeInTheDocument(); +}); + +test('selects a leaf filter', () => { + render(, { + useRedux: true, + }); + expect(getCheckboxState(FILTER_C)).toBe(UNCHECKED); + clickCheckbox(FILTER_C); + expect(getCheckboxState(FILTER_C)).toBe(CHECKED); +}); + +test('selects a leaf chart', () => { + render(, { + useRedux: true, + }); + userEvent.click(screen.getAllByRole('button', { name: EXPAND_ALL })[1]); + expect(getCheckboxState(CHART_D)).toBe(UNCHECKED); + clickCheckbox(CHART_D); + expect(getCheckboxState(CHART_D)).toBe(CHECKED); +}); + +test('selects a branch of filters', () => { + render(, { + useRedux: true, + }); + expect(getCheckboxState(FILTER_A)).toBe(UNCHECKED); + expect(getCheckboxState(FILTER_B)).toBe(UNCHECKED); + expect(getCheckboxState(FILTER_C)).toBe(UNCHECKED); + clickCheckbox(FILTER_A); + expect(getCheckboxState(FILTER_A)).toBe(CHECKED); + expect(getCheckboxState(FILTER_B)).toBe(CHECKED); + expect(getCheckboxState(FILTER_C)).toBe(CHECKED); +}); + +test('selects a branch of charts', () => { + render(, { + useRedux: true, + }); + + const tabA = screen.getByText(TAB_A); + userEvent.click(tabA); + + expect(getCheckboxState(TAB_A)).toBe(UNCHECKED); + expect(getCheckboxState(CHART_A)).toBe(UNCHECKED); + expect(getCheckboxState(CHART_B)).toBe(UNCHECKED); + clickCheckbox(TAB_A); + expect(getCheckboxState(TAB_A)).toBe(CHECKED); + expect(getCheckboxState(CHART_A)).toBe(CHECKED); + expect(getCheckboxState(CHART_B)).toBe(CHECKED); +}); + +test('selects all filters', () => { + render(, { + useRedux: true, + }); + userEvent.click(screen.getAllByRole('button', { name: EXPAND_ALL })[0]); + expect(getCheckboxState(ALL_FILTERS)).toBe(UNCHECKED); + expect(getCheckboxState(FILTER_A)).toBe(UNCHECKED); + expect(getCheckboxState(FILTER_B)).toBe(UNCHECKED); + expect(getCheckboxState(FILTER_C)).toBe(UNCHECKED); + clickCheckbox(ALL_FILTERS); + expect(getCheckboxState(ALL_FILTERS)).toBe(CHECKED); + expect(getCheckboxState(FILTER_A)).toBe(CHECKED); + expect(getCheckboxState(FILTER_B)).toBe(CHECKED); + expect(getCheckboxState(FILTER_C)).toBe(CHECKED); +}); + +test('selects all charts', () => { + render(, { + useRedux: true, + }); + userEvent.click(screen.getAllByRole('button', { name: EXPAND_ALL })[1]); + expect(getCheckboxState(TAB_A)).toBe(UNCHECKED); + expect(getCheckboxState(CHART_A)).toBe(UNCHECKED); + expect(getCheckboxState(CHART_B)).toBe(UNCHECKED); + expect(getCheckboxState(TAB_B)).toBe(UNCHECKED); + expect(getCheckboxState(CHART_C)).toBe(UNCHECKED); + expect(getCheckboxState(CHART_D)).toBe(UNCHECKED); + clickCheckbox(ALL_CHARTS); + expect(getCheckboxState(TAB_A)).toBe(CHECKED); + expect(getCheckboxState(CHART_A)).toBe(CHECKED); + expect(getCheckboxState(CHART_B)).toBe(CHECKED); + expect(getCheckboxState(TAB_B)).toBe(CHECKED); + expect(getCheckboxState(CHART_C)).toBe(CHECKED); + expect(getCheckboxState(CHART_D)).toBe(CHECKED); +}); + +test('triggers onClose', () => { + const onCloseModal = jest.fn(); + render( + , + { + useRedux: true, + }, + ); + expect(onCloseModal).toHaveBeenCalledTimes(0); + userEvent.click(screen.getByRole('button', { name: 'Close' })); + expect(onCloseModal).toHaveBeenCalledTimes(1); +}); + +test('triggers onSave', () => { + const updateDashboardFiltersScope = jest.fn(); + const setUnsavedChanges = jest.fn(); + const onCloseModal = jest.fn(); + render( + , + { + useRedux: true, + }, + ); + expect(updateDashboardFiltersScope).toHaveBeenCalledTimes(0); + expect(setUnsavedChanges).toHaveBeenCalledTimes(0); + expect(onCloseModal).toHaveBeenCalledTimes(0); + userEvent.click(screen.getByRole('button', { name: 'Save' })); + expect(updateDashboardFiltersScope).toHaveBeenCalledTimes(1); + expect(setUnsavedChanges).toHaveBeenCalledTimes(1); + expect(onCloseModal).toHaveBeenCalledTimes(1); +}); diff --git a/superset-frontend/src/dashboard/components/filterscope/FilterScopeModal.tsx b/superset-frontend/src/dashboard/components/filterscope/FilterScopeModal.tsx index 588b3c1d35..53bd0e170f 100644 --- a/superset-frontend/src/dashboard/components/filterscope/FilterScopeModal.tsx +++ b/superset-frontend/src/dashboard/components/filterscope/FilterScopeModal.tsx @@ -18,7 +18,6 @@ */ import React, { RefObject } from 'react'; import { styled } from '@superset-ui/core'; - import ModalTrigger from 'src/components/ModalTrigger'; import FilterScope from 'src/dashboard/containers/FilterScope'; diff --git a/superset-frontend/src/dashboard/components/filterscope/FilterScopeSelector.jsx b/superset-frontend/src/dashboard/components/filterscope/FilterScopeSelector.jsx index 882931983e..e0d962f60b 100644 --- a/superset-frontend/src/dashboard/components/filterscope/FilterScopeSelector.jsx +++ b/superset-frontend/src/dashboard/components/filterscope/FilterScopeSelector.jsx @@ -22,23 +22,23 @@ import cx from 'classnames'; import Button from 'src/components/Button'; import { t, styled } from '@superset-ui/core'; -import buildFilterScopeTreeEntry from '../../util/buildFilterScopeTreeEntry'; -import getFilterScopeNodesTree from '../../util/getFilterScopeNodesTree'; -import getFilterFieldNodesTree from '../../util/getFilterFieldNodesTree'; -import getFilterScopeParentNodes from '../../util/getFilterScopeParentNodes'; -import getKeyForFilterScopeTree from '../../util/getKeyForFilterScopeTree'; -import getSelectedChartIdForFilterScopeTree from '../../util/getSelectedChartIdForFilterScopeTree'; -import getFilterScopeFromNodesTree from '../../util/getFilterScopeFromNodesTree'; -import getRevertedFilterScope from '../../util/getRevertedFilterScope'; -import FilterScopeTree from './FilterScopeTree'; -import FilterFieldTree from './FilterFieldTree'; -import { getChartIdsInFilterScope } from '../../util/activeDashboardFilters'; +import buildFilterScopeTreeEntry from 'src/dashboard/util/buildFilterScopeTreeEntry'; +import getFilterScopeNodesTree from 'src/dashboard/util/getFilterScopeNodesTree'; +import getFilterFieldNodesTree from 'src/dashboard/util/getFilterFieldNodesTree'; +import getFilterScopeParentNodes from 'src/dashboard/util/getFilterScopeParentNodes'; +import getKeyForFilterScopeTree from 'src/dashboard/util/getKeyForFilterScopeTree'; +import getSelectedChartIdForFilterScopeTree from 'src/dashboard/util/getSelectedChartIdForFilterScopeTree'; +import getFilterScopeFromNodesTree from 'src/dashboard/util/getFilterScopeFromNodesTree'; +import getRevertedFilterScope from 'src/dashboard/util/getRevertedFilterScope'; +import { getChartIdsInFilterScope } from 'src/dashboard/util/activeDashboardFilters'; import { getChartIdAndColumnFromFilterKey, getDashboardFilterKey, -} from '../../util/getDashboardFilterKey'; -import { ALL_FILTERS_ROOT } from '../../util/constants'; -import { dashboardFilterPropShape } from '../../util/propShapes'; +} from 'src/dashboard/util/getDashboardFilterKey'; +import { ALL_FILTERS_ROOT } from 'src/dashboard/util/constants'; +import { dashboardFilterPropShape } from 'src/dashboard/util/propShapes'; +import FilterScopeTree from './FilterScopeTree'; +import FilterFieldTree from './FilterFieldTree'; const propTypes = { dashboardFilters: PropTypes.objectOf(dashboardFilterPropShape).isRequired, diff --git a/superset-frontend/src/dashboard/components/filterscope/FilterScopeTree.jsx b/superset-frontend/src/dashboard/components/filterscope/FilterScopeTree.jsx index 2d663e0d35..9ebe95bab0 100644 --- a/superset-frontend/src/dashboard/components/filterscope/FilterScopeTree.jsx +++ b/superset-frontend/src/dashboard/components/filterscope/FilterScopeTree.jsx @@ -19,10 +19,9 @@ import React from 'react'; import PropTypes from 'prop-types'; import CheckboxTree from 'react-checkbox-tree'; - +import { filterScopeSelectorTreeNodePropShape } from 'src/dashboard/util/propShapes'; import renderFilterScopeTreeNodes from './renderFilterScopeTreeNodes'; import treeIcons from './treeIcons'; -import { filterScopeSelectorTreeNodePropShape } from '../../util/propShapes'; const propTypes = { nodes: PropTypes.arrayOf(filterScopeSelectorTreeNodePropShape).isRequired, diff --git a/superset-frontend/src/dashboard/components/filterscope/renderFilterFieldTreeNodes.jsx b/superset-frontend/src/dashboard/components/filterscope/renderFilterFieldTreeNodes.jsx index 55d1bae910..e9e521b899 100644 --- a/superset-frontend/src/dashboard/components/filterscope/renderFilterFieldTreeNodes.jsx +++ b/superset-frontend/src/dashboard/components/filterscope/renderFilterFieldTreeNodes.jsx @@ -17,7 +17,6 @@ * under the License. */ import React from 'react'; - import FilterFieldItem from './FilterFieldItem'; export default function renderFilterFieldTreeNodes({ nodes, activeKey }) { diff --git a/superset-frontend/src/dashboard/components/filterscope/renderFilterScopeTreeNodes.jsx b/superset-frontend/src/dashboard/components/filterscope/renderFilterScopeTreeNodes.jsx index c6e1be1558..d35c663343 100644 --- a/superset-frontend/src/dashboard/components/filterscope/renderFilterScopeTreeNodes.jsx +++ b/superset-frontend/src/dashboard/components/filterscope/renderFilterScopeTreeNodes.jsx @@ -19,8 +19,8 @@ import React from 'react'; import cx from 'classnames'; -import ChartIcon from '../../../components/ChartIcon'; -import { CHART_TYPE } from '../../util/componentTypes'; +import ChartIcon from 'src/components/ChartIcon'; +import { CHART_TYPE } from 'src/dashboard/util/componentTypes'; function traverse({ currentNode = {}, selectedChartId }) { if (!currentNode) { diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Header.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Header.tsx index 0f2282a99a..f7d7cbad1e 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Header.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Header.tsx @@ -98,10 +98,14 @@ const Header: FC = ({ {t('Filters')} {canEdit && ( - + )} - toggleFiltersBar(false)} /> + toggleFiltersBar(false)} + />