From 5ec3a276be0f6a80d5ccf052c0a48fe7e8f1bb31 Mon Sep 17 00:00:00 2001 From: Catherine Liu Date: Tue, 20 Jun 2023 08:27:46 -0700 Subject: [PATCH] [Canvas] Versioned and internal routes (#159426) ## Summary Closes #157101 and #157083. This refactors all Canvas routes to use the versioned router for BWC and makes access for all routes internal only. I also removed the `es_fields` routes because the last usage of that route was removed in https://github.com/elastic/kibana/pull/139610. All routes have been changed from `/api/canvas/*` to `/internal/canvas/*`. ### Checklist Delete any items that are not applicable to this PR. - [ ] 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/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [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 - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### Risk Matrix Delete this section if it is not applicable to this PR. Before closing this PR, invite QA, stakeholders, and other developers to identify risks that should be tested prior to the change/feature release. When forming the risk matrix, consider some of the following examples and how they may potentially impact the change: | Risk | Probability | Severity | Mitigation/Notes | |---------------------------|-------------|----------|-------------------------| | Multiple Spaces—unexpected behavior in non-default Kibana Space. | Low | High | Integration tests will verify that all features are still supported in non-default Kibana Space and when user switches between spaces. | | Multiple nodes—Elasticsearch polling might have race conditions when multiple Kibana nodes are polling for the same tasks. | High | Low | Tasks are idempotent, so executing them multiple times will not result in logical error, but will degrade performance. To test for this case we add plenty of unit tests around this logic and document manual testing procedure. | | Code should gracefully handle cases when feature X or plugin Y are disabled. | Medium | High | Unit tests will verify that any feature flag or plugin combination still results in our service operational. | | [See more potential risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) | ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --- x-pack/plugins/canvas/common/lib/constants.ts | 14 +- .../public/services/kibana/custom_element.ts | 11 +- .../canvas/public/services/kibana/platform.ts | 5 - .../canvas/public/services/kibana/workpad.ts | 20 +- .../canvas/public/services/platform.ts | 7 - .../canvas/public/services/stubs/platform.ts | 2 - .../canvas/public/setup_expressions.ts | 4 +- x-pack/plugins/canvas/public/store.ts | 5 +- .../routes/custom_elements/create.test.ts | 3 +- .../server/routes/custom_elements/create.ts | 55 +++--- .../routes/custom_elements/delete.test.ts | 3 +- .../server/routes/custom_elements/delete.ts | 34 ++-- .../routes/custom_elements/find.test.ts | 3 +- .../server/routes/custom_elements/find.ts | 107 ++++++----- .../server/routes/custom_elements/get.test.ts | 3 +- .../server/routes/custom_elements/get.ts | 49 ++--- .../routes/custom_elements/update.test.ts | 3 +- .../server/routes/custom_elements/update.ts | 73 ++++---- .../server/routes/es_fields/es_fields.test.ts | 171 ------------------ .../server/routes/es_fields/es_fields.ts | 58 ------ .../canvas/server/routes/es_fields/index.ts | 13 -- .../server/routes/functions/functions.test.ts | 3 +- .../server/routes/functions/functions.ts | 13 +- x-pack/plugins/canvas/server/routes/index.ts | 2 - .../server/routes/shareables/download.test.ts | 3 +- .../server/routes/shareables/download.ts | 13 +- .../server/routes/shareables/zip.test.ts | 3 +- .../canvas/server/routes/shareables/zip.ts | 32 ++-- .../server/routes/templates/list.test.ts | 3 +- .../canvas/server/routes/templates/list.ts | 53 +++--- .../server/routes/workpad/create.test.ts | 3 +- .../canvas/server/routes/workpad/create.ts | 51 +++--- .../server/routes/workpad/delete.test.ts | 3 +- .../canvas/server/routes/workpad/delete.ts | 33 ++-- .../canvas/server/routes/workpad/find.test.ts | 3 +- .../canvas/server/routes/workpad/find.ts | 83 +++++---- .../canvas/server/routes/workpad/get.test.ts | 3 +- .../canvas/server/routes/workpad/get.ts | 45 +++-- .../canvas/server/routes/workpad/import.ts | 35 ++-- .../server/routes/workpad/resolve.test.ts | 3 +- .../canvas/server/routes/workpad/resolve.ts | 59 +++--- .../server/routes/workpad/update.test.ts | 6 +- .../canvas/server/routes/workpad/update.ts | 137 ++++++++------ 43 files changed, 554 insertions(+), 678 deletions(-) delete mode 100644 x-pack/plugins/canvas/server/routes/es_fields/es_fields.test.ts delete mode 100644 x-pack/plugins/canvas/server/routes/es_fields/es_fields.ts delete mode 100644 x-pack/plugins/canvas/server/routes/es_fields/index.ts diff --git a/x-pack/plugins/canvas/common/lib/constants.ts b/x-pack/plugins/canvas/common/lib/constants.ts index fd9efafec10a3..fdc6df4c60193 100644 --- a/x-pack/plugins/canvas/common/lib/constants.ts +++ b/x-pack/plugins/canvas/common/lib/constants.ts @@ -15,14 +15,14 @@ export const TEMPLATE_TYPE = `${CANVAS_TYPE}-template`; export const CANVAS_APP = 'canvas'; export const APP_ROUTE = '/app/canvas'; export const APP_ROUTE_WORKPAD = `${APP_ROUTE}#/workpad`; -export const API_ROUTE = '/api/canvas'; -export const API_ROUTE_WORKPAD = `${API_ROUTE}/workpad`; +export const INTERNAL_API_ROUTE = '/internal/canvas'; +export const API_ROUTE_WORKPAD = `${INTERNAL_API_ROUTE}/workpad`; export const API_ROUTE_WORKPAD_EXPORT = `${API_ROUTE_WORKPAD}/export`; export const API_ROUTE_WORKPAD_IMPORT = `${API_ROUTE_WORKPAD}/import`; -export const API_ROUTE_WORKPAD_ASSETS = `${API_ROUTE}/workpad-assets`; -export const API_ROUTE_WORKPAD_STRUCTURES = `${API_ROUTE}/workpad-structures`; -export const API_ROUTE_CUSTOM_ELEMENT = `${API_ROUTE}/custom-element`; -export const API_ROUTE_TEMPLATES = `${API_ROUTE}/templates`; +export const API_ROUTE_WORKPAD_ASSETS = `${INTERNAL_API_ROUTE}/workpad-assets`; +export const API_ROUTE_WORKPAD_STRUCTURES = `${INTERNAL_API_ROUTE}/workpad-structures`; +export const API_ROUTE_CUSTOM_ELEMENT = `${INTERNAL_API_ROUTE}/custom-element`; +export const API_ROUTE_TEMPLATES = `${INTERNAL_API_ROUTE}/templates`; export const LOCALSTORAGE_PREFIX = `kibana.canvas`; export const LOCALSTORAGE_CLIPBOARD = `${LOCALSTORAGE_PREFIX}.clipboard`; export const SESSIONSTORAGE_LASTPATH = 'lastPath:canvas'; @@ -48,5 +48,5 @@ export const API_ROUTE_SHAREABLE_RUNTIME = '/public/canvas/runtime'; export const API_ROUTE_SHAREABLE_RUNTIME_DOWNLOAD = `/public/canvas/${SHAREABLE_RUNTIME_NAME}.js`; export const CANVAS_EMBEDDABLE_CLASSNAME = `canvasEmbeddable`; export const CONTEXT_MENU_TOP_BORDER_CLASSNAME = 'canvasContextMenu--topBorder'; -export const API_ROUTE_FUNCTIONS = `${API_ROUTE}/fns`; +export const API_ROUTE_FUNCTIONS = `${INTERNAL_API_ROUTE}/fns`; export const HEADER_BANNER_HEIGHT = 32; // This value is also declared in `/src/core/public/_variables.scss` diff --git a/x-pack/plugins/canvas/public/services/kibana/custom_element.ts b/x-pack/plugins/canvas/public/services/kibana/custom_element.ts index 4f580330ef7c2..a548ccdc23b2f 100644 --- a/x-pack/plugins/canvas/public/services/kibana/custom_element.ts +++ b/x-pack/plugins/canvas/public/services/kibana/custom_element.ts @@ -22,19 +22,22 @@ export const customElementServiceFactory: CanvasCustomElementServiceFactory = ({ const apiPath = `${API_ROUTE_CUSTOM_ELEMENT}`; return { - create: (customElement) => http.post(apiPath, { body: JSON.stringify(customElement) }), + create: (customElement) => + http.post(apiPath, { body: JSON.stringify(customElement), version: '1' }), get: (customElementId) => http - .get<{ data: CustomElement }>(`${apiPath}/${customElementId}`) + .get<{ data: CustomElement }>(`${apiPath}/${customElementId}`, { version: '1' }) .then(({ data: element }) => element), - update: (id, element) => http.put(`${apiPath}/${id}`, { body: JSON.stringify(element) }), - remove: (id) => http.delete(`${apiPath}/${id}`), + update: (id, element) => + http.put(`${apiPath}/${id}`, { body: JSON.stringify(element), version: '1' }), + remove: (id) => http.delete(`${apiPath}/${id}`, { version: '1' }), find: async (name) => { return http.get(`${apiPath}/find`, { query: { name, perPage: 10000, }, + version: '1', }); }, }; diff --git a/x-pack/plugins/canvas/public/services/kibana/platform.ts b/x-pack/plugins/canvas/public/services/kibana/platform.ts index ab5fb175bd6d1..f88851a5e3bd0 100644 --- a/x-pack/plugins/canvas/public/services/kibana/platform.ts +++ b/x-pack/plugins/canvas/public/services/kibana/platform.ts @@ -39,11 +39,6 @@ export const platformServiceFactory: CanvaPlatformServiceFactory = ({ setFullscreen: coreStart.chrome.setIsVisible, redirectLegacyUrl: startPlugins.spaces?.ui.redirectLegacyUrl, getLegacyUrlConflict: startPlugins.spaces?.ui.components.getLegacyUrlConflict, - - // TODO: these should go away. We want thin accessors, not entire objects. - // Entire objects are hard to mock, and hide our dependency on the external service. - getSavedObjects: () => coreStart.savedObjects, - getSavedObjectsClient: () => coreStart.savedObjects.client, getUISettings: () => coreStart.uiSettings, getHttp: () => coreStart.http, getSavedObjectsManagement: () => startPlugins.savedObjectsManagement, diff --git a/x-pack/plugins/canvas/public/services/kibana/workpad.ts b/x-pack/plugins/canvas/public/services/kibana/workpad.ts index 9a8a5f1d9a428..3b70244440cc8 100644 --- a/x-pack/plugins/canvas/public/services/kibana/workpad.ts +++ b/x-pack/plugins/canvas/public/services/kibana/workpad.ts @@ -64,13 +64,14 @@ export const workpadServiceFactory: CanvasWorkpadServiceFactory = ({ coreStart, return { get: async (id: string) => { - const workpad = await coreStart.http.get(`${getApiPath()}/${id}`); + const workpad = await coreStart.http.get(`${getApiPath()}/${id}`, { version: '1' }); return { css: DEFAULT_WORKPAD_CSS, variables: [], ...workpad }; }, export: async (id: string) => { const workpad = await coreStart.http.get>( - `${getApiPath()}/export/${id}` + `${getApiPath()}/export/${id}`, + { version: '1' } ); const { attributes } = workpad; @@ -85,7 +86,8 @@ export const workpadServiceFactory: CanvasWorkpadServiceFactory = ({ coreStart, }, resolve: async (id: string) => { const { workpad, ...resolveProps } = await coreStart.http.get( - `${getApiPath()}/resolve/${id}` + `${getApiPath()}/resolve/${id}`, + { version: '1' } ); return { @@ -106,6 +108,7 @@ export const workpadServiceFactory: CanvasWorkpadServiceFactory = ({ coreStart, assets: workpad.assets || {}, variables: workpad.variables || [], }), + version: '1', }); }, import: (workpad: CanvasWorkpad) => @@ -115,13 +118,15 @@ export const workpadServiceFactory: CanvasWorkpadServiceFactory = ({ coreStart, assets: workpad.assets || {}, variables: workpad.variables || [], }), + version: '1', }), createFromTemplate: (templateId: string) => { return coreStart.http.post(getApiPath(), { body: JSON.stringify({ templateId }), + version: '1', }); }, - findTemplates: async () => coreStart.http.get(API_ROUTE_TEMPLATES), + findTemplates: async () => coreStart.http.get(API_ROUTE_TEMPLATES, { version: '1' }), find: (searchTerm: string) => { // TODO: this shouldn't be necessary. Check for usage. const validSearchTerm = typeof searchTerm === 'string' && searchTerm.length > 0; @@ -131,29 +136,34 @@ export const workpadServiceFactory: CanvasWorkpadServiceFactory = ({ coreStart, perPage: 10000, name: validSearchTerm ? searchTerm : '', }, + version: '1', }); }, remove: (id: string) => { - return coreStart.http.delete(`${getApiPath()}/${id}`); + return coreStart.http.delete(`${getApiPath()}/${id}`, { version: '1' }); }, update: (id, workpad) => { return coreStart.http.put(`${getApiPath()}/${id}`, { body: JSON.stringify({ ...sanitizeWorkpad({ ...workpad }) }), + version: '1', }); }, updateWorkpad: (id, workpad) => { return coreStart.http.put(`${API_ROUTE_WORKPAD_STRUCTURES}/${id}`, { body: JSON.stringify({ ...sanitizeWorkpad({ ...workpad }) }), + version: '1', }); }, updateAssets: (id, assets) => { return coreStart.http.put(`${API_ROUTE_WORKPAD_ASSETS}/${id}`, { body: JSON.stringify(assets), + version: '1', }); }, getRuntimeZip: (workpad) => { return coreStart.http.post(API_ROUTE_SHAREABLE_ZIP, { body: JSON.stringify(workpad), + version: '1', }); }, }; diff --git a/x-pack/plugins/canvas/public/services/platform.ts b/x-pack/plugins/canvas/public/services/platform.ts index 77649a8fa0592..4eeaa8da8f1dc 100644 --- a/x-pack/plugins/canvas/public/services/platform.ts +++ b/x-pack/plugins/canvas/public/services/platform.ts @@ -7,8 +7,6 @@ import { Observable } from 'rxjs'; import { - SavedObjectsStart, - SavedObjectsClientContract, IUiSettingsClient, ChromeBreadcrumb, IBasePath, @@ -33,11 +31,6 @@ export interface CanvasPlatformService { setFullscreen: ChromeStart['setIsVisible']; redirectLegacyUrl?: SpacesPluginStart['ui']['redirectLegacyUrl']; getLegacyUrlConflict?: SpacesPluginStart['ui']['components']['getLegacyUrlConflict']; - - // TODO: these should go away. We want thin accessors, not entire objects. - // Entire objects are hard to mock, and hide our dependency on the external service. - getSavedObjects: () => SavedObjectsStart; - getSavedObjectsClient: () => SavedObjectsClientContract; getUISettings: () => IUiSettingsClient; getHttp: () => HttpStart; getSavedObjectsManagement: () => SavedObjectsManagementPluginStart; diff --git a/x-pack/plugins/canvas/public/services/stubs/platform.ts b/x-pack/plugins/canvas/public/services/stubs/platform.ts index cdb75dc96322b..3e40352f9ef59 100644 --- a/x-pack/plugins/canvas/public/services/stubs/platform.ts +++ b/x-pack/plugins/canvas/public/services/stubs/platform.ts @@ -30,8 +30,6 @@ export const platformServiceFactory: CanvasPlatformServiceFactory = () => ({ hasHeaderBanner$: noop, setBreadcrumbs: noop, setRecentlyAccessed: noop, - getSavedObjects: noop, - getSavedObjectsClient: noop, getUISettings: noop, setFullscreen: noop, redirectLegacyUrl: noop, diff --git a/x-pack/plugins/canvas/public/setup_expressions.ts b/x-pack/plugins/canvas/public/setup_expressions.ts index 2098ad62ad756..655daef80f856 100644 --- a/x-pack/plugins/canvas/public/setup_expressions.ts +++ b/x-pack/plugins/canvas/public/setup_expressions.ts @@ -26,7 +26,9 @@ export const setupExpressions = async ({ const loadServerFunctionWrappers = async () => { if (!cached) { cached = (async () => { - const serverFunctionList = await coreSetup.http.get(API_ROUTE_FUNCTIONS); + const serverFunctionList = await coreSetup.http.get(API_ROUTE_FUNCTIONS, { + version: '1', + }); const batchedFunction = bfetch.batchedFunction({ url: API_ROUTE_FUNCTIONS }); const { serialize } = serializeProvider(expressions.getTypes()); diff --git a/x-pack/plugins/canvas/public/store.ts b/x-pack/plugins/canvas/public/store.ts index 83e36a41da322..abd29b246b60a 100644 --- a/x-pack/plugins/canvas/public/store.ts +++ b/x-pack/plugins/canvas/public/store.ts @@ -32,7 +32,10 @@ export async function createFreshStore(core: CoreSetup) { const basePath = core.http.basePath.get(); // Retrieve server functions - const serverFunctionsResponse = await core.http.get>(API_ROUTE_FUNCTIONS); + const serverFunctionsResponse = await core.http.get>( + API_ROUTE_FUNCTIONS, + { version: '1' } + ); const serverFunctions = Object.values(serverFunctionsResponse); initialState.app = { diff --git a/x-pack/plugins/canvas/server/routes/custom_elements/create.test.ts b/x-pack/plugins/canvas/server/routes/custom_elements/create.test.ts index 72f449098de98..036f57baf98ee 100644 --- a/x-pack/plugins/canvas/server/routes/custom_elements/create.test.ts +++ b/x-pack/plugins/canvas/server/routes/custom_elements/create.test.ts @@ -44,7 +44,8 @@ describe('POST custom element', () => { const routerDeps = getMockedRouterDeps(); initializeCreateCustomElementRoute(routerDeps); - routeHandler = routerDeps.router.post.mock.calls[0][1]; + routeHandler = + routerDeps.router.versioned.post.mock.results[0].value.addVersion.mock.calls[0][1]; }); afterEach(() => { diff --git a/x-pack/plugins/canvas/server/routes/custom_elements/create.ts b/x-pack/plugins/canvas/server/routes/custom_elements/create.ts index 8164c124e27dc..e0ea4be8d35f1 100644 --- a/x-pack/plugins/canvas/server/routes/custom_elements/create.ts +++ b/x-pack/plugins/canvas/server/routes/custom_elements/create.ts @@ -15,39 +15,44 @@ import { catchErrorHandler } from '../catch_error_handler'; export function initializeCreateCustomElementRoute(deps: RouteInitializerDeps) { const { router } = deps; - router.post( - { + router.versioned + .post({ path: `${API_ROUTE_CUSTOM_ELEMENT}`, - validate: { - body: CustomElementSchema, - }, options: { body: { maxBytes: 26214400, // 25MB payload limit accepts: ['application/json'], }, }, - }, - catchErrorHandler(async (context, request, response) => { - const customElement = request.body; + access: 'internal', + }) + .addVersion( + { + version: '1', + validate: { + request: { body: CustomElementSchema }, + }, + }, + catchErrorHandler(async (context, request, response) => { + const customElement = request.body; - const now = new Date().toISOString(); - const { id, ...payload } = customElement; + const now = new Date().toISOString(); + const { id, ...payload } = customElement; - const soClient = (await context.core).savedObjects.client; - await soClient.create( - CUSTOM_ELEMENT_TYPE, - { - ...payload, - '@timestamp': now, - '@created': now, - }, - { id: id || getId('custom-element') } - ); + const soClient = (await context.core).savedObjects.client; + await soClient.create( + CUSTOM_ELEMENT_TYPE, + { + ...payload, + '@timestamp': now, + '@created': now, + }, + { id: id || getId('custom-element') } + ); - return response.ok({ - body: okResponse, - }); - }) - ); + return response.ok({ + body: okResponse, + }); + }) + ); } diff --git a/x-pack/plugins/canvas/server/routes/custom_elements/delete.test.ts b/x-pack/plugins/canvas/server/routes/custom_elements/delete.test.ts index 72b6d3dbdb9ef..1e6eaaee78017 100644 --- a/x-pack/plugins/canvas/server/routes/custom_elements/delete.test.ts +++ b/x-pack/plugins/canvas/server/routes/custom_elements/delete.test.ts @@ -32,7 +32,8 @@ describe('DELETE custom element', () => { const routerDeps = getMockedRouterDeps(); initializeDeleteCustomElementRoute(routerDeps); - routeHandler = routerDeps.router.delete.mock.calls[0][1]; + routeHandler = + routerDeps.router.versioned.delete.mock.results[0].value.addVersion.mock.calls[0][1]; }); it(`returns 200 ok when the custom element is deleted`, async () => { diff --git a/x-pack/plugins/canvas/server/routes/custom_elements/delete.ts b/x-pack/plugins/canvas/server/routes/custom_elements/delete.ts index 60092f2a721f9..94ea9cda5e367 100644 --- a/x-pack/plugins/canvas/server/routes/custom_elements/delete.ts +++ b/x-pack/plugins/canvas/server/routes/custom_elements/delete.ts @@ -13,19 +13,27 @@ import { catchErrorHandler } from '../catch_error_handler'; export function initializeDeleteCustomElementRoute(deps: RouteInitializerDeps) { const { router } = deps; - router.delete( - { + router.versioned + .delete({ path: `${API_ROUTE_CUSTOM_ELEMENT}/{id}`, - validate: { - params: schema.object({ - id: schema.string(), - }), - }, - }, - catchErrorHandler(async (context, request, response) => { - const soClient = (await context.core).savedObjects.client; - await soClient.delete(CUSTOM_ELEMENT_TYPE, request.params.id); - return response.ok({ body: okResponse }); + + access: 'internal', }) - ); + .addVersion( + { + version: '1', + validate: { + request: { + params: schema.object({ + id: schema.string(), + }), + }, + }, + }, + catchErrorHandler(async (context, request, response) => { + const soClient = (await context.core).savedObjects.client; + await soClient.delete(CUSTOM_ELEMENT_TYPE, request.params.id); + return response.ok({ body: okResponse }); + }) + ); } diff --git a/x-pack/plugins/canvas/server/routes/custom_elements/find.test.ts b/x-pack/plugins/canvas/server/routes/custom_elements/find.test.ts index 65a9d528a849d..b6943b6bacb1d 100644 --- a/x-pack/plugins/canvas/server/routes/custom_elements/find.test.ts +++ b/x-pack/plugins/canvas/server/routes/custom_elements/find.test.ts @@ -26,7 +26,8 @@ describe('Find custom element', () => { const routerDeps = getMockedRouterDeps(); initializeFindCustomElementsRoute(routerDeps); - routeHandler = routerDeps.router.get.mock.calls[0][1]; + routeHandler = + routerDeps.router.versioned.get.mock.results[0].value.addVersion.mock.calls[0][1]; }); it(`returns 200 with the found custom elements`, async () => { diff --git a/x-pack/plugins/canvas/server/routes/custom_elements/find.ts b/x-pack/plugins/canvas/server/routes/custom_elements/find.ts index 0f7f492cef87c..a45eb217cd9cb 100644 --- a/x-pack/plugins/canvas/server/routes/custom_elements/find.ts +++ b/x-pack/plugins/canvas/server/routes/custom_elements/find.ts @@ -12,59 +12,66 @@ import { CUSTOM_ELEMENT_TYPE, API_ROUTE_CUSTOM_ELEMENT } from '../../../common/l export function initializeFindCustomElementsRoute(deps: RouteInitializerDeps) { const { router } = deps; - router.get( - { + router.versioned + .get({ path: `${API_ROUTE_CUSTOM_ELEMENT}/find`, - validate: { - query: schema.object({ - name: schema.string(), - page: schema.maybe(schema.number()), - perPage: schema.number(), - }), + access: 'internal', + }) + .addVersion( + { + version: '1', + validate: { + request: { + query: schema.object({ + name: schema.string(), + page: schema.maybe(schema.number()), + perPage: schema.number(), + }), + }, + }, }, - }, - async (context, request, response) => { - const savedObjectsClient = (await context.core).savedObjects.client; - const { name, page, perPage } = request.query; + async (context, request, response) => { + const savedObjectsClient = (await context.core).savedObjects.client; + const { name, page, perPage } = request.query; - try { - const customElements = await savedObjectsClient.find({ - type: CUSTOM_ELEMENT_TYPE, - sortField: '@timestamp', - sortOrder: 'desc', - search: name ? `${name}* | ${name}` : '*', - searchFields: ['name'], - fields: [ - 'id', - 'name', - 'displayName', - 'help', - 'image', - 'content', - '@created', - '@timestamp', - ], - page, - perPage, - }); + try { + const customElements = await savedObjectsClient.find({ + type: CUSTOM_ELEMENT_TYPE, + sortField: '@timestamp', + sortOrder: 'desc', + search: name ? `${name}* | ${name}` : '*', + searchFields: ['name'], + fields: [ + 'id', + 'name', + 'displayName', + 'help', + 'image', + 'content', + '@created', + '@timestamp', + ], + page, + perPage, + }); - return response.ok({ - body: { - total: customElements.total, - customElements: customElements.saved_objects.map((hit) => ({ - id: hit.id, - ...hit.attributes, - })), - }, - }); - } catch (error) { - return response.ok({ - body: { - total: 0, - customElements: [], - }, - }); + return response.ok({ + body: { + total: customElements.total, + customElements: customElements.saved_objects.map((hit) => ({ + id: hit.id, + ...hit.attributes, + })), + }, + }); + } catch (error) { + return response.ok({ + body: { + total: 0, + customElements: [], + }, + }); + } } - } - ); + ); } diff --git a/x-pack/plugins/canvas/server/routes/custom_elements/get.test.ts b/x-pack/plugins/canvas/server/routes/custom_elements/get.test.ts index ac3081ea850d2..a2fb7c78c3563 100644 --- a/x-pack/plugins/canvas/server/routes/custom_elements/get.test.ts +++ b/x-pack/plugins/canvas/server/routes/custom_elements/get.test.ts @@ -32,7 +32,8 @@ describe('GET custom element', () => { const routerDeps = getMockedRouterDeps(); initializeGetCustomElementRoute(routerDeps); - routeHandler = routerDeps.router.get.mock.calls[0][1]; + routeHandler = + routerDeps.router.versioned.get.mock.results[0].value.addVersion.mock.calls[0][1]; }); it(`returns 200 when the custom element is found`, async () => { diff --git a/x-pack/plugins/canvas/server/routes/custom_elements/get.ts b/x-pack/plugins/canvas/server/routes/custom_elements/get.ts index 06696ca017ca8..4775d4cb497fb 100644 --- a/x-pack/plugins/canvas/server/routes/custom_elements/get.ts +++ b/x-pack/plugins/canvas/server/routes/custom_elements/get.ts @@ -13,28 +13,35 @@ import { catchErrorHandler } from '../catch_error_handler'; export function initializeGetCustomElementRoute(deps: RouteInitializerDeps) { const { router } = deps; - router.get( - { + router.versioned + .get({ path: `${API_ROUTE_CUSTOM_ELEMENT}/{id}`, - validate: { - params: schema.object({ - id: schema.string(), - }), + access: 'internal', + }) + .addVersion( + { + version: '1', + validate: { + request: { + params: schema.object({ + id: schema.string(), + }), + }, + }, }, - }, - catchErrorHandler(async (context, request, response) => { - const soClient = (await context.core).savedObjects.client; - const customElement = await soClient.get( - CUSTOM_ELEMENT_TYPE, - request.params.id - ); + catchErrorHandler(async (context, request, response) => { + const soClient = (await context.core).savedObjects.client; + const customElement = await soClient.get( + CUSTOM_ELEMENT_TYPE, + request.params.id + ); - return response.ok({ - body: { - id: customElement.id, - ...customElement.attributes, - }, - }); - }) - ); + return response.ok({ + body: { + id: customElement.id, + ...customElement.attributes, + }, + }); + }) + ); } diff --git a/x-pack/plugins/canvas/server/routes/custom_elements/update.test.ts b/x-pack/plugins/canvas/server/routes/custom_elements/update.test.ts index 1eb2d3ddee6ee..9e966cfbaf333 100644 --- a/x-pack/plugins/canvas/server/routes/custom_elements/update.test.ts +++ b/x-pack/plugins/canvas/server/routes/custom_elements/update.test.ts @@ -59,7 +59,8 @@ describe('PUT custom element', () => { const routerDeps = getMockedRouterDeps(); initializeUpdateCustomElementRoute(routerDeps); - routeHandler = routerDeps.router.put.mock.calls[0][1]; + routeHandler = + routerDeps.router.versioned.put.mock.results[0].value.addVersion.mock.calls[0][1]; }); afterEach(() => { diff --git a/x-pack/plugins/canvas/server/routes/custom_elements/update.ts b/x-pack/plugins/canvas/server/routes/custom_elements/update.ts index d0c1b8072e1d0..eee18fb1e9a07 100644 --- a/x-pack/plugins/canvas/server/routes/custom_elements/update.ts +++ b/x-pack/plugins/canvas/server/routes/custom_elements/update.ts @@ -16,48 +16,55 @@ import { catchErrorHandler } from '../catch_error_handler'; export function initializeUpdateCustomElementRoute(deps: RouteInitializerDeps) { const { router } = deps; - router.put( - { + router.versioned + .put({ path: `${API_ROUTE_CUSTOM_ELEMENT}/{id}`, - validate: { - params: schema.object({ - id: schema.string(), - }), - body: CustomElementUpdateSchema, - }, options: { body: { maxBytes: 26214400, // 25MB payload limit accepts: ['application/json'], }, }, - }, - catchErrorHandler(async (context, request, response) => { - const payload = request.body; - const id = request.params.id; + access: 'internal', + }) + .addVersion( + { + version: '1', + validate: { + request: { + params: schema.object({ + id: schema.string(), + }), + body: CustomElementUpdateSchema, + }, + }, + }, + catchErrorHandler(async (context, request, response) => { + const payload = request.body; + const id = request.params.id; - const now = new Date().toISOString(); - const soClient = (await context.core).savedObjects.client; + const now = new Date().toISOString(); + const soClient = (await context.core).savedObjects.client; - const customElementObject = await soClient.get( - CUSTOM_ELEMENT_TYPE, - id - ); + const customElementObject = await soClient.get( + CUSTOM_ELEMENT_TYPE, + id + ); - await soClient.create( - CUSTOM_ELEMENT_TYPE, - { - ...customElementObject.attributes, - ...omit(payload, 'id'), // never write the id property - '@timestamp': now, - '@created': customElementObject.attributes['@created'], // ensure created is not modified - }, - { overwrite: true, id } - ); + await soClient.create( + CUSTOM_ELEMENT_TYPE, + { + ...customElementObject.attributes, + ...omit(payload, 'id'), // never write the id property + '@timestamp': now, + '@created': customElementObject.attributes['@created'], // ensure created is not modified + }, + { overwrite: true, id } + ); - return response.ok({ - body: okResponse, - }); - }) - ); + return response.ok({ + body: okResponse, + }); + }) + ); } diff --git a/x-pack/plugins/canvas/server/routes/es_fields/es_fields.test.ts b/x-pack/plugins/canvas/server/routes/es_fields/es_fields.test.ts deleted file mode 100644 index 06a30af03c921..0000000000000 --- a/x-pack/plugins/canvas/server/routes/es_fields/es_fields.test.ts +++ /dev/null @@ -1,171 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { AwaitedProperties } from '@kbn/utility-types'; -import { initializeESFieldsRoute } from './es_fields'; -import { kibanaResponseFactory, RequestHandlerContext, RequestHandler } from '@kbn/core/server'; -import { httpServerMock, elasticsearchServiceMock, coreMock } from '@kbn/core/server/mocks'; -import { getMockedRouterDeps } from '../test_helpers'; - -const mockRouteContext = { - core: { - elasticsearch: { - client: elasticsearchServiceMock.createScopedClusterClient(), - }, - }, -} as unknown as AwaitedProperties; - -const path = `api/canvas/workpad/find`; - -describe('Retrieve ES Fields', () => { - let routeHandler: RequestHandler; - - beforeEach(() => { - const routerDeps = getMockedRouterDeps(); - initializeESFieldsRoute(routerDeps); - - routeHandler = routerDeps.router.get.mock.calls[0][1]; - }); - - it(`returns 200 with fields from existing index/data view`, async () => { - const index = 'test'; - const mockResults = { - indices: ['test'], - fields: { - '@timestamp': { - date: { - type: 'date', - searchable: true, - aggregatable: true, - }, - }, - name: { - text: { - type: 'text', - searchable: true, - aggregatable: false, - }, - }, - products: { - object: { - type: 'object', - searchable: false, - aggregatable: false, - }, - }, - }, - }; - const request = httpServerMock.createKibanaRequest({ - method: 'get', - path, - query: { - index, - }, - }); - - const fieldCapsMock = mockRouteContext.core.elasticsearch.client.asCurrentUser - .fieldCaps as jest.Mock; - - fieldCapsMock.mockResolvedValueOnce(mockResults); - - const response = await routeHandler( - coreMock.createCustomRequestHandlerContext(mockRouteContext), - request, - kibanaResponseFactory - ); - - expect(response.status).toBe(200); - expect(response.payload).toMatchInlineSnapshot(` - Object { - "@timestamp": "date", - "name": "string", - "products": "unsupported", - } - `); - }); - - it(`returns 200 with empty object when index/data view has no fields`, async () => { - const index = 'test'; - const mockResults = { indices: [index], fields: {} }; - const request = httpServerMock.createKibanaRequest({ - method: 'get', - path, - query: { - index, - }, - }); - - const fieldCapsMock = mockRouteContext.core.elasticsearch.client.asCurrentUser - .fieldCaps as jest.Mock; - - fieldCapsMock.mockResolvedValueOnce(mockResults); - - const response = await routeHandler( - coreMock.createCustomRequestHandlerContext(mockRouteContext), - request, - kibanaResponseFactory - ); - - expect(response.status).toBe(200); - expect(response.payload).toMatchInlineSnapshot('Object {}'); - }); - - it(`returns 200 with empty object when index/data view does not have specified field(s)`, async () => { - const index = 'test'; - - const mockResults = { - indices: [index], - fields: {}, - }; - - const request = httpServerMock.createKibanaRequest({ - method: 'get', - path, - query: { - index, - fields: ['foo', 'bar'], - }, - }); - - const fieldCapsMock = mockRouteContext.core.elasticsearch.client.asCurrentUser - .fieldCaps as jest.Mock; - - fieldCapsMock.mockResolvedValueOnce(mockResults); - - const response = await routeHandler( - coreMock.createCustomRequestHandlerContext(mockRouteContext), - request, - kibanaResponseFactory - ); - - expect(response.status).toBe(200); - expect(response.payload).toMatchInlineSnapshot(`Object {}`); - }); - - it(`returns 500 when index does not exist`, async () => { - const request = httpServerMock.createKibanaRequest({ - method: 'get', - path, - query: { - index: 'foo', - }, - }); - - const fieldCapsMock = mockRouteContext.core.elasticsearch.client.asCurrentUser - .fieldCaps as jest.Mock; - - fieldCapsMock.mockRejectedValueOnce(new Error('Index not found')); - - await expect( - routeHandler( - coreMock.createCustomRequestHandlerContext(mockRouteContext), - request, - kibanaResponseFactory - ) - ).rejects.toThrowError('Index not found'); - }); -}); diff --git a/x-pack/plugins/canvas/server/routes/es_fields/es_fields.ts b/x-pack/plugins/canvas/server/routes/es_fields/es_fields.ts deleted file mode 100644 index 1923d4648a2a3..0000000000000 --- a/x-pack/plugins/canvas/server/routes/es_fields/es_fields.ts +++ /dev/null @@ -1,58 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { mapValues, keys } from 'lodash'; -import { schema } from '@kbn/config-schema'; -import { API_ROUTE } from '../../../common/lib'; -import { catchErrorHandler } from '../catch_error_handler'; -import { normalizeType } from '../../../common/lib/request/normalize_type'; -import { RouteInitializerDeps } from '..'; - -const ESFieldsRequestSchema = schema.object({ - index: schema.string(), - fields: schema.maybe(schema.arrayOf(schema.string())), -}); - -export function initializeESFieldsRoute(deps: RouteInitializerDeps) { - const { router } = deps; - - router.get( - { - path: `${API_ROUTE}/es_fields`, - validate: { - query: ESFieldsRequestSchema, - }, - }, - catchErrorHandler(async (context, request, response) => { - const client = (await context.core).elasticsearch.client.asCurrentUser; - const { index, fields } = request.query; - - const config = { - index, - fields: fields || '*', - }; - - const esFields = await client.fieldCaps(config).then((resp) => { - return mapValues(resp.fields, (types) => { - if (keys(types).length > 1) { - return 'conflict'; - } - - try { - return normalizeType(keys(types)[0]); - } catch (e) { - return 'unsupported'; - } - }); - }); - - return response.ok({ - body: esFields, - }); - }) - ); -} diff --git a/x-pack/plugins/canvas/server/routes/es_fields/index.ts b/x-pack/plugins/canvas/server/routes/es_fields/index.ts deleted file mode 100644 index f0ca3cab0b45f..0000000000000 --- a/x-pack/plugins/canvas/server/routes/es_fields/index.ts +++ /dev/null @@ -1,13 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { initializeESFieldsRoute } from './es_fields'; -import { RouteInitializerDeps } from '..'; - -export function initESFieldsRoutes(deps: RouteInitializerDeps) { - initializeESFieldsRoute(deps); -} diff --git a/x-pack/plugins/canvas/server/routes/functions/functions.test.ts b/x-pack/plugins/canvas/server/routes/functions/functions.test.ts index 9a308b8f0b545..ebf38b6e7a0fa 100644 --- a/x-pack/plugins/canvas/server/routes/functions/functions.test.ts +++ b/x-pack/plugins/canvas/server/routes/functions/functions.test.ts @@ -31,7 +31,8 @@ describe('Get list of serverside expression functions', () => { initializeGetFunctionsRoute(routerDeps); - routeHandler = routerDeps.router.get.mock.calls[0][1]; + routeHandler = + routerDeps.router.versioned.get.mock.results[0].value.addVersion.mock.calls[0][1]; }); afterAll(() => { diff --git a/x-pack/plugins/canvas/server/routes/functions/functions.ts b/x-pack/plugins/canvas/server/routes/functions/functions.ts index a2cc8f0a365e9..73036b35af05b 100644 --- a/x-pack/plugins/canvas/server/routes/functions/functions.ts +++ b/x-pack/plugins/canvas/server/routes/functions/functions.ts @@ -17,19 +17,18 @@ interface FunctionCall { export function initializeGetFunctionsRoute(deps: RouteInitializerDeps) { const { router, expressions } = deps; - router.get( - { + router.versioned + .get({ path: API_ROUTE_FUNCTIONS, - validate: false, - }, - async (context, request, response) => { + access: 'internal', + }) + .addVersion({ version: '1', validate: false }, async (context, request, response) => { const functions = expressions.getFunctions('canvas'); const body = JSON.stringify(functions); return response.ok({ body, }); - } - ); + }); } export function initializeBatchFunctionsRoute(deps: RouteInitializerDeps) { diff --git a/x-pack/plugins/canvas/server/routes/index.ts b/x-pack/plugins/canvas/server/routes/index.ts index 73829cb372e70..9b2d712b17c5f 100644 --- a/x-pack/plugins/canvas/server/routes/index.ts +++ b/x-pack/plugins/canvas/server/routes/index.ts @@ -9,7 +9,6 @@ import { IRouter, Logger } from '@kbn/core/server'; import { ExpressionsServerSetup } from '@kbn/expressions-plugin/server'; import { BfetchServerSetup } from '@kbn/bfetch-plugin/server'; import { initCustomElementsRoutes } from './custom_elements'; -import { initESFieldsRoutes } from './es_fields'; import { initShareablesRoutes } from './shareables'; import { initWorkpadRoutes } from './workpad'; import { initTemplateRoutes } from './templates'; @@ -25,7 +24,6 @@ export interface RouteInitializerDeps { export function initRoutes(deps: RouteInitializerDeps) { initCustomElementsRoutes(deps); - initESFieldsRoutes(deps); initShareablesRoutes(deps); initWorkpadRoutes(deps); initTemplateRoutes(deps); diff --git a/x-pack/plugins/canvas/server/routes/shareables/download.test.ts b/x-pack/plugins/canvas/server/routes/shareables/download.test.ts index cb93695a1ee49..f8e5213d6ecc0 100644 --- a/x-pack/plugins/canvas/server/routes/shareables/download.test.ts +++ b/x-pack/plugins/canvas/server/routes/shareables/download.test.ts @@ -23,7 +23,8 @@ describe('Download Canvas shareables runtime', () => { const routerDeps = getMockedRouterDeps(); initializeDownloadShareableWorkpadRoute(routerDeps); - routeHandler = routerDeps.router.get.mock.calls[0][1]; + routeHandler = + routerDeps.router.versioned.get.mock.results[0].value.addVersion.mock.calls[0][1]; }); afterAll(() => { diff --git a/x-pack/plugins/canvas/server/routes/shareables/download.ts b/x-pack/plugins/canvas/server/routes/shareables/download.ts index bec6d3fb194e8..2d3b4af74855b 100644 --- a/x-pack/plugins/canvas/server/routes/shareables/download.ts +++ b/x-pack/plugins/canvas/server/routes/shareables/download.ts @@ -13,12 +13,12 @@ import { API_ROUTE_SHAREABLE_RUNTIME_DOWNLOAD } from '../../../common/lib/consta export function initializeDownloadShareableWorkpadRoute(deps: RouteInitializerDeps) { const { router } = deps; - router.get( - { + router.versioned + .get({ path: API_ROUTE_SHAREABLE_RUNTIME_DOWNLOAD, - validate: false, - }, - async (_context, _request, response) => { + access: 'internal', + }) + .addVersion({ version: '1', validate: false }, async (_context, _request, response) => { // TODO: check if this is still an issue on cloud after migrating to NP // // The option setting is not for typical use. We're using it here to avoid @@ -29,6 +29,5 @@ export function initializeDownloadShareableWorkpadRoute(deps: RouteInitializerDe headers: { 'content-type': 'application/octet-stream' }, body: file, }); - } - ); + }); } diff --git a/x-pack/plugins/canvas/server/routes/shareables/zip.test.ts b/x-pack/plugins/canvas/server/routes/shareables/zip.test.ts index a0a741fa6b538..a98c413692b9b 100644 --- a/x-pack/plugins/canvas/server/routes/shareables/zip.test.ts +++ b/x-pack/plugins/canvas/server/routes/shareables/zip.test.ts @@ -30,7 +30,8 @@ describe('Zips Canvas shareables runtime together with workpad', () => { beforeEach(() => { const routerDeps = getMockedRouterDeps(); initializeZipShareableWorkpadRoute(routerDeps); - routeHandler = routerDeps.router.post.mock.calls[0][1]; + routeHandler = + routerDeps.router.versioned.post.mock.results[0].value.addVersion.mock.calls[0][1]; }); afterAll(() => { diff --git a/x-pack/plugins/canvas/server/routes/shareables/zip.ts b/x-pack/plugins/canvas/server/routes/shareables/zip.ts index a0fba57ba6390..7f5a414c5dba0 100644 --- a/x-pack/plugins/canvas/server/routes/shareables/zip.ts +++ b/x-pack/plugins/canvas/server/routes/shareables/zip.ts @@ -18,22 +18,24 @@ import { RouteInitializerDeps } from '..'; export function initializeZipShareableWorkpadRoute(deps: RouteInitializerDeps) { const { router } = deps; - router.post( - { + router.versioned + .post({ path: API_ROUTE_SHAREABLE_ZIP, - validate: { body: RenderedWorkpadSchema }, - }, - async (_context, request, response) => { - const workpad = request.body; - const archive = archiver('zip'); - archive.append(JSON.stringify(workpad), { name: 'workpad.json' }); - archive.file(`${SHAREABLE_RUNTIME_SRC}/template.html`, { name: 'index.html' }); - archive.file(SHAREABLE_RUNTIME_FILE, { name: `${SHAREABLE_RUNTIME_NAME}.js` }); + access: 'internal', + }) + .addVersion( + { version: '1', validate: { request: { body: RenderedWorkpadSchema } } }, + async (_context, request, response) => { + const workpad = request.body; + const archive = archiver('zip'); + archive.append(JSON.stringify(workpad), { name: 'workpad.json' }); + archive.file(`${SHAREABLE_RUNTIME_SRC}/template.html`, { name: 'index.html' }); + archive.file(SHAREABLE_RUNTIME_FILE, { name: `${SHAREABLE_RUNTIME_NAME}.js` }); - const result = { headers: { 'content-type': 'application/zip' }, body: archive }; - archive.finalize(); + const result = { headers: { 'content-type': 'application/zip' }, body: archive }; + archive.finalize(); - return response.ok(result); - } - ); + return response.ok(result); + } + ); } diff --git a/x-pack/plugins/canvas/server/routes/templates/list.test.ts b/x-pack/plugins/canvas/server/routes/templates/list.test.ts index a1480eb0eaea1..f4a5845728096 100644 --- a/x-pack/plugins/canvas/server/routes/templates/list.test.ts +++ b/x-pack/plugins/canvas/server/routes/templates/list.test.ts @@ -27,7 +27,8 @@ describe('Find workpad', () => { const routerDeps = getMockedRouterDeps(); initializeListTemplates(routerDeps); - routeHandler = routerDeps.router.get.mock.calls[0][1]; + routeHandler = + routerDeps.router.versioned.get.mock.results[0].value.addVersion.mock.calls[0][1]; }); it(`returns 200 with the found templates`, async () => { diff --git a/x-pack/plugins/canvas/server/routes/templates/list.ts b/x-pack/plugins/canvas/server/routes/templates/list.ts index c0e32fb3722b1..e311ef3c8c24b 100644 --- a/x-pack/plugins/canvas/server/routes/templates/list.ts +++ b/x-pack/plugins/canvas/server/routes/templates/list.ts @@ -13,32 +13,37 @@ import { CanvasTemplate } from '../../../types'; export function initializeListTemplates(deps: RouteInitializerDeps) { const { router } = deps; - router.get( - { + router.versioned + .get({ path: `${API_ROUTE_TEMPLATES}`, - validate: { - params: schema.object({}), + access: 'internal', + }) + .addVersion( + { + version: '1', + validate: { + request: { params: schema.object({}) }, + }, }, - }, - catchErrorHandler(async (context, request, response) => { - const savedObjectsClient = (await context.core).savedObjects.client; + catchErrorHandler(async (context, request, response) => { + const savedObjectsClient = (await context.core).savedObjects.client; - const templates = await savedObjectsClient.find({ - type: TEMPLATE_TYPE, - sortField: 'name.keyword', - sortOrder: 'desc', - search: '*', - searchFields: ['name', 'help'], - fields: ['id', 'name', 'help', 'tags'], - }); + const templates = await savedObjectsClient.find({ + type: TEMPLATE_TYPE, + sortField: 'name.keyword', + sortOrder: 'desc', + search: '*', + searchFields: ['name', 'help'], + fields: ['id', 'name', 'help', 'tags'], + }); - return response.ok({ - body: { - templates: templates.saved_objects.map((hit) => ({ - ...hit.attributes, - })), - }, - }); - }) - ); + return response.ok({ + body: { + templates: templates.saved_objects.map((hit) => ({ + ...hit.attributes, + })), + }, + }); + }) + ); } diff --git a/x-pack/plugins/canvas/server/routes/workpad/create.test.ts b/x-pack/plugins/canvas/server/routes/workpad/create.test.ts index 7e9e32e1c4184..e09a180755a62 100644 --- a/x-pack/plugins/canvas/server/routes/workpad/create.test.ts +++ b/x-pack/plugins/canvas/server/routes/workpad/create.test.ts @@ -41,7 +41,8 @@ describe('POST workpad', () => { const routerDeps = getMockedRouterDeps(); initializeCreateWorkpadRoute(routerDeps); - routeHandler = routerDeps.router.post.mock.calls[0][1]; + routeHandler = + routerDeps.router.versioned.post.mock.results[0].value.addVersion.mock.calls[0][1]; }); it(`returns 200 when the workpad is created`, async () => { diff --git a/x-pack/plugins/canvas/server/routes/workpad/create.ts b/x-pack/plugins/canvas/server/routes/workpad/create.ts index dec03c8945ac6..c1e4f7e2f8353 100644 --- a/x-pack/plugins/canvas/server/routes/workpad/create.ts +++ b/x-pack/plugins/canvas/server/routes/workpad/create.ts @@ -33,37 +33,42 @@ function isCreateFromTemplate( export function initializeCreateWorkpadRoute(deps: RouteInitializerDeps) { const { router } = deps; - router.post( - { + router.versioned + .post({ path: `${API_ROUTE_WORKPAD}`, - validate: { - body: createRequestBodySchema, - }, options: { body: { maxBytes: 26214400, accepts: ['application/json'], }, }, - }, - catchErrorHandler(async (context, request, response) => { - let workpad = request.body as CanvasWorkpad; + access: 'internal', + }) + .addVersion( + { + version: '1', + validate: { + request: { body: createRequestBodySchema }, + }, + }, + catchErrorHandler(async (context, request, response) => { + let workpad = request.body as CanvasWorkpad; - if (isCreateFromTemplate(request.body)) { - const soClient = (await context.core).savedObjects.client; - const templateSavedObject = await soClient.get( - TEMPLATE_TYPE, - request.body.templateId - ); - workpad = templateSavedObject.attributes.template; - } + if (isCreateFromTemplate(request.body)) { + const soClient = (await context.core).savedObjects.client; + const templateSavedObject = await soClient.get( + TEMPLATE_TYPE, + request.body.templateId + ); + workpad = templateSavedObject.attributes.template; + } - const canvasContext = await context.canvas; - const createdObject = await canvasContext.workpad.create(workpad); + const canvasContext = await context.canvas; + const createdObject = await canvasContext.workpad.create(workpad); - return response.ok({ - body: { ...okResponse, id: createdObject.id }, - }); - }) - ); + return response.ok({ + body: { ...okResponse, id: createdObject.id }, + }); + }) + ); } diff --git a/x-pack/plugins/canvas/server/routes/workpad/delete.test.ts b/x-pack/plugins/canvas/server/routes/workpad/delete.test.ts index c0f7e31729817..866be481c5d2e 100644 --- a/x-pack/plugins/canvas/server/routes/workpad/delete.test.ts +++ b/x-pack/plugins/canvas/server/routes/workpad/delete.test.ts @@ -32,7 +32,8 @@ describe('DELETE workpad', () => { const routerDeps = getMockedRouterDeps(); initializeDeleteWorkpadRoute(routerDeps); - routeHandler = routerDeps.router.delete.mock.calls[0][1]; + routeHandler = + routerDeps.router.versioned.delete.mock.results[0].value.addVersion.mock.calls[0][1]; }); it(`returns 200 ok when the workpad is deleted`, async () => { diff --git a/x-pack/plugins/canvas/server/routes/workpad/delete.ts b/x-pack/plugins/canvas/server/routes/workpad/delete.ts index 0a1f69e273d05..d5abbbfed7a78 100644 --- a/x-pack/plugins/canvas/server/routes/workpad/delete.ts +++ b/x-pack/plugins/canvas/server/routes/workpad/delete.ts @@ -13,19 +13,26 @@ import { catchErrorHandler } from '../catch_error_handler'; export function initializeDeleteWorkpadRoute(deps: RouteInitializerDeps) { const { router } = deps; - router.delete( - { + router.versioned + .delete({ path: `${API_ROUTE_WORKPAD}/{id}`, - validate: { - params: schema.object({ - id: schema.string(), - }), - }, - }, - catchErrorHandler(async (context, request, response) => { - const soClient = (await context.core).savedObjects.client; - await soClient.delete(CANVAS_TYPE, request.params.id); - return response.ok({ body: okResponse }); + access: 'internal', }) - ); + .addVersion( + { + version: '1', + validate: { + request: { + params: schema.object({ + id: schema.string(), + }), + }, + }, + }, + catchErrorHandler(async (context, request, response) => { + const soClient = (await context.core).savedObjects.client; + await soClient.delete(CANVAS_TYPE, request.params.id); + return response.ok({ body: okResponse }); + }) + ); } diff --git a/x-pack/plugins/canvas/server/routes/workpad/find.test.ts b/x-pack/plugins/canvas/server/routes/workpad/find.test.ts index 39626e95c3f06..c74188da82b19 100644 --- a/x-pack/plugins/canvas/server/routes/workpad/find.test.ts +++ b/x-pack/plugins/canvas/server/routes/workpad/find.test.ts @@ -26,7 +26,8 @@ describe('Find workpad', () => { const routerDeps = getMockedRouterDeps(); initializeFindWorkpadsRoute(routerDeps); - routeHandler = routerDeps.router.get.mock.calls[0][1]; + routeHandler = + routerDeps.router.versioned.get.mock.results[0].value.addVersion.mock.calls[0][1]; }); it(`returns 200 with the found workpads`, async () => { diff --git a/x-pack/plugins/canvas/server/routes/workpad/find.ts b/x-pack/plugins/canvas/server/routes/workpad/find.ts index 43db20ee11db5..1e993a347d89f 100644 --- a/x-pack/plugins/canvas/server/routes/workpad/find.ts +++ b/x-pack/plugins/canvas/server/routes/workpad/find.ts @@ -12,47 +12,54 @@ import { CANVAS_TYPE, API_ROUTE_WORKPAD } from '../../../common/lib/constants'; export function initializeFindWorkpadsRoute(deps: RouteInitializerDeps) { const { router } = deps; - router.get( - { + router.versioned + .get({ path: `${API_ROUTE_WORKPAD}/find`, - validate: { - query: schema.object({ - name: schema.string(), - page: schema.maybe(schema.number()), - perPage: schema.number(), - }), + access: 'internal', + }) + .addVersion( + { + version: '1', + validate: { + request: { + query: schema.object({ + name: schema.string(), + page: schema.maybe(schema.number()), + perPage: schema.number(), + }), + }, + }, }, - }, - async (context, request, response) => { - const savedObjectsClient = (await context.core).savedObjects.client; - const { name, page, perPage } = request.query; + async (context, request, response) => { + const savedObjectsClient = (await context.core).savedObjects.client; + const { name, page, perPage } = request.query; - try { - const workpads = await savedObjectsClient.find({ - type: CANVAS_TYPE, - sortField: '@timestamp', - sortOrder: 'desc', - search: name ? `${name}* | ${name}` : '*', - searchFields: ['name'], - fields: ['id', 'name', '@created', '@timestamp'], - page, - perPage, - }); + try { + const workpads = await savedObjectsClient.find({ + 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: [], - }, - }); + 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: [], + }, + }); + } } - } - ); + ); } diff --git a/x-pack/plugins/canvas/server/routes/workpad/get.test.ts b/x-pack/plugins/canvas/server/routes/workpad/get.test.ts index 565fdb16f9f30..28665c7fa28b5 100644 --- a/x-pack/plugins/canvas/server/routes/workpad/get.test.ts +++ b/x-pack/plugins/canvas/server/routes/workpad/get.test.ts @@ -26,7 +26,8 @@ describe('GET workpad', () => { const routerDeps = getMockedRouterDeps(); initializeGetWorkpadRoute(routerDeps); - routeHandler = routerDeps.router.get.mock.calls[0][1]; + routeHandler = + routerDeps.router.versioned.get.mock.results[0].value.addVersion.mock.calls[0][1]; }); afterEach(() => { diff --git a/x-pack/plugins/canvas/server/routes/workpad/get.ts b/x-pack/plugins/canvas/server/routes/workpad/get.ts index fe38b768f64a1..8ce7687627611 100644 --- a/x-pack/plugins/canvas/server/routes/workpad/get.ts +++ b/x-pack/plugins/canvas/server/routes/workpad/get.ts @@ -13,27 +13,34 @@ import { shimWorkpad } from './shim_workpad'; export function initializeGetWorkpadRoute(deps: RouteInitializerDeps) { const { router } = deps; - router.get( - { + router.versioned + .get({ path: `${API_ROUTE_WORKPAD}/{id}`, - validate: { - params: schema.object({ - id: schema.string(), - }), + access: 'internal', + }) + .addVersion( + { + version: '1', + validate: { + request: { + params: schema.object({ + id: schema.string(), + }), + }, + }, }, - }, - catchErrorHandler(async (context, request, response) => { - const canvasContext = await context.canvas; - const workpad = await canvasContext.workpad.get(request.params.id); + catchErrorHandler(async (context, request, response) => { + const canvasContext = await context.canvas; + const workpad = await canvasContext.workpad.get(request.params.id); - shimWorkpad(workpad); + shimWorkpad(workpad); - return response.ok({ - body: { - id: workpad.id, - ...workpad.attributes, - }, - }); - }) - ); + return response.ok({ + body: { + id: workpad.id, + ...workpad.attributes, + }, + }); + }) + ); } diff --git a/x-pack/plugins/canvas/server/routes/workpad/import.ts b/x-pack/plugins/canvas/server/routes/workpad/import.ts index 4734e1e554f64..5490d460f8834 100644 --- a/x-pack/plugins/canvas/server/routes/workpad/import.ts +++ b/x-pack/plugins/canvas/server/routes/workpad/import.ts @@ -16,28 +16,33 @@ const createRequestBodySchema = ImportedWorkpadSchema; export function initializeImportWorkpadRoute(deps: RouteInitializerDeps) { const { router } = deps; - router.post( - { + router.versioned + .post({ path: `${API_ROUTE_WORKPAD_IMPORT}`, - validate: { - body: createRequestBodySchema, - }, options: { body: { maxBytes: 26214400, accepts: ['application/json'], }, }, - }, - catchErrorHandler(async (context, request, response) => { - const workpad = request.body as ImportedCanvasWorkpad; + access: 'internal', + }) + .addVersion( + { + version: '1', + validate: { + request: { body: createRequestBodySchema }, + }, + }, + catchErrorHandler(async (context, request, response) => { + const workpad = request.body as ImportedCanvasWorkpad; - const canvasContext = await context.canvas; - const createdObject = await canvasContext.workpad.import(workpad); + const canvasContext = await context.canvas; + const createdObject = await canvasContext.workpad.import(workpad); - return response.ok({ - body: { ...okResponse, id: createdObject.id }, - }); - }) - ); + return response.ok({ + body: { ...okResponse, id: createdObject.id }, + }); + }) + ); } diff --git a/x-pack/plugins/canvas/server/routes/workpad/resolve.test.ts b/x-pack/plugins/canvas/server/routes/workpad/resolve.test.ts index e43e30b86dd4d..51d5d7e85f9dd 100644 --- a/x-pack/plugins/canvas/server/routes/workpad/resolve.test.ts +++ b/x-pack/plugins/canvas/server/routes/workpad/resolve.test.ts @@ -26,7 +26,8 @@ describe('RESOLVE workpad', () => { const routerDeps = getMockedRouterDeps(); initializeResolveWorkpadRoute(routerDeps); - routeHandler = routerDeps.router.get.mock.calls[0][1]; + routeHandler = + routerDeps.router.versioned.get.mock.results[0].value.addVersion.mock.calls[0][1]; }); afterEach(() => { diff --git a/x-pack/plugins/canvas/server/routes/workpad/resolve.ts b/x-pack/plugins/canvas/server/routes/workpad/resolve.ts index d2a4b71f35af9..b6260e9e47694 100644 --- a/x-pack/plugins/canvas/server/routes/workpad/resolve.ts +++ b/x-pack/plugins/canvas/server/routes/workpad/resolve.ts @@ -13,35 +13,42 @@ import { shimWorkpad } from './shim_workpad'; export function initializeResolveWorkpadRoute(deps: RouteInitializerDeps) { const { router } = deps; - router.get( - { + router.versioned + .get({ path: `${API_ROUTE_WORKPAD}/resolve/{id}`, - validate: { - params: schema.object({ - id: schema.string(), - }), + access: 'internal', + }) + .addVersion( + { + version: '1', + validate: { + request: { + params: schema.object({ + id: schema.string(), + }), + }, + }, }, - }, - catchErrorHandler(async (context, request, response) => { - const canvasContext = await context.canvas; - const resolved = await canvasContext.workpad.resolve(request.params.id); - const { saved_object: workpad } = resolved; + catchErrorHandler(async (context, request, response) => { + const canvasContext = await context.canvas; + const resolved = await canvasContext.workpad.resolve(request.params.id); + const { saved_object: workpad } = resolved; - shimWorkpad(workpad); + shimWorkpad(workpad); - return response.ok({ - body: { - workpad: { - id: workpad.id, - ...workpad.attributes, + return response.ok({ + body: { + workpad: { + id: workpad.id, + ...workpad.attributes, + }, + outcome: resolved.outcome, + aliasId: resolved.alias_target_id, + ...(resolved.alias_purpose !== undefined && { + aliasPurpose: resolved.alias_purpose, + }), }, - outcome: resolved.outcome, - aliasId: resolved.alias_target_id, - ...(resolved.alias_purpose !== undefined && { - aliasPurpose: resolved.alias_purpose, - }), - }, - }); - }) - ); + }); + }) + ); } diff --git a/x-pack/plugins/canvas/server/routes/workpad/update.test.ts b/x-pack/plugins/canvas/server/routes/workpad/update.test.ts index fcf24f68743dd..87ac2015e1c2e 100644 --- a/x-pack/plugins/canvas/server/routes/workpad/update.test.ts +++ b/x-pack/plugins/canvas/server/routes/workpad/update.test.ts @@ -42,7 +42,8 @@ describe('PUT workpad', () => { const routerDeps = getMockedRouterDeps(); initializeUpdateWorkpadRoute(routerDeps); - routeHandler = routerDeps.router.put.mock.calls[0][1]; + routeHandler = + routerDeps.router.versioned.put.mock.results[0].value.addVersion.mock.calls[0][1]; }); afterEach(() => { @@ -133,7 +134,8 @@ describe('update assets', () => { const routerDeps = getMockedRouterDeps(); initializeUpdateWorkpadAssetsRoute(routerDeps); - routeHandler = routerDeps.router.put.mock.calls[0][1]; + routeHandler = + routerDeps.router.versioned.put.mock.results[0].value.addVersion.mock.calls[0][1]; }); afterEach(() => { diff --git a/x-pack/plugins/canvas/server/routes/workpad/update.ts b/x-pack/plugins/canvas/server/routes/workpad/update.ts index d74e57f824f51..ead5a097a6945 100644 --- a/x-pack/plugins/canvas/server/routes/workpad/update.ts +++ b/x-pack/plugins/canvas/server/routes/workpad/update.ts @@ -23,92 +23,115 @@ 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( - { + router.versioned + .put({ path: `${API_ROUTE_WORKPAD}/{id}`, - validate: { - params: schema.object({ - id: schema.string(), - }), - body: WorkpadSchema, - }, + options: { body: { maxBytes: 26214400, accepts: ['application/json'], }, }, - }, - catchErrorHandler(async (context, request, response) => { - const canvasContext = await context.canvas; - await canvasContext.workpad.update(request.params.id, request.body as CanvasWorkpad); - - return response.ok({ - body: okResponse, - }); + access: 'internal', }) - ); + .addVersion( + { + version: '1', + validate: { + request: { + params: schema.object({ + id: schema.string(), + }), + body: WorkpadSchema, + }, + }, + }, + catchErrorHandler(async (context, request, response) => { + const canvasContext = await context.canvas; + await canvasContext.workpad.update(request.params.id, request.body as CanvasWorkpad); + + return response.ok({ + body: okResponse, + }); + }) + ); - router.put( - { + router.versioned + .put({ path: `${API_ROUTE_WORKPAD_STRUCTURES}/{id}`, - validate: { - params: schema.object({ - id: schema.string(), - }), - body: WorkpadSchema, - }, options: { body: { maxBytes: 26214400, accepts: ['application/json'], }, }, - }, - catchErrorHandler(async (context, request, response) => { - const canvasContext = await context.canvas; - await canvasContext.workpad.update(request.params.id, request.body as CanvasWorkpad); - - return response.ok({ - body: okResponse, - }); + access: 'internal', }) - ); + .addVersion( + { + version: '1', + validate: { + request: { + params: schema.object({ + id: schema.string(), + }), + body: WorkpadSchema, + }, + }, + }, + catchErrorHandler(async (context, request, response) => { + const canvasContext = await context.canvas; + await canvasContext.workpad.update(request.params.id, request.body as CanvasWorkpad); + + return response.ok({ + body: okResponse, + }); + }) + ); } export function initializeUpdateWorkpadAssetsRoute(deps: RouteInitializerDeps) { const { router } = deps; - router.put( - { + router.versioned + .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({}, { unknowns: 'allow' }), - }, + options: { body: { maxBytes: 26214400, accepts: ['application/json'], }, }, - }, - async (context, request, response) => { - const workpadAssets = { - assets: AssetsRecordSchema.validate(request.body), - }; + access: 'internal', + }) + .addVersion( + { + version: '1', + validate: { + request: { + 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({}, { unknowns: 'allow' }), + }, + }, + }, + async (context, request, response) => { + const workpadAssets = { + assets: AssetsRecordSchema.validate(request.body), + }; - const canvasContext = await context.canvas; - await canvasContext.workpad.update(request.params.id, workpadAssets as CanvasWorkpad); + const canvasContext = await context.canvas; + await canvasContext.workpad.update(request.params.id, workpadAssets as CanvasWorkpad); - return response.ok({ - body: okResponse, - }); - } - ); + return response.ok({ + body: okResponse, + }); + } + ); }