From 4642a8c5a7e4121a5c55f7c6bfc1462d5c594714 Mon Sep 17 00:00:00 2001 From: Corey Robertson Date: Mon, 15 Nov 2021 13:32:55 -0500 Subject: [PATCH] Improve workpad schema validation (#97838) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../routes/workpad/workpad_schema.test.ts | 110 ++++++++++++++++++ .../server/routes/workpad/workpad_schema.ts | 53 ++++++--- 2 files changed, 148 insertions(+), 15 deletions(-) create mode 100644 x-pack/plugins/canvas/server/routes/workpad/workpad_schema.test.ts diff --git a/x-pack/plugins/canvas/server/routes/workpad/workpad_schema.test.ts b/x-pack/plugins/canvas/server/routes/workpad/workpad_schema.test.ts new file mode 100644 index 0000000000000..bcc7ce888d8d4 --- /dev/null +++ b/x-pack/plugins/canvas/server/routes/workpad/workpad_schema.test.ts @@ -0,0 +1,110 @@ +/* + * 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 { WorkpadSchema } from './workpad_schema'; + +const pageOneId = 'page-1'; +const pageTwoId = 'page-2'; +const elementOneId = 'element-1'; +const elementTwoId = 'element-2'; +const elementThreeId = 'element-3'; + +const position = { + angle: 0, + height: 0, + left: 0, + parent: null, + top: 0, + width: 0, +}; +const baseWorkpad = { + colors: [], + css: '', + variables: [], + height: 0, + id: 'workpad-id', + name: 'workpad', + page: 1, + pages: [ + { + elements: [ + { + expression: 'expression', + id: elementOneId, + position, + }, + { + expression: 'expression', + id: elementTwoId, + position, + }, + ], + id: pageOneId, + style: {}, + }, + { + elements: [ + { + expression: 'expression', + id: elementThreeId, + position, + }, + ], + id: pageTwoId, + style: {}, + }, + ], + width: 0, +}; + +it('validates there are no duplicate page ids', () => { + const dupePage = { + ...baseWorkpad, + pages: [{ ...baseWorkpad.pages[0] }, { ...baseWorkpad.pages[1], id: pageOneId }], + }; + + expect(() => WorkpadSchema.validate(dupePage)).toThrowError('Page Ids are not unique'); +}); + +it('validates there are no duplicate element ids on the same page', () => { + const dupeElement = { + ...baseWorkpad, + pages: [ + { + ...baseWorkpad.pages[0], + elements: [ + { ...baseWorkpad.pages[0].elements[0] }, + { ...baseWorkpad.pages[0].elements[1], id: elementOneId }, + ], + }, + { ...baseWorkpad.pages[1] }, + ], + }; + + expect(() => WorkpadSchema.validate(dupeElement)).toThrowError('Element Ids are not unique'); +}); + +it('validates there are no duplicate element ids in the workpad', () => { + const dupeElement = { + ...baseWorkpad, + pages: [ + { + ...baseWorkpad.pages[0], + elements: [ + { ...baseWorkpad.pages[0].elements[0] }, + { ...baseWorkpad.pages[0].elements[1], id: elementOneId }, + ], + }, + { + ...baseWorkpad.pages[1], + elements: [{ ...baseWorkpad.pages[1].elements[0], id: elementOneId }], + }, + ], + }; + + expect(() => WorkpadSchema.validate(dupeElement)).toThrowError('Element Ids are not unique'); +}); diff --git a/x-pack/plugins/canvas/server/routes/workpad/workpad_schema.ts b/x-pack/plugins/canvas/server/routes/workpad/workpad_schema.ts index 57e02367fcd3a..9bde26298185b 100644 --- a/x-pack/plugins/canvas/server/routes/workpad/workpad_schema.ts +++ b/x-pack/plugins/canvas/server/routes/workpad/workpad_schema.ts @@ -58,18 +58,41 @@ export const WorkpadVariable = schema.object({ type: 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(), - variables: schema.arrayOf(WorkpadVariable), - height: schema.number(), - id: schema.string(), - isWriteable: schema.maybe(schema.boolean()), - name: schema.string(), - page: schema.number(), - pages: schema.arrayOf(WorkpadPageSchema), - width: schema.number(), -}); +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(), + variables: schema.arrayOf(WorkpadVariable), + height: schema.number(), + id: schema.string(), + isWriteable: schema.maybe(schema.boolean()), + name: schema.string(), + page: schema.number(), + pages: schema.arrayOf(WorkpadPageSchema), + width: schema.number(), + }, + { + validate: (workpad) => { + // Validate unique page ids + const pageIdsArray = workpad.pages.map((page) => page.id); + const pageIdsSet = new Set(pageIdsArray); + + if (pageIdsArray.length !== pageIdsSet.size) { + return 'Page Ids are not unique'; + } + + // Validate unique element ids + const elementIdsArray = workpad.pages + .map((page) => page.elements.map((element) => element.id)) + .flat(); + const elementIdsSet = new Set(elementIdsArray); + + if (elementIdsArray.length !== elementIdsSet.size) { + return 'Element Ids are not unique'; + } + }, + } +);