Skip to content

Commit be9f062

Browse files
authored
🎨 [Frontend] Enh: validate UI config (#7225)
1 parent 4bed41e commit be9f062

File tree

2 files changed

+141
-21
lines changed

2 files changed

+141
-21
lines changed

services/static-webserver/client/source/class/osparc/store/Products.js

Lines changed: 52 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717

1818
/**
1919
* @asset(osparc/ui_config.json")
20+
* @asset(schemas/product-ui.json)
21+
* @asset(object-path/object-path-0-11-4.min.js)
22+
* @asset(ajv/ajv-6-11-0.min.js)
23+
* @ignore(Ajv)
2024
*/
2125

2226
qx.Class.define("osparc.store.Products", {
@@ -27,31 +31,58 @@ qx.Class.define("osparc.store.Products", {
2731
__uiConfig: null,
2832

2933
fetchUiConfig: function() {
30-
if (osparc.auth.Data.getInstance().isGuest()) {
31-
return new Promise(resolve => {
34+
return new Promise(resolve => {
35+
if (osparc.auth.Data.getInstance().isGuest()) {
3236
this.__uiConfig = {};
3337
resolve(this.__uiConfig);
34-
});
35-
}
38+
}
3639

37-
return Promise.all([
38-
osparc.data.Resources.fetch("productMetadata", "getUiConfig"),
39-
osparc.utils.Utils.fetchJSON("/resource/osparc/ui_config.json"),
40-
])
41-
.then(values => {
42-
let uiConfig = {};
43-
if (values[0] && values[0]["ui"] && Object.keys(values[0]["ui"]).length) {
44-
uiConfig = values[0]["ui"];
45-
} else {
46-
const product = osparc.product.Utils.getProductName();
47-
if (values[1] && product in values[1]) {
48-
uiConfig = values[1][product];
40+
Promise.all([
41+
osparc.data.Resources.fetch("productMetadata", "getUiConfig"),
42+
osparc.utils.Utils.fetchJSON("/resource/osparc/ui_config.json"),
43+
osparc.utils.Utils.fetchJSON("/resource/schemas/product-ui.json"),
44+
])
45+
.then(values => {
46+
let uiConfig = {};
47+
const beUiConfig = values[0];
48+
const feUiConfig = values[1];
49+
const schema = values[2];
50+
if (beUiConfig && beUiConfig["ui"] && Object.keys(beUiConfig["ui"]).length) {
51+
uiConfig = beUiConfig["ui"];
52+
} else {
53+
const product = osparc.product.Utils.getProductName();
54+
if (feUiConfig && product in feUiConfig) {
55+
uiConfig = feUiConfig[product];
56+
}
4957
}
50-
}
51-
this.__uiConfig = uiConfig;
52-
return this.__uiConfig;
53-
})
54-
.catch(console.error);
58+
const ajvLoader = new qx.util.DynamicScriptLoader([
59+
"/resource/ajv/ajv-6-11-0.min.js",
60+
"/resource/object-path/object-path-0-11-4.min.js"
61+
]);
62+
ajvLoader.addListener("ready", () => {
63+
const ajv = new Ajv({
64+
allErrors: true,
65+
strictDefaults: true,
66+
useDefaults: true,
67+
strictTypes: true,
68+
});
69+
const validate = ajv.compile(schema);
70+
const valid = validate(uiConfig);
71+
if (valid) {
72+
this.__uiConfig = uiConfig;
73+
resolve(this.__uiConfig);
74+
} else {
75+
osparc.FlashMessenger.getInstance().logAs("Wrong product.ui config", "ERROR");
76+
validate.errors.forEach(err => {
77+
console.error(`Error at ${err.dataPath}: ${err.message}`);
78+
});
79+
}
80+
});
81+
ajvLoader.addListener("failed", console.error, this);
82+
ajvLoader.start();
83+
})
84+
.catch(console.error);
85+
});
5586
},
5687

5788
getPlusButtonUiConfig: function() {
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema#",
3+
"type": "object",
4+
"properties": {
5+
"plusButton": {
6+
"$ref": "#/definitions/buttonConfig"
7+
},
8+
"newStudies": {
9+
"$ref": "#/definitions/buttonConfig"
10+
}
11+
},
12+
"additionalProperties": false,
13+
"definitions": {
14+
"buttonConfig": {
15+
"type": "object",
16+
"properties": {
17+
"categories": {
18+
"type": "array",
19+
"items": {
20+
"type": "object",
21+
"properties": {
22+
"id": { "type": "string" },
23+
"title": { "type": "string" },
24+
"description": { "type": "string" }
25+
},
26+
"required": ["id", "title"]
27+
}
28+
},
29+
"resources": {
30+
"type": "array",
31+
"items": {
32+
"oneOf": [{
33+
"type": "object",
34+
"properties": {
35+
"resourceType": { "enum": ["study"] },
36+
"title": { "type": "string" },
37+
"icon": { "type": "string" },
38+
"newStudyLabel": { "type": "string" },
39+
"idToWidget": { "type": "string" }
40+
},
41+
"required": ["resourceType", "title"]
42+
}, {
43+
"type": "object",
44+
"properties": {
45+
"resourceType": { "enum": ["template"] },
46+
"expectedTemplateLabel": { "type": "string" },
47+
"title": { "type": "string" },
48+
"icon": { "type": "string" },
49+
"newStudyLabel": { "type": "string" },
50+
"category": { "type": "string" },
51+
"idToWidget": { "type": "string" }
52+
},
53+
"required": ["resourceType", "expectedTemplateLabel", "title"]
54+
}, {
55+
"type": "object",
56+
"properties": {
57+
"resourceType": { "enum": ["service"] },
58+
"expectedKey": { "type": "string" },
59+
"title": { "type": "string" },
60+
"icon": { "type": "string" },
61+
"newStudyLabel": { "type": "string" },
62+
"category": { "type": "string" },
63+
"idToWidget": { "type": "string" }
64+
},
65+
"required": ["resourceType", "expectedKey", "title"]
66+
}, {
67+
"type": "object",
68+
"properties": {
69+
"showDisabled": {
70+
"type": "boolean",
71+
"enum": [true]
72+
},
73+
"title": { "type": "string" },
74+
"icon": { "type": "string" },
75+
"reason": { "type": "string" },
76+
"newStudyLabel": { "type": "string" },
77+
"category": { "type": "string" },
78+
"idToWidget": { "type": "string" }
79+
},
80+
"required": ["showDisabled", "title"]
81+
}]
82+
},
83+
"additionalProperties": false
84+
}
85+
},
86+
"additionalProperties": false
87+
}
88+
}
89+
}

0 commit comments

Comments
 (0)