@contrail/flexplm 1.1.22 → 1.1.23

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.
@@ -20,6 +20,9 @@ export declare class DataConverter {
20
20
  getEntityValue(prop: any, data: any): Promise<any>;
21
21
  setEnumerationKeys(prop: any, nd: any, matchByDisplay: any): any;
22
22
  getPersistableChanges(entity: object, changes: object): object;
23
+ setObjectReferenceValue(prop: any, nd: any): Promise<any>;
24
+ getAllObjectReferences(entityType: any, rootTypeCriteria: any): Promise<any[]>;
25
+ checkKeysAndValues(criteria: any, arrayOfObjects: any, entityTypePath: any): any[];
23
26
  setUserListValue(prop: any, nd: any): Promise<any>;
24
27
  getUserByEmail(nd: any): Promise<any>;
25
28
  processGroupMemberCheck(prop: any, userEmail: any): Promise<void>;
@@ -201,7 +201,7 @@ class DataConverter {
201
201
  const propertyType = prop['propertyType'];
202
202
  const slug = prop['slug'];
203
203
  let keyName = slug;
204
- if (propertyType === 'userList') {
204
+ if (propertyType === 'userList' || propertyType === 'object_reference') {
205
205
  keyName = slug + 'Id';
206
206
  }
207
207
  entity[keyName] = await this.getEntityValue(prop, data);
@@ -262,6 +262,7 @@ class DataConverter {
262
262
  value = this.setEnumerationKeys(prop, nd, this.useDisplayForEnumerationMatching);
263
263
  }
264
264
  else if ('object_reference' === propertyType) {
265
+ value = await this.setObjectReferenceValue(prop, nd);
265
266
  }
266
267
  else if ('image' === propertyType) {
267
268
  }
@@ -321,6 +322,94 @@ class DataConverter {
321
322
  }
322
323
  return diffValues;
323
324
  }
325
+ async setObjectReferenceValue(prop, nd) {
326
+ let objectReferenceId = "";
327
+ if (!nd) {
328
+ return objectReferenceId;
329
+ }
330
+ const entityType = prop['referencedTypeRootSlug'];
331
+ const entityTypePath = prop['referencedTypePath'];
332
+ const entityKeys = Object.keys(nd);
333
+ const identifierKeys = await type_conversion_utils_1.TypeConversionUtils.getIdentifierPropertiesFromObject(this.transformMapFile, this.mapFileUtil, nd);
334
+ const hasAllIdentifiers = identifierKeys.every(key => entityKeys.includes(key));
335
+ if (identifierKeys.length == 0 || !hasAllIdentifiers) {
336
+ console.warn(`The inbound ${entityType} doesnt have all "identifier properties, so there is no processing. Identifier properties: ${identifierKeys}`);
337
+ return objectReferenceId;
338
+ }
339
+ const rootType = await this.typeUtils.getByRootAndPath({ root: entityType });
340
+ const rootTypeProps = this.typeUtils.filterTypeProperties(rootType, nd);
341
+ let rootTypeCriteria = {};
342
+ let typeCriteria = {};
343
+ identifierKeys.forEach(keyName => {
344
+ const foundInObjects = rootTypeProps.some(obj => obj.slug === keyName);
345
+ if (foundInObjects) {
346
+ rootTypeCriteria[keyName] = nd[keyName];
347
+ }
348
+ else {
349
+ typeCriteria[keyName] = nd[keyName];
350
+ }
351
+ });
352
+ const combinedCriteria = { ...rootTypeCriteria, ...typeCriteria, typePath: entityTypePath };
353
+ const cacheKey = Object.values(combinedCriteria).join('_');
354
+ if (this.objRefCache[cacheKey]) {
355
+ if (app_framework_1.Logger.isDebugOn()) {
356
+ console.debug(`object reference cache hit: ${cacheKey}`);
357
+ }
358
+ return this.objRefCache[cacheKey];
359
+ }
360
+ let arrObjectReferences = await this.getAllObjectReferences(entityType, rootTypeCriteria);
361
+ if (entityType !== entityTypePath) {
362
+ arrObjectReferences = this.checkKeysAndValues(typeCriteria, arrObjectReferences, entityTypePath);
363
+ }
364
+ if (arrObjectReferences.length) {
365
+ if (arrObjectReferences.length === 1) {
366
+ objectReferenceId = arrObjectReferences[0].id;
367
+ }
368
+ else {
369
+ console.warn(`The passed in object reference criteria has duplicate records found ${JSON.stringify(combinedCriteria)}.`);
370
+ }
371
+ }
372
+ if (!objectReferenceId) {
373
+ console.warn(`The passed in object reference criteria ${JSON.stringify(combinedCriteria)} not found.`);
374
+ return objectReferenceId;
375
+ }
376
+ this.objRefCache[cacheKey] = objectReferenceId;
377
+ return objectReferenceId;
378
+ }
379
+ async getAllObjectReferences(entityType, rootTypeCriteria) {
380
+ let loads = [];
381
+ let nextPageKey;
382
+ do {
383
+ const loadPage = await new sdk_1.Entities().get({ entityName: entityType, criteria: rootTypeCriteria, apiVersion: sdk_1.API_VERSION.V2, nextPageKey });
384
+ nextPageKey = loadPage.nextPageKey;
385
+ loads.push(...loadPage.results);
386
+ } while (nextPageKey);
387
+ return loads;
388
+ }
389
+ checkKeysAndValues(criteria, arrayOfObjects, entityTypePath) {
390
+ let arrOfMatchObjects = [];
391
+ for (let i = 0; i < arrayOfObjects.length; i++) {
392
+ const currentObj = arrayOfObjects[i];
393
+ let found = true;
394
+ if (entityTypePath && currentObj['typePath']) {
395
+ const currentObjPath = '' + currentObj['typePath'];
396
+ if (!currentObjPath.startsWith(entityTypePath)) {
397
+ found = false;
398
+ break;
399
+ }
400
+ }
401
+ for (const key in criteria) {
402
+ if (!(key in currentObj) || currentObj[key] !== criteria[key]) {
403
+ found = false;
404
+ break;
405
+ }
406
+ }
407
+ if (found) {
408
+ arrOfMatchObjects.push(currentObj);
409
+ }
410
+ }
411
+ return arrOfMatchObjects;
412
+ }
324
413
  async setUserListValue(prop, nd) {
325
414
  if (!nd?.email) {
326
415
  return "";
@@ -589,3 +589,66 @@ describe('setEnumerationKeys', () => {
589
589
  expect(returnValue).toEqual([]);
590
590
  });
591
591
  });
592
+ describe('checkKeysAndValues', () => {
593
+ const config = {
594
+ apiHost: 'host',
595
+ userName: () => 'user',
596
+ password: () => 'pass',
597
+ urlContext: 'xxx',
598
+ vibeEventEndpoint: '/rfa/vibeiq/vibeEvents',
599
+ csrfEndpoint: '/servlet/rest/security/csrf',
600
+ itemPreDevelopmentLifecycleStages: ['concept']
601
+ };
602
+ const mapFileUtil = new transform_data_1.MapFileUtil(new sdk_1.Entities());
603
+ const dc = new data_converter_1.DataConverter(config, mapFileUtil);
604
+ const groupData = [
605
+ { name: 'Group 1', typePath: 'custom-entity:grouping' },
606
+ { name: 'Group 2', typePath: 'custom-entity:grouping' },
607
+ { name: 'Group 2', typePath: 'custom-entity:grouping' },
608
+ { name: 'Group 4', typePath: 'custom-entity:grouping' },
609
+ { name: 'Group 4', typePath: 'custom-entity:grouping:sub' },
610
+ { name: 'Group 5', typePath: 'custom-entity:grouping:sub' }
611
+ ];
612
+ it('Group 1', () => {
613
+ const criteria = {
614
+ name: 'Group 1'
615
+ };
616
+ const typePath = 'custom-entity:grouping';
617
+ const results = dc.checkKeysAndValues(criteria, groupData, typePath);
618
+ expect(results.length).toBe(1);
619
+ });
620
+ it('Group 5 - sub type of typePath', () => {
621
+ const criteria = {
622
+ name: 'Group 5'
623
+ };
624
+ const typePath = 'custom-entity:grouping';
625
+ const results = dc.checkKeysAndValues(criteria, groupData, typePath);
626
+ expect(results.length).toBe(1);
627
+ });
628
+ it('Group 2 - returns the 2 matching', () => {
629
+ const criteria = {
630
+ name: 'Group 2'
631
+ };
632
+ const typePath = 'custom-entity:grouping';
633
+ const results = dc.checkKeysAndValues(criteria, groupData, typePath);
634
+ expect(results.length).toBe(2);
635
+ });
636
+ it('2 Same Name - but 1 is a sub type; returns 2', () => {
637
+ const criteria = {
638
+ name: 'Group 4'
639
+ };
640
+ const typePath = 'custom-entity:grouping';
641
+ const results = dc.checkKeysAndValues(criteria, groupData, typePath);
642
+ expect(results.length).toBe(2);
643
+ });
644
+ it('2 Same Name - but 1 wrong type', () => {
645
+ const criteria = {
646
+ name: 'Group 1'
647
+ };
648
+ const testData = [...groupData];
649
+ testData.push({ name: 'Group 1', typePath: 'custom-entity:label' });
650
+ const typePath = 'custom-entity:grouping';
651
+ const results = dc.checkKeysAndValues(criteria, testData, typePath);
652
+ expect(results.length).toBe(1);
653
+ });
654
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrail/flexplm",
3
- "version": "1.1.22",
3
+ "version": "1.1.23",
4
4
  "description": "Library used for integration with flexplm.",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -869,4 +869,75 @@ describe('setEnumerationKeys', () =>{
869
869
  expect(returnValue).toEqual([]);
870
870
  });
871
871
 
872
+ });
873
+
874
+ describe('checkKeysAndValues', () =>{
875
+ const config: FCConfig = {
876
+ apiHost: 'host',
877
+ userName: () => 'user',
878
+ password: () => 'pass',
879
+ urlContext: 'xxx',
880
+ vibeEventEndpoint: '/rfa/vibeiq/vibeEvents',
881
+ csrfEndpoint: '/servlet/rest/security/csrf',
882
+ itemPreDevelopmentLifecycleStages: ['concept']
883
+ };
884
+
885
+ const mapFileUtil = new MapFileUtil(new Entities());
886
+ const dc = new DataConverter(config, mapFileUtil);
887
+ const groupData = [
888
+ {name: 'Group 1', typePath: 'custom-entity:grouping'},
889
+ {name: 'Group 2', typePath: 'custom-entity:grouping'},
890
+ {name: 'Group 2', typePath: 'custom-entity:grouping'},
891
+ {name: 'Group 4', typePath: 'custom-entity:grouping'},
892
+ {name: 'Group 4', typePath: 'custom-entity:grouping:sub'},
893
+ {name: 'Group 5', typePath: 'custom-entity:grouping:sub'}
894
+ ];
895
+ //item:product:newBalance:accessories, item:product:newBalance:apparel
896
+
897
+ it('Group 1', () =>{
898
+ const criteria = {
899
+ name: 'Group 1'
900
+ }
901
+ const typePath = 'custom-entity:grouping';
902
+ const results = dc.checkKeysAndValues(criteria, groupData, typePath);
903
+ expect(results.length).toBe(1);
904
+ });
905
+
906
+ it('Group 5 - sub type of typePath', () =>{
907
+ const criteria = {
908
+ name: 'Group 5'
909
+ }
910
+ const typePath = 'custom-entity:grouping';
911
+ const results = dc.checkKeysAndValues(criteria, groupData, typePath);
912
+ expect(results.length).toBe(1);
913
+ });
914
+ it('Group 2 - returns the 2 matching', () =>{
915
+ const criteria = {
916
+ name: 'Group 2'
917
+ }
918
+ const typePath = 'custom-entity:grouping';
919
+ const results = dc.checkKeysAndValues(criteria, groupData, typePath);
920
+ expect(results.length).toBe(2);
921
+ });
922
+
923
+ it('2 Same Name - but 1 is a sub type; returns 2', () =>{
924
+ const criteria = {
925
+ name: 'Group 4'
926
+ }
927
+ const typePath = 'custom-entity:grouping';
928
+ const results = dc.checkKeysAndValues(criteria, groupData, typePath);
929
+ expect(results.length).toBe(2);
930
+ });
931
+
932
+ it('2 Same Name - but 1 wrong type', () =>{
933
+ const criteria = {
934
+ name: 'Group 1'
935
+ }
936
+ const testData =[...groupData];
937
+ testData.push({name: 'Group 1', typePath: 'custom-entity:label'});
938
+ const typePath = 'custom-entity:grouping';
939
+ const results = dc.checkKeysAndValues(criteria, testData, typePath);
940
+ expect(results.length).toBe(1);
941
+ });
942
+
872
943
  });
@@ -1,4 +1,4 @@
1
- import { Entities, TypeClientOptions } from '@contrail/sdk';
1
+ import { API_VERSION, Entities, TypeClientOptions } from '@contrail/sdk';
2
2
  import { TypeUtils } from './type-utils';
3
3
  import { FCConfig } from '../interfaces/interfaces';
4
4
  import { MapFileUtil } from '@contrail/transform-data';
@@ -240,7 +240,7 @@ export class DataConverter {
240
240
  const propertyType = prop['propertyType'];
241
241
  const slug = prop['slug'];
242
242
  let keyName = slug;
243
- if(propertyType === 'userList') {
243
+ if (propertyType === 'userList' || propertyType === 'object_reference') {
244
244
  keyName = slug + 'Id';
245
245
  }
246
246
  entity[keyName] = await this.getEntityValue(prop, data);
@@ -323,8 +323,7 @@ export class DataConverter {
323
323
  value = this.setEnumerationKeys(prop, nd, this.useDisplayForEnumerationMatching);
324
324
 
325
325
  } else if ('object_reference' === propertyType) {
326
- //TODO write code
327
- //use referencedTypeRootSlug to get the entity type
326
+ value = await this.setObjectReferenceValue(prop, nd);
328
327
  } else if ('image' === propertyType) {
329
328
  // console.log('TODO-image');
330
329
  } else if ('formula' === propertyType) {
@@ -395,6 +394,129 @@ export class DataConverter {
395
394
 
396
395
  }
397
396
 
397
+ /** Sets object reference value from FlexPLM data passed in
398
+ *
399
+ * @param prop the VibeIQ property
400
+ * @param nd the VibeIQ data
401
+ * @returns the object reference id from VibeIQ
402
+ */
403
+ async setObjectReferenceValue(prop, nd) {
404
+ let objectReferenceId = "";
405
+ if(!nd) {
406
+ return objectReferenceId;
407
+ }
408
+
409
+ const entityType = prop['referencedTypeRootSlug'];
410
+ const entityTypePath = prop['referencedTypePath'];
411
+ const entityKeys = Object.keys(nd);
412
+ const identifierKeys = await TypeConversionUtils.getIdentifierPropertiesFromObject(this.transformMapFile, this.mapFileUtil, nd)
413
+ const hasAllIdentifiers = identifierKeys.every(key => entityKeys.includes(key));
414
+ if(identifierKeys.length == 0 || !hasAllIdentifiers){
415
+ console.warn(`The inbound ${entityType} doesnt have all "identifier properties, so there is no processing. Identifier properties: ${identifierKeys}`);
416
+ return objectReferenceId;
417
+ }
418
+
419
+ const rootType = await this.typeUtils.getByRootAndPath({root: entityType});
420
+ const rootTypeProps = this.typeUtils.filterTypeProperties(rootType, nd);
421
+
422
+ let rootTypeCriteria = {};
423
+ let typeCriteria = { };
424
+ identifierKeys.forEach(keyName => {
425
+ const foundInObjects = rootTypeProps.some(obj => obj.slug === keyName);
426
+ if (foundInObjects) {
427
+ rootTypeCriteria[keyName] = nd[keyName];
428
+ } else {
429
+ typeCriteria[keyName] = nd[keyName];
430
+ }
431
+ });
432
+
433
+ const combinedCriteria = { ...rootTypeCriteria, ...typeCriteria, typePath: entityTypePath };
434
+ const cacheKey = Object.values(combinedCriteria).join('_');
435
+
436
+ if (this.objRefCache[cacheKey]) {
437
+ if (Logger.isDebugOn()) {
438
+ console.debug(`object reference cache hit: ${cacheKey}`);
439
+ }
440
+ return this.objRefCache[cacheKey];
441
+ }
442
+
443
+ let arrObjectReferences = await this.getAllObjectReferences(entityType, rootTypeCriteria);
444
+ if(entityType !== entityTypePath) {
445
+ arrObjectReferences = this.checkKeysAndValues(typeCriteria, arrObjectReferences, entityTypePath);
446
+ }
447
+
448
+ if(arrObjectReferences.length) {
449
+ if(arrObjectReferences.length === 1) {
450
+ objectReferenceId = arrObjectReferences[0].id;
451
+ } else {
452
+ console.warn(`The passed in object reference criteria has duplicate records found ${JSON.stringify(combinedCriteria)}.`);
453
+ }
454
+ }
455
+
456
+ if (!objectReferenceId) {
457
+ console.warn(`The passed in object reference criteria ${JSON.stringify(combinedCriteria)} not found.`);
458
+ return objectReferenceId;
459
+ }
460
+
461
+ this.objRefCache[cacheKey] = objectReferenceId;
462
+
463
+ return objectReferenceId;
464
+ }
465
+
466
+ /**
467
+ * Retrieves all object references of a specified entity type based on the provided criteria.
468
+ * This function handles pagination and asynchronously fetches object references until there are no more pages.
469
+ * @param {string} entityType - The type of entity for which object references are to be retrieved.
470
+ * @param {object} rootTypeCriteria - The criteria used to filter object references.
471
+ * @returns {Promise<Array>} A Promise that resolves to an array containing all object references.
472
+ */
473
+ async getAllObjectReferences(entityType, rootTypeCriteria) {
474
+ let loads = []
475
+ let nextPageKey;
476
+ do {
477
+ const loadPage = await new Entities().get({ entityName: entityType, criteria: rootTypeCriteria, apiVersion: API_VERSION.V2, nextPageKey })
478
+ nextPageKey = loadPage.nextPageKey;
479
+ loads.push(...loadPage.results);
480
+ } while (nextPageKey);
481
+ return loads;
482
+ }
483
+
484
+ /**
485
+ * Checks if all keys and values of a given object are present in an array of objects.
486
+ * @param {Object} criteria - The object whose keys and values are to be checked in the array of objects.
487
+ * @param {Array<Object>} arrayOfObjects - The array of objects to be searched for matching keys and values.
488
+ * @param {string} entityTypePath - The type / subtype for the property; objects must be this type or a sub type of it.
489
+ * @returns {(Object|boolean)} Returns the array of objects that matches all keys and values of the provided object.
490
+ * If no match is found, returns empty array.
491
+ */
492
+ checkKeysAndValues(criteria, arrayOfObjects, entityTypePath) {
493
+ let arrOfMatchObjects = [];
494
+ for (let i = 0; i < arrayOfObjects.length; i++) {
495
+ const currentObj = arrayOfObjects[i];
496
+ let found = true;
497
+
498
+ if(entityTypePath && currentObj['typePath']){
499
+ const currentObjPath = '' + currentObj['typePath'];
500
+ if(!currentObjPath.startsWith(entityTypePath)){
501
+ found = false;
502
+ break;
503
+ }
504
+ }
505
+ for (const key in criteria) {
506
+ if (!(key in currentObj) || currentObj[key] !== criteria[key]) {
507
+ found = false;
508
+ break;
509
+ }
510
+ }
511
+
512
+ if (found) {
513
+ arrOfMatchObjects.push(currentObj);
514
+ }
515
+ }
516
+
517
+ return arrOfMatchObjects;
518
+ }
519
+
398
520
  /** Sets userListId value from FlexPLM data passed in
399
521
  *
400
522
  * @param prop the VibeIQ property