This is an automated email from the ASF dual-hosted git repository.
kgabryje pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/master by this push:
new ae0f2ce3c1 fix: useTruncation infinite loop, reenable dashboard cross
links on ChartList (#27701)
ae0f2ce3c1 is described below
commit ae0f2ce3c11aaeef9d8f3ee17ab68d4a4219ae81
Author: Kamil Gabryjelski <[email protected]>
AuthorDate: Tue Apr 9 12:30:57 2024 +0200
fix: useTruncation infinite loop, reenable dashboard cross links on
ChartList (#27701)
---
.../cypress/e2e/chart_list/list.test.ts | 9 +-
.../cypress-base/cypress/e2e/explore/chart.test.js | 4 +-
.../useChildElementTruncation.test.ts | 196 +++++++++++++++++----
.../useTruncation/useChildElementTruncation.ts | 122 ++++++-------
.../src/components/ListView/CrossLinks.tsx | 25 +--
.../components/ListView/DashboardCrossLinks.tsx | 37 ++++
.../src/components/TruncatedList/index.tsx | 10 +-
.../nativeFilters/FilterCard/DependenciesRow.tsx | 10 +-
.../nativeFilters/FilterCard/NameRow.tsx | 5 +-
.../nativeFilters/FilterCard/ScopeRow.tsx | 12 +-
.../DashboardsSubMenu.test.tsx | 2 +-
.../useExploreAdditionalActionsMenu/index.jsx | 2 +-
superset-frontend/src/pages/ChartList/index.tsx | 19 +-
13 files changed, 278 insertions(+), 175 deletions(-)
diff --git a/superset-frontend/cypress-base/cypress/e2e/chart_list/list.test.ts
b/superset-frontend/cypress-base/cypress/e2e/chart_list/list.test.ts
index 3cd1f91b49..4e1dc17410 100644
--- a/superset-frontend/cypress-base/cypress/e2e/chart_list/list.test.ts
+++ b/superset-frontend/cypress-base/cypress/e2e/chart_list/list.test.ts
@@ -54,7 +54,7 @@ function visitChartList() {
}
describe('Charts list', () => {
- describe.skip('Cross-referenced dashboards', () => {
+ describe('Cross-referenced dashboards', () => {
beforeEach(() => {
cy.createSampleDashboards([0, 1, 2, 3]);
cy.createSampleCharts([0]);
@@ -112,9 +112,10 @@ describe('Charts list', () => {
cy.getBySel('sort-header').eq(1).contains('Name');
cy.getBySel('sort-header').eq(2).contains('Type');
cy.getBySel('sort-header').eq(3).contains('Dataset');
- cy.getBySel('sort-header').eq(4).contains('Owners');
- cy.getBySel('sort-header').eq(5).contains('Last modified');
- cy.getBySel('sort-header').eq(6).contains('Actions');
+ cy.getBySel('sort-header').eq(4).contains('On dashboards');
+ cy.getBySel('sort-header').eq(5).contains('Owners');
+ cy.getBySel('sort-header').eq(6).contains('Last modified');
+ cy.getBySel('sort-header').eq(7).contains('Actions');
});
it('should sort correctly in list mode', () => {
diff --git a/superset-frontend/cypress-base/cypress/e2e/explore/chart.test.js
b/superset-frontend/cypress-base/cypress/e2e/explore/chart.test.js
index d198672ef3..14c386e0ea 100644
--- a/superset-frontend/cypress-base/cypress/e2e/explore/chart.test.js
+++ b/superset-frontend/cypress-base/cypress/e2e/explore/chart.test.js
@@ -31,13 +31,13 @@ const SAMPLE_DASHBOARDS_INDEXES = [0, 1, 2, 3, 4, 5, 6, 7,
8, 9, 10];
function openDashboardsAddedTo() {
cy.getBySel('actions-trigger').click();
cy.get('.ant-dropdown-menu-submenu-title')
- .contains('Dashboards added to')
+ .contains('On dashboards')
.trigger('mouseover', { force: true });
}
function closeDashboardsAddedTo() {
cy.get('.ant-dropdown-menu-submenu-title')
- .contains('Dashboards added to')
+ .contains('On dashboards')
.trigger('mouseout', { force: true });
cy.getBySel('actions-trigger').click();
}
diff --git
a/superset-frontend/packages/superset-ui-core/src/hooks/useTruncation/useChildElementTruncation.test.ts
b/superset-frontend/packages/superset-ui-core/src/hooks/useTruncation/useChildElementTruncation.test.ts
index ee3e95139f..7441c25987 100644
---
a/superset-frontend/packages/superset-ui-core/src/hooks/useTruncation/useChildElementTruncation.test.ts
+++
b/superset-frontend/packages/superset-ui-core/src/hooks/useTruncation/useChildElementTruncation.test.ts
@@ -20,6 +20,10 @@ import { renderHook } from '@testing-library/react-hooks';
import { RefObject } from 'react';
import useChildElementTruncation from './useChildElementTruncation';
+let observeMock: jest.Mock;
+let disconnectMock: jest.Mock;
+let originalResizeObserver: typeof ResizeObserver;
+
const genElements = (
scrollWidth: number,
clientWidth: number,
@@ -34,26 +38,87 @@ const genElements = (
};
return [elementRef, plusRef];
};
-const useTruncation = (elementRef: any, plusRef: any) =>
- useChildElementTruncation(
- elementRef as RefObject<HTMLElement>,
- plusRef as RefObject<HTMLElement>,
+
+const testTruncationHookWithInitialValues = (
+ [scrollWidth, clientWidth, offsetWidth, childNodes = []]: [
+ number,
+ number,
+ number | undefined,
+ any?,
+ ],
+ expectedElementsTruncated: number,
+ shouldHaveHiddenElements: boolean,
+) => {
+ const [elementRef, plusRef] = genElements(
+ scrollWidth,
+ clientWidth,
+ offsetWidth,
+ childNodes,
);
+ const { result, rerender } = renderHook(() => useChildElementTruncation());
+
+ Object.defineProperty(result.current[0], 'current', {
+ value: elementRef.current,
+ });
+ Object.defineProperty(result.current[1], 'current', {
+ value: plusRef.current,
+ });
+
+ rerender();
+
+ expect(result.current).toEqual([
+ elementRef,
+ plusRef,
+ expectedElementsTruncated,
+ shouldHaveHiddenElements,
+ ]);
+};
+
+beforeAll(() => {
+ // Store the original ResizeObserver
+ originalResizeObserver = window.ResizeObserver;
+
+ // Mock ResizeObserver
+ observeMock = jest.fn();
+ disconnectMock = jest.fn();
+ window.ResizeObserver = jest.fn(() => ({
+ observe: observeMock,
+ disconnect: disconnectMock,
+ })) as unknown as typeof ResizeObserver;
+});
+
+afterAll(() => {
+ // Restore original ResizeObserver after all tests are done
+ window.ResizeObserver = originalResizeObserver;
+});
+
+afterEach(() => {
+ observeMock.mockClear();
+ disconnectMock.mockClear();
+});
test('should return [0, false] when elementRef.current is not defined', () => {
- const { result } = renderHook(() =>
- useTruncation({ current: undefined }, { current: undefined }),
- );
+ const { result } = renderHook(() => useChildElementTruncation());
+ expect(result.current).toEqual([
+ { current: null },
+ { current: null },
+ 0,
+ false,
+ ]);
- expect(result.current).toEqual([0, false]);
+ expect(observeMock).not.toHaveBeenCalled();
});
test('should not recompute when previousEffectInfo is the same as previous',
() => {
- const elementRef = { current: document.createElement('div') };
- const plusRef = { current: document.createElement('div') };
- const { result, rerender } = renderHook(() =>
- useTruncation(elementRef, plusRef),
- );
+ const { result, rerender } = renderHook(() => useChildElementTruncation());
+
+ Object.defineProperty(result.current[0], 'current', {
+ value: document.createElement('div'),
+ });
+ Object.defineProperty(result.current[1], 'current', {
+ value: document.createElement('div'),
+ });
+
const previousEffectInfo = result.current;
rerender();
@@ -62,41 +127,96 @@ test('should not recompute when previousEffectInfo is the
same as previous', ()
});
test('should return [0, false] when there are no truncated/hidden elements',
() => {
- const [elementRef, plusRef] = genElements(100, 100, 10);
- const { result } = renderHook(() => useTruncation(elementRef, plusRef));
- expect(result.current).toEqual([0, false]);
+ testTruncationHookWithInitialValues([100, 100, 10], 0, false);
});
test('should return [1, false] when there is only one truncated element', ()
=> {
- const [elementRef, plusRef] = genElements(150, 100, 10);
- const { result } = renderHook(() => useTruncation(elementRef, plusRef));
- expect(result.current).toEqual([1, false]);
+ testTruncationHookWithInitialValues([150, 100, 10], 1, false);
});
test('should return [1, true] with one truncated and hidden elements', () => {
- const [elementRef, plusRef] = genElements(150, 100, 10, [
- { offsetWidth: 150 } as HTMLElement,
- { offsetWidth: 150 } as HTMLElement,
- ]);
- const { result } = renderHook(() => useTruncation(elementRef, plusRef));
- expect(result.current).toEqual([1, true]);
+ testTruncationHookWithInitialValues(
+ [
+ 150,
+ 100,
+ 10,
+ [
+ { offsetWidth: 150 } as HTMLElement,
+ { offsetWidth: 150 } as HTMLElement,
+ ],
+ ],
+ 1,
+ true,
+ );
});
test('should return [2, true] with 2 truncated and hidden elements', () => {
- const [elementRef, plusRef] = genElements(150, 100, 10, [
- { offsetWidth: 150 } as HTMLElement,
- { offsetWidth: 150 } as HTMLElement,
- { offsetWidth: 150 } as HTMLElement,
- ]);
- const { result } = renderHook(() => useTruncation(elementRef, plusRef));
- expect(result.current).toEqual([2, true]);
+ testTruncationHookWithInitialValues(
+ [
+ 150,
+ 100,
+ 10,
+ [
+ { offsetWidth: 150 } as HTMLElement,
+ { offsetWidth: 150 } as HTMLElement,
+ { offsetWidth: 150 } as HTMLElement,
+ ],
+ ],
+ 2,
+ true,
+ );
});
test('should return [1, true] with plusSize offsetWidth undefined', () => {
- const [elementRef, plusRef] = genElements(150, 100, undefined, [
- { offsetWidth: 150 } as HTMLElement,
- { offsetWidth: 150 } as HTMLElement,
- ]);
- const { result } = renderHook(() => useTruncation(elementRef, plusRef));
- expect(result.current).toEqual([1, true]);
+ testTruncationHookWithInitialValues(
+ [
+ 150,
+ 100,
+ undefined,
+ [
+ { offsetWidth: 150 } as HTMLElement,
+ { offsetWidth: 150 } as HTMLElement,
+ ],
+ ],
+ 1,
+ true,
+ );
+});
+
+test('should call ResizeObserver.observe on element parent', () => {
+ const elementRef = { current: document.createElement('div') };
+ Object.defineProperty(elementRef.current, 'parentElement', {
+ value: document.createElement('div'),
+ });
+ const plusRef = { current: document.createElement('div') };
+ const { result, rerender } = renderHook(() => useChildElementTruncation());
+
+ Object.defineProperty(result.current[0], 'current', {
+ value: elementRef.current,
+ });
+ Object.defineProperty(result.current[1], 'current', {
+ value: plusRef.current,
+ });
+
+ rerender();
+
+ expect(observeMock).toHaveBeenCalled();
+ expect(observeMock).toHaveBeenCalledWith(elementRef.current.parentElement);
+});
+
+test('should not call ResizeObserver.observe if element parent is undefined',
() => {
+ const elementRef = { current: document.createElement('div') };
+ const plusRef = { current: document.createElement('div') };
+ const { result, rerender } = renderHook(() => useChildElementTruncation());
+
+ Object.defineProperty(result.current[0], 'current', {
+ value: elementRef.current,
+ });
+ Object.defineProperty(result.current[1], 'current', {
+ value: plusRef.current,
+ });
+
+ rerender();
+
+ expect(observeMock).not.toHaveBeenCalled();
});
diff --git
a/superset-frontend/packages/superset-ui-core/src/hooks/useTruncation/useChildElementTruncation.ts
b/superset-frontend/packages/superset-ui-core/src/hooks/useTruncation/useChildElementTruncation.ts
index 4f6b628642..2c95aa98b0 100644
---
a/superset-frontend/packages/superset-ui-core/src/hooks/useTruncation/useChildElementTruncation.ts
+++
b/superset-frontend/packages/superset-ui-core/src/hooks/useTruncation/useChildElementTruncation.ts
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { RefObject, useLayoutEffect, useState, useRef } from 'react';
+import { useLayoutEffect, useRef, useState } from 'react';
/**
* This hook encapsulates logic to support truncation of child HTML
@@ -27,92 +27,68 @@ import { RefObject, useLayoutEffect, useState, useRef }
from 'react';
* (including those completely hidden) and whether any elements
* are completely hidden.
*/
-const useChildElementTruncation = (
- elementRef: RefObject<HTMLElement>,
- plusRef?: RefObject<HTMLElement>,
-) => {
+const useChildElementTruncation = () => {
const [elementsTruncated, setElementsTruncated] = useState(0);
const [hasHiddenElements, setHasHiddenElements] = useState(false);
-
- const previousEffectInfoRef = useRef({
- scrollWidth: 0,
- parentElementWidth: 0,
- plusRefWidth: 0,
- });
+ const elementRef = useRef<HTMLDivElement>(null);
+ const plusRef = useRef<HTMLDivElement>(null);
useLayoutEffect(() => {
- const currentElement = elementRef.current;
- const plusRefElement = plusRef?.current;
-
- if (!currentElement) {
- return;
- }
-
- const { scrollWidth, clientWidth, childNodes } = currentElement;
-
- // By using the result of this effect to truncate content
- // we're effectively changing it's size.
- // That will trigger another pass at this effect.
- // Depending on the content elements width, that second rerender could
- // yield a different truncate count, thus potentially leading to a
- // rendering loop.
- // There's only a need to recompute if the parent width or the width of
- // the child nodes changes.
- const previousEffectInfo = previousEffectInfoRef.current;
- const parentElementWidth = currentElement.parentElement?.clientWidth || 0;
- const plusRefWidth = plusRefElement?.offsetWidth || 0;
- previousEffectInfoRef.current = {
- scrollWidth,
- parentElementWidth,
- plusRefWidth,
- };
-
- if (
- previousEffectInfo.parentElementWidth === parentElementWidth &&
- previousEffectInfo.scrollWidth === scrollWidth &&
- previousEffectInfo.plusRefWidth === plusRefWidth
- ) {
- return;
- }
+ const onResize = () => {
+ const currentElement = elementRef.current;
+ if (!currentElement) {
+ return;
+ }
+ const plusRefElement = plusRef.current;
+ const { scrollWidth, clientWidth, childNodes } = currentElement;
- if (scrollWidth > clientWidth) {
- // "..." is around 6px wide
- const truncationWidth = 6;
- const plusSize = plusRefElement?.offsetWidth || 0;
- const maxWidth = clientWidth - truncationWidth;
- const elementsCount = childNodes.length;
+ if (scrollWidth > clientWidth) {
+ // "..." is around 6px wide
+ const truncationWidth = 6;
+ const plusSize = plusRefElement?.offsetWidth || 0;
+ const maxWidth = clientWidth - truncationWidth;
+ const elementsCount = childNodes.length;
- let width = 0;
- let hiddenElements = 0;
- for (let i = 0; i < elementsCount; i += 1) {
- const itemWidth = (childNodes[i] as HTMLElement).offsetWidth;
- const remainingWidth = maxWidth - truncationWidth - width - plusSize;
+ let width = 0;
+ let hiddenElements = 0;
+ for (let i = 0; i < elementsCount; i += 1) {
+ const itemWidth = (childNodes[i] as HTMLElement).offsetWidth;
+ const remainingWidth = maxWidth - width - plusSize;
- // assures it shows +{number} only when the item is not visible
- if (remainingWidth <= 0) {
- hiddenElements += 1;
+ // assures it shows +{number} only when the item is not visible
+ if (remainingWidth <= 0) {
+ hiddenElements += 1;
+ }
+ width += itemWidth;
}
- width += itemWidth;
- }
- if (elementsCount > 1 && hiddenElements) {
- setHasHiddenElements(true);
- setElementsTruncated(hiddenElements);
+ if (elementsCount > 1 && hiddenElements) {
+ setHasHiddenElements(true);
+ setElementsTruncated(hiddenElements);
+ } else {
+ setHasHiddenElements(false);
+ setElementsTruncated(1);
+ }
} else {
setHasHiddenElements(false);
- setElementsTruncated(1);
+ setElementsTruncated(0);
}
- } else {
- setHasHiddenElements(false);
- setElementsTruncated(0);
+ };
+ const obs = new ResizeObserver(onResize);
+
+ const element = elementRef.current?.parentElement;
+ if (element) {
+ obs.observe(element);
}
- }, [
- elementRef.current?.offsetWidth,
- elementRef.current?.clientWidth,
- elementRef,
- ]);
- return [elementsTruncated, hasHiddenElements];
+ onResize();
+
+ return () => {
+ obs.disconnect();
+ };
+ }, [plusRef.current]); // plus is rendered dynamically - the component
rerenders the hook when plus appears, this makes sure that useLayoutEffect is
rerun
+
+ return [elementRef, plusRef, elementsTruncated, hasHiddenElements] as const;
};
export default useChildElementTruncation;
diff --git a/superset-frontend/src/components/ListView/CrossLinks.tsx
b/superset-frontend/src/components/ListView/CrossLinks.tsx
index e315750674..6b3eb5e4b1 100644
--- a/superset-frontend/src/components/ListView/CrossLinks.tsx
+++ b/superset-frontend/src/components/ListView/CrossLinks.tsx
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-import React, { useMemo, useRef } from 'react';
+import React, { useMemo } from 'react';
import { styled, useTruncation } from '@superset-ui/core';
import { Link } from 'react-router-dom';
import CrossLinksTooltip from './CrossLinksTooltip';
@@ -60,17 +60,13 @@ const StyledCrossLinks = styled.div`
`}
`;
-export default function CrossLinks({
+function CrossLinks({
crossLinks,
maxLinks = 20,
linkPrefix = '/superset/dashboard/',
}: CrossLinksProps) {
- const crossLinksRef = useRef<HTMLDivElement>(null);
- const plusRef = useRef<HTMLDivElement>(null);
- const [elementsTruncated, hasHiddenElements] = useTruncation(
- crossLinksRef,
- plusRef,
- );
+ const [crossLinksRef, plusRef, elementsTruncated, hasHiddenElements] =
+ useTruncation();
const hasMoreItems = useMemo(
() =>
crossLinks.length > maxLinks ? crossLinks.length - maxLinks : undefined,
@@ -80,18 +76,13 @@ export default function CrossLinks({
() => (
<span className="truncated" ref={crossLinksRef} data-test="crosslinks">
{crossLinks.map((link, index) => (
- <Link
- key={link.id}
- to={linkPrefix + link.id}
- target="_blank"
- rel="noreferer noopener"
- >
+ <Link key={link.id} to={linkPrefix + link.id}>
{index === 0 ? link.title : `, ${link.title}`}
</Link>
))}
</span>
),
- [crossLinks],
+ [crossLinks, crossLinksRef, linkPrefix],
);
const tooltipLinks = useMemo(
() =>
@@ -99,7 +90,7 @@ export default function CrossLinks({
title: l.title,
to: linkPrefix + l.id,
})),
- [crossLinks, maxLinks],
+ [crossLinks, linkPrefix, maxLinks],
);
return (
@@ -119,3 +110,5 @@ export default function CrossLinks({
</StyledCrossLinks>
);
}
+
+export default React.memo(CrossLinks);
diff --git a/superset-frontend/src/components/ListView/DashboardCrossLinks.tsx
b/superset-frontend/src/components/ListView/DashboardCrossLinks.tsx
new file mode 100644
index 0000000000..409f24bfb7
--- /dev/null
+++ b/superset-frontend/src/components/ListView/DashboardCrossLinks.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, { useMemo } from 'react';
+import { ensureIsArray } from '@superset-ui/core';
+import { ChartLinkedDashboard } from 'src/types/Chart';
+import CrossLinks from './CrossLinks';
+
+export const DashboardCrossLinks = React.memo(
+ ({ dashboards }: { dashboards: ChartLinkedDashboard[] }) => {
+ const crossLinks = useMemo(
+ () =>
+ ensureIsArray(dashboards).map((d: ChartLinkedDashboard) => ({
+ title: d.dashboard_title,
+ id: d.id,
+ })),
+ [dashboards],
+ );
+ return <CrossLinks crossLinks={crossLinks} />;
+ },
+);
diff --git a/superset-frontend/src/components/TruncatedList/index.tsx
b/superset-frontend/src/components/TruncatedList/index.tsx
index 00e0acc0c3..883a5ad61e 100644
--- a/superset-frontend/src/components/TruncatedList/index.tsx
+++ b/superset-frontend/src/components/TruncatedList/index.tsx
@@ -17,7 +17,7 @@
* under the License.
*/
-import React, { ReactNode, useMemo, useRef } from 'react';
+import React, { ReactNode, useMemo } from 'react';
import { styled, t, useTruncation } from '@superset-ui/core';
import { Tooltip } from '../Tooltip';
@@ -99,12 +99,8 @@ export default function TruncatedList<ListItemType>({
getKey = item => item as unknown as React.Key,
maxLinks = 20,
}: TruncatedListProps<ListItemType>) {
- const itemsNotInTooltipRef = useRef<HTMLDivElement>(null);
- const plusRef = useRef<HTMLDivElement>(null);
- const [elementsTruncated, hasHiddenElements] = useTruncation(
- itemsNotInTooltipRef,
- plusRef,
- ) as [number, boolean];
+ const [itemsNotInTooltipRef, plusRef, elementsTruncated, hasHiddenElements] =
+ useTruncation();
const nMoreItems = useMemo(
() => (items.length > maxLinks ? items.length - maxLinks : undefined),
diff --git
a/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/DependenciesRow.tsx
b/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/DependenciesRow.tsx
index 253ce4649d..3ac76882ba 100644
---
a/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/DependenciesRow.tsx
+++
b/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/DependenciesRow.tsx
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-import React, { useCallback, useMemo, useRef } from 'react';
+import React, { useCallback, useMemo } from 'react';
import { useDispatch } from 'react-redux';
import { css, t, useTheme, useTruncation } from '@superset-ui/core';
import Icons from 'src/components/Icons';
@@ -53,12 +53,8 @@ const DependencyValue = ({
export const DependenciesRow = React.memo(({ filter }: FilterCardRowProps) => {
const dependencies = useFilterDependencies(filter);
- const dependenciesRef = useRef<HTMLDivElement>(null);
- const plusRef = useRef<HTMLDivElement>(null);
- const [elementsTruncated, hasHiddenElements] = useTruncation(
- dependenciesRef,
- plusRef,
- );
+ const [dependenciesRef, plusRef, elementsTruncated, hasHiddenElements] =
+ useTruncation();
const theme = useTheme();
const tooltipText = useMemo(
diff --git
a/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/NameRow.tsx
b/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/NameRow.tsx
index 37f18eda29..58e9969b91 100644
---
a/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/NameRow.tsx
+++
b/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/NameRow.tsx
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-import React, { useRef } from 'react';
+import React from 'react';
import { useSelector } from 'react-redux';
import { css, SupersetTheme, useTheme, useTruncation } from
'@superset-ui/core';
import Icons from 'src/components/Icons';
@@ -31,8 +31,7 @@ export const NameRow = ({
hidePopover,
}: FilterCardRowProps & { hidePopover: () => void }) => {
const theme = useTheme();
- const filterNameRef = useRef<HTMLElement>(null);
- const [elementsTruncated] = useTruncation(filterNameRef);
+ const [filterNameRef, , elementsTruncated] = useTruncation();
const dashboardId = useSelector<RootState, number>(
({ dashboardInfo }) => dashboardInfo.id,
);
diff --git
a/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/ScopeRow.tsx
b/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/ScopeRow.tsx
index ff5c1142a5..910fb99aaa 100644
---
a/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/ScopeRow.tsx
+++
b/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/ScopeRow.tsx
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-import React, { useMemo, useRef } from 'react';
+import React, { useMemo } from 'react';
import { t, useTruncation } from '@superset-ui/core';
import { useFilterScope } from './useFilterScope';
import {
@@ -44,13 +44,9 @@ const getTooltipSection = (items: string[] | undefined,
label: string) =>
export const ScopeRow = React.memo(({ filter }: FilterCardRowProps) => {
const scope = useFilterScope(filter);
- const scopeRef = useRef<HTMLDivElement>(null);
- const plusRef = useRef<HTMLDivElement>(null);
- const [elementsTruncated, hasHiddenElements] = useTruncation(
- scopeRef,
- plusRef,
- );
+ const [scopeRef, plusRef, elementsTruncated, hasHiddenElements] =
+ useTruncation();
const tooltipText = useMemo(() => {
if (elementsTruncated === 0 || !scope) {
return null;
@@ -81,7 +77,7 @@ export const ScopeRow = React.memo(({ filter }:
FilterCardRowProps) => {
))
: t('None')}
</RowValue>
- {hasHiddenElements > 0 && (
+ {hasHiddenElements && (
<RowTruncationCount ref={plusRef}>
+{elementsTruncated}
</RowTruncationCount>
diff --git
a/superset-frontend/src/explore/components/useExploreAdditionalActionsMenu/DashboardsSubMenu.test.tsx
b/superset-frontend/src/explore/components/useExploreAdditionalActionsMenu/DashboardsSubMenu.test.tsx
index aea0b4a8e5..4b14d5600c 100644
---
a/superset-frontend/src/explore/components/useExploreAdditionalActionsMenu/DashboardsSubMenu.test.tsx
+++
b/superset-frontend/src/explore/components/useExploreAdditionalActionsMenu/DashboardsSubMenu.test.tsx
@@ -30,7 +30,7 @@ const asyncRender = (numberOfItems: number) =>
}
render(
<Menu openKeys={['menu']}>
- <Menu.SubMenu title="Dashboards added to" key="menu">
+ <Menu.SubMenu title="On dashboards" key="menu">
<DashboardItems key="menu" dashboards={dashboards} />
</Menu.SubMenu>
</Menu>,
diff --git
a/superset-frontend/src/explore/components/useExploreAdditionalActionsMenu/index.jsx
b/superset-frontend/src/explore/components/useExploreAdditionalActionsMenu/index.jsx
index 0d30e57355..6ec0cf62d2 100644
---
a/superset-frontend/src/explore/components/useExploreAdditionalActionsMenu/index.jsx
+++
b/superset-frontend/src/explore/components/useExploreAdditionalActionsMenu/index.jsx
@@ -310,7 +310,7 @@ export const useExploreAdditionalActionsMenu = (
</Menu.Item>
)}
<Menu.SubMenu
- title={t('Dashboards added to')}
+ title={t('On dashboards')}
key={MENU_KEYS.DASHBOARDS_ADDED_TO}
>
<DashboardsSubMenu
diff --git a/superset-frontend/src/pages/ChartList/index.tsx
b/superset-frontend/src/pages/ChartList/index.tsx
index d354707201..bdfabc3ea8 100644
--- a/superset-frontend/src/pages/ChartList/index.tsx
+++ b/superset-frontend/src/pages/ChartList/index.tsx
@@ -17,7 +17,6 @@
* under the License.
*/
import {
- ensureIsArray,
isFeatureEnabled,
FeatureFlag,
getChartMetadataRegistry,
@@ -53,13 +52,12 @@ import ListView, {
ListViewProps,
SelectOption,
} from 'src/components/ListView';
-import CrossLinks from 'src/components/ListView/CrossLinks';
import Loading from 'src/components/Loading';
import { dangerouslyGetItemDoNotUse } from 'src/utils/localStorageHelpers';
import withToasts from 'src/components/MessageToasts/withToasts';
import PropertiesModal from 'src/explore/components/PropertiesModal';
import ImportModelsModal from 'src/components/ImportModal/index';
-import Chart, { ChartLinkedDashboard } from 'src/types/Chart';
+import Chart from 'src/types/Chart';
import Tag from 'src/types/TagType';
import { Tooltip } from 'src/components/Tooltip';
import Icons from 'src/components/Icons';
@@ -72,6 +70,7 @@ import FacePile from 'src/components/FacePile';
import ChartCard from 'src/features/charts/ChartCard';
import { UserWithPermissionsAndRoles } from 'src/types/bootstrapTypes';
import { findPermission } from 'src/utils/findPermission';
+import { DashboardCrossLinks } from
'src/components/ListView/DashboardCrossLinks';
import { ModifiedInfo } from 'src/components/AuditInfo';
import { QueryObjectColumns } from 'src/views/CRUD/types';
@@ -390,21 +389,11 @@ function ChartList(props: ChartListProps) {
row: {
original: { dashboards },
},
- }: any) => (
- <CrossLinks
- crossLinks={ensureIsArray(dashboards).map(
- (d: ChartLinkedDashboard) => ({
- title: d.dashboard_title,
- id: d.id,
- }),
- )}
- />
- ),
- Header: t('Dashboards added to'),
+ }: any) => <DashboardCrossLinks dashboards={dashboards} />,
+ Header: t('On dashboards'),
accessor: 'dashboards',
disableSortBy: true,
size: 'xxl',
- hidden: true,
},
{
Cell: ({