mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Canvas] Move workpad api routes to New Platform (#51116)
* Move workpad api routes to New Platform * Cleanup * Clean up/Pr Feedback * Adding missing dependency to tests * Fix typecheck * Loosen workpad schema restrictions
This commit is contained in:
parent
3b6e51b2d8
commit
f5296293c2
23 changed files with 1201 additions and 718 deletions
|
@ -192,3 +192,16 @@ export const elements: CanvasElement[] = [
|
|||
{ ...BaseElement, expression: 'filters | demodata | pointseries | pie | render' },
|
||||
{ ...BaseElement, expression: 'image | render' },
|
||||
];
|
||||
|
||||
export const workpadWithGroupAsElement: CanvasWorkpad = {
|
||||
...BaseWorkpad,
|
||||
pages: [
|
||||
{
|
||||
...BasePage,
|
||||
elements: [
|
||||
{ ...BaseElement, expression: 'image | render' },
|
||||
{ ...BaseElement, id: 'group-1234' },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
@ -29,6 +29,7 @@ export function get(workpadId) {
|
|||
});
|
||||
}
|
||||
|
||||
// TODO: I think this function is never used. Look into and remove the corresponding route as well
|
||||
export function update(id, workpad) {
|
||||
return fetch.put(`${apiPath}/${id}`, workpad);
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { workpad } from './workpad';
|
||||
import { esFields } from './es_fields';
|
||||
import { customElements } from './custom_elements';
|
||||
import { shareableWorkpads } from './shareables';
|
||||
|
@ -13,6 +12,5 @@ import { CoreSetup } from '../shim';
|
|||
export function routes(setup: CoreSetup): void {
|
||||
customElements(setup.http.route, setup.elasticsearch);
|
||||
esFields(setup.http.route, setup.elasticsearch);
|
||||
workpad(setup.http.route, setup.elasticsearch);
|
||||
shareableWorkpads(setup.http.route);
|
||||
}
|
||||
|
|
|
@ -1,462 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import Hapi from 'hapi';
|
||||
import {
|
||||
CANVAS_TYPE,
|
||||
API_ROUTE_WORKPAD,
|
||||
API_ROUTE_WORKPAD_ASSETS,
|
||||
API_ROUTE_WORKPAD_STRUCTURES,
|
||||
} from '../../common/lib/constants';
|
||||
import { workpad } from './workpad';
|
||||
|
||||
const routePrefix = API_ROUTE_WORKPAD;
|
||||
const routePrefixAssets = API_ROUTE_WORKPAD_ASSETS;
|
||||
const routePrefixStructures = API_ROUTE_WORKPAD_STRUCTURES;
|
||||
|
||||
jest.mock('uuid/v4', () => jest.fn().mockReturnValue('123abc'));
|
||||
|
||||
describe(`${CANVAS_TYPE} API`, () => {
|
||||
const savedObjectsClient = {
|
||||
get: jest.fn(),
|
||||
create: jest.fn(),
|
||||
delete: jest.fn(),
|
||||
find: jest.fn(),
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
savedObjectsClient.get.mockReset();
|
||||
savedObjectsClient.create.mockReset();
|
||||
savedObjectsClient.delete.mockReset();
|
||||
savedObjectsClient.find.mockReset();
|
||||
});
|
||||
|
||||
// Mock toISOString function of all Date types
|
||||
global.Date = class Date extends global.Date {
|
||||
toISOString() {
|
||||
return '2019-02-12T21:01:22.479Z';
|
||||
}
|
||||
};
|
||||
|
||||
// Setup mock server
|
||||
const mockServer = new Hapi.Server({ debug: false, port: 0 });
|
||||
const mockEs = {
|
||||
getCluster: () => ({
|
||||
errors: {
|
||||
// formatResponse will fail without objects here
|
||||
'400': Error,
|
||||
'401': Error,
|
||||
'403': Error,
|
||||
'404': Error,
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
mockServer.ext('onRequest', (req, h) => {
|
||||
req.getSavedObjectsClient = () => savedObjectsClient;
|
||||
return h.continue;
|
||||
});
|
||||
workpad(mockServer.route.bind(mockServer), mockEs);
|
||||
|
||||
describe(`GET ${routePrefix}/{id}`, () => {
|
||||
test('returns successful response', async () => {
|
||||
const request = {
|
||||
method: 'GET',
|
||||
url: `${routePrefix}/123`,
|
||||
};
|
||||
|
||||
savedObjectsClient.get.mockResolvedValueOnce({ id: '123', attributes: { foo: true } });
|
||||
|
||||
const { payload, statusCode } = await mockServer.inject(request);
|
||||
const response = JSON.parse(payload);
|
||||
|
||||
expect(statusCode).toBe(200);
|
||||
expect(response).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"foo": true,
|
||||
"id": "123",
|
||||
}
|
||||
`);
|
||||
expect(savedObjectsClient.get.mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
"canvas-workpad",
|
||||
"123",
|
||||
],
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe(`POST ${routePrefix}`, () => {
|
||||
test('returns successful response without id in payload', async () => {
|
||||
const request = {
|
||||
method: 'POST',
|
||||
url: routePrefix,
|
||||
payload: {
|
||||
foo: true,
|
||||
},
|
||||
};
|
||||
|
||||
savedObjectsClient.create.mockResolvedValueOnce({});
|
||||
|
||||
const { payload, statusCode } = await mockServer.inject(request);
|
||||
const response = JSON.parse(payload);
|
||||
|
||||
expect(statusCode).toBe(200);
|
||||
expect(response).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"ok": true,
|
||||
}
|
||||
`);
|
||||
expect(savedObjectsClient.create.mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
"canvas-workpad",
|
||||
Object {
|
||||
"@created": "2019-02-12T21:01:22.479Z",
|
||||
"@timestamp": "2019-02-12T21:01:22.479Z",
|
||||
"foo": true,
|
||||
},
|
||||
Object {
|
||||
"id": "workpad-123abc",
|
||||
},
|
||||
],
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
test('returns succesful response with id in payload', async () => {
|
||||
const request = {
|
||||
method: 'POST',
|
||||
url: routePrefix,
|
||||
payload: {
|
||||
id: '123',
|
||||
foo: true,
|
||||
},
|
||||
};
|
||||
|
||||
savedObjectsClient.create.mockResolvedValueOnce({});
|
||||
|
||||
const { payload, statusCode } = await mockServer.inject(request);
|
||||
const response = JSON.parse(payload);
|
||||
|
||||
expect(statusCode).toBe(200);
|
||||
expect(response).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"ok": true,
|
||||
}
|
||||
`);
|
||||
expect(savedObjectsClient.create.mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
"canvas-workpad",
|
||||
Object {
|
||||
"@created": "2019-02-12T21:01:22.479Z",
|
||||
"@timestamp": "2019-02-12T21:01:22.479Z",
|
||||
"foo": true,
|
||||
},
|
||||
Object {
|
||||
"id": "123",
|
||||
},
|
||||
],
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe(`PUT ${routePrefix}/{id}`, () => {
|
||||
test('formats successful response', async () => {
|
||||
const request = {
|
||||
method: 'PUT',
|
||||
url: `${routePrefix}/123`,
|
||||
payload: {
|
||||
id: '234',
|
||||
foo: true,
|
||||
},
|
||||
};
|
||||
|
||||
savedObjectsClient.get.mockResolvedValueOnce({
|
||||
attributes: {
|
||||
'@created': new Date().toISOString(),
|
||||
},
|
||||
});
|
||||
savedObjectsClient.create.mockResolvedValueOnce({});
|
||||
|
||||
const { payload, statusCode } = await mockServer.inject(request);
|
||||
const response = JSON.parse(payload);
|
||||
|
||||
expect(statusCode).toBe(200);
|
||||
expect(response).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"ok": true,
|
||||
}
|
||||
`);
|
||||
expect(savedObjectsClient.get.mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
"canvas-workpad",
|
||||
"123",
|
||||
],
|
||||
]
|
||||
`);
|
||||
expect(savedObjectsClient.create.mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
"canvas-workpad",
|
||||
Object {
|
||||
"@created": "2019-02-12T21:01:22.479Z",
|
||||
"@timestamp": "2019-02-12T21:01:22.479Z",
|
||||
"foo": true,
|
||||
},
|
||||
Object {
|
||||
"id": "123",
|
||||
"overwrite": true,
|
||||
},
|
||||
],
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe(`DELETE ${routePrefix}/{id}`, () => {
|
||||
test('formats successful response', async () => {
|
||||
const request = {
|
||||
method: 'DELETE',
|
||||
url: `${routePrefix}/123`,
|
||||
};
|
||||
|
||||
savedObjectsClient.delete.mockResolvedValueOnce({});
|
||||
|
||||
const { payload, statusCode } = await mockServer.inject(request);
|
||||
const response = JSON.parse(payload);
|
||||
|
||||
expect(statusCode).toBe(200);
|
||||
expect(response).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"ok": true,
|
||||
}
|
||||
`);
|
||||
expect(savedObjectsClient.delete.mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
"canvas-workpad",
|
||||
"123",
|
||||
],
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
it(`GET ${routePrefix}/find`, async () => {
|
||||
const request = {
|
||||
method: 'GET',
|
||||
url: `${routePrefix}/find?name=abc&page=2&perPage=10`,
|
||||
};
|
||||
|
||||
savedObjectsClient.find.mockResolvedValueOnce({
|
||||
saved_objects: [
|
||||
{
|
||||
id: '1',
|
||||
attributes: {
|
||||
foo: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const { payload, statusCode } = await mockServer.inject(request);
|
||||
const response = JSON.parse(payload);
|
||||
|
||||
expect(statusCode).toBe(200);
|
||||
expect(response).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"workpads": Array [
|
||||
Object {
|
||||
"foo": true,
|
||||
"id": "1",
|
||||
},
|
||||
],
|
||||
}
|
||||
`);
|
||||
expect(savedObjectsClient.find.mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
Object {
|
||||
"fields": Array [
|
||||
"id",
|
||||
"name",
|
||||
"@created",
|
||||
"@timestamp",
|
||||
],
|
||||
"page": "2",
|
||||
"perPage": "10",
|
||||
"search": "abc* | abc",
|
||||
"searchFields": Array [
|
||||
"name",
|
||||
],
|
||||
"sortField": "@timestamp",
|
||||
"sortOrder": "desc",
|
||||
"type": "canvas-workpad",
|
||||
},
|
||||
],
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
describe(`PUT ${routePrefixAssets}/{id}`, () => {
|
||||
test('only updates assets', async () => {
|
||||
const request = {
|
||||
method: 'PUT',
|
||||
url: `${routePrefixAssets}/123`,
|
||||
payload: {
|
||||
'asset-123': {
|
||||
id: 'asset-123',
|
||||
'@created': '2019-02-14T00:00:00.000Z',
|
||||
type: 'dataurl',
|
||||
value: 'mockbase64data',
|
||||
},
|
||||
'asset-456': {
|
||||
id: 'asset-456',
|
||||
'@created': '2019-02-15T00:00:00.000Z',
|
||||
type: 'dataurl',
|
||||
value: 'mockbase64data',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// provide some existing workpad data to check that it's preserved
|
||||
savedObjectsClient.get.mockResolvedValueOnce({
|
||||
attributes: {
|
||||
'@created': new Date().toISOString(),
|
||||
name: 'fake workpad',
|
||||
},
|
||||
});
|
||||
savedObjectsClient.create.mockResolvedValueOnce({});
|
||||
|
||||
const { payload, statusCode } = await mockServer.inject(request);
|
||||
const response = JSON.parse(payload);
|
||||
|
||||
expect(statusCode).toBe(200);
|
||||
expect(response).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"ok": true,
|
||||
}
|
||||
`);
|
||||
expect(savedObjectsClient.get.mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
"canvas-workpad",
|
||||
"123",
|
||||
],
|
||||
]
|
||||
`);
|
||||
expect(savedObjectsClient.create.mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
"canvas-workpad",
|
||||
Object {
|
||||
"@created": "2019-02-12T21:01:22.479Z",
|
||||
"@timestamp": "2019-02-12T21:01:22.479Z",
|
||||
"assets": Object {
|
||||
"asset-123": Object {
|
||||
"@created": "2019-02-14T00:00:00.000Z",
|
||||
"id": "asset-123",
|
||||
"type": "dataurl",
|
||||
"value": "mockbase64data",
|
||||
},
|
||||
"asset-456": Object {
|
||||
"@created": "2019-02-15T00:00:00.000Z",
|
||||
"id": "asset-456",
|
||||
"type": "dataurl",
|
||||
"value": "mockbase64data",
|
||||
},
|
||||
},
|
||||
"name": "fake workpad",
|
||||
},
|
||||
Object {
|
||||
"id": "123",
|
||||
"overwrite": true,
|
||||
},
|
||||
],
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe(`PUT ${routePrefixStructures}/{id}`, () => {
|
||||
test('only updates workpad', async () => {
|
||||
const request = {
|
||||
method: 'PUT',
|
||||
url: `${routePrefixStructures}/123`,
|
||||
payload: {
|
||||
name: 'renamed workpad',
|
||||
css: '.canvasPage { color: LavenderBlush; }',
|
||||
},
|
||||
};
|
||||
|
||||
// provide some existing asset data and a name to replace
|
||||
savedObjectsClient.get.mockResolvedValueOnce({
|
||||
attributes: {
|
||||
'@created': new Date().toISOString(),
|
||||
name: 'fake workpad',
|
||||
assets: {
|
||||
'asset-123': {
|
||||
id: 'asset-123',
|
||||
'@created': '2019-02-14T00:00:00.000Z',
|
||||
type: 'dataurl',
|
||||
value: 'mockbase64data',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
savedObjectsClient.create.mockResolvedValueOnce({});
|
||||
|
||||
const { payload, statusCode } = await mockServer.inject(request);
|
||||
const response = JSON.parse(payload);
|
||||
|
||||
expect(statusCode).toBe(200);
|
||||
expect(response).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"ok": true,
|
||||
}
|
||||
`);
|
||||
expect(savedObjectsClient.get.mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
"canvas-workpad",
|
||||
"123",
|
||||
],
|
||||
]
|
||||
`);
|
||||
expect(savedObjectsClient.create.mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
"canvas-workpad",
|
||||
Object {
|
||||
"@created": "2019-02-12T21:01:22.479Z",
|
||||
"@timestamp": "2019-02-12T21:01:22.479Z",
|
||||
"assets": Object {
|
||||
"asset-123": Object {
|
||||
"@created": "2019-02-14T00:00:00.000Z",
|
||||
"id": "asset-123",
|
||||
"type": "dataurl",
|
||||
"value": "mockbase64data",
|
||||
},
|
||||
},
|
||||
"css": ".canvasPage { color: LavenderBlush; }",
|
||||
"name": "renamed workpad",
|
||||
},
|
||||
Object {
|
||||
"id": "123",
|
||||
"overwrite": true,
|
||||
},
|
||||
],
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,254 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import boom from 'boom';
|
||||
import { omit } from 'lodash';
|
||||
import { SavedObjectsClientContract, SavedObjectAttributes } from 'src/core/server';
|
||||
import {
|
||||
CANVAS_TYPE,
|
||||
API_ROUTE_WORKPAD,
|
||||
API_ROUTE_WORKPAD_ASSETS,
|
||||
API_ROUTE_WORKPAD_STRUCTURES,
|
||||
} from '../../common/lib/constants';
|
||||
import { getId } from '../../public/lib/get_id';
|
||||
import { CoreSetup } from '../shim';
|
||||
// @ts-ignore Untyped Local
|
||||
import { formatResponse as formatRes } from '../lib/format_response';
|
||||
import { CanvasWorkpad } from '../../types';
|
||||
|
||||
type WorkpadAttributes = Pick<CanvasWorkpad, Exclude<keyof CanvasWorkpad, 'id'>> & {
|
||||
'@timestamp': string;
|
||||
'@created': string;
|
||||
};
|
||||
|
||||
interface WorkpadRequestFacade {
|
||||
getSavedObjectsClient: () => SavedObjectsClientContract;
|
||||
}
|
||||
|
||||
type WorkpadRequest = WorkpadRequestFacade & {
|
||||
params: {
|
||||
id: string;
|
||||
};
|
||||
payload: CanvasWorkpad;
|
||||
};
|
||||
|
||||
type FindWorkpadRequest = WorkpadRequestFacade & {
|
||||
query: {
|
||||
name: string;
|
||||
page: number;
|
||||
perPage: number;
|
||||
};
|
||||
};
|
||||
|
||||
type AssetsRequest = WorkpadRequestFacade & {
|
||||
params: {
|
||||
id: string;
|
||||
};
|
||||
payload: CanvasWorkpad['assets'];
|
||||
};
|
||||
|
||||
export function workpad(
|
||||
route: CoreSetup['http']['route'],
|
||||
elasticsearch: CoreSetup['elasticsearch']
|
||||
) {
|
||||
// @ts-ignore EsErrors is not on the Cluster type
|
||||
const { errors: esErrors } = elasticsearch.getCluster('data');
|
||||
const routePrefix = API_ROUTE_WORKPAD;
|
||||
const routePrefixAssets = API_ROUTE_WORKPAD_ASSETS;
|
||||
const routePrefixStructures = API_ROUTE_WORKPAD_STRUCTURES;
|
||||
const formatResponse = formatRes(esErrors);
|
||||
|
||||
function createWorkpad(req: WorkpadRequest) {
|
||||
const savedObjectsClient = req.getSavedObjectsClient();
|
||||
|
||||
if (!req.payload) {
|
||||
return Promise.reject(boom.badRequest('A workpad payload is required'));
|
||||
}
|
||||
|
||||
const now = new Date().toISOString();
|
||||
const { id, ...payload } = req.payload;
|
||||
return savedObjectsClient.create<WorkpadAttributes>(
|
||||
CANVAS_TYPE,
|
||||
{
|
||||
...payload,
|
||||
'@timestamp': now,
|
||||
'@created': now,
|
||||
},
|
||||
{ id: id || getId('workpad') }
|
||||
);
|
||||
}
|
||||
|
||||
function updateWorkpad(
|
||||
req: WorkpadRequest | AssetsRequest,
|
||||
newPayload?: CanvasWorkpad | { assets: CanvasWorkpad['assets'] }
|
||||
) {
|
||||
const savedObjectsClient = req.getSavedObjectsClient();
|
||||
const { id } = req.params;
|
||||
const payload = newPayload ? newPayload : req.payload;
|
||||
|
||||
const now = new Date().toISOString();
|
||||
|
||||
return savedObjectsClient.get<WorkpadAttributes>(CANVAS_TYPE, id).then(workpadObject => {
|
||||
// TODO: Using create with force over-write because of version conflict issues with update
|
||||
return savedObjectsClient.create(
|
||||
CANVAS_TYPE,
|
||||
{
|
||||
...(workpadObject.attributes as SavedObjectAttributes),
|
||||
...omit(payload, 'id'), // never write the id property
|
||||
'@timestamp': now, // always update the modified time
|
||||
'@created': workpadObject.attributes['@created'], // ensure created is not modified
|
||||
},
|
||||
{ overwrite: true, id }
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function deleteWorkpad(req: WorkpadRequest) {
|
||||
const savedObjectsClient = req.getSavedObjectsClient();
|
||||
const { id } = req.params;
|
||||
|
||||
return savedObjectsClient.delete(CANVAS_TYPE, id);
|
||||
}
|
||||
|
||||
function findWorkpad(req: FindWorkpadRequest) {
|
||||
const savedObjectsClient = req.getSavedObjectsClient();
|
||||
const { name, page, perPage } = req.query;
|
||||
|
||||
return savedObjectsClient.find({
|
||||
type: CANVAS_TYPE,
|
||||
sortField: '@timestamp',
|
||||
sortOrder: 'desc',
|
||||
search: name ? `${name}* | ${name}` : '*',
|
||||
searchFields: ['name'],
|
||||
fields: ['id', 'name', '@created', '@timestamp'],
|
||||
page,
|
||||
perPage,
|
||||
});
|
||||
}
|
||||
|
||||
// get workpad
|
||||
route({
|
||||
method: 'GET',
|
||||
path: `${routePrefix}/{id}`,
|
||||
handler(req: WorkpadRequest) {
|
||||
const savedObjectsClient = req.getSavedObjectsClient();
|
||||
const { id } = req.params;
|
||||
|
||||
return savedObjectsClient
|
||||
.get<WorkpadAttributes>(CANVAS_TYPE, id)
|
||||
.then(obj => {
|
||||
if (
|
||||
// not sure if we need to be this defensive
|
||||
obj.type === 'canvas-workpad' &&
|
||||
obj.attributes &&
|
||||
obj.attributes.pages &&
|
||||
obj.attributes.pages.length
|
||||
) {
|
||||
obj.attributes.pages.forEach(page => {
|
||||
const elements = (page.elements || []).filter(
|
||||
({ id: pageId }) => !pageId.startsWith('group')
|
||||
);
|
||||
const groups = (page.groups || []).concat(
|
||||
(page.elements || []).filter(({ id: pageId }) => pageId.startsWith('group'))
|
||||
);
|
||||
page.elements = elements;
|
||||
page.groups = groups;
|
||||
});
|
||||
}
|
||||
return obj;
|
||||
})
|
||||
.then(obj => ({ id: obj.id, ...obj.attributes }))
|
||||
.then(formatResponse)
|
||||
.catch(formatResponse);
|
||||
},
|
||||
});
|
||||
|
||||
// create workpad
|
||||
route({
|
||||
method: 'POST',
|
||||
path: routePrefix,
|
||||
// @ts-ignore config option missing on route method type
|
||||
config: { payload: { allow: 'application/json', maxBytes: 26214400 } }, // 25MB payload limit
|
||||
handler(request: WorkpadRequest) {
|
||||
return createWorkpad(request)
|
||||
.then(() => ({ ok: true }))
|
||||
.catch(formatResponse);
|
||||
},
|
||||
});
|
||||
|
||||
// update workpad
|
||||
route({
|
||||
method: 'PUT',
|
||||
path: `${routePrefix}/{id}`,
|
||||
// @ts-ignore config option missing on route method type
|
||||
config: { payload: { allow: 'application/json', maxBytes: 26214400 } }, // 25MB payload limit
|
||||
handler(request: WorkpadRequest) {
|
||||
return updateWorkpad(request)
|
||||
.then(() => ({ ok: true }))
|
||||
.catch(formatResponse);
|
||||
},
|
||||
});
|
||||
|
||||
// update workpad assets
|
||||
route({
|
||||
method: 'PUT',
|
||||
path: `${routePrefixAssets}/{id}`,
|
||||
// @ts-ignore config option missing on route method type
|
||||
config: { payload: { allow: 'application/json', maxBytes: 26214400 } }, // 25MB payload limit
|
||||
handler(request: AssetsRequest) {
|
||||
const payload = { assets: request.payload };
|
||||
return updateWorkpad(request, payload)
|
||||
.then(() => ({ ok: true }))
|
||||
.catch(formatResponse);
|
||||
},
|
||||
});
|
||||
|
||||
// update workpad structures
|
||||
route({
|
||||
method: 'PUT',
|
||||
path: `${routePrefixStructures}/{id}`,
|
||||
// @ts-ignore config option missing on route method type
|
||||
config: { payload: { allow: 'application/json', maxBytes: 26214400 } }, // 25MB payload limit
|
||||
handler(request: WorkpadRequest) {
|
||||
return updateWorkpad(request)
|
||||
.then(() => ({ ok: true }))
|
||||
.catch(formatResponse);
|
||||
},
|
||||
});
|
||||
|
||||
// delete workpad
|
||||
route({
|
||||
method: 'DELETE',
|
||||
path: `${routePrefix}/{id}`,
|
||||
handler(request: WorkpadRequest) {
|
||||
return deleteWorkpad(request)
|
||||
.then(() => ({ ok: true }))
|
||||
.catch(formatResponse);
|
||||
},
|
||||
});
|
||||
|
||||
// find workpads
|
||||
route({
|
||||
method: 'GET',
|
||||
path: `${routePrefix}/find`,
|
||||
handler(request: FindWorkpadRequest) {
|
||||
return findWorkpad(request)
|
||||
.then(formatResponse)
|
||||
.then(resp => {
|
||||
return {
|
||||
total: resp.total,
|
||||
workpads: resp.saved_objects.map(hit => ({ id: hit.id, ...hit.attributes })),
|
||||
};
|
||||
})
|
||||
.catch(() => {
|
||||
return {
|
||||
total: 0,
|
||||
workpads: [],
|
||||
};
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
10
x-pack/plugins/canvas/kibana.json
Normal file
10
x-pack/plugins/canvas/kibana.json
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"id": "canvas",
|
||||
"version": "8.0.0",
|
||||
"kibanaVersion": "kibana",
|
||||
"configPath": ["xpack", "canvas"],
|
||||
"server": true,
|
||||
"ui": false,
|
||||
"requiredPlugins": []
|
||||
}
|
||||
|
11
x-pack/plugins/canvas/server/index.ts
Normal file
11
x-pack/plugins/canvas/server/index.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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { PluginInitializerContext } from 'src/core/server';
|
||||
import { CanvasPlugin } from './plugin';
|
||||
|
||||
export const plugin = (initializerContext: PluginInitializerContext) =>
|
||||
new CanvasPlugin(initializerContext);
|
25
x-pack/plugins/canvas/server/plugin.ts
Normal file
25
x-pack/plugins/canvas/server/plugin.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { CoreSetup, PluginInitializerContext, Plugin, Logger } from 'src/core/server';
|
||||
import { initRoutes } from './routes';
|
||||
|
||||
export class CanvasPlugin implements Plugin {
|
||||
private readonly logger: Logger;
|
||||
constructor(initializerContext: PluginInitializerContext) {
|
||||
this.logger = initializerContext.logger.get();
|
||||
}
|
||||
|
||||
public setup(coreSetup: CoreSetup): void {
|
||||
const canvasRouter = coreSetup.http.createRouter();
|
||||
|
||||
initRoutes({ router: canvasRouter, logger: this.logger });
|
||||
}
|
||||
|
||||
public start() {}
|
||||
|
||||
public stop() {}
|
||||
}
|
30
x-pack/plugins/canvas/server/routes/catch_error_handler.ts
Normal file
30
x-pack/plugins/canvas/server/routes/catch_error_handler.ts
Normal file
|
@ -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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { ObjectType } from '@kbn/config-schema';
|
||||
import { RequestHandler } from 'src/core/server';
|
||||
|
||||
export const catchErrorHandler: <
|
||||
P extends ObjectType<any>,
|
||||
Q extends ObjectType<any>,
|
||||
B extends ObjectType<any>
|
||||
>(
|
||||
fn: RequestHandler<P, Q, B>
|
||||
) => RequestHandler<P, Q, B> = fn => {
|
||||
return async (context, request, response) => {
|
||||
try {
|
||||
return await fn(context, request, response);
|
||||
} catch (error) {
|
||||
if (error.isBoom) {
|
||||
return response.customError({
|
||||
body: error.output.payload,
|
||||
statusCode: error.output.statusCode,
|
||||
});
|
||||
}
|
||||
return response.internalError({ body: error });
|
||||
}
|
||||
};
|
||||
};
|
17
x-pack/plugins/canvas/server/routes/index.ts
Normal file
17
x-pack/plugins/canvas/server/routes/index.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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { IRouter, Logger } from 'src/core/server';
|
||||
import { initWorkpadRoutes } from './workpad';
|
||||
|
||||
export interface RouteInitializerDeps {
|
||||
router: IRouter;
|
||||
logger: Logger;
|
||||
}
|
||||
|
||||
export function initRoutes(deps: RouteInitializerDeps) {
|
||||
initWorkpadRoutes(deps);
|
||||
}
|
102
x-pack/plugins/canvas/server/routes/workpad/create.test.ts
Normal file
102
x-pack/plugins/canvas/server/routes/workpad/create.test.ts
Normal file
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import sinon from 'sinon';
|
||||
import {
|
||||
savedObjectsClientMock,
|
||||
httpServiceMock,
|
||||
httpServerMock,
|
||||
loggingServiceMock,
|
||||
} from 'src/core/server/mocks';
|
||||
import { CANVAS_TYPE } from '../../../../../legacy/plugins/canvas/common/lib/constants';
|
||||
import { initializeCreateWorkpadRoute } from './create';
|
||||
import {
|
||||
IRouter,
|
||||
kibanaResponseFactory,
|
||||
RequestHandlerContext,
|
||||
RequestHandler,
|
||||
} from 'src/core/server';
|
||||
|
||||
const mockRouteContext = ({
|
||||
core: {
|
||||
savedObjects: {
|
||||
client: savedObjectsClientMock.create(),
|
||||
},
|
||||
},
|
||||
} as unknown) as RequestHandlerContext;
|
||||
|
||||
const mockedUUID = '123abc';
|
||||
const now = new Date();
|
||||
const nowIso = now.toISOString();
|
||||
|
||||
jest.mock('uuid/v4', () => jest.fn().mockReturnValue('123abc'));
|
||||
|
||||
describe('POST workpad', () => {
|
||||
let routeHandler: RequestHandler<any, any, any>;
|
||||
let clock: sinon.SinonFakeTimers;
|
||||
|
||||
beforeEach(() => {
|
||||
clock = sinon.useFakeTimers(now);
|
||||
|
||||
const httpService = httpServiceMock.createSetupContract();
|
||||
|
||||
const router = httpService.createRouter('') as jest.Mocked<IRouter>;
|
||||
initializeCreateWorkpadRoute({
|
||||
router,
|
||||
logger: loggingServiceMock.create().get(),
|
||||
});
|
||||
|
||||
routeHandler = router.post.mock.calls[0][1];
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it(`returns 200 when the workpad is created`, async () => {
|
||||
const mockWorkpad = {
|
||||
pages: [],
|
||||
};
|
||||
|
||||
const request = httpServerMock.createKibanaRequest({
|
||||
method: 'post',
|
||||
path: 'api/canvas/workpad',
|
||||
body: mockWorkpad,
|
||||
});
|
||||
|
||||
const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.payload).toEqual({ ok: true });
|
||||
expect(mockRouteContext.core.savedObjects.client.create).toBeCalledWith(
|
||||
CANVAS_TYPE,
|
||||
{
|
||||
...mockWorkpad,
|
||||
'@timestamp': nowIso,
|
||||
'@created': nowIso,
|
||||
},
|
||||
{
|
||||
id: `workpad-${mockedUUID}`,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it(`returns bad request if create is unsuccessful`, async () => {
|
||||
const request = httpServerMock.createKibanaRequest({
|
||||
method: 'post',
|
||||
path: 'api/canvas/workpad',
|
||||
body: {},
|
||||
});
|
||||
|
||||
(mockRouteContext.core.savedObjects.client.create as jest.Mock).mockImplementation(() => {
|
||||
throw mockRouteContext.core.savedObjects.client.errors.createBadRequestError('bad request');
|
||||
});
|
||||
|
||||
const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory);
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
});
|
||||
});
|
57
x-pack/plugins/canvas/server/routes/workpad/create.ts
Normal file
57
x-pack/plugins/canvas/server/routes/workpad/create.ts
Normal file
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { RouteInitializerDeps } from '../';
|
||||
import {
|
||||
CANVAS_TYPE,
|
||||
API_ROUTE_WORKPAD,
|
||||
} from '../../../../../legacy/plugins/canvas/common/lib/constants';
|
||||
import { CanvasWorkpad } from '../../../../../legacy/plugins/canvas/types';
|
||||
import { getId } from '../../../../../legacy/plugins/canvas/public/lib/get_id';
|
||||
import { WorkpadSchema } from './workpad_schema';
|
||||
import { okResponse } from './ok_response';
|
||||
import { catchErrorHandler } from '../catch_error_handler';
|
||||
|
||||
export type WorkpadAttributes = Pick<CanvasWorkpad, Exclude<keyof CanvasWorkpad, 'id'>> & {
|
||||
'@timestamp': string;
|
||||
'@created': string;
|
||||
};
|
||||
|
||||
export function initializeCreateWorkpadRoute(deps: RouteInitializerDeps) {
|
||||
const { router } = deps;
|
||||
router.post(
|
||||
{
|
||||
path: `${API_ROUTE_WORKPAD}`,
|
||||
validate: {
|
||||
body: WorkpadSchema,
|
||||
},
|
||||
},
|
||||
catchErrorHandler(async (context, request, response) => {
|
||||
if (!request.body) {
|
||||
return response.badRequest({ body: 'A workpad payload is required' });
|
||||
}
|
||||
|
||||
const workpad = request.body as CanvasWorkpad;
|
||||
|
||||
const now = new Date().toISOString();
|
||||
const { id, ...payload } = workpad;
|
||||
|
||||
await context.core.savedObjects.client.create<WorkpadAttributes>(
|
||||
CANVAS_TYPE,
|
||||
{
|
||||
...payload,
|
||||
'@timestamp': now,
|
||||
'@created': now,
|
||||
},
|
||||
{ id: id || getId('workpad') }
|
||||
);
|
||||
|
||||
return response.ok({
|
||||
body: okResponse,
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
78
x-pack/plugins/canvas/server/routes/workpad/delete.test.ts
Normal file
78
x-pack/plugins/canvas/server/routes/workpad/delete.test.ts
Normal file
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { CANVAS_TYPE } from '../../../../../legacy/plugins/canvas/common/lib/constants';
|
||||
import { initializeDeleteWorkpadRoute } from './delete';
|
||||
import {
|
||||
IRouter,
|
||||
kibanaResponseFactory,
|
||||
RequestHandlerContext,
|
||||
RequestHandler,
|
||||
} from 'src/core/server';
|
||||
import {
|
||||
savedObjectsClientMock,
|
||||
httpServiceMock,
|
||||
httpServerMock,
|
||||
loggingServiceMock,
|
||||
} from 'src/core/server/mocks';
|
||||
|
||||
const mockRouteContext = ({
|
||||
core: {
|
||||
savedObjects: {
|
||||
client: savedObjectsClientMock.create(),
|
||||
},
|
||||
},
|
||||
} as unknown) as RequestHandlerContext;
|
||||
|
||||
describe('DELETE workpad', () => {
|
||||
let routeHandler: RequestHandler<any, any, any>;
|
||||
|
||||
beforeEach(() => {
|
||||
const httpService = httpServiceMock.createSetupContract();
|
||||
const router = httpService.createRouter('') as jest.Mocked<IRouter>;
|
||||
initializeDeleteWorkpadRoute({
|
||||
router,
|
||||
logger: loggingServiceMock.create().get(),
|
||||
});
|
||||
|
||||
routeHandler = router.delete.mock.calls[0][1];
|
||||
});
|
||||
|
||||
it(`returns 200 ok when the workpad is deleted`, async () => {
|
||||
const id = 'some-id';
|
||||
const request = httpServerMock.createKibanaRequest({
|
||||
method: 'delete',
|
||||
path: `api/canvas/workpad/${id}`,
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.payload).toEqual({ ok: true });
|
||||
expect(mockRouteContext.core.savedObjects.client.delete).toBeCalledWith(CANVAS_TYPE, id);
|
||||
});
|
||||
|
||||
it(`returns bad request if delete is unsuccessful`, async () => {
|
||||
const request = httpServerMock.createKibanaRequest({
|
||||
method: 'delete',
|
||||
path: `api/canvas/workpad/some-id`,
|
||||
params: {
|
||||
id: 'some-id',
|
||||
},
|
||||
});
|
||||
|
||||
(mockRouteContext.core.savedObjects.client.delete as jest.Mock).mockImplementationOnce(() => {
|
||||
throw mockRouteContext.core.savedObjects.client.errors.createBadRequestError('bad request');
|
||||
});
|
||||
|
||||
const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory);
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
});
|
||||
});
|
32
x-pack/plugins/canvas/server/routes/workpad/delete.ts
Normal file
32
x-pack/plugins/canvas/server/routes/workpad/delete.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { RouteInitializerDeps } from '../';
|
||||
import {
|
||||
CANVAS_TYPE,
|
||||
API_ROUTE_WORKPAD,
|
||||
} from '../../../../../legacy/plugins/canvas/common/lib/constants';
|
||||
import { okResponse } from './ok_response';
|
||||
import { catchErrorHandler } from '../catch_error_handler';
|
||||
|
||||
export function initializeDeleteWorkpadRoute(deps: RouteInitializerDeps) {
|
||||
const { router } = deps;
|
||||
router.delete(
|
||||
{
|
||||
path: `${API_ROUTE_WORKPAD}/{id}`,
|
||||
validate: {
|
||||
params: schema.object({
|
||||
id: schema.string(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
catchErrorHandler(async (context, request, response) => {
|
||||
context.core.savedObjects.client.delete(CANVAS_TYPE, request.params.id);
|
||||
return response.ok({ body: okResponse });
|
||||
})
|
||||
);
|
||||
}
|
113
x-pack/plugins/canvas/server/routes/workpad/find.test.ts
Normal file
113
x-pack/plugins/canvas/server/routes/workpad/find.test.ts
Normal file
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { initializeFindWorkpadsRoute } from './find';
|
||||
import {
|
||||
IRouter,
|
||||
kibanaResponseFactory,
|
||||
RequestHandlerContext,
|
||||
RequestHandler,
|
||||
} from 'src/core/server';
|
||||
import {
|
||||
savedObjectsClientMock,
|
||||
httpServiceMock,
|
||||
httpServerMock,
|
||||
loggingServiceMock,
|
||||
} from 'src/core/server/mocks';
|
||||
|
||||
const mockRouteContext = ({
|
||||
core: {
|
||||
savedObjects: {
|
||||
client: savedObjectsClientMock.create(),
|
||||
},
|
||||
},
|
||||
} as unknown) as RequestHandlerContext;
|
||||
|
||||
describe('Find workpad', () => {
|
||||
let routeHandler: RequestHandler<any, any, any>;
|
||||
|
||||
beforeEach(() => {
|
||||
const httpService = httpServiceMock.createSetupContract();
|
||||
const router = httpService.createRouter('') as jest.Mocked<IRouter>;
|
||||
initializeFindWorkpadsRoute({
|
||||
router,
|
||||
logger: loggingServiceMock.create().get(),
|
||||
});
|
||||
|
||||
routeHandler = router.get.mock.calls[0][1];
|
||||
});
|
||||
|
||||
it(`returns 200 with the found workpads`, async () => {
|
||||
const name = 'something';
|
||||
const perPage = 10000;
|
||||
const mockResults = {
|
||||
total: 2,
|
||||
saved_objects: [
|
||||
{ id: 1, attributes: { key: 'value' } },
|
||||
{ id: 2, attributes: { key: 'other-value' } },
|
||||
],
|
||||
};
|
||||
|
||||
const findMock = mockRouteContext.core.savedObjects.client.find as jest.Mock;
|
||||
|
||||
findMock.mockResolvedValueOnce(mockResults);
|
||||
|
||||
const request = httpServerMock.createKibanaRequest({
|
||||
method: 'get',
|
||||
path: `api/canvas/workpad/find`,
|
||||
query: {
|
||||
name,
|
||||
perPage,
|
||||
},
|
||||
});
|
||||
|
||||
const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory);
|
||||
expect(response.status).toBe(200);
|
||||
expect(findMock.mock.calls[0][0].search).toBe(`${name}* | ${name}`);
|
||||
expect(findMock.mock.calls[0][0].perPage).toBe(perPage);
|
||||
|
||||
expect(response.payload).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"total": 2,
|
||||
"workpads": Array [
|
||||
Object {
|
||||
"id": 1,
|
||||
"key": "value",
|
||||
},
|
||||
Object {
|
||||
"id": 2,
|
||||
"key": "other-value",
|
||||
},
|
||||
],
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it(`returns 200 with empty results on error`, async () => {
|
||||
(mockRouteContext.core.savedObjects.client.find as jest.Mock).mockImplementationOnce(() => {
|
||||
throw new Error('generic error');
|
||||
});
|
||||
|
||||
const request = httpServerMock.createKibanaRequest({
|
||||
method: 'get',
|
||||
path: `api/canvas/workpad/find`,
|
||||
query: {
|
||||
name: 'something',
|
||||
perPage: 1000,
|
||||
},
|
||||
});
|
||||
|
||||
const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.payload).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"total": 0,
|
||||
"workpads": Array [],
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
60
x-pack/plugins/canvas/server/routes/workpad/find.ts
Normal file
60
x-pack/plugins/canvas/server/routes/workpad/find.ts
Normal file
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { SavedObjectAttributes } from 'src/core/server';
|
||||
import { RouteInitializerDeps } from '../';
|
||||
import {
|
||||
CANVAS_TYPE,
|
||||
API_ROUTE_WORKPAD,
|
||||
} from '../../../../../legacy/plugins/canvas/common/lib/constants';
|
||||
|
||||
export function initializeFindWorkpadsRoute(deps: RouteInitializerDeps) {
|
||||
const { router } = deps;
|
||||
router.get(
|
||||
{
|
||||
path: `${API_ROUTE_WORKPAD}/find`,
|
||||
validate: {
|
||||
query: schema.object({
|
||||
name: schema.string(),
|
||||
page: schema.maybe(schema.number()),
|
||||
perPage: schema.number(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const savedObjectsClient = context.core.savedObjects.client;
|
||||
const { name, page, perPage } = request.query;
|
||||
|
||||
try {
|
||||
const workpads = await savedObjectsClient.find<SavedObjectAttributes>({
|
||||
type: CANVAS_TYPE,
|
||||
sortField: '@timestamp',
|
||||
sortOrder: 'desc',
|
||||
search: name ? `${name}* | ${name}` : '*',
|
||||
searchFields: ['name'],
|
||||
fields: ['id', 'name', '@created', '@timestamp'],
|
||||
page,
|
||||
perPage,
|
||||
});
|
||||
|
||||
return response.ok({
|
||||
body: {
|
||||
total: workpads.total,
|
||||
workpads: workpads.saved_objects.map(hit => ({ id: hit.id, ...hit.attributes })),
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
return response.ok({
|
||||
body: {
|
||||
total: 0,
|
||||
workpads: [],
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
140
x-pack/plugins/canvas/server/routes/workpad/get.test.ts
Normal file
140
x-pack/plugins/canvas/server/routes/workpad/get.test.ts
Normal file
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { CANVAS_TYPE } from '../../../../../legacy/plugins/canvas/common/lib/constants';
|
||||
import { initializeGetWorkpadRoute } from './get';
|
||||
import {
|
||||
IRouter,
|
||||
kibanaResponseFactory,
|
||||
RequestHandlerContext,
|
||||
RequestHandler,
|
||||
} from 'src/core/server';
|
||||
import {
|
||||
savedObjectsClientMock,
|
||||
httpServiceMock,
|
||||
httpServerMock,
|
||||
loggingServiceMock,
|
||||
} from 'src/core/server/mocks';
|
||||
import { workpadWithGroupAsElement } from '../../../../../legacy/plugins/canvas/__tests__/fixtures/workpads';
|
||||
import { CanvasWorkpad } from '../../../../../legacy/plugins/canvas/types';
|
||||
|
||||
const mockRouteContext = ({
|
||||
core: {
|
||||
savedObjects: {
|
||||
client: savedObjectsClientMock.create(),
|
||||
},
|
||||
},
|
||||
} as unknown) as RequestHandlerContext;
|
||||
|
||||
describe('GET workpad', () => {
|
||||
let routeHandler: RequestHandler<any, any, any>;
|
||||
|
||||
beforeEach(() => {
|
||||
const httpService = httpServiceMock.createSetupContract();
|
||||
const router = httpService.createRouter('') as jest.Mocked<IRouter>;
|
||||
initializeGetWorkpadRoute({
|
||||
router,
|
||||
logger: loggingServiceMock.create().get(),
|
||||
});
|
||||
|
||||
routeHandler = router.get.mock.calls[0][1];
|
||||
});
|
||||
|
||||
it(`returns 200 when the workpad is found`, async () => {
|
||||
const request = httpServerMock.createKibanaRequest({
|
||||
method: 'get',
|
||||
path: 'api/canvas/workpad/123',
|
||||
params: {
|
||||
id: '123',
|
||||
},
|
||||
});
|
||||
|
||||
const savedObjectsClient = savedObjectsClientMock.create();
|
||||
savedObjectsClient.get.mockResolvedValueOnce({
|
||||
id: '123',
|
||||
type: CANVAS_TYPE,
|
||||
attributes: { foo: true },
|
||||
references: [],
|
||||
});
|
||||
|
||||
mockRouteContext.core.savedObjects.client = savedObjectsClient;
|
||||
|
||||
const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.payload).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"foo": true,
|
||||
"id": "123",
|
||||
}
|
||||
`);
|
||||
|
||||
expect(savedObjectsClient.get.mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
"canvas-workpad",
|
||||
"123",
|
||||
],
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('corrects elements that should be groups', async () => {
|
||||
const request = httpServerMock.createKibanaRequest({
|
||||
method: 'get',
|
||||
path: 'api/canvas/workpad/123',
|
||||
params: {
|
||||
id: '123',
|
||||
},
|
||||
});
|
||||
|
||||
const savedObjectsClient = savedObjectsClientMock.create();
|
||||
savedObjectsClient.get.mockResolvedValueOnce({
|
||||
id: '123',
|
||||
type: CANVAS_TYPE,
|
||||
attributes: workpadWithGroupAsElement as any,
|
||||
references: [],
|
||||
});
|
||||
|
||||
mockRouteContext.core.savedObjects.client = savedObjectsClient;
|
||||
|
||||
const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory);
|
||||
const workpad = response.payload as CanvasWorkpad;
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(workpad).not.toBeUndefined();
|
||||
|
||||
expect(workpad.pages[0].elements.length).toBe(1);
|
||||
expect(workpad.pages[0].groups.length).toBe(1);
|
||||
});
|
||||
|
||||
it('returns 404 if the workpad is not found', async () => {
|
||||
const id = '123';
|
||||
const request = httpServerMock.createKibanaRequest({
|
||||
method: 'get',
|
||||
path: 'api/canvas/workpad/123',
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
const savedObjectsClient = savedObjectsClientMock.create();
|
||||
savedObjectsClient.get.mockImplementation(() => {
|
||||
throw savedObjectsClient.errors.createGenericNotFoundError(CANVAS_TYPE, id);
|
||||
});
|
||||
mockRouteContext.core.savedObjects.client = savedObjectsClient;
|
||||
|
||||
const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory);
|
||||
|
||||
expect(response.payload).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"error": "Not Found",
|
||||
"message": "Saved object [canvas-workpad/123] not found",
|
||||
"statusCode": 404,
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
65
x-pack/plugins/canvas/server/routes/workpad/get.ts
Normal file
65
x-pack/plugins/canvas/server/routes/workpad/get.ts
Normal file
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { RouteInitializerDeps } from '../';
|
||||
import {
|
||||
CANVAS_TYPE,
|
||||
API_ROUTE_WORKPAD,
|
||||
} from '../../../../../legacy/plugins/canvas/common/lib/constants';
|
||||
import { CanvasWorkpad } from '../../../../../legacy/plugins/canvas/types';
|
||||
import { catchErrorHandler } from '../catch_error_handler';
|
||||
|
||||
export type WorkpadAttributes = Pick<CanvasWorkpad, Exclude<keyof CanvasWorkpad, 'id'>> & {
|
||||
'@timestamp': string;
|
||||
'@created': string;
|
||||
};
|
||||
|
||||
export function initializeGetWorkpadRoute(deps: RouteInitializerDeps) {
|
||||
const { router } = deps;
|
||||
router.get(
|
||||
{
|
||||
path: `${API_ROUTE_WORKPAD}/{id}`,
|
||||
validate: {
|
||||
params: schema.object({
|
||||
id: schema.string(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
catchErrorHandler(async (context, request, response) => {
|
||||
const workpad = await context.core.savedObjects.client.get<WorkpadAttributes>(
|
||||
CANVAS_TYPE,
|
||||
request.params.id
|
||||
);
|
||||
|
||||
if (
|
||||
// not sure if we need to be this defensive
|
||||
workpad.type === 'canvas-workpad' &&
|
||||
workpad.attributes &&
|
||||
workpad.attributes.pages &&
|
||||
workpad.attributes.pages.length
|
||||
) {
|
||||
workpad.attributes.pages.forEach(page => {
|
||||
const elements = (page.elements || []).filter(
|
||||
({ id: pageId }) => !pageId.startsWith('group')
|
||||
);
|
||||
const groups = (page.groups || []).concat(
|
||||
(page.elements || []).filter(({ id: pageId }) => pageId.startsWith('group'))
|
||||
);
|
||||
page.elements = elements;
|
||||
page.groups = groups;
|
||||
});
|
||||
}
|
||||
|
||||
return response.ok({
|
||||
body: {
|
||||
id: workpad.id,
|
||||
...workpad.attributes,
|
||||
},
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
21
x-pack/plugins/canvas/server/routes/workpad/index.ts
Normal file
21
x-pack/plugins/canvas/server/routes/workpad/index.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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { RouteInitializerDeps } from '../';
|
||||
import { initializeFindWorkpadsRoute } from './find';
|
||||
import { initializeGetWorkpadRoute } from './get';
|
||||
import { initializeCreateWorkpadRoute } from './create';
|
||||
import { initializeUpdateWorkpadRoute, initializeUpdateWorkpadAssetsRoute } from './update';
|
||||
import { initializeDeleteWorkpadRoute } from './delete';
|
||||
|
||||
export function initWorkpadRoutes(deps: RouteInitializerDeps) {
|
||||
initializeFindWorkpadsRoute(deps);
|
||||
initializeGetWorkpadRoute(deps);
|
||||
initializeCreateWorkpadRoute(deps);
|
||||
initializeUpdateWorkpadRoute(deps);
|
||||
initializeUpdateWorkpadAssetsRoute(deps);
|
||||
initializeDeleteWorkpadRoute(deps);
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export const okResponse = {
|
||||
ok: true,
|
||||
};
|
223
x-pack/plugins/canvas/server/routes/workpad/update.test.ts
Normal file
223
x-pack/plugins/canvas/server/routes/workpad/update.test.ts
Normal file
|
@ -0,0 +1,223 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import sinon from 'sinon';
|
||||
import { CANVAS_TYPE } from '../../../../../legacy/plugins/canvas/common/lib/constants';
|
||||
import { initializeUpdateWorkpadRoute, initializeUpdateWorkpadAssetsRoute } from './update';
|
||||
import {
|
||||
IRouter,
|
||||
kibanaResponseFactory,
|
||||
RequestHandlerContext,
|
||||
RequestHandler,
|
||||
} from 'src/core/server';
|
||||
import {
|
||||
savedObjectsClientMock,
|
||||
httpServiceMock,
|
||||
httpServerMock,
|
||||
loggingServiceMock,
|
||||
} from 'src/core/server/mocks';
|
||||
import { workpads } from '../../../../../legacy/plugins/canvas/__tests__/fixtures/workpads';
|
||||
import { okResponse } from './ok_response';
|
||||
|
||||
const mockRouteContext = ({
|
||||
core: {
|
||||
savedObjects: {
|
||||
client: savedObjectsClientMock.create(),
|
||||
},
|
||||
},
|
||||
} as unknown) as RequestHandlerContext;
|
||||
|
||||
const workpad = workpads[0];
|
||||
const now = new Date();
|
||||
const nowIso = now.toISOString();
|
||||
|
||||
jest.mock('uuid/v4', () => jest.fn().mockReturnValue('123abc'));
|
||||
|
||||
describe('PUT workpad', () => {
|
||||
let routeHandler: RequestHandler<any, any, any>;
|
||||
let clock: sinon.SinonFakeTimers;
|
||||
|
||||
beforeEach(() => {
|
||||
clock = sinon.useFakeTimers(now);
|
||||
|
||||
const httpService = httpServiceMock.createSetupContract();
|
||||
const router = httpService.createRouter('') as jest.Mocked<IRouter>;
|
||||
initializeUpdateWorkpadRoute({
|
||||
router,
|
||||
logger: loggingServiceMock.create().get(),
|
||||
});
|
||||
|
||||
routeHandler = router.put.mock.calls[0][1];
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it(`returns 200 ok when the workpad is updated`, async () => {
|
||||
const updatedWorkpad = { name: 'new name' };
|
||||
const { id, ...workpadAttributes } = workpad;
|
||||
|
||||
const request = httpServerMock.createKibanaRequest({
|
||||
method: 'put',
|
||||
path: `api/canvas/workpad/${id}`,
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
body: updatedWorkpad,
|
||||
});
|
||||
|
||||
const savedObjectsClient = savedObjectsClientMock.create();
|
||||
savedObjectsClient.get.mockResolvedValueOnce({
|
||||
id,
|
||||
type: CANVAS_TYPE,
|
||||
attributes: workpadAttributes as any,
|
||||
references: [],
|
||||
});
|
||||
|
||||
mockRouteContext.core.savedObjects.client = savedObjectsClient;
|
||||
|
||||
const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.payload).toEqual(okResponse);
|
||||
expect(mockRouteContext.core.savedObjects.client.create).toBeCalledWith(
|
||||
CANVAS_TYPE,
|
||||
{
|
||||
...workpadAttributes,
|
||||
...updatedWorkpad,
|
||||
'@timestamp': nowIso,
|
||||
'@created': workpad['@created'],
|
||||
},
|
||||
{
|
||||
overwrite: true,
|
||||
id,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it(`returns not found if existing workpad is not found`, async () => {
|
||||
const request = httpServerMock.createKibanaRequest({
|
||||
method: 'put',
|
||||
path: 'api/canvas/workpad/some-id',
|
||||
params: {
|
||||
id: 'not-found',
|
||||
},
|
||||
body: {},
|
||||
});
|
||||
|
||||
(mockRouteContext.core.savedObjects.client.get as jest.Mock).mockImplementationOnce(() => {
|
||||
throw mockRouteContext.core.savedObjects.client.errors.createGenericNotFoundError(
|
||||
'not found'
|
||||
);
|
||||
});
|
||||
|
||||
const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory);
|
||||
|
||||
expect(response.status).toBe(404);
|
||||
});
|
||||
|
||||
it(`returns bad request if the write fails`, async () => {
|
||||
const request = httpServerMock.createKibanaRequest({
|
||||
method: 'put',
|
||||
path: 'api/canvas/workpad/some-id',
|
||||
params: {
|
||||
id: 'some-id',
|
||||
},
|
||||
body: {},
|
||||
});
|
||||
|
||||
const savedObjectsClient = savedObjectsClientMock.create();
|
||||
savedObjectsClient.get.mockResolvedValueOnce({
|
||||
id: 'some-id',
|
||||
type: CANVAS_TYPE,
|
||||
attributes: {},
|
||||
references: [],
|
||||
});
|
||||
|
||||
mockRouteContext.core.savedObjects.client = savedObjectsClient;
|
||||
|
||||
(mockRouteContext.core.savedObjects.client.create as jest.Mock).mockImplementationOnce(() => {
|
||||
throw mockRouteContext.core.savedObjects.client.errors.createBadRequestError('bad request');
|
||||
});
|
||||
|
||||
const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory);
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
});
|
||||
});
|
||||
|
||||
describe('update assets', () => {
|
||||
let routeHandler: RequestHandler<any, any, any>;
|
||||
let clock: sinon.SinonFakeTimers;
|
||||
|
||||
beforeEach(() => {
|
||||
clock = sinon.useFakeTimers(now);
|
||||
const httpService = httpServiceMock.createSetupContract();
|
||||
const router = httpService.createRouter('') as jest.Mocked<IRouter>;
|
||||
initializeUpdateWorkpadAssetsRoute({
|
||||
router,
|
||||
logger: loggingServiceMock.create().get(),
|
||||
});
|
||||
|
||||
routeHandler = router.put.mock.calls[0][1];
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
it('updates assets', async () => {
|
||||
const { id, ...attributes } = workpad;
|
||||
const assets = {
|
||||
'asset-1': {
|
||||
'@created': new Date().toISOString(),
|
||||
id: 'asset-1',
|
||||
type: 'asset',
|
||||
value: 'some-url-encoded-asset',
|
||||
},
|
||||
'asset-2': {
|
||||
'@created': new Date().toISOString(),
|
||||
id: 'asset-2',
|
||||
type: 'asset',
|
||||
value: 'some-other asset',
|
||||
},
|
||||
};
|
||||
|
||||
const request = httpServerMock.createKibanaRequest({
|
||||
method: 'put',
|
||||
path: 'api/canvas/workpad-assets/some-id',
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
body: assets,
|
||||
});
|
||||
|
||||
(mockRouteContext.core.savedObjects.client.get as jest.Mock).mockResolvedValueOnce({
|
||||
id,
|
||||
type: CANVAS_TYPE,
|
||||
attributes: attributes as any,
|
||||
references: [],
|
||||
});
|
||||
|
||||
const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory);
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
expect(mockRouteContext.core.savedObjects.client.create).toBeCalledWith(
|
||||
CANVAS_TYPE,
|
||||
{
|
||||
...attributes,
|
||||
'@timestamp': nowIso,
|
||||
assets,
|
||||
},
|
||||
{
|
||||
id,
|
||||
overwrite: true,
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
129
x-pack/plugins/canvas/server/routes/workpad/update.ts
Normal file
129
x-pack/plugins/canvas/server/routes/workpad/update.ts
Normal file
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { schema, TypeOf } from '@kbn/config-schema';
|
||||
import { omit } from 'lodash';
|
||||
import { KibanaResponseFactory } from 'src/core/server';
|
||||
import { SavedObjectsClientContract } from 'src/core/server';
|
||||
import { RouteInitializerDeps } from '../';
|
||||
import {
|
||||
CANVAS_TYPE,
|
||||
API_ROUTE_WORKPAD,
|
||||
API_ROUTE_WORKPAD_STRUCTURES,
|
||||
API_ROUTE_WORKPAD_ASSETS,
|
||||
} from '../../../../../legacy/plugins/canvas/common/lib/constants';
|
||||
import { CanvasWorkpad } from '../../../../../legacy/plugins/canvas/types';
|
||||
import { WorkpadSchema, WorkpadAssetSchema } from './workpad_schema';
|
||||
import { okResponse } from './ok_response';
|
||||
import { catchErrorHandler } from '../catch_error_handler';
|
||||
|
||||
export type WorkpadAttributes = Pick<CanvasWorkpad, Exclude<keyof CanvasWorkpad, 'id'>> & {
|
||||
'@timestamp': string;
|
||||
'@created': string;
|
||||
};
|
||||
|
||||
const AssetsRecordSchema = schema.recordOf(schema.string(), WorkpadAssetSchema);
|
||||
|
||||
const AssetPayloadSchema = schema.object({
|
||||
assets: AssetsRecordSchema,
|
||||
});
|
||||
|
||||
const workpadUpdateHandler = async (
|
||||
payload: TypeOf<typeof WorkpadSchema> | TypeOf<typeof AssetPayloadSchema>,
|
||||
id: string,
|
||||
savedObjectsClient: SavedObjectsClientContract,
|
||||
response: KibanaResponseFactory
|
||||
) => {
|
||||
const now = new Date().toISOString();
|
||||
|
||||
const workpadObject = await savedObjectsClient.get<WorkpadAttributes>(CANVAS_TYPE, id);
|
||||
await savedObjectsClient.create<WorkpadAttributes>(
|
||||
CANVAS_TYPE,
|
||||
{
|
||||
...workpadObject.attributes,
|
||||
...omit(payload, 'id'), // never write the id property
|
||||
'@timestamp': now, // always update the modified time
|
||||
'@created': workpadObject.attributes['@created'], // ensure created is not modified
|
||||
},
|
||||
{ overwrite: true, id }
|
||||
);
|
||||
|
||||
return response.ok({
|
||||
body: okResponse,
|
||||
});
|
||||
};
|
||||
|
||||
export function initializeUpdateWorkpadRoute(deps: RouteInitializerDeps) {
|
||||
const { router } = deps;
|
||||
// TODO: This route is likely deprecated and everything is using the workpad_structures
|
||||
// path instead. Investigate further.
|
||||
router.put(
|
||||
{
|
||||
path: `${API_ROUTE_WORKPAD}/{id}`,
|
||||
validate: {
|
||||
params: schema.object({
|
||||
id: schema.string(),
|
||||
}),
|
||||
body: WorkpadSchema,
|
||||
},
|
||||
},
|
||||
catchErrorHandler(async (context, request, response) => {
|
||||
return workpadUpdateHandler(
|
||||
request.body,
|
||||
request.params.id,
|
||||
context.core.savedObjects.client,
|
||||
response
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
router.put(
|
||||
{
|
||||
path: `${API_ROUTE_WORKPAD_STRUCTURES}/{id}`,
|
||||
validate: {
|
||||
params: schema.object({
|
||||
id: schema.string(),
|
||||
}),
|
||||
body: WorkpadSchema,
|
||||
},
|
||||
},
|
||||
catchErrorHandler(async (context, request, response) => {
|
||||
return workpadUpdateHandler(
|
||||
request.body,
|
||||
request.params.id,
|
||||
context.core.savedObjects.client,
|
||||
response
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export function initializeUpdateWorkpadAssetsRoute(deps: RouteInitializerDeps) {
|
||||
const { router } = deps;
|
||||
|
||||
router.put(
|
||||
{
|
||||
path: `${API_ROUTE_WORKPAD_ASSETS}/{id}`,
|
||||
validate: {
|
||||
params: schema.object({
|
||||
id: schema.string(),
|
||||
}),
|
||||
// ToDo: Currently the validation must be a schema.object
|
||||
// Because we don't know what keys the assets will have, we have to allow
|
||||
// unknowns and then validate in the handler
|
||||
body: schema.object({}, { allowUnknowns: true }),
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
return workpadUpdateHandler(
|
||||
{ assets: AssetsRecordSchema.validate(request.body) },
|
||||
request.params.id,
|
||||
context.core.savedObjects.client,
|
||||
response
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
|
||||
export const PositionSchema = schema.object({
|
||||
angle: schema.number(),
|
||||
height: schema.number(),
|
||||
left: schema.number(),
|
||||
parent: schema.nullable(schema.string()),
|
||||
top: schema.number(),
|
||||
width: schema.number(),
|
||||
});
|
||||
|
||||
export const WorkpadElementSchema = schema.object({
|
||||
expression: schema.string(),
|
||||
filter: schema.maybe(schema.nullable(schema.string())),
|
||||
id: schema.string(),
|
||||
position: PositionSchema,
|
||||
});
|
||||
|
||||
export const WorkpadPageSchema = schema.object({
|
||||
elements: schema.arrayOf(WorkpadElementSchema),
|
||||
groups: schema.arrayOf(
|
||||
schema.object({
|
||||
id: schema.string(),
|
||||
position: PositionSchema,
|
||||
})
|
||||
),
|
||||
id: schema.string(),
|
||||
style: schema.recordOf(schema.string(), schema.string()),
|
||||
transition: schema.maybe(
|
||||
schema.oneOf([
|
||||
schema.object({}),
|
||||
schema.object({
|
||||
name: schema.string(),
|
||||
}),
|
||||
])
|
||||
),
|
||||
});
|
||||
|
||||
export const WorkpadAssetSchema = schema.object({
|
||||
'@created': schema.string(),
|
||||
id: schema.string(),
|
||||
type: schema.string(),
|
||||
value: schema.string(),
|
||||
});
|
||||
|
||||
export const WorkpadSchema = schema.object({
|
||||
'@created': schema.maybe(schema.string()),
|
||||
'@timestamp': schema.maybe(schema.string()),
|
||||
assets: schema.maybe(schema.recordOf(schema.string(), WorkpadAssetSchema)),
|
||||
colors: schema.arrayOf(schema.string()),
|
||||
css: schema.string(),
|
||||
height: schema.number(),
|
||||
id: schema.string(),
|
||||
isWriteable: schema.maybe(schema.boolean()),
|
||||
name: schema.string(),
|
||||
page: schema.number(),
|
||||
pages: schema.arrayOf(WorkpadPageSchema),
|
||||
width: schema.number(),
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue