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

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 (34) 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 +210 -0
  12. package/lib/index.d.ts +1 -0
  13. package/lib/index.js +1 -0
  14. package/lib/publish/base-process-publish-assortment-callback.js +30 -0
  15. package/lib/publish/base-process-publish-assortment-callback.spec.d.ts +1 -0
  16. package/lib/publish/base-process-publish-assortment-callback.spec.js +62 -0
  17. package/lib/publish/base-process-publish-assortment.d.ts +24 -0
  18. package/lib/publish/base-process-publish-assortment.js +234 -132
  19. package/lib/publish/base-process-publish-assortment.spec.js +188 -0
  20. package/lib/util/error-response-object.js +2 -0
  21. package/lib/util/event-short-message-status.js +1 -1
  22. package/lib/util/flexplm-connect.d.ts +4 -0
  23. package/lib/util/flexplm-connect.js +210 -145
  24. package/lib/util/flexplm-connect.telemetry.spec.d.ts +1 -0
  25. package/lib/util/flexplm-connect.telemetry.spec.js +212 -0
  26. package/lib/util/logger-config.js +1 -2
  27. package/lib/util/telemetry-attributes.d.ts +68 -0
  28. package/lib/util/telemetry-attributes.js +73 -0
  29. package/lib/util/telemetry-utils.d.ts +3 -0
  30. package/lib/util/telemetry-utils.js +10 -0
  31. package/lib/util/thumbnail-util.d.ts +2 -1
  32. package/lib/util/thumbnail-util.js +35 -7
  33. package/lib/util/thumbnail-util.spec.js +42 -0
  34. 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,115 @@ 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
+ this.recordPublishStatus({
53
+ status: eventStatus,
54
+ assortmentId,
55
+ assortmentPublishChangeId,
56
+ message,
57
+ // Only an unexpected error is swallowed here; the other two are expected skips.
58
+ error: eventStatus === event_short_message_status_1.EventShortMessageStatus.FAILURE ? e : undefined,
59
+ });
60
+ const output = {
61
+ results: { message, event },
62
+ skip_await: true
63
+ };
64
+ return output;
65
+ }
66
+ apcHistory = await this.getApcHistory(assortmentId);
67
+ const sinceDate = await this.getSinceDate(assortmentId, assortmentPublishChangeId, apcHistory);
68
+ //Get detail information
69
+ const assortmentPublishChange = await this.downloadAssortmentPublishChange(assortmentId, assortmentPublishChangeId);
70
+ publisher = this.getPublisher(assortmentPublishChange);
71
+ const changeDetail = await this.downloadHydratedChangeDetail(assortmentPublishChange);
72
+ // Without the change detail the counts are unknown, not zero; failed_downloads says why.
73
+ if (changeDetail) {
74
+ span.setAttributes({
75
+ [telemetry_attributes_1.ATTR_FLEXPLM_PUBLISH_ADDED_COUNT]: changeDetail.adds?.length ?? 0,
76
+ [telemetry_attributes_1.ATTR_FLEXPLM_PUBLISH_REMOVED_COUNT]: changeDetail.deletes?.length ?? 0,
77
+ [telemetry_attributes_1.ATTR_FLEXPLM_PUBLISH_CHANGED_COUNT]: changeDetail.updates?.length ?? 0,
78
+ });
79
+ }
80
+ const assortmentBaseline = await this.downloadAssortmentBaseline(assortmentPublishChange);
81
+ const failedDownloads = [!changeDetail && PUBLISH_FILE_HYDRATED_DETAIL, !assortmentBaseline && PUBLISH_FILE_BASELINE].filter(Boolean);
82
+ if (failedDownloads.length > 0) {
83
+ span.setAttribute(telemetry_attributes_1.ATTR_FLEXPLM_PUBLISH_FAILED_DOWNLOADS, failedDownloads);
84
+ }
85
+ const deleteChanges = await this.getDeleteChanges(assortmentPublishChange, apcHistory, assortmentBaseline, sinceDate);
86
+ const releasedForDevelopmentItemIds = this.getReleasedForDevelopmentItemAndFamilyIds(assortmentBaseline, deleteChanges);
87
+ const itemToFederatedIdMapping = await this.getItemFederatedIds( /*allItemIds*/);
88
+ const pcd = new publish_change_data_1.PublishChangeData(assortmentId, seasonFed, assortmentPublishChangeId, sinceDate, publisher);
89
+ pcd.itemToFederatedIdMapping = itemToFederatedIdMapping;
90
+ pcd.releasedForDevelopmentItemIds = releasedForDevelopmentItemIds;
91
+ const output = await this.processPublish(pcd, changeDetail, assortmentBaseline, deleteChanges);
92
+ output['publishInfo'] = await this.getPublishInfo(assortmentId, assortmentPublishChangeId, apcHistory, publisher);
93
+ return output;
37
94
  }
