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

[DataUsage] Add internal API/UX hooks to interact with serverless project metrics API #193966

Merged
Merged
Show file tree
Hide file tree
Changes from 51 commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
78baf01
add ui components to data_usage and mock data
neptunian Sep 24, 2024
5b0ef1e
remove deprecated extraPublicDirs
neptunian Sep 25, 2024
ec52676
update icon card to stats
neptunian Sep 30, 2024
4d4114e
data stream action menu
neptunian Sep 30, 2024
4d6eb3f
add elasticsearch feature privilege
neptunian Oct 3, 2024
274f6d4
add descriptive text and fix layout spacing
neptunian Oct 3, 2024
41a584b
fix chart theme dark mode
neptunian Oct 3, 2024
af5e833
fix type
neptunian Oct 3, 2024
1fe1f9a
add data quality set locator
neptunian Oct 3, 2024
817a112
fix title
neptunian Oct 3, 2024
d3e9296
initial internal API setup
ashokaditya Sep 25, 2024
2a4de17
data streams internal API
ashokaditya Sep 30, 2024
36e0b02
public hook to fetch metrics data
ashokaditya Oct 2, 2024
8aee1e8
plug metrics API with charts UI
ashokaditya Oct 2, 2024
fdfa96b
update date picker to work with API and update URL
ashokaditya Oct 4, 2024
63db939
add kibana index link and separate out dataset quality link component
neptunian Oct 4, 2024
38d338b
app copy function and spacer
neptunian Oct 4, 2024
2225978
create LegendAction component
neptunian Oct 4, 2024
1552111
clean up DatasetQualityLink component
neptunian Oct 4, 2024
b160b59
separate out components
neptunian Oct 4, 2024
16a5efd
pass datepicker state to dataset quality link
neptunian Oct 7, 2024
21c3af3
add ui components to data_usage and mock data
neptunian Sep 24, 2024
48231a3
remove deprecated extraPublicDirs
neptunian Sep 25, 2024
d0b8bca
update icon card to stats
neptunian Sep 30, 2024
bae6117
data stream action menu
neptunian Sep 30, 2024
a034716
add elasticsearch feature privilege
neptunian Oct 3, 2024
dbd9120
add descriptive text and fix layout spacing
neptunian Oct 3, 2024
d42614f
fix chart theme dark mode
neptunian Oct 3, 2024
c17321e
fix type
neptunian Oct 3, 2024
9a02d58
add data quality set locator
neptunian Oct 3, 2024
70e5683
fix title
neptunian Oct 3, 2024
accb18a
initial internal API setup
ashokaditya Sep 25, 2024
c94762f
data streams internal API
ashokaditya Sep 30, 2024
44ca8a9
public hook to fetch metrics data
ashokaditya Oct 2, 2024
e1e7d33
plug metrics API with charts UI
ashokaditya Oct 2, 2024
b2561c6
update date picker to work with API and update URL
ashokaditya Oct 4, 2024
8ee050a
add kibana index link and separate out dataset quality link component
neptunian Oct 4, 2024
b68749c
app copy function and spacer
neptunian Oct 4, 2024
bd1b1d5
create LegendAction component
neptunian Oct 4, 2024
bf72cda
clean up DatasetQualityLink component
neptunian Oct 4, 2024
cc1bf4c
separate out components
neptunian Oct 4, 2024
34c15c8
pass datepicker state to dataset quality link
neptunian Oct 7, 2024
1265c9f
[CI] Auto-commit changed files from 'node scripts/notice'
kibanamachine Oct 7, 2024
6fdfbad
Merge branch 'task/serverless-data-usage-API-192965' of github.com:as…
ashokaditya Oct 7, 2024
dcdfca9
remove redundant file
ashokaditya Oct 7, 2024
c786714
use data quality details locator
neptunian Oct 7, 2024
720e8a1
update mock data to align with autoOps response
neptunian Oct 7, 2024
d043643
remove size for now
neptunian Oct 7, 2024
889018f
fix types
neptunian Oct 7, 2024
222c6db
fix test
neptunian Oct 8, 2024
9c2fc1a
remove redundant const
ashokaditya Oct 8, 2024
8f639d7
update tests for API schema
ashokaditya Oct 8, 2024
297091d
cleanup
ashokaditya Oct 8, 2024
3b39d20
set default URL params for metrics and date ranges
ashokaditya Oct 8, 2024
d1a4128
Merge branch 'main' into task/serverless-data-usage-API-192965
ashokaditya Oct 8, 2024
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
2 changes: 1 addition & 1 deletion packages/kbn-management/cards_navigation/src/consts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export const appDefinitions: Record<AppId, AppDefinition> = {
description: i18n.translate('management.landing.withCardNavigation.dataUsageDescription', {
defaultMessage: 'View data usage and retention.',
}),
icon: 'documents',
icon: 'stats',
},

