mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Infrastructure UI] Implement inventory views CRUD endpoints (#154900)
## 📓 Summary Part of #152617 Closes #155158 This PR implements the CRUD endpoints for the inventory views. Following the approach used for the LogViews service, it exposes a client that abstracts all the logic concerned to the `inventory-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/inventory_views // Create one POST kbn:/api/infra/inventory_views { "attributes": { "name": "My inventory view" } } // Get one GET kbn:/api/infra/inventory_views/<switch-with-id> // Update one PUT kbn:/api/infra/inventory_views/<switch-with-id> { "attributes": { "name": "My inventory view 2" } } // Delete one DELETE kbn:/api/infra/inventory_views/<switch-with-id> ``` ## 👣 Next steps - Replicate the same logic for the metrics explorer saved object - Create a client-side abstraction to consume the service - Update the existing react custom hooks to consume the endpoint --------- Co-authored-by: Marco Antonio Ghiani <marcoantonio.ghiani@elastic.co> Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
f6e037794a
commit
6ac4e1919c
42 changed files with 1855 additions and 4 deletions
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
export type { IndexPatternType } from './src/index_pattern_rt';
|
||||
export type { NonEmptyStringBrand } from './src/non_empty_string_rt';
|
||||
export type { NonEmptyString, NonEmptyStringBrand } from './src/non_empty_string_rt';
|
||||
|
||||
export { deepExactRt } from './src/deep_exact_rt';
|
||||
export { indexPatternRt } from './src/index_pattern_rt';
|
||||
|
|
|
@ -14,3 +14,9 @@ export * from './log_alerts';
|
|||
export * from './snapshot_api';
|
||||
export * from './host_details';
|
||||
export * from './infra';
|
||||
|
||||
/**
|
||||
* Exporting versioned APIs types
|
||||
*/
|
||||
export * from './latest';
|
||||
export * as inventoryViewsV1 from './inventory_views/v1';
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* 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 INVENTORY_VIEW_URL = '/api/infra/inventory_views';
|
||||
export const INVENTORY_VIEW_URL_ENTITY = `${INVENTORY_VIEW_URL}/{inventoryViewId}`;
|
||||
export const getInventoryViewUrl = (inventoryViewId?: string) =>
|
||||
[INVENTORY_VIEW_URL, inventoryViewId].filter(Boolean).join('/');
|
||||
|
||||
const inventoryViewIdRT = new rt.Type<string, string, unknown>(
|
||||
'InventoryViewId',
|
||||
rt.string.is,
|
||||
(u, c) =>
|
||||
either.chain(rt.string.validate(u, c), (id) => {
|
||||
return id === '0'
|
||||
? rt.failure(u, c, `The inventory view with id ${id} is not configurable.`)
|
||||
: rt.success(id);
|
||||
}),
|
||||
String
|
||||
);
|
||||
|
||||
export const inventoryViewRequestParamsRT = rt.type({
|
||||
inventoryViewId: inventoryViewIdRT,
|
||||
});
|
||||
|
||||
export type InventoryViewRequestParams = rt.TypeOf<typeof inventoryViewRequestParamsRT>;
|
||||
|
||||
export const inventoryViewRequestQueryRT = rt.partial({
|
||||
sourceId: rt.string,
|
||||
});
|
||||
|
||||
export type InventoryViewRequestQuery = rt.TypeOf<typeof inventoryViewRequestQueryRT>;
|
||||
|
||||
const inventoryViewAttributesResponseRT = rt.intersection([
|
||||
rt.strict({
|
||||
name: nonEmptyStringRt,
|
||||
isDefault: rt.boolean,
|
||||
isStatic: rt.boolean,
|
||||
}),
|
||||
rt.UnknownRecord,
|
||||
]);
|
||||
|
||||
const inventoryViewResponseRT = rt.exact(
|
||||
rt.intersection([
|
||||
rt.type({
|
||||
id: rt.string,
|
||||
attributes: inventoryViewAttributesResponseRT,
|
||||
}),
|
||||
rt.partial({
|
||||
updatedAt: rt.number,
|
||||
version: rt.string,
|
||||
}),
|
||||
])
|
||||
);
|
||||
|
||||
export const inventoryViewResponsePayloadRT = rt.type({
|
||||
data: inventoryViewResponseRT,
|
||||
});
|
||||
|
||||
export type GetInventoryViewResponsePayload = rt.TypeOf<typeof inventoryViewResponsePayloadRT>;
|
|
@ -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 createInventoryViewAttributesRequestPayloadRT = rt.intersection([
|
||||
rt.type({
|
||||
name: nonEmptyStringRt,
|
||||
}),
|
||||
rt.UnknownRecord,
|
||||
rt.exact(rt.partial({ isDefault: rt.undefined, isStatic: rt.undefined })),
|
||||
]);
|
||||
|
||||
export type CreateInventoryViewAttributesRequestPayload = rt.TypeOf<
|
||||
typeof createInventoryViewAttributesRequestPayloadRT
|
||||
>;
|
||||
|
||||
export const createInventoryViewRequestPayloadRT = rt.type({
|
||||
attributes: createInventoryViewAttributesRequestPayloadRT,
|
||||
});
|
||||
|
||||
export type CreateInventoryViewRequestPayload = rt.TypeOf<
|
||||
typeof createInventoryViewRequestPayloadRT
|
||||
>;
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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 findInventoryViewAttributesResponseRT = rt.strict({
|
||||
name: nonEmptyStringRt,
|
||||
isDefault: rt.boolean,
|
||||
isStatic: rt.boolean,
|
||||
});
|
||||
|
||||
const findInventoryViewResponseRT = rt.exact(
|
||||
rt.intersection([
|
||||
rt.type({
|
||||
id: rt.string,
|
||||
attributes: findInventoryViewAttributesResponseRT,
|
||||
}),
|
||||
rt.partial({
|
||||
updatedAt: rt.number,
|
||||
version: rt.string,
|
||||
}),
|
||||
])
|
||||
);
|
||||
|
||||
export const findInventoryViewResponsePayloadRT = rt.type({
|
||||
data: rt.array(findInventoryViewResponseRT),
|
||||
});
|
||||
|
||||
export type FindInventoryViewResponsePayload = rt.TypeOf<typeof findInventoryViewResponsePayloadRT>;
|
|
@ -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.
|
||||
*/
|
||||
|
||||
import * as rt from 'io-ts';
|
||||
|
||||
export const getInventoryViewRequestParamsRT = rt.type({
|
||||
inventoryViewId: rt.string,
|
||||
});
|
||||
|
||||
export type GetInventoryViewRequestParams = rt.TypeOf<typeof getInventoryViewRequestParamsRT>;
|
|
@ -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_inventory_view';
|
||||
export * from './find_inventory_view';
|
||||
export * from './create_inventory_view';
|
||||
export * from './update_inventory_view';
|
|
@ -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 updateInventoryViewAttributesRequestPayloadRT = rt.intersection([
|
||||
rt.type({
|
||||
name: nonEmptyStringRt,
|
||||
}),
|
||||
rt.UnknownRecord,
|
||||
rt.exact(rt.partial({ isDefault: rt.undefined, isStatic: rt.undefined })),
|
||||
]);
|
||||
|
||||
export type UpdateInventoryViewAttributesRequestPayload = rt.TypeOf<
|
||||
typeof updateInventoryViewAttributesRequestPayloadRT
|
||||
>;
|
||||
|
||||
export const updateInventoryViewRequestPayloadRT = rt.type({
|
||||
attributes: updateInventoryViewAttributesRequestPayloadRT,
|
||||
});
|
||||
|
||||
export type UpdateInventoryViewRequestPayload = rt.TypeOf<
|
||||
typeof updateInventoryViewRequestPayloadRT
|
||||
>;
|
8
x-pack/plugins/infra/common/http_api/latest.ts
Normal file
8
x-pack/plugins/infra/common/http_api/latest.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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 './inventory_views/v1';
|
52
x-pack/plugins/infra/common/inventory_views/defaults.ts
Normal file
52
x-pack/plugins/infra/common/inventory_views/defaults.ts
Normal file
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
import type { NonEmptyString } from '@kbn/io-ts-utils';
|
||||
import type { InventoryViewAttributes } from './types';
|
||||
|
||||
export const staticInventoryViewId = '0';
|
||||
|
||||
export const staticInventoryViewAttributes: InventoryViewAttributes = {
|
||||
name: i18n.translate('xpack.infra.savedView.defaultViewNameHosts', {
|
||||
defaultMessage: 'Default view',
|
||||
}) as NonEmptyString,
|
||||
isDefault: false,
|
||||
isStatic: true,
|
||||
metric: {
|
||||
type: 'cpu',
|
||||
},
|
||||
groupBy: [],
|
||||
nodeType: 'host',
|
||||
view: 'map',
|
||||
customOptions: [],
|
||||
boundsOverride: {
|
||||
max: 1,
|
||||
min: 0,
|
||||
},
|
||||
autoBounds: true,
|
||||
accountId: '',
|
||||
region: '',
|
||||
customMetrics: [],
|
||||
legend: {
|
||||
palette: 'cool',
|
||||
steps: 10,
|
||||
reverseColors: false,
|
||||
},
|
||||
source: 'default',
|
||||
sort: {
|
||||
by: 'name',
|
||||
direction: 'desc',
|
||||
},
|
||||
timelineOpen: false,
|
||||
filterQuery: {
|
||||
kind: 'kuery',
|
||||
expression: '',
|
||||
},
|
||||
time: Date.now(),
|
||||
autoReload: false,
|
||||
};
|
9
x-pack/plugins/infra/common/inventory_views/index.ts
Normal file
9
x-pack/plugins/infra/common/inventory_views/index.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export * from './defaults';
|
||||
export * from './types';
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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 { staticInventoryViewAttributes } from './defaults';
|
||||
import type { InventoryView, InventoryViewAttributes } from './types';
|
||||
|
||||
export const createInventoryViewMock = (
|
||||
id: string,
|
||||
attributes: InventoryViewAttributes,
|
||||
updatedAt?: number,
|
||||
version?: string
|
||||
): InventoryView => ({
|
||||
id,
|
||||
attributes: {
|
||||
...staticInventoryViewAttributes,
|
||||
...attributes,
|
||||
},
|
||||
updatedAt,
|
||||
version,
|
||||
});
|
35
x-pack/plugins/infra/common/inventory_views/types.ts
Normal file
35
x-pack/plugins/infra/common/inventory_views/types.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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 inventoryViewAttributesRT = rt.intersection([
|
||||
rt.strict({
|
||||
name: nonEmptyStringRt,
|
||||
isDefault: rt.boolean,
|
||||
isStatic: rt.boolean,
|
||||
}),
|
||||
rt.UnknownRecord,
|
||||
]);
|
||||
|
||||
export type InventoryViewAttributes = rt.TypeOf<typeof inventoryViewAttributesRT>;
|
||||
|
||||
export const inventoryViewRT = rt.exact(
|
||||
rt.intersection([
|
||||
rt.type({
|
||||
id: rt.string,
|
||||
attributes: inventoryViewAttributesRT,
|
||||
}),
|
||||
rt.partial({
|
||||
updatedAt: rt.number,
|
||||
version: rt.string,
|
||||
}),
|
||||
])
|
||||
);
|
||||
|
||||
export type InventoryView = rt.TypeOf<typeof inventoryViewRT>;
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
import type { NonEmptyString } from '@kbn/io-ts-utils';
|
||||
import type { MetricsExplorerViewAttributes } from './types';
|
||||
|
||||
export const staticMetricsExplorerViewId = 'static';
|
||||
|
||||
export const staticMetricsExplorerViewAttributes: MetricsExplorerViewAttributes = {
|
||||
name: i18n.translate('xpack.infra.savedView.defaultViewNameHosts', {
|
||||
defaultMessage: 'Default view',
|
||||
}) as NonEmptyString,
|
||||
isDefault: false,
|
||||
isStatic: true,
|
||||
options: {
|
||||
aggregation: 'avg',
|
||||
metrics: [
|
||||
{
|
||||
aggregation: 'avg',
|
||||
field: 'system.cpu.total.norm.pct',
|
||||
color: 'color0',
|
||||
},
|
||||
{
|
||||
aggregation: 'avg',
|
||||
field: 'kubernetes.pod.cpu.usage.node.pct',
|
||||
color: 'color1',
|
||||
},
|
||||
{
|
||||
aggregation: 'avg',
|
||||
field: 'docker.cpu.total.pct',
|
||||
color: 'color2',
|
||||
},
|
||||
],
|
||||
source: 'default',
|
||||
},
|
||||
chartOptions: {
|
||||
type: 'line',
|
||||
yAxisMode: 'fromZero',
|
||||
stack: false,
|
||||
},
|
||||
currentTimerange: {
|
||||
from: 'now-1h',
|
||||
to: 'now',
|
||||
interval: '>=10s',
|
||||
},
|
||||
};
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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 './types';
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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 { staticMetricsExplorerViewAttributes } from './defaults';
|
||||
import type { MetricsExplorerView, MetricsExplorerViewAttributes } from './types';
|
||||
|
||||
export const createmetricsExplorerViewMock = (
|
||||
id: string,
|
||||
attributes: MetricsExplorerViewAttributes,
|
||||
updatedAt?: number,
|
||||
version?: string
|
||||
): MetricsExplorerView => ({
|
||||
id,
|
||||
attributes: {
|
||||
...staticMetricsExplorerViewAttributes,
|
||||
...attributes,
|
||||
},
|
||||
updatedAt,
|
||||
version,
|
||||
});
|
35
x-pack/plugins/infra/common/metrics_explorer_views/types.ts
Normal file
35
x-pack/plugins/infra/common/metrics_explorer_views/types.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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 metricsExplorerViewAttributesRT = rt.intersection([
|
||||
rt.strict({
|
||||
name: nonEmptyStringRt,
|
||||
isDefault: rt.boolean,
|
||||
isStatic: rt.boolean,
|
||||
}),
|
||||
rt.UnknownRecord,
|
||||
]);
|
||||
|
||||
export type MetricsExplorerViewAttributes = rt.TypeOf<typeof metricsExplorerViewAttributesRT>;
|
||||
|
||||
export const metricsExplorerViewRT = rt.exact(
|
||||
rt.intersection([
|
||||
rt.type({
|
||||
id: rt.string,
|
||||
attributes: metricsExplorerViewAttributesRT,
|
||||
}),
|
||||
rt.partial({
|
||||
updatedAt: rt.number,
|
||||
version: rt.string,
|
||||
}),
|
||||
])
|
||||
);
|
||||
|
||||
export type MetricsExplorerView = rt.TypeOf<typeof metricsExplorerViewRT>;
|
|
@ -8,6 +8,7 @@
|
|||
import { InfraBackendLibs } from './lib/infra_types';
|
||||
import { initGetHostsAnomaliesRoute, initGetK8sAnomaliesRoute } from './routes/infra_ml';
|
||||
import { initInventoryMetaRoute } from './routes/inventory_metadata';
|
||||
import { initInventoryViewRoutes } from './routes/inventory_views';
|
||||
import { initIpToHostName } from './routes/ip_to_hostname';
|
||||
import { initGetLogAlertsChartPreviewDataRoute } from './routes/log_alerts';
|
||||
import {
|
||||
|
@ -61,6 +62,7 @@ export const initInfraServer = (libs: InfraBackendLibs) => {
|
|||
initMetricsAPIRoute(libs);
|
||||
initMetadataRoute(libs);
|
||||
initInventoryMetaRoute(libs);
|
||||
initInventoryViewRoutes(libs);
|
||||
initGetLogAlertsChartPreviewDataRoute(libs);
|
||||
initProcessListRoute(libs);
|
||||
initOverviewRoute(libs);
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { createInventoryViewsServiceStartMock } from './services/inventory_views/inventory_views_service.mock';
|
||||
import {
|
||||
createLogViewsServiceSetupMock,
|
||||
createLogViewsServiceStartMock,
|
||||
|
@ -23,6 +24,7 @@ const createInfraSetupMock = () => {
|
|||
const createInfraStartMock = () => {
|
||||
const infraStartMock: jest.Mocked<InfraPluginStart> = {
|
||||
getMetricIndices: jest.fn(),
|
||||
inventoryViews: createInventoryViewsServiceStartMock(),
|
||||
logViews: createLogViewsServiceStartMock(),
|
||||
};
|
||||
return infraStartMock;
|
||||
|
|
|
@ -20,8 +20,6 @@ import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common';
|
|||
import { LOGS_FEATURE_ID, METRICS_FEATURE_ID } from '../common/constants';
|
||||
import { defaultLogViewsStaticConfig } from '../common/log_views';
|
||||
import { publicConfigKeys } from '../common/plugin_config_types';
|
||||
import { inventoryViewSavedObjectType } from '../common/saved_objects/inventory_view';
|
||||
import { metricsExplorerViewSavedObjectType } from '../common/saved_objects/metrics_explorer_view';
|
||||
import { configDeprecations, getInfraDeprecationsFactory } from './deprecations';
|
||||
import { LOGS_FEATURE, METRICS_FEATURE } from './features';
|
||||
import { initInfraServer } from './infra_server';
|
||||
|
@ -43,7 +41,12 @@ import { InfraBackendLibs, InfraDomainLibs } from './lib/infra_types';
|
|||
import { makeGetMetricIndices } from './lib/metrics/make_get_metric_indices';
|
||||
import { infraSourceConfigurationSavedObjectType, InfraSources } from './lib/sources';
|
||||
import { InfraSourceStatus } from './lib/source_status';
|
||||
import { logViewSavedObjectType } from './saved_objects';
|
||||
import {
|
||||
inventoryViewSavedObjectType,
|
||||
logViewSavedObjectType,
|
||||
metricsExplorerViewSavedObjectType,
|
||||
} from './saved_objects';
|
||||
import { InventoryViewsService } from './services/inventory_views';
|
||||
import { LogEntriesService } from './services/log_entries';
|
||||
import { LogViewsService } from './services/log_views';
|
||||
import { RulesService } from './services/rules';
|
||||
|
@ -117,6 +120,7 @@ export class InfraServerPlugin
|
|||
|
||||
private logsRules: RulesService;
|
||||
private metricsRules: RulesService;
|
||||
private inventoryViews: InventoryViewsService;
|
||||
private logViews: LogViewsService;
|
||||
|
||||
constructor(context: PluginInitializerContext<InfraConfig>) {
|
||||
|
@ -134,6 +138,7 @@ export class InfraServerPlugin
|
|||
this.logger.get('metricsRules')
|
||||
);
|
||||
|
||||
this.inventoryViews = new InventoryViewsService(this.logger.get('inventoryViews'));
|
||||
this.logViews = new LogViewsService(this.logger.get('logViews'));
|
||||
}
|
||||
|
||||
|
@ -148,6 +153,7 @@ export class InfraServerPlugin
|
|||
sources,
|
||||
}
|
||||
);
|
||||
const inventoryViews = this.inventoryViews.setup();
|
||||
const logViews = this.logViews.setup();
|
||||
|
||||
// register saved object types
|
||||
|
@ -229,11 +235,17 @@ export class InfraServerPlugin
|
|||
|
||||
return {
|
||||
defineInternalSourceConfiguration: sources.defineInternalSourceConfiguration.bind(sources),
|
||||
inventoryViews,
|
||||
logViews,
|
||||
} as InfraPluginSetup;
|
||||
}
|
||||
|
||||
start(core: CoreStart, plugins: InfraServerPluginStartDeps) {
|
||||
const inventoryViews = this.inventoryViews.start({
|
||||
infraSources: this.libs.sources,
|
||||
savedObjects: core.savedObjects,
|
||||
});
|
||||
|
||||
const logViews = this.logViews.start({
|
||||
infraSources: this.libs.sources,
|
||||
savedObjects: core.savedObjects,
|
||||
|
@ -247,6 +259,7 @@ export class InfraServerPlugin
|
|||
});
|
||||
|
||||
return {
|
||||
inventoryViews,
|
||||
logViews,
|
||||
getMetricIndices: makeGetMetricIndices(this.libs.sources),
|
||||
};
|
||||
|
|
350
x-pack/plugins/infra/server/routes/inventory_views/README.md
Normal file
350
x-pack/plugins/infra/server/routes/inventory_views/README.md
Normal file
|
@ -0,0 +1,350 @@
|
|||
# Inventory Views CRUD api
|
||||
|
||||
## Find all: `GET /api/infra/inventory_views`
|
||||
|
||||
Retrieves all inventory views in a reduced version.
|
||||
|
||||
### Request
|
||||
|
||||
- **Method**: GET
|
||||
- **Path**: /api/infra/inventory_views
|
||||
- **Query params**:
|
||||
- `sourceId` _(optional)_: Specify a source id related to the inventory views. Default value: `default`.
|
||||
|
||||
### Response
|
||||
|
||||
```json
|
||||
GET /api/infra/inventory_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/inventory_views/{inventoryViewId}`
|
||||
|
||||
Retrieves a single inventory view by ID
|
||||
|
||||
### Request
|
||||
|
||||
- **Method**: GET
|
||||
- **Path**: /api/infra/inventory_views/{inventoryViewId}
|
||||
- **Query params**:
|
||||
- `sourceId` _(optional)_: Specify a source id related to the inventory view. Default value: `default`.
|
||||
|
||||
### Response
|
||||
|
||||
```json
|
||||
GET /api/infra/inventory_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/inventory_views/random-id
|
||||
|
||||
Status code: 404
|
||||
|
||||
{
|
||||
"statusCode": 404,
|
||||
"error": "Not Found",
|
||||
"message": "Saved object [inventory-view/random-id] not found"
|
||||
}
|
||||
```
|
||||
|
||||
## Create one: `POST /api/infra/inventory_views`
|
||||
|
||||
Creates a new inventory view.
|
||||
|
||||
### Request
|
||||
|
||||
- **Method**: POST
|
||||
- **Path**: /api/infra/inventory_views
|
||||
- **Request body**:
|
||||
```json
|
||||
{
|
||||
"attributes": {
|
||||
"name": "View name",
|
||||
"metric": {
|
||||
"type": "cpu"
|
||||
},
|
||||
"sort": {
|
||||
"by": "name",
|
||||
"direction": "desc"
|
||||
},
|
||||
//...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Response
|
||||
|
||||
```json
|
||||
POST /api/infra/inventory_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/inventory_views
|
||||
|
||||
Status code: 409
|
||||
|
||||
{
|
||||
"statusCode": 409,
|
||||
"error": "Conflict",
|
||||
"message": "A view with that name already exists."
|
||||
}
|
||||
```
|
||||
|
||||
## Update one: `PUT /api/infra/inventory_views/{inventoryViewId}`
|
||||
|
||||
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.
|
||||
|
||||
### Request
|
||||
|
||||
- **Method**: PUT
|
||||
- **Path**: /api/infra/inventory_views/{inventoryViewId}
|
||||
- **Query params**:
|
||||
- `sourceId` _(optional)_: Specify a source id related to the inventory view. Default value: `default`.
|
||||
- **Request body**:
|
||||
```json
|
||||
{
|
||||
"attributes": {
|
||||
"name": "View name",
|
||||
"metric": {
|
||||
"type": "cpu"
|
||||
},
|
||||
"sort": {
|
||||
"by": "name",
|
||||
"direction": "desc"
|
||||
},
|
||||
//...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Response
|
||||
|
||||
```json
|
||||
PUT /api/infra/inventory_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/inventory_views/random-id
|
||||
|
||||
Status code: 404
|
||||
|
||||
{
|
||||
"statusCode": 404,
|
||||
"error": "Not Found",
|
||||
"message": "Saved object [inventory-view/random-id] not found"
|
||||
}
|
||||
```
|
||||
|
||||
Send in the payload a `name` attribute already held by another view:
|
||||
```json
|
||||
PUT /api/infra/inventory_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/inventory_views/{inventoryViewId}`
|
||||
|
||||
Deletes an inventory view.
|
||||
|
||||
### Request
|
||||
|
||||
- **Method**: DELETE
|
||||
- **Path**: /api/infra/inventory_views/{inventoryViewId}
|
||||
|
||||
### Response
|
||||
|
||||
```json
|
||||
DELETE /api/infra/inventory_views/927ad6a0-da0c-11ed-9487-41e9b90f96b9
|
||||
|
||||
Status code: 204 No content
|
||||
```
|
||||
|
||||
```json
|
||||
DELETE /api/infra/inventory_views/random-id
|
||||
|
||||
Status code: 404
|
||||
|
||||
{
|
||||
"statusCode": 404,
|
||||
"error": "Not Found",
|
||||
"message": "Saved object [inventory-view/random-id] not found"
|
||||
}
|
||||
```
|
|
@ -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 {
|
||||
createInventoryViewRequestPayloadRT,
|
||||
inventoryViewResponsePayloadRT,
|
||||
INVENTORY_VIEW_URL,
|
||||
} from '../../../common/http_api/latest';
|
||||
import type { InfraBackendLibs } from '../../lib/infra_types';
|
||||
|
||||
export const initCreateInventoryViewRoute = ({
|
||||
framework,
|
||||
getStartServices,
|
||||
}: Pick<InfraBackendLibs, 'framework' | 'getStartServices'>) => {
|
||||
framework.registerRoute(
|
||||
{
|
||||
method: 'post',
|
||||
path: INVENTORY_VIEW_URL,
|
||||
validate: {
|
||||
body: createValidationFunction(createInventoryViewRequestPayloadRT),
|
||||
},
|
||||
},
|
||||
async (_requestContext, request, response) => {
|
||||
const { body } = request;
|
||||
const { inventoryViews } = (await getStartServices())[2];
|
||||
const inventoryViewsClient = inventoryViews.getScopedClient(request);
|
||||
|
||||
try {
|
||||
const inventoryView = await inventoryViewsClient.create(body.attributes);
|
||||
|
||||
return response.custom({
|
||||
statusCode: 201,
|
||||
body: inventoryViewResponsePayloadRT.encode({ data: inventoryView }),
|
||||
});
|
||||
} 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',
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
|
@ -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 {
|
||||
inventoryViewRequestParamsRT,
|
||||
INVENTORY_VIEW_URL_ENTITY,
|
||||
} from '../../../common/http_api/latest';
|
||||
import type { InfraBackendLibs } from '../../lib/infra_types';
|
||||
|
||||
export const initDeleteInventoryViewRoute = ({
|
||||
framework,
|
||||
getStartServices,
|
||||
}: Pick<InfraBackendLibs, 'framework' | 'getStartServices'>) => {
|
||||
framework.registerRoute(
|
||||
{
|
||||
method: 'delete',
|
||||
path: INVENTORY_VIEW_URL_ENTITY,
|
||||
validate: {
|
||||
params: createValidationFunction(inventoryViewRequestParamsRT),
|
||||
},
|
||||
},
|
||||
async (_requestContext, request, response) => {
|
||||
const { params } = request;
|
||||
const { inventoryViews } = (await getStartServices())[2];
|
||||
const inventoryViewsClient = inventoryViews.getScopedClient(request);
|
||||
|
||||
try {
|
||||
await inventoryViewsClient.delete(params.inventoryViewId);
|
||||
|
||||
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',
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
|
@ -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 {
|
||||
findInventoryViewResponsePayloadRT,
|
||||
inventoryViewRequestQueryRT,
|
||||
INVENTORY_VIEW_URL,
|
||||
} from '../../../common/http_api/latest';
|
||||
import type { InfraBackendLibs } from '../../lib/infra_types';
|
||||
|
||||
export const initFindInventoryViewRoute = ({
|
||||
framework,
|
||||
getStartServices,
|
||||
}: Pick<InfraBackendLibs, 'framework' | 'getStartServices'>) => {
|
||||
framework.registerRoute(
|
||||
{
|
||||
method: 'get',
|
||||
path: INVENTORY_VIEW_URL,
|
||||
validate: {
|
||||
query: createValidationFunction(inventoryViewRequestQueryRT),
|
||||
},
|
||||
},
|
||||
async (_requestContext, request, response) => {
|
||||
const { query } = request;
|
||||
const { inventoryViews } = (await getStartServices())[2];
|
||||
const inventoryViewsClient = inventoryViews.getScopedClient(request);
|
||||
|
||||
try {
|
||||
const inventoryViewsList = await inventoryViewsClient.find(query);
|
||||
|
||||
return response.ok({
|
||||
body: findInventoryViewResponsePayloadRT.encode({ data: inventoryViewsList }),
|
||||
});
|
||||
} catch (error) {
|
||||
return response.customError({
|
||||
statusCode: error.statusCode ?? 500,
|
||||
body: {
|
||||
message: error.message ?? 'An unexpected error occurred',
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* 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 {
|
||||
inventoryViewResponsePayloadRT,
|
||||
inventoryViewRequestQueryRT,
|
||||
INVENTORY_VIEW_URL_ENTITY,
|
||||
getInventoryViewRequestParamsRT,
|
||||
} from '../../../common/http_api/latest';
|
||||
import type { InfraBackendLibs } from '../../lib/infra_types';
|
||||
|
||||
export const initGetInventoryViewRoute = ({
|
||||
framework,
|
||||
getStartServices,
|
||||
}: Pick<InfraBackendLibs, 'framework' | 'getStartServices'>) => {
|
||||
framework.registerRoute(
|
||||
{
|
||||
method: 'get',
|
||||
path: INVENTORY_VIEW_URL_ENTITY,
|
||||
validate: {
|
||||
params: createValidationFunction(getInventoryViewRequestParamsRT),
|
||||
query: createValidationFunction(inventoryViewRequestQueryRT),
|
||||
},
|
||||
},
|
||||
async (_requestContext, request, response) => {
|
||||
const { params, query } = request;
|
||||
const { inventoryViews } = (await getStartServices())[2];
|
||||
const inventoryViewsClient = inventoryViews.getScopedClient(request);
|
||||
|
||||
try {
|
||||
const inventoryView = await inventoryViewsClient.get(params.inventoryViewId, query);
|
||||
|
||||
return response.ok({
|
||||
body: inventoryViewResponsePayloadRT.encode({ data: inventoryView }),
|
||||
});
|
||||
} 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',
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
23
x-pack/plugins/infra/server/routes/inventory_views/index.ts
Normal file
23
x-pack/plugins/infra/server/routes/inventory_views/index.ts
Normal file
|
@ -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 { initCreateInventoryViewRoute } from './create_inventory_view';
|
||||
import { initDeleteInventoryViewRoute } from './delete_inventory_view';
|
||||
import { initFindInventoryViewRoute } from './find_inventory_view';
|
||||
import { initGetInventoryViewRoute } from './get_inventory_view';
|
||||
import { initUpdateInventoryViewRoute } from './update_inventory_view';
|
||||
|
||||
export const initInventoryViewRoutes = (
|
||||
dependencies: Pick<InfraBackendLibs, 'framework' | 'getStartServices'>
|
||||
) => {
|
||||
initCreateInventoryViewRoute(dependencies);
|
||||
initDeleteInventoryViewRoute(dependencies);
|
||||
initFindInventoryViewRoute(dependencies);
|
||||
initGetInventoryViewRoute(dependencies);
|
||||
initUpdateInventoryViewRoute(dependencies);
|
||||
};
|
|
@ -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 {
|
||||
inventoryViewRequestParamsRT,
|
||||
inventoryViewRequestQueryRT,
|
||||
inventoryViewResponsePayloadRT,
|
||||
INVENTORY_VIEW_URL_ENTITY,
|
||||
updateInventoryViewRequestPayloadRT,
|
||||
} from '../../../common/http_api/latest';
|
||||
import type { InfraBackendLibs } from '../../lib/infra_types';
|
||||
|
||||
export const initUpdateInventoryViewRoute = ({
|
||||
framework,
|
||||
getStartServices,
|
||||
}: Pick<InfraBackendLibs, 'framework' | 'getStartServices'>) => {
|
||||
framework.registerRoute(
|
||||
{
|
||||
method: 'put',
|
||||
path: INVENTORY_VIEW_URL_ENTITY,
|
||||
validate: {
|
||||
params: createValidationFunction(inventoryViewRequestParamsRT),
|
||||
query: createValidationFunction(inventoryViewRequestQueryRT),
|
||||
body: createValidationFunction(updateInventoryViewRequestPayloadRT),
|
||||
},
|
||||
},
|
||||
async (_requestContext, request, response) => {
|
||||
const { body, params, query } = request;
|
||||
const { inventoryViews } = (await getStartServices())[2];
|
||||
const inventoryViewsClient = inventoryViews.getScopedClient(request);
|
||||
|
||||
try {
|
||||
const inventoryView = await inventoryViewsClient.update(
|
||||
params.inventoryViewId,
|
||||
body.attributes,
|
||||
query
|
||||
);
|
||||
|
||||
return response.ok({
|
||||
body: inventoryViewResponsePayloadRT.encode({ data: inventoryView }),
|
||||
});
|
||||
} 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',
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
|
@ -5,4 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export * from './inventory_view';
|
||||
export * from './log_view';
|
||||
export * from './metrics_explorer_view';
|
||||
|
|
|
@ -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 {
|
||||
inventoryViewSavedObjectName,
|
||||
inventoryViewSavedObjectType,
|
||||
} from './inventory_view_saved_object';
|
||||
export { inventoryViewSavedObjectRT } from './types';
|
|
@ -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 { fold } from 'fp-ts/lib/Either';
|
||||
import { pipe } from 'fp-ts/lib/pipeable';
|
||||
import type { SavedObject, SavedObjectsType } from '@kbn/core/server';
|
||||
import { inventoryViewSavedObjectRT } from './types';
|
||||
|
||||
export const inventoryViewSavedObjectName = 'inventory-view';
|
||||
|
||||
const getInventoryViewTitle = (savedObject: SavedObject<unknown>) =>
|
||||
pipe(
|
||||
inventoryViewSavedObjectRT.decode(savedObject),
|
||||
fold(
|
||||
() => `Inventory view [id=${savedObject.id}]`,
|
||||
({ attributes: { name } }) => name
|
||||
)
|
||||
);
|
||||
|
||||
export const inventoryViewSavedObjectType: SavedObjectsType = {
|
||||
name: inventoryViewSavedObjectName,
|
||||
hidden: false,
|
||||
namespaceType: 'single',
|
||||
management: {
|
||||
defaultSearchField: 'name',
|
||||
displayName: 'inventory view',
|
||||
getTitle: getInventoryViewTitle,
|
||||
icon: 'metricsApp',
|
||||
importableAndExportable: true,
|
||||
},
|
||||
mappings: {
|
||||
dynamic: false,
|
||||
properties: {},
|
||||
},
|
||||
};
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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 { isoToEpochRt, nonEmptyStringRt } from '@kbn/io-ts-utils';
|
||||
import * as rt from 'io-ts';
|
||||
|
||||
export const inventoryViewSavedObjectAttributesRT = rt.intersection([
|
||||
rt.strict({
|
||||
name: nonEmptyStringRt,
|
||||
}),
|
||||
rt.UnknownRecord,
|
||||
]);
|
||||
|
||||
export const inventoryViewSavedObjectRT = rt.intersection([
|
||||
rt.type({
|
||||
id: rt.string,
|
||||
attributes: inventoryViewSavedObjectAttributesRT,
|
||||
}),
|
||||
rt.partial({
|
||||
version: rt.string,
|
||||
updated_at: isoToEpochRt,
|
||||
}),
|
||||
]);
|
|
@ -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 {
|
||||
metricsExplorerViewSavedObjectName,
|
||||
metricsExplorerViewSavedObjectType,
|
||||
} from './metrics_explorer_view_saved_object';
|
||||
export { metricsExplorerViewSavedObjectRT } from './types';
|
|
@ -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 { fold } from 'fp-ts/lib/Either';
|
||||
import { pipe } from 'fp-ts/lib/pipeable';
|
||||
import type { SavedObject, SavedObjectsType } from '@kbn/core/server';
|
||||
import { metricsExplorerViewSavedObjectRT } from './types';
|
||||
|
||||
export const metricsExplorerViewSavedObjectName = 'metrics-explorer-view';
|
||||
|
||||
const getMetricsExplorerViewTitle = (savedObject: SavedObject<unknown>) =>
|
||||
pipe(
|
||||
metricsExplorerViewSavedObjectRT.decode(savedObject),
|
||||
fold(
|
||||
() => `Metrics explorer view [id=${savedObject.id}]`,
|
||||
({ attributes: { name } }) => name
|
||||
)
|
||||
);
|
||||
|
||||
export const metricsExplorerViewSavedObjectType: SavedObjectsType = {
|
||||
name: metricsExplorerViewSavedObjectName,
|
||||
hidden: false,
|
||||
namespaceType: 'single',
|
||||
management: {
|
||||
defaultSearchField: 'name',
|
||||
displayName: 'metrics explorer view',
|
||||
getTitle: getMetricsExplorerViewTitle,
|
||||
icon: 'metricsApp',
|
||||
importableAndExportable: true,
|
||||
},
|
||||
mappings: {
|
||||
dynamic: false,
|
||||
properties: {},
|
||||
},
|
||||
};
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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 { isoToEpochRt } from '@kbn/io-ts-utils';
|
||||
import * as rt from 'io-ts';
|
||||
import { metricsExplorerViewAttributesRT } from '../../../common/metrics_explorer_views';
|
||||
|
||||
export const metricsExplorerViewSavedObjectRT = rt.intersection([
|
||||
rt.type({
|
||||
id: rt.string,
|
||||
attributes: metricsExplorerViewAttributesRT,
|
||||
}),
|
||||
rt.partial({
|
||||
version: rt.string,
|
||||
updated_at: isoToEpochRt,
|
||||
}),
|
||||
]);
|
|
@ -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 { InventoryViewsService } from './inventory_views_service';
|
||||
export { InventoryViewsClient } from './inventory_views_client';
|
||||
export type {
|
||||
InventoryViewsServiceSetup,
|
||||
InventoryViewsServiceStart,
|
||||
InventoryViewsServiceStartDeps,
|
||||
} from './types';
|
|
@ -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 type { IInventoryViewsClient } from './types';
|
||||
|
||||
export const createInventoryViewsClientMock = (): jest.Mocked<IInventoryViewsClient> => ({
|
||||
delete: jest.fn(),
|
||||
find: jest.fn(),
|
||||
get: jest.fn(),
|
||||
create: jest.fn(),
|
||||
update: jest.fn(),
|
||||
});
|
|
@ -0,0 +1,255 @@
|
|||
/*
|
||||
* 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 { InventoryViewAttributes } from '../../../common/inventory_views';
|
||||
|
||||
import { InfraSource } from '../../lib/sources';
|
||||
import { createInfraSourcesMock } from '../../lib/sources/mocks';
|
||||
import { inventoryViewSavedObjectName } from '../../saved_objects/inventory_view';
|
||||
import { InventoryViewsClient } from './inventory_views_client';
|
||||
import { createInventoryViewMock } from '../../../common/inventory_views/inventory_view.mock';
|
||||
import {
|
||||
CreateInventoryViewAttributesRequestPayload,
|
||||
UpdateInventoryViewAttributesRequestPayload,
|
||||
} from '../../../common/http_api/latest';
|
||||
|
||||
describe('InventoryViewsClient class', () => {
|
||||
const mockFindInventoryList = (savedObjectsClient: jest.Mocked<SavedObjectsClientContract>) => {
|
||||
const inventoryViewListMock = [
|
||||
createInventoryViewMock('0', {
|
||||
isDefault: true,
|
||||
} as InventoryViewAttributes),
|
||||
createInventoryViewMock('default_id', {
|
||||
name: 'Default view 2',
|
||||
isStatic: false,
|
||||
} as InventoryViewAttributes),
|
||||
createInventoryViewMock('custom_id', {
|
||||
name: 'Custom',
|
||||
isStatic: false,
|
||||
} as InventoryViewAttributes),
|
||||
];
|
||||
|
||||
savedObjectsClient.find.mockResolvedValue({
|
||||
total: 2,
|
||||
saved_objects: inventoryViewListMock.slice(1).map((view) => ({
|
||||
...view,
|
||||
type: inventoryViewSavedObjectName,
|
||||
score: 0,
|
||||
references: [],
|
||||
})),
|
||||
per_page: 1000,
|
||||
page: 1,
|
||||
});
|
||||
|
||||
return inventoryViewListMock;
|
||||
};
|
||||
|
||||
describe('.find', () => {
|
||||
it('resolves the list of existing inventory views', async () => {
|
||||
const { inventoryViewsClient, infraSources, savedObjectsClient } =
|
||||
createInventoryViewsClient();
|
||||
|
||||
infraSources.getSourceConfiguration.mockResolvedValue(basicTestSourceConfiguration);
|
||||
|
||||
const inventoryViewListMock = mockFindInventoryList(savedObjectsClient);
|
||||
|
||||
const inventoryViewList = await inventoryViewsClient.find({});
|
||||
|
||||
expect(savedObjectsClient.find).toHaveBeenCalled();
|
||||
expect(inventoryViewList).toEqual(inventoryViewListMock);
|
||||
});
|
||||
|
||||
it('always resolves at least the static inventory view', async () => {
|
||||
const { inventoryViewsClient, infraSources, savedObjectsClient } =
|
||||
createInventoryViewsClient();
|
||||
|
||||
const inventoryViewListMock = [
|
||||
createInventoryViewMock('0', {
|
||||
isDefault: true,
|
||||
} as InventoryViewAttributes),
|
||||
];
|
||||
|
||||
infraSources.getSourceConfiguration.mockResolvedValue(basicTestSourceConfiguration);
|
||||
|
||||
savedObjectsClient.find.mockResolvedValue({
|
||||
total: 2,
|
||||
saved_objects: [],
|
||||
per_page: 1000,
|
||||
page: 1,
|
||||
});
|
||||
|
||||
const inventoryViewList = await inventoryViewsClient.find({});
|
||||
|
||||
expect(savedObjectsClient.find).toHaveBeenCalled();
|
||||
expect(inventoryViewList).toEqual(inventoryViewListMock);
|
||||
});
|
||||
});
|
||||
|
||||
it('.get resolves the an inventory view by id', async () => {
|
||||
const { inventoryViewsClient, infraSources, savedObjectsClient } = createInventoryViewsClient();
|
||||
|
||||
const inventoryViewMock = createInventoryViewMock('custom_id', {
|
||||
name: 'Custom',
|
||||
isDefault: false,
|
||||
isStatic: false,
|
||||
} as InventoryViewAttributes);
|
||||
|
||||
infraSources.getSourceConfiguration.mockResolvedValue(basicTestSourceConfiguration);
|
||||
|
||||
savedObjectsClient.get.mockResolvedValue({
|
||||
...inventoryViewMock,
|
||||
type: inventoryViewSavedObjectName,
|
||||
references: [],
|
||||
});
|
||||
|
||||
const inventoryView = await inventoryViewsClient.get('custom_id', {});
|
||||
|
||||
expect(savedObjectsClient.get).toHaveBeenCalled();
|
||||
expect(inventoryView).toEqual(inventoryViewMock);
|
||||
});
|
||||
|
||||
describe('.create', () => {
|
||||
it('generate a new inventory view', async () => {
|
||||
const { inventoryViewsClient, savedObjectsClient } = createInventoryViewsClient();
|
||||
|
||||
const inventoryViewMock = createInventoryViewMock('new_id', {
|
||||
name: 'New view',
|
||||
isStatic: false,
|
||||
} as InventoryViewAttributes);
|
||||
|
||||
mockFindInventoryList(savedObjectsClient);
|
||||
|
||||
savedObjectsClient.create.mockResolvedValue({
|
||||
...inventoryViewMock,
|
||||
type: inventoryViewSavedObjectName,
|
||||
references: [],
|
||||
});
|
||||
|
||||
const inventoryView = await inventoryViewsClient.create({
|
||||
name: 'New view',
|
||||
} as CreateInventoryViewAttributesRequestPayload);
|
||||
|
||||
expect(savedObjectsClient.create).toHaveBeenCalled();
|
||||
expect(inventoryView).toEqual(inventoryViewMock);
|
||||
});
|
||||
|
||||
it('throws an error when a conflicting name is given', async () => {
|
||||
const { inventoryViewsClient, savedObjectsClient } = createInventoryViewsClient();
|
||||
|
||||
mockFindInventoryList(savedObjectsClient);
|
||||
|
||||
await expect(
|
||||
async () =>
|
||||
await inventoryViewsClient.create({
|
||||
name: 'Custom',
|
||||
} as CreateInventoryViewAttributesRequestPayload)
|
||||
).rejects.toThrow('A view with that name already exists.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('.update', () => {
|
||||
it('update an existing inventory view by id', async () => {
|
||||
const { inventoryViewsClient, infraSources, savedObjectsClient } =
|
||||
createInventoryViewsClient();
|
||||
|
||||
const inventoryViews = mockFindInventoryList(savedObjectsClient);
|
||||
|
||||
const inventoryViewMock = {
|
||||
...inventoryViews[1],
|
||||
attributes: {
|
||||
...inventoryViews[1].attributes,
|
||||
name: 'New name',
|
||||
},
|
||||
};
|
||||
|
||||
infraSources.getSourceConfiguration.mockResolvedValue(basicTestSourceConfiguration);
|
||||
|
||||
savedObjectsClient.update.mockResolvedValue({
|
||||
...inventoryViewMock,
|
||||
type: inventoryViewSavedObjectName,
|
||||
references: [],
|
||||
});
|
||||
|
||||
const inventoryView = await inventoryViewsClient.update(
|
||||
'default_id',
|
||||
{
|
||||
name: 'New name',
|
||||
} as UpdateInventoryViewAttributesRequestPayload,
|
||||
{}
|
||||
);
|
||||
|
||||
expect(savedObjectsClient.update).toHaveBeenCalled();
|
||||
expect(inventoryView).toEqual(inventoryViewMock);
|
||||
});
|
||||
|
||||
it('throws an error when a conflicting name is given', async () => {
|
||||
const { inventoryViewsClient, savedObjectsClient } = createInventoryViewsClient();
|
||||
|
||||
mockFindInventoryList(savedObjectsClient);
|
||||
|
||||
await expect(
|
||||
async () =>
|
||||
await inventoryViewsClient.update(
|
||||
'default_id',
|
||||
{
|
||||
name: 'Custom',
|
||||
} as UpdateInventoryViewAttributesRequestPayload,
|
||||
{}
|
||||
)
|
||||
).rejects.toThrow('A view with that name already exists.');
|
||||
});
|
||||
});
|
||||
|
||||
it('.delete removes an inventory view by id', async () => {
|
||||
const { inventoryViewsClient, savedObjectsClient } = createInventoryViewsClient();
|
||||
|
||||
savedObjectsClient.delete.mockResolvedValue({});
|
||||
|
||||
const inventoryView = await inventoryViewsClient.delete('custom_id');
|
||||
|
||||
expect(savedObjectsClient.delete).toHaveBeenCalled();
|
||||
expect(inventoryView).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
const createInventoryViewsClient = () => {
|
||||
const logger = loggerMock.create();
|
||||
const savedObjectsClient = savedObjectsClientMock.create();
|
||||
const infraSources = createInfraSourcesMock();
|
||||
|
||||
const inventoryViewsClient = new InventoryViewsClient(logger, savedObjectsClient, infraSources);
|
||||
|
||||
return {
|
||||
infraSources,
|
||||
inventoryViewsClient,
|
||||
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: 'METRICS_EXPLORER_DEFAULT_VIEW',
|
||||
anomalyThreshold: 0,
|
||||
},
|
||||
};
|
|
@ -0,0 +1,199 @@
|
|||
/*
|
||||
* 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 {
|
||||
staticInventoryViewAttributes,
|
||||
staticInventoryViewId,
|
||||
} from '../../../common/inventory_views';
|
||||
import type {
|
||||
CreateInventoryViewAttributesRequestPayload,
|
||||
InventoryViewRequestQuery,
|
||||
} from '../../../common/http_api/latest';
|
||||
import type { InventoryView, InventoryViewAttributes } from '../../../common/inventory_views';
|
||||
import { decodeOrThrow } from '../../../common/runtime_types';
|
||||
import type { IInfraSources } from '../../lib/sources';
|
||||
import { inventoryViewSavedObjectName } from '../../saved_objects/inventory_view';
|
||||
import { inventoryViewSavedObjectRT } from '../../saved_objects/inventory_view/types';
|
||||
import type { IInventoryViewsClient } from './types';
|
||||
|
||||
export class InventoryViewsClient implements IInventoryViewsClient {
|
||||
constructor(
|
||||
private readonly logger: Logger,
|
||||
private readonly savedObjectsClient: SavedObjectsClientContract,
|
||||
private readonly infraSources: IInfraSources
|
||||
) {}
|
||||
|
||||
static STATIC_VIEW_ID = '0';
|
||||
|
||||
public async find(query: InventoryViewRequestQuery): Promise<InventoryView[]> {
|
||||
this.logger.debug('Trying to load inventory views ...');
|
||||
|
||||
const sourceId = query.sourceId ?? 'default';
|
||||
|
||||
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
|
||||
}),
|
||||
]);
|
||||
|
||||
const defaultView = InventoryViewsClient.createStaticView(
|
||||
sourceConfiguration.configuration.inventoryDefaultView
|
||||
);
|
||||
const views = inventoryViewSavedObject.saved_objects.map((savedObject) =>
|
||||
this.mapSavedObjectToInventoryView(
|
||||
savedObject,
|
||||
sourceConfiguration.configuration.inventoryDefaultView
|
||||
)
|
||||
);
|
||||
|
||||
const inventoryViews = [defaultView, ...views];
|
||||
|
||||
const sortedInventoryViews = this.moveDefaultViewOnTop(inventoryViews);
|
||||
|
||||
return sortedInventoryViews;
|
||||
}
|
||||
|
||||
public async get(
|
||||
inventoryViewId: string,
|
||||
query: InventoryViewRequestQuery
|
||||
): Promise<InventoryView> {
|
||||
this.logger.debug(`Trying to load inventory view with id ${inventoryViewId} ...`);
|
||||
|
||||
const sourceId = query.sourceId ?? 'default';
|
||||
|
||||
// Handle the case where the requested resource is the static inventory view
|
||||
if (inventoryViewId === InventoryViewsClient.STATIC_VIEW_ID) {
|
||||
const sourceConfiguration = await this.infraSources.getSourceConfiguration(
|
||||
this.savedObjectsClient,
|
||||
sourceId
|
||||
);
|
||||
|
||||
return InventoryViewsClient.createStaticView(
|
||||
sourceConfiguration.configuration.inventoryDefaultView
|
||||
);
|
||||
}
|
||||
|
||||
const [sourceConfiguration, inventoryViewSavedObject] = await Promise.all([
|
||||
this.infraSources.getSourceConfiguration(this.savedObjectsClient, sourceId),
|
||||
this.savedObjectsClient.get(inventoryViewSavedObjectName, inventoryViewId),
|
||||
]);
|
||||
|
||||
return this.mapSavedObjectToInventoryView(
|
||||
inventoryViewSavedObject,
|
||||
sourceConfiguration.configuration.inventoryDefaultView
|
||||
);
|
||||
}
|
||||
|
||||
public async create(
|
||||
attributes: CreateInventoryViewAttributesRequestPayload
|
||||
): Promise<InventoryView> {
|
||||
this.logger.debug(`Trying to create inventory view ...`);
|
||||
|
||||
// Validate there is not a view with the same name
|
||||
await this.assertNameConflict(attributes.name);
|
||||
|
||||
const inventoryViewSavedObject = await this.savedObjectsClient.create(
|
||||
inventoryViewSavedObjectName,
|
||||
attributes
|
||||
);
|
||||
|
||||
return this.mapSavedObjectToInventoryView(inventoryViewSavedObject);
|
||||
}
|
||||
|
||||
public async update(
|
||||
inventoryViewId: string,
|
||||
attributes: CreateInventoryViewAttributesRequestPayload,
|
||||
query: InventoryViewRequestQuery
|
||||
): Promise<InventoryView> {
|
||||
this.logger.debug(`Trying to update inventory view with id "${inventoryViewId}"...`);
|
||||
|
||||
// Validate there is not a view with the same name
|
||||
await this.assertNameConflict(attributes.name, [inventoryViewId]);
|
||||
|
||||
const sourceId = query.sourceId ?? 'default';
|
||||
|
||||
const [sourceConfiguration, inventoryViewSavedObject] = await Promise.all([
|
||||
this.infraSources.getSourceConfiguration(this.savedObjectsClient, sourceId),
|
||||
this.savedObjectsClient.update(inventoryViewSavedObjectName, inventoryViewId, attributes),
|
||||
]);
|
||||
|
||||
return this.mapSavedObjectToInventoryView(
|
||||
inventoryViewSavedObject,
|
||||
sourceConfiguration.configuration.inventoryDefaultView
|
||||
);
|
||||
}
|
||||
|
||||
public delete(inventoryViewId: string): Promise<{}> {
|
||||
this.logger.debug(`Trying to delete inventory view with id ${inventoryViewId} ...`);
|
||||
|
||||
return this.savedObjectsClient.delete(inventoryViewSavedObjectName, inventoryViewId);
|
||||
}
|
||||
|
||||
private mapSavedObjectToInventoryView(
|
||||
savedObject: SavedObject | SavedObjectsUpdateResponse,
|
||||
defaultViewId?: string
|
||||
) {
|
||||
const inventoryViewSavedObject = decodeOrThrow(inventoryViewSavedObjectRT)(savedObject);
|
||||
|
||||
return {
|
||||
id: inventoryViewSavedObject.id,
|
||||
version: inventoryViewSavedObject.version,
|
||||
updatedAt: inventoryViewSavedObject.updated_at,
|
||||
attributes: {
|
||||
...inventoryViewSavedObject.attributes,
|
||||
isDefault: inventoryViewSavedObject.id === defaultViewId,
|
||||
isStatic: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private moveDefaultViewOnTop(views: InventoryView[]) {
|
||||
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.savedObjectsClient.find<InventoryViewAttributes>({
|
||||
type: inventoryViewSavedObjectName,
|
||||
perPage: 1000,
|
||||
});
|
||||
|
||||
const hasConflict = [InventoryViewsClient.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): InventoryView => ({
|
||||
id: staticInventoryViewId,
|
||||
attributes: {
|
||||
...staticInventoryViewAttributes,
|
||||
isDefault: defaultViewId === InventoryViewsClient.STATIC_VIEW_ID,
|
||||
},
|
||||
});
|
||||
}
|
|
@ -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 { createInventoryViewsClientMock } from './inventory_views_client.mock';
|
||||
import type { InventoryViewsServiceSetup, InventoryViewsServiceStart } from './types';
|
||||
|
||||
export const createInventoryViewsServiceSetupMock =
|
||||
(): jest.Mocked<InventoryViewsServiceSetup> => {};
|
||||
|
||||
export const createInventoryViewsServiceStartMock =
|
||||
(): jest.Mocked<InventoryViewsServiceStart> => ({
|
||||
getClient: jest.fn((_savedObjectsClient: any) => createInventoryViewsClientMock()),
|
||||
getScopedClient: jest.fn((_request: any) => createInventoryViewsClientMock()),
|
||||
});
|
|
@ -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 { InventoryViewsClient } from './inventory_views_client';
|
||||
import type {
|
||||
InventoryViewsServiceSetup,
|
||||
InventoryViewsServiceStart,
|
||||
InventoryViewsServiceStartDeps,
|
||||
} from './types';
|
||||
|
||||
export class InventoryViewsService {
|
||||
constructor(private readonly logger: Logger) {}
|
||||
|
||||
public setup(): InventoryViewsServiceSetup {}
|
||||
|
||||
public start({
|
||||
infraSources,
|
||||
savedObjects,
|
||||
}: InventoryViewsServiceStartDeps): InventoryViewsServiceStart {
|
||||
const { logger } = this;
|
||||
|
||||
return {
|
||||
getClient(savedObjectsClient: SavedObjectsClientContract) {
|
||||
return new InventoryViewsClient(logger, savedObjectsClient, infraSources);
|
||||
},
|
||||
|
||||
getScopedClient(request: KibanaRequest) {
|
||||
const savedObjectsClient = savedObjects.getScopedClient(request);
|
||||
|
||||
return this.getClient(savedObjectsClient);
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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 {
|
||||
CreateInventoryViewAttributesRequestPayload,
|
||||
InventoryViewRequestQuery,
|
||||
UpdateInventoryViewAttributesRequestPayload,
|
||||
} from '../../../common/http_api/latest';
|
||||
import type { InventoryView } from '../../../common/inventory_views';
|
||||
import type { InfraSources } from '../../lib/sources';
|
||||
|
||||
export interface InventoryViewsServiceStartDeps {
|
||||
infraSources: InfraSources;
|
||||
savedObjects: SavedObjectsServiceStart;
|
||||
}
|
||||
|
||||
export type InventoryViewsServiceSetup = void;
|
||||
|
||||
export interface InventoryViewsServiceStart {
|
||||
getClient(savedObjectsClient: SavedObjectsClientContract): IInventoryViewsClient;
|
||||
getScopedClient(request: KibanaRequest): IInventoryViewsClient;
|
||||
}
|
||||
|
||||
export interface IInventoryViewsClient {
|
||||
delete(inventoryViewId: string): Promise<{}>;
|
||||
find(query: InventoryViewRequestQuery): Promise<InventoryView[]>;
|
||||
get(inventoryViewId: string, query: InventoryViewRequestQuery): Promise<InventoryView>;
|
||||
create(
|
||||
inventoryViewAttributes: CreateInventoryViewAttributesRequestPayload
|
||||
): Promise<InventoryView>;
|
||||
update(
|
||||
inventoryViewId: string,
|
||||
inventoryViewAttributes: UpdateInventoryViewAttributesRequestPayload,
|
||||
query: InventoryViewRequestQuery
|
||||
): Promise<InventoryView>;
|
||||
}
|
|
@ -14,6 +14,7 @@ import type { SearchRequestHandlerContext } from '@kbn/data-plugin/server';
|
|||
import type { MlPluginSetup } from '@kbn/ml-plugin/server';
|
||||
import type { InfraStaticSourceConfiguration } from '../common/source_configuration/source_configuration';
|
||||
import { InfraServerPluginStartDeps } from './lib/adapters/framework';
|
||||
import { InventoryViewsServiceStart } from './services/inventory_views';
|
||||
import { LogViewsServiceSetup, LogViewsServiceStart } from './services/log_views/types';
|
||||
|
||||
export type { InfraConfig } from '../common/plugin_config_types';
|
||||
|
@ -30,6 +31,7 @@ export interface InfraPluginSetup {
|
|||
}
|
||||
|
||||
export interface InfraPluginStart {
|
||||
inventoryViews: InventoryViewsServiceStart;
|
||||
logViews: LogViewsServiceStart;
|
||||
getMetricIndices: (
|
||||
savedObjectsClient: SavedObjectsClientContract,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue