Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[8.x] [Data Usage] process autoops mock data (#195640) #195819

Merged
merged 1 commit into from
Oct 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 33 additions & 46 deletions x-pack/plugins/data_usage/common/rest_types/usage_metrics.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,48 +10,29 @@ import { UsageMetricsRequestSchema } from './usage_metrics';
describe('usage_metrics schemas', () => {
it('should accept valid request query', () => {
expect(() =>
UsageMetricsRequestSchema.query.validate({
UsageMetricsRequestSchema.validate({
from: new Date().toISOString(),
to: new Date().toISOString(),
metricTypes: ['storage_retained'],
})
).not.toThrow();
});

it('should accept a single `metricTypes` in request query', () => {
expect(() =>
UsageMetricsRequestSchema.query.validate({
from: new Date().toISOString(),
to: new Date().toISOString(),
metricTypes: 'ingest_rate',
dataStreams: ['data_stream_1', 'data_stream_2', 'data_stream_3'],
})
).not.toThrow();
});

it('should accept multiple `metricTypes` in request query', () => {
expect(() =>
UsageMetricsRequestSchema.query.validate({
UsageMetricsRequestSchema.validate({
from: new Date().toISOString(),
to: new Date().toISOString(),
metricTypes: ['ingest_rate', 'storage_retained', 'index_rate'],
})
).not.toThrow();
});

it('should accept a single string as `dataStreams` in request query', () => {
expect(() =>
UsageMetricsRequestSchema.query.validate({
from: new Date().toISOString(),
to: new Date().toISOString(),
metricTypes: 'storage_retained',
dataStreams: 'data_stream_1',
dataStreams: ['data_stream_1', 'data_stream_2', 'data_stream_3'],
})
).not.toThrow();
});

it('should accept `dataStream` list', () => {
expect(() =>
UsageMetricsRequestSchema.query.validate({
UsageMetricsRequestSchema.validate({
from: new Date().toISOString(),
to: new Date().toISOString(),
metricTypes: ['storage_retained'],
Expand All @@ -62,74 +43,76 @@ describe('usage_metrics schemas', () => {

it('should error if `dataStream` list is empty', () => {
expect(() =>
UsageMetricsRequestSchema.query.validate({
UsageMetricsRequestSchema.validate({
from: new Date().toISOString(),
to: new Date().toISOString(),
metricTypes: ['storage_retained'],
dataStreams: [],
})
).toThrowError('expected value of type [string] but got [Array]');
).toThrowError('[dataStreams]: array size is [0], but cannot be smaller than [1]');
});

it('should error if `dataStream` is given an empty string', () => {
it('should error if `dataStream` is given type not array', () => {
expect(() =>
UsageMetricsRequestSchema.query.validate({
UsageMetricsRequestSchema.validate({
from: new Date().toISOString(),
to: new Date().toISOString(),
metricTypes: ['storage_retained'],
dataStreams: ' ',
})
).toThrow('[dataStreams] must have at least one value');
).toThrow('[dataStreams]: could not parse array value from json input');
});

it('should error if `dataStream` is given an empty item in the list', () => {
expect(() =>
UsageMetricsRequestSchema.query.validate({
UsageMetricsRequestSchema.validate({
from: new Date().toISOString(),
to: new Date().toISOString(),
metricTypes: ['storage_retained'],
dataStreams: ['ds_1', ' '],
})
).toThrow('[dataStreams] list can not contain empty values');
).toThrow('[dataStreams]: [dataStreams] list cannot contain empty values');
});

it('should error if `metricTypes` is empty string', () => {
expect(() =>
UsageMetricsRequestSchema.query.validate({
UsageMetricsRequestSchema.validate({
from: new Date().toISOString(),
to: new Date().toISOString(),
dataStreams: ['data_stream_1', 'data_stream_2', 'data_stream_3'],
metricTypes: ' ',
})
).toThrow();
});

it('should error if `metricTypes` is empty item', () => {
it('should error if `metricTypes` contains an empty item', () => {
expect(() =>
UsageMetricsRequestSchema.query.validate({
UsageMetricsRequestSchema.validate({
from: new Date().toISOString(),
to: new Date().toISOString(),
metricTypes: [' ', 'storage_retained'],
dataStreams: ['data_stream_1', 'data_stream_2', 'data_stream_3'],
metricTypes: [' ', 'storage_retained'], // First item is invalid
})
).toThrow('[metricTypes] list can not contain empty values');
).toThrowError(/list cannot contain empty values/);
});

it('should error if `metricTypes` is not a valid value', () => {
it('should error if `metricTypes` is not a valid type', () => {
expect(() =>
UsageMetricsRequestSchema.query.validate({
UsageMetricsRequestSchema.validate({
from: new Date().toISOString(),
to: new Date().toISOString(),
dataStreams: ['data_stream_1', 'data_stream_2', 'data_stream_3'],
metricTypes: 'foo',
})
).toThrow(
'[metricTypes] must be one of storage_retained, ingest_rate, search_vcu, ingest_vcu, ml_vcu, index_latency, index_rate, search_latency, search_rate'
);
).toThrow('[metricTypes]: could not parse array value from json input');
});

it('should error if `metricTypes` is not a valid list', () => {
expect(() =>
UsageMetricsRequestSchema.query.validate({
UsageMetricsRequestSchema.validate({
from: new Date().toISOString(),
to: new Date().toISOString(),
dataStreams: ['data_stream_1', 'data_stream_2', 'data_stream_3'],
metricTypes: ['storage_retained', 'foo'],
})
).toThrow(
Expand All @@ -139,39 +122,43 @@ describe('usage_metrics schemas', () => {

it('should error if `from` is not a valid input', () => {
expect(() =>
UsageMetricsRequestSchema.query.validate({
UsageMetricsRequestSchema.validate({
from: 1010,
to: new Date().toISOString(),
dataStreams: ['data_stream_1', 'data_stream_2', 'data_stream_3'],
metricTypes: ['storage_retained', 'foo'],
})
).toThrow('[from]: expected value of type [string] but got [number]');
});

it('should error if `to` is not a valid input', () => {
expect(() =>
UsageMetricsRequestSchema.query.validate({
UsageMetricsRequestSchema.validate({
from: new Date().toISOString(),
to: 1010,
dataStreams: ['data_stream_1', 'data_stream_2', 'data_stream_3'],
metricTypes: ['storage_retained', 'foo'],
})
).toThrow('[to]: expected value of type [string] but got [number]');
});

it('should error if `from` is empty string', () => {
expect(() =>
UsageMetricsRequestSchema.query.validate({
UsageMetricsRequestSchema.validate({
from: ' ',
to: new Date().toISOString(),
dataStreams: ['data_stream_1', 'data_stream_2', 'data_stream_3'],
metricTypes: ['storage_retained', 'foo'],
})
).toThrow('[from]: Date ISO string must not be empty');
});

it('should error if `to` is empty string', () => {
expect(() =>
UsageMetricsRequestSchema.query.validate({
UsageMetricsRequestSchema.validate({
from: new Date().toISOString(),
to: ' ',
dataStreams: ['data_stream_1', 'data_stream_2', 'data_stream_3'],
metricTypes: ['storage_retained', 'foo'],
})
).toThrow('[to]: Date ISO string must not be empty');
Expand Down
99 changes: 54 additions & 45 deletions x-pack/plugins/data_usage/common/rest_types/usage_metrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,51 +37,31 @@ const metricTypesSchema = schema.oneOf(
// @ts-expect-error TS2769: No overload matches this call
METRIC_TYPE_VALUES.map((metricType) => schema.literal(metricType)) // Create a oneOf schema for the keys
);
export const UsageMetricsRequestSchema = {
query: schema.object({
from: DateSchema,
to: DateSchema,
metricTypes: schema.oneOf([
schema.arrayOf(schema.string(), {
minSize: 1,
validate: (values) => {
if (values.map((v) => v.trim()).some((v) => !v.length)) {
return '[metricTypes] list can not contain empty values';
} else if (values.map((v) => v.trim()).some((v) => !isValidMetricType(v))) {
return `[metricTypes] must be one of ${METRIC_TYPE_VALUES.join(', ')}`;
}
},
}),
schema.string({
validate: (v) => {
if (!v.trim().length) {
return '[metricTypes] must have at least one value';
} else if (!isValidMetricType(v)) {
return `[metricTypes] must be one of ${METRIC_TYPE_VALUES.join(', ')}`;
}
},
}),
]),
dataStreams: schema.maybe(
schema.oneOf([
schema.arrayOf(schema.string(), {
minSize: 1,
validate: (values) => {
if (values.map((v) => v.trim()).some((v) => !v.length)) {
return '[dataStreams] list can not contain empty values';
}
},
}),
schema.string({
validate: (v) =>
v.trim().length ? undefined : '[dataStreams] must have at least one value',
}),
])
),
export const UsageMetricsRequestSchema = schema.object({
from: DateSchema,
to: DateSchema,
metricTypes: schema.arrayOf(schema.string(), {
minSize: 1,
validate: (values) => {
const trimmedValues = values.map((v) => v.trim());
if (trimmedValues.some((v) => !v.length)) {
return '[metricTypes] list cannot contain empty values';
} else if (trimmedValues.some((v) => !isValidMetricType(v))) {
return `[metricTypes] must be one of ${METRIC_TYPE_VALUES.join(', ')}`;
}
},
}),
};
dataStreams: schema.arrayOf(schema.string(), {
minSize: 1,
validate: (values) => {
if (values.map((v) => v.trim()).some((v) => !v.length)) {
return '[dataStreams] list cannot contain empty values';
}
},
}),
});

export type UsageMetricsRequestSchemaQueryParams = TypeOf<typeof UsageMetricsRequestSchema.query>;
export type UsageMetricsRequestSchemaQueryParams = TypeOf<typeof UsageMetricsRequestSchema>;

export const UsageMetricsResponseSchema = {
body: () =>
Expand All @@ -92,11 +72,40 @@ export const UsageMetricsResponseSchema = {
schema.object({
name: schema.string(),
data: schema.arrayOf(
schema.arrayOf(schema.number(), { minSize: 2, maxSize: 2 }) // Each data point is an array of 2 numbers
schema.object({
x: schema.number(),
y: schema.number(),
})
),
})
)
),
}),
};
export type UsageMetricsResponseSchemaBody = TypeOf<typeof UsageMetricsResponseSchema.body>;
export type UsageMetricsResponseSchemaBody = Omit<
TypeOf<typeof UsageMetricsResponseSchema.body>,
'metrics'
> & {
metrics: Partial<Record<MetricTypes, MetricSeries[]>>;
};
export type MetricSeries = TypeOf<
typeof UsageMetricsResponseSchema.body
>['metrics'][MetricTypes][number];

export const UsageMetricsAutoOpsResponseSchema = {
body: () =>
schema.object({
metrics: schema.recordOf(
metricTypesSchema,
schema.arrayOf(
schema.object({
name: schema.string(),
data: schema.arrayOf(schema.arrayOf(schema.number(), { minSize: 2, maxSize: 2 })),
})
)
),
}),
};
export type UsageMetricsAutoOpsResponseSchemaBody = TypeOf<
typeof UsageMetricsAutoOpsResponseSchema.body
>;
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ import {
} from '@elastic/charts';
import { i18n } from '@kbn/i18n';
import { LegendAction } from './legend_action';
import { MetricTypes } from '../../../common/rest_types';
import { MetricSeries } from '../types';
import { MetricTypes, MetricSeries } from '../../../common/rest_types';

// TODO: Remove this when we have a title for each metric type
type ChartKey = Extract<MetricTypes, 'ingest_rate' | 'storage_retained'>;
Expand Down Expand Up @@ -50,7 +49,7 @@ export const ChartPanel: React.FC<ChartPanelProps> = ({
}) => {
const theme = useEuiTheme();

const chartTimestamps = series.flatMap((stream) => stream.data.map((d) => d[0]));
const chartTimestamps = series.flatMap((stream) => stream.data.map((d) => d.x));

const [minTimestamp, maxTimestamp] = [Math.min(...chartTimestamps), Math.max(...chartTimestamps)];

Expand All @@ -72,6 +71,7 @@ export const ChartPanel: React.FC<ChartPanelProps> = ({
},
[idx, popoverOpen, togglePopover]
);

return (
<EuiFlexItem grow={false} key={metricType}>
<EuiPanel hasShadow={false} hasBorder={true}>
Expand All @@ -94,9 +94,9 @@ export const ChartPanel: React.FC<ChartPanelProps> = ({
data={stream.data}
xScaleType={ScaleType.Time}
yScaleType={ScaleType.Linear}
xAccessor={0} // x is the first element in the tuple
yAccessors={[1]} // y is the second element in the tuple
stackAccessors={[0]}
xAccessor="x"
yAccessors={['y']}
stackAccessors={['x']}
/>
))}

Expand All @@ -118,6 +118,7 @@ export const ChartPanel: React.FC<ChartPanelProps> = ({
</EuiFlexItem>
);
};

const formatBytes = (bytes: number) => {
return numeral(bytes).format('0.0 b');
};
4 changes: 2 additions & 2 deletions x-pack/plugins/data_usage/public/app/components/charts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
*/
import React, { useCallback, useState } from 'react';
import { EuiFlexGroup } from '@elastic/eui';
import { MetricsResponse } from '../types';
import { MetricTypes } from '../../../common/rest_types';
import { ChartPanel } from './chart_panel';
import { UsageMetricsResponseSchemaBody } from '../../../common/rest_types';
interface ChartsProps {
data: MetricsResponse;
data: UsageMetricsResponseSchemaBody;
}

export const Charts: React.FC<ChartsProps> = ({ data }) => {
Expand Down
Loading