[AppIds.RULES]: {
Expand Down
4 changes: 4 additions & 0 deletions x-pack/plugins/data_usage/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,7 @@ export const PLUGIN_ID = 'data_usage';
export const PLUGIN_NAME = i18n.translate('xpack.dataUsage.name', {
defaultMessage: 'Data Usage',
});

export const DATA_USAGE_API_ROUTE_PREFIX = '/api/data_usage/';
export const DATA_USAGE_METRICS_API_ROUTE = `/internal${DATA_USAGE_API_ROUTE_PREFIX}metrics`;
export const DATA_USAGE_DATA_STREAMS_API_ROUTE = `/internal${DATA_USAGE_API_ROUTE_PREFIX}data_streams`;
57 changes: 57 additions & 0 deletions x-pack/plugins/data_usage/common/query_client.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import type { PropsWithChildren } from 'react';
import React, { memo, useMemo } from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

type QueryClientOptionsProp = ConstructorParameters<typeof QueryClient>[0];

/**
* Default Query Client for Data Usage.
*/
export class DataUsageQueryClient extends QueryClient {
constructor(options: QueryClientOptionsProp = {}) {
const optionsWithDefaults: QueryClientOptionsProp = {
...options,
defaultOptions: {
...(options.defaultOptions ?? {}),
queries: {
refetchIntervalInBackground: false,
refetchOnWindowFocus: false,
refetchOnMount: true,
keepPreviousData: true,
...(options?.defaultOptions?.queries ?? {}),
},
},
};
super(optionsWithDefaults);
}
}

/**
* The default Data Usage Query Client. Can be imported and used from outside of React hooks
* and still benefit from ReactQuery features (like caching, etc)
*
* @see https://tanstack.com/query/v4/docs/reference/QueryClient
*/
export const dataUsageQueryClient = new DataUsageQueryClient();

export type ReactQueryClientProviderProps = PropsWithChildren<{
queryClient?: DataUsageQueryClient;
}>;

export const DataUsageReactQueryClientProvider = memo<ReactQueryClientProviderProps>(
({ queryClient, children }) => {
const client = useMemo(() => {
return queryClient || dataUsageQueryClient;
}, [queryClient]);
return <QueryClientProvider client={client}>{children}</QueryClientProvider>;
}
);

DataUsageReactQueryClientProvider.displayName = 'DataUsageReactQueryClientProvider';
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
* 2.0.
*/

/* eslint-disable @typescript-eslint/no-empty-interface*/
import { schema } from '@kbn/config-schema';

export interface DataUsageSetupDependencies {}

export interface DataUsageStartDependencies {}

export interface DataUsageServerSetup {}

export interface DataUsageServerStart {}
export const DataStreamsResponseSchema = {
body: () =>
schema.arrayOf(
schema.object({
name: schema.string(),
storageSizeBytes: schema.number(),
})
),
};
9 changes: 9 additions & 0 deletions x-pack/plugins/data_usage/common/rest_types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

export * from './usage_metrics';
export * from './data_streams';
139 changes: 139 additions & 0 deletions x-pack/plugins/data_usage/common/rest_types/usage_metrics.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { UsageMetricsRequestSchema } from './usage_metrics';

describe('usage_metrics schemas', () => {
it('should accept valid request query', () => {
expect(() =>
UsageMetricsRequestSchema.query.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',
})
).not.toThrow();
});

it('should accept multiple `metricTypes` in request query', () => {
expect(() =>
UsageMetricsRequestSchema.query.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',
})
).not.toThrow();
});

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

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

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

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

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

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

it('should error if `metricTypes` is not a valid value', () => {
expect(() =>
UsageMetricsRequestSchema.query.validate({
from: new Date().toISOString(),
to: new Date().toISOString(),
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'
);
});

it('should error if `metricTypes` is not a valid list', () => {
expect(() =>
UsageMetricsRequestSchema.query.validate({
from: new Date().toISOString(),
to: new Date().toISOString(),
metricTypes: ['storage_retained', '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'
);
});
});
99 changes: 99 additions & 0 deletions x-pack/plugins/data_usage/common/rest_types/usage_metrics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { schema, type TypeOf } from '@kbn/config-schema';

const METRIC_TYPE_VALUES = [
'storage_retained',
'ingest_rate',
'search_vcu',
'ingest_vcu',
'ml_vcu',
'index_latency',
'index_rate',
'search_latency',
'search_rate',
] as const;

export type MetricTypes = (typeof METRIC_TYPE_VALUES)[number];

// type guard for MetricTypes
export const isMetricType = (type: string): type is MetricTypes =>
METRIC_TYPE_VALUES.includes(type as MetricTypes);

// @ts-ignore
const isValidMetricType = (value: string) => METRIC_TYPE_VALUES.includes(value);

const DateSchema = schema.string();

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 type UsageMetricsRequestSchemaQueryParams = TypeOf<typeof UsageMetricsRequestSchema.query>;

export const UsageMetricsResponseSchema = {
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 }) // Each data point is an array of 2 numbers
),
})
)
),
}),
};
export type UsageMetricsResponseSchemaBody = TypeOf<typeof UsageMetricsResponseSchema.body>;
9 changes: 9 additions & 0 deletions x-pack/plugins/data_usage/common/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

// temporary type until agreed on
export type MetricKey = 'ingestedMax' | 'retainedMax';
20 changes: 16 additions & 4 deletions x-pack/plugins/data_usage/kibana.jsonc
Original file line number Diff line number Diff line change
@@ -1,16 +1,28 @@
{
"type": "plugin",
"id": "@kbn/data-usage-plugin",
"owner": ["@elastic/obs-ai-assistant", "@elastic/security-solution"],
"owner": [
"@elastic/obs-ai-assistant",
"@elastic/security-solution"
],
"plugin": {
"id": "dataUsage",
"server": true,
"browser": true,
"configPath": ["xpack", "dataUsage"],
"requiredPlugins": ["home", "management", "features", "share"],
"configPath": [
"xpack",
"dataUsage"
],
"requiredPlugins": [
"home",
"management",
"features",
"share"
],
"optionalPlugins": [],
"requiredBundles": [
"kibanaReact",
],
"data"
]
}
}
Loading