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

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 +238 -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,119 @@ 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
+ telemetry_1.logger.error({ error: e2, assortment: { id: assortmentId, publish_change_id: assortmentPublishChangeId } }, 'getPublishInfo failed');
106
+ }
107
+ this.recordPublishStatus({
108
+ status: event_short_message_status_1.EventShortMessageStatus.FAILURE, assortmentId, assortmentPublishChangeId, error: e,
109
+ // sendAndRecordPublishEvent only sets this after FlexPLM accepted the publish.
110
+ sentUnrecorded: e?.sendPublishPayloadEventResult !== undefined,
111
+ });
112
+ throw e;
53
113
  }
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;
114
+ });
115
+ }
116
+ /** Records the result of one assortment publish: attributes on the active span plus one structured log.
117
+ * Not_Publishable / No_Federation_Information are handled skips, so the span stays OK for them.
118
+ */
119
+ recordPublishStatus(statusObject) {
120
+ const { status, assortmentId, assortmentPublishChangeId, resultsCount, message, error } = statusObject;
121
+ const sentUnrecorded = statusObject.sentUnrecorded || undefined;
122
+ (0, telemetry_utils_1.setActiveSpanAttributes)({
123
+ [telemetry_attributes_1.ATTR_FLEXPLM_STATUS]: status,
124
+ [telemetry_attributes_1.ATTR_FLEXPLM_ASSORTMENT_ID]: assortmentId,
125
+ [telemetry_attributes_1.ATTR_FLEXPLM_PUBLISH_CHANGE_ID]: assortmentPublishChangeId,
126
+ [telemetry_attributes_1.ATTR_FLEXPLM_PUBLISH_PRODUCT_SEASON_COUNT]: resultsCount?.productSeasons,
127
+ [telemetry_attributes_1.ATTR_FLEXPLM_PUBLISH_COLORWAY_SEASON_COUNT]: resultsCount?.colorwaySeasons,
128
+ [telemetry_attributes_1.ATTR_FLEXPLM_PUBLISH_SENT_UNRECORDED]: sentUnrecorded,
129
+ });
130
+ const flexplm = {
131
+ direction: 'publish', status, assortment_id: assortmentId, publish_change_id: assortmentPublishChangeId,
132
+ product_season_count: resultsCount?.productSeasons, colorway_season_count: resultsCount?.colorwaySeasons, reason: message,
133
+ sent_unrecorded: sentUnrecorded,
134
+ };
135
+ if (sentUnrecorded) {
136
+ telemetry_1.logger.error({ error, flexplm }, 'FlexPLM assortment publish sent but not recorded');
71
137
  }
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;
138
+ else if (status === event_short_message_status_1.EventShortMessageStatus.FAILURE) {
139
+ telemetry_1.logger.error({ error, flexplm }, 'FlexPLM assortment publish failed');
140
+ }
141
+ else if (status === event_short_message_status_1.EventShortMessageStatus.NO_FEDERATION_INFO) {
142
+ telemetry_1.logger.warn({ flexplm }, 'FlexPLM assortment publish skipped');
143
+ }
144
+ else {
145
+ telemetry_1.logger.info({ flexplm }, 'FlexPLM assortment publish processed');
82
146
  }
83
147
  }
84
148
  async getPublishInfo(assortmentId, assortmentPublishChangeId, apcHistory, publisher) {
@@ -211,13 +275,18 @@ class BaseProcessPublishAssortment {
211
275
  return sinceDate;
212
276
  }
213
277
  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
- }
278
+ return (0, telemetry_1.traceSection)('get_publish_history', {}, async (span) => {
279
+ const resource = `/assortments/${assortmentId}/history/`;
280
+ const history = await sdk_1.Request.request(resource, {
281
+ method: 'GET',
282
+ headers: {
283
+ Accept: 'application/json',
284
+ 'Content-Type': 'application/json'
285
+ }
286
+ });
287
+ if (Array.isArray(history))
288
+ span.setAttribute(telemetry_attributes_1.ATTR_FLEXPLM_PUBLISH_HISTORY_COUNT, history.length);
289
+ return history;
221
290
  });
222
291
  }
223
292
  updatedSinceDate(entity, sinceDate) {
@@ -315,30 +384,42 @@ class BaseProcessPublishAssortment {
315
384
  });
316
385
  }
317
386
  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;
387
+ return this.downloadPublishFile(assortmentPublishChange?.hydratedDetailDownloadLink, PUBLISH_FILE_HYDRATED_DETAIL);
328
388
  }
