feat(cross-filter): Cross filter badge (#13687)
This commit is contained in:
parent
9efe1a44ec
commit
577ecc284f
|
|
@ -0,0 +1,22 @@
|
|||
<!--
|
||||
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.
|
||||
-->
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="12" cy="12" r="12" fill="transparent" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.6177 16.3979C18.957 14.0586 18.957 10.2519 16.6177 7.91263C14.2784 5.57335 10.4717 5.57335 8.13245 7.91263L7.07178 6.85197C9.99603 3.92773 14.7541 3.92773 17.6784 6.85197C20.6026 9.77621 20.6026 14.5343 17.6784 17.4586C14.7541 20.3828 9.99603 20.3828 7.07178 17.4586L8.13245 16.3979C10.4717 18.7372 14.2779 18.7377 16.6177 16.3979ZM4.50001 11.7499C4.50001 11.4738 4.72387 11.2499 5.00001 11.2499L6.75001 11.2499L6.75001 10.7499C6.75001 10.3379 7.22039 10.1027 7.55001 10.3499L9.75001 11.9999C9.86952 12.072 8.45348 13.0769 7.52541 13.7191C7.19597 13.947 6.75001 13.71 6.75001 13.3094L6.75001 12.7499H5.00001C4.72387 12.7499 4.50001 12.5261 4.50001 12.2499V11.7499ZM10.2538 10.034C11.4237 8.86404 13.327 8.86457 14.4964 10.034C15.6658 11.2033 15.6663 13.1067 14.4964 14.2766C13.3265 15.4465 11.4231 15.446 10.2538 14.2766L9.19311 15.3373C10.948 17.0921 13.8022 17.0921 15.5571 15.3373C17.3119 13.5824 17.3119 10.7282 15.5571 8.97329C13.8022 7.21843 10.948 7.21843 9.19311 8.97329L10.2538 10.034Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
|
|
@ -24,6 +24,7 @@ import { ReactComponent as AlertSolidSmallIcon } from 'images/icons/alert_solid_
|
|||
import { ReactComponent as BinocularsIcon } from 'images/icons/binoculars.svg';
|
||||
import { ReactComponent as BoltIcon } from 'images/icons/bolt.svg';
|
||||
import { ReactComponent as BoltSmallIcon } from 'images/icons/bolt_small.svg';
|
||||
import { ReactComponent as CrossFilterBadge } from 'images/icons/cross-filter-badge.svg';
|
||||
import { ReactComponent as BoltSmallRunIcon } from 'images/icons/bolt_small_run.svg';
|
||||
import { ReactComponent as CalendarIcon } from 'images/icons/calendar.svg';
|
||||
import { ReactComponent as CancelIcon } from 'images/icons/cancel.svg';
|
||||
|
|
@ -165,6 +166,7 @@ export type IconName =
|
|||
| 'caret-right'
|
||||
| 'caret-up'
|
||||
| 'certified'
|
||||
| 'cross-filter-badge'
|
||||
| 'check'
|
||||
| 'checkbox-half'
|
||||
| 'checkbox-off'
|
||||
|
|
@ -281,6 +283,7 @@ export const iconsRegistry: Record<
|
|||
'alert-solid-small': AlertSolidSmallIcon,
|
||||
'bolt-small': BoltSmallIcon,
|
||||
'bolt-small-run': BoltSmallRunIcon,
|
||||
'cross-filter-badge': CrossFilterBadge,
|
||||
'cancel-solid': CancelSolidIcon,
|
||||
'cancel-x': CancelXIcon,
|
||||
'card-view': CardViewIcon,
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import { useForceUpdate } from '../nativeFilters/FiltersConfigModal/FiltersConfi
|
|||
import { CrossFilterScopingFormType } from './types';
|
||||
|
||||
type CrossFilterScopingFormProps = {
|
||||
chartId: number;
|
||||
scope: Scope;
|
||||
form: FormInstance<CrossFilterScopingFormType>;
|
||||
};
|
||||
|
|
@ -32,6 +33,7 @@ type CrossFilterScopingFormProps = {
|
|||
const CrossFilterScopingForm: FC<CrossFilterScopingFormProps> = ({
|
||||
form,
|
||||
scope,
|
||||
chartId,
|
||||
}) => {
|
||||
const forceUpdate = useForceUpdate();
|
||||
const formScope = form.getFieldValue('scope');
|
||||
|
|
@ -44,6 +46,7 @@ const CrossFilterScopingForm: FC<CrossFilterScopingFormProps> = ({
|
|||
});
|
||||
}}
|
||||
scope={scope}
|
||||
chartId={chartId}
|
||||
formScope={formScope}
|
||||
forceUpdate={forceUpdate}
|
||||
formScoping={formScoping}
|
||||
|
|
|
|||
|
|
@ -49,7 +49,10 @@ const CrossFilterScopingModal: FC<CrossFilterScopingModalProps> = ({
|
|||
dispatch(
|
||||
setChartConfiguration({
|
||||
...chartConfig,
|
||||
[chartId]: { crossFilters: { scope: form.getFieldValue('scope') } },
|
||||
[chartId]: {
|
||||
id: chartId,
|
||||
crossFilters: { scope: form.getFieldValue('scope') },
|
||||
},
|
||||
}),
|
||||
);
|
||||
onClose();
|
||||
|
|
@ -88,7 +91,7 @@ const CrossFilterScopingModal: FC<CrossFilterScopingModalProps> = ({
|
|||
}
|
||||
>
|
||||
<StyledForm preserve={false} form={form} layout="vertical">
|
||||
<CrossFilterScopingForm form={form} scope={scope} />
|
||||
<CrossFilterScopingForm form={form} scope={scope} chartId={chartId} />
|
||||
</StyledForm>
|
||||
</StyledModal>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@
|
|||
import React, { useState } from 'react';
|
||||
import { t, useTheme, css } from '@superset-ui/core';
|
||||
import {
|
||||
SearchOutlined,
|
||||
MinusCircleFilled,
|
||||
CheckCircleFilled,
|
||||
ExclamationCircleFilled,
|
||||
|
|
@ -27,43 +26,13 @@ import {
|
|||
import { Popover } from 'src/common/components/index';
|
||||
import Collapse from 'src/common/components/Collapse';
|
||||
import { Global } from '@emotion/core';
|
||||
import {
|
||||
Indent,
|
||||
Item,
|
||||
ItemIcon,
|
||||
Panel,
|
||||
Reset,
|
||||
Title,
|
||||
FilterValue,
|
||||
} from './Styles';
|
||||
import Icon from 'src/components/Icon';
|
||||
import { Indent, Panel, Reset, Title } from './Styles';
|
||||
import { Indicator } from './selectors';
|
||||
import { getFilterValueForDisplay } from '../nativeFilters/FilterBar/FilterSets/utils';
|
||||
|
||||
export interface IndicatorProps {
|
||||
indicator: Indicator;
|
||||
onClick: (path: string[]) => void;
|
||||
}
|
||||
|
||||
const Indicator = ({
|
||||
indicator: { column, name, value = [], path },
|
||||
onClick,
|
||||
}: IndicatorProps) => {
|
||||
const resultValue = getFilterValueForDisplay(value);
|
||||
return (
|
||||
<Item onClick={() => onClick([...path, `LABEL-${column}`])}>
|
||||
<Title bold>
|
||||
<ItemIcon>
|
||||
<SearchOutlined />
|
||||
</ItemIcon>
|
||||
{name}
|
||||
{resultValue ? ': ' : ''}
|
||||
</Title>
|
||||
<FilterValue>{resultValue}</FilterValue>
|
||||
</Item>
|
||||
);
|
||||
};
|
||||
import FilterIndicator from './FilterIndicator';
|
||||
|
||||
export interface DetailsPanelProps {
|
||||
appliedCrossFilterIndicators: Indicator[];
|
||||
appliedIndicators: Indicator[];
|
||||
incompatibleIndicators: Indicator[];
|
||||
unsetIndicators: Indicator[];
|
||||
|
|
@ -72,6 +41,7 @@ export interface DetailsPanelProps {
|
|||
}
|
||||
|
||||
const DetailsPanelPopover = ({
|
||||
appliedCrossFilterIndicators = [],
|
||||
appliedIndicators = [],
|
||||
incompatibleIndicators = [],
|
||||
unsetIndicators = [],
|
||||
|
|
@ -80,21 +50,34 @@ const DetailsPanelPopover = ({
|
|||
}: DetailsPanelProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
function defaultActivePanel() {
|
||||
if (incompatibleIndicators.length) return 'incompatible';
|
||||
if (appliedIndicators.length) return 'applied';
|
||||
return 'unset';
|
||||
}
|
||||
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[]>(() => [
|
||||
defaultActivePanel(),
|
||||
...getDefaultActivePanel(),
|
||||
]);
|
||||
|
||||
function handlePopoverStatus(isOpen: boolean) {
|
||||
// every time the popover opens, make sure the most relevant panel is active
|
||||
if (isOpen) {
|
||||
if (!activePanels.includes(defaultActivePanel())) {
|
||||
setActivePanels([...activePanels, defaultActivePanel()]);
|
||||
if (
|
||||
!activePanels.find(panel => getDefaultActivePanel().includes(panel))
|
||||
) {
|
||||
setActivePanels([...activePanels, ...getDefaultActivePanel()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -168,6 +151,33 @@ const DetailsPanelPopover = ({
|
|||
activeKey={activePanels}
|
||||
onChange={handleActivePanelChange}
|
||||
>
|
||||
{appliedCrossFilterIndicators.length ? (
|
||||
<Collapse.Panel
|
||||
key="appliedCrossFilters"
|
||||
header={
|
||||
<Title bold color={theme.colors.primary.light1}>
|
||||
<Icon
|
||||
name="cross-filter-badge"
|
||||
css={{ fill: theme.colors.primary.light1 }}
|
||||
/>
|
||||
{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"
|
||||
|
|
@ -180,7 +190,7 @@ const DetailsPanelPopover = ({
|
|||
>
|
||||
<Indent css={{ paddingBottom: theme.gridUnit * 3 }}>
|
||||
{appliedIndicators.map(indicator => (
|
||||
<Indicator
|
||||
<FilterIndicator
|
||||
key={indicatorKey(indicator)}
|
||||
indicator={indicator}
|
||||
onClick={onHighlightFilterSource}
|
||||
|
|
@ -204,7 +214,7 @@ const DetailsPanelPopover = ({
|
|||
>
|
||||
<Indent css={{ paddingBottom: theme.gridUnit * 3 }}>
|
||||
{incompatibleIndicators.map(indicator => (
|
||||
<Indicator
|
||||
<FilterIndicator
|
||||
key={indicatorKey(indicator)}
|
||||
indicator={indicator}
|
||||
onClick={onHighlightFilterSource}
|
||||
|
|
@ -226,7 +236,7 @@ const DetailsPanelPopover = ({
|
|||
>
|
||||
<Indent css={{ paddingBottom: theme.gridUnit * 3 }}>
|
||||
{unsetIndicators.map(indicator => (
|
||||
<Indicator
|
||||
<FilterIndicator
|
||||
key={indicatorKey(indicator)}
|
||||
indicator={indicator}
|
||||
onClick={onHighlightFilterSource}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
/**
|
||||
* 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 { SearchOutlined } from '@ant-design/icons';
|
||||
import React, { FC } from 'react';
|
||||
import { getFilterValueForDisplay } from '../nativeFilters/FilterBar/FilterSets/utils';
|
||||
import { FilterValue, Item, ItemIcon, Title } from './Styles';
|
||||
|
||||
import { Indicator } from './selectors';
|
||||
|
||||
export interface IndicatorProps {
|
||||
indicator: Indicator;
|
||||
onClick?: (path: string[]) => void;
|
||||
}
|
||||
|
||||
const FilterIndicator: FC<IndicatorProps> = ({
|
||||
indicator: { column, name, value, path = [] },
|
||||
onClick = () => {},
|
||||
}) => {
|
||||
const resultValue = getFilterValueForDisplay(value);
|
||||
return (
|
||||
<Item onClick={() => onClick([...path, `LABEL-${column}`])}>
|
||||
<Title bold>
|
||||
<ItemIcon>
|
||||
<SearchOutlined />
|
||||
</ItemIcon>
|
||||
{name}
|
||||
{resultValue ? ': ' : ''}
|
||||
</Title>
|
||||
<FilterValue>{resultValue}</FilterValue>
|
||||
</Item>
|
||||
);
|
||||
};
|
||||
|
||||
export default FilterIndicator;
|
||||
|
|
@ -48,6 +48,13 @@ export const Pill = styled.div`
|
|||
background: ${({ theme }) => theme.colors.grayscale.dark1};
|
||||
}
|
||||
|
||||
&.has-cross-filters {
|
||||
background: ${({ theme }) => theme.colors.primary.base};
|
||||
&:hover {
|
||||
background: ${({ theme }) => theme.colors.primary.dark1};
|
||||
}
|
||||
}
|
||||
|
||||
&.has-incompatible-filters {
|
||||
color: ${({ theme }) => theme.colors.grayscale.dark2};
|
||||
background: ${({ theme }) => theme.colors.alert.base};
|
||||
|
|
@ -73,15 +80,6 @@ export const Pill = styled.div`
|
|||
}
|
||||
`;
|
||||
|
||||
export const WarningPill = styled(Pill)`
|
||||
background: ${({ theme }) => theme.colors.alert.base};
|
||||
color: ${({ theme }) => theme.colors.grayscale.dark1};
|
||||
`;
|
||||
|
||||
export const UnsetPill = styled(Pill)`
|
||||
background: ${({ theme }) => theme.colors.grayscale.light1};
|
||||
`;
|
||||
|
||||
export interface TitleProps {
|
||||
bold?: boolean;
|
||||
color?: string;
|
||||
|
|
@ -95,6 +93,11 @@ export const Title = styled.span<TitleProps>`
|
|||
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`
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import { Pill } from './Styles';
|
|||
import { Indicator } from './selectors';
|
||||
|
||||
export interface FiltersBadgeProps {
|
||||
appliedCrossFilterIndicators: Indicator[];
|
||||
appliedIndicators: Indicator[];
|
||||
unsetIndicators: Indicator[];
|
||||
incompatibleIndicators: Indicator[];
|
||||
|
|
@ -31,12 +32,14 @@ export interface FiltersBadgeProps {
|
|||
}
|
||||
|
||||
const FiltersBadge = ({
|
||||
appliedCrossFilterIndicators,
|
||||
appliedIndicators,
|
||||
unsetIndicators,
|
||||
incompatibleIndicators,
|
||||
onHighlightFilterSource,
|
||||
}: FiltersBadgeProps) => {
|
||||
if (
|
||||
!appliedCrossFilterIndicators.length &&
|
||||
!appliedIndicators.length &&
|
||||
!incompatibleIndicators.length &&
|
||||
!unsetIndicators.length
|
||||
|
|
@ -45,10 +48,13 @@ const FiltersBadge = ({
|
|||
}
|
||||
|
||||
const isInactive =
|
||||
!appliedIndicators.length && !incompatibleIndicators.length;
|
||||
!appliedCrossFilterIndicators.length &&
|
||||
!appliedIndicators.length &&
|
||||
!incompatibleIndicators.length;
|
||||
|
||||
return (
|
||||
<DetailsPanelPopover
|
||||
appliedCrossFilterIndicators={appliedCrossFilterIndicators}
|
||||
appliedIndicators={appliedIndicators}
|
||||
unsetIndicators={unsetIndicators}
|
||||
incompatibleIndicators={incompatibleIndicators}
|
||||
|
|
@ -58,13 +64,14 @@ const FiltersBadge = ({
|
|||
className={cx(
|
||||
'filter-counts',
|
||||
!!incompatibleIndicators.length && 'has-incompatible-filters',
|
||||
!!appliedCrossFilterIndicators.length && 'has-cross-filters',
|
||||
isInactive && 'filters-inactive',
|
||||
)}
|
||||
>
|
||||
<Icon name="filter" />
|
||||
{!isInactive && (
|
||||
<span data-test="applied-filter-count">
|
||||
{appliedIndicators.length}
|
||||
{appliedIndicators.length + appliedCrossFilterIndicators.length}
|
||||
</span>
|
||||
)}
|
||||
{incompatibleIndicators.length ? (
|
||||
|
|
|
|||
|
|
@ -18,16 +18,19 @@
|
|||
*/
|
||||
import { TIME_FILTER_MAP } from 'src/explore/constants';
|
||||
import { getChartIdsInFilterScope } from 'src/dashboard/util/activeDashboardFilters';
|
||||
import { NativeFiltersState } from 'src/dashboard/reducers/types';
|
||||
import { DataMaskStateWithId } from 'src/dataMask/types';
|
||||
import {
|
||||
ChartConfiguration,
|
||||
NativeFiltersState,
|
||||
} from 'src/dashboard/reducers/types';
|
||||
import { DataMaskStateWithId, DataMaskType } from 'src/dataMask/types';
|
||||
import { Layout } from '../../types';
|
||||
import { getTreeCheckedItems } from '../nativeFilters/FiltersConfigModal/FiltersConfigForm/FilterScope/utils';
|
||||
import { FilterValue } from '../nativeFilters/types';
|
||||
|
||||
export enum IndicatorStatus {
|
||||
Unset = 'UNSET',
|
||||
Applied = 'APPLIED',
|
||||
Incompatible = 'INCOMPATIBLE',
|
||||
CrossFilterApplied = 'CROSS_FILTER_APPLIED',
|
||||
}
|
||||
|
||||
const TIME_GRANULARITY_FIELDS = new Set(Object.values(TIME_FILTER_MAP));
|
||||
|
|
@ -53,7 +56,7 @@ const selectIndicatorValue = (
|
|||
columnKey: string,
|
||||
filter: Filter,
|
||||
datasource: Datasource,
|
||||
): FilterValue => {
|
||||
): any => {
|
||||
const values = filter.columns[columnKey];
|
||||
const arrValues = Array.isArray(values) ? values : [values];
|
||||
|
||||
|
|
@ -133,9 +136,9 @@ const getRejectedColumns = (chart: any): Set<string> =>
|
|||
export type Indicator = {
|
||||
column?: string;
|
||||
name: string;
|
||||
value: FilterValue;
|
||||
status: IndicatorStatus;
|
||||
path: string[];
|
||||
value?: any;
|
||||
status?: IndicatorStatus;
|
||||
path?: string[];
|
||||
};
|
||||
|
||||
// inspects redux state to find what the filter indicators should be shown for a given chart
|
||||
|
|
@ -179,22 +182,32 @@ export const selectNativeIndicatorsForChart = (
|
|||
chartId: number,
|
||||
charts: any,
|
||||
dashboardLayout: Layout,
|
||||
chartConfiguration: ChartConfiguration = {},
|
||||
): Indicator[] => {
|
||||
const chart = charts[chartId];
|
||||
|
||||
const appliedColumns = getAppliedColumns(chart);
|
||||
const rejectedColumns = getRejectedColumns(chart);
|
||||
|
||||
const getStatus = (
|
||||
value: FilterValue,
|
||||
isAffectedByScope: boolean,
|
||||
column?: string,
|
||||
): IndicatorStatus => {
|
||||
const getStatus = ({
|
||||
value,
|
||||
isAffectedByScope,
|
||||
column,
|
||||
type = DataMaskType.NativeFilters,
|
||||
}: {
|
||||
value: any;
|
||||
isAffectedByScope: boolean;
|
||||
column?: string;
|
||||
type?: DataMaskType;
|
||||
}): IndicatorStatus => {
|
||||
// a filter is only considered unset if it's value is null
|
||||
const hasValue = value !== null;
|
||||
if (!isAffectedByScope) {
|
||||
return IndicatorStatus.Unset;
|
||||
}
|
||||
if (type === DataMaskType.CrossFilters && hasValue) {
|
||||
return IndicatorStatus.CrossFilterApplied;
|
||||
}
|
||||
if (!column && hasValue) {
|
||||
// Filter without datasource
|
||||
return IndicatorStatus.Applied;
|
||||
|
|
@ -207,26 +220,59 @@ export const selectNativeIndicatorsForChart = (
|
|||
return IndicatorStatus.Unset;
|
||||
};
|
||||
|
||||
const indicators = Object.values(nativeFilters.filters).map(nativeFilter => {
|
||||
const isAffectedByScope = getTreeCheckedItems(
|
||||
nativeFilter.scope,
|
||||
dashboardLayout,
|
||||
).some(
|
||||
layoutItem => dashboardLayout[layoutItem]?.meta?.chartId === chartId,
|
||||
);
|
||||
const column = nativeFilter.targets[0]?.column?.name;
|
||||
const dataMaskNativeFilters = dataMask.nativeFilters?.[nativeFilter.id];
|
||||
let value = dataMaskNativeFilters?.currentState?.value ?? [];
|
||||
if (!Array.isArray(value)) {
|
||||
value = [value];
|
||||
}
|
||||
return {
|
||||
column,
|
||||
name: nativeFilter.name,
|
||||
path: [nativeFilter.id],
|
||||
status: getStatus(value, isAffectedByScope, column),
|
||||
value,
|
||||
};
|
||||
});
|
||||
return indicators;
|
||||
const nativeFilterIndicators = Object.values(nativeFilters.filters).map(
|
||||
nativeFilter => {
|
||||
const isAffectedByScope = getTreeCheckedItems(
|
||||
nativeFilter.scope,
|
||||
dashboardLayout,
|
||||
).some(
|
||||
layoutItem => dashboardLayout[layoutItem]?.meta?.chartId === chartId,
|
||||
);
|
||||
const column = nativeFilter.targets[0]?.column?.name;
|
||||
const dataMaskNativeFilters = dataMask.nativeFilters?.[nativeFilter.id];
|
||||
let value = dataMaskNativeFilters?.currentState?.value ?? null;
|
||||
if (!Array.isArray(value) && value !== null) {
|
||||
value = [value];
|
||||
}
|
||||
return {
|
||||
column,
|
||||
name: nativeFilter.name,
|
||||
path: [nativeFilter.id],
|
||||
status: getStatus({ value, isAffectedByScope, column }),
|
||||
value,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
const crossFilterIndicators = Object.values(chartConfiguration).map(
|
||||
chartConfig => {
|
||||
const scope = chartConfig?.crossFilters?.scope;
|
||||
const isAffectedByScope = getTreeCheckedItems(
|
||||
scope,
|
||||
dashboardLayout,
|
||||
).some(
|
||||
layoutItem => dashboardLayout[layoutItem]?.meta?.chartId === chartId,
|
||||
);
|
||||
|
||||
const dataMaskCrossFilters = dataMask.crossFilters?.[chartConfig.id];
|
||||
let value = dataMaskCrossFilters?.currentState?.value ?? null;
|
||||
if (!Array.isArray(value) && value !== null) {
|
||||
value = [value];
|
||||
}
|
||||
return {
|
||||
name: Object.values(dashboardLayout).find(
|
||||
layoutItem => layoutItem?.meta?.chartId === chartConfig.id,
|
||||
)?.meta?.sliceName as string,
|
||||
path: [`${chartConfig.id}`],
|
||||
status: getStatus({
|
||||
value,
|
||||
isAffectedByScope,
|
||||
type: DataMaskType.CrossFilters,
|
||||
}),
|
||||
value,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
return crossFilterIndicators.concat(nativeFilterIndicators);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,179 +0,0 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { t } from '@superset-ui/core';
|
||||
import { Tooltip } from 'src/common/components/Tooltip';
|
||||
import EditableTitle from '../../components/EditableTitle';
|
||||
import SliceHeaderControls from './SliceHeaderControls';
|
||||
import FiltersBadge from '../containers/FiltersBadge';
|
||||
|
||||
const propTypes = {
|
||||
innerRef: PropTypes.func,
|
||||
slice: PropTypes.object.isRequired,
|
||||
isExpanded: PropTypes.bool,
|
||||
isCached: PropTypes.arrayOf(PropTypes.bool),
|
||||
cachedDttm: PropTypes.arrayOf(PropTypes.string),
|
||||
updatedDttm: PropTypes.number,
|
||||
updateSliceName: PropTypes.func,
|
||||
toggleExpandSlice: PropTypes.func,
|
||||
forceRefresh: PropTypes.func,
|
||||
exploreChart: PropTypes.func,
|
||||
exportCSV: PropTypes.func,
|
||||
editMode: PropTypes.bool,
|
||||
annotationQuery: PropTypes.object,
|
||||
annotationError: PropTypes.object,
|
||||
sliceName: PropTypes.string,
|
||||
supersetCanExplore: PropTypes.bool,
|
||||
supersetCanCSV: PropTypes.bool,
|
||||
sliceCanEdit: PropTypes.bool,
|
||||
componentId: PropTypes.string.isRequired,
|
||||
dashboardId: PropTypes.number.isRequired,
|
||||
filters: PropTypes.object.isRequired,
|
||||
addSuccessToast: PropTypes.func.isRequired,
|
||||
addDangerToast: PropTypes.func.isRequired,
|
||||
handleToggleFullSize: PropTypes.func.isRequired,
|
||||
chartStatus: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
innerRef: null,
|
||||
forceRefresh: () => ({}),
|
||||
updateSliceName: () => ({}),
|
||||
toggleExpandSlice: () => ({}),
|
||||
exploreChart: () => ({}),
|
||||
exportCSV: () => ({}),
|
||||
editMode: false,
|
||||
annotationQuery: {},
|
||||
annotationError: {},
|
||||
cachedDttm: null,
|
||||
updatedDttm: null,
|
||||
isCached: [],
|
||||
isExpanded: [],
|
||||
sliceName: '',
|
||||
supersetCanExplore: false,
|
||||
supersetCanCSV: false,
|
||||
sliceCanEdit: false,
|
||||
};
|
||||
|
||||
const annoationsLoading = t('Annotation layers are still loading.');
|
||||
const annoationsError = t('One ore more annotation layers failed loading.');
|
||||
|
||||
class SliceHeader extends React.PureComponent {
|
||||
render() {
|
||||
const {
|
||||
slice,
|
||||
isExpanded,
|
||||
isCached,
|
||||
cachedDttm,
|
||||
updatedDttm,
|
||||
toggleExpandSlice,
|
||||
forceRefresh,
|
||||
exploreChart,
|
||||
exportCSV,
|
||||
innerRef,
|
||||
sliceName,
|
||||
supersetCanExplore,
|
||||
supersetCanCSV,
|
||||
sliceCanEdit,
|
||||
editMode,
|
||||
updateSliceName,
|
||||
annotationQuery,
|
||||
annotationError,
|
||||
componentId,
|
||||
dashboardId,
|
||||
addSuccessToast,
|
||||
addDangerToast,
|
||||
handleToggleFullSize,
|
||||
isFullSize,
|
||||
chartStatus,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div className="chart-header" ref={innerRef}>
|
||||
<div className="header-title">
|
||||
<EditableTitle
|
||||
title={
|
||||
sliceName ||
|
||||
(editMode
|
||||
? '---' // this makes an empty title clickable
|
||||
: '')
|
||||
}
|
||||
canEdit={editMode}
|
||||
emptyText=""
|
||||
onSaveTitle={updateSliceName}
|
||||
showTooltip={false}
|
||||
/>
|
||||
{!!Object.values(annotationQuery).length && (
|
||||
<Tooltip
|
||||
id="annotations-loading-tooltip"
|
||||
placement="top"
|
||||
title={annoationsLoading}
|
||||
>
|
||||
<i className="fa fa-refresh warning" />
|
||||
</Tooltip>
|
||||
)}
|
||||
{!!Object.values(annotationError).length && (
|
||||
<Tooltip
|
||||
id="annoation-errors-tooltip"
|
||||
placement="top"
|
||||
title={annoationsError}
|
||||
>
|
||||
<i className="fa fa-exclamation-circle danger" />
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
<div className="header-controls">
|
||||
{!editMode && (
|
||||
<>
|
||||
<FiltersBadge chartId={slice.slice_id} />
|
||||
<SliceHeaderControls
|
||||
slice={slice}
|
||||
isCached={isCached}
|
||||
isExpanded={isExpanded}
|
||||
cachedDttm={cachedDttm}
|
||||
updatedDttm={updatedDttm}
|
||||
toggleExpandSlice={toggleExpandSlice}
|
||||
forceRefresh={forceRefresh}
|
||||
exploreChart={exploreChart}
|
||||
exportCSV={exportCSV}
|
||||
supersetCanExplore={supersetCanExplore}
|
||||
supersetCanCSV={supersetCanCSV}
|
||||
sliceCanEdit={sliceCanEdit}
|
||||
componentId={componentId}
|
||||
dashboardId={dashboardId}
|
||||
addSuccessToast={addSuccessToast}
|
||||
addDangerToast={addDangerToast}
|
||||
handleToggleFullSize={handleToggleFullSize}
|
||||
isFullSize={isFullSize}
|
||||
chartStatus={chartStatus}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SliceHeader.propTypes = propTypes;
|
||||
SliceHeader.defaultProps = defaultProps;
|
||||
|
||||
export default SliceHeader;
|
||||
|
|
@ -0,0 +1,184 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import React, { FC } from 'react';
|
||||
import { styled, t } from '@superset-ui/core';
|
||||
import { Tooltip } from 'src/common/components/Tooltip';
|
||||
import { useSelector } from 'react-redux';
|
||||
import EditableTitle from '../../components/EditableTitle';
|
||||
import SliceHeaderControls from './SliceHeaderControls';
|
||||
import FiltersBadge from '../containers/FiltersBadge';
|
||||
import Icon from '../../components/Icon';
|
||||
import { RootState } from '../types';
|
||||
import { Slice } from '../../types/Chart';
|
||||
import FilterIndicator from './FiltersBadge/FilterIndicator';
|
||||
|
||||
type SliceHeaderProps = {
|
||||
innerRef?: string;
|
||||
slice: Slice;
|
||||
isExpanded?: boolean;
|
||||
isCached?: boolean[];
|
||||
cachedDttm?: string[];
|
||||
updatedDttm?: number;
|
||||
updateSliceName?: (arg0: string) => void;
|
||||
toggleExpandSlice?: Function;
|
||||
forceRefresh?: Function;
|
||||
exploreChart?: Function;
|
||||
exportCSV?: Function;
|
||||
editMode?: boolean;
|
||||
isFullSize?: boolean;
|
||||
annotationQuery?: object;
|
||||
annotationError?: object;
|
||||
sliceName?: string;
|
||||
supersetCanExplore?: boolean;
|
||||
supersetCanCSV?: boolean;
|
||||
sliceCanEdit?: boolean;
|
||||
componentId: string;
|
||||
dashboardId: number;
|
||||
filters: object;
|
||||
addSuccessToast: Function;
|
||||
addDangerToast: Function;
|
||||
handleToggleFullSize: Function;
|
||||
chartStatus: string;
|
||||
};
|
||||
|
||||
const annoationsLoading = t('Annotation layers are still loading.');
|
||||
const annoationsError = t('One ore more annotation layers failed loading.');
|
||||
|
||||
const CrossFilterIcon = styled(Icon)`
|
||||
fill: ${({ theme }) => theme.colors.grayscale.light5};
|
||||
& circle {
|
||||
fill: ${({ theme }) => theme.colors.primary.base};
|
||||
}
|
||||
`;
|
||||
|
||||
const SliceHeader: FC<SliceHeaderProps> = ({
|
||||
innerRef = null,
|
||||
forceRefresh = () => ({}),
|
||||
updateSliceName = () => ({}),
|
||||
toggleExpandSlice = () => ({}),
|
||||
exploreChart = () => ({}),
|
||||
exportCSV = () => ({}),
|
||||
editMode = false,
|
||||
annotationQuery = {},
|
||||
annotationError = {},
|
||||
cachedDttm = null,
|
||||
updatedDttm = null,
|
||||
isCached = [],
|
||||
isExpanded = [],
|
||||
sliceName = '',
|
||||
supersetCanExplore = false,
|
||||
supersetCanCSV = false,
|
||||
sliceCanEdit = false,
|
||||
slice,
|
||||
componentId,
|
||||
dashboardId,
|
||||
addSuccessToast,
|
||||
addDangerToast,
|
||||
handleToggleFullSize,
|
||||
isFullSize,
|
||||
chartStatus,
|
||||
}) => {
|
||||
// TODO: change to indicator field after it will be implemented
|
||||
const crossFilterValue = useSelector<RootState, any>(
|
||||
state =>
|
||||
state.dataMask?.crossFilters?.[slice?.slice_id]?.currentState?.value,
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="chart-header" ref={innerRef}>
|
||||
<div className="header-title">
|
||||
<EditableTitle
|
||||
title={
|
||||
sliceName ||
|
||||
(editMode
|
||||
? '---' // this makes an empty title clickable
|
||||
: '')
|
||||
}
|
||||
canEdit={editMode}
|
||||
emptyText=""
|
||||
onSaveTitle={updateSliceName}
|
||||
showTooltip={false}
|
||||
/>
|
||||
{!!Object.values(annotationQuery).length && (
|
||||
<Tooltip
|
||||
id="annotations-loading-tooltip"
|
||||
placement="top"
|
||||
title={annoationsLoading}
|
||||
>
|
||||
<i className="fa fa-refresh warning" />
|
||||
</Tooltip>
|
||||
)}
|
||||
{!!Object.values(annotationError).length && (
|
||||
<Tooltip
|
||||
id="annoation-errors-tooltip"
|
||||
placement="top"
|
||||
title={annoationsError}
|
||||
>
|
||||
<i className="fa fa-exclamation-circle danger" />
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
<div className="header-controls">
|
||||
{!editMode && (
|
||||
<>
|
||||
{crossFilterValue && (
|
||||
<Tooltip
|
||||
placement="top"
|
||||
title={
|
||||
<FilterIndicator
|
||||
indicator={{
|
||||
value: crossFilterValue,
|
||||
name: t('Emitted values'),
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<CrossFilterIcon name="cross-filter-badge" />
|
||||
</Tooltip>
|
||||
)}
|
||||
<FiltersBadge chartId={slice.slice_id} />
|
||||
<SliceHeaderControls
|
||||
slice={slice}
|
||||
isCached={isCached}
|
||||
isExpanded={isExpanded}
|
||||
cachedDttm={cachedDttm}
|
||||
updatedDttm={updatedDttm}
|
||||
toggleExpandSlice={toggleExpandSlice}
|
||||
forceRefresh={forceRefresh}
|
||||
exploreChart={exploreChart}
|
||||
exportCSV={exportCSV}
|
||||
supersetCanExplore={supersetCanExplore}
|
||||
supersetCanCSV={supersetCanCSV}
|
||||
sliceCanEdit={sliceCanEdit}
|
||||
componentId={componentId}
|
||||
dashboardId={dashboardId}
|
||||
addSuccessToast={addSuccessToast}
|
||||
addDangerToast={addDangerToast}
|
||||
handleToggleFullSize={handleToggleFullSize}
|
||||
isFullSize={isFullSize}
|
||||
chartStatus={chartStatus}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SliceHeader;
|
||||
|
|
@ -33,6 +33,7 @@ type FilterScopeProps = {
|
|||
forceUpdate: Function;
|
||||
scope?: Scope;
|
||||
formScoping?: Scoping;
|
||||
chartId?: number;
|
||||
};
|
||||
|
||||
const Wrapper = styled.div`
|
||||
|
|
@ -54,9 +55,10 @@ const FilterScope: FC<FilterScopeProps> = ({
|
|||
forceUpdate,
|
||||
scope,
|
||||
updateFormValues,
|
||||
chartId,
|
||||
}) => {
|
||||
const initialScope = scope || getDefaultScopeValue();
|
||||
const initialScoping = isScopingAll(initialScope)
|
||||
const initialScope = scope || getDefaultScopeValue(chartId);
|
||||
const initialScoping = isScopingAll(initialScope, chartId)
|
||||
? Scoping.all
|
||||
: Scoping.specific;
|
||||
|
||||
|
|
@ -70,8 +72,9 @@ const FilterScope: FC<FilterScopeProps> = ({
|
|||
<Radio.Group
|
||||
onChange={({ target: { value } }) => {
|
||||
if (value === Scoping.all) {
|
||||
const scope = getDefaultScopeValue(chartId);
|
||||
updateFormValues({
|
||||
scope: getDefaultScopeValue(),
|
||||
scope,
|
||||
});
|
||||
}
|
||||
forceUpdate();
|
||||
|
|
@ -94,6 +97,7 @@ const FilterScope: FC<FilterScopeProps> = ({
|
|||
initialScope={initialScope}
|
||||
formScope={formScope}
|
||||
forceUpdate={forceUpdate}
|
||||
chartId={chartId}
|
||||
/>
|
||||
)}
|
||||
<CleanFormItem
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ type ScopingTreeProps = {
|
|||
updateFormValues: (values: any) => void;
|
||||
formScope?: Scope;
|
||||
initialScope: Scope;
|
||||
chartId?: number;
|
||||
};
|
||||
|
||||
const ScopingTree: FC<ScopingTreeProps> = ({
|
||||
|
|
@ -36,22 +37,28 @@ const ScopingTree: FC<ScopingTreeProps> = ({
|
|||
initialScope,
|
||||
forceUpdate,
|
||||
updateFormValues,
|
||||
chartId,
|
||||
}) => {
|
||||
const [expandedKeys, setExpandedKeys] = useState<string[]>([
|
||||
DASHBOARD_ROOT_ID,
|
||||
]);
|
||||
|
||||
const { treeData, layout } = useFilterScopeTree();
|
||||
const { treeData, layout } = useFilterScopeTree(chartId);
|
||||
const [autoExpandParent, setAutoExpandParent] = useState<boolean>(true);
|
||||
|
||||
const handleExpand = (expandedKeys: string[]) => {
|
||||
setExpandedKeys(expandedKeys);
|
||||
setAutoExpandParent(false);
|
||||
};
|
||||
|
||||
const handleCheck = (checkedKeys: string[]) => {
|
||||
forceUpdate();
|
||||
const scope = findFilterScope(checkedKeys, layout);
|
||||
if (chartId !== undefined) {
|
||||
scope.excluded = [...new Set([...scope.excluded, chartId])];
|
||||
}
|
||||
updateFormValues({
|
||||
scope: findFilterScope(checkedKeys, layout),
|
||||
scope,
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,9 @@ import { TreeItem } from './types';
|
|||
import { buildTree } from './utils';
|
||||
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export function useFilterScopeTree(): {
|
||||
export function useFilterScopeTree(
|
||||
currentChartId?: number,
|
||||
): {
|
||||
treeData: [TreeItem];
|
||||
layout: Layout;
|
||||
} {
|
||||
|
|
@ -49,12 +51,12 @@ export function useFilterScopeTree(): {
|
|||
const validNodes = useMemo(
|
||||
() =>
|
||||
Object.values(layout).reduce<string[]>((acc, cur) => {
|
||||
if (cur?.type === CHART_TYPE) {
|
||||
if (cur?.type === CHART_TYPE && currentChartId !== cur?.meta?.chartId) {
|
||||
return [...new Set([...acc, ...cur?.parents, cur.id])];
|
||||
}
|
||||
return acc;
|
||||
}, []),
|
||||
[layout],
|
||||
[layout, currentChartId],
|
||||
);
|
||||
|
||||
useMemo(() => {
|
||||
|
|
|
|||
|
|
@ -145,10 +145,12 @@ export const findFilterScope = (
|
|||
};
|
||||
};
|
||||
|
||||
export const getDefaultScopeValue = () => ({
|
||||
export const getDefaultScopeValue = (chartId?: number): Scope => ({
|
||||
rootPath: [DASHBOARD_ROOT_ID],
|
||||
excluded: [],
|
||||
excluded: chartId ? [chartId] : [],
|
||||
});
|
||||
|
||||
export const isScopingAll = (scope: Scope) =>
|
||||
!scope || (scope.rootPath[0] === DASHBOARD_ROOT_ID && !scope.excluded.length);
|
||||
export const isScopingAll = (scope: Scope, chartId?: number) =>
|
||||
!scope ||
|
||||
(scope.rootPath[0] === DASHBOARD_ROOT_ID &&
|
||||
!scope.excluded.filter(item => item !== chartId).length);
|
||||
|
|
|
|||
|
|
@ -37,12 +37,10 @@ export interface Target {
|
|||
// clarityColumns?: Column[];
|
||||
}
|
||||
|
||||
export type FilterValue = string | number | (string | number)[] | null;
|
||||
|
||||
export interface Filter {
|
||||
cascadeParentIds: string[];
|
||||
defaultValue: FilterValue;
|
||||
currentValue?: FilterValue;
|
||||
defaultValue: any;
|
||||
currentValue?: any;
|
||||
isInstant: boolean;
|
||||
id: string; // randomly generated at filter creation
|
||||
name: string;
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ function mapStateToProps(
|
|||
supersetCanCSV: !!dashboardInfo.superset_can_csv,
|
||||
sliceCanEdit: !!dashboardInfo.slice_can_edit,
|
||||
ownCurrentState: dataMask.ownFilters?.[id]?.currentState,
|
||||
crossFilterCurrentState: dataMask.crossFilters?.[id]?.currentState,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -47,7 +47,9 @@ const sortByStatus = (indicators: Indicator[]): Indicator[] => {
|
|||
IndicatorStatus.Incompatible,
|
||||
];
|
||||
return indicators.sort(
|
||||
(a, b) => statuses.indexOf(a.status) - statuses.indexOf(b.status),
|
||||
(a, b) =>
|
||||
statuses.indexOf(a.status as IndicatorStatus) -
|
||||
statuses.indexOf(b.status as IndicatorStatus),
|
||||
);
|
||||
};
|
||||
|
||||
|
|
@ -56,6 +58,7 @@ const mapStateToProps = (
|
|||
datasources,
|
||||
dashboardFilters,
|
||||
nativeFilters,
|
||||
dashboardInfo,
|
||||
charts,
|
||||
dataMask,
|
||||
dashboardLayout: { present },
|
||||
|
|
@ -75,6 +78,7 @@ const mapStateToProps = (
|
|||
chartId,
|
||||
charts,
|
||||
present,
|
||||
dashboardInfo.metadata?.chart_configuration,
|
||||
);
|
||||
|
||||
const indicators = uniqWith(
|
||||
|
|
@ -86,6 +90,9 @@ const mapStateToProps = (
|
|||
ind2.status !== IndicatorStatus.Applied),
|
||||
);
|
||||
|
||||
const appliedCrossFilterIndicators = indicators.filter(
|
||||
indicator => indicator.status === IndicatorStatus.CrossFilterApplied,
|
||||
);
|
||||
const appliedIndicators = indicators.filter(
|
||||
indicator => indicator.status === IndicatorStatus.Applied,
|
||||
);
|
||||
|
|
@ -99,6 +106,7 @@ const mapStateToProps = (
|
|||
return {
|
||||
chartId,
|
||||
appliedIndicators,
|
||||
appliedCrossFilterIndicators,
|
||||
unsetIndicators,
|
||||
incompatibleIndicators,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -27,7 +27,8 @@ export enum Scoping {
|
|||
}
|
||||
|
||||
export type ChartConfiguration = {
|
||||
[chartId: string]: {
|
||||
[chartId: number]: {
|
||||
id: number;
|
||||
crossFilters: {
|
||||
scope: Scope;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
import { ChartProps } from '@superset-ui/core';
|
||||
import { chart } from 'src/chart/chartReducer';
|
||||
import componentTypes from 'src/dashboard/util/componentTypes';
|
||||
import { DataMaskStateWithId } from '../dataMask/types';
|
||||
|
||||
export type ChartReducerInitialState = typeof chart;
|
||||
|
||||
|
|
@ -44,6 +45,7 @@ export type RootState = {
|
|||
charts: { [key: string]: Chart };
|
||||
dashboardLayout: { present: { [key: string]: LayoutItem } };
|
||||
dashboardFilters: {};
|
||||
dataMask: DataMaskStateWithId;
|
||||
};
|
||||
|
||||
/** State of dashboardLayout in redux */
|
||||
|
|
|
|||
Loading…
Reference in New Issue