diff --git a/x-pack/legacy/plugins/infra/public/components/nodes_overview/table.tsx b/x-pack/legacy/plugins/infra/public/components/nodes_overview/table.tsx index dc0de6f6e9c69..5c793f670119c 100644 --- a/x-pack/legacy/plugins/infra/public/components/nodes_overview/table.tsx +++ b/x-pack/legacy/plugins/infra/public/components/nodes_overview/table.tsx @@ -8,29 +8,24 @@ import { EuiButtonEmpty, EuiInMemoryTable, EuiToolTip, EuiBasicTableColumn } fro import { i18n } from '@kbn/i18n'; import { last } from 'lodash'; -import React from 'react'; +import React, { useState, useCallback, useEffect } from 'react'; import { createWaffleMapNode } from '../../containers/waffle/nodes_to_wafflemap'; import { InfraWaffleMapNode, InfraWaffleMapOptions } from '../../lib/lib'; import { fieldToName } from '../waffle/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 { ROOT_ELEMENT_ID } from '../../app'; interface Props { nodes: SnapshotNode[]; nodeType: InventoryItemType; options: InfraWaffleMapOptions; - formatter: (subject: string | number) => string; currentTime: number; + formatter: (subject: string | number) => string; onFilter: (filter: string) => void; } -const initialState = { - isPopoverOpen: [] as string[], -}; - -type State = Readonly; - const getGroupPaths = (path: SnapshotNodePath[]) => { switch (path.length) { case 3: @@ -42,126 +37,131 @@ const getGroupPaths = (path: SnapshotNodePath[]) => { } }; -export const TableView = class extends React.PureComponent { - public readonly state: State = initialState; - public render() { - const { nodes, options, formatter, currentTime, nodeType } = this.props; - const columns: Array> = [ - { - field: 'name', - name: i18n.translate('xpack.infra.tableView.columnName.name', { defaultMessage: 'Name' }), - sortable: true, - truncateText: true, - textOnly: true, - render: (value: string, item: { node: InfraWaffleMapNode }) => { - const tooltipText = item.node.id === value ? `${value}` : `${value} (${item.node.id})`; - // For the table we need to create a UniqueID that takes into to account the groupings - // as well as the node name. There is the possibility that a node can be present in two - // different groups and be on the screen at the same time. - const uniqueID = [...item.node.path.map(p => p.value), item.node.name].join(':'); - return ( - - - {value} - - - ); - }, - }, - ...options.groupBy.map((grouping, index) => ({ - field: `group_${index}`, - name: fieldToName((grouping && grouping.field) || ''), - sortable: true, - truncateText: true, - textOnly: true, - render: (value: string) => { - const handleClick = () => this.props.onFilter(`${grouping.field}:"${value}"`); - return ( - - {value} +export const TableView = (props: Props) => { + const { nodes, options, formatter, currentTime, nodeType } = props; + const [openPopovers, setOpenPopovers] = useState([]); + 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] + ); + + useEffect(() => { + if (openPopovers.length > 0) { + document.getElementById(ROOT_ELEMENT_ID)!.style.overflowY = 'hidden'; + } else { + document.getElementById(ROOT_ELEMENT_ID)!.style.overflowY = 'auto'; + } + }, [openPopovers]); + + const columns: Array> = [ + { + field: 'name', + name: i18n.translate('xpack.infra.tableView.columnName.name', { defaultMessage: 'Name' }), + sortable: true, + truncateText: true, + textOnly: true, + render: (value: string, item: { node: InfraWaffleMapNode }) => { + const tooltipText = item.node.id === value ? `${value}` : `${value} (${item.node.id})`; + // For the table we need to create a UniqueID that takes into to account the groupings + // as well as the node name. There is the possibility that a node can be present in two + // different groups and be on the screen at the same time. + const uniqueID = [...item.node.path.map(p => p.value), item.node.name].join(':'); + return ( + + + {value} - ); - }, - })), - { - field: 'value', - name: i18n.translate('xpack.infra.tableView.columnName.last1m', { - defaultMessage: 'Last 1m', - }), - sortable: true, - truncateText: true, - dataType: 'number', - render: (value: number) => {formatter(value)}, - }, - { - field: 'avg', - name: i18n.translate('xpack.infra.tableView.columnName.avg', { defaultMessage: 'Avg' }), - sortable: true, - truncateText: true, - dataType: 'number', - render: (value: number) => {formatter(value)}, + + ); }, - { - field: 'max', - name: i18n.translate('xpack.infra.tableView.columnName.max', { defaultMessage: 'Max' }), - sortable: true, - truncateText: true, - dataType: 'number', - render: (value: number) => {formatter(value)}, + }, + ...options.groupBy.map((grouping, index) => ({ + field: `group_${index}`, + name: fieldToName((grouping && grouping.field) || ''), + sortable: true, + truncateText: true, + textOnly: true, + render: (value: string) => { + const handleClick = () => props.onFilter(`${grouping.field}:"${value}"`); + return ( + + {value} + + ); }, - ]; - const items = nodes.map(node => { - const name = last(node.path); - return { - name: (name && name.label) || 'unknown', - ...getGroupPaths(node.path).reduce( - (acc, path, index) => ({ - ...acc, - [`group_${index}`]: path.label, - }), - {} - ), - value: node.metric.value, - avg: node.metric.avg, - max: node.metric.max, - node: createWaffleMapNode(node), - }; - }); - const initialSorting = { - sort: { - field: 'value', - direction: 'desc', - }, - } as const; - return ( - - ); - } + })), + { + field: 'value', + name: i18n.translate('xpack.infra.tableView.columnName.last1m', { + defaultMessage: 'Last 1m', + }), + sortable: true, + truncateText: true, + dataType: 'number', + render: (value: number) => {formatter(value)}, + }, + { + field: 'avg', + name: i18n.translate('xpack.infra.tableView.columnName.avg', { defaultMessage: 'Avg' }), + sortable: true, + truncateText: true, + dataType: 'number', + render: (value: number) => {formatter(value)}, + }, + { + field: 'max', + name: i18n.translate('xpack.infra.tableView.columnName.max', { defaultMessage: 'Max' }), + sortable: true, + truncateText: true, + dataType: 'number', + render: (value: number) => {formatter(value)}, + }, + ]; - private openPopoverFor = (id: string) => () => { - this.setState(prevState => ({ isPopoverOpen: [...prevState.isPopoverOpen, id] })); - }; + const items = nodes.map(node => { + const name = last(node.path); + return { + name: (name && name.label) || 'unknown', + ...getGroupPaths(node.path).reduce( + (acc, path, index) => ({ + ...acc, + [`group_${index}`]: path.label, + }), + {} + ), + value: node.metric.value, + avg: node.metric.avg, + max: node.metric.max, + node: createWaffleMapNode(node), + }; + }); + const initialSorting = { + sort: { + field: 'value', + direction: 'desc', + }, + } as const; - private closePopoverFor = (id: string) => () => { - if (this.state.isPopoverOpen.includes(id)) { - this.setState(prevState => { - return { - isPopoverOpen: prevState.isPopoverOpen.filter(subject => subject !== id), - }; - }); - } - }; + return ( + + ); }; diff --git a/x-pack/legacy/plugins/infra/public/components/waffle/node_context_menu.tsx b/x-pack/legacy/plugins/infra/public/components/waffle/node_context_menu.tsx index 86a22c358b4d5..97b85a7e6e17d 100644 --- a/x-pack/legacy/plugins/infra/public/components/waffle/node_context_menu.tsx +++ b/x-pack/legacy/plugins/infra/public/components/waffle/node_context_menu.tsx @@ -28,7 +28,6 @@ import { interface Props { options: InfraWaffleMapOptions; currentTime: number; - children: any; node: InfraWaffleMapNode; nodeType: InventoryItemType; isPopoverOpen: boolean; @@ -36,7 +35,7 @@ interface Props { popoverPosition: EuiPopoverProps['anchorPosition']; } -export const NodeContextMenu = ({ +export const NodeContextMenu: React.FC = ({ options, currentTime, children, @@ -45,7 +44,7 @@ export const NodeContextMenu = ({ closePopover, nodeType, popoverPosition, -}: Props) => { +}) => { const uiCapabilities = useKibana().services.application?.capabilities; const inventoryModel = findInventoryModel(nodeType); const nodeDetailFrom = currentTime - inventoryModel.metrics.defaultTimeRangeInSeconds * 1000; @@ -132,7 +131,7 @@ export const NodeContextMenu = ({ closePopover={closePopover} id={`${node.pathId}-popover`} isOpen={isPopoverOpen} - button={children} + button={children!} anchorPosition={popoverPosition} >