@contrail/flexplm 1.8.1-alpha.85853aa → 2.0.0-alpha.9af4b96

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.
Files changed (36) hide show
  1. package/lib/__tests__/telemetry-test-utils.d.ts +8 -0
  2. package/lib/__tests__/telemetry-test-utils.js +30 -0
  3. package/lib/cli/commands/compile.js +17 -7
  4. package/lib/cli/commands/compile.spec.js +17 -7
  5. package/lib/cli/commands/create.js +17 -7
  6. package/lib/cli/commands/create.spec.js +17 -7
  7. package/lib/cli/commands/upload.js +17 -7
  8. package/lib/cli/index.js +2 -2
  9. package/lib/entity-processor/base-entity-processor.d.ts +29 -0
  10. package/lib/entity-processor/base-entity-processor.js +238 -143
  11. package/lib/entity-processor/base-entity-processor.spec.js +212 -0
  12. package/lib/index.d.ts +1 -0
  13. package/lib/index.js +1 -0
  14. package/lib/interfaces/interfaces.d.ts +1 -1
  15. package/lib/publish/base-process-publish-assortment-callback.js +30 -0
  16. package/lib/publish/base-process-publish-assortment-callback.spec.d.ts +1 -0
  17. package/lib/publish/base-process-publish-assortment-callback.spec.js +62 -0
  18. package/lib/publish/base-process-publish-assortment.d.ts +24 -0
  19. package/lib/publish/base-process-publish-assortment.js +243 -132
  20. package/lib/publish/base-process-publish-assortment.spec.js +188 -0
  21. package/lib/util/config-defaults.js +2 -0
  22. package/lib/util/error-response-object.js +2 -0
  23. package/lib/util/event-short-message-status.js +1 -1
  24. package/lib/util/flexplm-connect.d.ts +4 -0
  25. package/lib/util/flexplm-connect.js +210 -145
  26. package/lib/util/flexplm-connect.telemetry.spec.d.ts +1 -0
  27. package/lib/util/flexplm-connect.telemetry.spec.js +212 -0
  28. package/lib/util/logger-config.js +1 -2
  29. package/lib/util/telemetry-attributes.d.ts +68 -0
  30. package/lib/util/telemetry-attributes.js +73 -0
  31. package/lib/util/telemetry-utils.d.ts +3 -0
  32. package/lib/util/telemetry-utils.js +10 -0
  33. package/lib/util/thumbnail-util.d.ts +2 -1
  34. package/lib/util/thumbnail-util.js +35 -7
  35. package/lib/util/thumbnail-util.spec.js +42 -0
  36. package/package.json +10 -3
@@ -11,6 +11,12 @@ const fsPromise = require("fs/promises");
11
11
  const path = require("path");
12
12
  const app_framework_1 = require("@contrail/app-framework");
13
13
  const event_short_message_status_1 = require("../util/event-short-message-status");
