[Infra] Custom dashboards api and saved object redesign (#179576)

Closes #179356

## Summary

This PR changes the custom dashboards' saved object and API from having
one saved object per asset type to having one saved object per
dashboard.
| Before | After | 
|--------|------|
| ``` { dashboardIdList: string[]; assetType: string } ``` | ``` {
dashboardId: string; dashboardFilterAssetIdEnabled: boolean; assetType:
string; }```|

The API endpoints are changed as well to
`api/infra/{assetType}/custom-dashboards`:

 - GET  api/infra/host/custom-dashboards

 - POST api/infra/host/custom-dashboards
{ 
  "dashboardSavedObjectId": string,
  "dashboardFilterAssetIdEnabled": boolean
}

The new endpoints are using
`api/infra/{assetType}/custom-dashboards/{id}` where `id` is the id of
the saved object
- **new** PUT api/infra/host/custom-dashboards/123
- **new** DELETE api/infra/host/custom-dashboards/123

After adding the separate PUT endpoint I added a validation to prevent
posting the same dashboard object (payload with the same asset type and
dashboard id) twice. So if the POST endpoint is called twice with the
same params/payload it will return 400 and a message that the dashboard
already exists: "Dashboard with id {id} has already been linked to
{asset type}"


![image](0729a9b1-f8e9-40d9-8be1-f27bfa2b1e22)
 

Testing: 
1. Unit tests
 - Run in this order in separate terminal tabs/windows
- `node scripts/functional_tests_server --config
x-pack/test/api_integration/config`
- `node scripts/functional_test_runner --config
x-pack/test/api_integration/apis/metrics_ui/config.ts --grep 'Infra
Custom Dashboards API'`
2. Dev tools
 - First go to Stack Management > Advanced Settings
- Search for "infra" and enable `Custom dashboards for asset details in
Infrastructure`:
<img width="1704" alt="image"
src="614258c8-ee36-4466-b16c-d48f58c3f5dc">
 - Go to Dev Tools and run

   - POST:
 
 ```
  POST kbn:/api/infra/host/custom-dashboards
  { 
    "dashboardSavedObjectId": "123",
    "dashboardFilterAssetIdEnabled": true
  }
  ```
if the same payload is posted 2 times the **second** time the response
will be 400 because the dashboard for the current asset already exists:

   - GET: `GET  kbn:/api/infra/host/custom-dashboards` 
   Example Response: 
   ```
  [
    {
      "id": "a21acbc4-8102-4b09-8bd1-1a9aa4c36f10",
      "assetType": "host",
      "dashboardSavedObjectId": "123",
      "dashboardFilterAssetIdEnabled": true
    }
  ]
   ```
   - PUT (update the returned dashboard):
 
 ```
PUT
kbn:/api/infra/host/custom-dashboards/a21acbc4-8102-4b09-8bd1-1a9aa4c36f10
  { 
    "dashboardSavedObjectId": "123",
    "dashboardFilterAssetIdEnabled": false
  }
  ```
- DELETE (delete the returned dashboard): `DELETE
kbn:/api/infra/host/custom-dashboards/a21acbc4-8102-4b09-8bd1-1a9aa4c36f10`

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
jennypavlova 2024-04-04 19:26:52 +02:00 committed by GitHub
parent 8b00070cdf
commit c9846714f1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 432 additions and 103 deletions

View file

@ -176,7 +176,7 @@ export const HASH_TO_VERSION_MAP = {
'guided-onboarding-guide-state|a3db59c45a3fd2730816d4f53c35c7d9': '10.0.0',
'guided-onboarding-plugin-state|3d1b76c39bfb2cc8296b024d73854724': '10.0.0',
'index-pattern|83c02d842fe2a94d14dfa13f7dcd6e87': '10.0.0',
'infra-custom-dashboards|6eed22cbe14594bad8c076fa864930de': '10.0.0',
'infra-custom-dashboards|1eb3c9e1888b8daea8495769e8d3ba2d': '10.2.0',
'infrastructure-monitoring-log-view|c50526fc6040c5355ed027d34d05b35c': '10.0.0',
'infrastructure-ui-source|3d1b76c39bfb2cc8296b024d73854724': '10.0.0',
'ingest_manager_settings|b91ffb075799c78ffd7dbd51a279c8c9': '10.1.0',

View file

@ -462,8 +462,8 @@
],
"infra-custom-dashboards": [
"assetType",
"dashboardIdList",
"kuery"
"dashboardFilterAssetIdEnabled",
"dashboardSavedObjectId"
],
"infrastructure-monitoring-log-view": [
"name"

View file

@ -1559,15 +1559,16 @@
}
},
"infra-custom-dashboards": {
"dynamic": false,
"properties": {
"assetType": {
"type": "keyword"
},
"dashboardIdList": {
"dashboardSavedObjectId": {
"type": "keyword"
},
"kuery": {
"type": "text"
"dashboardFilterAssetIdEnabled": {
"type": "boolean"
}
}
},

View file

@ -103,7 +103,7 @@ describe('checking migration metadata changes on all registered SO types', () =>
"guided-onboarding-guide-state": "d338972ed887ac480c09a1a7fbf582d6a3827c91",
"guided-onboarding-plugin-state": "bc109e5ef46ca594fdc179eda15f3095ca0a37a4",
"index-pattern": "997108a9ea1e8076e22231e1c95517cdb192b9c5",
"infra-custom-dashboards": "b92b6db1c1f8998af6e2951a17b76cf886c6bee5",
"infra-custom-dashboards": "1a5994f2e05bb8a1609825ddbf5012f77c5c67f3",
"infrastructure-monitoring-log-view": "5f86709d3c27aed7a8379153b08ee5d3d90d77f5",
"infrastructure-ui-source": "113182d6895764378dfe7fa9fa027244f3a457c4",
"ingest-agent-policies": "7633e578f60c074f8267bc50ec4763845e431437",

View file

@ -10,7 +10,11 @@ import { InventoryItemType } from '@kbn/metrics-data-access-plugin/common';
export type InfraCustomDashboardAssetType = InventoryItemType;
export interface InfraCustomDashboard {
dashboardIdList: string[];
dashboardSavedObjectId: string;
assetType: InfraCustomDashboardAssetType;
kuery?: string;
dashboardFilterAssetIdEnabled: boolean;
}
export interface InfraSavedCustomDashboard extends InfraCustomDashboard {
id: string;
}

View file

@ -12,24 +12,22 @@ const AssetTypeRT = rt.type({
assetType: ItemTypeRT,
});
const CustomDashboardRT = rt.intersection([
AssetTypeRT,
rt.type({
dashboardIdList: rt.array(rt.string),
}),
rt.partial({
kuery: rt.string,
}),
]);
const PayloadRT = rt.type({
dashboardSavedObjectId: rt.string,
dashboardFilterAssetIdEnabled: rt.boolean,
});
const SavedObjectIdRT = rt.type({
id: rt.string,
});
const InfraCustomDashboardRT = rt.intersection([AssetTypeRT, PayloadRT, SavedObjectIdRT]);
/**
GET endpoint
*/
export const InfraGetCustomDashboardsRequestParamsRT = AssetTypeRT;
export const InfraGetCustomDashboardsResponseBodyRT = CustomDashboardRT;
export type InfraGetCustomDashboardsRequestParams = rt.TypeOf<
typeof InfraGetCustomDashboardsRequestParamsRT
>;
export const InfraGetCustomDashboardsRequestPathParamsRT = AssetTypeRT;
export const InfraGetCustomDashboardsResponseBodyRT = rt.array(InfraCustomDashboardRT);
export type InfraGetCustomDashboardsResponseBody = rt.TypeOf<
typeof InfraGetCustomDashboardsResponseBodyRT
>;
@ -37,11 +35,27 @@ export type InfraGetCustomDashboardsResponseBody = rt.TypeOf<
/**
* POST endpoint
*/
export const InfraSaveCustomDashboardsRequestPayloadRT = CustomDashboardRT;
export const InfraSaveCustomDashboardsResponseBodyRT = CustomDashboardRT;
export const InfraSaveCustomDashboardsRequestPayloadRT = PayloadRT;
export const InfraSaveCustomDashboardsResponseBodyRT = InfraCustomDashboardRT;
export type InfraSaveCustomDashboardsRequestPayload = rt.TypeOf<
typeof InfraSaveCustomDashboardsRequestPayloadRT
>;
export type InfraSaveCustomDashboardsResponseBody = rt.TypeOf<
typeof InfraSaveCustomDashboardsResponseBodyRT
>;
/**
* PUT endpoint
*/
export const InfraUpdateCustomDashboardsRequestPathParamsRT = rt.intersection([
AssetTypeRT,
SavedObjectIdRT,
]);
/**
* DELETE endpoint
*/
export const InfraDeleteCustomDashboardsRequestParamsRT = rt.intersection([
AssetTypeRT,
SavedObjectIdRT,
]);

View file

@ -8,8 +8,12 @@
import type { KibanaFramework } from '../../lib/adapters/framework/kibana_framework_adapter';
import { initGetCustomDashboardRoute } from './get_custom_dashboard';
import { initSaveCustomDashboardRoute } from './save_custom_dashboard';
import { initDeleteCustomDashboardRoute } from './delete_custom_dashboard';
import { initUpdateCustomDashboardRoute } from './update_custom_dashboard';
export function initCustomDashboardsRoutes(framework: KibanaFramework) {
initGetCustomDashboardRoute(framework);
initSaveCustomDashboardRoute(framework);
initDeleteCustomDashboardRoute(framework);
initUpdateCustomDashboardRoute(framework);
}

View file

@ -0,0 +1,46 @@
/*
* 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 { createRouteValidationFunction } from '@kbn/io-ts-utils';
import { InfraDeleteCustomDashboardsRequestParamsRT } from '../../../common/http_api/custom_dashboards_api';
import { KibanaFramework } from '../../lib/adapters/framework/kibana_framework_adapter';
import { handleRouteErrors } from '../../utils/handle_route_errors';
import { checkCustomDashboardsEnabled } from './lib/check_custom_dashboards_enabled';
import { deleteCustomDashboard } from './lib/delete_custom_dashboard';
export function initDeleteCustomDashboardRoute(framework: KibanaFramework) {
const validateParams = createRouteValidationFunction(InfraDeleteCustomDashboardsRequestParamsRT);
framework.registerRoute(
{
method: 'delete',
path: '/api/infra/{assetType}/custom-dashboards/{id}',
validate: {
params: validateParams,
},
options: {
access: 'internal',
},
},
handleRouteErrors(async (context, request, response) => {
const { savedObjectsClient, uiSettingsClient } = await context.infra;
await checkCustomDashboardsEnabled(uiSettingsClient);
const { id } = request.params;
await deleteCustomDashboard({
savedObjectsClient,
savedObjectId: id,
});
return response.ok({
body: id,
});
})
);
}

View file

@ -7,7 +7,7 @@
import { createRouteValidationFunction } from '@kbn/io-ts-utils';
import {
InfraGetCustomDashboardsRequestParamsRT,
InfraGetCustomDashboardsRequestPathParamsRT,
InfraGetCustomDashboardsResponseBodyRT,
} from '../../../common/http_api/custom_dashboards_api';
import { KibanaFramework } from '../../lib/adapters/framework/kibana_framework_adapter';
@ -16,12 +16,12 @@ import { checkCustomDashboardsEnabled } from './lib/check_custom_dashboards_enab
import { findCustomDashboard } from './lib/find_custom_dashboard';
export function initGetCustomDashboardRoute(framework: KibanaFramework) {
const validateParams = createRouteValidationFunction(InfraGetCustomDashboardsRequestParamsRT);
const validateParams = createRouteValidationFunction(InfraGetCustomDashboardsRequestPathParamsRT);
framework.registerRoute(
{
method: 'get',
path: '/api/infra/custom-dashboards/{assetType}',
path: '/api/infra/{assetType}/custom-dashboards',
validate: {
params: validateParams,
},
@ -37,24 +37,8 @@ export function initGetCustomDashboardRoute(framework: KibanaFramework) {
const params = request.params;
const customDashboards = await findCustomDashboard(params.assetType, savedObjectsClient);
if (customDashboards.total === 0) {
return response.ok({
body: InfraGetCustomDashboardsResponseBodyRT.encode({
assetType: params.assetType,
dashboardIdList: [],
kuery: undefined,
}),
});
}
const attributes = customDashboards.saved_objects[0].attributes;
return response.ok({
body: InfraGetCustomDashboardsResponseBodyRT.encode({
assetType: attributes.assetType,
dashboardIdList: attributes.dashboardIdList,
kuery: attributes.kuery,
}),
body: InfraGetCustomDashboardsResponseBodyRT.encode(customDashboards),
});
})
);

View file

@ -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 SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server';
import { INFRA_CUSTOM_DASHBOARDS_SAVED_OBJECT_TYPE } from '../../../saved_objects';
interface Options {
savedObjectsClient: SavedObjectsClientContract;
savedObjectId: string;
}
export function deleteCustomDashboard({ savedObjectsClient, savedObjectId }: Options) {
return savedObjectsClient.delete(INFRA_CUSTOM_DASHBOARDS_SAVED_OBJECT_TYPE, savedObjectId);
}

View file

@ -12,13 +12,22 @@ import type {
InfraCustomDashboardAssetType,
} from '../../../../common/custom_dashboards';
export function findCustomDashboard(
export async function findCustomDashboard(
assetType: InfraCustomDashboardAssetType,
savedObjectsClient: SavedObjectsClientContract
) {
return savedObjectsClient.find<InfraCustomDashboard>({
const result = await savedObjectsClient.find<InfraCustomDashboard>({
type: INFRA_CUSTOM_DASHBOARDS_SAVED_OBJECT_TYPE,
search: assetType,
searchFields: ['assetType'] as [keyof InfraCustomDashboard],
page: 1,
perPage: 1000,
sortField: 'updated_at',
sortOrder: 'desc',
});
return result.saved_objects.map(({ id, attributes }) => ({
id,
...attributes,
}));
}

View file

@ -9,24 +9,26 @@ import { createRouteValidationFunction } from '@kbn/io-ts-utils';
import { InfraCustomDashboard } from '../../../common/custom_dashboards';
import {
InfraSaveCustomDashboardsRequestPayloadRT,
InfraSaveCustomDashboardsRequestPayload,
InfraSaveCustomDashboardsResponseBodyRT,
InfraGetCustomDashboardsRequestPathParamsRT,
} from '../../../common/http_api/custom_dashboards_api';
import { KibanaFramework } from '../../lib/adapters/framework/kibana_framework_adapter';
import { INFRA_CUSTOM_DASHBOARDS_SAVED_OBJECT_TYPE } from '../../saved_objects';
import { checkCustomDashboardsEnabled } from './lib/check_custom_dashboards_enabled';
import { findCustomDashboard } from './lib/find_custom_dashboard';
import { handleRouteErrors } from '../../utils/handle_route_errors';
import { findCustomDashboard } from './lib/find_custom_dashboard';
export function initSaveCustomDashboardRoute(framework: KibanaFramework) {
const validatePayload = createRouteValidationFunction(InfraSaveCustomDashboardsRequestPayloadRT);
const validateParams = createRouteValidationFunction(InfraGetCustomDashboardsRequestPathParamsRT);
framework.registerRoute(
{
method: 'post',
path: '/api/infra/custom-dashboards',
path: '/api/infra/{assetType}/custom-dashboards',
validate: {
body: validatePayload,
params: validateParams,
},
options: {
access: 'internal',
@ -37,29 +39,33 @@ export function initSaveCustomDashboardRoute(framework: KibanaFramework) {
await checkCustomDashboardsEnabled(uiSettingsClient);
const payload: InfraSaveCustomDashboardsRequestPayload = request.body;
const customDashboards = await findCustomDashboard(payload.assetType, savedObjectsClient);
const { dashboardSavedObjectId } = request.body;
if (customDashboards.total === 0) {
const savedCustomDashboard = await savedObjectsClient.create<InfraCustomDashboard>(
INFRA_CUSTOM_DASHBOARDS_SAVED_OBJECT_TYPE,
payload
);
const { assetType } = request.params;
return response.ok({
body: InfraSaveCustomDashboardsResponseBodyRT.encode(savedCustomDashboard.attributes),
const customDashboards = await findCustomDashboard(assetType, savedObjectsClient);
const dashboardExist = customDashboards.find(
(customDashboard) => customDashboard.dashboardSavedObjectId === dashboardSavedObjectId
);
if (dashboardExist) {
return response.badRequest({
body: `Dashboard with id ${dashboardSavedObjectId} has already been linked to ${assetType}`,
});
}
const savedCustomDashboard = await savedObjectsClient.update<InfraCustomDashboard>(
const savedCustomDashboard = await savedObjectsClient.create<InfraCustomDashboard>(
INFRA_CUSTOM_DASHBOARDS_SAVED_OBJECT_TYPE,
customDashboards.saved_objects[0].id,
payload
{
assetType,
...request.body,
}
);
return response.ok({
body: InfraSaveCustomDashboardsResponseBodyRT.encode({
...payload,
id: savedCustomDashboard.id,
...savedCustomDashboard.attributes,
}),
});

View file

@ -0,0 +1,64 @@
/*
* 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 { createRouteValidationFunction } from '@kbn/io-ts-utils';
import { InfraCustomDashboard } from '../../../common/custom_dashboards';
import {
InfraSaveCustomDashboardsRequestPayloadRT,
InfraSaveCustomDashboardsResponseBodyRT,
InfraUpdateCustomDashboardsRequestPathParamsRT,
} from '../../../common/http_api/custom_dashboards_api';
import { KibanaFramework } from '../../lib/adapters/framework/kibana_framework_adapter';
import { INFRA_CUSTOM_DASHBOARDS_SAVED_OBJECT_TYPE } from '../../saved_objects';
import { checkCustomDashboardsEnabled } from './lib/check_custom_dashboards_enabled';
import { handleRouteErrors } from '../../utils/handle_route_errors';
export function initUpdateCustomDashboardRoute(framework: KibanaFramework) {
const validatePayload = createRouteValidationFunction(InfraSaveCustomDashboardsRequestPayloadRT);
const validateParams = createRouteValidationFunction(
InfraUpdateCustomDashboardsRequestPathParamsRT
);
framework.registerRoute(
{
method: 'put',
path: '/api/infra/{assetType}/custom-dashboards/{id}',
validate: {
body: validatePayload,
params: validateParams,
},
options: {
access: 'internal',
},
},
handleRouteErrors(async (context, request, response) => {
const { savedObjectsClient, uiSettingsClient } = await context.infra;
await checkCustomDashboardsEnabled(uiSettingsClient);
const { id, assetType } = request.params;
const savedCustomDashboard = await savedObjectsClient.update<InfraCustomDashboard>(
INFRA_CUSTOM_DASHBOARDS_SAVED_OBJECT_TYPE,
id,
{
assetType,
...request.body,
}
);
return response.ok({
body: InfraSaveCustomDashboardsResponseBodyRT.encode({
id: savedCustomDashboard.id,
assetType,
...request.body,
...savedCustomDashboard.attributes,
}),
});
})
);
}

View file

@ -7,20 +7,21 @@
import { SavedObjectsFieldMapping, SavedObjectsType } from '@kbn/core/server';
import { i18n } from '@kbn/i18n';
import { schema, Type } from '@kbn/config-schema';
import { schema } from '@kbn/config-schema';
import { InfraCustomDashboard } from '../../../common/custom_dashboards';
export const INFRA_CUSTOM_DASHBOARDS_SAVED_OBJECT_TYPE = 'infra-custom-dashboards';
const properties: Record<keyof InfraCustomDashboard, SavedObjectsFieldMapping> = {
dashboardIdList: { type: 'keyword' },
assetType: { type: 'keyword' },
kuery: { type: 'text' },
};
const createSchema: Record<keyof InfraCustomDashboard, Type<any>> = {
dashboardIdList: schema.arrayOf(schema.string()),
assetType: schema.string(),
kuery: schema.maybe(schema.string()),
assetType: {
type: 'keyword',
},
dashboardSavedObjectId: {
type: 'keyword',
},
dashboardFilterAssetIdEnabled: {
type: 'boolean',
},
};
export const infraCustomDashboardsSavedObjectType: SavedObjectsType = {
@ -28,6 +29,7 @@ export const infraCustomDashboardsSavedObjectType: SavedObjectsType = {
hidden: false,
namespaceType: 'multiple',
mappings: {
dynamic: false,
properties,
},
management: {
@ -42,7 +44,37 @@ export const infraCustomDashboardsSavedObjectType: SavedObjectsType = {
'1': {
changes: [],
schemas: {
create: schema.object(createSchema),
create: schema.object({
dashboardIdList: schema.arrayOf(schema.string()),
assetType: schema.string(),
kuery: schema.maybe(schema.string()),
}),
},
},
'2': {
changes: [
{
type: 'mappings_addition',
addedMappings: {
dashboardSavedObjectId: {
type: 'keyword',
},
dashboardFilterAssetIdEnabled: {
type: 'boolean',
},
},
},
{
type: 'mappings_deprecation',
deprecatedMappings: ['dashboardIdList', 'kuery'],
},
],
schemas: {
create: schema.object({
dashboardSavedObjectId: schema.string(),
assetType: schema.string(),
dashboardFilterAssetIdEnabled: schema.boolean(),
}),
},
},
},

View file

@ -12,7 +12,10 @@ import { INFRA_CUSTOM_DASHBOARDS_SAVED_OBJECT_TYPE } from '@kbn/infra-plugin/ser
import { enableInfrastructureAssetCustomDashboards } from '@kbn/observability-plugin/common';
import { FtrProviderContext } from '../../ftr_provider_context';
const CUSTOM_DASHBOARDS_API_URL = '/api/infra/custom-dashboards';
const getCustomDashboardsUrl = (assetType: string, dashboardSavedObjectId?: string) =>
dashboardSavedObjectId
? `/api/infra/${assetType}/custom-dashboards/${dashboardSavedObjectId}`
: `/api/infra/${assetType}/custom-dashboards`;
export default function ({ getService }: FtrProviderContext) {
const supertest = getService('supertest');
@ -31,7 +34,7 @@ export default function ({ getService }: FtrProviderContext) {
[enableInfrastructureAssetCustomDashboards]: false,
});
await supertest.get(`${CUSTOM_DASHBOARDS_API_URL}/host`).expect(403);
await supertest.get(getCustomDashboardsUrl('host')).expect(403);
});
it('responds with an error when trying to request a custom dashboard for unsupported asset type', async () => {
@ -39,7 +42,7 @@ export default function ({ getService }: FtrProviderContext) {
[enableInfrastructureAssetCustomDashboards]: false,
});
await supertest.get(`${CUSTOM_DASHBOARDS_API_URL}/unsupported-asset-type`).expect(400);
await supertest.get(getCustomDashboardsUrl('unsupported-asset-type')).expect(400);
});
it('responds with an empty configuration if custom dashboard saved object does not exist', async () => {
@ -50,18 +53,16 @@ export default function ({ getService }: FtrProviderContext) {
types: [INFRA_CUSTOM_DASHBOARDS_SAVED_OBJECT_TYPE],
});
const response = await supertest.get(`${CUSTOM_DASHBOARDS_API_URL}/host`).expect(200);
const response = await supertest.get(getCustomDashboardsUrl('host')).expect(200);
expect(response.body).to.be.eql({
assetType: 'host',
dashboardIdList: [],
});
expect(response.body).to.be.eql([]);
});
it('responds with the custom dashboard configuration for a given asset type when it exists', async () => {
const customDashboard: InfraCustomDashboard = {
assetType: 'host',
dashboardIdList: ['123'],
dashboardSavedObjectId: '123',
dashboardFilterAssetIdEnabled: true,
};
await kibanaServer.uiSettings.update({
[enableInfrastructureAssetCustomDashboards]: true,
@ -72,24 +73,27 @@ export default function ({ getService }: FtrProviderContext) {
overwrite: true,
});
const response = await supertest.get(`${CUSTOM_DASHBOARDS_API_URL}/host`).expect(200);
const response = await supertest.get(getCustomDashboardsUrl('host')).expect(200);
expect(response.body).to.be.eql(customDashboard);
expect(response.body).to.have.length(1);
expect(response.body[0]).to.have.property('dashboardFilterAssetIdEnabled', true);
expect(response.body[0]).to.have.property('assetType', 'host');
expect(response.body[0]).to.have.property('dashboardSavedObjectId', '123');
});
});
describe('POST endpoint for saving (creating or updating) custom dashboard', () => {
describe('POST endpoint for saving custom dashboard', () => {
it('responds with an error if Custom Dashboards UI setting is not enabled', async () => {
const payload: InfraSaveCustomDashboardsRequestPayload = {
assetType: 'host',
dashboardIdList: ['123'],
dashboardSavedObjectId: '123',
dashboardFilterAssetIdEnabled: true,
};
await kibanaServer.uiSettings.update({
[enableInfrastructureAssetCustomDashboards]: false,
});
await supertest
.post(`${CUSTOM_DASHBOARDS_API_URL}`)
.post(getCustomDashboardsUrl('host'))
.set('kbn-xsrf', 'xxx')
.send(payload)
.expect(403);
@ -97,15 +101,15 @@ export default function ({ getService }: FtrProviderContext) {
it('responds with an error when trying to update a custom dashboard for unsupported asset type', async () => {
const payload = {
assetType: 'unsupported-asset-type',
dashboardIdList: ['123'],
dashboardSavedObjectId: '123',
dashboardFilterAssetIdEnabled: true,
};
await kibanaServer.uiSettings.update({
[enableInfrastructureAssetCustomDashboards]: true,
});
await supertest
.post(`${CUSTOM_DASHBOARDS_API_URL}`)
.post(getCustomDashboardsUrl('unsupported-asset-type'))
.set('kbn-xsrf', 'xxx')
.send(payload)
.expect(400);
@ -113,8 +117,8 @@ export default function ({ getService }: FtrProviderContext) {
it('creates a new dashboard configuration when saving for the first time', async () => {
const payload: InfraSaveCustomDashboardsRequestPayload = {
assetType: 'host',
dashboardIdList: ['123'],
dashboardSavedObjectId: '123',
dashboardFilterAssetIdEnabled: true,
};
await kibanaServer.uiSettings.update({
[enableInfrastructureAssetCustomDashboards]: true,
@ -124,15 +128,23 @@ export default function ({ getService }: FtrProviderContext) {
});
const response = await supertest
.post(`${CUSTOM_DASHBOARDS_API_URL}`)
.post(getCustomDashboardsUrl('host'))
.set('kbn-xsrf', 'xxx')
.send(payload)
.expect(200);
expect(response.body).to.be.eql(payload);
expect(response.body).to.have.property('id');
expect(response.body).to.have.property('dashboardFilterAssetIdEnabled', true);
expect(response.body).to.have.property('assetType', 'host');
expect(response.body).to.have.property('dashboardSavedObjectId', '123');
});
it('updates existing dashboard configuration when for a given asset type', async () => {
it('returns 400 when the dashboard already exist and tries to create it again', async () => {
const payload: InfraSaveCustomDashboardsRequestPayload = {
dashboardSavedObjectId: '123',
dashboardFilterAssetIdEnabled: true,
};
await kibanaServer.uiSettings.update({
[enableInfrastructureAssetCustomDashboards]: true,
});
@ -143,24 +155,160 @@ export default function ({ getService }: FtrProviderContext) {
type: INFRA_CUSTOM_DASHBOARDS_SAVED_OBJECT_TYPE,
attributes: {
assetType: 'host',
dashboardIdList: ['123'],
dashboardSavedObjectId: '123',
dashboardFilterAssetIdEnabled: true,
},
overwrite: true,
});
const response = await supertest
.post(getCustomDashboardsUrl('host'))
.set('kbn-xsrf', 'xxx')
.send(payload)
.expect(400);
expect(response.body.error).to.be.eql('Bad Request');
expect(response.body.message).to.be.eql(
'Dashboard with id 123 has already been linked to host'
);
});
});
describe('PUT endpoint for updating custom dashboard', () => {
it('responds with an error if Custom Dashboards UI setting is not enabled', async () => {
const payload: InfraSaveCustomDashboardsRequestPayload = {
dashboardSavedObjectId: '123',
dashboardFilterAssetIdEnabled: true,
};
await kibanaServer.uiSettings.update({
[enableInfrastructureAssetCustomDashboards]: false,
});
await supertest
.put(getCustomDashboardsUrl('host', '123'))
.set('kbn-xsrf', 'xxx')
.send(payload)
.expect(403);
});
it('responds with an error when trying to update not existing dashboard', async () => {
const payload: InfraSaveCustomDashboardsRequestPayload = {
dashboardSavedObjectId: '123',
dashboardFilterAssetIdEnabled: true,
};
await kibanaServer.uiSettings.update({
[enableInfrastructureAssetCustomDashboards]: true,
});
await supertest
.put(getCustomDashboardsUrl('host', '000'))
.set('kbn-xsrf', 'xxx')
.send(payload)
.expect(404);
});
it('updates existing dashboard configuration for a given asset type', async () => {
await kibanaServer.uiSettings.update({
[enableInfrastructureAssetCustomDashboards]: true,
});
await kibanaServer.savedObjects.clean({
types: [INFRA_CUSTOM_DASHBOARDS_SAVED_OBJECT_TYPE],
});
await kibanaServer.savedObjects.create({
type: INFRA_CUSTOM_DASHBOARDS_SAVED_OBJECT_TYPE,
attributes: {
assetType: 'host',
dashboardSavedObjectId: '456',
dashboardFilterAssetIdEnabled: true,
},
overwrite: true,
});
const existingDashboardSavedObject = await kibanaServer.savedObjects.create({
type: INFRA_CUSTOM_DASHBOARDS_SAVED_OBJECT_TYPE,
attributes: {
assetType: 'host',
dashboardSavedObjectId: '123',
dashboardFilterAssetIdEnabled: true,
},
overwrite: true,
});
const payload: InfraSaveCustomDashboardsRequestPayload = {
assetType: 'host',
dashboardIdList: ['123', '456'],
dashboardSavedObjectId: '123',
dashboardFilterAssetIdEnabled: false,
};
const updateResponse = await supertest
.post(`${CUSTOM_DASHBOARDS_API_URL}`)
.put(getCustomDashboardsUrl('host', existingDashboardSavedObject.id))
.set('kbn-xsrf', 'xxx')
.send(payload)
.expect(200);
const getResponse = await supertest.get(`${CUSTOM_DASHBOARDS_API_URL}/host`).expect(200);
const getResponse = await supertest.get(getCustomDashboardsUrl('host')).expect(200);
expect(updateResponse.body).to.be.eql(payload);
expect(getResponse.body).to.be.eql(payload);
expect(updateResponse.body).to.be.eql({
...payload,
assetType: 'host',
id: updateResponse.body.id,
});
expect(getResponse.body).to.have.length(2);
expect(getResponse.body[0]).to.have.property('dashboardSavedObjectId', '123');
expect(getResponse.body[0]).to.have.property('dashboardFilterAssetIdEnabled', false);
expect(getResponse.body[0]).to.have.property('assetType', 'host');
expect(getResponse.body[1]).to.have.property('dashboardSavedObjectId', '456');
expect(getResponse.body[1]).to.have.property('dashboardFilterAssetIdEnabled', true);
expect(getResponse.body[1]).to.have.property('assetType', 'host');
});
});
describe('DELETE endpoint for removing a custom dashboard', () => {
it('responds with an error if Custom Dashboards UI setting is not enabled', async () => {
await kibanaServer.uiSettings.update({
[enableInfrastructureAssetCustomDashboards]: false,
});
await supertest
.delete(getCustomDashboardsUrl('host', '123'))
.set('kbn-xsrf', 'xxx')
.expect(403);
});
it('responds with an error when trying to delete not existing dashboard', async () => {
await kibanaServer.uiSettings.update({
[enableInfrastructureAssetCustomDashboards]: true,
});
await supertest
.delete(getCustomDashboardsUrl('host', '000'))
.set('kbn-xsrf', 'xxx')
.expect(404);
});
it('deletes an existing dashboard', async () => {
await kibanaServer.uiSettings.update({
[enableInfrastructureAssetCustomDashboards]: true,
});
await kibanaServer.savedObjects.clean({
types: [INFRA_CUSTOM_DASHBOARDS_SAVED_OBJECT_TYPE],
});
const existingDashboardSavedObject = await kibanaServer.savedObjects.create({
type: INFRA_CUSTOM_DASHBOARDS_SAVED_OBJECT_TYPE,
attributes: {
assetType: 'host',
dashboardSavedObjectId: '123',
dashboardFilterAssetIdEnabled: true,
},
overwrite: true,
});
await supertest
.delete(getCustomDashboardsUrl('host', existingDashboardSavedObject.id))
.set('kbn-xsrf', 'xxx')
.expect(200);
const afterDeleteResponse = await supertest.get(getCustomDashboardsUrl('host')).expect(200);
expect(afterDeleteResponse.body).to.be.eql([]);
});
});
});