38
95
  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;
96
+ try {
97
+ const publishInfo = await this.getPublishInfo(assortmentId, assortmentPublishChangeId, apcHistory, publisher);
98
+ e.publishInfo = publishInfo;
99
+ }
100
+ catch (e2) {
101
+ telemetry_1.logger.error({ error: e2, assortment: { id: assortmentId, publish_change_id: assortmentPublishChangeId } }, 'getPublishInfo failed');
102
+ }
103
+ this.recordPublishStatus({
104
+ status: event_short_message_status_1.EventShortMessageStatus.FAILURE, assortmentId, assortmentPublishChangeId, error: e,
105
+ // sendAndRecordPublishEvent only sets this after FlexPLM accepted the publish.
106
+ sentUnrecorded: e?.sendPublishPayloadEventResult !== undefined,
107
+ });
108
+ throw e;
53
109
  }
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;
110
+ });
111
+ }
112
+ /** Records the result of one assortment publish: attributes on the active span plus one structured log.
113
+ * Not_Publishable / No_Federation_Information are handled skips, so the span stays OK for them.
114
+ */
115
+ recordPublishStatus(statusObject) {
116
+ const { status, assortmentId, assortmentPublishChangeId, resultsCount, message, error } = statusObject;
117
+ const sentUnrecorded = statusObject.sentUnrecorded || undefined;
118
+ (0, telemetry_utils_1.setActiveSpanAttributes)({
119
+ [telemetry_attributes_1.ATTR_FLEXPLM_STATUS]: status,
120
+ [telemetry_attributes_1.ATTR_FLEXPLM_ASSORTMENT_ID]: assortmentId,
121
+ [telemetry_attributes_1.ATTR_FLEXPLM_PUBLISH_CHANGE_ID]: assortmentPublishChangeId,
122
+ [telemetry_attributes_1.ATTR_FLEXPLM_PUBLISH_PRODUCT_SEASON_COUNT]: resultsCount?.productSeasons,
123
+ [telemetry_attributes_1.ATTR_FLEXPLM_PUBLISH_COLORWAY_SEASON_COUNT]: resultsCount?.colorwaySeasons,
124
+ [telemetry_attributes_1.ATTR_FLEXPLM_PUBLISH_SENT_UNRECORDED]: sentUnrecorded,
125
+ });
126
+ const flexplm = {
127
+ direction: 'publish', status, assortment_id: assortmentId, publish_change_id: assortmentPublishChangeId,
128
+ product_season_count: resultsCount?.productSeasons, colorway_season_count: resultsCount?.colorwaySeasons, reason: message,
129
+ sent_unrecorded: sentUnrecorded,
130
+ };
131
+ if (sentUnrecorded) {
132
+ telemetry_1.logger.error({ error, flexplm }, 'FlexPLM assortment publish sent but not recorded');
71
133
  }
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;
134
+ else if (status === event_short_message_status_1.EventShortMessageStatus.FAILURE) {
135
+ telemetry_1.logger.error({ error, flexplm }, 'FlexPLM assortment publish failed');
136
+ }
137
+ else if (status === event_short_message_status_1.EventShortMessageStatus.NO_FEDERATION_INFO) {
138
+ telemetry_1.logger.warn({ flexplm }, 'FlexPLM assortment publish skipped');
139
+ }
140
+ else {
141
+ telemetry_1.logger.info({ flexplm }, 'FlexPLM assortment publish processed');
82
142
  }
83
143
  }
84
144
  async getPublishInfo(assortmentId, assortmentPublishChangeId, apcHistory, publisher) {
@@ -211,13 +271,18 @@ class BaseProcessPublishAssortment {
211
271
  return sinceDate;
212
272
  }
213
273
  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
- }
274
+ return (0, telemetry_1.traceSection)('get_publish_history', {}, async (span) => {
275
+ const resource = `/assortments/${assortmentId}/history/`;
276
+ const history = await sdk_1.Request.request(resource, {
277
+ method: 'GET',
278
+ headers: {
279
+ Accept: 'application/json',
280
+ 'Content-Type': 'application/json'
281
+ }
282
+ });
283
+ if (Array.isArray(history))
284
+ span.setAttribute(telemetry_attributes_1.ATTR_FLEXPLM_PUBLISH_HISTORY_COUNT, history.length);
285
+ return history;
221
286
  });
