[Metrics UI] Design Refresh: Inventory View, Episode 1 (#64026)
* [Metrics UI] Design Refresh: Inventory View, Episode 1 * Removing unused i18n labels * Removing obsolete code removed in previous PR Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
e3ee02c687
commit
f37185fd8f
19
x-pack/plugins/infra/public/components/toolbar_panel.ts
Normal file
19
x-pack/plugins/infra/public/components/toolbar_panel.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* 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 { EuiPanel } from '@elastic/eui';
|
||||
import { euiStyled } from '../../../observability/public';
|
||||
|
||||
export const ToolbarPanel = euiStyled(EuiPanel).attrs(() => ({
|
||||
grow: false,
|
||||
paddingSize: 'none',
|
||||
}))`
|
||||
border-top: none;
|
||||
border-right: none;
|
||||
border-left: none;
|
||||
border-radius: 0;
|
||||
padding: ${props => `12px ${props.theme.eui.paddingSizes.m}`};
|
||||
`;
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* 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 { EuiFlexGroup, EuiFlexItem, EuiButtonEmpty } from '@elastic/eui';
|
||||
import React, { ReactNode } from 'react';
|
||||
import { withTheme, EuiTheme } from '../../../../../../observability/public';
|
||||
|
||||
interface Props {
|
||||
label: string;
|
||||
onClick: () => void;
|
||||
theme: EuiTheme;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const DropdownButton = withTheme(({ onClick, label, theme, children }: Props) => {
|
||||
return (
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
gutterSize="none"
|
||||
style={{
|
||||
border: theme.eui.euiFormInputGroupBorder,
|
||||
boxShadow: `0px 3px 2px ${theme.eui.euiTableActionsBorderColor}, 0px 1px 1px ${theme.eui.euiTableActionsBorderColor}`,
|
||||
}}
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
style={{
|
||||
padding: 12,
|
||||
background: theme.eui.euiFormInputGroupLabelBackground,
|
||||
fontSize: '0.75em',
|
||||
fontWeight: 600,
|
||||
color: theme.eui.euiTitleColor,
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
color="text"
|
||||
iconType="arrowDown"
|
||||
onClick={onClick}
|
||||
iconSide="right"
|
||||
size="xs"
|
||||
>
|
||||
{children}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
});
|
|
@ -7,17 +7,13 @@
|
|||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
|
||||
import { Toolbar } from '../../../components/eui/toolbar';
|
||||
import { WaffleTimeControls } from './components/waffle/waffle_time_controls';
|
||||
import { WaffleInventorySwitcher } from './components/waffle/waffle_inventory_switcher';
|
||||
import { SearchBar } from './components/search_bar';
|
||||
import { WaffleTimeControls } from './waffle/waffle_time_controls';
|
||||
import { SearchBar } from './search_bar';
|
||||
import { ToolbarPanel } from '../../../../components/toolbar_panel';
|
||||
|
||||
export const SnapshotToolbar = () => (
|
||||
<Toolbar>
|
||||
export const FilterBar = () => (
|
||||
<ToolbarPanel>
|
||||
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween" gutterSize="m">
|
||||
<EuiFlexItem grow={false}>
|
||||
<WaffleInventorySwitcher />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<SearchBar />
|
||||
</EuiFlexItem>
|
||||
|
@ -25,5 +21,5 @@ export const SnapshotToolbar = () => (
|
|||
<WaffleTimeControls />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</Toolbar>
|
||||
</ToolbarPanel>
|
||||
);
|
|
@ -4,20 +4,26 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useInterval } from 'react-use';
|
||||
|
||||
import { euiPaletteColorBlind } from '@elastic/eui';
|
||||
import { NodesOverview } from './nodes_overview';
|
||||
import { Toolbar } from './toolbars/toolbar';
|
||||
import { euiPaletteColorBlind, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { convertIntervalToString } from '../../../../utils/convert_interval_to_string';
|
||||
import { NodesOverview, calculateBoundsFromNodes } from './nodes_overview';
|
||||
import { PageContent } from '../../../../components/page';
|
||||
import { useSnapshot } from '../hooks/use_snaphot';
|
||||
import { useInventoryMeta } from '../hooks/use_inventory_meta';
|
||||
import { useWaffleTimeContext } from '../hooks/use_waffle_time';
|
||||
import { useWaffleFiltersContext } from '../hooks/use_waffle_filters';
|
||||
import { useWaffleOptionsContext } from '../hooks/use_waffle_options';
|
||||
import { useSourceContext } from '../../../../containers/source';
|
||||
import { InfraFormatterType, InfraWaffleMapGradientLegend } from '../../../../lib/lib';
|
||||
import { euiStyled } from '../../../../../../observability/public';
|
||||
import { Toolbar } from './toolbars/toolbar';
|
||||
import { ViewSwitcher } from './waffle/view_switcher';
|
||||
import { SavedViews } from './saved_views';
|
||||
import { IntervalLabel } from './waffle/interval_label';
|
||||
import { Legend } from './waffle/legend';
|
||||
import { createInventoryMetricFormatter } from '../lib/create_inventory_metric_formatter';
|
||||
|
||||
const euiVisColorPalette = euiPaletteColorBlind();
|
||||
|
||||
|
@ -34,7 +40,6 @@ export const Layout = () => {
|
|||
autoBounds,
|
||||
boundsOverride,
|
||||
} = useWaffleOptionsContext();
|
||||
const { accounts, regions } = useInventoryMeta(sourceId, nodeType);
|
||||
const { currentTime, jumpToTime, isAutoReloading } = useWaffleTimeContext();
|
||||
const { filterQueryAsJson, applyFilterQuery } = useWaffleFiltersContext();
|
||||
const { loading, nodes, reload, interval } = useSnapshot(
|
||||
|
@ -72,25 +77,75 @@ export const Layout = () => {
|
|||
isAutoReloading ? 5000 : null
|
||||
);
|
||||
|
||||
const intervalAsString = convertIntervalToString(interval);
|
||||
const dataBounds = calculateBoundsFromNodes(nodes);
|
||||
const bounds = autoBounds ? dataBounds : boundsOverride;
|
||||
const formatter = useCallback(createInventoryMetricFormatter(options.metric), [options.metric]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Toolbar accounts={accounts} regions={regions} nodeType={nodeType} />
|
||||
<PageContent>
|
||||
<NodesOverview
|
||||
nodes={nodes}
|
||||
options={options}
|
||||
nodeType={nodeType}
|
||||
loading={loading}
|
||||
reload={reload}
|
||||
onDrilldown={applyFilterQuery}
|
||||
currentTime={currentTime}
|
||||
onViewChange={changeView}
|
||||
view={view}
|
||||
autoBounds={autoBounds}
|
||||
boundsOverride={boundsOverride}
|
||||
interval={interval}
|
||||
/>
|
||||
<MainContainer>
|
||||
<TopActionContainer>
|
||||
<EuiFlexGroup justifyContent="spaceBetween" gutterSize="m">
|
||||
<Toolbar nodeType={nodeType} />
|
||||
<EuiFlexItem grow={false}>
|
||||
<ViewSwitcher view={view} onChange={changeView} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</TopActionContainer>
|
||||
<NodesOverview
|
||||
nodes={nodes}
|
||||
options={options}
|
||||
nodeType={nodeType}
|
||||
loading={loading}
|
||||
reload={reload}
|
||||
onDrilldown={applyFilterQuery}
|
||||
currentTime={currentTime}
|
||||
view={view}
|
||||
autoBounds={autoBounds}
|
||||
boundsOverride={boundsOverride}
|
||||
formatter={formatter}
|
||||
/>
|
||||
<BottomActionContainer>
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<SavedViews />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false} style={{ position: 'relative', minWidth: 400 }}>
|
||||
<Legend
|
||||
formatter={formatter}
|
||||
bounds={bounds}
|
||||
dataBounds={dataBounds}
|
||||
legend={options.legend}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<IntervalLabel intervalAsString={intervalAsString} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</BottomActionContainer>
|
||||
</MainContainer>
|
||||
</PageContent>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const MainContainer = euiStyled.div`
|
||||
position: relative;
|
||||
flex: 1 1 auto;
|
||||
`;
|
||||
|
||||
const TopActionContainer = euiStyled.div`
|
||||
padding: ${props => `12px ${props.theme.eui.paddingSizes.m}`};
|
||||
`;
|
||||
|
||||
const BottomActionContainer = euiStyled.div`
|
||||
background-color: ${props => props.theme.eui.euiPageBackgroundColor};
|
||||
padding: ${props => props.theme.eui.paddingSizes.m} ${props =>
|
||||
props.theme.eui.paddingSizes.m} ${props => props.theme.eui.paddingSizes.s};
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 4px;
|
||||
right: 0;
|
||||
`;
|
||||
|
|
|
@ -4,31 +4,18 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { get, max, min } from 'lodash';
|
||||
import React from 'react';
|
||||
import { max, min } from 'lodash';
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
import { InventoryItemType } from '../../../../../common/inventory_models/types';
|
||||
import { euiStyled } from '../../../../../../observability/public';
|
||||
import {
|
||||
InfraFormatterType,
|
||||
InfraWaffleMapBounds,
|
||||
InfraWaffleMapOptions,
|
||||
} from '../../../../lib/lib';
|
||||
import { createFormatter } from '../../../../utils/formatters';
|
||||
import { InfraWaffleMapBounds, InfraWaffleMapOptions, InfraFormatter } from '../../../../lib/lib';
|
||||
import { NoData } from '../../../../components/empty_states';
|
||||
import { InfraLoadingPanel } from '../../../../components/loading';
|
||||
import { Map } from './waffle/map';
|
||||
import { ViewSwitcher } from './waffle/view_switcher';
|
||||
import { TableView } from './table_view';
|
||||
import {
|
||||
SnapshotNode,
|
||||
SnapshotCustomMetricInputRT,
|
||||
} from '../../../../../common/http_api/snapshot_api';
|
||||
import { convertIntervalToString } from '../../../../utils/convert_interval_to_string';
|
||||
import { InventoryItemType } from '../../../../../common/inventory_models/types';
|
||||
import { createFormatterForMetric } from '../../metrics_explorer/components/helpers/create_formatter_for_metric';
|
||||
import { SnapshotNode } from '../../../../../common/http_api/snapshot_api';
|
||||
|
||||
export interface KueryFilterQuery {
|
||||
kind: 'kuery';
|
||||
|
@ -43,74 +30,13 @@ interface Props {
|
|||
reload: () => void;
|
||||
onDrilldown: (filter: KueryFilterQuery) => void;
|
||||
currentTime: number;
|
||||
onViewChange: (view: string) => void;
|
||||
view: string;
|
||||
boundsOverride: InfraWaffleMapBounds;
|
||||
autoBounds: boolean;
|
||||
interval: string;
|
||||
formatter: InfraFormatter;
|
||||
}
|
||||
|
||||
interface MetricFormatter {
|
||||
formatter: InfraFormatterType;
|
||||
template: string;
|
||||
bounds?: { min: number; max: number };
|
||||
}
|
||||
|
||||
interface MetricFormatters {
|
||||
[key: string]: MetricFormatter;
|
||||
}
|
||||
|
||||
const METRIC_FORMATTERS: MetricFormatters = {
|
||||
['count']: { formatter: InfraFormatterType.number, template: '{{value}}' },
|
||||
['cpu']: {
|
||||
formatter: InfraFormatterType.percent,
|
||||
template: '{{value}}',
|
||||
},
|
||||
['memory']: {
|
||||
formatter: InfraFormatterType.percent,
|
||||
template: '{{value}}',
|
||||
},
|
||||
['rx']: { formatter: InfraFormatterType.bits, template: '{{value}}/s' },
|
||||
['tx']: { formatter: InfraFormatterType.bits, template: '{{value}}/s' },
|
||||
['logRate']: {
|
||||
formatter: InfraFormatterType.abbreviatedNumber,
|
||||
template: '{{value}}/s',
|
||||
},
|
||||
['diskIOReadBytes']: {
|
||||
formatter: InfraFormatterType.bytes,
|
||||
template: '{{value}}/s',
|
||||
},
|
||||
['diskIOWriteBytes']: {
|
||||
formatter: InfraFormatterType.bytes,
|
||||
template: '{{value}}/s',
|
||||
},
|
||||
['s3BucketSize']: {
|
||||
formatter: InfraFormatterType.bytes,
|
||||
template: '{{value}}',
|
||||
},
|
||||
['s3TotalRequests']: {
|
||||
formatter: InfraFormatterType.abbreviatedNumber,
|
||||
template: '{{value}}',
|
||||
},
|
||||
['s3NumberOfObjects']: {
|
||||
formatter: InfraFormatterType.abbreviatedNumber,
|
||||
template: '{{value}}',
|
||||
},
|
||||
['s3UploadBytes']: {
|
||||
formatter: InfraFormatterType.bytes,
|
||||
template: '{{value}}',
|
||||
},
|
||||
['s3DownloadBytes']: {
|
||||
formatter: InfraFormatterType.bytes,
|
||||
template: '{{value}}',
|
||||
},
|
||||
['sqsOldestMessage']: {
|
||||
formatter: InfraFormatterType.number,
|
||||
template: '{{value}} seconds',
|
||||
},
|
||||
};
|
||||
|
||||
const calculateBoundsFromNodes = (nodes: SnapshotNode[]): InfraWaffleMapBounds => {
|
||||
export const calculateBoundsFromNodes = (nodes: SnapshotNode[]): InfraWaffleMapBounds => {
|
||||
const maxValues = nodes.map(node => node.metric.max);
|
||||
const minValues = nodes.map(node => node.metric.value);
|
||||
// if there is only one value then we need to set the bottom range to zero for min
|
||||
|
@ -122,141 +48,97 @@ const calculateBoundsFromNodes = (nodes: SnapshotNode[]): InfraWaffleMapBounds =
|
|||
return { min: min(minValues) || 0, max: max(maxValues) || 0 };
|
||||
};
|
||||
|
||||
export const NodesOverview = class extends React.Component<Props, {}> {
|
||||
public static displayName = 'Waffle';
|
||||
public render() {
|
||||
const {
|
||||
autoBounds,
|
||||
boundsOverride,
|
||||
loading,
|
||||
nodes,
|
||||
nodeType,
|
||||
reload,
|
||||
view,
|
||||
currentTime,
|
||||
options,
|
||||
interval,
|
||||
} = this.props;
|
||||
if (loading) {
|
||||
return (
|
||||
<InfraLoadingPanel
|
||||
height="100%"
|
||||
width="100%"
|
||||
text={i18n.translate('xpack.infra.waffle.loadingDataText', {
|
||||
defaultMessage: 'Loading data',
|
||||
})}
|
||||
/>
|
||||
);
|
||||
} else if (!loading && nodes && nodes.length === 0) {
|
||||
return (
|
||||
<NoData
|
||||
titleText={i18n.translate('xpack.infra.waffle.noDataTitle', {
|
||||
defaultMessage: 'There is no data to display.',
|
||||
})}
|
||||
bodyText={i18n.translate('xpack.infra.waffle.noDataDescription', {
|
||||
defaultMessage: 'Try adjusting your time or filter.',
|
||||
})}
|
||||
refetchText={i18n.translate('xpack.infra.waffle.checkNewDataButtonLabel', {
|
||||
defaultMessage: 'Check for new data',
|
||||
})}
|
||||
onRefetch={() => {
|
||||
reload();
|
||||
}}
|
||||
testString="noMetricsDataPrompt"
|
||||
/>
|
||||
);
|
||||
}
|
||||
const dataBounds = calculateBoundsFromNodes(nodes);
|
||||
const bounds = autoBounds ? dataBounds : boundsOverride;
|
||||
const intervalAsString = convertIntervalToString(interval);
|
||||
export const NodesOverview = ({
|
||||
autoBounds,
|
||||
boundsOverride,
|
||||
loading,
|
||||
nodes,
|
||||
nodeType,
|
||||
reload,
|
||||
view,
|
||||
currentTime,
|
||||
options,
|
||||
formatter,
|
||||
onDrilldown,
|
||||
}: Props) => {
|
||||
const handleDrilldown = useCallback(
|
||||
(filter: string) => {
|
||||
onDrilldown({
|
||||
kind: 'kuery',
|
||||
expression: filter,
|
||||
});
|
||||
return;
|
||||
},
|
||||
[onDrilldown]
|
||||
);
|
||||
|
||||
const noData = !loading && nodes && nodes.length === 0;
|
||||
if (loading) {
|
||||
return (
|
||||
<MainContainer>
|
||||
<ViewSwitcherContainer>
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<ViewSwitcher view={view} onChange={this.handleViewChange} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText color="subdued">
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.homePage.toolbar.showingLastOneMinuteDataText"
|
||||
defaultMessage="Showing the last {duration} of data at the selected time"
|
||||
values={{ duration: intervalAsString }}
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</ViewSwitcherContainer>
|
||||
{view === 'table' ? (
|
||||
<TableContainer>
|
||||
<TableView
|
||||
nodeType={nodeType}
|
||||
nodes={nodes}
|
||||
options={options}
|
||||
formatter={this.formatter}
|
||||
currentTime={currentTime}
|
||||
onFilter={this.handleDrilldown}
|
||||
/>
|
||||
</TableContainer>
|
||||
) : (
|
||||
<MapContainer>
|
||||
<Map
|
||||
nodeType={nodeType}
|
||||
nodes={nodes}
|
||||
options={options}
|
||||
formatter={this.formatter}
|
||||
currentTime={currentTime}
|
||||
onFilter={this.handleDrilldown}
|
||||
bounds={bounds}
|
||||
dataBounds={dataBounds}
|
||||
/>
|
||||
</MapContainer>
|
||||
)}
|
||||
</MainContainer>
|
||||
<InfraLoadingPanel
|
||||
height="100%"
|
||||
width="100%"
|
||||
text={i18n.translate('xpack.infra.waffle.loadingDataText', {
|
||||
defaultMessage: 'Loading data',
|
||||
})}
|
||||
/>
|
||||
);
|
||||
} else if (noData) {
|
||||
return (
|
||||
<NoData
|
||||
titleText={i18n.translate('xpack.infra.waffle.noDataTitle', {
|
||||
defaultMessage: 'There is no data to display.',
|
||||
})}
|
||||
bodyText={i18n.translate('xpack.infra.waffle.noDataDescription', {
|
||||
defaultMessage: 'Try adjusting your time or filter.',
|
||||
})}
|
||||
refetchText={i18n.translate('xpack.infra.waffle.checkNewDataButtonLabel', {
|
||||
defaultMessage: 'Check for new data',
|
||||
})}
|
||||
onRefetch={() => {
|
||||
reload();
|
||||
}}
|
||||
testString="noMetricsDataPrompt"
|
||||
/>
|
||||
);
|
||||
}
|
||||
const dataBounds = calculateBoundsFromNodes(nodes);
|
||||
const bounds = autoBounds ? dataBounds : boundsOverride;
|
||||
|
||||
private handleViewChange = (view: string) => this.props.onViewChange(view);
|
||||
|
||||
// TODO: Change this to a real implimentation using the tickFormatter from the prototype as an example.
|
||||
private formatter = (val: string | number) => {
|
||||
const { metric } = this.props.options;
|
||||
if (SnapshotCustomMetricInputRT.is(metric)) {
|
||||
const formatter = createFormatterForMetric(metric);
|
||||
return formatter(val);
|
||||
}
|
||||
const metricFormatter = get(METRIC_FORMATTERS, metric.type, METRIC_FORMATTERS.count);
|
||||
if (val == null) {
|
||||
return '';
|
||||
}
|
||||
const formatter = createFormatter(metricFormatter.formatter, metricFormatter.template);
|
||||
return formatter(val);
|
||||
};
|
||||
|
||||
private handleDrilldown = (filter: string) => {
|
||||
this.props.onDrilldown({
|
||||
kind: 'kuery',
|
||||
expression: filter,
|
||||
});
|
||||
return;
|
||||
};
|
||||
if (view === 'table') {
|
||||
return (
|
||||
<TableContainer>
|
||||
<TableView
|
||||
nodeType={nodeType}
|
||||
nodes={nodes}
|
||||
options={options}
|
||||
formatter={formatter}
|
||||
currentTime={currentTime}
|
||||
onFilter={handleDrilldown}
|
||||
/>
|
||||
</TableContainer>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<MapContainer>
|
||||
<Map
|
||||
nodeType={nodeType}
|
||||
nodes={nodes}
|
||||
options={options}
|
||||
formatter={formatter}
|
||||
currentTime={currentTime}
|
||||
onFilter={handleDrilldown}
|
||||
bounds={bounds}
|
||||
dataBounds={dataBounds}
|
||||
/>
|
||||
</MapContainer>
|
||||
);
|
||||
};
|
||||
|
||||
const MainContainer = euiStyled.div`
|
||||
position: relative;
|
||||
flex: 1 1 auto;
|
||||
`;
|
||||
|
||||
const TableContainer = euiStyled.div`
|
||||
padding: ${props => props.theme.eui.paddingSizes.l};
|
||||
`;
|
||||
|
||||
const ViewSwitcherContainer = euiStyled.div`
|
||||
padding: ${props => props.theme.eui.paddingSizes.l};
|
||||
`;
|
||||
|
||||
const MapContainer = euiStyled.div`
|
||||
position: absolute;
|
||||
display: flex;
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { SavedViewsToolbarControls } from '../../../../../components/saved_views/toolbar_control';
|
||||
import { inventoryViewSavedObjectType } from '../../../../../../common/saved_objects/inventory_view';
|
||||
import { useWaffleViewState } from '../../hooks/use_waffle_view_state';
|
||||
import { SavedViewsToolbarControls } from '../../../../components/saved_views/toolbar_control';
|
||||
import { inventoryViewSavedObjectType } from '../../../../../common/saved_objects/inventory_view';
|
||||
import { useWaffleViewState } from '../hooks/use_waffle_view_state';
|
||||
|
||||
export const SavedViews = () => {
|
||||
const { viewState, defaultViewState, onViewChange } = useWaffleViewState();
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import { EuiFlexItem } from '@elastic/eui';
|
||||
import { useSourceContext } from '../../../../../containers/source';
|
||||
import {
|
||||
SnapshotMetricInput,
|
||||
SnapshotGroupBy,
|
||||
|
@ -19,7 +20,7 @@ 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 { SavedViews } from './save_views';
|
||||
import { useInventoryMeta } from '../../hooks/use_inventory_meta';
|
||||
|
||||
export interface ToolbarProps
|
||||
extends Omit<WaffleOptionsState, 'view' | 'boundsOverride' | 'autoBounds'> {
|
||||
|
@ -45,9 +46,6 @@ const wrapToolbarItems = (
|
|||
<>
|
||||
<ToolbarItems {...props} accounts={accounts} regions={regions} />
|
||||
<EuiFlexItem grow={true} />
|
||||
<EuiFlexItem grow={false}>
|
||||
<SavedViews />
|
||||
</EuiFlexItem>
|
||||
</>
|
||||
)}
|
||||
</ToolbarWrapper>
|
||||
|
@ -56,10 +54,11 @@ const wrapToolbarItems = (
|
|||
|
||||
interface Props {
|
||||
nodeType: InventoryItemType;
|
||||
regions: string[];
|
||||
accounts: InventoryCloudAccount[];
|
||||
}
|
||||
export const Toolbar = ({ nodeType, accounts, regions }: Props) => {
|
||||
|
||||
export const Toolbar = ({ nodeType }: Props) => {
|
||||
const { sourceId } = useSourceContext();
|
||||
const { accounts, regions } = useInventoryMeta(sourceId, nodeType);
|
||||
const ToolbarItems = findToolbar(nodeType);
|
||||
return wrapToolbarItems(ToolbarItems, accounts, regions);
|
||||
};
|
||||
|
|
|
@ -5,14 +5,14 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiFlexGroup } from '@elastic/eui';
|
||||
import { EuiFlexItem } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { SnapshotMetricType } from '../../../../../../common/inventory_models/types';
|
||||
import { Toolbar } from '../../../../../components/eui/toolbar';
|
||||
import { ToolbarProps } from './toolbar';
|
||||
import { fieldToName } from '../../lib/field_to_display_name';
|
||||
import { useSourceContext } from '../../../../../containers/source';
|
||||
import { useWaffleOptionsContext } from '../../hooks/use_waffle_options';
|
||||
import { WaffleInventorySwitcher } from '../waffle/waffle_inventory_switcher';
|
||||
import { ToolbarProps } from './toolbar';
|
||||
|
||||
interface Props {
|
||||
children: (props: Omit<ToolbarProps, 'accounts' | 'regions'>) => React.ReactElement;
|
||||
|
@ -36,26 +36,27 @@ export const ToolbarWrapper = (props: Props) => {
|
|||
} = useWaffleOptionsContext();
|
||||
const { createDerivedIndexPattern } = useSourceContext();
|
||||
return (
|
||||
<Toolbar>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="m">
|
||||
{props.children({
|
||||
createDerivedIndexPattern,
|
||||
changeMetric,
|
||||
changeGroupBy,
|
||||
changeAccount,
|
||||
changeRegion,
|
||||
changeCustomOptions,
|
||||
customOptions,
|
||||
groupBy,
|
||||
metric,
|
||||
nodeType,
|
||||
region,
|
||||
accountId,
|
||||
customMetrics,
|
||||
changeCustomMetrics,
|
||||
})}
|
||||
</EuiFlexGroup>
|
||||
</Toolbar>
|
||||
<>
|
||||
<EuiFlexItem grow={false}>
|
||||
<WaffleInventorySwitcher />
|
||||
</EuiFlexItem>
|
||||
{props.children({
|
||||
createDerivedIndexPattern,
|
||||
changeMetric,
|
||||
changeGroupBy,
|
||||
changeAccount,
|
||||
changeRegion,
|
||||
changeCustomOptions,
|
||||
customOptions,
|
||||
groupBy,
|
||||
metric,
|
||||
nodeType,
|
||||
region,
|
||||
accountId,
|
||||
customMetrics,
|
||||
changeCustomMetrics,
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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 from 'react';
|
||||
import { EuiText } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
interface Props {
|
||||
intervalAsString?: string;
|
||||
}
|
||||
|
||||
export const IntervalLabel = ({ intervalAsString }: Props) => {
|
||||
if (!intervalAsString) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiText color="subdued" size="s">
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.homePage.toolbar.showingLastOneMinuteDataText"
|
||||
defaultMessage="Last {duration} of data for the selected time"
|
||||
values={{ duration: intervalAsString }}
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
);
|
||||
};
|
|
@ -53,7 +53,7 @@ export const Legend: React.FC<Props> = ({ dataBounds, legend, bounds, formatter
|
|||
|
||||
const LegendContainer = euiStyled.div`
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
bottom: 0px;
|
||||
left: 10px;
|
||||
right: 10px;
|
||||
`;
|
||||
|
|
|
@ -40,7 +40,7 @@ export const LegendControls = ({ autoBounds, boundsOverride, onChange, dataBound
|
|||
const [draftBounds, setDraftBounds] = useState(autoBounds ? dataBounds : boundsOverride); // should come from bounds prop
|
||||
const buttonComponent = (
|
||||
<EuiButtonIcon
|
||||
iconType="gear"
|
||||
iconType="controlsHorizontal"
|
||||
color="text"
|
||||
aria-label={i18n.translate('xpack.infra.legendControls.buttonLabel', {
|
||||
defaultMessage: 'configure legend',
|
||||
|
|
|
@ -12,7 +12,6 @@ import { InfraWaffleMapBounds, InfraWaffleMapOptions } from '../../../../../lib/
|
|||
import { AutoSizer } from '../../../../../components/auto_sizer';
|
||||
import { GroupOfGroups } from './group_of_groups';
|
||||
import { GroupOfNodes } from './group_of_nodes';
|
||||
import { Legend } from './legend';
|
||||
import { applyWaffleMapLayout } from '../../lib/apply_wafflemap_layout';
|
||||
import { SnapshotNode } from '../../../../../../common/http_api/snapshot_api';
|
||||
import { InventoryItemType } from '../../../../../../common/inventory_models/types';
|
||||
|
@ -78,12 +77,6 @@ export const Map: React.FC<Props> = ({
|
|||
}
|
||||
})}
|
||||
</WaffleMapInnerContainer>
|
||||
<Legend
|
||||
formatter={formatter}
|
||||
bounds={bounds}
|
||||
dataBounds={dataBounds}
|
||||
legend={options.legend}
|
||||
/>
|
||||
</WaffleMapOuterContainer>
|
||||
);
|
||||
}}
|
||||
|
|
|
@ -4,9 +4,8 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiFilterButton, EuiFilterGroup, EuiPopover } from '@elastic/eui';
|
||||
import { EuiPopover } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { IFieldType } from 'src/plugins/data/public';
|
||||
import {
|
||||
|
@ -21,6 +20,7 @@ import { ModeSwitcher } from './mode_switcher';
|
|||
import { MetricsEditMode } from './metrics_edit_mode';
|
||||
import { CustomMetricMode } from './types';
|
||||
import { SnapshotMetricType } from '../../../../../../../common/inventory_models/types';
|
||||
import { DropdownButton } from '../../dropdown_button';
|
||||
|
||||
interface Props {
|
||||
options: Array<{ text: string; value: string }>;
|
||||
|
@ -132,17 +132,13 @@ export const WaffleMetricControls = ({
|
|||
}
|
||||
|
||||
const button = (
|
||||
<EuiFilterButton iconType="arrowDown" onClick={handleToggle}>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.waffle.metricButtonLabel"
|
||||
defaultMessage="Metric: {selectedMetric}"
|
||||
values={{ selectedMetric: currentLabel }}
|
||||
/>
|
||||
</EuiFilterButton>
|
||||
<DropdownButton onClick={handleToggle} label="Metric">
|
||||
{currentLabel}
|
||||
</DropdownButton>
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiFilterGroup>
|
||||
<>
|
||||
<EuiPopover
|
||||
isOpen={isPopoverOpen}
|
||||
id="metricsPanel"
|
||||
|
@ -194,6 +190,6 @@ export const WaffleMetricControls = ({
|
|||
customMetrics={customMetrics}
|
||||
/>
|
||||
</EuiPopover>
|
||||
</EuiFilterGroup>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -28,7 +28,7 @@ export const ViewSwitcher = ({ view, onChange }: Props) => {
|
|||
label: i18n.translate('xpack.infra.viewSwitcher.tableViewLabel', {
|
||||
defaultMessage: 'Table view',
|
||||
}),
|
||||
iconType: 'editorUnorderedList',
|
||||
iconType: 'visTable',
|
||||
},
|
||||
];
|
||||
return (
|
||||
|
@ -37,9 +37,11 @@ export const ViewSwitcher = ({ view, onChange }: Props) => {
|
|||
defaultMessage: 'Switch between table and map view',
|
||||
})}
|
||||
options={buttons}
|
||||
color="primary"
|
||||
color="text"
|
||||
buttonSize="m"
|
||||
idSelected={view}
|
||||
onChange={onChange}
|
||||
isIconOnly
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -4,17 +4,11 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import {
|
||||
EuiContextMenuPanelDescriptor,
|
||||
EuiFilterButton,
|
||||
EuiFilterGroup,
|
||||
EuiPopover,
|
||||
EuiContextMenu,
|
||||
} from '@elastic/eui';
|
||||
import { EuiContextMenuPanelDescriptor, EuiPopover, EuiContextMenu } from '@elastic/eui';
|
||||
import React, { useCallback, useState, useMemo } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { InventoryCloudAccount } from '../../../../../../common/http_api/inventory_meta_api';
|
||||
import { DropdownButton } from '../dropdown_button';
|
||||
|
||||
interface Props {
|
||||
accountId: string;
|
||||
|
@ -63,32 +57,26 @@ export const WaffleAccountsControls = (props: Props) => {
|
|||
[options, accountId, changeAccount]
|
||||
);
|
||||
|
||||
const button = (
|
||||
<DropdownButton label="Account" onClick={showPopover}>
|
||||
{currentLabel
|
||||
? currentLabel.name
|
||||
: i18n.translate('xpack.infra.waffle.accountAllTitle', {
|
||||
defaultMessage: 'All',
|
||||
})}
|
||||
</DropdownButton>
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiFilterGroup>
|
||||
<EuiPopover
|
||||
isOpen={isOpen}
|
||||
id="accontPopOver"
|
||||
button={
|
||||
<EuiFilterButton iconType="arrowDown" onClick={showPopover}>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.waffle.accountLabel"
|
||||
defaultMessage="Account: {selectedAccount}"
|
||||
values={{
|
||||
selectedAccount: currentLabel
|
||||
? currentLabel.name
|
||||
: i18n.translate('xpack.infra.waffle.accountAllTitle', {
|
||||
defaultMessage: 'All',
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
</EuiFilterButton>
|
||||
}
|
||||
anchorPosition="downLeft"
|
||||
panelPaddingSize="none"
|
||||
closePopover={closePopover}
|
||||
>
|
||||
<EuiContextMenu initialPanelId={0} panels={panels} />
|
||||
</EuiPopover>
|
||||
</EuiFilterGroup>
|
||||
<EuiPopover
|
||||
isOpen={isOpen}
|
||||
id="accontPopOver"
|
||||
button={button}
|
||||
anchorPosition="downLeft"
|
||||
panelPaddingSize="none"
|
||||
closePopover={closePopover}
|
||||
>
|
||||
<EuiContextMenu initialPanelId={0} panels={panels} />
|
||||
</EuiPopover>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -9,8 +9,6 @@ import {
|
|||
EuiContextMenu,
|
||||
EuiContextMenuPanelDescriptor,
|
||||
EuiContextMenuPanelItemDescriptor,
|
||||
EuiFilterButton,
|
||||
EuiFilterGroup,
|
||||
EuiPopover,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
@ -22,6 +20,7 @@ import { CustomFieldPanel } from './custom_field_panel';
|
|||
import { euiStyled } from '../../../../../../../observability/public';
|
||||
import { InventoryItemType } from '../../../../../../common/inventory_models/types';
|
||||
import { SnapshotGroupBy } from '../../../../../../common/http_api/snapshot_api';
|
||||
import { DropdownButton } from '../dropdown_button';
|
||||
|
||||
interface Props {
|
||||
options: Array<{ text: string; field: string; toolTipContent?: string }>;
|
||||
|
@ -121,29 +120,31 @@ export const WaffleGroupByControls = class extends React.PureComponent<Props, St
|
|||
.filter(o => o != null)
|
||||
// In this map the `o && o.field` is totally unnecessary but Typescript is
|
||||
// too stupid to realize that the filter above prevents the next map from being null
|
||||
.map(o => <EuiBadge key={o && o.field}>{o && o.text}</EuiBadge>)
|
||||
.map(o => (
|
||||
<EuiBadge color="hollow" key={o && o.field}>
|
||||
{o && o.text}
|
||||
</EuiBadge>
|
||||
))
|
||||
) : (
|
||||
<FormattedMessage id="xpack.infra.waffle.groupByAllTitle" defaultMessage="All" />
|
||||
);
|
||||
|
||||
const button = (
|
||||
<EuiFilterButton iconType="arrowDown" onClick={this.handleToggle}>
|
||||
<FormattedMessage id="xpack.infra.waffle.groupByButtonLabel" defaultMessage="Group By: " />
|
||||
<DropdownButton label="Group By" onClick={this.handleToggle}>
|
||||
{buttonBody}
|
||||
</EuiFilterButton>
|
||||
</DropdownButton>
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiFilterGroup>
|
||||
<EuiPopover
|
||||
isOpen={this.state.isPopoverOpen}
|
||||
id="groupByPanel"
|
||||
button={button}
|
||||
panelPaddingSize="none"
|
||||
closePopover={this.handleClose}
|
||||
>
|
||||
<StyledContextMenu initialPanelId="firstPanel" panels={panels} />
|
||||
</EuiPopover>
|
||||
</EuiFilterGroup>
|
||||
<EuiPopover
|
||||
isOpen={this.state.isPopoverOpen}
|
||||
id="groupByPanel"
|
||||
button={button}
|
||||
panelPaddingSize="none"
|
||||
closePopover={this.handleClose}
|
||||
>
|
||||
<StyledContextMenu initialPanelId="firstPanel" panels={panels} />
|
||||
</EuiPopover>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -4,19 +4,13 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import {
|
||||
EuiPopover,
|
||||
EuiContextMenu,
|
||||
EuiFilterButton,
|
||||
EuiFilterGroup,
|
||||
EuiContextMenuPanelDescriptor,
|
||||
} from '@elastic/eui';
|
||||
import { EuiPopover, EuiContextMenu, EuiContextMenuPanelDescriptor } from '@elastic/eui';
|
||||
|
||||
import React, { useCallback, useState, useMemo } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { findInventoryModel } from '../../../../../../common/inventory_models';
|
||||
import { InventoryItemType } from '../../../../../../common/inventory_models/types';
|
||||
import { useWaffleOptionsContext } from '../../hooks/use_waffle_options';
|
||||
import { DropdownButton } from '../dropdown_button';
|
||||
|
||||
const getDisplayNameForType = (type: InventoryItemType) => {
|
||||
const inventoryModel = findInventoryModel(type);
|
||||
|
@ -120,27 +114,23 @@ export const WaffleInventorySwitcher: React.FC = () => {
|
|||
return getDisplayNameForType(nodeType);
|
||||
}, [nodeType]);
|
||||
|
||||
const button = (
|
||||
<DropdownButton onClick={openPopover} label="Show">
|
||||
{selectedText}
|
||||
</DropdownButton>
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiFilterGroup>
|
||||
<EuiPopover
|
||||
id="contextMenu"
|
||||
button={
|
||||
<EuiFilterButton iconType="arrowDown" onClick={openPopover}>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.waffle.inventoryButtonLabel"
|
||||
defaultMessage="View: {selectedText}"
|
||||
values={{ selectedText }}
|
||||
/>
|
||||
</EuiFilterButton>
|
||||
}
|
||||
isOpen={isOpen}
|
||||
closePopover={closePopover}
|
||||
panelPaddingSize="none"
|
||||
withTitle
|
||||
anchorPosition="downLeft"
|
||||
>
|
||||
<EuiContextMenu initialPanelId="firstPanel" panels={panels} />
|
||||
</EuiPopover>
|
||||
</EuiFilterGroup>
|
||||
<EuiPopover
|
||||
id="contextMenu"
|
||||
button={button}
|
||||
isOpen={isOpen}
|
||||
closePopover={closePopover}
|
||||
panelPaddingSize="none"
|
||||
withTitle
|
||||
anchorPosition="downLeft"
|
||||
>
|
||||
<EuiContextMenu initialPanelId="firstPanel" panels={panels} />
|
||||
</EuiPopover>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -4,16 +4,10 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import {
|
||||
EuiContextMenuPanelDescriptor,
|
||||
EuiFilterButton,
|
||||
EuiFilterGroup,
|
||||
EuiPopover,
|
||||
EuiContextMenu,
|
||||
} from '@elastic/eui';
|
||||
import { EuiContextMenuPanelDescriptor, EuiPopover, EuiContextMenu } from '@elastic/eui';
|
||||
import React, { useCallback, useState, useMemo } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { DropdownButton } from '../dropdown_button';
|
||||
|
||||
interface Props {
|
||||
region?: string;
|
||||
|
@ -62,32 +56,25 @@ export const WaffleRegionControls = (props: Props) => {
|
|||
[changeRegion, options, region]
|
||||
);
|
||||
|
||||
const button = (
|
||||
<DropdownButton onClick={showPopover} label="Region">
|
||||
{currentLabel ||
|
||||
i18n.translate('xpack.infra.waffle.region', {
|
||||
defaultMessage: 'All',
|
||||
})}
|
||||
</DropdownButton>
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiFilterGroup>
|
||||
<EuiPopover
|
||||
isOpen={isOpen}
|
||||
id="regionPanel"
|
||||
button={
|
||||
<EuiFilterButton iconType="arrowDown" onClick={showPopover}>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.waffle.regionLabel"
|
||||
defaultMessage="Region: {selectedRegion}"
|
||||
values={{
|
||||
selectedRegion:
|
||||
currentLabel ||
|
||||
i18n.translate('xpack.infra.waffle.region', {
|
||||
defaultMessage: 'All',
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
</EuiFilterButton>
|
||||
}
|
||||
anchorPosition="downLeft"
|
||||
panelPaddingSize="none"
|
||||
closePopover={closePopover}
|
||||
>
|
||||
<EuiContextMenu initialPanelId={0} panels={panels} />
|
||||
</EuiPopover>
|
||||
</EuiFilterGroup>
|
||||
<EuiPopover
|
||||
isOpen={isOpen}
|
||||
id="regionPanel"
|
||||
button={button}
|
||||
anchorPosition="downLeft"
|
||||
panelPaddingSize="none"
|
||||
closePopover={closePopover}
|
||||
>
|
||||
<EuiContextMenu initialPanelId={0} panels={panels} />
|
||||
</EuiPopover>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -8,7 +8,7 @@ import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useContext } from 'react';
|
||||
|
||||
import { SnapshotToolbar } from './toolbar';
|
||||
import { FilterBar } from './components/filter_bar';
|
||||
|
||||
import { DocumentTitle } from '../../../components/document_title';
|
||||
import { NoIndices } from '../../../components/empty_states/no_indices';
|
||||
|
@ -56,7 +56,7 @@ export const SnapshotPage = () => {
|
|||
<SourceLoadingPage />
|
||||
) : metricIndicesExist ? (
|
||||
<>
|
||||
<SnapshotToolbar />
|
||||
<FilterBar />
|
||||
<Layout />
|
||||
</>
|
||||
) : hasFailedLoadingSource ? (
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* 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 { get } from 'lodash';
|
||||
import { createFormatter } from '../../../../utils/formatters';
|
||||
import { InfraFormatterType } from '../../../../lib/lib';
|
||||
import {
|
||||
SnapshotMetricInput,
|
||||
SnapshotCustomMetricInputRT,
|
||||
} from '../../../../../common/http_api/snapshot_api';
|
||||
import { createFormatterForMetric } from '../../metrics_explorer/components/helpers/create_formatter_for_metric';
|
||||
|
||||
interface MetricFormatter {
|
||||
formatter: InfraFormatterType;
|
||||
template: string;
|
||||
bounds?: { min: number; max: number };
|
||||
}
|
||||
|
||||
interface MetricFormatters {
|
||||
[key: string]: MetricFormatter;
|
||||
}
|
||||
|
||||
const METRIC_FORMATTERS: MetricFormatters = {
|
||||
['count']: { formatter: InfraFormatterType.number, template: '{{value}}' },
|
||||
['cpu']: {
|
||||
formatter: InfraFormatterType.percent,
|
||||
template: '{{value}}',
|
||||
},
|
||||
['memory']: {
|
||||
formatter: InfraFormatterType.percent,
|
||||
template: '{{value}}',
|
||||
},
|
||||
['rx']: { formatter: InfraFormatterType.bits, template: '{{value}}/s' },
|
||||
['tx']: { formatter: InfraFormatterType.bits, template: '{{value}}/s' },
|
||||
['logRate']: {
|
||||
formatter: InfraFormatterType.abbreviatedNumber,
|
||||
template: '{{value}}/s',
|
||||
},
|
||||
['diskIOReadBytes']: {
|
||||
formatter: InfraFormatterType.bytes,
|
||||
template: '{{value}}/s',
|
||||
},
|
||||
['diskIOWriteBytes']: {
|
||||
formatter: InfraFormatterType.bytes,
|
||||
template: '{{value}}/s',
|
||||
},
|
||||
['s3BucketSize']: {
|
||||
formatter: InfraFormatterType.bytes,
|
||||
template: '{{value}}',
|
||||
},
|
||||
['s3TotalRequests']: {
|
||||
formatter: InfraFormatterType.abbreviatedNumber,
|
||||
template: '{{value}}',
|
||||
},
|
||||
['s3NumberOfObjects']: {
|
||||
formatter: InfraFormatterType.abbreviatedNumber,
|
||||
template: '{{value}}',
|
||||
},
|
||||
['s3UploadBytes']: {
|
||||
formatter: InfraFormatterType.bytes,
|
||||
template: '{{value}}',
|
||||
},
|
||||
['s3DownloadBytes']: {
|
||||
formatter: InfraFormatterType.bytes,
|
||||
template: '{{value}}',
|
||||
},
|
||||
['sqsOldestMessage']: {
|
||||
formatter: InfraFormatterType.number,
|
||||
template: '{{value}} seconds',
|
||||
},
|
||||
};
|
||||
|
||||
export const createInventoryMetricFormatter = (metric: SnapshotMetricInput) => (
|
||||
val: string | number
|
||||
) => {
|
||||
if (SnapshotCustomMetricInputRT.is(metric)) {
|
||||
const formatter = createFormatterForMetric(metric);
|
||||
return formatter(val);
|
||||
}
|
||||
const metricFormatter = get(METRIC_FORMATTERS, metric.type, METRIC_FORMATTERS.count);
|
||||
if (val == null) {
|
||||
return '';
|
||||
}
|
||||
const formatter = createFormatter(metricFormatter.formatter, metricFormatter.template);
|
||||
return formatter(val);
|
||||
};
|
|
@ -17,7 +17,6 @@ import {
|
|||
MetricsExplorerTimeOptions,
|
||||
MetricsExplorerChartOptions,
|
||||
} from '../hooks/use_metrics_explorer_options';
|
||||
import { Toolbar } from '../../../../components/eui/toolbar';
|
||||
import { MetricsExplorerKueryBar } from './kuery_bar';
|
||||
import { MetricsExplorerMetrics } from './metrics';
|
||||
import { MetricsExplorerGroupBy } from './group_by';
|
||||
|
@ -28,6 +27,7 @@ import { MetricExplorerViewState } from '../hooks/use_metric_explorer_state';
|
|||
import { metricsExplorerViewSavedObjectType } from '../../../../../common/saved_objects/metrics_explorer_view';
|
||||
import { useKibanaUiSetting } from '../../../../utils/use_kibana_ui_setting';
|
||||
import { mapKibanaQuickRangesToDatePickerRanges } from '../../../../utils/map_timepicker_quickranges_to_datepicker_ranges';
|
||||
import { ToolbarPanel } from '../../../../components/toolbar_panel';
|
||||
|
||||
interface Props {
|
||||
derivedIndexPattern: IIndexPattern;
|
||||
|
@ -65,7 +65,7 @@ export const MetricsExplorerToolbar = ({
|
|||
const commonlyUsedRanges = mapKibanaQuickRangesToDatePickerRanges(timepickerQuickRanges);
|
||||
|
||||
return (
|
||||
<Toolbar>
|
||||
<ToolbarPanel>
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem grow={options.aggregation === 'count' ? 2 : false}>
|
||||
<MetricsExplorerAggregationPicker
|
||||
|
@ -143,6 +143,6 @@ export const MetricsExplorerToolbar = ({
|
|||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</Toolbar>
|
||||
</ToolbarPanel>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,65 +0,0 @@
|
|||
/*
|
||||
* 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 { isDisplayable } from './is_displayable';
|
||||
|
||||
describe('isDisplayable()', () => {
|
||||
test('field that is not displayable', () => {
|
||||
const field = {
|
||||
name: 'some.field',
|
||||
type: 'number',
|
||||
displayable: false,
|
||||
};
|
||||
expect(isDisplayable(field)).toBe(false);
|
||||
});
|
||||
test('field that is displayable', () => {
|
||||
const field = {
|
||||
name: 'some.field',
|
||||
type: 'number',
|
||||
displayable: true,
|
||||
};
|
||||
expect(isDisplayable(field)).toBe(true);
|
||||
});
|
||||
test('field that an ecs field', () => {
|
||||
const field = {
|
||||
name: '@timestamp',
|
||||
type: 'date',
|
||||
displayable: true,
|
||||
};
|
||||
expect(isDisplayable(field)).toBe(true);
|
||||
});
|
||||
test('field that matches same prefix', () => {
|
||||
const field = {
|
||||
name: 'system.network.name',
|
||||
type: 'string',
|
||||
displayable: true,
|
||||
};
|
||||
expect(isDisplayable(field, ['system.network'])).toBe(true);
|
||||
});
|
||||
test('field that does not matches same prefix', () => {
|
||||
const field = {
|
||||
name: 'system.load.1',
|
||||
type: 'number',
|
||||
displayable: true,
|
||||
};
|
||||
expect(isDisplayable(field, ['system.network'])).toBe(false);
|
||||
});
|
||||
test('field that is an K8s allowed field but does not match prefix', () => {
|
||||
const field = {
|
||||
name: 'kubernetes.namespace',
|
||||
type: 'string',
|
||||
displayable: true,
|
||||
};
|
||||
expect(isDisplayable(field, ['kubernetes.pod'])).toBe(true);
|
||||
});
|
||||
test('field that is a Prometheus allowed field but does not match prefix', () => {
|
||||
const field = {
|
||||
name: 'prometheus.labels.foo.bar',
|
||||
type: 'string',
|
||||
displayable: true,
|
||||
};
|
||||
expect(isDisplayable(field, ['prometheus.metrics'])).toBe(true);
|
||||
});
|
||||
});
|
|
@ -1,30 +0,0 @@
|
|||
/*
|
||||
* 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 { IFieldType } from 'src/plugins/data/public';
|
||||
import { startsWith, uniq } from 'lodash';
|
||||
import { getAllowedListForPrefix } from '../../common/ecs_allowed_list';
|
||||
|
||||
interface DisplayableFieldType extends IFieldType {
|
||||
displayable?: boolean;
|
||||
}
|
||||
|
||||
const fieldStartsWith = (field: DisplayableFieldType) => (name: string) =>
|
||||
startsWith(field.name, name);
|
||||
|
||||
export const isDisplayable = (field: DisplayableFieldType, additionalPrefixes: string[] = []) => {
|
||||
// We need to start with at least one prefix, even if it's empty
|
||||
const prefixes = additionalPrefixes && additionalPrefixes.length ? additionalPrefixes : [''];
|
||||
// Create a set of allowed list based on the prefixes
|
||||
const allowedList = prefixes.reduce((acc, prefix) => {
|
||||
return uniq([...acc, ...getAllowedListForPrefix(prefix)]);
|
||||
}, [] as string[]);
|
||||
// If the field is displayable and part of the allowed list or covered by the prefix
|
||||
return (
|
||||
(field.displayable && prefixes.some(fieldStartsWith(field))) ||
|
||||
allowedList.some(fieldStartsWith(field))
|
||||
);
|
||||
};
|
|
@ -8165,7 +8165,6 @@
|
|||
"xpack.infra.viewSwitcher.mapViewLabel": "マップビュー",
|
||||
"xpack.infra.viewSwitcher.tableViewLabel": "表ビュー",
|
||||
"xpack.infra.waffle.accountAllTitle": "すべて",
|
||||
"xpack.infra.waffle.accountLabel": "アカウント: {selectedAccount}",
|
||||
"xpack.infra.waffle.aggregationNames.avg": "{field} の平均",
|
||||
"xpack.infra.waffle.aggregationNames.max": "{field} の最大値",
|
||||
"xpack.infra.waffle.aggregationNames.min": "{field} の最小値",
|
||||
|
@ -8201,11 +8200,8 @@
|
|||
"xpack.infra.waffle.customMetrics.modeSwitcher.saveButtonAriaLabel": "カスタムメトリックの変更を保存",
|
||||
"xpack.infra.waffle.customMetrics.submitLabel": "保存",
|
||||
"xpack.infra.waffle.groupByAllTitle": "すべて",
|
||||
"xpack.infra.waffle.groupByButtonLabel": "グループ分けの条件: ",
|
||||
"xpack.infra.waffle.inventoryButtonLabel": "ビュー: {selectedText}",
|
||||
"xpack.infra.waffle.loadingDataText": "データを読み込み中",
|
||||
"xpack.infra.waffle.maxGroupByTooltip": "一度に選択できるグループは 2 つのみです",
|
||||
"xpack.infra.waffle.metricButtonLabel": "メトリック: {selectedMetric}",
|
||||
"xpack.infra.waffle.metricOptions.countText": "カウント",
|
||||
"xpack.infra.waffle.metricOptions.cpuUsageText": "CPU 使用状況",
|
||||
"xpack.infra.waffle.metricOptions.diskIOReadBytes": "ディスク読み取り",
|
||||
|
@ -8232,7 +8228,6 @@
|
|||
"xpack.infra.waffle.noDataDescription": "期間またはフィルターを調整してみてください。",
|
||||
"xpack.infra.waffle.noDataTitle": "表示するデータがありません。",
|
||||
"xpack.infra.waffle.region": "すべて",
|
||||
"xpack.infra.waffle.regionLabel": "地域: {selectedRegion}",
|
||||
"xpack.infra.waffle.savedView.createHeader": "ビューを保存",
|
||||
"xpack.infra.waffle.savedViews.cancel": "キャンセル",
|
||||
"xpack.infra.waffle.savedViews.cancelButton": "キャンセル",
|
||||
|
|
|
@ -8168,7 +8168,6 @@
|
|||
"xpack.infra.viewSwitcher.mapViewLabel": "地图视图",
|
||||
"xpack.infra.viewSwitcher.tableViewLabel": "表视图",
|
||||
"xpack.infra.waffle.accountAllTitle": "全部",
|
||||
"xpack.infra.waffle.accountLabel": "帐户:{selectedAccount}",
|
||||
"xpack.infra.waffle.aggregationNames.avg": "“{field}”的平均值",
|
||||
"xpack.infra.waffle.aggregationNames.max": "“{field}”的最大值",
|
||||
"xpack.infra.waffle.aggregationNames.min": "“{field}”的最小值",
|
||||
|
@ -8204,11 +8203,8 @@
|
|||
"xpack.infra.waffle.customMetrics.modeSwitcher.saveButtonAriaLabel": "保存定制指标的更改",
|
||||
"xpack.infra.waffle.customMetrics.submitLabel": "保存",
|
||||
"xpack.infra.waffle.groupByAllTitle": "全部",
|
||||
"xpack.infra.waffle.groupByButtonLabel": "分组依据: ",
|
||||
"xpack.infra.waffle.inventoryButtonLabel": "视图:{selectedText}",
|
||||
"xpack.infra.waffle.loadingDataText": "正在加载数据",
|
||||
"xpack.infra.waffle.maxGroupByTooltip": "一次只能选择两个分组",
|
||||
"xpack.infra.waffle.metricButtonLabel": "指标:{selectedMetric}",
|
||||
"xpack.infra.waffle.metricOptions.countText": "计数",
|
||||
"xpack.infra.waffle.metricOptions.cpuUsageText": "CPU 使用",
|
||||
"xpack.infra.waffle.metricOptions.diskIOReadBytes": "磁盘读取",
|
||||
|
@ -8235,7 +8231,6 @@
|
|||
"xpack.infra.waffle.noDataDescription": "尝试调整您的时间或筛选。",
|
||||
"xpack.infra.waffle.noDataTitle": "没有可显示的数据。",
|
||||
"xpack.infra.waffle.region": "全部",
|
||||
"xpack.infra.waffle.regionLabel": "地区:{selectedRegion}",
|
||||
"xpack.infra.waffle.savedView.createHeader": "保存视图",
|
||||
"xpack.infra.waffle.savedViews.cancel": "取消",
|
||||
"xpack.infra.waffle.savedViews.cancelButton": "取消",
|
||||
|
|
Loading…
Reference in a new issue