Skip to content

Commit

Permalink
[Metrics UI] Add sorting for name and value to Inventory View (#66644)
Browse files Browse the repository at this point in the history
* [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 <[email protected]>
  • Loading branch information
simianhacker and elasticmachine authored May 19, 2020
1 parent 89402ac commit a7c2db7
Show file tree
Hide file tree
Showing 17 changed files with 222 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -58,6 +59,11 @@ export const MetricsAndGroupByToolbarItems = (props: Props) => {
customOptions={props.customOptions}
/>
</EuiFlexItem>
{props.view === 'map' && (
<EuiFlexItem grow={false}>
<WaffleSortControls sort={props.sort} onChange={props.changeSort} />
</EuiFlexItem>
)}
</>
);
};
10 changes: 10 additions & 0 deletions x-pack/plugins/infra/common/saved_objects/inventory_view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,16 @@ export const inventoryViewSavedObjectType: SavedObjectsType = {
name: {
type: 'keyword',
},
sort: {
properties: {
by: {
type: 'keyword',
},
direction: {
type: 'keyword',
},
},
},
metric: {
properties: {
type: {
Expand Down
2 changes: 2 additions & 0 deletions x-pack/plugins/infra/public/lib/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -163,6 +164,7 @@ export interface InfraWaffleMapOptions {
metric: SnapshotMetricInput;
groupBy: SnapshotGroupBy;
legend: InfraWaffleMapLegend;
sort: WaffleSortOption;
}

export interface InfraOptions {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export const Layout = () => {
const {
metric,
groupBy,
sort,
nodeType,
accountId,
region,
Expand Down Expand Up @@ -64,6 +65,7 @@ export const Layout = () => {
],
} as InfraWaffleMapGradientLegend,
metric,
sort,
fields: source?.configuration?.fields,
groupBy,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<WaffleOptionsState, 'view' | 'boundsOverride' | 'autoBounds'> {
export interface ToolbarProps extends Omit<WaffleOptionsState, 'boundsOverride' | 'autoBounds'> {
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,15 @@ export const ToolbarWrapper = (props: Props) => {
changeCustomOptions,
changeAccount,
changeRegion,
changeSort,
customOptions,
groupBy,
metric,
nodeType,
accountId,
view,
region,
sort,
customMetrics,
changeCustomMetrics,
} = useWaffleOptionsContext();
Expand All @@ -47,8 +50,11 @@ export const ToolbarWrapper = (props: Props) => {
changeAccount,
changeRegion,
changeCustomOptions,
changeSort,
customOptions,
groupBy,
sort,
view,
metric,
nodeType,
region,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
Expand All @@ -37,7 +38,8 @@ export const Map: React.FC<Props> = ({
nodeType,
dataBounds,
}) => {
const map = nodesToWaffleMap(nodes);
const sortedNodes = sortNodes(options.sort, nodes);
const map = nodesToWaffleMap(sortedNodes);
return (
<AutoSizer content>
{({ measureRef, content: { width = 0, height = 0 } }) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,10 @@ export const WaffleMetricControls = ({
}

const button = (
<DropdownButton onClick={handleToggle} label="Metric">
<DropdownButton
onClick={handleToggle}
label={i18n.translate('xpack.infra.waffle.metriclabel', { defaultMessage: 'Metric' })}
>
{currentLabel}
</DropdownButton>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@ export const WaffleAccountsControls = (props: Props) => {
);

const button = (
<DropdownButton label="Account" onClick={showPopover}>
<DropdownButton
label={i18n.translate('xpack.infra.waffle.accountLabel', { defaultMessage: 'Account' })}
onClick={showPopover}
>
{currentLabel
? currentLabel.name
: i18n.translate('xpack.infra.waffle.accountAllTitle', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,10 @@ export const WaffleGroupByControls = class extends React.PureComponent<Props, St
);

const button = (
<DropdownButton label="Group By" onClick={this.handleToggle}>
<DropdownButton
label={i18n.translate('xpack.infra.waffle.groupByLabel', { defaultMessage: 'Group by' })}
onClick={this.handleToggle}
>
{buttonBody}
</DropdownButton>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -115,7 +116,10 @@ export const WaffleInventorySwitcher: React.FC = () => {
}, [nodeType]);

const button = (
<DropdownButton onClick={openPopover} label="Show">
<DropdownButton
onClick={openPopover}
label={i18n.translate('xpack.infra.waffle.showLabel', { defaultMessage: 'Show' })}
>
{selectedText}
</DropdownButton>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,10 @@ export const WaffleRegionControls = (props: Props) => {
);

const button = (
<DropdownButton onClick={showPopover} label="Region">
<DropdownButton
onClick={showPopover}
label={i18n.translate('xpack.infra.waffle.regionLabel', { defaultMessage: 'Region' })}
>
{currentLabel ||
i18n.translate('xpack.infra.waffle.region', {
defaultMessage: 'All',
Expand Down
Original file line number Diff line number Diff line change
@@ -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<boolean>(false);

const showPopover = useCallback(() => {
setIsOpen(true);
}, [setIsOpen]);

const closePopover = useCallback(() => {
setIsOpen(false);
}, [setIsOpen]);

const label = LABELS[sort.by];

const button = (
<DropdownButton
label={i18n.translate('xpack.infra.waffle.sortLabel', { defaultMessage: 'Sort by' })}
onClick={showPopover}
>
{label}
</DropdownButton>
);

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<EuiContextMenuPanelDescriptor[]>(
() => [
{
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 (
<EuiPopover
isOpen={isOpen}
id="sortPopover"
button={button}
anchorPosition="downLeft"
panelPaddingSize="none"
closePopover={closePopover}
>
<EuiContextMenu initialPanelId={0} panels={panels} />
<SwitchContainer>
<EuiSwitch
compressed
label={i18n.translate('xpack.infra.waffle.sortDirectionLabel', {
defaultMessage: 'Reverse direction',
})}
checked={sort.direction === 'desc'}
onChange={toggleSort}
/>
</SwitchContainer>
</EuiPopover>
);
};

interface SwitchContainerProps {
theme: EuiTheme;
children: ReactNode;
}

const SwitchContainer = withTheme(({ children, theme }: SwitchContainerProps) => {
return (
<div
style={{
padding: theme.eui.paddingSizes.m,
borderTop: `1px solid ${theme.eui.euiBorderColor}`,
}}
>
{children}
</div>
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export const DEFAULT_WAFFLE_OPTIONS_STATE: WaffleOptionsState = {
accountId: '',
region: '',
customMetrics: [],
sort: { by: 'name', direction: 'desc' },
};

export const useWaffleOptions = () => {
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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<typeof WaffleSortOptionRT>;
export type WaffleOptionsState = rt.TypeOf<typeof WaffleOptionsStateRT>;
const encodeUrlState = (state: WaffleOptionsState) => {
return WaffleOptionsStateRT.encode(state);
Expand Down
Loading

0 comments on commit a7c2db7

Please sign in to comment.