Skip to content

Commit

Permalink
[8.x] [Dashboard] [Collapsable Panels] Reduce re-renders (#197343) (#…
Browse files Browse the repository at this point in the history
…198219)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[Dashboard] [Collapsable Panels] Reduce re-renders
(#197343)](#197343)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Hannah
Mudge","email":"[email protected]"},"sourceCommit":{"committedDate":"2024-10-29T22:07:45Z","message":"[Dashboard]
[Collapsable Panels] Reduce re-renders (#197343)\n\nCloses
https://github.com/elastic/kibana/issues/191131\r\n\r\n##
Summary\r\n\r\nThis PR greatly reduces the number of React re-renders
that happen as\r\npanels get dragged around and/or resized. Now, the
actions that trigger\r\na panel to get rendered are as follows:\r\n1.
Obviously, when the grid first loads, every panel has to be
rendered.\r\n2. When a panel gets dragged from one row to the next, both
the original\r\nrow and the new row will re-render all of their panels
because the panel\r\nIDs in both rows changed - however, because of the
`key` prop on the\r\n`GridPanel` component, only the **dragged** panel
will actually be fully\r\nre-mounted.\r\n3. When a panel gets collapsed
and expanded, all panels in that row will\r\nget re-mounted and
rendered.\r\n4. When a panel ID gets changed (this currently isn't
possible, but in\r\ntheory, this would also trigger the panel to get
re-rendered due to the\r\n`key` prop on the `GridPanel`
component)\r\n\r\nIn order to accomplish this, we are now handling **all
style changes**\r\nvia a subscription rather than setting the CSS
directly; so, as the\r\n`gridLayout# Backport

This will backport the following commits from `main` to `8.x`:
- [[Dashboard] [Collapsable Panels] Reduce re-renders
(#197343)](#197343)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT behaviour subjects publishes changes, we update the row
+\r\npanel styles via the panel reference. This allows us to change how
the\r\ngrid looks without triggering React to rerender the entire
panel.\r\n\r\n**How to Test:**\r\nAdd a `console.log` to the
`renderPanelContents` in\r\n`examples/grid_example/public/app.tsx` -
this will tell you when a panel\r\nis getting re-rendered.\r\n\r\n###
Checklist\r\n\r\n- [x] This was checked for
[cross-browser\r\ncompatibility](https://www.elastic.co/support/matrix#matrix_browsers)\r\n\r\n\r\n\r\n###
For maintainers\r\n\r\n- [ ] This was checked for breaking API changes
and was
[labeled\r\nappropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#_add_your_labels)\r\n-
[ ] This will appear in the **Release Notes** and follow
the\r\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\r\n\r\n---------\r\n\r\nCo-authored-by:
kibanamachine
<[email protected]>","sha":"b91fa562bb7663a119fdd9c22054560960f625a0","branchLabelMapping":{"^v9.0.0$":"main","^v8.17.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["Feature:Dashboard","Team:Presentation","loe:medium","release_note:skip","impact:high","v9.0.0","backport:prev-minor","Project:Collapsable
Panels"],"title":"[Dashboard] [Collapsable Panels] Reduce
re-renders","number":197343,"url":"https://github.com/elastic/kibana/pull/197343","mergeCommit":{"message":"[Dashboard]
[Collapsable Panels] Reduce re-renders (#197343)\n\nCloses
https://github.com/elastic/kibana/issues/191131\r\n\r\n##
Summary\r\n\r\nThis PR greatly reduces the number of React re-renders
that happen as\r\npanels get dragged around and/or resized. Now, the
actions that trigger\r\na panel to get rendered are as follows:\r\n1.
Obviously, when the grid first loads, every panel has to be
rendered.\r\n2. When a panel gets dragged from one row to the next, both
the original\r\nrow and the new row will re-render all of their panels
because the panel\r\nIDs in both rows changed - however, because of the
`key` prop on the\r\n`GridPanel` component, only the **dragged** panel
will actually be fully\r\nre-mounted.\r\n3. When a panel gets collapsed
and expanded, all panels in that row will\r\nget re-mounted and
rendered.\r\n4. When a panel ID gets changed (this currently isn't
possible, but in\r\ntheory, this would also trigger the panel to get
re-rendered due to the\r\n`key` prop on the `GridPanel`
component)\r\n\r\nIn order to accomplish this, we are now handling **all
style changes**\r\nvia a subscription rather than setting the CSS
directly; so, as the\r\n`gridLayout# Backport

This will backport the following commits from `main` to `8.x`:
- [[Dashboard] [Collapsable Panels] Reduce re-renders
(#197343)](#197343)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT behaviour subjects publishes changes, we update the row
+\r\npanel styles via the panel reference. This allows us to change how
the\r\ngrid looks without triggering React to rerender the entire
panel.\r\n\r\n**How to Test:**\r\nAdd a `console.log` to the
`renderPanelContents` in\r\n`examples/grid_example/public/app.tsx` -
this will tell you when a panel\r\nis getting re-rendered.\r\n\r\n###
Checklist\r\n\r\n- [x] This was checked for
[cross-browser\r\ncompatibility](https://www.elastic.co/support/matrix#matrix_browsers)\r\n\r\n\r\n\r\n###
For maintainers\r\n\r\n- [ ] This was checked for breaking API changes
and was
[labeled\r\nappropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#_add_your_labels)\r\n-
[ ] This will appear in the **Release Notes** and follow
the\r\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\r\n\r\n---------\r\n\r\nCo-authored-by:
kibanamachine
<[email protected]>","sha":"b91fa562bb7663a119fdd9c22054560960f625a0"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/197343","number":197343,"mergeCommit":{"message":"[Dashboard]
[Collapsable Panels] Reduce re-renders (#197343)\n\nCloses
https://github.com/elastic/kibana/issues/191131\r\n\r\n##
Summary\r\n\r\nThis PR greatly reduces the number of React re-renders
that happen as\r\npanels get dragged around and/or resized. Now, the
actions that trigger\r\na panel to get rendered are as follows:\r\n1.
Obviously, when the grid first loads, every panel has to be
rendered.\r\n2. When a panel gets dragged from one row to the next, both
the original\r\nrow and the new row will re-render all of their panels
because the panel\r\nIDs in both rows changed - however, because of the
`key` prop on the\r\n`GridPanel` component, only the **dragged** panel
will actually be fully\r\nre-mounted.\r\n3. When a panel gets collapsed
and expanded, all panels in that row will\r\nget re-mounted and
rendered.\r\n4. When a panel ID gets changed (this currently isn't
possible, but in\r\ntheory, this would also trigger the panel to get
re-rendered due to the\r\n`key` prop on the `GridPanel`
component)\r\n\r\nIn order to accomplish this, we are now handling **all
style changes**\r\nvia a subscription rather than setting the CSS
directly; so, as the\r\n`gridLayout# Backport

This will backport the following commits from `main` to `8.x`:
- [[Dashboard] [Collapsable Panels] Reduce re-renders
(#197343)](#197343)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT behaviour subjects publishes changes, we update the row
+\r\npanel styles via the panel reference. This allows us to change how
the\r\ngrid looks without triggering React to rerender the entire
panel.\r\n\r\n**How to Test:**\r\nAdd a `console.log` to the
`renderPanelContents` in\r\n`examples/grid_example/public/app.tsx` -
this will tell you when a panel\r\nis getting re-rendered.\r\n\r\n###
Checklist\r\n\r\n- [x] This was checked for
[cross-browser\r\ncompatibility](https://www.elastic.co/support/matrix#matrix_browsers)\r\n\r\n\r\n\r\n###
For maintainers\r\n\r\n- [ ] This was checked for breaking API changes
and was
[labeled\r\nappropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#_add_your_labels)\r\n-
[ ] This will appear in the **Release Notes** and follow
the\r\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\r\n\r\n---------\r\n\r\nCo-authored-by:
kibanamachine
<[email protected]>","sha":"b91fa562bb7663a119fdd9c22054560960f625a0"}}]}]
BACKPORT-->

Co-authored-by: Hannah Mudge <[email protected]>
  • Loading branch information
kibanamachine and Heenawter authored Oct 29, 2024
1 parent 3f8de10 commit 1f9e9e7
Show file tree
Hide file tree
Showing 7 changed files with 415 additions and 291 deletions.
71 changes: 71 additions & 0 deletions packages/kbn-grid-layout/grid/drag_preview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import React, { useEffect, useRef } from 'react';
import { combineLatest, skip } from 'rxjs';

import { transparentize } from '@elastic/eui';
import { css } from '@emotion/react';
import { euiThemeVars } from '@kbn/ui-theme';

import { GridLayoutStateManager } from './types';

export const DragPreview = ({
rowIndex,
gridLayoutStateManager,
}: {
rowIndex: number;
gridLayoutStateManager: GridLayoutStateManager;
}) => {
const dragPreviewRef = useRef<HTMLDivElement | null>(null);

useEffect(
() => {
/** Update the styles of the drag preview via a subscription to prevent re-renders */
const styleSubscription = combineLatest([
gridLayoutStateManager.activePanel$,
gridLayoutStateManager.gridLayout$,
])
.pipe(skip(1)) // skip the first emit because the drag preview is only rendered after a user action
.subscribe(([activePanel, gridLayout]) => {
if (!dragPreviewRef.current) return;

if (!activePanel || !gridLayout[rowIndex].panels[activePanel.id]) {
dragPreviewRef.current.style.display = 'none';
} else {
const panel = gridLayout[rowIndex].panels[activePanel.id];
dragPreviewRef.current.style.display = 'block';
dragPreviewRef.current.style.gridColumnStart = `${panel.column + 1}`;
dragPreviewRef.current.style.gridColumnEnd = `${panel.column + 1 + panel.width}`;
dragPreviewRef.current.style.gridRowStart = `${panel.row + 1}`;
dragPreviewRef.current.style.gridRowEnd = `${panel.row + 1 + panel.height}`;
}
});

return () => {
styleSubscription.unsubscribe();
};
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
);

return (
<div
ref={dragPreviewRef}
css={css`
display: none;
pointer-events: none;
border-radius: ${euiThemeVars.euiBorderRadius};
background-color: ${transparentize(euiThemeVars.euiColorSuccess, 0.2)};
transition: opacity 100ms linear;
`}
/>
);
};
37 changes: 25 additions & 12 deletions packages/kbn-grid-layout/grid/grid_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import React from 'react';

import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing';
import React, { useEffect, useState } from 'react';
import { distinctUntilChanged, map, skip } from 'rxjs';
import { v4 as uuidv4 } from 'uuid';

import { GridHeightSmoother } from './grid_height_smoother';
import { GridRow } from './grid_row';
Expand All @@ -29,12 +29,28 @@ export const GridLayout = ({
});
useGridLayoutEvents({ gridLayoutStateManager });

const [gridLayout, runtimeSettings, interactionEvent] = useBatchedPublishingSubjects(
gridLayoutStateManager.gridLayout$,
gridLayoutStateManager.runtimeSettings$,
gridLayoutStateManager.interactionEvent$
const [rowCount, setRowCount] = useState<number>(
gridLayoutStateManager.gridLayout$.getValue().length
);

useEffect(() => {
/**
* The only thing that should cause the entire layout to re-render is adding a new row;
* this subscription ensures this by updating the `rowCount` state when it changes.
*/
const rowCountSubscription = gridLayoutStateManager.gridLayout$
.pipe(
skip(1), // we initialized `rowCount` above, so skip the initial emit
map((newLayout) => newLayout.length),
distinctUntilChanged()
)
.subscribe((newRowCount) => {
setRowCount(newRowCount);
});
return () => rowCountSubscription.unsubscribe();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

return (
<>
<GridHeightSmoother gridLayoutStateManager={gridLayoutStateManager}>
Expand All @@ -43,15 +59,12 @@ export const GridLayout = ({
setDimensionsRef(divElement);
}}
>
{gridLayout.map((rowData, rowIndex) => {
{Array.from({ length: rowCount }, (_, rowIndex) => {
return (
<GridRow
rowData={rowData}
key={rowData.title}
key={uuidv4()}
rowIndex={rowIndex}
runtimeSettings={runtimeSettings}
renderPanelContents={renderPanelContents}
targetRowIndex={interactionEvent?.targetRowIndex}
gridLayoutStateManager={gridLayoutStateManager}
toggleIsCollapsed={() => {
const currentLayout = gridLayoutStateManager.gridLayout$.value;
Expand Down
Loading

0 comments on commit 1f9e9e7

Please sign in to comment.