From 32b58d1104f83b63f7300eef26df5e86c97127ee Mon Sep 17 00:00:00 2001 From: Nico Rehwaldt Date: Mon, 9 Dec 2024 12:14:14 +0100 Subject: [PATCH 01/11] test: explicitly set element templates per describe This ensures the tests clearly layout expectations. --- .../ElementTemplates.spec.js | 64 +++++++++++-------- 1 file changed, 39 insertions(+), 25 deletions(-) diff --git a/test/spec/cloud-element-templates/ElementTemplates.spec.js b/test/spec/cloud-element-templates/ElementTemplates.spec.js index b3fc70e4..0fb42db 100644 --- a/test/spec/cloud-element-templates/ElementTemplates.spec.js +++ b/test/spec/cloud-element-templates/ElementTemplates.spec.js @@ -53,13 +53,14 @@ describe('provider/cloud-element-templates - ElementTemplates', function() { } })); - beforeEach(inject(function(elementTemplates) { - elementTemplates.set(templates); - })); - describe('get', function() { + beforeEach(inject(function(elementTemplates) { + elementTemplates.set(templates); + })); + + it('should get template by ID', inject(function(elementTemplates) { // when @@ -147,6 +148,11 @@ describe('provider/cloud-element-templates - ElementTemplates', function() { describe('getAll', function() { + beforeEach(inject(function(elementTemplates) { + elementTemplates.set(templates); + })); + + it('should get all templates', inject(function(elementTemplates) { // when @@ -220,6 +226,11 @@ describe('provider/cloud-element-templates - ElementTemplates', function() { describe('getLatest', function() { + beforeEach(inject(function(elementTemplates) { + elementTemplates.set(templates); + })); + + it('should get all latest templates', inject(function(elementTemplates) { // when @@ -346,6 +357,11 @@ describe('provider/cloud-element-templates - ElementTemplates', function() { describe('createElement', function() { + beforeEach(inject(function(elementTemplates) { + elementTemplates.set(templates); + })); + + it('should create element', inject(function(elementTemplates) { // given @@ -528,6 +544,11 @@ describe('provider/cloud-element-templates - ElementTemplates', function() { describe('applyTemplate', function() { + beforeEach(inject(function(elementTemplates) { + elementTemplates.set(templates); + })); + + it('should set template on element', inject(function(elementRegistry, elementTemplates) { // given @@ -807,6 +828,11 @@ describe('provider/cloud-element-templates - ElementTemplates', function() { describe('unlinkTemplate', function() { + beforeEach(inject(function(elementTemplates) { + elementTemplates.set(templates); + })); + + it('should unlink task template', inject(function(elementRegistry, elementTemplates) { // given @@ -862,6 +888,11 @@ describe('provider/cloud-element-templates - ElementTemplates', function() { describe('removeTemplate', function() { + beforeEach(inject(function(elementTemplates) { + elementTemplates.set(templates); + })); + + it('should remove task template', inject(function(elementRegistry, elementTemplates) { // given @@ -1008,29 +1039,11 @@ describe('provider/cloud-element-templates - ElementTemplates', function() { describe('updateTemplate', function() { - let container; - - beforeEach(function() { - container = TestContainer.get(this); - }); - - beforeEach(bootstrapModeler(diagramXML, { - container: container, - modules: [ - coreModule, - elementTemplatesCoreModule, - modelingModule, - { - propertiesPanel: [ 'value', { registerProvider() {} } ] - } - ], - moddleExtensions: { - zeebe: zeebeModdlePackage - }, - elementTemplates: [ + beforeEach(inject(function(elementTemplates) { + elementTemplates.set([ ...templates, ...messageTemplates - ] + ]); })); @@ -1120,6 +1133,7 @@ describe('provider/cloud-element-templates - ElementTemplates', function() { newTemplate }); })); + }); }); From 5b0ceb48d027bb3a7c086822852269e8d3ddc115 Mon Sep 17 00:00:00 2001 From: Nico Rehwaldt Date: Mon, 9 Dec 2024 12:22:02 +0100 Subject: [PATCH 02/11] feat(element-templates): recognize as a complex object This allows us to pass additional configuration in the future. --- .../ElementTemplatesLoader.js | 10 +++++++-- src/element-templates/ElementTemplates.js | 22 +++++++++---------- .../ElementTemplatesLoader.js | 17 ++++++++++---- 3 files changed, 32 insertions(+), 17 deletions(-) diff --git a/src/cloud-element-templates/ElementTemplatesLoader.js b/src/cloud-element-templates/ElementTemplatesLoader.js index 2892b94..50c57c5 100644 --- a/src/cloud-element-templates/ElementTemplatesLoader.js +++ b/src/cloud-element-templates/ElementTemplatesLoader.js @@ -2,10 +2,16 @@ import { Validator } from './Validator'; import { default as TemplatesLoader } from '../element-templates/ElementTemplatesLoader'; +/** + * @param {Object|Array|Function} config + * @param {EventBus} eventBus + * @param {ElementTemplates} elementTemplates + * @param {Moddle} moddle + */ export default class ElementTemplatesLoader extends TemplatesLoader { - constructor(loadTemplates, eventBus, elementTemplates, moddle) { + constructor(config, eventBus, elementTemplates, moddle) { - super(loadTemplates, eventBus, elementTemplates, moddle); + super(config, eventBus, elementTemplates, moddle); this._elementTemplates = elementTemplates; } diff --git a/src/element-templates/ElementTemplates.js b/src/element-templates/ElementTemplates.js index adc4321..754bfaa 100644 --- a/src/element-templates/ElementTemplates.js +++ b/src/element-templates/ElementTemplates.js @@ -26,7 +26,7 @@ export default class ElementTemplates { this._injector = injector; this._modeling = modeling; - this._templates = {}; + this._templatesById = {}; } /** @@ -38,7 +38,7 @@ export default class ElementTemplates { * @return {ElementTemplate} */ get(id, version) { - const templates = this._templates; + const templates = this._templatesById; let element; @@ -108,23 +108,23 @@ export default class ElementTemplates { * @param {Array} templates */ set(templates) { - this._templates = {}; + this._templatesById = {}; templates.forEach((template) => { const id = template.id, version = isUndefined(template.version) ? '_' : template.version; - if (!this._templates[ id ]) { - this._templates[ id ] = { + if (!this._templatesById[ id ]) { + this._templatesById[ id ] = { latest: template }; } - this._templates[ id ][ version ] = template; + this._templatesById[ id ][ version ] = template; - const latestVerions = this._templates[ id ].latest.version; + const latestVerions = this._templatesById[ id ].latest.version; if (isUndefined(latestVerions) || template.version > latestVerions) { - this._templates[ id ].latest = template; + this._templatesById[ id ].latest = template; } }); } @@ -142,7 +142,7 @@ export default class ElementTemplates { deprecated: includeDeprecated } = options; - const templates = this._templates; + const templatesById = this._templatesById; const getVersions = (template) => { const { latest, ...versions } = template; return latestOnly ? ( @@ -151,7 +151,7 @@ export default class ElementTemplates { }; if (isNil(id)) { - return flatten(values(templates).map(getVersions)); + return flatten(values(templatesById).map(getVersions)); } if (isObject(id)) { @@ -163,7 +163,7 @@ export default class ElementTemplates { } if (isString(id)) { - return templates[ id ] && getVersions(templates[ id ]); + return templatesById[ id ] && getVersions(templatesById[ id ]); } throw new Error('argument must be of type {string|djs.model.Base|undefined}'); diff --git a/src/element-templates/ElementTemplatesLoader.js b/src/element-templates/ElementTemplatesLoader.js index f8c42ed..b67eb14 100644 --- a/src/element-templates/ElementTemplatesLoader.js +++ b/src/element-templates/ElementTemplatesLoader.js @@ -1,6 +1,7 @@ import { isFunction, - isUndefined + isUndefined, + isArray, } from 'min-dash'; import { Validator } from './Validator'; @@ -14,18 +15,26 @@ import { Validator } from './Validator'; * descriptors or a node style callback to retrieve * the templates asynchronously. * - * @param {Array|Function} loadTemplates + * @param {Array|Function} config * @param {EventBus} eventBus * @param {ElementTemplates} elementTemplates * @param {Moddle} moddle */ export default class ElementTemplatesLoader { - constructor(loadTemplates, eventBus, elementTemplates, moddle) { - this._loadTemplates = loadTemplates; + constructor(config, eventBus, elementTemplates, moddle) { + this._loadTemplates; this._eventBus = eventBus; this._elementTemplates = elementTemplates; this._moddle = moddle; + if (isArray(config) || isFunction(config)) { + this._loadTemplates = config; + } + + if (config && config.loadTemplates) { + this._loadTemplates = config.templates; + } + eventBus.on('diagram.init', () => { this.reload(); }); From f8f60ba434315ca29af9916e7f3a5ef73c9cc85b Mon Sep 17 00:00:00 2001 From: Nico Rehwaldt Date: Wed, 11 Dec 2024 12:34:50 +0100 Subject: [PATCH 03/11] deps: update to @bpmn-io/element-templates-validator@2.2.0 feat: support field in schema validation See https://github.com/camunda/element-templates-json-schema/blob/main/packages/zeebe-element-templates-json-schema/CHANGELOG.md#0210 Related to https://github.com/camunda/element-templates-json-schema/pull/152 --- package-lock.json | 49 +++++++++++++++++++++++++---------------------- package.json | 2 +- 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/package-lock.json b/package-lock.json index da5dcb7..01de95e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "2.3.0", "license": "MIT", "dependencies": { - "@bpmn-io/element-templates-validator": "^2.1.0", + "@bpmn-io/element-templates-validator": "^2.2.0", "@bpmn-io/extract-process-variables": "^1.0.0", "bpmnlint": "^10.3.0", "classnames": "^2.3.1", @@ -463,12 +463,13 @@ } }, "node_modules/@bpmn-io/element-templates-validator": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@bpmn-io/element-templates-validator/-/element-templates-validator-2.1.0.tgz", - "integrity": "sha512-e8oYLUaZbL1ZuJjwXFyhhStbg0YgMNosIlzhKWdY7ysPhCFVMJlJ6yNYdaxyqfpPATTKb05uXMAsIgcqTQpoLg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@bpmn-io/element-templates-validator/-/element-templates-validator-2.2.0.tgz", + "integrity": "sha512-YsgvHUSSf8oGpc6C5AchQwD7wWo41vmMw3Aa9dT+myI7vgNJlo32aRfdtNkBdRCaU1OZQMIZoF0bCEasRmKgPQ==", + "license": "MIT", "dependencies": { - "@camunda/element-templates-json-schema": "^0.18.0", - "@camunda/zeebe-element-templates-json-schema": "^0.20.0", + "@camunda/element-templates-json-schema": "^0.18.1", + "@camunda/zeebe-element-templates-json-schema": "^0.21.0", "json-source-map": "^0.6.1", "min-dash": "^4.1.1" } @@ -567,9 +568,10 @@ } }, "node_modules/@camunda/element-templates-json-schema": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@camunda/element-templates-json-schema/-/element-templates-json-schema-0.18.0.tgz", - "integrity": "sha512-k2k+1Z7UiW1TSA1oAvDQamgFZljH3hkFjU9VSpjVXnPgcjVxJMLX0mrHjLVtXhEx2tw576FzYGqlfudw6OOMKg==" + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@camunda/element-templates-json-schema/-/element-templates-json-schema-0.18.1.tgz", + "integrity": "sha512-gwQJHUYx1FrIJCgJISx2cpqTJYgnsqrJ6dpPX/R0p6ELyK6u4rHAi/m9QS1O4F6ua7dBlFFFOOtuIAbo5mAfAg==", + "license": "MIT" }, "node_modules/@camunda/linting": { "version": "3.23.0", @@ -623,9 +625,10 @@ } }, "node_modules/@camunda/zeebe-element-templates-json-schema": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@camunda/zeebe-element-templates-json-schema/-/zeebe-element-templates-json-schema-0.20.0.tgz", - "integrity": "sha512-7YRN32Nq73H8S1rCOy2/6cfx+fKiTnhveJYfP6aRaIi83ZSlhVomRJ5+pnPmlDJqdFeNcIx1qqQwVFAdgNPFhg==" + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@camunda/zeebe-element-templates-json-schema/-/zeebe-element-templates-json-schema-0.21.0.tgz", + "integrity": "sha512-etC2PXoHAQ+74xokXYczfb5HmLtisSYCeKSl1c5PCKD6nWWLpGaha86edkPvTKt6SbHCDLZ0dzWXY0iBpyWfow==", + "license": "MIT" }, "node_modules/@codemirror/autocomplete": { "version": "6.17.0", @@ -10544,12 +10547,12 @@ } }, "@bpmn-io/element-templates-validator": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@bpmn-io/element-templates-validator/-/element-templates-validator-2.1.0.tgz", - "integrity": "sha512-e8oYLUaZbL1ZuJjwXFyhhStbg0YgMNosIlzhKWdY7ysPhCFVMJlJ6yNYdaxyqfpPATTKb05uXMAsIgcqTQpoLg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@bpmn-io/element-templates-validator/-/element-templates-validator-2.2.0.tgz", + "integrity": "sha512-YsgvHUSSf8oGpc6C5AchQwD7wWo41vmMw3Aa9dT+myI7vgNJlo32aRfdtNkBdRCaU1OZQMIZoF0bCEasRmKgPQ==", "requires": { - "@camunda/element-templates-json-schema": "^0.18.0", - "@camunda/zeebe-element-templates-json-schema": "^0.20.0", + "@camunda/element-templates-json-schema": "^0.18.1", + "@camunda/zeebe-element-templates-json-schema": "^0.21.0", "json-source-map": "^0.6.1", "min-dash": "^4.1.1" } @@ -10637,9 +10640,9 @@ } }, "@camunda/element-templates-json-schema": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@camunda/element-templates-json-schema/-/element-templates-json-schema-0.18.0.tgz", - "integrity": "sha512-k2k+1Z7UiW1TSA1oAvDQamgFZljH3hkFjU9VSpjVXnPgcjVxJMLX0mrHjLVtXhEx2tw576FzYGqlfudw6OOMKg==" + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@camunda/element-templates-json-schema/-/element-templates-json-schema-0.18.1.tgz", + "integrity": "sha512-gwQJHUYx1FrIJCgJISx2cpqTJYgnsqrJ6dpPX/R0p6ELyK6u4rHAi/m9QS1O4F6ua7dBlFFFOOtuIAbo5mAfAg==" }, "@camunda/linting": { "version": "3.23.0", @@ -10680,9 +10683,9 @@ } }, "@camunda/zeebe-element-templates-json-schema": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@camunda/zeebe-element-templates-json-schema/-/zeebe-element-templates-json-schema-0.20.0.tgz", - "integrity": "sha512-7YRN32Nq73H8S1rCOy2/6cfx+fKiTnhveJYfP6aRaIi83ZSlhVomRJ5+pnPmlDJqdFeNcIx1qqQwVFAdgNPFhg==" + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@camunda/zeebe-element-templates-json-schema/-/zeebe-element-templates-json-schema-0.21.0.tgz", + "integrity": "sha512-etC2PXoHAQ+74xokXYczfb5HmLtisSYCeKSl1c5PCKD6nWWLpGaha86edkPvTKt6SbHCDLZ0dzWXY0iBpyWfow==" }, "@codemirror/autocomplete": { "version": "6.17.0", diff --git a/package.json b/package.json index 3edce23..b4610ea 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ ], "license": "MIT", "dependencies": { - "@bpmn-io/element-templates-validator": "^2.1.0", + "@bpmn-io/element-templates-validator": "^2.2.0", "@bpmn-io/extract-process-variables": "^1.0.0", "bpmnlint": "^10.3.0", "classnames": "^2.3.1", From 47edb77245d7662593f2f39c0ea6a8f346a7cc4f Mon Sep 17 00:00:00 2001 From: Nico Rehwaldt Date: Mon, 9 Dec 2024 12:16:26 +0100 Subject: [PATCH 04/11] feat: support element templates runtime versions Config for current runtime platform and version can now be passed to elementTemplates. If template has information about supported engines, compatibility will be verified. Related to https://github.com/camunda/camunda-modeler/issues/4530 feat: emit as part of This allows us to safely use to reflect internal changes. --- package-lock.json | 153 +++++-- package.json | 1 + .../ElementTemplates.js | 8 +- .../ElementTemplatesLoader.js | 4 +- src/cloud-element-templates/Validator.js | 44 +- src/element-templates/ElementTemplates.js | 128 +++++- .../ElementTemplatesLoader.js | 16 +- src/element-templates/Validator.js | 43 +- .../ElementTemplates.engines-templates.json | 82 ++++ .../ElementTemplates.spec.js | 392 ++++++++++++++++++ .../cloud-element-templates/Validator.spec.js | 39 ++ .../fixtures/engines-invalid.json | 16 + .../fixtures/engines.json | 97 +++++ .../fixtures/falsy-version.json | 10 + .../ElementTemplates.engines-templates.json | 82 ++++ .../ElementTemplates.spec.js | 309 ++++++++++++++ .../ElementTemplatesLoader.spec.js | 52 +++ test/spec/element-templates/Validator.spec.js | 38 ++ .../fixtures/engines-invalid.json | 15 + .../element-templates/fixtures/engines.json | 91 ++++ 20 files changed, 1521 insertions(+), 99 deletions(-) create mode 100644 test/spec/cloud-element-templates/ElementTemplates.engines-templates.json create mode 100644 test/spec/cloud-element-templates/fixtures/engines-invalid.json create mode 100644 test/spec/cloud-element-templates/fixtures/engines.json create mode 100644 test/spec/cloud-element-templates/fixtures/falsy-version.json create mode 100644 test/spec/element-templates/ElementTemplates.engines-templates.json create mode 100644 test/spec/element-templates/fixtures/engines-invalid.json create mode 100644 test/spec/element-templates/fixtures/engines.json diff --git a/package-lock.json b/package-lock.json index 01de95e..02a4c77 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "min-dash": "^4.0.0", "min-dom": "^4.0.3", "preact-markup": "^2.1.1", + "semver": "^7.6.3", "semver-compare": "^1.0.0", "uuid": "^11.0.0" }, @@ -163,6 +164,15 @@ "url": "https://opencollective.com/babel" } }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@babel/generator": { "version": "7.26.2", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.2.tgz", @@ -209,6 +219,15 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@babel/helper-module-imports": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", @@ -1192,19 +1211,6 @@ "node": ">=12" } }, - "node_modules/@puppeteer/browsers/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@puppeteer/browsers/node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", @@ -2590,18 +2596,6 @@ "node": ">=10" } }, - "node_modules/babel-plugin-istanbul/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -4336,6 +4330,15 @@ "node": ">=0.10.0" } }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/eslint-plugin-mocha": { "version": "10.5.0", "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-10.5.0.tgz", @@ -4444,6 +4447,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/eslint-scope": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", @@ -6261,6 +6273,15 @@ "node": ">=8" } }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/istanbul-lib-report": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", @@ -6983,6 +7004,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -8843,12 +8873,14 @@ "dev": true }, "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "bin": { "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/semver-compare": { @@ -10330,6 +10362,14 @@ "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + } } }, "@babel/generator": { @@ -10365,6 +10405,14 @@ "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + } } }, "@babel/helper-module-imports": { @@ -11107,12 +11155,6 @@ "wrap-ansi": "^7.0.0" } }, - "semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true - }, "yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", @@ -12114,12 +12156,6 @@ "istanbul-lib-coverage": "^3.2.0", "semver": "^7.5.4" } - }, - "semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true } } }, @@ -13510,6 +13546,12 @@ "requires": { "esutils": "^2.0.2" } + }, + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true } } }, @@ -13580,6 +13622,12 @@ "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" } + }, + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true } } }, @@ -14750,6 +14798,14 @@ "@istanbuljs/schema": "^0.1.2", "istanbul-lib-coverage": "^3.2.0", "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + } } }, "istanbul-lib-report": { @@ -15325,6 +15381,14 @@ "dev": true, "requires": { "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + } } }, "media-typer": { @@ -16671,10 +16735,9 @@ } }, "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==" }, "semver-compare": { "version": "1.0.0", diff --git a/package.json b/package.json index b4610ea..c946e55 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,7 @@ "min-dash": "^4.0.0", "min-dom": "^4.0.3", "preact-markup": "^2.1.1", + "semver": "^7.6.3", "semver-compare": "^1.0.0", "uuid": "^11.0.0" }, diff --git a/src/cloud-element-templates/ElementTemplates.js b/src/cloud-element-templates/ElementTemplates.js index 08df40e..2fecc16 100644 --- a/src/cloud-element-templates/ElementTemplates.js +++ b/src/cloud-element-templates/ElementTemplates.js @@ -9,8 +9,8 @@ import { default as DefaultElementTemplates } from '../element-templates/Element * Registry for element templates. */ export default class ElementTemplates extends DefaultElementTemplates { - constructor(templateElementFactory, commandStack, eventBus, modeling, injector) { - super(commandStack, eventBus, modeling, injector); + constructor(templateElementFactory, commandStack, eventBus, modeling, injector, config) { + super(commandStack, eventBus, modeling, injector, config); this._templateElementFactory = templateElementFactory; } @@ -75,6 +75,7 @@ export default class ElementTemplates extends DefaultElementTemplates { return context.element; } + } ElementTemplates.$inject = [ @@ -82,5 +83,6 @@ ElementTemplates.$inject = [ 'commandStack', 'eventBus', 'modeling', - 'injector' + 'injector', + 'config.elementTemplates', ]; diff --git a/src/cloud-element-templates/ElementTemplatesLoader.js b/src/cloud-element-templates/ElementTemplatesLoader.js index 50c57c5..1f9df91 100644 --- a/src/cloud-element-templates/ElementTemplatesLoader.js +++ b/src/cloud-element-templates/ElementTemplatesLoader.js @@ -28,10 +28,8 @@ export default class ElementTemplatesLoader extends TemplatesLoader { elementTemplates.set(validTemplates); if (errors.length) { - this.templateErrors(errors); + this._templateErrors(errors); } - - this.templatesChanged(); } } diff --git a/src/cloud-element-templates/Validator.js b/src/cloud-element-templates/Validator.js index a2b9518..8b51d3e 100644 --- a/src/cloud-element-templates/Validator.js +++ b/src/cloud-element-templates/Validator.js @@ -6,11 +6,16 @@ import { import semverCompare from 'semver-compare'; +import { + validRange as isSemverRangeValid +} from 'semver'; + import { validateZeebe as validateAgainstSchema, getZeebeSchemaPackage as getTemplateSchemaPackage, getZeebeSchemaVersion as getTemplateSchemaVersion } from '@bpmn-io/element-templates-validator'; +import { forEach } from 'min-dash'; const SUPPORTED_SCHEMA_VERSION = getTemplateSchemaVersion(); const SUPPORTED_SCHEMA_PACKAGE = getTemplateSchemaPackage(); @@ -31,8 +36,6 @@ export class Validator extends BaseValidator { * @return {Error} validation error, if any */ _validateTemplate(template) { - let err; - const id = template.id, version = template.version || '_', schema = template.$schema, @@ -78,25 +81,48 @@ export class Validator extends BaseValidator { } // (5) JSON schema compliance - const validationResult = validateAgainstSchema(template); + const schemaValidationResult = validateAgainstSchema(template); const { - errors, + errors: schemaErrors, valid - } = validationResult; + } = schemaValidationResult; if (!valid) { - err = new Error('invalid template'); - - filteredSchemaErrors(errors).forEach((error) => { + filteredSchemaErrors(schemaErrors).forEach((error) => { this._logError(error.message, template); }); + + return new Error('invalid template'); } - return err; + // (6) engines validation + const enginesError = this._validateEngines(template); + + if (enginesError) { + return enginesError; + } + + return null; } isSchemaValid(schema) { return schema && schema.includes(SUPPORTED_SCHEMA_PACKAGE); } + + _validateEngines(template) { + + let err; + + forEach(template.engines, (rangeStr, engine) => { + + if (!isSemverRangeValid(rangeStr)) { + err = this._logError(new Error( + `Engine <${engine}> specifies invalid semver range <${rangeStr}>` + ), template); + } + }); + + return err; + } } diff --git a/src/element-templates/ElementTemplates.js b/src/element-templates/ElementTemplates.js index 754bfaa..38ea80b 100644 --- a/src/element-templates/ElementTemplates.js +++ b/src/element-templates/ElementTemplates.js @@ -2,10 +2,12 @@ import { filter, find, flatten, + has, isNil, isObject, isString, isUndefined, + reduce, values } from 'min-dash'; @@ -16,17 +18,32 @@ import { import { isAny } from 'bpmn-js/lib/util/ModelUtil'; +import { + valid as isSemverValid, + satisfies as isSemverCompatible, + coerce +} from 'semver'; + /** * Registry for element templates. */ export default class ElementTemplates { - constructor(commandStack, eventBus, modeling, injector) { + constructor(commandStack, eventBus, modeling, injector, config) { this._commandStack = commandStack; this._eventBus = eventBus; this._injector = injector; this._modeling = modeling; this._templatesById = {}; + this._templates = []; + + config = config || {}; + + this._engines = this._coerceEngines(config.engines || {}); + + eventBus.on('elementTemplates.engines.changed', event => { + this.set(this._templates); + }); } /** @@ -109,24 +126,94 @@ export default class ElementTemplates { */ set(templates) { this._templatesById = {}; + this._templates = templates; templates.forEach((template) => { - const id = template.id, - version = isUndefined(template.version) ? '_' : template.version; + const id = template.id; + const version = isUndefined(template.version) ? '_' : template.version; if (!this._templatesById[ id ]) { - this._templatesById[ id ] = { - latest: template - }; + this._templatesById[ id ] = { }; } this._templatesById[ id ][ version ] = template; - const latestVerions = this._templatesById[ id ].latest.version; - if (isUndefined(latestVerions) || template.version > latestVerions) { - this._templatesById[ id ].latest = template; + const latest = this._templatesById[ id ].latest; + + if (this.isCompatible(template)) { + if (!latest || isUndefined(latest.version) || latest.version < version) { + this._templatesById[ id ].latest = template; + } } }); + + this._fire('changed'); + } + + getEngines() { + return this._engines; + } + + setEngines(engines) { + this._engines = this._coerceEngines(engines); + + this._fire('engines.changed'); + } + + /** + * Ensures that only valid engines are kept around + * + * @param { Record } engines + * + * @return { Record } filtered, valid engines + */ + _coerceEngines(engines) { + + return reduce(engines, (validEngines, version, engine) => { + + const coercedVersion = coerce(version); + + if (!isSemverValid(coercedVersion)) { + console.error( + new Error(`Engine <${ engine }> specifies unparseable version <${version}>`) + ); + + return validEngines; + } + + return { + ...validEngines, + [ engine ]: coercedVersion.raw + }; + }, {}); + } + + /** + * Check if template is compatible with currently set engine version. + * + * @param {ElementTemplate} template + * + * @return {boolean} - true if compatible or no engine is set for elementTemplates or template. + */ + isCompatible(template) { + const localEngines = this._engines; + const templateEngines = template.engines; + + for (const engine in templateEngines) { + + // we check compatibility against all locally provided + // engines, hence computing the overlap here. + + if (!has(localEngines, engine)) { + continue; + } + + if (!isSemverCompatible(localEngines[engine], templateEngines[engine])) { + return false; + } + } + + return true; } /** @@ -138,15 +225,15 @@ export default class ElementTemplates { _getTemplateVerions(id, options = {}) { const { - latest: latestOnly, + latest: includeLatestOnly, deprecated: includeDeprecated } = options; const templatesById = this._templatesById; const getVersions = (template) => { const { latest, ...versions } = template; - return latestOnly ? ( - !includeDeprecated && latest.deprecated ? [] : [ latest ] + return includeLatestOnly ? ( + !includeDeprecated && (latest && latest.deprecated) ? [] : (latest ? [ latest ] : []) ) : values(versions) ; }; @@ -208,11 +295,15 @@ export default class ElementTemplates { this._commandStack.execute('propertiesPanel.camunda.changeTemplate', context); - this._eventBus.fire(`elementTemplates.${action}`, payload); + this._fire(action, payload); return context.element; } + _fire(action, payload) { + return this._eventBus.fire(`elementTemplates.${action}`, payload); + } + /** * Remove template from a given element. * @@ -221,9 +312,7 @@ export default class ElementTemplates { * @return {djs.model.Base} the updated element */ removeTemplate(element) { - const eventBus = this._injector.get('eventBus'); - - eventBus.fire('elementTemplates.remove', { element }); + this._fire('remove', { element }); const context = { element @@ -252,7 +341,6 @@ ElementTemplates.$inject = [ 'commandStack', 'eventBus', 'modeling', - 'injector' -]; - - + 'injector', + 'config.elementTemplates' +]; \ No newline at end of file diff --git a/src/element-templates/ElementTemplatesLoader.js b/src/element-templates/ElementTemplatesLoader.js index b67eb14..f4c74c3 100644 --- a/src/element-templates/ElementTemplatesLoader.js +++ b/src/element-templates/ElementTemplatesLoader.js @@ -32,7 +32,7 @@ export default class ElementTemplatesLoader { } if (config && config.loadTemplates) { - this._loadTemplates = config.templates; + this._loadTemplates = config.loadTemplates; } eventBus.on('diagram.init', () => { @@ -54,7 +54,7 @@ export default class ElementTemplatesLoader { return loadTemplates((err, templates) => { if (err) { - return this.templateErrors([ err ]); + return this._templateErrors([ err ]); } this.setTemplates(templates); @@ -79,18 +79,12 @@ export default class ElementTemplatesLoader { elementTemplates.set(validTemplates); if (errors.length) { - this.templateErrors(errors); + this._templateErrors(errors); } - - this.templatesChanged(); - } - - templatesChanged() { - this._eventBus.fire('elementTemplates.changed'); } - templateErrors(errors) { - this._eventBus.fire('elementTemplates.errors', { + _templateErrors(errors) { + this._elementTemplates._fire('errors', { errors: errors }); } diff --git a/src/element-templates/Validator.js b/src/element-templates/Validator.js index 5f31e7b..87db86f 100644 --- a/src/element-templates/Validator.js +++ b/src/element-templates/Validator.js @@ -1,11 +1,16 @@ import { filter, + forEach, isArray, isString } from 'min-dash'; import semverCompare from 'semver-compare'; +import { + validRange as isSemverRangeValid +} from 'semver'; + import { validate as validateAgainstSchema, getSchemaVersion as getTemplateSchemaVersion @@ -14,6 +19,7 @@ import { const SUPPORTED_SCHEMA_VERSION = getTemplateSchemaVersion(); const MORPHABLE_TYPES = [ 'bpmn:Activity', 'bpmn:Event', 'bpmn:Gateway' ]; + /** * A element template validator. */ @@ -79,8 +85,6 @@ export class Validator { * @return {Error} validation error, if any */ _validateTemplate(template) { - let err; - const id = template.id, version = template.version || '_', schemaVersion = template.$schema && getSchemaVersion(template.$schema); @@ -110,21 +114,44 @@ export class Validator { } // (4) JSON schema compliance - const validationResult = validateAgainstSchema(template); + const schemaValidationResult = validateAgainstSchema(template); const { - errors, + errors: schemaErrors, valid - } = validationResult; + } = schemaValidationResult; if (!valid) { - err = new Error('invalid template'); - - filteredSchemaErrors(errors).forEach((error) => { + filteredSchemaErrors(schemaErrors).forEach((error) => { this._logError(error.message, template); }); + + return new Error('invalid template'); + } + + // (5) engines validation + const enginesError = this._validateEngines(template); + + if (enginesError) { + return enginesError; } + return null; + } + + _validateEngines(template) { + + let err; + + forEach(template.engines, (rangeStr, engine) => { + + if (!isSemverRangeValid(rangeStr)) { + err = this._logError(new Error( + `Engine <${engine}> specifies invalid semver range <${rangeStr}>` + ), template); + } + }); + return err; } diff --git a/test/spec/cloud-element-templates/ElementTemplates.engines-templates.json b/test/spec/cloud-element-templates/ElementTemplates.engines-templates.json new file mode 100644 index 0000000..da5ffbb --- /dev/null +++ b/test/spec/cloud-element-templates/ElementTemplates.engines-templates.json @@ -0,0 +1,82 @@ +[ + { + "id": "example.engines.test.multiple", + "name": " Test - Multiple", + "description": "does not match if { desktopModeler: >=1 } is provided", + "version": 2, + "engines": { + "camunda": "^8.6", + "webModeler": "^4.1", + "desktopModeler": "^0" + }, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [] + }, + { + "id": "example.engines.test.multiple", + "name": " Test - Multiple", + "description": "matches if { camunda: ^8.6, webModeler: ^4.1 } engine is indicated, or properties are not provided", + "version": 1, + "engines": { + "camunda": "^8.6", + "webModeler": "^4.1" + }, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [] + }, + + { + "id": "example.engines.test.basic", + "name": " Test - Basic", + "description": "matches if { camunda: ^8.6 }, or if no engine is provided", + "version": 3, + "engines": { + "camunda": "^8.6" + }, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [] + }, + { + "id": "example.engines.test.basic", + "name": " Test - Basic", + "description": "matches if { camunda: ^8.5 }, or if no engine is provided", + "version": 2, + "engines": { + "camunda": "^8.5" + }, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [] + }, + { + "id": "example.engines.test.basic", + "name": " Test - Basic", + "description": "always matches", + "version": 1, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [] + }, + + { + "id": "example.engines.test.broken", + "name": " Test - broken Semver range", + "description": "specifies broken semver range", + "version": 1, + "engines": { + "camunda": "invalid-version" + }, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [] + } +] \ No newline at end of file diff --git a/test/spec/cloud-element-templates/ElementTemplates.spec.js b/test/spec/cloud-element-templates/ElementTemplates.spec.js index 0fb42db..e086386 100644 --- a/test/spec/cloud-element-templates/ElementTemplates.spec.js +++ b/test/spec/cloud-element-templates/ElementTemplates.spec.js @@ -24,8 +24,10 @@ import zeebeModdlePackage from 'zeebe-bpmn-moddle/resources/zeebe'; import diagramXML from './ElementTemplates.bpmn'; import integrationXML from './fixtures/integration.bpmn'; import messageTemplates from './ElementTemplates.message-templates.json'; +import enginesTemplates from './ElementTemplates.engines-templates.json'; import templates from './fixtures/simple'; +import falsyVersionTemplate from './fixtures/falsy-version'; import complexTemplates from './fixtures/complex'; import integrationTemplates from './fixtures/integration'; import { findExtensions, findExtension } from 'src/cloud-element-templates/Helper'; @@ -343,6 +345,206 @@ describe('provider/cloud-element-templates - ElementTemplates', function() { })); + describe(' compatibility', function() { + + beforeEach(inject(function(elementTemplates) { + elementTemplates.set(enginesTemplates); + })); + + + describe('should retrieve latest compatible', function() { + + it('single template', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + camunda: '8.6.3' + }); + + // when + const templates = elementTemplates.getLatest('example.engines.test.basic'); + + // then + expect(templates).to.have.length(1); + expect(templates[0].version).to.eql(3); + })); + + + it('all templates', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + camunda: '8.6.3' + }); + + // when + const templates = elementTemplates.getLatest(); + + // then + // expect all compatible templates to be returned + // example.engines.test.multiple v2 + // example.engines.test.basic v2 + expect(templates).to.have.length(2); + })); + + }); + + + it('should retrieve older compatible', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + camunda: '8.5' + }); + + // when + const templates = elementTemplates.getLatest('example.engines.test.basic'); + + // then + expect(templates).to.have.length(1); + expect(templates[0].version).to.eql(2); + })); + + + it('should retrieve fallback (no meta-data)', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + camunda: '4' + }); + + // when + const templates = elementTemplates.getLatest('example.engines.test.basic'); + + // then + expect(templates).to.have.length(1); + expect(templates[0].version).to.eql(1); + })); + + + describe('should handle no context provided', function() { + + it('single template', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({}); + + // when + const templates = elementTemplates.getLatest('example.engines.test.basic'); + + // then + expect(templates).to.have.length(1); + expect(templates[0].version).to.eql(3); + })); + + + it('list templates', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({}); + + // when + const templates = elementTemplates.getLatest(); + + // then + // example.engines.test.multiple v2 + // example.engines.test.basic v3 + // example.engines.test.broken v1 + expect(templates).to.have.length(3); + })); + + }); + + + it('should support multiple engines', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + camunda: '8.6', + webModeler: '4.3' + }); + + // when + const templates = elementTemplates.getLatest('example.engines.test.multiple'); + + // then + expect(templates).to.have.length(1); + expect(templates[0].version).to.eql(2); + })); + + + it('should exclude engine', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + camunda: '8.6', + webModeler: '4.3', + desktopModeler: '5.4' + }); + + // when + const templates = elementTemplates.getLatest('example.engines.test.multiple'); + + // then + expect(templates).to.have.length(1); + expect(templates[0].version).to.eql(1); + })); + + + it('should ignore incompatible', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + camunda: '8.4' + }); + + // when + const templates = elementTemplates.getLatest('example.engines.test.multiple'); + + // then + expect(templates).to.be.empty; + })); + + + it('should handle broken provided at run-time', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + camunda: 'one-hundred' + }); + + // when + const templates = elementTemplates.getLatest('example.engines.test.basic'); + + // then + // we ignore the context entry, assume it is not there + expect(templates).to.have.length(1); + expect(templates[0].version).to.eql(3); + })); + + + it('should handle broken provided by template', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + camunda: '8.6' + }); + + // when + const templates = elementTemplates.getLatest('example.engines.test.broken'); + + // then + expect(templates).to.be.empty; + + // and + // we still regard such template as a valid template + const template = elementTemplates.get('example.engines.test.broken', 1); + expect(template).to.exist; + })); + + }); + + it('should throw for invalid argument', inject(function(elementTemplates) { // then @@ -542,6 +744,84 @@ describe('provider/cloud-element-templates - ElementTemplates', function() { }); + describe('set', function() { + + it('should set templates', inject(function(elementTemplates) { + + // when + elementTemplates.set(templates.slice(0, 3)); + + // then + expect(elementTemplates.getAll()).to.have.length(3); + })); + + + it('should not ignore version set to 0', inject(function(elementTemplates) { + + // when + elementTemplates.set(falsyVersionTemplate); + + // then + expect(elementTemplates.get(falsyVersionTemplate[0].id, 0)).to.exist; + })); + + + it('should emit event', inject(function(elementTemplates, eventBus) { + + // given + const spy = sinon.spy(); + + eventBus.on('elementTemplates.changed', spy); + + // when + elementTemplates.set(templates); + + // then + expect(spy).to.have.been.calledOnce; + })); + + }); + + + describe('getEngines', function() { + + it('should provide set engines', inject(function(elementTemplates) { + + // when + elementTemplates.setEngines({ + 'camunda': '8', + 'other': '100.5' + }); + + // then + expect(elementTemplates.getEngines()).to.eql({ + 'camunda': '8.0.0', + 'other': '100.5.0' + }); + })); + + }); + + + describe('setEngines', function() { + + it('should emit event', inject(function(elementTemplates, eventBus) { + + // given + const spy = sinon.spy(); + + eventBus.on('elementTemplates.engines.changed', spy); + + // when + elementTemplates.setEngines({}); + + // then + expect(spy).to.have.been.calledOnce; + })); + + }); + + describe('applyTemplate', function() { beforeEach(inject(function(elementTemplates) { @@ -866,6 +1146,7 @@ describe('provider/cloud-element-templates - ElementTemplates', function() { expect(getBusinessObject(task).get('zeebe:modelerTemplateIcon')).to.not.exist; })); + it('should fire elementTemplates.unlink event', inject(function(elementRegistry, elementTemplates, eventBus) { // given @@ -1136,6 +1417,117 @@ describe('provider/cloud-element-templates - ElementTemplates', function() { }); + + describe('isCompatible', function() { + + const compatibleTemplate = { + engines: { + camunda: '^8.5' + } + }; + + const incompatibleTemplate = { + engines: { + camunda: '^8.6' + } + }; + + + it('should accept compatible', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + camunda: '8.5' + }); + + // then + expect(elementTemplates.isCompatible(compatibleTemplate)).to.be.true; + })); + + + it('should reject incompatible', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + camunda: '8.5' + }); + + // then + expect(elementTemplates.isCompatible(incompatibleTemplate)).to.be.false; + })); + + + it('should accept non matching engine', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + nonMatchingEngine: '8.5' + }); + + // then + expect(elementTemplates.isCompatible(compatibleTemplate)).to.be.true; + expect(elementTemplates.isCompatible(incompatibleTemplate)).to.be.true; + })); + + }); + + + describe('error handling', function() { + + // given + const invalidEngines = { + camunda: '8.5', + invalid: 'not-a-semver' + }; + + it('should filter invalid on set', inject(function(elementTemplates) { + + // when + elementTemplates.setEngines(invalidEngines); + + // then + expect(elementTemplates.getEngines()).to.have.property('camunda'); + expect(elementTemplates.getEngines()).to.not.have.property('invalid'); + })); + }); + +}); + + +describe('provider/cloud-element-templates - ElementTemplates - error handling on instantiation', function() { + + let container; + + beforeEach(function() { + container = TestContainer.get(this); + }); + + // given + const invalidEngines = { + camunda: '8.5', + invalid: 'not-a-semver' + }; + + beforeEach(bootstrapModeler(diagramXML, { + container: container, + modules: [ + coreModule, + modelingModule, + elementTemplatesCoreModule, + ], + elementTemplates: { + engines: invalidEngines + } + })); + + + it('should filter invalid on instantiation', inject(function(elementTemplates) { + + // then + expect(elementTemplates.getEngines()).to.have.property('camunda', '8.5.0'); + expect(elementTemplates.getEngines()).to.not.have.property('invalid'); + })); + }); diff --git a/test/spec/cloud-element-templates/Validator.spec.js b/test/spec/cloud-element-templates/Validator.spec.js index c63f4be..1b2a8e8 100644 --- a/test/spec/cloud-element-templates/Validator.spec.js +++ b/test/spec/cloud-element-templates/Validator.spec.js @@ -24,6 +24,7 @@ describe('provider/cloud-element-templates - Validator', function() { moddle = new BPMNModdle(); }); + describe('schema version', function() { it('should accept when template and library have the same version', function() { @@ -584,4 +585,42 @@ describe('provider/cloud-element-templates - Validator', function() { }); + + describe('engines validation', function() { + + it('should accept template with valid semver range', function() { + + // given + const templates = new Validator(moddle); + + const templateDescriptor = require('./fixtures/engines'); + + // when + templates.addAll(templateDescriptor); + + // then + expect(errors(templates)).to.be.empty; + + expect(valid(templates)).to.have.length(templateDescriptor.length); + }); + + + it('should reject template with invalid semver range', function() { + + // given + const templates = new Validator(moddle); + + const templateDescriptor = require('./fixtures/engines-invalid'); + + // when + templates.addAll(templateDescriptor); + + // then + expect(errors(templates)).to.contain('Engine specifies invalid semver range '); + + expect(valid(templates)).to.be.empty; + }); + + }); + }); diff --git a/test/spec/cloud-element-templates/fixtures/engines-invalid.json b/test/spec/cloud-element-templates/fixtures/engines-invalid.json new file mode 100644 index 0000000..540001e --- /dev/null +++ b/test/spec/cloud-element-templates/fixtures/engines-invalid.json @@ -0,0 +1,16 @@ +[ + { + "$schema": "https://unpkg.com/@camunda/zeebe-element-templates-json-schema/resources/schema.json", + "id": "example.engines.test.broken", + "name": " Test - broken Semver range", + "description": "specifies broken semver range", + "version": 1, + "engines": { + "camunda": "invalid-version" + }, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [] + } +] \ No newline at end of file diff --git a/test/spec/cloud-element-templates/fixtures/engines.json b/test/spec/cloud-element-templates/fixtures/engines.json new file mode 100644 index 0000000..c525f14 --- /dev/null +++ b/test/spec/cloud-element-templates/fixtures/engines.json @@ -0,0 +1,97 @@ +[ + { + "$schema": "https://unpkg.com/@camunda/zeebe-element-templates-json-schema/resources/schema.json", + "id": "example.engines.test.multiple", + "name": " Test - Multiple", + "description": "does not match if { desktopModeler: >=1 } is provided", + "version": 2, + "engines": { + "camunda": "^8.6", + "webModeler": "^4.1", + "desktopModeler": "^0" + }, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [] + }, + { + "$schema": "https://unpkg.com/@camunda/zeebe-element-templates-json-schema/resources/schema.json", + "id": "example.engines.test.multiple", + "name": " Test - Multiple", + "description": "matches if { camunda: ^8.6, webModeler: ^4.1 } engine is indicated, or properties are not provided", + "version": 1, + "engines": { + "camunda": "^8.6", + "webModeler": "^4.1" + }, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [] + }, + + { + "$schema": "https://unpkg.com/@camunda/zeebe-element-templates-json-schema/resources/schema.json", + "id": "example.engines.test.basic", + "name": " Test - Basic", + "description": "matches if { camunda: ^8.6 }, or if no engine is provided", + "version": 3, + "engines": { + "camunda": "^8.6" + }, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [] + }, + { + "$schema": "https://unpkg.com/@camunda/zeebe-element-templates-json-schema/resources/schema.json", + "id": "example.engines.test.basic", + "name": " Test - Basic", + "description": "matches if { camunda: ^8.5 }, or if no engine is provided", + "version": 2, + "engines": { + "camunda": "^8.5" + }, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [] + }, + { + "$schema": "https://unpkg.com/@camunda/zeebe-element-templates-json-schema/resources/schema.json", + "id": "example.engines.test.basic", + "name": " Test - Basic", + "description": "always matches", + "version": 1, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [] + }, + + { + "$schema": "https://unpkg.com/@camunda/zeebe-element-templates-json-schema/resources/schema.json", + "id": "example.engines.test.incompatible", + "name": " Test - incompatible", + "description": "incompatible with all camunda versions", + "engines": { + "camunda": "0" + }, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [ + { + "label": "Task Header 2", + "type": "String", + "value": "header-2-value", + "binding": { + "type": "zeebe:taskHeader", + "key": "header-2-key" + } + } + ] + } +] \ No newline at end of file diff --git a/test/spec/cloud-element-templates/fixtures/falsy-version.json b/test/spec/cloud-element-templates/fixtures/falsy-version.json new file mode 100644 index 0000000..86d6fd7 --- /dev/null +++ b/test/spec/cloud-element-templates/fixtures/falsy-version.json @@ -0,0 +1,10 @@ +[ + { + "$schema": "https://unpkg.com/@camunda/zeebe-element-templates-json-schema/resources/schema.json", + "id": "foo", + "name":"Foo 1", + "version": 0, + "appliesTo": [ "bpmn:Task" ], + "properties": [] + } +] \ No newline at end of file diff --git a/test/spec/element-templates/ElementTemplates.engines-templates.json b/test/spec/element-templates/ElementTemplates.engines-templates.json new file mode 100644 index 0000000..1f4d7fe --- /dev/null +++ b/test/spec/element-templates/ElementTemplates.engines-templates.json @@ -0,0 +1,82 @@ +[ + { + "id": "example.engines.test.multiple", + "name": " Test - Multiple", + "description": "does not match if { desktopModeler: >=1 } is provided", + "version": 2, + "engines": { + "camunda": "^7.14", + "webModeler": "^4.1", + "desktopModeler": "^0" + }, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [] + }, + { + "id": "example.engines.test.multiple", + "name": " Test - Multiple", + "description": "matches if { camunda: ^7.14, webModeler: ^4.1 } engine is indicated, or properties are not provided", + "version": 1, + "engines": { + "camunda": "^7.14", + "webModeler": "^4.1" + }, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [] + }, + + { + "id": "example.engines.test.basic", + "name": " Test - Basic", + "description": "matches if { camunda: ^7.14 }, or if no engine is provided", + "version": 3, + "engines": { + "camunda": "^7.14" + }, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [] + }, + { + "id": "example.engines.test.basic", + "name": " Test - Basic", + "description": "matches if { camunda: ^7.13 }, or if no engine is provided", + "version": 2, + "engines": { + "camunda": "^7.13" + }, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [] + }, + { + "id": "example.engines.test.basic", + "name": " Test - Basic", + "description": "always matches", + "version": 1, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [] + }, + + { + "id": "example.engines.test.broken", + "name": " Test - broken Semver range", + "description": "specifies broken semver range", + "version": 1, + "engines": { + "camunda": "invalid-version" + }, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [] + } +] \ No newline at end of file diff --git a/test/spec/element-templates/ElementTemplates.spec.js b/test/spec/element-templates/ElementTemplates.spec.js index cd10562..b57ccc2 100644 --- a/test/spec/element-templates/ElementTemplates.spec.js +++ b/test/spec/element-templates/ElementTemplates.spec.js @@ -20,6 +20,7 @@ import diagramXML from './ElementTemplates.bpmn'; import templates from './fixtures/simple'; import falsyVersionTemplate from './fixtures/falsy-version'; +import enginesTemplates from './ElementTemplates.engines-templates.json'; describe('provider/element-templates - ElementTemplates', function() { @@ -219,6 +220,206 @@ describe('provider/element-templates - ElementTemplates', function() { })); + describe(' compatibility', function() { + + beforeEach(inject(function(elementTemplates) { + elementTemplates.set(enginesTemplates); + })); + + + describe('should retrieve latest compatible', function() { + + it('single template', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + camunda: '7.14.3' + }); + + // when + const templates = elementTemplates.getLatest('example.engines.test.basic'); + + // then + expect(templates).to.have.length(1); + expect(templates[0].version).to.eql(3); + })); + + + it('all templates', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + camunda: '7.14.3' + }); + + // when + const templates = elementTemplates.getLatest(); + + // then + // expect all compatible templates to be returned + // example.engines.test.multiple v2 + // example.engines.test.basic v2 + expect(templates).to.have.length(2); + })); + + }); + + + it('should retrieve older compatible', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + camunda: '7.13' + }); + + // when + const templates = elementTemplates.getLatest('example.engines.test.basic'); + + // then + expect(templates).to.have.length(1); + expect(templates[0].version).to.eql(2); + })); + + + it('should retrieve fallback (no meta-data)', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + camunda: '4' + }); + + // when + const templates = elementTemplates.getLatest('example.engines.test.basic'); + + // then + expect(templates).to.have.length(1); + expect(templates[0].version).to.eql(1); + })); + + + describe('should handle no context provided', function() { + + it('single template', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({}); + + // when + const templates = elementTemplates.getLatest('example.engines.test.basic'); + + // then + expect(templates).to.have.length(1); + expect(templates[0].version).to.eql(3); + })); + + + it('list templates', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({}); + + // when + const templates = elementTemplates.getLatest(); + + // then + // example.engines.test.multiple v2 + // example.engines.test.basic v3 + // example.engines.test.broken v1 + expect(templates).to.have.length(3); + })); + + }); + + + it('should support multiple engines', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + camunda: '7.14', + webModeler: '4.3' + }); + + // when + const templates = elementTemplates.getLatest('example.engines.test.multiple'); + + // then + expect(templates).to.have.length(1); + expect(templates[0].version).to.eql(2); + })); + + + it('should exclude engine', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + camunda: '7.14', + webModeler: '4.3', + desktopModeler: '5.4' + }); + + // when + const templates = elementTemplates.getLatest('example.engines.test.multiple'); + + // then + expect(templates).to.have.length(1); + expect(templates[0].version).to.eql(1); + })); + + + it('should ignore incompatible', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + camunda: '7.12' + }); + + // when + const templates = elementTemplates.getLatest('example.engines.test.multiple'); + + // then + expect(templates).to.be.empty; + })); + + + it('should handle broken provided at run-time', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + camunda: 'one-hundred' + }); + + // when + const templates = elementTemplates.getLatest('example.engines.test.basic'); + + // then + // we ignore the context entry, assume it is not there + expect(templates).to.have.length(1); + expect(templates[0].version).to.eql(3); + })); + + + it('should handle broken provided by template', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + camunda: '7.14' + }); + + // when + const templates = elementTemplates.getLatest('example.engines.test.broken'); + + // then + expect(templates).to.be.empty; + + // and + // we still regard such template as a valid template + const template = elementTemplates.get('example.engines.test.broken', 1); + expect(template).to.exist; + })); + + }); + + it('should throw for invalid argument', inject(function(elementTemplates) { // then @@ -378,6 +579,40 @@ describe('provider/element-templates - ElementTemplates', function() { expect(elementTemplates.get(falsyVersionTemplate[0].id, 0)).to.exist; })); + + it('should emit event', inject(function(elementTemplates, eventBus) { + + // given + const spy = sinon.spy(); + + eventBus.on('elementTemplates.changed', spy); + + // when + elementTemplates.set(templates); + + // then + expect(spy).to.have.been.calledOnce; + })); + + }); + + + describe('setEngines', function() { + + it('should emit event', inject(function(elementTemplates, eventBus) { + + // given + const spy = sinon.spy(); + + eventBus.on('elementTemplates.engines.changed', spy); + + // when + elementTemplates.setEngines({}); + + // then + expect(spy).to.have.been.calledOnce; + })); + }); @@ -578,6 +813,80 @@ describe('provider/element-templates - ElementTemplates', function() { }); + + describe('isCompatible', function() { + + const compatibleTemplate = { + engines: { + camunda: '^8.5' + } + }; + + const incompatibleTemplate = { + engines: { + camunda: '^8.6' + } + }; + + + it('should accept compatible', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + camunda: '8.5' + }); + + // then + expect(elementTemplates.isCompatible(compatibleTemplate)).to.be.true; + })); + + + it('should reject incompatible', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + camunda: '8.5' + }); + + // then + expect(elementTemplates.isCompatible(incompatibleTemplate)).to.be.false; + })); + + + it('should accept non matching engine', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + nonMatchingEngine: '8.5' + }); + + // then + expect(elementTemplates.isCompatible(compatibleTemplate)).to.be.true; + expect(elementTemplates.isCompatible(incompatibleTemplate)).to.be.true; + })); + + }); + + + describe('error handling', function() { + + // given + const invalidEngines = { + camunda: '7.12', + invalid: 'not-a-semver' + }; + + it('should filter invalid on set', inject(function(elementTemplates) { + + // when + elementTemplates.setEngines(invalidEngines); + + // then + expect(elementTemplates.getEngines()).to.have.property('camunda'); + expect(elementTemplates.getEngines()).to.not.have.property('invalid'); + })); + }); + }); diff --git a/test/spec/element-templates/ElementTemplatesLoader.spec.js b/test/spec/element-templates/ElementTemplatesLoader.spec.js index 366fc0b..6c8558d 100644 --- a/test/spec/element-templates/ElementTemplatesLoader.spec.js +++ b/test/spec/element-templates/ElementTemplatesLoader.spec.js @@ -34,6 +34,58 @@ describe('provider/element-templates - ElementTemplatesLoader', function() { }); + describe('init with config={ loadTemplates } as Array', function() { + + beforeEach(bootstrapModeler(diagramXML, { + container: container, + modules, + moddleExtensions: { + camunda: camundaModdlePackage + }, + elementTemplates: { + loadTemplates: templateDescriptors + } + })); + + it('should configure elementTemplates service', inject(function(elementTemplates) { + + // then + expect(elementTemplates.getAll()).to.eql(templateDescriptors); + })); + + }); + + + describe('init with config={ loadTemplates } as function', function() { + + let provider = function(done) { + done(null, templateDescriptors); + }; + + const templateProviderFn = function(done) { + provider(done); + }; + + beforeEach(bootstrapModeler(diagramXML, { + container: container, + modules, + moddleExtensions: { + camunda: camundaModdlePackage + }, + elementTemplates: { + loadTemplates: templateProviderFn + } + })); + + it('should configure elementTemplates service', inject(function(elementTemplates) { + + // then + expect(elementTemplates.getAll()).to.eql(templateDescriptors); + })); + + }); + + describe('init with Array', function() { beforeEach(bootstrapModeler(diagramXML, { diff --git a/test/spec/element-templates/Validator.spec.js b/test/spec/element-templates/Validator.spec.js index a8a5726..817f41f 100644 --- a/test/spec/element-templates/Validator.spec.js +++ b/test/spec/element-templates/Validator.spec.js @@ -712,4 +712,42 @@ describe('provider/element-templates - Validator', function() { }); + + describe('engines validation', function() { + + it('should accept template with valid semver range', function() { + + // given + const templates = new Validator(moddle); + + const templateDescriptor = require('./fixtures/engines'); + + // when + templates.addAll(templateDescriptor); + + // then + expect(errors(templates)).to.be.empty; + + expect(valid(templates)).to.have.length(templateDescriptor.length); + }); + + + it('should reject template with invalid semver range', function() { + + // given + const templates = new Validator(moddle); + + const templateDescriptor = require('./fixtures/engines-invalid'); + + // when + templates.addAll(templateDescriptor); + + // then + expect(errors(templates)).to.contain('Engine specifies invalid semver range '); + + expect(valid(templates)).to.be.empty; + }); + + }); + }); diff --git a/test/spec/element-templates/fixtures/engines-invalid.json b/test/spec/element-templates/fixtures/engines-invalid.json new file mode 100644 index 0000000..a01cce2 --- /dev/null +++ b/test/spec/element-templates/fixtures/engines-invalid.json @@ -0,0 +1,15 @@ +[ + { + "id": "example.engines.test.basic", + "name": " Test - Basic", + "description": "basic template with invalid engines", + "version": 3, + "engines": { + "camunda": "invalid-version" + }, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [] + } +] \ No newline at end of file diff --git a/test/spec/element-templates/fixtures/engines.json b/test/spec/element-templates/fixtures/engines.json new file mode 100644 index 0000000..aec9dff --- /dev/null +++ b/test/spec/element-templates/fixtures/engines.json @@ -0,0 +1,91 @@ +[ + { + "id": "example.engines.test.multiple", + "name": " Test - Multiple", + "description": "does not match if { desktopModeler: >=1 } is provided", + "version": 2, + "engines": { + "camunda": "^7.13", + "webModeler": "^4.1", + "desktopModeler": "^0" + }, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [] + }, + { + "id": "example.engines.test.multiple", + "name": " Test - Multiple", + "description": "matches if { camunda: ^7.13, webModeler: ^4.1 } engine is indicated, or properties are not provided", + "version": 1, + "engines": { + "camunda": "^7.13", + "webModeler": "^4.1" + }, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [] + }, + + { + "id": "example.engines.test.basic", + "name": " Test - Basic", + "description": "matches if { camunda: ^7.13 }, or if no engine is provided", + "version": 3, + "engines": { + "camunda": "^7.13" + }, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [] + }, + { + "id": "example.engines.test.basic", + "name": " Test - Basic", + "description": "matches if { camunda: <=7.12 }, or if no engine is provided", + "version": 2, + "engines": { + "camunda": "<=7.12" + }, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [] + }, + { + "id": "example.engines.test.basic", + "name": " Test - Basic", + "description": "always matches", + "version": 1, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [] + }, + + { + "id": "example.engines.test.incompatible", + "name": " Test - incompatible", + "description": "incompatible with all camunda versions", + "engines": { + "camunda": "0" + }, + "appliesTo": [ + "bpmn:Task" + ], + "properties": [ + { + "label": "Custom Property 2", + "type": "String", + "value": "property-2-value", + "binding": { + "type": "camunda:property", + "name": "property-2-key" + } + } + ] + } +] \ No newline at end of file From cae6ebbc1e276062759736e6564f37ae992436c6 Mon Sep 17 00:00:00 2001 From: Nico Rehwaldt Date: Mon, 9 Dec 2024 11:59:17 +0100 Subject: [PATCH 05/11] fix(cloud/linting): account for actual dependency --- src/cloud-element-templates/linting/LinterPlugin.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/cloud-element-templates/linting/LinterPlugin.js b/src/cloud-element-templates/linting/LinterPlugin.js index 827eef0..3b99f99 100644 --- a/src/cloud-element-templates/linting/LinterPlugin.js +++ b/src/cloud-element-templates/linting/LinterPlugin.js @@ -10,6 +10,8 @@ import StaticResolver from 'bpmnlint/lib/resolver/static-resolver'; import ElementTemplates from '../ElementTemplates'; +import EventBus from 'diagram-js/lib/core/EventBus'; + import { getPropertyValue, validateProperty } from '../util/propertyUtil'; import { applyConditions } from '../Condition'; @@ -30,7 +32,9 @@ export const elementTemplateLintRule = ({ templates = [] }) => { // We use the ElementTemplates Module without the required bpmn-js modules // As we only use it to facilitate template ID and version lookup, // access to commandstack etc. is not required - const elementTemplates = new ElementTemplates(); + const eventBus = new EventBus(); + const elementTemplates = new ElementTemplates(null, null, eventBus, null, null); + elementTemplates.set(validTemplates); function check(node, reporter) { From 8bfd71bc16d11019a8199acb1db125d895bc3b03 Mon Sep 17 00:00:00 2001 From: Jarek Danielak Date: Mon, 9 Dec 2024 16:35:14 +0100 Subject: [PATCH 06/11] feat(cloud): add incompatible state to properties panel --- assets/element-templates.css | 23 +++++- src/components/ElementTemplatesGroup.js | 78 ++++++++++++++++--- .../fixtures/complex.bpmn | 16 +++- .../ElementTemplatesPropertiesProvider.bpmn | 4 + .../ElementTemplatesPropertiesProvider.json | 33 ++++++++ ...ElementTemplatesPropertiesProvider.spec.js | 69 ++++++++++++++++ 6 files changed, 204 insertions(+), 19 deletions(-) diff --git a/assets/element-templates.css b/assets/element-templates.css index c7392c5..80af07f 100644 --- a/assets/element-templates.css +++ b/assets/element-templates.css @@ -9,6 +9,9 @@ --unknown-template-background-color: var(--color-red-360-100-45); --unknown-template-hover-background-color: var(--color-red-360-100-40); + --incompatible-template-background-color: rgb(255, 131, 43); + --incompatible-template-hover-background-color: hsl(25, 100%, 50%); + --select-template-information-text-color: var(--color-grey-225-10-55); --deprecated-template-hover-background-color: var(--color-grey-225-10-50); @@ -59,7 +62,8 @@ .bio-properties-panel-template-update-available:last-child, .bio-properties-panel-applied-template-button:last-child, -.bio-properties-panel-template-not-found:last-child { +.bio-properties-panel-template-not-found:last-child, +.bio-properties-panel-template-incompatible:last-child { margin-right: 32px; } @@ -77,7 +81,6 @@ background-color: var(--deprecated-template-hover-background-color); } - .bio-properties-panel-template-not-found .bio-properties-panel-group-header-button { background-color: var(--unknown-template-background-color); color: var(--select-template-label-color); @@ -88,13 +91,25 @@ background-color: var(--unknown-template-hover-background-color); } +.bio-properties-panel-template-incompatible .bio-properties-panel-group-header-button { + background-color: var(--incompatible-template-background-color); + color: var(--select-template-label-color); + fill: var(--select-template-fill-color); +} + +.bio-properties-panel-template-incompatible .bio-properties-panel-group-header-button:hover { + background-color: var(--incompatible-template-hover-background-color); +} + .bio-properties-panel-template-not-found-text, -.bio-properties-panel-template-update-available-text { +.bio-properties-panel-template-update-available-text, +.bio-properties-panel-template-incompatible-text { color: var(--select-template-information-text-color); } .bio-properties-panel-template-not-found-text, .bio-properties-panel-template-update-available-text, -.bio-properties-panel-deprecated-template-text { +.bio-properties-panel-deprecated-template-text, +.bio-properties-panel-template-incompatible-text { width: 216px; } diff --git a/src/components/ElementTemplatesGroup.js b/src/components/ElementTemplatesGroup.js index 121e3dd..b9405c9 100644 --- a/src/components/ElementTemplatesGroup.js +++ b/src/components/ElementTemplatesGroup.js @@ -149,6 +149,8 @@ function TemplateGroupButtons({ element, getTemplateId }) { return ; } else if (templateState.type === 'DEPRECATED_TEMPLATE') { return ; + } else if (templateState.type === 'INCOMPATIBLE_TEMPLATE') { + return ; } else if (templateState.type === 'OUTDATED_TEMPLATE') { return ( }, + { entry: }, { separator: true }, { entry: translate('Update'), action: () => elementTemplates.applyTemplate(element, newerTemplate) }, { entry: translate('Unlink'), action: () => elementTemplates.unlinkTemplate(element) }, { entry: , action: () => elementTemplates.removeTemplate(element) } ]; + const cls = compatible + ? 'bio-properties-panel-template-update-available' + : 'bio-properties-panel-template-incompatible'; + + const text = compatible + ? translate('Update available') + : translate('Incompatible'); + return ( - + - { translate('Update available') } + { text } ); } -function UpdateAvailableText({ newerTemplate }) { +function UpdateAvailableText({ newerTemplate, compatible }) { const translate = useService('translate'); - const text = translate( - 'A new version of the template is available: {templateVersion}', - { templateVersion: getVersionOrDateFromTemplate(newerTemplate) } - ); + const text = compatible + ? translate( + 'A new version of the template is available: {templateVersion}', + { templateVersion: getVersionOrDateFromTemplate(newerTemplate) } + ) + : translate( + 'A version of this template is available that supports your environment: {templateVersion}', + { templateVersion: getVersionOrDateFromTemplate(newerTemplate) } + ); return
{text}
; } @@ -320,6 +335,39 @@ function DocumentationIcon() { ; } +function IncompatibleTemplate({ element }) { + const translate = useService('translate'), + elementTemplates = useService('elementTemplates'); + + const menuItems = [ + { entry: }, + { separator: true }, + { entry: translate('Unlink'), action: () => elementTemplates.unlinkTemplate(element) }, + { entry: , action: () => elementTemplates.removeTemplate(element) } + ]; + + return ( + + + { translate('Incompatible') } + + + + ); +} + +function IncompatibleText() { + const translate = useService('translate'); + + return ( +
+ { translate( + 'No compatible version of this template was found for your environment. Unlink to access the template’s data.' + ) } +
+ ); +} + // helper ////// @@ -347,10 +395,16 @@ function getTemplateState(elementTemplates, element, getTemplateId) { return { type: 'DEPRECATED_TEMPLATE', template }; } - const newerTemplate = elementTemplates.getLatest(templateId, { deprecated: true })[0]; + const compatible = elementTemplates.isCompatible(template); + + const latestTemplate = elementTemplates.getLatest(templateId, { deprecated: true })[0]; + + if (latestTemplate && latestTemplate !== template) { + return { type: 'OUTDATED_TEMPLATE', template, newerTemplate: latestTemplate, compatible }; + } - if (newerTemplate !== template) { - return { type: 'OUTDATED_TEMPLATE', template, newerTemplate }; + if (!compatible) { + return { type: 'INCOMPATIBLE_TEMPLATE', template }; } return { type: 'KNOWN_TEMPLATE', template }; diff --git a/test/spec/cloud-element-templates/fixtures/complex.bpmn b/test/spec/cloud-element-templates/fixtures/complex.bpmn index a755b46..2cb154b 100644 --- a/test/spec/cloud-element-templates/fixtures/complex.bpmn +++ b/test/spec/cloud-element-templates/fixtures/complex.bpmn @@ -1,5 +1,5 @@ - + Flow_0gw5hlt @@ -9,7 +9,7 @@ Flow_0636r17 - + @@ -94,7 +94,9 @@ - + + + @@ -139,6 +141,14 @@ + + + + + + + + diff --git a/test/spec/cloud-element-templates/properties-panel/ElementTemplatesPropertiesProvider.bpmn b/test/spec/cloud-element-templates/properties-panel/ElementTemplatesPropertiesProvider.bpmn index 315a387..f02efd8 100644 --- a/test/spec/cloud-element-templates/properties-panel/ElementTemplatesPropertiesProvider.bpmn +++ b/test/spec/cloud-element-templates/properties-panel/ElementTemplatesPropertiesProvider.bpmn @@ -4,6 +4,7 @@ + @@ -46,6 +47,9 @@ + + + diff --git a/test/spec/cloud-element-templates/properties-panel/ElementTemplatesPropertiesProvider.json b/test/spec/cloud-element-templates/properties-panel/ElementTemplatesPropertiesProvider.json index 02e613f..2a65747 100644 --- a/test/spec/cloud-element-templates/properties-panel/ElementTemplatesPropertiesProvider.json +++ b/test/spec/cloud-element-templates/properties-panel/ElementTemplatesPropertiesProvider.json @@ -94,5 +94,38 @@ } } ] + }, + { + "$schema": "https://unpkg.com/@camunda/zeebe-element-templates-json-schema/resources/schema.json", + "id": "engines", + "name":"Engines 1", + "version": 1, + "appliesTo": [ "bpmn:Task" ], + "engines": { + "camunda": "^8.4" + }, + "properties": [] + }, + { + "$schema": "https://unpkg.com/@camunda/zeebe-element-templates-json-schema/resources/schema.json", + "id": "engines", + "name":"Engines 2", + "version": 2, + "appliesTo": [ "bpmn:Task" ], + "engines": { + "camunda": "^8.5" + }, + "properties": [] + }, + { + "$schema": "https://unpkg.com/@camunda/zeebe-element-templates-json-schema/resources/schema.json", + "id": "engines", + "name":"Engines 3", + "version": 3, + "appliesTo": [ "bpmn:Task" ], + "engines": { + "camunda": "^8.6" + }, + "properties": [] } ] diff --git a/test/spec/cloud-element-templates/properties-panel/ElementTemplatesPropertiesProvider.spec.js b/test/spec/cloud-element-templates/properties-panel/ElementTemplatesPropertiesProvider.spec.js index 9195fa0..edfbe1c 100644 --- a/test/spec/cloud-element-templates/properties-panel/ElementTemplatesPropertiesProvider.spec.js +++ b/test/spec/cloud-element-templates/properties-panel/ElementTemplatesPropertiesProvider.spec.js @@ -495,6 +495,75 @@ describe('provider/cloud-element-templates - ElementTemplatesPropertiesProvider' ); }); + describe('engines', function() { + + it('should display update button if latest is compatible', inject( + async function(elementRegistry, selection, elementTemplates) { + + // given + const element = elementRegistry.get('Task_4'); + + // when + elementTemplates.setEngines({ + camunda: '8.6' + }); + + await act(() => { + selection.select(element); + }); + + // then + const updateAvailable = domQuery('.bio-properties-panel-template-update-available', container); + expect(updateAvailable).to.exist; + }) + ); + + + it('should NOT display update button if latest is incompatible', inject( + async function(elementRegistry, selection, elementTemplates) { + + // given + const element = elementRegistry.get('Task_4'); + + // when + elementTemplates.setEngines({ + camunda: '8.5' + }); + + await act(() => { + selection.select(element); + }); + + // then + const updateAvailable = domQuery('.bio-properties-panel-template-update-available', container); + expect(updateAvailable).not.to.exist; + }) + ); + + + it('should display incompatible button when template is incompatible', inject( + async function(elementRegistry, selection, elementTemplates) { + + // given + const element = elementRegistry.get('Task_4'); + + // when + elementTemplates.setEngines({ + camunda: '8.0' + }); + + await act(() => { + selection.select(element); + }); + + // then + const incompatible = domQuery('.bio-properties-panel-template-incompatible', container); + expect(incompatible).to.exist; + }) + ); + + }); + describe('conditional entries', function() { From 0fca0ab338b6529c7226b0d7d9361d0383c83255 Mon Sep 17 00:00:00 2001 From: Nico Rehwaldt Date: Mon, 9 Dec 2024 16:43:59 +0100 Subject: [PATCH 07/11] test: verify engines in example test bed test: simplify example testbed --- package-lock.json | 4 +- package.json | 1 + test/spec/Example.spec.js | 260 +++++++++++++++++++++++++------------- 3 files changed, 175 insertions(+), 90 deletions(-) diff --git a/package-lock.json b/package-lock.json index 02a4c77..c91a914 100644 --- a/package-lock.json +++ b/package-lock.json @@ -65,6 +65,7 @@ "karma-webpack": "^5.0.1", "mocha": "^10.8.2", "mocha-test-container-support": "^0.2.0", + "modeler-moddle": "^0.2.0", "npm-run-all2": "^7.0.0", "puppeteer": "^23.7.0", "raw-loader": "^4.0.2", @@ -7379,7 +7380,8 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/modeler-moddle/-/modeler-moddle-0.2.0.tgz", "integrity": "sha512-l8OUpvX94m3spe+RBwWFQ0bGvPBZ3FBCiSY3yNtDk52j0YRj+cnVOxTMQvVM+i6k1T326IfqYM3F9HJfPZtXRw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/mri": { "version": "1.2.0", diff --git a/package.json b/package.json index c946e55..d72cf44 100644 --- a/package.json +++ b/package.json @@ -116,6 +116,7 @@ "karma-webpack": "^5.0.1", "mocha": "^10.8.2", "mocha-test-container-support": "^0.2.0", + "modeler-moddle": "^0.2.0", "npm-run-all2": "^7.0.0", "puppeteer": "^23.7.0", "raw-loader": "^4.0.2", diff --git a/test/spec/Example.spec.js b/test/spec/Example.spec.js index 736c788..d1185a6 100644 --- a/test/spec/Example.spec.js +++ b/test/spec/Example.spec.js @@ -38,7 +38,7 @@ import CamundaBehaviorsModule from 'camunda-bpmn-js-behaviors/lib/camunda-platfo import ZeebeBehaviorsModule from 'camunda-bpmn-js-behaviors/lib/camunda-cloud'; import CamundaModdle from 'camunda-bpmn-moddle/resources/camunda'; - +import ModelerModdle from 'modeler-moddle/resources/modeler'; import ZeebeModdle from 'zeebe-bpmn-moddle/resources/zeebe'; import CloudElementTemplatesPropertiesProviderModule from 'src/cloud-element-templates'; @@ -50,56 +50,105 @@ const singleStart = window.__env__ && window.__env__.SINGLE_START; insertCoreStyles(); insertBpmnStyles(); -insertCSS('bottom-panel.css', ` + +insertCSS('example.css', ` .test-container { display: flex; flex-direction: column; } + .test-container { + --example-properties-width: 20vw; + --example-bottom-height: 30vh; + } + + .bjs-container { + padding-right: var(--example-properties-width); + padding-bottom: var(--example-bottom-height); + } + .properties-panel-container { position: absolute; top: 0; right: 0; - width: 250px; + width: var(--example-properties-width); height: 100%; border-left: solid 1px #ccc; background-color: #f7f7f8; } - .panel { + .bottom-panel { position: absolute; bottom: 0; left: 0; - width: calc(100% - 250px - 1px); - height: 200px; + width: calc(100% - var(--example-properties-width) - 1px); + height: var(--example-bottom-height); display: flex; flex-direction: column; background-color: #f7f7f8; - padding: 5px; box-sizing: border-box; border-top: solid 1px #ccc; font-family: sans-serif; } - .panel .errors { + .bottom-panel .error-container { resize: none; flex-grow: 1; background-color: #f7f7f8; border: none; - margin-bottom: 5px; + padding: 5px; font-family: sans-serif; line-height: 1.5; outline: none; - overflow-y: scroll; + overflow: auto; } - .panel button, - .panel input { + .bottom-panel .error-item { + cursor: pointer; + } + + .bottom-panel .footer-container { + border-top: solid 1px #ccc; + padding: 5px; + } + + .bottom-panel button, + .bottom-panel input { width: 200px; } `); +const ChangeEnginesModule = { + + __init__: [ function(bpmnjs, eventBus, elementTemplates) { + + eventBus.on([ + 'import.done', + 'elements.changed' + ], function() { + const executionPlatformVersion = bpmnjs.getDefinitions().get('executionPlatformVersion'); + + if (elementTemplates.getEngines().camunda !== executionPlatformVersion) { + elementTemplates.setEngines({ + camunda: executionPlatformVersion + }); + } + }); + } ] +}; + +const LogTemplateErrorsModule = { + + __init__: [ function(eventBus) { + + eventBus.on('elementTemplates.errors', function({ errors }) { + console.error('element template parse errors', errors); + }); + } ] +}; + + describe('', function() { let modelerContainer; @@ -133,7 +182,8 @@ describe('', function() { LintingModule ], moddleExtensions = { - zeebe: ZeebeModdle + zeebe: ZeebeModdle, + modeler: ModelerModdle }, propertiesPanel = {}, description = {}, @@ -197,10 +247,13 @@ describe('', function() { ElementTemplatesIconsRenderer, CreateAppendAnythingModule, CreateAppendElementTemplatesModule, + ChangeEnginesModule, + LogTemplateErrorsModule, LintingModule ], moddleExtensions: { - zeebe: ZeebeModdle + zeebe: ZeebeModdle, + modeler: ModelerModdle }, propertiesPanel: { parent: null @@ -212,71 +265,11 @@ describe('', function() { } ); - const modeler = result.modeler; - - const linter = new Linter({ - plugins: [ - ElementTemplateLinterPlugin(elementTemplates) - ] - - }); - - const linting = modeler.get('linting'); - const bpmnjs = modeler.get('bpmnjs'); - const eventBus = modeler.get('eventBus'); - - const lint = () => { - const definitions = bpmnjs.getDefinitions(); - - linter.lint(definitions).then(reports => { - linting.setErrors(reports); - - const errorContainer = panel.querySelector('.errors'); - errorContainer.innerHTML = ''; - - reports.forEach((report) => { - let { id, message, node, data } = report; - node = node || (data && data.node); - const name = node && node.name; - - const errorMessage = `${ name || id }: ${ message }`; - const item = domify(`
${escapeHtml(errorMessage)}
`); - item.addEventListener('click', () => { - linting.showError(report); - }); - - errorContainer.appendChild(item); - }); - }); - }; - - lint(); - - eventBus.on('elements.changed', lint); - linting.activate(); - - const propertiesPanelParent = domify('
'); - - bpmnjs._container.appendChild(propertiesPanelParent); - - modeler.get('propertiesPanel').attachTo(propertiesPanelParent); - - const panel = domify(` -
-
-
- - - -
-
- `); - - bpmnjs._container.appendChild(panel); - - // then expect(result.error).not.to.exist; + + // and + createTestUI(result.modeler); }); @@ -301,28 +294,117 @@ describe('', function() { ElementTemplatesPropertiesProviderModule ], moddleExtensions: { - camunda: CamundaModdle + camunda: CamundaModdle, + modeler: ModelerModdle }, elementTemplates } ); - const modeler = result.modeler; - const bpmnjs = modeler.get('bpmnjs'); - const propertiesPanelParent = domify('
'); - - bpmnjs._container.appendChild(propertiesPanelParent); - - modeler.get('propertiesPanel').attachTo(propertiesPanelParent); - - // then expect(result.error).not.to.exist; + + // and then + createTestUI(result.modeler); }); }); -const escapeHtml = (unsafe) => { +function escapeHTML(unsafe) { return unsafe.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"').replaceAll("'", '''); -}; +} + +function createTestUI(modeler) { + + const linting = modeler.get('linting', false); + const elementTemplates = modeler.get('elementTemplates', false); + const propertiesPanel = modeler.get('propertiesPanel', false); + + const canvas = modeler.get('canvas'); + const modeling = modeler.get('modeling'); + const bpmnjs = modeler.get('bpmnjs'); + const eventBus = modeler.get('eventBus'); + + const container = bpmnjs._container; + + if (propertiesPanel) { + + const propertiesPanelParent = domify('
'); + + container.appendChild(propertiesPanelParent); + + propertiesPanel.attachTo(propertiesPanelParent); + } + + if (linting && elementTemplates) { + const linter = new Linter({ + plugins: [ + ElementTemplateLinterPlugin(elementTemplates.getAll()) + ] + }); + + const bottomPanel = domify(` +
+
+ +
+ `); + + container.appendChild(bottomPanel); + + bottomPanel.querySelector('input').value = bpmnjs.getDefinitions().get('executionPlatformVersion'); + + bottomPanel.querySelector('input').addEventListener('input', ({ target }) => { + modeling.updateModdleProperties( + canvas.getRootElement(), + bpmnjs.getDefinitions(), + { executionPlatformVersion: target.value } + ); + }); + + const lint = () => { + const definitions = bpmnjs.getDefinitions(); + + linter.lint(definitions).then(reports => { + linting.setErrors(reports); + + const errorContainer = bottomPanel.querySelector('.error-container'); + errorContainer.innerHTML = ''; + + reports.map((report) => { + const { id, message, category, rule, documentation } = report; + + if (category === 'rule-error') { + return domify(`
${ category } Rule <${ escapeHTML(rule) }> errored with the following message: ${ escapeHTML(message) }
`); + } + + const element = domify(`
${ category } ${ id }: ${escapeHTML(message) }
`); + + if (documentation.url) { + const documentationLink = domify(`ref`); + + documentationLink.addEventListener('click', e => e.stopPropagation()); + + element.appendChild(documentationLink); + } + + element.addEventListener('click', () => { + linting.showError(report); + }); + + return element; + }).forEach(item => errorContainer.appendChild(item)); + }); + }; + + linting.activate(); + + lint(); + + eventBus.on('elements.changed', lint); + } +} \ No newline at end of file From 6205b11a1068d2086d96c8c6b2c1352bfbfa897b Mon Sep 17 00:00:00 2001 From: Nico Rehwaldt Date: Wed, 11 Dec 2024 23:32:52 +0100 Subject: [PATCH 08/11] test: move moddle helpers close to use --- test/TestHelper.js | 57 +---------- .../linting/LinterPlugin.spec.js | 97 +++++++++++++++---- 2 files changed, 78 insertions(+), 76 deletions(-) diff --git a/test/TestHelper.js b/test/TestHelper.js index 1209f77..584ceb9 100644 --- a/test/TestHelper.js +++ b/test/TestHelper.js @@ -20,9 +20,6 @@ import download from 'downloadjs'; import Modeler from 'bpmn-js/lib/Modeler'; -import BPMNModdle from 'bpmn-moddle'; -import zeebeModdle from 'zeebe-bpmn-moddle/resources/zeebe'; - let PROPERTIES_PANEL_CONTAINER; global.chai.use(function(chai, utils) { @@ -231,56 +228,4 @@ document.addEventListener('keydown', function(event) { bpmnJS.saveXML({ format: true }).then(function(result) { download(result.xml, 'test.bpmn', 'application/xml'); }); -}); - -// Moddle helpers ////////////////////// -export async function createModdle(xml) { - const moddle = new BPMNModdle({ - zeebe: zeebeModdle - }); - - let root, warnings; - - try { - ({ - rootElement: root, - warnings = [] - } = await moddle.fromXML(xml, 'bpmn:Definitions', { lax: true })); - } catch (err) { - console.log(err); - } - - return { - root, - moddle, - context: { - warnings - }, - warnings - }; -} - -export function createDefinitions(xml = '') { - return ` - - ${ xml } - - `; -} - - -export function createProcess(bpmn = '', bpmndi = '') { - return createDefinitions(` - - ${ bpmn } - - ${ bpmndi } - `); -} \ No newline at end of file +}); \ No newline at end of file diff --git a/test/spec/cloud-element-templates/linting/LinterPlugin.spec.js b/test/spec/cloud-element-templates/linting/LinterPlugin.spec.js index f56e0fb..00ce481 100644 --- a/test/spec/cloud-element-templates/linting/LinterPlugin.spec.js +++ b/test/spec/cloud-element-templates/linting/LinterPlugin.spec.js @@ -2,60 +2,58 @@ import RuleTester from 'bpmnlint/lib/testers/rule-tester'; import { elementTemplateLintRule } from 'src/cloud-element-templates/linting'; -import { - createDefinitions, - createModdle, - createProcess -} from '../../../TestHelper'; import templates from './LinterPlugin.json'; +import BPMNModdle from 'bpmn-moddle'; +import zeebeModdle from 'zeebe-bpmn-moddle/resources/zeebe'; + const valid = [ { name: 'Valid Template', - moddleElement: createModdle(createProcess('')), + moddleElement: createProcess(''), config: { templates } }, { name: 'Conditional Template - property hidden', - moddleElement: createModdle(createProcess('')), + moddleElement: createProcess(''), config: { templates } }, { name: 'No Template', - moddleElement: createModdle(createProcess('')), + moddleElement: createProcess(''), config: { templates } }, { name: 'All Messages', - moddleElement: createModdle(createDefinitions('')), + moddleElement: createDefinitions(''), config: { templates } }, { name: 'FEEL (Min Length)', - moddleElement: createModdle(createProcess('')), + moddleElement: createProcess(''), config: { templates } }, { name: 'FEEL (Max Length)', - moddleElement: createModdle(createProcess('')), + moddleElement: createProcess(''), config: { templates } }, { name: 'FEEL (Pattern)', - moddleElement: createModdle(createProcess('')), + moddleElement: createProcess(''), config: { templates } @@ -66,7 +64,7 @@ const valid = [ const invalid = [ { name: 'Template Not Found', - moddleElement: createModdle(createProcess('')), + moddleElement: createProcess(''), config: { templates }, @@ -77,7 +75,7 @@ const invalid = [ }, { name: 'Min Length', - moddleElement: createModdle(createProcess('')), + moddleElement: createProcess(''), config: { templates }, @@ -90,7 +88,7 @@ const invalid = [ }, { name: 'Max Length', - moddleElement: createModdle(createProcess('')), + moddleElement: createProcess(''), config: { templates }, @@ -103,7 +101,7 @@ const invalid = [ }, { name: 'Not Empty', - moddleElement: createModdle(createProcess('')), + moddleElement: createProcess(''), config: { templates }, @@ -115,7 +113,7 @@ const invalid = [ }, { name: 'Pattern', - moddleElement: createModdle(createProcess('')), + moddleElement: createProcess(''), config: { templates }, @@ -127,7 +125,7 @@ const invalid = [ }, { name: 'Pattern (custom message)', - moddleElement: createModdle(createProcess('')), + moddleElement: createProcess(''), config: { templates }, @@ -139,7 +137,7 @@ const invalid = [ }, { name: 'Conditional Template - property shown', - moddleElement: createModdle(createProcess('')), + moddleElement: createProcess(''), config: { templates }, @@ -168,4 +166,63 @@ describe('element-templates Linting', function() { invalid }); -}); \ No newline at end of file +}); + + +// helpers /////////// + +async function createModdle(xml) { + const moddle = new BPMNModdle({ + zeebe: zeebeModdle + }); + + let root, warnings; + + try { + ({ + rootElement: root, + warnings = [] + } = await moddle.fromXML(xml, 'bpmn:Definitions', { lax: true })); + } catch (err) { + console.log(err); + } + + return { + root, + moddle, + context: { + warnings + }, + warnings + }; +} + +function createDefinitions(xml = '', { executionPlatform, executionPlatformVersion } = { + executionPlatform: 'Camunda Cloud', + executionPlatformVersion: '8.5' +}) { + return createModdle(` + + ${ xml } + + `); +} + + +function createProcess(bpmn = '', bpmndi = '') { + return createDefinitions(` + + ${ bpmn } + + ${ bpmndi } + `); +} \ No newline at end of file From 7e1746320f899850b465447c49e755a82f94aa55 Mon Sep 17 00:00:00 2001 From: Nico Rehwaldt Date: Thu, 12 Dec 2024 00:29:17 +0100 Subject: [PATCH 09/11] feat: add elementTemplates#getIncompatibleEngines API Can be used to test an element template for compatibility, returning current and expected engine versions. --- src/element-templates/ElementTemplates.js | 27 ++++-- .../ElementTemplates.spec.js | 82 +++++++++++++++++++ 2 files changed, 101 insertions(+), 8 deletions(-) diff --git a/src/element-templates/ElementTemplates.js b/src/element-templates/ElementTemplates.js index 38ea80b..5510210 100644 --- a/src/element-templates/ElementTemplates.js +++ b/src/element-templates/ElementTemplates.js @@ -196,24 +196,35 @@ export default class ElementTemplates { * @return {boolean} - true if compatible or no engine is set for elementTemplates or template. */ isCompatible(template) { + return !Object.keys(this.getIncompatibleEngines(template)).length; + } + + /** + * Get engines that are incompatible with the template. + * + * @param {any} template + * + * @return { Record { if (!has(localEngines, engine)) { - continue; + return result; } if (!isSemverCompatible(localEngines[engine], templateEngines[engine])) { - return false; + result[engine] = { + actual: localEngines[engine], + required: templateEngines[engine] + }; } - } - return true; + return result; + }, {}); } /** diff --git a/test/spec/cloud-element-templates/ElementTemplates.spec.js b/test/spec/cloud-element-templates/ElementTemplates.spec.js index e086386..7e8bdf9 100644 --- a/test/spec/cloud-element-templates/ElementTemplates.spec.js +++ b/test/spec/cloud-element-templates/ElementTemplates.spec.js @@ -1491,6 +1491,86 @@ describe('provider/cloud-element-templates - ElementTemplates', function() { })); }); + + describe('getIncompatibleEngines', function() { + + it('should return incompatible engine', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + camunda: '8.5' + }); + + const template = { + engines: { + camunda: '>= 8.6' + } + }; + + // then + const result = elementTemplates.getIncompatibleEngines(template); + + expect(result).to.eql({ + camunda: { + actual: '8.5.0', + required: '>= 8.6' + } + }); + })); + + + it('should return multiple', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + camunda: '8.5', + desktopModeler: '5.30' + }); + + const template = { + engines: { + camunda: '>= 8.6', + desktopModeler: '^4.0' + } + }; + + // then + const result = elementTemplates.getIncompatibleEngines(template); + + expect(result).to.eql({ + camunda: { + actual: '8.5.0', + required: '>= 8.6' + }, + desktopModeler: { + actual: '5.30.0', + required: '^4.0' + } + }); + })); + + + it('should return empty object if compatible', inject(function(elementTemplates) { + + // given + elementTemplates.setEngines({ + nonMatchingEngine: '8.5' + }); + + const template = { + engines: { + camunda: '8.5', + } + }; + + // then + const result = elementTemplates.getIncompatibleEngines(template); + + expect(result).to.eql({}); + })); + + }); + }); @@ -1531,6 +1611,8 @@ describe('provider/cloud-element-templates - ElementTemplates - error handling o }); + + describe('provider/cloud-element-templates - ElementTemplates - integration', function() { let container; From 9bbc9e4d22cb88e7c4b9bf6ff4a280ca1387e7d3 Mon Sep 17 00:00:00 2001 From: Nico Rehwaldt Date: Thu, 12 Dec 2024 00:27:47 +0100 Subject: [PATCH 10/11] feat(cloud/linting): verify template compatibility Clean up linting and add a new rule for element template compatibility. --- src/cloud-element-templates/linting/index.js | 33 ++++- .../rules/element-templates-compatibility.js | 115 ++++++++++++++++++ .../element-templates-validate.js} | 28 +---- .../linting/LinterPlugin.json | 50 ++++++++ .../linting/LinterPlugin.spec.js | 94 +++++++++++--- 5 files changed, 277 insertions(+), 43 deletions(-) create mode 100644 src/cloud-element-templates/linting/rules/element-templates-compatibility.js rename src/cloud-element-templates/linting/{LinterPlugin.js => rules/element-templates-validate.js} (78%) diff --git a/src/cloud-element-templates/linting/index.js b/src/cloud-element-templates/linting/index.js index 1853ba8..03555b7 100644 --- a/src/cloud-element-templates/linting/index.js +++ b/src/cloud-element-templates/linting/index.js @@ -1,4 +1,29 @@ -export { - elementTemplateLintRule, - ElementTemplateLinterPlugin -} from './LinterPlugin'; +/** + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright + * ownership. + * + * Camunda licenses this file to you under the MIT; you may not use this file + * except in compliance with the MIT License. + */ + +import StaticResolver from 'bpmnlint/lib/resolver/static-resolver'; + +import validate from './rules/element-templates-validate'; +import compatibility from './rules/element-templates-compatibility'; + +export const ElementTemplateLinterPlugin = function(templates) { + return { + config: { + rules: { + 'element-templates/validate': [ 'error', { templates } ], + 'element-templates/compatibility': [ 'warn', { templates } ] + } + }, + resolver: new StaticResolver({ + 'rule:bpmnlint-plugin-element-templates/validate': validate, + 'rule:bpmnlint-plugin-element-templates/compatibility': compatibility + }) + }; +}; diff --git a/src/cloud-element-templates/linting/rules/element-templates-compatibility.js b/src/cloud-element-templates/linting/rules/element-templates-compatibility.js new file mode 100644 index 0000000..7577f39 --- /dev/null +++ b/src/cloud-element-templates/linting/rules/element-templates-compatibility.js @@ -0,0 +1,115 @@ +/** + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright + * ownership. + * + * Camunda licenses this file to you under the MIT; you may not use this file + * except in compliance with the MIT License. + */ + +import ElementTemplates from '../../ElementTemplates'; +import EventBus from 'diagram-js/lib/core/EventBus'; + +import BpmnModdle from 'bpmn-moddle'; +import { is } from 'bpmn-js/lib/util/ModelUtil'; + +import zeebeModdle from 'zeebe-bpmn-moddle/resources/zeebe'; + +import { Validator } from '../../Validator'; + +export default function({ templates = [] }) { + const moddle = new BpmnModdle({ zeebe: zeebeModdle }); + + const validator = new Validator(moddle).addAll(templates); + const validTemplates = validator.getValidTemplates(); + + // We use the ElementTemplates Module without the required bpmn-js modules + // As we only use it to facilitate template ID and version lookup, + // access to commandstack etc. is not required + const eventBus = new EventBus(); + const elementTemplates = new ElementTemplates(null, null, eventBus, null, null); + + elementTemplates.set(validTemplates); + + function isUpdateAvailable(template) { + + const latestTemplate = elementTemplates.getLatest(template.id, { deprecated: true })[0]; + + if (latestTemplate && latestTemplate !== template) { + return true; + } + + return false; + } + + function check(node, reporter) { + + if (is(node, 'bpmn:Definitions')) { + elementTemplates.setEngines(getEnginesConfig(node)); + } + + if (!is(node, 'bpmn:FlowElement')) { + return; + } + + let template = elementTemplates.get(node); + + if (!template) { + return; + } + + // Check compatibility + if (template.engines) { + const incomp = elementTemplates.getIncompatibleEngines(template); + Object.keys(incomp).forEach((engine) => { + reporter.report( + node.id, + getIncompatibilityText(engine, incomp[engine], isUpdateAvailable(template)), + { + name: node.name + } + ); + }); + } + } + + return { + check + }; + +}; + +// helpers ////////////////////// + +function getEnginesConfig(definitions) { + const { + exporter, + exporterVersion + } = definitions; + + const engines = {}; + + const executionPlatform = definitions.get('modeler:executionPlatform'); + const executionPlatformVersion = definitions.get('modeler:executionPlatformVersion'); + + if (executionPlatform === 'Camunda Cloud' && executionPlatformVersion) { + engines.camunda = executionPlatformVersion; + } + + if (exporter === 'Camunda Modeler' && exporterVersion) { + engines.camundaDesktopModeler = exporterVersion; + } + + return engines; +} + + +function getIncompatibilityText(engine, { actual, required }, updateAvailable) { + const message = + `Element template incompatible with current <${engine}> environment. ` + + `Requires '${engine} ${required}'; found '${actual}'. ` + + `${updateAvailable ? 'Update available.' : ''}`; + + return message.trim(); +} diff --git a/src/cloud-element-templates/linting/LinterPlugin.js b/src/cloud-element-templates/linting/rules/element-templates-validate.js similarity index 78% rename from src/cloud-element-templates/linting/LinterPlugin.js rename to src/cloud-element-templates/linting/rules/element-templates-validate.js index 3b99f99..50d5126 100644 --- a/src/cloud-element-templates/linting/LinterPlugin.js +++ b/src/cloud-element-templates/linting/rules/element-templates-validate.js @@ -8,22 +8,21 @@ * except in compliance with the MIT License. */ -import StaticResolver from 'bpmnlint/lib/resolver/static-resolver'; -import ElementTemplates from '../ElementTemplates'; +import ElementTemplates from '../../ElementTemplates'; import EventBus from 'diagram-js/lib/core/EventBus'; -import { getPropertyValue, validateProperty } from '../util/propertyUtil'; +import { getPropertyValue, validateProperty } from '../../util/propertyUtil'; -import { applyConditions } from '../Condition'; +import { applyConditions } from '../../Condition'; import BpmnModdle from 'bpmn-moddle'; import { is } from 'bpmn-js/lib/util/ModelUtil'; import zeebeModdle from 'zeebe-bpmn-moddle/resources/zeebe'; -import { Validator } from '../Validator'; +import { Validator } from '../../Validator'; -export const elementTemplateLintRule = ({ templates = [] }) => { +export default function({ templates = [] }) { const moddle = new BpmnModdle({ zeebe: zeebeModdle }); const validator = new Validator(moddle).addAll(templates); @@ -93,21 +92,6 @@ export const elementTemplateLintRule = ({ templates = [] }) => { }; - -export const ElementTemplateLinterPlugin = function(templates) { - return { - config: { - rules: { - 'element-templates/validate': [ 'error', { templates } ] - } - }, - resolver: new StaticResolver({ - 'rule:bpmnlint-plugin-element-templates/validate': elementTemplateLintRule - }) - }; -}; - - // helpers ////////////////////// function getEntryId(property, template) { @@ -123,4 +107,4 @@ function getEntryId(property, template) { path.push(index); return path.join('-'); -} \ No newline at end of file +} diff --git a/test/spec/cloud-element-templates/linting/LinterPlugin.json b/test/spec/cloud-element-templates/linting/LinterPlugin.json index 151a113..a75cf40 100644 --- a/test/spec/cloud-element-templates/linting/LinterPlugin.json +++ b/test/spec/cloud-element-templates/linting/LinterPlugin.json @@ -216,5 +216,55 @@ "feel": "optional" } ] + }, + { + "$schema": "https://unpkg.com/@camunda/zeebe-element-templates-json-schema/resources/schema.json", + "name": "compatible", + "id": "compatible", + "appliesTo": [ + "bpmn:Task" + ], + "engines": { + "camunda": ">1.0" + }, + "properties": [] + }, + { + "$schema": "https://unpkg.com/@camunda/zeebe-element-templates-json-schema/resources/schema.json", + "name": "incompatible", + "id": "incompatible", + "appliesTo": [ + "bpmn:Task" + ], + "engines": { + "camunda": "0" + }, + "properties": [] + }, + { + "$schema": "https://unpkg.com/@camunda/zeebe-element-templates-json-schema/resources/schema.json", + "name": "incompatible-updatable", + "id": "incompatible-updatable", + "version": 1, + "appliesTo": [ + "bpmn:Task" + ], + "engines": { + "camunda": "0" + }, + "properties": [] + }, + { + "$schema": "https://unpkg.com/@camunda/zeebe-element-templates-json-schema/resources/schema.json", + "name": "incompatible-updatable", + "id": "incompatible-updatable", + "version": 2, + "appliesTo": [ + "bpmn:Task" + ], + "engines": { + "camunda": "^8.5" + }, + "properties": [] } ] \ No newline at end of file diff --git a/test/spec/cloud-element-templates/linting/LinterPlugin.spec.js b/test/spec/cloud-element-templates/linting/LinterPlugin.spec.js index 00ce481..49e0f2d 100644 --- a/test/spec/cloud-element-templates/linting/LinterPlugin.spec.js +++ b/test/spec/cloud-element-templates/linting/LinterPlugin.spec.js @@ -1,7 +1,7 @@ import RuleTester from 'bpmnlint/lib/testers/rule-tester'; -import { elementTemplateLintRule } from 'src/cloud-element-templates/linting'; - +import validateRule from 'src/cloud-element-templates/linting/rules/element-templates-validate'; +import compatibilityRule from 'src/cloud-element-templates/linting/rules/element-templates-compatibility'; import templates from './LinterPlugin.json'; @@ -150,8 +150,68 @@ const invalid = [ } ]; +const compatible = [ + { + name: 'Template compatible', + moddleElement: createProcess(''), + config: { + templates + } + }, + + { + name: 'Template compatible (no execution platform)', + moddleElement: createProcess( + '', + '', + { executionPlatform: '' } + ), + config: { + templates + } + }, + + { + name: 'Template compatible (no execution platform version)', + moddleElement: createProcess( + '', + '', + { executionPlatform: '8.5' }), + config: { + templates + } + }, +]; + +const incompatible = [ + { + name: 'Template incompatible', + moddleElement: createProcess(''), + config: { + templates + }, + report: { + id: 'Task_1', + message: 'Element template incompatible with current environment. Requires \'camunda 0\'; found \'8.5.0\'.' + } + }, + { + name: 'Template incompatible with update', + moddleElement: createProcess( + '' + ), + config: { + templates + }, + report: { + id: 'Task_1', + message: 'Element template incompatible with current environment. Requires \'camunda 0\'; found \'8.5.0\'. Update available.' + } + }, +]; + -describe('element-templates Linting', function() { +describe('cloud-element-templates/linting', function() { before(function() { @@ -161,9 +221,14 @@ describe('element-templates Linting', function() { } }); - RuleTester.verify('element-templates', elementTemplateLintRule, { + RuleTester.verify('element-templates/validate', validateRule, { valid, - invalid + invalid, + }); + + RuleTester.verify('element-templates/compatibility', compatibilityRule, { + valid: compatible, + invalid: incompatible }); }); @@ -176,16 +241,10 @@ async function createModdle(xml) { zeebe: zeebeModdle }); - let root, warnings; - - try { - ({ - rootElement: root, - warnings = [] - } = await moddle.fromXML(xml, 'bpmn:Definitions', { lax: true })); - } catch (err) { - console.log(err); - } + const { + rootElement: root, + warnings = [] + } = await moddle.fromXML(xml, 'bpmn:Definitions', { lax: true }); return { root, @@ -201,6 +260,7 @@ function createDefinitions(xml = '', { executionPlatform, executionPlatformVersi executionPlatform: 'Camunda Cloud', executionPlatformVersion: '8.5' }) { + return createModdle(` ${ bpmn } ${ bpmndi } - `); + `, options); } \ No newline at end of file From 0f33d201ad7cc5f7ce62160a358bdf3e74d85418 Mon Sep 17 00:00:00 2001 From: Nico Rehwaldt Date: Thu, 12 Dec 2024 00:31:38 +0100 Subject: [PATCH 11/11] feat: add elementTemplates as default engine `elementTemplates` engine is by default set to `bpmn-js-elements-template` version. This allows templates authors to indicate compatibility with specific library versions. --- karma.config.js | 6 ++++ package-lock.json | 33 +++++++++++++++++++ package.json | 1 + rollup.config.mjs | 7 ++++ .../rules/element-templates-compatibility.js | 9 ----- src/element-templates/ElementTemplates.js | 13 ++++++++ .../ElementTemplates.spec.js | 26 +++++++++++++++ 7 files changed, 86 insertions(+), 9 deletions(-) diff --git a/karma.config.js b/karma.config.js index ead3699..9db925d 100644 --- a/karma.config.js +++ b/karma.config.js @@ -1,6 +1,9 @@ /* eslint-env node */ const path = require('path'); + +const pkg = require('./package.json'); + const { DefinePlugin, NormalModuleReplacementPlugin @@ -93,6 +96,9 @@ module.exports = function(karma) { plugins: [ new DefinePlugin({ + // @nikku needs to be defined + 'process.env.PKG_VERSION': JSON.stringify(pkg.version), + // @barmac: process.env has to be defined to make @testing-library/preact work 'process.env': {} }), diff --git a/package-lock.json b/package-lock.json index c91a914..4fcf02c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,6 +34,7 @@ "@rollup/plugin-commonjs": "^28.0.1", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^15.3.0", + "@rollup/plugin-replace": "^6.0.1", "@testing-library/preact": "^2.0.1", "@testing-library/preact-hooks": "^1.1.0", "assert": "^2.1.0", @@ -1357,6 +1358,28 @@ } } }, + "node_modules/@rollup/plugin-replace": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-6.0.1.tgz", + "integrity": "sha512-2sPh9b73dj5IxuMmDAsQWVFT7mR+yoHweBaXG2W/R8vQ+IWZlnaI7BR7J6EguVQUp1hd8Z7XuozpDjEKQAAC2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "magic-string": "^0.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, "node_modules/@rollup/pluginutils": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", @@ -11234,6 +11257,16 @@ "resolve": "^1.22.1" } }, + "@rollup/plugin-replace": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-6.0.1.tgz", + "integrity": "sha512-2sPh9b73dj5IxuMmDAsQWVFT7mR+yoHweBaXG2W/R8vQ+IWZlnaI7BR7J6EguVQUp1hd8Z7XuozpDjEKQAAC2Q==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^5.0.1", + "magic-string": "^0.30.3" + } + }, "@rollup/pluginutils": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", diff --git a/package.json b/package.json index d72cf44..01b329b 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ "@rollup/plugin-commonjs": "^28.0.1", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^15.3.0", + "@rollup/plugin-replace": "^6.0.1", "@testing-library/preact": "^2.0.1", "@testing-library/preact-hooks": "^1.1.0", "assert": "^2.1.0", diff --git a/rollup.config.mjs b/rollup.config.mjs index df420c1..74f091e 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -4,6 +4,7 @@ import commonjs from '@rollup/plugin-commonjs'; import copy from 'rollup-plugin-copy'; import json from '@rollup/plugin-json'; import resolve from '@rollup/plugin-node-resolve'; +import replace from '@rollup/plugin-replace'; import { readFileSync @@ -81,6 +82,12 @@ function pgl(plugins = []) { function corePlugins(plugins = []) { return [ ...plugins, + replace({ + preventAssignment: true, + values: { + 'process.env.PKG_VERSION': JSON.stringify(pkg.version) + } + }), json(), resolve({ mainFields: [ diff --git a/src/cloud-element-templates/linting/rules/element-templates-compatibility.js b/src/cloud-element-templates/linting/rules/element-templates-compatibility.js index 7577f39..9a10499 100644 --- a/src/cloud-element-templates/linting/rules/element-templates-compatibility.js +++ b/src/cloud-element-templates/linting/rules/element-templates-compatibility.js @@ -83,11 +83,6 @@ export default function({ templates = [] }) { // helpers ////////////////////// function getEnginesConfig(definitions) { - const { - exporter, - exporterVersion - } = definitions; - const engines = {}; const executionPlatform = definitions.get('modeler:executionPlatform'); @@ -97,10 +92,6 @@ function getEnginesConfig(definitions) { engines.camunda = executionPlatformVersion; } - if (exporter === 'Camunda Modeler' && exporterVersion) { - engines.camundaDesktopModeler = exporterVersion; - } - return engines; } diff --git a/src/element-templates/ElementTemplates.js b/src/element-templates/ElementTemplates.js index 5510210..8e92e47 100644 --- a/src/element-templates/ElementTemplates.js +++ b/src/element-templates/ElementTemplates.js @@ -24,6 +24,10 @@ import { coerce } from 'semver'; +// eslint-disable-next-line no-undef +const packageVersion = process.env.PKG_VERSION; + + /** * Registry for element templates. */ @@ -155,6 +159,7 @@ export default class ElementTemplates { } setEngines(engines) { + this._engines = this._coerceEngines(engines); this._fire('engines.changed'); @@ -169,6 +174,14 @@ export default class ElementTemplates { */ _coerceEngines(engines) { + // we provide engine with the current + // package version; templates may use that engine to declare + // compatibility with this library + engines = { + elementTemplates: packageVersion, + ...engines + }; + return reduce(engines, (validEngines, version, engine) => { const coercedVersion = coerce(version); diff --git a/test/spec/cloud-element-templates/ElementTemplates.spec.js b/test/spec/cloud-element-templates/ElementTemplates.spec.js index 7e8bdf9..69bcb0e 100644 --- a/test/spec/cloud-element-templates/ElementTemplates.spec.js +++ b/test/spec/cloud-element-templates/ElementTemplates.spec.js @@ -34,6 +34,9 @@ import { findExtensions, findExtension } from 'src/cloud-element-templates/Helpe import { getLabel } from 'bpmn-js/lib/features/label-editing/LabelUtil'; import { findMessage } from 'src/cloud-element-templates/Helper'; +// eslint-disable-next-line no-undef +const packageVersion = process.env.PKG_VERSION; + describe('provider/cloud-element-templates - ElementTemplates', function() { @@ -785,6 +788,15 @@ describe('provider/cloud-element-templates - ElementTemplates', function() { describe('getEngines', function() { + it('should provide default engine', inject(function(elementTemplates) { + + // then + expect( + elementTemplates.getEngines() + ).to.have.property('elementTemplates', packageVersion); + })); + + it('should provide set engines', inject(function(elementTemplates) { // when @@ -795,9 +807,11 @@ describe('provider/cloud-element-templates - ElementTemplates', function() { // then expect(elementTemplates.getEngines()).to.eql({ + 'elementTemplates': packageVersion, 'camunda': '8.0.0', 'other': '100.5.0' }); + })); }); @@ -819,6 +833,18 @@ describe('provider/cloud-element-templates - ElementTemplates', function() { expect(spy).to.have.been.calledOnce; })); + + it('should override engine', inject(function(elementTemplates) { + + // when + elementTemplates.setEngines({ + elementTemplates: '1.0.0' + }); + + // then + expect(elementTemplates.getEngines()).to.have.property('elementTemplates', '1.0.0'); + })); + });