cassproject 5.0.2 → 5.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/package.json +11 -11
- package/src/org/cass/exporter/CSVExport.js +11 -1
- package/src/org/cass/importer/CTDLASNCSVConceptImport.js +174 -9
- package/src/org/cass/importer/CTDLASNCSVImport.js +163 -8
- package/src/test/1.EcAesCtrAsync.test.js +20 -20
- package/src/test/1.EcAesCtrAsyncWorker.test.js +19 -19
- package/src/test/1.EcRepository.l0.test.js +103 -103
- package/src/test/1.EcRepository.l1.test.js +93 -93
- package/src/test/1.EcRepository.l2.test.js +120 -120
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cassproject",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.3",
|
|
4
4
|
"description": "Competency and Skills Service",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -10,12 +10,12 @@
|
|
|
10
10
|
"multitest": "concurrently --kill-others-on-fail \"npm run test15\" \"npm run test14\" \"npm run test13\" \"npm run test12\"",
|
|
11
11
|
"testCassTest": "npm run testkill && docker run -d --platform linux/amd64 --network cass-net --name cass-test -p80:80 -e CASS_LOOPBACK cass-test && wait-on http://localhost/api/ping && npm run testNode18 && npm run testNode18Fips && npm run testNode16 && npm run testNode15 && npm run testNode14 && npm run testNode13 && npm run testNode12 && npm run testCypressEdge && npm run testCypress && npm run testkill",
|
|
12
12
|
"testDevHttps": "npm run testkill && docker run -d --platform linux/amd64 --network cass-net --name cass-test -p443:80 -e CASS_LOOPBACK -e HTTPS=true cassproject/cass:dev && wait-on https://localhost/api/ping && npm run testNode18HttpsFips && npm run testNode18Https && npm run testNode16Https && npm run testNode15Https && npm run testNode14Https && npm run testNode13Https && npm run testNode12Https && npm run testCypressEdgeHttps && npm run testCypressHttps",
|
|
13
|
-
"test16HttpsFips": "export CASS_LOOPBACK=https://cass-testsf16/api/|| set CASS_LOOPBACK=https://cass-testsf16/api/&& npm run testkillsf16 && docker run -d --platform linux/amd64 --network cass-net --name cass-testsf16 -p450:443 -e CASS_LOOPBACK -e HTTPS=true cassproject/cass:1.6.
|
|
14
|
-
"test16Https11Fips": "export CASS_LOOPBACK=https://cass-testsf1116/api/|| set CASS_LOOPBACK=https://cass-testsf1116/api/&& npm run testkillsf1116 && docker run -d --platform linux/amd64 --network cass-net --name cass-testsf1116 -p449:443 -e CASS_LOOPBACK -e HTTPS=true -e HTTP2=false cassproject/cass:1.6.
|
|
15
|
-
"test16Https": "export CASS_LOOPBACK=https://cass-tests16/api/|| set CASS_LOOPBACK=https://cass-tests16/api/&& npm run testkills16 && docker run -d --platform linux/amd64 --network cass-net --name cass-tests16 -p448:443 -e CASS_LOOPBACK -e HTTPS=true cassproject/cass:1.6.
|
|
16
|
-
"test16Https11": "export CASS_LOOPBACK=https://cass-tests1116/api/|| set CASS_LOOPBACK=https://cass-tests1116/api/&& npm run testkills1116 && docker run -d --platform linux/amd64 --network cass-net --name cass-tests1116 -p451:443 -e CASS_LOOPBACK -e HTTPS=true -e HTTP2=false cassproject/cass:1.6.
|
|
17
|
-
"test16Fips": "export CASS_LOOPBACK=http://cass-testf16/api/|| set CASS_LOOPBACK=http://cass-testf16/api/&& npm run testkillf16 && docker run -d --platform linux/amd64 --network cass-net -e CASS_LOOPBACK --name cass-testf16 -p83:80 cassproject/cass:1.6.
|
|
18
|
-
"test16": "export CASS_LOOPBACK=http://cass-test16/api/|| set CASS_LOOPBACK=http://cass-test16/api/&& npm run testkill16 && docker run -d --platform linux/amd64 --network cass-net -e CASS_LOOPBACK --name cass-test16 -p82:80 cassproject/cass:1.6.
|
|
13
|
+
"test16HttpsFips": "export CASS_LOOPBACK=https://cass-testsf16/api/|| set CASS_LOOPBACK=https://cass-testsf16/api/&& npm run testkillsf16 && docker run -d --platform linux/amd64 --network cass-net --name cass-testsf16 -p450:443 -e CASS_LOOPBACK -e HTTPS=true cassproject/cass:1.6.6 && wait-on https://localhost:450/api/ping && concurrently --kill-others-on-fail \"npm run testCypressHttps\" \"npm run testNode24Https\" \"npm run testNode24HttpsFips\" \"npm run testNode24HttpsForceFips\" \"npm run testNode22Https\" \"npm run testNode22HttpsFips\" \"npm run testNode22HttpsForceFips\" \"npm run testNode20Https\" \"npm run testNode20HttpsFips\" \"npm run testNode20HttpsForceFips\" \"npm run testNode18Https\" && npm run testkillsf16",
|
|
14
|
+
"test16Https11Fips": "export CASS_LOOPBACK=https://cass-testsf1116/api/|| set CASS_LOOPBACK=https://cass-testsf1116/api/&& npm run testkillsf1116 && docker run -d --platform linux/amd64 --network cass-net --name cass-testsf1116 -p449:443 -e CASS_LOOPBACK -e HTTPS=true -e HTTP2=false cassproject/cass:1.6.6 && wait-on https://localhost:449/api/ping && concurrently --kill-others-on-fail \"npm run testCypressHttps\" \"npm run testNode24Https\" \"npm run testNode24HttpsFips\" \"npm run testNode24HttpsForceFips\" \"npm run testNode22Https\" \"npm run testNode22HttpsFips\" \"npm run testNode22HttpsForceFips\" \"npm run testNode20Https\" \"npm run testNode20HttpsFips\" \"npm run testNode20HttpsForceFips\" \"npm run testNode18Https\" && npm run testkillsf1116",
|
|
15
|
+
"test16Https": "export CASS_LOOPBACK=https://cass-tests16/api/|| set CASS_LOOPBACK=https://cass-tests16/api/&& npm run testkills16 && docker run -d --platform linux/amd64 --network cass-net --name cass-tests16 -p448:443 -e CASS_LOOPBACK -e HTTPS=true cassproject/cass:1.6.6 && wait-on https://localhost:448/api/ping && concurrently --kill-others-on-fail \"npm run testCypressHttps\" \"npm run testNode24Https\" \"npm run testNode24HttpsFips\" \"npm run testNode22Https\" \"npm run testNode22HttpsFips\" \"npm run testNode20Https\" \"npm run testNode20HttpsFips\" \"npm run testNode18Https\" && npm run testkills16",
|
|
16
|
+
"test16Https11": "export CASS_LOOPBACK=https://cass-tests1116/api/|| set CASS_LOOPBACK=https://cass-tests1116/api/&& npm run testkills1116 && docker run -d --platform linux/amd64 --network cass-net --name cass-tests1116 -p451:443 -e CASS_LOOPBACK -e HTTPS=true -e HTTP2=false cassproject/cass:1.6.6 && wait-on https://localhost:451/api/ping && concurrently --kill-others-on-fail \"npm run testCypressHttps\" \"npm run testNode24Https\" \"npm run testNode24HttpsFips\" \"npm run testNode22Https\" \"npm run testNode22HttpsFips\" \"npm run testNode20Https\" \"npm run testNode20HttpsFips\" \"npm run testNode18Https\" && npm run testkills1116",
|
|
17
|
+
"test16Fips": "export CASS_LOOPBACK=http://cass-testf16/api/|| set CASS_LOOPBACK=http://cass-testf16/api/&& npm run testkillf16 && docker run -d --platform linux/amd64 --network cass-net -e CASS_LOOPBACK --name cass-testf16 -p83:80 cassproject/cass:1.6.6 && wait-on http://localhost:83/api/ping && concurrently --kill-others-on-fail \"npm run testCypress\" \"npm run testNode24\" \"npm run testNode24Fips\" \"npm run testNode24ForceFips\" \"npm run testNode22\" \"npm run testNode22Fips\" \"npm run testNode22ForceFips\" \"npm run testNode20\" \"npm run testNode20Fips\" \"npm run testNode20ForceFips\" \"npm run testNode18\" && npm run testkillf16",
|
|
18
|
+
"test16": "export CASS_LOOPBACK=http://cass-test16/api/|| set CASS_LOOPBACK=http://cass-test16/api/&& npm run testkill16 && docker run -d --platform linux/amd64 --network cass-net -e CASS_LOOPBACK --name cass-test16 -p82:80 cassproject/cass:1.6.6 && wait-on http://localhost:82/api/ping && concurrently --kill-others-on-fail \"npm run testCypress\" \"npm run testNode24\" \"npm run testNode24Fips\" \"npm run testNode22\" \"npm run testNode22Fips\" \"npm run testNode20\" \"npm run testNode20Fips\" \"npm run testNode18\" && npm run testkill16",
|
|
19
19
|
"test15HttpsFips": "export TESTLEVEL=15|| set TESTLEVEL=15&& export CASS_LOOPBACK=https://cass-testsf15/api/|| set CASS_LOOPBACK=https://cass-testsf15/api/&& npm run testkillsf15 && docker run -d --platform linux/amd64 --network cass-net --name cass-testsf15 -p446:443 -e CASS_LOOPBACK -e HTTPS=true cassproject/cass:1.5.75 && wait-on https://localhost:446/api/ping && concurrently --kill-others-on-fail \"npm run testCypressHttps\" \"npm run testNode24Https\" \"npm run testNode24HttpsFips\" \"npm run testNode22Https\" \"npm run testNode22HttpsFips\" \"npm run testNode22HttpsForceFips\" \"npm run testNode20Https\" \"npm run testNode20HttpsFips\" \"npm run testNode20HttpsForceFips\" \"npm run testNode18Https\" && npm run testkillsf15",
|
|
20
20
|
"test15Https11Fips": "export TESTLEVEL=15|| set TESTLEVEL=15&& export CASS_LOOPBACK=https://cass-testsf1115/api/|| set CASS_LOOPBACK=https://cass-testsf1115/api/&& npm run testkillsf1115 && docker run -d --platform linux/amd64 --network cass-net --name cass-testsf1115 -p447:443 -e CASS_LOOPBACK -e HTTPS=true -e HTTP2=false cassproject/cass:1.5.75 && wait-on https://localhost:447/api/ping && concurrently --kill-others-on-fail \"npm run testCypressHttps\" \"npm run testNode24Https\" \"npm run testNode24HttpsFips\" \"npm run testNode24HttpsForceFips\" \"npm run testNode22Https\" \"npm run testNode22HttpsFips\" \"npm run testNode22HttpsForceFips\" \"npm run testNode20Https\" \"npm run testNode20HttpsFips\" \"npm run testNode20HttpsForceFips\" \"npm run testNode18Https\" && npm run testkillsf1115",
|
|
21
21
|
"test15Https": "export TESTLEVEL=15|| set TESTLEVEL=15&& export CASS_LOOPBACK=https://cass-tests15/api/|| set CASS_LOOPBACK=https://cass-tests15/api/&& npm run testkills15 && docker run -d --platform linux/amd64 --network cass-net --name cass-tests15 -p444:443 -e CASS_LOOPBACK -e HTTPS=true cassproject/cass:1.5.75 && wait-on https://localhost:444/api/ping && concurrently --kill-others-on-fail \"npm run testCypressHttps\" \"npm run testNode24Https\" \"npm run testNode24HttpsFips\" \"npm run testNode22Https\" \"npm run testNode22HttpsFips\" \"npm run testNode20Https\" \"npm run testNode20HttpsFips\" \"npm run testNode18Https\" && npm run testkills15",
|
|
@@ -188,10 +188,10 @@
|
|
|
188
188
|
"chai": "4.5.0",
|
|
189
189
|
"concurrently": "^9.2.1",
|
|
190
190
|
"convert-hrtime": "^5.0.0",
|
|
191
|
-
"cypress": "^15.
|
|
191
|
+
"cypress": "^15.4.0",
|
|
192
192
|
"cypress-fail-fast": "^7.1.1",
|
|
193
|
-
"eslint": "^9.
|
|
194
|
-
"fake-indexeddb": "^6.2.
|
|
193
|
+
"eslint": "^9.37.0",
|
|
194
|
+
"fake-indexeddb": "^6.2.3",
|
|
195
195
|
"mocha": "^10.8.2",
|
|
196
196
|
"node-polyfill-webpack-plugin": "^4.1.0",
|
|
197
197
|
"nodemon": "^3.1.10",
|
|
@@ -199,7 +199,7 @@
|
|
|
199
199
|
"sinon": "^19.0.5",
|
|
200
200
|
"url-polyfill": "^1.1.14",
|
|
201
201
|
"wait-on": "^8.0.5",
|
|
202
|
-
"webpack": "^5.
|
|
202
|
+
"webpack": "^5.102.1",
|
|
203
203
|
"webpack-cli": "^5.1.4"
|
|
204
204
|
},
|
|
205
205
|
"packageManager": "npm@11.3.0+sha512.96eb611483f49c55f7fa74df61b588de9e213f80a256728e6798ddc67176c7b07e4a1cfc7de8922422cbce02543714367037536955221fa451b0c4fefaf20c66"
|
|
@@ -23,6 +23,7 @@ module.exports = class CSVExport extends Exporter {
|
|
|
23
23
|
compExport.downloadCSV(fileName);
|
|
24
24
|
}
|
|
25
25
|
static exportCTDLASN(json, name) {
|
|
26
|
+
console.log('export CTDL');
|
|
26
27
|
let objects = [];
|
|
27
28
|
CSVExport.findGraphs(json, objects);
|
|
28
29
|
CSVExport.exportObjects(objects, name + ".csv", true);
|
|
@@ -202,7 +203,16 @@ module.exports = class CSVExport extends Exporter {
|
|
|
202
203
|
display += props2[prop2][prop3] + "|";
|
|
203
204
|
}
|
|
204
205
|
} else {
|
|
205
|
-
|
|
206
|
+
if (prop === 'ceterms:versionIdentifier') {
|
|
207
|
+
if (props2[prop2]['ceterms:identifierValueCode'])
|
|
208
|
+
display += props2[prop2]['ceterms:identifierValueCode'] + "~";
|
|
209
|
+
if (props2[prop2]['ceterms:identifierTypeName'])
|
|
210
|
+
display += Object.values(props2[prop2]['ceterms:identifierTypeName'])[0] + "~";
|
|
211
|
+
if (props2[prop2]['ceterms:identifierType'])
|
|
212
|
+
display += props2[prop2]['ceterms:identifierType'] + "|";
|
|
213
|
+
} else {
|
|
214
|
+
display += props2[prop2] + "|";
|
|
215
|
+
}
|
|
206
216
|
}
|
|
207
217
|
}
|
|
208
218
|
display = display.substring(0, display.length - 1);
|
|
@@ -109,7 +109,8 @@ module.exports = class CTDLASNCSVConceptImport {
|
|
|
109
109
|
ceo,
|
|
110
110
|
endpoint,
|
|
111
111
|
eim,
|
|
112
|
-
progressionsFlag
|
|
112
|
+
progressionsFlag,
|
|
113
|
+
validationRules
|
|
113
114
|
) {
|
|
114
115
|
if (eim === undefined || eim == null)
|
|
115
116
|
eim = EcIdentityManager.default;
|
|
@@ -123,7 +124,7 @@ module.exports = class CTDLASNCSVConceptImport {
|
|
|
123
124
|
failure("Invalid file type");
|
|
124
125
|
}
|
|
125
126
|
if (progressionsFlag) {
|
|
126
|
-
return this.importProgressions(repo, file, success, failure, ceo, endpoint, eim);
|
|
127
|
+
return this.importProgressions(repo, file, success, failure, ceo, endpoint, eim, validationRules);
|
|
127
128
|
}
|
|
128
129
|
Papa.parse(file, {
|
|
129
130
|
header: true,
|
|
@@ -139,21 +140,50 @@ module.exports = class CTDLASNCSVConceptImport {
|
|
|
139
140
|
} catch (e) {
|
|
140
141
|
console.error('Error trimming data', e);
|
|
141
142
|
}
|
|
143
|
+
|
|
144
|
+
// Validate hierarchy before processing (only if validation rules provided)
|
|
145
|
+
if (validationRules && validationRules.validateHierarchy !== false) {
|
|
146
|
+
const hierarchyError = CTDLASNCSVConceptImport.validateConceptHierarchy(
|
|
147
|
+
tabularData,
|
|
148
|
+
validationRules.hierarchyRules
|
|
149
|
+
);
|
|
150
|
+
if (hierarchyError) {
|
|
151
|
+
failure(hierarchyError);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
142
155
|
const terms = JSON.parse(JSON.stringify((await EcRemote.getExpectingObject("https://schema.cassproject.org/0.4/jsonld1.1/ceasn2cassConceptsTerms"))));
|
|
143
156
|
let schemeArray = [];
|
|
144
157
|
let concepts = [];
|
|
145
158
|
for (let each = 0; each < tabularData.length; each++) {
|
|
146
159
|
let pretranslatedE = tabularData[each];
|
|
147
|
-
//
|
|
148
|
-
if (!pretranslatedE["@
|
|
160
|
+
// Skip extra lines if found in file
|
|
161
|
+
if (!pretranslatedE || !pretranslatedE["@type"]) {
|
|
149
162
|
continue;
|
|
150
163
|
}
|
|
164
|
+
if (!pretranslatedE["@id"]) {
|
|
165
|
+
failure(`Row ${each + 2}: is missing an @id`);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
151
168
|
if (
|
|
152
169
|
pretranslatedE["@type"].toLowerCase().startsWith('sample') || pretranslatedE["@type"].toLowerCase().startsWith('instruction')
|
|
153
170
|
) {
|
|
154
171
|
continue;
|
|
155
172
|
}
|
|
156
173
|
if (pretranslatedE["@type"] == "skos:ConceptScheme") {
|
|
174
|
+
// Validate required properties (only if validation rules provided)
|
|
175
|
+
if (validationRules && validationRules.requiredProps) {
|
|
176
|
+
const validationError = CTDLASNCSVConceptImport.validateRequiredProperties(
|
|
177
|
+
pretranslatedE,
|
|
178
|
+
"skos:ConceptScheme",
|
|
179
|
+
each + 2,
|
|
180
|
+
validationRules.requiredProps
|
|
181
|
+
);
|
|
182
|
+
if (validationError) {
|
|
183
|
+
failure(validationError);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
157
187
|
let translator = new EcLinkedData(null, null);
|
|
158
188
|
translator.copyFrom(pretranslatedE);
|
|
159
189
|
CTDLASNCSVImport.cleanUpTranslator(
|
|
@@ -223,6 +253,19 @@ module.exports = class CTDLASNCSVConceptImport {
|
|
|
223
253
|
setVersionIdentifier(f);
|
|
224
254
|
schemeArray.push(f);
|
|
225
255
|
} else if (pretranslatedE["@type"] == "skos:Concept") {
|
|
256
|
+
// Validate required properties (only if validation rules provided)
|
|
257
|
+
if (validationRules && validationRules.requiredProps) {
|
|
258
|
+
const validationError = CTDLASNCSVConceptImport.validateRequiredProperties(
|
|
259
|
+
pretranslatedE,
|
|
260
|
+
"skos:Concept",
|
|
261
|
+
each + 2,
|
|
262
|
+
validationRules.requiredProps
|
|
263
|
+
);
|
|
264
|
+
if (validationError) {
|
|
265
|
+
failure(validationError);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
226
269
|
let translator = new EcLinkedData(null, null);
|
|
227
270
|
translator.copyFrom(pretranslatedE);
|
|
228
271
|
CTDLASNCSVImport.cleanUpTranslator(
|
|
@@ -251,6 +294,7 @@ module.exports = class CTDLASNCSVConceptImport {
|
|
|
251
294
|
let f = new EcConcept();
|
|
252
295
|
f.copyFrom(e);
|
|
253
296
|
if (e["id"] == null) {
|
|
297
|
+
failure(`Row ${each + 2}: Concept is missing an id`);
|
|
254
298
|
return;
|
|
255
299
|
}
|
|
256
300
|
if (
|
|
@@ -364,10 +408,11 @@ module.exports = class CTDLASNCSVConceptImport {
|
|
|
364
408
|
pretranslatedE["@type"] == null ||
|
|
365
409
|
pretranslatedE["@type"] == ""
|
|
366
410
|
) {
|
|
411
|
+
failure(`Row ${each + 2}: Missing or empty @type`);
|
|
367
412
|
return;
|
|
368
413
|
} else {
|
|
369
|
-
|
|
370
|
-
|
|
414
|
+
failure(
|
|
415
|
+
`Row ${each + 2}: Found unknown type: ` + pretranslatedE["@type"]
|
|
371
416
|
);
|
|
372
417
|
return;
|
|
373
418
|
}
|
|
@@ -384,7 +429,8 @@ module.exports = class CTDLASNCSVConceptImport {
|
|
|
384
429
|
failure,
|
|
385
430
|
ceo,
|
|
386
431
|
endpoint,
|
|
387
|
-
eim
|
|
432
|
+
eim,
|
|
433
|
+
validationRules
|
|
388
434
|
) {
|
|
389
435
|
Papa.parse(file, {
|
|
390
436
|
header: true,
|
|
@@ -400,17 +446,50 @@ module.exports = class CTDLASNCSVConceptImport {
|
|
|
400
446
|
} catch (e) {
|
|
401
447
|
console.error('Error trimming data', e);
|
|
402
448
|
}
|
|
449
|
+
|
|
450
|
+
// Validate hierarchy before processing (only if validation rules provided)
|
|
451
|
+
if (validationRules && validationRules.validateHierarchy !== false) {
|
|
452
|
+
const hierarchyError = CTDLASNCSVConceptImport.validateConceptHierarchy(
|
|
453
|
+
tabularData,
|
|
454
|
+
validationRules.hierarchyRules
|
|
455
|
+
);
|
|
456
|
+
if (hierarchyError) {
|
|
457
|
+
failure(hierarchyError);
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
403
461
|
const terms = JSON.parse(JSON.stringify((await EcRemote.getExpectingObject("https://schema.cassproject.org/0.4/jsonld1.1/ceasn2cassConceptsTerms"))));
|
|
404
462
|
let schemeArray = [];
|
|
405
463
|
let concepts = [];
|
|
406
464
|
for (let each = 0; each < tabularData.length; each++) {
|
|
407
465
|
let pretranslatedE = tabularData[each];
|
|
466
|
+
// Skip extra lines if found in file
|
|
467
|
+
if (!pretranslatedE || !pretranslatedE["@type"]) {
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
if (!pretranslatedE["@id"]) {
|
|
471
|
+
failure(`Row ${each + 2}: is missing an @id`);
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
408
474
|
if (
|
|
409
475
|
pretranslatedE["@type"].toLowerCase().startsWith('sample') || pretranslatedE["@type"].toLowerCase().startsWith('instruction')
|
|
410
476
|
) {
|
|
411
477
|
continue;
|
|
412
478
|
}
|
|
413
479
|
if (pretranslatedE["@type"] == "asn:ProgressionModel") {
|
|
480
|
+
// Validate required properties (only if validation rules provided)
|
|
481
|
+
if (validationRules && validationRules.requiredProps) {
|
|
482
|
+
const validationError = CTDLASNCSVConceptImport.validateRequiredProperties(
|
|
483
|
+
pretranslatedE,
|
|
484
|
+
"asn:ProgressionModel",
|
|
485
|
+
each + 2,
|
|
486
|
+
validationRules.requiredProps
|
|
487
|
+
);
|
|
488
|
+
if (validationError) {
|
|
489
|
+
failure(validationError);
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
414
493
|
let translator = new EcLinkedData(null, null);
|
|
415
494
|
translator.copyFrom(pretranslatedE);
|
|
416
495
|
CTDLASNCSVImport.cleanUpTranslator(
|
|
@@ -476,6 +555,19 @@ module.exports = class CTDLASNCSVConceptImport {
|
|
|
476
555
|
f.subType = "Progression";
|
|
477
556
|
schemeArray.push(f);
|
|
478
557
|
} else if (pretranslatedE["@type"] == "asn:ProgressionLevel") {
|
|
558
|
+
// Validate required properties (only if validation rules provided)
|
|
559
|
+
if (validationRules && validationRules.requiredProps) {
|
|
560
|
+
const validationError = CTDLASNCSVConceptImport.validateRequiredProperties(
|
|
561
|
+
pretranslatedE,
|
|
562
|
+
"asn:ProgressionLevel",
|
|
563
|
+
each + 2,
|
|
564
|
+
validationRules.requiredProps
|
|
565
|
+
);
|
|
566
|
+
if (validationError) {
|
|
567
|
+
failure(validationError);
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
479
571
|
let translator = new EcLinkedData(null, null);
|
|
480
572
|
translator.copyFrom(pretranslatedE);
|
|
481
573
|
CTDLASNCSVImport.cleanUpTranslator(
|
|
@@ -504,6 +596,7 @@ module.exports = class CTDLASNCSVConceptImport {
|
|
|
504
596
|
let f = new EcConcept();
|
|
505
597
|
f.copyFrom(e);
|
|
506
598
|
if (e["id"] == null) {
|
|
599
|
+
failure(`Row ${each + 2}: Concept is missing an id`);
|
|
507
600
|
return;
|
|
508
601
|
}
|
|
509
602
|
if (
|
|
@@ -583,10 +676,11 @@ module.exports = class CTDLASNCSVConceptImport {
|
|
|
583
676
|
pretranslatedE["@type"] == null ||
|
|
584
677
|
pretranslatedE["@type"] == ""
|
|
585
678
|
) {
|
|
679
|
+
failure(`Row ${each + 2}: Missing or empty @type`);
|
|
586
680
|
return;
|
|
587
681
|
} else {
|
|
588
|
-
|
|
589
|
-
|
|
682
|
+
failure(
|
|
683
|
+
`Row ${each + 2}: Found unknown type: ` + pretranslatedE["@type"]
|
|
590
684
|
);
|
|
591
685
|
return;
|
|
592
686
|
}
|
|
@@ -596,4 +690,75 @@ module.exports = class CTDLASNCSVConceptImport {
|
|
|
596
690
|
error: failure
|
|
597
691
|
});
|
|
598
692
|
}
|
|
693
|
+
|
|
694
|
+
static validateRequiredProperties(obj, type, rowIndex, requiredProps) {
|
|
695
|
+
// Use provided validation rules or fall back to defaults
|
|
696
|
+
if (!requiredProps) {
|
|
697
|
+
return 'Required properties not defined.'
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
const props = requiredProps[type];
|
|
701
|
+
if (!props) return null;
|
|
702
|
+
|
|
703
|
+
const missing = [];
|
|
704
|
+
for (const prop of props) {
|
|
705
|
+
if (!obj[prop] || (typeof obj[prop] === 'string' && obj[prop].trim() === '')) {
|
|
706
|
+
missing.push(prop);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
if (missing.length > 0) {
|
|
711
|
+
return `Row ${rowIndex}: Missing required properties for ${type}: ${missing.join(', ')}`;
|
|
712
|
+
}
|
|
713
|
+
return null;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
static validateConceptHierarchy(tabularData, hierarchyRules) {
|
|
717
|
+
// Use provided hierarchy rules or fall back to defaults
|
|
718
|
+
if (!hierarchyRules) {
|
|
719
|
+
return 'Hierarchy rules not defined.'
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// Check each type defined in hierarchy rules
|
|
723
|
+
for (const [type, rules] of Object.entries(hierarchyRules)) {
|
|
724
|
+
const hasType = tabularData.some(r => r && r["@type"] === type);
|
|
725
|
+
if (!hasType) continue;
|
|
726
|
+
|
|
727
|
+
let hasRequiredProp = false;
|
|
728
|
+
let hasChildProp = false;
|
|
729
|
+
|
|
730
|
+
for (const row of tabularData) {
|
|
731
|
+
if (!row || !row["@type"]) continue;
|
|
732
|
+
|
|
733
|
+
// Check if this row has any of the required properties
|
|
734
|
+
if (row["@type"] === type && rules.requiredProperties) {
|
|
735
|
+
for (const prop of rules.requiredProperties) {
|
|
736
|
+
if (row[prop]) {
|
|
737
|
+
hasRequiredProp = true;
|
|
738
|
+
break;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// Check if any row has child properties
|
|
744
|
+
if (rules.childProperties) {
|
|
745
|
+
for (const prop of rules.childProperties) {
|
|
746
|
+
if (row[prop]) {
|
|
747
|
+
hasChildProp = true;
|
|
748
|
+
break;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
if (hasRequiredProp && hasChildProp) break;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// Validate that at least one of the hierarchy properties exists
|
|
757
|
+
if (!hasRequiredProp && !hasChildProp) {
|
|
758
|
+
return rules.errorMessage || `CSV must contain hierarchy properties for ${type}`;
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
return null;
|
|
763
|
+
}
|
|
599
764
|
};
|
|
@@ -133,7 +133,8 @@ module.exports = class CTDLASNCSVImport {
|
|
|
133
133
|
endpoint,
|
|
134
134
|
eim,
|
|
135
135
|
collectionsFlag,
|
|
136
|
-
skip
|
|
136
|
+
skip,
|
|
137
|
+
validationRules
|
|
137
138
|
) {
|
|
138
139
|
if (eim === undefined || eim == null)
|
|
139
140
|
eim = EcIdentityManager.default;
|
|
@@ -147,7 +148,7 @@ module.exports = class CTDLASNCSVImport {
|
|
|
147
148
|
failure("Invalid file type");
|
|
148
149
|
}
|
|
149
150
|
if (collectionsFlag) {
|
|
150
|
-
return this.importCollectionsAndCompetencies(repo, file, success, failure, ceo, endpoint, eim, skip);
|
|
151
|
+
return this.importCollectionsAndCompetencies(repo, file, success, failure, ceo, endpoint, eim, skip, validationRules);
|
|
151
152
|
}
|
|
152
153
|
Papa.parse(file, {
|
|
153
154
|
header: true,
|
|
@@ -163,7 +164,19 @@ module.exports = class CTDLASNCSVImport {
|
|
|
163
164
|
} catch (e) {
|
|
164
165
|
console.error('Error trimming data', e);
|
|
165
166
|
}
|
|
166
|
-
|
|
167
|
+
|
|
168
|
+
// Validate hierarchy before processing (only if validation rules provided)
|
|
169
|
+
if (validationRules && validationRules.validateHierarchy !== false) {
|
|
170
|
+
const hierarchyError = CTDLASNCSVImport.validateFrameworkHierarchy(
|
|
171
|
+
tabularData,
|
|
172
|
+
validationRules.hierarchyRules
|
|
173
|
+
);
|
|
174
|
+
if (hierarchyError) {
|
|
175
|
+
failure(hierarchyError);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
167
180
|
const terms = JSON.parse(JSON.stringify((await EcRemote.getExpectingObject("https://schema.cassproject.org/0.4/jsonld1.1/ceasn2cassTerms"))));
|
|
168
181
|
let frameworks = {};
|
|
169
182
|
let frameworkArray = [];
|
|
@@ -206,6 +219,20 @@ module.exports = class CTDLASNCSVImport {
|
|
|
206
219
|
pretranslatedE["@type"] ==
|
|
207
220
|
"ceasn:CompetencyFramework"
|
|
208
221
|
) {
|
|
222
|
+
// Validate required properties (only if validation rules provided)
|
|
223
|
+
if (validationRules && validationRules.requiredProps) {
|
|
224
|
+
const validationError = CTDLASNCSVImport.validateRequiredProperties(
|
|
225
|
+
pretranslatedE,
|
|
226
|
+
"ceasn:CompetencyFramework",
|
|
227
|
+
i + 2,
|
|
228
|
+
validationRules.requiredProps
|
|
229
|
+
);
|
|
230
|
+
if (validationError) {
|
|
231
|
+
failure(validationError);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
209
236
|
let translator = new EcLinkedData(null, null);
|
|
210
237
|
translator.copyFrom(pretranslatedE);
|
|
211
238
|
CTDLASNCSVImport.cleanUpTranslator(
|
|
@@ -318,9 +345,9 @@ module.exports = class CTDLASNCSVImport {
|
|
|
318
345
|
// Remove skipped competencies
|
|
319
346
|
if (skip && Array.isArray(skip) && skip.length > 0 && f.competency) {
|
|
320
347
|
skip.forEach((element) => {
|
|
321
|
-
const id = (element.ctid ? element.ctid : element).replace('ce-', '');
|
|
348
|
+
const id = (element.ctid ? element.ctid : element).replace('ce-', '');
|
|
322
349
|
const index = f.competency.findIndex((comp) => comp.includes(id));
|
|
323
|
-
if (index) {
|
|
350
|
+
if (index >= 0) {
|
|
324
351
|
f.competency.splice(index, 1);
|
|
325
352
|
}
|
|
326
353
|
});
|
|
@@ -334,6 +361,20 @@ module.exports = class CTDLASNCSVImport {
|
|
|
334
361
|
} else if (
|
|
335
362
|
pretranslatedE["@type"] == "ceasn:Competency"
|
|
336
363
|
) {
|
|
364
|
+
// Validate required properties (only if validation rules provided)
|
|
365
|
+
if (validationRules && validationRules.requiredProps) {
|
|
366
|
+
const validationError = CTDLASNCSVImport.validateRequiredProperties(
|
|
367
|
+
pretranslatedE,
|
|
368
|
+
"ceasn:Competency",
|
|
369
|
+
i + 2,
|
|
370
|
+
validationRules.requiredProps
|
|
371
|
+
);
|
|
372
|
+
if (validationError) {
|
|
373
|
+
failure(validationError);
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
337
378
|
let translator = new EcLinkedData(null, null);
|
|
338
379
|
translator.copyFrom(pretranslatedE);
|
|
339
380
|
CTDLASNCSVImport.cleanUpTranslator(
|
|
@@ -369,6 +410,7 @@ module.exports = class CTDLASNCSVImport {
|
|
|
369
410
|
let f = new EcCompetency();
|
|
370
411
|
f.copyFrom(e);
|
|
371
412
|
if (e["id"] == null) {
|
|
413
|
+
failure(`Row ${i+2}: Competency is missing an id`);
|
|
372
414
|
return;
|
|
373
415
|
}
|
|
374
416
|
if (e["ceasn:isPartOf"] != null) {
|
|
@@ -632,6 +674,7 @@ module.exports = class CTDLASNCSVImport {
|
|
|
632
674
|
pretranslatedE["@type"] == null ||
|
|
633
675
|
pretranslatedE["@type"] == ""
|
|
634
676
|
) {
|
|
677
|
+
failure(`Row ${i+2}: Missing or empty @type`);
|
|
635
678
|
return;
|
|
636
679
|
} else {
|
|
637
680
|
failure(
|
|
@@ -653,7 +696,8 @@ module.exports = class CTDLASNCSVImport {
|
|
|
653
696
|
ceo,
|
|
654
697
|
endpoint,
|
|
655
698
|
eim,
|
|
656
|
-
skip
|
|
699
|
+
skip,
|
|
700
|
+
validationRules
|
|
657
701
|
) {
|
|
658
702
|
Papa.parse(file, {
|
|
659
703
|
header: true,
|
|
@@ -670,6 +714,18 @@ module.exports = class CTDLASNCSVImport {
|
|
|
670
714
|
console.error('Error trimming data', e);
|
|
671
715
|
}
|
|
672
716
|
|
|
717
|
+
// Validate hierarchy before processing (only if validation rules provided)
|
|
718
|
+
if (validationRules && validationRules.validateHierarchy !== false) {
|
|
719
|
+
const hierarchyError = CTDLASNCSVImport.validateFrameworkHierarchy(
|
|
720
|
+
tabularData,
|
|
721
|
+
validationRules.hierarchyRules
|
|
722
|
+
);
|
|
723
|
+
if (hierarchyError) {
|
|
724
|
+
failure(hierarchyError);
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
673
729
|
const terms = JSON.parse(JSON.stringify((await EcRemote.getExpectingObject("https://schema.cassproject.org/0.4/jsonld1.1/ceasn2cassTerms"))));
|
|
674
730
|
let frameworks = {};
|
|
675
731
|
let frameworkArray = [];
|
|
@@ -705,6 +761,19 @@ module.exports = class CTDLASNCSVImport {
|
|
|
705
761
|
pretranslatedE["@type"] ==
|
|
706
762
|
"ceterms:Collection"
|
|
707
763
|
) {
|
|
764
|
+
// Validate required properties (only if validation rules provided)
|
|
765
|
+
if (validationRules && validationRules.requiredProps) {
|
|
766
|
+
const validationError = CTDLASNCSVImport.validateRequiredProperties(
|
|
767
|
+
pretranslatedE,
|
|
768
|
+
"ceterms:Collection",
|
|
769
|
+
i + 2,
|
|
770
|
+
validationRules.requiredProps
|
|
771
|
+
);
|
|
772
|
+
if (validationError) {
|
|
773
|
+
failure(validationError);
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
708
777
|
let translator = new EcLinkedData(null, null);
|
|
709
778
|
translator.copyFrom(pretranslatedE);
|
|
710
779
|
CTDLASNCSVImport.cleanUpTranslator(
|
|
@@ -762,9 +831,9 @@ module.exports = class CTDLASNCSVImport {
|
|
|
762
831
|
// Remove skipped competencies
|
|
763
832
|
if (skip && Array.isArray(skip) && skip.length > 0 && f.competency) {
|
|
764
833
|
skip.forEach((element) => {
|
|
765
|
-
const id = (element.ctid ? element.ctid : element).replace('ce-', '');
|
|
834
|
+
const id = (element.ctid ? element.ctid : element).replace('ce-', '');
|
|
766
835
|
const index = f.competency.findIndex((comp) => comp.includes(id));
|
|
767
|
-
if (index) {
|
|
836
|
+
if (index >= 0) {
|
|
768
837
|
f.competency.splice(index, 1);
|
|
769
838
|
}
|
|
770
839
|
});
|
|
@@ -776,6 +845,19 @@ module.exports = class CTDLASNCSVImport {
|
|
|
776
845
|
} else if (
|
|
777
846
|
pretranslatedE["@type"] == "ceasn:Competency"
|
|
778
847
|
) {
|
|
848
|
+
// Validate required properties (only if validation rules provided)
|
|
849
|
+
if (validationRules && validationRules.requiredProps) {
|
|
850
|
+
const validationError = CTDLASNCSVImport.validateRequiredProperties(
|
|
851
|
+
pretranslatedE,
|
|
852
|
+
"ceterms:Collection:Competency",
|
|
853
|
+
i + 2,
|
|
854
|
+
validationRules.requiredProps
|
|
855
|
+
);
|
|
856
|
+
if (validationError) {
|
|
857
|
+
failure(validationError);
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
}
|
|
779
861
|
let translator = new EcLinkedData(null, null);
|
|
780
862
|
translator.copyFrom(pretranslatedE);
|
|
781
863
|
CTDLASNCSVImport.cleanUpTranslator(
|
|
@@ -799,6 +881,7 @@ module.exports = class CTDLASNCSVImport {
|
|
|
799
881
|
let f = new EcCompetency();
|
|
800
882
|
f.copyFrom(e);
|
|
801
883
|
if (e["id"] == null) {
|
|
884
|
+
failure(`Row ${i+2}: Competency is missing an id`);
|
|
802
885
|
return;
|
|
803
886
|
}
|
|
804
887
|
if (e["ceterms:isMemberOf"] != null) {
|
|
@@ -992,6 +1075,7 @@ module.exports = class CTDLASNCSVImport {
|
|
|
992
1075
|
pretranslatedE["@type"] == null ||
|
|
993
1076
|
pretranslatedE["@type"] == ""
|
|
994
1077
|
) {
|
|
1078
|
+
failure(`Row ${i+2}: Missing or empty @type`);
|
|
995
1079
|
return;
|
|
996
1080
|
} else {
|
|
997
1081
|
failure(
|
|
@@ -1244,6 +1328,77 @@ module.exports = class CTDLASNCSVImport {
|
|
|
1244
1328
|
frameworks[EcRemoteLinkedData.trimVersionFromUrl(e["ceasn:isPartOf"])].relation.push(r.shortId());
|
|
1245
1329
|
}
|
|
1246
1330
|
}
|
|
1331
|
+
static validateRequiredProperties(obj, type, rowIndex, requiredProps) {
|
|
1332
|
+
// Use provided validation rules or fall back to defaults
|
|
1333
|
+
if (!requiredProps) {
|
|
1334
|
+
return 'Required properties not defined.'
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
const props = requiredProps[type];
|
|
1338
|
+
if (!props) return null;
|
|
1339
|
+
|
|
1340
|
+
const missing = [];
|
|
1341
|
+
for (const prop of props) {
|
|
1342
|
+
if (!obj[prop] || (typeof obj[prop] === 'string' && obj[prop].trim() === '')) {
|
|
1343
|
+
missing.push(prop);
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
if (missing.length > 0) {
|
|
1348
|
+
return `Row ${rowIndex}: Missing required properties for ${type}: ${missing.join(', ')}`;
|
|
1349
|
+
}
|
|
1350
|
+
return null;
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
static validateFrameworkHierarchy(tabularData, hierarchyRules) {
|
|
1354
|
+
// Use provided hierarchy rules or fall back to defaults
|
|
1355
|
+
if (!hierarchyRules) {
|
|
1356
|
+
return 'Hierarchy rules not defined.'
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
// Check each type defined in hierarchy rules
|
|
1360
|
+
for (const [type, rules] of Object.entries(hierarchyRules)) {
|
|
1361
|
+
const hasType = tabularData.some(r => r && r["@type"] === type);
|
|
1362
|
+
if (!hasType) continue;
|
|
1363
|
+
|
|
1364
|
+
let hasRequiredProp = false;
|
|
1365
|
+
let hasChildProp = false;
|
|
1366
|
+
|
|
1367
|
+
for (const row of tabularData) {
|
|
1368
|
+
if (!row || !row["@type"]) continue;
|
|
1369
|
+
|
|
1370
|
+
// Check if this row has any of the required properties
|
|
1371
|
+
if (row["@type"] === type && rules.requiredProperties) {
|
|
1372
|
+
for (const prop of rules.requiredProperties) {
|
|
1373
|
+
if (row[prop]) {
|
|
1374
|
+
hasRequiredProp = true;
|
|
1375
|
+
break;
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
// Check if any row has child properties
|
|
1381
|
+
if (rules.childProperties) {
|
|
1382
|
+
for (const prop of rules.childProperties) {
|
|
1383
|
+
if (row[prop]) {
|
|
1384
|
+
hasChildProp = true;
|
|
1385
|
+
break;
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
if (hasRequiredProp && hasChildProp) break;
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
// Validate that at least one of the hierarchy properties exists
|
|
1394
|
+
if (!hasRequiredProp && !hasChildProp) {
|
|
1395
|
+
return rules.errorMessage || `CSV must contain hierarchy properties for ${type}`;
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
return null;
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1247
1402
|
static setDateCreated(importObject, object) {
|
|
1248
1403
|
if (
|
|
1249
1404
|
importObject["ceasn:dateCreated"] == null &&
|