mirror of
https://github.com/apache/superset.git
synced 2024-09-12 08:39:45 -04:00
feat(native-filters): Set default scope by filters' and charts' datasets (#15302)
* feat(native-filters): Set default scope by filter's and charts datasets * Fix undefined error * Use JSON.stringify in dependency array * Fix lint issue * Lock scope after switching radio buttons * Fix weird eslint issue * Change prop names * Implement useComponentDidUpdate * Fix lint * Refactor useComponentDidUpdate * Remove screen.debug()
This commit is contained in:
parent
352656a398
commit
f0b64190b5
@ -48,6 +48,7 @@ describe('getFormDataWithExtraFilters', () => {
|
|||||||
val: ['United States'],
|
val: ['United States'],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
datasource: '123',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const mockArgs: GetFormDataWithExtraFiltersArguments = {
|
const mockArgs: GetFormDataWithExtraFiltersArguments = {
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export * from './useComponentDidUpdate';
|
@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* 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 { renderHook } from '@testing-library/react-hooks';
|
||||||
|
import { useComponentDidUpdate } from './useComponentDidUpdate';
|
||||||
|
|
||||||
|
test('the effect should not be executed on the first render', () => {
|
||||||
|
const effect = jest.fn();
|
||||||
|
const hook = renderHook(props => useComponentDidUpdate(props.effect), {
|
||||||
|
initialProps: { effect },
|
||||||
|
});
|
||||||
|
expect(effect).toBeCalledTimes(0);
|
||||||
|
const changedEffect = jest.fn();
|
||||||
|
hook.rerender({ effect: changedEffect });
|
||||||
|
expect(changedEffect).toBeCalledTimes(1);
|
||||||
|
});
|
@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* 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 { EffectCallback, useEffect, useRef } from 'react';
|
||||||
|
|
||||||
|
export const useComponentDidUpdate = (effect: EffectCallback) => {
|
||||||
|
const isMountedRef = useRef(false);
|
||||||
|
useEffect(() => {
|
||||||
|
if (isMountedRef.current) {
|
||||||
|
effect();
|
||||||
|
} else {
|
||||||
|
isMountedRef.current = true;
|
||||||
|
}
|
||||||
|
}, [effect]);
|
||||||
|
};
|
@ -43,9 +43,9 @@ test('Should send correct props', () => {
|
|||||||
expect(FilterScope).toHaveBeenCalledWith(
|
expect(FilterScope).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
chartId: 123,
|
chartId: 123,
|
||||||
scope: 'Scope',
|
filterScope: 'Scope',
|
||||||
formScope: 'scope',
|
formFilterScope: 'scope',
|
||||||
formScoping: 'scoping',
|
formScopingType: 'scoping',
|
||||||
}),
|
}),
|
||||||
{},
|
{},
|
||||||
);
|
);
|
||||||
|
@ -45,11 +45,11 @@ const CrossFilterScopingForm: FC<CrossFilterScopingFormProps> = ({
|
|||||||
...values,
|
...values,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
scope={scope}
|
filterScope={scope}
|
||||||
chartId={chartId}
|
chartId={chartId}
|
||||||
formScope={formScope}
|
formFilterScope={formScope}
|
||||||
forceUpdate={forceUpdate}
|
forceUpdate={forceUpdate}
|
||||||
formScoping={formScoping}
|
formScopingType={formScoping}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -17,23 +17,25 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { FC } from 'react';
|
import React, { FC, useCallback, useState } from 'react';
|
||||||
import { t, styled } from '@superset-ui/core';
|
import { t, styled } from '@superset-ui/core';
|
||||||
import { Radio } from 'src/components/Radio';
|
import { Radio } from 'src/components/Radio';
|
||||||
import { Form, Typography } from 'src/common/components';
|
import { Form, Typography } from 'src/common/components';
|
||||||
|
import { useComponentDidUpdate } from 'src/common/hooks/useComponentDidUpdate/useComponentDidUpdate';
|
||||||
import { Scope } from '../../../types';
|
import { Scope } from '../../../types';
|
||||||
import { Scoping } from './types';
|
import { ScopingType } from './types';
|
||||||
import ScopingTree from './ScopingTree';
|
import ScopingTree from './ScopingTree';
|
||||||
import { getDefaultScopeValue, isScopingAll } from './utils';
|
import { getDefaultScopeValue, isScopingAll } from './utils';
|
||||||
|
|
||||||
type FilterScopeProps = {
|
type FilterScopeProps = {
|
||||||
pathToFormValue?: string[];
|
pathToFormValue?: string[];
|
||||||
updateFormValues: (values: any) => void;
|
updateFormValues: (values: any) => void;
|
||||||
formScope?: Scope;
|
formFilterScope?: Scope;
|
||||||
forceUpdate: Function;
|
forceUpdate: Function;
|
||||||
scope?: Scope;
|
filterScope?: Scope;
|
||||||
formScoping?: Scoping;
|
formScopingType?: ScopingType;
|
||||||
chartId?: number;
|
chartId?: number;
|
||||||
|
initiallyExcludedCharts?: number[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
@ -50,59 +52,98 @@ const CleanFormItem = styled(Form.Item)`
|
|||||||
|
|
||||||
const FilterScope: FC<FilterScopeProps> = ({
|
const FilterScope: FC<FilterScopeProps> = ({
|
||||||
pathToFormValue = [],
|
pathToFormValue = [],
|
||||||
formScoping,
|
formScopingType,
|
||||||
formScope,
|
formFilterScope,
|
||||||
forceUpdate,
|
forceUpdate,
|
||||||
scope,
|
filterScope,
|
||||||
updateFormValues,
|
updateFormValues,
|
||||||
chartId,
|
chartId,
|
||||||
|
initiallyExcludedCharts,
|
||||||
}) => {
|
}) => {
|
||||||
const initialScope = scope || getDefaultScopeValue(chartId);
|
const [initialFilterScope] = useState(
|
||||||
const initialScoping = isScopingAll(initialScope, chartId)
|
filterScope || getDefaultScopeValue(chartId, initiallyExcludedCharts),
|
||||||
? Scoping.all
|
);
|
||||||
: Scoping.specific;
|
const [initialScopingType] = useState(
|
||||||
|
isScopingAll(initialFilterScope, chartId)
|
||||||
|
? ScopingType.all
|
||||||
|
: ScopingType.specific,
|
||||||
|
);
|
||||||
|
const [hasScopeBeenModified, setHasScopeBeenModified] = useState(
|
||||||
|
!!filterScope,
|
||||||
|
);
|
||||||
|
|
||||||
|
const onUpdateFormValues = useCallback(
|
||||||
|
(formValues: any) => {
|
||||||
|
updateFormValues(formValues);
|
||||||
|
setHasScopeBeenModified(true);
|
||||||
|
},
|
||||||
|
[updateFormValues],
|
||||||
|
);
|
||||||
|
|
||||||
|
const updateScopes = useCallback(() => {
|
||||||
|
if (filterScope || hasScopeBeenModified) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newScope = getDefaultScopeValue(chartId, initiallyExcludedCharts);
|
||||||
|
updateFormValues({
|
||||||
|
scope: newScope,
|
||||||
|
scoping: isScopingAll(newScope, chartId)
|
||||||
|
? ScopingType.all
|
||||||
|
: ScopingType.specific,
|
||||||
|
});
|
||||||
|
}, [
|
||||||
|
chartId,
|
||||||
|
filterScope,
|
||||||
|
hasScopeBeenModified,
|
||||||
|
initiallyExcludedCharts,
|
||||||
|
updateFormValues,
|
||||||
|
]);
|
||||||
|
useComponentDidUpdate(updateScopes);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
<CleanFormItem
|
<CleanFormItem
|
||||||
name={[...pathToFormValue, 'scoping']}
|
name={[...pathToFormValue, 'scoping']}
|
||||||
initialValue={initialScoping}
|
initialValue={initialScopingType}
|
||||||
>
|
>
|
||||||
<Radio.Group
|
<Radio.Group
|
||||||
onChange={({ target: { value } }) => {
|
onChange={({ target: { value } }) => {
|
||||||
if (value === Scoping.all) {
|
if (value === ScopingType.all) {
|
||||||
const scope = getDefaultScopeValue(chartId);
|
const scope = getDefaultScopeValue(chartId);
|
||||||
updateFormValues({
|
updateFormValues({
|
||||||
scope,
|
scope,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
setHasScopeBeenModified(true);
|
||||||
forceUpdate();
|
forceUpdate();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Radio value={Scoping.all}>{t('Apply to all panels')}</Radio>
|
<Radio value={ScopingType.all}>{t('Apply to all panels')}</Radio>
|
||||||
<Radio value={Scoping.specific}>
|
<Radio value={ScopingType.specific}>
|
||||||
{t('Apply to specific panels')}
|
{t('Apply to specific panels')}
|
||||||
</Radio>
|
</Radio>
|
||||||
</Radio.Group>
|
</Radio.Group>
|
||||||
</CleanFormItem>
|
</CleanFormItem>
|
||||||
<Typography.Text type="secondary">
|
<Typography.Text type="secondary">
|
||||||
{(formScoping ?? initialScoping) === Scoping.specific
|
{(formScopingType ?? initialScopingType) === ScopingType.specific
|
||||||
? t('Only selected panels will be affected by this filter')
|
? t('Only selected panels will be affected by this filter')
|
||||||
: t('All panels with this column will be affected by this filter')}
|
: t('All panels with this column will be affected by this filter')}
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
{(formScoping ?? initialScoping) === Scoping.specific && (
|
{(formScopingType ?? initialScopingType) === ScopingType.specific && (
|
||||||
<ScopingTree
|
<ScopingTree
|
||||||
updateFormValues={updateFormValues}
|
updateFormValues={onUpdateFormValues}
|
||||||
initialScope={initialScope}
|
initialScope={initialFilterScope}
|
||||||
formScope={formScope}
|
formScope={formFilterScope}
|
||||||
forceUpdate={forceUpdate}
|
forceUpdate={forceUpdate}
|
||||||
chartId={chartId}
|
chartId={chartId}
|
||||||
|
initiallyExcludedCharts={initiallyExcludedCharts}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<CleanFormItem
|
<CleanFormItem
|
||||||
name={[...pathToFormValue, 'scope']}
|
name={[...pathToFormValue, 'scope']}
|
||||||
hidden
|
hidden
|
||||||
initialValue={initialScope}
|
initialValue={initialFilterScope}
|
||||||
/>
|
/>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
|
@ -20,6 +20,8 @@
|
|||||||
import React, { FC, useMemo, useState } from 'react';
|
import React, { FC, useMemo, useState } from 'react';
|
||||||
import { Tree } from 'src/common/components';
|
import { Tree } from 'src/common/components';
|
||||||
import { DASHBOARD_ROOT_ID } from 'src/dashboard/util/constants';
|
import { DASHBOARD_ROOT_ID } from 'src/dashboard/util/constants';
|
||||||
|
import { Tooltip } from 'src/components/Tooltip';
|
||||||
|
import Icons from 'src/components/Icons';
|
||||||
import { useFilterScopeTree } from './state';
|
import { useFilterScopeTree } from './state';
|
||||||
import { findFilterScope, getTreeCheckedItems } from './utils';
|
import { findFilterScope, getTreeCheckedItems } from './utils';
|
||||||
import { Scope } from '../../../types';
|
import { Scope } from '../../../types';
|
||||||
@ -30,6 +32,26 @@ type ScopingTreeProps = {
|
|||||||
formScope?: Scope;
|
formScope?: Scope;
|
||||||
initialScope: Scope;
|
initialScope: Scope;
|
||||||
chartId?: number;
|
chartId?: number;
|
||||||
|
initiallyExcludedCharts?: number[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildTreeLeafTitle = (
|
||||||
|
label: string,
|
||||||
|
hasTooltip: boolean,
|
||||||
|
tooltipTitle?: string,
|
||||||
|
) => {
|
||||||
|
let title = <span>{label}</span>;
|
||||||
|
if (hasTooltip) {
|
||||||
|
title = (
|
||||||
|
<>
|
||||||
|
{title}
|
||||||
|
<Tooltip title={tooltipTitle}>
|
||||||
|
<Icons.Info iconSize="m" />
|
||||||
|
</Tooltip>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return title;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ScopingTree: FC<ScopingTreeProps> = ({
|
const ScopingTree: FC<ScopingTreeProps> = ({
|
||||||
@ -38,12 +60,17 @@ const ScopingTree: FC<ScopingTreeProps> = ({
|
|||||||
forceUpdate,
|
forceUpdate,
|
||||||
updateFormValues,
|
updateFormValues,
|
||||||
chartId,
|
chartId,
|
||||||
|
initiallyExcludedCharts = [],
|
||||||
}) => {
|
}) => {
|
||||||
const [expandedKeys, setExpandedKeys] = useState<string[]>([
|
const [expandedKeys, setExpandedKeys] = useState<string[]>([
|
||||||
DASHBOARD_ROOT_ID,
|
DASHBOARD_ROOT_ID,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const { treeData, layout } = useFilterScopeTree(chartId);
|
const { treeData, layout } = useFilterScopeTree(
|
||||||
|
chartId,
|
||||||
|
initiallyExcludedCharts,
|
||||||
|
buildTreeLeafTitle,
|
||||||
|
);
|
||||||
const [autoExpandParent, setAutoExpandParent] = useState<boolean>(true);
|
const [autoExpandParent, setAutoExpandParent] = useState<boolean>(true);
|
||||||
|
|
||||||
const handleExpand = (expandedKeys: string[]) => {
|
const handleExpand = (expandedKeys: string[]) => {
|
||||||
|
@ -25,12 +25,14 @@ import {
|
|||||||
CHART_TYPE,
|
CHART_TYPE,
|
||||||
DASHBOARD_ROOT_TYPE,
|
DASHBOARD_ROOT_TYPE,
|
||||||
} from 'src/dashboard/util/componentTypes';
|
} from 'src/dashboard/util/componentTypes';
|
||||||
import { TreeItem } from './types';
|
import { BuildTreeLeafTitle, TreeItem } from './types';
|
||||||
import { buildTree } from './utils';
|
import { buildTree } from './utils';
|
||||||
|
|
||||||
// eslint-disable-next-line import/prefer-default-export
|
// eslint-disable-next-line import/prefer-default-export
|
||||||
export function useFilterScopeTree(
|
export function useFilterScopeTree(
|
||||||
currentChartId?: number,
|
currentChartId?: number,
|
||||||
|
initiallyExcludedCharts: number[] = [],
|
||||||
|
buildTreeLeafTitle: BuildTreeLeafTitle = label => label,
|
||||||
): {
|
): {
|
||||||
treeData: [TreeItem];
|
treeData: [TreeItem];
|
||||||
layout: Layout;
|
layout: Layout;
|
||||||
@ -61,8 +63,16 @@ export function useFilterScopeTree(
|
|||||||
);
|
);
|
||||||
|
|
||||||
useMemo(() => {
|
useMemo(() => {
|
||||||
buildTree(layout[DASHBOARD_ROOT_ID], tree, layout, charts, validNodes);
|
buildTree(
|
||||||
}, [charts, layout, tree]);
|
layout[DASHBOARD_ROOT_ID],
|
||||||
|
tree,
|
||||||
|
layout,
|
||||||
|
charts,
|
||||||
|
validNodes,
|
||||||
|
initiallyExcludedCharts,
|
||||||
|
buildTreeLeafTitle,
|
||||||
|
);
|
||||||
|
}, [layout, tree, charts, initiallyExcludedCharts, buildTreeLeafTitle]);
|
||||||
|
|
||||||
return { treeData: [tree], layout };
|
return { treeData: [tree], layout };
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,9 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export enum Scoping {
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
|
export enum ScopingType {
|
||||||
all,
|
all,
|
||||||
specific,
|
specific,
|
||||||
}
|
}
|
||||||
@ -26,5 +28,11 @@ export enum Scoping {
|
|||||||
export type TreeItem = {
|
export type TreeItem = {
|
||||||
children: TreeItem[];
|
children: TreeItem[];
|
||||||
key: string;
|
key: string;
|
||||||
title: string;
|
title: ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type BuildTreeLeafTitle = (
|
||||||
|
label: string,
|
||||||
|
hasTooltip: boolean,
|
||||||
|
tooltipTitle?: string,
|
||||||
|
) => ReactNode;
|
||||||
|
@ -23,7 +23,8 @@ import {
|
|||||||
TAB_TYPE,
|
TAB_TYPE,
|
||||||
} from 'src/dashboard/util/componentTypes';
|
} from 'src/dashboard/util/componentTypes';
|
||||||
import { DASHBOARD_ROOT_ID } from 'src/dashboard/util/constants';
|
import { DASHBOARD_ROOT_ID } from 'src/dashboard/util/constants';
|
||||||
import { TreeItem } from './types';
|
import { t } from '@superset-ui/core';
|
||||||
|
import { BuildTreeLeafTitle, TreeItem } from './types';
|
||||||
import { Scope } from '../../../types';
|
import { Scope } from '../../../types';
|
||||||
|
|
||||||
export const isShowTypeInTree = ({ type, meta }: LayoutItem, charts?: Charts) =>
|
export const isShowTypeInTree = ({ type, meta }: LayoutItem, charts?: Charts) =>
|
||||||
@ -36,6 +37,8 @@ export const buildTree = (
|
|||||||
layout: Layout,
|
layout: Layout,
|
||||||
charts: Charts,
|
charts: Charts,
|
||||||
validNodes: string[],
|
validNodes: string[],
|
||||||
|
initiallyExcludedCharts: number[],
|
||||||
|
buildTreeLeafTitle: BuildTreeLeafTitle,
|
||||||
) => {
|
) => {
|
||||||
let itemToPass: TreeItem = treeItem;
|
let itemToPass: TreeItem = treeItem;
|
||||||
if (
|
if (
|
||||||
@ -43,20 +46,35 @@ export const buildTree = (
|
|||||||
node.type !== DASHBOARD_ROOT_TYPE &&
|
node.type !== DASHBOARD_ROOT_TYPE &&
|
||||||
validNodes.includes(node.id)
|
validNodes.includes(node.id)
|
||||||
) {
|
) {
|
||||||
const currentTreeItem = {
|
const title = buildTreeLeafTitle(
|
||||||
key: node.id,
|
node.meta.sliceNameOverride ||
|
||||||
title:
|
|
||||||
node.meta.sliceNameOverride ||
|
|
||||||
node.meta.sliceName ||
|
node.meta.sliceName ||
|
||||||
node.meta.text ||
|
node.meta.text ||
|
||||||
node.id.toString(),
|
node.id.toString(),
|
||||||
|
initiallyExcludedCharts.includes(node.meta?.chartId),
|
||||||
|
t(
|
||||||
|
"This chart might be incompatible with the filter (datasets don't match)",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const currentTreeItem = {
|
||||||
|
key: node.id,
|
||||||
|
title,
|
||||||
children: [],
|
children: [],
|
||||||
};
|
};
|
||||||
treeItem.children.push(currentTreeItem);
|
treeItem.children.push(currentTreeItem);
|
||||||
itemToPass = currentTreeItem;
|
itemToPass = currentTreeItem;
|
||||||
}
|
}
|
||||||
node.children.forEach(child =>
|
node.children.forEach(child =>
|
||||||
buildTree(layout[child], itemToPass, layout, charts, validNodes),
|
buildTree(
|
||||||
|
layout[child],
|
||||||
|
itemToPass,
|
||||||
|
layout,
|
||||||
|
charts,
|
||||||
|
validNodes,
|
||||||
|
initiallyExcludedCharts,
|
||||||
|
buildTreeLeafTitle,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -148,9 +166,14 @@ export const findFilterScope = (
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getDefaultScopeValue = (chartId?: number): Scope => ({
|
export const getDefaultScopeValue = (
|
||||||
|
chartId?: number,
|
||||||
|
initiallyExcludedCharts: number[] = [],
|
||||||
|
): Scope => ({
|
||||||
rootPath: [DASHBOARD_ROOT_ID],
|
rootPath: [DASHBOARD_ROOT_ID],
|
||||||
excluded: chartId ? [chartId] : [],
|
excluded: chartId
|
||||||
|
? [chartId, ...initiallyExcludedCharts]
|
||||||
|
: initiallyExcludedCharts,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const isScopingAll = (scope: Scope, chartId?: number) =>
|
export const isScopingAll = (scope: Scope, chartId?: number) =>
|
||||||
|
@ -64,6 +64,12 @@ import Tabs from 'src/components/Tabs';
|
|||||||
import Icons from 'src/components/Icons';
|
import Icons from 'src/components/Icons';
|
||||||
import { Tooltip } from 'src/components/Tooltip';
|
import { Tooltip } from 'src/components/Tooltip';
|
||||||
import BasicErrorAlert from 'src/components/ErrorMessage/BasicErrorAlert';
|
import BasicErrorAlert from 'src/components/ErrorMessage/BasicErrorAlert';
|
||||||
|
import {
|
||||||
|
Chart,
|
||||||
|
ChartsState,
|
||||||
|
DatasourcesState,
|
||||||
|
RootState,
|
||||||
|
} from 'src/dashboard/types';
|
||||||
import { ColumnSelect } from './ColumnSelect';
|
import { ColumnSelect } from './ColumnSelect';
|
||||||
import { NativeFiltersForm } from '../types';
|
import { NativeFiltersForm } from '../types';
|
||||||
import {
|
import {
|
||||||
@ -324,9 +330,10 @@ const FiltersConfigForm = (
|
|||||||
)
|
)
|
||||||
.map(([key]) => key);
|
.map(([key]) => key);
|
||||||
|
|
||||||
const loadedDatasets = useSelector<any, DatasourceMeta>(
|
const loadedDatasets = useSelector<RootState, DatasourcesState>(
|
||||||
({ datasources }) => datasources,
|
({ datasources }) => datasources,
|
||||||
);
|
);
|
||||||
|
const charts = useSelector<RootState, ChartsState>(({ charts }) => charts);
|
||||||
|
|
||||||
const doLoadedDatasetsHaveTemporalColumns = useMemo(
|
const doLoadedDatasetsHaveTemporalColumns = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@ -349,9 +356,9 @@ const FiltersConfigForm = (
|
|||||||
?.datasourceCount;
|
?.datasourceCount;
|
||||||
const hasColumn =
|
const hasColumn =
|
||||||
hasDataset && !FILTERS_WITHOUT_COLUMN.includes(formFilter?.filterType);
|
hasDataset && !FILTERS_WITHOUT_COLUMN.includes(formFilter?.filterType);
|
||||||
|
const nativeFilterItem = nativeFilterItems[formFilter?.filterType] ?? {};
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const enableNoResults = !!nativeFilterItems[formFilter?.filterType]?.value
|
const enableNoResults = !!nativeFilterItem.value?.enableNoResults;
|
||||||
?.enableNoResults;
|
|
||||||
const datasetId = formFilter?.dataset?.value;
|
const datasetId = formFilter?.dataset?.value;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -517,6 +524,11 @@ const FiltersConfigForm = (
|
|||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const updateFormValues = useCallback(
|
||||||
|
(values: any) => setNativeFilterFieldValues(form, filterId, values),
|
||||||
|
[filterId, form],
|
||||||
|
);
|
||||||
|
|
||||||
const parentFilterOptions = parentFilters.map(filter => ({
|
const parentFilterOptions = parentFilters.map(filter => ({
|
||||||
value: filter.id,
|
value: filter.id,
|
||||||
label: filter.title,
|
label: filter.title,
|
||||||
@ -598,6 +610,28 @@ const FiltersConfigForm = (
|
|||||||
setActiveFilterPanelKey(activeFilterPanelKey);
|
setActiveFilterPanelKey(activeFilterPanelKey);
|
||||||
}, [hasCheckedAdvancedControl]);
|
}, [hasCheckedAdvancedControl]);
|
||||||
|
|
||||||
|
const initiallyExcludedCharts = useMemo(() => {
|
||||||
|
const excluded: number[] = [];
|
||||||
|
if (formFilter?.dataset?.value === undefined) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.values(charts).forEach((chart: Chart) => {
|
||||||
|
const chartDatasetUid = chart.formData?.datasource;
|
||||||
|
if (chartDatasetUid === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (loadedDatasets[chartDatasetUid]?.id !== formFilter?.dataset?.value) {
|
||||||
|
excluded.push(chart.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return excluded;
|
||||||
|
}, [
|
||||||
|
JSON.stringify(charts),
|
||||||
|
formFilter?.dataset?.value,
|
||||||
|
JSON.stringify(loadedDatasets),
|
||||||
|
]);
|
||||||
|
|
||||||
if (removed) {
|
if (removed) {
|
||||||
return <RemovedFilter onClick={() => restoreFilter(filterId)} />;
|
return <RemovedFilter onClick={() => restoreFilter(filterId)} />;
|
||||||
}
|
}
|
||||||
@ -1053,14 +1087,13 @@ const FiltersConfigForm = (
|
|||||||
forceRender
|
forceRender
|
||||||
>
|
>
|
||||||
<FilterScope
|
<FilterScope
|
||||||
updateFormValues={(values: any) =>
|
updateFormValues={updateFormValues}
|
||||||
setNativeFilterFieldValues(form, filterId, values)
|
|
||||||
}
|
|
||||||
pathToFormValue={['filters', filterId]}
|
pathToFormValue={['filters', filterId]}
|
||||||
forceUpdate={forceUpdate}
|
forceUpdate={forceUpdate}
|
||||||
scope={filterToEdit?.scope}
|
filterScope={filterToEdit?.scope}
|
||||||
formScope={formFilter?.scope}
|
formFilterScope={formFilter?.scope}
|
||||||
formScoping={formFilter?.scoping}
|
formScopingType={formFilter?.scoping}
|
||||||
|
initiallyExcludedCharts={initiallyExcludedCharts}
|
||||||
/>
|
/>
|
||||||
</TabPane>
|
</TabPane>
|
||||||
</StyledTabs>
|
</StyledTabs>
|
||||||
|
@ -16,7 +16,13 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { ChartProps, ExtraFormData, JsonObject } from '@superset-ui/core';
|
import {
|
||||||
|
ChartProps,
|
||||||
|
ExtraFormData,
|
||||||
|
GenericDataType,
|
||||||
|
JsonObject,
|
||||||
|
} from '@superset-ui/core';
|
||||||
|
import { DatasourceMeta } from '@superset-ui/chart-controls';
|
||||||
import { chart } from 'src/chart/chartReducer';
|
import { chart } from 'src/chart/chartReducer';
|
||||||
import componentTypes from 'src/dashboard/util/componentTypes';
|
import componentTypes from 'src/dashboard/util/componentTypes';
|
||||||
import { DataMaskStateWithId } from '../dataMask/types';
|
import { DataMaskStateWithId } from '../dataMask/types';
|
||||||
@ -38,6 +44,7 @@ export interface ChartQueryPayload extends Partial<ChartReducerInitialState> {
|
|||||||
export type Chart = ChartState & {
|
export type Chart = ChartState & {
|
||||||
formData: {
|
formData: {
|
||||||
viz_type: string;
|
viz_type: string;
|
||||||
|
datasource: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -60,10 +67,16 @@ export type DashboardInfo = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type ChartsState = { [key: string]: Chart };
|
export type ChartsState = { [key: string]: Chart };
|
||||||
|
export type DatasourcesState = {
|
||||||
|
[key: string]: DatasourceMeta & {
|
||||||
|
column_types: GenericDataType[];
|
||||||
|
table_name: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
/** Root state of redux */
|
/** Root state of redux */
|
||||||
export type RootState = {
|
export type RootState = {
|
||||||
datasources: JsonObject;
|
datasources: DatasourcesState;
|
||||||
sliceEntities: JsonObject;
|
sliceEntities: JsonObject;
|
||||||
charts: ChartsState;
|
charts: ChartsState;
|
||||||
dashboardLayout: DashboardLayoutState;
|
dashboardLayout: DashboardLayoutState;
|
||||||
|
Loading…
Reference in New Issue
Block a user