You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by kg...@apache.org on 2023/03/10 19:58:33 UTC
[superset] branch master updated: feat(dashboard): Refactor FiltersBadge (#23286)
This is an automated email from the ASF dual-hosted git repository.
kgabryje pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/master by this push:
new c2b282ac71 feat(dashboard): Refactor FiltersBadge (#23286)
c2b282ac71 is described below
commit c2b282ac71eb84efe82ef550d9559d409153313e
Author: Kamil Gabryjelski <ka...@gmail.com>
AuthorDate: Fri Mar 10 20:58:24 2023 +0100
feat(dashboard): Refactor FiltersBadge (#23286)
---
.../integration/dashboard/nativeFilters.test.ts | 7 +-
.../DetailsPanel/DetailsPanel.test.tsx | 80 +-------
.../components/FiltersBadge/DetailsPanel/index.tsx | 211 +++++----------------
.../FiltersBadge/FilterIndicator/index.tsx | 49 ++---
.../components/FiltersBadge/FiltersBadge.test.tsx | 63 ------
.../dashboard/components/FiltersBadge/Styles.tsx | 106 ++++-------
.../dashboard/components/FiltersBadge/index.tsx | 47 +----
.../src/dashboard/components/SliceHeader/index.tsx | 30 +--
.../FilterBar/CrossFilters/Vertical.tsx | 10 +-
.../FilterBar/CrossFilters/selectors.ts | 46 +++--
.../FilterBar/FilterControls/FilterControls.tsx | 10 +-
.../nativeFilters/FilterBar/Horizontal.tsx | 9 +-
.../components/nativeFilters/selectors.ts | 93 +++++----
13 files changed, 237 insertions(+), 524 deletions(-)
diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilters.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilters.test.ts
index 7e1d3d5371..1e119ae775 100644
--- a/superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilters.test.ts
+++ b/superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilters.test.ts
@@ -378,10 +378,13 @@ describe('Horizontal FilterBar', () => {
{ name: 'test_12', column: 'year', datasetId: 2 },
]);
setFilterBarOrientation('horizontal');
+ openMoreFilters();
+ applyNativeFilterValueWithIndex(8, testItems.filterDefaultValue);
+ cy.get(nativeFilters.applyFilter).click({ force: true });
cy.getBySel('slice-header').within(() => {
- cy.get('.filter-counts').click();
+ cy.get('.filter-counts').trigger('mouseover');
});
- cy.get('.filterStatusPopover').contains('test_8').click();
+ cy.get('.filterStatusPopover').contains('test_9').click();
cy.getBySel('dropdown-content').should('be.visible');
cy.get('.ant-select-focused').should('be.visible');
});
diff --git a/superset-frontend/src/dashboard/components/FiltersBadge/DetailsPanel/DetailsPanel.test.tsx b/superset-frontend/src/dashboard/components/FiltersBadge/DetailsPanel/DetailsPanel.test.tsx
index 3c3e25ee56..ef4b49d218 100644
--- a/superset-frontend/src/dashboard/components/FiltersBadge/DetailsPanel/DetailsPanel.test.tsx
+++ b/superset-frontend/src/dashboard/components/FiltersBadge/DetailsPanel/DetailsPanel.test.tsx
@@ -86,7 +86,7 @@ const createProps = () => ({
onHighlightFilterSource: jest.fn(),
});
-test('Should render "appliedCrossFilterIndicators"', () => {
+test('Should render "appliedCrossFilterIndicators"', async () => {
const props = createProps();
props.appliedIndicators = [];
props.incompatibleIndicators = [];
@@ -99,8 +99,10 @@ test('Should render "appliedCrossFilterIndicators"', () => {
{ useRedux: true },
);
- userEvent.click(screen.getByTestId('details-panel-content'));
- expect(screen.getByText('Applied Cross Filters (1)')).toBeInTheDocument();
+ userEvent.hover(screen.getByTestId('details-panel-content'));
+ expect(
+ await screen.findByText('Applied cross-filters (1)'),
+ ).toBeInTheDocument();
expect(
screen.getByRole('button', { name: 'Clinical Stage' }),
).toBeInTheDocument();
@@ -118,7 +120,7 @@ test('Should render "appliedCrossFilterIndicators"', () => {
]);
});
-test('Should render "appliedIndicators"', () => {
+test('Should render "appliedIndicators"', async () => {
const props = createProps();
props.appliedCrossFilterIndicators = [];
props.incompatibleIndicators = [];
@@ -131,8 +133,8 @@ test('Should render "appliedIndicators"', () => {
{ useRedux: true },
);
- userEvent.click(screen.getByTestId('details-panel-content'));
- expect(screen.getByText('Applied Filters (1)')).toBeInTheDocument();
+ userEvent.hover(screen.getByTestId('details-panel-content'));
+ expect(await screen.findByText('Applied filters (1)')).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Country' })).toBeInTheDocument();
expect(props.onHighlightFilterSource).toBeCalledTimes(0);
@@ -148,72 +150,6 @@ test('Should render "appliedIndicators"', () => {
]);
});
-test('Should render "incompatibleIndicators"', () => {
- const props = createProps();
- props.appliedCrossFilterIndicators = [];
- props.appliedIndicators = [];
- props.unsetIndicators = [];
-
- render(
- <DetailsPanel {...props}>
- <div data-test="details-panel-content">Content</div>
- </DetailsPanel>,
- { useRedux: true },
- );
-
- userEvent.click(screen.getByTestId('details-panel-content'));
- expect(screen.getByText('Incompatible Filters (1)')).toBeInTheDocument();
- expect(
- screen.getByRole('button', { name: 'Vaccine Approach Copy' }),
- ).toBeInTheDocument();
-
- expect(props.onHighlightFilterSource).toBeCalledTimes(0);
- userEvent.click(
- screen.getByRole('button', { name: 'Vaccine Approach Copy' }),
- );
- expect(props.onHighlightFilterSource).toBeCalledTimes(1);
- expect(props.onHighlightFilterSource).toBeCalledWith([
- 'ROOT_ID',
- 'TABS-wUKya7eQ0Zz',
- 'TAB-BCIJF4NvgQq',
- 'ROW-xSeNAspgww',
- 'CHART-eirDduqb1Aa',
- 'LABEL-product_category_copy',
- ]);
-});
-
-test('Should render "unsetIndicators"', () => {
- const props = createProps();
- props.appliedCrossFilterIndicators = [];
- props.appliedIndicators = [];
- props.incompatibleIndicators = [];
-
- render(
- <DetailsPanel {...props}>
- <div data-test="details-panel-content">Content</div>
- </DetailsPanel>,
- { useRedux: true },
- );
-
- userEvent.click(screen.getByTestId('details-panel-content'));
- expect(screen.getByText('Unset Filters (1)')).toBeInTheDocument();
- expect(
- screen.getByRole('button', { name: 'Vaccine Approach' }),
- ).toBeInTheDocument();
-
- expect(props.onHighlightFilterSource).toBeCalledTimes(0);
- userEvent.click(screen.getByRole('button', { name: 'Vaccine Approach' }));
- expect(props.onHighlightFilterSource).toBeCalledTimes(1);
- expect(props.onHighlightFilterSource).toBeCalledWith([
- 'ROOT_ID',
- 'TABS-wUKya7eQ0Z',
- 'TAB-BCIJF4NvgQ',
- 'ROW-xSeNAspgw',
- 'CHART-eirDduqb1A',
- 'LABEL-product_category',
- ]);
-});
-
test('Should render empty', () => {
const props = createProps();
props.appliedCrossFilterIndicators = [];
diff --git a/superset-frontend/src/dashboard/components/FiltersBadge/DetailsPanel/index.tsx b/superset-frontend/src/dashboard/components/FiltersBadge/DetailsPanel/index.tsx
index 022dab7968..53d20cd1f8 100644
--- a/superset-frontend/src/dashboard/components/FiltersBadge/DetailsPanel/index.tsx
+++ b/superset-frontend/src/dashboard/components/FiltersBadge/DetailsPanel/index.tsx
@@ -19,31 +19,21 @@
import React, { useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import { Global, css } from '@emotion/react';
-import { t, useTheme } from '@superset-ui/core';
+import { t } from '@superset-ui/core';
import Popover from 'src/components/Popover';
-import Collapse from 'src/components/Collapse';
-import Icons from 'src/components/Icons';
import {
- Indent,
- Panel,
- Reset,
- Title,
+ FiltersContainer,
+ FiltersDetailsContainer,
+ Separator,
+ SectionName,
} from 'src/dashboard/components/FiltersBadge/Styles';
import { Indicator } from 'src/dashboard/components/nativeFilters/selectors';
import FilterIndicator from 'src/dashboard/components/FiltersBadge/FilterIndicator';
import { RootState } from 'src/dashboard/types';
-const iconReset = css`
- span {
- line-height: 0;
- }
-`;
-
export interface DetailsPanelProps {
appliedCrossFilterIndicators: Indicator[];
appliedIndicators: Indicator[];
- incompatibleIndicators: Indicator[];
- unsetIndicators: Indicator[];
onHighlightFilterSource: (path: string[]) => void;
children: JSX.Element;
}
@@ -51,13 +41,10 @@ export interface DetailsPanelProps {
const DetailsPanelPopover = ({
appliedCrossFilterIndicators = [],
appliedIndicators = [],
- incompatibleIndicators = [],
- unsetIndicators = [],
onHighlightFilterSource,
children,
}: DetailsPanelProps) => {
const [visible, setVisible] = useState(false);
- const theme = useTheme();
const activeTabs = useSelector<RootState>(
state => state.dashboardState?.activeTabs,
);
@@ -76,57 +63,22 @@ const DetailsPanelPopover = ({
setVisible(false);
}, [activeTabs]);
- const getDefaultActivePanel = () => {
- const result = [];
- if (appliedCrossFilterIndicators.length) {
- result.push('appliedCrossFilters');
- }
- if (appliedIndicators.length) {
- result.push('applied');
- }
- if (incompatibleIndicators.length) {
- result.push('incompatible');
- }
- if (result.length) {
- return result;
- }
- return ['unset'];
- };
-
- const [activePanels, setActivePanels] = useState<string[]>(() => [
- ...getDefaultActivePanel(),
- ]);
-
function handlePopoverStatus(isOpen: boolean) {
setVisible(isOpen);
- // every time the popover opens, make sure the most relevant panel is active
- if (isOpen) {
- setActivePanels(getDefaultActivePanel());
- }
- }
-
- function handleActivePanelChange(panels: string | string[]) {
- // need to convert to an array so that handlePopoverStatus will work
- if (typeof panels === 'string') {
- setActivePanels([panels]);
- } else {
- setActivePanels(panels);
- }
}
const indicatorKey = (indicator: Indicator): string =>
`${indicator.column} - ${indicator.name}`;
const content = (
- <Panel>
+ <FiltersDetailsContainer>
<Global
- styles={css`
+ styles={theme => css`
.filterStatusPopover {
.ant-popover-inner {
background-color: ${theme.colors.grayscale.dark2}cc;
.ant-popover-inner-content {
- padding-top: 0;
- padding-bottom: 0;
+ padding: ${theme.gridUnit * 2}px;
}
}
&.ant-popover-placement-bottom,
@@ -168,110 +120,47 @@ const DetailsPanelPopover = ({
}
`}
/>
- <Reset>
- <Collapse
- ghost
- light
- activeKey={activePanels}
- onChange={handleActivePanelChange}
- >
- {appliedCrossFilterIndicators.length ? (
- <Collapse.Panel
- key="appliedCrossFilters"
- header={
- <Title bold color={theme.colors.primary.light1}>
- <Icons.CursorTarget
- css={{ fill: theme.colors.primary.light1 }}
- iconSize="xl"
- />
- {t(
- 'Applied Cross Filters (%d)',
- appliedCrossFilterIndicators.length,
- )}
- </Title>
- }
- >
- <Indent css={{ paddingBottom: theme.gridUnit * 3 }}>
- {appliedCrossFilterIndicators.map(indicator => (
- <FilterIndicator
- key={indicatorKey(indicator)}
- indicator={indicator}
- onClick={onHighlightFilterSource}
- />
- ))}
- </Indent>
- </Collapse.Panel>
- ) : null}
- {appliedIndicators.length ? (
- <Collapse.Panel
- key="applied"
- header={
- <Title bold color={theme.colors.success.base}>
- <Icons.CheckCircleFilled css={iconReset} iconSize="m" />{' '}
- {t('Applied Filters (%d)', appliedIndicators.length)}
- </Title>
- }
- >
- <Indent css={{ paddingBottom: theme.gridUnit * 3 }}>
- {appliedIndicators.map(indicator => (
- <FilterIndicator
- key={indicatorKey(indicator)}
- indicator={indicator}
- onClick={onHighlightFilterSource}
- />
- ))}
- </Indent>
- </Collapse.Panel>
- ) : null}
- {incompatibleIndicators.length ? (
- <Collapse.Panel
- key="incompatible"
- header={
- <Title bold color={theme.colors.alert.base}>
- <Icons.ExclamationCircleFilled css={iconReset} iconSize="m" />{' '}
- {t(
- 'Incompatible Filters (%d)',
- incompatibleIndicators.length,
- )}
- </Title>
- }
- >
- <Indent css={{ paddingBottom: theme.gridUnit * 3 }}>
- {incompatibleIndicators.map(indicator => (
- <FilterIndicator
- key={indicatorKey(indicator)}
- indicator={indicator}
- onClick={onHighlightFilterSource}
- />
- ))}
- </Indent>
- </Collapse.Panel>
- ) : null}
- {unsetIndicators.length ? (
- <Collapse.Panel
- key="unset"
- header={
- <Title bold color={theme.colors.grayscale.light1}>
- <Icons.MinusCircleFilled css={iconReset} iconSize="m" />{' '}
- {t('Unset Filters (%d)', unsetIndicators.length)}
- </Title>
- }
- disabled={!unsetIndicators.length}
- >
- <Indent css={{ paddingBottom: theme.gridUnit * 3 }}>
- {unsetIndicators.map(indicator => (
- <FilterIndicator
- key={indicatorKey(indicator)}
- indicator={indicator}
- onClick={onHighlightFilterSource}
- />
- ))}
- </Indent>
- </Collapse.Panel>
- ) : null}
- </Collapse>
- </Reset>
- </Panel>
+ <div>
+ {appliedCrossFilterIndicators.length ? (
+ <div>
+ <SectionName>
+ {t(
+ 'Applied cross-filters (%d)',
+ appliedCrossFilterIndicators.length,
+ )}
+ </SectionName>
+ <FiltersContainer>
+ {appliedCrossFilterIndicators.map(indicator => (
+ <FilterIndicator
+ key={indicatorKey(indicator)}
+ indicator={indicator}
+ onClick={onHighlightFilterSource}
+ />
+ ))}
+ </FiltersContainer>
+ </div>
+ ) : null}
+ {appliedCrossFilterIndicators.length && appliedIndicators.length ? (
+ <Separator />
+ ) : null}
+ {appliedIndicators.length ? (
+ <div>
+ <SectionName>
+ {t('Applied filters (%d)', appliedIndicators.length)}
+ </SectionName>
+ <FiltersContainer>
+ {appliedIndicators.map(indicator => (
+ <FilterIndicator
+ key={indicatorKey(indicator)}
+ indicator={indicator}
+ onClick={onHighlightFilterSource}
+ />
+ ))}
+ </FiltersContainer>
+ </div>
+ ) : null}
+ </div>
+ </FiltersDetailsContainer>
);
return (
@@ -281,7 +170,7 @@ const DetailsPanelPopover = ({
visible={visible}
onVisibleChange={handlePopoverStatus}
placement="bottomRight"
- trigger="click"
+ trigger="hover"
>
{children}
</Popover>
diff --git a/superset-frontend/src/dashboard/components/FiltersBadge/FilterIndicator/index.tsx b/superset-frontend/src/dashboard/components/FiltersBadge/FilterIndicator/index.tsx
index 2954474e64..ed09e16790 100644
--- a/superset-frontend/src/dashboard/components/FiltersBadge/FilterIndicator/index.tsx
+++ b/superset-frontend/src/dashboard/components/FiltersBadge/FilterIndicator/index.tsx
@@ -22,47 +22,48 @@ import { css } from '@superset-ui/core';
import Icons from 'src/components/Icons';
import { getFilterValueForDisplay } from 'src/dashboard/components/nativeFilters/FilterBar/FilterSets/utils';
import {
- FilterIndicatorText,
FilterValue,
- Item,
- ItemIcon,
- Title,
+ FilterItem,
+ FilterName,
} from 'src/dashboard/components/FiltersBadge/Styles';
import { Indicator } from 'src/dashboard/components/nativeFilters/selectors';
export interface IndicatorProps {
indicator: Indicator;
onClick?: (path: string[]) => void;
- text?: string;
}
const FilterIndicator: FC<IndicatorProps> = ({
indicator: { column, name, value, path = [] },
- onClick = () => {},
- text,
+ onClick,
}) => {
const resultValue = getFilterValueForDisplay(value);
return (
- <>
- <Item onClick={() => onClick([...path, `LABEL-${column}`])}>
- <Title bold>
- <ItemIcon>
- <Icons.SearchOutlined
- iconSize="m"
- css={css`
- span {
- vertical-align: 0;
- }
- `}
- />
- </ItemIcon>
+ <FilterItem
+ onClick={
+ onClick ? () => onClick([...path, `LABEL-${column}`]) : undefined
+ }
+ >
+ {onClick && (
+ <i>
+ <Icons.SearchOutlined
+ iconSize="m"
+ css={css`
+ span {
+ vertical-align: 0;
+ }
+ `}
+ />
+ </i>
+ )}
+ <div>
+ <FilterName>
{name}
{resultValue ? ': ' : ''}
- </Title>
+ </FilterName>
<FilterValue>{resultValue}</FilterValue>
- </Item>
- {text && <FilterIndicatorText>{text}</FilterIndicatorText>}
- </>
+ </div>
+ </FilterItem>
);
};
diff --git a/superset-frontend/src/dashboard/components/FiltersBadge/FiltersBadge.test.tsx b/superset-frontend/src/dashboard/components/FiltersBadge/FiltersBadge.test.tsx
index 2a1f7bcfb0..c9e6c9e1fc 100644
--- a/superset-frontend/src/dashboard/components/FiltersBadge/FiltersBadge.test.tsx
+++ b/superset-frontend/src/dashboard/components/FiltersBadge/FiltersBadge.test.tsx
@@ -36,7 +36,6 @@ import {
import { sliceId } from 'spec/fixtures/mockChartQueries';
import { dashboardFilters } from 'spec/fixtures/mockDashboardFilters';
import { dashboardWithFilter } from 'spec/fixtures/mockDashboardLayout';
-import Icons from 'src/components/Icons';
import { FeatureFlag } from 'src/featureFlags';
const defaultStore = getMockStoreWithFilters();
@@ -111,36 +110,6 @@ describe('FiltersBadge', () => {
);
expect(wrapper.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,
- queriesResponse: [
- {
- status: 'success',
- applied_filters: [],
- rejected_filters: [
- { column: 'region', reason: 'not_in_datasource' },
- ],
- },
- ],
- dashboardFilters,
- });
- 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.find('[data-test="incompatible-filter-count"]'),
- ).toHaveText('1');
- // to look at the shape of the wrapper use:
- expect(wrapper.find(Icons.AlertSolid)).toExist();
- });
});
describe('for native filters', () => {
@@ -189,37 +158,5 @@ describe('FiltersBadge', () => {
);
expect(wrapper.find('WarningFilled')).not.toExist();
});
-
- it("shows a warning when there's a rejected filter", () => {
- // @ts-ignore
- global.featureFlags = {
- [FeatureFlag.DASHBOARD_NATIVE_FILTERS]: true,
- };
- const store = getMockStoreWithNativeFilters();
- // start with basic dashboard state, dispatch an event to simulate query completion
- store.dispatch({
- type: CHART_UPDATE_SUCCEEDED,
- key: sliceId,
- queriesResponse: [
- {
- status: 'success',
- applied_filters: [],
- rejected_filters: [
- { column: 'region', reason: 'not_in_datasource' },
- ],
- },
- ],
- });
- 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.find('[data-test="incompatible-filter-count"]'),
- ).toHaveText('1');
- expect(wrapper.find(Icons.AlertSolid)).toExist();
- });
});
});
diff --git a/superset-frontend/src/dashboard/components/FiltersBadge/Styles.tsx b/superset-frontend/src/dashboard/components/FiltersBadge/Styles.tsx
index c80e0ec0cb..0018a007ba 100644
--- a/superset-frontend/src/dashboard/components/FiltersBadge/Styles.tsx
+++ b/superset-frontend/src/dashboard/components/FiltersBadge/Styles.tsx
@@ -20,7 +20,7 @@ import { css, styled } from '@superset-ui/core';
export const Pill = styled.div`
${({ theme }) => css`
- display: inline-block;
+ display: flex;
color: ${theme.colors.grayscale.light5};
background: ${theme.colors.grayscale.base};
border-radius: 1em;
@@ -36,7 +36,6 @@ export const Pill = styled.div`
svg {
position: relative;
- top: -2px;
color: ${theme.colors.grayscale.light5};
width: 1em;
height: 1em;
@@ -55,64 +54,27 @@ export const Pill = styled.div`
background: ${theme.colors.primary.dark1};
}
}
-
- &.has-incompatible-filters {
- color: ${theme.colors.grayscale.dark2};
- background: ${theme.colors.alert.base};
- &:hover {
- background: ${theme.colors.alert.dark1};
- }
- svg {
- color: ${theme.colors.grayscale.dark2};
- }
- }
-
- &.filters-inactive {
- color: ${theme.colors.grayscale.light5};
- background: ${theme.colors.grayscale.light1};
- padding: ${theme.gridUnit}px;
- text-align: center;
- height: 22px;
- width: 22px;
-
- &:hover {
- background: ${theme.colors.grayscale.base};
- }
- }
`}
`;
-export interface TitleProps {
- bold?: boolean;
- color?: string;
-}
-
-export const Title = styled.span<TitleProps>`
- position: relative;
- margin-right: ${({ theme }) => theme.gridUnit}px;
- font-weight: ${({ bold, theme }) => {
- if (bold) return theme.typography.weights.bold;
- return 'auto';
- }};
- color: ${({ color, theme }) => color || theme.colors.grayscale.light5};
- display: flex;
- align-items: center;
- & > * {
- margin-right: ${({ theme }) => theme.gridUnit}px;
- }
+export const SectionName = styled.span`
+ ${({ theme }) => css`
+ font-weight: ${theme.typography.weights.bold};
+ `}
`;
-
-export const ItemIcon = styled.i`
- position: absolute;
- top: 50%;
- transform: translateY(-50%);
- left: -${({ theme }) => theme.gridUnit * 5}px;
+export const FilterName = styled.span`
+ ${({ theme }) => css`
+ padding-right: ${theme.gridUnit}px;
+ font-style: italic;
+ & > * {
+ margin-right: ${theme.gridUnit}px;
+ }
+ `}
`;
-export const Item = styled.button`
+export const FilterItem = styled.button`
cursor: pointer;
display: flex;
- flex-wrap: wrap;
text-align: left;
padding: 0;
border: none;
@@ -134,34 +96,36 @@ export const Item = styled.button`
}
`;
-export const Reset = styled.div`
- margin: 0 -${({ theme }) => theme.gridUnit * 4}px;
+export const FiltersContainer = styled.div`
+ ${({ theme }) => css`
+ margin-top: ${theme.gridUnit}px;
+ &:not(:last-child) {
+ padding-bottom: ${theme.gridUnit * 3}px;
+ }
+ `}
`;
-export const Indent = styled.div`
- padding-left: ${({ theme }) => theme.gridUnit * 6}px;
- margin: -${({ theme }) => theme.gridUnit * 3}px 0;
-`;
+export const FiltersDetailsContainer = styled.div`
+ ${({ theme }) => css`
+ min-width: 200px;
+ max-width: 300px;
+ overflow-x: hidden;
-export const Panel = styled.div`
- min-width: 200px;
- max-width: 300px;
- overflow-x: hidden;
+ color: ${theme.colors.grayscale.light5};
+ `}
`;
-export const FilterValue = styled.div`
+export const FilterValue = styled.span`
max-width: 100%;
flex-grow: 1;
overflow: auto;
- color: ${({ theme }) => theme.colors.grayscale.light5};
`;
-export const FilterIndicatorText = styled.div`
- ${({ theme }) => `
- padding-top: ${theme.gridUnit * 3}px;
- max-width: 100%;
- flex-grow: 1;
- overflow: auto;
- color: ${theme.colors.grayscale.light5};
+export const Separator = styled.div`
+ ${({ theme }) => css`
+ width: 100%;
+ height: 1px;
+ background-color: ${theme.colors.grayscale.light1};
+ margin: ${theme.gridUnit * 4}px 0;
`}
`;
diff --git a/superset-frontend/src/dashboard/components/FiltersBadge/index.tsx b/superset-frontend/src/dashboard/components/FiltersBadge/index.tsx
index fb0718bf54..655a97c7d8 100644
--- a/superset-frontend/src/dashboard/components/FiltersBadge/index.tsx
+++ b/superset-frontend/src/dashboard/components/FiltersBadge/index.tsx
@@ -211,66 +211,27 @@ export const FiltersBadge = ({ chartId }: FiltersBadgeProps) => {
),
[indicators],
);
- const unsetIndicators = useMemo(
- () =>
- indicators.filter(
- indicator => indicator.status === IndicatorStatus.Unset,
- ),
- [indicators],
- );
- const incompatibleIndicators = useMemo(
- () =>
- indicators.filter(
- indicator => indicator.status === IndicatorStatus.Incompatible,
- ),
- [indicators],
- );
- if (
- !appliedCrossFilterIndicators.length &&
- !appliedIndicators.length &&
- !incompatibleIndicators.length &&
- !unsetIndicators.length
- ) {
+ if (!appliedCrossFilterIndicators.length && !appliedIndicators.length) {
return null;
}
- const isInactive =
- !appliedCrossFilterIndicators.length &&
- !appliedIndicators.length &&
- !incompatibleIndicators.length;
-
return (
<DetailsPanelPopover
appliedCrossFilterIndicators={appliedCrossFilterIndicators}
appliedIndicators={appliedIndicators}
- unsetIndicators={unsetIndicators}
- incompatibleIndicators={incompatibleIndicators}
onHighlightFilterSource={onHighlightFilterSource}
>
<Pill
className={cx(
'filter-counts',
- !!incompatibleIndicators.length && 'has-incompatible-filters',
!!appliedCrossFilterIndicators.length && 'has-cross-filters',
- isInactive && 'filters-inactive',
)}
>
<Icons.Filter iconSize="m" />
- {!isInactive && (
- <span data-test="applied-filter-count">
- {appliedIndicators.length + appliedCrossFilterIndicators.length}
- </span>
- )}
- {incompatibleIndicators.length ? (
- <>
- {' '}
- <Icons.AlertSolid />
- <span data-test="incompatible-filter-count">
- {incompatibleIndicators.length}
- </span>
- </>
- ) : null}
+ <span data-test="applied-filter-count">
+ {appliedIndicators.length + appliedCrossFilterIndicators.length}
+ </span>
</Pill>
</DetailsPanelPopover>
);
diff --git a/superset-frontend/src/dashboard/components/SliceHeader/index.tsx b/superset-frontend/src/dashboard/components/SliceHeader/index.tsx
index a928933b54..41b6a7dbe7 100644
--- a/superset-frontend/src/dashboard/components/SliceHeader/index.tsx
+++ b/superset-frontend/src/dashboard/components/SliceHeader/index.tsx
@@ -21,11 +21,10 @@ import React, {
ReactNode,
useContext,
useEffect,
- useMemo,
useRef,
useState,
} from 'react';
-import { css, styled, t } from '@superset-ui/core';
+import { css, styled, SupersetTheme, t } from '@superset-ui/core';
import { useUiConfig } from 'src/components/UiConfigContext';
import { Tooltip } from 'src/components/Tooltip';
import { useDispatch, useSelector } from 'react-redux';
@@ -36,10 +35,10 @@ import SliceHeaderControls, {
import FiltersBadge from 'src/dashboard/components/FiltersBadge';
import Icons from 'src/components/Icons';
import { RootState } from 'src/dashboard/types';
-import FilterIndicator from 'src/dashboard/components/FiltersBadge/FilterIndicator';
import { getSliceHeaderTooltip } from 'src/dashboard/util/getSliceHeaderTooltip';
import { DashboardPageIdContext } from 'src/dashboard/containers/DashboardPage';
import { clearDataMask } from 'src/dataMask/actions';
+import { getFilterValueForDisplay } from '../nativeFilters/FilterBar/FilterSets/utils';
type SliceHeaderProps = SliceHeaderControlsProps & {
innerRef?: string;
@@ -173,14 +172,6 @@ const SliceHeader: FC<SliceHeaderProps> = ({
({ dashboardInfo }) => dashboardInfo.crossFiltersEnabled,
);
- const indicator = useMemo(
- () => ({
- value: crossFilterValue,
- name: t('Emitted values'),
- }),
- [crossFilterValue],
- );
-
const canExplore = !editMode && supersetCanExplore;
useEffect(() => {
@@ -251,10 +242,19 @@ const SliceHeader: FC<SliceHeaderProps> = ({
<Tooltip
placement="top"
title={
- <FilterIndicator
- indicator={indicator}
- text={t('Click to clear emitted filters')}
- />
+ <div>
+ <span>{t('Emitted values: ')}</span>
+ <span>{getFilterValueForDisplay(crossFilterValue)}</span>
+ <div
+ css={(theme: SupersetTheme) =>
+ css`
+ margin-top: ${theme.gridUnit * 2}px;
+ `
+ }
+ >
+ {t('Click to clear emitted filters')}
+ </div>
+ </div>
}
>
<CrossFilterIcon
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/Vertical.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/Vertical.tsx
index 93fb649d68..907e73c39e 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/Vertical.tsx
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/Vertical.tsx
@@ -18,9 +18,9 @@
*/
import React from 'react';
-import { DataMaskStateWithId } from '@superset-ui/core';
+import { DataMaskStateWithId, JsonObject } from '@superset-ui/core';
import { useSelector } from 'react-redux';
-import { DashboardInfo, DashboardLayout, RootState } from 'src/dashboard/types';
+import { DashboardLayout, RootState } from 'src/dashboard/types';
import crossFiltersSelector from './selectors';
import VerticalCollapse from './VerticalCollapse';
@@ -28,15 +28,15 @@ const CrossFiltersVertical = () => {
const dataMask = useSelector<RootState, DataMaskStateWithId>(
state => state.dataMask,
);
- const dashboardInfo = useSelector<RootState, DashboardInfo>(
- state => state.dashboardInfo,
+ const chartConfiguration = useSelector<RootState, JsonObject>(
+ state => state.dashboardInfo.metadata?.chart_configuration,
);
const dashboardLayout = useSelector<RootState, DashboardLayout>(
state => state.dashboardLayout.present,
);
const selectedCrossFilters = crossFiltersSelector({
dataMask,
- dashboardInfo,
+ chartConfiguration,
dashboardLayout,
});
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/selectors.ts b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/selectors.ts
index c0f45af5b3..bf19ecabf9 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/selectors.ts
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/CrossFilters/selectors.ts
@@ -17,37 +17,35 @@
* under the License.
*/
-import { DataMaskStateWithId } from '@superset-ui/core';
-import { DashboardInfo, DashboardLayout } from 'src/dashboard/types';
-import { CrossFilterIndicator, selectChartCrossFilters } from '../../selectors';
+import { DataMaskStateWithId, isDefined, JsonObject } from '@superset-ui/core';
+import { DashboardLayout } from 'src/dashboard/types';
+import { CrossFilterIndicator, getCrossFilterIndicator } from '../../selectors';
export const crossFiltersSelector = (props: {
dataMask: DataMaskStateWithId;
- dashboardInfo: DashboardInfo;
+ chartConfiguration: JsonObject;
dashboardLayout: DashboardLayout;
}): CrossFilterIndicator[] => {
- const { dataMask, dashboardInfo, dashboardLayout } = props;
- const chartConfiguration = dashboardInfo.metadata?.chart_configuration;
+ const { dataMask, chartConfiguration, dashboardLayout } = props;
const chartsIds = Object.keys(chartConfiguration);
- const shouldFilterEmitters = true;
- let selectedCrossFilters: CrossFilterIndicator[] = [];
-
- for (let i = 0; i < chartsIds.length; i += 1) {
- const chartId = Number(chartsIds[i]);
- const crossFilters = selectChartCrossFilters(
- dataMask,
- chartId,
- dashboardLayout,
- chartConfiguration,
- shouldFilterEmitters,
- );
- selectedCrossFilters = [
- ...selectedCrossFilters,
- ...(crossFilters as CrossFilterIndicator[]),
- ];
- }
- return selectedCrossFilters;
+ return chartsIds
+ .map(chartId => {
+ const id = Number(chartId);
+ const filterIndicator = getCrossFilterIndicator(
+ id,
+ dataMask[id],
+ dashboardLayout,
+ );
+ if (
+ isDefined(filterIndicator.column) &&
+ isDefined(filterIndicator.value)
+ ) {
+ return { ...filterIndicator, emitterId: id };
+ }
+ return null;
+ })
+ .filter(Boolean) as CrossFilterIndicator[];
};
export default crossFiltersSelector;
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterControls.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterControls.tsx
index 589e7609e8..b44591f4b1 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterControls.tsx
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterControls.tsx
@@ -35,6 +35,7 @@ import {
isFeatureEnabled,
FeatureFlag,
isNativeFilterWithDataMask,
+ JsonObject,
} from '@superset-ui/core';
import {
createHtmlPortalNode,
@@ -47,7 +48,6 @@ import {
useSelectFiltersInScope,
} from 'src/dashboard/components/nativeFilters/state';
import {
- DashboardInfo,
DashboardLayout,
FilterBarOrientation,
RootState,
@@ -87,8 +87,8 @@ const FilterControls: FC<FilterControlsProps> = ({
const dataMask = useSelector<RootState, DataMaskStateWithId>(
state => state.dataMask,
);
- const dashboardInfo = useSelector<RootState, DashboardInfo>(
- state => state.dashboardInfo,
+ const chartConfiguration = useSelector<RootState, JsonObject>(
+ state => state.dashboardInfo.metadata?.chart_configuration,
);
const dashboardLayout = useSelector<RootState, DashboardLayout>(
state => state.dashboardLayout.present,
@@ -101,11 +101,11 @@ const FilterControls: FC<FilterControlsProps> = ({
isCrossFiltersEnabled
? crossFiltersSelector({
dataMask,
- dashboardInfo,
+ chartConfiguration,
dashboardLayout,
})
: [],
- [dashboardInfo, dashboardLayout, dataMask, isCrossFiltersEnabled],
+ [chartConfiguration, dashboardLayout, dataMask, isCrossFiltersEnabled],
);
const { filterControlFactory, filtersWithValues } = useFilterControlFactory(
dataMaskSelected,
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Horizontal.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Horizontal.tsx
index badff64295..24b2caa033 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Horizontal.tsx
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/Horizontal.tsx
@@ -22,12 +22,13 @@ import {
DataMaskStateWithId,
FeatureFlag,
isFeatureEnabled,
+ JsonObject,
styled,
t,
} from '@superset-ui/core';
import Icons from 'src/components/Icons';
import Loading from 'src/components/Loading';
-import { DashboardInfo, DashboardLayout, RootState } from 'src/dashboard/types';
+import { DashboardLayout, RootState } from 'src/dashboard/types';
import { useSelector } from 'react-redux';
import FilterControls from './FilterControls/FilterControls';
import { getFilterBarTestId } from './utils';
@@ -107,8 +108,8 @@ const HorizontalFilterBar: React.FC<HorizontalBarProps> = ({
const dataMask = useSelector<RootState, DataMaskStateWithId>(
state => state.dataMask,
);
- const dashboardInfo = useSelector<RootState, DashboardInfo>(
- state => state.dashboardInfo,
+ const chartConfiguration = useSelector<RootState, JsonObject>(
+ state => state.dashboardInfo.metadata?.chart_configuration,
);
const dashboardLayout = useSelector<RootState, DashboardLayout>(
state => state.dashboardLayout.present,
@@ -119,7 +120,7 @@ const HorizontalFilterBar: React.FC<HorizontalBarProps> = ({
const selectedCrossFilters = isCrossFiltersEnabled
? crossFiltersSelector({
dataMask,
- dashboardInfo,
+ chartConfiguration,
dashboardLayout,
})
: [];
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/selectors.ts b/superset-frontend/src/dashboard/components/nativeFilters/selectors.ts
index 27845727aa..5d4e980a40 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/selectors.ts
+++ b/superset-frontend/src/dashboard/components/nativeFilters/selectors.ts
@@ -17,6 +17,7 @@
* under the License.
*/
import {
+ DataMask,
DataMaskStateWithId,
DataMaskType,
ensureIsArray,
@@ -32,7 +33,7 @@ import {
import { TIME_FILTER_MAP } from 'src/explore/constants';
import { getChartIdsInFilterBoxScope } from 'src/dashboard/util/activeDashboardFilters';
import { ChartConfiguration } from 'src/dashboard/reducers/types';
-import { Layout } from 'src/dashboard/types';
+import { DashboardLayout, Layout } from 'src/dashboard/types';
import { areObjectsEqual } from 'src/reduxUtils';
export enum IndicatorStatus {
@@ -61,7 +62,7 @@ type Filter = {
datasourceId: string;
};
-const extractLabel = (filter?: FilterState): string | null => {
+export const extractLabel = (filter?: FilterState): string | null => {
if (filter?.label && !filter?.label?.includes(undefined)) {
return filter.label;
}
@@ -162,6 +163,36 @@ export type Indicator = {
export type CrossFilterIndicator = Indicator & { emitterId: number };
+export const getCrossFilterIndicator = (
+ chartId: number,
+ dataMask: DataMask,
+ dashboardLayout: DashboardLayout,
+) => {
+ const filterState = dataMask?.filterState;
+ const filters = dataMask?.extraFormData?.filters;
+ const label = extractLabel(filterState);
+ const filtersState = filterState?.filters;
+ const column =
+ filters?.[0]?.col || (filtersState && Object.keys(filtersState)[0]);
+
+ const dashboardLayoutItem = Object.values(dashboardLayout).find(
+ layoutItem => layoutItem?.meta?.chartId === chartId,
+ );
+ const filterObject: Indicator = {
+ column,
+ name:
+ dashboardLayoutItem?.meta?.sliceNameOverride ||
+ dashboardLayoutItem?.meta?.sliceName ||
+ '',
+ path: [
+ ...(dashboardLayoutItem?.parents ?? []),
+ dashboardLayoutItem?.id || '',
+ ],
+ value: label,
+ };
+ return filterObject;
+};
+
const cachedIndicatorsForChart = {};
const cachedDashboardFilterDataForChart = {};
// inspects redux state to find what the filter indicators should be shown for a given chart
@@ -233,17 +264,18 @@ const getStatus = ({
}): IndicatorStatus => {
// a filter is only considered unset if it's value is null
const hasValue = label !== null;
- if (type === DataMaskType.CrossFilters && hasValue) {
- return IndicatorStatus.CrossFilterApplied;
- }
+ const APPLIED_STATUS =
+ type === DataMaskType.CrossFilters
+ ? IndicatorStatus.CrossFilterApplied
+ : IndicatorStatus.Applied;
if (!column && hasValue) {
// Filter without datasource
- return IndicatorStatus.Applied;
+ return APPLIED_STATUS;
}
if (column && rejectedColumns?.has(column))
return IndicatorStatus.Incompatible;
if (column && appliedColumns?.has(column) && hasValue) {
- return IndicatorStatus.Applied;
+ return APPLIED_STATUS;
}
return IndicatorStatus.Unset;
};
@@ -254,11 +286,12 @@ export const selectChartCrossFilters = (
chartId: number,
dashboardLayout: Layout,
chartConfiguration: ChartConfiguration = defaultChartConfig,
+ appliedColumns: Set<string>,
+ rejectedColumns: Set<string>,
filterEmitter = false,
): Indicator[] | CrossFilterIndicator[] => {
let crossFilterIndicators: any = [];
if (isFeatureEnabled(FeatureFlag.DASHBOARD_CROSS_FILTERS)) {
- const dashboardLayoutValues = Object.values(dashboardLayout);
crossFilterIndicators = Object.values(chartConfiguration)
.filter(chartConfig => {
const inScope =
@@ -272,34 +305,22 @@ export const selectChartCrossFilters = (
return false;
})
.map(chartConfig => {
- const filterState = dataMask[chartConfig.id]?.filterState;
- const extraFormData = dataMask[chartConfig.id]?.extraFormData;
- const label = extractLabel(filterState);
- const filtersState = filterState?.filters;
- const column =
- extraFormData?.filters?.[0]?.col ||
- (filtersState && Object.keys(filtersState)[0]);
-
- const dashboardLayoutItem = dashboardLayoutValues.find(
- layoutItem => layoutItem?.meta?.chartId === chartConfig.id,
+ const filterIndicator = getCrossFilterIndicator(
+ chartConfig.id,
+ dataMask[chartConfig.id],
+ dashboardLayout,
);
- const filterObject: Indicator = {
- column,
- name: dashboardLayoutItem?.meta?.sliceName as string,
- path: [
- ...(dashboardLayoutItem?.parents ?? []),
- dashboardLayoutItem?.id || '',
- ],
- status: getStatus({
- label,
- type: DataMaskType.CrossFilters,
- }),
- value: label,
- };
- if (filterEmitter) {
- (filterObject as CrossFilterIndicator).emitterId = chartId;
- }
- return filterObject;
+ const filterStatus = getStatus({
+ label: filterIndicator.value,
+ column: filterIndicator.column
+ ? getColumnLabel(filterIndicator.column)
+ : undefined,
+ type: DataMaskType.CrossFilters,
+ appliedColumns,
+ rejectedColumns,
+ });
+
+ return { ...filterIndicator, status: filterStatus };
})
.filter(filter => filter.status === IndicatorStatus.CrossFilterApplied);
}
@@ -369,6 +390,8 @@ export const selectNativeIndicatorsForChart = (
chartId,
dashboardLayout,
chartConfiguration,
+ appliedColumns,
+ rejectedColumns,
);
}
const indicators = crossFilterIndicators.concat(nativeFilterIndicators);