From a7c2db73c5cfc813d1b6647f506a46b419f52e04 Mon Sep 17 00:00:00 2001 From: Chris Cowan Date: Tue, 19 May 2020 12:06:07 -0700 Subject: [PATCH] [Metrics UI] Add sorting for name and value to Inventory View (#66644) * [Metrics UI] Add sorting for name and value to Inventory View * Fixing saved views * Fixing overlooked i18n translations * Fixing type issue * Fixing i18n paths Co-authored-by: Elastic Machine --- .../metrics_and_groupby_toolbar_items.tsx | 6 + .../common/saved_objects/inventory_view.ts | 10 ++ x-pack/plugins/infra/public/lib/lib.ts | 2 + .../inventory_view/components/layout.tsx | 2 + .../components/toolbars/toolbar.tsx | 6 +- .../components/toolbars/toolbar_wrapper.tsx | 6 + .../inventory_view/components/waffle/map.tsx | 4 +- .../waffle/metric_control/index.tsx | 5 +- .../waffle/waffle_accounts_controls.tsx | 5 +- .../waffle/waffle_group_by_controls.tsx | 5 +- .../waffle/waffle_inventory_switcher.tsx | 6 +- .../waffle/waffle_region_controls.tsx | 5 +- .../waffle/waffle_sort_controls.tsx | 125 ++++++++++++++++++ .../hooks/use_waffle_options.ts | 17 +++ .../hooks/use_waffle_view_state.ts | 3 + .../lib/create_uptime_link.test.ts | 1 + .../metrics/inventory_view/lib/sort_nodes.ts | 23 ++++ 17 files changed, 222 insertions(+), 9 deletions(-) create mode 100644 x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/waffle_sort_controls.tsx create mode 100644 x-pack/plugins/infra/public/pages/metrics/inventory_view/lib/sort_nodes.ts diff --git a/x-pack/plugins/infra/common/inventory_models/shared/components/metrics_and_groupby_toolbar_items.tsx b/x-pack/plugins/infra/common/inventory_models/shared/components/metrics_and_groupby_toolbar_items.tsx index bf37828ed0856..4a4accc6edfed 100644 --- a/x-pack/plugins/infra/common/inventory_models/shared/components/metrics_and_groupby_toolbar_items.tsx +++ b/x-pack/plugins/infra/common/inventory_models/shared/components/metrics_and_groupby_toolbar_items.tsx @@ -7,6 +7,7 @@ import React, { useMemo } from 'react'; import { EuiFlexItem } from '@elastic/eui'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { WaffleSortControls } from '../../../../public/pages/metrics/inventory_view/components/waffle/waffle_sort_controls'; import { ToolbarProps } from '../../../../public/pages/metrics/inventory_view/components/toolbars/toolbar'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { WaffleMetricControls } from '../../../../public/pages/metrics/inventory_view/components/waffle/metric_control'; @@ -58,6 +59,11 @@ export const MetricsAndGroupByToolbarItems = (props: Props) => { customOptions={props.customOptions} /> + {props.view === 'map' && ( + + + + )} ); }; diff --git a/x-pack/plugins/infra/common/saved_objects/inventory_view.ts b/x-pack/plugins/infra/common/saved_objects/inventory_view.ts index c14b75efc6887..15874f2eee348 100644 --- a/x-pack/plugins/infra/common/saved_objects/inventory_view.ts +++ b/x-pack/plugins/infra/common/saved_objects/inventory_view.ts @@ -21,6 +21,16 @@ export const inventoryViewSavedObjectType: SavedObjectsType = { name: { type: 'keyword', }, + sort: { + properties: { + by: { + type: 'keyword', + }, + direction: { + type: 'keyword', + }, + }, + }, metric: { properties: { type: { diff --git a/x-pack/plugins/infra/public/lib/lib.ts b/x-pack/plugins/infra/public/lib/lib.ts index 9043b4d9f6979..6bd4c10c881e3 100644 --- a/x-pack/plugins/infra/public/lib/lib.ts +++ b/x-pack/plugins/infra/public/lib/lib.ts @@ -18,6 +18,7 @@ import { SnapshotNodeMetric, SnapshotNodePath, } from '../../common/http_api/snapshot_api'; +import { WaffleSortOption } from '../pages/metrics/inventory_view/hooks/use_waffle_options'; export interface InfraFrontendLibs { apolloClient: InfraApolloClient; @@ -163,6 +164,7 @@ export interface InfraWaffleMapOptions { metric: SnapshotMetricInput; groupBy: SnapshotGroupBy; legend: InfraWaffleMapLegend; + sort: WaffleSortOption; } export interface InfraOptions { diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx index a71e43874b480..32f8e47fe9b7d 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx @@ -32,6 +32,7 @@ export const Layout = () => { const { metric, groupBy, + sort, nodeType, accountId, region, @@ -64,6 +65,7 @@ export const Layout = () => { ], } as InfraWaffleMapGradientLegend, metric, + sort, fields: source?.configuration?.fields, groupBy, }; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/toolbars/toolbar.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/toolbars/toolbar.tsx index e8485fb812586..6a68599aee38c 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/toolbars/toolbar.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/toolbars/toolbar.tsx @@ -19,17 +19,17 @@ import { ToolbarWrapper } from './toolbar_wrapper'; import { InfraGroupByOptions } from '../../../../../lib/lib'; import { IIndexPattern } from '../../../../../../../../../src/plugins/data/public'; import { InventoryItemType } from '../../../../../../common/inventory_models/types'; -import { WaffleOptionsState } from '../../hooks/use_waffle_options'; +import { WaffleOptionsState, WaffleSortOption } from '../../hooks/use_waffle_options'; import { useInventoryMeta } from '../../hooks/use_inventory_meta'; -export interface ToolbarProps - extends Omit { +export interface ToolbarProps extends Omit { createDerivedIndexPattern: (type: 'logs' | 'metrics' | 'both') => IIndexPattern; changeMetric: (payload: SnapshotMetricInput) => void; changeGroupBy: (payload: SnapshotGroupBy) => void; changeCustomOptions: (payload: InfraGroupByOptions[]) => void; changeAccount: (id: string) => void; changeRegion: (name: string) => void; + changeSort: (sort: WaffleSortOption) => void; accounts: InventoryCloudAccount[]; regions: string[]; changeCustomMetrics: (payload: SnapshotCustomMetricInput[]) => void; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/toolbars/toolbar_wrapper.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/toolbars/toolbar_wrapper.tsx index ea53122984161..3606bcc6944d1 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/toolbars/toolbar_wrapper.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/toolbars/toolbar_wrapper.tsx @@ -25,12 +25,15 @@ export const ToolbarWrapper = (props: Props) => { changeCustomOptions, changeAccount, changeRegion, + changeSort, customOptions, groupBy, metric, nodeType, accountId, + view, region, + sort, customMetrics, changeCustomMetrics, } = useWaffleOptionsContext(); @@ -47,8 +50,11 @@ export const ToolbarWrapper = (props: Props) => { changeAccount, changeRegion, changeCustomOptions, + changeSort, customOptions, groupBy, + sort, + view, metric, nodeType, region, diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/map.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/map.tsx index b1f816fb46d2e..eee8c69091ef0 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/map.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/map.tsx @@ -15,6 +15,7 @@ import { GroupOfNodes } from './group_of_nodes'; import { applyWaffleMapLayout } from '../../lib/apply_wafflemap_layout'; import { SnapshotNode } from '../../../../../../common/http_api/snapshot_api'; import { InventoryItemType } from '../../../../../../common/inventory_models/types'; +import { sortNodes } from '../../lib/sort_nodes'; interface Props { nodes: SnapshotNode[]; @@ -37,7 +38,8 @@ export const Map: React.FC = ({ nodeType, dataBounds, }) => { - const map = nodesToWaffleMap(nodes); + const sortedNodes = sortNodes(options.sort, nodes); + const map = nodesToWaffleMap(sortedNodes); return ( {({ measureRef, content: { width = 0, height = 0 } }) => { diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/metric_control/index.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/metric_control/index.tsx index f91e9a4034bc2..0cf1faec06c11 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/metric_control/index.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/metric_control/index.tsx @@ -132,7 +132,10 @@ export const WaffleMetricControls = ({ } const button = ( - + {currentLabel} ); diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/waffle_accounts_controls.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/waffle_accounts_controls.tsx index 3e4ff1de8291d..5ac6320510257 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/waffle_accounts_controls.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/waffle_accounts_controls.tsx @@ -58,7 +58,10 @@ export const WaffleAccountsControls = (props: Props) => { ); const button = ( - + {currentLabel ? currentLabel.name : i18n.translate('xpack.infra.waffle.accountAllTitle', { diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/waffle_group_by_controls.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/waffle_group_by_controls.tsx index c1f406f31e85e..cc09ce226b2fe 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/waffle_group_by_controls.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/waffle_group_by_controls.tsx @@ -130,7 +130,10 @@ export const WaffleGroupByControls = class extends React.PureComponent + {buttonBody} ); diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/waffle_inventory_switcher.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/waffle_inventory_switcher.tsx index e534c97eda090..c31b7e0a3e6ff 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/waffle_inventory_switcher.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/waffle_inventory_switcher.tsx @@ -7,6 +7,7 @@ import { EuiPopover, EuiContextMenu, EuiContextMenuPanelDescriptor } from '@elastic/eui'; import React, { useCallback, useState, useMemo } from 'react'; +import { i18n } from '@kbn/i18n'; import { findInventoryModel } from '../../../../../../common/inventory_models'; import { InventoryItemType } from '../../../../../../common/inventory_models/types'; import { useWaffleOptionsContext } from '../../hooks/use_waffle_options'; @@ -115,7 +116,10 @@ export const WaffleInventorySwitcher: React.FC = () => { }, [nodeType]); const button = ( - + {selectedText} ); diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/waffle_region_controls.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/waffle_region_controls.tsx index 9d759424cdc93..9c28f0798b519 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/waffle_region_controls.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/waffle_region_controls.tsx @@ -57,7 +57,10 @@ export const WaffleRegionControls = (props: Props) => { ); const button = ( - + {currentLabel || i18n.translate('xpack.infra.waffle.region', { defaultMessage: 'All', diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/waffle_sort_controls.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/waffle_sort_controls.tsx new file mode 100644 index 0000000000000..b5e6aacd0e6f4 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/waffle_sort_controls.tsx @@ -0,0 +1,125 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useCallback, useMemo, useState, ReactNode } from 'react'; +import { EuiSwitch, EuiContextMenuPanelDescriptor, EuiPopover, EuiContextMenu } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { EuiTheme, withTheme } from '../../../../../../../observability/public'; +import { WaffleSortOption } from '../../hooks/use_waffle_options'; +import { DropdownButton } from '../dropdown_button'; + +interface Props { + sort: WaffleSortOption; + onChange: (sort: WaffleSortOption) => void; +} + +const LABELS = { + name: i18n.translate('xpack.infra.waffle.sortNameLabel', { defaultMessage: 'Name' }), + value: i18n.translate('xpack.infra.waffle.sort.valueLabel', { defaultMessage: 'Metric value' }), +}; + +export const WaffleSortControls = ({ sort, onChange }: Props) => { + const [isOpen, setIsOpen] = useState(false); + + const showPopover = useCallback(() => { + setIsOpen(true); + }, [setIsOpen]); + + const closePopover = useCallback(() => { + setIsOpen(false); + }, [setIsOpen]); + + const label = LABELS[sort.by]; + + const button = ( + + {label} + + ); + + const selectName = useCallback(() => { + onChange({ ...sort, by: 'name' }); + closePopover(); + }, [closePopover, onChange, sort]); + + const selectValue = useCallback(() => { + onChange({ ...sort, by: 'value' }); + closePopover(); + }, [closePopover, onChange, sort]); + + const toggleSort = useCallback(() => { + onChange({ + ...sort, + direction: sort.direction === 'asc' ? 'desc' : 'asc', + }); + }, [sort, onChange]); + + const panels = useMemo( + () => [ + { + id: 0, + title: '', + items: [ + { + name: LABELS.name, + icon: sort.by === 'name' ? 'check' : 'empty', + onClick: selectName, + }, + { + name: LABELS.value, + icon: sort.by === 'value' ? 'check' : 'empty', + onClick: selectValue, + }, + ], + }, + ], + [sort.by, selectName, selectValue] + ); + + return ( + + + + + + + ); +}; + +interface SwitchContainerProps { + theme: EuiTheme; + children: ReactNode; +} + +const SwitchContainer = withTheme(({ children, theme }: SwitchContainerProps) => { + return ( +
+ {children} +
+ ); +}); diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_waffle_options.ts b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_waffle_options.ts index 32bfe6e085b4e..92f9ee4897f2d 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_waffle_options.ts +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_waffle_options.ts @@ -32,6 +32,7 @@ export const DEFAULT_WAFFLE_OPTIONS_STATE: WaffleOptionsState = { accountId: '', region: '', customMetrics: [], + sort: { by: 'name', direction: 'desc' }, }; export const useWaffleOptions = () => { @@ -99,7 +100,15 @@ export const useWaffleOptions = () => { [setState] ); + const changeSort = useCallback( + (sort: WaffleSortOption) => { + setState(previous => ({ ...previous, sort })); + }, + [setState] + ); + return { + ...DEFAULT_WAFFLE_OPTIONS_STATE, ...state, changeMetric, changeGroupBy, @@ -111,10 +120,16 @@ export const useWaffleOptions = () => { changeAccount, changeRegion, changeCustomMetrics, + changeSort, setWaffleOptionsState: setState, }; }; +export const WaffleSortOptionRT = rt.type({ + by: rt.keyof({ name: null, value: null }), + direction: rt.keyof({ asc: null, desc: null }), +}); + export const WaffleOptionsStateRT = rt.type({ metric: SnapshotMetricInputRT, groupBy: SnapshotGroupByRT, @@ -134,8 +149,10 @@ export const WaffleOptionsStateRT = rt.type({ accountId: rt.string, region: rt.string, customMetrics: rt.array(SnapshotCustomMetricInputRT), + sort: WaffleSortOptionRT, }); +export type WaffleSortOption = rt.TypeOf; export type WaffleOptionsState = rt.TypeOf; const encodeUrlState = (state: WaffleOptionsState) => { return WaffleOptionsStateRT.encode(state); diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_waffle_view_state.ts b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_waffle_view_state.ts index 869560b2b8709..306c30228596f 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_waffle_view_state.ts +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_waffle_view_state.ts @@ -28,6 +28,7 @@ export const useWaffleViewState = () => { autoBounds, accountId, region, + sort, setWaffleOptionsState, } = useWaffleOptionsContext(); const { currentTime, isAutoReloading, setWaffleTimeState } = useWaffleTimeContext(); @@ -35,6 +36,7 @@ export const useWaffleViewState = () => { const viewState: WaffleViewState = { metric, + sort, groupBy, nodeType, view, @@ -59,6 +61,7 @@ export const useWaffleViewState = () => { const onViewChange = useCallback( (newState: WaffleViewState) => { setWaffleOptionsState({ + sort: newState.sort, metric: newState.metric, groupBy: newState.groupBy, nodeType: newState.nodeType, diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/lib/create_uptime_link.test.ts b/x-pack/plugins/infra/public/pages/metrics/inventory_view/lib/create_uptime_link.test.ts index 5f760cf2f591e..fe9b2aebe27f7 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/lib/create_uptime_link.test.ts +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/lib/create_uptime_link.test.ts @@ -21,6 +21,7 @@ const options: InfraWaffleMapOptions = { formatTemplate: '{{value}}', metric: { type: 'cpu' }, groupBy: [], + sort: { by: 'name', direction: 'asc' }, legend: { type: 'gradient', rules: [], diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/lib/sort_nodes.ts b/x-pack/plugins/infra/public/pages/metrics/inventory_view/lib/sort_nodes.ts new file mode 100644 index 0000000000000..e676fb3e09c2b --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/lib/sort_nodes.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { sortBy, last } from 'lodash'; +import { SnapshotNode } from '../../../../../common/http_api/snapshot_api'; +import { WaffleSortOption } from '../hooks/use_waffle_options'; + +const SORT_PATHS = { + name: (node: SnapshotNode) => last(node.path), + value: 'metric.value', +}; + +export const sortNodes = (sort: WaffleSortOption, nodes: SnapshotNode[]) => { + const sortPath = SORT_PATHS[sort.by]; + const sortedNodes = sortBy(nodes, sortPath); + if (sort.direction === 'desc') { + return sortedNodes.reverse(); + } + return sortedNodes; +};