diff --git a/services/static-webserver/client/source/class/osparc/store/Products.js b/services/static-webserver/client/source/class/osparc/store/Products.js index 2a359d27fb6b..b0cef978485e 100644 --- a/services/static-webserver/client/source/class/osparc/store/Products.js +++ b/services/static-webserver/client/source/class/osparc/store/Products.js @@ -17,6 +17,10 @@ /** * @asset(osparc/ui_config.json") + * @asset(schemas/product-ui.json) + * @asset(object-path/object-path-0-11-4.min.js) + * @asset(ajv/ajv-6-11-0.min.js) + * @ignore(Ajv) */ qx.Class.define("osparc.store.Products", { @@ -27,31 +31,58 @@ qx.Class.define("osparc.store.Products", { __uiConfig: null, fetchUiConfig: function() { - if (osparc.auth.Data.getInstance().isGuest()) { - return new Promise(resolve => { + return new Promise(resolve => { + if (osparc.auth.Data.getInstance().isGuest()) { this.__uiConfig = {}; resolve(this.__uiConfig); - }); - } + } - return Promise.all([ - osparc.data.Resources.fetch("productMetadata", "getUiConfig"), - osparc.utils.Utils.fetchJSON("/resource/osparc/ui_config.json"), - ]) - .then(values => { - let uiConfig = {}; - if (values[0] && values[0]["ui"] && Object.keys(values[0]["ui"]).length) { - uiConfig = values[0]["ui"]; - } else { - const product = osparc.product.Utils.getProductName(); - if (values[1] && product in values[1]) { - uiConfig = values[1][product]; + Promise.all([ + osparc.data.Resources.fetch("productMetadata", "getUiConfig"), + osparc.utils.Utils.fetchJSON("/resource/osparc/ui_config.json"), + osparc.utils.Utils.fetchJSON("/resource/schemas/product-ui.json"), + ]) + .then(values => { + let uiConfig = {}; + const beUiConfig = values[0]; + const feUiConfig = values[1]; + const schema = values[2]; + if (beUiConfig && beUiConfig["ui"] && Object.keys(beUiConfig["ui"]).length) { + uiConfig = beUiConfig["ui"]; + } else { + const product = osparc.product.Utils.getProductName(); + if (feUiConfig && product in feUiConfig) { + uiConfig = feUiConfig[product]; + } } - } - this.__uiConfig = uiConfig; - return this.__uiConfig; - }) - .catch(console.error); + const ajvLoader = new qx.util.DynamicScriptLoader([ + "/resource/ajv/ajv-6-11-0.min.js", + "/resource/object-path/object-path-0-11-4.min.js" + ]); + ajvLoader.addListener("ready", () => { + const ajv = new Ajv({ + allErrors: true, + strictDefaults: true, + useDefaults: true, + strictTypes: true, + }); + const validate = ajv.compile(schema); + const valid = validate(uiConfig); + if (valid) { + this.__uiConfig = uiConfig; + resolve(this.__uiConfig); + } else { + osparc.FlashMessenger.getInstance().logAs("Wrong product.ui config", "ERROR"); + validate.errors.forEach(err => { + console.error(`Error at ${err.dataPath}: ${err.message}`); + }); + } + }); + ajvLoader.addListener("failed", console.error, this); + ajvLoader.start(); + }) + .catch(console.error); + }); }, getPlusButtonUiConfig: function() { diff --git a/services/static-webserver/client/source/resource/schemas/product-ui.json b/services/static-webserver/client/source/resource/schemas/product-ui.json new file mode 100644 index 000000000000..5b5c09f986b8 --- /dev/null +++ b/services/static-webserver/client/source/resource/schemas/product-ui.json @@ -0,0 +1,89 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "plusButton": { + "$ref": "#/definitions/buttonConfig" + }, + "newStudies": { + "$ref": "#/definitions/buttonConfig" + } + }, + "additionalProperties": false, + "definitions": { + "buttonConfig": { + "type": "object", + "properties": { + "categories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { "type": "string" }, + "title": { "type": "string" }, + "description": { "type": "string" } + }, + "required": ["id", "title"] + } + }, + "resources": { + "type": "array", + "items": { + "oneOf": [{ + "type": "object", + "properties": { + "resourceType": { "enum": ["study"] }, + "title": { "type": "string" }, + "icon": { "type": "string" }, + "newStudyLabel": { "type": "string" }, + "idToWidget": { "type": "string" } + }, + "required": ["resourceType", "title"] + }, { + "type": "object", + "properties": { + "resourceType": { "enum": ["template"] }, + "expectedTemplateLabel": { "type": "string" }, + "title": { "type": "string" }, + "icon": { "type": "string" }, + "newStudyLabel": { "type": "string" }, + "category": { "type": "string" }, + "idToWidget": { "type": "string" } + }, + "required": ["resourceType", "expectedTemplateLabel", "title"] + }, { + "type": "object", + "properties": { + "resourceType": { "enum": ["service"] }, + "expectedKey": { "type": "string" }, + "title": { "type": "string" }, + "icon": { "type": "string" }, + "newStudyLabel": { "type": "string" }, + "category": { "type": "string" }, + "idToWidget": { "type": "string" } + }, + "required": ["resourceType", "expectedKey", "title"] + }, { + "type": "object", + "properties": { + "showDisabled": { + "type": "boolean", + "enum": [true] + }, + "title": { "type": "string" }, + "icon": { "type": "string" }, + "reason": { "type": "string" }, + "newStudyLabel": { "type": "string" }, + "category": { "type": "string" }, + "idToWidget": { "type": "string" } + }, + "required": ["showDisabled", "title"] + }] + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + } +}