diff --git a/superset-frontend/spec/javascripts/dashboard/components/FiltersBadge_spec.tsx b/superset-frontend/spec/javascripts/dashboard/components/FiltersBadge_spec.tsx index d6bd4974d54..f24b973199d 100644 --- a/superset-frontend/spec/javascripts/dashboard/components/FiltersBadge_spec.tsx +++ b/superset-frontend/spec/javascripts/dashboard/components/FiltersBadge_spec.tsx @@ -20,10 +20,15 @@ import React from 'react'; import { shallow } from 'enzyme'; import { supersetTheme } from '@superset-ui/core'; import { Provider } from 'react-redux'; +import { Store } from 'redux'; import * as SupersetUI from '@superset-ui/core'; -import { CHART_UPDATE_SUCCEEDED } from 'src/chart/chartAction'; +import { styledMount as mount } from 'spec/helpers/theming'; +import { + CHART_RENDERING_SUCCEEDED, + CHART_UPDATE_SUCCEEDED, +} from 'src/chart/chartAction'; import { buildActiveFilters } from 'src/dashboard/util/activeDashboardFilters'; -import FiltersBadge from 'src/dashboard/containers/FiltersBadge'; +import { FiltersBadge } from 'src/dashboard/components/FiltersBadge'; import { getMockStoreWithFilters, getMockStoreWithNativeFilters, @@ -34,6 +39,15 @@ import { dashboardWithFilter } from 'spec/fixtures/mockDashboardLayout'; import Icons from 'src/components/Icons'; import { FeatureFlag } from 'src/featureFlags'; +const defaultStore = getMockStoreWithFilters(); +function setup(store: Store = defaultStore) { + return mount( + + + , + ); +} + describe('FiltersBadge', () => { // there's this bizarre "active filters" thing // that doesn't actually use any kind of state management. @@ -71,9 +85,7 @@ describe('FiltersBadge', () => { , , ); - expect( - wrapper.dive().find('[data-test="applied-filter-count"]'), - ).not.toExist(); + expect(wrapper.find('[data-test="applied-filter-count"]')).not.toExist(); }); it('shows the indicator when filters have been applied', () => { @@ -91,14 +103,13 @@ describe('FiltersBadge', () => { ], dashboardFilters, }); - const wrapper = shallow( - , - ).dive(); - expect(wrapper.dive().find('DetailsPanelPopover')).toExist(); - expect( - wrapper.dive().find('[data-test="applied-filter-count"]'), - ).toHaveText('1'); - expect(wrapper.dive().find('WarningFilled')).not.toExist(); + store.dispatch({ type: CHART_RENDERING_SUCCEEDED, key: sliceId }); + const wrapper = setup(store); + expect(wrapper.find('DetailsPanelPopover')).toExist(); + expect(wrapper.find('[data-test="applied-filter-count"]')).toHaveText( + '1', + ); + expect(wrapper.find('WarningFilled')).not.toExist(); }); it("shows a warning when there's a rejected filter", () => { @@ -118,19 +129,18 @@ describe('FiltersBadge', () => { ], dashboardFilters, }); - const wrapper = shallow( - , - ).dive(); - expect(wrapper.dive().find('DetailsPanelPopover')).toExist(); + store.dispatch({ type: CHART_RENDERING_SUCCEEDED, key: sliceId }); + const wrapper = setup(store); + expect(wrapper.find('DetailsPanelPopover')).toExist(); + expect(wrapper.find('[data-test="applied-filter-count"]')).toHaveText( + '0', + ); expect( - wrapper.dive().find('[data-test="applied-filter-count"]'), - ).toHaveText('0'); - expect( - wrapper.dive().find('[data-test="incompatible-filter-count"]'), + wrapper.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(Icons.AlertSolid)).toExist(); + // console.log(wrapper.debug()) + expect(wrapper.find(Icons.AlertSolid)).toExist(); }); }); @@ -149,14 +159,9 @@ describe('FiltersBadge', () => { }, ], }); - const wrapper = shallow( - - , - , - ); - expect( - wrapper.dive().find('[data-test="applied-filter-count"]'), - ).not.toExist(); + store.dispatch({ type: CHART_RENDERING_SUCCEEDED, key: sliceId }); + const wrapper = setup(store); + expect(wrapper.find('[data-test="applied-filter-count"]')).not.toExist(); }); it('shows the indicator when filters have been applied', () => { @@ -177,14 +182,13 @@ describe('FiltersBadge', () => { }, ], }); - const wrapper = shallow( - , - ).dive(); - expect(wrapper.dive().find('DetailsPanelPopover')).toExist(); - expect( - wrapper.dive().find('[data-test="applied-filter-count"]'), - ).toHaveText('1'); - expect(wrapper.dive().find('WarningFilled')).not.toExist(); + store.dispatch({ type: CHART_RENDERING_SUCCEEDED, key: sliceId }); + const wrapper = setup(store); + expect(wrapper.find('DetailsPanelPopover')).toExist(); + expect(wrapper.find('[data-test="applied-filter-count"]')).toHaveText( + '1', + ); + expect(wrapper.find('WarningFilled')).not.toExist(); }); it("shows a warning when there's a rejected filter", () => { @@ -207,17 +211,16 @@ describe('FiltersBadge', () => { }, ], }); - const wrapper = shallow( - , - ).dive(); - expect(wrapper.dive().find('DetailsPanelPopover')).toExist(); + store.dispatch({ type: CHART_RENDERING_SUCCEEDED, key: sliceId }); + const wrapper = setup(store); + expect(wrapper.find('DetailsPanelPopover')).toExist(); + expect(wrapper.find('[data-test="applied-filter-count"]')).toHaveText( + '0', + ); expect( - wrapper.dive().find('[data-test="applied-filter-count"]'), - ).toHaveText('0'); - expect( - wrapper.dive().find('[data-test="incompatible-filter-count"]'), + wrapper.find('[data-test="incompatible-filter-count"]'), ).toHaveText('1'); - expect(wrapper.dive().find(Icons.AlertSolid)).toExist(); + expect(wrapper.find(Icons.AlertSolid)).toExist(); }); }); }); diff --git a/superset-frontend/spec/javascripts/dashboard/components/gridComponents/Tabs_spec.jsx b/superset-frontend/spec/javascripts/dashboard/components/gridComponents/Tabs_spec.jsx index 517ac305ace..0985fd6ed93 100644 --- a/superset-frontend/spec/javascripts/dashboard/components/gridComponents/Tabs_spec.jsx +++ b/superset-frontend/spec/javascripts/dashboard/components/gridComponents/Tabs_spec.jsx @@ -31,7 +31,7 @@ import DashboardComponent from 'src/dashboard/containers/DashboardComponent'; import DeleteComponentButton from 'src/dashboard/components/DeleteComponentButton'; import HoverMenu from 'src/dashboard/components/menu/HoverMenu'; import DragDroppable from 'src/dashboard/components/dnd/DragDroppable'; -import Tabs from 'src/dashboard/components/gridComponents/Tabs'; +import { Tabs } from 'src/dashboard/components/gridComponents/Tabs'; import { DASHBOARD_ROOT_ID } from 'src/dashboard/util/constants'; import emptyDashboardLayout from 'src/dashboard/fixtures/emptyDashboardLayout'; import { dashboardLayoutWithTabs } from 'spec/fixtures/mockDashboardLayout'; @@ -66,14 +66,15 @@ describe('Tabs', () => { nativeFilters: nativeFilters.filters, }; + const mockStore = getMockStore({ + ...initialState, + dashboardLayout: dashboardLayoutWithTabs, + dashboardFilters: {}, + }); + function setup(overrideProps) { // We have to wrap provide DragDropContext for the underlying DragDroppable // otherwise we cannot assert on DragDroppable children - const mockStore = getMockStore({ - ...initialState, - dashboardLayout: dashboardLayoutWithTabs, - dashboardFilters: {}, - }); const wrapper = mount( diff --git a/superset-frontend/src/dashboard/components/FiltersBadge/index.tsx b/superset-frontend/src/dashboard/components/FiltersBadge/index.tsx index 506dc5216fe..5cc86e1ebdb 100644 --- a/superset-frontend/src/dashboard/components/FiltersBadge/index.tsx +++ b/superset-frontend/src/dashboard/components/FiltersBadge/index.tsx @@ -16,29 +16,159 @@ * specific language governing permissions and limitations * under the License. */ -import React from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { uniqWith } from 'lodash'; import cx from 'classnames'; import Icon from 'src/components/Icon'; import Icons from 'src/components/Icons'; +import { usePrevious } from 'src/common/hooks/usePrevious'; +import { DataMaskStateWithId } from 'src/dataMask/types'; import DetailsPanelPopover from './DetailsPanel'; import { Pill } from './Styles'; -import { Indicator } from './selectors'; +import { + Indicator, + IndicatorStatus, + selectIndicatorsForChart, + selectNativeIndicatorsForChart, +} from './selectors'; +import { setDirectPathToChild } from '../../actions/dashboardState'; +import { + ChartsState, + DashboardInfo, + DashboardLayout, + RootState, +} from '../../types'; +import { Filters } from '../../reducers/types'; export interface FiltersBadgeProps { - appliedCrossFilterIndicators: Indicator[]; - appliedIndicators: Indicator[]; - unsetIndicators: Indicator[]; - incompatibleIndicators: Indicator[]; - onHighlightFilterSource: (path: string[]) => void; + chartId: number; } -const FiltersBadge = ({ - appliedCrossFilterIndicators, - appliedIndicators, - unsetIndicators, - incompatibleIndicators, - onHighlightFilterSource, -}: FiltersBadgeProps) => { +const sortByStatus = (indicators: Indicator[]): Indicator[] => { + const statuses = [ + IndicatorStatus.Applied, + IndicatorStatus.Unset, + IndicatorStatus.Incompatible, + ]; + return indicators.sort( + (a, b) => + statuses.indexOf(a.status as IndicatorStatus) - + statuses.indexOf(b.status as IndicatorStatus), + ); +}; + +export const FiltersBadge = ({ chartId }: FiltersBadgeProps) => { + const dispatch = useDispatch(); + const datasources = useSelector(state => state.datasources); + const dashboardFilters = useSelector( + state => state.dashboardFilters, + ); + const nativeFilters = useSelector( + state => state.nativeFilters?.filters, + ); + const dashboardInfo = useSelector( + state => state.dashboardInfo, + ); + const charts = useSelector(state => state.charts); + const present = useSelector( + state => state.dashboardLayout.present, + ); + const dataMask = useSelector( + state => state.dataMask, + ); + + const [nativeIndicators, setNativeIndicators] = useState([]); + const [dashboardIndicators, setDashboardIndicators] = useState( + [], + ); + + const onHighlightFilterSource = useCallback( + (path: string[]) => { + dispatch(setDirectPathToChild(path)); + }, + [dispatch], + ); + + const chart = charts[chartId]; + const prevChartStatus = usePrevious(chart?.chartStatus); + + const showIndicators = useCallback( + () => + chart?.chartStatus && ['rendered', 'success'].includes(chart.chartStatus), + [chart.chartStatus], + ); + useEffect(() => { + if (!showIndicators) { + setDashboardIndicators([]); + } + if (prevChartStatus !== 'success') { + setDashboardIndicators( + selectIndicatorsForChart(chartId, dashboardFilters, datasources, chart), + ); + } + }, [ + chart, + chartId, + dashboardFilters, + datasources, + prevChartStatus, + showIndicators, + ]); + + useEffect(() => { + if (!showIndicators) { + setNativeIndicators([]); + } + if (prevChartStatus !== 'success') { + setNativeIndicators( + selectNativeIndicatorsForChart( + nativeFilters, + dataMask, + chartId, + chart, + present, + dashboardInfo.metadata?.chart_configuration, + ), + ); + } + }, [ + chart, + chartId, + dashboardInfo.metadata?.chart_configuration, + dataMask, + nativeFilters, + present, + prevChartStatus, + showIndicators, + ]); + + const indicators = useMemo( + () => + uniqWith( + sortByStatus([...dashboardIndicators, ...nativeIndicators]), + (ind1, ind2) => + ind1.column === ind2.column && + ind1.name === ind2.name && + (ind1.status !== IndicatorStatus.Applied || + ind2.status !== IndicatorStatus.Applied), + ), + [dashboardIndicators, nativeIndicators], + ); + + const appliedCrossFilterIndicators = indicators.filter( + indicator => indicator.status === IndicatorStatus.CrossFilterApplied, + ); + const appliedIndicators = indicators.filter( + indicator => indicator.status === IndicatorStatus.Applied, + ); + const unsetIndicators = indicators.filter( + indicator => indicator.status === IndicatorStatus.Unset, + ); + const incompatibleIndicators = indicators.filter( + indicator => indicator.status === IndicatorStatus.Incompatible, + ); + if ( !appliedCrossFilterIndicators.length && !appliedIndicators.length && @@ -89,4 +219,4 @@ const FiltersBadge = ({ ); }; -export default FiltersBadge; +export default React.memo(FiltersBadge); diff --git a/superset-frontend/src/dashboard/components/FiltersBadge/selectors.ts b/superset-frontend/src/dashboard/components/FiltersBadge/selectors.ts index 8b5854dfe16..48d706fc835 100644 --- a/superset-frontend/src/dashboard/components/FiltersBadge/selectors.ts +++ b/superset-frontend/src/dashboard/components/FiltersBadge/selectors.ts @@ -18,10 +18,7 @@ */ import { NO_TIME_RANGE, TIME_FILTER_MAP } from 'src/explore/constants'; import { getChartIdsInFilterScope } from 'src/dashboard/util/activeDashboardFilters'; -import { - ChartConfiguration, - NativeFiltersState, -} from 'src/dashboard/reducers/types'; +import { ChartConfiguration, Filters } from 'src/dashboard/reducers/types'; import { DataMaskStateWithId, DataMaskType } from 'src/dataMask/types'; import { FeatureFlag, isFeatureEnabled } from '@superset-ui/core'; import { Layout } from '../../types'; @@ -147,12 +144,8 @@ export const selectIndicatorsForChart = ( chartId: number, filters: { [key: number]: Filter }, datasources: { [key: string]: Datasource }, - charts: any, + chart: any, ): Indicator[] => { - const chart = charts[chartId]; - // no indicators if chart is loading - if (chart.chartStatus === 'loading') return []; - // for now we only need to know which columns are compatible/incompatible, // so grab the columns from the applied/rejected filters const appliedColumns = getAppliedColumns(chart); @@ -178,15 +171,13 @@ export const selectIndicatorsForChart = ( }; export const selectNativeIndicatorsForChart = ( - nativeFilters: NativeFiltersState, + nativeFilters: Filters, dataMask: DataMaskStateWithId, chartId: number, - charts: any, + chart: any, dashboardLayout: Layout, chartConfiguration: ChartConfiguration = {}, ): Indicator[] => { - const chart = charts[chartId]; - const appliedColumns = getAppliedColumns(chart); const rejectedColumns = getRejectedColumns(chart); @@ -218,29 +209,32 @@ export const selectNativeIndicatorsForChart = ( let nativeFilterIndicators: any = []; if (isFeatureEnabled(FeatureFlag.DASHBOARD_NATIVE_FILTERS)) { - nativeFilterIndicators = Object.values(nativeFilters.filters) - .filter(nativeFilter => - getTreeCheckedItems(nativeFilter.scope, dashboardLayout).some( - layoutItem => dashboardLayout[layoutItem]?.meta?.chartId === chartId, - ), - ) - .map(nativeFilter => { - const column = nativeFilter.targets[0]?.column?.name; - let value = - dataMask[nativeFilter.id]?.filterState?.label ?? - dataMask[nativeFilter.id]?.filterState?.value ?? - null; - if (!Array.isArray(value) && value !== null) { - value = [value]; - } - return { - column, - name: nativeFilter.name, - path: [nativeFilter.id], - status: getStatus({ value, column }), - value, - }; - }); + nativeFilterIndicators = + nativeFilters && + Object.values(nativeFilters) + .filter(nativeFilter => + getTreeCheckedItems(nativeFilter.scope, dashboardLayout).some( + layoutItem => + dashboardLayout[layoutItem]?.meta?.chartId === chartId, + ), + ) + .map(nativeFilter => { + const column = nativeFilter.targets[0]?.column?.name; + let value = + dataMask[nativeFilter.id]?.filterState?.label ?? + dataMask[nativeFilter.id]?.filterState?.value ?? + null; + if (!Array.isArray(value) && value !== null) { + value = [value]; + } + return { + column, + name: nativeFilter.name, + path: [nativeFilter.id], + status: getStatus({ value, column }), + value, + }; + }); } let crossFilterIndicators: any = []; diff --git a/superset-frontend/src/dashboard/components/SliceHeader/SliceHeader.test.tsx b/superset-frontend/src/dashboard/components/SliceHeader/SliceHeader.test.tsx index fc67bb56299..3416fef388d 100644 --- a/superset-frontend/src/dashboard/components/SliceHeader/SliceHeader.test.tsx +++ b/superset-frontend/src/dashboard/components/SliceHeader/SliceHeader.test.tsx @@ -93,7 +93,7 @@ jest.mock('src/dashboard/components/SliceHeaderControls', () => ({ ), })); -jest.mock('src/dashboard/containers/FiltersBadge', () => ({ +jest.mock('src/dashboard/components/FiltersBadge', () => ({ __esModule: true, default: (props: any) => (
diff --git a/superset-frontend/src/dashboard/components/SliceHeader/index.tsx b/superset-frontend/src/dashboard/components/SliceHeader/index.tsx index d6f41100da5..2e0e234b611 100644 --- a/superset-frontend/src/dashboard/components/SliceHeader/index.tsx +++ b/superset-frontend/src/dashboard/components/SliceHeader/index.tsx @@ -22,7 +22,7 @@ import { Tooltip } from 'src/components/Tooltip'; import { useSelector } from 'react-redux'; import EditableTitle from 'src/components/EditableTitle'; import SliceHeaderControls from 'src/dashboard/components/SliceHeaderControls'; -import FiltersBadge from 'src/dashboard/containers/FiltersBadge'; +import FiltersBadge from 'src/dashboard/components/FiltersBadge'; import Icon from 'src/components/Icon'; import { RootState } from 'src/dashboard/types'; import FilterIndicator from 'src/dashboard/components/FiltersBadge/FilterIndicator'; diff --git a/superset-frontend/src/dashboard/components/gridComponents/ChartHolder.jsx b/superset-frontend/src/dashboard/components/gridComponents/ChartHolder.jsx index 01be44f91ff..32b07953e1e 100644 --- a/superset-frontend/src/dashboard/components/gridComponents/ChartHolder.jsx +++ b/superset-frontend/src/dashboard/components/gridComponents/ChartHolder.jsx @@ -20,6 +20,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import cx from 'classnames'; import { useTheme } from '@superset-ui/core'; +import { useSelector } from 'react-redux'; import { getChartIdsInFilterScope } from 'src/dashboard/util/activeDashboardFilters'; import Chart from '../../containers/Chart'; @@ -30,13 +31,13 @@ import HoverMenu from '../menu/HoverMenu'; import ResizableContainer from '../resizable/ResizableContainer'; import getChartAndLabelComponentIdFromPath from '../../util/getChartAndLabelComponentIdFromPath'; import { componentShape } from '../../util/propShapes'; -import { ROW_TYPE, COLUMN_TYPE } from '../../util/componentTypes'; +import { COLUMN_TYPE, ROW_TYPE } from '../../util/componentTypes'; import { - GRID_MIN_COLUMN_COUNT, - GRID_MIN_ROW_UNITS, GRID_BASE_UNIT, GRID_GUTTER_SIZE, + GRID_MIN_COLUMN_COUNT, + GRID_MIN_ROW_UNITS, } from '../../util/constants'; const CHART_MARGIN = 32; @@ -72,6 +73,21 @@ const defaultProps = { directPathLastUpdated: 0, }; +/** + * Selects the chart scope of the filter input that has focus. + * + * @returns {{chartId: number, scope: { scope: string[], immune: string[] }} | null } + * the scope of the currently focused filter, if any + */ +function selectFocusedFilterScope(dashboardState, dashboardFilters) { + if (!dashboardState.focusedFilterField) return null; + const { chartId, column } = dashboardState.focusedFilterField; + return { + chartId, + scope: dashboardFilters[chartId].scopes[column], + }; +} + /** * Renders any styles necessary to highlight the chart's relationship to the focused filter. * @@ -85,8 +101,16 @@ const defaultProps = { * If ChartHolder were a function component, this could be implemented as a hook instead. */ const FilterFocusHighlight = React.forwardRef( - ({ chartId, focusedFilterScope, nativeFilters, ...otherProps }, ref) => { + ({ chartId, ...otherProps }, ref) => { const theme = useTheme(); + + const nativeFilters = useSelector(state => state.nativeFilters); + const dashboardState = useSelector(state => state.dashboardState); + const dashboardFilters = useSelector(state => state.dashboardFilters); + const focusedFilterScope = selectFocusedFilterScope( + dashboardState, + dashboardFilters, + ); const focusedNativeFilterId = nativeFilters.focusedFilterId; if (!(focusedFilterScope || focusedNativeFilterId)) return
; @@ -239,8 +263,6 @@ class ChartHolder extends React.Component { editMode, isComponentVisible, dashboardId, - focusedFilterScope, - nativeFilters, } = this.props; // inherit the size of parent columns @@ -298,8 +320,6 @@ class ChartHolder extends React.Component { > jest.fn(props => ( diff --git a/superset-frontend/src/dashboard/components/gridComponents/index.js b/superset-frontend/src/dashboard/components/gridComponents/index.js index 27f419c9dd9..ce014f8d57c 100644 --- a/superset-frontend/src/dashboard/components/gridComponents/index.js +++ b/superset-frontend/src/dashboard/components/gridComponents/index.js @@ -34,7 +34,7 @@ import Divider from './Divider'; import Header from './Header'; import Row from './Row'; import Tab from './Tab'; -import Tabs from './Tabs'; +import TabsConnected from './Tabs'; export { default as ChartHolder } from './ChartHolder'; export { default as Markdown } from './Markdown'; @@ -53,5 +53,5 @@ export const componentLookup = { [HEADER_TYPE]: Header, [ROW_TYPE]: Row, [TAB_TYPE]: Tab, - [TABS_TYPE]: Tabs, + [TABS_TYPE]: TabsConnected, }; diff --git a/superset-frontend/src/dashboard/containers/DashboardComponent.jsx b/superset-frontend/src/dashboard/containers/DashboardComponent.jsx index 296b3bd1413..eb31a724882 100644 --- a/superset-frontend/src/dashboard/containers/DashboardComponent.jsx +++ b/superset-frontend/src/dashboard/containers/DashboardComponent.jsx @@ -64,29 +64,8 @@ const defaultProps = { isComponentVisible: true, }; -/** - * Selects the chart scope of the filter input that has focus. - * - * @returns {{chartId: number, scope: { scope: string[], immune: string[] }} | null } - * the scope of the currently focused filter, if any - */ -function selectFocusedFilterScope(dashboardState, dashboardFilters) { - if (!dashboardState.focusedFilterField) return null; - const { chartId, column } = dashboardState.focusedFilterField; - return { - chartId, - scope: dashboardFilters[chartId].scopes[column], - }; -} - function mapStateToProps( - { - dashboardLayout: undoableLayout, - dashboardState, - dashboardInfo, - dashboardFilters, - nativeFilters, - }, + { dashboardLayout: undoableLayout, dashboardState, dashboardInfo }, ownProps, ) { const dashboardLayout = undoableLayout.present; @@ -104,11 +83,6 @@ function mapStateToProps( activeTabs: dashboardState.activeTabs, directPathLastUpdated: dashboardState.directPathLastUpdated, dashboardId: dashboardInfo.id, - nativeFilters, - focusedFilterScope: selectFocusedFilterScope( - dashboardState, - dashboardFilters, - ), }; // rows and columns need more data about their child dimensions diff --git a/superset-frontend/src/dashboard/containers/FiltersBadge.tsx b/superset-frontend/src/dashboard/containers/FiltersBadge.tsx deleted file mode 100644 index 546b96303f8..00000000000 --- a/superset-frontend/src/dashboard/containers/FiltersBadge.tsx +++ /dev/null @@ -1,115 +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 { connect } from 'react-redux'; -import { bindActionCreators, Dispatch } from 'redux'; -import { uniqWith } from 'lodash'; -import { setDirectPathToChild } from 'src/dashboard/actions/dashboardState'; -import { - selectIndicatorsForChart, - Indicator, - IndicatorStatus, - selectNativeIndicatorsForChart, -} from 'src/dashboard/components/FiltersBadge/selectors'; -import FiltersBadge from 'src/dashboard/components/FiltersBadge'; - -export interface FiltersBadgeProps { - chartId: number; -} - -const mapDispatchToProps = (dispatch: Dispatch) => - bindActionCreators( - { - onHighlightFilterSource: setDirectPathToChild, - }, - dispatch, - ); - -const sortByStatus = (indicators: Indicator[]): Indicator[] => { - const statuses = [ - IndicatorStatus.Applied, - IndicatorStatus.Unset, - IndicatorStatus.Incompatible, - ]; - return indicators.sort( - (a, b) => - statuses.indexOf(a.status as IndicatorStatus) - - statuses.indexOf(b.status as IndicatorStatus), - ); -}; - -const mapStateToProps = ( - { - datasources, - dashboardFilters, - nativeFilters, - dashboardInfo, - charts, - dataMask, - dashboardLayout: { present }, - }: any, - { chartId }: FiltersBadgeProps, -) => { - const dashboardIndicators = selectIndicatorsForChart( - chartId, - dashboardFilters, - datasources, - charts, - ); - - const nativeIndicators = selectNativeIndicatorsForChart( - nativeFilters, - dataMask, - chartId, - charts, - present, - dashboardInfo.metadata?.chart_configuration, - ); - - const indicators = uniqWith( - sortByStatus([...dashboardIndicators, ...nativeIndicators]), - (ind1, ind2) => - ind1.column === ind2.column && - ind1.name === ind2.name && - (ind1.status !== IndicatorStatus.Applied || - ind2.status !== IndicatorStatus.Applied), - ); - - const appliedCrossFilterIndicators = indicators.filter( - indicator => indicator.status === IndicatorStatus.CrossFilterApplied, - ); - const appliedIndicators = indicators.filter( - indicator => indicator.status === IndicatorStatus.Applied, - ); - const unsetIndicators = indicators.filter( - indicator => indicator.status === IndicatorStatus.Unset, - ); - const incompatibleIndicators = indicators.filter( - indicator => indicator.status === IndicatorStatus.Incompatible, - ); - - return { - chartId, - appliedIndicators, - appliedCrossFilterIndicators, - unsetIndicators, - incompatibleIndicators, - }; -}; - -export default connect(mapStateToProps, mapDispatchToProps)(FiltersBadge);