[Metrics UI] Add inventory view timeline (#77804)
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
0f8bbf11f4
commit
a0f03ddbd1
|
@ -99,7 +99,7 @@ export const SnapshotRequestRT = rt.intersection([
|
||||||
rt.type({
|
rt.type({
|
||||||
timerange: InfraTimerangeInputRT,
|
timerange: InfraTimerangeInputRT,
|
||||||
metrics: rt.array(SnapshotMetricInputRT),
|
metrics: rt.array(SnapshotMetricInputRT),
|
||||||
groupBy: SnapshotGroupByRT,
|
groupBy: rt.union([SnapshotGroupByRT, rt.null]),
|
||||||
nodeType: ItemTypeRT,
|
nodeType: ItemTypeRT,
|
||||||
sourceId: rt.string,
|
sourceId: rt.string,
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -5,36 +5,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { SnapshotMetricType } from './types';
|
import { toMetricOpt } from '../snapshot_metric_i18n';
|
||||||
export const CPUUsage = i18n.translate('xpack.infra.waffle.metricOptions.cpuUsageText', {
|
import { SnapshotMetricType, SnapshotMetricTypeKeys } from './types';
|
||||||
defaultMessage: 'CPU usage',
|
|
||||||
});
|
|
||||||
|
|
||||||
export const MemoryUsage = i18n.translate('xpack.infra.waffle.metricOptions.memoryUsageText', {
|
|
||||||
defaultMessage: 'Memory usage',
|
|
||||||
});
|
|
||||||
|
|
||||||
export const InboundTraffic = i18n.translate(
|
|
||||||
'xpack.infra.waffle.metricOptions.inboundTrafficText',
|
|
||||||
{
|
|
||||||
defaultMessage: 'Inbound traffic',
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
export const OutboundTraffic = i18n.translate(
|
|
||||||
'xpack.infra.waffle.metricOptions.outboundTrafficText',
|
|
||||||
{
|
|
||||||
defaultMessage: 'Outbound traffic',
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
export const LogRate = i18n.translate('xpack.infra.waffle.metricOptions.hostLogRateText', {
|
|
||||||
defaultMessage: 'Log rate',
|
|
||||||
});
|
|
||||||
|
|
||||||
export const Load = i18n.translate('xpack.infra.waffle.metricOptions.loadText', {
|
|
||||||
defaultMessage: 'Load',
|
|
||||||
});
|
|
||||||
|
|
||||||
interface Lookup {
|
interface Lookup {
|
||||||
[id: string]: string;
|
[id: string]: string;
|
||||||
|
@ -70,80 +42,9 @@ export const fieldToName = (field: string) => {
|
||||||
return LOOKUP[field] || field;
|
return LOOKUP[field] || field;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SNAPSHOT_METRIC_TRANSLATIONS = {
|
const snapshotTypeKeys = Object.keys(SnapshotMetricTypeKeys) as SnapshotMetricType[];
|
||||||
cpu: i18n.translate('xpack.infra.waffle.metricOptions.cpuUsageText', {
|
export const SNAPSHOT_METRIC_TRANSLATIONS = snapshotTypeKeys.reduce((result, metric) => {
|
||||||
defaultMessage: 'CPU usage',
|
const text = toMetricOpt(metric)?.text;
|
||||||
}),
|
if (text) return { ...result, [metric]: text };
|
||||||
|
return result;
|
||||||
memory: i18n.translate('xpack.infra.waffle.metricOptions.memoryUsageText', {
|
}, {}) as Record<SnapshotMetricType, string>;
|
||||||
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>;
|
|
||||||
|
|
|
@ -314,7 +314,7 @@ export const ESAggregationRT = rt.union([
|
||||||
export const MetricsUIAggregationRT = rt.record(rt.string, ESAggregationRT);
|
export const MetricsUIAggregationRT = rt.record(rt.string, ESAggregationRT);
|
||||||
export type MetricsUIAggregation = rt.TypeOf<typeof MetricsUIAggregationRT>;
|
export type MetricsUIAggregation = rt.TypeOf<typeof MetricsUIAggregationRT>;
|
||||||
|
|
||||||
export const SnapshotMetricTypeRT = rt.keyof({
|
export const SnapshotMetricTypeKeys = {
|
||||||
count: null,
|
count: null,
|
||||||
cpu: null,
|
cpu: null,
|
||||||
load: null,
|
load: null,
|
||||||
|
@ -339,7 +339,8 @@ export const SnapshotMetricTypeRT = rt.keyof({
|
||||||
sqsMessagesEmpty: null,
|
sqsMessagesEmpty: null,
|
||||||
sqsOldestMessage: null,
|
sqsOldestMessage: null,
|
||||||
custom: null,
|
custom: null,
|
||||||
});
|
};
|
||||||
|
export const SnapshotMetricTypeRT = rt.keyof(SnapshotMetricTypeKeys);
|
||||||
|
|
||||||
export type SnapshotMetricType = rt.TypeOf<typeof SnapshotMetricTypeRT>;
|
export type SnapshotMetricType = rt.TypeOf<typeof SnapshotMetricTypeRT>;
|
||||||
|
|
||||||
|
|
|
@ -4,204 +4,235 @@
|
||||||
* 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 { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
|
import { mapValues } from 'lodash';
|
||||||
import { SnapshotMetricType } from './inventory_models/types';
|
import { SnapshotMetricType } from './inventory_models/types';
|
||||||
|
|
||||||
const Translations = {
|
// Lowercase versions of all metrics, for when they need to be used in the middle of a sentence;
|
||||||
|
// these may need to be translated differently depending on language, e.g. still capitalizing "CPU"
|
||||||
|
const TranslationsLowercase = {
|
||||||
CPUUsage: i18n.translate('xpack.infra.waffle.metricOptions.cpuUsageText', {
|
CPUUsage: i18n.translate('xpack.infra.waffle.metricOptions.cpuUsageText', {
|
||||||
defaultMessage: 'CPU usage',
|
defaultMessage: 'CPU usage',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
MemoryUsage: i18n.translate('xpack.infra.waffle.metricOptions.memoryUsageText', {
|
MemoryUsage: i18n.translate('xpack.infra.waffle.metricOptions.memoryUsageText', {
|
||||||
defaultMessage: 'Memory usage',
|
defaultMessage: 'memory usage',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
InboundTraffic: i18n.translate('xpack.infra.waffle.metricOptions.inboundTrafficText', {
|
InboundTraffic: i18n.translate('xpack.infra.waffle.metricOptions.inboundTrafficText', {
|
||||||
defaultMessage: 'Inbound traffic',
|
defaultMessage: 'inbound traffic',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
OutboundTraffic: i18n.translate('xpack.infra.waffle.metricOptions.outboundTrafficText', {
|
OutboundTraffic: i18n.translate('xpack.infra.waffle.metricOptions.outboundTrafficText', {
|
||||||
defaultMessage: 'Outbound traffic',
|
defaultMessage: 'outbound traffic',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
LogRate: i18n.translate('xpack.infra.waffle.metricOptions.hostLogRateText', {
|
LogRate: i18n.translate('xpack.infra.waffle.metricOptions.hostLogRateText', {
|
||||||
defaultMessage: 'Log rate',
|
defaultMessage: 'log rate',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
Load: i18n.translate('xpack.infra.waffle.metricOptions.loadText', {
|
Load: i18n.translate('xpack.infra.waffle.metricOptions.loadText', {
|
||||||
defaultMessage: 'Load',
|
defaultMessage: 'load',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
Count: i18n.translate('xpack.infra.waffle.metricOptions.countText', {
|
Count: i18n.translate('xpack.infra.waffle.metricOptions.countText', {
|
||||||
defaultMessage: 'Count',
|
defaultMessage: 'count',
|
||||||
}),
|
}),
|
||||||
DiskIOReadBytes: i18n.translate('xpack.infra.waffle.metricOptions.diskIOReadBytes', {
|
DiskIOReadBytes: i18n.translate('xpack.infra.waffle.metricOptions.diskIOReadBytes', {
|
||||||
defaultMessage: 'Disk Reads',
|
defaultMessage: 'disk reads',
|
||||||
}),
|
}),
|
||||||
DiskIOWriteBytes: i18n.translate('xpack.infra.waffle.metricOptions.diskIOWriteBytes', {
|
DiskIOWriteBytes: i18n.translate('xpack.infra.waffle.metricOptions.diskIOWriteBytes', {
|
||||||
defaultMessage: 'Disk Writes',
|
defaultMessage: 'disk writes',
|
||||||
}),
|
}),
|
||||||
s3BucketSize: i18n.translate('xpack.infra.waffle.metricOptions.s3BucketSize', {
|
s3BucketSize: i18n.translate('xpack.infra.waffle.metricOptions.s3BucketSize', {
|
||||||
defaultMessage: 'Bucket Size',
|
defaultMessage: 'bucket size',
|
||||||
}),
|
}),
|
||||||
s3TotalRequests: i18n.translate('xpack.infra.waffle.metricOptions.s3TotalRequests', {
|
s3TotalRequests: i18n.translate('xpack.infra.waffle.metricOptions.s3TotalRequests', {
|
||||||
defaultMessage: 'Total Requests',
|
defaultMessage: 'total requests',
|
||||||
}),
|
}),
|
||||||
s3NumberOfObjects: i18n.translate('xpack.infra.waffle.metricOptions.s3NumberOfObjects', {
|
s3NumberOfObjects: i18n.translate('xpack.infra.waffle.metricOptions.s3NumberOfObjects', {
|
||||||
defaultMessage: 'Number of Objects',
|
defaultMessage: 'number of objects',
|
||||||
}),
|
}),
|
||||||
s3DownloadBytes: i18n.translate('xpack.infra.waffle.metricOptions.s3DownloadBytes', {
|
s3DownloadBytes: i18n.translate('xpack.infra.waffle.metricOptions.s3DownloadBytes', {
|
||||||
defaultMessage: 'Downloads (Bytes)',
|
defaultMessage: 'downloads (bytes)',
|
||||||
}),
|
}),
|
||||||
s3UploadBytes: i18n.translate('xpack.infra.waffle.metricOptions.s3UploadBytes', {
|
s3UploadBytes: i18n.translate('xpack.infra.waffle.metricOptions.s3UploadBytes', {
|
||||||
defaultMessage: 'Uploads (Bytes)',
|
defaultMessage: 'uploads (bytes)',
|
||||||
}),
|
}),
|
||||||
rdsConnections: i18n.translate('xpack.infra.waffle.metricOptions.rdsConnections', {
|
rdsConnections: i18n.translate('xpack.infra.waffle.metricOptions.rdsConnections', {
|
||||||
defaultMessage: 'Connections',
|
defaultMessage: 'connections',
|
||||||
}),
|
}),
|
||||||
rdsQueriesExecuted: i18n.translate('xpack.infra.waffle.metricOptions.rdsQueriesExecuted', {
|
rdsQueriesExecuted: i18n.translate('xpack.infra.waffle.metricOptions.rdsQueriesExecuted', {
|
||||||
defaultMessage: 'Queries Executed',
|
defaultMessage: 'queries executed',
|
||||||
}),
|
}),
|
||||||
rdsActiveTransactions: i18n.translate('xpack.infra.waffle.metricOptions.rdsActiveTransactions', {
|
rdsActiveTransactions: i18n.translate('xpack.infra.waffle.metricOptions.rdsActiveTransactions', {
|
||||||
defaultMessage: 'Active Transactions',
|
defaultMessage: 'active transactions',
|
||||||
}),
|
}),
|
||||||
rdsLatency: i18n.translate('xpack.infra.waffle.metricOptions.rdsLatency', {
|
rdsLatency: i18n.translate('xpack.infra.waffle.metricOptions.rdsLatency', {
|
||||||
defaultMessage: 'Latency',
|
defaultMessage: 'latency',
|
||||||
}),
|
}),
|
||||||
sqsMessagesVisible: i18n.translate('xpack.infra.waffle.metricOptions.sqsMessagesVisible', {
|
sqsMessagesVisible: i18n.translate('xpack.infra.waffle.metricOptions.sqsMessagesVisible', {
|
||||||
defaultMessage: 'Messages Available',
|
defaultMessage: 'messages available',
|
||||||
}),
|
}),
|
||||||
sqsMessagesDelayed: i18n.translate('xpack.infra.waffle.metricOptions.sqsMessagesDelayed', {
|
sqsMessagesDelayed: i18n.translate('xpack.infra.waffle.metricOptions.sqsMessagesDelayed', {
|
||||||
defaultMessage: 'Messages Delayed',
|
defaultMessage: 'messages delayed',
|
||||||
}),
|
}),
|
||||||
sqsMessagesSent: i18n.translate('xpack.infra.waffle.metricOptions.sqsMessagesSent', {
|
sqsMessagesSent: i18n.translate('xpack.infra.waffle.metricOptions.sqsMessagesSent', {
|
||||||
defaultMessage: 'Messages Added',
|
defaultMessage: 'messages added',
|
||||||
}),
|
}),
|
||||||
sqsMessagesEmpty: i18n.translate('xpack.infra.waffle.metricOptions.sqsMessagesEmpty', {
|
sqsMessagesEmpty: i18n.translate('xpack.infra.waffle.metricOptions.sqsMessagesEmpty', {
|
||||||
defaultMessage: 'Messages Returned Empty',
|
defaultMessage: 'messages returned empty',
|
||||||
}),
|
}),
|
||||||
sqsOldestMessage: i18n.translate('xpack.infra.waffle.metricOptions.sqsOldestMessage', {
|
sqsOldestMessage: i18n.translate('xpack.infra.waffle.metricOptions.sqsOldestMessage', {
|
||||||
defaultMessage: 'Oldest Message',
|
defaultMessage: 'oldest message',
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const Translations = mapValues(
|
||||||
|
TranslationsLowercase,
|
||||||
|
(translation) => `${translation[0].toUpperCase()}${translation.slice(1)}`
|
||||||
|
);
|
||||||
|
|
||||||
export const toMetricOpt = (
|
export const toMetricOpt = (
|
||||||
metric: SnapshotMetricType
|
metric: SnapshotMetricType
|
||||||
): { text: string; value: SnapshotMetricType } | undefined => {
|
): { text: string; textLC: string; value: SnapshotMetricType } | undefined => {
|
||||||
switch (metric) {
|
switch (metric) {
|
||||||
case 'cpu':
|
case 'cpu':
|
||||||
return {
|
return {
|
||||||
text: Translations.CPUUsage,
|
text: Translations.CPUUsage,
|
||||||
|
textLC: TranslationsLowercase.CPUUsage,
|
||||||
value: 'cpu',
|
value: 'cpu',
|
||||||
};
|
};
|
||||||
case 'memory':
|
case 'memory':
|
||||||
return {
|
return {
|
||||||
text: Translations.MemoryUsage,
|
text: Translations.MemoryUsage,
|
||||||
|
textLC: TranslationsLowercase.MemoryUsage,
|
||||||
value: 'memory',
|
value: 'memory',
|
||||||
};
|
};
|
||||||
case 'rx':
|
case 'rx':
|
||||||
return {
|
return {
|
||||||
text: Translations.InboundTraffic,
|
text: Translations.InboundTraffic,
|
||||||
|
textLC: TranslationsLowercase.InboundTraffic,
|
||||||
value: 'rx',
|
value: 'rx',
|
||||||
};
|
};
|
||||||
case 'tx':
|
case 'tx':
|
||||||
return {
|
return {
|
||||||
text: Translations.OutboundTraffic,
|
text: Translations.OutboundTraffic,
|
||||||
|
textLC: TranslationsLowercase.OutboundTraffic,
|
||||||
value: 'tx',
|
value: 'tx',
|
||||||
};
|
};
|
||||||
case 'logRate':
|
case 'logRate':
|
||||||
return {
|
return {
|
||||||
text: Translations.LogRate,
|
text: Translations.LogRate,
|
||||||
|
textLC: TranslationsLowercase.LogRate,
|
||||||
value: 'logRate',
|
value: 'logRate',
|
||||||
};
|
};
|
||||||
case 'load':
|
case 'load':
|
||||||
return {
|
return {
|
||||||
text: Translations.Load,
|
text: Translations.Load,
|
||||||
|
textLC: TranslationsLowercase.Load,
|
||||||
value: 'load',
|
value: 'load',
|
||||||
};
|
};
|
||||||
|
|
||||||
case 'count':
|
case 'count':
|
||||||
return {
|
return {
|
||||||
text: Translations.Count,
|
text: Translations.Count,
|
||||||
|
textLC: TranslationsLowercase.Count,
|
||||||
value: 'count',
|
value: 'count',
|
||||||
};
|
};
|
||||||
case 'diskIOReadBytes':
|
case 'diskIOReadBytes':
|
||||||
return {
|
return {
|
||||||
text: Translations.DiskIOReadBytes,
|
text: Translations.DiskIOReadBytes,
|
||||||
|
textLC: TranslationsLowercase.DiskIOReadBytes,
|
||||||
value: 'diskIOReadBytes',
|
value: 'diskIOReadBytes',
|
||||||
};
|
};
|
||||||
case 'diskIOWriteBytes':
|
case 'diskIOWriteBytes':
|
||||||
return {
|
return {
|
||||||
text: Translations.DiskIOWriteBytes,
|
text: Translations.DiskIOWriteBytes,
|
||||||
|
textLC: TranslationsLowercase.DiskIOWriteBytes,
|
||||||
value: 'diskIOWriteBytes',
|
value: 'diskIOWriteBytes',
|
||||||
};
|
};
|
||||||
case 's3BucketSize':
|
case 's3BucketSize':
|
||||||
return {
|
return {
|
||||||
text: Translations.s3BucketSize,
|
text: Translations.s3BucketSize,
|
||||||
|
textLC: TranslationsLowercase.s3BucketSize,
|
||||||
value: 's3BucketSize',
|
value: 's3BucketSize',
|
||||||
};
|
};
|
||||||
case 's3TotalRequests':
|
case 's3TotalRequests':
|
||||||
return {
|
return {
|
||||||
text: Translations.s3TotalRequests,
|
text: Translations.s3TotalRequests,
|
||||||
|
textLC: TranslationsLowercase.s3TotalRequests,
|
||||||
value: 's3TotalRequests',
|
value: 's3TotalRequests',
|
||||||
};
|
};
|
||||||
case 's3NumberOfObjects':
|
case 's3NumberOfObjects':
|
||||||
return {
|
return {
|
||||||
text: Translations.s3NumberOfObjects,
|
text: Translations.s3NumberOfObjects,
|
||||||
|
textLC: TranslationsLowercase.s3NumberOfObjects,
|
||||||
value: 's3NumberOfObjects',
|
value: 's3NumberOfObjects',
|
||||||
};
|
};
|
||||||
case 's3DownloadBytes':
|
case 's3DownloadBytes':
|
||||||
return {
|
return {
|
||||||
text: Translations.s3DownloadBytes,
|
text: Translations.s3DownloadBytes,
|
||||||
|
textLC: TranslationsLowercase.s3DownloadBytes,
|
||||||
value: 's3DownloadBytes',
|
value: 's3DownloadBytes',
|
||||||
};
|
};
|
||||||
case 's3UploadBytes':
|
case 's3UploadBytes':
|
||||||
return {
|
return {
|
||||||
text: Translations.s3UploadBytes,
|
text: Translations.s3UploadBytes,
|
||||||
|
textLC: TranslationsLowercase.s3UploadBytes,
|
||||||
value: 's3UploadBytes',
|
value: 's3UploadBytes',
|
||||||
};
|
};
|
||||||
case 'rdsConnections':
|
case 'rdsConnections':
|
||||||
return {
|
return {
|
||||||
text: Translations.rdsConnections,
|
text: Translations.rdsConnections,
|
||||||
|
textLC: TranslationsLowercase.rdsConnections,
|
||||||
value: 'rdsConnections',
|
value: 'rdsConnections',
|
||||||
};
|
};
|
||||||
case 'rdsQueriesExecuted':
|
case 'rdsQueriesExecuted':
|
||||||
return {
|
return {
|
||||||
text: Translations.rdsQueriesExecuted,
|
text: Translations.rdsQueriesExecuted,
|
||||||
|
textLC: TranslationsLowercase.rdsQueriesExecuted,
|
||||||
value: 'rdsQueriesExecuted',
|
value: 'rdsQueriesExecuted',
|
||||||
};
|
};
|
||||||
case 'rdsActiveTransactions':
|
case 'rdsActiveTransactions':
|
||||||
return {
|
return {
|
||||||
text: Translations.rdsActiveTransactions,
|
text: Translations.rdsActiveTransactions,
|
||||||
|
textLC: TranslationsLowercase.rdsActiveTransactions,
|
||||||
value: 'rdsActiveTransactions',
|
value: 'rdsActiveTransactions',
|
||||||
};
|
};
|
||||||
case 'rdsLatency':
|
case 'rdsLatency':
|
||||||
return {
|
return {
|
||||||
text: Translations.rdsLatency,
|
text: Translations.rdsLatency,
|
||||||
|
textLC: TranslationsLowercase.rdsLatency,
|
||||||
value: 'rdsLatency',
|
value: 'rdsLatency',
|
||||||
};
|
};
|
||||||
case 'sqsMessagesVisible':
|
case 'sqsMessagesVisible':
|
||||||
return {
|
return {
|
||||||
text: Translations.sqsMessagesVisible,
|
text: Translations.sqsMessagesVisible,
|
||||||
|
textLC: TranslationsLowercase.sqsMessagesVisible,
|
||||||
value: 'sqsMessagesVisible',
|
value: 'sqsMessagesVisible',
|
||||||
};
|
};
|
||||||
case 'sqsMessagesDelayed':
|
case 'sqsMessagesDelayed':
|
||||||
return {
|
return {
|
||||||
text: Translations.sqsMessagesDelayed,
|
text: Translations.sqsMessagesDelayed,
|
||||||
|
textLC: TranslationsLowercase.sqsMessagesDelayed,
|
||||||
value: 'sqsMessagesDelayed',
|
value: 'sqsMessagesDelayed',
|
||||||
};
|
};
|
||||||
case 'sqsMessagesSent':
|
case 'sqsMessagesSent':
|
||||||
return {
|
return {
|
||||||
text: Translations.sqsMessagesSent,
|
text: Translations.sqsMessagesSent,
|
||||||
|
textLC: TranslationsLowercase.sqsMessagesSent,
|
||||||
value: 'sqsMessagesSent',
|
value: 'sqsMessagesSent',
|
||||||
};
|
};
|
||||||
case 'sqsMessagesEmpty':
|
case 'sqsMessagesEmpty':
|
||||||
return {
|
return {
|
||||||
text: Translations.sqsMessagesEmpty,
|
text: Translations.sqsMessagesEmpty,
|
||||||
|
textLC: TranslationsLowercase.sqsMessagesEmpty,
|
||||||
value: 'sqsMessagesEmpty',
|
value: 'sqsMessagesEmpty',
|
||||||
};
|
};
|
||||||
case 'sqsOldestMessage':
|
case 'sqsOldestMessage':
|
||||||
return {
|
return {
|
||||||
text: Translations.sqsOldestMessage,
|
text: Translations.sqsOldestMessage,
|
||||||
|
textLC: TranslationsLowercase.sqsOldestMessage,
|
||||||
value: 'sqsOldestMessage',
|
value: 'sqsOldestMessage',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,10 +13,9 @@ import {
|
||||||
EuiDescriptionListTitle,
|
EuiDescriptionListTitle,
|
||||||
EuiDescriptionListDescription,
|
EuiDescriptionListDescription,
|
||||||
} from '@elastic/eui';
|
} from '@elastic/eui';
|
||||||
import { EuiPopover } from '@elastic/eui';
|
import { EuiPopover, EuiLink } from '@elastic/eui';
|
||||||
import { EuiListGroup, EuiListGroupItem } from '@elastic/eui';
|
import { EuiListGroup, EuiListGroupItem } from '@elastic/eui';
|
||||||
import { EuiFlexItem } from '@elastic/eui';
|
import { EuiFlexItem } from '@elastic/eui';
|
||||||
import { EuiButtonIcon } from '@elastic/eui';
|
|
||||||
import { SavedViewCreateModal } from './create_modal';
|
import { SavedViewCreateModal } from './create_modal';
|
||||||
import { SavedViewUpdateModal } from './update_modal';
|
import { SavedViewUpdateModal } from './update_modal';
|
||||||
import { SavedViewManageViewsFlyout } from './manage_views_flyout';
|
import { SavedViewManageViewsFlyout } from './manage_views_flyout';
|
||||||
|
@ -151,15 +150,6 @@ export function SavedViewsToolbarControls<ViewState>(props: Props<ViewState>) {
|
||||||
<EuiPopover
|
<EuiPopover
|
||||||
button={
|
button={
|
||||||
<EuiFlexGroup gutterSize={'s'} alignItems="center">
|
<EuiFlexGroup gutterSize={'s'} alignItems="center">
|
||||||
<EuiFlexItem grow={false}>
|
|
||||||
<EuiButtonIcon
|
|
||||||
aria-label={i18n.translate('xpack.infra.savedView.changeView', {
|
|
||||||
defaultMessage: 'Change view',
|
|
||||||
})}
|
|
||||||
onClick={showSavedViewMenu}
|
|
||||||
iconType="globe"
|
|
||||||
/>
|
|
||||||
</EuiFlexItem>
|
|
||||||
<EuiFlexItem>
|
<EuiFlexItem>
|
||||||
<EuiDescriptionList
|
<EuiDescriptionList
|
||||||
style={{ cursor: 'pointer' }}
|
style={{ cursor: 'pointer' }}
|
||||||
|
@ -172,13 +162,15 @@ export function SavedViewsToolbarControls<ViewState>(props: Props<ViewState>) {
|
||||||
id="xpack.infra.savedView.currentView"
|
id="xpack.infra.savedView.currentView"
|
||||||
/>
|
/>
|
||||||
</EuiDescriptionListTitle>
|
</EuiDescriptionListTitle>
|
||||||
<EuiDescriptionListDescription>
|
<EuiLink>
|
||||||
{currentView
|
<EuiDescriptionListDescription>
|
||||||
? currentView.name
|
{currentView
|
||||||
: i18n.translate('xpack.infra.savedView.unknownView', {
|
? currentView.name
|
||||||
defaultMessage: 'No view selected',
|
: i18n.translate('xpack.infra.savedView.unknownView', {
|
||||||
})}
|
defaultMessage: 'No view selected',
|
||||||
</EuiDescriptionListDescription>
|
})}
|
||||||
|
</EuiDescriptionListDescription>
|
||||||
|
</EuiLink>
|
||||||
</EuiDescriptionList>
|
</EuiDescriptionList>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
</EuiFlexGroup>
|
</EuiFlexGroup>
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { useCallback, useState } from 'react';
|
||||||
|
import { i18n } from '@kbn/i18n';
|
||||||
|
import { EuiFlexGroup, EuiFlexItem, EuiButtonEmpty, EuiSpacer } from '@elastic/eui';
|
||||||
|
|
||||||
|
import { euiStyled, useUiTracker } from '../../../../../../observability/public';
|
||||||
|
import { InfraFormatter } from '../../../../lib/lib';
|
||||||
|
import { Timeline } from './timeline/timeline';
|
||||||
|
|
||||||
|
const showHistory = i18n.translate('xpack.infra.showHistory', {
|
||||||
|
defaultMessage: 'Show history',
|
||||||
|
});
|
||||||
|
const hideHistory = i18n.translate('xpack.infra.hideHistory', {
|
||||||
|
defaultMessage: 'Hide history',
|
||||||
|
});
|
||||||
|
|
||||||
|
const TRANSITION_MS = 300;
|
||||||
|
|
||||||
|
export const BottomDrawer: React.FC<{
|
||||||
|
measureRef: (instance: HTMLElement | null) => void;
|
||||||
|
interval: string;
|
||||||
|
formatter: InfraFormatter;
|
||||||
|
}> = ({ measureRef, interval, formatter, children }) => {
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
||||||
|
const trackDrawerOpen = useUiTracker({ app: 'infra_metrics' });
|
||||||
|
const onClick = useCallback(() => {
|
||||||
|
if (!isOpen) trackDrawerOpen({ metric: 'open_timeline_drawer__inventory' });
|
||||||
|
setIsOpen(!isOpen);
|
||||||
|
}, [isOpen, trackDrawerOpen]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BottomActionContainer ref={isOpen ? measureRef : null} isOpen={isOpen}>
|
||||||
|
<BottomActionTopBar ref={isOpen ? null : measureRef}>
|
||||||
|
<EuiFlexItem grow={false}>
|
||||||
|
<ShowHideButton iconType={isOpen ? 'arrowDown' : 'arrowRight'} onClick={onClick}>
|
||||||
|
{isOpen ? hideHistory : showHistory}
|
||||||
|
</ShowHideButton>
|
||||||
|
</EuiFlexItem>
|
||||||
|
<EuiFlexItem
|
||||||
|
grow={false}
|
||||||
|
style={{
|
||||||
|
position: 'relative',
|
||||||
|
minWidth: 400,
|
||||||
|
alignSelf: 'center',
|
||||||
|
height: '16px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</EuiFlexItem>
|
||||||
|
<EuiFlexItem grow={false}>
|
||||||
|
<EuiSpacer size="xs" />
|
||||||
|
</EuiFlexItem>
|
||||||
|
</BottomActionTopBar>
|
||||||
|
<EuiFlexGroup style={{ marginTop: 0 }}>
|
||||||
|
<Timeline isVisible={isOpen} interval={interval} yAxisFormatter={formatter} />
|
||||||
|
</EuiFlexGroup>
|
||||||
|
</BottomActionContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const BottomActionContainer = euiStyled.div<{ isOpen: boolean }>`
|
||||||
|
padding: ${(props) => props.theme.eui.paddingSizes.m} 0;
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
transition: transform ${TRANSITION_MS}ms;
|
||||||
|
transform: translateY(${(props) => (props.isOpen ? 0 : '224px')})
|
||||||
|
`;
|
||||||
|
|
||||||
|
const BottomActionTopBar = euiStyled(EuiFlexGroup).attrs({
|
||||||
|
justifyContent: 'spaceBetween',
|
||||||
|
alignItems: 'center',
|
||||||
|
})`
|
||||||
|
margin-bottom: 0;
|
||||||
|
height: 48px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ShowHideButton = euiStyled(EuiButtonEmpty).attrs({ size: 's' })`
|
||||||
|
width: 140px;
|
||||||
|
`;
|
|
@ -7,7 +7,7 @@
|
||||||
import React, { useCallback, useEffect } from 'react';
|
import React, { useCallback, useEffect } from 'react';
|
||||||
import { useInterval } from 'react-use';
|
import { useInterval } from 'react-use';
|
||||||
|
|
||||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
|
||||||
import { AutoSizer } from '../../../../components/auto_sizer';
|
import { AutoSizer } from '../../../../components/auto_sizer';
|
||||||
import { convertIntervalToString } from '../../../../utils/convert_interval_to_string';
|
import { convertIntervalToString } from '../../../../utils/convert_interval_to_string';
|
||||||
import { NodesOverview } from './nodes_overview';
|
import { NodesOverview } from './nodes_overview';
|
||||||
|
@ -23,12 +23,13 @@ import { euiStyled } from '../../../../../../observability/public';
|
||||||
import { Toolbar } from './toolbars/toolbar';
|
import { Toolbar } from './toolbars/toolbar';
|
||||||
import { ViewSwitcher } from './waffle/view_switcher';
|
import { ViewSwitcher } from './waffle/view_switcher';
|
||||||
import { IntervalLabel } from './waffle/interval_label';
|
import { IntervalLabel } from './waffle/interval_label';
|
||||||
import { Legend } from './waffle/legend';
|
|
||||||
import { createInventoryMetricFormatter } from '../lib/create_inventory_metric_formatter';
|
import { createInventoryMetricFormatter } from '../lib/create_inventory_metric_formatter';
|
||||||
import { createLegend } from '../lib/create_legend';
|
import { createLegend } from '../lib/create_legend';
|
||||||
import { useSavedViewContext } from '../../../../containers/saved_view/saved_view';
|
import { useSavedViewContext } from '../../../../containers/saved_view/saved_view';
|
||||||
import { useWaffleViewState } from '../hooks/use_waffle_view_state';
|
import { useWaffleViewState } from '../hooks/use_waffle_view_state';
|
||||||
import { SavedViewsToolbarControls } from '../../../../components/saved_views/toolbar_control';
|
import { SavedViewsToolbarControls } from '../../../../components/saved_views/toolbar_control';
|
||||||
|
import { BottomDrawer } from './bottom_drawer';
|
||||||
|
import { Legend } from './waffle/legend';
|
||||||
|
|
||||||
export const Layout = () => {
|
export const Layout = () => {
|
||||||
const { sourceId, source } = useSourceContext();
|
const { sourceId, source } = useSourceContext();
|
||||||
|
@ -104,12 +105,19 @@ export const Layout = () => {
|
||||||
<PageContent>
|
<PageContent>
|
||||||
<MainContainer>
|
<MainContainer>
|
||||||
<TopActionContainer>
|
<TopActionContainer>
|
||||||
<EuiFlexGroup justifyContent="spaceBetween" gutterSize="m">
|
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center" gutterSize="m">
|
||||||
<Toolbar nodeType={nodeType} />
|
<Toolbar nodeType={nodeType} />
|
||||||
|
<EuiFlexItem grow={false}>
|
||||||
|
<IntervalLabel intervalAsString={intervalAsString} />
|
||||||
|
</EuiFlexItem>
|
||||||
<EuiFlexItem grow={false}>
|
<EuiFlexItem grow={false}>
|
||||||
<ViewSwitcher view={view} onChange={changeView} />
|
<ViewSwitcher view={view} onChange={changeView} />
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
</EuiFlexGroup>
|
</EuiFlexGroup>
|
||||||
|
<EuiSpacer />
|
||||||
|
<SavedViewContainer>
|
||||||
|
<SavedViewsToolbarControls viewState={viewState} />
|
||||||
|
</SavedViewContainer>
|
||||||
</TopActionContainer>
|
</TopActionContainer>
|
||||||
<AutoSizer bounds>
|
<AutoSizer bounds>
|
||||||
{({ measureRef, bounds: { height = 0 } }) => (
|
{({ measureRef, bounds: { height = 0 } }) => (
|
||||||
|
@ -128,24 +136,14 @@ export const Layout = () => {
|
||||||
formatter={formatter}
|
formatter={formatter}
|
||||||
bottomMargin={height}
|
bottomMargin={height}
|
||||||
/>
|
/>
|
||||||
<BottomActionContainer ref={measureRef}>
|
<BottomDrawer measureRef={measureRef} interval={interval} formatter={formatter}>
|
||||||
<EuiFlexGroup justifyContent="spaceBetween">
|
<Legend
|
||||||
<EuiFlexItem grow={false}>
|
formatter={formatter}
|
||||||
<SavedViewsToolbarControls viewState={viewState} />
|
bounds={bounds}
|
||||||
</EuiFlexItem>
|
dataBounds={dataBounds}
|
||||||
<EuiFlexItem grow={false} style={{ position: 'relative', minWidth: 400 }}>
|
legend={options.legend}
|
||||||
<Legend
|
/>
|
||||||
formatter={formatter}
|
</BottomDrawer>
|
||||||
bounds={bounds}
|
|
||||||
dataBounds={dataBounds}
|
|
||||||
legend={options.legend}
|
|
||||||
/>
|
|
||||||
</EuiFlexItem>
|
|
||||||
<EuiFlexItem grow={false}>
|
|
||||||
<IntervalLabel intervalAsString={intervalAsString} />
|
|
||||||
</EuiFlexItem>
|
|
||||||
</EuiFlexGroup>
|
|
||||||
</BottomActionContainer>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</AutoSizer>
|
</AutoSizer>
|
||||||
|
@ -164,12 +162,8 @@ const TopActionContainer = euiStyled.div`
|
||||||
padding: ${(props) => `12px ${props.theme.eui.paddingSizes.m}`};
|
padding: ${(props) => `12px ${props.theme.eui.paddingSizes.m}`};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const BottomActionContainer = euiStyled.div`
|
const SavedViewContainer = euiStyled.div`
|
||||||
background-color: ${(props) => props.theme.eui.euiPageBackgroundColor};
|
position: relative;
|
||||||
padding: ${(props) => props.theme.eui.paddingSizes.m} ${(props) =>
|
z-index: 1;
|
||||||
props.theme.eui.paddingSizes.m};
|
padding-left: ${(props) => props.theme.eui.paddingSizes.m};
|
||||||
position: fixed;
|
|
||||||
left: 0;
|
|
||||||
bottom: 0;
|
|
||||||
right: 0;
|
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -0,0 +1,228 @@
|
||||||
|
/*
|
||||||
|
* 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, { useMemo, useCallback } from 'react';
|
||||||
|
import { i18n } from '@kbn/i18n';
|
||||||
|
import { FormattedMessage } from '@kbn/i18n/react';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { first, last } from 'lodash';
|
||||||
|
import { EuiLoadingChart, EuiText, EuiEmptyPrompt, EuiButton } from '@elastic/eui';
|
||||||
|
import {
|
||||||
|
Axis,
|
||||||
|
Chart,
|
||||||
|
Settings,
|
||||||
|
Position,
|
||||||
|
TooltipValue,
|
||||||
|
niceTimeFormatter,
|
||||||
|
ElementClickListener,
|
||||||
|
} from '@elastic/charts';
|
||||||
|
import { useUiSetting } from '../../../../../../../../../src/plugins/kibana_react/public';
|
||||||
|
import { toMetricOpt } from '../../../../../../common/snapshot_metric_i18n';
|
||||||
|
import { MetricsExplorerAggregation } from '../../../../../../common/http_api';
|
||||||
|
import { Color } from '../../../../../../common/color_palette';
|
||||||
|
import { useSourceContext } from '../../../../../containers/source';
|
||||||
|
import { useTimeline } from '../../hooks/use_timeline';
|
||||||
|
import { useWaffleOptionsContext } from '../../hooks/use_waffle_options';
|
||||||
|
import { useWaffleTimeContext } from '../../hooks/use_waffle_time';
|
||||||
|
import { useWaffleFiltersContext } from '../../hooks/use_waffle_filters';
|
||||||
|
import { MetricExplorerSeriesChart } from '../../../metrics_explorer/components/series_chart';
|
||||||
|
import { MetricsExplorerChartType } from '../../../metrics_explorer/hooks/use_metrics_explorer_options';
|
||||||
|
import { getTimelineChartTheme } from '../../../metrics_explorer/components/helpers/get_chart_theme';
|
||||||
|
import { calculateDomain } from '../../../metrics_explorer/components/helpers/calculate_domain';
|
||||||
|
|
||||||
|
import { euiStyled } from '../../../../../../../observability/public';
|
||||||
|
import { InfraFormatter } from '../../../../../lib/lib';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
interval: string;
|
||||||
|
yAxisFormatter: InfraFormatter;
|
||||||
|
isVisible: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Timeline: React.FC<Props> = ({ interval, yAxisFormatter, isVisible }) => {
|
||||||
|
const { sourceId } = useSourceContext();
|
||||||
|
const { metric, nodeType, accountId, region } = useWaffleOptionsContext();
|
||||||
|
const { currentTime, jumpToTime, stopAutoReload } = useWaffleTimeContext();
|
||||||
|
const { filterQueryAsJson } = useWaffleFiltersContext();
|
||||||
|
const { loading, error, timeseries, reload } = useTimeline(
|
||||||
|
filterQueryAsJson,
|
||||||
|
[metric],
|
||||||
|
nodeType,
|
||||||
|
sourceId,
|
||||||
|
currentTime,
|
||||||
|
accountId,
|
||||||
|
region,
|
||||||
|
interval,
|
||||||
|
isVisible
|
||||||
|
);
|
||||||
|
|
||||||
|
const metricLabel = toMetricOpt(metric.type)?.textLC;
|
||||||
|
|
||||||
|
const chartMetric = {
|
||||||
|
color: Color.color0,
|
||||||
|
aggregation: 'avg' as MetricsExplorerAggregation,
|
||||||
|
label: metricLabel,
|
||||||
|
};
|
||||||
|
|
||||||
|
const dateFormatter = useMemo(() => {
|
||||||
|
if (!timeseries) return () => '';
|
||||||
|
const firstTimestamp = first(timeseries.rows)?.timestamp;
|
||||||
|
const lastTimestamp = last(timeseries.rows)?.timestamp;
|
||||||
|
|
||||||
|
if (firstTimestamp == null || lastTimestamp == null) {
|
||||||
|
return (value: number) => `${value}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return niceTimeFormatter([firstTimestamp, lastTimestamp]);
|
||||||
|
}, [timeseries]);
|
||||||
|
|
||||||
|
const isDarkMode = useUiSetting<boolean>('theme:darkMode');
|
||||||
|
const tooltipProps = {
|
||||||
|
headerFormatter: (tooltipValue: TooltipValue) =>
|
||||||
|
moment(tooltipValue.value).format('Y-MM-DD HH:mm:ss.SSS'),
|
||||||
|
};
|
||||||
|
|
||||||
|
const dataDomain = timeseries ? calculateDomain(timeseries, [chartMetric], false) : null;
|
||||||
|
const domain = dataDomain
|
||||||
|
? {
|
||||||
|
max: dataDomain.max * 1.1, // add 10% headroom.
|
||||||
|
min: dataDomain.min,
|
||||||
|
}
|
||||||
|
: { max: 0, min: 0 };
|
||||||
|
|
||||||
|
const onClickPoint: ElementClickListener = useCallback(
|
||||||
|
([[geometryValue]]) => {
|
||||||
|
if (!Array.isArray(geometryValue)) {
|
||||||
|
const { x: timestamp } = geometryValue;
|
||||||
|
jumpToTime(timestamp);
|
||||||
|
stopAutoReload();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[jumpToTime, stopAutoReload]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<TimelineContainer>
|
||||||
|
<TimelineLoadingContainer>
|
||||||
|
<EuiLoadingChart size="xl" />
|
||||||
|
</TimelineLoadingContainer>
|
||||||
|
</TimelineContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!loading && (error || !timeseries)) {
|
||||||
|
return (
|
||||||
|
<TimelineContainer>
|
||||||
|
<EuiEmptyPrompt
|
||||||
|
iconType="visArea"
|
||||||
|
title={<h4>{error ? errorTitle : noHistoryDataTitle}</h4>}
|
||||||
|
actions={
|
||||||
|
<EuiButton color="primary" fill onClick={reload}>
|
||||||
|
{error ? retryButtonLabel : checkNewDataButtonLabel}
|
||||||
|
</EuiButton>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</TimelineContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TimelineContainer>
|
||||||
|
<TimelineHeader>
|
||||||
|
<EuiText>
|
||||||
|
<strong>
|
||||||
|
<FormattedMessage
|
||||||
|
id="xpack.infra.inventoryTimeline.header"
|
||||||
|
defaultMessage="Average {metricLabel}"
|
||||||
|
values={{ metricLabel }}
|
||||||
|
/>
|
||||||
|
</strong>
|
||||||
|
</EuiText>
|
||||||
|
</TimelineHeader>
|
||||||
|
<TimelineChartContainer>
|
||||||
|
<Chart>
|
||||||
|
<MetricExplorerSeriesChart
|
||||||
|
type={MetricsExplorerChartType.area}
|
||||||
|
metric={chartMetric}
|
||||||
|
id="0"
|
||||||
|
series={timeseries!}
|
||||||
|
stack={false}
|
||||||
|
/>
|
||||||
|
<Axis
|
||||||
|
id={'timestamp'}
|
||||||
|
position={Position.Bottom}
|
||||||
|
showOverlappingTicks={true}
|
||||||
|
tickFormat={dateFormatter}
|
||||||
|
/>
|
||||||
|
<Axis
|
||||||
|
id={'values'}
|
||||||
|
position={Position.Left}
|
||||||
|
tickFormat={yAxisFormatter}
|
||||||
|
domain={domain}
|
||||||
|
ticks={6}
|
||||||
|
showGridLines
|
||||||
|
/>
|
||||||
|
<Settings
|
||||||
|
tooltip={tooltipProps}
|
||||||
|
theme={getTimelineChartTheme(isDarkMode)}
|
||||||
|
onElementClick={onClickPoint}
|
||||||
|
/>
|
||||||
|
</Chart>
|
||||||
|
</TimelineChartContainer>
|
||||||
|
</TimelineContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const TimelineContainer = euiStyled.div`
|
||||||
|
background-color: ${(props) => props.theme.eui.euiPageBackgroundColor};
|
||||||
|
border-top: 1px solid ${(props) => props.theme.eui.euiColorLightShade};
|
||||||
|
height: 220px;
|
||||||
|
width: 100%;
|
||||||
|
padding: ${(props) => props.theme.eui.paddingSizes.s} ${(props) =>
|
||||||
|
props.theme.eui.paddingSizes.m};
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const TimelineHeader = euiStyled.div`
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
padding: ${(props) => props.theme.eui.paddingSizes.s} ${(props) =>
|
||||||
|
props.theme.eui.paddingSizes.m};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const TimelineChartContainer = euiStyled.div`
|
||||||
|
padding-left: ${(props) => props.theme.eui.paddingSizes.xs};
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const TimelineLoadingContainer = euiStyled.div`
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100%;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const noHistoryDataTitle = i18n.translate('xpack.infra.inventoryTimeline.noHistoryDataTitle', {
|
||||||
|
defaultMessage: 'There is no history data to display.',
|
||||||
|
});
|
||||||
|
|
||||||
|
const errorTitle = i18n.translate('xpack.infra.inventoryTimeline.errorTitle', {
|
||||||
|
defaultMessage: 'Unable to display history data.',
|
||||||
|
});
|
||||||
|
|
||||||
|
const checkNewDataButtonLabel = i18n.translate(
|
||||||
|
'xpack.infra.inventoryTimeline.checkNewDataButtonLabel',
|
||||||
|
{
|
||||||
|
defaultMessage: 'Check for new data',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const retryButtonLabel = i18n.translate('xpack.infra.inventoryTimeline.retryButtonLabel', {
|
||||||
|
defaultMessage: 'Try again',
|
||||||
|
});
|
|
@ -22,7 +22,7 @@ export const IntervalLabel = ({ intervalAsString }: Props) => {
|
||||||
<p>
|
<p>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="xpack.infra.homePage.toolbar.showingLastOneMinuteDataText"
|
id="xpack.infra.homePage.toolbar.showingLastOneMinuteDataText"
|
||||||
defaultMessage="Last {duration} of data for the selected time"
|
defaultMessage="Last {duration} of data"
|
||||||
values={{ duration: intervalAsString }}
|
values={{ duration: intervalAsString }}
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -43,7 +43,7 @@ export function useSnapshot(
|
||||||
const timerange: InfraTimerangeInput = {
|
const timerange: InfraTimerangeInput = {
|
||||||
interval: '1m',
|
interval: '1m',
|
||||||
to: currentTime,
|
to: currentTime,
|
||||||
from: currentTime - 360 * 1000,
|
from: currentTime - 1200 * 1000,
|
||||||
lookbackSize: 20,
|
lookbackSize: 20,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,121 @@
|
||||||
|
/*
|
||||||
|
* 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 { fold } from 'fp-ts/lib/Either';
|
||||||
|
import { identity } from 'fp-ts/lib/function';
|
||||||
|
import { pipe } from 'fp-ts/lib/pipeable';
|
||||||
|
import { first } from 'lodash';
|
||||||
|
import { useEffect, useMemo, useCallback } from 'react';
|
||||||
|
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||||
|
import { getIntervalInSeconds } from '../../../../../server/utils/get_interval_in_seconds';
|
||||||
|
import { throwErrors, createPlainError } from '../../../../../common/runtime_types';
|
||||||
|
import { useHTTPRequest } from '../../../../hooks/use_http_request';
|
||||||
|
import {
|
||||||
|
SnapshotNodeResponseRT,
|
||||||
|
SnapshotNodeResponse,
|
||||||
|
SnapshotRequest,
|
||||||
|
InfraTimerangeInput,
|
||||||
|
} from '../../../../../common/http_api/snapshot_api';
|
||||||
|
import {
|
||||||
|
InventoryItemType,
|
||||||
|
SnapshotMetricType,
|
||||||
|
} from '../../../../../common/inventory_models/types';
|
||||||
|
|
||||||
|
const ONE_MINUTE = 60;
|
||||||
|
const ONE_HOUR = ONE_MINUTE * 60;
|
||||||
|
const ONE_DAY = ONE_HOUR * 24;
|
||||||
|
const ONE_WEEK = ONE_DAY * 7;
|
||||||
|
|
||||||
|
const getTimeLengthFromInterval = (interval: string | undefined) => {
|
||||||
|
if (interval) {
|
||||||
|
const intervalInSeconds = getIntervalInSeconds(interval);
|
||||||
|
const multiplier =
|
||||||
|
intervalInSeconds < ONE_MINUTE
|
||||||
|
? ONE_HOUR / intervalInSeconds
|
||||||
|
: intervalInSeconds < ONE_HOUR
|
||||||
|
? 60
|
||||||
|
: intervalInSeconds < ONE_DAY
|
||||||
|
? 7
|
||||||
|
: intervalInSeconds < ONE_WEEK
|
||||||
|
? 30
|
||||||
|
: 1;
|
||||||
|
const timeLength = intervalInSeconds * multiplier;
|
||||||
|
return { timeLength, intervalInSeconds };
|
||||||
|
} else {
|
||||||
|
return { timeLength: 0, intervalInSeconds: 0 };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useTimeline(
|
||||||
|
filterQuery: string | null | undefined,
|
||||||
|
metrics: Array<{ type: SnapshotMetricType }>,
|
||||||
|
nodeType: InventoryItemType,
|
||||||
|
sourceId: string,
|
||||||
|
currentTime: number,
|
||||||
|
accountId: string,
|
||||||
|
region: string,
|
||||||
|
interval: string | undefined,
|
||||||
|
shouldReload: boolean
|
||||||
|
) {
|
||||||
|
const decodeResponse = (response: any) => {
|
||||||
|
return pipe(
|
||||||
|
SnapshotNodeResponseRT.decode(response),
|
||||||
|
fold(throwErrors(createPlainError), identity)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const timeLengthResult = useMemo(() => getTimeLengthFromInterval(interval), [interval]);
|
||||||
|
const { timeLength, intervalInSeconds } = timeLengthResult;
|
||||||
|
|
||||||
|
const timerange: InfraTimerangeInput = {
|
||||||
|
interval: interval ?? '',
|
||||||
|
to: currentTime + intervalInSeconds * 1000,
|
||||||
|
from: currentTime - timeLength * 1000,
|
||||||
|
lookbackSize: 0,
|
||||||
|
ignoreLookback: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const { error, loading, response, makeRequest } = useHTTPRequest<SnapshotNodeResponse>(
|
||||||
|
'/api/metrics/snapshot',
|
||||||
|
'POST',
|
||||||
|
JSON.stringify({
|
||||||
|
metrics,
|
||||||
|
groupBy: null,
|
||||||
|
nodeType,
|
||||||
|
timerange,
|
||||||
|
filterQuery,
|
||||||
|
sourceId,
|
||||||
|
accountId,
|
||||||
|
region,
|
||||||
|
includeTimeseries: true,
|
||||||
|
} as SnapshotRequest),
|
||||||
|
decodeResponse
|
||||||
|
);
|
||||||
|
|
||||||
|
const loadData = useCallback(() => {
|
||||||
|
if (shouldReload) return makeRequest();
|
||||||
|
return Promise.resolve();
|
||||||
|
}, [makeRequest, shouldReload]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
if (timeLength) {
|
||||||
|
await loadData();
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}, [loadData, timeLength]);
|
||||||
|
|
||||||
|
const timeseries = response
|
||||||
|
? first(response.nodes.map((node) => first(node.metrics)?.timeseries))
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
error: (error && error.message) || null,
|
||||||
|
loading: !interval ? true : loading,
|
||||||
|
timeseries,
|
||||||
|
reload: makeRequest,
|
||||||
|
};
|
||||||
|
}
|
|
@ -4,8 +4,33 @@
|
||||||
* 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 { Theme, LIGHT_THEME, DARK_THEME } from '@elastic/charts';
|
import {
|
||||||
|
Theme,
|
||||||
|
PartialTheme,
|
||||||
|
LIGHT_THEME,
|
||||||
|
DARK_THEME,
|
||||||
|
mergeWithDefaultTheme,
|
||||||
|
} from '@elastic/charts';
|
||||||
|
|
||||||
export function getChartTheme(isDarkMode: boolean): Theme {
|
export function getChartTheme(isDarkMode: boolean): Theme {
|
||||||
return isDarkMode ? DARK_THEME : LIGHT_THEME;
|
return isDarkMode ? DARK_THEME : LIGHT_THEME;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getTimelineChartTheme(isDarkMode: boolean): Theme {
|
||||||
|
return isDarkMode ? DARK_THEME : mergeWithDefaultTheme(TIMELINE_LIGHT_THEME, LIGHT_THEME);
|
||||||
|
}
|
||||||
|
|
||||||
|
const TIMELINE_LIGHT_THEME: PartialTheme = {
|
||||||
|
crosshair: {
|
||||||
|
band: {
|
||||||
|
fill: '#D3DAE6',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
axes: {
|
||||||
|
gridLine: {
|
||||||
|
horizontal: {
|
||||||
|
stroke: '#eaeaea',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
|
@ -56,8 +56,10 @@ export const transformRequestToMetricsAPIRequest = async (
|
||||||
snapshotRequest.nodeType,
|
snapshotRequest.nodeType,
|
||||||
source.configuration.fields
|
source.configuration.fields
|
||||||
);
|
);
|
||||||
const groupBy = snapshotRequest.groupBy.map((g) => g.field).filter(Boolean) as string[];
|
if (snapshotRequest.groupBy) {
|
||||||
metricsApiRequest.groupBy = [...groupBy, inventoryFields.id];
|
const groupBy = snapshotRequest.groupBy.map((g) => g.field).filter(Boolean) as string[];
|
||||||
|
metricsApiRequest.groupBy = [...groupBy, inventoryFields.id];
|
||||||
|
}
|
||||||
|
|
||||||
const metaAggregation = {
|
const metaAggregation = {
|
||||||
id: META_KEY,
|
id: META_KEY,
|
||||||
|
|
|
@ -8815,7 +8815,6 @@
|
||||||
"xpack.infra.registerFeatures.logsDescription": "ログをリアルタイムでストリーするか、コンソール式の UI で履歴ビューをスクロールします。",
|
"xpack.infra.registerFeatures.logsDescription": "ログをリアルタイムでストリーするか、コンソール式の UI で履歴ビューをスクロールします。",
|
||||||
"xpack.infra.registerFeatures.logsTitle": "ログ",
|
"xpack.infra.registerFeatures.logsTitle": "ログ",
|
||||||
"xpack.infra.sampleDataLinkLabel": "ログ",
|
"xpack.infra.sampleDataLinkLabel": "ログ",
|
||||||
"xpack.infra.savedView.changeView": "ビューの変更",
|
|
||||||
"xpack.infra.savedView.currentView": "現在のビュー",
|
"xpack.infra.savedView.currentView": "現在のビュー",
|
||||||
"xpack.infra.savedView.defaultViewNameHosts": "デフォルトビュー",
|
"xpack.infra.savedView.defaultViewNameHosts": "デフォルトビュー",
|
||||||
"xpack.infra.savedView.errorOnCreate.duplicateViewName": "その名前のビューは既に存在します",
|
"xpack.infra.savedView.errorOnCreate.duplicateViewName": "その名前のビューは既に存在します",
|
||||||
|
|
|
@ -8821,7 +8821,6 @@
|
||||||
"xpack.infra.registerFeatures.logsDescription": "实时流式传输日志或在类似控制台的工具中滚动浏览历史视图。",
|
"xpack.infra.registerFeatures.logsDescription": "实时流式传输日志或在类似控制台的工具中滚动浏览历史视图。",
|
||||||
"xpack.infra.registerFeatures.logsTitle": "日志",
|
"xpack.infra.registerFeatures.logsTitle": "日志",
|
||||||
"xpack.infra.sampleDataLinkLabel": "日志",
|
"xpack.infra.sampleDataLinkLabel": "日志",
|
||||||
"xpack.infra.savedView.changeView": "更改视图",
|
|
||||||
"xpack.infra.savedView.currentView": "当前视图",
|
"xpack.infra.savedView.currentView": "当前视图",
|
||||||
"xpack.infra.savedView.defaultViewNameHosts": "默认视图",
|
"xpack.infra.savedView.defaultViewNameHosts": "默认视图",
|
||||||
"xpack.infra.savedView.errorOnCreate.duplicateViewName": "具有该名称的视图已存在。",
|
"xpack.infra.savedView.errorOnCreate.duplicateViewName": "具有该名称的视图已存在。",
|
||||||
|
|
Loading…
Reference in a new issue