222
287
  }
223
288
  updatedSinceDate(entity, sinceDate) {
@@ -315,30 +380,42 @@ class BaseProcessPublishAssortment {
315
380
  });
316
381
  }
317
382
  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;
383
+ return this.downloadPublishFile(assortmentPublishChange?.hydratedDetailDownloadLink, PUBLISH_FILE_HYDRATED_DETAIL);
328
384
  }
329
385
  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;
386
+ return this.downloadPublishFile(assortmentPublishChange?.assortmentBaselineDownloadLink, PUBLISH_FILE_BASELINE);
387
+ }
388
+ /** Downloads one publish file as JSON. Returns undefined when it can't be read, so the publish continues without it;
389
+ * the span is marked ERROR here because nothing is thrown. The link is never recorded (it is a signed URL).
390
+ */
391
+ async downloadPublishFile(link, file) {
392
+ return (0, telemetry_1.traceSection)('download_publish_file', { [telemetry_attributes_1.ATTR_FLEXPLM_PUBLISH_FILE]: file }, async (span) => {
393
+ let httpStatus;
394
+ try {
395
+ const response = await fetch(link);
396
+ httpStatus = response.status;
397
+ span.setAttribute(telemetry_attributes_1.ATTR_HTTP_RESPONSE_STATUS_CODE, httpStatus);
398
+ if (!response.ok) {
399
+ throw new Error('Download failed: status: ' + httpStatus);
400
+ }
401
+ return await response.json();
402
+ }
403
+ catch (e) {
404
+ span.recordException(e);
405
+ span.setStatus({ code: api_1.SpanStatusCode.ERROR, message: e?.message });
406
+ telemetry_1.logger.error({ error: e, flexplm: { direction: 'publish', publish_file: file, http_status: httpStatus } }, 'FlexPLM publish file download failed');
407
+ return undefined;
408
+ }
409
+ });
340
410
  }
