Skip to content

Commit

Permalink
perf: Optimize dashboard chart-related components (#31241)
Browse files Browse the repository at this point in the history
  • Loading branch information
kgabryje authored Dec 2, 2024
1 parent 3d3c09d commit eab888c
Show file tree
Hide file tree
Showing 6 changed files with 850 additions and 884 deletions.
342 changes: 177 additions & 165 deletions superset-frontend/src/dashboard/components/SliceHeader/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,14 @@
* specific language governing permissions and limitations
* under the License.
*/
import { FC, ReactNode, useContext, useEffect, useRef, useState } from 'react';
import {
forwardRef,
ReactNode,
useContext,
useEffect,
useRef,
useState,
} from 'react';
import { css, getExtensionsRegistry, styled, t } from '@superset-ui/core';
import { useUiConfig } from 'src/components/UiConfigContext';
import { Tooltip } from 'src/components/Tooltip';
Expand All @@ -34,7 +41,6 @@ import { DashboardPageIdContext } from 'src/dashboard/containers/DashboardPage';
const extensionsRegistry = getExtensionsRegistry();

type SliceHeaderProps = SliceHeaderControlsProps & {
innerRef?: string;
updateSliceName?: (arg0: string) => void;
editMode?: boolean;
annotationQuery?: object;
Expand Down Expand Up @@ -122,176 +128,182 @@ const ChartHeaderStyles = styled.div`
`}
`;

const SliceHeader: FC<SliceHeaderProps> = ({
innerRef = null,
forceRefresh = () => ({}),
updateSliceName = () => ({}),
toggleExpandSlice = () => ({}),
logExploreChart = () => ({}),
logEvent,
exportCSV = () => ({}),
exportXLSX = () => ({}),
editMode = false,
annotationQuery = {},
annotationError = {},
cachedDttm = null,
updatedDttm = null,
isCached = [],
isExpanded = false,
sliceName = '',
supersetCanExplore = false,
supersetCanShare = false,
supersetCanCSV = false,
exportPivotCSV,
exportFullCSV,
exportFullXLSX,
slice,
componentId,
dashboardId,
addSuccessToast,
addDangerToast,
handleToggleFullSize,
isFullSize,
chartStatus,
formData,
width,
height,
}) => {
const SliceHeaderExtension = extensionsRegistry.get('dashboard.slice.header');
const uiConfig = useUiConfig();
const dashboardPageId = useContext(DashboardPageIdContext);
const [headerTooltip, setHeaderTooltip] = useState<ReactNode | null>(null);
const headerRef = useRef<HTMLDivElement>(null);
// TODO: change to indicator field after it will be implemented
const crossFilterValue = useSelector<RootState, any>(
state => state.dataMask[slice?.slice_id]?.filterState?.value,
);
const isCrossFiltersEnabled = useSelector<RootState, boolean>(
({ dashboardInfo }) => dashboardInfo.crossFiltersEnabled,
);
const SliceHeader = forwardRef<HTMLDivElement, SliceHeaderProps>(
(
{
forceRefresh = () => ({}),
updateSliceName = () => ({}),
toggleExpandSlice = () => ({}),
logExploreChart = () => ({}),
logEvent,
exportCSV = () => ({}),
exportXLSX = () => ({}),
editMode = false,
annotationQuery = {},
annotationError = {},
cachedDttm = null,
updatedDttm = null,
isCached = [],
isExpanded = false,
sliceName = '',
supersetCanExplore = false,
supersetCanShare = false,
supersetCanCSV = false,
exportPivotCSV,
exportFullCSV,
exportFullXLSX,
slice,
componentId,
dashboardId,
addSuccessToast,
addDangerToast,
handleToggleFullSize,
isFullSize,
chartStatus,
formData,
width,
height,
},
ref,
) => {
const SliceHeaderExtension = extensionsRegistry.get(
'dashboard.slice.header',
);
const uiConfig = useUiConfig();
const dashboardPageId = useContext(DashboardPageIdContext);
const [headerTooltip, setHeaderTooltip] = useState<ReactNode | null>(null);
const headerRef = useRef<HTMLDivElement>(null);
// TODO: change to indicator field after it will be implemented
const crossFilterValue = useSelector<RootState, any>(
state => state.dataMask[slice?.slice_id]?.filterState?.value,
);
const isCrossFiltersEnabled = useSelector<RootState, boolean>(
({ dashboardInfo }) => dashboardInfo.crossFiltersEnabled,
);

const canExplore = !editMode && supersetCanExplore;
const canExplore = !editMode && supersetCanExplore;

useEffect(() => {
const headerElement = headerRef.current;
if (canExplore) {
setHeaderTooltip(getSliceHeaderTooltip(sliceName));
} else if (
headerElement &&
(headerElement.scrollWidth > headerElement.offsetWidth ||
headerElement.scrollHeight > headerElement.offsetHeight)
) {
setHeaderTooltip(sliceName ?? null);
} else {
setHeaderTooltip(null);
}
}, [sliceName, width, height, canExplore]);
useEffect(() => {
const headerElement = headerRef.current;
if (canExplore) {
setHeaderTooltip(getSliceHeaderTooltip(sliceName));
} else if (
headerElement &&
(headerElement.scrollWidth > headerElement.offsetWidth ||
headerElement.scrollHeight > headerElement.offsetHeight)
) {
setHeaderTooltip(sliceName ?? null);
} else {
setHeaderTooltip(null);
}
}, [sliceName, width, height, canExplore]);

const exploreUrl = `/explore/?dashboard_page_id=${dashboardPageId}&slice_id=${slice.slice_id}`;
const exploreUrl = `/explore/?dashboard_page_id=${dashboardPageId}&slice_id=${slice.slice_id}`;

return (
<ChartHeaderStyles data-test="slice-header" ref={innerRef}>
<div className="header-title" ref={headerRef}>
<Tooltip title={headerTooltip}>
<EditableTitle
title={
sliceName ||
(editMode
? '---' // this makes an empty title clickable
: '')
}
canEdit={editMode}
onSaveTitle={updateSliceName}
showTooltip={false}
url={canExplore ? exploreUrl : undefined}
/>
</Tooltip>
{!!Object.values(annotationQuery).length && (
<Tooltip
id="annotations-loading-tooltip"
placement="top"
title={annotationsLoading}
>
<i
role="img"
aria-label={annotationsLoading}
className="fa fa-refresh warning"
return (
<ChartHeaderStyles data-test="slice-header" ref={ref}>
<div className="header-title" ref={headerRef}>
<Tooltip title={headerTooltip}>
<EditableTitle
title={
sliceName ||
(editMode
? '---' // this makes an empty title clickable
: '')
}
canEdit={editMode}
onSaveTitle={updateSliceName}
showTooltip={false}
url={canExplore ? exploreUrl : undefined}
/>
</Tooltip>
)}
{!!Object.values(annotationError).length && (
<Tooltip
id="annotation-errors-tooltip"
placement="top"
title={annotationsError}
>
<i
role="img"
aria-label={annotationsError}
className="fa fa-exclamation-circle danger"
/>
</Tooltip>
)}
</div>
<div className="header-controls">
{!editMode && (
<>
{SliceHeaderExtension && (
<SliceHeaderExtension
sliceId={slice.slice_id}
dashboardId={dashboardId}
{!!Object.values(annotationQuery).length && (
<Tooltip
id="annotations-loading-tooltip"
placement="top"
title={annotationsLoading}
>
<i
role="img"
aria-label={annotationsLoading}
className="fa fa-refresh warning"
/>
)}
{crossFilterValue && (
<Tooltip
placement="top"
title={t(
'This chart applies cross-filters to charts whose datasets contain columns with the same name.',
)}
>
<CrossFilterIcon iconSize="m" />
</Tooltip>
)}
{!uiConfig.hideChartControls && (
<FiltersBadge chartId={slice.slice_id} />
)}
{!uiConfig.hideChartControls && (
<SliceHeaderControls
slice={slice}
isCached={isCached}
isExpanded={isExpanded}
cachedDttm={cachedDttm}
updatedDttm={updatedDttm}
toggleExpandSlice={toggleExpandSlice}
forceRefresh={forceRefresh}
logExploreChart={logExploreChart}
logEvent={logEvent}
exportCSV={exportCSV}
exportPivotCSV={exportPivotCSV}
exportFullCSV={exportFullCSV}
exportXLSX={exportXLSX}
exportFullXLSX={exportFullXLSX}
supersetCanExplore={supersetCanExplore}
supersetCanShare={supersetCanShare}
supersetCanCSV={supersetCanCSV}
componentId={componentId}
dashboardId={dashboardId}
addSuccessToast={addSuccessToast}
addDangerToast={addDangerToast}
handleToggleFullSize={handleToggleFullSize}
isFullSize={isFullSize}
isDescriptionExpanded={isExpanded}
chartStatus={chartStatus}
formData={formData}
exploreUrl={exploreUrl}
crossFiltersEnabled={isCrossFiltersEnabled}
</Tooltip>
)}
{!!Object.values(annotationError).length && (
<Tooltip
id="annotation-errors-tooltip"
placement="top"
title={annotationsError}
>
<i
role="img"
aria-label={annotationsError}
className="fa fa-exclamation-circle danger"
/>
)}
</>
)}
</div>
</ChartHeaderStyles>
);
};
</Tooltip>
)}
</div>
<div className="header-controls">
{!editMode && (
<>
{SliceHeaderExtension && (
<SliceHeaderExtension
sliceId={slice.slice_id}
dashboardId={dashboardId}
/>
)}
{crossFilterValue && (
<Tooltip
placement="top"
title={t(
'This chart applies cross-filters to charts whose datasets contain columns with the same name.',
)}
>
<CrossFilterIcon iconSize="m" />
</Tooltip>
)}
{!uiConfig.hideChartControls && (
<FiltersBadge chartId={slice.slice_id} />
)}
{!uiConfig.hideChartControls && (
<SliceHeaderControls
slice={slice}
isCached={isCached}
isExpanded={isExpanded}
cachedDttm={cachedDttm}
updatedDttm={updatedDttm}
toggleExpandSlice={toggleExpandSlice}
forceRefresh={forceRefresh}
logExploreChart={logExploreChart}
logEvent={logEvent}
exportCSV={exportCSV}
exportPivotCSV={exportPivotCSV}
exportFullCSV={exportFullCSV}
exportXLSX={exportXLSX}
exportFullXLSX={exportFullXLSX}
supersetCanExplore={supersetCanExplore}
supersetCanShare={supersetCanShare}
supersetCanCSV={supersetCanCSV}
componentId={componentId}
dashboardId={dashboardId}
addSuccessToast={addSuccessToast}
addDangerToast={addDangerToast}
handleToggleFullSize={handleToggleFullSize}
isFullSize={isFullSize}
isDescriptionExpanded={isExpanded}
chartStatus={chartStatus}
formData={formData}
exploreUrl={exploreUrl}
crossFiltersEnabled={isCrossFiltersEnabled}
/>
)}
</>
)}
</div>
</ChartHeaderStyles>
);
},
);

export default SliceHeader;
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import Popover, { PopoverProps } from 'src/components/Popover';
import CopyToClipboard from 'src/components/CopyToClipboard';
import { getDashboardPermalink } from 'src/utils/urlUtils';
import { useToasts } from 'src/components/MessageToasts/withToasts';
import { useSelector } from 'react-redux';
import { shallowEqual, useSelector } from 'react-redux';
import { RootState } from 'src/dashboard/types';

export type URLShortLinkButtonProps = {
Expand All @@ -42,10 +42,13 @@ export default function URLShortLinkButton({
}: URLShortLinkButtonProps) {
const [shortUrl, setShortUrl] = useState('');
const { addDangerToast } = useToasts();
const { dataMask, activeTabs } = useSelector((state: RootState) => ({
dataMask: state.dataMask,
activeTabs: state.dashboardState.activeTabs,
}));
const { dataMask, activeTabs } = useSelector(
(state: RootState) => ({
dataMask: state.dataMask,
activeTabs: state.dashboardState.activeTabs,
}),
shallowEqual,
);

const getCopyUrl = async () => {
try {
Expand Down
Loading

0 comments on commit eab888c

Please sign in to comment.