@contrail/flexplm 1.7.4-alpha.2fb7e1f → 1.7.4-alpha.458d29b
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/lib/interfaces/item-family-changes.d.ts +1 -0
- package/lib/interfaces/item-family-changes.js +2 -0
- package/lib/publish/base-process-publish-assortment.js +68 -27
- package/lib/publish/base-process-publish-assortment.spec.js +209 -0
- package/lib/util/data-converter.js +18 -0
- package/package.json +66 -66
|
@@ -12,6 +12,7 @@ export declare class ItemFamilyChanges {
|
|
|
12
12
|
colorDeletes: string[];
|
|
13
13
|
colorUpdates: string[];
|
|
14
14
|
colorUnchanged: string[];
|
|
15
|
+
colorSuppressed: string[];
|
|
15
16
|
assortmentItemFullChangeMap: Map<string, any>;
|
|
16
17
|
assortmentItemDeleteMap: Map<string, any>;
|
|
17
18
|
itemToFederatedIdMapping: Map<string, string>;
|
|
@@ -14,6 +14,7 @@ class ItemFamilyChanges {
|
|
|
14
14
|
this.colorDeletes = [];
|
|
15
15
|
this.colorUpdates = [];
|
|
16
16
|
this.colorUnchanged = [];
|
|
17
|
+
this.colorSuppressed = []; // changed this window, but excluded by the lifecycle filter
|
|
17
18
|
this.assortmentItemFullChangeMap = new Map();
|
|
18
19
|
this.assortmentItemDeleteMap = new Map();
|
|
19
20
|
this.itemToFederatedIdMapping = new Map();
|
|
@@ -32,6 +33,7 @@ class ItemFamilyChanges {
|
|
|
32
33
|
s += ' colorDeletes: [' + this.colorDeletes + ']\n';
|
|
33
34
|
s += ' colorUpdates: [' + this.colorUpdates + ']\n';
|
|
34
35
|
s += ' colorUnchanged: [' + this.colorUnchanged + ']\n';
|
|
36
|
+
s += ' colorSuppressed: [' + this.colorSuppressed + ']\n';
|
|
35
37
|
s += 'assortmentItemFullChangeMap:\n';
|
|
36
38
|
s += 'size: ' + this.assortmentItemFullChangeMap.size + '\n[';
|
|
37
39
|
for (const key of this.assortmentItemFullChangeMap.keys()) {
|
|
@@ -29,8 +29,7 @@ class BaseProcessPublishAssortment {
|
|
|
29
29
|
let publisher;
|
|
30
30
|
const assortmentId = event.assortmentId;
|
|
31
31
|
try {
|
|
32
|
-
console.info('process-start
|
|
33
|
-
+ ', assortmentPublishChangeId: ' + assortmentPublishChangeId);
|
|
32
|
+
console.info('process-start!');
|
|
34
33
|
let seasonFed;
|
|
35
34
|
try {
|
|
36
35
|
seasonFed = await this.getSeasonFederation(assortmentId);
|
|
@@ -75,9 +74,9 @@ class BaseProcessPublishAssortment {
|
|
|
75
74
|
e.publishInfo = publishInfo;
|
|
76
75
|
}
|
|
77
76
|
catch (e2) {
|
|
78
|
-
console.
|
|
77
|
+
console.log('catch e2: ' + e2.message);
|
|
79
78
|
}
|
|
80
|
-
console.
|
|
79
|
+
console.log('catch e: ' + e.message);
|
|
81
80
|
throw e;
|
|
82
81
|
}
|
|
83
82
|
}
|
|
@@ -316,17 +315,19 @@ class BaseProcessPublishAssortment {
|
|
|
316
315
|
}
|
|
317
316
|
async downloadHydratedChangeDetail(assortmentPublishChange) {
|
|
318
317
|
try {
|
|
318
|
+
console.info('downloadHydratedChangeDetail-start');
|
|
319
319
|
const link = assortmentPublishChange?.hydratedDetailDownloadLink;
|
|
320
320
|
const response = await fetch(link);
|
|
321
321
|
const data = await response.json();
|
|
322
322
|
return data;
|
|
323
323
|
}
|
|
324
324
|
catch (e) {
|
|
325
|
-
console.
|
|
325
|
+
console.log('Error hydratedDetailDownloadLink: ' + e.message);
|
|
326
326
|
}
|
|
327
327
|
return undefined;
|
|
328
328
|
}
|
|
329
329
|
async downloadAssortmentBaseline(assortmentPublishChange) {
|
|
330
|
+
console.info('downloadAssortmentBaseline-start');
|
|
330
331
|
try {
|
|
331
332
|
const link = assortmentPublishChange?.assortmentBaselineDownloadLink;
|
|
332
333
|
const response = await fetch(link);
|
|
@@ -334,12 +335,15 @@ class BaseProcessPublishAssortment {
|
|
|
334
335
|
return data;
|
|
335
336
|
}
|
|
336
337
|
catch (e) {
|
|
337
|
-
console.
|
|
338
|
+
console.log('Error assortmentBaselineDownloadLink: ' + e.message);
|
|
338
339
|
}
|
|
339
340
|
return undefined;
|
|
340
341
|
}
|
|
341
342
|
async getDeleteChanges(assortmentPublishChange, apcHistory, assortmentBaseline, sinceDate) {
|
|
343
|
+
console.info('getDeleteChanges(): ' + assortmentPublishChange?.id);
|
|
344
|
+
console.info(sinceDate);
|
|
342
345
|
const currentAPCIndex = apcHistory.findIndex(pc => pc?.id === assortmentPublishChange?.id);
|
|
346
|
+
console.info(' currentAPCIndex: ' + currentAPCIndex);
|
|
343
347
|
if (currentAPCIndex == 0) {
|
|
344
348
|
return [];
|
|
345
349
|
}
|
|
@@ -348,13 +352,16 @@ class BaseProcessPublishAssortment {
|
|
|
348
352
|
const apcDeletes = await this.downloadDeleteChanges(assortmentPublishChange);
|
|
349
353
|
let previousApcDate = Date.parse(apcCreatedOn);
|
|
350
354
|
const sinceDateMilliseconds = sinceDate.getTime();
|
|
351
|
-
|
|
352
|
-
|
|
355
|
+
if (app_framework_1.Logger.isInfoOn()) {
|
|
356
|
+
console.info('apcCreatedOn: ' + apcCreatedOn);
|
|
357
|
+
console.info('sinceDate: ' + sinceDate);
|
|
358
|
+
console.info('sinceDateMilliseconds: ' + sinceDateMilliseconds);
|
|
359
|
+
console.info('apcDateMilliseconds: ' + previousApcDate);
|
|
360
|
+
}
|
|
353
361
|
//if only 1 apc, no processing needed
|
|
354
362
|
if (sinceDateMilliseconds !== previousApcDate) {
|
|
363
|
+
console.info('sinceDateMilliseconds !== apcDateMilliseconds');
|
|
355
364
|
const currentAssortmentItemIds = this.getBaselineItemIds(assortmentBaseline);
|
|
356
|
-
let scannedCount = 0;
|
|
357
|
-
let addedCount = 0;
|
|
358
365
|
const deleteIds = (apcDeletes.length === 0)
|
|
359
366
|
? []
|
|
360
367
|
: apcDeletes.map(item => item?.itemId);
|
|
@@ -369,20 +376,19 @@ class BaseProcessPublishAssortment {
|
|
|
369
376
|
? apcHistory[i - 1]
|
|
370
377
|
: undefined;
|
|
371
378
|
const deleteChanges = await this.buildDeleteChanges(workingAPC, previousApc);
|
|
379
|
+
console.info('checking deleteChanges');
|
|
372
380
|
for (const dItem of deleteChanges) {
|
|
373
381
|
const dItemId = dItem?.itemId;
|
|
374
|
-
|
|
382
|
+
console.info(dItemId);
|
|
375
383
|
if (!currentAssortmentItemIds.includes(dItemId) && !deleteIds.includes(dItemId)) {
|
|
376
|
-
|
|
384
|
+
console.info('adding');
|
|
377
385
|
deleteIds.push(dItemId);
|
|
378
386
|
apcDeletes.push(dItem);
|
|
379
387
|
}
|
|
380
388
|
}
|
|
381
389
|
}
|
|
382
390
|
}
|
|
383
|
-
console.info('getDeleteChanges():
|
|
384
|
-
+ ', scanned ' + scannedCount + ' historical delete changes, added ' + addedCount
|
|
385
|
-
+ ', deleteIds: ' + deleteIds.length);
|
|
391
|
+
console.info('getDeleteChanges()-currentAssortmentItemIds: ' + currentAssortmentItemIds);
|
|
386
392
|
console.info('getDeleteChanges()-deleteIds: ' + deleteIds);
|
|
387
393
|
}
|
|
388
394
|
return apcDeletes;
|
|
@@ -396,6 +402,7 @@ class BaseProcessPublishAssortment {
|
|
|
396
402
|
return itemIds;
|
|
397
403
|
}
|
|
398
404
|
async downloadDeleteChanges(apc) {
|
|
405
|
+
console.info('deleteDataDownloadLink-start');
|
|
399
406
|
try {
|
|
400
407
|
const link = apc?.deleteDataDownloadLink;
|
|
401
408
|
const response = await fetch(link);
|
|
@@ -403,17 +410,19 @@ class BaseProcessPublishAssortment {
|
|
|
403
410
|
return data;
|
|
404
411
|
}
|
|
405
412
|
catch (e) {
|
|
406
|
-
console.
|
|
413
|
+
console.log('Error deleteDataDownloadLink: ' + e.message);
|
|
407
414
|
}
|
|
408
415
|
return undefined;
|
|
409
416
|
}
|
|
410
417
|
async buildDeleteChanges(apc, previousApc) {
|
|
418
|
+
console.info('buildDeleteChanges()');
|
|
411
419
|
if (apc?.deleteDataDownloadLink) {
|
|
412
420
|
const deleteChanges = await this.downloadDeleteChanges(apc);
|
|
413
421
|
if (deleteChanges) {
|
|
414
422
|
return deleteChanges;
|
|
415
423
|
}
|
|
416
424
|
}
|
|
425
|
+
console.info('pulling down full APC');
|
|
417
426
|
apc = await this.downloadAssortmentPublishChange(apc?.assortmentId, apc?.id);
|
|
418
427
|
if (apc?.deleteDataDownloadLink) {
|
|
419
428
|
const deleteChanges = await this.downloadDeleteChanges(apc);
|
|
@@ -425,18 +434,19 @@ class BaseProcessPublishAssortment {
|
|
|
425
434
|
throw new Error(BaseProcessPublishAssortment.NOT_ABLE_TO_PROCESS_DELETE_CHANGES);
|
|
426
435
|
}
|
|
427
436
|
let previousBaseline;
|
|
437
|
+
console.info('check previousApc');
|
|
428
438
|
if (previousApc?.assortmentBaselineDownloadLink) {
|
|
429
439
|
previousBaseline = await this.downloadAssortmentBaseline(previousApc);
|
|
430
440
|
}
|
|
431
441
|
else {
|
|
442
|
+
console.info('previousApc pulling down full APC');
|
|
432
443
|
previousApc = await this.downloadAssortmentPublishChange(previousApc?.assortmentId, previousApc?.id);
|
|
433
444
|
previousBaseline = await this.downloadAssortmentBaseline(previousApc);
|
|
434
445
|
}
|
|
435
446
|
const deleteIds = apc?.detail?.deletes.map(dItem => dItem?.id);
|
|
436
447
|
//building deletes based on previous baseline; because some APCs don't have delete data
|
|
437
448
|
const deleteArray = previousBaseline?.assortmentItems.filter(aItem => deleteIds.includes(aItem?.itemId));
|
|
438
|
-
console.info('
|
|
439
|
-
+ '; rebuilding ' + deleteArray.length + ' deletes from previous baseline');
|
|
449
|
+
console.info('deleteArray.length: ' + deleteArray.length);
|
|
440
450
|
return deleteArray;
|
|
441
451
|
}
|
|
442
452
|
async getItemFederatedIds( /*itemIds*/) {
|
|
@@ -474,6 +484,7 @@ class BaseProcessPublishAssortment {
|
|
|
474
484
|
return deleteItems;
|
|
475
485
|
}
|
|
476
486
|
getReleasedForDevelopmentItemAndFamilyIds(fullChange, deleteChanges) {
|
|
487
|
+
console.info('getReleasedForDevelopmentItemAndFamilyIds');
|
|
477
488
|
const releasedForDevelopmentItemIds = [];
|
|
478
489
|
const itemFamilySet = new Set();
|
|
479
490
|
const assortmentItemsArray = fullChange?.assortmentItems;
|
|
@@ -484,13 +495,18 @@ class BaseProcessPublishAssortment {
|
|
|
484
495
|
const itemFamilyId = item?.itemFamilyId;
|
|
485
496
|
if (meetsCriteria) {
|
|
486
497
|
releasedForDevelopmentItemIds.push(itemId);
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
498
|
+
}
|
|
499
|
+
// ---- NEW (Step 5): family resolution moved OUT of the meetsCriteria block.
|
|
500
|
+
// A family whose options are ALL pre-development has no other route into this
|
|
501
|
+
// list when it has no assortment-item row of its own. ----
|
|
502
|
+
if (!itemFamilySet.has(itemFamilyId) && itemId !== itemFamilyId) {
|
|
503
|
+
const familyItem = item?.itemFamily;
|
|
504
|
+
if (familyItem) {
|
|
505
|
+
if (this.meetsCriteria({ item: familyItem })
|
|
506
|
+
&& !releasedForDevelopmentItemIds.includes(itemFamilyId)) {
|
|
491
507
|
releasedForDevelopmentItemIds.push(itemFamilyId);
|
|
492
508
|
}
|
|
493
|
-
itemFamilySet.add(itemFamilyId);
|
|
509
|
+
itemFamilySet.add(itemFamilyId); // only mark resolved when we had data to judge
|
|
494
510
|
}
|
|
495
511
|
}
|
|
496
512
|
}
|
|
@@ -525,6 +541,7 @@ class BaseProcessPublishAssortment {
|
|
|
525
541
|
return !!lifecycleStage && !this.config.itemPreDevelopmentLifecycleStages.includes(lifecycleStage);
|
|
526
542
|
}
|
|
527
543
|
async processPublish(pcd, changeDetail, fullChange, deleteChanges) {
|
|
544
|
+
console.info('processPublish-start');
|
|
528
545
|
const event = {
|
|
529
546
|
assortmentId: pcd.assortmentId,
|
|
530
547
|
assortmentPublishChangeId: pcd.assortmentPublishChangeId
|
|
@@ -708,6 +725,7 @@ class BaseProcessPublishAssortment {
|
|
|
708
725
|
+ '-' + d.getUTCMilliseconds();
|
|
709
726
|
}
|
|
710
727
|
getItemFamilyChanges(pcd, changeDetail, assortmentItemFullChangeMap, assortmentItemDeleteMap) {
|
|
728
|
+
console.info('getItemFamilyChanges-start');
|
|
711
729
|
const itemFamilyChanges = new Map();
|
|
712
730
|
const { adds, deletes, updates, familyItemsRemoved } = changeDetail;
|
|
713
731
|
const addIds = adds.map(item => item.id);
|
|
@@ -717,7 +735,7 @@ class BaseProcessPublishAssortment {
|
|
|
717
735
|
for (const [itemId, aItem] of assortmentItemFullChangeMap) {
|
|
718
736
|
const projectItem = aItem?.projectItem;
|
|
719
737
|
const itemFamilyId = aItem?.item?.itemFamilyId;
|
|
720
|
-
if (!pcd.releasedForDevelopmentItemIds.includes(itemFamilyId)
|
|
738
|
+
if (!pcd.releasedForDevelopmentItemIds.includes(itemFamilyId)) {
|
|
721
739
|
continue;
|
|
722
740
|
}
|
|
723
741
|
const ifc = itemFamilyChanges.get(itemFamilyId) || new item_family_changes_1.ItemFamilyChanges(itemFamilyId, pcd.sinceDate);
|
|
@@ -729,6 +747,20 @@ class BaseProcessPublishAssortment {
|
|
|
729
747
|
if (pcd.itemToFederatedIdMapping.has(itemId)) {
|
|
730
748
|
ifc.itemToFederatedIdMapping.set(itemId, pcd.itemToFederatedIdMapping.get(itemId));
|
|
731
749
|
}
|
|
750
|
+
// ---- NEW Step to enable ItemFamily publish ET-243: option is pre-development. It must not produce an
|
|
751
|
+
// LCSSKUSeasonLink, but a change to it still makes the family publishable. ----
|
|
752
|
+
if (!pcd.releasedForDevelopmentItemIds.includes(itemId)) {
|
|
753
|
+
const changedInWindow = addIds.includes(itemId)
|
|
754
|
+
|| deleteIds.includes(itemId)
|
|
755
|
+
|| updateIds.includes(itemId)
|
|
756
|
+
|| this.updatedSinceDate(projectItem, pcd.sinceDate)
|
|
757
|
+
|| this.updatedSinceDate(aItem?.item, pcd.sinceDate);
|
|
758
|
+
if (changedInWindow) {
|
|
759
|
+
ifc.colorSuppressed.push(itemId);
|
|
760
|
+
}
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
763
|
+
// NEW Step - ends
|
|
732
764
|
if (itemId === itemFamilyId) {
|
|
733
765
|
if (addIds.includes(itemId))
|
|
734
766
|
ifc.familyAdd = true;
|
|
@@ -744,6 +776,12 @@ class BaseProcessPublishAssortment {
|
|
|
744
776
|
ifc.familyUpdate = true;
|
|
745
777
|
}
|
|
746
778
|
}
|
|
779
|
+
// NEW Step to enable ItemFamily publish ET-243: item-level edits (incl. lifecycleStage) must publish
|
|
780
|
+
// the family. Guarded so the "first option added" flow stays suppressed. ----
|
|
781
|
+
if (!ifc.familyItemRemoved && this.updatedSinceDate(aItem?.item, pcd.sinceDate)) {
|
|
782
|
+
ifc.familyUpdate = true;
|
|
783
|
+
}
|
|
784
|
+
// NEW Step - ends
|
|
747
785
|
if (projectItem && !projectItem?.roles && aItem?.item?.roles) {
|
|
748
786
|
projectItem.roles = aItem?.item?.roles;
|
|
749
787
|
}
|
|
@@ -772,7 +810,8 @@ class BaseProcessPublishAssortment {
|
|
|
772
810
|
}
|
|
773
811
|
else {
|
|
774
812
|
if (!item) {
|
|
775
|
-
console.error('Failed to find deleted item entity
|
|
813
|
+
console.error('Failed to find deleted item entity');
|
|
814
|
+
console.error(' itemFamilyId: ' + itemFamilyId + ' -itemId: ' + itemId);
|
|
776
815
|
continue;
|
|
777
816
|
}
|
|
778
817
|
}
|
|
@@ -809,6 +848,7 @@ class BaseProcessPublishAssortment {
|
|
|
809
848
|
return itemFamilyChanges;
|
|
810
849
|
}
|
|
811
850
|
async getEventsForPublishChangeData(publishChangeData) {
|
|
851
|
+
console.info('getEventsForPublishChangeData-start');
|
|
812
852
|
const seasonalPayloads = [];
|
|
813
853
|
for (const itemFamilyChange of publishChangeData.itemFamilyChanges.values()) {
|
|
814
854
|
const events = await this.getEventsForItemFamilyChanges(itemFamilyChange, publishChangeData.assortmentId, publishChangeData.seasonFed, publishChangeData.itemToFederatedIdMapping);
|
|
@@ -816,8 +856,6 @@ class BaseProcessPublishAssortment {
|
|
|
816
856
|
seasonalPayloads.push(...events);
|
|
817
857
|
}
|
|
818
858
|
}
|
|
819
|
-
console.info('getEventsForPublishChangeData: itemFamilies: '
|
|
820
|
-
+ publishChangeData.itemFamilyChanges.size + ', payloads: ' + seasonalPayloads.length);
|
|
821
859
|
return seasonalPayloads;
|
|
822
860
|
}
|
|
823
861
|
/**Returns the events for a given ItemFamilyChanges object
|
|
@@ -837,12 +875,15 @@ class BaseProcessPublishAssortment {
|
|
|
837
875
|
* @returns
|
|
838
876
|
*/
|
|
839
877
|
async getEventsForItemFamilyChanges(itemFamilyChanges, assortmentId, seasonFed, itemToFederatedIdMapping) {
|
|
878
|
+
console.info('getEventsForItemFamilyChanges()');
|
|
840
879
|
const events = [];
|
|
841
880
|
const LCSSeason = Object.assign({}, seasonFed);
|
|
842
881
|
const assortment = await this.getAssortment(assortmentId);
|
|
843
882
|
const projectItem = this.getProjectItem(itemFamilyChanges, itemFamilyChanges.itemFamilyId);
|
|
883
|
+
// ---- NEW Step to enable ItemFamily publish ET-243: added color suppressed length to existing condition, no other changes on familyAssortmentItem boolean
|
|
844
884
|
const familyAssortmentItem = itemFamilyChanges.familyAdd || itemFamilyChanges.familyUpdate || itemFamilyChanges.familyDelete
|
|
845
885
|
|| itemFamilyChanges.colorAdds.length > 0 || itemFamilyChanges.colorUpdates.length > 0 || itemFamilyChanges.colorDeletes.length > 0
|
|
886
|
+
|| itemFamilyChanges.colorSuppressed.length > 0
|
|
846
887
|
|| (projectItem && this.updatedSinceDate(projectItem, itemFamilyChanges.sinceDate));
|
|
847
888
|
//familyItemRemoved is used when adding the first option to an assortment
|
|
848
889
|
//and will have updates for the family item.
|
|
@@ -1337,6 +1337,151 @@ describe('getItemFamilyChanges', () => {
|
|
|
1337
1337
|
expect(ifc.colorDeletes.length).toEqual(1);
|
|
1338
1338
|
});
|
|
1339
1339
|
});
|
|
1340
|
+
describe('getItemFamilyChanges - lifecycle-suppressed options', () => {
|
|
1341
|
+
const familyId = 'AERfdvdAt1HHEVvQ';
|
|
1342
|
+
const maroonId = 'YwWKVoL_vGN8khLy';
|
|
1343
|
+
const limeId = 'rsmWhWiXRNv_XBJG';
|
|
1344
|
+
const sinceDate = new Date('2026-08-01T00:00:00.000Z');
|
|
1345
|
+
const afterSince = '2026-08-26T00:00:00.000Z';
|
|
1346
|
+
const beforeSince = '2026-05-11T00:00:00.000Z';
|
|
1347
|
+
const noDetailChanges = () => ({ adds: [], deletes: [], updates: [], familyItemsRemoved: [] });
|
|
1348
|
+
const buildPpa = () => {
|
|
1349
|
+
const config = { itemPreDevelopmentLifecycleStages: ['concept'] };
|
|
1350
|
+
const mapFileUtil = new transform_data_1.MapFileUtil(new sdk_1.Entities());
|
|
1351
|
+
const dc = new data_converter_1.DataConverter(config, mapFileUtil);
|
|
1352
|
+
return new base_process_publish_assortment_1.BaseProcessPublishAssortment(config, dc, mapFileUtil);
|
|
1353
|
+
};
|
|
1354
|
+
const buildPcd = (releasedIds) => {
|
|
1355
|
+
const seasonFed = {
|
|
1356
|
+
entityReference: 'assortment:11EZML5dMn3tREuT',
|
|
1357
|
+
objectClass: 'LCSSeason'
|
|
1358
|
+
};
|
|
1359
|
+
const pcd = new publish_change_data_1.PublishChangeData('11EZML5dMn3tREuT', seasonFed, 'Pg3y2x5IclYxk3hM', sinceDate);
|
|
1360
|
+
pcd.itemToFederatedIdMapping = new Map();
|
|
1361
|
+
releasedIds.forEach(id => pcd.releasedForDevelopmentItemIds.push(id));
|
|
1362
|
+
return pcd;
|
|
1363
|
+
};
|
|
1364
|
+
const familyItem = (updatedOn = beforeSince, lifecycleStage = 'released') => ({
|
|
1365
|
+
id: familyId, itemFamilyId: familyId, lifecycleStage, name: 'Sweatpant', updatedOn
|
|
1366
|
+
});
|
|
1367
|
+
const familyRow = (updatedOn = beforeSince, lifecycleStage = 'released') => ({
|
|
1368
|
+
itemId: familyId,
|
|
1369
|
+
item: familyItem(updatedOn, lifecycleStage),
|
|
1370
|
+
projectItem: { id: 'pi-' + familyId, updatedOn: beforeSince }
|
|
1371
|
+
});
|
|
1372
|
+
const optionRow = (id, updatedOn, lifecycleStage = 'concept') => ({
|
|
1373
|
+
itemId: id,
|
|
1374
|
+
item: { id, itemFamilyId: familyId, lifecycleStage, updatedOn, itemFamily: familyItem() },
|
|
1375
|
+
projectItem: { id: 'pi-' + id, updatedOn: beforeSince },
|
|
1376
|
+
familyProjectItem: { id: 'pi-' + familyId, updatedOn: beforeSince }
|
|
1377
|
+
});
|
|
1378
|
+
it('records changed concept options as suppressed, not as colour updates', () => {
|
|
1379
|
+
const ppa = buildPpa();
|
|
1380
|
+
const fullChangeMap = ppa.getFullChangeAssortmentMap({
|
|
1381
|
+
assortmentItems: [familyRow(), optionRow(maroonId, afterSince), optionRow(limeId, afterSince)]
|
|
1382
|
+
});
|
|
1383
|
+
const pcd = buildPcd([familyId]);
|
|
1384
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, noDetailChanges(), fullChangeMap, new Map());
|
|
1385
|
+
expect(pcd.itemFamilyChanges.size).toEqual(1);
|
|
1386
|
+
const ifc = pcd.itemFamilyChanges.get(familyId);
|
|
1387
|
+
expect(ifc.colorSuppressed).toEqual([maroonId, limeId]);
|
|
1388
|
+
expect(ifc.colorAdds.length).toEqual(0);
|
|
1389
|
+
expect(ifc.colorUpdates.length).toEqual(0);
|
|
1390
|
+
expect(ifc.colorDeletes.length).toEqual(0);
|
|
1391
|
+
expect(ifc.familyUpdate).toBe(false); // family itself unchanged
|
|
1392
|
+
});
|
|
1393
|
+
it('keeps the family reachable when only concept option rows are in the baseline', () => {
|
|
1394
|
+
const ppa = buildPpa();
|
|
1395
|
+
const assortmentItems = [optionRow(maroonId, afterSince), optionRow(limeId, afterSince)];
|
|
1396
|
+
const fullChangeMap = ppa.getFullChangeAssortmentMap({ assortmentItems });
|
|
1397
|
+
// derive the gate the way production does, rather than seeding it
|
|
1398
|
+
const releasedIds = ppa.getReleasedForDevelopmentItemAndFamilyIds({ assortmentItems }, []);
|
|
1399
|
+
expect(releasedIds).toEqual([familyId]);
|
|
1400
|
+
const pcd = buildPcd(releasedIds);
|
|
1401
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, noDetailChanges(), fullChangeMap, new Map());
|
|
1402
|
+
expect(pcd.itemFamilyChanges.size).toEqual(1);
|
|
1403
|
+
const ifc = pcd.itemFamilyChanges.get(familyId);
|
|
1404
|
+
expect(ifc.itemFamilyObject.id).toEqual(familyId);
|
|
1405
|
+
expect(ifc.colorSuppressed.length).toEqual(2);
|
|
1406
|
+
expect(ppa.getProjectItem(ifc, familyId)).toEqual({ id: 'pi-' + familyId, updatedOn: beforeSince });
|
|
1407
|
+
});
|
|
1408
|
+
it('does not suppress-record an unchanged concept option', () => {
|
|
1409
|
+
const ppa = buildPpa();
|
|
1410
|
+
const fullChangeMap = ppa.getFullChangeAssortmentMap({
|
|
1411
|
+
assortmentItems: [familyRow(), optionRow(maroonId, beforeSince)]
|
|
1412
|
+
});
|
|
1413
|
+
const pcd = buildPcd([familyId]);
|
|
1414
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, noDetailChanges(), fullChangeMap, new Map());
|
|
1415
|
+
const ifc = pcd.itemFamilyChanges.get(familyId);
|
|
1416
|
+
expect(ifc.colorSuppressed.length).toEqual(0);
|
|
1417
|
+
});
|
|
1418
|
+
it('picks up a concept option listed in the change detail even when timestamps are stale', () => {
|
|
1419
|
+
const ppa = buildPpa();
|
|
1420
|
+
const fullChangeMap = ppa.getFullChangeAssortmentMap({
|
|
1421
|
+
assortmentItems: [familyRow(), optionRow(maroonId, beforeSince)]
|
|
1422
|
+
});
|
|
1423
|
+
const details = noDetailChanges();
|
|
1424
|
+
details.updates = [{ id: maroonId }];
|
|
1425
|
+
const pcd = buildPcd([familyId]);
|
|
1426
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, details, fullChangeMap, new Map());
|
|
1427
|
+
expect(pcd.itemFamilyChanges.get(familyId).colorSuppressed).toEqual([maroonId]);
|
|
1428
|
+
});
|
|
1429
|
+
it('skips everything when the family itself is pre-development', () => {
|
|
1430
|
+
const ppa = buildPpa();
|
|
1431
|
+
const fullChangeMap = ppa.getFullChangeAssortmentMap({
|
|
1432
|
+
assortmentItems: [familyRow(afterSince, 'concept'), optionRow(maroonId, afterSince)]
|
|
1433
|
+
});
|
|
1434
|
+
const pcd = buildPcd([]); // nothing released
|
|
1435
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, noDetailChanges(), fullChangeMap, new Map());
|
|
1436
|
+
expect(pcd.itemFamilyChanges.size).toEqual(0);
|
|
1437
|
+
});
|
|
1438
|
+
it('still classifies released options normally', () => {
|
|
1439
|
+
const ppa = buildPpa();
|
|
1440
|
+
const fullChangeMap = ppa.getFullChangeAssortmentMap({
|
|
1441
|
+
assortmentItems: [familyRow(), optionRow(maroonId, afterSince, 'released')]
|
|
1442
|
+
});
|
|
1443
|
+
const details = noDetailChanges();
|
|
1444
|
+
details.adds = [{ id: maroonId }];
|
|
1445
|
+
const pcd = buildPcd([familyId, maroonId]);
|
|
1446
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, details, fullChangeMap, new Map());
|
|
1447
|
+
const ifc = pcd.itemFamilyChanges.get(familyId);
|
|
1448
|
+
expect(ifc.colorAdds).toEqual([maroonId]);
|
|
1449
|
+
expect(ifc.colorSuppressed.length).toEqual(0);
|
|
1450
|
+
});
|
|
1451
|
+
it('publishes a family-only row whose lifecycle just flipped (item-level edit)', () => {
|
|
1452
|
+
const ppa = buildPpa();
|
|
1453
|
+
const fullChangeMap = ppa.getFullChangeAssortmentMap({
|
|
1454
|
+
assortmentItems: [familyRow(afterSince)] // item.updatedOn moved, projectItem did not
|
|
1455
|
+
});
|
|
1456
|
+
const pcd = buildPcd([familyId]);
|
|
1457
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, noDetailChanges(), fullChangeMap, new Map());
|
|
1458
|
+
const ifc = pcd.itemFamilyChanges.get(familyId);
|
|
1459
|
+
expect(ifc.familyUpdate).toBe(true); // fails without Step 3
|
|
1460
|
+
expect(ifc.colorSuppressed.length).toEqual(0);
|
|
1461
|
+
});
|
|
1462
|
+
it('leaves an unchanged family-only row with no flags', () => {
|
|
1463
|
+
const ppa = buildPpa();
|
|
1464
|
+
const fullChangeMap = ppa.getFullChangeAssortmentMap({ assortmentItems: [familyRow()] });
|
|
1465
|
+
const pcd = buildPcd([familyId]);
|
|
1466
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, noDetailChanges(), fullChangeMap, new Map());
|
|
1467
|
+
const ifc = pcd.itemFamilyChanges.get(familyId);
|
|
1468
|
+
expect(ifc.familyAdd).toBe(false);
|
|
1469
|
+
expect(ifc.familyUpdate).toBe(false);
|
|
1470
|
+
expect(ifc.familyDelete).toBe(false);
|
|
1471
|
+
expect(ifc.colorSuppressed.length).toEqual(0);
|
|
1472
|
+
});
|
|
1473
|
+
it('does not set familyUpdate when the family item was removed', () => {
|
|
1474
|
+
const ppa = buildPpa();
|
|
1475
|
+
const fullChangeMap = ppa.getFullChangeAssortmentMap({ assortmentItems: [familyRow(afterSince)] });
|
|
1476
|
+
const details = noDetailChanges();
|
|
1477
|
+
details.familyItemsRemoved = [{ itemId: familyId }]; // note: itemId, not id
|
|
1478
|
+
const pcd = buildPcd([familyId]);
|
|
1479
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, details, fullChangeMap, new Map());
|
|
1480
|
+
const ifc = pcd.itemFamilyChanges.get(familyId);
|
|
1481
|
+
expect(ifc.familyItemRemoved).toBe(true);
|
|
1482
|
+
expect(ifc.familyUpdate).toBe(false); // fails if the !familyItemRemoved guard is dropped
|
|
1483
|
+
});
|
|
1484
|
+
});
|
|
1340
1485
|
describe('getProjectItem', () => {
|
|
1341
1486
|
const config = {
|
|
1342
1487
|
identifierAtts: {
|
|
@@ -1500,6 +1645,70 @@ describe('meetsCriteria', () => {
|
|
|
1500
1645
|
expect(results).toBeTruthy();
|
|
1501
1646
|
});
|
|
1502
1647
|
});
|
|
1648
|
+
describe('getReleasedForDevelopmentItemAndFamilyIds', () => {
|
|
1649
|
+
const familyId = 'AERfdvdAt1HHEVvQ';
|
|
1650
|
+
const maroonId = 'YwWKVoL_vGN8khLy';
|
|
1651
|
+
const limeId = 'rsmWhWiXRNv_XBJG';
|
|
1652
|
+
const buildPpa = (preDev = ['concept']) => {
|
|
1653
|
+
const config = {
|
|
1654
|
+
identifierAtts: { LCSSeason: ['flexPLMSeasonName'] },
|
|
1655
|
+
itemPreDevelopmentLifecycleStages: preDev
|
|
1656
|
+
};
|
|
1657
|
+
const mapFileUtil = new transform_data_1.MapFileUtil(new sdk_1.Entities());
|
|
1658
|
+
const dc = new data_converter_1.DataConverter(config, mapFileUtil);
|
|
1659
|
+
return new base_process_publish_assortment_1.BaseProcessPublishAssortment(config, dc, mapFileUtil);
|
|
1660
|
+
};
|
|
1661
|
+
const familyItem = (lifecycleStage = 'released') => ({
|
|
1662
|
+
id: familyId, itemFamilyId: familyId, lifecycleStage, name: 'Sweatpant'
|
|
1663
|
+
});
|
|
1664
|
+
// option row as the hydrated baseline actually delivers it: itemFamily present
|
|
1665
|
+
const optionRow = (id, lifecycleStage = 'concept', family = familyItem()) => ({
|
|
1666
|
+
itemId: id,
|
|
1667
|
+
item: { id, itemFamilyId: familyId, lifecycleStage, itemFamily: family }
|
|
1668
|
+
});
|
|
1669
|
+
const familyRow = (lifecycleStage = 'released') => ({
|
|
1670
|
+
itemId: familyId,
|
|
1671
|
+
item: familyItem(lifecycleStage)
|
|
1672
|
+
});
|
|
1673
|
+
const run = (ppa, assortmentItems) => ppa.getReleasedForDevelopmentItemAndFamilyIds({ assortmentItems }, []);
|
|
1674
|
+
it('surfaces the family when every option is pre-development', () => {
|
|
1675
|
+
const ppa = buildPpa();
|
|
1676
|
+
expect(run(ppa, [optionRow(maroonId), optionRow(limeId)])).toEqual([familyId]);
|
|
1677
|
+
});
|
|
1678
|
+
it('does not duplicate the family id when a family row and a released option both exist', () => {
|
|
1679
|
+
const ppa = buildPpa();
|
|
1680
|
+
const ids = run(ppa, [familyRow(), optionRow(limeId, 'released')]);
|
|
1681
|
+
expect(ids.filter(id => id === familyId)).toHaveLength(1);
|
|
1682
|
+
});
|
|
1683
|
+
it('still resolves the family when an earlier row lacks itemFamily hydration', () => {
|
|
1684
|
+
const ppa = buildPpa();
|
|
1685
|
+
const noFamilyHydration = {
|
|
1686
|
+
itemId: maroonId,
|
|
1687
|
+
item: { id: maroonId, itemFamilyId: familyId, lifecycleStage: 'concept' }
|
|
1688
|
+
};
|
|
1689
|
+
expect(run(ppa, [noFamilyHydration, optionRow(limeId)])).toEqual([familyId]);
|
|
1690
|
+
});
|
|
1691
|
+
it('returns nothing when the family itself is pre-development', () => {
|
|
1692
|
+
const ppa = buildPpa();
|
|
1693
|
+
expect(run(ppa, [optionRow(maroonId, 'concept', familyItem('concept'))])).toEqual([]);
|
|
1694
|
+
});
|
|
1695
|
+
it('includes a family-only row exactly once', () => {
|
|
1696
|
+
const ppa = buildPpa();
|
|
1697
|
+
expect(run(ppa, [familyRow()])).toEqual([familyId]);
|
|
1698
|
+
});
|
|
1699
|
+
it('includes a released option and its family, excluding the concept option', () => {
|
|
1700
|
+
const ppa = buildPpa();
|
|
1701
|
+
const ids = run(ppa, [optionRow(maroonId, 'concept'), optionRow(limeId, 'released')]);
|
|
1702
|
+
expect(ids).toContain(familyId);
|
|
1703
|
+
expect(ids).toContain(limeId);
|
|
1704
|
+
expect(ids).not.toContain(maroonId);
|
|
1705
|
+
});
|
|
1706
|
+
it('treats the stage list as case-sensitive (documents the Concept vs concept trap)', () => {
|
|
1707
|
+
const ppa = buildPpa(['Concept']); // as mis-configured in praveen-dev-1
|
|
1708
|
+
const ids = run(ppa, [optionRow(maroonId), optionRow(limeId)]);
|
|
1709
|
+
expect(ids).toEqual([maroonId, familyId, limeId]); // filter inert: everything qualifies
|
|
1710
|
+
});
|
|
1711
|
+
});
|
|
1503
1712
|
describe('getEventsForItemFamilyChanges - conditional eventType', () => {
|
|
1504
1713
|
const config = {
|
|
1505
1714
|
identifierAtts: {
|
|
@@ -175,12 +175,18 @@ class DataConverter {
|
|
|
175
175
|
}
|
|
176
176
|
async getObjectReferenceValue(prop, newData, inflateObjRef = false) {
|
|
177
177
|
const slug = prop['slug'];
|
|
178
|
+
if (app_framework_1.Logger.isDebugOn()) {
|
|
179
|
+
console.debug('getObjectReferenceValue-prop: ' + slug);
|
|
180
|
+
}
|
|
178
181
|
let value = newData[slug];
|
|
179
182
|
const entityType = prop['referencedTypeRootSlug'];
|
|
180
183
|
const entityId = newData[slug + 'Id'];
|
|
181
184
|
if ((!value || typeof value === 'string') && inflateObjRef) {
|
|
182
185
|
if (entityId) {
|
|
183
186
|
if (this.objRefCache[entityId]) {
|
|
187
|
+
if (app_framework_1.Logger.isDebugOn()) {
|
|
188
|
+
console.debug('cache hit: ' + entityId);
|
|
189
|
+
}
|
|
184
190
|
return this.objRefCache[entityId];
|
|
185
191
|
}
|
|
186
192
|
const criteria = {
|
|
@@ -426,6 +432,9 @@ class DataConverter {
|
|
|
426
432
|
return "";
|
|
427
433
|
}
|
|
428
434
|
if (this.objRefCache[ctx.cacheKey]) {
|
|
435
|
+
if (app_framework_1.Logger.isDebugOn()) {
|
|
436
|
+
console.debug(`object reference cache hit: ${ctx.cacheKey}`);
|
|
437
|
+
}
|
|
429
438
|
return this.objRefCache[ctx.cacheKey];
|
|
430
439
|
}
|
|
431
440
|
const objectReferenceId = ctx.useIdentityService
|
|
@@ -625,6 +634,9 @@ class DataConverter {
|
|
|
625
634
|
}
|
|
626
635
|
let cacheUser = DataConverter.getFromStaticCache(nd.email);
|
|
627
636
|
if (cacheUser) {
|
|
637
|
+
if (app_framework_1.Logger.isDebugOn()) {
|
|
638
|
+
console.debug('user cache hit: ' + nd.email);
|
|
639
|
+
}
|
|
628
640
|
await this.processGroupMemberCheck(prop, nd.email);
|
|
629
641
|
return cacheUser;
|
|
630
642
|
}
|
|
@@ -694,12 +706,18 @@ class DataConverter {
|
|
|
694
706
|
*/
|
|
695
707
|
async getUserListValue(prop, newData) {
|
|
696
708
|
const slug = prop['slug'];
|
|
709
|
+
if (app_framework_1.Logger.isDebugOn()) {
|
|
710
|
+
console.debug('getUserListValue-prop: ' + slug);
|
|
711
|
+
}
|
|
697
712
|
const entityId = newData[slug + 'Id'];
|
|
698
713
|
if (!entityId) {
|
|
699
714
|
return {};
|
|
700
715
|
}
|
|
701
716
|
const cacheUser = DataConverter.getFromStaticCache(entityId);
|
|
702
717
|
if (cacheUser) {
|
|
718
|
+
if (app_framework_1.Logger.isDebugOn()) {
|
|
719
|
+
console.debug('user cache hit: ' + entityId);
|
|
720
|
+
}
|
|
703
721
|
return Object.assign({}, cacheUser);
|
|
704
722
|
}
|
|
705
723
|
const user = await this.getUserById(entityId);
|
package/package.json
CHANGED
|
@@ -1,66 +1,66 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@contrail/flexplm",
|
|
3
|
-
"version": "1.7.4-alpha.
|
|
4
|
-
"description": "Library used for integration with flexplm.",
|
|
5
|
-
"main": "lib/index.js",
|
|
6
|
-
"types": "lib/index.d.ts",
|
|
7
|
-
"bin": {
|
|
8
|
-
"flexplm-mapping": "lib/cli/index.js"
|
|
9
|
-
},
|
|
10
|
-
"files": [
|
|
11
|
-
"lib/**/*",
|
|
12
|
-
"scripts/copy-template.js"
|
|
13
|
-
],
|
|
14
|
-
"scripts": {
|
|
15
|
-
"build": "tsc; node scripts/copy-template.js",
|
|
16
|
-
"build:win": "tsc && node scripts/copy-template.js",
|
|
17
|
-
"format": "prettier --write \"src/**/*.ts\" \"src/**/*.js\"",
|
|
18
|
-
"lint": "tslint -p tsconfig.json",
|
|
19
|
-
"test": "jest",
|
|
20
|
-
"test-watch": "jest --watch",
|
|
21
|
-
"test-debug": "jest --runInBand",
|
|
22
|
-
"test-coverage": "jest --coverage"
|
|
23
|
-
},
|
|
24
|
-
"keywords": [],
|
|
25
|
-
"author": "VibeIQ",
|
|
26
|
-
"license": "ISC",
|
|
27
|
-
"devDependencies": {
|
|
28
|
-
"@types/jest": "^29.5.2",
|
|
29
|
-
"jest": "^29.5.0",
|
|
30
|
-
"prettier": "^1.19.1",
|
|
31
|
-
"ts-jest": "^29.1.1",
|
|
32
|
-
"tslint": "^5.11.0",
|
|
33
|
-
"tslint-config-prettier": "^1.18.0",
|
|
34
|
-
"typescript": "^4.0.0"
|
|
35
|
-
},
|
|
36
|
-
"peerDependencies": {
|
|
37
|
-
"typescript": ">=4.0.0"
|
|
38
|
-
},
|
|
39
|
-
"peerDependenciesMeta": {
|
|
40
|
-
"typescript": {
|
|
41
|
-
"optional": true
|
|
42
|
-
}
|
|
43
|
-
},
|
|
44
|
-
"jest": {
|
|
45
|
-
"moduleFileExtensions": [
|
|
46
|
-
"js",
|
|
47
|
-
"json",
|
|
48
|
-
"ts"
|
|
49
|
-
],
|
|
50
|
-
"rootDir": "src",
|
|
51
|
-
"testRegex": ".spec.ts$",
|
|
52
|
-
"transform": {
|
|
53
|
-
"^.+\\.(t|j)s$": "ts-jest"
|
|
54
|
-
},
|
|
55
|
-
"coverageDirectory": "../coverage",
|
|
56
|
-
"testEnvironment": "node"
|
|
57
|
-
},
|
|
58
|
-
"dependencies": {
|
|
59
|
-
"@contrail/app-framework": "^1.4.3",
|
|
60
|
-
"@contrail/sdk": "^1.5.10",
|
|
61
|
-
"@contrail/transform-data": "^1.3.2",
|
|
62
|
-
"@contrail/util": "^1.3.1",
|
|
63
|
-
"axios": "^1.4.0",
|
|
64
|
-
"p-limit": "^3.1.0"
|
|
65
|
-
}
|
|
66
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@contrail/flexplm",
|
|
3
|
+
"version": "1.7.4-alpha.458d29b",
|
|
4
|
+
"description": "Library used for integration with flexplm.",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"types": "lib/index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"flexplm-mapping": "lib/cli/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"lib/**/*",
|
|
12
|
+
"scripts/copy-template.js"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc; node scripts/copy-template.js",
|
|
16
|
+
"build:win": "tsc && node scripts/copy-template.js",
|
|
17
|
+
"format": "prettier --write \"src/**/*.ts\" \"src/**/*.js\"",
|
|
18
|
+
"lint": "tslint -p tsconfig.json",
|
|
19
|
+
"test": "jest",
|
|
20
|
+
"test-watch": "jest --watch",
|
|
21
|
+
"test-debug": "jest --runInBand",
|
|
22
|
+
"test-coverage": "jest --coverage"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [],
|
|
25
|
+
"author": "VibeIQ",
|
|
26
|
+
"license": "ISC",
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/jest": "^29.5.2",
|
|
29
|
+
"jest": "^29.5.0",
|
|
30
|
+
"prettier": "^1.19.1",
|
|
31
|
+
"ts-jest": "^29.1.1",
|
|
32
|
+
"tslint": "^5.11.0",
|
|
33
|
+
"tslint-config-prettier": "^1.18.0",
|
|
34
|
+
"typescript": "^4.0.0"
|
|
35
|
+
},
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"typescript": ">=4.0.0"
|
|
38
|
+
},
|
|
39
|
+
"peerDependenciesMeta": {
|
|
40
|
+
"typescript": {
|
|
41
|
+
"optional": true
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"jest": {
|
|
45
|
+
"moduleFileExtensions": [
|
|
46
|
+
"js",
|
|
47
|
+
"json",
|
|
48
|
+
"ts"
|
|
49
|
+
],
|
|
50
|
+
"rootDir": "src",
|
|
51
|
+
"testRegex": ".spec.ts$",
|
|
52
|
+
"transform": {
|
|
53
|
+
"^.+\\.(t|j)s$": "ts-jest"
|
|
54
|
+
},
|
|
55
|
+
"coverageDirectory": "../coverage",
|
|
56
|
+
"testEnvironment": "node"
|
|
57
|
+
},
|
|
58
|
+
"dependencies": {
|
|
59
|
+
"@contrail/app-framework": "^1.4.3",
|
|
60
|
+
"@contrail/sdk": "^1.5.10",
|
|
61
|
+
"@contrail/transform-data": "^1.3.2",
|
|
62
|
+
"@contrail/util": "^1.3.1",
|
|
63
|
+
"axios": "^1.4.0",
|
|
64
|
+
"p-limit": "^3.1.0"
|
|
65
|
+
}
|
|
66
|
+
}
|