diff --git a/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/common.ts b/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/common.ts index 22b3f834cd8c0..8e8d1e755c8dd 100644 --- a/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/common.ts +++ b/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/common.ts @@ -4,9 +4,9 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { nonEmptyStringRt } from '@kbn/io-ts-utils'; import * as rt from 'io-ts'; import { either } from 'fp-ts/Either'; +import { metricsExplorerViewRT } from '../../../metrics_explorer_views'; export const METRICS_EXPLORER_VIEW_URL = '/api/infra/metrics_explorer_views'; export const METRICS_EXPLORER_VIEW_URL_ENTITY = `${METRICS_EXPLORER_VIEW_URL}/{metricsExplorerViewId}`; @@ -35,28 +35,6 @@ export const metricsExplorerViewRequestQueryRT = rt.partial({ export type MetricsExplorerViewRequestQuery = rt.TypeOf; -const metricsExplorerViewAttributesResponseRT = rt.intersection([ - rt.strict({ - name: nonEmptyStringRt, - isDefault: rt.boolean, - isStatic: rt.boolean, - }), - rt.UnknownRecord, -]); - -const metricsExplorerViewResponseRT = rt.exact( - rt.intersection([ - rt.type({ - id: rt.string, - attributes: metricsExplorerViewAttributesResponseRT, - }), - rt.partial({ - updatedAt: rt.number, - version: rt.string, - }), - ]) -); - export const metricsExplorerViewResponsePayloadRT = rt.type({ - data: metricsExplorerViewResponseRT, + data: metricsExplorerViewRT, }); diff --git a/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/create_metrics_explorer_view.ts b/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/create_metrics_explorer_view.ts index 64582a8d682dc..1947f013bc389 100644 --- a/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/create_metrics_explorer_view.ts +++ b/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/create_metrics_explorer_view.ts @@ -5,15 +5,15 @@ * 2.0. */ -import { nonEmptyStringRt } from '@kbn/io-ts-utils'; import * as rt from 'io-ts'; +import { + metricsExplorerViewAttributesRT, + metricsExplorerViewRT, +} from '../../../metrics_explorer_views'; export const createMetricsExplorerViewAttributesRequestPayloadRT = rt.intersection([ - rt.type({ - name: nonEmptyStringRt, - }), - rt.UnknownRecord, - rt.exact(rt.partial({ isDefault: rt.undefined, isStatic: rt.undefined })), + metricsExplorerViewAttributesRT, + rt.partial({ isDefault: rt.undefined, isStatic: rt.undefined }), ]); export type CreateMetricsExplorerViewAttributesRequestPayload = rt.TypeOf< @@ -23,3 +23,5 @@ export type CreateMetricsExplorerViewAttributesRequestPayload = rt.TypeOf< export const createMetricsExplorerViewRequestPayloadRT = rt.type({ attributes: createMetricsExplorerViewAttributesRequestPayloadRT, }); + +export type CreateMetricsExplorerViewResponsePayload = rt.TypeOf; diff --git a/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/find_metrics_explorer_view.ts b/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/find_metrics_explorer_view.ts index 4419b3d34e4bc..0c5e28e9524a8 100644 --- a/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/find_metrics_explorer_view.ts +++ b/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/find_metrics_explorer_view.ts @@ -5,28 +5,13 @@ * 2.0. */ -import { nonEmptyStringRt } from '@kbn/io-ts-utils'; import * as rt from 'io-ts'; - -export const findMetricsExplorerViewAttributesResponseRT = rt.strict({ - name: nonEmptyStringRt, - isDefault: rt.boolean, - isStatic: rt.boolean, -}); - -const findMetricsExplorerViewResponseRT = rt.exact( - rt.intersection([ - rt.type({ - id: rt.string, - attributes: findMetricsExplorerViewAttributesResponseRT, - }), - rt.partial({ - updatedAt: rt.number, - version: rt.string, - }), - ]) -); +import { singleMetricsExplorerViewRT } from '../../../metrics_explorer_views'; export const findMetricsExplorerViewResponsePayloadRT = rt.type({ - data: rt.array(findMetricsExplorerViewResponseRT), + data: rt.array(singleMetricsExplorerViewRT), }); + +export type FindMetricsExplorerViewResponsePayload = rt.TypeOf< + typeof findMetricsExplorerViewResponsePayloadRT +>; diff --git a/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/get_metrics_explorer_view.ts b/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/get_metrics_explorer_view.ts index aa4f37b864fea..b7ef763a72916 100644 --- a/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/get_metrics_explorer_view.ts +++ b/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/get_metrics_explorer_view.ts @@ -6,7 +6,10 @@ */ import * as rt from 'io-ts'; +import { metricsExplorerViewRT } from '../../../metrics_explorer_views'; export const getMetricsExplorerViewRequestParamsRT = rt.type({ metricsExplorerViewId: rt.string, }); + +export type GetMetricsExplorerViewResponsePayload = rt.TypeOf; diff --git a/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/update_metrics_explorer_view.ts b/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/update_metrics_explorer_view.ts index e0df717662342..19c9760c00c84 100644 --- a/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/update_metrics_explorer_view.ts +++ b/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/update_metrics_explorer_view.ts @@ -5,15 +5,15 @@ * 2.0. */ -import { nonEmptyStringRt } from '@kbn/io-ts-utils'; import * as rt from 'io-ts'; +import { + metricsExplorerViewAttributesRT, + metricsExplorerViewRT, +} from '../../../metrics_explorer_views'; export const updateMetricsExplorerViewAttributesRequestPayloadRT = rt.intersection([ - rt.type({ - name: nonEmptyStringRt, - }), - rt.UnknownRecord, - rt.exact(rt.partial({ isDefault: rt.undefined, isStatic: rt.undefined })), + metricsExplorerViewAttributesRT, + rt.partial({ isDefault: rt.undefined, isStatic: rt.undefined }), ]); export type UpdateMetricsExplorerViewAttributesRequestPayload = rt.TypeOf< @@ -23,3 +23,5 @@ export type UpdateMetricsExplorerViewAttributesRequestPayload = rt.TypeOf< export const updateMetricsExplorerViewRequestPayloadRT = rt.type({ attributes: updateMetricsExplorerViewAttributesRequestPayloadRT, }); + +export type UpdateMetricsExplorerViewResponsePayload = rt.TypeOf; diff --git a/x-pack/plugins/infra/common/metrics_explorer_views/defaults.ts b/x-pack/plugins/infra/common/metrics_explorer_views/defaults.ts index 8c7e6ffff192f..1eb1a46245ae5 100644 --- a/x-pack/plugins/infra/common/metrics_explorer_views/defaults.ts +++ b/x-pack/plugins/infra/common/metrics_explorer_views/defaults.ts @@ -7,7 +7,12 @@ import { i18n } from '@kbn/i18n'; import type { NonEmptyString } from '@kbn/io-ts-utils'; -import type { MetricsExplorerViewAttributes } from './types'; +import { Color } from '../color_palette'; +import { + MetricsExplorerChartType, + MetricsExplorerViewAttributes, + MetricsExplorerYAxisMode, +} from './types'; export const staticMetricsExplorerViewId = '0'; @@ -23,24 +28,24 @@ export const staticMetricsExplorerViewAttributes: MetricsExplorerViewAttributes { aggregation: 'avg', field: 'system.cpu.total.norm.pct', - color: 'color0', + color: Color.color0, }, { aggregation: 'avg', field: 'kubernetes.pod.cpu.usage.node.pct', - color: 'color1', + color: Color.color1, }, { aggregation: 'avg', field: 'docker.cpu.total.pct', - color: 'color2', + color: Color.color2, }, ], source: 'default', }, chartOptions: { - type: 'line', - yAxisMode: 'fromZero', + type: MetricsExplorerChartType.line, + yAxisMode: MetricsExplorerYAxisMode.fromZero, stack: false, }, currentTimerange: { diff --git a/x-pack/plugins/infra/common/metrics_explorer_views/types.ts b/x-pack/plugins/infra/common/metrics_explorer_views/types.ts index 0d0c2fa3166e0..46e18753c8a6f 100644 --- a/x-pack/plugins/infra/common/metrics_explorer_views/types.ts +++ b/x-pack/plugins/infra/common/metrics_explorer_views/types.ts @@ -5,19 +5,101 @@ * 2.0. */ -import { nonEmptyStringRt } from '@kbn/io-ts-utils'; +import { isoToEpochRt, nonEmptyStringRt } from '@kbn/io-ts-utils'; import * as rt from 'io-ts'; +import { Color } from '../color_palette'; +import { + metricsExplorerAggregationRT, + metricsExplorerMetricRT, +} from '../http_api/metrics_explorer'; -export const metricsExplorerViewAttributesRT = rt.intersection([ - rt.type({ - name: nonEmptyStringRt, - isDefault: rt.boolean, - isStatic: rt.boolean, +export const inventorySortOptionRT = rt.type({ + by: rt.keyof({ name: null, value: null }), + direction: rt.keyof({ asc: null, desc: null }), +}); + +export enum MetricsExplorerChartType { + line = 'line', + area = 'area', + bar = 'bar', +} + +export enum MetricsExplorerYAxisMode { + fromZero = 'fromZero', + auto = 'auto', +} + +export const metricsExplorerChartOptionsRT = rt.type({ + yAxisMode: rt.keyof( + Object.fromEntries(Object.values(MetricsExplorerYAxisMode).map((v) => [v, null])) as Record< + MetricsExplorerYAxisMode, + null + > + ), + type: rt.keyof( + Object.fromEntries(Object.values(MetricsExplorerChartType).map((v) => [v, null])) as Record< + MetricsExplorerChartType, + null + > + ), + stack: rt.boolean, +}); + +export const metricsExplorerTimeOptionsRT = rt.type({ + from: rt.string, + to: rt.string, + interval: rt.string, +}); +const metricsExplorerOptionsMetricRT = rt.intersection([ + metricsExplorerMetricRT, + rt.partial({ + rate: rt.boolean, + color: rt.keyof( + Object.fromEntries(Object.values(Color).map((c) => [c, null])) as Record + ), + label: rt.string, }), - rt.UnknownRecord, ]); -export type MetricsExplorerViewAttributes = rt.TypeOf; +export const metricExplorerOptionsRequiredRT = rt.type({ + aggregation: metricsExplorerAggregationRT, + metrics: rt.array(metricsExplorerOptionsMetricRT), +}); + +export const metricExplorerOptionsOptionalRT = rt.partial({ + limit: rt.number, + groupBy: rt.union([rt.string, rt.array(rt.string)]), + filterQuery: rt.string, + source: rt.string, + forceInterval: rt.boolean, + dropLastBucket: rt.boolean, +}); +export const metricsExplorerOptionsRT = rt.intersection([ + metricExplorerOptionsRequiredRT, + metricExplorerOptionsOptionalRT, +]); + +export const metricExplorerViewStateRT = rt.type({ + chartOptions: metricsExplorerChartOptionsRT, + currentTimerange: metricsExplorerTimeOptionsRT, + options: metricsExplorerOptionsRT, +}); + +export const metricsExplorerViewBasicAttributesRT = rt.type({ + name: nonEmptyStringRt, +}); + +const metricsExplorerViewFlagsRT = rt.partial({ isDefault: rt.boolean, isStatic: rt.boolean }); + +export const metricsExplorerViewAttributesRT = rt.intersection([ + metricExplorerViewStateRT, + metricsExplorerViewBasicAttributesRT, + metricsExplorerViewFlagsRT, +]); + +const singleMetricsExplorerViewAttributesRT = rt.exact( + rt.intersection([metricsExplorerViewBasicAttributesRT, metricsExplorerViewFlagsRT]) +); export const metricsExplorerViewRT = rt.exact( rt.intersection([ @@ -26,10 +108,29 @@ export const metricsExplorerViewRT = rt.exact( attributes: metricsExplorerViewAttributesRT, }), rt.partial({ - updatedAt: rt.number, + updatedAt: isoToEpochRt, version: rt.string, }), ]) ); +export const singleMetricsExplorerViewRT = rt.exact( + rt.intersection([ + rt.type({ + id: rt.string, + attributes: singleMetricsExplorerViewAttributesRT, + }), + rt.partial({ + updatedAt: isoToEpochRt, + version: rt.string, + }), + ]) +); + +export type MetricsExplorerChartOptions = rt.TypeOf; +export type MetricsExplorerOptions = rt.TypeOf; +export type MetricsExplorerOptionsMetric = rt.TypeOf; +export type MetricsExplorerViewState = rt.TypeOf; +export type MetricsExplorerTimeOptions = rt.TypeOf; +export type MetricsExplorerViewAttributes = rt.TypeOf; export type MetricsExplorerView = rt.TypeOf; diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/hooks/use_metrics_explorer_chart_data.ts b/x-pack/plugins/infra/public/alerting/metric_threshold/hooks/use_metrics_explorer_chart_data.ts index 1696ed4b52ec4..532e694896f18 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/hooks/use_metrics_explorer_chart_data.ts +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/hooks/use_metrics_explorer_chart_data.ts @@ -13,7 +13,7 @@ import { MetricsSourceConfiguration } from '../../../../common/metrics_sources'; import { MetricExpression, TimeRange } from '../types'; import { MetricsExplorerOptions, - MetricsExplorerTimestampsRT, + MetricsExplorerTimestamp, } from '../../../pages/metrics/metrics_explorer/hooks/use_metrics_explorer_options'; import { useMetricsExplorerData } from '../../../pages/metrics/metrics_explorer/hooks/use_metrics_explorer_data'; import { MetricExplorerCustomMetricAggregations } from '../../../../common/http_api/metrics_explorer'; @@ -59,7 +59,7 @@ export const useMetricsExplorerChartData = ( groupBy, ] ); - const timestamps: MetricsExplorerTimestampsRT = useMemo(() => { + const timestamps: MetricsExplorerTimestamp = useMemo(() => { const from = timeRange.from ?? `now-${(timeSize || 1) * 20}${timeUnit}`; const to = timeRange.to ?? 'now'; const fromTimestamp = DateMath.parse(from)!.valueOf(); diff --git a/x-pack/plugins/infra/public/containers/metrics_explorer/with_metrics_explorer_options_url_state.tsx b/x-pack/plugins/infra/public/containers/metrics_explorer/with_metrics_explorer_options_url_state.tsx index 757cf3d100fce..fc963a4afed6f 100644 --- a/x-pack/plugins/infra/public/containers/metrics_explorer/with_metrics_explorer_options_url_state.tsx +++ b/x-pack/plugins/infra/public/containers/metrics_explorer/with_metrics_explorer_options_url_state.tsx @@ -10,11 +10,11 @@ import React, { useMemo } from 'react'; import { ThrowReporter } from 'io-ts/lib/ThrowReporter'; import { UrlStateContainer } from '../../utils/url_state'; import { - MetricsExplorerOptions, + type MetricsExplorerOptions, + type MetricsExplorerTimeOptions, + type MetricsExplorerChartOptions, useMetricsExplorerOptionsContainerContext, - MetricsExplorerTimeOptions, - MetricsExplorerChartOptions, - metricExplorerOptionsRT, + metricsExplorerOptionsRT, metricsExplorerChartOptionsRT, metricsExplorerTimeOptionsRT, } from '../../pages/metrics/metrics_explorer/hooks/use_metrics_explorer_options'; @@ -73,7 +73,7 @@ export const WithMetricsExplorerOptionsUrlState = () => { }; function isMetricExplorerOptions(subject: any): subject is MetricsExplorerOptions { - const result = metricExplorerOptionsRT.decode(subject); + const result = metricsExplorerOptionsRT.decode(subject); try { ThrowReporter.report(result); diff --git a/x-pack/plugins/infra/public/hooks/use_inventory_views.ts b/x-pack/plugins/infra/public/hooks/use_inventory_views.ts index 93873a307d59d..35bb5a154379d 100644 --- a/x-pack/plugins/infra/public/hooks/use_inventory_views.ts +++ b/x-pack/plugins/infra/public/hooks/use_inventory_views.ts @@ -19,7 +19,10 @@ import { UpdateViewParams, } from '../../common/saved_views'; import { MetricsSourceConfigurationResponse } from '../../common/metrics_sources'; -import { CreateInventoryViewAttributesRequestPayload } from '../../common/http_api/latest'; +import { + CreateInventoryViewAttributesRequestPayload, + UpdateInventoryViewAttributesRequestPayload, +} from '../../common/http_api/latest'; import type { InventoryView } from '../../common/inventory_views'; import { useKibanaContextForPlugin } from './use_kibana'; import { useUrlState } from '../utils/use_url_state'; @@ -133,7 +136,7 @@ export const useInventoryViews = (): UseInventoryViewsResult => { const { mutateAsync: updateViewById, isLoading: isUpdatingView } = useMutation< InventoryView, ServerError, - UpdateViewParams + UpdateViewParams >({ mutationFn: ({ id, attributes }) => inventoryViews.client.updateInventoryView(id, attributes), onError: (error) => { diff --git a/x-pack/plugins/infra/public/hooks/use_metrics_explorer_views.ts b/x-pack/plugins/infra/public/hooks/use_metrics_explorer_views.ts index f2c9500f9ea63..dac743a1f2191 100644 --- a/x-pack/plugins/infra/public/hooks/use_metrics_explorer_views.ts +++ b/x-pack/plugins/infra/public/hooks/use_metrics_explorer_views.ts @@ -19,7 +19,10 @@ import { UpdateViewParams, } from '../../common/saved_views'; import { MetricsSourceConfigurationResponse } from '../../common/metrics_sources'; -import { CreateMetricsExplorerViewAttributesRequestPayload } from '../../common/http_api/latest'; +import { + CreateMetricsExplorerViewAttributesRequestPayload, + UpdateMetricsExplorerViewAttributesRequestPayload, +} from '../../common/http_api/latest'; import { MetricsExplorerView } from '../../common/metrics_explorer_views'; import { useKibanaContextForPlugin } from './use_kibana'; import { useUrlState } from '../utils/use_url_state'; @@ -133,7 +136,7 @@ export const useMetricsExplorerViews = (): UseMetricsExplorerViewsResult => { const { mutateAsync: updateViewById, isLoading: isUpdatingView } = useMutation< MetricsExplorerView, ServerError, - UpdateViewParams + UpdateViewParams >({ mutationFn: ({ id, attributes }) => metricsExplorerViews.client.updateMetricsExplorerView(id, attributes), diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/saved_views.tsx b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/saved_views.tsx index ddce0eac506fe..bf1d914463c96 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/saved_views.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/saved_views.tsx @@ -8,10 +8,10 @@ import React from 'react'; import { useMetricsExplorerViews } from '../../../../hooks/use_metrics_explorer_views'; import { SavedViewsToolbarControls } from '../../../../components/saved_views/toolbar_control'; -import { MetricExplorerViewState } from '../hooks/use_metric_explorer_state'; +import { MetricsExplorerViewState } from '../hooks/use_metric_explorer_state'; interface Props { - viewState: MetricExplorerViewState; + viewState: MetricsExplorerViewState; } export const SavedViews = ({ viewState }: Props) => { @@ -31,7 +31,7 @@ export const SavedViews = ({ viewState }: Props) => { } = useMetricsExplorerViews(); return ( - + { options: MetricsExplorerOptions; source: MetricsSourceConfigurationProperties | undefined; derivedIndexPattern: DataViewBase; - timestamps: MetricsExplorerTimestampsRT; + timestamps: MetricsExplorerTimestamp; }) => useMetricsExplorerData( props.options, diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_data.ts b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_data.ts index a110ae1939840..2db5427331286 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_data.ts +++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_data.ts @@ -15,17 +15,14 @@ import { metricsExplorerResponseRT, } from '../../../../../common/http_api/metrics_explorer'; import { convertKueryToElasticSearchQuery } from '../../../../utils/kuery'; -import { - MetricsExplorerOptions, - MetricsExplorerTimestampsRT, -} from './use_metrics_explorer_options'; +import { MetricsExplorerOptions, MetricsExplorerTimestamp } from './use_metrics_explorer_options'; import { decodeOrThrow } from '../../../../../common/runtime_types'; export function useMetricsExplorerData( options: MetricsExplorerOptions, source: MetricsSourceConfigurationProperties | undefined, derivedIndexPattern: DataViewBase, - { fromTimestamp, toTimestamp, interval }: MetricsExplorerTimestampsRT, + { fromTimestamp, toTimestamp, interval }: MetricsExplorerTimestamp, enabled = true ) { const { http } = useKibana().services; diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_options.ts b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_options.ts index fa0d4189e2949..6eabfb407731c 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_options.ts +++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_options.ts @@ -7,91 +7,48 @@ import DateMath from '@kbn/datemath'; import * as t from 'io-ts'; -import { values } from 'lodash'; import createContainer from 'constate'; import type { TimeRange } from '@kbn/es-query'; import { useState, useEffect, useMemo, Dispatch, SetStateAction } from 'react'; +import { + type MetricsExplorerChartOptions, + type MetricsExplorerOptions, + type MetricsExplorerOptionsMetric, + type MetricsExplorerTimeOptions, + MetricsExplorerYAxisMode, + MetricsExplorerChartType, + metricsExplorerOptionsRT, + metricsExplorerChartOptionsRT, + metricsExplorerTimeOptionsRT, +} from '../../../../../common/metrics_explorer_views'; import { useAlertPrefillContext } from '../../../../alerting/use_alert_prefill'; import { Color } from '../../../../../common/color_palette'; -import { metricsExplorerMetricRT } from '../../../../../common/http_api/metrics_explorer'; import { useKibanaTimefilterTime, useSyncKibanaTimeFilterTime, } from '../../../../hooks/use_kibana_timefilter_time'; -const metricsExplorerOptionsMetricRT = t.intersection([ - metricsExplorerMetricRT, - t.partial({ - rate: t.boolean, - color: t.keyof(Object.fromEntries(values(Color).map((c) => [c, null])) as Record), - label: t.string, - }), -]); - -export type MetricsExplorerOptionsMetric = t.TypeOf; - -export enum MetricsExplorerChartType { - line = 'line', - area = 'area', - bar = 'bar', -} - -export enum MetricsExplorerYAxisMode { - fromZero = 'fromZero', - auto = 'auto', -} - -export const metricsExplorerChartOptionsRT = t.type({ - yAxisMode: t.keyof( - Object.fromEntries(values(MetricsExplorerYAxisMode).map((v) => [v, null])) as Record< - MetricsExplorerYAxisMode, - null - > - ), - type: t.keyof( - Object.fromEntries(values(MetricsExplorerChartType).map((v) => [v, null])) as Record< - MetricsExplorerChartType, - null - > - ), - stack: t.boolean, -}); - -export type MetricsExplorerChartOptions = t.TypeOf; - -const metricExplorerOptionsRequiredRT = t.type({ - aggregation: t.string, - metrics: t.array(metricsExplorerOptionsMetricRT), -}); - -const metricExplorerOptionsOptionalRT = t.partial({ - limit: t.number, - groupBy: t.union([t.string, t.array(t.string)]), - filterQuery: t.string, - source: t.string, - forceInterval: t.boolean, - dropLastBucket: t.boolean, -}); -export const metricExplorerOptionsRT = t.intersection([ - metricExplorerOptionsRequiredRT, - metricExplorerOptionsOptionalRT, -]); - -export type MetricsExplorerOptions = t.TypeOf; - export const metricsExplorerTimestampsRT = t.type({ fromTimestamp: t.number, toTimestamp: t.number, interval: t.string, }); -export type MetricsExplorerTimestampsRT = t.TypeOf; -export const metricsExplorerTimeOptionsRT = t.type({ - from: t.string, - to: t.string, - interval: t.string, -}); -export type MetricsExplorerTimeOptions = t.TypeOf; +export type { + MetricsExplorerOptions, + MetricsExplorerTimeOptions, + MetricsExplorerChartOptions, + MetricsExplorerOptionsMetric, +}; + +export { + MetricsExplorerYAxisMode, + MetricsExplorerChartType, + metricsExplorerOptionsRT, + metricsExplorerChartOptionsRT, + metricsExplorerTimeOptionsRT, +}; +export type MetricsExplorerTimestamp = t.TypeOf; export const DEFAULT_TIMERANGE: MetricsExplorerTimeOptions = { from: 'now-1h', @@ -182,7 +139,7 @@ export const useMetricsExplorerOptions = () => { to, interval: DEFAULT_TIMERANGE.interval, }); - const [timestamps, setTimestamps] = useState( + const [timestamps, setTimestamps] = useState( getDefaultTimeRange({ from, to }) ); diff --git a/x-pack/plugins/infra/public/services/metrics_explorer_views/metrics_explorer_views_client.ts b/x-pack/plugins/infra/public/services/metrics_explorer_views/metrics_explorer_views_client.ts index 788a8789abe73..8961522feea2e 100644 --- a/x-pack/plugins/infra/public/services/metrics_explorer_views/metrics_explorer_views_client.ts +++ b/x-pack/plugins/infra/public/services/metrics_explorer_views/metrics_explorer_views_client.ts @@ -7,17 +7,20 @@ import { HttpStart } from '@kbn/core/public'; import { - CreateMetricsExplorerViewAttributesRequestPayload, + CreateMetricsExplorerViewResponsePayload, createMetricsExplorerViewRequestPayloadRT, + FindMetricsExplorerViewResponsePayload, findMetricsExplorerViewResponsePayloadRT, + GetMetricsExplorerViewResponsePayload, getMetricsExplorerViewUrl, metricsExplorerViewResponsePayloadRT, + UpdateMetricsExplorerViewResponsePayload, + CreateMetricsExplorerViewAttributesRequestPayload, UpdateMetricsExplorerViewAttributesRequestPayload, } from '../../../common/http_api/latest'; import { DeleteMetricsExplorerViewError, FetchMetricsExplorerViewError, - MetricsExplorerView, UpsertMetricsExplorerViewError, } from '../../../common/metrics_explorer_views'; import { decodeOrThrow } from '../../../common/runtime_types'; @@ -26,7 +29,7 @@ import { IMetricsExplorerViewsClient } from './types'; export class MetricsExplorerViewsClient implements IMetricsExplorerViewsClient { constructor(private readonly http: HttpStart) {} - async findMetricsExplorerViews(): Promise { + async findMetricsExplorerViews(): Promise { const response = await this.http.get(getMetricsExplorerViewUrl()).catch((error) => { throw new FetchMetricsExplorerViewError(`Failed to fetch metrics explorer views: ${error}`); }); @@ -40,7 +43,9 @@ export class MetricsExplorerViewsClient implements IMetricsExplorerViewsClient { return data; } - async getMetricsExplorerView(metricsExplorerViewId: string): Promise { + async getMetricsExplorerView( + metricsExplorerViewId: string + ): Promise { const response = await this.http .get(getMetricsExplorerViewUrl(metricsExplorerViewId)) .catch((error) => { @@ -62,7 +67,7 @@ export class MetricsExplorerViewsClient implements IMetricsExplorerViewsClient { async createMetricsExplorerView( metricsExplorerViewAttributes: CreateMetricsExplorerViewAttributesRequestPayload - ): Promise { + ): Promise { const response = await this.http .post(getMetricsExplorerViewUrl(), { body: JSON.stringify( @@ -91,7 +96,7 @@ export class MetricsExplorerViewsClient implements IMetricsExplorerViewsClient { async updateMetricsExplorerView( metricsExplorerViewId: string, metricsExplorerViewAttributes: UpdateMetricsExplorerViewAttributesRequestPayload - ): Promise { + ): Promise { const response = await this.http .put(getMetricsExplorerViewUrl(metricsExplorerViewId), { body: JSON.stringify( diff --git a/x-pack/plugins/infra/public/services/metrics_explorer_views/types.ts b/x-pack/plugins/infra/public/services/metrics_explorer_views/types.ts index 7825d362eb9c5..edd86bb2283cb 100644 --- a/x-pack/plugins/infra/public/services/metrics_explorer_views/types.ts +++ b/x-pack/plugins/infra/public/services/metrics_explorer_views/types.ts @@ -7,9 +7,12 @@ import { HttpStart } from '@kbn/core/public'; import { - MetricsExplorerView, - MetricsExplorerViewAttributes, -} from '../../../common/metrics_explorer_views'; + FindMetricsExplorerViewResponsePayload, + CreateMetricsExplorerViewResponsePayload, + UpdateMetricsExplorerViewResponsePayload, + GetMetricsExplorerViewResponsePayload, +} from '../../../common/http_api'; +import { MetricsExplorerViewAttributes } from '../../../common/metrics_explorer_views'; export type MetricsExplorerViewsServiceSetup = void; @@ -22,14 +25,16 @@ export interface MetricsExplorerViewsServiceStartDeps { } export interface IMetricsExplorerViewsClient { - findMetricsExplorerViews(): Promise; - getMetricsExplorerView(metricsExplorerViewId: string): Promise; + findMetricsExplorerViews(): Promise; + getMetricsExplorerView( + metricsExplorerViewId: string + ): Promise; createMetricsExplorerView( metricsExplorerViewAttributes: Partial - ): Promise; + ): Promise; updateMetricsExplorerView( metricsExplorerViewId: string, metricsExplorerViewAttributes: Partial - ): Promise; + ): Promise; deleteMetricsExplorerView(metricsExplorerViewId: string): Promise; } diff --git a/x-pack/plugins/infra/public/utils/fixtures/metrics_explorer.ts b/x-pack/plugins/infra/public/utils/fixtures/metrics_explorer.ts index 58ccc468a06f4..92fed7ce8fd20 100644 --- a/x-pack/plugins/infra/public/utils/fixtures/metrics_explorer.ts +++ b/x-pack/plugins/infra/public/utils/fixtures/metrics_explorer.ts @@ -15,7 +15,7 @@ import { MetricsExplorerChartType, MetricsExplorerYAxisMode, MetricsExplorerChartOptions, - MetricsExplorerTimestampsRT, + MetricsExplorerTimestamp, } from '../../pages/metrics/metrics_explorer/hooks/use_metrics_explorer_options'; export const options: MetricsExplorerOptions = { @@ -56,7 +56,7 @@ export const timeRange: MetricsExplorerTimeOptions = { interval: '>=10s', }; -export const timestamps: MetricsExplorerTimestampsRT = { +export const timestamps: MetricsExplorerTimestamp = { fromTimestamp: 1678376367166, toTimestamp: 1678379973620, interval: '>=10s', diff --git a/x-pack/plugins/infra/server/routes/metrics_explorer_views/README.md b/x-pack/plugins/infra/server/routes/metrics_explorer_views/README.md index d14d8298d0d0f..53623b80b05e3 100644 --- a/x-pack/plugins/infra/server/routes/metrics_explorer_views/README.md +++ b/x-pack/plugins/infra/server/routes/metrics_explorer_views/README.md @@ -77,38 +77,32 @@ Status code: 200 "updatedAt": 1681398305034, "attributes": { "name": "Ad-hoc", - "isDefault": true, - "isStatic": false, - "metric": { - "type": "cpu" + "options": { + "aggregation": "avg", + "metrics": [ + { + "aggregation": "avg", + "field": "system.cpu.total.norm.pct", + "color": "color0" + }, + ], + "source": "default", + "groupBy": [ + "host.name" + ] }, - "sort": { - "by": "name", - "direction": "desc" + "chartOptions": { + "type": "line", + "yAxisMode": "fromZero", + "stack": false }, - "groupBy": [], - "nodeType": "host", - "view": "map", - "customOptions": [], - "customMetrics": [], - "boundsOverride": { - "max": 1, - "min": 0 + "currentTimerange": { + "from": "now-1h", + "to": "now", + "interval": ">=10s" }, - "autoBounds": true, - "accountId": "", - "region": "", - "autoReload": false, - "filterQuery": { - "expression": "", - "kind": "kuery" - }, - "legend": { - "palette": "cool", - "reverseColors": false, - "steps": 10 - }, - "timelineOpen": false + "isDefault": false, + "isStatic": false } } } @@ -130,23 +124,47 @@ Status code: 404 Creates a new metrics explorer view. +`aggregation`: `"avg" | "max" | "min" | "cardinality" | "rate" | "count" | "sum" | "p95" | "p99" | "custom"` + +`metrics.aggregation`: `"avg" | "max" | "min" | "cardinality" | "rate" | "count" | "sum" | "p95" | "p99" | "custom"` + +`chartOptions.type`: `"line" | "area" | "bar"` +`chartOptions.yAxisMode`: `"fromZero" | "auto" | "bar"` + ### Request - **Method**: POST - **Path**: /api/infra/metrics_explorer_views - **Request body**: + ```json { "attributes": { "name": "View name", - "metric": { - "type": "cpu" + "options": { + "aggregation": "avg", + "metrics": [ + { + "aggregation": "avg", + "field": "system.cpu.total.norm.pct", + "color": "color0" + }, + ], + "source": "default", + "groupBy": [ + "host.name" + ] }, - "sort": { - "by": "name", - "direction": "desc" + "chartOptions": { + "type": "line", + "yAxisMode": "fromZero", + "stack": false + }, + "currentTimerange": { + "from": "now-1h", + "to": "now", + "interval": ">=10s" }, - //... } } ``` @@ -165,38 +183,32 @@ Status code: 201 "updatedAt": 1681398305034, "attributes": { "name": "View name", - "isDefault": false, - "isStatic": false, - "metric": { - "type": "cpu" + "options": { + "aggregation": "avg", + "metrics": [ + { + "aggregation": "avg", + "field": "system.cpu.total.norm.pct", + "color": "color0" + }, + ], + "source": "default", + "groupBy": [ + "host.name" + ] }, - "sort": { - "by": "name", - "direction": "desc" + "chartOptions": { + "type": "line", + "yAxisMode": "fromZero", + "stack": false }, - "groupBy": [], - "nodeType": "host", - "view": "map", - "customOptions": [], - "customMetrics": [], - "boundsOverride": { - "max": 1, - "min": 0 + "currentTimerange": { + "from": "now-1h", + "to": "now", + "interval": ">=10s" }, - "autoBounds": true, - "accountId": "", - "region": "", - "autoReload": false, - "filterQuery": { - "expression": "", - "kind": "kuery" - }, - "legend": { - "palette": "cool", - "reverseColors": false, - "steps": 10 - }, - "timelineOpen": false + "isDefault": false, + "isStatic": false } } } @@ -234,14 +246,30 @@ Any attempt to update the static view with id `0` will return a `400 The metrics { "attributes": { "name": "View name", - "metric": { - "type": "cpu" + "options": { + "aggregation": "avg", + "metrics": [ + { + "aggregation": "avg", + "field": "system.cpu.total.norm.pct", + "color": "color0" + }, + ], + "source": "default", + "groupBy": [ + "host.name" + ] }, - "sort": { - "by": "name", - "direction": "desc" + "chartOptions": { + "type": "line", + "yAxisMode": "fromZero", + "stack": false }, - //... + "currentTimerange": { + "from": "now-1h", + "to": "now", + "interval": ">=10s" + } } } ``` @@ -260,38 +288,32 @@ Status code: 200 "updatedAt": 1681398305034, "attributes": { "name": "View name", - "isDefault": false, - "isStatic": false, - "metric": { - "type": "cpu" - }, - "sort": { - "by": "name", - "direction": "desc" + "options": { + "aggregation": "avg", + "metrics": [ + { + "aggregation": "avg", + "field": "system.cpu.total.norm.pct", + "color": "color0" + }, + ], + "source": "default", + "groupBy": [ + "host.name" + ] }, - "groupBy": [], - "nodeType": "host", - "view": "map", - "customOptions": [], - "customMetrics": [], - "boundsOverride": { - "max": 1, - "min": 0 + "chartOptions": { + "type": "line", + "yAxisMode": "fromZero", + "stack": false }, - "autoBounds": true, - "accountId": "", - "region": "", - "autoReload": false, - "filterQuery": { - "expression": "", - "kind": "kuery" + "currentTimerange": { + "from": "now-1h", + "to": "now", + "interval": ">=10s" }, - "legend": { - "palette": "cool", - "reverseColors": false, - "steps": 10 - }, - "timelineOpen": false + "isDefault": false, + "isStatic": false } } } diff --git a/x-pack/plugins/infra/server/saved_objects/metrics_explorer_view/types.ts b/x-pack/plugins/infra/server/saved_objects/metrics_explorer_view/types.ts index 15fe0eb970cc2..484d5ad166cf5 100644 --- a/x-pack/plugins/infra/server/saved_objects/metrics_explorer_view/types.ts +++ b/x-pack/plugins/infra/server/saved_objects/metrics_explorer_view/types.ts @@ -8,11 +8,59 @@ import { isoToEpochRt, nonEmptyStringRt } from '@kbn/io-ts-utils'; import * as rt from 'io-ts'; -export const metricsExplorerViewSavedObjectAttributesRT = rt.intersection([ - rt.strict({ +const metricsExplorerSavedObjectChartTypeRT = rt.keyof({ line: null, area: null, bar: null }); +const metricsExplorerYAxisModeRT = rt.keyof({ fromZero: null, auto: null }); + +const metricsExplorerSavedObjectChartOptionsRT = rt.type({ + yAxisMode: metricsExplorerYAxisModeRT, + type: metricsExplorerSavedObjectChartTypeRT, + stack: rt.boolean, +}); + +export const metricsExplorerSavedObjectTimeOptionsRT = rt.type({ + from: rt.string, + to: rt.string, + interval: rt.string, +}); +const metricsExplorerSavedObjectOptionsMetricRT = rt.intersection([ + rt.UnknownRecord, + rt.partial({ + rate: rt.boolean, + color: rt.string, + label: rt.string, + }), +]); + +const metricExplorerSavedObjectOptionsRequiredRT = rt.type({ + aggregation: rt.string, + metrics: rt.array(metricsExplorerSavedObjectOptionsMetricRT), +}); + +const metricExplorerSavedObjectOptionsOptionalRT = rt.partial({ + limit: rt.number, + groupBy: rt.union([rt.string, rt.array(rt.string)]), + filterQuery: rt.string, + source: rt.string, + forceInterval: rt.boolean, + dropLastBucket: rt.boolean, +}); +export const metricsExplorerSavedObjectOptionsRT = rt.intersection([ + metricExplorerSavedObjectOptionsRequiredRT, + metricExplorerSavedObjectOptionsOptionalRT, +]); + +const metricExplorerViewsSavedObjectStateRT = rt.type({ + chartOptions: metricsExplorerSavedObjectChartOptionsRT, + currentTimerange: metricsExplorerSavedObjectTimeOptionsRT, + options: metricsExplorerSavedObjectOptionsRT, +}); + +const metricsExplorerViewSavedObjectAttributesRT = rt.intersection([ + metricExplorerViewsSavedObjectStateRT, + rt.type({ name: nonEmptyStringRt, }), - rt.UnknownRecord, + rt.partial({ isDefault: rt.boolean, isStatic: rt.boolean }), ]); export const metricsExplorerViewSavedObjectRT = rt.intersection([ diff --git a/x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_client.ts b/x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_client.ts index e2dd15940bb19..d8f55a9901ad9 100644 --- a/x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_client.ts +++ b/x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_client.ts @@ -14,12 +14,16 @@ import { } from '@kbn/core/server'; import Boom from '@hapi/boom'; import { + metricsExplorerViewAttributesRT, staticMetricsExplorerViewAttributes, staticMetricsExplorerViewId, } from '../../../common/metrics_explorer_views'; import type { CreateMetricsExplorerViewAttributesRequestPayload, + FindMetricsExplorerViewResponsePayload, + GetMetricsExplorerViewResponsePayload, MetricsExplorerViewRequestQuery, + UpdateMetricsExplorerViewResponsePayload, } from '../../../common/http_api/latest'; import type { MetricsExplorerView, @@ -41,7 +45,9 @@ export class MetricsExplorerViewsClient implements IMetricsExplorerViewsClient { static STATIC_VIEW_ID = '0'; static DEFAULT_SOURCE_ID = 'default'; - public async find(query: MetricsExplorerViewRequestQuery): Promise { + public async find( + query: MetricsExplorerViewRequestQuery + ): Promise { this.logger.debug('Trying to load metrics explorer views ...'); const sourceId = query.sourceId ?? MetricsExplorerViewsClient.DEFAULT_SOURCE_ID; @@ -71,7 +77,7 @@ export class MetricsExplorerViewsClient implements IMetricsExplorerViewsClient { public async get( metricsExplorerViewId: string, query: MetricsExplorerViewRequestQuery - ): Promise { + ): Promise { this.logger.debug(`Trying to load metrics explorer view with id ${metricsExplorerViewId} ...`); const sourceId = query.sourceId ?? MetricsExplorerViewsClient.DEFAULT_SOURCE_ID; @@ -103,7 +109,7 @@ export class MetricsExplorerViewsClient implements IMetricsExplorerViewsClient { metricsExplorerViewId: string | null, attributes: CreateMetricsExplorerViewAttributesRequestPayload, query: MetricsExplorerViewRequestQuery - ): Promise { + ): Promise { this.logger.debug( `Trying to update metrics explorer view with id "${metricsExplorerViewId}"...` ); @@ -147,10 +153,10 @@ export class MetricsExplorerViewsClient implements IMetricsExplorerViewsClient { }); } - private mapSavedObjectToMetricsExplorerView( - savedObject: SavedObject | SavedObjectsUpdateResponse, + private mapSavedObjectToMetricsExplorerView( + savedObject: SavedObject | SavedObjectsUpdateResponse, defaultViewId?: string - ) { + ): MetricsExplorerView { const metricsExplorerViewSavedObject = decodeOrThrow(metricsExplorerViewSavedObjectRT)( savedObject ); @@ -160,7 +166,9 @@ export class MetricsExplorerViewsClient implements IMetricsExplorerViewsClient { version: metricsExplorerViewSavedObject.version, updatedAt: metricsExplorerViewSavedObject.updated_at, attributes: { - ...metricsExplorerViewSavedObject.attributes, + ...decodeOrThrow(metricsExplorerViewAttributesRT)( + metricsExplorerViewSavedObject.attributes + ), isDefault: metricsExplorerViewSavedObject.id === defaultViewId, isStatic: false, }, diff --git a/x-pack/plugins/infra/server/services/metrics_explorer_views/types.ts b/x-pack/plugins/infra/server/services/metrics_explorer_views/types.ts index 851cdf3ad77f0..b4ec7fb1051f2 100644 --- a/x-pack/plugins/infra/server/services/metrics_explorer_views/types.ts +++ b/x-pack/plugins/infra/server/services/metrics_explorer_views/types.ts @@ -11,10 +11,12 @@ import type { SavedObjectsServiceStart, } from '@kbn/core/server'; import type { + FindMetricsExplorerViewResponsePayload, + GetMetricsExplorerViewResponsePayload, MetricsExplorerViewRequestQuery, UpdateMetricsExplorerViewAttributesRequestPayload, + UpdateMetricsExplorerViewResponsePayload, } from '../../../common/http_api/latest'; -import type { MetricsExplorerView } from '../../../common/metrics_explorer_views'; import type { InfraSources } from '../../lib/sources'; export interface MetricsExplorerViewsServiceStartDeps { @@ -31,14 +33,16 @@ export interface MetricsExplorerViewsServiceStart { export interface IMetricsExplorerViewsClient { delete(metricsExplorerViewId: string): Promise<{}>; - find(query: MetricsExplorerViewRequestQuery): Promise; + find( + query: MetricsExplorerViewRequestQuery + ): Promise; get( metricsExplorerViewId: string, query: MetricsExplorerViewRequestQuery - ): Promise; + ): Promise; update( metricsExplorerViewId: string | null, metricsExplorerViewAttributes: UpdateMetricsExplorerViewAttributesRequestPayload, query: MetricsExplorerViewRequestQuery - ): Promise; + ): Promise; }