Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Metrics UI] Add sorting for name and value to Inventory View #66644

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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