341
411
  async getDeleteChanges(assortmentPublishChange, apcHistory, assortmentBaseline, sinceDate) {
412
+ return (0, telemetry_1.traceSection)('get_delete_changes', {}, async (span) => {
413
+ const deleteChanges = await this.collectDeleteChanges(assortmentPublishChange, apcHistory, assortmentBaseline, sinceDate);
414
+ span.setAttribute(telemetry_attributes_1.ATTR_FLEXPLM_PUBLISH_DELETE_CHANGE_COUNT, deleteChanges?.length ?? 0);
415
+ return deleteChanges;
416
+ });
417
+ }
418
+ async collectDeleteChanges(assortmentPublishChange, apcHistory, assortmentBaseline, sinceDate) {
342
419
  const currentAPCIndex = apcHistory.findIndex(pc => pc?.id === assortmentPublishChange?.id);
343
420
  if (currentAPCIndex == 0) {
344
421
  return [];
@@ -383,6 +460,7 @@ class BaseProcessPublishAssortment {
383
460
  console.info('getDeleteChanges(): baselineItems: ' + currentAssortmentItemIds.length
384
461
  + ', scanned ' + scannedCount + ' historical delete changes, added ' + addedCount
385
462
  + ', deleteIds: ' + deleteIds.length);
463
+ (0, telemetry_utils_1.setActiveSpanAttributes)({ [telemetry_attributes_1.ATTR_FLEXPLM_PUBLISH_HISTORICAL_DELETE_COUNT]: addedCount });
386
464
  console.info('getDeleteChanges()-deleteIds: ' + deleteIds);
387
465
  }
388
466
  return apcDeletes;
@@ -559,12 +637,12 @@ class BaseProcessPublishAssortment {
559
637
  pcd.itemFamilyChanges = this.getItemFamilyChanges(pcd, changeDetail, assortmentItemFullChangeMap, assortmentItemDeleteMap);
560
638
  const events = await this.getEventsForPublishChangeData(pcd);
561
639
  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
640
  const message = 'No Events to Send; returning';
567
- console.log(message);
641
+ this.recordPublishStatus({
642
+ status: event_short_message_status_1.EventShortMessageStatus.NO_EVENTS_TO_SEND,
643
+ assortmentId: pcd.assortmentId,
644
+ assortmentPublishChangeId: pcd.assortmentPublishChangeId,
645
+ });
568
646
  return {
569
647
  results: { message, event },
570
648
  skip_await: true
@@ -572,11 +650,12 @@ class BaseProcessPublishAssortment {
572
650
  }
573
651
  const results = await this.sendEvents(events);
574
652
  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);
653
+ this.recordPublishStatus({
654
+ status: event_short_message_status_1.EventShortMessageStatus.SENDING_EVENTS,
655
+ assortmentId: pcd.assortmentId,
656
+ assortmentPublishChangeId: pcd.assortmentPublishChangeId,
657
+ resultsCount,
658
+ });
580
659
  return { results, event, resultsCount };
581
660
  }
582
661
  getResultsCount(events) {
@@ -595,6 +674,7 @@ class BaseProcessPublishAssortment {
595
674
  console.info('sendEvents()');
596
675
  const eventType = 'ASYNC_PUBLISH_SEASON';
597
676
  const sendMode = (this.config?.sendMode[eventType] || '').toLowerCase();
677
+ (0, telemetry_utils_1.setActiveSpanAttributes)({ [telemetry_attributes_1.ATTR_FLEXPLM_PUBLISH_SEND_MODE]: sendMode });
598
678
  switch (sendMode) {
599
679
  case 'sendtoflexplm':
600
680
  return this.sendToFlexPLM(events, eventType);
@@ -604,22 +684,25 @@ class BaseProcessPublishAssortment {
604
684
  case 'vibeiqfile-dontsendtoflexplm':
605
685
  return this.handleVibeIQFile(events, eventType, sendMode);
606
686
  default:
687
+ telemetry_1.logger.warn({ flexplm: { direction: 'publish', send_mode: sendMode, payload_count: events?.length ?? 0 } }, 'Unknown FlexPLM publish sendMode; events not sent');
607
688
  return {}; // Or handle other cases as required
608
689
  }
609
690
  }
610
691
  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 };
692
+ return (0, telemetry_1.traceSection)('send_publish_batch', { [telemetry_attributes_1.ATTR_FLEXPLM_PAYLOAD_COUNT]: events?.length ?? 0 }, async () => {
693
+ const outboundPublishEvent = {
694
+ taskId: this.config.taskId,
695
+ eventType,
696
+ objectClass: 'LCSSeason',
697
+ events
698
+ };
699
+ const flexPLMConnect = new flexplm_connect_1.FlexPLMConnect(this.config);
700
+ const result = await this.sendAndRecordPublishEvent(flexPLMConnect, outboundPublishEvent);
701
+ const isResultObject = typeof result === 'object' && result !== null;
702
+ return isResultObject
703
+ ? { ...result, outboundPublishEvent }
704
+ : { result, outboundPublishEvent };
705
+ });
623
706
  }
624
707
  async sendAndRecordPublishEvent(flexPLMConnect, outboundPublishEvent) {
625
708
  let sendResult;
@@ -668,6 +751,7 @@ class BaseProcessPublishAssortment {
668
751
  };
669
752
  }
670
753
  catch (e) {
754
+ telemetry_1.logger.error({ error: e, flexplm: { direction: 'publish', send_mode: 'localfile' } }, 'Saving FlexPLM publish events to a local file failed');
671
755
  results['error'] = e.message;
672
756
  }
673
757
  finally {
@@ -678,7 +762,7 @@ class BaseProcessPublishAssortment {
678
762
  async handleVibeIQFile(events, eventType, sendMode) {
679
763
  const eventBuffer = Buffer.from(JSON.stringify(events), 'utf-8');
680
764
  const fileName = 'ASYNC_PUBLISH_SEASON-events.json';
681
- const uploadFile = await new sdk_1.Files().createAndUploadFileFromBuffer(eventBuffer, 'application/json', fileName, null, this.TTL);
765
+ 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
766
  const outboundPublishEvent = {
683
767
  taskId: this.config.taskId,
684
768
  eventType,
@@ -688,12 +772,14 @@ class BaseProcessPublishAssortment {
688
772
  eventsAdminDownloadLink: uploadFile.adminDownloadUrl
689
773
  };
690
774
  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 };
775
+ return (0, telemetry_1.traceSection)('send_publish_batch', { [telemetry_attributes_1.ATTR_FLEXPLM_PAYLOAD_COUNT]: events?.length ?? 0 }, async () => {
776
+ const flexPLMConnect = new flexplm_connect_1.FlexPLMConnect(this.config);
777
+ const result = await this.sendAndRecordPublishEvent(flexPLMConnect, outboundPublishEvent);
778
+ const isResultObject = typeof result === 'object' && result !== null;
779
+ return isResultObject
780
+ ? { ...result, outboundPublishEvent }
781
+ : { result, outboundPublishEvent };
782
+ });
697
783
  }
698
784
  else {
699
785
  await this.sendPublishPayloadEvent(outboundPublishEvent);
@@ -704,22 +790,24 @@ class BaseProcessPublishAssortment {
704
790
  }
705
791
  }
706
792
  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
793
+ return (0, telemetry_1.traceSection)('record_publish_event', {}, async () => {
794
+ console.info('sendPublishPayloadEvent()');
795
+ let initialEvent = {};
796
+ if (this.config?.event) {
797
+ initialEvent = (typeof this.config?.event === 'string')
798
+ ? JSON.parse(this.config?.event)
799
+ : this.config?.event;
800
+ }
801
+ const eventBody = {
802
+ originSystemType: 'VibeIQ',
803
+ objectClass: 'AssortmentPublishedToFlexPLM',
804
+ outboundPublishEvent,
805
+ initialEvent
806
+ };
807
+ await new sdk_1.Entities().create({
808
+ entityName: 'external-event',
809
+ object: eventBody
810
+ });
723
811
  });
724
812
  }
725
813
  getCurrentDateString() {
@@ -812,6 +900,7 @@ class BaseProcessPublishAssortment {
812
900
  }
813
901
  }
814
902
  }
903
+ const skippedDeletes = [];
815
904
  for (const [itemId, delEntity] of assortmentItemDeleteMap) {
816
905
  const itemFamilyId = delEntity?.item?.itemFamilyId;
817
906
  const item = delEntity?.item;
@@ -823,7 +912,7 @@ class BaseProcessPublishAssortment {
823
912
  }
824
913
  else {
825
914
  if (!item) {
826
- console.error('Failed to find deleted item entity: itemFamilyId: ' + itemFamilyId + ', itemId: ' + itemId);
915
+ skippedDeletes.push({ item_family_id: itemFamilyId, item_id: itemId });
827
916
  continue;
828
917
  }
829
918
  }
@@ -855,6 +944,14 @@ class BaseProcessPublishAssortment {
855
944
  ifc.colorDeletes.push(itemId);
856
945
  }
857
946
  } //End deletes for loop
947
+ if (skippedDeletes.length > 0) {
948
+ // These deletes are not sent to FlexPLM; one warning for the publish rather than one per item.
949
+ (0, telemetry_utils_1.setActiveSpanAttributes)({ [telemetry_attributes_1.ATTR_FLEXPLM_PUBLISH_SKIPPED_DELETE_COUNT]: skippedDeletes.length });
950
+ telemetry_1.logger.warn({ flexplm: {
951
+ direction: 'publish', assortment_id: pcd.assortmentId, publish_change_id: pcd.assortmentPublishChangeId,
952
+ skipped_delete_count: skippedDeletes.length, skipped_deletes: skippedDeletes,
953
+ } }, 'FlexPLM publish skipped deleted items with no item entity');
954
+ }
858
955
  if (app_framework_1.Logger.isDebugOn()) {
859
956
  console.debug('returning size: ' + itemFamilyChanges.size);
860
957
  for (const [key, value] of itemFamilyChanges) {
@@ -864,16 +961,21 @@ class BaseProcessPublishAssortment {
864
961
  return itemFamilyChanges;
865
962
  }
866
963
  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);
964
+ return (0, telemetry_1.traceSection)('build_publish_events', {
965
+ [telemetry_attributes_1.ATTR_FLEXPLM_PUBLISH_ITEM_FAMILY_COUNT]: publishChangeData.itemFamilyChanges?.size ?? 0,
966
+ }, async (span) => {
967
+ const seasonalPayloads = [];
968
+ for (const itemFamilyChange of publishChangeData.itemFamilyChanges.values()) {
969
+ const events = await this.getEventsForItemFamilyChanges(itemFamilyChange, publishChangeData.assortmentId, publishChangeData.seasonFed, publishChangeData.itemToFederatedIdMapping);
970
+ if (events.length > 0) {
971
+ seasonalPayloads.push(...events);
972
+ }
872
973
  }
873
- }
874
- console.info('getEventsForPublishChangeData: itemFamilies: '
875
- + publishChangeData.itemFamilyChanges.size + ', payloads: ' + seasonalPayloads.length);
876
- return seasonalPayloads;
974
+ console.info('getEventsForPublishChangeData: itemFamilies: '
975
+ + publishChangeData.itemFamilyChanges.size + ', payloads: ' + seasonalPayloads.length);
976
+ span.setAttribute(telemetry_attributes_1.ATTR_FLEXPLM_PAYLOAD_COUNT, seasonalPayloads.length);
977
+ return seasonalPayloads;
978
+ });
877
979
  }
878
980
  /**Returns the events for a given ItemFamilyChanges object
879
981
  *