mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 10:40:07 -04:00
[Lens] Add internal CRUD api routes (#223296)
## Summary This adds basic Lens CRUD api routes using the Content Management system. | Operation | URI | |--------|--------| | Create | `POST api/lens/visualizations` | | Get | `GET api/lens/visualizations/{id}` | | Search | `GET api/lens/visualizations?query=test` | | Update | `PUT api/lens/visualizations/{id}` | | Delete | `DELETE api/lens/visualizations/{id}` | ### Changes to Lens Content Management The custom `update` method uses `soClient.create` under the hood for reasons (i.e. #160116). However, doing this acts as an update or create method with the provided `id`. I changed this behavior so now any update where the id is not found will return a `404` error. Closes #221941 Closes #221942 - OpenAPI docs auto generate from route schema ### Testing You can testing this locally in kibana dev console like so... ``` GET kbn:/api/lens/visualizations/<id>?apiVersion=1 ``` > The `apiVersion` query param is needed to test `internal` api routes. ## Checklist - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md) - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The `release_note:breaking` label should be applied in these situations. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Marco Vettorello <marco.vettorello@elastic.co>
This commit is contained in:
parent
2c55a7d9d0
commit
17c2556fc6
37 changed files with 1582 additions and 37 deletions
|
@ -378,6 +378,7 @@ enabled:
|
|||
- x-pack/platform/test/api_integration/apis/management/config.ts
|
||||
- x-pack/platform/test/api_integration/apis/management/index_management/disabled_data_enrichers/config.ts
|
||||
- x-pack/platform/test/api_integration/apis/maps/config.ts
|
||||
- x-pack/platform/test/api_integration/apis/lens/config.ts
|
||||
- x-pack/platform/test/api_integration/apis/ml/config.ts
|
||||
- x-pack/platform/test/api_integration/apis/monitoring/config.ts
|
||||
- x-pack/platform/test/api_integration/apis/monitoring_collection/config.ts
|
||||
|
|
4
.github/CODEOWNERS
vendored
4
.github/CODEOWNERS
vendored
|
@ -1262,7 +1262,8 @@ src/platform/plugins/shared/discover/public/context_awareness/profile_providers/
|
|||
/x-pack/test/functional/page_objects/lens_page.ts @elastic/kibana-visualizations
|
||||
/x-pack/test/functional/es_archives/lens @elastic/kibana-visualizations
|
||||
/x-pack/test/examples/embedded_lens @elastic/kibana-visualizations
|
||||
/x-pack/test/api_integration/fixtures/kbn_archiver/lens/constant_keyword.json @elastic/kibana-visualizations
|
||||
/x-pack/test/api_integration/fixtures/kbn_archiver/lens/ @elastic/kibana-visualizations
|
||||
/x-pack/platform/test/api_integration/apis/lens @elastic/kibana-visualizations
|
||||
/src/platform/test/plugin_functional/test_suites/custom_visualizations @elastic/kibana-visualizations
|
||||
/src/platform/test/plugin_functional/plugins/kbn_tp_custom_visualizations @elastic/kibana-visualizations
|
||||
/x-pack/test/functional/fixtures/kbn_archiver/visualize @elastic/kibana-visualizations
|
||||
|
@ -2742,7 +2743,6 @@ x-pack/solutions/security/plugins/security_solution/public/security_integrations
|
|||
x-pack/solutions/security/plugins/security_solution/server/security_integrations @elastic/security-service-integrations
|
||||
x-pack/solutions/security/plugins/security_solution/server/lib/security_integrations @elastic/security-service-integrations
|
||||
|
||||
|
||||
# Kibana design
|
||||
# scss overrides should be below this line for specificity
|
||||
**/*.scss @elastic/kibana-design
|
||||
|
|
|
@ -56,25 +56,21 @@ export type LensSavedObject = LensCrudTypes['Item'];
|
|||
export type PartialLensSavedObject = LensCrudTypes['PartialItem'];
|
||||
|
||||
// ----------- GET --------------
|
||||
|
||||
export type LensGetIn = LensCrudTypes['GetIn'];
|
||||
|
||||
export type LensGetOut = LensCrudTypes['GetOut'];
|
||||
|
||||
// ----------- CREATE --------------
|
||||
|
||||
export type LensCreateIn = LensCrudTypes['CreateIn'];
|
||||
|
||||
export type LensCreateOut = LensCrudTypes['CreateOut'];
|
||||
// ----------- UPDATE --------------
|
||||
|
||||
// ----------- UPDATE --------------
|
||||
export type LensUpdateIn = LensCrudTypes['UpdateIn'];
|
||||
export type LensUpdateOut = LensCrudTypes['UpdateOut'];
|
||||
// ----------- DELETE --------------
|
||||
|
||||
// ----------- DELETE --------------
|
||||
export type LensDeleteIn = LensCrudTypes['DeleteIn'];
|
||||
export type LensDeleteOut = LensCrudTypes['DeleteOut'];
|
||||
// ----------- SEARCH --------------
|
||||
|
||||
// ----------- SEARCH --------------
|
||||
export type LensSearchIn = LensCrudTypes['SearchIn'];
|
||||
export type LensSearchOut = LensCrudTypes['SearchOut'];
|
||||
|
|
11
x-pack/platform/plugins/shared/lens/server/api/constants.ts
Normal file
11
x-pack/platform/plugins/shared/lens/server/api/constants.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* 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 const PUBLIC_API_VERSION = '1';
|
||||
export const PUBLIC_API_CONTENT_MANAGEMENT_VERSION = 1;
|
||||
export const PUBLIC_API_PATH = '/api/lens';
|
||||
export const PUBLIC_API_ACCESS = 'internal';
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* 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 { RegisterAPIRoutesArgs } from '../types';
|
||||
import { registerLensVisualizationsAPIRoutes } from './visualizations';
|
||||
|
||||
export function registerLensAPIRoutes(args: RegisterAPIRoutesArgs) {
|
||||
registerLensVisualizationsAPIRoutes(args);
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
|
||||
import { boomify, isBoom } from '@hapi/boom';
|
||||
import { CONTENT_ID, type LensSavedObject } from '../../../../common/content_management';
|
||||
import {
|
||||
PUBLIC_API_PATH,
|
||||
PUBLIC_API_VERSION,
|
||||
PUBLIC_API_CONTENT_MANAGEMENT_VERSION,
|
||||
PUBLIC_API_ACCESS,
|
||||
} from '../../constants';
|
||||
import {
|
||||
lensAttributesSchema,
|
||||
lensCreateOptionsSchema,
|
||||
lensSavedObjectSchema,
|
||||
} from '../../../content_management/v1';
|
||||
import { RegisterAPIRouteFn } from '../../types';
|
||||
|
||||
export const registerLensVisualizationsCreateAPIRoute: RegisterAPIRouteFn = (
|
||||
router,
|
||||
{ contentManagement }
|
||||
) => {
|
||||
const createRoute = router.post({
|
||||
path: `${PUBLIC_API_PATH}/visualizations`,
|
||||
access: PUBLIC_API_ACCESS,
|
||||
enableQueryVersion: true,
|
||||
summary: 'Create Lens visualization',
|
||||
description: 'Create a new Lens visualization.',
|
||||
options: {
|
||||
tags: ['oas-tag:Lens'],
|
||||
availability: {
|
||||
stability: 'experimental',
|
||||
},
|
||||
},
|
||||
security: {
|
||||
authz: {
|
||||
enabled: false,
|
||||
reason: 'Relies on Content Client for authorization',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
createRoute.addVersion(
|
||||
{
|
||||
version: PUBLIC_API_VERSION,
|
||||
validate: {
|
||||
request: {
|
||||
body: schema.object({
|
||||
options: lensCreateOptionsSchema,
|
||||
data: lensAttributesSchema,
|
||||
}),
|
||||
},
|
||||
response: {
|
||||
201: {
|
||||
body: () => lensSavedObjectSchema,
|
||||
description: 'Created',
|
||||
},
|
||||
400: {
|
||||
description: 'Malformed request',
|
||||
},
|
||||
401: {
|
||||
description: 'Unauthorized',
|
||||
},
|
||||
403: {
|
||||
description: 'Forbidden',
|
||||
},
|
||||
500: {
|
||||
description: 'Internal Server Error',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (ctx, req, res) => {
|
||||
let result;
|
||||
const { data, options } = req.body;
|
||||
const client = contentManagement.contentClient
|
||||
.getForRequest({ request: req, requestHandlerContext: ctx })
|
||||
.for<LensSavedObject>(CONTENT_ID, PUBLIC_API_CONTENT_MANAGEMENT_VERSION);
|
||||
|
||||
try {
|
||||
({ result } = await client.create(data, options));
|
||||
} catch (error) {
|
||||
if (isBoom(error) && error.output.statusCode === 403) {
|
||||
return res.forbidden();
|
||||
}
|
||||
|
||||
return boomify(error); // forward unknown error
|
||||
}
|
||||
|
||||
return res.created({ body: result.item });
|
||||
}
|
||||
);
|
||||
};
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
|
||||
import { boomify, isBoom } from '@hapi/boom';
|
||||
import { CONTENT_ID, type LensSavedObject } from '../../../../common/content_management';
|
||||
import {
|
||||
PUBLIC_API_PATH,
|
||||
PUBLIC_API_VERSION,
|
||||
PUBLIC_API_CONTENT_MANAGEMENT_VERSION,
|
||||
PUBLIC_API_ACCESS,
|
||||
} from '../../constants';
|
||||
import { RegisterAPIRouteFn } from '../../types';
|
||||
|
||||
export const registerLensVisualizationsDeleteAPIRoute: RegisterAPIRouteFn = (
|
||||
router,
|
||||
{ contentManagement }
|
||||
) => {
|
||||
const deleteRoute = router.delete({
|
||||
path: `${PUBLIC_API_PATH}/visualizations/{id}`,
|
||||
access: PUBLIC_API_ACCESS,
|
||||
enableQueryVersion: true,
|
||||
summary: 'Delete Lens visualization',
|
||||
description: 'Delete a Lens visualization by id.',
|
||||
options: {
|
||||
tags: ['oas-tag:Lens'],
|
||||
availability: {
|
||||
stability: 'experimental',
|
||||
},
|
||||
},
|
||||
security: {
|
||||
authz: {
|
||||
enabled: false,
|
||||
reason: 'Relies on Content Client for authorization',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
deleteRoute.addVersion(
|
||||
{
|
||||
version: PUBLIC_API_VERSION,
|
||||
validate: {
|
||||
request: {
|
||||
params: schema.object({
|
||||
id: schema.string({
|
||||
meta: {
|
||||
description: 'The saved object id of a Lens visualization.',
|
||||
},
|
||||
}),
|
||||
}),
|
||||
},
|
||||
response: {
|
||||
204: {
|
||||
description: 'No Content',
|
||||
},
|
||||
400: {
|
||||
description: 'Malformed request',
|
||||
},
|
||||
401: {
|
||||
description: 'Unauthorized',
|
||||
},
|
||||
403: {
|
||||
description: 'Forbidden',
|
||||
},
|
||||
404: {
|
||||
description: 'Resource not found',
|
||||
},
|
||||
500: {
|
||||
description: 'Internal Server Error',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (ctx, req, res) => {
|
||||
const client = contentManagement.contentClient
|
||||
.getForRequest({ request: req, requestHandlerContext: ctx })
|
||||
.for<LensSavedObject>(CONTENT_ID, PUBLIC_API_CONTENT_MANAGEMENT_VERSION);
|
||||
|
||||
try {
|
||||
await client.delete(req.params.id);
|
||||
} catch (error) {
|
||||
if (isBoom(error)) {
|
||||
if (error.output.statusCode === 404) {
|
||||
return res.notFound({
|
||||
body: {
|
||||
message: `A Lens visualization with saved object id [${req.params.id}] was not found.`,
|
||||
},
|
||||
});
|
||||
}
|
||||
if (error.output.statusCode === 403) {
|
||||
return res.forbidden();
|
||||
}
|
||||
}
|
||||
|
||||
return boomify(error); // forward unknown error
|
||||
}
|
||||
|
||||
return res.noContent();
|
||||
}
|
||||
);
|
||||
};
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
|
||||
import { boomify, isBoom } from '@hapi/boom';
|
||||
import { CONTENT_ID, type LensSavedObject } from '../../../../common/content_management';
|
||||
import {
|
||||
PUBLIC_API_PATH,
|
||||
PUBLIC_API_VERSION,
|
||||
PUBLIC_API_CONTENT_MANAGEMENT_VERSION,
|
||||
PUBLIC_API_ACCESS,
|
||||
} from '../../constants';
|
||||
import { lensSavedObjectSchema } from '../../../content_management/v1';
|
||||
import { RegisterAPIRouteFn } from '../../types';
|
||||
|
||||
export const registerLensVisualizationsGetAPIRoute: RegisterAPIRouteFn = (
|
||||
router,
|
||||
{ contentManagement }
|
||||
) => {
|
||||
const getRoute = router.get({
|
||||
path: `${PUBLIC_API_PATH}/visualizations/{id}`,
|
||||
access: PUBLIC_API_ACCESS,
|
||||
enableQueryVersion: true,
|
||||
summary: 'Get Lens visualization',
|
||||
description: 'Get a Lens visualization from id.',
|
||||
options: {
|
||||
tags: ['oas-tag:Lens'],
|
||||
availability: {
|
||||
stability: 'experimental',
|
||||
},
|
||||
},
|
||||
security: {
|
||||
authz: {
|
||||
enabled: false,
|
||||
reason: 'Relies on Content Client for authorization',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
getRoute.addVersion(
|
||||
{
|
||||
version: PUBLIC_API_VERSION,
|
||||
validate: {
|
||||
request: {
|
||||
params: schema.object({
|
||||
id: schema.string({
|
||||
meta: {
|
||||
description: 'The saved object id of a Lens visualization.',
|
||||
},
|
||||
}),
|
||||
}),
|
||||
},
|
||||
response: {
|
||||
200: {
|
||||
body: () => lensSavedObjectSchema,
|
||||
description: 'Ok',
|
||||
},
|
||||
400: {
|
||||
description: 'Malformed request',
|
||||
},
|
||||
401: {
|
||||
description: 'Unauthorized',
|
||||
},
|
||||
403: {
|
||||
description: 'Forbidden',
|
||||
},
|
||||
404: {
|
||||
description: 'Resource not found',
|
||||
},
|
||||
500: {
|
||||
description: 'Internal Server Error',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (ctx, req, res) => {
|
||||
let result;
|
||||
const client = contentManagement.contentClient
|
||||
.getForRequest({ request: req, requestHandlerContext: ctx })
|
||||
.for<LensSavedObject>(CONTENT_ID, PUBLIC_API_CONTENT_MANAGEMENT_VERSION);
|
||||
|
||||
try {
|
||||
({ result } = await client.get(req.params.id));
|
||||
} catch (error) {
|
||||
if (isBoom(error)) {
|
||||
if (error.output.statusCode === 404) {
|
||||
return res.notFound({
|
||||
body: {
|
||||
message: `A Lens visualization with saved object id [${req.params.id}] was not found.`,
|
||||
},
|
||||
});
|
||||
}
|
||||
if (error.output.statusCode === 403) {
|
||||
return res.forbidden();
|
||||
}
|
||||
}
|
||||
|
||||
return boomify(error); // forward unknown error
|
||||
}
|
||||
|
||||
return res.ok({ body: result.item });
|
||||
}
|
||||
);
|
||||
};
|
|
@ -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 { RegisterAPIRoutesArgs } from '../../types';
|
||||
import { registerLensVisualizationsCreateAPIRoute } from './create';
|
||||
import { registerLensVisualizationsGetAPIRoute } from './get';
|
||||
import { registerLensVisualizationsUpdateAPIRoute } from './update';
|
||||
import { registerLensVisualizationsDeleteAPIRoute } from './delete';
|
||||
import { registerLensVisualizationsSearchAPIRoute } from './search';
|
||||
|
||||
export function registerLensVisualizationsAPIRoutes({ http, ...rest }: RegisterAPIRoutesArgs) {
|
||||
const { versioned: versionedRouter } = http.createRouter();
|
||||
|
||||
registerLensVisualizationsCreateAPIRoute(versionedRouter, rest);
|
||||
registerLensVisualizationsGetAPIRoute(versionedRouter, rest);
|
||||
registerLensVisualizationsUpdateAPIRoute(versionedRouter, rest);
|
||||
registerLensVisualizationsDeleteAPIRoute(versionedRouter, rest);
|
||||
registerLensVisualizationsSearchAPIRoute(versionedRouter, rest);
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
|
||||
import { isBoom, boomify } from '@hapi/boom';
|
||||
import { CONTENT_ID, type LensSavedObject } from '../../../../common/content_management';
|
||||
import {
|
||||
PUBLIC_API_PATH,
|
||||
PUBLIC_API_VERSION,
|
||||
PUBLIC_API_CONTENT_MANAGEMENT_VERSION,
|
||||
PUBLIC_API_ACCESS,
|
||||
} from '../../constants';
|
||||
import { lensSavedObjectSchema } from '../../../content_management/v1';
|
||||
import { RegisterAPIRouteFn } from '../../types';
|
||||
|
||||
export const registerLensVisualizationsSearchAPIRoute: RegisterAPIRouteFn = (
|
||||
router,
|
||||
{ contentManagement }
|
||||
) => {
|
||||
const searchRoute = router.get({
|
||||
path: `${PUBLIC_API_PATH}/visualizations`,
|
||||
access: PUBLIC_API_ACCESS,
|
||||
enableQueryVersion: true,
|
||||
summary: 'Search Lens visualizations',
|
||||
description: 'Get list of Lens visualizations.',
|
||||
options: {
|
||||
tags: ['oas-tag:Lens'],
|
||||
availability: {
|
||||
stability: 'experimental',
|
||||
},
|
||||
},
|
||||
security: {
|
||||
authz: {
|
||||
enabled: false,
|
||||
reason: 'Relies on Content Client for authorization',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
searchRoute.addVersion(
|
||||
{
|
||||
version: PUBLIC_API_VERSION,
|
||||
validate: {
|
||||
request: {
|
||||
query: schema.object({
|
||||
query: schema.maybe(
|
||||
schema.string({
|
||||
meta: {
|
||||
description: 'The text to search for Lens visualizations',
|
||||
},
|
||||
})
|
||||
),
|
||||
page: schema.number({
|
||||
meta: {
|
||||
description: 'Specifies the current page number of the paginated result.',
|
||||
},
|
||||
min: 1,
|
||||
defaultValue: 1,
|
||||
}),
|
||||
perPage: schema.number({
|
||||
meta: {
|
||||
description: 'Maximum number of Lens visualizations included in a single response',
|
||||
},
|
||||
defaultValue: 20,
|
||||
min: 1,
|
||||
max: 1000,
|
||||
}),
|
||||
}),
|
||||
},
|
||||
response: {
|
||||
200: {
|
||||
body: () => schema.arrayOf(lensSavedObjectSchema),
|
||||
description: 'Ok',
|
||||
},
|
||||
400: {
|
||||
description: 'Malformed request',
|
||||
},
|
||||
401: {
|
||||
description: 'Unauthorized',
|
||||
},
|
||||
403: {
|
||||
description: 'Forbidden',
|
||||
},
|
||||
500: {
|
||||
description: 'Internal Server Error',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (ctx, req, res) => {
|
||||
let result;
|
||||
const { query, page, perPage: limit } = req.query;
|
||||
const client = contentManagement.contentClient
|
||||
.getForRequest({ request: req, requestHandlerContext: ctx })
|
||||
.for<LensSavedObject>(CONTENT_ID, PUBLIC_API_CONTENT_MANAGEMENT_VERSION);
|
||||
|
||||
try {
|
||||
({ result } = await client.search(
|
||||
{
|
||||
text: query,
|
||||
cursor: page.toString(),
|
||||
limit,
|
||||
},
|
||||
{
|
||||
searchFields: ['title', 'description'],
|
||||
}
|
||||
));
|
||||
} catch (error) {
|
||||
if (isBoom(error) && error.output.statusCode === 403) {
|
||||
return res.forbidden();
|
||||
}
|
||||
|
||||
return boomify(error); // forward unknown error
|
||||
}
|
||||
|
||||
return res.ok({ body: result.hits });
|
||||
}
|
||||
);
|
||||
};
|
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { boomify, isBoom } from '@hapi/boom';
|
||||
|
||||
import { CONTENT_ID, type LensSavedObject } from '../../../../common/content_management';
|
||||
import {
|
||||
PUBLIC_API_PATH,
|
||||
PUBLIC_API_VERSION,
|
||||
PUBLIC_API_CONTENT_MANAGEMENT_VERSION,
|
||||
PUBLIC_API_ACCESS,
|
||||
} from '../../constants';
|
||||
import {
|
||||
lensAttributesSchema,
|
||||
lensCreateOptionsSchema,
|
||||
lensSavedObjectSchema,
|
||||
} from '../../../content_management/v1';
|
||||
import { RegisterAPIRouteFn } from '../../types';
|
||||
|
||||
export const registerLensVisualizationsUpdateAPIRoute: RegisterAPIRouteFn = (
|
||||
router,
|
||||
{ contentManagement }
|
||||
) => {
|
||||
const updateRoute = router.put({
|
||||
path: `${PUBLIC_API_PATH}/visualizations/{id}`,
|
||||
access: PUBLIC_API_ACCESS,
|
||||
enableQueryVersion: true,
|
||||
summary: 'Update Lens visualization',
|
||||
description: 'Update an existing Lens visualization.',
|
||||
options: {
|
||||
tags: ['oas-tag:Lens'],
|
||||
availability: {
|
||||
stability: 'experimental',
|
||||
},
|
||||
},
|
||||
security: {
|
||||
authz: {
|
||||
enabled: false,
|
||||
reason: 'Relies on Content Client for authorization',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
updateRoute.addVersion(
|
||||
{
|
||||
version: PUBLIC_API_VERSION,
|
||||
validate: {
|
||||
request: {
|
||||
params: schema.object({
|
||||
id: schema.string({
|
||||
meta: {
|
||||
description: 'The saved object id of a Lens visualization.',
|
||||
},
|
||||
}),
|
||||
}),
|
||||
body: schema.object({
|
||||
options: lensCreateOptionsSchema,
|
||||
data: lensAttributesSchema,
|
||||
}),
|
||||
},
|
||||
response: {
|
||||
200: {
|
||||
body: () => lensSavedObjectSchema,
|
||||
description: 'Ok',
|
||||
},
|
||||
400: {
|
||||
description: 'Malformed request',
|
||||
},
|
||||
401: {
|
||||
description: 'Unauthorized',
|
||||
},
|
||||
403: {
|
||||
description: 'Forbidden',
|
||||
},
|
||||
404: {
|
||||
description: 'Resource not found',
|
||||
},
|
||||
500: {
|
||||
description: 'Internal Server Error',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (ctx, req, res) => {
|
||||
let result;
|
||||
const { data, options } = req.body;
|
||||
const client = contentManagement.contentClient
|
||||
.getForRequest({ request: req, requestHandlerContext: ctx })
|
||||
.for<LensSavedObject>(CONTENT_ID, PUBLIC_API_CONTENT_MANAGEMENT_VERSION);
|
||||
|
||||
try {
|
||||
({ result } = await client.update(req.params.id, data, options));
|
||||
} catch (error) {
|
||||
if (isBoom(error)) {
|
||||
if (error.output.statusCode === 404) {
|
||||
return res.notFound({
|
||||
body: {
|
||||
message: `A Lens visualization with saved object id [${req.params.id}] was not found.`,
|
||||
},
|
||||
});
|
||||
}
|
||||
if (error.output.statusCode === 403) {
|
||||
return res.forbidden();
|
||||
}
|
||||
}
|
||||
|
||||
return boomify(error); // forward unknown error
|
||||
}
|
||||
|
||||
return res.ok({ body: result.item });
|
||||
}
|
||||
);
|
||||
};
|
21
x-pack/platform/plugins/shared/lens/server/api/types.ts
Normal file
21
x-pack/platform/plugins/shared/lens/server/api/types.ts
Normal file
|
@ -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 { HttpServiceSetup, Logger, RequestHandlerContext } from '@kbn/core/server';
|
||||
import { ContentManagementServerSetup } from '@kbn/content-management-plugin/server';
|
||||
import { VersionedRouter } from '@kbn/core-http-server';
|
||||
|
||||
export interface RegisterAPIRoutesArgs {
|
||||
http: HttpServiceSetup;
|
||||
contentManagement: ContentManagementServerSetup;
|
||||
logger: Logger;
|
||||
}
|
||||
|
||||
export type RegisterAPIRouteFn = (
|
||||
router: VersionedRouter<RequestHandlerContext>,
|
||||
args: Omit<RegisterAPIRoutesArgs, 'http'>
|
||||
) => void;
|
|
@ -136,6 +136,9 @@ export class LensStorage extends SOContentStorage<LensCrudTypes> {
|
|||
// Save data in DB
|
||||
const soClient = await savedObjectClientFromRequest(ctx);
|
||||
|
||||
// since we use create below this is to throw if SO id not found
|
||||
await soClient.get(CONTENT_ID, id);
|
||||
|
||||
const savedObject = await soClient.create<LensSavedObjectAttributes>(CONTENT_ID, dataToLatest, {
|
||||
id,
|
||||
overwrite: true,
|
||||
|
|
|
@ -25,7 +25,7 @@ const referenceSchema = schema.object(
|
|||
|
||||
const referencesSchema = schema.arrayOf(referenceSchema);
|
||||
|
||||
const lensAttributesSchema = schema.object(
|
||||
export const lensAttributesSchema = schema.object(
|
||||
{
|
||||
title: schema.string(),
|
||||
description: schema.maybe(schema.nullable(schema.string())),
|
||||
|
@ -38,7 +38,7 @@ const lensAttributesSchema = schema.object(
|
|||
{ unknowns: 'forbid' }
|
||||
);
|
||||
|
||||
const lensSavedObjectSchema = schema.object(
|
||||
export const lensSavedObjectSchema = schema.object(
|
||||
{
|
||||
id: schema.string(),
|
||||
type: schema.string(),
|
||||
|
@ -54,7 +54,7 @@ const lensSavedObjectSchema = schema.object(
|
|||
{ unknowns: 'allow' }
|
||||
);
|
||||
|
||||
const getResultSchema = schema.object(
|
||||
const lensGetResultSchema = schema.object(
|
||||
{
|
||||
item: lensSavedObjectSchema,
|
||||
meta: schema.object(
|
||||
|
@ -78,63 +78,72 @@ const getResultSchema = schema.object(
|
|||
{ unknowns: 'forbid' }
|
||||
);
|
||||
|
||||
const createOptionsSchema = schema.object({
|
||||
export const lensCreateOptionsSchema = schema.object({
|
||||
overwrite: schema.maybe(schema.boolean()),
|
||||
references: schema.maybe(referencesSchema),
|
||||
});
|
||||
|
||||
export const lensSearchOptionsSchema = schema.maybe(
|
||||
schema.object(
|
||||
{
|
||||
searchFields: schema.maybe(schema.arrayOf(schema.string())),
|
||||
types: schema.maybe(schema.arrayOf(schema.string())),
|
||||
},
|
||||
{ unknowns: 'forbid' }
|
||||
)
|
||||
);
|
||||
|
||||
const lensCreateResultSchema = schema.object(
|
||||
{
|
||||
item: lensSavedObjectSchema,
|
||||
},
|
||||
{ unknowns: 'forbid' }
|
||||
);
|
||||
|
||||
// Content management service definition.
|
||||
// We need it for BWC support between different versions of the content
|
||||
export const serviceDefinition: ServicesDefinition = {
|
||||
get: {
|
||||
out: {
|
||||
result: {
|
||||
schema: getResultSchema,
|
||||
schema: lensGetResultSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
create: {
|
||||
in: {
|
||||
options: {
|
||||
schema: createOptionsSchema,
|
||||
},
|
||||
data: {
|
||||
schema: lensAttributesSchema,
|
||||
},
|
||||
options: {
|
||||
schema: lensCreateOptionsSchema,
|
||||
},
|
||||
},
|
||||
out: {
|
||||
result: {
|
||||
schema: schema.object(
|
||||
{
|
||||
item: lensSavedObjectSchema,
|
||||
},
|
||||
{ unknowns: 'forbid' }
|
||||
),
|
||||
schema: lensCreateResultSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
update: {
|
||||
in: {
|
||||
options: {
|
||||
schema: createOptionsSchema, // same schema as "create"
|
||||
},
|
||||
data: {
|
||||
schema: lensAttributesSchema,
|
||||
},
|
||||
options: {
|
||||
schema: lensCreateOptionsSchema,
|
||||
},
|
||||
},
|
||||
out: {
|
||||
result: {
|
||||
schema: lensCreateResultSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
search: {
|
||||
in: {
|
||||
options: {
|
||||
schema: schema.maybe(
|
||||
schema.object(
|
||||
{
|
||||
searchFields: schema.maybe(schema.arrayOf(schema.string())),
|
||||
types: schema.maybe(schema.arrayOf(schema.string())),
|
||||
},
|
||||
{ unknowns: 'forbid' }
|
||||
)
|
||||
),
|
||||
schema: lensSearchOptionsSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -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 './cm_services';
|
|
@ -13,4 +13,6 @@ export const plugin = async (initContext: PluginInitializerContext) => {
|
|||
return new LensServerPlugin(initContext);
|
||||
};
|
||||
|
||||
export { PUBLIC_API_PATH, PUBLIC_API_VERSION } from './api/constants';
|
||||
|
||||
export type { LensDocShape715 } from './migrations/types';
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { Plugin, CoreSetup, CoreStart, PluginInitializerContext } from '@kbn/core/server';
|
||||
import { Plugin, CoreSetup, CoreStart, PluginInitializerContext, Logger } from '@kbn/core/server';
|
||||
import { PluginStart as DataViewsServerPluginStart } from '@kbn/data-views-plugin/server';
|
||||
import {
|
||||
PluginStart as DataPluginStart,
|
||||
|
@ -30,6 +30,7 @@ import type { CustomVisualizationMigrations } from './migrations/types';
|
|||
import { LensAppLocatorDefinition } from '../common/locator/locator';
|
||||
import { CONTENT_ID, LATEST_VERSION } from '../common/content_management';
|
||||
import { LensStorage } from './content_management';
|
||||
import { registerLensAPIRoutes } from './api/routes';
|
||||
|
||||
export interface PluginSetupContract {
|
||||
taskManager?: TaskManagerSetupContract;
|
||||
|
@ -65,8 +66,11 @@ export class LensServerPlugin
|
|||
implements Plugin<LensServerPluginSetup, {}, PluginSetupContract, PluginStartContract>
|
||||
{
|
||||
private customVisualizationMigrations: CustomVisualizationMigrations = {};
|
||||
private readonly logger: Logger;
|
||||
|
||||
constructor(private initializerContext: PluginInitializerContext) {}
|
||||
constructor(private initializerContext: PluginInitializerContext) {
|
||||
this.logger = initializerContext.logger.get();
|
||||
}
|
||||
|
||||
setup(core: CoreSetup<PluginStartContract>, plugins: PluginSetupContract) {
|
||||
const getFilterMigrations = plugins.data.query.filterManager.getAllMigrations.bind(
|
||||
|
@ -96,6 +100,13 @@ export class LensServerPlugin
|
|||
this.customVisualizationMigrations
|
||||
);
|
||||
plugins.embeddable.registerEmbeddableFactory(lensEmbeddableFactory());
|
||||
|
||||
registerLensAPIRoutes({
|
||||
http: core.http,
|
||||
contentManagement: plugins.contentManagement,
|
||||
logger: this.logger,
|
||||
});
|
||||
|
||||
return {
|
||||
lensEmbeddableFactory,
|
||||
registerVisualizationMigration: (
|
||||
|
|
|
@ -124,6 +124,7 @@
|
|||
"@kbn/fields-metadata-plugin",
|
||||
"@kbn/alerts-ui-shared",
|
||||
"@kbn/deeplinks-analytics",
|
||||
"@kbn/core-http-server",
|
||||
],
|
||||
"exclude": ["target/**/*"]
|
||||
}
|
||||
|
|
17
x-pack/platform/test/api_integration/apis/lens/config.ts
Normal file
17
x-pack/platform/test/api_integration/apis/lens/config.ts
Normal 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 { FtrConfigProviderContext } from '@kbn/test';
|
||||
|
||||
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
||||
const baseIntegrationTestsConfig = await readConfigFile(require.resolve('../../config.ts'));
|
||||
|
||||
return {
|
||||
...baseIntegrationTestsConfig.getAll(),
|
||||
testFiles: [require.resolve('.')],
|
||||
};
|
||||
}
|
74
x-pack/platform/test/api_integration/apis/lens/examples.ts
Normal file
74
x-pack/platform/test/api_integration/apis/lens/examples.ts
Normal file
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* 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 const getExampleLensBody = (title = `Lens vis - ${Date.now()} - ${Math.random()}`) => ({
|
||||
data: {
|
||||
title,
|
||||
description: '',
|
||||
visualizationType: 'lnsMetric',
|
||||
state: {
|
||||
visualization: {
|
||||
layerId: '32e889c6-89f9-4873-b1f7-d5bea381c582',
|
||||
layerType: 'data',
|
||||
metricAccessor: '1c6729bc-ec92-4000-8dcc-0fdd7b56d5b8',
|
||||
secondaryTrend: {
|
||||
type: 'none',
|
||||
},
|
||||
},
|
||||
query: {
|
||||
query: '',
|
||||
language: 'kuery',
|
||||
},
|
||||
filters: [],
|
||||
datasourceStates: {
|
||||
formBased: {
|
||||
layers: {
|
||||
'32e889c6-89f9-4873-b1f7-d5bea381c582': {
|
||||
columns: {
|
||||
'1c6729bc-ec92-4000-8dcc-0fdd7b56d5b8': {
|
||||
label: 'Count of records',
|
||||
dataType: 'number',
|
||||
operationType: 'count',
|
||||
isBucketed: false,
|
||||
scale: 'ratio',
|
||||
sourceField: '___records___',
|
||||
params: {
|
||||
emptyAsNull: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
columnOrder: ['1c6729bc-ec92-4000-8dcc-0fdd7b56d5b8'],
|
||||
incompleteColumns: {
|
||||
'd0b92889-f74c-4194-b738-76eb5d268524': {
|
||||
operationType: 'date_histogram',
|
||||
},
|
||||
},
|
||||
sampling: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
indexpattern: {
|
||||
layers: {},
|
||||
},
|
||||
textBased: {
|
||||
layers: {},
|
||||
},
|
||||
},
|
||||
internalReferences: [],
|
||||
adHocDataViews: {},
|
||||
},
|
||||
},
|
||||
options: {
|
||||
references: [
|
||||
{
|
||||
type: 'index-pattern',
|
||||
id: '91200a00-9efd-11e7-acb3-3dab96693fab',
|
||||
name: 'indexpattern-datasource-layer-32e889c6-89f9-4873-b1f7-d5bea381c582',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
14
x-pack/platform/test/api_integration/apis/lens/index.ts
Normal file
14
x-pack/platform/test/api_integration/apis/lens/index.ts
Normal file
|
@ -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 { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
export default function ({ loadTestFile }: FtrProviderContext) {
|
||||
describe('lens', () => {
|
||||
loadTestFile(require.resolve('./visualizations'));
|
||||
});
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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 { FtrProviderContext } from '../../../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService, loadTestFile }: FtrProviderContext) {
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
describe('visualizations - create', () => {
|
||||
before(async () => {
|
||||
await kibanaServer.importExport.load(
|
||||
'src/platform/test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json'
|
||||
);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await kibanaServer.savedObjects.cleanStandardList();
|
||||
await kibanaServer.importExport.unload(
|
||||
'src/platform/test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json'
|
||||
);
|
||||
});
|
||||
loadTestFile(require.resolve('./main'));
|
||||
loadTestFile(require.resolve('./validation'));
|
||||
});
|
||||
}
|
|
@ -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 expect from '@kbn/expect';
|
||||
import { PUBLIC_API_PATH, PUBLIC_API_VERSION } from '@kbn/lens-plugin/server';
|
||||
import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common';
|
||||
|
||||
import type { FtrProviderContext } from '../../../../ftr_provider_context';
|
||||
import { getExampleLensBody } from '../../examples';
|
||||
|
||||
export default function ({ getService }: FtrProviderContext) {
|
||||
const supertest = getService('supertest');
|
||||
|
||||
describe('main', () => {
|
||||
it('should create a lens visualization', async () => {
|
||||
const response = await supertest
|
||||
.post(`${PUBLIC_API_PATH}/visualizations`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.set(ELASTIC_HTTP_VERSION_HEADER, PUBLIC_API_VERSION)
|
||||
.send(getExampleLensBody());
|
||||
|
||||
expect(response.status).to.be(201);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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 expect from '@kbn/expect';
|
||||
import { PUBLIC_API_PATH, PUBLIC_API_VERSION } from '@kbn/lens-plugin/server';
|
||||
import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common';
|
||||
|
||||
import type { FtrProviderContext } from '../../../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService }: FtrProviderContext) {
|
||||
const supertest = getService('supertest');
|
||||
describe('validation', () => {
|
||||
it('should return error if body is empty', async () => {
|
||||
const response = await supertest
|
||||
.post(`${PUBLIC_API_PATH}/visualizations`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.set(ELASTIC_HTTP_VERSION_HEADER, PUBLIC_API_VERSION)
|
||||
.send({});
|
||||
|
||||
expect(response.status).to.be(400);
|
||||
expect(response.body.message).to.be(
|
||||
'[request body.data.title]: expected value of type [string] but got [undefined]'
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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 { FtrProviderContext } from '../../../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService, loadTestFile }: FtrProviderContext) {
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
describe('visualizations - create', () => {
|
||||
before(async () => {
|
||||
await kibanaServer.importExport.load(
|
||||
'src/platform/test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json'
|
||||
);
|
||||
await kibanaServer.importExport.load(
|
||||
'x-pack/test/api_integration/fixtures/kbn_archiver/lens/example_docs.json'
|
||||
);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await kibanaServer.savedObjects.cleanStandardList();
|
||||
await kibanaServer.importExport.unload(
|
||||
'src/platform/test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json'
|
||||
);
|
||||
await kibanaServer.importExport.unload(
|
||||
'x-pack/test/api_integration/fixtures/kbn_archiver/lens/example_docs.json'
|
||||
);
|
||||
});
|
||||
loadTestFile(require.resolve('./main'));
|
||||
});
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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 expect from '@kbn/expect';
|
||||
import { PUBLIC_API_PATH, PUBLIC_API_VERSION } from '@kbn/lens-plugin/server';
|
||||
import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common';
|
||||
|
||||
import type { FtrProviderContext } from '../../../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService }: FtrProviderContext) {
|
||||
const supertest = getService('supertest');
|
||||
|
||||
describe('main', () => {
|
||||
it('should delete a lens visualization', async () => {
|
||||
const id = '71c9c185-3e6d-49d0-b7e5-f966eaf51625'; // known id
|
||||
const response = await supertest
|
||||
.delete(`${PUBLIC_API_PATH}/visualizations/${id}`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.set(ELASTIC_HTTP_VERSION_HEADER, PUBLIC_API_VERSION)
|
||||
.send();
|
||||
|
||||
expect(response.status).to.be(204);
|
||||
});
|
||||
|
||||
it('should error when deleting an unknown lens visualization', async () => {
|
||||
const id = '123'; // unknown id
|
||||
const response = await supertest
|
||||
.delete(`${PUBLIC_API_PATH}/visualizations/${id}`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.set(ELASTIC_HTTP_VERSION_HEADER, PUBLIC_API_VERSION)
|
||||
.send();
|
||||
|
||||
expect(response.status).to.be(404);
|
||||
expect(response.body.message).to.be(
|
||||
'A Lens visualization with saved object id [123] was not found.'
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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 { FtrProviderContext } from '../../../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService, loadTestFile }: FtrProviderContext) {
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
describe('visualizations - create', () => {
|
||||
before(async () => {
|
||||
await kibanaServer.importExport.load(
|
||||
'src/platform/test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json'
|
||||
);
|
||||
await kibanaServer.importExport.load(
|
||||
'x-pack/test/api_integration/fixtures/kbn_archiver/lens/example_docs.json'
|
||||
);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await kibanaServer.savedObjects.cleanStandardList();
|
||||
await kibanaServer.importExport.unload(
|
||||
'src/platform/test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json'
|
||||
);
|
||||
await kibanaServer.importExport.unload(
|
||||
'x-pack/test/api_integration/fixtures/kbn_archiver/lens/example_docs.json'
|
||||
);
|
||||
});
|
||||
loadTestFile(require.resolve('./main'));
|
||||
});
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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 expect from '@kbn/expect';
|
||||
import { PUBLIC_API_PATH, PUBLIC_API_VERSION } from '@kbn/lens-plugin/server';
|
||||
import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common';
|
||||
|
||||
import type { FtrProviderContext } from '../../../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService }: FtrProviderContext) {
|
||||
const supertest = getService('supertest');
|
||||
|
||||
describe('main', () => {
|
||||
it('should get a lens visualization', async () => {
|
||||
const id = '71c9c185-3e6d-49d0-b7e5-f966eaf51625'; // known id
|
||||
const response = await supertest
|
||||
.get(`${PUBLIC_API_PATH}/visualizations/${id}`)
|
||||
.set(ELASTIC_HTTP_VERSION_HEADER, PUBLIC_API_VERSION)
|
||||
.send();
|
||||
|
||||
expect(response.status).to.be(200);
|
||||
expect(response.body.attributes.title).to.be('Lens example - 1');
|
||||
});
|
||||
|
||||
it('should error when fetching an unknown lens visualization', async () => {
|
||||
const id = '123'; // unknown id
|
||||
const response = await supertest
|
||||
.get(`${PUBLIC_API_PATH}/visualizations/${id}`)
|
||||
.set(ELASTIC_HTTP_VERSION_HEADER, PUBLIC_API_VERSION)
|
||||
.send();
|
||||
|
||||
expect(response.status).to.be(404);
|
||||
expect(response.body.message).to.be(
|
||||
'A Lens visualization with saved object id [123] was not found.'
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -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 { FtrProviderContext } from '../../../../functional/ftr_provider_context';
|
||||
|
||||
export default function ({ loadTestFile }: FtrProviderContext) {
|
||||
describe('visualizations', () => {
|
||||
loadTestFile(require.resolve('./create'));
|
||||
loadTestFile(require.resolve('./get'));
|
||||
loadTestFile(require.resolve('./update'));
|
||||
loadTestFile(require.resolve('./delete'));
|
||||
loadTestFile(require.resolve('./search'));
|
||||
});
|
||||
}
|
|
@ -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 type { FtrProviderContext } from '../../../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService, loadTestFile }: FtrProviderContext) {
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
describe('visualizations - update', () => {
|
||||
before(async () => {
|
||||
await kibanaServer.importExport.load(
|
||||
'src/platform/test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json'
|
||||
);
|
||||
await kibanaServer.importExport.load(
|
||||
'x-pack/test/api_integration/fixtures/kbn_archiver/lens/example_docs.json'
|
||||
);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await kibanaServer.savedObjects.cleanStandardList();
|
||||
await kibanaServer.importExport.unload(
|
||||
'src/platform/test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json'
|
||||
);
|
||||
await kibanaServer.importExport.unload(
|
||||
'x-pack/test/api_integration/fixtures/kbn_archiver/lens/example_docs.json'
|
||||
);
|
||||
});
|
||||
loadTestFile(require.resolve('./main'));
|
||||
loadTestFile(require.resolve('./validation'));
|
||||
});
|
||||
}
|
|
@ -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 expect from '@kbn/expect';
|
||||
import { PUBLIC_API_PATH, PUBLIC_API_VERSION } from '@kbn/lens-plugin/server';
|
||||
import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common';
|
||||
|
||||
import type { FtrProviderContext } from '../../../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService }: FtrProviderContext) {
|
||||
const supertest = getService('supertest');
|
||||
|
||||
describe('main', () => {
|
||||
it('should get list of lens visualizations', async () => {
|
||||
const response = await supertest
|
||||
.get(`${PUBLIC_API_PATH}/visualizations`)
|
||||
.set(ELASTIC_HTTP_VERSION_HEADER, PUBLIC_API_VERSION)
|
||||
.send();
|
||||
|
||||
expect(response.status).to.be(200);
|
||||
expect(response.body.length).to.be(4);
|
||||
});
|
||||
|
||||
it('should filter lens visualizations by title and description', async () => {
|
||||
const response = await supertest
|
||||
.get(`${PUBLIC_API_PATH}/visualizations`)
|
||||
.query({ query: '1' })
|
||||
.set(ELASTIC_HTTP_VERSION_HEADER, PUBLIC_API_VERSION)
|
||||
.send();
|
||||
|
||||
expect(response.status).to.be(200);
|
||||
expect(response.body.length).to.be(2);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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 expect from '@kbn/expect';
|
||||
import { PUBLIC_API_PATH, PUBLIC_API_VERSION } from '@kbn/lens-plugin/server';
|
||||
import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common';
|
||||
|
||||
import type { FtrProviderContext } from '../../../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService }: FtrProviderContext) {
|
||||
const supertest = getService('supertest');
|
||||
describe('validation', () => {
|
||||
it('should return error if using unknown params', async () => {
|
||||
const response = await supertest
|
||||
.get(`${PUBLIC_API_PATH}/visualizations`)
|
||||
.query({ xyz: 'unknown param' })
|
||||
.set(ELASTIC_HTTP_VERSION_HEADER, PUBLIC_API_VERSION)
|
||||
.send({});
|
||||
|
||||
expect(response.status).to.be(400);
|
||||
expect(response.body.message).to.be(
|
||||
'[request query.xyz]: definition for this key is missing'
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -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 type { FtrProviderContext } from '../../../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService, loadTestFile }: FtrProviderContext) {
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
describe('visualizations - update', () => {
|
||||
before(async () => {
|
||||
await kibanaServer.importExport.load(
|
||||
'src/platform/test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json'
|
||||
);
|
||||
await kibanaServer.importExport.load(
|
||||
'x-pack/test/api_integration/fixtures/kbn_archiver/lens/example_docs.json'
|
||||
);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await kibanaServer.savedObjects.cleanStandardList();
|
||||
await kibanaServer.importExport.unload(
|
||||
'src/platform/test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json'
|
||||
);
|
||||
await kibanaServer.importExport.unload(
|
||||
'x-pack/test/api_integration/fixtures/kbn_archiver/lens/example_docs.json'
|
||||
);
|
||||
});
|
||||
loadTestFile(require.resolve('./main'));
|
||||
loadTestFile(require.resolve('./validation'));
|
||||
});
|
||||
}
|
|
@ -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 expect from '@kbn/expect';
|
||||
import { PUBLIC_API_PATH, PUBLIC_API_VERSION } from '@kbn/lens-plugin/server';
|
||||
import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common';
|
||||
|
||||
import type { FtrProviderContext } from '../../../../ftr_provider_context';
|
||||
import { getExampleLensBody } from '../../examples';
|
||||
|
||||
export default function ({ getService }: FtrProviderContext) {
|
||||
const supertest = getService('supertest');
|
||||
|
||||
describe('main', () => {
|
||||
it('should update a lens visualization', async () => {
|
||||
const id = '71c9c185-3e6d-49d0-b7e5-f966eaf51625'; // known id
|
||||
const response = await supertest
|
||||
.put(`${PUBLIC_API_PATH}/visualizations/${id}`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.set(ELASTIC_HTTP_VERSION_HEADER, PUBLIC_API_VERSION)
|
||||
.send(getExampleLensBody('Custom title'));
|
||||
|
||||
expect(response.status).to.be(200);
|
||||
expect(response.body.attributes.title).to.be('Custom title');
|
||||
});
|
||||
|
||||
it('should error when updating an unknown lens visualization', async () => {
|
||||
const id = '123'; // unknown id
|
||||
const response = await supertest
|
||||
.put(`${PUBLIC_API_PATH}/visualizations/${id}`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.set(ELASTIC_HTTP_VERSION_HEADER, PUBLIC_API_VERSION)
|
||||
.send(getExampleLensBody('Custom title'));
|
||||
|
||||
expect(response.status).to.be(404);
|
||||
expect(response.body.message).to.be(
|
||||
'A Lens visualization with saved object id [123] was not found.'
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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 expect from '@kbn/expect';
|
||||
import { PUBLIC_API_PATH, PUBLIC_API_VERSION } from '@kbn/lens-plugin/server';
|
||||
import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common';
|
||||
|
||||
import type { FtrProviderContext } from '../../../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService }: FtrProviderContext) {
|
||||
const supertest = getService('supertest');
|
||||
describe('validation', () => {
|
||||
it('should return error if body is empty', async () => {
|
||||
const id = '71c9c185-3e6d-49d0-b7e5-f966eaf51625'; // known id
|
||||
const response = await supertest
|
||||
.put(`${PUBLIC_API_PATH}/visualizations/${id}`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.set(ELASTIC_HTTP_VERSION_HEADER, PUBLIC_API_VERSION)
|
||||
.send({});
|
||||
|
||||
expect(response.status).to.be(400);
|
||||
expect(response.body.message).to.be(
|
||||
'[request body.data.title]: expected value of type [string] but got [undefined]'
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -105,5 +105,6 @@
|
|||
"@kbn/repo-info",
|
||||
"@kbn/dev-cli-errors",
|
||||
"@kbn/journeys",
|
||||
"@kbn/lens-plugin",
|
||||
]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,315 @@
|
|||
{
|
||||
"id": "71c9c185-3e6d-49d0-b7e5-f966eaf51625",
|
||||
"type": "lens",
|
||||
"namespaces": [
|
||||
"default"
|
||||
],
|
||||
"updated_at": "2025-06-17T15:47:13.313Z",
|
||||
"created_at": "2025-06-17T15:47:13.313Z",
|
||||
"version": "WzU5LDFd",
|
||||
"attributes": {
|
||||
"title": "Lens example - 1",
|
||||
"description": "",
|
||||
"visualizationType": "lnsMetric",
|
||||
"state": {
|
||||
"visualization": {
|
||||
"layerId": "7aa8fd7f-f664-48fe-8232-3a26054f9cdc",
|
||||
"layerType": "data",
|
||||
"metricAccessor": "89a69d8d-a6bc-47a8-80c6-94272762e785",
|
||||
"secondaryTrend": {
|
||||
"type": "none"
|
||||
}
|
||||
},
|
||||
"query": {
|
||||
"query": "",
|
||||
"language": "kuery"
|
||||
},
|
||||
"filters": [],
|
||||
"datasourceStates": {
|
||||
"formBased": {
|
||||
"layers": {
|
||||
"7aa8fd7f-f664-48fe-8232-3a26054f9cdc": {
|
||||
"columns": {
|
||||
"89a69d8d-a6bc-47a8-80c6-94272762e785": {
|
||||
"label": "Count of records",
|
||||
"dataType": "number",
|
||||
"operationType": "count",
|
||||
"isBucketed": false,
|
||||
"scale": "ratio",
|
||||
"sourceField": "___records___",
|
||||
"params": {
|
||||
"emptyAsNull": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"columnOrder": [
|
||||
"89a69d8d-a6bc-47a8-80c6-94272762e785"
|
||||
],
|
||||
"incompleteColumns": {
|
||||
"806819b9-b606-4383-9337-e6a40b8602ad": {
|
||||
"operationType": "date_histogram"
|
||||
}
|
||||
},
|
||||
"sampling": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"indexpattern": {
|
||||
"layers": {}
|
||||
},
|
||||
"textBased": {
|
||||
"layers": {}
|
||||
}
|
||||
},
|
||||
"internalReferences": [],
|
||||
"adHocDataViews": {}
|
||||
}
|
||||
},
|
||||
"references": [
|
||||
{
|
||||
"type": "index-pattern",
|
||||
"id": "91200a00-9efd-11e7-acb3-3dab96693fab",
|
||||
"name": "indexpattern-datasource-layer-7aa8fd7f-f664-48fe-8232-3a26054f9cdc"
|
||||
}
|
||||
],
|
||||
"managed": false,
|
||||
"coreMigrationVersion": "8.8.0",
|
||||
"typeMigrationVersion": "8.9.0"
|
||||
}
|
||||
|
||||
{
|
||||
"id": "4fad4960-5be9-408c-854b-3b53ac80df81",
|
||||
"type": "lens",
|
||||
"namespaces": [
|
||||
"default"
|
||||
],
|
||||
"updated_at": "2025-06-17T15:48:59.917Z",
|
||||
"created_at": "2025-06-17T15:48:59.917Z",
|
||||
"version": "WzYyLDFd",
|
||||
"attributes": {
|
||||
"title": "Lens example - 2",
|
||||
"description": "",
|
||||
"visualizationType": "lnsMetric",
|
||||
"state": {
|
||||
"visualization": {
|
||||
"layerId": "7aa8fd7f-f664-48fe-8232-3a26054f9cdc",
|
||||
"layerType": "data",
|
||||
"metricAccessor": "89a69d8d-a6bc-47a8-80c6-94272762e785",
|
||||
"secondaryTrend": {
|
||||
"type": "none"
|
||||
}
|
||||
},
|
||||
"query": {
|
||||
"query": "",
|
||||
"language": "kuery"
|
||||
},
|
||||
"filters": [],
|
||||
"datasourceStates": {
|
||||
"formBased": {
|
||||
"layers": {
|
||||
"7aa8fd7f-f664-48fe-8232-3a26054f9cdc": {
|
||||
"columns": {
|
||||
"89a69d8d-a6bc-47a8-80c6-94272762e785": {
|
||||
"label": "Count of records",
|
||||
"dataType": "number",
|
||||
"operationType": "count",
|
||||
"isBucketed": false,
|
||||
"scale": "ratio",
|
||||
"sourceField": "___records___",
|
||||
"params": {
|
||||
"emptyAsNull": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"columnOrder": [
|
||||
"89a69d8d-a6bc-47a8-80c6-94272762e785"
|
||||
],
|
||||
"incompleteColumns": {
|
||||
"806819b9-b606-4383-9337-e6a40b8602ad": {
|
||||
"operationType": "date_histogram"
|
||||
}
|
||||
},
|
||||
"sampling": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"indexpattern": {
|
||||
"layers": {}
|
||||
},
|
||||
"textBased": {
|
||||
"layers": {}
|
||||
}
|
||||
},
|
||||
"internalReferences": [],
|
||||
"adHocDataViews": {}
|
||||
}
|
||||
},
|
||||
"references": [
|
||||
{
|
||||
"type": "index-pattern",
|
||||
"id": "91200a00-9efd-11e7-acb3-3dab96693fab",
|
||||
"name": "indexpattern-datasource-layer-7aa8fd7f-f664-48fe-8232-3a26054f9cdc"
|
||||
}
|
||||
],
|
||||
"managed": false,
|
||||
"coreMigrationVersion": "8.8.0",
|
||||
"typeMigrationVersion": "8.9.0"
|
||||
}
|
||||
|
||||
{
|
||||
"id": "af239293-933a-4f35-969f-9dcccfefa8e4",
|
||||
"type": "lens",
|
||||
"namespaces": [
|
||||
"default"
|
||||
],
|
||||
"updated_at": "2025-06-17T15:50:00.097Z",
|
||||
"created_at": "2025-06-17T15:50:00.097Z",
|
||||
"version": "WzY0LDFd",
|
||||
"attributes": {
|
||||
"title": "Lens example - 3",
|
||||
"description": "Some description - 1",
|
||||
"visualizationType": "lnsMetric",
|
||||
"state": {
|
||||
"visualization": {
|
||||
"layerId": "7aa8fd7f-f664-48fe-8232-3a26054f9cdc",
|
||||
"layerType": "data",
|
||||
"metricAccessor": "89a69d8d-a6bc-47a8-80c6-94272762e785",
|
||||
"secondaryTrend": {
|
||||
"type": "none"
|
||||
}
|
||||
},
|
||||
"query": {
|
||||
"query": "",
|
||||
"language": "kuery"
|
||||
},
|
||||
"filters": [],
|
||||
"datasourceStates": {
|
||||
"formBased": {
|
||||
"layers": {
|
||||
"7aa8fd7f-f664-48fe-8232-3a26054f9cdc": {
|
||||
"columns": {
|
||||
"89a69d8d-a6bc-47a8-80c6-94272762e785": {
|
||||
"label": "Count of records",
|
||||
"dataType": "number",
|
||||
"operationType": "count",
|
||||
"isBucketed": false,
|
||||
"scale": "ratio",
|
||||
"sourceField": "___records___",
|
||||
"params": {
|
||||
"emptyAsNull": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"columnOrder": [
|
||||
"89a69d8d-a6bc-47a8-80c6-94272762e785"
|
||||
],
|
||||
"incompleteColumns": {
|
||||
"806819b9-b606-4383-9337-e6a40b8602ad": {
|
||||
"operationType": "date_histogram"
|
||||
}
|
||||
},
|
||||
"sampling": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"indexpattern": {
|
||||
"layers": {}
|
||||
},
|
||||
"textBased": {
|
||||
"layers": {}
|
||||
}
|
||||
},
|
||||
"internalReferences": [],
|
||||
"adHocDataViews": {}
|
||||
}
|
||||
},
|
||||
"references": [
|
||||
{
|
||||
"type": "index-pattern",
|
||||
"id": "91200a00-9efd-11e7-acb3-3dab96693fab",
|
||||
"name": "indexpattern-datasource-layer-7aa8fd7f-f664-48fe-8232-3a26054f9cdc"
|
||||
}
|
||||
],
|
||||
"managed": false,
|
||||
"coreMigrationVersion": "8.8.0",
|
||||
"typeMigrationVersion": "8.9.0"
|
||||
}
|
||||
|
||||
{
|
||||
"id": "f708d303-2418-4313-aa28-c2830f7cf4cd",
|
||||
"type": "lens",
|
||||
"namespaces": [
|
||||
"default"
|
||||
],
|
||||
"updated_at": "2025-06-17T15:50:05.680Z",
|
||||
"created_at": "2025-06-17T15:50:05.680Z",
|
||||
"version": "WzY2LDFd",
|
||||
"attributes": {
|
||||
"title": "Lens example - 4",
|
||||
"description": "",
|
||||
"visualizationType": "lnsMetric",
|
||||
"state": {
|
||||
"visualization": {
|
||||
"layerId": "7aa8fd7f-f664-48fe-8232-3a26054f9cdc",
|
||||
"layerType": "data",
|
||||
"metricAccessor": "89a69d8d-a6bc-47a8-80c6-94272762e785",
|
||||
"secondaryTrend": {
|
||||
"type": "none"
|
||||
}
|
||||
},
|
||||
"query": {
|
||||
"query": "",
|
||||
"language": "kuery"
|
||||
},
|
||||
"filters": [],
|
||||
"datasourceStates": {
|
||||
"formBased": {
|
||||
"layers": {
|
||||
"7aa8fd7f-f664-48fe-8232-3a26054f9cdc": {
|
||||
"columns": {
|
||||
"89a69d8d-a6bc-47a8-80c6-94272762e785": {
|
||||
"label": "Count of records",
|
||||
"dataType": "number",
|
||||
"operationType": "count",
|
||||
"isBucketed": false,
|
||||
"scale": "ratio",
|
||||
"sourceField": "___records___",
|
||||
"params": {
|
||||
"emptyAsNull": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"columnOrder": [
|
||||
"89a69d8d-a6bc-47a8-80c6-94272762e785"
|
||||
],
|
||||
"incompleteColumns": {
|
||||
"806819b9-b606-4383-9337-e6a40b8602ad": {
|
||||
"operationType": "date_histogram"
|
||||
}
|
||||
},
|
||||
"sampling": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"indexpattern": {
|
||||
"layers": {}
|
||||
},
|
||||
"textBased": {
|
||||
"layers": {}
|
||||
}
|
||||
},
|
||||
"internalReferences": [],
|
||||
"adHocDataViews": {}
|
||||
}
|
||||
},
|
||||
"references": [
|
||||
{
|
||||
"type": "index-pattern",
|
||||
"id": "91200a00-9efd-11e7-acb3-3dab96693fab",
|
||||
"name": "indexpattern-datasource-layer-7aa8fd7f-f664-48fe-8232-3a26054f9cdc"
|
||||
}
|
||||
],
|
||||
"managed": false,
|
||||
"coreMigrationVersion": "8.8.0",
|
||||
"typeMigrationVersion": "8.9.0"
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue