blob: b1740b0c6500a2caf2c098c0095139a15a17c59e [file] [log] [blame]
/**
* 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 { t } from '@superset-ui/core';
import { Charts, Layout, LayoutItem } from 'src/dashboard/types';
import {
CHART_TYPE,
DASHBOARD_ROOT_TYPE,
TAB_TYPE,
} from 'src/dashboard/util/componentTypes';
import { FormInstance } from 'antd/lib/form';
import React from 'react';
import { DASHBOARD_ROOT_ID } from 'src/dashboard/util/constants';
import { TreeItem } from './types';
import { FilterType, Scope } from '../types';
export const useForceUpdate = () => {
const [, updateState] = React.useState({});
return React.useCallback(() => updateState({}), []);
};
export const isShowTypeInTree = ({ type, meta }: LayoutItem, charts?: Charts) =>
(type === TAB_TYPE || type === CHART_TYPE || type === DASHBOARD_ROOT_TYPE) &&
(!charts || charts[meta?.chartId]?.formData?.viz_type !== 'filter_box');
export const buildTree = (
node: LayoutItem,
treeItem: TreeItem,
layout: Layout,
charts: Charts,
validNodes: string[],
) => {
let itemToPass: TreeItem = treeItem;
if (
isShowTypeInTree(node, charts) &&
node.type !== DASHBOARD_ROOT_TYPE &&
validNodes.includes(node.id)
) {
const currentTreeItem = {
key: node.id,
title: node.meta.sliceName || node.meta.text || node.id.toString(),
children: [],
};
treeItem.children.push(currentTreeItem);
itemToPass = currentTreeItem;
}
node.children.forEach(child =>
buildTree(layout[child], itemToPass, layout, charts, validNodes),
);
};
const addInvisibleParents = (layout: Layout, item: string) => [
...(layout[item]?.children || []),
...Object.values(layout)
.filter(
val =>
val.parents &&
val.parents[val.parents.length - 1] === item &&
!isShowTypeInTree(layout[val.parents[val.parents.length - 1]]),
)
.map(({ id }) => id),
];
// Generate checked options for Ant tree from redux scope
const checkTreeItem = (
checkedItems: string[],
layout: Layout,
items: string[],
excluded: number[],
) => {
items.forEach(item => {
checkTreeItem(
checkedItems,
layout,
addInvisibleParents(layout, item),
excluded,
);
if (
layout[item]?.type === CHART_TYPE &&
!excluded.includes(layout[item]?.meta.chartId)
) {
checkedItems.push(item);
}
});
};
export const getTreeCheckedItems = (scope: Scope, layout: Layout) => {
const checkedItems: string[] = [];
checkTreeItem(checkedItems, layout, [...scope.rootPath], [...scope.excluded]);
return [...new Set(checkedItems)];
};
// Looking for first common parent for selected charts/tabs/tab
export const findFilterScope = (
checkedKeys: string[],
layout: Layout,
): Scope => {
if (!checkedKeys.length) {
return {
rootPath: [],
excluded: [],
};
}
// Get arrays of parents for selected charts
const checkedItemParents = checkedKeys
.filter(item => layout[item]?.type === CHART_TYPE)
.map(key => {
const parents = [DASHBOARD_ROOT_ID, ...(layout[key]?.parents || [])];
return parents.filter(parent => isShowTypeInTree(layout[parent]));
});
// Sort arrays of parents to get first shortest array of parents,
// that means on it's level of parents located common parent, from this place parents start be different
checkedItemParents.sort((p1, p2) => p1.length - p2.length);
const rootPath = checkedItemParents.map(
parents => parents[checkedItemParents[0].length - 1],
);
const excluded: number[] = [];
const isExcluded = (parent: string, item: string) =>
rootPath.includes(parent) && !checkedKeys.includes(item);
// looking for charts to be excluded: iterate over all charts
// and looking for charts that have one of their parents in `rootPath` and not in selected items
Object.entries(layout).forEach(([key, value]) => {
if (
value.type === CHART_TYPE &&
[DASHBOARD_ROOT_ID, ...value.parents]?.find(parent =>
isExcluded(parent, key),
)
) {
excluded.push(value.meta.chartId);
}
});
return {
rootPath: [...new Set(rootPath)],
excluded,
};
};
export const FilterTypeNames = {
[FilterType.filter_select]: t('Select'),
[FilterType.filter_range]: t('Range'),
};
export const setFilterFieldValues = (
form: FormInstance,
filterId: string,
values: object,
) => {
const formFilters = form.getFieldValue('filters');
form.setFieldsValue({
filters: {
...formFilters,
[filterId]: {
...formFilters[filterId],
...values,
},
},
});
};
export const isScopingAll = (scope: Scope) =>
!scope || (scope.rootPath[0] === DASHBOARD_ROOT_ID && !scope.excluded.length);
type AppendFormData = {
filters: {
val?: number | string | null;
}[];
};
export const extractDefaultValue = {
[FilterType.filter_select]: (appendFormData: AppendFormData) =>
appendFormData.filters?.[0]?.val,
[FilterType.filter_range]: (appendFormData: AppendFormData) => ({
min: appendFormData.filters?.[0].val,
max: appendFormData.filters?.[1].val,
}),
};