@contrail/flexplm 1.1.61 → 1.1.63

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.
@@ -1,746 +1,745 @@
1
- import { API_VERSION, Entities, TypeClientOptions } from '@contrail/sdk';
2
- import { TypeUtils } from './type-utils';
3
- import { FCConfig } from '../interfaces/interfaces';
4
- import { MapFileUtil } from '@contrail/transform-data';
5
- import { Logger } from '@contrail/app-framework';
6
- import { ObjectDiff, ObjectUtil } from '@contrail/util';
7
- import { TypeConversionUtils } from './type-conversion-utils';
8
- import { MapUtil } from './map-utils';
9
-
10
- export class DataConverter {
11
- private typeUtils: TypeUtils;
12
- private transformMapFile: string;
13
- private useDisplayForEnumerationMatching = false;
14
- private verboseDebug = false;
15
- private objRefCache = {};
16
- private userRefCache = {};
17
- static staticUserCache = {};
18
- static clearStaticUserCache() {
19
- DataConverter.staticUserCache = {};
20
- }
21
- static getFromStaticCache(id: string) {
22
- return DataConverter.staticUserCache[id];
23
- }
24
- static setToStaticCache(id: string, value: any) {
25
- DataConverter.staticUserCache[id] = value;
26
- }
27
-
28
- constructor(private config: FCConfig, private mapFileUtil: MapFileUtil){
29
- this.typeUtils = new TypeUtils();
30
- this.transformMapFile = this.config['transformMapFile'];
31
- this.useDisplayForEnumerationMatching = this.config['dataConverter']
32
- && this.config['dataConverter']['useDisplayForEnumerationMatching'];
33
- this.verboseDebug = this.config['dataConverter']
34
- && this.config['dataConverter']['verboseDebug'];
35
- }
36
-
37
- public setVerboseDebug(val: boolean = false) {
38
- this.verboseDebug= val;
39
- }
40
- public isVerboseDebugOn(): boolean {
41
- return this.verboseDebug && Logger.isDebugOn();
42
- }
43
-
44
- async getFlexPLMObjectDataFromEvent(event, dataToSkip: string[]) {
45
- return this.getFlexPLMObjectData(event.newData, dataToSkip, true);
46
- }
47
-
48
- async getFlexPLMObjectData(newData, dataToSkip: string[], inflateObjRef: boolean) {
49
- if(this.isVerboseDebugOn()) {
50
- console.debug('newData: ' + JSON.stringify(newData));
51
- }
52
- //Using event to get propertyDiffs to find emptied values
53
- //Add standard atts to skip
54
- dataToSkip = dataToSkip.concat(['updatedOn', 'updatedById', 'createdOn', 'createdById', 'modifiedAt', 'orgId', 'createdAt', 'id', 'typeId', 'workspaceId']);
55
- // const oldData = event.oldData;
56
- const data = {};
57
- const typeId = newData?.typeId;
58
- if (!typeId) {
59
- return;// Don't have a type; so can't process
60
- }
61
- data['typePath'] = newData['typePath'];
62
- const type = await this.typeUtils.getTypeById(typeId);
63
- const typeProps = this.typeUtils.filterTypeProperties(type, newData);
64
- for(const prop of typeProps){
65
- // if(this.logger.isTraceOn()){
66
- // this.logger.log('prop: ' + JSON.stringify(prop));
67
- // }
68
- const slug = prop['slug'];
69
- if (dataToSkip.includes(slug)) {
70
- continue;
71
- }
72
- data[slug] = await this.getFlexPLMValue(prop, newData, inflateObjRef);
73
- }
74
-
75
- if(this.isVerboseDebugOn()) {
76
- console.debug('getFlexPLMObjectData-data: ' + JSON.stringify(data));
77
- }
78
- return data;
79
-
80
- }
81
-
82
- async getFlexPLMValue(prop, newData, inflateObjRef: boolean) {
83
- const propertyType = prop['propertyType'];
84
- const slug = prop['slug'];
85
- const nd = newData[slug];
86
- // console.log('getFlexPLMValue: ' + propertyType + ', ' +slug + ', ' + nd + ', ' + od);
87
- let value;
88
- if(['string', 'text'].includes(propertyType)){
89
- value = nd || '';
90
- }else if(['number', 'currency', 'percent', 'sequence'].includes(propertyType)){
91
- value = nd || 0;
92
- }else if('date' === propertyType){
93
- if(nd){
94
- value = nd;
95
- // const d = new Date(nd);
96
- // console.log('Date.getTimezoneOffset(): ' + d.getTimezoneOffset());
97
- // value = d.toISOString();
98
- // console.log('date: ' + nd + ' -- ' + value);
99
- } else {
100
- value = null;
101
- }
102
- }else if('boolean' === propertyType){
103
- value =(nd)? true: false;
104
- }else if('choice' === propertyType){
105
- value = this.getEnumerationValue(prop, nd);
106
- }else if('multi_select' === propertyType){
107
- value = this.getEnumerationValue(prop, nd);
108
- }else if('object_reference' === propertyType){
109
- value = await this.getObjectReferenceValue(prop, newData, inflateObjRef);
110
- if(this.isVerboseDebugOn()){
111
- console.debug('object_reference: ' + JSON.stringify(value));
112
- }
113
- } else if ('image' === propertyType) {
114
- // console.log('image-TODO');
115
- }else if('formula' === propertyType){
116
- value = nd;
117
- }else if('json' === propertyType){
118
- // console.log('json-TODO');
119
- value = nd;
120
- } else if ('userList' === propertyType) {
121
- value = await this.getUserListValue(prop, newData);
122
- }
123
-
124
- return value;
125
-
126
- }
127
- /** Returns the display values for list properties (choice & multi_select)
128
- *
129
- * @param prop
130
- * @param newData
131
- * @returns
132
- */
133
- getEnumerationValue(prop, nd){
134
- const propertyType = prop['propertyType'];
135
- let value;
136
- if(['choice', 'multi_select'].includes(propertyType)){
137
- const options: [{value:string, display:string}] = prop['options'] || [];
138
- if('choice' === propertyType){
139
- if(nd){
140
- const option = options.find(option => option.value == nd );
141
- if(option){
142
- value = Object.assign({},option);
143
- }
144
- }
145
- if(!value) {
146
- value = {};
147
- }
148
- } else if ('multi_select' === propertyType) {
149
- value = [];
150
- if (nd instanceof Array) {
151
- nd.forEach(key => {
152
- const optionObject = options.find(option => option.value == key);
153
- if(optionObject){
154
- const option = Object.assign({},optionObject);
155
- value.push(option);
156
- }
157
- });
158
- } else if( typeof nd === 'string' && '' !== nd) {
159
- const optionObject = options.find(option => option.value == nd);
160
- if(optionObject){
161
- const option = Object.assign({},optionObject);
162
- value.push(option);
163
- }
164
- }
165
- }
166
- }
167
- return value;
168
- }
169
-
170
- public async getObjectReferenceValue(prop: any, newData: any, inflateObjRef = false) {
171
- const slug = prop['slug'];
172
- if(Logger.isDebugOn()) {
173
- console.debug('getObjectReferenceValue-prop: ' + slug);
174
- }
175
- let value = newData[slug];
176
- const entityType = prop['referencedTypeRootSlug'];
177
- const entityId = newData[slug + 'Id'];
178
- if ((!value || typeof value === 'string' )&& inflateObjRef) {
179
- if (entityId) {
180
- if(this.objRefCache[entityId]){
181
- if(Logger.isDebugOn()) {
182
- console.debug('cache hit: ' + entityId);
183
- }
184
- return this.objRefCache[entityId];
185
- }
186
- const criteria = {
187
- id: entityId
188
- };
189
- const entities = await new Entities().get({
190
- entityName: entityType,
191
- criteria
192
- });
193
- value = (entities && entities[0]) ? entities[0] : undefined;
194
- }
195
- }
196
-
197
- if(value && !(typeof value === 'string')){
198
- const unprocessedValue = value;
199
- const objectClass = await TypeConversionUtils.getObjectClass(this.transformMapFile, this.mapFileUtil, unprocessedValue);
200
- const flexPLMTypePath = await TypeConversionUtils.getObjectTypePath(this.transformMapFile, this.mapFileUtil, unprocessedValue);
201
- value = await this.getFlexPLMObjectData(value, [], false);
202
- value['entityReference'] = entityType + ':' + entityId;
203
- value['objectClass'] = objectClass;
204
- value['flexPLMTypePath'] = flexPLMTypePath;
205
-
206
- if(this.transformMapFile){
207
- const mapKey = await TypeConversionUtils.getMapKey(this.transformMapFile, this.mapFileUtil, unprocessedValue, TypeConversionUtils.VIBE2FLEX_DIRECTION);
208
- value = await MapUtil.applyTransformMap(this.transformMapFile, this.mapFileUtil, value, mapKey, TypeConversionUtils.VIBE2FLEX_DIRECTION)
209
- }
210
-
211
- } else {
212
- value = {};
213
- }
214
- this.objRefCache[entityId] = value;
215
- return value;
216
- }
217
-
218
- /** (Deprecated) Use TypeConversionUtils.getMapKey()
219
- * Will return the class to use to get mapping.
220
- * This is needed because mappings will be different for different sub types
221
- * of custom-entity
222
- *
223
- * @param obj: Entity being checked
224
- * @param mapping: The whole mapping file
225
- */
226
- getMappingClass(entity: object, mapping: any): string{
227
- const entityTypePath = entity['typePath'];
228
- const typeMapKey = mapping['typeMapKey'];
229
- let objClass = typeMapKey[entityTypePath];
230
- const entityType = entity['entityType'];
231
-
232
- if(!objClass){
233
- objClass = this.typeUtils.getEventObjectClass(entityType, entity);
234
- }
235
- if(!objClass){
236
- objClass = entityType;
237
- }
238
- return objClass;
239
- }
240
-
241
- /** Sets entity values from FlexPLM data passed in
242
- * Assumes the entity has a VibeIQ typeId and 'roles' value to filter if necessary.
243
- * @param entity the entity to update
244
- * @param data the FlexPLM data
245
- * @param keysToSkip properties to skip
246
- * @returns the modified entity with VibeIQ values
247
- */
248
- async setEntityValues(entity, data, keysToSkip: string[] = []){
249
- // this.logger.log('setEntityValues: ' + JSON.stringify(entity));
250
- // this.logger.log('data: ' + JSON.stringify(data));
251
- const type = await this.typeUtils.getTypeById(entity.typeId);
252
- keysToSkip = keysToSkip.concat(['updatedOn', 'updatedById', 'createdOn', 'createdById', 'modifiedAt', 'orgId', 'createdAt', 'id', 'typeId', 'typePath', 'workspaceId']);
253
- let typeProps = this.typeUtils.filterTypeProperties(type, entity);
254
- typeProps = typeProps.filter( prop => !keysToSkip.includes(prop['slug']));
255
- //Only process properties that had a value sent; to not accidentally clear out values
256
- //for properties that weren't sent.
257
- const dataKeys = Object.getOwnPropertyNames(data);
258
- typeProps = typeProps.filter( prop => dataKeys.includes(prop['slug']));
259
-
260
- for(const prop of typeProps){
261
- const propertyType = prop['propertyType'];
262
- const slug = prop['slug'];
263
- let keyName = slug;
264
- if (propertyType === 'userList' || propertyType === 'object_reference') {
265
- keyName = slug + 'Id';
266
- }
267
- entity[keyName] = await this.getEntityValue(prop, data);
268
- // console.log('entity[slug]: ' + entity[slug]);
269
- }
270
-
271
- return entity;
272
-
273
- }
274
-
275
- /** Gets the entity values from FlexPLM data
276
- * Assumes there isn't a VibeIQ typeId, so uses FlexPLM data to determine type
277
- * @param objectClass FlexPLM object class
278
- * @param data object data
279
- * @param keysToSkip type properties to not process
280
- * @returns object with VibeIQ values
281
- */
282
- async getEntityValues(objectClass: string, data: any, keysToSkip: string[] = []){
283
- const entityValues = {};
284
- const tco: TypeClientOptions = await this.typeUtils.getEntityTypeClientOptionsUsingMapping(this.transformMapFile, this.mapFileUtil, data);
285
- const type = await this.typeUtils.getByRootAndPath(tco);
286
- const typePath = type['typePath'];
287
- if(typePath && (typePath.startsWith('item') || typePath.startsWith('project-item'))){
288
- if(['LCSProduct', 'LCSProductSeasonLink'].includes(objectClass)){
289
- entityValues['roles'] = ['family'];
290
- }else{
291
- entityValues['roles'] = ['color', 'option'];
292
- }
293
- }
294
-
295
- let typeProps = this.typeUtils.filterTypeProperties(type, entityValues);
296
- typeProps = typeProps.filter( prop => !keysToSkip.includes(prop['slug']));
297
-
298
- for(const prop of typeProps){
299
- const slug = prop['slug'];
300
- const value = await this.getEntityValue(prop, data);
301
- if(value){
302
- entityValues[slug] = value;
303
- }
304
- }
305
-
306
- return entityValues;
307
- }
308
-
309
- /** Gets the VibeIQ value for the property from data
310
- *
311
- * @param prop the VibeIQ property
312
- * @param data the FlexPLM data
313
- * @returns value to be set in VibeIQ
314
- */
315
- async getEntityValue(prop, data) {
316
- const propertyType = prop['propertyType'];
317
- const slug = prop['slug'];
318
- const nd = data[slug];
319
- // this.logger.log('getValue: ' + propertyType + ', ' +slug + ', ' + nd);
320
-
321
- let value;
322
- if (['string', 'text'].includes(propertyType)) {
323
- value = nd;
324
- }else if(['number', 'currency', 'percent', 'sequence'].includes(propertyType)){
325
- value = (null === nd)? 0 : nd;
326
- }else if('date' === propertyType){
327
- if(nd){
328
- // const offset = new Date(nd).getTimezoneOffset() * 60 * 1_000;
329
- // this.logger.log('nd: ' + nd);
330
- // this.logger.log(offset);
331
- // this.logger.log('No Offset: ' + new Date(nd).toISOString());
332
- // const d = new Date(nd + offset);// Take system Timezone into account.
333
- const d = new Date(nd);
334
- value = d.toISOString();
335
-
336
- } else {
337
- value = null;
338
- }
339
- }else if('boolean' === propertyType){
340
- value =(nd)? true: false;
341
- }else if('choice' === propertyType){
342
- value = this.setEnumerationKeys(prop, nd, this.useDisplayForEnumerationMatching);
343
- }else if('multi_select' === propertyType){
344
- value = this.setEnumerationKeys(prop, nd, this.useDisplayForEnumerationMatching);
345
-
346
- } else if ('object_reference' === propertyType) {
347
- value = await this.setObjectReferenceValue(prop, nd);
348
- } else if ('image' === propertyType) {
349
- // console.log('TODO-image');
350
- } else if ('formula' === propertyType) {
351
- // console.log('TODO-formula');
352
- } else if ('json' === propertyType) {
353
- // console.log('TODO-json');
354
- } else if ('userList' === propertyType) {
355
- value = await this.setUserListValue(prop, nd);
356
- }
357
-
358
- // console.log(value);
359
- return value;
360
- }
361
-
362
- setEnumerationKeys(prop, nd, matchByDisplay) {
363
- const propertyType = prop['propertyType'];
364
- let value;
365
- if(['choice', 'multi_select'].includes(propertyType)){
366
- //If there are no options, use empty array to not error out and don't set anything
367
- const options: [{value:string, display:string}] = prop['options'] || [];
368
- if('choice' === propertyType){
369
- if(nd){
370
- const matchKey = (matchByDisplay)? 'display' : 'value';
371
- const option = options.find(option => option[matchKey] == nd[matchKey] );
372
- if(option){
373
- value = option.value;
374
- }
375
- } else {
376
- value = undefined;
377
- }
378
- }else if('multi_select' === propertyType){
379
- value = [];
380
- const matchKey = (matchByDisplay)? 'display' : 'value';
381
- if(nd instanceof Array){
382
- nd.forEach(selectedOpt =>{
383
- const optionObject = options.find(option => option[matchKey] == selectedOpt[matchKey]);
384
- if(optionObject){
385
- const option = optionObject.value;
386
- value.push(option);
387
- }
388
- });
389
-
390
- }
391
- }
392
- }
393
- return value;
394
- }
395
-
396
- /** Compares the potential changes to the entity and returns only the actual differences.
397
- *
398
- * @param entity the full entity
399
- * @param changes the potential changes
400
- * @returns only the change values that are different from the entity's value
401
- */
402
- getPersistableChanges(entity: object, changes: object): object {
403
- const entityCompareValues = {};
404
- for(const key of (Object.getOwnPropertyNames(changes))){
405
- entityCompareValues[key] = entity[key];
406
- }
407
- const diffs: ObjectDiff[] = ObjectUtil.compareDeep(entityCompareValues, changes, '');
408
- const diffValues = {};
409
- if(diffs && diffs.length > 0){
410
- for(const diff of diffs){
411
- diffValues[diff.propertyName] = diff.newValue;
412
- }
413
- }
414
- return diffValues;
415
-
416
- }
417
-
418
- /** Sets object reference value from FlexPLM data passed in
419
- *
420
- * @param prop the VibeIQ property
421
- * @param nd the VibeIQ data
422
- * @returns the object reference id from VibeIQ
423
- */
424
- async setObjectReferenceValue(prop, nd) {
425
- let objectReferenceId = "";
426
- if(!nd) {
427
- return objectReferenceId;
428
- }
429
- if(this.transformMapFile){
430
- const mapKey = await TypeConversionUtils.getMapKeyFromObject(this.transformMapFile, this.mapFileUtil, nd, TypeConversionUtils.FLEX2VIBE_DIRECTION);
431
- nd = await MapUtil.applyTransformMap(this.transformMapFile, this.mapFileUtil, nd, mapKey, TypeConversionUtils.FLEX2VIBE_DIRECTION)
432
- }
433
-
434
- const entityType = prop['referencedTypeRootSlug'];
435
- const entityTypePath = prop['referencedTypePath'];
436
- const entityKeys = Object.keys(nd);
437
- const identifierKeys = await TypeConversionUtils.getIdentifierPropertiesFromObject(this.transformMapFile, this.mapFileUtil, nd)
438
- const hasAllIdentifiers = identifierKeys.every(key => entityKeys.includes(key));
439
- if(identifierKeys.length == 0 || !hasAllIdentifiers){
440
- console.warn(`The inbound ${entityType} for prop '${prop['slug']}' doesnt have all "identifier" properties, so there is no processing. Identifier properties: ${identifierKeys}`);
441
- return objectReferenceId;
442
- }
443
-
444
- const rootType = await this.typeUtils.getByRootAndPath({root: entityType});
445
- const rootTypeProps = this.typeUtils.filterTypeProperties(rootType, nd);
446
-
447
- let rootTypeCriteria = {};
448
- let typeCriteria = { };
449
- identifierKeys.forEach(keyName => {
450
- const foundInObjects = rootTypeProps.some(obj => obj.slug === keyName);
451
- if (foundInObjects) {
452
- rootTypeCriteria[keyName] = nd[keyName];
453
- } else {
454
- typeCriteria[keyName] = nd[keyName];
455
- }
456
- });
457
-
458
- const combinedCriteria = { ...rootTypeCriteria, ...typeCriteria, typePath: entityTypePath };
459
- const cacheKey = Object.values(combinedCriteria).join('_');
460
-
461
- if (this.objRefCache[cacheKey]) {
462
- if (Logger.isDebugOn()) {
463
- console.debug(`object reference cache hit: ${cacheKey}`);
464
- }
465
- return this.objRefCache[cacheKey];
466
- }
467
-
468
- let arrObjectReferences = await this.getAllObjectReferences(entityType, rootTypeCriteria);
469
- if(entityType !== entityTypePath) {
470
- arrObjectReferences = this.checkKeysAndValues(typeCriteria, arrObjectReferences, entityTypePath);
471
- }
472
-
473
- if(arrObjectReferences.length) {
474
- if(arrObjectReferences.length === 1) {
475
- objectReferenceId = arrObjectReferences[0].id;
476
- } else {
477
- console.warn(`The passed in object reference criteria has duplicate records found ${JSON.stringify(combinedCriteria)}.`);
478
- }
479
- }
480
-
481
- if (!objectReferenceId) {
482
- console.warn(`The passed in object reference criteria ${JSON.stringify(combinedCriteria)} didn't match any entities.`);
483
- return objectReferenceId;
484
- }
485
-
486
- this.objRefCache[cacheKey] = objectReferenceId;
487
-
488
- return objectReferenceId;
489
- }
490
-
491
- /**
492
- * Retrieves all object references of a specified entity type based on the provided criteria.
493
- * This function handles pagination and asynchronously fetches object references until there are no more pages.
494
- * @param {string} entityType - The type of entity for which object references are to be retrieved.
495
- * @param {object} rootTypeCriteria - The criteria used to filter object references.
496
- * @returns {Promise<Array>} A Promise that resolves to an array containing all object references.
497
- */
498
- async getAllObjectReferences(entityType: string, rootTypeCriteria, postProcessCriteria = null) {
499
- const entities = new Entities();
500
- let loads = [];
501
- let nextPageKey;
502
- let usedV2 = false;
503
- do {
504
- const take = 1000;
505
- const loadPage = await entities.get({
506
- entityName: entityType,
507
- criteria: rootTypeCriteria,
508
- apiVersion: API_VERSION.V2,
509
- take,
510
- nextPageKey,
511
- });
512
- nextPageKey = loadPage?.nextPageKey;
513
- if (Object.keys(loadPage).includes('results')) {
514
- usedV2 = true;
515
- let postProcessedResults = loadPage.results;
516
- if(postProcessedResults?.length > 0 && postProcessCriteria && Object.keys(postProcessCriteria).length !== 0) {
517
- const subEntityTypePath = rootTypeCriteria.typePath || entityType;
518
- postProcessedResults = this.checkKeysAndValues(postProcessCriteria, postProcessedResults, subEntityTypePath);
519
- }
520
- loads.push(...postProcessedResults);
521
- } else {
522
- nextPageKey = null;
523
- }
524
- } while (nextPageKey);
525
-
526
- if (!usedV2) {
527
- const take = 1000;
528
- let skip = 0;
529
- let done = false;
530
- while (!done) {
531
- const loadPage = await entities.get({
532
- entityName: entityType,
533
- criteria: rootTypeCriteria,
534
- take,
535
- skip,
536
- });
537
- let postProcessedResults = loadPage;
538
- if(postProcessCriteria && Object.keys(postProcessCriteria).length !== 0) {
539
- const subEntityTypePath = rootTypeCriteria.typePath || entityType;
540
- postProcessedResults = this.checkKeysAndValues(postProcessCriteria, postProcessedResults, subEntityTypePath);
541
- }
542
- loads.push(...postProcessedResults);
543
-
544
- if (loadPage.length !== take) {
545
- done = true;
546
- } else {
547
- skip += take;
548
- }
549
- }
550
- }
551
-
552
- return loads;
553
- }
554
-
555
- /**
556
- * Checks if all keys and values of a given object are present in an array of objects.
557
- * @param {Object} criteria - The object whose keys and values are to be checked in the array of objects.
558
- * @param {Array<Object>} arrayOfObjects - The array of objects to be searched for matching keys and values.
559
- * @param {string} entityTypePath - The type / subtype for the property; objects must be this type or a sub type of it.
560
- * @returns {(Object|boolean)} Returns the array of objects that matches all keys and values of the provided object.
561
- * If no match is found, returns empty array.
562
- */
563
- checkKeysAndValues(criteria, arrayOfObjects, entityTypePath) {
564
- let arrOfMatchObjects = [];
565
- for (let i = 0; i < arrayOfObjects.length; i++) {
566
- const currentObj = arrayOfObjects[i];
567
- let found = true;
568
-
569
- if(entityTypePath && currentObj['typePath']){
570
- const currentObjPath = '' + currentObj['typePath'];
571
- if(!currentObjPath.startsWith(entityTypePath)){
572
- found = false;
573
- break;
574
- }
575
- }
576
- for (const key in criteria) {
577
- if (!(key in currentObj) || currentObj[key] !== criteria[key]) {
578
- found = false;
579
- break;
580
- }
581
- }
582
-
583
- if (found) {
584
- arrOfMatchObjects.push(currentObj);
585
- }
586
- }
587
-
588
- return arrOfMatchObjects;
589
- }
590
-
591
- /** Sets userListId value from FlexPLM data passed in
592
- *
593
- * @param prop the VibeIQ property
594
- * @param nd the VibeIQ data
595
- * @returns the modified entity with FlexPLM values
596
- */
597
- async setUserListValue(prop, nd) {
598
- if(!nd?.email) {
599
- return "";
600
- }
601
- let cacheUser = DataConverter.getFromStaticCache(nd.email);
602
- if (cacheUser) {
603
- if (Logger.isDebugOn()) {
604
- console.debug('user cache hit: ' + nd.email);
605
- }
606
-
607
- await this.processGroupMemberCheck(prop, nd.email);
608
- return cacheUser;
609
- }
610
-
611
- const user = await this.getUserByEmail(nd);
612
- const userId = (user)?user.id:undefined;
613
-
614
- if(userId) {
615
- DataConverter.setToStaticCache(nd.email, userId);
616
- await this.processGroupMemberCheck(prop, nd.email);
617
- }
618
-
619
- return userId;
620
- }
621
-
622
- /** Makes batch calls of 1000 of user-org entities until
623
- * it find one with userEmail of passed in nd.email.
624
- * Maxes out after querying for 15,000 user-org entities
625
- *
626
- * @param nd
627
- * @returns
628
- */
629
- async getUserByEmail(nd: any) {
630
- let userOrg = undefined;
631
- let count =0;
632
- let size = 0;
633
- let emailInput = nd?.email.toLowerCase();
634
-
635
- const entities = new Entities();
636
- const getOptionsCriteria = {
637
- entityName: 'user-org',
638
- take:1000
639
- }
640
-
641
- do {
642
- const userBatch: [any] = await entities.get(getOptionsCriteria);
643
- userOrg = userBatch.find(uo => uo?.userEmail.toLowerCase() === emailInput);
644
-
645
- }while( !userOrg && size == getOptionsCriteria.take && count < 15);
646
- return userOrg?.user
647
-
648
- }
649
-
650
- /** Shows warning if user email address not present in group associated to property
651
- *
652
- * @param prop the VibeIQ property
653
- * @param userEmail user email address
654
- */
655
- async processGroupMemberCheck(prop, userEmail) {
656
- let arrUserList = [];
657
- if(this.userRefCache[prop.typePropertyUserListId]) {
658
- arrUserList = this.userRefCache[prop.typePropertyUserListId];
659
- } else {
660
- const userListResult = await new Entities().get({
661
- entityName: 'user-list',
662
- id: prop.typePropertyUserListId
663
- });
664
-
665
- if(userListResult && Object.keys(userListResult).length) {
666
- arrUserList = userListResult.userList;
667
- this.userRefCache[prop.typePropertyUserListId] = arrUserList;
668
- }
669
- }
670
-
671
- if(arrUserList.length) {
672
- const result = arrUserList.find(element => element['display'] === userEmail);
673
- if(!result) {
674
- console.warn(`The passed in user ${userEmail} in property slug ${prop.slug} should be a member of the group associated with the property 'typePropertyUserListId'`)
675
- }
676
- }
677
- }
678
-
679
- /** Gets the VibeIQ value for the userList property from data
680
- *
681
- * @param prop the VibeIQ property
682
- * @param newData the FlexPLM data
683
- * @returns value to be set in VibeIQ
684
- */
685
- async getUserListValue(prop, newData) {
686
- const slug = prop['slug'];
687
- if (Logger.isDebugOn()) {
688
- console.debug('getUserListValue-prop: ' + slug);
689
- }
690
-
691
- const entityId = newData[slug + 'Id'];
692
-
693
- if(!entityId) {
694
- return {};
695
- }
696
-
697
- const cacheUser = DataConverter.getFromStaticCache(entityId);
698
- if (cacheUser) {
699
- if (Logger.isDebugOn()) {
700
- console.debug('user cache hit: ' + entityId);
701
- }
702
- return Object.assign({}, cacheUser);
703
- }
704
-
705
- const user = await this.getUserById(entityId);
706
-
707
- const value = (user) ? {
708
- email: user.email,
709
- firstName: user.first,
710
- lastName: user.last,
711
- isSsoUser: user.isSsoUser,
712
- } : undefined;
713
-
714
- if(value) {
715
- DataConverter.setToStaticCache(entityId, value);
716
- }
717
-
718
- return value;
719
- }
720
-
721
- /** Makes batch calls of 1000 of user-org entities until
722
- * it find one with user.id of passed in userId.
723
- * Maxes out after querying for 15,000 user-org entities
724
- *
725
- * @param userId
726
- * @returns
727
- */
728
- async getUserById(userId: any) {
729
- let userOrg = undefined;
730
- let count =0;
731
- let size = 0;
732
- const entities = new Entities();
733
- const getOptionsCriteria = {
734
- entityName: 'user-org',
735
- take:1000
736
- }
737
-
738
- do {
739
- const userBatch: [any] = await entities.get(getOptionsCriteria);
740
- userOrg = userBatch.find(uo => uo?.user?.id === userId);
741
-
742
- }while( !userOrg && size == getOptionsCriteria.take && count < 15);
743
- return userOrg?.user
744
- }
745
-
1
+ import { API_VERSION, Entities, TypeClientOptions } from '@contrail/sdk';
2
+ import { TypeUtils } from './type-utils';
3
+ import { FCConfig } from '../interfaces/interfaces';
4
+ import { MapFileUtil } from '@contrail/transform-data';
5
+ import { Logger } from '@contrail/app-framework';
6
+ import { ObjectDiff, ObjectUtil } from '@contrail/util';
7
+ import { TypeConversionUtils } from './type-conversion-utils';
8
+ import { MapUtil } from './map-utils';
9
+
10
+ export class DataConverter {
11
+ private typeUtils: TypeUtils;
12
+ private transformMapFile: string;
13
+ private useDisplayForEnumerationMatching = false;
14
+ private verboseDebug = false;
15
+ private objRefCache = {};
16
+ private userRefCache = {};
17
+ static staticUserCache = {};
18
+ static clearStaticUserCache() {
19
+ DataConverter.staticUserCache = {};
20
+ }
21
+ static getFromStaticCache(id: string) {
22
+ return DataConverter.staticUserCache[id];
23
+ }
24
+ static setToStaticCache(id: string, value: any) {
25
+ DataConverter.staticUserCache[id] = value;
26
+ }
27
+
28
+ constructor(private config: FCConfig, private mapFileUtil: MapFileUtil){
29
+ this.typeUtils = new TypeUtils();
30
+ this.transformMapFile = this.config['transformMapFile'];
31
+ this.useDisplayForEnumerationMatching = this.config['dataConverter']
32
+ && this.config['dataConverter']['useDisplayForEnumerationMatching'];
33
+ this.verboseDebug = this.config['dataConverter']
34
+ && this.config['dataConverter']['verboseDebug'];
35
+ }
36
+
37
+ public setVerboseDebug(val: boolean = false) {
38
+ this.verboseDebug= val;
39
+ }
40
+ public isVerboseDebugOn(): boolean {
41
+ return this.verboseDebug && Logger.isDebugOn();
42
+ }
43
+
44
+ async getFlexPLMObjectDataFromEvent(event, dataToSkip: string[]) {
45
+ return this.getFlexPLMObjectData(event.newData, dataToSkip, true);
46
+ }
47
+
48
+ async getFlexPLMObjectData(newData, dataToSkip: string[], inflateObjRef: boolean) {
49
+ if(this.isVerboseDebugOn()) {
50
+ console.debug('newData: ' + JSON.stringify(newData));
51
+ }
52
+ //Using event to get propertyDiffs to find emptied values
53
+ //Add standard atts to skip
54
+ dataToSkip = dataToSkip.concat(['updatedOn', 'updatedById', 'createdOn', 'createdById', 'modifiedAt', 'orgId', 'createdAt', 'id', 'typeId', 'workspaceId']);
55
+ // const oldData = event.oldData;
56
+ const data = {};
57
+ const typeId = newData?.typeId;
58
+ if (!typeId) {
59
+ return;// Don't have a type; so can't process
60
+ }
61
+ data['typePath'] = newData['typePath'];
62
+ const type = await this.typeUtils.getTypeById(typeId);
63
+ const typeProps = this.typeUtils.filterTypeProperties(type, newData);
64
+ for(const prop of typeProps){
65
+ // if(this.logger.isTraceOn()){
66
+ // this.logger.log('prop: ' + JSON.stringify(prop));
67
+ // }
68
+ const slug = prop['slug'];
69
+ if (dataToSkip.includes(slug)) {
70
+ continue;
71
+ }
72
+ data[slug] = await this.getFlexPLMValue(prop, newData, inflateObjRef);
73
+ }
74
+
75
+ if(this.isVerboseDebugOn()) {
76
+ console.debug('getFlexPLMObjectData-data: ' + JSON.stringify(data));
77
+ }
78
+ return data;
79
+
80
+ }
81
+
82
+ async getFlexPLMValue(prop, newData, inflateObjRef: boolean) {
83
+ const propertyType = prop['propertyType'];
84
+ const slug = prop['slug'];
85
+ const nd = newData[slug];
86
+ // console.log('getFlexPLMValue: ' + propertyType + ', ' +slug + ', ' + nd + ', ' + od);
87
+ let value;
88
+ if(['string', 'text'].includes(propertyType)){
89
+ value = nd || '';
90
+ }else if(['number', 'currency', 'percent', 'sequence'].includes(propertyType)){
91
+ value = nd || 0;
92
+ }else if('date' === propertyType){
93
+ if(nd){
94
+ value = nd;
95
+ // const d = new Date(nd);
96
+ // console.log('Date.getTimezoneOffset(): ' + d.getTimezoneOffset());
97
+ // value = d.toISOString();
98
+ // console.log('date: ' + nd + ' -- ' + value);
99
+ } else {
100
+ value = null;
101
+ }
102
+ }else if('boolean' === propertyType){
103
+ value =(nd)? true: false;
104
+ }else if('choice' === propertyType){
105
+ value = this.getEnumerationValue(prop, nd);
106
+ }else if('multi_select' === propertyType){
107
+ value = this.getEnumerationValue(prop, nd);
108
+ }else if('object_reference' === propertyType){
109
+ value = await this.getObjectReferenceValue(prop, newData, inflateObjRef);
110
+ if(this.isVerboseDebugOn()){
111
+ console.debug('object_reference: ' + JSON.stringify(value));
112
+ }
113
+ } else if ('image' === propertyType) {
114
+ // console.log('image-TODO');
115
+ }else if('formula' === propertyType){
116
+ value = nd;
117
+ }else if('json' === propertyType){
118
+ // console.log('json-TODO');
119
+ value = nd;
120
+ } else if ('userList' === propertyType) {
121
+ value = await this.getUserListValue(prop, newData);
122
+ }
123
+
124
+ return value;
125
+
126
+ }
127
+ /** Returns the display values for list properties (choice & multi_select)
128
+ *
129
+ * @param prop
130
+ * @param newData
131
+ * @returns
132
+ */
133
+ getEnumerationValue(prop, nd){
134
+ const propertyType = prop['propertyType'];
135
+ let value;
136
+ if(['choice', 'multi_select'].includes(propertyType)){
137
+ const options: [{value:string, display:string}] = prop['options'] || [];
138
+ if('choice' === propertyType){
139
+ if(nd){
140
+ const option = options.find(option => option.value == nd );
141
+ if(option){
142
+ value = Object.assign({},option);
143
+ }
144
+ }
145
+ if(!value) {
146
+ value = {};
147
+ }
148
+ } else if ('multi_select' === propertyType) {
149
+ value = [];
150
+ if (nd instanceof Array) {
151
+ nd.forEach(key => {
152
+ const optionObject = options.find(option => option.value == key);
153
+ if(optionObject){
154
+ const option = Object.assign({},optionObject);
155
+ value.push(option);
156
+ }
157
+ });
158
+ } else if( typeof nd === 'string' && '' !== nd) {
159
+ const optionObject = options.find(option => option.value == nd);
160
+ if(optionObject){
161
+ const option = Object.assign({},optionObject);
162
+ value.push(option);
163
+ }
164
+ }
165
+ }
166
+ }
167
+ return value;
168
+ }
169
+
170
+ public async getObjectReferenceValue(prop: any, newData: any, inflateObjRef = false) {
171
+ const slug = prop['slug'];
172
+ if(Logger.isDebugOn()) {
173
+ console.debug('getObjectReferenceValue-prop: ' + slug);
174
+ }
175
+ let value = newData[slug];
176
+ const entityType = prop['referencedTypeRootSlug'];
177
+ const entityId = newData[slug + 'Id'];
178
+ if ((!value || typeof value === 'string' )&& inflateObjRef) {
179
+ if (entityId) {
180
+ if(this.objRefCache[entityId]){
181
+ if(Logger.isDebugOn()) {
182
+ console.debug('cache hit: ' + entityId);
183
+ }
184
+ return this.objRefCache[entityId];
185
+ }
186
+ const criteria = {
187
+ id: entityId
188
+ };
189
+ const entities = await new Entities().get({
190
+ entityName: entityType,
191
+ criteria
192
+ });
193
+ value = (entities && entities[0]) ? entities[0] : undefined;
194
+ }
195
+ }
196
+
197
+ if(value && !(typeof value === 'string')){
198
+ const unprocessedValue = value;
199
+ const objectClass = await TypeConversionUtils.getObjectClass(this.transformMapFile, this.mapFileUtil, unprocessedValue);
200
+ const flexPLMTypePath = await TypeConversionUtils.getObjectTypePath(this.transformMapFile, this.mapFileUtil, unprocessedValue);
201
+ value = await this.getFlexPLMObjectData(value, [], false);
202
+ value['entityReference'] = entityType + ':' + entityId;
203
+ value['objectClass'] = objectClass;
204
+ value['flexPLMTypePath'] = flexPLMTypePath;
205
+
206
+ if(this.transformMapFile){
207
+ const mapKey = await TypeConversionUtils.getMapKey(this.transformMapFile, this.mapFileUtil, unprocessedValue, TypeConversionUtils.VIBE2FLEX_DIRECTION);
208
+ value = await MapUtil.applyTransformMap(this.transformMapFile, this.mapFileUtil, value, mapKey, TypeConversionUtils.VIBE2FLEX_DIRECTION)
209
+ }
210
+
211
+ } else {
212
+ value = {};
213
+ }
214
+ this.objRefCache[entityId] = value;
215
+ return value;
216
+ }
217
+
218
+ /** (Deprecated) Use TypeConversionUtils.getMapKey()
219
+ * Will return the class to use to get mapping.
220
+ * This is needed because mappings will be different for different sub types
221
+ * of custom-entity
222
+ *
223
+ * @param obj: Entity being checked
224
+ * @param mapping: The whole mapping file
225
+ */
226
+ getMappingClass(entity: object, mapping: any): string{
227
+ const entityTypePath = entity['typePath'];
228
+ const typeMapKey = mapping['typeMapKey'];
229
+ let objClass = typeMapKey[entityTypePath];
230
+ const entityType = entity['entityType'];
231
+
232
+ if(!objClass){
233
+ objClass = this.typeUtils.getEventObjectClass(entityType, entity);
234
+ }
235
+ if(!objClass){
236
+ objClass = entityType;
237
+ }
238
+ return objClass;
239
+ }
240
+
241
+ /** Sets entity values from FlexPLM data passed in
242
+ * Assumes the entity has a VibeIQ typeId and 'roles' value to filter if necessary.
243
+ * @param entity the entity to update
244
+ * @param data the FlexPLM data
245
+ * @param keysToSkip properties to skip
246
+ * @returns the modified entity with VibeIQ values
247
+ */
248
+ async setEntityValues(entity, data, keysToSkip: string[] = []){
249
+ // this.logger.log('setEntityValues: ' + JSON.stringify(entity));
250
+ // this.logger.log('data: ' + JSON.stringify(data));
251
+ const type = await this.typeUtils.getTypeById(entity.typeId);
252
+ keysToSkip = keysToSkip.concat(['updatedOn', 'updatedById', 'createdOn', 'createdById', 'modifiedAt', 'orgId', 'createdAt', 'id', 'typeId', 'typePath', 'workspaceId']);
253
+ let typeProps = this.typeUtils.filterTypeProperties(type, entity);
254
+ typeProps = typeProps.filter( prop => !keysToSkip.includes(prop['slug']));
255
+ //Only process properties that had a value sent; to not accidentally clear out values
256
+ //for properties that weren't sent.
257
+ const dataKeys = Object.getOwnPropertyNames(data);
258
+ typeProps = typeProps.filter( prop => dataKeys.includes(prop['slug']));
259
+
260
+ for(const prop of typeProps){
261
+ const propertyType = prop['propertyType'];
262
+ const slug = prop['slug'];
263
+ let keyName = slug;
264
+ if (propertyType === 'userList' || propertyType === 'object_reference') {
265
+ keyName = slug + 'Id';
266
+ }
267
+ entity[keyName] = await this.getEntityValue(prop, data);
268
+ // console.log('entity[slug]: ' + entity[slug]);
269
+ }
270
+
271
+ return entity;
272
+
273
+ }
274
+
275
+ /** Gets the entity values from FlexPLM data
276
+ * Assumes there isn't a VibeIQ typeId, so uses FlexPLM data to determine type
277
+ * @param objectClass FlexPLM object class
278
+ * @param data object data
279
+ * @param keysToSkip type properties to not process
280
+ * @returns object with VibeIQ values
281
+ */
282
+ async getEntityValues(objectClass: string, data: any, keysToSkip: string[] = []){
283
+ const entityValues = {};
284
+ const tco: TypeClientOptions = await this.typeUtils.getEntityTypeClientOptionsUsingMapping(this.transformMapFile, this.mapFileUtil, data);
285
+ const type = await this.typeUtils.getByRootAndPath(tco);
286
+ const typePath = type['typePath'];
287
+ if(typePath && (typePath.startsWith('item') || typePath.startsWith('project-item'))){
288
+ if(['LCSProduct', 'LCSProductSeasonLink'].includes(objectClass)){
289
+ entityValues['roles'] = ['family'];
290
+ }else{
291
+ entityValues['roles'] = ['color', 'option'];
292
+ }
293
+ }
294
+
295
+ let typeProps = this.typeUtils.filterTypeProperties(type, entityValues);
296
+ typeProps = typeProps.filter( prop => !keysToSkip.includes(prop['slug']));
297
+
298
+ for(const prop of typeProps){
299
+ const slug = prop['slug'];
300
+ const value = await this.getEntityValue(prop, data);
301
+ if(value){
302
+ entityValues[slug] = value;
303
+ }
304
+ }
305
+
306
+ return entityValues;
307
+ }
308
+
309
+ /** Gets the VibeIQ value for the property from data
310
+ *
311
+ * @param prop the VibeIQ property
312
+ * @param data the FlexPLM data
313
+ * @returns value to be set in VibeIQ
314
+ */
315
+ async getEntityValue(prop, data) {
316
+ const propertyType = prop['propertyType'];
317
+ const slug = prop['slug'];
318
+ const nd = data[slug];
319
+ // this.logger.log('getValue: ' + propertyType + ', ' +slug + ', ' + nd);
320
+
321
+ let value;
322
+ if (['string', 'text'].includes(propertyType)) {
323
+ value = nd;
324
+ }else if(['number', 'currency', 'percent', 'sequence'].includes(propertyType)){
325
+ value = (null === nd)? 0 : nd;
326
+ }else if('date' === propertyType){
327
+ if(nd){
328
+ // const offset = new Date(nd).getTimezoneOffset() * 60 * 1_000;
329
+ // this.logger.log('nd: ' + nd);
330
+ // this.logger.log(offset);
331
+ // this.logger.log('No Offset: ' + new Date(nd).toISOString());
332
+ // const d = new Date(nd + offset);// Take system Timezone into account.
333
+ const d = new Date(nd);
334
+ value = d.toISOString();
335
+
336
+ } else {
337
+ value = null;
338
+ }
339
+ }else if('boolean' === propertyType){
340
+ value =(nd)? true: false;
341
+ }else if('choice' === propertyType){
342
+ value = this.setEnumerationKeys(prop, nd, this.useDisplayForEnumerationMatching);
343
+ }else if('multi_select' === propertyType){
344
+ value = this.setEnumerationKeys(prop, nd, this.useDisplayForEnumerationMatching);
345
+
346
+ } else if ('object_reference' === propertyType) {
347
+ value = await this.setObjectReferenceValue(prop, nd);
348
+ } else if ('image' === propertyType) {
349
+ // console.log('TODO-image');
350
+ } else if ('formula' === propertyType) {
351
+ // console.log('TODO-formula');
352
+ } else if ('json' === propertyType) {
353
+ // console.log('TODO-json');
354
+ } else if ('userList' === propertyType) {
355
+ value = await this.setUserListValue(prop, nd);
356
+ }
357
+
358
+ // console.log(value);
359
+ return value;
360
+ }
361
+
362
+ setEnumerationKeys(prop, nd, matchByDisplay) {
363
+ const propertyType = prop['propertyType'];
364
+ let value;
365
+ if(['choice', 'multi_select'].includes(propertyType)){
366
+ //If there are no options, use empty array to not error out and don't set anything
367
+ const options: [{value:string, display:string}] = prop['options'] || [];
368
+ if('choice' === propertyType){
369
+ if(nd){
370
+ const matchKey = (matchByDisplay)? 'display' : 'value';
371
+ const option = options.find(option => option[matchKey] == nd[matchKey] );
372
+ if(option){
373
+ value = option.value;
374
+ }
375
+ } else {
376
+ value = undefined;
377
+ }
378
+ }else if('multi_select' === propertyType){
379
+ value = [];
380
+ const matchKey = (matchByDisplay)? 'display' : 'value';
381
+ if(nd instanceof Array){
382
+ nd.forEach(selectedOpt =>{
383
+ const optionObject = options.find(option => option[matchKey] == selectedOpt[matchKey]);
384
+ if(optionObject){
385
+ const option = optionObject.value;
386
+ value.push(option);
387
+ }
388
+ });
389
+
390
+ }
391
+ }
392
+ }
393
+ return value;
394
+ }
395
+
396
+ /** Compares the potential changes to the entity and returns only the actual differences.
397
+ *
398
+ * @param entity the full entity
399
+ * @param changes the potential changes
400
+ * @returns only the change values that are different from the entity's value
401
+ */
402
+ getPersistableChanges(entity: object, changes: object): object {
403
+ const entityCompareValues = {};
404
+ for(const key of (Object.getOwnPropertyNames(changes))){
405
+ entityCompareValues[key] = entity[key];
406
+ }
407
+ const diffs: ObjectDiff[] = ObjectUtil.compareDeep(entityCompareValues, changes, '');
408
+ const diffValues = {};
409
+ if(diffs && diffs.length > 0){
410
+ for(const diff of diffs){
411
+ diffValues[diff.propertyName] = diff.newValue;
412
+ }
413
+ }
414
+ return diffValues;
415
+
416
+ }
417
+
418
+ /** Sets object reference value from FlexPLM data passed in
419
+ *
420
+ * @param prop the VibeIQ property
421
+ * @param nd the VibeIQ data
422
+ * @returns the object reference id from VibeIQ
423
+ */
424
+ async setObjectReferenceValue(prop, nd) {
425
+ let objectReferenceId = "";
426
+ if(!nd) {
427
+ return objectReferenceId;
428
+ }
429
+ if(this.transformMapFile){
430
+ const mapKey = await TypeConversionUtils.getMapKeyFromObject(this.transformMapFile, this.mapFileUtil, nd, TypeConversionUtils.FLEX2VIBE_DIRECTION);
431
+ nd = await MapUtil.applyTransformMap(this.transformMapFile, this.mapFileUtil, nd, mapKey, TypeConversionUtils.FLEX2VIBE_DIRECTION)
432
+ }
433
+
434
+ const entityType = prop['referencedTypeRootSlug'];
435
+ const entityTypePath = prop['referencedTypePath'];
436
+ const entityKeys = Object.keys(nd);
437
+ const identifierKeys = await TypeConversionUtils.getIdentifierPropertiesFromObject(this.transformMapFile, this.mapFileUtil, nd)
438
+ const hasAllIdentifiers = identifierKeys.every(key => entityKeys.includes(key));
439
+ if(identifierKeys.length == 0 || !hasAllIdentifiers){
440
+ console.warn(`The inbound ${entityType} for prop '${prop['slug']}' doesnt have all "identifier" properties, so there is no processing. Identifier properties: ${identifierKeys}`);
441
+ return objectReferenceId;
442
+ }
443
+
444
+ const rootType = await this.typeUtils.getByRootAndPath({root: entityType});
445
+ const rootTypeProps = this.typeUtils.filterTypeProperties(rootType, nd);
446
+
447
+ let rootTypeCriteria = {};
448
+ let typeCriteria = { };
449
+ identifierKeys.forEach(keyName => {
450
+ const foundInObjects = rootTypeProps.some(obj => obj.slug === keyName);
451
+ if (foundInObjects) {
452
+ rootTypeCriteria[keyName] = nd[keyName];
453
+ } else {
454
+ typeCriteria[keyName] = nd[keyName];
455
+ }
456
+ });
457
+
458
+ const combinedCriteria = { ...rootTypeCriteria, ...typeCriteria, typePath: entityTypePath };
459
+ const cacheKey = Object.values(combinedCriteria).join('_');
460
+
461
+ if (this.objRefCache[cacheKey]) {
462
+ if (Logger.isDebugOn()) {
463
+ console.debug(`object reference cache hit: ${cacheKey}`);
464
+ }
465
+ return this.objRefCache[cacheKey];
466
+ }
467
+
468
+ let arrObjectReferences = await this.getAllObjectReferences(entityType, rootTypeCriteria);
469
+ if(entityType !== entityTypePath) {
470
+ arrObjectReferences = this.checkKeysAndValues(typeCriteria, arrObjectReferences, entityTypePath);
471
+ }
472
+
473
+ if(arrObjectReferences.length) {
474
+ if(arrObjectReferences.length === 1) {
475
+ objectReferenceId = arrObjectReferences[0].id;
476
+ } else {
477
+ console.warn(`The passed in object reference criteria has duplicate records found ${JSON.stringify(combinedCriteria)}.`);
478
+ }
479
+ }
480
+
481
+ if (!objectReferenceId) {
482
+ console.warn(`The passed in object reference criteria ${JSON.stringify(combinedCriteria)} didn't match any entities.`);
483
+ return objectReferenceId;
484
+ }
485
+
486
+ this.objRefCache[cacheKey] = objectReferenceId;
487
+
488
+ return objectReferenceId;
489
+ }
490
+
491
+ /**
492
+ * Retrieves all object references of a specified entity type based on the provided criteria.
493
+ * This function handles pagination and asynchronously fetches object references until there are no more pages.
494
+ * @param {string} entityType - The type of entity for which object references are to be retrieved.
495
+ * @param {object} rootTypeCriteria - The criteria used to filter object references.
496
+ * @returns {Promise<Array>} A Promise that resolves to an array containing all object references.
497
+ */
498
+ async getAllObjectReferences(entityType: string, rootTypeCriteria, postProcessCriteria = null) {
499
+ const entities = new Entities();
500
+ let loads = [];
501
+ let nextPageKey;
502
+ let usedV2 = false;
503
+ do {
504
+ const take = 1000;
505
+ const loadPage = await entities.get({
506
+ entityName: entityType,
507
+ criteria: rootTypeCriteria,
508
+ apiVersion: API_VERSION.V2,
509
+ take,
510
+ nextPageKey,
511
+ });
512
+ nextPageKey = loadPage?.nextPageKey;
513
+ if (Object.keys(loadPage).includes('results')) {
514
+ usedV2 = true;
515
+ let postProcessedResults = loadPage.results;
516
+ if(postProcessedResults?.length > 0 && postProcessCriteria && Object.keys(postProcessCriteria).length !== 0) {
517
+ const subEntityTypePath = rootTypeCriteria.typePath || entityType;
518
+ postProcessedResults = this.checkKeysAndValues(postProcessCriteria, postProcessedResults, subEntityTypePath);
519
+ }
520
+ loads.push(...postProcessedResults);
521
+ } else {
522
+ nextPageKey = null;
523
+ }
524
+ } while (nextPageKey);
525
+
526
+ if (!usedV2) {
527
+ const take = 1000;
528
+ let skip = 0;
529
+ let done = false;
530
+ while (!done) {
531
+ const loadPage = await entities.get({
532
+ entityName: entityType,
533
+ criteria: rootTypeCriteria,
534
+ take,
535
+ skip,
536
+ });
537
+ let postProcessedResults = loadPage;
538
+ if(postProcessCriteria && Object.keys(postProcessCriteria).length !== 0) {
539
+ const subEntityTypePath = rootTypeCriteria.typePath || entityType;
540
+ postProcessedResults = this.checkKeysAndValues(postProcessCriteria, postProcessedResults, subEntityTypePath);
541
+ }
542
+ loads.push(...postProcessedResults);
543
+
544
+ if (loadPage.length !== take) {
545
+ done = true;
546
+ } else {
547
+ skip += take;
548
+ }
549
+ }
550
+ }
551
+
552
+ return loads;
553
+ }
554
+
555
+ /**
556
+ * Checks if all keys and values of a given object are present in an array of objects.
557
+ * @param {Object} criteria - The object whose keys and values are to be checked in the array of objects.
558
+ * @param {Array<Object>} arrayOfObjects - The array of objects to be searched for matching keys and values.
559
+ * @param {string} entityTypePath - The type / subtype for the property; objects must be this type or a sub type of it.
560
+ * @returns {(Object|boolean)} Returns the array of objects that matches all keys and values of the provided object.
561
+ * If no match is found, returns empty array.
562
+ */
563
+ checkKeysAndValues(criteria, arrayOfObjects, entityTypePath) {
564
+ let arrOfMatchObjects = [];
565
+ for (let i = 0; i < arrayOfObjects.length; i++) {
566
+ const currentObj = arrayOfObjects[i];
567
+ let found = true;
568
+
569
+ if(entityTypePath && currentObj['typePath']){
570
+ const currentObjPath = '' + currentObj['typePath'];
571
+ if(!currentObjPath.startsWith(entityTypePath)){
572
+ found = false;
573
+ }
574
+ }
575
+ for (const key in criteria) {
576
+ if (!(key in currentObj) || currentObj[key] !== criteria[key]) {
577
+ found = false;
578
+ break;
579
+ }
580
+ }
581
+
582
+ if (found) {
583
+ arrOfMatchObjects.push(currentObj);
584
+ }
585
+ }
586
+
587
+ return arrOfMatchObjects;
588
+ }
589
+
590
+ /** Sets userListId value from FlexPLM data passed in
591
+ *
592
+ * @param prop the VibeIQ property
593
+ * @param nd the VibeIQ data
594
+ * @returns the modified entity with FlexPLM values
595
+ */
596
+ async setUserListValue(prop, nd) {
597
+ if(!nd?.email) {
598
+ return "";
599
+ }
600
+ let cacheUser = DataConverter.getFromStaticCache(nd.email);
601
+ if (cacheUser) {
602
+ if (Logger.isDebugOn()) {
603
+ console.debug('user cache hit: ' + nd.email);
604
+ }
605
+
606
+ await this.processGroupMemberCheck(prop, nd.email);
607
+ return cacheUser;
608
+ }
609
+
610
+ const user = await this.getUserByEmail(nd);
611
+ const userId = (user)?user.id:undefined;
612
+
613
+ if(userId) {
614
+ DataConverter.setToStaticCache(nd.email, userId);
615
+ await this.processGroupMemberCheck(prop, nd.email);
616
+ }
617
+
618
+ return userId;
619
+ }
620
+
621
+ /** Makes batch calls of 1000 of user-org entities until
622
+ * it find one with userEmail of passed in nd.email.
623
+ * Maxes out after querying for 15,000 user-org entities
624
+ *
625
+ * @param nd
626
+ * @returns
627
+ */
628
+ async getUserByEmail(nd: any) {
629
+ let userOrg = undefined;
630
+ let count =0;
631
+ let size = 0;
632
+ let emailInput = nd?.email.toLowerCase();
633
+
634
+ const entities = new Entities();
635
+ const getOptionsCriteria = {
636
+ entityName: 'user-org',
637
+ take:1000
638
+ }
639
+
640
+ do {
641
+ const userBatch: [any] = await entities.get(getOptionsCriteria);
642
+ userOrg = userBatch.find(uo => uo?.userEmail.toLowerCase() === emailInput);
643
+
644
+ }while( !userOrg && size == getOptionsCriteria.take && count < 15);
645
+ return userOrg?.user
646
+
647
+ }
648
+
649
+ /** Shows warning if user email address not present in group associated to property
650
+ *
651
+ * @param prop the VibeIQ property
652
+ * @param userEmail user email address
653
+ */
654
+ async processGroupMemberCheck(prop, userEmail) {
655
+ let arrUserList = [];
656
+ if(this.userRefCache[prop.typePropertyUserListId]) {
657
+ arrUserList = this.userRefCache[prop.typePropertyUserListId];
658
+ } else {
659
+ const userListResult = await new Entities().get({
660
+ entityName: 'user-list',
661
+ id: prop.typePropertyUserListId
662
+ });
663
+
664
+ if(userListResult && Object.keys(userListResult).length) {
665
+ arrUserList = userListResult.userList;
666
+ this.userRefCache[prop.typePropertyUserListId] = arrUserList;
667
+ }
668
+ }
669
+
670
+ if(arrUserList.length) {
671
+ const result = arrUserList.find(element => element['display'] === userEmail);
672
+ if(!result) {
673
+ console.warn(`The passed in user ${userEmail} in property slug ${prop.slug} should be a member of the group associated with the property 'typePropertyUserListId'`)
674
+ }
675
+ }
676
+ }
677
+
678
+ /** Gets the VibeIQ value for the userList property from data
679
+ *
680
+ * @param prop the VibeIQ property
681
+ * @param newData the FlexPLM data
682
+ * @returns value to be set in VibeIQ
683
+ */
684
+ async getUserListValue(prop, newData) {
685
+ const slug = prop['slug'];
686
+ if (Logger.isDebugOn()) {
687
+ console.debug('getUserListValue-prop: ' + slug);
688
+ }
689
+
690
+ const entityId = newData[slug + 'Id'];
691
+
692
+ if(!entityId) {
693
+ return {};
694
+ }
695
+
696
+ const cacheUser = DataConverter.getFromStaticCache(entityId);
697
+ if (cacheUser) {
698
+ if (Logger.isDebugOn()) {
699
+ console.debug('user cache hit: ' + entityId);
700
+ }
701
+ return Object.assign({}, cacheUser);
702
+ }
703
+
704
+ const user = await this.getUserById(entityId);
705
+
706
+ const value = (user) ? {
707
+ email: user.email,
708
+ firstName: user.first,
709
+ lastName: user.last,
710
+ isSsoUser: user.isSsoUser,
711
+ } : undefined;
712
+
713
+ if(value) {
714
+ DataConverter.setToStaticCache(entityId, value);
715
+ }
716
+
717
+ return value;
718
+ }
719
+
720
+ /** Makes batch calls of 1000 of user-org entities until
721
+ * it find one with user.id of passed in userId.
722
+ * Maxes out after querying for 15,000 user-org entities
723
+ *
724
+ * @param userId
725
+ * @returns
726
+ */
727
+ async getUserById(userId: any) {
728
+ let userOrg = undefined;
729
+ let count =0;
730
+ let size = 0;
731
+ const entities = new Entities();
732
+ const getOptionsCriteria = {
733
+ entityName: 'user-org',
734
+ take:1000
735
+ }
736
+
737
+ do {
738
+ const userBatch: [any] = await entities.get(getOptionsCriteria);
739
+ userOrg = userBatch.find(uo => uo?.user?.id === userId);
740
+
741
+ }while( !userOrg && size == getOptionsCriteria.take && count < 15);
742
+ return userOrg?.user
743
+ }
744
+
746
745
  }