diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterControls.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterControls.tsx
index dd1bfd97110..d0591dfa72d 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterControls.tsx
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterControls.tsx
@@ -44,10 +44,6 @@ const Wrapper = styled.div`
padding: ${({ theme }) => theme.gridUnit * 4}px;
// 108px padding to make room for buttons with position: absolute
padding-bottom: ${({ theme }) => theme.gridUnit * 27}px;
-
- &:hover {
- cursor: pointer;
- }
`;
type FilterControlsProps = {
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/DependenciesRow.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/DependenciesRow.tsx
new file mode 100644
index 00000000000..c585fc40e41
--- /dev/null
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/DependenciesRow.tsx
@@ -0,0 +1,113 @@
+/**
+ * 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, { useCallback, useMemo, useRef } from 'react';
+import { useDispatch } from 'react-redux';
+import { css, t, useTheme } from '@superset-ui/core';
+import { setDirectPathToChild } from 'src/dashboard/actions/dashboardState';
+import Icons from 'src/components/Icons';
+import {
+ DependencyItem,
+ Row,
+ RowLabel,
+ RowTruncationCount,
+ RowValue,
+ TooltipList,
+} from './Styles';
+import { useFilterDependencies } from './useFilterDependencies';
+import { useTruncation } from './useTruncation';
+import { DependencyValueProps, FilterCardRowProps } from './types';
+import { TooltipWithTruncation } from './TooltipWithTruncation';
+
+const DependencyValue = ({ dependency, label }: DependencyValueProps) => {
+ const dispatch = useDispatch();
+ const handleClick = useCallback(() => {
+ dispatch(setDirectPathToChild([dependency.id]));
+ }, [dependency.id, dispatch]);
+ return (
+
+ {label}
+
+ );
+};
+
+export const DependenciesRow = React.memo(({ filter }: FilterCardRowProps) => {
+ const dependencies = useFilterDependencies(filter);
+ const dependenciesRef = useRef(null);
+ const [elementsTruncated, hasHiddenElements] = useTruncation(dependenciesRef);
+ const theme = useTheme();
+
+ const tooltipText = useMemo(
+ () =>
+ elementsTruncated > 0 && dependencies ? (
+
+ {dependencies.map(dependency => (
+
+
+
+ ))}
+
+ ) : null,
+ [elementsTruncated, dependencies],
+ );
+
+ if (!Array.isArray(dependencies) || dependencies.length === 0) {
+ return null;
+ }
+ return (
+
+
+ {t('Dependent on')}{' '}
+
+
+
+
+
+
+ {dependencies.map((dependency, index) => (
+
+ ))}
+
+ {hasHiddenElements && (
+ +{elementsTruncated}
+ )}
+
+
+ );
+});
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/FilterCardContent.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/FilterCardContent.tsx
new file mode 100644
index 00000000000..41696ed98f8
--- /dev/null
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/FilterCardContent.tsx
@@ -0,0 +1,34 @@
+/**
+ * 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 { Filter } from '@superset-ui/core';
+import { ScopeRow } from './ScopeRow';
+import { DependenciesRow } from './DependenciesRow';
+import { NameRow } from './NameRow';
+import { TypeRow } from './TypeRow';
+
+export const FilterCardContent = ({ filter }: { filter: Filter }) => (
+
+
+
+
+
+
+);
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/NameRow.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/NameRow.tsx
new file mode 100644
index 00000000000..05cb8119487
--- /dev/null
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/NameRow.tsx
@@ -0,0 +1,50 @@
+/**
+ * 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, { useRef } from 'react';
+import { css, SupersetTheme } from '@superset-ui/core';
+import Icons from 'src/components/Icons';
+import { Row, FilterName } from './Styles';
+import { FilterCardRowProps } from './types';
+import { useTruncation } from './useTruncation';
+import { TooltipWithTruncation } from './TooltipWithTruncation';
+
+export const NameRow = ({ filter }: FilterCardRowProps) => {
+ const filterNameRef = useRef(null);
+ const [elementsTruncated] = useTruncation(filterNameRef);
+ return (
+
+ css`
+ margin-bottom: ${theme.gridUnit * 3}px;
+ `
+ }
+ >
+
+ css`
+ margin-right: ${theme.gridUnit}px;
+ `
+ }
+ />
+
+ {filter.name}
+
+
+ );
+};
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/ScopeRow.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/ScopeRow.tsx
new file mode 100644
index 00000000000..8f005874d23
--- /dev/null
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/ScopeRow.tsx
@@ -0,0 +1,68 @@
+/**
+ * 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, { useMemo, useRef } from 'react';
+import { t } from '@superset-ui/core';
+import { useFilterScope } from './useFilterScope';
+import {
+ Row,
+ RowLabel,
+ RowTruncationCount,
+ RowValue,
+ TooltipList,
+} from './Styles';
+import { useTruncation } from './useTruncation';
+import { FilterCardRowProps } from './types';
+import { TooltipWithTruncation } from './TooltipWithTruncation';
+
+export const ScopeRow = React.memo(({ filter }: FilterCardRowProps) => {
+ const scope = useFilterScope(filter);
+ const scopeRef = useRef(null);
+
+ const [elementsTruncated, hasHiddenElements] = useTruncation(scopeRef);
+ const tooltipText = useMemo(
+ () =>
+ elementsTruncated > 0 && scope ? (
+
+ {scope.map(val => (
+ {val}
+ ))}
+
+ ) : null,
+ [elementsTruncated, scope],
+ );
+
+ if (!Array.isArray(scope) || scope.length === 0) {
+ return null;
+ }
+ return (
+
+ {t('Scope')}
+
+
+ {scope.map((element, index) => (
+ {index === 0 ? element : `, ${element}`}
+ ))}
+
+ {hasHiddenElements > 0 && (
+ +{elementsTruncated}
+ )}
+
+
+ );
+});
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/Styles.ts b/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/Styles.ts
new file mode 100644
index 00000000000..9799a0d333b
--- /dev/null
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/Styles.ts
@@ -0,0 +1,90 @@
+/**
+ * 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 { css, styled } from '@superset-ui/core';
+
+export const Row = styled.div`
+ ${({ theme }) => css`
+ display: flex;
+ align-items: center;
+ margin: ${theme.gridUnit}px 0;
+ font-size: ${theme.typography.sizes.s}px;
+
+ &:first-of-type {
+ margin-top: 0;
+ }
+
+ &:last-of-type {
+ margin-bottom: 0;
+ }
+
+ & .ant-tooltip-open {
+ display: inline-flex;
+ }
+ `};
+`;
+
+export const RowLabel = styled.span`
+ ${({ theme }) => css`
+ color: ${theme.colors.grayscale.base};
+ padding-right: ${theme.gridUnit * 4}px;
+ margin-right: auto;
+ text-transform: uppercase;
+ white-space: nowrap;
+ `};
+`;
+
+export const RowValue = styled.div`
+ ${({ theme }) => css`
+ color: ${theme.colors.grayscale.dark1};
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ display: inline;
+ `};
+`;
+
+export const FilterName = styled.span`
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+`;
+
+export const DependencyItem = styled.span`
+ text-decoration: underline;
+ cursor: pointer;
+`;
+
+export const RowTruncationCount = styled.span`
+ ${({ theme }) => css`
+ color: ${theme.colors.primary.base};
+ `}
+`;
+
+export const TooltipList = styled.ul`
+ ${({ theme }) => css`
+ padding-left: ${theme.gridUnit * 3}px;
+ margin-bottom: 0;
+ `};
+`;
+
+export const TooltipTrigger = styled.div`
+ min-width: 0;
+ display: inline-flex;
+ white-space: nowrap;
+`;
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/TooltipWithTruncation.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/TooltipWithTruncation.tsx
new file mode 100644
index 00000000000..cfb36013a74
--- /dev/null
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/TooltipWithTruncation.tsx
@@ -0,0 +1,37 @@
+/**
+ * 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 { TooltipProps } from 'antd/lib/tooltip';
+import { Tooltip } from 'src/components/Tooltip';
+import { TooltipTrigger } from './Styles';
+
+export const TooltipWithTruncation = ({
+ title,
+ children,
+ ...props
+}: TooltipProps) => (
+
+ {children}
+
+);
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/TypeRow.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/TypeRow.tsx
new file mode 100644
index 00000000000..f276c32f283
--- /dev/null
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/TypeRow.tsx
@@ -0,0 +1,35 @@
+/**
+ * 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, { useMemo } from 'react';
+import { getChartMetadataRegistry, t } from '@superset-ui/core';
+import { Row, RowLabel, RowValue } from './Styles';
+import { FilterCardRowProps } from './types';
+
+export const TypeRow = ({ filter }: FilterCardRowProps) => {
+ const metadata = useMemo(
+ () => getChartMetadataRegistry().get(filter.filterType),
+ [filter.filterType],
+ );
+ return (
+
+ {t('Filter type')}
+ {metadata?.name}
+
+ );
+};
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/index.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/index.tsx
new file mode 100644
index 00000000000..6f544fc95e8
--- /dev/null
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/index.tsx
@@ -0,0 +1,50 @@
+/**
+ * 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, { useState } from 'react';
+import Popover from 'src/components/Popover';
+import { FilterCardContent } from './FilterCardContent';
+import { FilterCardProps } from './types';
+
+export const FilterCard = ({
+ children,
+ filter,
+ getPopupContainer,
+ isVisible: externalIsVisible = true,
+}: FilterCardProps) => {
+ const [internalIsVisible, setInternalIsVisible] = useState(false);
+
+ return (
+ {
+ setInternalIsVisible(visible);
+ }}
+ visible={externalIsVisible && internalIsVisible}
+ content={}
+ getPopupContainer={getPopupContainer ?? (() => document.body)}
+ destroyTooltipOnHide
+ >
+ {children}
+
+ );
+};
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/types.ts b/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/types.ts
new file mode 100644
index 00000000000..dd5bdf477be
--- /dev/null
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/types.ts
@@ -0,0 +1,36 @@
+/**
+ * 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 { ReactNode } from 'react';
+import { Filter } from '@superset-ui/core';
+
+export interface FilterCardProps {
+ children: ReactNode;
+ filter: Filter;
+ getPopupContainer?: (node: HTMLElement) => HTMLElement;
+ isVisible?: boolean;
+}
+
+export interface FilterCardRowProps {
+ filter: Filter;
+}
+
+export interface DependencyValueProps {
+ dependency: Filter;
+ label: string;
+}
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/useFilterDependencies.ts b/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/useFilterDependencies.ts
new file mode 100644
index 00000000000..60966658d61
--- /dev/null
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/useFilterDependencies.ts
@@ -0,0 +1,34 @@
+/**
+ * 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 { ensureIsArray, Filter } from '@superset-ui/core';
+import { useSelector } from 'react-redux';
+import { RootState } from 'src/dashboard/types';
+
+export const useFilterDependencies = (filter: Filter) => {
+ const filterDependencyIds = ensureIsArray(filter.cascadeParentIds);
+ return useSelector(state => {
+ if (filterDependencyIds.length === 0) {
+ return [];
+ }
+ return filterDependencyIds.reduce((acc: Filter[], filterDependencyId) => {
+ acc.push(state.nativeFilters.filters[filterDependencyId]);
+ return acc;
+ }, []);
+ });
+};
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/useFilterScope.ts b/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/useFilterScope.ts
new file mode 100644
index 00000000000..f713076168c
--- /dev/null
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/useFilterScope.ts
@@ -0,0 +1,143 @@
+/**
+ * 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 { Filter, t } from '@superset-ui/core';
+import { useSelector } from 'react-redux';
+import {
+ ChartsState,
+ Layout,
+ LayoutItem,
+ RootState,
+} from 'src/dashboard/types';
+import { DASHBOARD_ROOT_ID } from 'src/dashboard/util/constants';
+import { CHART_TYPE } from 'src/dashboard/util/componentTypes';
+import { useMemo } from 'react';
+
+const extractTabLabel = (tab?: LayoutItem) =>
+ tab?.meta?.text || tab?.meta?.defaultText;
+const extractChartLabel = (chart?: LayoutItem) =>
+ chart?.meta?.sliceNameOverride || chart?.meta?.sliceName || chart?.id;
+
+const useCharts = () => {
+ const charts = useSelector(state => state.charts);
+ return useMemo(() => Object.values(charts), [charts]);
+};
+
+export const useFilterScope = (filter: Filter) => {
+ const layout = useSelector(
+ state => state.dashboardLayout.present,
+ );
+ const charts = useCharts();
+
+ return useMemo(() => {
+ let topLevelTabs: string[] | undefined;
+ const topElementId = layout[DASHBOARD_ROOT_ID].children[0];
+ if (topElementId.startsWith('TABS-')) {
+ topLevelTabs = layout[topElementId].children;
+ }
+
+ // no charts in scope
+ if (filter.scope.rootPath.length === 0) {
+ return undefined;
+ }
+
+ // all charts in scope
+ // no charts excluded and no top level tabs
+ // OR no charts excluded and every top level tab is in rootPath
+ if (
+ filter.scope.excluded.length === 0 &&
+ (filter.scope.rootPath[0] === DASHBOARD_ROOT_ID ||
+ (topLevelTabs &&
+ topLevelTabs.every(topLevelTab =>
+ filter.scope.rootPath.includes(topLevelTab),
+ )))
+ ) {
+ return [t('All charts')];
+ }
+
+ // no charts excluded and not every top level tab in scope
+ // returns "TAB1, TAB2"
+ if (filter.scope.excluded.length === 0 && topLevelTabs) {
+ return filter.scope.rootPath.map(tabId => extractTabLabel(layout[tabId]));
+ }
+
+ const layoutCharts = Object.values(layout).filter(
+ layoutElement => layoutElement.type === CHART_TYPE,
+ );
+
+ // no top level tabs, charts excluded
+ // returns "CHART1, CHART2"
+ if (filter.scope.rootPath[0] === DASHBOARD_ROOT_ID) {
+ return charts
+ .filter(chart => !filter.scope.excluded.includes(chart.id))
+ .map(chart => {
+ const layoutElement = layoutCharts.find(
+ layoutChart => layoutChart.meta.chartId === chart.id,
+ );
+ return extractChartLabel(layoutElement);
+ })
+ .filter(Boolean);
+ }
+
+ // top level tabs, charts excluded
+ // returns "TAB1, TAB2, CHART1"
+ if (topLevelTabs) {
+ // We start assuming that all charts are in scope for all tabs in the root path
+ const topLevelTabsInFullScope = [...filter.scope.rootPath];
+ const layoutChartElementsInTabsInScope = layoutCharts.filter(element =>
+ element.parents.some(parent =>
+ topLevelTabsInFullScope.includes(parent),
+ ),
+ );
+ // Exclude the tabs that contain excluded charts
+ filter.scope.excluded.forEach(chartId => {
+ const excludedIndex = topLevelTabsInFullScope.findIndex(tabId =>
+ layoutChartElementsInTabsInScope
+ .find(chart => chart.meta.chartId === chartId)
+ ?.parents.includes(tabId),
+ );
+ if (excludedIndex > -1) {
+ topLevelTabsInFullScope.splice(excludedIndex, 1);
+ }
+ });
+ // Handle charts that are in scope but belong to excluded tabs.
+ const chartsInExcludedTabs = charts
+ .filter(chart => !filter.scope.excluded.includes(chart.id))
+ .reduce((acc: LayoutItem[], chart) => {
+ const layoutChartElementInExcludedTab =
+ layoutChartElementsInTabsInScope.find(
+ element =>
+ element.meta.chartId === chart.id &&
+ element.parents.every(
+ parent => !topLevelTabsInFullScope.includes(parent),
+ ),
+ );
+ if (layoutChartElementInExcludedTab) {
+ acc.push(layoutChartElementInExcludedTab);
+ }
+ return acc;
+ }, []);
+ // Join tab names and chart names
+ return topLevelTabsInFullScope
+ .map(tabId => extractTabLabel(layout[tabId]))
+ .concat(chartsInExcludedTabs.map(extractChartLabel));
+ }
+
+ return undefined;
+ }, [charts, filter.scope.excluded, filter.scope.rootPath, layout]);
+};
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/useTruncation.ts b/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/useTruncation.ts
new file mode 100644
index 00000000000..4f2e1723691
--- /dev/null
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/useTruncation.ts
@@ -0,0 +1,58 @@
+/**
+ * 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 { RefObject, useLayoutEffect, useState } from 'react';
+
+export const useTruncation = (elementRef: RefObject) => {
+ const [elementsTruncated, setElementsTruncated] = useState(0);
+ const [hasHiddenElements, setHasHiddenElements] = useState(false);
+
+ useLayoutEffect(() => {
+ const currentElement = elementRef.current;
+ if (!currentElement) {
+ return;
+ }
+ const { scrollWidth, clientWidth, childNodes } = currentElement;
+ if (scrollWidth > clientWidth) {
+ // "..." is around 6px wide
+ const maxWidth = clientWidth - 6;
+ const elementsCount = childNodes.length;
+ let width = 0;
+ let i = 0;
+ while (width < maxWidth) {
+ width += (childNodes[i] as HTMLElement).offsetWidth;
+ i += 1;
+ }
+ if (i === elementsCount) {
+ setElementsTruncated(1);
+ setHasHiddenElements(false);
+ } else {
+ setElementsTruncated(elementsCount - i);
+ setHasHiddenElements(true);
+ }
+ } else {
+ setElementsTruncated(0);
+ }
+ }, [
+ elementRef.current?.offsetWidth,
+ elementRef.current?.clientWidth,
+ elementRef,
+ ]);
+
+ return [elementsTruncated, hasHiddenElements];
+};
diff --git a/superset-frontend/src/dashboard/containers/DashboardPage.tsx b/superset-frontend/src/dashboard/containers/DashboardPage.tsx
index c4638d8d345..878f3d68695 100644
--- a/superset-frontend/src/dashboard/containers/DashboardPage.tsx
+++ b/superset-frontend/src/dashboard/containers/DashboardPage.tsx
@@ -17,8 +17,9 @@
* under the License.
*/
import React, { FC, useRef, useEffect, useState } from 'react';
-import { FeatureFlag, isFeatureEnabled, t } from '@superset-ui/core';
+import { FeatureFlag, isFeatureEnabled, t, useTheme } from '@superset-ui/core';
import { useDispatch, useSelector } from 'react-redux';
+import { Global } from '@emotion/react';
import { useParams } from 'react-router-dom';
import { useToasts } from 'src/components/MessageToasts/withToasts';
import Loading from 'src/components/Loading';
@@ -49,6 +50,7 @@ import { getUrlParam } from 'src/utils/urlUtils';
import { canUserEditDashboard } from 'src/dashboard/util/findPermission';
import { getFilterSets } from '../actions/nativeFilters';
import { getFilterValue } from '../components/nativeFilters/FilterBar/keyValue';
+import { filterCardPopoverStyle } from '../styles';
export const MigrationContext = React.createContext(
FILTER_BOX_MIGRATION_STATES.NOOP,
@@ -68,6 +70,7 @@ const originalDocumentTitle = document.title;
const DashboardPage: FC = () => {
const dispatch = useDispatch();
+ const theme = useTheme();
const user = useSelector(
state => state.user,
);
@@ -226,6 +229,7 @@ const DashboardPage: FC = () => {
return (
<>
+
css`
+ .filter-card-popover {
+ width: 240px;
+ padding: 0;
+ border-radius: 4px;
+
+ .ant-popover-inner-content {
+ padding: ${theme.gridUnit * 4}px;
+ }
+
+ .ant-popover-arrow {
+ display: none;
+ }
+ }
+
+ .filter-card-tooltip {
+ &.ant-tooltip-placement-bottom {
+ padding-top: 0;
+ & .ant-tooltip-arrow {
+ top: -13px;
+ }
+ }
+ }
+`;