[Infrastructure UI] Fix inventory table pagination navigation (#153849)

## 📓 Summary

Closes #153739 

Each time the whole inventory table was rerendering due to a click on an
entry, a new list of items was regenerated, reinitializing the in-memory
table component from the first page.

Memoizing the generated items solves the issue and the table work as
expected.

This PR also solve another issue occurring when multiple table entries
were clicked, which resulted in many popovers opened and a blocked UI as
per the following screenshot:

<img width="1267" alt="multi-popover"
src="https://user-images.githubusercontent.com/34506779/228196645-9c0444b7-9114-47c2-8e5d-0bec29ed5305.png">

## 🧪 Testing
- Navigate to `Inventory` and select the `Kubernetes Pods` option from
the **Show** filter.
- Switch the view to a table with the top-right selector
- Verify the popover correctly opens on any entry of the first table
page, then switch to different pages and check the behaviour is
maintained and there are no reinitializations for the table.
- Verify the same from the `Docker Containers` list.



https://user-images.githubusercontent.com/34506779/228198613-afafe3fe-f714-4c88-a288-fc4c95ca801a.mov

Co-authored-by: Marco Antonio Ghiani <marcoantonio.ghiani@elastic.co>
This commit is contained in:
Marco Antonio Ghiani 2023-03-29 09:44:24 +02:00 committed by GitHub
parent 598453d2d3
commit f17457b86e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 38 additions and 56 deletions

View file

@ -7,8 +7,6 @@
import { APP_WRAPPER_CLASS } from '@kbn/core/public';
export const CONTAINER_CLASSNAME = 'infra-container-element';
export const prepareMountElement = (element: HTMLElement, testSubject?: string) => {
// Ensure all wrapping elements have the APP_WRAPPER_CLASS so that the KinanaPageTemplate works as expected
element.classList.add(APP_WRAPPER_CLASS);

View file

@ -9,7 +9,7 @@ import { EuiButtonEmpty, EuiInMemoryTable, EuiToolTip, EuiBasicTableColumn } fro
import { i18n } from '@kbn/i18n';
import { last, first } from 'lodash';
import React, { useState, useCallback, useEffect } from 'react';
import React, { useState, useMemo } from 'react';
import { EuiPopover } from '@elastic/eui';
import { createWaffleMapNode } from '../lib/nodes_to_wafflemap';
import { InfraWaffleMapNode, InfraWaffleMapOptions } from '../../../../lib/lib';
@ -17,7 +17,6 @@ import { fieldToName } from '../lib/field_to_display_name';
import { NodeContextMenu } from './waffle/node_context_menu';
import { InventoryItemType } from '../../../../../common/inventory_models/types';
import { SnapshotNode, SnapshotNodePath } from '../../../../../common/http_api/snapshot_api';
import { CONTAINER_CLASSNAME } from '../../../../apps/common_styles';
interface Props {
nodes: SnapshotNode[];
@ -28,6 +27,13 @@ interface Props {
onFilter: (filter: string) => void;
}
const initialSorting = {
sort: {
field: 'value',
direction: 'desc',
},
} as const;
const getGroupPaths = (path: SnapshotNodePath[]) => {
switch (path.length) {
case 3:
@ -41,33 +47,10 @@ const getGroupPaths = (path: SnapshotNodePath[]) => {
export const TableView = (props: Props) => {
const { nodes, options, formatter, currentTime, nodeType } = props;
const [openPopovers, setOpenPopovers] = useState<string[]>([]);
const openPopoverFor = useCallback(
(id: string) => () => {
setOpenPopovers([...openPopovers, id]);
},
[openPopovers]
);
const closePopoverFor = useCallback(
(id: string) => () => {
if (openPopovers.includes(id)) {
setOpenPopovers(openPopovers.filter((subject) => subject !== id));
}
},
[openPopovers]
);
const [openPopoverId, setOpenPopoverId] = useState<string | null>(null);
useEffect(() => {
const el = document.getElementsByClassName(CONTAINER_CLASSNAME)[0];
if (el instanceof HTMLElement) {
if (openPopovers.length > 0) {
el.style.overflowY = 'hidden';
} else {
el.style.overflowY = 'auto';
}
}
}, [openPopovers]);
const closePopover = () => setOpenPopoverId(null);
const columns: Array<EuiBasicTableColumn<typeof items[number]>> = [
{
@ -84,7 +67,10 @@ export const TableView = (props: Props) => {
const uniqueID = [...item.node.path.map((p) => p.value), item.node.name].join(':');
const button = (
<EuiToolTip content={tooltipText}>
<EuiButtonEmpty data-test-subj="infraColumnsButton" onClick={openPopoverFor(uniqueID)}>
<EuiButtonEmpty
data-test-subj="infraColumnsButton"
onClick={() => setOpenPopoverId(uniqueID)}
>
{value}
</EuiButtonEmpty>
</EuiToolTip>
@ -93,8 +79,8 @@ export const TableView = (props: Props) => {
return (
<EuiPopover
button={button}
isOpen={openPopovers.includes(uniqueID)}
closePopover={closePopoverFor(uniqueID)}
isOpen={openPopoverId === uniqueID}
closePopover={closePopover}
anchorPosition="rightCenter"
>
<NodeContextMenu
@ -152,30 +138,28 @@ export const TableView = (props: Props) => {
},
];
const items = nodes.map((node) => {
const name = last(node.path);
const metric = first(node.metrics);
return {
name: (name && name.label) || 'unknown',
...getGroupPaths(node.path).reduce(
(acc, path, index) => ({
...acc,
[`group_${index}`]: path.label,
}),
{}
),
value: (metric && metric.value) || 0,
avg: (metric && metric.avg) || 0,
max: (metric && metric.max) || 0,
node: createWaffleMapNode(node),
};
});
const initialSorting = {
sort: {
field: 'value',
direction: 'desc',
},
} as const;
const items = useMemo(
() =>
nodes.map((node) => {
const name = last(node.path);
const metric = first(node.metrics);
return {
name: (name && name.label) || 'unknown',
...getGroupPaths(node.path).reduce(
(acc, path, index) => ({
...acc,
[`group_${index}`]: path.label,
}),
{}
),
value: (metric && metric.value) || 0,
avg: (metric && metric.avg) || 0,
max: (metric && metric.max) || 0,
node: createWaffleMapNode(node),
};
}),
[nodes]
);
return (
<EuiInMemoryTable pagination={true} sorting={initialSorting} items={items} columns={columns} />