@contrail/flexplm 1.5.0-alpha.aaef470 → 1.5.1-alpha.14abddb
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/CHANGELOG.md +6 -0
- package/lib/publish/base-process-publish-assortment.d.ts +1 -0
- package/lib/publish/base-process-publish-assortment.js +37 -7
- package/lib/publish/base-process-publish-assortment.spec.js +139 -0
- package/lib/util/data-converter.d.ts +1 -1
- package/lib/util/data-converter.js +8 -6
- package/lib/util/data-converter.spec.js +1 -1
- package/package.json +1 -1
- package/src/publish/base-process-publish-assortment.spec.ts +163 -1
- package/src/publish/base-process-publish-assortment.ts +41 -8
- package/src/util/data-converter.spec.ts +1 -1
- package/src/util/data-converter.ts +8 -6
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,12 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
|
6
6
|
Versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [1.5.1] - 2026-05-14
|
|
11
|
+
### Added
|
|
12
|
+
- Added sending an external event with the publish payload and trigger key of `VibeIQ|AssortmentPublishedToFlexPLM` to enable secondary events to run on the event / data which was generated. Also includes the payload in the data which can be passed to the next action.
|
|
13
|
+
|
|
14
|
+
## [1.5.0] - 2026-05-12
|
|
9
15
|
### Added
|
|
10
16
|
- Added support for Inbound `LCSMaterial` to sync to the entity class `item` with type path `item:material` and `itemNumber` as identifier. This is controlled by an `LCSMaterial.processAsItem` (default `false`) config default.
|
|
11
17
|
- Added optional identity-service lookup in `DataConverter.setObjectReferenceValue` for resolving inbound `object_reference` values. Enabled per referenced entity type via `config.search.<entityType>.useIdentityServiceForInboundData`. When enabled the reference is resolved via the identity service using a uniqueness pool key; otherwise behavior falls through to the existing `getAllObjectReferences` query path.
|
|
@@ -79,6 +79,7 @@ export declare class BaseProcessPublishAssortment {
|
|
|
79
79
|
private sendToFlexPLM;
|
|
80
80
|
private saveToLocalFile;
|
|
81
81
|
private handleVibeIQFile;
|
|
82
|
+
sendPublishPayloadEvent(outboundEvent: any): Promise<void>;
|
|
82
83
|
private getCurrentDateString;
|
|
83
84
|
getItemFamilyChanges(pcd: PublishChangeData, changeDetail: any, assortmentItemFullChangeMap: Map<string, any>, assortmentItemDeleteMap: Map<string, any>): Map<string, ItemFamilyChanges>;
|
|
84
85
|
getEventsForPublishChangeData(publishChangeData: PublishChangeData): Promise<SeasonalPayload[]>;
|
|
@@ -569,14 +569,19 @@ class BaseProcessPublishAssortment {
|
|
|
569
569
|
}
|
|
570
570
|
}
|
|
571
571
|
async sendToFlexPLM(events, eventType) {
|
|
572
|
-
const
|
|
572
|
+
const outboundPublishEvent = {
|
|
573
573
|
taskId: this.config.taskId,
|
|
574
574
|
eventType,
|
|
575
575
|
objectClass: 'LCSSeason',
|
|
576
576
|
events
|
|
577
577
|
};
|
|
578
578
|
const flexPLMConnect = new flexplm_connect_1.FlexPLMConnect(this.config);
|
|
579
|
-
|
|
579
|
+
const [result] = await Promise.all([
|
|
580
|
+
flexPLMConnect.sendToFlexPLM(outboundPublishEvent),
|
|
581
|
+
this.sendPublishPayloadEvent(outboundPublishEvent)
|
|
582
|
+
]);
|
|
583
|
+
result['outboundPublishEvent'] = outboundPublishEvent;
|
|
584
|
+
return result;
|
|
580
585
|
}
|
|
581
586
|
async saveToLocalFile(events, eventType) {
|
|
582
587
|
let results = {};
|
|
@@ -588,15 +593,16 @@ class BaseProcessPublishAssortment {
|
|
|
588
593
|
const eventDirName = 'events-output';
|
|
589
594
|
const fileName = `${path.sep}${eventDirName}${path.sep}events-${dateString}.json`;
|
|
590
595
|
fileHandle = await fsPromise.open('.' + fileName, 'a');
|
|
591
|
-
const
|
|
596
|
+
const outboundPublishEvent = {
|
|
592
597
|
taskId: this.config.taskId,
|
|
593
598
|
eventType,
|
|
594
599
|
objectClass: 'LCSSeason',
|
|
595
600
|
events
|
|
596
601
|
};
|
|
597
|
-
await fileHandle.writeFile(JSON.stringify(
|
|
602
|
+
await fileHandle.writeFile(JSON.stringify(outboundPublishEvent));
|
|
598
603
|
results = {
|
|
599
604
|
message: 'Successfully Saved File',
|
|
605
|
+
outboundPublishEvent,
|
|
600
606
|
fileLocation: dirName + fileName
|
|
601
607
|
};
|
|
602
608
|
}
|
|
@@ -612,7 +618,7 @@ class BaseProcessPublishAssortment {
|
|
|
612
618
|
const eventBuffer = Buffer.from(JSON.stringify(events), 'utf-8');
|
|
613
619
|
const fileName = 'ASYNC_PUBLISH_SEASON-events.json';
|
|
614
620
|
const uploadFile = await new sdk_1.Files().createAndUploadFileFromBuffer(eventBuffer, 'application/json', fileName, null, this.TTL);
|
|
615
|
-
const
|
|
621
|
+
const outboundPublishEvent = {
|
|
616
622
|
taskId: this.config.taskId,
|
|
617
623
|
eventType,
|
|
618
624
|
objectClass: 'LCSSeason',
|
|
@@ -622,15 +628,39 @@ class BaseProcessPublishAssortment {
|
|
|
622
628
|
};
|
|
623
629
|
if (sendMode === 'vibeiqfile') {
|
|
624
630
|
const flexPLMConnect = new flexplm_connect_1.FlexPLMConnect(this.config);
|
|
625
|
-
|
|
631
|
+
const [result] = await Promise.all([
|
|
632
|
+
flexPLMConnect.sendToFlexPLM(outboundPublishEvent),
|
|
633
|
+
this.sendPublishPayloadEvent(outboundPublishEvent)
|
|
634
|
+
]);
|
|
635
|
+
return { ...result, outboundPublishEvent };
|
|
626
636
|
}
|
|
627
637
|
else {
|
|
638
|
+
await this.sendPublishPayloadEvent(outboundPublishEvent);
|
|
628
639
|
return {
|
|
629
640
|
message: 'Successfully Uploaded File.',
|
|
630
|
-
|
|
641
|
+
outboundPublishEvent
|
|
631
642
|
};
|
|
632
643
|
}
|
|
633
644
|
}
|
|
645
|
+
async sendPublishPayloadEvent(outboundEvent) {
|
|
646
|
+
console.info('sendPublishPayloadEvent()');
|
|
647
|
+
let initialEvent = {};
|
|
648
|
+
if (this.config?.event) {
|
|
649
|
+
initialEvent = (typeof this.config?.event === 'string')
|
|
650
|
+
? JSON.parse(this.config?.event)
|
|
651
|
+
: this.config?.event;
|
|
652
|
+
}
|
|
653
|
+
const eventBody = {
|
|
654
|
+
originSystemType: 'VibeIQ',
|
|
655
|
+
objectClass: 'AssortmentPublishedToFlexPLM',
|
|
656
|
+
outboundEvent,
|
|
657
|
+
initialEvent
|
|
658
|
+
};
|
|
659
|
+
await new sdk_1.Entities().create({
|
|
660
|
+
entityName: 'external-event',
|
|
661
|
+
object: eventBody
|
|
662
|
+
});
|
|
663
|
+
}
|
|
634
664
|
getCurrentDateString() {
|
|
635
665
|
const d = new Date();
|
|
636
666
|
return '' + d.getUTCFullYear()
|
|
@@ -9,6 +9,7 @@ const sdk_1 = require("@contrail/sdk");
|
|
|
9
9
|
const type_conversion_utils_1 = require("../util/type-conversion-utils");
|
|
10
10
|
const map_utils_1 = require("../util/map-utils");
|
|
11
11
|
const item_family_changes_1 = require("../interfaces/item-family-changes");
|
|
12
|
+
const flexplm_connect_1 = require("../util/flexplm-connect");
|
|
12
13
|
let federatedId = '';
|
|
13
14
|
jest.mock('../util/data-converter', () => {
|
|
14
15
|
return {
|
|
@@ -30,6 +31,13 @@ jest.mock('../util/federation', () => {
|
|
|
30
31
|
});
|
|
31
32
|
let entityObject = {};
|
|
32
33
|
let getOptionsObject = {};
|
|
34
|
+
let createCallArg = undefined;
|
|
35
|
+
let fileUploadCalls = [];
|
|
36
|
+
let fileUploadResult = {
|
|
37
|
+
id: 'file-123',
|
|
38
|
+
downloadUrl: 'https://download.url',
|
|
39
|
+
adminDownloadUrl: 'https://admin.download.url'
|
|
40
|
+
};
|
|
33
41
|
jest.mock('@contrail/sdk', () => {
|
|
34
42
|
return {
|
|
35
43
|
Entities: class {
|
|
@@ -37,6 +45,18 @@ jest.mock('@contrail/sdk', () => {
|
|
|
37
45
|
getOptionsObject = _getOtionsObject;
|
|
38
46
|
return entityObject;
|
|
39
47
|
}
|
|
48
|
+
create(arg) {
|
|
49
|
+
createCallArg = arg;
|
|
50
|
+
return Promise.resolve({});
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
Files: class {
|
|
54
|
+
createAndUploadFileFromBuffer(buffer, mimeType, fileName, _x, ttl) {
|
|
55
|
+
fileUploadCalls.push({ buffer, mimeType, fileName, ttl });
|
|
56
|
+
return Promise.resolve(fileUploadResult);
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
Request: class {
|
|
40
60
|
}
|
|
41
61
|
};
|
|
42
62
|
});
|
|
@@ -1668,3 +1688,122 @@ describe('getEventsForItemFamilyChanges - conditional eventType', () => {
|
|
|
1668
1688
|
expect(type_conversion_utils_1.TypeConversionUtils.isOutboundCreatableFromEntity).toHaveBeenNthCalledWith(2, undefined, mapFileUtil, colorProjectItem, { item: itemData, assortment });
|
|
1669
1689
|
});
|
|
1670
1690
|
});
|
|
1691
|
+
describe('sendToFlexPLM / handleVibeIQFile / sendPublishPayloadEvent', () => {
|
|
1692
|
+
const config = {
|
|
1693
|
+
taskId: 'task-abc',
|
|
1694
|
+
event: '{"sourceEventId":"src-1"}'
|
|
1695
|
+
};
|
|
1696
|
+
const mapFileUtil = new transform_data_1.MapFileUtil(new sdk_1.Entities());
|
|
1697
|
+
const dc = new data_converter_1.DataConverter(config, mapFileUtil);
|
|
1698
|
+
const events = [{ objectClass: 'LCSProductSeasonLink' }];
|
|
1699
|
+
const eventType = 'ASYNC_PUBLISH_SEASON';
|
|
1700
|
+
beforeEach(() => {
|
|
1701
|
+
createCallArg = undefined;
|
|
1702
|
+
fileUploadCalls = [];
|
|
1703
|
+
fileUploadResult = {
|
|
1704
|
+
id: 'file-123',
|
|
1705
|
+
downloadUrl: 'https://download.url',
|
|
1706
|
+
adminDownloadUrl: 'https://admin.download.url'
|
|
1707
|
+
};
|
|
1708
|
+
});
|
|
1709
|
+
afterEach(() => {
|
|
1710
|
+
jest.restoreAllMocks();
|
|
1711
|
+
});
|
|
1712
|
+
it('sendToFlexPLM merges outboundPublishEvent into result and calls sendPublishPayloadEvent in parallel', async () => {
|
|
1713
|
+
const bppa = new base_process_publish_assortment_1.BaseProcessPublishAssortment(config, dc, mapFileUtil);
|
|
1714
|
+
const flexResponse = { status: 200, data: { ok: true } };
|
|
1715
|
+
let flexResolved = false;
|
|
1716
|
+
let eventResolved = false;
|
|
1717
|
+
const spyFlex = jest.spyOn(flexplm_connect_1.FlexPLMConnect.prototype, 'sendToFlexPLM')
|
|
1718
|
+
.mockImplementation(async () => {
|
|
1719
|
+
await new Promise(r => setTimeout(r, 5));
|
|
1720
|
+
flexResolved = true;
|
|
1721
|
+
return flexResponse;
|
|
1722
|
+
});
|
|
1723
|
+
const spyEvent = jest.spyOn(bppa, 'sendPublishPayloadEvent')
|
|
1724
|
+
.mockImplementation(async () => {
|
|
1725
|
+
await new Promise(r => setTimeout(r, 5));
|
|
1726
|
+
eventResolved = true;
|
|
1727
|
+
});
|
|
1728
|
+
const result = await bppa.sendToFlexPLM(events, eventType);
|
|
1729
|
+
expect(spyFlex).toHaveBeenCalledTimes(1);
|
|
1730
|
+
expect(spyEvent).toHaveBeenCalledTimes(1);
|
|
1731
|
+
expect(flexResolved).toBe(true);
|
|
1732
|
+
expect(eventResolved).toBe(true);
|
|
1733
|
+
const passedOutboundPublishEvent = spyFlex.mock.calls[0][0];
|
|
1734
|
+
expect(passedOutboundPublishEvent.taskId).toBe('task-abc');
|
|
1735
|
+
expect(passedOutboundPublishEvent.eventType).toBe(eventType);
|
|
1736
|
+
expect(passedOutboundPublishEvent.objectClass).toBe('LCSSeason');
|
|
1737
|
+
expect(passedOutboundPublishEvent.events).toBe(events);
|
|
1738
|
+
expect(spyEvent).toHaveBeenCalledWith(passedOutboundPublishEvent);
|
|
1739
|
+
expect(result).toEqual({ ...flexResponse, outboundPublishEvent: passedOutboundPublishEvent });
|
|
1740
|
+
});
|
|
1741
|
+
it('handleVibeIQFile (vibeiqfile) merges outboundPublishEvent into FlexPLM result and calls sendPublishPayloadEvent', async () => {
|
|
1742
|
+
const bppa = new base_process_publish_assortment_1.BaseProcessPublishAssortment(config, dc, mapFileUtil);
|
|
1743
|
+
const flexResponse = { status: 200, data: { ok: true } };
|
|
1744
|
+
const spyFlex = jest.spyOn(flexplm_connect_1.FlexPLMConnect.prototype, 'sendToFlexPLM')
|
|
1745
|
+
.mockResolvedValue(flexResponse);
|
|
1746
|
+
const spyEvent = jest.spyOn(bppa, 'sendPublishPayloadEvent')
|
|
1747
|
+
.mockResolvedValue(undefined);
|
|
1748
|
+
const result = await bppa.handleVibeIQFile(events, eventType, 'vibeiqfile');
|
|
1749
|
+
expect(fileUploadCalls).toHaveLength(1);
|
|
1750
|
+
expect(fileUploadCalls[0].fileName).toBe('ASYNC_PUBLISH_SEASON-events.json');
|
|
1751
|
+
expect(fileUploadCalls[0].mimeType).toBe('application/json');
|
|
1752
|
+
const passedOutboundPublishEvent = spyFlex.mock.calls[0][0];
|
|
1753
|
+
expect(passedOutboundPublishEvent.taskId).toBe('task-abc');
|
|
1754
|
+
expect(passedOutboundPublishEvent.eventType).toBe(eventType);
|
|
1755
|
+
expect(passedOutboundPublishEvent.objectClass).toBe('LCSSeason');
|
|
1756
|
+
expect(passedOutboundPublishEvent.eventsFileId).toBe('file-123');
|
|
1757
|
+
expect(passedOutboundPublishEvent.eventsDownloadLink).toBe('https://download.url');
|
|
1758
|
+
expect(passedOutboundPublishEvent.eventsAdminDownloadLink).toBe('https://admin.download.url');
|
|
1759
|
+
expect(spyFlex).toHaveBeenCalledTimes(1);
|
|
1760
|
+
expect(spyEvent).toHaveBeenCalledTimes(1);
|
|
1761
|
+
expect(spyEvent).toHaveBeenCalledWith(passedOutboundPublishEvent);
|
|
1762
|
+
expect(result).toEqual({ ...flexResponse, outboundPublishEvent: passedOutboundPublishEvent });
|
|
1763
|
+
});
|
|
1764
|
+
it('handleVibeIQFile (vibeiqfile-dontsendtoflexplm) skips FlexPLM but still calls sendPublishPayloadEvent', async () => {
|
|
1765
|
+
const bppa = new base_process_publish_assortment_1.BaseProcessPublishAssortment(config, dc, mapFileUtil);
|
|
1766
|
+
const spyFlex = jest.spyOn(flexplm_connect_1.FlexPLMConnect.prototype, 'sendToFlexPLM')
|
|
1767
|
+
.mockResolvedValue({});
|
|
1768
|
+
const spyEvent = jest.spyOn(bppa, 'sendPublishPayloadEvent')
|
|
1769
|
+
.mockResolvedValue(undefined);
|
|
1770
|
+
const result = await bppa.handleVibeIQFile(events, eventType, 'vibeiqfile-dontsendtoflexplm');
|
|
1771
|
+
expect(spyFlex).not.toHaveBeenCalled();
|
|
1772
|
+
expect(spyEvent).toHaveBeenCalledTimes(1);
|
|
1773
|
+
const passedOutboundPublishEvent = spyEvent.mock.calls[0][0];
|
|
1774
|
+
expect(passedOutboundPublishEvent.eventsFileId).toBe('file-123');
|
|
1775
|
+
expect(result).toEqual({
|
|
1776
|
+
message: 'Successfully Uploaded File.',
|
|
1777
|
+
outboundPublishEvent: passedOutboundPublishEvent
|
|
1778
|
+
});
|
|
1779
|
+
});
|
|
1780
|
+
it('sendPublishPayloadEvent creates an external-event with initialEvent parsed from config', async () => {
|
|
1781
|
+
const bppa = new base_process_publish_assortment_1.BaseProcessPublishAssortment(config, dc, mapFileUtil);
|
|
1782
|
+
const outboundEvent = { foo: 'bar' };
|
|
1783
|
+
await bppa.sendPublishPayloadEvent(outboundEvent);
|
|
1784
|
+
expect(createCallArg).toEqual({
|
|
1785
|
+
entityName: 'external-event',
|
|
1786
|
+
object: {
|
|
1787
|
+
originSystemType: 'VibeIQ',
|
|
1788
|
+
objectClass: 'AssortmentPublishedToFlexPLM',
|
|
1789
|
+
outboundEvent,
|
|
1790
|
+
initialEvent: { sourceEventId: 'src-1' }
|
|
1791
|
+
}
|
|
1792
|
+
});
|
|
1793
|
+
});
|
|
1794
|
+
it('sendPublishPayloadEvent defaults initialEvent to {} when config has no event', async () => {
|
|
1795
|
+
const bareConfig = { taskId: 'task-x' };
|
|
1796
|
+
const bppa = new base_process_publish_assortment_1.BaseProcessPublishAssortment(bareConfig, dc, mapFileUtil);
|
|
1797
|
+
const outboundEvent = { hello: 'world' };
|
|
1798
|
+
await bppa.sendPublishPayloadEvent(outboundEvent);
|
|
1799
|
+
expect(createCallArg).toEqual({
|
|
1800
|
+
entityName: 'external-event',
|
|
1801
|
+
object: {
|
|
1802
|
+
originSystemType: 'VibeIQ',
|
|
1803
|
+
objectClass: 'AssortmentPublishedToFlexPLM',
|
|
1804
|
+
outboundEvent,
|
|
1805
|
+
initialEvent: {}
|
|
1806
|
+
}
|
|
1807
|
+
});
|
|
1808
|
+
});
|
|
1809
|
+
});
|
|
@@ -32,7 +32,7 @@ export declare class DataConverter {
|
|
|
32
32
|
private buildObjectReferenceContext;
|
|
33
33
|
private lookupObjectReferenceViaIdentityService;
|
|
34
34
|
private lookupObjectReferenceViaQuery;
|
|
35
|
-
private
|
|
35
|
+
private pickSingleResult;
|
|
36
36
|
getAllObjectReferences(entityType: string, rootTypeCriteria: any, postProcessCriteria?: any): Promise<any[]>;
|
|
37
37
|
checkKeysAndValues(criteria: any, arrayOfObjects: any, entityTypePath: any): any[];
|
|
38
38
|
filterOutArchivedAndTrashedEntities(entities: any[]): any[];
|
|
@@ -417,25 +417,27 @@ class DataConverter {
|
|
|
417
417
|
entityName: 'identity',
|
|
418
418
|
criteria: { poolKey, propertyName, propertyValue }
|
|
419
419
|
}) ?? [];
|
|
420
|
-
|
|
420
|
+
const match = this.pickSingleResult(identityResults, ctx.combinedCriteria);
|
|
421
|
+
return match ? match.entityReference.split(':')[1] : "";
|
|
421
422
|
}
|
|
422
423
|
async lookupObjectReferenceViaQuery(ctx) {
|
|
423
424
|
let arrObjectReferences = await this.getAllObjectReferences(ctx.entityType, ctx.rootTypeCriteria);
|
|
424
425
|
if (ctx.entityType !== ctx.entityTypePath) {
|
|
425
426
|
arrObjectReferences = this.checkKeysAndValues(ctx.typeCriteria, arrObjectReferences, ctx.entityTypePath);
|
|
426
427
|
}
|
|
427
|
-
|
|
428
|
+
const match = this.pickSingleResult(arrObjectReferences, ctx.combinedCriteria);
|
|
429
|
+
return match ? match.id : "";
|
|
428
430
|
}
|
|
429
|
-
|
|
431
|
+
pickSingleResult(results, combinedCriteria) {
|
|
430
432
|
if (!results.length) {
|
|
431
433
|
console.warn(`The passed in object reference criteria ${JSON.stringify(combinedCriteria)} didn't match any entities.`);
|
|
432
|
-
return
|
|
434
|
+
return undefined;
|
|
433
435
|
}
|
|
434
436
|
if (results.length > 1) {
|
|
435
437
|
console.warn(`The passed in object reference criteria has duplicate records found ${JSON.stringify(combinedCriteria)}.`);
|
|
436
|
-
return
|
|
438
|
+
return undefined;
|
|
437
439
|
}
|
|
438
|
-
return
|
|
440
|
+
return results[0];
|
|
439
441
|
}
|
|
440
442
|
async getAllObjectReferences(entityType, rootTypeCriteria, postProcessCriteria = null) {
|
|
441
443
|
const entities = new sdk_1.Entities();
|
|
@@ -826,7 +826,7 @@ describe('setObjectReferenceValue - identity service', () => {
|
|
|
826
826
|
checkSpy.mockRestore();
|
|
827
827
|
}
|
|
828
828
|
});
|
|
829
|
-
it('
|
|
829
|
+
it('pickSingleResult single result - identity branch parses id from entityReference', async () => {
|
|
830
830
|
const config = baseConfig();
|
|
831
831
|
config.search = { item: { useIdentityServiceForInboundData: true } };
|
|
832
832
|
const dc = new data_converter_1.DataConverter(config, new transform_data_1.MapFileUtil(new sdk_1.Entities()));
|
package/package.json
CHANGED
|
@@ -8,6 +8,7 @@ import { Entities } from '@contrail/sdk';
|
|
|
8
8
|
import { TypeConversionUtils } from '../util/type-conversion-utils';
|
|
9
9
|
import { MapUtil } from '../util/map-utils';
|
|
10
10
|
import { ItemFamilyChanges } from '../interfaces/item-family-changes';
|
|
11
|
+
import { FlexPLMConnect } from '../util/flexplm-connect';
|
|
11
12
|
|
|
12
13
|
let federatedId = '';
|
|
13
14
|
jest.mock('../util/data-converter', () => {
|
|
@@ -32,6 +33,13 @@ jest.mock('../util/federation', () => {
|
|
|
32
33
|
});
|
|
33
34
|
let entityObject = {};
|
|
34
35
|
let getOptionsObject = {};
|
|
36
|
+
let createCallArg: any = undefined;
|
|
37
|
+
let fileUploadCalls: any[] = [];
|
|
38
|
+
let fileUploadResult: any = {
|
|
39
|
+
id: 'file-123',
|
|
40
|
+
downloadUrl: 'https://download.url',
|
|
41
|
+
adminDownloadUrl: 'https://admin.download.url'
|
|
42
|
+
};
|
|
35
43
|
jest.mock('@contrail/sdk', () => {
|
|
36
44
|
return {
|
|
37
45
|
Entities: class{
|
|
@@ -39,7 +47,18 @@ jest.mock('@contrail/sdk', () => {
|
|
|
39
47
|
getOptionsObject = _getOtionsObject;
|
|
40
48
|
return entityObject;
|
|
41
49
|
}
|
|
42
|
-
|
|
50
|
+
create(arg){
|
|
51
|
+
createCallArg = arg;
|
|
52
|
+
return Promise.resolve({});
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
Files: class{
|
|
56
|
+
createAndUploadFileFromBuffer(buffer, mimeType, fileName, _x, ttl){
|
|
57
|
+
fileUploadCalls.push({ buffer, mimeType, fileName, ttl });
|
|
58
|
+
return Promise.resolve(fileUploadResult);
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
Request: class{}
|
|
43
62
|
};
|
|
44
63
|
});
|
|
45
64
|
describe('process publish assortment', () => {
|
|
@@ -1989,4 +2008,147 @@ describe('getEventsForItemFamilyChanges - conditional eventType', () => {
|
|
|
1989
2008
|
);
|
|
1990
2009
|
});
|
|
1991
2010
|
|
|
2011
|
+
});
|
|
2012
|
+
|
|
2013
|
+
describe('sendToFlexPLM / handleVibeIQFile / sendPublishPayloadEvent', () => {
|
|
2014
|
+
const config = {
|
|
2015
|
+
taskId: 'task-abc',
|
|
2016
|
+
event: '{"sourceEventId":"src-1"}'
|
|
2017
|
+
} as unknown as FCConfig;
|
|
2018
|
+
const mapFileUtil = new MapFileUtil(new Entities());
|
|
2019
|
+
const dc = new DataConverter(config, mapFileUtil);
|
|
2020
|
+
const events = [{ objectClass: 'LCSProductSeasonLink' }] as any;
|
|
2021
|
+
const eventType = 'ASYNC_PUBLISH_SEASON';
|
|
2022
|
+
|
|
2023
|
+
beforeEach(() => {
|
|
2024
|
+
createCallArg = undefined;
|
|
2025
|
+
fileUploadCalls = [];
|
|
2026
|
+
fileUploadResult = {
|
|
2027
|
+
id: 'file-123',
|
|
2028
|
+
downloadUrl: 'https://download.url',
|
|
2029
|
+
adminDownloadUrl: 'https://admin.download.url'
|
|
2030
|
+
};
|
|
2031
|
+
});
|
|
2032
|
+
afterEach(() => {
|
|
2033
|
+
jest.restoreAllMocks();
|
|
2034
|
+
});
|
|
2035
|
+
|
|
2036
|
+
it('sendToFlexPLM merges outboundPublishEvent into result and calls sendPublishPayloadEvent in parallel', async () => {
|
|
2037
|
+
const bppa = new BaseProcessPublishAssortment(config, dc, mapFileUtil);
|
|
2038
|
+
const flexResponse = { status: 200, data: { ok: true } };
|
|
2039
|
+
|
|
2040
|
+
let flexResolved = false;
|
|
2041
|
+
let eventResolved = false;
|
|
2042
|
+
const spyFlex = jest.spyOn(FlexPLMConnect.prototype, 'sendToFlexPLM')
|
|
2043
|
+
.mockImplementation(async () => {
|
|
2044
|
+
await new Promise(r => setTimeout(r, 5));
|
|
2045
|
+
flexResolved = true;
|
|
2046
|
+
return flexResponse as any;
|
|
2047
|
+
});
|
|
2048
|
+
const spyEvent = jest.spyOn(bppa, 'sendPublishPayloadEvent')
|
|
2049
|
+
.mockImplementation(async () => {
|
|
2050
|
+
await new Promise(r => setTimeout(r, 5));
|
|
2051
|
+
eventResolved = true;
|
|
2052
|
+
});
|
|
2053
|
+
|
|
2054
|
+
const result = await (bppa as any).sendToFlexPLM(events, eventType);
|
|
2055
|
+
|
|
2056
|
+
expect(spyFlex).toHaveBeenCalledTimes(1);
|
|
2057
|
+
expect(spyEvent).toHaveBeenCalledTimes(1);
|
|
2058
|
+
expect(flexResolved).toBe(true);
|
|
2059
|
+
expect(eventResolved).toBe(true);
|
|
2060
|
+
|
|
2061
|
+
const passedOutboundPublishEvent = spyFlex.mock.calls[0][0] as any;
|
|
2062
|
+
expect(passedOutboundPublishEvent.taskId).toBe('task-abc');
|
|
2063
|
+
expect(passedOutboundPublishEvent.eventType).toBe(eventType);
|
|
2064
|
+
expect(passedOutboundPublishEvent.objectClass).toBe('LCSSeason');
|
|
2065
|
+
expect(passedOutboundPublishEvent.events).toBe(events);
|
|
2066
|
+
expect(spyEvent).toHaveBeenCalledWith(passedOutboundPublishEvent);
|
|
2067
|
+
expect(result).toEqual({ ...flexResponse, outboundPublishEvent: passedOutboundPublishEvent });
|
|
2068
|
+
});
|
|
2069
|
+
|
|
2070
|
+
it('handleVibeIQFile (vibeiqfile) merges outboundPublishEvent into FlexPLM result and calls sendPublishPayloadEvent', async () => {
|
|
2071
|
+
const bppa = new BaseProcessPublishAssortment(config, dc, mapFileUtil);
|
|
2072
|
+
const flexResponse = { status: 200, data: { ok: true } };
|
|
2073
|
+
|
|
2074
|
+
const spyFlex = jest.spyOn(FlexPLMConnect.prototype, 'sendToFlexPLM')
|
|
2075
|
+
.mockResolvedValue(flexResponse as any);
|
|
2076
|
+
const spyEvent = jest.spyOn(bppa, 'sendPublishPayloadEvent')
|
|
2077
|
+
.mockResolvedValue(undefined as any);
|
|
2078
|
+
|
|
2079
|
+
const result = await (bppa as any).handleVibeIQFile(events, eventType, 'vibeiqfile');
|
|
2080
|
+
|
|
2081
|
+
expect(fileUploadCalls).toHaveLength(1);
|
|
2082
|
+
expect(fileUploadCalls[0].fileName).toBe('ASYNC_PUBLISH_SEASON-events.json');
|
|
2083
|
+
expect(fileUploadCalls[0].mimeType).toBe('application/json');
|
|
2084
|
+
|
|
2085
|
+
const passedOutboundPublishEvent = spyFlex.mock.calls[0][0] as any;
|
|
2086
|
+
expect(passedOutboundPublishEvent.taskId).toBe('task-abc');
|
|
2087
|
+
expect(passedOutboundPublishEvent.eventType).toBe(eventType);
|
|
2088
|
+
expect(passedOutboundPublishEvent.objectClass).toBe('LCSSeason');
|
|
2089
|
+
expect(passedOutboundPublishEvent.eventsFileId).toBe('file-123');
|
|
2090
|
+
expect(passedOutboundPublishEvent.eventsDownloadLink).toBe('https://download.url');
|
|
2091
|
+
expect(passedOutboundPublishEvent.eventsAdminDownloadLink).toBe('https://admin.download.url');
|
|
2092
|
+
|
|
2093
|
+
expect(spyFlex).toHaveBeenCalledTimes(1);
|
|
2094
|
+
expect(spyEvent).toHaveBeenCalledTimes(1);
|
|
2095
|
+
expect(spyEvent).toHaveBeenCalledWith(passedOutboundPublishEvent);
|
|
2096
|
+
expect(result).toEqual({ ...flexResponse, outboundPublishEvent: passedOutboundPublishEvent });
|
|
2097
|
+
});
|
|
2098
|
+
|
|
2099
|
+
it('handleVibeIQFile (vibeiqfile-dontsendtoflexplm) skips FlexPLM but still calls sendPublishPayloadEvent', async () => {
|
|
2100
|
+
const bppa = new BaseProcessPublishAssortment(config, dc, mapFileUtil);
|
|
2101
|
+
|
|
2102
|
+
const spyFlex = jest.spyOn(FlexPLMConnect.prototype, 'sendToFlexPLM')
|
|
2103
|
+
.mockResolvedValue({} as any);
|
|
2104
|
+
const spyEvent = jest.spyOn(bppa, 'sendPublishPayloadEvent')
|
|
2105
|
+
.mockResolvedValue(undefined as any);
|
|
2106
|
+
|
|
2107
|
+
const result = await (bppa as any).handleVibeIQFile(events, eventType, 'vibeiqfile-dontsendtoflexplm');
|
|
2108
|
+
|
|
2109
|
+
expect(spyFlex).not.toHaveBeenCalled();
|
|
2110
|
+
expect(spyEvent).toHaveBeenCalledTimes(1);
|
|
2111
|
+
|
|
2112
|
+
const passedOutboundPublishEvent = spyEvent.mock.calls[0][0] as any;
|
|
2113
|
+
expect(passedOutboundPublishEvent.eventsFileId).toBe('file-123');
|
|
2114
|
+
expect(result).toEqual({
|
|
2115
|
+
message: 'Successfully Uploaded File.',
|
|
2116
|
+
outboundPublishEvent: passedOutboundPublishEvent
|
|
2117
|
+
});
|
|
2118
|
+
});
|
|
2119
|
+
|
|
2120
|
+
it('sendPublishPayloadEvent creates an external-event with initialEvent parsed from config', async () => {
|
|
2121
|
+
const bppa = new BaseProcessPublishAssortment(config, dc, mapFileUtil);
|
|
2122
|
+
const outboundEvent = { foo: 'bar' };
|
|
2123
|
+
|
|
2124
|
+
await bppa.sendPublishPayloadEvent(outboundEvent);
|
|
2125
|
+
|
|
2126
|
+
expect(createCallArg).toEqual({
|
|
2127
|
+
entityName: 'external-event',
|
|
2128
|
+
object: {
|
|
2129
|
+
originSystemType: 'VibeIQ',
|
|
2130
|
+
objectClass: 'AssortmentPublishedToFlexPLM',
|
|
2131
|
+
outboundEvent,
|
|
2132
|
+
initialEvent: { sourceEventId: 'src-1' }
|
|
2133
|
+
}
|
|
2134
|
+
});
|
|
2135
|
+
});
|
|
2136
|
+
|
|
2137
|
+
it('sendPublishPayloadEvent defaults initialEvent to {} when config has no event', async () => {
|
|
2138
|
+
const bareConfig = { taskId: 'task-x' } as unknown as FCConfig;
|
|
2139
|
+
const bppa = new BaseProcessPublishAssortment(bareConfig, dc, mapFileUtil);
|
|
2140
|
+
const outboundEvent = { hello: 'world' };
|
|
2141
|
+
|
|
2142
|
+
await bppa.sendPublishPayloadEvent(outboundEvent);
|
|
2143
|
+
|
|
2144
|
+
expect(createCallArg).toEqual({
|
|
2145
|
+
entityName: 'external-event',
|
|
2146
|
+
object: {
|
|
2147
|
+
originSystemType: 'VibeIQ',
|
|
2148
|
+
objectClass: 'AssortmentPublishedToFlexPLM',
|
|
2149
|
+
outboundEvent,
|
|
2150
|
+
initialEvent: {}
|
|
2151
|
+
}
|
|
2152
|
+
});
|
|
2153
|
+
});
|
|
1992
2154
|
});
|
|
@@ -685,7 +685,7 @@ export class BaseProcessPublishAssortment {
|
|
|
685
685
|
}
|
|
686
686
|
|
|
687
687
|
private async sendToFlexPLM(events: SeasonalPayload[], eventType: string) {
|
|
688
|
-
const
|
|
688
|
+
const outboundPublishEvent: AsyncEventsPayloadType = {
|
|
689
689
|
taskId: this.config.taskId,
|
|
690
690
|
eventType,
|
|
691
691
|
objectClass: 'LCSSeason',
|
|
@@ -693,7 +693,12 @@ export class BaseProcessPublishAssortment {
|
|
|
693
693
|
};
|
|
694
694
|
|
|
695
695
|
const flexPLMConnect = new FlexPLMConnect(this.config);
|
|
696
|
-
|
|
696
|
+
const [result] = await Promise.all([
|
|
697
|
+
flexPLMConnect.sendToFlexPLM(outboundPublishEvent),
|
|
698
|
+
this.sendPublishPayloadEvent(outboundPublishEvent)
|
|
699
|
+
]);
|
|
700
|
+
result['outboundPublishEvent'] = outboundPublishEvent;
|
|
701
|
+
return result;
|
|
697
702
|
}
|
|
698
703
|
|
|
699
704
|
private async saveToLocalFile(events: SeasonalPayload[], eventType: string) {
|
|
@@ -708,17 +713,18 @@ export class BaseProcessPublishAssortment {
|
|
|
708
713
|
const fileName = `${path.sep}${eventDirName}${path.sep}events-${dateString}.json`;
|
|
709
714
|
fileHandle = await fsPromise.open('.' + fileName, 'a');
|
|
710
715
|
|
|
711
|
-
const
|
|
716
|
+
const outboundPublishEvent: AsyncEventsPayloadType = {
|
|
712
717
|
taskId: this.config.taskId,
|
|
713
718
|
eventType,
|
|
714
719
|
objectClass: 'LCSSeason',
|
|
715
720
|
events
|
|
716
721
|
};
|
|
717
722
|
|
|
718
|
-
await fileHandle.writeFile(JSON.stringify(
|
|
723
|
+
await fileHandle.writeFile(JSON.stringify(outboundPublishEvent));
|
|
719
724
|
|
|
720
725
|
results = {
|
|
721
726
|
message: 'Successfully Saved File',
|
|
727
|
+
outboundPublishEvent,
|
|
722
728
|
fileLocation: dirName + fileName
|
|
723
729
|
};
|
|
724
730
|
} catch (e) {
|
|
@@ -735,26 +741,53 @@ export class BaseProcessPublishAssortment {
|
|
|
735
741
|
const fileName = 'ASYNC_PUBLISH_SEASON-events.json';
|
|
736
742
|
const uploadFile = await new Files().createAndUploadFileFromBuffer(eventBuffer, 'application/json', fileName, null, this.TTL);
|
|
737
743
|
|
|
738
|
-
const
|
|
744
|
+
const outboundPublishEvent: AsyncEventsPayloadType = {
|
|
739
745
|
taskId: this.config.taskId,
|
|
740
746
|
eventType,
|
|
741
747
|
objectClass: 'LCSSeason',
|
|
742
748
|
eventsFileId: uploadFile.id,
|
|
743
749
|
eventsDownloadLink: uploadFile.downloadUrl,
|
|
744
750
|
eventsAdminDownloadLink: uploadFile.adminDownloadUrl
|
|
745
|
-
};
|
|
751
|
+
};
|
|
746
752
|
|
|
747
753
|
if (sendMode === 'vibeiqfile') {
|
|
748
754
|
const flexPLMConnect = new FlexPLMConnect(this.config);
|
|
749
|
-
|
|
755
|
+
const [result] = await Promise.all([
|
|
756
|
+
flexPLMConnect.sendToFlexPLM(outboundPublishEvent),
|
|
757
|
+
this.sendPublishPayloadEvent(outboundPublishEvent)
|
|
758
|
+
]);
|
|
759
|
+
return { ...result, outboundPublishEvent };
|
|
750
760
|
} else {
|
|
761
|
+
await this.sendPublishPayloadEvent(outboundPublishEvent);
|
|
751
762
|
return {
|
|
752
763
|
message: 'Successfully Uploaded File.',
|
|
753
|
-
|
|
764
|
+
outboundPublishEvent
|
|
754
765
|
};
|
|
755
766
|
}
|
|
756
767
|
}
|
|
757
768
|
|
|
769
|
+
public async sendPublishPayloadEvent(outboundEvent: any){
|
|
770
|
+
console.info('sendPublishPayloadEvent()');
|
|
771
|
+
let initialEvent = {};
|
|
772
|
+
if(this.config?.event){
|
|
773
|
+
initialEvent = (typeof this.config?.event === 'string')
|
|
774
|
+
? JSON.parse(this.config?.event)
|
|
775
|
+
: this.config?.event;
|
|
776
|
+
}
|
|
777
|
+
const eventBody = {
|
|
778
|
+
originSystemType: 'VibeIQ',
|
|
779
|
+
objectClass: 'AssortmentPublishedToFlexPLM',
|
|
780
|
+
outboundEvent,
|
|
781
|
+
initialEvent
|
|
782
|
+
};
|
|
783
|
+
|
|
784
|
+
await new Entities().create({
|
|
785
|
+
entityName: 'external-event',
|
|
786
|
+
object: eventBody
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
|
|
758
791
|
private getCurrentDateString() {
|
|
759
792
|
const d = new Date();
|
|
760
793
|
return '' + d.getUTCFullYear()
|
|
@@ -947,7 +947,7 @@ describe('setObjectReferenceValue - identity service', () => {
|
|
|
947
947
|
}
|
|
948
948
|
});
|
|
949
949
|
|
|
950
|
-
it('
|
|
950
|
+
it('pickSingleResult single result - identity branch parses id from entityReference', async () => {
|
|
951
951
|
const config = baseConfig();
|
|
952
952
|
config.search = { item: { useIdentityServiceForInboundData: true } };
|
|
953
953
|
const dc = new DataConverter(config, new MapFileUtil(new Entities()));
|
|
@@ -520,7 +520,8 @@ export class DataConverter {
|
|
|
520
520
|
criteria: { poolKey, propertyName, propertyValue }
|
|
521
521
|
}) ?? [];
|
|
522
522
|
|
|
523
|
-
|
|
523
|
+
const match = this.pickSingleResult(identityResults, ctx.combinedCriteria);
|
|
524
|
+
return match ? match.entityReference.split(':')[1] : "";
|
|
524
525
|
}
|
|
525
526
|
|
|
526
527
|
private async lookupObjectReferenceViaQuery(ctx: ObjectReferenceContext): Promise<string> {
|
|
@@ -528,19 +529,20 @@ export class DataConverter {
|
|
|
528
529
|
if (ctx.entityType !== ctx.entityTypePath) {
|
|
529
530
|
arrObjectReferences = this.checkKeysAndValues(ctx.typeCriteria, arrObjectReferences, ctx.entityTypePath);
|
|
530
531
|
}
|
|
531
|
-
|
|
532
|
+
const match = this.pickSingleResult(arrObjectReferences, ctx.combinedCriteria);
|
|
533
|
+
return match ? match.id : "";
|
|
532
534
|
}
|
|
533
535
|
|
|
534
|
-
private
|
|
536
|
+
private pickSingleResult(results: any[], combinedCriteria: any): any | undefined {
|
|
535
537
|
if (!results.length) {
|
|
536
538
|
console.warn(`The passed in object reference criteria ${JSON.stringify(combinedCriteria)} didn't match any entities.`);
|
|
537
|
-
return
|
|
539
|
+
return undefined;
|
|
538
540
|
}
|
|
539
541
|
if (results.length > 1) {
|
|
540
542
|
console.warn(`The passed in object reference criteria has duplicate records found ${JSON.stringify(combinedCriteria)}.`);
|
|
541
|
-
return
|
|
543
|
+
return undefined;
|
|
542
544
|
}
|
|
543
|
-
return
|
|
545
|
+
return results[0];
|
|
544
546
|
}
|
|
545
547
|
|
|
546
548
|
/**
|