Skip to content

Commit

Permalink
[Metrics-UI] Fix toolbar popover for metrics table row (#56796) (#57111)
Browse files Browse the repository at this point in the history
* Disable scroll when popover is open. Switch to hooks

* Remove unused prop

* UseEffect to update styles

* Remove ununsed import

* Rename variables for clarity

Co-authored-by: Elastic Machine <[email protected]>

Co-authored-by: Elastic Machine <[email protected]>
  • Loading branch information
phillipb and elasticmachine authored Feb 7, 2020
1 parent 0395ba7 commit 29b4933
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 129 deletions.
250 changes: 125 additions & 125 deletions x-pack/legacy/plugins/infra/public/components/nodes_overview/table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof initialState>;

const getGroupPaths = (path: SnapshotNodePath[]) => {
switch (path.length) {
case 3:
Expand All @@ -42,126 +37,131 @@ const getGroupPaths = (path: SnapshotNodePath[]) => {
}
};

export const TableView = class extends React.PureComponent<Props, State> {
public readonly state: State = initialState;
public render() {
const { nodes, options, formatter, currentTime, nodeType } = this.props;
const columns: Array<EuiBasicTableColumn<typeof items[number]>> = [
{
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 (
<NodeContextMenu
node={item.node}
nodeType={nodeType}
closePopover={this.closePopoverFor(uniqueID)}
currentTime={currentTime}
isPopoverOpen={this.state.isPopoverOpen.includes(uniqueID)}
options={options}
popoverPosition="rightCenter"
>
<EuiToolTip content={tooltipText}>
<EuiButtonEmpty onClick={this.openPopoverFor(uniqueID)}>{value}</EuiButtonEmpty>
</EuiToolTip>
</NodeContextMenu>
);
},
},
...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 (
<EuiToolTip content="Set Filter">
<EuiButtonEmpty onClick={handleClick}>{value}</EuiButtonEmpty>
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]
);

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<EuiBasicTableColumn<typeof items[number]>> = [
{
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 (
<NodeContextMenu
node={item.node}
nodeType={nodeType}
closePopover={closePopoverFor(uniqueID)}
currentTime={currentTime}
isPopoverOpen={openPopovers.includes(uniqueID)}
options={options}
popoverPosition="rightCenter"
>
<EuiToolTip content={tooltipText}>
<EuiButtonEmpty onClick={openPopoverFor(uniqueID)}>{value}</EuiButtonEmpty>
</EuiToolTip>
);
},
})),
{
field: 'value',
name: i18n.translate('xpack.infra.tableView.columnName.last1m', {
defaultMessage: 'Last 1m',
}),
sortable: true,
truncateText: true,
dataType: 'number',
render: (value: number) => <span>{formatter(value)}</span>,
},
{
field: 'avg',
name: i18n.translate('xpack.infra.tableView.columnName.avg', { defaultMessage: 'Avg' }),
sortable: true,
truncateText: true,
dataType: 'number',
render: (value: number) => <span>{formatter(value)}</span>,
</NodeContextMenu>
);
},
{
field: 'max',
name: i18n.translate('xpack.infra.tableView.columnName.max', { defaultMessage: 'Max' }),
sortable: true,
truncateText: true,
dataType: 'number',
render: (value: number) => <span>{formatter(value)}</span>,
},
...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 (
<EuiToolTip content="Set Filter">
<EuiButtonEmpty onClick={handleClick}>{value}</EuiButtonEmpty>
</EuiToolTip>
);
},
];
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 (
<EuiInMemoryTable
pagination={true}
sorting={initialSorting}
items={items}
columns={columns}
/>
);
}
})),
{
field: 'value',
name: i18n.translate('xpack.infra.tableView.columnName.last1m', {
defaultMessage: 'Last 1m',
}),
sortable: true,
truncateText: true,
dataType: 'number',
render: (value: number) => <span>{formatter(value)}</span>,
},
{
field: 'avg',
name: i18n.translate('xpack.infra.tableView.columnName.avg', { defaultMessage: 'Avg' }),
sortable: true,
truncateText: true,
dataType: 'number',
render: (value: number) => <span>{formatter(value)}</span>,
},
{
field: 'max',
name: i18n.translate('xpack.infra.tableView.columnName.max', { defaultMessage: 'Max' }),
sortable: true,
truncateText: true,
dataType: 'number',
render: (value: number) => <span>{formatter(value)}</span>,
},
];

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 (
<EuiInMemoryTable pagination={true} sorting={initialSorting} items={items} columns={columns} />
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,14 @@ import {
interface Props {
options: InfraWaffleMapOptions;
currentTime: number;
children: any;
node: InfraWaffleMapNode;
nodeType: InventoryItemType;
isPopoverOpen: boolean;
closePopover: () => void;
popoverPosition: EuiPopoverProps['anchorPosition'];
}

export const NodeContextMenu = ({
export const NodeContextMenu: React.FC<Props> = ({
options,
currentTime,
children,
Expand All @@ -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;
Expand Down Expand Up @@ -132,7 +131,7 @@ export const NodeContextMenu = ({
closePopover={closePopover}
id={`${node.pathId}-popover`}
isOpen={isPopoverOpen}
button={children}
button={children!}
anchorPosition={popoverPosition}
>
<div style={{ maxWidth: 300 }} data-test-subj="nodeContextMenu">
Expand Down

0 comments on commit 29b4933

Please sign in to comment.