From 61bb52c65b4a829218039e1e42057f6fe022aa59 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Wed, 26 Apr 2023 14:23:48 +0200 Subject: [PATCH] [Infrastructure UI] Implement Metrics explorer views CRUD endpoints (#155621) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📓 Summary Part of #152617 Closes #155111 This PR implements the CRUD endpoints for the metrics explorer views. Following the approach used for the InventoryView service, it exposes a client that abstracts all the logic concerned to the `metrics-explorer-view` saved objects. It also follows the guideline provided for [Versioning interfaces](https://docs.elastic.dev/kibana-dev-docs/versioning-interfaces) and [Versioning HTTP APIs](https://docs.elastic.dev/kibana-dev-docs/versioning-http-apis), preparing for the serverless. ## 🤓 Tips for the reviewer You can open the Kibana dev tools and play with the following snippet to test the create APIs, or you can perform the same requests with your preferred client: ``` // Get all GET kbn:/api/infra/metrics_explorer_views // Create one POST kbn:/api/infra/metrics_explorer_views { "attributes": { "name": "My view" } } // Get one GET kbn:/api/infra/metrics_explorer_views/ // Update one PUT kbn:/api/infra/metrics_explorer_views/ { "attributes": { "name": "My view 2" } } // Delete one DELETE kbn:/api/infra/metrics_explorer_views/ ``` --------- Co-authored-by: Marco Antonio Ghiani Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/infra/common/http_api/index.ts | 1 + .../http_api/inventory_views/v1/common.ts | 2 +- .../plugins/infra/common/http_api/latest.ts | 1 + .../metrics_explorer_views/v1/common.ts | 68 ++++ .../v1/create_metrics_explorer_view.ts | 29 ++ .../v1/find_metrics_explorer_view.ts | 36 ++ .../v1/get_metrics_explorer_view.ts | 16 + .../metrics_explorer_views/v1/index.ts | 12 + .../v1/update_metrics_explorer_view.ts | 29 ++ .../common/metrics_explorer_views/defaults.ts | 2 +- .../common/metrics_explorer_views/index.ts | 1 + ....mock.ts => metrics_explorer_view.mock.ts} | 2 +- x-pack/plugins/infra/server/infra_server.ts | 2 + x-pack/plugins/infra/server/mocks.ts | 2 + x-pack/plugins/infra/server/plugin.ts | 15 +- .../server/routes/inventory_views/README.md | 4 + .../inventory_views/create_inventory_view.ts | 2 +- .../inventory_views/delete_inventory_view.ts | 2 +- .../inventory_views/find_inventory_view.ts | 2 +- .../inventory_views/get_inventory_view.ts | 2 +- .../inventory_views/update_inventory_view.ts | 2 +- .../routes/metrics_explorer_views/README.md | 354 ++++++++++++++++++ .../create_metrics_explorer_view.ts | 58 +++ .../delete_metrics_explorer_view.ts | 54 +++ .../find_metrics_explorer_view.ts | 49 +++ .../get_metrics_explorer_view.ts | 62 +++ .../routes/metrics_explorer_views/index.ts | 23 ++ .../update_metrics_explorer_view.ts | 65 ++++ .../metrics_explorer_view/types.ts | 12 +- .../inventory_views/inventory_views_client.ts | 24 +- .../services/metrics_explorer_views/index.ts | 14 + .../metrics_explorer_views_client.mock.ts | 17 + .../metrics_explorer_views_client.test.ts | 262 +++++++++++++ .../metrics_explorer_views_client.ts | 218 +++++++++++ .../metrics_explorer_views_service.mock.ts | 18 + .../metrics_explorer_views_service.ts | 39 ++ .../services/metrics_explorer_views/types.ts | 48 +++ x-pack/plugins/infra/server/types.ts | 2 + 38 files changed, 1528 insertions(+), 23 deletions(-) create mode 100644 x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/common.ts create mode 100644 x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/create_metrics_explorer_view.ts create mode 100644 x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/find_metrics_explorer_view.ts create mode 100644 x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/get_metrics_explorer_view.ts create mode 100644 x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/index.ts create mode 100644 x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/update_metrics_explorer_view.ts rename x-pack/plugins/infra/common/metrics_explorer_views/{metric_explorer_view.mock.ts => metrics_explorer_view.mock.ts} (93%) create mode 100644 x-pack/plugins/infra/server/routes/metrics_explorer_views/README.md create mode 100644 x-pack/plugins/infra/server/routes/metrics_explorer_views/create_metrics_explorer_view.ts create mode 100644 x-pack/plugins/infra/server/routes/metrics_explorer_views/delete_metrics_explorer_view.ts create mode 100644 x-pack/plugins/infra/server/routes/metrics_explorer_views/find_metrics_explorer_view.ts create mode 100644 x-pack/plugins/infra/server/routes/metrics_explorer_views/get_metrics_explorer_view.ts create mode 100644 x-pack/plugins/infra/server/routes/metrics_explorer_views/index.ts create mode 100644 x-pack/plugins/infra/server/routes/metrics_explorer_views/update_metrics_explorer_view.ts create mode 100644 x-pack/plugins/infra/server/services/metrics_explorer_views/index.ts create mode 100644 x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_client.mock.ts create mode 100644 x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_client.test.ts create mode 100644 x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_client.ts create mode 100644 x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_service.mock.ts create mode 100644 x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_service.ts create mode 100644 x-pack/plugins/infra/server/services/metrics_explorer_views/types.ts diff --git a/x-pack/plugins/infra/common/http_api/index.ts b/x-pack/plugins/infra/common/http_api/index.ts index 355c5925702f7..e9dc741f39652 100644 --- a/x-pack/plugins/infra/common/http_api/index.ts +++ b/x-pack/plugins/infra/common/http_api/index.ts @@ -20,3 +20,4 @@ export * from './infra'; */ export * from './latest'; export * as inventoryViewsV1 from './inventory_views/v1'; +export * as metricsExplorerViewsV1 from './metrics_explorer_views/v1'; diff --git a/x-pack/plugins/infra/common/http_api/inventory_views/v1/common.ts b/x-pack/plugins/infra/common/http_api/inventory_views/v1/common.ts index c229170b8007b..3db684628334e 100644 --- a/x-pack/plugins/infra/common/http_api/inventory_views/v1/common.ts +++ b/x-pack/plugins/infra/common/http_api/inventory_views/v1/common.ts @@ -63,4 +63,4 @@ export const inventoryViewResponsePayloadRT = rt.type({ data: inventoryViewResponseRT, }); -export type GetInventoryViewResponsePayload = rt.TypeOf; +export type InventoryViewResponsePayload = rt.TypeOf; diff --git a/x-pack/plugins/infra/common/http_api/latest.ts b/x-pack/plugins/infra/common/http_api/latest.ts index 519da4a60dec1..effdbeda041da 100644 --- a/x-pack/plugins/infra/common/http_api/latest.ts +++ b/x-pack/plugins/infra/common/http_api/latest.ts @@ -6,3 +6,4 @@ */ export * from './inventory_views/v1'; +export * from './metrics_explorer_views/v1'; 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 new file mode 100644 index 0000000000000..76b6daf60a324 --- /dev/null +++ b/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/common.ts @@ -0,0 +1,68 @@ +/* + * 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 { nonEmptyStringRt } from '@kbn/io-ts-utils'; +import * as rt from 'io-ts'; +import { either } from 'fp-ts/Either'; + +export const METRICS_EXPLORER_VIEW_URL = '/api/infra/metrics_explorer_views'; +export const METRICS_EXPLORER_VIEW_URL_ENTITY = `${METRICS_EXPLORER_VIEW_URL}/{metricsExplorerViewId}`; +export const getMetricsExplorerViewUrl = (metricsExplorerViewId?: string) => + [METRICS_EXPLORER_VIEW_URL, metricsExplorerViewId].filter(Boolean).join('/'); + +const metricsExplorerViewIdRT = new rt.Type( + 'MetricsExplorerViewId', + rt.string.is, + (u, c) => + either.chain(rt.string.validate(u, c), (id) => { + return id === '0' + ? rt.failure(u, c, `The metrics explorer view with id ${id} is not configurable.`) + : rt.success(id); + }), + String +); + +export const metricsExplorerViewRequestParamsRT = rt.type({ + metricsExplorerViewId: metricsExplorerViewIdRT, +}); + +export type MetricsExplorerViewRequestParams = rt.TypeOf; + +export const metricsExplorerViewRequestQueryRT = rt.partial({ + sourceId: rt.string, +}); + +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, +}); + +export type GetMetricsExplorerViewResponsePayload = rt.TypeOf< + typeof metricsExplorerViewResponsePayloadRT +>; 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 new file mode 100644 index 0000000000000..5550404529cf1 --- /dev/null +++ b/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/create_metrics_explorer_view.ts @@ -0,0 +1,29 @@ +/* + * 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 { nonEmptyStringRt } from '@kbn/io-ts-utils'; +import * as rt from 'io-ts'; + +export const createMetricsExplorerViewAttributesRequestPayloadRT = rt.intersection([ + rt.type({ + name: nonEmptyStringRt, + }), + rt.UnknownRecord, + rt.exact(rt.partial({ isDefault: rt.undefined, isStatic: rt.undefined })), +]); + +export type CreateMetricsExplorerViewAttributesRequestPayload = rt.TypeOf< + typeof createMetricsExplorerViewAttributesRequestPayloadRT +>; + +export const createMetricsExplorerViewRequestPayloadRT = rt.type({ + attributes: createMetricsExplorerViewAttributesRequestPayloadRT, +}); + +export type CreateMetricsExplorerViewRequestPayload = rt.TypeOf< + typeof createMetricsExplorerViewRequestPayloadRT +>; 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 new file mode 100644 index 0000000000000..c504b54a4f914 --- /dev/null +++ b/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/find_metrics_explorer_view.ts @@ -0,0 +1,36 @@ +/* + * 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 { 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, + }), + ]) +); + +export const findMetricsExplorerViewResponsePayloadRT = rt.type({ + data: rt.array(findMetricsExplorerViewResponseRT), +}); + +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 new file mode 100644 index 0000000000000..8a828e00c917f --- /dev/null +++ b/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/get_metrics_explorer_view.ts @@ -0,0 +1,16 @@ +/* + * 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 * as rt from 'io-ts'; + +export const getMetricsExplorerViewRequestParamsRT = rt.type({ + metricsExplorerViewId: rt.string, +}); + +export type GetMetricsExplorerViewRequestParams = rt.TypeOf< + typeof getMetricsExplorerViewRequestParamsRT +>; diff --git a/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/index.ts b/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/index.ts new file mode 100644 index 0000000000000..62a0b7a633975 --- /dev/null +++ b/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/index.ts @@ -0,0 +1,12 @@ +/* + * 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 './common'; +export * from './get_metrics_explorer_view'; +export * from './find_metrics_explorer_view'; +export * from './create_metrics_explorer_view'; +export * from './update_metrics_explorer_view'; 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 new file mode 100644 index 0000000000000..5bf327789a65c --- /dev/null +++ b/x-pack/plugins/infra/common/http_api/metrics_explorer_views/v1/update_metrics_explorer_view.ts @@ -0,0 +1,29 @@ +/* + * 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 { nonEmptyStringRt } from '@kbn/io-ts-utils'; +import * as rt from 'io-ts'; + +export const updateMetricsExplorerViewAttributesRequestPayloadRT = rt.intersection([ + rt.type({ + name: nonEmptyStringRt, + }), + rt.UnknownRecord, + rt.exact(rt.partial({ isDefault: rt.undefined, isStatic: rt.undefined })), +]); + +export type UpdateMetricsExplorerViewAttributesRequestPayload = rt.TypeOf< + typeof updateMetricsExplorerViewAttributesRequestPayloadRT +>; + +export const updateMetricsExplorerViewRequestPayloadRT = rt.type({ + attributes: updateMetricsExplorerViewAttributesRequestPayloadRT, +}); + +export type UpdateMetricsExplorerViewRequestPayload = rt.TypeOf< + typeof updateMetricsExplorerViewRequestPayloadRT +>; 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 88771d1a76fcb..8c7e6ffff192f 100644 --- a/x-pack/plugins/infra/common/metrics_explorer_views/defaults.ts +++ b/x-pack/plugins/infra/common/metrics_explorer_views/defaults.ts @@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n'; import type { NonEmptyString } from '@kbn/io-ts-utils'; import type { MetricsExplorerViewAttributes } from './types'; -export const staticMetricsExplorerViewId = 'static'; +export const staticMetricsExplorerViewId = '0'; export const staticMetricsExplorerViewAttributes: MetricsExplorerViewAttributes = { name: i18n.translate('xpack.infra.savedView.defaultViewNameHosts', { diff --git a/x-pack/plugins/infra/common/metrics_explorer_views/index.ts b/x-pack/plugins/infra/common/metrics_explorer_views/index.ts index 6cc0ccaa93a6d..ae809a6c7c615 100644 --- a/x-pack/plugins/infra/common/metrics_explorer_views/index.ts +++ b/x-pack/plugins/infra/common/metrics_explorer_views/index.ts @@ -5,4 +5,5 @@ * 2.0. */ +export * from './defaults'; export * from './types'; diff --git a/x-pack/plugins/infra/common/metrics_explorer_views/metric_explorer_view.mock.ts b/x-pack/plugins/infra/common/metrics_explorer_views/metrics_explorer_view.mock.ts similarity index 93% rename from x-pack/plugins/infra/common/metrics_explorer_views/metric_explorer_view.mock.ts rename to x-pack/plugins/infra/common/metrics_explorer_views/metrics_explorer_view.mock.ts index e921c37dd21f8..98f6675f42a66 100644 --- a/x-pack/plugins/infra/common/metrics_explorer_views/metric_explorer_view.mock.ts +++ b/x-pack/plugins/infra/common/metrics_explorer_views/metrics_explorer_view.mock.ts @@ -8,7 +8,7 @@ import { staticMetricsExplorerViewAttributes } from './defaults'; import type { MetricsExplorerView, MetricsExplorerViewAttributes } from './types'; -export const createmetricsExplorerViewMock = ( +export const createMetricsExplorerViewMock = ( id: string, attributes: MetricsExplorerViewAttributes, updatedAt?: number, diff --git a/x-pack/plugins/infra/server/infra_server.ts b/x-pack/plugins/infra/server/infra_server.ts index 3b6ea0333f236..4d29974ceb75f 100644 --- a/x-pack/plugins/infra/server/infra_server.ts +++ b/x-pack/plugins/infra/server/infra_server.ts @@ -37,6 +37,7 @@ import { initOverviewRoute } from './routes/overview'; import { initProcessListRoute } from './routes/process_list'; import { initSnapshotRoute } from './routes/snapshot'; import { initInfraMetricsRoute } from './routes/infra'; +import { initMetricsExplorerViewRoutes } from './routes/metrics_explorer_views'; export const initInfraServer = (libs: InfraBackendLibs) => { initIpToHostName(libs); @@ -59,6 +60,7 @@ export const initInfraServer = (libs: InfraBackendLibs) => { initLogEntriesSummaryHighlightsRoute(libs); initLogViewRoutes(libs); initMetricExplorerRoute(libs); + initMetricsExplorerViewRoutes(libs); initMetricsAPIRoute(libs); initMetadataRoute(libs); initInventoryMetaRoute(libs); diff --git a/x-pack/plugins/infra/server/mocks.ts b/x-pack/plugins/infra/server/mocks.ts index 5a97f4a7d9a52..7e5a349cb1e01 100644 --- a/x-pack/plugins/infra/server/mocks.ts +++ b/x-pack/plugins/infra/server/mocks.ts @@ -10,6 +10,7 @@ import { createLogViewsServiceSetupMock, createLogViewsServiceStartMock, } from './services/log_views/log_views_service.mock'; +import { createMetricsExplorerViewsServiceStartMock } from './services/metrics_explorer_views/metrics_explorer_views_service.mock'; import { InfraPluginSetup, InfraPluginStart } from './types'; const createInfraSetupMock = () => { @@ -26,6 +27,7 @@ const createInfraStartMock = () => { getMetricIndices: jest.fn(), inventoryViews: createInventoryViewsServiceStartMock(), logViews: createLogViewsServiceStartMock(), + metricsExplorerViews: createMetricsExplorerViewsServiceStartMock(), }; return infraStartMock; }; diff --git a/x-pack/plugins/infra/server/plugin.ts b/x-pack/plugins/infra/server/plugin.ts index 2c114bb75d6e5..eb8777665895a 100644 --- a/x-pack/plugins/infra/server/plugin.ts +++ b/x-pack/plugins/infra/server/plugin.ts @@ -49,6 +49,7 @@ import { import { InventoryViewsService } from './services/inventory_views'; import { LogEntriesService } from './services/log_entries'; import { LogViewsService } from './services/log_views'; +import { MetricsExplorerViewsService } from './services/metrics_explorer_views'; import { RulesService } from './services/rules'; import { InfraConfig, @@ -122,6 +123,7 @@ export class InfraServerPlugin private metricsRules: RulesService; private inventoryViews: InventoryViewsService; private logViews: LogViewsService; + private metricsExplorerViews: MetricsExplorerViewsService; constructor(context: PluginInitializerContext) { this.config = context.config.get(); @@ -140,6 +142,9 @@ export class InfraServerPlugin this.inventoryViews = new InventoryViewsService(this.logger.get('inventoryViews')); this.logViews = new LogViewsService(this.logger.get('logViews')); + this.metricsExplorerViews = new MetricsExplorerViewsService( + this.logger.get('metricsExplorerViews') + ); } setup(core: InfraPluginCoreSetup, plugins: InfraServerPluginSetupDeps) { @@ -155,12 +160,13 @@ export class InfraServerPlugin ); const inventoryViews = this.inventoryViews.setup(); const logViews = this.logViews.setup(); + const metricsExplorerViews = this.metricsExplorerViews.setup(); // register saved object types core.savedObjects.registerType(infraSourceConfigurationSavedObjectType); - core.savedObjects.registerType(metricsExplorerViewSavedObjectType); core.savedObjects.registerType(inventoryViewSavedObjectType); core.savedObjects.registerType(logViewSavedObjectType); + core.savedObjects.registerType(metricsExplorerViewSavedObjectType); // TODO: separate these out individually and do away with "domains" as a temporary group // and make them available via the request context so we can do away with @@ -237,6 +243,7 @@ export class InfraServerPlugin defineInternalSourceConfiguration: sources.defineInternalSourceConfiguration.bind(sources), inventoryViews, logViews, + metricsExplorerViews, } as InfraPluginSetup; } @@ -258,9 +265,15 @@ export class InfraServerPlugin }, }); + const metricsExplorerViews = this.metricsExplorerViews.start({ + infraSources: this.libs.sources, + savedObjects: core.savedObjects, + }); + return { inventoryViews, logViews, + metricsExplorerViews, getMetricIndices: makeGetMetricIndices(this.libs.sources), }; } diff --git a/x-pack/plugins/infra/server/routes/inventory_views/README.md b/x-pack/plugins/infra/server/routes/inventory_views/README.md index 8a09aedef1b75..be7d1c3734157 100644 --- a/x-pack/plugins/infra/server/routes/inventory_views/README.md +++ b/x-pack/plugins/infra/server/routes/inventory_views/README.md @@ -221,6 +221,8 @@ Updates an inventory view. Any attribute can be updated except for `isDefault` and `isStatic`, which are derived by the source configuration preference set by the user. +Any attempt to update the static view with id `0` will return a `400 The inventory view with id 0 is not configurable.` + ### Request - **Method**: PUT @@ -324,6 +326,8 @@ Status code: 409 Deletes an inventory view. +Any attempt to delete the static view with id `0` will return a `400 The inventory view with id 0 is not configurable.` + ### Request - **Method**: DELETE diff --git a/x-pack/plugins/infra/server/routes/inventory_views/create_inventory_view.ts b/x-pack/plugins/infra/server/routes/inventory_views/create_inventory_view.ts index 8f3d52db7a6dd..90bb47d8a2d76 100644 --- a/x-pack/plugins/infra/server/routes/inventory_views/create_inventory_view.ts +++ b/x-pack/plugins/infra/server/routes/inventory_views/create_inventory_view.ts @@ -28,7 +28,7 @@ export const initCreateInventoryViewRoute = ({ }, async (_requestContext, request, response) => { const { body } = request; - const { inventoryViews } = (await getStartServices())[2]; + const [, , { inventoryViews }] = await getStartServices(); const inventoryViewsClient = inventoryViews.getScopedClient(request); try { diff --git a/x-pack/plugins/infra/server/routes/inventory_views/delete_inventory_view.ts b/x-pack/plugins/infra/server/routes/inventory_views/delete_inventory_view.ts index 83ad61fc46c52..e86e44fc0ac05 100644 --- a/x-pack/plugins/infra/server/routes/inventory_views/delete_inventory_view.ts +++ b/x-pack/plugins/infra/server/routes/inventory_views/delete_inventory_view.ts @@ -27,7 +27,7 @@ export const initDeleteInventoryViewRoute = ({ }, async (_requestContext, request, response) => { const { params } = request; - const { inventoryViews } = (await getStartServices())[2]; + const [, , { inventoryViews }] = await getStartServices(); const inventoryViewsClient = inventoryViews.getScopedClient(request); try { diff --git a/x-pack/plugins/infra/server/routes/inventory_views/find_inventory_view.ts b/x-pack/plugins/infra/server/routes/inventory_views/find_inventory_view.ts index abdfc2f8749e4..a9de3a426f14f 100644 --- a/x-pack/plugins/infra/server/routes/inventory_views/find_inventory_view.ts +++ b/x-pack/plugins/infra/server/routes/inventory_views/find_inventory_view.ts @@ -27,7 +27,7 @@ export const initFindInventoryViewRoute = ({ }, async (_requestContext, request, response) => { const { query } = request; - const { inventoryViews } = (await getStartServices())[2]; + const [, , { inventoryViews }] = await getStartServices(); const inventoryViewsClient = inventoryViews.getScopedClient(request); try { diff --git a/x-pack/plugins/infra/server/routes/inventory_views/get_inventory_view.ts b/x-pack/plugins/infra/server/routes/inventory_views/get_inventory_view.ts index 1a5f5adec136d..0cb9f815ef089 100644 --- a/x-pack/plugins/infra/server/routes/inventory_views/get_inventory_view.ts +++ b/x-pack/plugins/infra/server/routes/inventory_views/get_inventory_view.ts @@ -30,7 +30,7 @@ export const initGetInventoryViewRoute = ({ }, async (_requestContext, request, response) => { const { params, query } = request; - const { inventoryViews } = (await getStartServices())[2]; + const [, , { inventoryViews }] = await getStartServices(); const inventoryViewsClient = inventoryViews.getScopedClient(request); try { diff --git a/x-pack/plugins/infra/server/routes/inventory_views/update_inventory_view.ts b/x-pack/plugins/infra/server/routes/inventory_views/update_inventory_view.ts index d2b583437d177..0f225e0546fd1 100644 --- a/x-pack/plugins/infra/server/routes/inventory_views/update_inventory_view.ts +++ b/x-pack/plugins/infra/server/routes/inventory_views/update_inventory_view.ts @@ -32,7 +32,7 @@ export const initUpdateInventoryViewRoute = ({ }, async (_requestContext, request, response) => { const { body, params, query } = request; - const { inventoryViews } = (await getStartServices())[2]; + const [, , { inventoryViews }] = await getStartServices(); const inventoryViewsClient = inventoryViews.getScopedClient(request); try { 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 new file mode 100644 index 0000000000000..d14d8298d0d0f --- /dev/null +++ b/x-pack/plugins/infra/server/routes/metrics_explorer_views/README.md @@ -0,0 +1,354 @@ +# Metrics Explorer Views CRUD api + +## Find all: `GET /api/infra/metrics_explorer_views` + +Retrieves all metrics explorer views in a reduced version. + +### Request + +- **Method**: GET +- **Path**: /api/infra/metrics_explorer_views +- **Query params**: + - `sourceId` _(optional)_: Specify a source id related to the metrics explorer views. Default value: `default`. + +### Response + +```json +GET /api/infra/metrics_explorer_views + +Status code: 200 + +{ + "data": [ + { + "id": "static", + "attributes": { + "name": "Default view", + "isDefault": false, + "isStatic": true + } + }, + { + "id": "927ad6a0-da0c-11ed-9487-41e9b90f96b9", + "version": "WzQwMiwxXQ==", + "updatedAt": 1681398305034, + "attributes": { + "name": "Ad-hoc", + "isDefault": true, + "isStatic": false + } + }, + { + "id": "c301ef20-da0c-11ed-aac0-77131228e6f1", + "version": "WzQxMCwxXQ==", + "updatedAt": 1681398386450, + "attributes": { + "name": "Custom", + "isDefault": false, + "isStatic": false + } + } + ] +} +``` + +## Get one: `GET /api/infra/metrics_explorer_views/{metricsExplorerViewId}` + +Retrieves a single metrics explorer view by ID + +### Request + +- **Method**: GET +- **Path**: /api/infra/metrics_explorer_views/{metricsExplorerViewId} +- **Query params**: + - `sourceId` _(optional)_: Specify a source id related to the metrics explorer view. Default value: `default`. + +### Response + +```json +GET /api/infra/metrics_explorer_views/927ad6a0-da0c-11ed-9487-41e9b90f96b9 + +Status code: 200 + +{ + "data": { + "id": "927ad6a0-da0c-11ed-9487-41e9b90f96b9", + "version": "WzQwMiwxXQ==", + "updatedAt": 1681398305034, + "attributes": { + "name": "Ad-hoc", + "isDefault": true, + "isStatic": false, + "metric": { + "type": "cpu" + }, + "sort": { + "by": "name", + "direction": "desc" + }, + "groupBy": [], + "nodeType": "host", + "view": "map", + "customOptions": [], + "customMetrics": [], + "boundsOverride": { + "max": 1, + "min": 0 + }, + "autoBounds": true, + "accountId": "", + "region": "", + "autoReload": false, + "filterQuery": { + "expression": "", + "kind": "kuery" + }, + "legend": { + "palette": "cool", + "reverseColors": false, + "steps": 10 + }, + "timelineOpen": false + } + } +} +``` + +```json +GET /api/infra/metrics_explorer_views/random-id + +Status code: 404 + +{ + "statusCode": 404, + "error": "Not Found", + "message": "Saved object [metrics-explorer-view/random-id] not found" +} +``` + +## Create one: `POST /api/infra/metrics_explorer_views` + +Creates a new metrics explorer view. + +### Request + +- **Method**: POST +- **Path**: /api/infra/metrics_explorer_views +- **Request body**: + ```json + { + "attributes": { + "name": "View name", + "metric": { + "type": "cpu" + }, + "sort": { + "by": "name", + "direction": "desc" + }, + //... + } + } + ``` + +### Response + +```json +POST /api/infra/metrics_explorer_views + +Status code: 201 + +{ + "data": { + "id": "927ad6a0-da0c-11ed-9487-41e9b90f96b9", + "version": "WzQwMiwxXQ==", + "updatedAt": 1681398305034, + "attributes": { + "name": "View name", + "isDefault": false, + "isStatic": false, + "metric": { + "type": "cpu" + }, + "sort": { + "by": "name", + "direction": "desc" + }, + "groupBy": [], + "nodeType": "host", + "view": "map", + "customOptions": [], + "customMetrics": [], + "boundsOverride": { + "max": 1, + "min": 0 + }, + "autoBounds": true, + "accountId": "", + "region": "", + "autoReload": false, + "filterQuery": { + "expression": "", + "kind": "kuery" + }, + "legend": { + "palette": "cool", + "reverseColors": false, + "steps": 10 + }, + "timelineOpen": false + } + } +} +``` + +Send in the payload a `name` attribute already held by another view: +```json +POST /api/infra/metrics_explorer_views + +Status code: 409 + +{ + "statusCode": 409, + "error": "Conflict", + "message": "A view with that name already exists." +} +``` + +## Update one: `PUT /api/infra/metrics_explorer_views/{metricsExplorerViewId}` + +Updates a metrics explorer view. + +Any attribute can be updated except for `isDefault` and `isStatic`, which are derived by the source configuration preference set by the user. + +Any attempt to update the static view with id `0` will return a `400 The metrics explorer view with id 0 is not configurable.` + +### Request + +- **Method**: PUT +- **Path**: /api/infra/metrics_explorer_views/{metricsExplorerViewId} +- **Query params**: + - `sourceId` _(optional)_: Specify a source id related to the metrics explorer view. Default value: `default`. +- **Request body**: + ```json + { + "attributes": { + "name": "View name", + "metric": { + "type": "cpu" + }, + "sort": { + "by": "name", + "direction": "desc" + }, + //... + } + } + ``` + +### Response + +```json +PUT /api/infra/metrics_explorer_views/927ad6a0-da0c-11ed-9487-41e9b90f96b9 + +Status code: 200 + +{ + "data": { + "id": "927ad6a0-da0c-11ed-9487-41e9b90f96b9", + "version": "WzQwMiwxXQ==", + "updatedAt": 1681398305034, + "attributes": { + "name": "View name", + "isDefault": false, + "isStatic": false, + "metric": { + "type": "cpu" + }, + "sort": { + "by": "name", + "direction": "desc" + }, + "groupBy": [], + "nodeType": "host", + "view": "map", + "customOptions": [], + "customMetrics": [], + "boundsOverride": { + "max": 1, + "min": 0 + }, + "autoBounds": true, + "accountId": "", + "region": "", + "autoReload": false, + "filterQuery": { + "expression": "", + "kind": "kuery" + }, + "legend": { + "palette": "cool", + "reverseColors": false, + "steps": 10 + }, + "timelineOpen": false + } + } +} +``` + +```json +PUT /api/infra/metrics_explorer_views/random-id + +Status code: 404 + +{ + "statusCode": 404, + "error": "Not Found", + "message": "Saved object [metrics-explorer-view/random-id] not found" +} +``` + +Send in the payload a `name` attribute already held by another view: +```json +PUT /api/infra/metrics_explorer_views/927ad6a0-da0c-11ed-9487-41e9b90f96b9 + +Status code: 409 + +{ + "statusCode": 409, + "error": "Conflict", + "message": "A view with that name already exists." +} +``` + +## Delete one: `DELETE /api/infra/metrics_explorer_views/{metricsExplorerViewId}` + +Deletes a metrics explorer view. + +Any attempt to delete the static view with id `0` will return a `400 The metrics explorer view with id 0 is not configurable.` + +### Request + +- **Method**: DELETE +- **Path**: /api/infra/metrics_explorer_views/{metricsExplorerViewId} + +### Response + +```json +DELETE /api/infra/metrics_explorer_views/927ad6a0-da0c-11ed-9487-41e9b90f96b9 + +Status code: 204 No content +``` + +```json +DELETE /api/infra/metrics_explorer_views/random-id + +Status code: 404 + +{ + "statusCode": 404, + "error": "Not Found", + "message": "Saved object [metrics-explorer-view/random-id] not found" +} +``` diff --git a/x-pack/plugins/infra/server/routes/metrics_explorer_views/create_metrics_explorer_view.ts b/x-pack/plugins/infra/server/routes/metrics_explorer_views/create_metrics_explorer_view.ts new file mode 100644 index 0000000000000..948dd757e7e01 --- /dev/null +++ b/x-pack/plugins/infra/server/routes/metrics_explorer_views/create_metrics_explorer_view.ts @@ -0,0 +1,58 @@ +/* + * 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 { isBoom } from '@hapi/boom'; +import { createValidationFunction } from '../../../common/runtime_types'; +import { + createMetricsExplorerViewRequestPayloadRT, + metricsExplorerViewResponsePayloadRT, + METRICS_EXPLORER_VIEW_URL, +} from '../../../common/http_api/latest'; +import type { InfraBackendLibs } from '../../lib/infra_types'; + +export const initCreateMetricsExplorerViewRoute = ({ + framework, + getStartServices, +}: Pick) => { + framework.registerRoute( + { + method: 'post', + path: METRICS_EXPLORER_VIEW_URL, + validate: { + body: createValidationFunction(createMetricsExplorerViewRequestPayloadRT), + }, + }, + async (_requestContext, request, response) => { + const { body } = request; + const [, , { metricsExplorerViews }] = await getStartServices(); + const metricsExplorerViewsClient = metricsExplorerViews.getScopedClient(request); + + try { + const metricsExplorerView = await metricsExplorerViewsClient.create(body.attributes); + + return response.custom({ + statusCode: 201, + body: metricsExplorerViewResponsePayloadRT.encode({ data: metricsExplorerView }), + }); + } catch (error) { + if (isBoom(error)) { + return response.customError({ + statusCode: error.output.statusCode, + body: { message: error.output.payload.message }, + }); + } + + return response.customError({ + statusCode: error.statusCode ?? 500, + body: { + message: error.message ?? 'An unexpected error occurred', + }, + }); + } + } + ); +}; diff --git a/x-pack/plugins/infra/server/routes/metrics_explorer_views/delete_metrics_explorer_view.ts b/x-pack/plugins/infra/server/routes/metrics_explorer_views/delete_metrics_explorer_view.ts new file mode 100644 index 0000000000000..a3b6f8b05f099 --- /dev/null +++ b/x-pack/plugins/infra/server/routes/metrics_explorer_views/delete_metrics_explorer_view.ts @@ -0,0 +1,54 @@ +/* + * 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 { isBoom } from '@hapi/boom'; +import { createValidationFunction } from '../../../common/runtime_types'; +import { + metricsExplorerViewRequestParamsRT, + METRICS_EXPLORER_VIEW_URL_ENTITY, +} from '../../../common/http_api/latest'; +import type { InfraBackendLibs } from '../../lib/infra_types'; + +export const initDeleteMetricsExplorerViewRoute = ({ + framework, + getStartServices, +}: Pick) => { + framework.registerRoute( + { + method: 'delete', + path: METRICS_EXPLORER_VIEW_URL_ENTITY, + validate: { + params: createValidationFunction(metricsExplorerViewRequestParamsRT), + }, + }, + async (_requestContext, request, response) => { + const { params } = request; + const [, , { metricsExplorerViews }] = await getStartServices(); + const metricsExplorerViewsClient = metricsExplorerViews.getScopedClient(request); + + try { + await metricsExplorerViewsClient.delete(params.metricsExplorerViewId); + + return response.noContent(); + } catch (error) { + if (isBoom(error)) { + return response.customError({ + statusCode: error.output.statusCode, + body: { message: error.output.payload.message }, + }); + } + + return response.customError({ + statusCode: error.statusCode ?? 500, + body: { + message: error.message ?? 'An unexpected error occurred', + }, + }); + } + } + ); +}; diff --git a/x-pack/plugins/infra/server/routes/metrics_explorer_views/find_metrics_explorer_view.ts b/x-pack/plugins/infra/server/routes/metrics_explorer_views/find_metrics_explorer_view.ts new file mode 100644 index 0000000000000..fbae7790b04eb --- /dev/null +++ b/x-pack/plugins/infra/server/routes/metrics_explorer_views/find_metrics_explorer_view.ts @@ -0,0 +1,49 @@ +/* + * 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 { createValidationFunction } from '../../../common/runtime_types'; +import { + findMetricsExplorerViewResponsePayloadRT, + metricsExplorerViewRequestQueryRT, + METRICS_EXPLORER_VIEW_URL, +} from '../../../common/http_api/latest'; +import type { InfraBackendLibs } from '../../lib/infra_types'; + +export const initFindMetricsExplorerViewRoute = ({ + framework, + getStartServices, +}: Pick) => { + framework.registerRoute( + { + method: 'get', + path: METRICS_EXPLORER_VIEW_URL, + validate: { + query: createValidationFunction(metricsExplorerViewRequestQueryRT), + }, + }, + async (_requestContext, request, response) => { + const { query } = request; + const [, , { metricsExplorerViews }] = await getStartServices(); + const metricsExplorerViewsClient = metricsExplorerViews.getScopedClient(request); + + try { + const metricsExplorerViewsList = await metricsExplorerViewsClient.find(query); + + return response.ok({ + body: findMetricsExplorerViewResponsePayloadRT.encode({ data: metricsExplorerViewsList }), + }); + } catch (error) { + return response.customError({ + statusCode: error.statusCode ?? 500, + body: { + message: error.message ?? 'An unexpected error occurred', + }, + }); + } + } + ); +}; diff --git a/x-pack/plugins/infra/server/routes/metrics_explorer_views/get_metrics_explorer_view.ts b/x-pack/plugins/infra/server/routes/metrics_explorer_views/get_metrics_explorer_view.ts new file mode 100644 index 0000000000000..b8e71a3c662d6 --- /dev/null +++ b/x-pack/plugins/infra/server/routes/metrics_explorer_views/get_metrics_explorer_view.ts @@ -0,0 +1,62 @@ +/* + * 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 { isBoom } from '@hapi/boom'; +import { createValidationFunction } from '../../../common/runtime_types'; +import { + metricsExplorerViewResponsePayloadRT, + metricsExplorerViewRequestQueryRT, + METRICS_EXPLORER_VIEW_URL_ENTITY, + getMetricsExplorerViewRequestParamsRT, +} from '../../../common/http_api/latest'; +import type { InfraBackendLibs } from '../../lib/infra_types'; + +export const initGetMetricsExplorerViewRoute = ({ + framework, + getStartServices, +}: Pick) => { + framework.registerRoute( + { + method: 'get', + path: METRICS_EXPLORER_VIEW_URL_ENTITY, + validate: { + params: createValidationFunction(getMetricsExplorerViewRequestParamsRT), + query: createValidationFunction(metricsExplorerViewRequestQueryRT), + }, + }, + async (_requestContext, request, response) => { + const { params, query } = request; + const [, , { metricsExplorerViews }] = await getStartServices(); + const metricsExplorerViewsClient = metricsExplorerViews.getScopedClient(request); + + try { + const metricsExplorerView = await metricsExplorerViewsClient.get( + params.metricsExplorerViewId, + query + ); + + return response.ok({ + body: metricsExplorerViewResponsePayloadRT.encode({ data: metricsExplorerView }), + }); + } catch (error) { + if (isBoom(error)) { + return response.customError({ + statusCode: error.output.statusCode, + body: { message: error.output.payload.message }, + }); + } + + return response.customError({ + statusCode: error.statusCode ?? 500, + body: { + message: error.message ?? 'An unexpected error occurred', + }, + }); + } + } + ); +}; diff --git a/x-pack/plugins/infra/server/routes/metrics_explorer_views/index.ts b/x-pack/plugins/infra/server/routes/metrics_explorer_views/index.ts new file mode 100644 index 0000000000000..e4a6165374422 --- /dev/null +++ b/x-pack/plugins/infra/server/routes/metrics_explorer_views/index.ts @@ -0,0 +1,23 @@ +/* + * 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 { InfraBackendLibs } from '../../lib/infra_types'; +import { initCreateMetricsExplorerViewRoute } from './create_metrics_explorer_view'; +import { initDeleteMetricsExplorerViewRoute } from './delete_metrics_explorer_view'; +import { initFindMetricsExplorerViewRoute } from './find_metrics_explorer_view'; +import { initGetMetricsExplorerViewRoute } from './get_metrics_explorer_view'; +import { initUpdateMetricsExplorerViewRoute } from './update_metrics_explorer_view'; + +export const initMetricsExplorerViewRoutes = ( + dependencies: Pick +) => { + initCreateMetricsExplorerViewRoute(dependencies); + initDeleteMetricsExplorerViewRoute(dependencies); + initFindMetricsExplorerViewRoute(dependencies); + initGetMetricsExplorerViewRoute(dependencies); + initUpdateMetricsExplorerViewRoute(dependencies); +}; diff --git a/x-pack/plugins/infra/server/routes/metrics_explorer_views/update_metrics_explorer_view.ts b/x-pack/plugins/infra/server/routes/metrics_explorer_views/update_metrics_explorer_view.ts new file mode 100644 index 0000000000000..ebd8caef8e030 --- /dev/null +++ b/x-pack/plugins/infra/server/routes/metrics_explorer_views/update_metrics_explorer_view.ts @@ -0,0 +1,65 @@ +/* + * 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 { isBoom } from '@hapi/boom'; +import { createValidationFunction } from '../../../common/runtime_types'; +import { + metricsExplorerViewRequestParamsRT, + metricsExplorerViewRequestQueryRT, + metricsExplorerViewResponsePayloadRT, + METRICS_EXPLORER_VIEW_URL_ENTITY, + updateMetricsExplorerViewRequestPayloadRT, +} from '../../../common/http_api/latest'; +import type { InfraBackendLibs } from '../../lib/infra_types'; + +export const initUpdateMetricsExplorerViewRoute = ({ + framework, + getStartServices, +}: Pick) => { + framework.registerRoute( + { + method: 'put', + path: METRICS_EXPLORER_VIEW_URL_ENTITY, + validate: { + params: createValidationFunction(metricsExplorerViewRequestParamsRT), + query: createValidationFunction(metricsExplorerViewRequestQueryRT), + body: createValidationFunction(updateMetricsExplorerViewRequestPayloadRT), + }, + }, + async (_requestContext, request, response) => { + const { body, params, query } = request; + const [, , { metricsExplorerViews }] = await getStartServices(); + const metricsExplorerViewsClient = metricsExplorerViews.getScopedClient(request); + + try { + const metricsExplorerView = await metricsExplorerViewsClient.update( + params.metricsExplorerViewId, + body.attributes, + query + ); + + return response.ok({ + body: metricsExplorerViewResponsePayloadRT.encode({ data: metricsExplorerView }), + }); + } catch (error) { + if (isBoom(error)) { + return response.customError({ + statusCode: error.output.statusCode, + body: { message: error.output.payload.message }, + }); + } + + return response.customError({ + statusCode: error.statusCode ?? 500, + body: { + message: error.message ?? 'An unexpected error occurred', + }, + }); + } + } + ); +}; 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 1168b2003994e..15fe0eb970cc2 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 @@ -5,14 +5,20 @@ * 2.0. */ -import { isoToEpochRt } from '@kbn/io-ts-utils'; +import { isoToEpochRt, nonEmptyStringRt } from '@kbn/io-ts-utils'; import * as rt from 'io-ts'; -import { metricsExplorerViewAttributesRT } from '../../../common/metrics_explorer_views'; + +export const metricsExplorerViewSavedObjectAttributesRT = rt.intersection([ + rt.strict({ + name: nonEmptyStringRt, + }), + rt.UnknownRecord, +]); export const metricsExplorerViewSavedObjectRT = rt.intersection([ rt.type({ id: rt.string, - attributes: metricsExplorerViewAttributesRT, + attributes: metricsExplorerViewSavedObjectAttributesRT, }), rt.partial({ version: rt.string, diff --git a/x-pack/plugins/infra/server/services/inventory_views/inventory_views_client.ts b/x-pack/plugins/infra/server/services/inventory_views/inventory_views_client.ts index 55a8df1024a6e..c32da344354b6 100644 --- a/x-pack/plugins/infra/server/services/inventory_views/inventory_views_client.ts +++ b/x-pack/plugins/infra/server/services/inventory_views/inventory_views_client.ts @@ -35,18 +35,16 @@ export class InventoryViewsClient implements IInventoryViewsClient { ) {} static STATIC_VIEW_ID = '0'; + static DEFAULT_SOURCE_ID = 'default'; public async find(query: InventoryViewRequestQuery): Promise { this.logger.debug('Trying to load inventory views ...'); - const sourceId = query.sourceId ?? 'default'; + const sourceId = query.sourceId ?? InventoryViewsClient.DEFAULT_SOURCE_ID; const [sourceConfiguration, inventoryViewSavedObject] = await Promise.all([ this.infraSources.getSourceConfiguration(this.savedObjectsClient, sourceId), - this.savedObjectsClient.find({ - type: inventoryViewSavedObjectName, - perPage: 1000, // Fetch 1 page by default with a max of 1000 results - }), + this.getAllViews(), ]); const defaultView = InventoryViewsClient.createStaticView( @@ -72,7 +70,7 @@ export class InventoryViewsClient implements IInventoryViewsClient { ): Promise { this.logger.debug(`Trying to load inventory view with id ${inventoryViewId} ...`); - const sourceId = query.sourceId ?? 'default'; + const sourceId = query.sourceId ?? InventoryViewsClient.DEFAULT_SOURCE_ID; // Handle the case where the requested resource is the static inventory view if (inventoryViewId === InventoryViewsClient.STATIC_VIEW_ID) { @@ -123,7 +121,7 @@ export class InventoryViewsClient implements IInventoryViewsClient { // Validate there is not a view with the same name await this.assertNameConflict(attributes.name, [inventoryViewId]); - const sourceId = query.sourceId ?? 'default'; + const sourceId = query.sourceId ?? InventoryViewsClient.DEFAULT_SOURCE_ID; const [sourceConfiguration, inventoryViewSavedObject] = await Promise.all([ this.infraSources.getSourceConfiguration(this.savedObjectsClient, sourceId), @@ -160,6 +158,13 @@ export class InventoryViewsClient implements IInventoryViewsClient { }; } + private getAllViews() { + return this.savedObjectsClient.find({ + type: inventoryViewSavedObjectName, + perPage: 1000, // Fetch 1 page by default with a max of 1000 results + }); + } + private moveDefaultViewOnTop(views: InventoryView[]) { const defaultViewPosition = views.findIndex((view) => view.attributes.isDefault); @@ -175,10 +180,7 @@ export class InventoryViewsClient implements IInventoryViewsClient { * We want to control conflicting names on the views */ private async assertNameConflict(name: string, whitelist: string[] = []) { - const results = await this.savedObjectsClient.find({ - type: inventoryViewSavedObjectName, - perPage: 1000, - }); + const results = await this.getAllViews(); const hasConflict = [InventoryViewsClient.createStaticView(), ...results.saved_objects].some( (obj) => !whitelist.includes(obj.id) && obj.attributes.name === name diff --git a/x-pack/plugins/infra/server/services/metrics_explorer_views/index.ts b/x-pack/plugins/infra/server/services/metrics_explorer_views/index.ts new file mode 100644 index 0000000000000..3cd3efd6c0f67 --- /dev/null +++ b/x-pack/plugins/infra/server/services/metrics_explorer_views/index.ts @@ -0,0 +1,14 @@ +/* + * 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 { MetricsExplorerViewsService } from './metrics_explorer_views_service'; +export { MetricsExplorerViewsClient } from './metrics_explorer_views_client'; +export type { + MetricsExplorerViewsServiceSetup, + MetricsExplorerViewsServiceStart, + MetricsExplorerViewsServiceStartDeps, +} from './types'; diff --git a/x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_client.mock.ts b/x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_client.mock.ts new file mode 100644 index 0000000000000..82a8cba3f6427 --- /dev/null +++ b/x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_client.mock.ts @@ -0,0 +1,17 @@ +/* + * 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 { IMetricsExplorerViewsClient } from './types'; + +export const createMetricsExplorerViewsClientMock = + (): jest.Mocked => ({ + delete: jest.fn(), + find: jest.fn(), + get: jest.fn(), + create: jest.fn(), + update: jest.fn(), + }); diff --git a/x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_client.test.ts b/x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_client.test.ts new file mode 100644 index 0000000000000..c903e9af360f8 --- /dev/null +++ b/x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_client.test.ts @@ -0,0 +1,262 @@ +/* + * 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 { loggerMock } from '@kbn/logging-mocks'; +import { SavedObjectsClientContract } from '@kbn/core/server'; +import { savedObjectsClientMock } from '@kbn/core/server/mocks'; +import { MetricsExplorerViewAttributes } from '../../../common/metrics_explorer_views'; + +import { InfraSource } from '../../lib/sources'; +import { createInfraSourcesMock } from '../../lib/sources/mocks'; +import { metricsExplorerViewSavedObjectName } from '../../saved_objects/metrics_explorer_view'; +import { MetricsExplorerViewsClient } from './metrics_explorer_views_client'; +import { createMetricsExplorerViewMock } from '../../../common/metrics_explorer_views/metrics_explorer_view.mock'; +import { + CreateMetricsExplorerViewAttributesRequestPayload, + UpdateMetricsExplorerViewAttributesRequestPayload, +} from '../../../common/http_api/latest'; + +describe('MetricsExplorerViewsClient class', () => { + const mockFindMetricsExplorerList = ( + savedObjectsClient: jest.Mocked + ) => { + const metricsExplorerViewListMock = [ + createMetricsExplorerViewMock('0', { + isDefault: true, + } as MetricsExplorerViewAttributes), + createMetricsExplorerViewMock('default_id', { + name: 'Default view 2', + isStatic: false, + } as MetricsExplorerViewAttributes), + createMetricsExplorerViewMock('custom_id', { + name: 'Custom', + isStatic: false, + } as MetricsExplorerViewAttributes), + ]; + + savedObjectsClient.find.mockResolvedValue({ + total: 2, + saved_objects: metricsExplorerViewListMock.slice(1).map((view) => ({ + ...view, + type: metricsExplorerViewSavedObjectName, + score: 0, + references: [], + })), + per_page: 1000, + page: 1, + }); + + return metricsExplorerViewListMock; + }; + + describe('.find', () => { + it('resolves the list of existing metrics explorer views', async () => { + const { metricsExplorerViewsClient, infraSources, savedObjectsClient } = + createMetricsExplorerViewsClient(); + + infraSources.getSourceConfiguration.mockResolvedValue(basicTestSourceConfiguration); + + const metricsExplorerViewListMock = mockFindMetricsExplorerList(savedObjectsClient); + + const metricsExplorerViewList = await metricsExplorerViewsClient.find({}); + + expect(savedObjectsClient.find).toHaveBeenCalled(); + expect(metricsExplorerViewList).toEqual(metricsExplorerViewListMock); + }); + + it('always resolves at least the static metrics explorer view', async () => { + const { metricsExplorerViewsClient, infraSources, savedObjectsClient } = + createMetricsExplorerViewsClient(); + + const metricsExplorerViewListMock = [ + createMetricsExplorerViewMock('0', { + isDefault: true, + } as MetricsExplorerViewAttributes), + ]; + + infraSources.getSourceConfiguration.mockResolvedValue(basicTestSourceConfiguration); + + savedObjectsClient.find.mockResolvedValue({ + total: 2, + saved_objects: [], + per_page: 1000, + page: 1, + }); + + const metricsExplorerViewList = await metricsExplorerViewsClient.find({}); + + expect(savedObjectsClient.find).toHaveBeenCalled(); + expect(metricsExplorerViewList).toEqual(metricsExplorerViewListMock); + }); + }); + + it('.get resolves the an metrics explorer view by id', async () => { + const { metricsExplorerViewsClient, infraSources, savedObjectsClient } = + createMetricsExplorerViewsClient(); + + const metricsExplorerViewMock = createMetricsExplorerViewMock('custom_id', { + name: 'Custom', + isDefault: false, + isStatic: false, + } as MetricsExplorerViewAttributes); + + infraSources.getSourceConfiguration.mockResolvedValue(basicTestSourceConfiguration); + + savedObjectsClient.get.mockResolvedValue({ + ...metricsExplorerViewMock, + type: metricsExplorerViewSavedObjectName, + references: [], + }); + + const metricsExplorerView = await metricsExplorerViewsClient.get('custom_id', {}); + + expect(savedObjectsClient.get).toHaveBeenCalled(); + expect(metricsExplorerView).toEqual(metricsExplorerViewMock); + }); + + describe('.create', () => { + it('generate a new metrics explorer view', async () => { + const { metricsExplorerViewsClient, savedObjectsClient } = createMetricsExplorerViewsClient(); + + const metricsExplorerViewMock = createMetricsExplorerViewMock('new_id', { + name: 'New view', + isStatic: false, + } as MetricsExplorerViewAttributes); + + mockFindMetricsExplorerList(savedObjectsClient); + + savedObjectsClient.create.mockResolvedValue({ + ...metricsExplorerViewMock, + type: metricsExplorerViewSavedObjectName, + references: [], + }); + + const metricsExplorerView = await metricsExplorerViewsClient.create({ + name: 'New view', + } as CreateMetricsExplorerViewAttributesRequestPayload); + + expect(savedObjectsClient.create).toHaveBeenCalled(); + expect(metricsExplorerView).toEqual(metricsExplorerViewMock); + }); + + it('throws an error when a conflicting name is given', async () => { + const { metricsExplorerViewsClient, savedObjectsClient } = createMetricsExplorerViewsClient(); + + mockFindMetricsExplorerList(savedObjectsClient); + + await expect( + async () => + await metricsExplorerViewsClient.create({ + name: 'Custom', + } as CreateMetricsExplorerViewAttributesRequestPayload) + ).rejects.toThrow('A view with that name already exists.'); + }); + }); + + describe('.update', () => { + it('update an existing metrics explorer view by id', async () => { + const { metricsExplorerViewsClient, infraSources, savedObjectsClient } = + createMetricsExplorerViewsClient(); + + const metricsExplorerViews = mockFindMetricsExplorerList(savedObjectsClient); + + const metricsExplorerViewMock = { + ...metricsExplorerViews[1], + attributes: { + ...metricsExplorerViews[1].attributes, + name: 'New name', + }, + }; + + infraSources.getSourceConfiguration.mockResolvedValue(basicTestSourceConfiguration); + + savedObjectsClient.update.mockResolvedValue({ + ...metricsExplorerViewMock, + type: metricsExplorerViewSavedObjectName, + references: [], + }); + + const metricsExplorerView = await metricsExplorerViewsClient.update( + 'default_id', + { + name: 'New name', + } as UpdateMetricsExplorerViewAttributesRequestPayload, + {} + ); + + expect(savedObjectsClient.update).toHaveBeenCalled(); + expect(metricsExplorerView).toEqual(metricsExplorerViewMock); + }); + + it('throws an error when a conflicting name is given', async () => { + const { metricsExplorerViewsClient, savedObjectsClient } = createMetricsExplorerViewsClient(); + + mockFindMetricsExplorerList(savedObjectsClient); + + await expect( + async () => + await metricsExplorerViewsClient.update( + 'default_id', + { + name: 'Custom', + } as UpdateMetricsExplorerViewAttributesRequestPayload, + {} + ) + ).rejects.toThrow('A view with that name already exists.'); + }); + }); + + it('.delete removes an metrics explorer view by id', async () => { + const { metricsExplorerViewsClient, savedObjectsClient } = createMetricsExplorerViewsClient(); + + savedObjectsClient.delete.mockResolvedValue({}); + + const metricsExplorerView = await metricsExplorerViewsClient.delete('custom_id'); + + expect(savedObjectsClient.delete).toHaveBeenCalled(); + expect(metricsExplorerView).toEqual({}); + }); +}); + +const createMetricsExplorerViewsClient = () => { + const logger = loggerMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); + const infraSources = createInfraSourcesMock(); + + const metricsExplorerViewsClient = new MetricsExplorerViewsClient( + logger, + savedObjectsClient, + infraSources + ); + + return { + infraSources, + metricsExplorerViewsClient, + savedObjectsClient, + }; +}; + +const basicTestSourceConfiguration: InfraSource = { + id: 'ID', + origin: 'stored', + configuration: { + name: 'NAME', + description: 'DESCRIPTION', + logIndices: { + type: 'index_pattern', + indexPatternId: 'INDEX_PATTERN_ID', + }, + logColumns: [], + fields: { + message: [], + }, + metricAlias: 'METRIC_ALIAS', + inventoryDefaultView: '0', + metricsExplorerDefaultView: '0', + anomalyThreshold: 0, + }, +}; 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 new file mode 100644 index 0000000000000..1ba34456d88a8 --- /dev/null +++ b/x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_client.ts @@ -0,0 +1,218 @@ +/* + * 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 { + Logger, + SavedObject, + SavedObjectsClientContract, + SavedObjectsUpdateResponse, +} from '@kbn/core/server'; +import Boom from '@hapi/boom'; +import { + staticMetricsExplorerViewAttributes, + staticMetricsExplorerViewId, +} from '../../../common/metrics_explorer_views'; +import type { + CreateMetricsExplorerViewAttributesRequestPayload, + MetricsExplorerViewRequestQuery, +} from '../../../common/http_api/latest'; +import type { + MetricsExplorerView, + MetricsExplorerViewAttributes, +} from '../../../common/metrics_explorer_views'; +import { decodeOrThrow } from '../../../common/runtime_types'; +import type { IInfraSources } from '../../lib/sources'; +import { metricsExplorerViewSavedObjectName } from '../../saved_objects/metrics_explorer_view'; +import { metricsExplorerViewSavedObjectRT } from '../../saved_objects/metrics_explorer_view/types'; +import type { IMetricsExplorerViewsClient } from './types'; + +export class MetricsExplorerViewsClient implements IMetricsExplorerViewsClient { + constructor( + private readonly logger: Logger, + private readonly savedObjectsClient: SavedObjectsClientContract, + private readonly infraSources: IInfraSources + ) {} + + static STATIC_VIEW_ID = '0'; + static DEFAULT_SOURCE_ID = 'default'; + + public async find(query: MetricsExplorerViewRequestQuery): Promise { + this.logger.debug('Trying to load metrics explorer views ...'); + + const sourceId = query.sourceId ?? MetricsExplorerViewsClient.DEFAULT_SOURCE_ID; + + const [sourceConfiguration, metricsExplorerViewSavedObject] = await Promise.all([ + this.infraSources.getSourceConfiguration(this.savedObjectsClient, sourceId), + this.getAllViews(), + ]); + + const defaultView = MetricsExplorerViewsClient.createStaticView( + sourceConfiguration.configuration.metricsExplorerDefaultView + ); + const views = metricsExplorerViewSavedObject.saved_objects.map((savedObject) => + this.mapSavedObjectToMetricsExplorerView( + savedObject, + sourceConfiguration.configuration.metricsExplorerDefaultView + ) + ); + + const metricsExplorerViews = [defaultView, ...views]; + + const sortedMetricsExplorerViews = this.moveDefaultViewOnTop(metricsExplorerViews); + + return sortedMetricsExplorerViews; + } + + public async get( + metricsExplorerViewId: string, + query: MetricsExplorerViewRequestQuery + ): Promise { + this.logger.debug(`Trying to load metrics explorer view with id ${metricsExplorerViewId} ...`); + + const sourceId = query.sourceId ?? MetricsExplorerViewsClient.DEFAULT_SOURCE_ID; + + // Handle the case where the requested resource is the static metrics explorer view + if (metricsExplorerViewId === MetricsExplorerViewsClient.STATIC_VIEW_ID) { + const sourceConfiguration = await this.infraSources.getSourceConfiguration( + this.savedObjectsClient, + sourceId + ); + + return MetricsExplorerViewsClient.createStaticView( + sourceConfiguration.configuration.metricsExplorerDefaultView + ); + } + + const [sourceConfiguration, metricsExplorerViewSavedObject] = await Promise.all([ + this.infraSources.getSourceConfiguration(this.savedObjectsClient, sourceId), + this.savedObjectsClient.get(metricsExplorerViewSavedObjectName, metricsExplorerViewId), + ]); + + return this.mapSavedObjectToMetricsExplorerView( + metricsExplorerViewSavedObject, + sourceConfiguration.configuration.metricsExplorerDefaultView + ); + } + + public async create( + attributes: CreateMetricsExplorerViewAttributesRequestPayload + ): Promise { + this.logger.debug(`Trying to create metrics explorer view ...`); + + // Validate there is not a view with the same name + await this.assertNameConflict(attributes.name); + + const metricsExplorerViewSavedObject = await this.savedObjectsClient.create( + metricsExplorerViewSavedObjectName, + attributes + ); + + return this.mapSavedObjectToMetricsExplorerView(metricsExplorerViewSavedObject); + } + + public async update( + metricsExplorerViewId: string, + attributes: CreateMetricsExplorerViewAttributesRequestPayload, + query: MetricsExplorerViewRequestQuery + ): Promise { + this.logger.debug( + `Trying to update metrics explorer view with id "${metricsExplorerViewId}"...` + ); + + // Validate there is not a view with the same name + await this.assertNameConflict(attributes.name, [metricsExplorerViewId]); + + const sourceId = query.sourceId ?? MetricsExplorerViewsClient.DEFAULT_SOURCE_ID; + + const [sourceConfiguration, metricsExplorerViewSavedObject] = await Promise.all([ + this.infraSources.getSourceConfiguration(this.savedObjectsClient, sourceId), + this.savedObjectsClient.update( + metricsExplorerViewSavedObjectName, + metricsExplorerViewId, + attributes + ), + ]); + + return this.mapSavedObjectToMetricsExplorerView( + metricsExplorerViewSavedObject, + sourceConfiguration.configuration.metricsExplorerDefaultView + ); + } + + public delete(metricsExplorerViewId: string): Promise<{}> { + this.logger.debug( + `Trying to delete metrics explorer view with id ${metricsExplorerViewId} ...` + ); + + return this.savedObjectsClient.delete( + metricsExplorerViewSavedObjectName, + metricsExplorerViewId + ); + } + + private getAllViews() { + return this.savedObjectsClient.find({ + type: metricsExplorerViewSavedObjectName, + perPage: 1000, // Fetch 1 page by default with a max of 1000 results + }); + } + + private mapSavedObjectToMetricsExplorerView( + savedObject: SavedObject | SavedObjectsUpdateResponse, + defaultViewId?: string + ) { + const metricsExplorerViewSavedObject = decodeOrThrow(metricsExplorerViewSavedObjectRT)( + savedObject + ); + + return { + id: metricsExplorerViewSavedObject.id, + version: metricsExplorerViewSavedObject.version, + updatedAt: metricsExplorerViewSavedObject.updated_at, + attributes: { + ...metricsExplorerViewSavedObject.attributes, + isDefault: metricsExplorerViewSavedObject.id === defaultViewId, + isStatic: false, + }, + }; + } + + private moveDefaultViewOnTop(views: MetricsExplorerView[]) { + const defaultViewPosition = views.findIndex((view) => view.attributes.isDefault); + + if (defaultViewPosition !== -1) { + const element = views.splice(defaultViewPosition, 1)[0]; + views.unshift(element); + } + + return views; + } + + /** + * We want to control conflicting names on the views + */ + private async assertNameConflict(name: string, whitelist: string[] = []) { + const results = await this.getAllViews(); + + const hasConflict = [ + MetricsExplorerViewsClient.createStaticView(), + ...results.saved_objects, + ].some((obj) => !whitelist.includes(obj.id) && obj.attributes.name === name); + + if (hasConflict) { + throw Boom.conflict('A view with that name already exists.'); + } + } + + private static createStaticView = (defaultViewId?: string): MetricsExplorerView => ({ + id: staticMetricsExplorerViewId, + attributes: { + ...staticMetricsExplorerViewAttributes, + isDefault: defaultViewId === MetricsExplorerViewsClient.STATIC_VIEW_ID, + }, + }); +} diff --git a/x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_service.mock.ts b/x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_service.mock.ts new file mode 100644 index 0000000000000..3739930944571 --- /dev/null +++ b/x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_service.mock.ts @@ -0,0 +1,18 @@ +/* + * 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 { createMetricsExplorerViewsClientMock } from './metrics_explorer_views_client.mock'; +import type { MetricsExplorerViewsServiceSetup, MetricsExplorerViewsServiceStart } from './types'; + +export const createMetricsExplorerViewsServiceSetupMock = + (): jest.Mocked => {}; + +export const createMetricsExplorerViewsServiceStartMock = + (): jest.Mocked => ({ + getClient: jest.fn((_savedObjectsClient: any) => createMetricsExplorerViewsClientMock()), + getScopedClient: jest.fn((_request: any) => createMetricsExplorerViewsClientMock()), + }); diff --git a/x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_service.ts b/x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_service.ts new file mode 100644 index 0000000000000..38c7ab4e1f925 --- /dev/null +++ b/x-pack/plugins/infra/server/services/metrics_explorer_views/metrics_explorer_views_service.ts @@ -0,0 +1,39 @@ +/* + * 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 { KibanaRequest, Logger, SavedObjectsClientContract } from '@kbn/core/server'; +import { MetricsExplorerViewsClient } from './metrics_explorer_views_client'; +import type { + MetricsExplorerViewsServiceSetup, + MetricsExplorerViewsServiceStart, + MetricsExplorerViewsServiceStartDeps, +} from './types'; + +export class MetricsExplorerViewsService { + constructor(private readonly logger: Logger) {} + + public setup(): MetricsExplorerViewsServiceSetup {} + + public start({ + infraSources, + savedObjects, + }: MetricsExplorerViewsServiceStartDeps): MetricsExplorerViewsServiceStart { + const { logger } = this; + + return { + getClient(savedObjectsClient: SavedObjectsClientContract) { + return new MetricsExplorerViewsClient(logger, savedObjectsClient, infraSources); + }, + + getScopedClient(request: KibanaRequest) { + const savedObjectsClient = savedObjects.getScopedClient(request); + + return this.getClient(savedObjectsClient); + }, + }; + } +} 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 new file mode 100644 index 0000000000000..0e64aaa83d27e --- /dev/null +++ b/x-pack/plugins/infra/server/services/metrics_explorer_views/types.ts @@ -0,0 +1,48 @@ +/* + * 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 { + KibanaRequest, + SavedObjectsClientContract, + SavedObjectsServiceStart, +} from '@kbn/core/server'; +import type { + CreateMetricsExplorerViewAttributesRequestPayload, + MetricsExplorerViewRequestQuery, + UpdateMetricsExplorerViewAttributesRequestPayload, +} from '../../../common/http_api/latest'; +import type { MetricsExplorerView } from '../../../common/metrics_explorer_views'; +import type { InfraSources } from '../../lib/sources'; + +export interface MetricsExplorerViewsServiceStartDeps { + infraSources: InfraSources; + savedObjects: SavedObjectsServiceStart; +} + +export type MetricsExplorerViewsServiceSetup = void; + +export interface MetricsExplorerViewsServiceStart { + getClient(savedObjectsClient: SavedObjectsClientContract): IMetricsExplorerViewsClient; + getScopedClient(request: KibanaRequest): IMetricsExplorerViewsClient; +} + +export interface IMetricsExplorerViewsClient { + delete(metricsExplorerViewId: string): Promise<{}>; + find(query: MetricsExplorerViewRequestQuery): Promise; + get( + metricsExplorerViewId: string, + query: MetricsExplorerViewRequestQuery + ): Promise; + create( + metricsExplorerViewAttributes: CreateMetricsExplorerViewAttributesRequestPayload + ): Promise; + update( + metricsExplorerViewId: string, + metricsExplorerViewAttributes: UpdateMetricsExplorerViewAttributesRequestPayload, + query: MetricsExplorerViewRequestQuery + ): Promise; +} diff --git a/x-pack/plugins/infra/server/types.ts b/x-pack/plugins/infra/server/types.ts index c415103d2256d..49dbca9b276b2 100644 --- a/x-pack/plugins/infra/server/types.ts +++ b/x-pack/plugins/infra/server/types.ts @@ -16,6 +16,7 @@ import type { InfraStaticSourceConfiguration } from '../common/source_configurat import { InfraServerPluginStartDeps } from './lib/adapters/framework'; import { InventoryViewsServiceStart } from './services/inventory_views'; import { LogViewsServiceSetup, LogViewsServiceStart } from './services/log_views/types'; +import { MetricsExplorerViewsServiceStart } from './services/metrics_explorer_views'; export type { InfraConfig } from '../common/plugin_config_types'; @@ -33,6 +34,7 @@ export interface InfraPluginSetup { export interface InfraPluginStart { inventoryViews: InventoryViewsServiceStart; logViews: LogViewsServiceStart; + metricsExplorerViews: MetricsExplorerViewsServiceStart; getMetricIndices: ( savedObjectsClient: SavedObjectsClientContract, sourceId?: string