14
+ const telemetry_1 = require("@contrail/telemetry");
15
+ const api_1 = require("@opentelemetry/api");
16
+ const telemetry_attributes_1 = require("../util/telemetry-attributes");
17
+ const telemetry_utils_1 = require("../util/telemetry-utils");
18
+ const PUBLISH_FILE_HYDRATED_DETAIL = 'hydrated_detail';
19
+ const PUBLISH_FILE_BASELINE = 'baseline';
14
20
  class BaseProcessPublishAssortment {
15
21
  constructor(_config, _dc, _mapFileUtil) {
16
22
  this.TTL = 30 * 24 * 60 * 60 * 1000; // 30 days
@@ -24,61 +30,120 @@ class BaseProcessPublishAssortment {
24
30
  this.orgSlug = this.config?.orgSlug || 'unset-orgSlug';
25
31
  }
26
32
  async process(event) {
27
- const assortmentPublishChangeId = event.assortmentPublishChangeId;
28
- let apcHistory;
29
- let publisher;
30
- const assortmentId = event.assortmentId;
31
- try {
32
- console.info('process-start: assortmentId: ' + assortmentId
33
- + ', assortmentPublishChangeId: ' + assortmentPublishChangeId);
34
- let seasonFed;
33
+ return (0, telemetry_1.traceSection)('publish_assortment', {}, async (span) => {
34
+ const assortmentPublishChangeId = event.assortmentPublishChangeId;
35
+ let apcHistory;
36
+ let publisher;
37
+ const assortmentId = event.assortmentId;
38
+ (0, telemetry_utils_1.setActiveSpanAttributes)({
39
+ [telemetry_attributes_1.ATTR_FLEXPLM_ASSORTMENT_ID]: assortmentId,
40
+ [telemetry_attributes_1.ATTR_FLEXPLM_PUBLISH_CHANGE_ID]: assortmentPublishChangeId,
41
+ });
35
42
  try {
36
- seasonFed = await this.getSeasonFederation(assortmentId);
43
+ let seasonFed;
44
+ try {
45
+ seasonFed = await this.getSeasonFederation(assortmentId);
46
+ }
47
+ catch (e) {
48
+ const message = e.message;
49
+ const eventStatus = (message.includes(BaseProcessPublishAssortment.ASSORTMENT_NOT_PUBLISHABLE)) ? event_short_message_status_1.EventShortMessageStatus.NOT_PUBLISHABLE :
50
+ (message.startsWith(BaseProcessPublishAssortment.ASSORTMENT_NO_FED_INFO)) ? event_short_message_status_1.EventShortMessageStatus.NO_FEDERATION_INFO :
51
+ event_short_message_status_1.EventShortMessageStatus.FAILURE;
52
+ if (eventStatus === event_short_message_status_1.EventShortMessageStatus.FAILURE) {
53
+ span.recordException(e);
54
+ span.setStatus({ code: api_1.SpanStatusCode.ERROR, message });
55
+ }
56
+ this.recordPublishStatus({
57
+ status: eventStatus,
58
+ assortmentId,
59
+ assortmentPublishChangeId,
60
+ message,
61
+ // Only an unexpected error is swallowed here; the other two are expected skips.
62
+ error: eventStatus === event_short_message_status_1.EventShortMessageStatus.FAILURE ? e : undefined,
63
+ });
64
+ const output = {
65
+ results: { message, event },
66
+ skip_await: true
67
+ };
68
+ return output;
69
+ }
70
+ apcHistory = await this.getApcHistory(assortmentId);
71
+ const sinceDate = await this.getSinceDate(assortmentId, assortmentPublishChangeId, apcHistory);
72
+ //Get detail information
73
+ const assortmentPublishChange = await this.downloadAssortmentPublishChange(assortmentId, assortmentPublishChangeId);
74
+ publisher = this.getPublisher(assortmentPublishChange);
75
+ const changeDetail = await this.downloadHydratedChangeDetail(assortmentPublishChange);
76
+ // Without the change detail the counts are unknown, not zero; failed_downloads says why.
77
+ if (changeDetail) {
78
+ span.setAttributes({
79
+ [telemetry_attributes_1.ATTR_FLEXPLM_PUBLISH_ADDED_COUNT]: changeDetail.adds?.length ?? 0,
80
+ [telemetry_attributes_1.ATTR_FLEXPLM_PUBLISH_REMOVED_COUNT]: changeDetail.deletes?.length ?? 0,
81
+ [telemetry_attributes_1.ATTR_FLEXPLM_PUBLISH_CHANGED_COUNT]: changeDetail.updates?.length ?? 0,
82
+ });
83
+ }
84
+ const assortmentBaseline = await this.downloadAssortmentBaseline(assortmentPublishChange);
85
+ const failedDownloads = [!changeDetail && PUBLISH_FILE_HYDRATED_DETAIL, !assortmentBaseline && PUBLISH_FILE_BASELINE].filter(Boolean);
86
+ if (failedDownloads.length > 0) {
87
+ span.setAttribute(telemetry_attributes_1.ATTR_FLEXPLM_PUBLISH_FAILED_DOWNLOADS, failedDownloads);
88
+ }
89
+ const deleteChanges = await this.getDeleteChanges(assortmentPublishChange, apcHistory, assortmentBaseline, sinceDate);
90
+ const releasedForDevelopmentItemIds = this.getReleasedForDevelopmentItemAndFamilyIds(assortmentBaseline, deleteChanges);
91
+ const itemToFederatedIdMapping = await this.getItemFederatedIds( /*allItemIds*/);
92
+ const pcd = new publish_change_data_1.PublishChangeData(assortmentId, seasonFed, assortmentPublishChangeId, sinceDate, publisher);
93
+ pcd.itemToFederatedIdMapping = itemToFederatedIdMapping;
94
+ pcd.releasedForDevelopmentItemIds = releasedForDevelopmentItemIds;
95
+ const output = await this.processPublish(pcd, changeDetail, assortmentBaseline, deleteChanges);
96
+ output['publishInfo'] = await this.getPublishInfo(assortmentId, assortmentPublishChangeId, apcHistory, publisher);
97
+ return output;
37
98
  }
38
99
  catch (e) {
39
- const message = e.message;
40
- const eventStatus = (message.includes(BaseProcessPublishAssortment.ASSORTMENT_NOT_PUBLISHABLE)) ? event_short_message_status_1.EventShortMessageStatus.NOT_PUBLISHABLE :
41
- (message.startsWith(BaseProcessPublishAssortment.ASSORTMENT_NO_FED_INFO)) ? event_short_message_status_1.EventShortMessageStatus.NO_FEDERATION_INFO :
42
- event_short_message_status_1.EventShortMessageStatus.FAILURE;
43
- const processStatus = 'BaseProcessPublishAssortment: ' + eventStatus
44
- + ': assortmentId: ' + event.assortmentId + ': assortmentPublishChangeId: ' + event.assortmentPublishChangeId
45
- + ', orgSlug: ' + this.orgSlug;
46
- console.log(JSON.stringify(processStatus));
47
- console.log(message);
48
- const output = {
49
- results: { message, event },
50
- skip_await: true
51
- };
52
- return output;
100
+ try {
101
+ const publishInfo = await this.getPublishInfo(assortmentId, assortmentPublishChangeId, apcHistory, publisher);
102
+ e.publishInfo = publishInfo;
103
+ }
104
+ catch (e2) {
105
+ const assortment = { id: assortmentId, publish_change_id: assortmentPublishChangeId };
106
+ telemetry_1.logger.error({ error: e2, assortment }, 'getPublishInfo failed: %j', assortment);
107
+ }
108
+ this.recordPublishStatus({
109
+ status: event_short_message_status_1.EventShortMessageStatus.FAILURE, assortmentId, assortmentPublishChangeId, error: e,
110
+ // sendAndRecordPublishEvent only sets this after FlexPLM accepted the publish.
111
+ sentUnrecorded: e?.sendPublishPayloadEventResult !== undefined,
112
+ });
113
+ throw e;
53
114
  }
54
- apcHistory = await this.getApcHistory(assortmentId);
55
- const sinceDate = await this.getSinceDate(assortmentId, assortmentPublishChangeId, apcHistory);
56
- //Get detail information
57
- const assortmentPublishChange = await this.downloadAssortmentPublishChange(assortmentId, assortmentPublishChangeId);
58
- publisher = this.getPublisher(assortmentPublishChange);
59
- const changeDetail = await this.downloadHydratedChangeDetail(assortmentPublishChange);
60
- const assortmentBaseline = await this.downloadAssortmentBaseline(assortmentPublishChange);
61
- const deleteChanges = await this.getDeleteChanges(assortmentPublishChange, apcHistory, assortmentBaseline, sinceDate);
62
- const releasedForDevelopmentItemIds = this.getReleasedForDevelopmentItemAndFamilyIds(assortmentBaseline, deleteChanges);
63
- const itemToFederatedIdMapping = await this.getItemFederatedIds( /*allItemIds*/);
64
- const pcd = new publish_change_data_1.PublishChangeData(assortmentId, seasonFed, assortmentPublishChangeId, sinceDate, publisher);
65
- pcd.itemToFederatedIdMapping = itemToFederatedIdMapping;
66
- pcd.releasedForDevelopmentItemIds = releasedForDevelopmentItemIds;
67
- const output = await this.processPublish(pcd, changeDetail, assortmentBaseline, deleteChanges);
68
- output['publishInfo'] = await this.getPublishInfo(assortmentId, assortmentPublishChangeId, apcHistory, publisher);
69
- console.info('process-end');
70
- return output;
115
+ });
116
+ }
117
+ /** Records the result of one assortment publish: attributes on the active span plus one structured log.
118
+ * Not_Publishable / No_Federation_Information are handled skips, so the span stays OK for them.
119
+ */
120
+ recordPublishStatus(statusObject) {
121
+ const { status, assortmentId, assortmentPublishChangeId, resultsCount, message, error } = statusObject;
122
+ const sentUnrecorded = statusObject.sentUnrecorded || undefined;
123
+ (0, telemetry_utils_1.setActiveSpanAttributes)({
124
+ [telemetry_attributes_1.ATTR_FLEXPLM_STATUS]: status,
125
+ [telemetry_attributes_1.ATTR_FLEXPLM_ASSORTMENT_ID]: assortmentId,
126
+ [telemetry_attributes_1.ATTR_FLEXPLM_PUBLISH_CHANGE_ID]: assortmentPublishChangeId,
127
+ [telemetry_attributes_1.ATTR_FLEXPLM_PUBLISH_PRODUCT_SEASON_COUNT]: resultsCount?.productSeasons,
128
+ [telemetry_attributes_1.ATTR_FLEXPLM_PUBLISH_COLORWAY_SEASON_COUNT]: resultsCount?.colorwaySeasons,
129
+ [telemetry_attributes_1.ATTR_FLEXPLM_PUBLISH_SENT_UNRECORDED]: sentUnrecorded,
130
+ });
131
+ const flexplm = {
132
+ direction: 'publish', status, assortment_id: assortmentId, publish_change_id: assortmentPublishChangeId,
133
+ product_season_count: resultsCount?.productSeasons, colorway_season_count: resultsCount?.colorwaySeasons, reason: message,
134
+ sent_unrecorded: sentUnrecorded,
135
+ };
136
+ if (sentUnrecorded) {
137
+ telemetry_1.logger.error({ error, flexplm }, 'FlexPLM assortment publish sent but not recorded: %j', flexplm);
71
138
  }
72
- catch (e) {
73
- try {
74
- const publishInfo = await this.getPublishInfo(assortmentId, assortmentPublishChangeId, apcHistory, publisher);
75
- e.publishInfo = publishInfo;
76
- }
77
- catch (e2) {
78
- console.error('getPublishInfo failed: ' + e2.message);
79
- }
80
- console.error('process failed: assortmentId: ' + assortmentId + ': ' + e.message);
81
- throw e;
139
+ else if (status === event_short_message_status_1.EventShortMessageStatus.FAILURE) {
140
+ telemetry_1.logger.error({ error, flexplm }, 'FlexPLM assortment publish failed: %j', flexplm);
141
+ }
142
+ else if (status === event_short_message_status_1.EventShortMessageStatus.NO_FEDERATION_INFO) {
143
+ telemetry_1.logger.warn({ flexplm }, 'FlexPLM assortment publish skipped: %j', flexplm);
144
+ }
145
+ else {
146
+ telemetry_1.logger.info({ flexplm }, 'FlexPLM assortment publish processed: %j', flexplm);
82
147
  }
83
148
  }
84
149
  async getPublishInfo(assortmentId, assortmentPublishChangeId, apcHistory, publisher) {
@@ -211,13 +276,18 @@ class BaseProcessPublishAssortment {
211
276
  return sinceDate;
212
277
  }
213
278
  async getApcHistory(assortmentId) {
214
- const resource = `/assortments/${assortmentId}/history/`;
215
- return sdk_1.Request.request(resource, {
216
- method: 'GET',
217
- headers: {
218
- Accept: 'application/json',
219
- 'Content-Type': 'application/json'
220
- }
279
+ return (0, telemetry_1.traceSection)('get_publish_history', {}, async (span) => {
280
+ const resource = `/assortments/${assortmentId}/history/`;
281
+ const history = await sdk_1.Request.request(resource, {
282
+ method: 'GET',
283
+ headers: {
284
+ Accept: 'application/json',
285
+ 'Content-Type': 'application/json'
286
+ }
287
+ });
288
+ if (Array.isArray(history))
289
+ span.setAttribute(telemetry_attributes_1.ATTR_FLEXPLM_PUBLISH_HISTORY_COUNT, history.length);
290
+ return history;
221
291
  });
222
292
  }
223
293
  updatedSinceDate(entity, sinceDate) {
@@ -315,30 +385,43 @@ class BaseProcessPublishAssortment {
315
385
  });
316
386
  }
317
387
  async downloadHydratedChangeDetail(assortmentPublishChange) {
318
- try {
319
- const link = assortmentPublishChange?.hydratedDetailDownloadLink;
320
- const response = await fetch(link);
321
- const data = await response.json();
322
- return data;
323
- }
324
- catch (e) {
325
- console.error('Error hydratedDetailDownloadLink: ' + e.message);
326
- }
327
- return undefined;
388
+ return this.downloadPublishFile(assortmentPublishChange?.hydratedDetailDownloadLink, PUBLISH_FILE_HYDRATED_DETAIL);
328
389
  }
329
390
  async downloadAssortmentBaseline(assortmentPublishChange) {
330
- try {
331
- const link = assortmentPublishChange?.assortmentBaselineDownloadLink;
332
- const response = await fetch(link);
333
- const data = await response.json();
334
- return data;
335
- }
336
- catch (e) {
337
- console.error('Error assortmentBaselineDownloadLink: ' + e.message);
338
- }
339
- return undefined;
391
+ return this.downloadPublishFile(assortmentPublishChange?.assortmentBaselineDownloadLink, PUBLISH_FILE_BASELINE);
392
+ }
393
+ /** Downloads one publish file as JSON. Returns undefined when it can't be read, so the publish continues without it;
394
+ * the span is marked ERROR here because nothing is thrown. The link is never recorded (it is a signed URL).
395
+ */
396
+ async downloadPublishFile(link, file) {
397
+ return (0, telemetry_1.traceSection)('download_publish_file', { [telemetry_attributes_1.ATTR_FLEXPLM_PUBLISH_FILE]: file }, async (span) => {
398
+ let httpStatus;
399
+ try {
400
+ const response = await fetch(link);
401
+ httpStatus = response.status;
402
+ span.setAttribute(telemetry_attributes_1.ATTR_HTTP_RESPONSE_STATUS_CODE, httpStatus);
403
+ if (!response.ok) {
404
+ throw new Error('Download failed: status: ' + httpStatus);
405
+ }
406
+ return await response.json();
407
+ }
408
+ catch (e) {
409
+ span.recordException(e);
410
+ span.setStatus({ code: api_1.SpanStatusCode.ERROR, message: e?.message });
411
+ const flexplm = { direction: 'publish', publish_file: file, http_status: httpStatus };
412
+ telemetry_1.logger.error({ error: e, flexplm }, 'FlexPLM publish file download failed: %j', flexplm);
413
+ return undefined;
414
+ }
415
+ });
340
416
  }
341
417
  async getDeleteChanges(assortmentPublishChange, apcHistory, assortmentBaseline, sinceDate) {
418
+ return (0, telemetry_1.traceSection)('get_delete_changes', {}, async (span) => {
419
+ const deleteChanges = await this.collectDeleteChanges(assortmentPublishChange, apcHistory, assortmentBaseline, sinceDate);
420
+ span.setAttribute(telemetry_attributes_1.ATTR_FLEXPLM_PUBLISH_DELETE_CHANGE_COUNT, deleteChanges?.length ?? 0);
421
+ return deleteChanges;
422
+ });
423
+ }
424
+ async collectDeleteChanges(assortmentPublishChange, apcHistory, assortmentBaseline, sinceDate) {
342
425
  const currentAPCIndex = apcHistory.findIndex(pc => pc?.id === assortmentPublishChange?.id);
343
426
  if (currentAPCIndex == 0) {
344
427
  return [];
@@ -383,6 +466,7 @@ class BaseProcessPublishAssortment {
383
466
  console.info('getDeleteChanges(): baselineItems: ' + currentAssortmentItemIds.length
384
467
  + ', scanned ' + scannedCount + ' historical delete changes, added ' + addedCount
385
468
  + ', deleteIds: ' + deleteIds.length);
469
+ (0, telemetry_utils_1.setActiveSpanAttributes)({ [telemetry_attributes_1.ATTR_FLEXPLM_PUBLISH_HISTORICAL_DELETE_COUNT]: addedCount });
386
470
  console.info('getDeleteChanges()-deleteIds: ' + deleteIds);
387
471
  }
388
472
  return apcDeletes;
@@ -559,12 +643,12 @@ class BaseProcessPublishAssortment {
559
643
  pcd.itemFamilyChanges = this.getItemFamilyChanges(pcd, changeDetail, assortmentItemFullChangeMap, assortmentItemDeleteMap);
560
644
  const events = await this.getEventsForPublishChangeData(pcd);
561
645
  if (events.length === 0) {
562
- const processStatus = 'BaseProcessPublishAssortment: ' + event_short_message_status_1.EventShortMessageStatus.NO_EVENTS_TO_SEND
563
- + ': assortmentId: ' + pcd.assortmentId + ': assortmentPublishChangeId: ' + pcd.assortmentPublishChangeId
564
- + ', orgSlug: ' + this.orgSlug;
565
- console.log(processStatus);
566
646
  const message = 'No Events to Send; returning';
567
- console.log(message);
647
+ this.recordPublishStatus({
648
+ status: event_short_message_status_1.EventShortMessageStatus.NO_EVENTS_TO_SEND,
649
+ assortmentId: pcd.assortmentId,
650
+ assortmentPublishChangeId: pcd.assortmentPublishChangeId,
651
+ });
568
652
  return {
569
653
  results: { message, event },
570
654
  skip_await: true
@@ -572,11 +656,12 @@ class BaseProcessPublishAssortment {
572
656
  }
573
657
  const results = await this.sendEvents(events);
574
658
  const resultsCount = this.getResultsCount(events);
575
- const processStatus = 'BaseProcessPublishAssortment: ' + event_short_message_status_1.EventShortMessageStatus.SENDING_EVENTS
576
- + ': assortmentId: ' + pcd.assortmentId + ': assortmentPublishChangeId: ' + pcd.assortmentPublishChangeId
577
- + ': resultsCount: ' + JSON.stringify(resultsCount)
578
- + ', orgSlug: ' + this.orgSlug;
579
- console.log(processStatus);
659
+ this.recordPublishStatus({
660
+ status: event_short_message_status_1.EventShortMessageStatus.SENDING_EVENTS,
661
+ assortmentId: pcd.assortmentId,
662
+ assortmentPublishChangeId: pcd.assortmentPublishChangeId,
663
+ resultsCount,
664
+ });
580
665
  return { results, event, resultsCount };
581
666
  }
582
667
  getResultsCount(events) {
@@ -595,6 +680,7 @@ class BaseProcessPublishAssortment {
595
680
  console.info('sendEvents()');
596
681
  const eventType = 'ASYNC_PUBLISH_SEASON';
597
682
  const sendMode = (this.config?.sendMode[eventType] || '').toLowerCase();
683
+ (0, telemetry_utils_1.setActiveSpanAttributes)({ [telemetry_attributes_1.ATTR_FLEXPLM_PUBLISH_SEND_MODE]: sendMode });
598
684
  switch (sendMode) {
599
685
  case 'sendtoflexplm':
600
686
  return this.sendToFlexPLM(events, eventType);
@@ -604,22 +690,26 @@ class BaseProcessPublishAssortment {
604
690
  case 'vibeiqfile-dontsendtoflexplm':
605
691
  return this.handleVibeIQFile(events, eventType, sendMode);
606
692
  default:
693
+ const flexplm = { direction: 'publish', send_mode: sendMode, payload_count: events?.length ?? 0 };
694
+ telemetry_1.logger.warn({ flexplm }, 'Unknown FlexPLM publish sendMode; events not sent: %j', flexplm);
607
695
  return {}; // Or handle other cases as required
608
696
  }
609
697
  }
610
698
  async sendToFlexPLM(events, eventType) {
611
- const outboundPublishEvent = {
612
- taskId: this.config.taskId,
613
- eventType,
614
- objectClass: 'LCSSeason',
615
- events
616
- };
617
- const flexPLMConnect = new flexplm_connect_1.FlexPLMConnect(this.config);
618
- const result = await this.sendAndRecordPublishEvent(flexPLMConnect, outboundPublishEvent);
619
- const isResultObject = typeof result === 'object' && result !== null;
620
- return isResultObject
621
- ? { ...result, outboundPublishEvent }
622
- : { result, outboundPublishEvent };
699
+ return (0, telemetry_1.traceSection)('send_publish_batch', { [telemetry_attributes_1.ATTR_FLEXPLM_PAYLOAD_COUNT]: events?.length ?? 0 }, async () => {
700
+ const outboundPublishEvent = {
701
+ taskId: this.config.taskId,
702
+ eventType,
703
+ objectClass: 'LCSSeason',
704
+ events
705
+ };
706
+ const flexPLMConnect = new flexplm_connect_1.FlexPLMConnect(this.config);
707
+ const result = await this.sendAndRecordPublishEvent(flexPLMConnect, outboundPublishEvent);
708
+ const isResultObject = typeof result === 'object' && result !== null;
709
+ return isResultObject
710
+ ? { ...result, outboundPublishEvent }
711
+ : { result, outboundPublishEvent };
712
+ });
623
713
  }
624
714
  async sendAndRecordPublishEvent(flexPLMConnect, outboundPublishEvent) {
625
715
  let sendResult;
@@ -668,6 +758,8 @@ class BaseProcessPublishAssortment {
668
758
  };
669
759
  }
670
760
  catch (e) {
761
+ const flexplm = { direction: 'publish', send_mode: 'localfile' };
762
+ telemetry_1.logger.error({ error: e, flexplm }, 'Saving FlexPLM publish events to a local file failed: %j', flexplm);
671
763
  results['error'] = e.message;
672
764
  }
673
765
  finally {
@@ -678,7 +770,7 @@ class BaseProcessPublishAssortment {
678
770
  async handleVibeIQFile(events, eventType, sendMode) {
679
771
  const eventBuffer = Buffer.from(JSON.stringify(events), 'utf-8');
680
772
  const fileName = 'ASYNC_PUBLISH_SEASON-events.json';
681
- const uploadFile = await new sdk_1.Files().createAndUploadFileFromBuffer(eventBuffer, 'application/json', fileName, null, this.TTL);
773
+ const uploadFile = await (0, telemetry_1.traceSection)('upload_publish_events_file', { [telemetry_attributes_1.ATTR_FLEXPLM_PAYLOAD_COUNT]: events?.length ?? 0 }, () => new sdk_1.Files().createAndUploadFileFromBuffer(eventBuffer, 'application/json', fileName, null, this.TTL));
682
774
  const outboundPublishEvent = {
683
775
  taskId: this.config.taskId,
684
776
  eventType,
@@ -688,12 +780,14 @@ class BaseProcessPublishAssortment {
688
780
  eventsAdminDownloadLink: uploadFile.adminDownloadUrl
689
781
  };
690
782
  if (sendMode === 'vibeiqfile') {
691
- const flexPLMConnect = new flexplm_connect_1.FlexPLMConnect(this.config);
692
- const result = await this.sendAndRecordPublishEvent(flexPLMConnect, outboundPublishEvent);
693
- const isResultObject = typeof result === 'object' && result !== null;
694
- return isResultObject
695
- ? { ...result, outboundPublishEvent }
696
- : { result, outboundPublishEvent };
783
+ return (0, telemetry_1.traceSection)('send_publish_batch', { [telemetry_attributes_1.ATTR_FLEXPLM_PAYLOAD_COUNT]: events?.length ?? 0 }, async () => {
784
+ const flexPLMConnect = new flexplm_connect_1.FlexPLMConnect(this.config);
785
+ const result = await this.sendAndRecordPublishEvent(flexPLMConnect, outboundPublishEvent);
786
+ const isResultObject = typeof result === 'object' && result !== null;
787
+ return isResultObject
788
+ ? { ...result, outboundPublishEvent }
789
+ : { result, outboundPublishEvent };
790
+ });
697
791
  }
698
792
  else {
699
793
  await this.sendPublishPayloadEvent(outboundPublishEvent);
@@ -704,22 +798,24 @@ class BaseProcessPublishAssortment {
704
798
  }
705
799
  }
706
800
  async sendPublishPayloadEvent(outboundPublishEvent) {
707
- console.info('sendPublishPayloadEvent()');
708
- let initialEvent = {};
709
- if (this.config?.event) {
710
- initialEvent = (typeof this.config?.event === 'string')
711
- ? JSON.parse(this.config?.event)
712
- : this.config?.event;
713
- }
714
- const eventBody = {
715
- originSystemType: 'VibeIQ',
716
- objectClass: 'AssortmentPublishedToFlexPLM',
717
- outboundPublishEvent,
718
- initialEvent
719
- };
720
- await new sdk_1.Entities().create({
721
- entityName: 'external-event',
722
- object: eventBody
801
+ return (0, telemetry_1.traceSection)('record_publish_event', {}, async () => {
802
+ console.info('sendPublishPayloadEvent()');
803
+ let initialEvent = {};
804
+ if (this.config?.event) {
805
+ initialEvent = (typeof this.config?.event === 'string')
806
+ ? JSON.parse(this.config?.event)
807
+ : this.config?.event;
808
+ }
809
+ const eventBody = {
810
+ originSystemType: 'VibeIQ',
811
+ objectClass: 'AssortmentPublishedToFlexPLM',
812
+ outboundPublishEvent,
813
+ initialEvent
814
+ };
815
+ await new sdk_1.Entities().create({
816
+ entityName: 'external-event',
817
+ object: eventBody
818
+ });
723
819
  });
724
820
  }
725
821
  getCurrentDateString() {
@@ -812,6 +908,7 @@ class BaseProcessPublishAssortment {
812
908
  }
813
909
  }
814
910
  }
911
+ const skippedDeletes = [];
815
912
  for (const [itemId, delEntity] of assortmentItemDeleteMap) {
816
913
  const itemFamilyId = delEntity?.item?.itemFamilyId;
817
914
  const item = delEntity?.item;
@@ -823,7 +920,7 @@ class BaseProcessPublishAssortment {
823
920
  }
824
921
  else {
825
922
  if (!item) {
826
- console.error('Failed to find deleted item entity: itemFamilyId: ' + itemFamilyId + ', itemId: ' + itemId);
923
+ skippedDeletes.push({ item_family_id: itemFamilyId, item_id: itemId });
827
924
  continue;
828
925
  }
829
926
  }
@@ -855,6 +952,15 @@ class BaseProcessPublishAssortment {
855
952
  ifc.colorDeletes.push(itemId);
856
953
  }
857
954
  } //End deletes for loop
955
+ if (skippedDeletes.length > 0) {
956
+ // These deletes are not sent to FlexPLM; one warning for the publish rather than one per item.
957
+ (0, telemetry_utils_1.setActiveSpanAttributes)({ [telemetry_attributes_1.ATTR_FLEXPLM_PUBLISH_SKIPPED_DELETE_COUNT]: skippedDeletes.length });
958
+ const flexplm = {
959
+ direction: 'publish', assortment_id: pcd.assortmentId, publish_change_id: pcd.assortmentPublishChangeId,
960
+ skipped_delete_count: skippedDeletes.length, skipped_deletes: skippedDeletes,
961
+ };
962
+ telemetry_1.logger.warn({ flexplm }, 'FlexPLM publish skipped deleted items with no item entity: %j', flexplm);
963
+ }
858
964
  if (app_framework_1.Logger.isDebugOn()) {
859
965
  console.debug('returning size: ' + itemFamilyChanges.size);
860
966
  for (const [key, value] of itemFamilyChanges) {
@@ -864,16 +970,21 @@ class BaseProcessPublishAssortment {
864
970
  return itemFamilyChanges;
865
971
  }
866
972
  async getEventsForPublishChangeData(publishChangeData) {
867
- const seasonalPayloads = [];
868
- for (const itemFamilyChange of publishChangeData.itemFamilyChanges.values()) {
869
- const events = await this.getEventsForItemFamilyChanges(itemFamilyChange, publishChangeData.assortmentId, publishChangeData.seasonFed, publishChangeData.itemToFederatedIdMapping);
870
- if (events.length > 0) {
871
- seasonalPayloads.push(...events);
973
+ return (0, telemetry_1.traceSection)('build_publish_events', {
974
+ [telemetry_attributes_1.ATTR_FLEXPLM_PUBLISH_ITEM_FAMILY_COUNT]: publishChangeData.itemFamilyChanges?.size ?? 0,
975
+ }, async (span) => {
976
+ const seasonalPayloads = [];
977
+ for (const itemFamilyChange of publishChangeData.itemFamilyChanges.values()) {
978
+ const events = await this.getEventsForItemFamilyChanges(itemFamilyChange, publishChangeData.assortmentId, publishChangeData.seasonFed, publishChangeData.itemToFederatedIdMapping);
979
+ if (events.length > 0) {
980
+ seasonalPayloads.push(...events);
981
+ }
872
982
  }
873
- }
874
- console.info('getEventsForPublishChangeData: itemFamilies: '
875
- + publishChangeData.itemFamilyChanges.size + ', payloads: ' + seasonalPayloads.length);
876
- return seasonalPayloads;
983
+ console.info('getEventsForPublishChangeData: itemFamilies: '
984
+ + publishChangeData.itemFamilyChanges.size + ', payloads: ' + seasonalPayloads.length);
985
+ span.setAttribute(telemetry_attributes_1.ATTR_FLEXPLM_PAYLOAD_COUNT, seasonalPayloads.length);
986
+ return seasonalPayloads;
987
+ });
877
988
  }
878
989
  /**Returns the events for a given ItemFamilyChanges object
879
990
  *