@contrail/flexplm 1.6.0 → 1.6.1-alpha.4a54a48

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.
@@ -86,8 +86,10 @@ export declare class BaseProcessPublishAssortment {
86
86
  getResultsCount(events: any): any;
87
87
  private sendEvents;
88
88
  private sendToFlexPLM;
89
+ private buildPublishError;
89
90
  private saveToLocalFile;
90
91
  private handleVibeIQFile;
92
+ private sendPublishPayloadEvent;
91
93
  private getCurrentDateString;
92
94
  getItemFamilyChanges(pcd: PublishChangeData, changeDetail: any, assortmentItemFullChangeMap: Map<string, any>, assortmentItemDeleteMap: Map<string, any>): Map<string, ItemFamilyChanges>;
93
95
  getEventsForPublishChangeData(publishChangeData: PublishChangeData): Promise<SeasonalPayload[]>;
@@ -595,14 +595,37 @@ class BaseProcessPublishAssortment {
595
595
  }
596
596
  }
597
597
  async sendToFlexPLM(events, eventType) {
598
- const asyncEvent = {
598
+ const outboundPublishEvent = {
599
599
  taskId: this.config.taskId,
600
600
  eventType,
601
601
  objectClass: 'LCSSeason',
602
602
  events
603
603
  };
604
604
  const flexPLMConnect = new flexplm_connect_1.FlexPLMConnect(this.config);
605
- return await flexPLMConnect.sendToFlexPLM(asyncEvent);
605
+ const [sendResult, eventResult] = await Promise.allSettled([
606
+ flexPLMConnect.sendToFlexPLM(outboundPublishEvent),
607
+ this.sendPublishPayloadEvent(outboundPublishEvent)
608
+ ]);
609
+ if (sendResult.status === 'rejected' || eventResult.status === 'rejected') {
610
+ throw this.buildPublishError(sendResult, eventResult, outboundPublishEvent);
611
+ }
612
+ const result = sendResult.value;
613
+ const isResultObject = typeof result === 'object' && result !== null;
614
+ return isResultObject
615
+ ? { ...result, outboundPublishEvent }
616
+ : { result, outboundPublishEvent };
617
+ }
618
+ buildPublishError(sendResult, eventResult, outboundPublishEvent) {
619
+ const baseReason = sendResult.status === 'rejected' ? sendResult.reason : eventResult.reason;
620
+ const error = baseReason instanceof Error ? baseReason : new Error(String(baseReason));
621
+ error.outboundPublishEvent = outboundPublishEvent;
622
+ error.sendToFlexPLMResult = sendResult.status === 'fulfilled'
623
+ ? sendResult.value
624
+ : { error: sendResult.reason?.message ?? String(sendResult.reason) };
625
+ error.sendPublishPayloadEventResult = eventResult.status === 'fulfilled'
626
+ ? eventResult.value
627
+ : { error: eventResult.reason?.message ?? String(eventResult.reason) };
628
+ return error;
606
629
  }
607
630
  async saveToLocalFile(events, eventType) {
608
631
  let results = {};
@@ -614,15 +637,16 @@ class BaseProcessPublishAssortment {
614
637
  const eventDirName = 'events-output';
615
638
  const fileName = `${path.sep}${eventDirName}${path.sep}events-${dateString}.json`;
616
639
  fileHandle = await fsPromise.open('.' + fileName, 'a');
617
- const asyncEvent = {
640
+ const outboundPublishEvent = {
618
641
  taskId: this.config.taskId,
619
642
  eventType,
620
643
  objectClass: 'LCSSeason',
621
644
  events
622
645
  };
623
- await fileHandle.writeFile(JSON.stringify(asyncEvent));
646
+ await fileHandle.writeFile(JSON.stringify(outboundPublishEvent));
624
647
  results = {
625
648
  message: 'Successfully Saved File',
649
+ outboundPublishEvent,
626
650
  fileLocation: dirName + fileName
627
651
  };
628
652
  }
@@ -638,7 +662,7 @@ class BaseProcessPublishAssortment {
638
662
  const eventBuffer = Buffer.from(JSON.stringify(events), 'utf-8');
639
663
  const fileName = 'ASYNC_PUBLISH_SEASON-events.json';
640
664
  const uploadFile = await new sdk_1.Files().createAndUploadFileFromBuffer(eventBuffer, 'application/json', fileName, null, this.TTL);
641
- const asyncEvent = {
665
+ const outboundPublishEvent = {
642
666
  taskId: this.config.taskId,
643
667
  eventType,
644
668
  objectClass: 'LCSSeason',
@@ -648,15 +672,46 @@ class BaseProcessPublishAssortment {
648
672
  };
649
673
  if (sendMode === 'vibeiqfile') {
650
674
  const flexPLMConnect = new flexplm_connect_1.FlexPLMConnect(this.config);
651
- return await flexPLMConnect.sendToFlexPLM(asyncEvent);
675
+ const [sendResult, eventResult] = await Promise.allSettled([
676
+ flexPLMConnect.sendToFlexPLM(outboundPublishEvent),
677
+ this.sendPublishPayloadEvent(outboundPublishEvent)
678
+ ]);
679
+ if (sendResult.status === 'rejected' || eventResult.status === 'rejected') {
680
+ throw this.buildPublishError(sendResult, eventResult, outboundPublishEvent);
681
+ }
682
+ const result = sendResult.value;
683
+ const isResultObject = typeof result === 'object' && result !== null;
684
+ return isResultObject
685
+ ? { ...result, outboundPublishEvent }
686
+ : { result, outboundPublishEvent };
652
687
  }
653
688
  else {
689
+ await this.sendPublishPayloadEvent(outboundPublishEvent);
654
690
  return {
655
691
  message: 'Successfully Uploaded File.',
656
- asyncEvent
692
+ outboundPublishEvent
657
693
  };
658
694
  }
659
695
  }
696
+ async sendPublishPayloadEvent(outboundPublishEvent) {
697
+ console.info('sendPublishPayloadEvent()');
698
+ let initialEvent = {};
699
+ if (this.config?.event) {
700
+ initialEvent = (typeof this.config?.event === 'string')
701
+ ? JSON.parse(this.config?.event)
702
+ : this.config?.event;
703
+ }
704
+ const eventBody = {
705
+ originSystemType: 'VibeIQ',
706
+ objectClass: 'AssortmentPublishedToFlexPLM',
707
+ outboundPublishEvent,
708
+ initialEvent
709
+ };
710
+ await new sdk_1.Entities().create({
711
+ entityName: 'external-event',
712
+ object: eventBody
713
+ });
714
+ }
660
715
  getCurrentDateString() {
661
716
  const d = new Date();
662
717
  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 {
@@ -31,6 +32,13 @@ jest.mock('../util/federation', () => {
31
32
  });
32
33
  let entityObject = {};
33
34
  let getOptionsObject = {};
35
+ let createCallArg = undefined;
36
+ let fileUploadCalls = [];
37
+ let fileUploadResult = {
38
+ id: 'file-123',
39
+ downloadUrl: 'https://download.url',
40
+ adminDownloadUrl: 'https://admin.download.url'
41
+ };
34
42
  jest.mock('@contrail/sdk', () => {
35
43
  return {
36
44
  Entities: class {
@@ -38,6 +46,18 @@ jest.mock('@contrail/sdk', () => {
38
46
  getOptionsObject = _getOtionsObject;
39
47
  return entityObject;
40
48
  }
49
+ create(arg) {
50
+ createCallArg = arg;
51
+ return Promise.resolve({});
52
+ }
53
+ },
54
+ Files: class {
55
+ createAndUploadFileFromBuffer(buffer, mimeType, fileName, _x, ttl) {
56
+ fileUploadCalls.push({ buffer, mimeType, fileName, ttl });
57
+ return Promise.resolve(fileUploadResult);
58
+ }
59
+ },
60
+ Request: class {
41
61
  }
42
62
  };
43
63
  });
@@ -1686,3 +1706,195 @@ describe('getEventsForItemFamilyChanges - conditional eventType', () => {
1686
1706
  expect(type_conversion_utils_1.TypeConversionUtils.isOutboundCreatableFromEntity).toHaveBeenNthCalledWith(2, undefined, mapFileUtil, colorProjectItem, { item: itemData, assortment });
1687
1707
  });
1688
1708
  });
1709
+ describe('sendToFlexPLM / handleVibeIQFile / sendPublishPayloadEvent', () => {
1710
+ const config = {
1711
+ taskId: 'task-abc',
1712
+ event: '{"sourceEventId":"src-1"}'
1713
+ };
1714
+ const mapFileUtil = new transform_data_1.MapFileUtil(new sdk_1.Entities());
1715
+ const dc = new data_converter_1.DataConverter(config, mapFileUtil);
1716
+ const events = [{ objectClass: 'LCSProductSeasonLink' }];
1717
+ const eventType = 'ASYNC_PUBLISH_SEASON';
1718
+ beforeEach(() => {
1719
+ createCallArg = undefined;
1720
+ fileUploadCalls = [];
1721
+ fileUploadResult = {
1722
+ id: 'file-123',
1723
+ downloadUrl: 'https://download.url',
1724
+ adminDownloadUrl: 'https://admin.download.url'
1725
+ };
1726
+ });
1727
+ afterEach(() => {
1728
+ jest.restoreAllMocks();
1729
+ });
1730
+ it('should merge outboundPublishEvent into result and call sendPublishPayloadEvent in parallel', async () => {
1731
+ const bppa = new base_process_publish_assortment_1.BaseProcessPublishAssortment(config, dc, mapFileUtil);
1732
+ const flexResponse = { status: 200, data: { ok: true } };
1733
+ let flexResolved = false;
1734
+ let eventResolved = false;
1735
+ const spyFlex = jest.spyOn(flexplm_connect_1.FlexPLMConnect.prototype, 'sendToFlexPLM')
1736
+ .mockImplementation(async () => {
1737
+ await new Promise(r => setTimeout(r, 5));
1738
+ flexResolved = true;
1739
+ return flexResponse;
1740
+ });
1741
+ const spyEvent = jest.spyOn(bppa, 'sendPublishPayloadEvent')
1742
+ .mockImplementation(async () => {
1743
+ await new Promise(r => setTimeout(r, 5));
1744
+ eventResolved = true;
1745
+ });
1746
+ const result = await bppa.sendToFlexPLM(events, eventType);
1747
+ expect(spyFlex).toHaveBeenCalledTimes(1);
1748
+ expect(spyEvent).toHaveBeenCalledTimes(1);
1749
+ expect(flexResolved).toBe(true);
1750
+ expect(eventResolved).toBe(true);
1751
+ const passedOutboundPublishEvent = spyFlex.mock.calls[0][0];
1752
+ expect(passedOutboundPublishEvent.taskId).toBe('task-abc');
1753
+ expect(passedOutboundPublishEvent.eventType).toBe(eventType);
1754
+ expect(passedOutboundPublishEvent.objectClass).toBe('LCSSeason');
1755
+ expect(passedOutboundPublishEvent.events).toBe(events);
1756
+ expect(spyEvent).toHaveBeenCalledWith(passedOutboundPublishEvent);
1757
+ expect(result).toEqual({ ...flexResponse, outboundPublishEvent: passedOutboundPublishEvent });
1758
+ });
1759
+ it('sendToFlexPLM throws when flexPLMConnect.sendToFlexPLM fails, attaching outboundPublishEvent and both results', async () => {
1760
+ const bppa = new base_process_publish_assortment_1.BaseProcessPublishAssortment(config, dc, mapFileUtil);
1761
+ const flexError = new Error('flex failed');
1762
+ const eventResponse = { eventOk: true };
1763
+ jest.spyOn(flexplm_connect_1.FlexPLMConnect.prototype, 'sendToFlexPLM').mockRejectedValue(flexError);
1764
+ jest.spyOn(bppa, 'sendPublishPayloadEvent').mockResolvedValue(eventResponse);
1765
+ await expect(bppa.sendToFlexPLM(events, eventType)).rejects.toMatchObject({
1766
+ message: 'flex failed',
1767
+ outboundPublishEvent: expect.objectContaining({ taskId: 'task-abc', eventType, objectClass: 'LCSSeason', events }),
1768
+ sendToFlexPLMResult: { error: 'flex failed' },
1769
+ sendPublishPayloadEventResult: eventResponse
1770
+ });
1771
+ });
1772
+ it('sendToFlexPLM throws when sendPublishPayloadEvent fails, with flex result attached', async () => {
1773
+ const bppa = new base_process_publish_assortment_1.BaseProcessPublishAssortment(config, dc, mapFileUtil);
1774
+ const flexResponse = { status: 200 };
1775
+ const eventError = new Error('event failed');
1776
+ jest.spyOn(flexplm_connect_1.FlexPLMConnect.prototype, 'sendToFlexPLM').mockResolvedValue(flexResponse);
1777
+ jest.spyOn(bppa, 'sendPublishPayloadEvent').mockRejectedValue(eventError);
1778
+ await expect(bppa.sendToFlexPLM(events, eventType)).rejects.toMatchObject({
1779
+ message: 'event failed',
1780
+ outboundPublishEvent: expect.objectContaining({ taskId: 'task-abc', eventType }),
1781
+ sendToFlexPLMResult: flexResponse,
1782
+ sendPublishPayloadEventResult: { error: 'event failed' }
1783
+ });
1784
+ });
1785
+ it('sendToFlexPLM throws the flexPLMConnect error when both fail', async () => {
1786
+ const bppa = new base_process_publish_assortment_1.BaseProcessPublishAssortment(config, dc, mapFileUtil);
1787
+ const flexError = new Error('flex failed');
1788
+ const eventError = new Error('event failed');
1789
+ jest.spyOn(flexplm_connect_1.FlexPLMConnect.prototype, 'sendToFlexPLM').mockRejectedValue(flexError);
1790
+ jest.spyOn(bppa, 'sendPublishPayloadEvent').mockRejectedValue(eventError);
1791
+ await expect(bppa.sendToFlexPLM(events, eventType)).rejects.toBe(flexError);
1792
+ expect(flexError.outboundPublishEvent).toBeDefined();
1793
+ expect(flexError.sendToFlexPLMResult).toEqual({ error: 'flex failed' });
1794
+ expect(flexError.sendPublishPayloadEventResult).toEqual({ error: 'event failed' });
1795
+ });
1796
+ it('handleVibeIQFile (vibeiqfile) throws when flexPLMConnect.sendToFlexPLM fails, with both results attached', async () => {
1797
+ const bppa = new base_process_publish_assortment_1.BaseProcessPublishAssortment(config, dc, mapFileUtil);
1798
+ const flexError = new Error('flex failed');
1799
+ const eventResponse = { eventOk: true };
1800
+ jest.spyOn(flexplm_connect_1.FlexPLMConnect.prototype, 'sendToFlexPLM').mockRejectedValue(flexError);
1801
+ jest.spyOn(bppa, 'sendPublishPayloadEvent').mockResolvedValue(eventResponse);
1802
+ await expect(bppa.handleVibeIQFile(events, eventType, 'vibeiqfile')).rejects.toMatchObject({
1803
+ message: 'flex failed',
1804
+ outboundPublishEvent: expect.objectContaining({ eventsFileId: 'file-123' }),
1805
+ sendToFlexPLMResult: { error: 'flex failed' },
1806
+ sendPublishPayloadEventResult: eventResponse
1807
+ });
1808
+ });
1809
+ it('handleVibeIQFile (vibeiqfile) throws when sendPublishPayloadEvent fails, with flex result attached', async () => {
1810
+ const bppa = new base_process_publish_assortment_1.BaseProcessPublishAssortment(config, dc, mapFileUtil);
1811
+ const flexResponse = { status: 200 };
1812
+ const eventError = new Error('event failed');
1813
+ jest.spyOn(flexplm_connect_1.FlexPLMConnect.prototype, 'sendToFlexPLM').mockResolvedValue(flexResponse);
1814
+ jest.spyOn(bppa, 'sendPublishPayloadEvent').mockRejectedValue(eventError);
1815
+ await expect(bppa.handleVibeIQFile(events, eventType, 'vibeiqfile')).rejects.toMatchObject({
1816
+ message: 'event failed',
1817
+ outboundPublishEvent: expect.objectContaining({ eventsFileId: 'file-123' }),
1818
+ sendToFlexPLMResult: flexResponse,
1819
+ sendPublishPayloadEventResult: { error: 'event failed' }
1820
+ });
1821
+ });
1822
+ it('handleVibeIQFile (vibeiqfile) throws the flexPLMConnect error when both fail', async () => {
1823
+ const bppa = new base_process_publish_assortment_1.BaseProcessPublishAssortment(config, dc, mapFileUtil);
1824
+ const flexError = new Error('flex failed');
1825
+ const eventError = new Error('event failed');
1826
+ jest.spyOn(flexplm_connect_1.FlexPLMConnect.prototype, 'sendToFlexPLM').mockRejectedValue(flexError);
1827
+ jest.spyOn(bppa, 'sendPublishPayloadEvent').mockRejectedValue(eventError);
1828
+ await expect(bppa.handleVibeIQFile(events, eventType, 'vibeiqfile')).rejects.toBe(flexError);
1829
+ expect(flexError.sendToFlexPLMResult).toEqual({ error: 'flex failed' });
1830
+ expect(flexError.sendPublishPayloadEventResult).toEqual({ error: 'event failed' });
1831
+ });
1832
+ it('should merge outboundPublishEvent into FlexPLM result and call sendPublishPayloadEvent when mode is vibeiqfile', async () => {
1833
+ const bppa = new base_process_publish_assortment_1.BaseProcessPublishAssortment(config, dc, mapFileUtil);
1834
+ const flexResponse = { status: 200, data: { ok: true } };
1835
+ const spyFlex = jest.spyOn(flexplm_connect_1.FlexPLMConnect.prototype, 'sendToFlexPLM')
1836
+ .mockResolvedValue(flexResponse);
1837
+ const spyEvent = jest.spyOn(bppa, 'sendPublishPayloadEvent')
1838
+ .mockResolvedValue(undefined);
1839
+ const result = await bppa.handleVibeIQFile(events, eventType, 'vibeiqfile');
1840
+ expect(fileUploadCalls).toHaveLength(1);
1841
+ expect(fileUploadCalls[0].fileName).toBe('ASYNC_PUBLISH_SEASON-events.json');
1842
+ expect(fileUploadCalls[0].mimeType).toBe('application/json');
1843
+ const passedOutboundPublishEvent = spyFlex.mock.calls[0][0];
1844
+ expect(passedOutboundPublishEvent.taskId).toBe('task-abc');
1845
+ expect(passedOutboundPublishEvent.eventType).toBe(eventType);
1846
+ expect(passedOutboundPublishEvent.objectClass).toBe('LCSSeason');
1847
+ expect(passedOutboundPublishEvent.eventsFileId).toBe('file-123');
1848
+ expect(passedOutboundPublishEvent.eventsDownloadLink).toBe('https://download.url');
1849
+ expect(passedOutboundPublishEvent.eventsAdminDownloadLink).toBe('https://admin.download.url');
1850
+ expect(spyFlex).toHaveBeenCalledTimes(1);
1851
+ expect(spyEvent).toHaveBeenCalledTimes(1);
1852
+ expect(spyEvent).toHaveBeenCalledWith(passedOutboundPublishEvent);
1853
+ expect(result).toEqual({ ...flexResponse, outboundPublishEvent: passedOutboundPublishEvent });
1854
+ });
1855
+ it('should skip FlexPLM but still call sendPublishPayloadEvent when mode is vibeiqfile-dontsendtoflexplm', async () => {
1856
+ const bppa = new base_process_publish_assortment_1.BaseProcessPublishAssortment(config, dc, mapFileUtil);
1857
+ const spyFlex = jest.spyOn(flexplm_connect_1.FlexPLMConnect.prototype, 'sendToFlexPLM')
1858
+ .mockResolvedValue({});
1859
+ const spyEvent = jest.spyOn(bppa, 'sendPublishPayloadEvent')
1860
+ .mockResolvedValue(undefined);
1861
+ const result = await bppa.handleVibeIQFile(events, eventType, 'vibeiqfile-dontsendtoflexplm');
1862
+ expect(spyFlex).not.toHaveBeenCalled();
1863
+ expect(spyEvent).toHaveBeenCalledTimes(1);
1864
+ const passedOutboundPublishEvent = spyEvent.mock.calls[0][0];
1865
+ expect(passedOutboundPublishEvent.eventsFileId).toBe('file-123');
1866
+ expect(result).toEqual({
1867
+ message: 'Successfully Uploaded File.',
1868
+ outboundPublishEvent: passedOutboundPublishEvent
1869
+ });
1870
+ });
1871
+ it('should create an external-event with initialEvent parsed from config', async () => {
1872
+ const bppa = new base_process_publish_assortment_1.BaseProcessPublishAssortment(config, dc, mapFileUtil);
1873
+ const outboundPublishEvent = { foo: 'bar' };
1874
+ await bppa.sendPublishPayloadEvent(outboundPublishEvent);
1875
+ expect(createCallArg).toEqual({
1876
+ entityName: 'external-event',
1877
+ object: {
1878
+ originSystemType: 'VibeIQ',
1879
+ objectClass: 'AssortmentPublishedToFlexPLM',
1880
+ outboundPublishEvent,
1881
+ initialEvent: { sourceEventId: 'src-1' }
1882
+ }
1883
+ });
1884
+ });
1885
+ it('should default initialEvent to {} when config has no event', async () => {
1886
+ const bareConfig = { taskId: 'task-x' };
1887
+ const bppa = new base_process_publish_assortment_1.BaseProcessPublishAssortment(bareConfig, dc, mapFileUtil);
1888
+ const outboundPublishEvent = { hello: 'world' };
1889
+ await bppa.sendPublishPayloadEvent(outboundPublishEvent);
1890
+ expect(createCallArg).toEqual({
1891
+ entityName: 'external-event',
1892
+ object: {
1893
+ originSystemType: 'VibeIQ',
1894
+ objectClass: 'AssortmentPublishedToFlexPLM',
1895
+ outboundPublishEvent,
1896
+ initialEvent: {}
1897
+ }
1898
+ });
1899
+ });
1900
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrail/flexplm",
3
- "version": "1.6.0",
3
+ "version": "1.6.1-alpha.4a54a48",
4
4
  "description": "Library used for integration with flexplm.",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",