Skip to content

Commit

Permalink
Dashboard Performance: Memoize widgets
Browse files Browse the repository at this point in the history
  • Loading branch information
gabrieldutra committed Mar 24, 2020
1 parent 3f66990 commit 8469458
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 48 deletions.
102 changes: 70 additions & 32 deletions client/app/components/dashboards/DashboardGrid.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,53 @@ const WidgetType = PropTypes.shape({
const SINGLE = "single-column";
const MULTI = "multi-column";

const DashboardWidget = React.memo(
function DashboardWidget({
widget,
dashboard,
onLoadWidget,
onRefreshWidget,
onRemoveWidget,
onParameterMappingsChange,
canEdit,
isPublic,
isLoading,
filters,
}) {
const { type } = widget;
const onLoad = () => onLoadWidget(widget);
const onRefresh = () => onRefreshWidget(widget);
const onDelete = () => onRemoveWidget(widget.id);

if (type === WidgetTypeEnum.VISUALIZATION) {
return (
<VisualizationWidget
widget={widget}
dashboard={dashboard}
filters={filters}
canEdit={canEdit}
isPublic={isPublic}
isLoading={isLoading}
onLoad={onLoad}
onRefresh={onRefresh}
onDelete={onDelete}
onParameterMappingsChange={onParameterMappingsChange}
/>
);
}
if (type === WidgetTypeEnum.TEXTBOX) {
return <TextboxWidget widget={widget} canEdit={canEdit} isPublic={isPublic} onDelete={onDelete} />;
}
return <RestrictedWidget widget={widget} />;
},
(prevProps, nextProps) =>
prevProps.widget === nextProps.widget &&
prevProps.canEdit === nextProps.canEdit &&
prevProps.isPublic === nextProps.isPublic &&
prevProps.isLoading === nextProps.isLoading &&
prevProps.filters === nextProps.filters
);

class DashboardGrid extends React.Component {
static propTypes = {
isEditing: PropTypes.bool.isRequired,
Expand Down Expand Up @@ -203,38 +250,29 @@ class DashboardGrid extends React.Component {
onLayoutChange={this.onLayoutChange}
onBreakpointChange={this.onBreakpointChange}
breakpoints={{ [MULTI]: cfg.mobileBreakPoint, [SINGLE]: 0 }}>
{widgets.map(widget => {
const widgetProps = {
widget,
filters,
isPublic,
canEdit: dashboard.canEdit(),
onDelete: () => onRemoveWidget(widget.id),
};
const { type } = widget;
return (
<div
key={widget.id}
data-grid={DashboardGrid.normalizeFrom(widget)}
data-widgetid={widget.id}
data-test={`WidgetId${widget.id}`}
className={cx("dashboard-widget-wrapper", {
"widget-auto-height-enabled": this.autoHeightCtrl.exists(widget.id),
})}>
{type === WidgetTypeEnum.VISUALIZATION && (
<VisualizationWidget
{...widgetProps}
dashboard={dashboard}
onLoad={() => onLoadWidget(widget)}
onRefresh={() => onRefreshWidget(widget)}
onParameterMappingsChange={onParameterMappingsChange}
/>
)}
{type === WidgetTypeEnum.TEXTBOX && <TextboxWidget {...widgetProps} />}
{type === WidgetTypeEnum.RESTRICTED && <RestrictedWidget widget={widget} />}
</div>
);
})}
{widgets.map(widget => (
<div
key={widget.id}
data-grid={DashboardGrid.normalizeFrom(widget)}
data-widgetid={widget.id}
data-test={`WidgetId${widget.id}`}
className={cx("dashboard-widget-wrapper", {
"widget-auto-height-enabled": this.autoHeightCtrl.exists(widget.id),
})}>
<DashboardWidget
dashboard={dashboard}
widget={widget}
filters={filters}
isPublic={isPublic}
isLoading={widget.loading}
canEdit={dashboard.canEdit()}
onLoadWidget={onLoadWidget}
onRefreshWidget={onRefreshWidget}
onRemoveWidget={onRemoveWidget}
onParameterMappingsChange={onParameterMappingsChange}
/>
</div>
))}
</ResponsiveGridLayout>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ class VisualizationWidget extends React.Component {
dashboard: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
filters: FiltersType,
isPublic: PropTypes.bool,
isLoading: PropTypes.bool,
canEdit: PropTypes.bool,
onLoad: PropTypes.func,
onRefresh: PropTypes.func,
Expand All @@ -197,6 +198,7 @@ class VisualizationWidget extends React.Component {
static defaultProps = {
filters: [],
isPublic: false,
isLoading: false,
canEdit: false,
onLoad: () => {},
onRefresh: () => {},
Expand Down Expand Up @@ -236,8 +238,8 @@ class VisualizationWidget extends React.Component {
};

renderVisualization() {
const { widget, filters } = this.props;
const widgetQueryResult = widget.getQueryResult();
const { isLoading, widget, filters } = this.props;
const widgetQueryResult = !isLoading && widget.getQueryResult();
const widgetStatus = widgetQueryResult && widgetQueryResult.getStatus();
switch (widgetStatus) {
case "failed":
Expand Down Expand Up @@ -273,10 +275,10 @@ class VisualizationWidget extends React.Component {
}

render() {
const { widget, isPublic, canEdit, onRefresh } = this.props;
const { widget, isLoading, isPublic, canEdit, onRefresh } = this.props;
const { localParameters } = this.state;
const widgetQueryResult = widget.getQueryResult();
const isRefreshing = widget.loading && !!(widgetQueryResult && widgetQueryResult.getStatus());
const isRefreshing = isLoading && !!(widgetQueryResult && widgetQueryResult.getStatus());

return (
<Widget
Expand Down
27 changes: 15 additions & 12 deletions client/app/pages/dashboards/hooks/useDashboard.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState, useEffect, useMemo, useCallback } from "react";
import { useState, useEffect, useMemo, useCallback, useRef } from "react";
import { isEmpty, includes, compact, map, has, pick, keys, extend, every, get } from "lodash";
import notification from "@/services/notification";
import location from "@/services/location";
Expand Down Expand Up @@ -100,28 +100,31 @@ function useDashboard(dashboardData) {

const refreshWidget = useCallback(widget => loadWidget(widget, true), [loadWidget]);

const removeWidget = useCallback(
widgetId => {
dashboard.widgets = dashboard.widgets.filter(widget => widget.id !== undefined && widget.id !== widgetId);
setDashboard(currentDashboard => extend({}, currentDashboard));
},
[dashboard]
);
const removeWidget = useCallback(widgetId => {
setDashboard(currentDashboard =>
extend({}, currentDashboard, {
widgets: currentDashboard.widgets.filter(widget => widget.id !== undefined && widget.id !== widgetId),
})
);
}, []);

const dashboardRef = useRef();
dashboardRef.current = dashboard;

const loadDashboard = useCallback(
(forceRefresh = false, updatedParameters = []) => {
const affectedWidgets = getAffectedWidgets(dashboard.widgets, updatedParameters);
const affectedWidgets = getAffectedWidgets(dashboardRef.current.widgets, updatedParameters);
const loadWidgetPromises = compact(
affectedWidgets.map(widget => loadWidget(widget, forceRefresh).catch(error => error))
);

return Promise.all(loadWidgetPromises).then(() => {
const queryResults = compact(map(dashboard.widgets, widget => widget.getQueryResult()));
const updatedFilters = collectDashboardFilters(dashboard, queryResults, location.search);
const queryResults = compact(map(dashboardRef.current.widgets, widget => widget.getQueryResult()));
const updatedFilters = collectDashboardFilters(dashboardRef.current, queryResults, location.search);
setFilters(updatedFilters);
});
},
[dashboard, loadWidget]
[loadWidget]
);

const refreshDashboard = useCallback(
Expand Down

0 comments on commit 8469458

Please sign in to comment.