329
389
  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;
390
+ return this.downloadPublishFile(assortmentPublishChange?.assortmentBaselineDownloadLink, PUBLISH_FILE_BASELINE);
391
+ }
392
+ /** Downloads one publish file as JSON. Returns undefined when it can't be read, so the publish continues without it;
393
+ * the span is marked ERROR here because nothing is thrown. The link is never recorded (it is a signed URL).
394
+ */
395
+ async downloadPublishFile(link, file) {
396
+ return (0, telemetry_1.traceSection)('download_publish_file', { [telemetry_attributes_1.ATTR_FLEXPLM_PUBLISH_FILE]: file }, async (span) => {
397
+ let httpStatus;
398
+ try {
399
+ const response = await fetch(link);
400
+ httpStatus = response.status;
401
+ span.setAttribute(telemetry_attributes_1.ATTR_HTTP_RESPONSE_STATUS_CODE, httpStatus);
402
+ if (!response.ok) {
403
+ throw new Error('Download failed: status: ' + httpStatus);
404
+ }
405
+ return await response.json();
406
+ }
407
+ catch (e) {
408
+ span.recordException(e);
409
+ span.setStatus({ code: api_1.SpanStatusCode.ERROR, message: e?.message });
410
+ telemetry_1.logger.error({ error: e, flexplm: { direction: 'publish', publish_file: file, http_status: httpStatus } }, 'FlexPLM publish file download failed');
411
+ return undefined;
412
+ }
413
+ });
340
414
  }
