mirror of https://github.com/apache/superset.git
feat(dashboard): Refactor FiltersBadge (#23286)
This commit is contained in:
parent
6311b40329
commit
c2b282ac71
|
@ -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');
|
||||
});
|
||||
|
|
|
@ -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 = [];
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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 const SectionName = styled.span`
|
||||
${({ theme }) => css`
|
||||
font-weight: ${theme.typography.weights.bold};
|
||||
`}
|
||||
`;
|
||||
export const FilterName = styled.span`
|
||||
${({ theme }) => css`
|
||||
padding-right: ${theme.gridUnit}px;
|
||||
font-style: italic;
|
||||
& > * {
|
||||
margin-right: ${theme.gridUnit}px;
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
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 ItemIcon = styled.i`
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
left: -${({ theme }) => theme.gridUnit * 5}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 Indent = styled.div`
|
||||
padding-left: ${({ theme }) => theme.gridUnit * 6}px;
|
||||
margin: -${({ theme }) => theme.gridUnit * 3}px 0;
|
||||
`;
|
||||
|
||||
export const Panel = styled.div`
|
||||
min-width: 200px;
|
||||
max-width: 300px;
|
||||
overflow-x: hidden;
|
||||
`;
|
||||
|
||||
export const FilterValue = styled.div`
|
||||
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 FiltersContainer = styled.div`
|
||||
${({ theme }) => css`
|
||||
margin-top: ${theme.gridUnit}px;
|
||||
&:not(:last-child) {
|
||||
padding-bottom: ${theme.gridUnit * 3}px;
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
export const FiltersDetailsContainer = styled.div`
|
||||
${({ theme }) => css`
|
||||
min-width: 200px;
|
||||
max-width: 300px;
|
||||
overflow-x: hidden;
|
||||
|
||||
color: ${theme.colors.grayscale.light5};
|
||||
`}
|
||||
`;
|
||||
|
||||
export const FilterValue = styled.span`
|
||||
max-width: 100%;
|
||||
flex-grow: 1;
|
||||
overflow: auto;
|
||||
`;
|
||||
|
||||
export const Separator = styled.div`
|
||||
${({ theme }) => css`
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background-color: ${theme.colors.grayscale.light1};
|
||||
margin: ${theme.gridUnit * 4}px 0;
|
||||
`}
|
||||
`;
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
: [];
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue