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 24d001e498 perf: Optimize Dashboard components (#31242)
24d001e498 is described below
commit 24d001e4983e25470260484e64c6f6ebe149775b
Author: Kamil Gabryjelski <[email protected]>
AuthorDate: Mon Dec 2 15:04:39 2024 +0100
perf: Optimize Dashboard components (#31242)
---
.../src/dashboard/components/Dashboard.jsx | 30 ++--
.../DashboardBuilder/DashboardBuilder.test.tsx | 6 +-
.../DashboardBuilder/DashboardBuilder.tsx | 75 ++++++----
.../DashboardBuilder/DashboardContainer.tsx | 157 ++++++++++++---------
.../dashboard/components/DashboardBuilder/state.ts | 30 ++--
.../src/dashboard/containers/Dashboard.ts | 6 +-
.../dashboard/containers/DashboardComponent.jsx | 148 +++++++++----------
7 files changed, 257 insertions(+), 195 deletions(-)
diff --git a/superset-frontend/src/dashboard/components/Dashboard.jsx
b/superset-frontend/src/dashboard/components/Dashboard.jsx
index 5bdce6d72e..48e70c8fd4 100644
--- a/superset-frontend/src/dashboard/components/Dashboard.jsx
+++ b/superset-frontend/src/dashboard/components/Dashboard.jsx
@@ -26,11 +26,7 @@ import getBootstrapData from 'src/utils/getBootstrapData';
import getChartIdsFromLayout from '../util/getChartIdsFromLayout';
import getLayoutComponentFromChartId from
'../util/getLayoutComponentFromChartId';
-import {
- slicePropShape,
- dashboardInfoPropShape,
- dashboardStatePropShape,
-} from '../util/propShapes';
+import { slicePropShape } from '../util/propShapes';
import {
LOG_ACTIONS_HIDE_BROWSER_TAB,
LOG_ACTIONS_MOUNT_DASHBOARD,
@@ -51,8 +47,10 @@ const propTypes = {
logEvent: PropTypes.func.isRequired,
clearDataMaskState: PropTypes.func.isRequired,
}).isRequired,
- dashboardInfo: dashboardInfoPropShape.isRequired,
- dashboardState: dashboardStatePropShape.isRequired,
+ dashboardId: PropTypes.number.isRequired,
+ editMode: PropTypes.bool,
+ isPublished: PropTypes.bool,
+ hasUnsavedChanges: PropTypes.bool,
slices: PropTypes.objectOf(slicePropShape).isRequired,
activeFilters: PropTypes.object.isRequired,
chartConfiguration: PropTypes.object,
@@ -96,13 +94,13 @@ class Dashboard extends PureComponent {
componentDidMount() {
const bootstrapData = getBootstrapData();
- const { dashboardState, layout } = this.props;
+ const { editMode, isPublished, layout } = this.props;
const eventData = {
is_soft_navigation: Logger.timeOriginOffset > 0,
- is_edit_mode: dashboardState.editMode,
+ is_edit_mode: editMode,
mount_duration: Logger.getTimestamp(),
is_empty: isDashboardEmpty(layout),
- is_published: dashboardState.isPublished,
+ is_published: isPublished,
bootstrap_data_length: bootstrapData.length,
};
const directLinkComponentId = getLocationHash();
@@ -130,7 +128,7 @@ class Dashboard extends PureComponent {
const currentChartIds = getChartIdsFromLayout(this.props.layout);
const nextChartIds = getChartIdsFromLayout(nextProps.layout);
- if (this.props.dashboardInfo.id !== nextProps.dashboardInfo.id) {
+ if (this.props.dashboardId !== nextProps.dashboardId) {
// single-page-app navigation check
return;
}
@@ -157,10 +155,14 @@ class Dashboard extends PureComponent {
}
applyCharts() {
- const { hasUnsavedChanges, editMode } = this.props.dashboardState;
-
+ const {
+ activeFilters,
+ ownDataCharts,
+ chartConfiguration,
+ hasUnsavedChanges,
+ editMode,
+ } = this.props;
const { appliedFilters, appliedOwnDataCharts } = this;
- const { activeFilters, ownDataCharts, chartConfiguration } = this.props;
if (
isFeatureEnabled(FeatureFlag.DashboardCrossFilters) &&
!chartConfiguration
diff --git
a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.test.tsx
b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.test.tsx
index 7da2064f96..43e207d8c8 100644
---
a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.test.tsx
+++
b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.test.tsx
@@ -208,7 +208,9 @@ describe('DashboardBuilder', () => {
});
it('should render a BuilderComponentPane if editMode=true and user selects
"Insert Components" pane', () => {
- const { queryAllByTestId } = setup({ dashboardState: { editMode: true } });
+ const { queryAllByTestId } = setup({
+ dashboardState: { ...mockState.dashboardState, editMode: true },
+ });
const builderComponents = queryAllByTestId('mock-builder-component-pane');
expect(builderComponents.length).toBeGreaterThanOrEqual(1);
});
@@ -241,7 +243,7 @@ describe('DashboardBuilder', () => {
it('should display a loading spinner when saving is in progress', async ()
=> {
const { findByAltText } = setup({
- dashboardState: { dashboardIsSaving: true },
+ dashboardState: { ...mockState.dashboardState, dashboardIsSaving: true },
});
expect(await findByAltText('Loading...')).toBeVisible();
diff --git
a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx
b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx
index 347858f039..30c61f0af6 100644
---
a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx
+++
b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardBuilder.tsx
@@ -370,6 +370,10 @@ const StyledDashboardContent = styled.div<{
`}
`;
+const ELEMENT_ON_SCREEN_OPTIONS = {
+ threshold: [1],
+};
+
const DashboardBuilder: FC<DashboardBuilderProps> = () => {
const dispatch = useDispatch();
const uiConfig = useUiConfig();
@@ -469,9 +473,9 @@ const DashboardBuilder: FC<DashboardBuilderProps> = () => {
nativeFiltersEnabled,
} = useNativeFilters();
- const [containerRef, isSticky] = useElementOnScreen<HTMLDivElement>({
- threshold: [1],
- });
+ const [containerRef, isSticky] = useElementOnScreen<HTMLDivElement>(
+ ELEMENT_ON_SCREEN_OPTIONS,
+ );
const showFilterBar =
(crossFiltersEnabled || nativeFiltersEnabled) && !editMode;
@@ -581,6 +585,43 @@ const DashboardBuilder: FC<DashboardBuilderProps> = () => {
? 0
: theme.gridUnit * 8;
+ const renderChild = useCallback(
+ adjustedWidth => {
+ const filterBarWidth = dashboardFiltersOpen
+ ? adjustedWidth
+ : CLOSED_FILTER_BAR_WIDTH;
+ return (
+ <FiltersPanel
+ width={filterBarWidth}
+ hidden={isReport}
+ data-test="dashboard-filters-panel"
+ >
+ <StickyPanel ref={containerRef} width={filterBarWidth}>
+ <ErrorBoundary>
+ <FilterBar
+ orientation={FilterBarOrientation.Vertical}
+ verticalConfig={{
+ filtersOpen: dashboardFiltersOpen,
+ toggleFiltersBar: toggleDashboardFiltersOpen,
+ width: filterBarWidth,
+ height: filterBarHeight,
+ offset: filterBarOffset,
+ }}
+ />
+ </ErrorBoundary>
+ </StickyPanel>
+ </FiltersPanel>
+ );
+ },
+ [
+ dashboardFiltersOpen,
+ toggleDashboardFiltersOpen,
+ filterBarHeight,
+ filterBarOffset,
+ isReport,
+ ],
+ );
+
return (
<DashboardWrapper>
{showFilterBar &&
@@ -593,33 +634,7 @@ const DashboardBuilder: FC<DashboardBuilderProps> = () => {
maxWidth={OPEN_FILTER_BAR_MAX_WIDTH}
initialWidth={OPEN_FILTER_BAR_WIDTH}
>
- {adjustedWidth => {
- const filterBarWidth = dashboardFiltersOpen
- ? adjustedWidth
- : CLOSED_FILTER_BAR_WIDTH;
- return (
- <FiltersPanel
- width={filterBarWidth}
- hidden={isReport}
- data-test="dashboard-filters-panel"
- >
- <StickyPanel ref={containerRef} width={filterBarWidth}>
- <ErrorBoundary>
- <FilterBar
- orientation={FilterBarOrientation.Vertical}
- verticalConfig={{
- filtersOpen: dashboardFiltersOpen,
- toggleFiltersBar: toggleDashboardFiltersOpen,
- width: filterBarWidth,
- height: filterBarHeight,
- offset: filterBarOffset,
- }}
- />
- </ErrorBoundary>
- </StickyPanel>
- </FiltersPanel>
- );
- }}
+ {renderChild}
</ResizableSidebar>
</>
)}
diff --git
a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardContainer.tsx
b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardContainer.tsx
index f36520ba31..c333e5318a 100644
---
a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardContainer.tsx
+++
b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardContainer.tsx
@@ -18,8 +18,17 @@
*/
// ParentSize uses resize observer so the dashboard will update size
// when its container size changes, due to e.g., builder side panel opening
-import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react';
+import {
+ FC,
+ memo,
+ useCallback,
+ useEffect,
+ useMemo,
+ useRef,
+ useState,
+} from 'react';
import { useDispatch, useSelector } from 'react-redux';
+import { createSelector } from '@reduxjs/toolkit';
import {
Filter,
Filters,
@@ -43,6 +52,7 @@ import {
import { getChartIdsInFilterScope } from
'src/dashboard/util/getChartIdsInFilterScope';
import findTabIndexByComponentId from
'src/dashboard/util/findTabIndexByComponentId';
import { setInScopeStatusOfFilters } from
'src/dashboard/actions/nativeFilters';
+import { useChartIds } from 'src/dashboard/util/charts/useChartIds';
import {
applyDashboardLabelsColorOnLoad,
updateDashboardLabelsColor,
@@ -59,6 +69,21 @@ type DashboardContainerProps = {
topLevelTabs?: LayoutItem;
};
+export const renderedChartIdsSelector = createSelector(
+ [(state: RootState) => state.charts],
+ charts =>
+ Object.values(charts)
+ .filter(chart => chart.chartStatus === 'rendered')
+ .map(chart => chart.id),
+);
+
+const useRenderedChartIds = () => {
+ const renderedChartIds = useSelector<RootState, number[]>(
+ renderedChartIdsSelector,
+ );
+ return useMemo(() => renderedChartIds, [JSON.stringify(renderedChartIds)]);
+};
+
const useNativeFilterScopes = () => {
const nativeFilters = useSelector<RootState, Filters>(
state => state.nativeFilters?.filters,
@@ -70,10 +95,12 @@ const useNativeFilterScopes = () => {
pick(filter, ['id', 'scope', 'type']),
)
: [],
- [JSON.stringify(nativeFilters)],
+ [nativeFilters],
);
};
+const TOP_OF_PAGE_RANGE = 220;
+
const DashboardContainer: FC<DashboardContainerProps> = ({ topLevelTabs }) => {
const nativeFilterScopes = useNativeFilterScopes();
const dispatch = useDispatch();
@@ -87,14 +114,10 @@ const DashboardContainer: FC<DashboardContainerProps> = ({
topLevelTabs }) => {
const directPathToChild = useSelector<RootState, string[]>(
state => state.dashboardState.directPathToChild,
);
- const chartIds = useSelector<RootState, number[]>(state =>
- Object.values(state.charts).map(chart => chart.id),
- );
- const renderedChartIds = useSelector<RootState, number[]>(state =>
- Object.values(state.charts)
- .filter(chart => chart.chartStatus === 'rendered')
- .map(chart => chart.id),
- );
+ const chartIds = useChartIds();
+
+ const renderedChartIds = useRenderedChartIds();
+
const [dashboardLabelsColorInitiated, setDashboardLabelsColorInitiated] =
useState(false);
const prevRenderedChartIds = useRef<number[]>([]);
@@ -136,11 +159,13 @@ const DashboardContainer: FC<DashboardContainerProps> =
({ topLevelTabs }) => {
chartsInScope: [],
};
}
+
const chartsInScope: number[] = getChartIdsInFilterScope(
filterScope.scope,
chartIds,
dashboardLayout,
);
+
const tabsInScope = findTabsWithChartsInScope(
dashboardLayout,
chartsInScope,
@@ -152,14 +177,14 @@ const DashboardContainer: FC<DashboardContainerProps> =
({ topLevelTabs }) => {
};
});
dispatch(setInScopeStatusOfFilters(scopes));
- }, [nativeFilterScopes, dashboardLayout, dispatch]);
+ }, [chartIds, JSON.stringify(nativeFilterScopes), dashboardLayout,
dispatch]);
- const childIds: string[] = topLevelTabs
- ? topLevelTabs.children
- : [DASHBOARD_GRID_ID];
+ const childIds: string[] = useMemo(
+ () => (topLevelTabs ? topLevelTabs.children : [DASHBOARD_GRID_ID]),
+ [topLevelTabs],
+ );
const min = Math.min(tabIndex, childIds.length - 1);
const activeKey = min === 0 ? DASHBOARD_GRID_ID : min.toString();
- const TOP_OF_PAGE_RANGE = 220;
useEffect(() => {
if (shouldForceFreshSharedLabelsColors) {
@@ -229,57 +254,63 @@ const DashboardContainer: FC<DashboardContainerProps> =
({ topLevelTabs }) => {
};
}, [onBeforeUnload]);
+ const renderTabBar = useCallback(() => <></>, []);
+ const handleFocus = useCallback(e => {
+ if (
+ // prevent scrolling when tabbing to the tab pane
+ e.target.classList.contains('ant-tabs-tabpane') &&
+ window.scrollY < TOP_OF_PAGE_RANGE
+ ) {
+ // prevent window from jumping down when tabbing
+ // if already at the top of the page
+ // to help with accessibility when using keyboard navigation
+ window.scrollTo(window.scrollX, 0);
+ }
+ }, []);
+
+ const renderParentSizeChildren = useCallback(
+ ({ width }) => (
+ /*
+ We use a TabContainer irrespective of whether top-level tabs exist to
maintain
+ a consistent React component tree. This avoids expensive mounts/unmounts
of
+ the entire dashboard upon adding/removing top-level tabs, which would
otherwise
+ happen because of React's diffing algorithm
+ */
+ <Tabs
+ id={DASHBOARD_GRID_ID}
+ activeKey={activeKey}
+ renderTabBar={renderTabBar}
+ fullWidth={false}
+ animated={false}
+ allowOverflow
+ onFocus={handleFocus}
+ >
+ {childIds.map((id, index) => (
+ // Matching the key of the first TabPane irrespective of topLevelTabs
+ // lets us keep the same React component tree when !!topLevelTabs
changes.
+ // This avoids expensive mounts/unmounts of the entire dashboard.
+ <Tabs.TabPane
+ key={index === 0 ? DASHBOARD_GRID_ID : index.toString()}
+ >
+ <DashboardGrid
+ gridComponent={dashboardLayout[id]}
+ // see isValidChild for why tabs do not increment the depth of
their children
+ depth={DASHBOARD_ROOT_DEPTH + 1} // (topLevelTabs ? 0 : 1)}
+ width={width}
+ isComponentVisible={index === tabIndex}
+ />
+ </Tabs.TabPane>
+ ))}
+ </Tabs>
+ ),
+ [activeKey, childIds, dashboardLayout, handleFocus, renderTabBar,
tabIndex],
+ );
+
return (
<div className="grid-container" data-test="grid-container">
- <ParentSize>
- {({ width }) => (
- /*
- We use a TabContainer irrespective of whether top-level tabs exist
to maintain
- a consistent React component tree. This avoids expensive
mounts/unmounts of
- the entire dashboard upon adding/removing top-level tabs, which
would otherwise
- happen because of React's diffing algorithm
- */
- <Tabs
- id={DASHBOARD_GRID_ID}
- activeKey={activeKey}
- renderTabBar={() => <></>}
- fullWidth={false}
- animated={false}
- allowOverflow
- onFocus={e => {
- if (
- // prevent scrolling when tabbing to the tab pane
- e.target.classList.contains('ant-tabs-tabpane') &&
- window.scrollY < TOP_OF_PAGE_RANGE
- ) {
- // prevent window from jumping down when tabbing
- // if already at the top of the page
- // to help with accessibility when using keyboard navigation
- window.scrollTo(window.scrollX, 0);
- }
- }}
- >
- {childIds.map((id, index) => (
- // Matching the key of the first TabPane irrespective of
topLevelTabs
- // lets us keep the same React component tree when
!!topLevelTabs changes.
- // This avoids expensive mounts/unmounts of the entire dashboard.
- <Tabs.TabPane
- key={index === 0 ? DASHBOARD_GRID_ID : index.toString()}
- >
- <DashboardGrid
- gridComponent={dashboardLayout[id]}
- // see isValidChild for why tabs do not increment the depth
of their children
- depth={DASHBOARD_ROOT_DEPTH + 1} // (topLevelTabs ? 0 : 1)}
- width={width}
- isComponentVisible={index === tabIndex}
- />
- </Tabs.TabPane>
- ))}
- </Tabs>
- )}
- </ParentSize>
+ <ParentSize>{renderParentSizeChildren}</ParentSize>
</div>
);
};
-export default DashboardContainer;
+export default memo(DashboardContainer);
diff --git
a/superset-frontend/src/dashboard/components/DashboardBuilder/state.ts
b/superset-frontend/src/dashboard/components/DashboardBuilder/state.ts
index b7e6c266de..ec1cc0bc1f 100644
--- a/superset-frontend/src/dashboard/components/DashboardBuilder/state.ts
+++ b/superset-frontend/src/dashboard/components/DashboardBuilder/state.ts
@@ -17,7 +17,7 @@
* under the License.
*/
import { useSelector } from 'react-redux';
-import { useCallback, useEffect, useState } from 'react';
+import { useCallback, useEffect, useMemo, useState } from 'react';
import { URL_PARAMS } from 'src/constants';
import { getUrlParam } from 'src/utils/urlUtils';
import { RootState } from 'src/dashboard/types';
@@ -34,7 +34,7 @@ export const useNativeFilters = () => {
);
const filters = useFilters();
- const filterValues = Object.values(filters);
+ const filterValues = useMemo(() => Object.values(filters), [filters]);
const expandFilters = getUrlParam(URL_PARAMS.expandFilters);
const [dashboardFiltersOpen, setDashboardFiltersOpen] = useState(
expandFilters ?? !!filterValues.length,
@@ -43,24 +43,28 @@ export const useNativeFilters = () => {
const nativeFiltersEnabled =
canEdit || (!canEdit && filterValues.length !== 0);
- const requiredFirstFilter = filterValues.filter(
- filter => filter.requiredFirst,
+ const requiredFirstFilter = useMemo(
+ () => filterValues.filter(filter => filter.requiredFirst),
+ [filterValues],
);
const dataMask = useNativeFiltersDataMask();
- const missingInitialFilters = requiredFirstFilter
- .filter(({ id }) => dataMask[id]?.filterState?.value === undefined)
- .map(({ name }) => name);
+ const missingInitialFilters = useMemo(
+ () =>
+ requiredFirstFilter
+ .filter(({ id }) => dataMask[id]?.filterState?.value === undefined)
+ .map(({ name }) => name),
+ [requiredFirstFilter, dataMask],
+ );
+
const showDashboard =
isInitialized ||
!nativeFiltersEnabled ||
missingInitialFilters.length === 0;
- const toggleDashboardFiltersOpen = useCallback(
- (visible?: boolean) => {
- setDashboardFiltersOpen(visible ?? !dashboardFiltersOpen);
- },
- [dashboardFiltersOpen],
- );
+
+ const toggleDashboardFiltersOpen = useCallback((visible?: boolean) => {
+ setDashboardFiltersOpen(prevState => visible ?? !prevState);
+ }, []);
useEffect(() => {
if (
diff --git a/superset-frontend/src/dashboard/containers/Dashboard.ts
b/superset-frontend/src/dashboard/containers/Dashboard.ts
index ab1f7c46fa..a59767c004 100644
--- a/superset-frontend/src/dashboard/containers/Dashboard.ts
+++ b/superset-frontend/src/dashboard/containers/Dashboard.ts
@@ -43,8 +43,10 @@ function mapStateToProps(state: RootState) {
return {
timeout: dashboardInfo.common?.conf?.SUPERSET_WEBSERVER_TIMEOUT,
userId: dashboardInfo.userId,
- dashboardInfo,
- dashboardState,
+ dashboardId: dashboardInfo.id,
+ editMode: dashboardState.editMode,
+ isPublished: dashboardState.isPublished,
+ hasUnsavedChanges: dashboardState.hasUnsavedChanges,
datasources,
chartConfiguration: dashboardInfo.metadata?.chart_configuration,
slices: sliceEntities.slices,
diff --git a/superset-frontend/src/dashboard/containers/DashboardComponent.jsx
b/superset-frontend/src/dashboard/containers/DashboardComponent.jsx
index b811f398ab..2bbc51915d 100644
--- a/superset-frontend/src/dashboard/containers/DashboardComponent.jsx
+++ b/superset-frontend/src/dashboard/containers/DashboardComponent.jsx
@@ -16,16 +16,15 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { PureComponent } from 'react';
+import { useCallback, memo, useMemo } from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
-import { connect } from 'react-redux';
+import { useSelector, useDispatch } from 'react-redux';
import { logEvent } from 'src/logger/actions';
import { addDangerToast } from 'src/components/MessageToasts/actions';
import { componentLookup } from 'src/dashboard/components/gridComponents';
import getDetailedComponentWidth from
'src/dashboard/util/getDetailedComponentWidth';
import { getActiveFilters } from 'src/dashboard/util/activeDashboardFilters';
-import { componentShape } from 'src/dashboard/util/propShapes';
import { COLUMN_TYPE, ROW_TYPE } from 'src/dashboard/util/componentTypes';
import {
createComponent,
@@ -47,86 +46,93 @@ const propTypes = {
renderHoverMenu: PropTypes.bool,
renderTabContent: PropTypes.bool,
onChangeTab: PropTypes.func,
- component: componentShape.isRequired,
- parentComponent: componentShape.isRequired,
- createComponent: PropTypes.func.isRequired,
- deleteComponent: PropTypes.func.isRequired,
- updateComponents: PropTypes.func.isRequired,
- handleComponentDrop: PropTypes.func.isRequired,
- logEvent: PropTypes.func.isRequired,
directPathToChild: PropTypes.arrayOf(PropTypes.string),
directPathLastUpdated: PropTypes.number,
- dashboardId: PropTypes.number.isRequired,
isComponentVisible: PropTypes.bool,
};
-const defaultProps = {
- isComponentVisible: true,
-};
+const DashboardComponent = props => {
+ const dispatch = useDispatch();
+ const dashboardLayout = useSelector(state => state.dashboardLayout.present);
+ const dashboardInfo = useSelector(state => state.dashboardInfo);
+ const editMode = useSelector(state => state.dashboardState.editMode);
+ const fullSizeChartId = useSelector(
+ state => state.dashboardState.fullSizeChartId,
+ );
+ const dashboardId = dashboardInfo.id;
+ const component = dashboardLayout[props.id];
+ const parentComponent = dashboardLayout[props.parentId];
+ const getComponentById = useCallback(
+ id => dashboardLayout[id],
+ [dashboardLayout],
+ );
+ const { isComponentVisible = true } = props;
+ const filters = getActiveFilters();
+ const embeddedMode = !dashboardInfo.userId;
-function mapStateToProps(
- { dashboardLayout: undoableLayout, dashboardState, dashboardInfo },
- ownProps,
-) {
- const dashboardLayout = undoableLayout.present;
- const { id, parentId } = ownProps;
- const component = dashboardLayout[id];
- const props = {
- component,
- getComponentById: id => dashboardLayout[id],
- parentComponent: dashboardLayout[parentId],
- editMode: dashboardState.editMode,
- filters: getActiveFilters(),
- dashboardId: dashboardInfo.id,
- dashboardInfo,
- fullSizeChartId: dashboardState.fullSizeChartId,
- embeddedMode: !dashboardInfo?.userId,
- };
+ const boundActionCreators = useMemo(
+ () =>
+ bindActionCreators(
+ {
+ addDangerToast,
+ createComponent,
+ deleteComponent,
+ updateComponents,
+ handleComponentDrop,
+ setDirectPathToChild,
+ setFullSizeChartId,
+ setActiveTab,
+ logEvent,
+ },
+ dispatch,
+ ),
+ [dispatch],
+ );
// rows and columns need more data about their child dimensions
// doing this allows us to not pass the entire component lookup to all
Components
- if (component) {
- const componentType = component.type;
- if (componentType === ROW_TYPE || componentType === COLUMN_TYPE) {
- const { occupiedWidth, minimumWidth } = getDetailedComponentWidth({
- id,
- components: dashboardLayout,
- });
+ const { occupiedColumnCount, minColumnWidth } = useMemo(() => {
+ if (component) {
+ const componentType = component.type;
+ if (componentType === ROW_TYPE || componentType === COLUMN_TYPE) {
+ const { occupiedWidth, minimumWidth } = getDetailedComponentWidth({
+ id: props.id,
+ components: dashboardLayout,
+ });
- if (componentType === ROW_TYPE) props.occupiedColumnCount =
occupiedWidth;
- if (componentType === COLUMN_TYPE) props.minColumnWidth = minimumWidth;
+ if (componentType === ROW_TYPE) {
+ return { occupiedColumnCount: occupiedWidth };
+ }
+ if (componentType === COLUMN_TYPE) {
+ return { minColumnWidth: minimumWidth };
+ }
+ }
+ return {};
}
- }
+ return {};
+ }, [component, dashboardLayout, props.id]);
- return props;
-}
-
-function mapDispatchToProps(dispatch) {
- return bindActionCreators(
- {
- addDangerToast,
- createComponent,
- deleteComponent,
- updateComponents,
- handleComponentDrop,
- setDirectPathToChild,
- setFullSizeChartId,
- setActiveTab,
- logEvent,
- },
- dispatch,
- );
-}
-
-class DashboardComponent extends PureComponent {
- render() {
- const { component } = this.props;
- const Component = component ? componentLookup[component.type] : null;
- return Component ? <Component {...this.props} /> : null;
- }
-}
+ const Component = component ? componentLookup[component.type] : null;
+ return Component ? (
+ <Component
+ {...props}
+ {...boundActionCreators}
+ component={component}
+ getComponentById={getComponentById}
+ parentComponent={parentComponent}
+ editMode={editMode}
+ filters={filters}
+ dashboardId={dashboardId}
+ dashboardInfo={dashboardInfo}
+ fullSizeChartId={fullSizeChartId}
+ occupiedColumnCount={occupiedColumnCount}
+ minColumnWidth={minColumnWidth}
+ isComponentVisible={isComponentVisible}
+ embeddedMode={embeddedMode}
+ />
+ ) : null;
+};
DashboardComponent.propTypes = propTypes;
-DashboardComponent.defaultProps = defaultProps;
-export default connect(mapStateToProps,
mapDispatchToProps)(DashboardComponent);
+export default memo(DashboardComponent);