@contrail/flexplm 1.7.4-alpha.72422a6 → 1.7.4-alpha.879820c
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 +60 -23
- package/lib/publish/base-process-publish-assortment.spec.js +144 -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()) {
|
|
@@ -9,6 +9,7 @@ const map_utils_1 = require("../util/map-utils");
|
|
|
9
9
|
const type_conversion_utils_1 = require("../util/type-conversion-utils");
|
|
10
10
|
const fsPromise = require("fs/promises");
|
|
11
11
|
const path = require("path");
|
|
12
|
+
const app_framework_1 = require("@contrail/app-framework");
|
|
12
13
|
const event_short_message_status_1 = require("../util/event-short-message-status");
|
|
13
14
|
class BaseProcessPublishAssortment {
|
|
14
15
|
constructor(_config, _dc, _mapFileUtil) {
|
|
@@ -28,8 +29,7 @@ class BaseProcessPublishAssortment {
|
|
|
28
29
|
let publisher;
|
|
29
30
|
const assortmentId = event.assortmentId;
|
|
30
31
|
try {
|
|
31
|
-
console.info('process-start
|
|
32
|
-
+ ', assortmentPublishChangeId: ' + assortmentPublishChangeId);
|
|
32
|
+
console.info('process-start!');
|
|
33
33
|
let seasonFed;
|
|
34
34
|
try {
|
|
35
35
|
seasonFed = await this.getSeasonFederation(assortmentId);
|
|
@@ -74,9 +74,9 @@ class BaseProcessPublishAssortment {
|
|
|
74
74
|
e.publishInfo = publishInfo;
|
|
75
75
|
}
|
|
76
76
|
catch (e2) {
|
|
77
|
-
console.
|
|
77
|
+
console.log('catch e2: ' + e2.message);
|
|
78
78
|
}
|
|
79
|
-
console.
|
|
79
|
+
console.log('catch e: ' + e.message);
|
|
80
80
|
throw e;
|
|
81
81
|
}
|
|
82
82
|
}
|
|
@@ -315,17 +315,19 @@ class BaseProcessPublishAssortment {
|
|
|
315
315
|
}
|
|
316
316
|
async downloadHydratedChangeDetail(assortmentPublishChange) {
|
|
317
317
|
try {
|
|
318
|
+
console.info('downloadHydratedChangeDetail-start');
|
|
318
319
|
const link = assortmentPublishChange?.hydratedDetailDownloadLink;
|
|
319
320
|
const response = await fetch(link);
|
|
320
321
|
const data = await response.json();
|
|
321
322
|
return data;
|
|
322
323
|
}
|
|
323
324
|
catch (e) {
|
|
324
|
-
console.
|
|
325
|
+
console.log('Error hydratedDetailDownloadLink: ' + e.message);
|
|
325
326
|
}
|
|
326
327
|
return undefined;
|
|
327
328
|
}
|
|
328
329
|
async downloadAssortmentBaseline(assortmentPublishChange) {
|
|
330
|
+
console.info('downloadAssortmentBaseline-start');
|
|
329
331
|
try {
|
|
330
332
|
const link = assortmentPublishChange?.assortmentBaselineDownloadLink;
|
|
331
333
|
const response = await fetch(link);
|
|
@@ -333,12 +335,15 @@ class BaseProcessPublishAssortment {
|
|
|
333
335
|
return data;
|
|
334
336
|
}
|
|
335
337
|
catch (e) {
|
|
336
|
-
console.
|
|
338
|
+
console.log('Error assortmentBaselineDownloadLink: ' + e.message);
|
|
337
339
|
}
|
|
338
340
|
return undefined;
|
|
339
341
|
}
|
|
340
342
|
async getDeleteChanges(assortmentPublishChange, apcHistory, assortmentBaseline, sinceDate) {
|
|
343
|
+
console.info('getDeleteChanges(): ' + assortmentPublishChange?.id);
|
|
344
|
+
console.info(sinceDate);
|
|
341
345
|
const currentAPCIndex = apcHistory.findIndex(pc => pc?.id === assortmentPublishChange?.id);
|
|
346
|
+
console.info(' currentAPCIndex: ' + currentAPCIndex);
|
|
342
347
|
if (currentAPCIndex == 0) {
|
|
343
348
|
return [];
|
|
344
349
|
}
|
|
@@ -347,13 +352,16 @@ class BaseProcessPublishAssortment {
|
|
|
347
352
|
const apcDeletes = await this.downloadDeleteChanges(assortmentPublishChange);
|
|
348
353
|
let previousApcDate = Date.parse(apcCreatedOn);
|
|
349
354
|
const sinceDateMilliseconds = sinceDate.getTime();
|
|
350
|
-
|
|
351
|
-
|
|
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
|
+
}
|
|
352
361
|
//if only 1 apc, no processing needed
|
|
353
362
|
if (sinceDateMilliseconds !== previousApcDate) {
|
|
363
|
+
console.info('sinceDateMilliseconds !== apcDateMilliseconds');
|
|
354
364
|
const currentAssortmentItemIds = this.getBaselineItemIds(assortmentBaseline);
|
|
355
|
-
let scannedCount = 0;
|
|
356
|
-
let addedCount = 0;
|
|
357
365
|
const deleteIds = (apcDeletes.length === 0)
|
|
358
366
|
? []
|
|
359
367
|
: apcDeletes.map(item => item?.itemId);
|
|
@@ -368,20 +376,19 @@ class BaseProcessPublishAssortment {
|
|
|
368
376
|
? apcHistory[i - 1]
|
|
369
377
|
: undefined;
|
|
370
378
|
const deleteChanges = await this.buildDeleteChanges(workingAPC, previousApc);
|
|
379
|
+
console.info('checking deleteChanges');
|
|
371
380
|
for (const dItem of deleteChanges) {
|
|
372
381
|
const dItemId = dItem?.itemId;
|
|
373
|
-
|
|
382
|
+
console.info(dItemId);
|
|
374
383
|
if (!currentAssortmentItemIds.includes(dItemId) && !deleteIds.includes(dItemId)) {
|
|
375
|
-
|
|
384
|
+
console.info('adding');
|
|
376
385
|
deleteIds.push(dItemId);
|
|
377
386
|
apcDeletes.push(dItem);
|
|
378
387
|
}
|
|
379
388
|
}
|
|
380
389
|
}
|
|
381
390
|
}
|
|
382
|
-
console.info('getDeleteChanges():
|
|
383
|
-
+ ', scanned ' + scannedCount + ' historical delete changes, added ' + addedCount
|
|
384
|
-
+ ', deleteIds: ' + deleteIds.length);
|
|
391
|
+
console.info('getDeleteChanges()-currentAssortmentItemIds: ' + currentAssortmentItemIds);
|
|
385
392
|
console.info('getDeleteChanges()-deleteIds: ' + deleteIds);
|
|
386
393
|
}
|
|
387
394
|
return apcDeletes;
|
|
@@ -395,6 +402,7 @@ class BaseProcessPublishAssortment {
|
|
|
395
402
|
return itemIds;
|
|
396
403
|
}
|
|
397
404
|
async downloadDeleteChanges(apc) {
|
|
405
|
+
console.info('deleteDataDownloadLink-start');
|
|
398
406
|
try {
|
|
399
407
|
const link = apc?.deleteDataDownloadLink;
|
|
400
408
|
const response = await fetch(link);
|
|
@@ -402,17 +410,19 @@ class BaseProcessPublishAssortment {
|
|
|
402
410
|
return data;
|
|
403
411
|
}
|
|
404
412
|
catch (e) {
|
|
405
|
-
console.
|
|
413
|
+
console.log('Error deleteDataDownloadLink: ' + e.message);
|
|
406
414
|
}
|
|
407
415
|
return undefined;
|
|
408
416
|
}
|
|
409
417
|
async buildDeleteChanges(apc, previousApc) {
|
|
418
|
+
console.info('buildDeleteChanges()');
|
|
410
419
|
if (apc?.deleteDataDownloadLink) {
|
|
411
420
|
const deleteChanges = await this.downloadDeleteChanges(apc);
|
|
412
421
|
if (deleteChanges) {
|
|
413
422
|
return deleteChanges;
|
|
414
423
|
}
|
|
415
424
|
}
|
|
425
|
+
console.info('pulling down full APC');
|
|
416
426
|
apc = await this.downloadAssortmentPublishChange(apc?.assortmentId, apc?.id);
|
|
417
427
|
if (apc?.deleteDataDownloadLink) {
|
|
418
428
|
const deleteChanges = await this.downloadDeleteChanges(apc);
|
|
@@ -424,18 +434,19 @@ class BaseProcessPublishAssortment {
|
|
|
424
434
|
throw new Error(BaseProcessPublishAssortment.NOT_ABLE_TO_PROCESS_DELETE_CHANGES);
|
|
425
435
|
}
|
|
426
436
|
let previousBaseline;
|
|
437
|
+
console.info('check previousApc');
|
|
427
438
|
if (previousApc?.assortmentBaselineDownloadLink) {
|
|
428
439
|
previousBaseline = await this.downloadAssortmentBaseline(previousApc);
|
|
429
440
|
}
|
|
430
441
|
else {
|
|
442
|
+
console.info('previousApc pulling down full APC');
|
|
431
443
|
previousApc = await this.downloadAssortmentPublishChange(previousApc?.assortmentId, previousApc?.id);
|
|
432
444
|
previousBaseline = await this.downloadAssortmentBaseline(previousApc);
|
|
433
445
|
}
|
|
434
446
|
const deleteIds = apc?.detail?.deletes.map(dItem => dItem?.id);
|
|
435
447
|
//building deletes based on previous baseline; because some APCs don't have delete data
|
|
436
448
|
const deleteArray = previousBaseline?.assortmentItems.filter(aItem => deleteIds.includes(aItem?.itemId));
|
|
437
|
-
console.info('
|
|
438
|
-
+ '; rebuilding ' + deleteArray.length + ' deletes from previous baseline');
|
|
449
|
+
console.info('deleteArray.length: ' + deleteArray.length);
|
|
439
450
|
return deleteArray;
|
|
440
451
|
}
|
|
441
452
|
async getItemFederatedIds( /*itemIds*/) {
|
|
@@ -473,6 +484,7 @@ class BaseProcessPublishAssortment {
|
|
|
473
484
|
return deleteItems;
|
|
474
485
|
}
|
|
475
486
|
getReleasedForDevelopmentItemAndFamilyIds(fullChange, deleteChanges) {
|
|
487
|
+
console.info('getReleasedForDevelopmentItemAndFamilyIds');
|
|
476
488
|
const releasedForDevelopmentItemIds = [];
|
|
477
489
|
const itemFamilySet = new Set();
|
|
478
490
|
const assortmentItemsArray = fullChange?.assortmentItems;
|
|
@@ -524,6 +536,7 @@ class BaseProcessPublishAssortment {
|
|
|
524
536
|
return !!lifecycleStage && !this.config.itemPreDevelopmentLifecycleStages.includes(lifecycleStage);
|
|
525
537
|
}
|
|
526
538
|
async processPublish(pcd, changeDetail, fullChange, deleteChanges) {
|
|
539
|
+
console.info('processPublish-start');
|
|
527
540
|
const event = {
|
|
528
541
|
assortmentId: pcd.assortmentId,
|
|
529
542
|
assortmentPublishChangeId: pcd.assortmentPublishChangeId
|
|
@@ -707,6 +720,7 @@ class BaseProcessPublishAssortment {
|
|
|
707
720
|
+ '-' + d.getUTCMilliseconds();
|
|
708
721
|
}
|
|
709
722
|
getItemFamilyChanges(pcd, changeDetail, assortmentItemFullChangeMap, assortmentItemDeleteMap) {
|
|
723
|
+
console.info('getItemFamilyChanges-start');
|
|
710
724
|
const itemFamilyChanges = new Map();
|
|
711
725
|
const { adds, deletes, updates, familyItemsRemoved } = changeDetail;
|
|
712
726
|
const addIds = adds.map(item => item.id);
|
|
@@ -716,7 +730,7 @@ class BaseProcessPublishAssortment {
|
|
|
716
730
|
for (const [itemId, aItem] of assortmentItemFullChangeMap) {
|
|
717
731
|
const projectItem = aItem?.projectItem;
|
|
718
732
|
const itemFamilyId = aItem?.item?.itemFamilyId;
|
|
719
|
-
if (!pcd.releasedForDevelopmentItemIds.includes(itemFamilyId)
|
|
733
|
+
if (!pcd.releasedForDevelopmentItemIds.includes(itemFamilyId)) {
|
|
720
734
|
continue;
|
|
721
735
|
}
|
|
722
736
|
const ifc = itemFamilyChanges.get(itemFamilyId) || new item_family_changes_1.ItemFamilyChanges(itemFamilyId, pcd.sinceDate);
|
|
@@ -728,6 +742,20 @@ class BaseProcessPublishAssortment {
|
|
|
728
742
|
if (pcd.itemToFederatedIdMapping.has(itemId)) {
|
|
729
743
|
ifc.itemToFederatedIdMapping.set(itemId, pcd.itemToFederatedIdMapping.get(itemId));
|
|
730
744
|
}
|
|
745
|
+
// ---- NEW Step to enable ItemFamily publish ET-243: option is pre-development. It must not produce an
|
|
746
|
+
// LCSSKUSeasonLink, but a change to it still makes the family publishable. ----
|
|
747
|
+
if (!pcd.releasedForDevelopmentItemIds.includes(itemId)) {
|
|
748
|
+
const changedInWindow = addIds.includes(itemId)
|
|
749
|
+
|| deleteIds.includes(itemId)
|
|
750
|
+
|| updateIds.includes(itemId)
|
|
751
|
+
|| this.updatedSinceDate(projectItem, pcd.sinceDate)
|
|
752
|
+
|| this.updatedSinceDate(aItem?.item, pcd.sinceDate);
|
|
753
|
+
if (changedInWindow) {
|
|
754
|
+
ifc.colorSuppressed.push(itemId);
|
|
755
|
+
}
|
|
756
|
+
continue;
|
|
757
|
+
}
|
|
758
|
+
// NEW Step - ends
|
|
731
759
|
if (itemId === itemFamilyId) {
|
|
732
760
|
if (addIds.includes(itemId))
|
|
733
761
|
ifc.familyAdd = true;
|
|
@@ -743,6 +771,12 @@ class BaseProcessPublishAssortment {
|
|
|
743
771
|
ifc.familyUpdate = true;
|
|
744
772
|
}
|
|
745
773
|
}
|
|
774
|
+
// NEW Step to enable ItemFamily publish ET-243: item-level edits (incl. lifecycleStage) must publish
|
|
775
|
+
// the family. Guarded so the "first option added" flow stays suppressed. ----
|
|
776
|
+
if (!ifc.familyItemRemoved && this.updatedSinceDate(aItem?.item, pcd.sinceDate)) {
|
|
777
|
+
ifc.familyUpdate = true;
|
|
778
|
+
}
|
|
779
|
+
// NEW Step - ends
|
|
746
780
|
if (projectItem && !projectItem?.roles && aItem?.item?.roles) {
|
|
747
781
|
projectItem.roles = aItem?.item?.roles;
|
|
748
782
|
}
|
|
@@ -771,7 +805,8 @@ class BaseProcessPublishAssortment {
|
|
|
771
805
|
}
|
|
772
806
|
else {
|
|
773
807
|
if (!item) {
|
|
774
|
-
console.error('Failed to find deleted item entity
|
|
808
|
+
console.error('Failed to find deleted item entity');
|
|
809
|
+
console.error(' itemFamilyId: ' + itemFamilyId + ' -itemId: ' + itemId);
|
|
775
810
|
continue;
|
|
776
811
|
}
|
|
777
812
|
}
|
|
@@ -799,7 +834,7 @@ class BaseProcessPublishAssortment {
|
|
|
799
834
|
ifc.colorDeletes.push(itemId);
|
|
800
835
|
}
|
|
801
836
|
} //End deletes for loop
|
|
802
|
-
if (Logger.isDebugOn()) {
|
|
837
|
+
if (app_framework_1.Logger.isDebugOn()) {
|
|
803
838
|
console.debug('returning size: ' + itemFamilyChanges.size);
|
|
804
839
|
for (const [key, value] of itemFamilyChanges) {
|
|
805
840
|
console.debug(key + ' => ' + value);
|
|
@@ -808,6 +843,7 @@ class BaseProcessPublishAssortment {
|
|
|
808
843
|
return itemFamilyChanges;
|
|
809
844
|
}
|
|
810
845
|
async getEventsForPublishChangeData(publishChangeData) {
|
|
846
|
+
console.info('getEventsForPublishChangeData-start');
|
|
811
847
|
const seasonalPayloads = [];
|
|
812
848
|
for (const itemFamilyChange of publishChangeData.itemFamilyChanges.values()) {
|
|
813
849
|
const events = await this.getEventsForItemFamilyChanges(itemFamilyChange, publishChangeData.assortmentId, publishChangeData.seasonFed, publishChangeData.itemToFederatedIdMapping);
|
|
@@ -815,8 +851,6 @@ class BaseProcessPublishAssortment {
|
|
|
815
851
|
seasonalPayloads.push(...events);
|
|
816
852
|
}
|
|
817
853
|
}
|
|
818
|
-
console.info('getEventsForPublishChangeData: itemFamilies: '
|
|
819
|
-
+ publishChangeData.itemFamilyChanges.size + ', payloads: ' + seasonalPayloads.length);
|
|
820
854
|
return seasonalPayloads;
|
|
821
855
|
}
|
|
822
856
|
/**Returns the events for a given ItemFamilyChanges object
|
|
@@ -836,12 +870,15 @@ class BaseProcessPublishAssortment {
|
|
|
836
870
|
* @returns
|
|
837
871
|
*/
|
|
838
872
|
async getEventsForItemFamilyChanges(itemFamilyChanges, assortmentId, seasonFed, itemToFederatedIdMapping) {
|
|
873
|
+
console.info('getEventsForItemFamilyChanges()');
|
|
839
874
|
const events = [];
|
|
840
875
|
const LCSSeason = Object.assign({}, seasonFed);
|
|
841
876
|
const assortment = await this.getAssortment(assortmentId);
|
|
842
877
|
const projectItem = this.getProjectItem(itemFamilyChanges, itemFamilyChanges.itemFamilyId);
|
|
878
|
+
// ---- NEW Step to enable ItemFamily publish ET-243: added color suppressed length to existing condition, no other changes on familyAssortmentItem boolean
|
|
843
879
|
const familyAssortmentItem = itemFamilyChanges.familyAdd || itemFamilyChanges.familyUpdate || itemFamilyChanges.familyDelete
|
|
844
880
|
|| itemFamilyChanges.colorAdds.length > 0 || itemFamilyChanges.colorUpdates.length > 0 || itemFamilyChanges.colorDeletes.length > 0
|
|
881
|
+
|| itemFamilyChanges.colorSuppressed.length > 0
|
|
845
882
|
|| (projectItem && this.updatedSinceDate(projectItem, itemFamilyChanges.sinceDate));
|
|
846
883
|
//familyItemRemoved is used when adding the first option to an assortment
|
|
847
884
|
//and will have updates for the family item.
|
|
@@ -1337,6 +1337,150 @@ 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 = {};
|
|
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 fullChangeMap = ppa.getFullChangeAssortmentMap({
|
|
1396
|
+
assortmentItems: [optionRow(maroonId, afterSince), optionRow(limeId, afterSince)]
|
|
1397
|
+
});
|
|
1398
|
+
const pcd = buildPcd([familyId]);
|
|
1399
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, noDetailChanges(), fullChangeMap, new Map());
|
|
1400
|
+
expect(pcd.itemFamilyChanges.size).toEqual(1);
|
|
1401
|
+
const ifc = pcd.itemFamilyChanges.get(familyId);
|
|
1402
|
+
expect(ifc.itemFamilyObject.id).toEqual(familyId);
|
|
1403
|
+
expect(ifc.colorSuppressed.length).toEqual(2);
|
|
1404
|
+
// the suppressed option must stay in the map so the family's project item is reachable
|
|
1405
|
+
expect(ppa.getProjectItem(ifc, familyId)).toEqual({ id: 'pi-' + familyId, updatedOn: beforeSince });
|
|
1406
|
+
});
|
|
1407
|
+
it('does not suppress-record an unchanged concept option', () => {
|
|
1408
|
+
const ppa = buildPpa();
|
|
1409
|
+
const fullChangeMap = ppa.getFullChangeAssortmentMap({
|
|
1410
|
+
assortmentItems: [familyRow(), optionRow(maroonId, beforeSince)]
|
|
1411
|
+
});
|
|
1412
|
+
const pcd = buildPcd([familyId]);
|
|
1413
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, noDetailChanges(), fullChangeMap, new Map());
|
|
1414
|
+
const ifc = pcd.itemFamilyChanges.get(familyId);
|
|
1415
|
+
expect(ifc.colorSuppressed.length).toEqual(0);
|
|
1416
|
+
});
|
|
1417
|
+
it('picks up a concept option listed in the change detail even when timestamps are stale', () => {
|
|
1418
|
+
const ppa = buildPpa();
|
|
1419
|
+
const fullChangeMap = ppa.getFullChangeAssortmentMap({
|
|
1420
|
+
assortmentItems: [familyRow(), optionRow(maroonId, beforeSince)]
|
|
1421
|
+
});
|
|
1422
|
+
const details = noDetailChanges();
|
|
1423
|
+
details.updates = [{ id: maroonId }];
|
|
1424
|
+
const pcd = buildPcd([familyId]);
|
|
1425
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, details, fullChangeMap, new Map());
|
|
1426
|
+
expect(pcd.itemFamilyChanges.get(familyId).colorSuppressed).toEqual([maroonId]);
|
|
1427
|
+
});
|
|
1428
|
+
it('skips everything when the family itself is pre-development', () => {
|
|
1429
|
+
const ppa = buildPpa();
|
|
1430
|
+
const fullChangeMap = ppa.getFullChangeAssortmentMap({
|
|
1431
|
+
assortmentItems: [familyRow(afterSince, 'concept'), optionRow(maroonId, afterSince)]
|
|
1432
|
+
});
|
|
1433
|
+
const pcd = buildPcd([]); // nothing released
|
|
1434
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, noDetailChanges(), fullChangeMap, new Map());
|
|
1435
|
+
expect(pcd.itemFamilyChanges.size).toEqual(0);
|
|
1436
|
+
});
|
|
1437
|
+
it('still classifies released options normally', () => {
|
|
1438
|
+
const ppa = buildPpa();
|
|
1439
|
+
const fullChangeMap = ppa.getFullChangeAssortmentMap({
|
|
1440
|
+
assortmentItems: [familyRow(), optionRow(maroonId, afterSince, 'released')]
|
|
1441
|
+
});
|
|
1442
|
+
const details = noDetailChanges();
|
|
1443
|
+
details.adds = [{ id: maroonId }];
|
|
1444
|
+
const pcd = buildPcd([familyId, maroonId]);
|
|
1445
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, details, fullChangeMap, new Map());
|
|
1446
|
+
const ifc = pcd.itemFamilyChanges.get(familyId);
|
|
1447
|
+
expect(ifc.colorAdds).toEqual([maroonId]);
|
|
1448
|
+
expect(ifc.colorSuppressed.length).toEqual(0);
|
|
1449
|
+
});
|
|
1450
|
+
it('publishes a family-only row whose lifecycle just flipped (item-level edit)', () => {
|
|
1451
|
+
const ppa = buildPpa();
|
|
1452
|
+
const fullChangeMap = ppa.getFullChangeAssortmentMap({
|
|
1453
|
+
assortmentItems: [familyRow(afterSince)] // item.updatedOn moved, projectItem did not
|
|
1454
|
+
});
|
|
1455
|
+
const pcd = buildPcd([familyId]);
|
|
1456
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, noDetailChanges(), fullChangeMap, new Map());
|
|
1457
|
+
const ifc = pcd.itemFamilyChanges.get(familyId);
|
|
1458
|
+
expect(ifc.familyUpdate).toBe(true); // fails without Step 3
|
|
1459
|
+
expect(ifc.colorSuppressed.length).toEqual(0);
|
|
1460
|
+
});
|
|
1461
|
+
it('leaves an unchanged family-only row with no flags', () => {
|
|
1462
|
+
const ppa = buildPpa();
|
|
1463
|
+
const fullChangeMap = ppa.getFullChangeAssortmentMap({ assortmentItems: [familyRow()] });
|
|
1464
|
+
const pcd = buildPcd([familyId]);
|
|
1465
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, noDetailChanges(), fullChangeMap, new Map());
|
|
1466
|
+
const ifc = pcd.itemFamilyChanges.get(familyId);
|
|
1467
|
+
expect(ifc.familyAdd).toBe(false);
|
|
1468
|
+
expect(ifc.familyUpdate).toBe(false);
|
|
1469
|
+
expect(ifc.familyDelete).toBe(false);
|
|
1470
|
+
expect(ifc.colorSuppressed.length).toEqual(0);
|
|
1471
|
+
});
|
|
1472
|
+
it('does not set familyUpdate when the family item was removed', () => {
|
|
1473
|
+
const ppa = buildPpa();
|
|
1474
|
+
const fullChangeMap = ppa.getFullChangeAssortmentMap({ assortmentItems: [familyRow(afterSince)] });
|
|
1475
|
+
const details = noDetailChanges();
|
|
1476
|
+
details.familyItemsRemoved = [{ itemId: familyId }]; // note: itemId, not id
|
|
1477
|
+
const pcd = buildPcd([familyId]);
|
|
1478
|
+
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, details, fullChangeMap, new Map());
|
|
1479
|
+
const ifc = pcd.itemFamilyChanges.get(familyId);
|
|
1480
|
+
expect(ifc.familyItemRemoved).toBe(true);
|
|
1481
|
+
expect(ifc.familyUpdate).toBe(false); // fails if the !familyItemRemoved guard is dropped
|
|
1482
|
+
});
|
|
1483
|
+
});
|
|
1340
1484
|
describe('getProjectItem', () => {
|
|
1341
1485
|
const config = {
|
|
1342
1486
|
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.879820c",
|
|
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
|
+
}
|