341
415
  async getDeleteChanges(assortmentPublishChange, apcHistory, assortmentBaseline, sinceDate) {
416
+ return (0, telemetry_1.traceSection)('get_delete_changes', {}, async (span) => {
417
+ const deleteChanges = await this.collectDeleteChanges(assortmentPublishChange, apcHistory, assortmentBaseline, sinceDate);
418
+ span.setAttribute(telemetry_attributes_1.ATTR_FLEXPLM_PUBLISH_DELETE_CHANGE_COUNT, deleteChanges?.length ?? 0);
419
+ return deleteChanges;
420
+ });
421
+ }
422
+ async collectDeleteChanges(assortmentPublishChange, apcHistory, assortmentBaseline, sinceDate) {
342
423
  const currentAPCIndex = apcHistory.findIndex(pc => pc?.id === assortmentPublishChange?.id);
343
424
  if (currentAPCIndex == 0) {
344
425
  return [];
@@ -383,6 +464,7 @@ class BaseProcessPublishAssortment {
383
464
  console.info('getDeleteChanges(): baselineItems: ' + currentAssortmentItemIds.length
384
465
  + ', scanned ' + scannedCount + ' historical delete changes, added ' + addedCount
385
466
  + ', deleteIds: ' + deleteIds.length);
467
+ (0, telemetry_utils_1.setActiveSpanAttributes)({ [telemetry_attributes_1.ATTR_FLEXPLM_PUBLISH_HISTORICAL_DELETE_COUNT]: addedCount });
386
468
  console.info('getDeleteChanges()-deleteIds: ' + deleteIds);
387
469
  }
388
470
  return apcDeletes;
@@ -559,12 +641,12 @@ class BaseProcessPublishAssortment {
559
641
  pcd.itemFamilyChanges = this.getItemFamilyChanges(pcd, changeDetail, assortmentItemFullChangeMap, assortmentItemDeleteMap);
560
642
  const events = await this.getEventsForPublishChangeData(pcd);
561
643
  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
644
  const message = 'No Events to Send; returning';
567
- console.log(message);
645
+ this.recordPublishStatus({
646
+ status: event_short_message_status_1.EventShortMessageStatus.NO_EVENTS_TO_SEND,
647
+ assortmentId: pcd.assortmentId,
648
+ assortmentPublishChangeId: pcd.assortmentPublishChangeId,
649
+ });
568
650
  return {
569
651
  results: { message, event },
570
652
  skip_await: true
@@ -572,11 +654,12 @@ class BaseProcessPublishAssortment {
572
654
  }
573
655
  const results = await this.sendEvents(events);
574
656
  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);
657
+ this.recordPublishStatus({
658
+ status: event_short_message_status_1.EventShortMessageStatus.SENDING_EVENTS,
659
+ assortmentId: pcd.assortmentId,
660
+ assortmentPublishChangeId: pcd.assortmentPublishChangeId,
661
+ resultsCount,
662
+ });
580
663
  return { results, event, resultsCount };
581
664
  }
582
665
  getResultsCount(events) {
@@ -595,6 +678,7 @@ class BaseProcessPublishAssortment {
595
678
  console.info('sendEvents()');
596
679
  const eventType = 'ASYNC_PUBLISH_SEASON';
597
680
  const sendMode = (this.config?.sendMode[eventType] || '').toLowerCase();
681
+ (0, telemetry_utils_1.setActiveSpanAttributes)({ [telemetry_attributes_1.ATTR_FLEXPLM_PUBLISH_SEND_MODE]: sendMode });
598
682
  switch (sendMode) {
599
683
  case 'sendtoflexplm':
600
684
  return this.sendToFlexPLM(events, eventType);
@@ -604,22 +688,25 @@ class BaseProcessPublishAssortment {
604
688
  case 'vibeiqfile-dontsendtoflexplm':
605
689
  return this.handleVibeIQFile(events, eventType, sendMode);
606
690
  default:
691
+ telemetry_1.logger.warn({ flexplm: { direction: 'publish', send_mode: sendMode, payload_count: events?.length ?? 0 } }, 'Unknown FlexPLM publish sendMode; events not sent');
607
692
  return {}; // Or handle other cases as required
608
693
  }
609
694
  }
610
695
  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 };
696
+ return (0, telemetry_1.traceSection)('send_publish_batch', { [telemetry_attributes_1.ATTR_FLEXPLM_PAYLOAD_COUNT]: events?.length ?? 0 }, async () => {
697
+ const outboundPublishEvent = {
698
+ taskId: this.config.taskId,
699
+ eventType,
700
+ objectClass: 'LCSSeason',
701
+ events
702
+ };
703
+ const flexPLMConnect = new flexplm_connect_1.FlexPLMConnect(this.config);
704
+ const result = await this.sendAndRecordPublishEvent(flexPLMConnect, outboundPublishEvent);
705
+ const isResultObject = typeof result === 'object' && result !== null;
706
+ return isResultObject
707
+ ? { ...result, outboundPublishEvent }
708
+ : { result, outboundPublishEvent };
709
+ });
623
710
  }
624
711
  async sendAndRecordPublishEvent(flexPLMConnect, outboundPublishEvent) {
625
712
  let sendResult;
@@ -668,6 +755,7 @@ class BaseProcessPublishAssortment {
668
755
  };
669
756
  }
670
757
  catch (e) {
758
+ telemetry_1.logger.error({ error: e, flexplm: { direction: 'publish', send_mode: 'localfile' } }, 'Saving FlexPLM publish events to a local file failed');
671
759
  results['error'] = e.message;
672
760
  }
673
761
  finally {
@@ -678,7 +766,7 @@ class BaseProcessPublishAssortment {
678
766
  async handleVibeIQFile(events, eventType, sendMode) {
679
767
  const eventBuffer = Buffer.from(JSON.stringify(events), 'utf-8');
680
768
  const fileName = 'ASYNC_PUBLISH_SEASON-events.json';
681
- const uploadFile = await new sdk_1.Files().createAndUploadFileFromBuffer(eventBuffer, 'application/json', fileName, null, this.TTL);
769
+ 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
770
  const outboundPublishEvent = {
683
771
  taskId: this.config.taskId,
684
772
  eventType,
@@ -688,12 +776,14 @@ class BaseProcessPublishAssortment {
688
776
  eventsAdminDownloadLink: uploadFile.adminDownloadUrl
689
777
  };
690
778
  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 };
779
+ return (0, telemetry_1.traceSection)('send_publish_batch', { [telemetry_attributes_1.ATTR_FLEXPLM_PAYLOAD_COUNT]: events?.length ?? 0 }, async () => {
780
+ const flexPLMConnect = new flexplm_connect_1.FlexPLMConnect(this.config);
781
+ const result = await this.sendAndRecordPublishEvent(flexPLMConnect, outboundPublishEvent);
782
+ const isResultObject = typeof result === 'object' && result !== null;
783
+ return isResultObject
784
+ ? { ...result, outboundPublishEvent }
785
+ : { result, outboundPublishEvent };
786
+ });
697
787
  }
698
788
  else {
699
789
  await this.sendPublishPayloadEvent(outboundPublishEvent);
@@ -704,22 +794,24 @@ class BaseProcessPublishAssortment {
704
794
  }
705
795
  }
706
796
  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
797
+ return (0, telemetry_1.traceSection)('record_publish_event', {}, async () => {
798
+ console.info('sendPublishPayloadEvent()');
799
+ let initialEvent = {};
800
+ if (this.config?.event) {
801
+ initialEvent = (typeof this.config?.event === 'string')
802
+ ? JSON.parse(this.config?.event)
803
+ : this.config?.event;
804
+ }
805
+ const eventBody = {
806
+ originSystemType: 'VibeIQ',
807
+ objectClass: 'AssortmentPublishedToFlexPLM',
808
+ outboundPublishEvent,
809
+ initialEvent
810
+ };
811
+ await new sdk_1.Entities().create({
812
+ entityName: 'external-event',
813
+ object: eventBody
814
+ });
723
815
  });
724
816
  }
725
817
  getCurrentDateString() {
@@ -812,6 +904,7 @@ class BaseProcessPublishAssortment {
812
904
  }
813
905
  }
814
906
  }
907
+ const skippedDeletes = [];
815
908
  for (const [itemId, delEntity] of assortmentItemDeleteMap) {
816
909
  const itemFamilyId = delEntity?.item?.itemFamilyId;
817
910
  const item = delEntity?.item;
@@ -823,7 +916,7 @@ class BaseProcessPublishAssortment {
823
916
  }
824
917
  else {
825
918
  if (!item) {
826
- console.error('Failed to find deleted item entity: itemFamilyId: ' + itemFamilyId + ', itemId: ' + itemId);
919
+ skippedDeletes.push({ item_family_id: itemFamilyId, item_id: itemId });
827
920
  continue;
828
921
  }
829
922
  }
@@ -855,6 +948,14 @@ class BaseProcessPublishAssortment {
855
948
  ifc.colorDeletes.push(itemId);
856
949
  }
857
950
  } //End deletes for loop
951
+ if (skippedDeletes.length > 0) {
952
+ // These deletes are not sent to FlexPLM; one warning for the publish rather than one per item.
953
+ (0, telemetry_utils_1.setActiveSpanAttributes)({ [telemetry_attributes_1.ATTR_FLEXPLM_PUBLISH_SKIPPED_DELETE_COUNT]: skippedDeletes.length });
954
+ telemetry_1.logger.warn({ flexplm: {
955
+ direction: 'publish', assortment_id: pcd.assortmentId, publish_change_id: pcd.assortmentPublishChangeId,
956
+ skipped_delete_count: skippedDeletes.length, skipped_deletes: skippedDeletes,
957
+ } }, 'FlexPLM publish skipped deleted items with no item entity');
958
+ }
858
959
  if (app_framework_1.Logger.isDebugOn()) {
859
960
  console.debug('returning size: ' + itemFamilyChanges.size);
860
961
  for (const [key, value] of itemFamilyChanges) {
@@ -864,16 +965,21 @@ class BaseProcessPublishAssortment {
864
965
  return itemFamilyChanges;
865
966
  }
866
967
  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);
968
+ return (0, telemetry_1.traceSection)('build_publish_events', {
969
+ [telemetry_attributes_1.ATTR_FLEXPLM_PUBLISH_ITEM_FAMILY_COUNT]: publishChangeData.itemFamilyChanges?.size ?? 0,
970
+ }, async (span) => {
971
+ const seasonalPayloads = [];
972
+ for (const itemFamilyChange of publishChangeData.itemFamilyChanges.values()) {
973
+ const events = await this.getEventsForItemFamilyChanges(itemFamilyChange, publishChangeData.assortmentId, publishChangeData.seasonFed, publishChangeData.itemToFederatedIdMapping);
974
+ if (events.length > 0) {
975
+ seasonalPayloads.push(...events);
976
+ }
872
977
  }
873
- }
874
- console.info('getEventsForPublishChangeData: itemFamilies: '
875
- + publishChangeData.itemFamilyChanges.size + ', payloads: ' + seasonalPayloads.length);
876
- return seasonalPayloads;
978
+ console.info('getEventsForPublishChangeData: itemFamilies: '
979
+ + publishChangeData.itemFamilyChanges.size + ', payloads: ' + seasonalPayloads.length);
980
+ span.setAttribute(telemetry_attributes_1.ATTR_FLEXPLM_PAYLOAD_COUNT, seasonalPayloads.length);
981
+ return seasonalPayloads;
982
+ });
877
983
  }
878
984
  /**Returns the events for a given ItemFamilyChanges object
879
985
  *