[Metrics UI] Enhance Inventory View Tooltips (#69757)
* [Metrics UI] Enhance Inventory View Tooltips * Fixing typos * Removing toMetricOpt Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
652b11f270
commit
b7f33b94a8
|
@ -34,7 +34,7 @@ export const SnapshotNodeMetricRT = rt.intersection([
|
||||||
SnapshotNodeMetricOptionalRT,
|
SnapshotNodeMetricOptionalRT,
|
||||||
]);
|
]);
|
||||||
export const SnapshotNodeRT = rt.type({
|
export const SnapshotNodeRT = rt.type({
|
||||||
metric: SnapshotNodeMetricRT,
|
metrics: rt.array(SnapshotNodeMetricRT),
|
||||||
path: rt.array(SnapshotNodePathRT),
|
path: rt.array(SnapshotNodePathRT),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -97,7 +97,7 @@ export const SnapshotMetricInputRT = rt.union([
|
||||||
export const SnapshotRequestRT = rt.intersection([
|
export const SnapshotRequestRT = rt.intersection([
|
||||||
rt.type({
|
rt.type({
|
||||||
timerange: InfraTimerangeInputRT,
|
timerange: InfraTimerangeInputRT,
|
||||||
metric: SnapshotMetricInputRT,
|
metrics: rt.array(SnapshotMetricInputRT),
|
||||||
groupBy: SnapshotGroupByRT,
|
groupBy: SnapshotGroupByRT,
|
||||||
nodeType: ItemTypeRT,
|
nodeType: ItemTypeRT,
|
||||||
sourceId: rt.string,
|
sourceId: rt.string,
|
||||||
|
|
|
@ -30,4 +30,5 @@ export const awsEC2: InventoryModel = {
|
||||||
ip: 'aws.ec2.instance.public.ip',
|
ip: 'aws.ec2.instance.public.ip',
|
||||||
},
|
},
|
||||||
requiredMetrics: ['awsEC2CpuUtilization', 'awsEC2NetworkTraffic', 'awsEC2DiskIOBytes'],
|
requiredMetrics: ['awsEC2CpuUtilization', 'awsEC2NetworkTraffic', 'awsEC2DiskIOBytes'],
|
||||||
|
tooltipMetrics: ['cpu', 'rx', 'tx'],
|
||||||
};
|
};
|
||||||
|
|
|
@ -35,4 +35,11 @@ export const awsRDS: InventoryModel = {
|
||||||
'awsRDSActiveTransactions',
|
'awsRDSActiveTransactions',
|
||||||
'awsRDSLatency',
|
'awsRDSLatency',
|
||||||
],
|
],
|
||||||
|
tooltipMetrics: [
|
||||||
|
'cpu',
|
||||||
|
'rdsLatency',
|
||||||
|
'rdsConnections',
|
||||||
|
'rdsQueriesExecuted',
|
||||||
|
'rdsActiveTransactions',
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
|
@ -35,4 +35,11 @@ export const awsS3: InventoryModel = {
|
||||||
'awsS3DownloadBytes',
|
'awsS3DownloadBytes',
|
||||||
'awsS3UploadBytes',
|
'awsS3UploadBytes',
|
||||||
],
|
],
|
||||||
|
tooltipMetrics: [
|
||||||
|
's3BucketSize',
|
||||||
|
's3NumberOfObjects',
|
||||||
|
's3TotalRequests',
|
||||||
|
's3UploadBytes',
|
||||||
|
's3DownloadBytes',
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
|
@ -35,4 +35,11 @@ export const awsSQS: InventoryModel = {
|
||||||
'awsSQSMessagesEmpty',
|
'awsSQSMessagesEmpty',
|
||||||
'awsSQSOldestMessage',
|
'awsSQSOldestMessage',
|
||||||
],
|
],
|
||||||
|
tooltipMetrics: [
|
||||||
|
'sqsMessagesVisible',
|
||||||
|
'sqsMessagesDelayed',
|
||||||
|
'sqsMessagesEmpty',
|
||||||
|
'sqsMessagesSent',
|
||||||
|
'sqsOldestMessage',
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
|
@ -37,4 +37,5 @@ export const container: InventoryModel = {
|
||||||
'containerDiskIOBytes',
|
'containerDiskIOBytes',
|
||||||
'containerDiskIOOps',
|
'containerDiskIOOps',
|
||||||
],
|
],
|
||||||
|
tooltipMetrics: ['cpu', 'memory', 'rx', 'tx'],
|
||||||
};
|
};
|
||||||
|
|
|
@ -47,4 +47,5 @@ export const host: InventoryModel = {
|
||||||
...awsRequiredMetrics,
|
...awsRequiredMetrics,
|
||||||
...nginxRequireMetrics,
|
...nginxRequireMetrics,
|
||||||
],
|
],
|
||||||
|
tooltipMetrics: ['cpu', 'memory', 'tx', 'rx'],
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
|
import { SnapshotMetricType } from './types';
|
||||||
export const CPUUsage = i18n.translate('xpack.infra.waffle.metricOptions.cpuUsageText', {
|
export const CPUUsage = i18n.translate('xpack.infra.waffle.metricOptions.cpuUsageText', {
|
||||||
defaultMessage: 'CPU usage',
|
defaultMessage: 'CPU usage',
|
||||||
});
|
});
|
||||||
|
@ -68,3 +69,81 @@ export const fieldToName = (field: string) => {
|
||||||
};
|
};
|
||||||
return LOOKUP[field] || field;
|
return LOOKUP[field] || field;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const SNAPSHOT_METRIC_TRANSLATIONS = {
|
||||||
|
cpu: i18n.translate('xpack.infra.waffle.metricOptions.cpuUsageText', {
|
||||||
|
defaultMessage: 'CPU usage',
|
||||||
|
}),
|
||||||
|
|
||||||
|
memory: i18n.translate('xpack.infra.waffle.metricOptions.memoryUsageText', {
|
||||||
|
defaultMessage: 'Memory usage',
|
||||||
|
}),
|
||||||
|
|
||||||
|
rx: i18n.translate('xpack.infra.waffle.metricOptions.inboundTrafficText', {
|
||||||
|
defaultMessage: 'Inbound traffic',
|
||||||
|
}),
|
||||||
|
|
||||||
|
tx: i18n.translate('xpack.infra.waffle.metricOptions.outboundTrafficText', {
|
||||||
|
defaultMessage: 'Outbound traffic',
|
||||||
|
}),
|
||||||
|
|
||||||
|
logRate: i18n.translate('xpack.infra.waffle.metricOptions.hostLogRateText', {
|
||||||
|
defaultMessage: 'Log rate',
|
||||||
|
}),
|
||||||
|
|
||||||
|
load: i18n.translate('xpack.infra.waffle.metricOptions.loadText', {
|
||||||
|
defaultMessage: 'Load',
|
||||||
|
}),
|
||||||
|
|
||||||
|
count: i18n.translate('xpack.infra.waffle.metricOptions.countText', {
|
||||||
|
defaultMessage: 'Count',
|
||||||
|
}),
|
||||||
|
diskIOReadBytes: i18n.translate('xpack.infra.waffle.metricOptions.diskIOReadBytes', {
|
||||||
|
defaultMessage: 'Disk Reads',
|
||||||
|
}),
|
||||||
|
diskIOWriteBytes: i18n.translate('xpack.infra.waffle.metricOptions.diskIOWriteBytes', {
|
||||||
|
defaultMessage: 'Disk Writes',
|
||||||
|
}),
|
||||||
|
s3BucketSize: i18n.translate('xpack.infra.waffle.metricOptions.s3BucketSize', {
|
||||||
|
defaultMessage: 'Bucket Size',
|
||||||
|
}),
|
||||||
|
s3TotalRequests: i18n.translate('xpack.infra.waffle.metricOptions.s3TotalRequests', {
|
||||||
|
defaultMessage: 'Total Requests',
|
||||||
|
}),
|
||||||
|
s3NumberOfObjects: i18n.translate('xpack.infra.waffle.metricOptions.s3NumberOfObjects', {
|
||||||
|
defaultMessage: 'Number of Objects',
|
||||||
|
}),
|
||||||
|
s3DownloadBytes: i18n.translate('xpack.infra.waffle.metricOptions.s3DownloadBytes', {
|
||||||
|
defaultMessage: 'Downloads (Bytes)',
|
||||||
|
}),
|
||||||
|
s3UploadBytes: i18n.translate('xpack.infra.waffle.metricOptions.s3UploadBytes', {
|
||||||
|
defaultMessage: 'Uploads (Bytes)',
|
||||||
|
}),
|
||||||
|
rdsConnections: i18n.translate('xpack.infra.waffle.metricOptions.rdsConnections', {
|
||||||
|
defaultMessage: 'Connections',
|
||||||
|
}),
|
||||||
|
rdsQueriesExecuted: i18n.translate('xpack.infra.waffle.metricOptions.rdsQueriesExecuted', {
|
||||||
|
defaultMessage: 'Queries Executed',
|
||||||
|
}),
|
||||||
|
rdsActiveTransactions: i18n.translate('xpack.infra.waffle.metricOptions.rdsActiveTransactions', {
|
||||||
|
defaultMessage: 'Active Transactions',
|
||||||
|
}),
|
||||||
|
rdsLatency: i18n.translate('xpack.infra.waffle.metricOptions.rdsLatency', {
|
||||||
|
defaultMessage: 'Latency',
|
||||||
|
}),
|
||||||
|
sqsMessagesVisible: i18n.translate('xpack.infra.waffle.metricOptions.sqsMessagesVisible', {
|
||||||
|
defaultMessage: 'Messages Available',
|
||||||
|
}),
|
||||||
|
sqsMessagesDelayed: i18n.translate('xpack.infra.waffle.metricOptions.sqsMessagesDelayed', {
|
||||||
|
defaultMessage: 'Messages Delayed',
|
||||||
|
}),
|
||||||
|
sqsMessagesSent: i18n.translate('xpack.infra.waffle.metricOptions.sqsMessagesSent', {
|
||||||
|
defaultMessage: 'Messages Added',
|
||||||
|
}),
|
||||||
|
sqsMessagesEmpty: i18n.translate('xpack.infra.waffle.metricOptions.sqsMessagesEmpty', {
|
||||||
|
defaultMessage: 'Messages Returned Empty',
|
||||||
|
}),
|
||||||
|
sqsOldestMessage: i18n.translate('xpack.infra.waffle.metricOptions.sqsOldestMessage', {
|
||||||
|
defaultMessage: 'Oldest Message',
|
||||||
|
}),
|
||||||
|
} as Record<SnapshotMetricType, string>;
|
||||||
|
|
|
@ -37,4 +37,5 @@ export const pod: InventoryModel = {
|
||||||
'podNetworkTraffic',
|
'podNetworkTraffic',
|
||||||
...nginxRequiredMetrics,
|
...nginxRequiredMetrics,
|
||||||
],
|
],
|
||||||
|
tooltipMetrics: ['cpu', 'memory', 'rx', 'tx'],
|
||||||
};
|
};
|
||||||
|
|
|
@ -351,4 +351,5 @@ export interface InventoryModel {
|
||||||
};
|
};
|
||||||
metrics: InventoryMetrics;
|
metrics: InventoryMetrics;
|
||||||
requiredMetrics: InventoryMetric[];
|
requiredMetrics: InventoryMetric[];
|
||||||
|
tooltipMetrics: SnapshotMetricType[];
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ export interface InfraWaffleMapNode {
|
||||||
name: string;
|
name: string;
|
||||||
ip?: string | null;
|
ip?: string | null;
|
||||||
path: SnapshotNodePath[];
|
path: SnapshotNodePath[];
|
||||||
metric: SnapshotNodeMetric;
|
metrics: SnapshotNodeMetric[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type InfraWaffleMapGroup = InfraWaffleMapGroupOfNodes | InfraWaffleMapGroupOfGroups;
|
export type InfraWaffleMapGroup = InfraWaffleMapGroupOfNodes | InfraWaffleMapGroupOfGroups;
|
||||||
|
|
|
@ -9,7 +9,8 @@ import { useInterval } from 'react-use';
|
||||||
|
|
||||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||||
import { convertIntervalToString } from '../../../../utils/convert_interval_to_string';
|
import { convertIntervalToString } from '../../../../utils/convert_interval_to_string';
|
||||||
import { NodesOverview, calculateBoundsFromNodes } from './nodes_overview';
|
import { NodesOverview } from './nodes_overview';
|
||||||
|
import { calculateBoundsFromNodes } from '../lib/calculate_bounds_from_nodes';
|
||||||
import { PageContent } from '../../../../components/page';
|
import { PageContent } from '../../../../components/page';
|
||||||
import { useSnapshot } from '../hooks/use_snaphot';
|
import { useSnapshot } from '../hooks/use_snaphot';
|
||||||
import { useWaffleTimeContext } from '../hooks/use_waffle_time';
|
import { useWaffleTimeContext } from '../hooks/use_waffle_time';
|
||||||
|
@ -48,7 +49,7 @@ export const Layout = () => {
|
||||||
const { filterQueryAsJson, applyFilterQuery } = useWaffleFiltersContext();
|
const { filterQueryAsJson, applyFilterQuery } = useWaffleFiltersContext();
|
||||||
const { loading, nodes, reload, interval } = useSnapshot(
|
const { loading, nodes, reload, interval } = useSnapshot(
|
||||||
filterQueryAsJson,
|
filterQueryAsJson,
|
||||||
metric,
|
[metric],
|
||||||
groupBy,
|
groupBy,
|
||||||
nodeType,
|
nodeType,
|
||||||
sourceId,
|
sourceId,
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { max, min } from 'lodash';
|
|
||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
|
|
||||||
import { InventoryItemType } from '../../../../../common/inventory_models/types';
|
import { InventoryItemType } from '../../../../../common/inventory_models/types';
|
||||||
|
@ -16,6 +15,7 @@ import { InfraLoadingPanel } from '../../../../components/loading';
|
||||||
import { Map } from './waffle/map';
|
import { Map } from './waffle/map';
|
||||||
import { TableView } from './table_view';
|
import { TableView } from './table_view';
|
||||||
import { SnapshotNode } from '../../../../../common/http_api/snapshot_api';
|
import { SnapshotNode } from '../../../../../common/http_api/snapshot_api';
|
||||||
|
import { calculateBoundsFromNodes } from '../lib/calculate_bounds_from_nodes';
|
||||||
|
|
||||||
export interface KueryFilterQuery {
|
export interface KueryFilterQuery {
|
||||||
kind: 'kuery';
|
kind: 'kuery';
|
||||||
|
@ -36,18 +36,6 @@ interface Props {
|
||||||
formatter: InfraFormatter;
|
formatter: InfraFormatter;
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
// otherwise the legend will look silly since both values are the same for top and
|
|
||||||
// bottom.
|
|
||||||
if (minValues.length === 1) {
|
|
||||||
minValues.unshift(0);
|
|
||||||
}
|
|
||||||
return { min: min(minValues) || 0, max: max(maxValues) || 0 };
|
|
||||||
};
|
|
||||||
|
|
||||||
export const NodesOverview = ({
|
export const NodesOverview = ({
|
||||||
autoBounds,
|
autoBounds,
|
||||||
boundsOverride,
|
boundsOverride,
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
import { EuiButtonEmpty, EuiInMemoryTable, EuiToolTip, EuiBasicTableColumn } from '@elastic/eui';
|
import { EuiButtonEmpty, EuiInMemoryTable, EuiToolTip, EuiBasicTableColumn } from '@elastic/eui';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
|
|
||||||
import { last } from 'lodash';
|
import { last, first } from 'lodash';
|
||||||
import React, { useState, useCallback, useEffect } from 'react';
|
import React, { useState, useCallback, useEffect } from 'react';
|
||||||
import { createWaffleMapNode } from '../lib/nodes_to_wafflemap';
|
import { createWaffleMapNode } from '../lib/nodes_to_wafflemap';
|
||||||
import { InfraWaffleMapNode, InfraWaffleMapOptions } from '../../../../lib/lib';
|
import { InfraWaffleMapNode, InfraWaffleMapOptions } from '../../../../lib/lib';
|
||||||
|
@ -142,6 +142,7 @@ export const TableView = (props: Props) => {
|
||||||
|
|
||||||
const items = nodes.map((node) => {
|
const items = nodes.map((node) => {
|
||||||
const name = last(node.path);
|
const name = last(node.path);
|
||||||
|
const metric = first(node.metrics);
|
||||||
return {
|
return {
|
||||||
name: (name && name.label) || 'unknown',
|
name: (name && name.label) || 'unknown',
|
||||||
...getGroupPaths(node.path).reduce(
|
...getGroupPaths(node.path).reduce(
|
||||||
|
@ -151,9 +152,9 @@ export const TableView = (props: Props) => {
|
||||||
}),
|
}),
|
||||||
{}
|
{}
|
||||||
),
|
),
|
||||||
value: node.metric.value,
|
value: (metric && metric.value) || 0,
|
||||||
avg: node.metric.avg,
|
avg: (metric && metric.avg) || 0,
|
||||||
max: node.metric.max,
|
max: (metric && metric.max) || 0,
|
||||||
node: createWaffleMapNode(node),
|
node: createWaffleMapNode(node),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`ConditionalToolTip should just work 1`] = `
|
||||||
|
<div
|
||||||
|
data-test-subj="conditionalTooltipContent"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"minWidth": 200,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"borderBottom": "1px solid #98a2b3",
|
||||||
|
"marginBottom": "4px",
|
||||||
|
"paddingBottom": "4px",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
host-01
|
||||||
|
</div>
|
||||||
|
<EuiFlexGroup
|
||||||
|
gutterSize="none"
|
||||||
|
>
|
||||||
|
<EuiFlexItem
|
||||||
|
grow={1}
|
||||||
|
>
|
||||||
|
CPU usage
|
||||||
|
</EuiFlexItem>
|
||||||
|
<EuiFlexItem
|
||||||
|
grow={false}
|
||||||
|
>
|
||||||
|
10%
|
||||||
|
</EuiFlexItem>
|
||||||
|
</EuiFlexGroup>
|
||||||
|
<EuiFlexGroup
|
||||||
|
gutterSize="none"
|
||||||
|
>
|
||||||
|
<EuiFlexItem
|
||||||
|
grow={1}
|
||||||
|
>
|
||||||
|
Memory usage
|
||||||
|
</EuiFlexItem>
|
||||||
|
<EuiFlexItem
|
||||||
|
grow={false}
|
||||||
|
>
|
||||||
|
80%
|
||||||
|
</EuiFlexItem>
|
||||||
|
</EuiFlexGroup>
|
||||||
|
<EuiFlexGroup
|
||||||
|
gutterSize="none"
|
||||||
|
>
|
||||||
|
<EuiFlexItem
|
||||||
|
grow={1}
|
||||||
|
>
|
||||||
|
Outbound traffic
|
||||||
|
</EuiFlexItem>
|
||||||
|
<EuiFlexItem
|
||||||
|
grow={false}
|
||||||
|
>
|
||||||
|
8Mbit/s
|
||||||
|
</EuiFlexItem>
|
||||||
|
</EuiFlexGroup>
|
||||||
|
<EuiFlexGroup
|
||||||
|
gutterSize="none"
|
||||||
|
>
|
||||||
|
<EuiFlexItem
|
||||||
|
grow={1}
|
||||||
|
>
|
||||||
|
Inbound traffic
|
||||||
|
</EuiFlexItem>
|
||||||
|
<EuiFlexItem
|
||||||
|
grow={false}
|
||||||
|
>
|
||||||
|
8Mbit/s
|
||||||
|
</EuiFlexItem>
|
||||||
|
</EuiFlexGroup>
|
||||||
|
</div>
|
||||||
|
`;
|
|
@ -0,0 +1,155 @@
|
||||||
|
/*
|
||||||
|
* 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 { mount } from 'enzyme';
|
||||||
|
// import { act } from 'react-dom/test-utils';
|
||||||
|
import { EuiThemeProvider } from '../../../../../../../observability/public';
|
||||||
|
import { EuiToolTip } from '@elastic/eui';
|
||||||
|
import { ConditionalToolTip } from './conditional_tooltip';
|
||||||
|
import {
|
||||||
|
InfraWaffleMapNode,
|
||||||
|
InfraWaffleMapOptions,
|
||||||
|
InfraFormatterType,
|
||||||
|
} from '../../../../../lib/lib';
|
||||||
|
|
||||||
|
jest.mock('../../../../../containers/source', () => ({
|
||||||
|
useSourceContext: () => ({ sourceId: 'default' }),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('../../hooks/use_snaphot');
|
||||||
|
import { useSnapshot } from '../../hooks/use_snaphot';
|
||||||
|
const mockedUseSnapshot = useSnapshot as jest.Mock<ReturnType<typeof useSnapshot>>;
|
||||||
|
|
||||||
|
const NODE: InfraWaffleMapNode = {
|
||||||
|
pathId: 'host-01',
|
||||||
|
id: 'host-01',
|
||||||
|
name: 'host-01',
|
||||||
|
path: [{ value: 'host-01', label: 'host-01' }],
|
||||||
|
metrics: [{ name: 'cpu' }],
|
||||||
|
};
|
||||||
|
|
||||||
|
const OPTIONS: InfraWaffleMapOptions = {
|
||||||
|
formatter: InfraFormatterType.percent,
|
||||||
|
formatTemplate: '{value}',
|
||||||
|
metric: { type: 'cpu' },
|
||||||
|
groupBy: [],
|
||||||
|
legend: {
|
||||||
|
type: 'steppedGradient',
|
||||||
|
rules: [],
|
||||||
|
},
|
||||||
|
sort: { by: 'value', direction: 'desc' },
|
||||||
|
};
|
||||||
|
|
||||||
|
export const nextTick = () => new Promise((res) => process.nextTick(res));
|
||||||
|
const ChildComponent = () => <div>child</div>;
|
||||||
|
|
||||||
|
describe('ConditionalToolTip', () => {
|
||||||
|
afterEach(() => {
|
||||||
|
mockedUseSnapshot.mockReset();
|
||||||
|
});
|
||||||
|
|
||||||
|
function createWrapper(currentTime: number = Date.now(), hidden: boolean = false) {
|
||||||
|
return mount(
|
||||||
|
<EuiThemeProvider darkMode={false}>
|
||||||
|
<ConditionalToolTip
|
||||||
|
currentTime={currentTime}
|
||||||
|
hidden={hidden}
|
||||||
|
node={NODE}
|
||||||
|
options={OPTIONS}
|
||||||
|
formatter={(v: number) => `${v}`}
|
||||||
|
nodeType="host"
|
||||||
|
>
|
||||||
|
<ChildComponent />
|
||||||
|
</ConditionalToolTip>
|
||||||
|
</EuiThemeProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should return children when hidden', () => {
|
||||||
|
mockedUseSnapshot.mockReturnValue({
|
||||||
|
nodes: [],
|
||||||
|
error: null,
|
||||||
|
loading: true,
|
||||||
|
interval: '',
|
||||||
|
reload: jest.fn(() => Promise.resolve()),
|
||||||
|
});
|
||||||
|
const currentTime = Date.now();
|
||||||
|
const wrapper = createWrapper(currentTime, true);
|
||||||
|
expect(wrapper.find(ChildComponent).exists()).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should just work', () => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
const reloadMock = jest.fn(() => Promise.resolve());
|
||||||
|
mockedUseSnapshot.mockReturnValue({
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
path: [{ label: 'host-01', value: 'host-01', ip: '192.168.1.10' }],
|
||||||
|
metrics: [
|
||||||
|
{ name: 'cpu', value: 0.1, avg: 0.4, max: 0.7 },
|
||||||
|
{ name: 'memory', value: 0.8, avg: 0.8, max: 1 },
|
||||||
|
{ name: 'tx', value: 1000000, avg: 1000000, max: 1000000 },
|
||||||
|
{ name: 'rx', value: 1000000, avg: 1000000, max: 1000000 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
error: null,
|
||||||
|
loading: false,
|
||||||
|
interval: '60s',
|
||||||
|
reload: reloadMock,
|
||||||
|
});
|
||||||
|
const currentTime = Date.now();
|
||||||
|
const wrapper = createWrapper(currentTime, false);
|
||||||
|
expect(wrapper.find(ChildComponent).exists()).toBeTruthy();
|
||||||
|
expect(wrapper.find(EuiToolTip).exists()).toBeTruthy();
|
||||||
|
const expectedQuery = JSON.stringify({
|
||||||
|
bool: {
|
||||||
|
filter: {
|
||||||
|
match_phrase: { 'host.name': 'host-01' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const expectedMetrics = [{ type: 'cpu' }, { type: 'memory' }, { type: 'tx' }, { type: 'rx' }];
|
||||||
|
expect(mockedUseSnapshot).toBeCalledWith(
|
||||||
|
expectedQuery,
|
||||||
|
expectedMetrics,
|
||||||
|
[],
|
||||||
|
'host',
|
||||||
|
'default',
|
||||||
|
currentTime,
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
false
|
||||||
|
);
|
||||||
|
wrapper.find('[data-test-subj~="conditionalTooltipMouseHandler"]').simulate('mouseOver');
|
||||||
|
wrapper.find(EuiToolTip).simulate('mouseOver');
|
||||||
|
jest.advanceTimersByTime(500);
|
||||||
|
expect(reloadMock).toHaveBeenCalled();
|
||||||
|
expect(wrapper.find(EuiToolTip).props().content).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not load data if mouse out before 200 ms', () => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
const reloadMock = jest.fn(() => Promise.resolve());
|
||||||
|
mockedUseSnapshot.mockReturnValue({
|
||||||
|
nodes: [],
|
||||||
|
error: null,
|
||||||
|
loading: true,
|
||||||
|
interval: '',
|
||||||
|
reload: reloadMock,
|
||||||
|
});
|
||||||
|
const currentTime = Date.now();
|
||||||
|
const wrapper = createWrapper(currentTime, false);
|
||||||
|
expect(wrapper.find(ChildComponent).exists()).toBeTruthy();
|
||||||
|
expect(wrapper.find(EuiToolTip).exists()).toBeTruthy();
|
||||||
|
wrapper.find('[data-test-subj~="conditionalTooltipMouseHandler"]').simulate('mouseOver');
|
||||||
|
jest.advanceTimersByTime(100);
|
||||||
|
wrapper.find('[data-test-subj~="conditionalTooltipMouseHandler"]').simulate('mouseOut');
|
||||||
|
jest.advanceTimersByTime(200);
|
||||||
|
expect(reloadMock).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
|
@ -3,18 +3,117 @@
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
import React from 'react';
|
import React, { useCallback, useState, useEffect } from 'react';
|
||||||
import { EuiToolTip, EuiToolTipProps } from '@elastic/eui';
|
import { EuiToolTip, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||||
import { omit } from 'lodash';
|
import { first } from 'lodash';
|
||||||
|
import { withTheme, EuiTheme } from '../../../../../../../observability/public';
|
||||||
|
import { useSourceContext } from '../../../../../containers/source';
|
||||||
|
import { findInventoryModel } from '../../../../../../common/inventory_models';
|
||||||
|
import {
|
||||||
|
InventoryItemType,
|
||||||
|
SnapshotMetricType,
|
||||||
|
} from '../../../../../../common/inventory_models/types';
|
||||||
|
import { InfraWaffleMapNode, InfraWaffleMapOptions } from '../../../../../lib/lib';
|
||||||
|
import { useSnapshot } from '../../hooks/use_snaphot';
|
||||||
|
import { createInventoryMetricFormatter } from '../../lib/create_inventory_metric_formatter';
|
||||||
|
import { SNAPSHOT_METRIC_TRANSLATIONS } from '../../../../../../common/inventory_models/intl_strings';
|
||||||
|
|
||||||
interface Props extends EuiToolTipProps {
|
export interface Props {
|
||||||
|
currentTime: number;
|
||||||
hidden: boolean;
|
hidden: boolean;
|
||||||
|
node: InfraWaffleMapNode;
|
||||||
|
options: InfraWaffleMapOptions;
|
||||||
|
formatter: (val: number) => string;
|
||||||
|
children: React.ReactElement;
|
||||||
|
nodeType: InventoryItemType;
|
||||||
|
theme: EuiTheme | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ConditionalToolTip = (props: Props) => {
|
export const ConditionalToolTip = withTheme(
|
||||||
if (props.hidden) {
|
({ theme, hidden, node, children, nodeType, currentTime }: Props) => {
|
||||||
return props.children;
|
const { sourceId } = useSourceContext();
|
||||||
|
const [timer, setTimer] = useState<ReturnType<typeof setTimeout> | null>(null);
|
||||||
|
const model = findInventoryModel(nodeType);
|
||||||
|
const requestMetrics = model.tooltipMetrics.map((type) => ({ type })) as Array<{
|
||||||
|
type: SnapshotMetricType;
|
||||||
|
}>;
|
||||||
|
const query = JSON.stringify({
|
||||||
|
bool: {
|
||||||
|
filter: {
|
||||||
|
match_phrase: { [model.fields.id]: node.id },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { nodes, reload } = useSnapshot(
|
||||||
|
query,
|
||||||
|
requestMetrics,
|
||||||
|
[],
|
||||||
|
nodeType,
|
||||||
|
sourceId,
|
||||||
|
currentTime,
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
false // Doesn't send request until reload() is called
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleDataLoad = useCallback(() => {
|
||||||
|
const id = setTimeout(reload, 200);
|
||||||
|
setTimer(id);
|
||||||
|
}, [reload]);
|
||||||
|
|
||||||
|
const cancelDataLoad = useCallback(() => {
|
||||||
|
return (timer && clearTimeout(timer)) || void 0;
|
||||||
|
}, [timer]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return cancelDataLoad;
|
||||||
|
}, [timer, cancelDataLoad]);
|
||||||
|
|
||||||
|
if (hidden) {
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dataNode = first(nodes);
|
||||||
|
const metrics = (dataNode && dataNode.metrics) || [];
|
||||||
|
const content = (
|
||||||
|
<div style={{ minWidth: 200 }} data-test-subj="conditionalTooltipContent">
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
borderBottom: `1px solid ${theme?.eui.euiColorMediumShade}`,
|
||||||
|
paddingBottom: theme?.eui.paddingSizes.xs,
|
||||||
|
marginBottom: theme?.eui.paddingSizes.xs,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{node.name}
|
||||||
|
</div>
|
||||||
|
{metrics.map((metric) => {
|
||||||
|
const name = SNAPSHOT_METRIC_TRANSLATIONS[metric.name] || metric.name;
|
||||||
|
const formatter = createInventoryMetricFormatter({ type: metric.name });
|
||||||
|
return (
|
||||||
|
<EuiFlexGroup gutterSize="none" key={metric.name}>
|
||||||
|
<EuiFlexItem grow={1}>{name}</EuiFlexItem>
|
||||||
|
<EuiFlexItem grow={false}>
|
||||||
|
{(metric.value && formatter(metric.value)) || '-'}
|
||||||
|
</EuiFlexItem>
|
||||||
|
</EuiFlexGroup>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EuiToolTip delay="regular" position="right" content={content}>
|
||||||
|
<div
|
||||||
|
data-test-subj="conditionalTooltipMouseHandler"
|
||||||
|
onMouseOver={handleDataLoad}
|
||||||
|
onFocus={handleDataLoad}
|
||||||
|
onMouseOut={cancelDataLoad}
|
||||||
|
onBlur={cancelDataLoad}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</EuiToolTip>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
const propsWithoutHidden = omit(props, 'hidden');
|
);
|
||||||
return <EuiToolTip {...propsWithoutHidden}>{props.children}</EuiToolTip>;
|
|
||||||
};
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import React from 'react';
|
||||||
|
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
|
|
||||||
|
import { first } from 'lodash';
|
||||||
import { ConditionalToolTip } from './conditional_tooltip';
|
import { ConditionalToolTip } from './conditional_tooltip';
|
||||||
import { euiStyled } from '../../../../../../../observability/public';
|
import { euiStyled } from '../../../../../../../observability/public';
|
||||||
import {
|
import {
|
||||||
|
@ -41,7 +42,7 @@ export const Node = class extends React.PureComponent<Props, State> {
|
||||||
public render() {
|
public render() {
|
||||||
const { nodeType, node, options, squareSize, bounds, formatter, currentTime } = this.props;
|
const { nodeType, node, options, squareSize, bounds, formatter, currentTime } = this.props;
|
||||||
const { isPopoverOpen } = this.state;
|
const { isPopoverOpen } = this.state;
|
||||||
const { metric } = node;
|
const metric = first(node.metrics);
|
||||||
const valueMode = squareSize > 70;
|
const valueMode = squareSize > 70;
|
||||||
const ellipsisMode = squareSize > 30;
|
const ellipsisMode = squareSize > 30;
|
||||||
const rawValue = (metric && metric.value) || 0;
|
const rawValue = (metric && metric.value) || 0;
|
||||||
|
@ -62,10 +63,12 @@ export const Node = class extends React.PureComponent<Props, State> {
|
||||||
popoverPosition="downCenter"
|
popoverPosition="downCenter"
|
||||||
>
|
>
|
||||||
<ConditionalToolTip
|
<ConditionalToolTip
|
||||||
delay="regular"
|
currentTime={currentTime}
|
||||||
|
formatter={formatter}
|
||||||
hidden={isPopoverOpen}
|
hidden={isPopoverOpen}
|
||||||
position="top"
|
node={node}
|
||||||
content={`${node.name} | ${value}`}
|
options={options}
|
||||||
|
nodeType={nodeType}
|
||||||
>
|
>
|
||||||
<NodeContainer
|
<NodeContainer
|
||||||
data-test-subj="nodeContainer"
|
data-test-subj="nodeContainer"
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
import { fold } from 'fp-ts/lib/Either';
|
import { fold } from 'fp-ts/lib/Either';
|
||||||
import { identity } from 'fp-ts/lib/function';
|
import { identity } from 'fp-ts/lib/function';
|
||||||
import { pipe } from 'fp-ts/lib/pipeable';
|
import { pipe } from 'fp-ts/lib/pipeable';
|
||||||
|
import { useEffect } from 'react';
|
||||||
import { throwErrors, createPlainError } from '../../../../../common/runtime_types';
|
import { throwErrors, createPlainError } from '../../../../../common/runtime_types';
|
||||||
import { useHTTPRequest } from '../../../../hooks/use_http_request';
|
import { useHTTPRequest } from '../../../../hooks/use_http_request';
|
||||||
import {
|
import {
|
||||||
|
@ -23,13 +24,14 @@ import {
|
||||||
|
|
||||||
export function useSnapshot(
|
export function useSnapshot(
|
||||||
filterQuery: string | null | undefined,
|
filterQuery: string | null | undefined,
|
||||||
metric: { type: SnapshotMetricType },
|
metrics: Array<{ type: SnapshotMetricType }>,
|
||||||
groupBy: SnapshotGroupBy,
|
groupBy: SnapshotGroupBy,
|
||||||
nodeType: InventoryItemType,
|
nodeType: InventoryItemType,
|
||||||
sourceId: string,
|
sourceId: string,
|
||||||
currentTime: number,
|
currentTime: number,
|
||||||
accountId: string,
|
accountId: string,
|
||||||
region: string
|
region: string,
|
||||||
|
sendRequestImmediatly = true
|
||||||
) {
|
) {
|
||||||
const decodeResponse = (response: any) => {
|
const decodeResponse = (response: any) => {
|
||||||
return pipe(
|
return pipe(
|
||||||
|
@ -49,7 +51,7 @@ export function useSnapshot(
|
||||||
'/api/metrics/snapshot',
|
'/api/metrics/snapshot',
|
||||||
'POST',
|
'POST',
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
metric,
|
metrics,
|
||||||
groupBy,
|
groupBy,
|
||||||
nodeType,
|
nodeType,
|
||||||
timerange,
|
timerange,
|
||||||
|
@ -62,6 +64,14 @@ export function useSnapshot(
|
||||||
decodeResponse
|
decodeResponse
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
if (sendRequestImmediatly) {
|
||||||
|
await makeRequest();
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}, [makeRequest, sendRequestImmediatly]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
error: (error && error.message) || null,
|
error: (error && error.message) || null,
|
||||||
loading,
|
loading,
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
* 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 { calculateBoundsFromNodes } from './calculate_bounds_from_nodes';
|
||||||
|
import { SnapshotNode } from '../../../../../common/http_api/snapshot_api';
|
||||||
|
const nodes: SnapshotNode[] = [
|
||||||
|
{
|
||||||
|
path: [{ value: 'host-01', label: 'host-01' }],
|
||||||
|
metrics: [
|
||||||
|
{
|
||||||
|
name: 'cpu',
|
||||||
|
value: 0.5,
|
||||||
|
max: 1.5,
|
||||||
|
avg: 0.7,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: [{ value: 'host-02', label: 'host-02' }],
|
||||||
|
metrics: [
|
||||||
|
{
|
||||||
|
name: 'cpu',
|
||||||
|
value: 0.2,
|
||||||
|
max: 0.7,
|
||||||
|
avg: 0.4,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('calculateBoundsFromNodes', () => {
|
||||||
|
it('should just work', () => {
|
||||||
|
const bounds = calculateBoundsFromNodes(nodes);
|
||||||
|
expect(bounds).toEqual({
|
||||||
|
min: 0.2,
|
||||||
|
max: 1.5,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should have a minimum of 0 for only a single node', () => {
|
||||||
|
const bounds = calculateBoundsFromNodes([nodes[0]]);
|
||||||
|
expect(bounds).toEqual({
|
||||||
|
min: 0,
|
||||||
|
max: 1.5,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should return zero for empty nodes', () => {
|
||||||
|
const bounds = calculateBoundsFromNodes([]);
|
||||||
|
expect(bounds).toEqual({
|
||||||
|
min: 0,
|
||||||
|
max: 0,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -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 { first, min, max, isFinite } from 'lodash';
|
||||||
|
import { SnapshotNode } from '../../../../../common/http_api/snapshot_api';
|
||||||
|
import { InfraWaffleMapBounds } from '../../../../lib/lib';
|
||||||
|
|
||||||
|
export const calculateBoundsFromNodes = (nodes: SnapshotNode[]): InfraWaffleMapBounds => {
|
||||||
|
const maxValues = nodes.map((node) => {
|
||||||
|
const metric = first(node.metrics);
|
||||||
|
if (!metric) return 0;
|
||||||
|
return metric.max;
|
||||||
|
});
|
||||||
|
const minValues = nodes.map((node) => {
|
||||||
|
const metric = first(node.metrics);
|
||||||
|
if (!metric) return 0;
|
||||||
|
return metric.value;
|
||||||
|
});
|
||||||
|
// if there is only one value then we need to set the bottom range to zero for min
|
||||||
|
// otherwise the legend will look silly since both values are the same for top and
|
||||||
|
// bottom.
|
||||||
|
if (minValues.length === 1) {
|
||||||
|
minValues.unshift(0);
|
||||||
|
}
|
||||||
|
const maxValue = max(maxValues) || 0;
|
||||||
|
const minValue = min(minValues) || 0;
|
||||||
|
return { min: isFinite(minValue) ? minValue : 0, max: isFinite(maxValue) ? maxValue : 0 };
|
||||||
|
};
|
|
@ -36,12 +36,14 @@ describe('createUptimeLink()', () => {
|
||||||
name: 'host-01',
|
name: 'host-01',
|
||||||
ip: '10.0.1.2',
|
ip: '10.0.1.2',
|
||||||
path: [],
|
path: [],
|
||||||
metric: {
|
metrics: [
|
||||||
name: 'cpu' as SnapshotMetricType,
|
{
|
||||||
value: 0.5,
|
name: 'cpu' as SnapshotMetricType,
|
||||||
max: 0.8,
|
value: 0.5,
|
||||||
avg: 0.6,
|
max: 0.8,
|
||||||
},
|
avg: 0.6,
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
expect(createUptimeLink(options, 'host', node)).toStrictEqual({
|
expect(createUptimeLink(options, 'host', node)).toStrictEqual({
|
||||||
app: 'uptime',
|
app: 'uptime',
|
||||||
|
@ -56,12 +58,14 @@ describe('createUptimeLink()', () => {
|
||||||
id: 'host-01',
|
id: 'host-01',
|
||||||
name: 'host-01',
|
name: 'host-01',
|
||||||
path: [],
|
path: [],
|
||||||
metric: {
|
metrics: [
|
||||||
name: 'cpu' as SnapshotMetricType,
|
{
|
||||||
value: 0.5,
|
name: 'cpu' as SnapshotMetricType,
|
||||||
max: 0.8,
|
value: 0.5,
|
||||||
avg: 0.6,
|
max: 0.8,
|
||||||
},
|
avg: 0.6,
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
expect(createUptimeLink(options, 'host', node)).toStrictEqual({
|
expect(createUptimeLink(options, 'host', node)).toStrictEqual({
|
||||||
app: 'uptime',
|
app: 'uptime',
|
||||||
|
@ -76,12 +80,14 @@ describe('createUptimeLink()', () => {
|
||||||
id: '29193-pod-02939',
|
id: '29193-pod-02939',
|
||||||
name: 'pod-01',
|
name: 'pod-01',
|
||||||
path: [],
|
path: [],
|
||||||
metric: {
|
metrics: [
|
||||||
name: 'cpu' as SnapshotMetricType,
|
{
|
||||||
value: 0.5,
|
name: 'cpu' as SnapshotMetricType,
|
||||||
max: 0.8,
|
value: 0.5,
|
||||||
avg: 0.6,
|
max: 0.8,
|
||||||
},
|
avg: 0.6,
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
expect(createUptimeLink(options, 'pod', node)).toStrictEqual({
|
expect(createUptimeLink(options, 'pod', node)).toStrictEqual({
|
||||||
app: 'uptime',
|
app: 'uptime',
|
||||||
|
@ -96,12 +102,14 @@ describe('createUptimeLink()', () => {
|
||||||
id: 'docker-1234',
|
id: 'docker-1234',
|
||||||
name: 'docker-01',
|
name: 'docker-01',
|
||||||
path: [],
|
path: [],
|
||||||
metric: {
|
metrics: [
|
||||||
name: 'cpu' as SnapshotMetricType,
|
{
|
||||||
value: 0.5,
|
name: 'cpu' as SnapshotMetricType,
|
||||||
max: 0.8,
|
value: 0.5,
|
||||||
avg: 0.6,
|
max: 0.8,
|
||||||
},
|
avg: 0.6,
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
expect(createUptimeLink(options, 'container', node)).toStrictEqual({
|
expect(createUptimeLink(options, 'container', node)).toStrictEqual({
|
||||||
app: 'uptime',
|
app: 'uptime',
|
||||||
|
|
|
@ -95,7 +95,7 @@ export function createWaffleMapNode(node: SnapshotNode): InfraWaffleMapNode {
|
||||||
id: nodePathItem.value,
|
id: nodePathItem.value,
|
||||||
ip: nodePathItem.ip,
|
ip: nodePathItem.ip,
|
||||||
name: nodePathItem.label || nodePathItem.value,
|
name: nodePathItem.label || nodePathItem.value,
|
||||||
metric: node.metric,
|
metrics: node.metrics,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
* 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 { sortNodes } from './sort_nodes';
|
||||||
|
import { SnapshotNode } from '../../../../../common/http_api/snapshot_api';
|
||||||
|
|
||||||
|
const nodes: SnapshotNode[] = [
|
||||||
|
{
|
||||||
|
path: [{ value: 'host-01', label: 'host-01' }],
|
||||||
|
metrics: [
|
||||||
|
{
|
||||||
|
name: 'cpu',
|
||||||
|
value: 0.5,
|
||||||
|
max: 1.5,
|
||||||
|
avg: 0.7,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: [{ value: 'host-02', label: 'host-02' }],
|
||||||
|
metrics: [
|
||||||
|
{
|
||||||
|
name: 'cpu',
|
||||||
|
value: 0.2,
|
||||||
|
max: 0.7,
|
||||||
|
avg: 0.4,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('sortNodes', () => {
|
||||||
|
describe('asc', () => {
|
||||||
|
it('should sort by name', () => {
|
||||||
|
const sortedNodes = sortNodes({ by: 'name', direction: 'asc' }, nodes);
|
||||||
|
expect(sortedNodes).toEqual(nodes);
|
||||||
|
});
|
||||||
|
it('should sort by merics', () => {
|
||||||
|
const sortedNodes = sortNodes({ by: 'value', direction: 'asc' }, nodes);
|
||||||
|
expect(sortedNodes).toEqual(nodes.reverse());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('desc', () => {
|
||||||
|
it('should sort by name', () => {
|
||||||
|
const sortedNodes = sortNodes({ by: 'name', direction: 'desc' }, nodes);
|
||||||
|
expect(sortedNodes).toEqual(nodes.reverse());
|
||||||
|
});
|
||||||
|
it('should sort by merics', () => {
|
||||||
|
const sortedNodes = sortNodes({ by: 'value', direction: 'desc' }, nodes);
|
||||||
|
expect(sortedNodes).toEqual(nodes);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -4,13 +4,16 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { sortBy, last } from 'lodash';
|
import { sortBy, last, first } from 'lodash';
|
||||||
import { SnapshotNode } from '../../../../../common/http_api/snapshot_api';
|
import { SnapshotNode } from '../../../../../common/http_api/snapshot_api';
|
||||||
import { WaffleSortOption } from '../hooks/use_waffle_options';
|
import { WaffleSortOption } from '../hooks/use_waffle_options';
|
||||||
|
|
||||||
const SORT_PATHS = {
|
const SORT_PATHS = {
|
||||||
name: (node: SnapshotNode) => last(node.path),
|
name: (node: SnapshotNode) => last(node.path),
|
||||||
value: (node: SnapshotNode) => node.metric.value || 0,
|
value: (node: SnapshotNode) => {
|
||||||
|
const metric = first(node.metrics);
|
||||||
|
return (metric && metric.value) || 0;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const sortNodes = (sort: WaffleSortOption, nodes: SnapshotNode[]) => {
|
export const sortNodes = (sort: WaffleSortOption, nodes: SnapshotNode[]) => {
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
import { mapValues, last } from 'lodash';
|
import { mapValues, last, first } from 'lodash';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import {
|
import {
|
||||||
InfraDatabaseSearchResponse,
|
InfraDatabaseSearchResponse,
|
||||||
|
@ -95,7 +95,7 @@ const getData = async (
|
||||||
nodeType,
|
nodeType,
|
||||||
groupBy: [],
|
groupBy: [],
|
||||||
sourceConfiguration,
|
sourceConfiguration,
|
||||||
metric: { type: metric },
|
metrics: [{ type: metric }],
|
||||||
timerange,
|
timerange,
|
||||||
includeTimeseries: Boolean(timerange.lookbackSize),
|
includeTimeseries: Boolean(timerange.lookbackSize),
|
||||||
};
|
};
|
||||||
|
@ -104,12 +104,13 @@ const getData = async (
|
||||||
|
|
||||||
return nodes.reduce((acc, n) => {
|
return nodes.reduce((acc, n) => {
|
||||||
const nodePathItem = last(n.path);
|
const nodePathItem = last(n.path);
|
||||||
if (n.metric?.value && n.metric?.timeseries) {
|
const m = first(n.metrics);
|
||||||
const { timeseries } = n.metric;
|
if (m && m.value && m.timeseries) {
|
||||||
|
const { timeseries } = m;
|
||||||
const values = timeseries.rows.map((row) => row.metric_0) as Array<number | null>;
|
const values = timeseries.rows.map((row) => row.metric_0) as Array<number | null>;
|
||||||
acc[nodePathItem.label] = values;
|
acc[nodePathItem.label] = values;
|
||||||
} else {
|
} else {
|
||||||
acc[nodePathItem.label] = n.metric && n.metric.value;
|
acc[nodePathItem.label] = m && m.value;
|
||||||
}
|
}
|
||||||
return acc;
|
return acc;
|
||||||
}, {} as Record<string, number | Array<number | string | null | undefined> | undefined | null>);
|
}, {} as Record<string, number | Array<number | string | null | undefined> | undefined | null>);
|
||||||
|
|
|
@ -54,14 +54,18 @@ export const getMetricsSources = (options: InfraSnapshotRequestOptions) => {
|
||||||
return [{ id: { terms: { field: fields.id } } }];
|
return [{ id: { terms: { field: fields.id } } }];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const metricToAggregation = (nodeType: InventoryItemType, metric: SnapshotMetricInput) => {
|
export const metricToAggregation = (
|
||||||
|
nodeType: InventoryItemType,
|
||||||
|
metric: SnapshotMetricInput,
|
||||||
|
index: number
|
||||||
|
) => {
|
||||||
const inventoryModel = findInventoryModel(nodeType);
|
const inventoryModel = findInventoryModel(nodeType);
|
||||||
if (SnapshotCustomMetricInputRT.is(metric)) {
|
if (SnapshotCustomMetricInputRT.is(metric)) {
|
||||||
if (metric.aggregation === 'rate') {
|
if (metric.aggregation === 'rate') {
|
||||||
return networkTraffic(metric.type, metric.field);
|
return networkTraffic(`custom_${index}`, metric.field);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
custom: {
|
[`custom_${index}`]: {
|
||||||
[metric.aggregation]: {
|
[metric.aggregation]: {
|
||||||
field: metric.field,
|
field: metric.field,
|
||||||
},
|
},
|
||||||
|
@ -72,19 +76,22 @@ export const metricToAggregation = (nodeType: InventoryItemType, metric: Snapsho
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getMetricsAggregations = (options: InfraSnapshotRequestOptions): SnapshotModel => {
|
export const getMetricsAggregations = (options: InfraSnapshotRequestOptions): SnapshotModel => {
|
||||||
const aggregation = metricToAggregation(options.nodeType, options.metric);
|
const { metrics } = options;
|
||||||
if (!SnapshotModelRT.is(aggregation)) {
|
return metrics.reduce((aggs, metric, index) => {
|
||||||
throw new Error(
|
const aggregation = metricToAggregation(options.nodeType, metric, index);
|
||||||
i18n.translate('xpack.infra.snapshot.missingSnapshotMetricError', {
|
if (!SnapshotModelRT.is(aggregation)) {
|
||||||
defaultMessage: 'The aggregation for {metric} for {nodeType} is not available.',
|
throw new Error(
|
||||||
values: {
|
i18n.translate('xpack.infra.snapshot.missingSnapshotMetricError', {
|
||||||
nodeType: options.nodeType,
|
defaultMessage: 'The aggregation for {metric} for {nodeType} is not available.',
|
||||||
metric: options.metric.type,
|
values: {
|
||||||
},
|
nodeType: options.nodeType,
|
||||||
})
|
metric: metric.type,
|
||||||
);
|
},
|
||||||
}
|
})
|
||||||
return aggregation;
|
);
|
||||||
|
}
|
||||||
|
return { ...aggs, ...aggregation };
|
||||||
|
}, {});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getDateHistogramOffset = (from: number, interval: string): string => {
|
export const getDateHistogramOffset = (from: number, interval: string): string => {
|
||||||
|
|
|
@ -118,26 +118,28 @@ export const getNodeMetricsForLookup = (
|
||||||
export const getNodeMetrics = (
|
export const getNodeMetrics = (
|
||||||
nodeBuckets: InfraSnapshotMetricsBucket[],
|
nodeBuckets: InfraSnapshotMetricsBucket[],
|
||||||
options: InfraSnapshotRequestOptions
|
options: InfraSnapshotRequestOptions
|
||||||
): SnapshotNodeMetric => {
|
): SnapshotNodeMetric[] => {
|
||||||
if (!nodeBuckets) {
|
if (!nodeBuckets) {
|
||||||
return {
|
return options.metrics.map((metric) => ({
|
||||||
name: options.metric.type,
|
name: metric.type,
|
||||||
value: null,
|
value: null,
|
||||||
max: null,
|
max: null,
|
||||||
avg: null,
|
avg: null,
|
||||||
};
|
}));
|
||||||
}
|
}
|
||||||
const lastBucket = findLastFullBucket(nodeBuckets, options);
|
const lastBucket = findLastFullBucket(nodeBuckets, options);
|
||||||
const result: SnapshotNodeMetric = {
|
return options.metrics.map((metric, index) => {
|
||||||
name: options.metric.type,
|
const metricResult: SnapshotNodeMetric = {
|
||||||
value: getMetricValueFromBucket(options.metric.type, lastBucket),
|
name: metric.type,
|
||||||
max: calculateMax(nodeBuckets, options.metric.type),
|
value: getMetricValueFromBucket(metric.type, lastBucket, index),
|
||||||
avg: calculateAvg(nodeBuckets, options.metric.type),
|
max: calculateMax(nodeBuckets, metric.type, index),
|
||||||
};
|
avg: calculateAvg(nodeBuckets, metric.type, index),
|
||||||
if (options.includeTimeseries) {
|
};
|
||||||
result.timeseries = getTimeseriesData(nodeBuckets, options.metric.type);
|
if (options.includeTimeseries) {
|
||||||
}
|
metricResult.timeseries = getTimeseriesData(nodeBuckets, metric.type, index);
|
||||||
return result;
|
}
|
||||||
|
return metricResult;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const findLastFullBucket = (
|
const findLastFullBucket = (
|
||||||
|
@ -156,22 +158,39 @@ const findLastFullBucket = (
|
||||||
}, last(buckets));
|
}, last(buckets));
|
||||||
};
|
};
|
||||||
|
|
||||||
const getMetricValueFromBucket = (type: SnapshotMetricType, bucket: InfraSnapshotMetricsBucket) => {
|
const getMetricValueFromBucket = (
|
||||||
const metric = bucket[type];
|
type: SnapshotMetricType,
|
||||||
|
bucket: InfraSnapshotMetricsBucket,
|
||||||
|
index: number
|
||||||
|
) => {
|
||||||
|
const key = type === 'custom' ? `custom_${index}` : type;
|
||||||
|
const metric = bucket[key];
|
||||||
return (metric && (metric.normalized_value || metric.value)) || 0;
|
return (metric && (metric.normalized_value || metric.value)) || 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
function calculateMax(buckets: InfraSnapshotMetricsBucket[], type: SnapshotMetricType) {
|
function calculateMax(
|
||||||
return max(buckets.map((bucket) => getMetricValueFromBucket(type, bucket))) || 0;
|
buckets: InfraSnapshotMetricsBucket[],
|
||||||
|
type: SnapshotMetricType,
|
||||||
|
index: number
|
||||||
|
) {
|
||||||
|
return max(buckets.map((bucket) => getMetricValueFromBucket(type, bucket, index))) || 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculateAvg(buckets: InfraSnapshotMetricsBucket[], type: SnapshotMetricType) {
|
function calculateAvg(
|
||||||
return sum(buckets.map((bucket) => getMetricValueFromBucket(type, bucket))) / buckets.length || 0;
|
buckets: InfraSnapshotMetricsBucket[],
|
||||||
|
type: SnapshotMetricType,
|
||||||
|
index: number
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
sum(buckets.map((bucket) => getMetricValueFromBucket(type, bucket, index))) / buckets.length ||
|
||||||
|
0
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTimeseriesData(
|
function getTimeseriesData(
|
||||||
buckets: InfraSnapshotMetricsBucket[],
|
buckets: InfraSnapshotMetricsBucket[],
|
||||||
type: SnapshotMetricType
|
type: SnapshotMetricType,
|
||||||
|
index: number
|
||||||
): MetricsExplorerSeries {
|
): MetricsExplorerSeries {
|
||||||
return {
|
return {
|
||||||
id: type,
|
id: type,
|
||||||
|
@ -181,7 +200,7 @@ function getTimeseriesData(
|
||||||
],
|
],
|
||||||
rows: buckets.map((bucket) => ({
|
rows: buckets.map((bucket) => ({
|
||||||
timestamp: bucket.key as number,
|
timestamp: bucket.key as number,
|
||||||
metric_0: getMetricValueFromBucket(type, bucket),
|
metric_0: getMetricValueFromBucket(type, bucket, index),
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,15 +110,22 @@ const requestGroupedNodes = async (
|
||||||
>(callClusterFactory(client), query, bucketSelector, handleAfterKey);
|
>(callClusterFactory(client), query, bucketSelector, handleAfterKey);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const calculateIndexPatterBasedOnMetrics = (options: InfraSnapshotRequestOptions) => {
|
||||||
|
const { metrics } = options;
|
||||||
|
if (metrics.every((m) => m.type === 'logRate')) {
|
||||||
|
return options.sourceConfiguration.logAlias;
|
||||||
|
}
|
||||||
|
if (metrics.some((m) => m.type === 'logRate')) {
|
||||||
|
return `${options.sourceConfiguration.logAlias},${options.sourceConfiguration.metricAlias}`;
|
||||||
|
}
|
||||||
|
return options.sourceConfiguration.metricAlias;
|
||||||
|
};
|
||||||
|
|
||||||
const requestNodeMetrics = async (
|
const requestNodeMetrics = async (
|
||||||
client: ESSearchClient,
|
client: ESSearchClient,
|
||||||
options: InfraSnapshotRequestOptions
|
options: InfraSnapshotRequestOptions
|
||||||
): Promise<InfraSnapshotNodeMetricsBucket[]> => {
|
): Promise<InfraSnapshotNodeMetricsBucket[]> => {
|
||||||
const index =
|
const index = calculateIndexPatterBasedOnMetrics(options);
|
||||||
options.metric.type === 'logRate'
|
|
||||||
? `${options.sourceConfiguration.logAlias}`
|
|
||||||
: `${options.sourceConfiguration.metricAlias}`;
|
|
||||||
|
|
||||||
const query = {
|
const query = {
|
||||||
allowNoIndices: true,
|
allowNoIndices: true,
|
||||||
index,
|
index,
|
||||||
|
@ -179,7 +186,7 @@ const mergeNodeBuckets = (
|
||||||
return nodeGroupByBuckets.map((node) => {
|
return nodeGroupByBuckets.map((node) => {
|
||||||
return {
|
return {
|
||||||
path: getNodePath(node, options),
|
path: getNodePath(node, options),
|
||||||
metric: getNodeMetrics(nodeMetricsForLookup[node.key.id], options),
|
metrics: getNodeMetrics(nodeMetricsForLookup[node.key.id], options),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -35,7 +35,7 @@ export const initSnapshotRoute = (libs: InfraBackendLibs) => {
|
||||||
nodeType,
|
nodeType,
|
||||||
groupBy,
|
groupBy,
|
||||||
sourceId,
|
sourceId,
|
||||||
metric,
|
metrics,
|
||||||
timerange,
|
timerange,
|
||||||
accountId,
|
accountId,
|
||||||
region,
|
region,
|
||||||
|
@ -56,7 +56,7 @@ export const initSnapshotRoute = (libs: InfraBackendLibs) => {
|
||||||
nodeType,
|
nodeType,
|
||||||
groupBy,
|
groupBy,
|
||||||
sourceConfiguration: source.configuration,
|
sourceConfiguration: source.configuration,
|
||||||
metric,
|
metrics,
|
||||||
timerange,
|
timerange,
|
||||||
includeTimeseries,
|
includeTimeseries,
|
||||||
};
|
};
|
||||||
|
|
|
@ -47,7 +47,7 @@ export default function ({ getService }: FtrProviderContext) {
|
||||||
from: min,
|
from: min,
|
||||||
interval: '1m',
|
interval: '1m',
|
||||||
},
|
},
|
||||||
metric: { type: 'cpu' } as InfraSnapshotMetricInput,
|
metrics: [{ type: 'cpu' }] as InfraSnapshotMetricInput[],
|
||||||
nodeType: 'container' as InfraNodeType,
|
nodeType: 'container' as InfraNodeType,
|
||||||
groupBy: [],
|
groupBy: [],
|
||||||
});
|
});
|
||||||
|
@ -68,13 +68,15 @@ export default function ({ getService }: FtrProviderContext) {
|
||||||
'242fddb9d376bbf0e38025d81764847ee5ec0308adfa095918fd3266f9d06c6a'
|
'242fddb9d376bbf0e38025d81764847ee5ec0308adfa095918fd3266f9d06c6a'
|
||||||
);
|
);
|
||||||
expect(first(firstNode.path)).to.have.property('label', 'docker-autodiscovery_nginx_1');
|
expect(first(firstNode.path)).to.have.property('label', 'docker-autodiscovery_nginx_1');
|
||||||
expect(firstNode).to.have.property('metric');
|
expect(firstNode).to.have.property('metrics');
|
||||||
expect(firstNode.metric).to.eql({
|
expect(firstNode.metrics).to.eql([
|
||||||
name: 'cpu',
|
{
|
||||||
value: 0,
|
name: 'cpu',
|
||||||
max: 0,
|
value: 0,
|
||||||
avg: 0,
|
max: 0,
|
||||||
});
|
avg: 0,
|
||||||
|
},
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -93,7 +95,7 @@ export default function ({ getService }: FtrProviderContext) {
|
||||||
from: min,
|
from: min,
|
||||||
interval: '1m',
|
interval: '1m',
|
||||||
},
|
},
|
||||||
metric: { type: 'cpu' } as InfraSnapshotMetricInput,
|
metrics: [{ type: 'cpu' }] as InfraSnapshotMetricInput[],
|
||||||
nodeType: 'pod' as InfraNodeType,
|
nodeType: 'pod' as InfraNodeType,
|
||||||
groupBy: [],
|
groupBy: [],
|
||||||
});
|
});
|
||||||
|
@ -125,7 +127,7 @@ export default function ({ getService }: FtrProviderContext) {
|
||||||
from: min,
|
from: min,
|
||||||
interval: '1m',
|
interval: '1m',
|
||||||
},
|
},
|
||||||
metric: { type: 'cpu' } as InfraSnapshotMetricInput,
|
metrics: [{ type: 'cpu' }] as InfraSnapshotMetricInput[],
|
||||||
nodeType: 'container' as InfraNodeType,
|
nodeType: 'container' as InfraNodeType,
|
||||||
groupBy: [],
|
groupBy: [],
|
||||||
});
|
});
|
||||||
|
@ -164,7 +166,7 @@ export default function ({ getService }: FtrProviderContext) {
|
||||||
from: min,
|
from: min,
|
||||||
interval: '1m',
|
interval: '1m',
|
||||||
},
|
},
|
||||||
metric: { type: 'cpu' } as InfraSnapshotMetricInput,
|
metrics: [{ type: 'cpu' }] as InfraSnapshotMetricInput[],
|
||||||
nodeType: 'host' as InfraNodeType,
|
nodeType: 'host' as InfraNodeType,
|
||||||
groupBy: [],
|
groupBy: [],
|
||||||
});
|
});
|
||||||
|
@ -179,13 +181,15 @@ export default function ({ getService }: FtrProviderContext) {
|
||||||
expect(firstNode.path.length).to.equal(1);
|
expect(firstNode.path.length).to.equal(1);
|
||||||
expect(first(firstNode.path)).to.have.property('value', 'demo-stack-mysql-01');
|
expect(first(firstNode.path)).to.have.property('value', 'demo-stack-mysql-01');
|
||||||
expect(first(firstNode.path)).to.have.property('label', 'demo-stack-mysql-01');
|
expect(first(firstNode.path)).to.have.property('label', 'demo-stack-mysql-01');
|
||||||
expect(firstNode).to.have.property('metric');
|
expect(firstNode).to.have.property('metrics');
|
||||||
expect(firstNode.metric).to.eql({
|
expect(firstNode.metrics).to.eql([
|
||||||
name: 'cpu',
|
{
|
||||||
value: 0.0032,
|
name: 'cpu',
|
||||||
max: 0.0038333333333333336,
|
value: 0.0032,
|
||||||
avg: 0.0027944444444444444,
|
max: 0.0038333333333333336,
|
||||||
});
|
avg: 0.0027944444444444444,
|
||||||
|
},
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -200,7 +204,7 @@ export default function ({ getService }: FtrProviderContext) {
|
||||||
forceInterval: true,
|
forceInterval: true,
|
||||||
ignoreLookback: true,
|
ignoreLookback: true,
|
||||||
},
|
},
|
||||||
metric: { type: 'cpu' } as InfraSnapshotMetricInput,
|
metrics: [{ type: 'cpu' }] as InfraSnapshotMetricInput[],
|
||||||
nodeType: 'host' as InfraNodeType,
|
nodeType: 'host' as InfraNodeType,
|
||||||
groupBy: [],
|
groupBy: [],
|
||||||
includeTimeseries: true,
|
includeTimeseries: true,
|
||||||
|
@ -216,10 +220,10 @@ export default function ({ getService }: FtrProviderContext) {
|
||||||
expect(firstNode.path.length).to.equal(1);
|
expect(firstNode.path.length).to.equal(1);
|
||||||
expect(first(firstNode.path)).to.have.property('value', 'demo-stack-mysql-01');
|
expect(first(firstNode.path)).to.have.property('value', 'demo-stack-mysql-01');
|
||||||
expect(first(firstNode.path)).to.have.property('label', 'demo-stack-mysql-01');
|
expect(first(firstNode.path)).to.have.property('label', 'demo-stack-mysql-01');
|
||||||
expect(firstNode).to.have.property('metric');
|
expect(firstNode).to.have.property('metrics');
|
||||||
expect(firstNode.metric).to.have.property('timeseries');
|
expect(firstNode.metrics[0]).to.have.property('timeseries');
|
||||||
expect(firstNode.metric.timeseries?.rows.length).to.equal(58);
|
expect(firstNode.metrics[0].timeseries?.rows.length).to.equal(58);
|
||||||
const rows = firstNode.metric.timeseries?.rows;
|
const rows = firstNode.metrics[0].timeseries?.rows;
|
||||||
const rowInterval = (rows?.[1]?.timestamp || 0) - (rows?.[0]?.timestamp || 0);
|
const rowInterval = (rows?.[1]?.timestamp || 0) - (rows?.[0]?.timestamp || 0);
|
||||||
expect(rowInterval).to.equal(10000);
|
expect(rowInterval).to.equal(10000);
|
||||||
}
|
}
|
||||||
|
@ -235,7 +239,7 @@ export default function ({ getService }: FtrProviderContext) {
|
||||||
interval: '1m',
|
interval: '1m',
|
||||||
lookbackSize: 6,
|
lookbackSize: 6,
|
||||||
},
|
},
|
||||||
metric: { type: 'cpu' } as InfraSnapshotMetricInput,
|
metrics: [{ type: 'cpu' }] as InfraSnapshotMetricInput[],
|
||||||
nodeType: 'host' as InfraNodeType,
|
nodeType: 'host' as InfraNodeType,
|
||||||
groupBy: [],
|
groupBy: [],
|
||||||
includeTimeseries: true,
|
includeTimeseries: true,
|
||||||
|
@ -251,9 +255,9 @@ export default function ({ getService }: FtrProviderContext) {
|
||||||
expect(firstNode.path.length).to.equal(1);
|
expect(firstNode.path.length).to.equal(1);
|
||||||
expect(first(firstNode.path)).to.have.property('value', 'demo-stack-mysql-01');
|
expect(first(firstNode.path)).to.have.property('value', 'demo-stack-mysql-01');
|
||||||
expect(first(firstNode.path)).to.have.property('label', 'demo-stack-mysql-01');
|
expect(first(firstNode.path)).to.have.property('label', 'demo-stack-mysql-01');
|
||||||
expect(firstNode).to.have.property('metric');
|
expect(firstNode).to.have.property('metrics');
|
||||||
expect(firstNode.metric).to.have.property('timeseries');
|
expect(firstNode.metrics[0]).to.have.property('timeseries');
|
||||||
expect(firstNode.metric.timeseries?.rows.length).to.equal(7);
|
expect(firstNode.metrics[0].timeseries?.rows.length).to.equal(7);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -266,12 +270,14 @@ export default function ({ getService }: FtrProviderContext) {
|
||||||
from: min,
|
from: min,
|
||||||
interval: '1m',
|
interval: '1m',
|
||||||
},
|
},
|
||||||
metric: {
|
metrics: [
|
||||||
type: 'custom',
|
{
|
||||||
field: 'system.cpu.user.pct',
|
type: 'custom',
|
||||||
aggregation: 'avg',
|
field: 'system.cpu.user.pct',
|
||||||
id: '1',
|
aggregation: 'avg',
|
||||||
} as SnapshotMetricInput,
|
id: '1',
|
||||||
|
},
|
||||||
|
] as SnapshotMetricInput[],
|
||||||
nodeType: 'host' as InfraNodeType,
|
nodeType: 'host' as InfraNodeType,
|
||||||
groupBy: [],
|
groupBy: [],
|
||||||
});
|
});
|
||||||
|
@ -286,13 +292,15 @@ export default function ({ getService }: FtrProviderContext) {
|
||||||
expect(firstNode.path.length).to.equal(1);
|
expect(firstNode.path.length).to.equal(1);
|
||||||
expect(first(firstNode.path)).to.have.property('value', 'demo-stack-mysql-01');
|
expect(first(firstNode.path)).to.have.property('value', 'demo-stack-mysql-01');
|
||||||
expect(first(firstNode.path)).to.have.property('label', 'demo-stack-mysql-01');
|
expect(first(firstNode.path)).to.have.property('label', 'demo-stack-mysql-01');
|
||||||
expect(firstNode).to.have.property('metric');
|
expect(firstNode).to.have.property('metrics');
|
||||||
expect(firstNode.metric).to.eql({
|
expect(firstNode.metrics).to.eql([
|
||||||
name: 'custom',
|
{
|
||||||
value: 0.0016,
|
name: 'custom',
|
||||||
max: 0.0018333333333333333,
|
value: 0.0016,
|
||||||
avg: 0.0013666666666666669,
|
max: 0.0018333333333333333,
|
||||||
});
|
avg: 0.0013666666666666669,
|
||||||
|
},
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -304,7 +312,7 @@ export default function ({ getService }: FtrProviderContext) {
|
||||||
from: min,
|
from: min,
|
||||||
interval: '1m',
|
interval: '1m',
|
||||||
},
|
},
|
||||||
metric: { type: 'cpu' } as InfraSnapshotMetricInput,
|
metrics: [{ type: 'cpu' }] as InfraSnapshotMetricInput[],
|
||||||
nodeType: 'host' as InfraNodeType,
|
nodeType: 'host' as InfraNodeType,
|
||||||
groupBy: [{ field: 'cloud.availability_zone' }],
|
groupBy: [{ field: 'cloud.availability_zone' }],
|
||||||
});
|
});
|
||||||
|
@ -331,7 +339,7 @@ export default function ({ getService }: FtrProviderContext) {
|
||||||
from: min,
|
from: min,
|
||||||
interval: '1m',
|
interval: '1m',
|
||||||
},
|
},
|
||||||
metric: { type: 'cpu' } as InfraSnapshotMetricInput,
|
metrics: [{ type: 'cpu' }] as InfraSnapshotMetricInput[],
|
||||||
nodeType: 'host' as InfraNodeType,
|
nodeType: 'host' as InfraNodeType,
|
||||||
groupBy: [{ field: 'cloud.provider' }, { field: 'cloud.availability_zone' }],
|
groupBy: [{ field: 'cloud.provider' }, { field: 'cloud.availability_zone' }],
|
||||||
});
|
});
|
||||||
|
@ -360,7 +368,7 @@ export default function ({ getService }: FtrProviderContext) {
|
||||||
from: min,
|
from: min,
|
||||||
interval: '1m',
|
interval: '1m',
|
||||||
},
|
},
|
||||||
metric: { type: 'cpu' } as InfraSnapshotMetricInput,
|
metrics: [{ type: 'cpu' }] as InfraSnapshotMetricInput[],
|
||||||
nodeType: 'host' as InfraNodeType,
|
nodeType: 'host' as InfraNodeType,
|
||||||
groupBy: [{ field: 'service.type' }],
|
groupBy: [{ field: 'service.type' }],
|
||||||
});
|
});
|
||||||
|
@ -375,25 +383,29 @@ export default function ({ getService }: FtrProviderContext) {
|
||||||
expect(firstNode.path.length).to.equal(2);
|
expect(firstNode.path.length).to.equal(2);
|
||||||
expect(firstNode.path[0]).to.have.property('value', 'mysql');
|
expect(firstNode.path[0]).to.have.property('value', 'mysql');
|
||||||
expect(firstNode.path[1]).to.have.property('value', 'demo-stack-mysql-01');
|
expect(firstNode.path[1]).to.have.property('value', 'demo-stack-mysql-01');
|
||||||
expect(firstNode).to.have.property('metric');
|
expect(firstNode).to.have.property('metrics');
|
||||||
expect(firstNode.metric).to.eql({
|
expect(firstNode.metrics).to.eql([
|
||||||
name: 'cpu',
|
{
|
||||||
value: 0.0032,
|
name: 'cpu',
|
||||||
max: 0.0038333333333333336,
|
value: 0.0032,
|
||||||
avg: 0.0027944444444444444,
|
max: 0.0038333333333333336,
|
||||||
});
|
avg: 0.0027944444444444444,
|
||||||
|
},
|
||||||
|
]);
|
||||||
const secondNode = nodes[1];
|
const secondNode = nodes[1];
|
||||||
expect(secondNode).to.have.property('path');
|
expect(secondNode).to.have.property('path');
|
||||||
expect(secondNode.path.length).to.equal(2);
|
expect(secondNode.path.length).to.equal(2);
|
||||||
expect(secondNode.path[0]).to.have.property('value', 'system');
|
expect(secondNode.path[0]).to.have.property('value', 'system');
|
||||||
expect(secondNode.path[1]).to.have.property('value', 'demo-stack-mysql-01');
|
expect(secondNode.path[1]).to.have.property('value', 'demo-stack-mysql-01');
|
||||||
expect(secondNode).to.have.property('metric');
|
expect(secondNode).to.have.property('metrics');
|
||||||
expect(secondNode.metric).to.eql({
|
expect(secondNode.metrics).to.eql([
|
||||||
name: 'cpu',
|
{
|
||||||
value: 0.0032,
|
name: 'cpu',
|
||||||
max: 0.0038333333333333336,
|
value: 0.0032,
|
||||||
avg: 0.0027944444444444444,
|
max: 0.0038333333333333336,
|
||||||
});
|
avg: 0.0027944444444444444,
|
||||||
|
},
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue