@atlaskit/editor-synced-block-provider 8.1.7 → 8.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @atlaskit/editor-synced-block-provider
2
2
 
3
+ ## 8.1.9
4
+
5
+ ### Patch Changes
6
+
7
+ - [`a4e834c2647ac`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/a4e834c2647ac) -
8
+ Add failure reason and HTTP status code attributes to synced block delete, update and create
9
+ operational error analytics, so failures can be broken down by cause.
10
+
11
+ ## 8.1.8
12
+
13
+ ### Patch Changes
14
+
15
+ - Updated dependencies
16
+
3
17
  ## 8.1.7
4
18
 
5
19
  ### Patch Changes
@@ -574,7 +574,8 @@ var writeDataBatch = exports.writeDataBatch = /*#__PURE__*/function () {
574
574
  return _context4.abrupt("return", data.map(function (block) {
575
575
  return {
576
576
  error: mapBlockError(_t6),
577
- resourceId: block.resourceId
577
+ resourceId: block.resourceId,
578
+ statusCode: _t6.status
578
579
  };
579
580
  }));
580
581
  case 11:
@@ -922,7 +923,8 @@ var BlockServiceADFWriteProvider = /*#__PURE__*/function () {
922
923
  }
923
924
  return _context8.abrupt("return", {
924
925
  error: mapBlockError(_t0),
925
- resourceId: resourceId
926
+ resourceId: resourceId,
927
+ statusCode: _t0.status
926
928
  });
927
929
  case 8:
928
930
  return _context8.abrupt("return", {
@@ -1002,7 +1004,8 @@ var BlockServiceADFWriteProvider = /*#__PURE__*/function () {
1002
1004
  }
1003
1005
  return _context9.abrupt("return", {
1004
1006
  error: mapBlockError(_t10),
1005
- resourceId: resourceId
1007
+ resourceId: resourceId,
1008
+ statusCode: _t10.status
1006
1009
  });
1007
1010
  case 8:
1008
1011
  return _context9.abrupt("return", {
@@ -1086,7 +1089,8 @@ var BlockServiceADFWriteProvider = /*#__PURE__*/function () {
1086
1089
  return _context0.abrupt("return", {
1087
1090
  resourceId: resourceId,
1088
1091
  success: false,
1089
- error: mapBlockError(_t11)
1092
+ error: mapBlockError(_t11),
1093
+ statusCode: _t11.status
1090
1094
  });
1091
1095
  case 7:
1092
1096
  return _context0.abrupt("return", {
@@ -1161,7 +1165,8 @@ var BlockServiceADFWriteProvider = /*#__PURE__*/function () {
1161
1165
  return _context1.abrupt("return", {
1162
1166
  resourceId: resourceId,
1163
1167
  success: false,
1164
- error: mapBlockError(_t13)
1168
+ error: mapBlockError(_t13),
1169
+ statusCode: _t13.status
1165
1170
  });
1166
1171
  case 9:
1167
1172
  return _context1.abrupt("return", {
@@ -1191,7 +1196,8 @@ var BlockServiceADFWriteProvider = /*#__PURE__*/function () {
1191
1196
  return _context1.abrupt("return", {
1192
1197
  resourceId: resourceId,
1193
1198
  success: false,
1194
- error: mapBlockError(_t14)
1199
+ error: mapBlockError(_t14),
1200
+ statusCode: _t14.status
1195
1201
  });
1196
1202
  case 13:
1197
1203
  return _context1.abrupt("return", {
@@ -1276,7 +1282,8 @@ var BlockServiceADFWriteProvider = /*#__PURE__*/function () {
1276
1282
  }
1277
1283
  return _context10.abrupt("return", {
1278
1284
  success: false,
1279
- error: mapBlockError(_t15)
1285
+ error: mapBlockError(_t15),
1286
+ statusCode: _t15.status
1280
1287
  });
1281
1288
  case 4:
1282
1289
  return _context10.abrupt("return", {
@@ -1443,7 +1450,8 @@ var BlockServiceADFWriteProvider = /*#__PURE__*/function () {
1443
1450
  return _context12.abrupt("return", data.map(function (block) {
1444
1451
  return {
1445
1452
  error: mapBlockError(_t18),
1446
- resourceId: block.resourceId
1453
+ resourceId: block.resourceId,
1454
+ statusCode: _t18.status
1447
1455
  };
1448
1456
  }));
1449
1457
  case 15:
@@ -1084,7 +1084,7 @@ var ReferenceSyncBlockStoreManager = exports.ReferenceSyncBlockStoreManager = /*
1084
1084
  (_this$saveExperience2 = this.saveExperience) === null || _this$saveExperience2 === void 0 || _this$saveExperience2.failure({
1085
1085
  reason: updateResult.error || 'Failed to update reference synced blocks on the document'
1086
1086
  });
1087
- (_this$fireAnalyticsEv8 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv8 === void 0 || _this$fireAnalyticsEv8.call(this, (0, _errorHandling.updateReferenceErrorPayload)(updateResult.error || 'Failed to update reference synced blocks on the document'));
1087
+ (_this$fireAnalyticsEv8 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv8 === void 0 || _this$fireAnalyticsEv8.call(this, (0, _errorHandling.updateReferenceErrorPayload)(updateResult.error || 'Failed to update reference synced blocks on the document', undefined, undefined, (0, _errorHandling.buildErrorAttribution)((0, _platformFeatureFlags.fg)('platform_editor_blocks_patch_3'), updateResult.error, updateResult.statusCode)));
1088
1088
  }
1089
1089
  _context4.next = 17;
1090
1090
  break;
@@ -1099,7 +1099,9 @@ var ReferenceSyncBlockStoreManager = exports.ReferenceSyncBlockStoreManager = /*
1099
1099
  reason: _t3.message
1100
1100
  });
1101
1101
  // No `resourceId` available in this catch — sourceProduct is intentionally omitted.
1102
- (_this$fireAnalyticsEv9 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv9 === void 0 || _this$fireAnalyticsEv9.call(this, (0, _errorHandling.updateReferenceErrorPayload)(_t3.message));
1102
+ // No structured SyncBlockError/status here, so the attribution `reason` falls back
1103
+ // to `unknown` when the gate is on.
1104
+ (_this$fireAnalyticsEv9 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv9 === void 0 || _this$fireAnalyticsEv9.call(this, (0, _errorHandling.updateReferenceErrorPayload)(_t3.message, undefined, undefined, (0, _errorHandling.buildErrorAttribution)((0, _platformFeatureFlags.fg)('platform_editor_blocks_patch_3'))));
1103
1105
  case 17:
1104
1106
  _context4.prev = 17;
1105
1107
  if (!success) {
@@ -12,6 +12,7 @@ var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/cl
12
12
  var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
13
13
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
14
14
  var _monitoring = require("@atlaskit/editor-common/monitoring");
15
+ var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
15
16
  var _types = require("../common/types");
16
17
  var _errorHandling = require("../utils/errorHandling");
17
18
  var _experienceTracking = require("../utils/experienceTracking");
@@ -165,7 +166,7 @@ var SourceSyncBlockStoreManager = exports.SourceSyncBlockStoreManager = /*#__PUR
165
166
  value: (function () {
166
167
  var _flush = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee() {
167
168
  var _this2 = this;
168
- var _this$saveExperience, timedOut, timeoutId, timeout, _iterator, _step, resourceId, bodiedSyncBlockNodes, bodiedSyncBlockData, writeResults, _this$saveExperience2, _this$saveExperience3, _this$fireAnalyticsEv2, _this$flushCompletion, _t;
169
+ var _this$saveExperience, timedOut, timeoutId, timeout, _iterator, _step, resourceId, bodiedSyncBlockNodes, bodiedSyncBlockData, writeResults, _this$saveExperience2, _this$saveExperience3, attributionEnabled, _this$fireAnalyticsEv2, _this$flushCompletion, _t;
169
170
  return _regenerator.default.wrap(function (_context) {
170
171
  while (1) switch (_context.prev = _context.next) {
171
172
  case 0:
@@ -284,11 +285,12 @@ var SourceSyncBlockStoreManager = exports.SourceSyncBlockStoreManager = /*#__PUR
284
285
  return _context.abrupt("return", true);
285
286
  case 7:
286
287
  (_this$saveExperience3 = this.saveExperience) === null || _this$saveExperience3 === void 0 || _this$saveExperience3.failure();
288
+ attributionEnabled = (0, _platformFeatureFlags.fg)('platform_editor_blocks_patch_3');
287
289
  writeResults.filter(function (result) {
288
290
  return !result.resourceId || result.error;
289
291
  }).forEach(function (result) {
290
292
  var _this2$fireAnalyticsE2;
291
- (_this2$fireAnalyticsE2 = _this2.fireAnalyticsEvent) === null || _this2$fireAnalyticsE2 === void 0 || _this2$fireAnalyticsE2.call(_this2, (0, _errorHandling.updateErrorPayload)(result.error || 'Failed to write data', result.resourceId, (0, _utils.getSourceProductFromResourceIdSafe)(result.resourceId)));
293
+ (_this2$fireAnalyticsE2 = _this2.fireAnalyticsEvent) === null || _this2$fireAnalyticsE2 === void 0 || _this2$fireAnalyticsE2.call(_this2, (0, _errorHandling.updateErrorPayload)(result.error || 'Failed to write data', result.resourceId, (0, _utils.getSourceProductFromResourceIdSafe)(result.resourceId), (0, _errorHandling.buildErrorAttribution)(attributionEnabled, result.error, result.statusCode)));
292
294
  });
293
295
  return _context.abrupt("return", false);
294
296
  case 8:
@@ -300,8 +302,9 @@ var SourceSyncBlockStoreManager = exports.SourceSyncBlockStoreManager = /*#__PUR
300
302
  (0, _monitoring.logException)(_t, {
301
303
  location: 'editor-synced-block-provider/sourceSyncBlockStoreManager'
302
304
  });
303
- // Top-level flush failure is not tied to a single resourceId.
304
- (_this$fireAnalyticsEv2 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv2 === void 0 || _this$fireAnalyticsEv2.call(this, (0, _errorHandling.updateErrorPayload)(_t.message));
305
+ // Top-level flush failure is not tied to a single resourceId. There is no structured
306
+ // SyncBlockError/status here, so the attribution `reason` falls back to `unknown`.
307
+ (_this$fireAnalyticsEv2 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv2 === void 0 || _this$fireAnalyticsEv2.call(this, (0, _errorHandling.updateErrorPayload)(_t.message, undefined, undefined, (0, _errorHandling.buildErrorAttribution)((0, _platformFeatureFlags.fg)('platform_editor_blocks_patch_3'))));
305
308
  return _context.abrupt("return", false);
306
309
  case 10:
307
310
  _context.prev = 10;
@@ -476,7 +479,7 @@ var SourceSyncBlockStoreManager = exports.SourceSyncBlockStoreManager = /*#__PUR
476
479
  (_this4$createExperien2 = _this4.createExperience) === null || _this4$createExperien2 === void 0 || _this4$createExperien2.failure({
477
480
  reason: result.error || 'Failed to create bodied sync block'
478
481
  });
479
- (_this4$fireAnalyticsE = _this4.fireAnalyticsEvent) === null || _this4$fireAnalyticsE === void 0 || _this4$fireAnalyticsE.call(_this4, (0, _errorHandling.createErrorPayload)(result.error || 'Failed to create bodied sync block', resourceId, (0, _utils.getSourceProductFromResourceIdSafe)(resourceId)));
482
+ (_this4$fireAnalyticsE = _this4.fireAnalyticsEvent) === null || _this4$fireAnalyticsE === void 0 || _this4$fireAnalyticsE.call(_this4, (0, _errorHandling.createErrorPayload)(result.error || 'Failed to create bodied sync block', resourceId, (0, _utils.getSourceProductFromResourceIdSafe)(resourceId), (0, _errorHandling.buildErrorAttribution)((0, _platformFeatureFlags.fg)('platform_editor_blocks_patch_3'), result.error, result.statusCode)));
480
483
  }
481
484
  }).catch(function (error) {
482
485
  var _this4$createExperien3, _this4$fireAnalyticsE2;
@@ -508,7 +511,7 @@ var SourceSyncBlockStoreManager = exports.SourceSyncBlockStoreManager = /*#__PUR
508
511
  value: function () {
509
512
  var _delete2 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee2(syncBlockIds, onDelete, onDeleteCompleted, reason) {
510
513
  var _this5 = this;
511
- var _this$deleteExperienc, results, callback, isDeleteSuccessful, _this$deleteExperienc2, _this$deleteExperienc3, _t2;
514
+ var _this$deleteExperienc, results, callback, isDeleteSuccessful, _this$deleteExperienc2, _this$deleteExperienc3, attributionEnabled, attribution, _t2;
512
515
  return _regenerator.default.wrap(function (_context2) {
513
516
  while (1) switch (_context2.prev = _context2.next) {
514
517
  case 0:
@@ -555,13 +558,14 @@ var SourceSyncBlockStoreManager = exports.SourceSyncBlockStoreManager = /*#__PUR
555
558
  _this5.setPendingDeletion(Ids, false);
556
559
  };
557
560
  (_this$deleteExperienc3 = this.deleteExperience) === null || _this$deleteExperienc3 === void 0 || _this$deleteExperienc3.failure();
561
+ attributionEnabled = (0, _platformFeatureFlags.fg)('platform_editor_blocks_patch_3');
558
562
  results.forEach(function (result) {
559
563
  if (result.success) {
560
564
  var _this5$fireAnalyticsE2;
561
565
  (_this5$fireAnalyticsE2 = _this5.fireAnalyticsEvent) === null || _this5$fireAnalyticsE2 === void 0 || _this5$fireAnalyticsE2.call(_this5, (0, _errorHandling.deleteSuccessPayload)(result.resourceId, (0, _utils.getSourceProductFromResourceIdSafe)(result.resourceId)));
562
566
  } else {
563
567
  var _this5$fireAnalyticsE3;
564
- (_this5$fireAnalyticsE3 = _this5.fireAnalyticsEvent) === null || _this5$fireAnalyticsE3 === void 0 || _this5$fireAnalyticsE3.call(_this5, (0, _errorHandling.deleteErrorPayload)(result.error || 'Failed to delete synced block', result.resourceId, (0, _utils.getSourceProductFromResourceIdSafe)(result.resourceId)));
568
+ (_this5$fireAnalyticsE3 = _this5.fireAnalyticsEvent) === null || _this5$fireAnalyticsE3 === void 0 || _this5$fireAnalyticsE3.call(_this5, (0, _errorHandling.deleteErrorPayload)(result.error || 'Failed to delete synced block', result.resourceId, (0, _utils.getSourceProductFromResourceIdSafe)(result.resourceId), (0, _errorHandling.buildErrorAttribution)(attributionEnabled, result.error, result.statusCode)));
565
569
  }
566
570
  });
567
571
  }
@@ -570,10 +574,13 @@ var SourceSyncBlockStoreManager = exports.SourceSyncBlockStoreManager = /*#__PUR
570
574
  case 4:
571
575
  _context2.prev = 4;
572
576
  _t2 = _context2["catch"](0);
577
+ // Thrown (non-result) failure — no structured SyncBlockError/status is available,
578
+ // so the attribution `reason` falls back to `unknown` when the gate is on.
579
+ attribution = (0, _errorHandling.buildErrorAttribution)((0, _platformFeatureFlags.fg)('platform_editor_blocks_patch_3'));
573
580
  syncBlockIds.forEach(function (Ids) {
574
581
  var _this5$fireAnalyticsE4;
575
582
  _this5.setPendingDeletion(Ids, false);
576
- (_this5$fireAnalyticsE4 = _this5.fireAnalyticsEvent) === null || _this5$fireAnalyticsE4 === void 0 || _this5$fireAnalyticsE4.call(_this5, (0, _errorHandling.deleteErrorPayload)(_t2.message, Ids.resourceId, (0, _utils.getSourceProductFromResourceIdSafe)(Ids.resourceId)));
583
+ (_this5$fireAnalyticsE4 = _this5.fireAnalyticsEvent) === null || _this5$fireAnalyticsE4 === void 0 || _this5$fireAnalyticsE4.call(_this5, (0, _errorHandling.deleteErrorPayload)(_t2.message, Ids.resourceId, (0, _utils.getSourceProductFromResourceIdSafe)(Ids.resourceId), attribution));
577
584
  });
578
585
  (0, _monitoring.logException)(_t2, {
579
586
  location: 'editor-synced-block-provider/sourceSyncBlockStoreManager'
@@ -4,9 +4,10 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
4
4
  Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
- exports.updateSuccessPayload = exports.updateReferenceErrorPayload = exports.updateErrorPayload = exports.updateCacheErrorPayload = exports.stringifyError = exports.sourceInfoOrphanedPayload = exports.getSourceInfoErrorPayload = exports.getErrorPayload = exports.fetchSuccessPayload = exports.fetchReferencesErrorPayload = exports.fetchErrorPayload = exports.deleteSuccessPayload = exports.deleteErrorPayload = exports.createSuccessPayloadNew = exports.createSuccessPayload = exports.createErrorPayload = exports.cacheDeletionForcedPayload = void 0;
7
+ exports.updateSuccessPayload = exports.updateReferenceErrorPayload = exports.updateErrorPayload = exports.updateCacheErrorPayload = exports.stringifyError = exports.sourceInfoOrphanedPayload = exports.getSourceInfoErrorPayload = exports.getErrorPayload = exports.fetchSuccessPayload = exports.fetchReferencesErrorPayload = exports.fetchErrorPayload = exports.deleteSuccessPayload = exports.deleteErrorPayload = exports.createSuccessPayloadNew = exports.createSuccessPayload = exports.createErrorPayload = exports.classifyErrorReason = exports.cacheDeletionForcedPayload = exports.buildErrorAttribution = void 0;
8
8
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
9
9
  var _analytics = require("@atlaskit/editor-common/analytics");
10
+ var _types = require("../common/types");
10
11
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
11
12
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
12
13
  var stringifyError = exports.stringifyError = function stringifyError(error) {
@@ -17,24 +18,77 @@ var stringifyError = exports.stringifyError = function stringifyError(error) {
17
18
  }
18
19
  };
19
20
 
21
+ /**
22
+ * The set of categorical failure reasons emitted on synced-block operational error
23
+ * events (EDITOR-7796). These let the analytics dashboard break delete/update/create
24
+ * failures down by cause (Block Service / HG / Relay) instead of relying on the
25
+ * free-text `error` blob. Mirrors the {@link SyncBlockError} enum values, plus an
26
+ * `unknown` fallback for unclassifiable errors.
27
+ */
28
+
29
+ var SYNC_BLOCK_ERROR_REASONS = new Set(Object.values(_types.SyncBlockError));
30
+
31
+ /**
32
+ * Maps a result `error` field (which may be a {@link SyncBlockError} enum value such as
33
+ * `'not_found'`, or an arbitrary stringified blob) to a stable categorical
34
+ * {@link SyncBlockErrorReason} for analytics grouping. Anything that is not a known
35
+ * `SyncBlockError` value collapses to `'unknown'` so the dashboard never has to group on
36
+ * free-text error blobs.
37
+ */
38
+ var classifyErrorReason = exports.classifyErrorReason = function classifyErrorReason(error) {
39
+ if (error && SYNC_BLOCK_ERROR_REASONS.has(error)) {
40
+ return error;
41
+ }
42
+ return 'unknown';
43
+ };
44
+
45
+ /**
46
+ * Extra, optional analytics attributes describing WHY an operational synced-block
47
+ * action failed. Spread conditionally so we never emit `undefined` keys (EDITOR-7796).
48
+ */
49
+
50
+ /**
51
+ * Builds the {@link ErrorAttributionAttributes} for a failed synced-block operation from
52
+ * the raw result `error` field and optional backend `statusCode`. Returns `undefined`
53
+ * when the `platform_editor_blocks_patch_3` gate is OFF, so the new `reason`/`statusCode`
54
+ * attributes are only emitted once the gate is rolled out (EDITOR-7796).
55
+ *
56
+ * `gateEnabled` is injected by the caller (the store managers evaluate `fg(...)`) so this
57
+ * helper stays pure and trivially unit-testable for both gate states.
58
+ */
59
+ var buildErrorAttribution = exports.buildErrorAttribution = function buildErrorAttribution(gateEnabled, error, statusCode) {
60
+ if (!gateEnabled) {
61
+ return undefined;
62
+ }
63
+ return _objectSpread({
64
+ reason: classifyErrorReason(error)
65
+ }, statusCode !== undefined && {
66
+ statusCode: statusCode
67
+ });
68
+ };
69
+
20
70
  // `sourceProduct` is threaded through every helper so analytics
21
71
  // events can be attributed to the source product (`confluence-page` vs `jira-work-item`).
22
72
  // All helpers accept it as an optional trailing argument; the spread-only-when-truthy
23
73
  // pattern below ensures we never emit `sourceProduct: undefined` (which would dirty the
24
74
  // event schema with empty keys).
25
75
 
26
- var getErrorPayload = exports.getErrorPayload = function getErrorPayload(actionSubjectId, error, resourceId, sourceProduct) {
76
+ var getErrorPayload = exports.getErrorPayload = function getErrorPayload(actionSubjectId, error, resourceId, sourceProduct, attribution) {
27
77
  return {
28
78
  action: _analytics.ACTION.ERROR,
29
79
  actionSubject: _analytics.ACTION_SUBJECT.SYNCED_BLOCK,
30
80
  actionSubjectId: actionSubjectId,
31
81
  eventType: _analytics.EVENT_TYPE.OPERATIONAL,
32
- attributes: _objectSpread(_objectSpread({
82
+ attributes: _objectSpread(_objectSpread(_objectSpread(_objectSpread({
33
83
  error: error
34
84
  }, resourceId && {
35
85
  resourceId: resourceId
36
86
  }), sourceProduct && {
37
87
  sourceProduct: sourceProduct
88
+ }), (attribution === null || attribution === void 0 ? void 0 : attribution.reason) && {
89
+ reason: attribution.reason
90
+ }), (attribution === null || attribution === void 0 ? void 0 : attribution.statusCode) !== undefined && {
91
+ statusCode: attribution.statusCode
38
92
  })
39
93
  };
40
94
  };
@@ -44,17 +98,17 @@ var fetchErrorPayload = exports.fetchErrorPayload = function fetchErrorPayload(e
44
98
  var getSourceInfoErrorPayload = exports.getSourceInfoErrorPayload = function getSourceInfoErrorPayload(error, resourceId, sourceProduct) {
45
99
  return getErrorPayload(_analytics.ACTION_SUBJECT_ID.SYNCED_BLOCK_GET_SOURCE_INFO, error, resourceId, sourceProduct);
46
100
  };
47
- var updateErrorPayload = exports.updateErrorPayload = function updateErrorPayload(error, resourceId, sourceProduct) {
48
- return getErrorPayload(_analytics.ACTION_SUBJECT_ID.SYNCED_BLOCK_UPDATE, error, resourceId, sourceProduct);
101
+ var updateErrorPayload = exports.updateErrorPayload = function updateErrorPayload(error, resourceId, sourceProduct, attribution) {
102
+ return getErrorPayload(_analytics.ACTION_SUBJECT_ID.SYNCED_BLOCK_UPDATE, error, resourceId, sourceProduct, attribution);
49
103
  };
50
- var updateReferenceErrorPayload = exports.updateReferenceErrorPayload = function updateReferenceErrorPayload(error, resourceId, sourceProduct) {
51
- return getErrorPayload(_analytics.ACTION_SUBJECT_ID.REFERENCE_SYNCED_BLOCK_UPDATE, error, resourceId, sourceProduct);
104
+ var updateReferenceErrorPayload = exports.updateReferenceErrorPayload = function updateReferenceErrorPayload(error, resourceId, sourceProduct, attribution) {
105
+ return getErrorPayload(_analytics.ACTION_SUBJECT_ID.REFERENCE_SYNCED_BLOCK_UPDATE, error, resourceId, sourceProduct, attribution);
52
106
  };
53
- var createErrorPayload = exports.createErrorPayload = function createErrorPayload(error, resourceId, sourceProduct) {
54
- return getErrorPayload(_analytics.ACTION_SUBJECT_ID.SYNCED_BLOCK_CREATE, error, resourceId, sourceProduct);
107
+ var createErrorPayload = exports.createErrorPayload = function createErrorPayload(error, resourceId, sourceProduct, attribution) {
108
+ return getErrorPayload(_analytics.ACTION_SUBJECT_ID.SYNCED_BLOCK_CREATE, error, resourceId, sourceProduct, attribution);
55
109
  };
56
- var deleteErrorPayload = exports.deleteErrorPayload = function deleteErrorPayload(error, resourceId, sourceProduct) {
57
- return getErrorPayload(_analytics.ACTION_SUBJECT_ID.SYNCED_BLOCK_DELETE, error, resourceId, sourceProduct);
110
+ var deleteErrorPayload = exports.deleteErrorPayload = function deleteErrorPayload(error, resourceId, sourceProduct, attribution) {
111
+ return getErrorPayload(_analytics.ACTION_SUBJECT_ID.SYNCED_BLOCK_DELETE, error, resourceId, sourceProduct, attribution);
58
112
  };
59
113
  var updateCacheErrorPayload = exports.updateCacheErrorPayload = function updateCacheErrorPayload(error, resourceId, sourceProduct) {
60
114
  return getErrorPayload(_analytics.ACTION_SUBJECT_ID.SYNCED_BLOCK_UPDATE_CACHE, error, resourceId, sourceProduct);
@@ -387,7 +387,8 @@ export const writeDataBatch = async (cloudId, parentAri, parentId, product, data
387
387
  if (error instanceof BlockError) {
388
388
  return data.map(block => ({
389
389
  error: mapBlockError(error),
390
- resourceId: block.resourceId
390
+ resourceId: block.resourceId,
391
+ statusCode: error.status
391
392
  }));
392
393
  }
393
394
  return data.map(block => ({
@@ -632,7 +633,8 @@ class BlockServiceADFWriteProvider {
632
633
  if (error instanceof BlockError) {
633
634
  return {
634
635
  error: mapBlockError(error),
635
- resourceId
636
+ resourceId,
637
+ statusCode: error.status
636
638
  };
637
639
  }
638
640
  return {
@@ -675,7 +677,8 @@ class BlockServiceADFWriteProvider {
675
677
  if (error instanceof BlockError) {
676
678
  return {
677
679
  error: mapBlockError(error),
678
- resourceId
680
+ resourceId,
681
+ statusCode: error.status
679
682
  };
680
683
  }
681
684
  return {
@@ -731,7 +734,8 @@ class BlockServiceADFWriteProvider {
731
734
  return {
732
735
  resourceId,
733
736
  success: false,
734
- error: mapBlockError(error)
737
+ error: mapBlockError(error),
738
+ statusCode: error.status
735
739
  };
736
740
  }
737
741
  return {
@@ -772,7 +776,8 @@ class BlockServiceADFWriteProvider {
772
776
  return {
773
777
  resourceId,
774
778
  success: false,
775
- error: mapBlockError(createError)
779
+ error: mapBlockError(createError),
780
+ statusCode: createError.status
776
781
  };
777
782
  } else {
778
783
  return {
@@ -796,7 +801,8 @@ class BlockServiceADFWriteProvider {
796
801
  return {
797
802
  resourceId,
798
803
  success: false,
799
- error: mapBlockError(error)
804
+ error: mapBlockError(error),
805
+ statusCode: error.status
800
806
  };
801
807
  }
802
808
  return {
@@ -848,7 +854,8 @@ class BlockServiceADFWriteProvider {
848
854
  if (error instanceof BlockError) {
849
855
  return {
850
856
  success: false,
851
- error: mapBlockError(error)
857
+ error: mapBlockError(error),
858
+ statusCode: error.status
852
859
  };
853
860
  }
854
861
  return {
@@ -923,7 +930,8 @@ class BlockServiceADFWriteProvider {
923
930
  if (error instanceof BlockError) {
924
931
  return data.map(block => ({
925
932
  error: mapBlockError(error),
926
- resourceId: block.resourceId
933
+ resourceId: block.resourceId,
934
+ statusCode: error.status
927
935
  }));
928
936
  }
929
937
  return data.map(block => ({
@@ -3,7 +3,7 @@ import isEqual from 'lodash/isEqual';
3
3
  import { logException } from '@atlaskit/editor-common/monitoring';
4
4
  import { fg } from '@atlaskit/platform-feature-flags';
5
5
  import { SyncBlockError } from '../common/types';
6
- import { cacheDeletionForcedPayload, fetchErrorPayload, fetchSuccessPayload, getSourceInfoErrorPayload, sourceInfoOrphanedPayload, updateReferenceErrorPayload } from '../utils/errorHandling';
6
+ import { buildErrorAttribution, cacheDeletionForcedPayload, fetchErrorPayload, fetchSuccessPayload, getSourceInfoErrorPayload, sourceInfoOrphanedPayload, updateReferenceErrorPayload } from '../utils/errorHandling';
7
7
  import { getFetchExperience, getFetchSourceInfoExperience, getSaveReferenceExperience } from '../utils/experienceTracking';
8
8
  import { resolveSyncBlockInstance } from '../utils/resolveSyncBlockInstance';
9
9
  import { createSyncBlockNode, getSourceProductFromResourceIdSafe, normalizeSyncBlockJSONContent } from '../utils/utils';
@@ -874,7 +874,7 @@ export class ReferenceSyncBlockStoreManager {
874
874
  (_this$saveExperience2 = this.saveExperience) === null || _this$saveExperience2 === void 0 ? void 0 : _this$saveExperience2.failure({
875
875
  reason: updateResult.error || 'Failed to update reference synced blocks on the document'
876
876
  });
877
- (_this$fireAnalyticsEv12 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv12 === void 0 ? void 0 : _this$fireAnalyticsEv12.call(this, updateReferenceErrorPayload(updateResult.error || 'Failed to update reference synced blocks on the document'));
877
+ (_this$fireAnalyticsEv12 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv12 === void 0 ? void 0 : _this$fireAnalyticsEv12.call(this, updateReferenceErrorPayload(updateResult.error || 'Failed to update reference synced blocks on the document', undefined, undefined, buildErrorAttribution(fg('platform_editor_blocks_patch_3'), updateResult.error, updateResult.statusCode)));
878
878
  }
879
879
  } catch (error) {
880
880
  var _this$saveExperience3, _this$fireAnalyticsEv13;
@@ -886,7 +886,9 @@ export class ReferenceSyncBlockStoreManager {
886
886
  reason: error.message
887
887
  });
888
888
  // No `resourceId` available in this catch — sourceProduct is intentionally omitted.
889
- (_this$fireAnalyticsEv13 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv13 === void 0 ? void 0 : _this$fireAnalyticsEv13.call(this, updateReferenceErrorPayload(error.message));
889
+ // No structured SyncBlockError/status here, so the attribution `reason` falls back
890
+ // to `unknown` when the gate is on.
891
+ (_this$fireAnalyticsEv13 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv13 === void 0 ? void 0 : _this$fireAnalyticsEv13.call(this, updateReferenceErrorPayload(error.message, undefined, undefined, buildErrorAttribution(fg('platform_editor_blocks_patch_3'))));
890
892
  } finally {
891
893
  if (!success) {
892
894
  // set isCacheDirty back to true for cases where it failed to update the reference synced blocks on the BE
@@ -1,7 +1,8 @@
1
1
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
2
  import { logException } from '@atlaskit/editor-common/monitoring';
3
+ import { fg } from '@atlaskit/platform-feature-flags';
3
4
  import { SyncBlockError } from '../common/types';
4
- import { updateErrorPayload, createErrorPayload, deleteErrorPayload, updateCacheErrorPayload, getSourceInfoErrorPayload, updateSuccessPayload, createSuccessPayload, deleteSuccessPayload, fetchReferencesErrorPayload } from '../utils/errorHandling';
5
+ import { updateErrorPayload, createErrorPayload, deleteErrorPayload, updateCacheErrorPayload, getSourceInfoErrorPayload, updateSuccessPayload, createSuccessPayload, deleteSuccessPayload, fetchReferencesErrorPayload, buildErrorAttribution } from '../utils/errorHandling';
5
6
  import { getCreateSourceExperience, getDeleteSourceExperience, getSaveSourceExperience, getFetchSourceInfoExperience } from '../utils/experienceTracking';
6
7
  import { convertSyncBlockPMNodeToSyncBlockData, getSourceProductFromResourceIdSafe } from '../utils/utils';
7
8
  /** Maximum time (ms) flush() will wait for in-flight block creations before proceeding. */
@@ -236,9 +237,10 @@ export class SourceSyncBlockStoreManager {
236
237
  } else {
237
238
  var _this$saveExperience3;
238
239
  (_this$saveExperience3 = this.saveExperience) === null || _this$saveExperience3 === void 0 ? void 0 : _this$saveExperience3.failure();
240
+ const attributionEnabled = fg('platform_editor_blocks_patch_3');
239
241
  writeResults.filter(result => !result.resourceId || result.error).forEach(result => {
240
242
  var _this$fireAnalyticsEv3;
241
- (_this$fireAnalyticsEv3 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv3 === void 0 ? void 0 : _this$fireAnalyticsEv3.call(this, updateErrorPayload(result.error || 'Failed to write data', result.resourceId, getSourceProductFromResourceIdSafe(result.resourceId)));
243
+ (_this$fireAnalyticsEv3 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv3 === void 0 ? void 0 : _this$fireAnalyticsEv3.call(this, updateErrorPayload(result.error || 'Failed to write data', result.resourceId, getSourceProductFromResourceIdSafe(result.resourceId), buildErrorAttribution(attributionEnabled, result.error, result.statusCode)));
242
244
  });
243
245
  return false;
244
246
  }
@@ -247,8 +249,9 @@ export class SourceSyncBlockStoreManager {
247
249
  logException(error, {
248
250
  location: 'editor-synced-block-provider/sourceSyncBlockStoreManager'
249
251
  });
250
- // Top-level flush failure is not tied to a single resourceId.
251
- (_this$fireAnalyticsEv4 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv4 === void 0 ? void 0 : _this$fireAnalyticsEv4.call(this, updateErrorPayload(error.message));
252
+ // Top-level flush failure is not tied to a single resourceId. There is no structured
253
+ // SyncBlockError/status here, so the attribution `reason` falls back to `unknown`.
254
+ (_this$fireAnalyticsEv4 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv4 === void 0 ? void 0 : _this$fireAnalyticsEv4.call(this, updateErrorPayload(error.message, undefined, undefined, buildErrorAttribution(fg('platform_editor_blocks_patch_3'))));
252
255
  return false;
253
256
  } finally {
254
257
  var _this$flushCompletion;
@@ -396,7 +399,7 @@ export class SourceSyncBlockStoreManager {
396
399
  (_this$createExperienc3 = this.createExperience) === null || _this$createExperienc3 === void 0 ? void 0 : _this$createExperienc3.failure({
397
400
  reason: result.error || 'Failed to create bodied sync block'
398
401
  });
399
- (_this$fireAnalyticsEv8 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv8 === void 0 ? void 0 : _this$fireAnalyticsEv8.call(this, createErrorPayload(result.error || 'Failed to create bodied sync block', resourceId, getSourceProductFromResourceIdSafe(resourceId)));
402
+ (_this$fireAnalyticsEv8 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv8 === void 0 ? void 0 : _this$fireAnalyticsEv8.call(this, createErrorPayload(result.error || 'Failed to create bodied sync block', resourceId, getSourceProductFromResourceIdSafe(resourceId), buildErrorAttribution(fg('platform_editor_blocks_patch_3'), result.error, result.statusCode)));
400
403
  }
401
404
  }).catch(error => {
402
405
  var _this$createExperienc4, _this$fireAnalyticsEv9;
@@ -456,23 +459,27 @@ export class SourceSyncBlockStoreManager {
456
459
  this.setPendingDeletion(Ids, false);
457
460
  };
458
461
  (_this$deleteExperienc3 = this.deleteExperience) === null || _this$deleteExperienc3 === void 0 ? void 0 : _this$deleteExperienc3.failure();
462
+ const attributionEnabled = fg('platform_editor_blocks_patch_3');
459
463
  results.forEach(result => {
460
464
  if (result.success) {
461
465
  var _this$fireAnalyticsEv10;
462
466
  (_this$fireAnalyticsEv10 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv10 === void 0 ? void 0 : _this$fireAnalyticsEv10.call(this, deleteSuccessPayload(result.resourceId, getSourceProductFromResourceIdSafe(result.resourceId)));
463
467
  } else {
464
468
  var _this$fireAnalyticsEv11;
465
- (_this$fireAnalyticsEv11 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv11 === void 0 ? void 0 : _this$fireAnalyticsEv11.call(this, deleteErrorPayload(result.error || 'Failed to delete synced block', result.resourceId, getSourceProductFromResourceIdSafe(result.resourceId)));
469
+ (_this$fireAnalyticsEv11 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv11 === void 0 ? void 0 : _this$fireAnalyticsEv11.call(this, deleteErrorPayload(result.error || 'Failed to delete synced block', result.resourceId, getSourceProductFromResourceIdSafe(result.resourceId), buildErrorAttribution(attributionEnabled, result.error, result.statusCode)));
466
470
  }
467
471
  });
468
472
  }
469
473
  syncBlockIds.forEach(callback);
470
474
  return isDeleteSuccessful;
471
475
  } catch (error) {
476
+ // Thrown (non-result) failure — no structured SyncBlockError/status is available,
477
+ // so the attribution `reason` falls back to `unknown` when the gate is on.
478
+ const attribution = buildErrorAttribution(fg('platform_editor_blocks_patch_3'));
472
479
  syncBlockIds.forEach(Ids => {
473
480
  var _this$fireAnalyticsEv12;
474
481
  this.setPendingDeletion(Ids, false);
475
- (_this$fireAnalyticsEv12 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv12 === void 0 ? void 0 : _this$fireAnalyticsEv12.call(this, deleteErrorPayload(error.message, Ids.resourceId, getSourceProductFromResourceIdSafe(Ids.resourceId)));
482
+ (_this$fireAnalyticsEv12 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv12 === void 0 ? void 0 : _this$fireAnalyticsEv12.call(this, deleteErrorPayload(error.message, Ids.resourceId, getSourceProductFromResourceIdSafe(Ids.resourceId), attribution));
476
483
  });
477
484
  logException(error, {
478
485
  location: 'editor-synced-block-provider/sourceSyncBlockStoreManager'
@@ -1,4 +1,5 @@
1
1
  import { ACTION, ACTION_SUBJECT, EVENT_TYPE, ACTION_SUBJECT_ID } from '@atlaskit/editor-common/analytics';
2
+ import { SyncBlockError } from '../common/types';
2
3
  export const stringifyError = error => {
3
4
  try {
4
5
  return JSON.stringify(error);
@@ -7,13 +8,63 @@ export const stringifyError = error => {
7
8
  }
8
9
  };
9
10
 
11
+ /**
12
+ * The set of categorical failure reasons emitted on synced-block operational error
13
+ * events (EDITOR-7796). These let the analytics dashboard break delete/update/create
14
+ * failures down by cause (Block Service / HG / Relay) instead of relying on the
15
+ * free-text `error` blob. Mirrors the {@link SyncBlockError} enum values, plus an
16
+ * `unknown` fallback for unclassifiable errors.
17
+ */
18
+
19
+ const SYNC_BLOCK_ERROR_REASONS = new Set(Object.values(SyncBlockError));
20
+
21
+ /**
22
+ * Maps a result `error` field (which may be a {@link SyncBlockError} enum value such as
23
+ * `'not_found'`, or an arbitrary stringified blob) to a stable categorical
24
+ * {@link SyncBlockErrorReason} for analytics grouping. Anything that is not a known
25
+ * `SyncBlockError` value collapses to `'unknown'` so the dashboard never has to group on
26
+ * free-text error blobs.
27
+ */
28
+ export const classifyErrorReason = error => {
29
+ if (error && SYNC_BLOCK_ERROR_REASONS.has(error)) {
30
+ return error;
31
+ }
32
+ return 'unknown';
33
+ };
34
+
35
+ /**
36
+ * Extra, optional analytics attributes describing WHY an operational synced-block
37
+ * action failed. Spread conditionally so we never emit `undefined` keys (EDITOR-7796).
38
+ */
39
+
40
+ /**
41
+ * Builds the {@link ErrorAttributionAttributes} for a failed synced-block operation from
42
+ * the raw result `error` field and optional backend `statusCode`. Returns `undefined`
43
+ * when the `platform_editor_blocks_patch_3` gate is OFF, so the new `reason`/`statusCode`
44
+ * attributes are only emitted once the gate is rolled out (EDITOR-7796).
45
+ *
46
+ * `gateEnabled` is injected by the caller (the store managers evaluate `fg(...)`) so this
47
+ * helper stays pure and trivially unit-testable for both gate states.
48
+ */
49
+ export const buildErrorAttribution = (gateEnabled, error, statusCode) => {
50
+ if (!gateEnabled) {
51
+ return undefined;
52
+ }
53
+ return {
54
+ reason: classifyErrorReason(error),
55
+ ...(statusCode !== undefined && {
56
+ statusCode
57
+ })
58
+ };
59
+ };
60
+
10
61
  // `sourceProduct` is threaded through every helper so analytics
11
62
  // events can be attributed to the source product (`confluence-page` vs `jira-work-item`).
12
63
  // All helpers accept it as an optional trailing argument; the spread-only-when-truthy
13
64
  // pattern below ensures we never emit `sourceProduct: undefined` (which would dirty the
14
65
  // event schema with empty keys).
15
66
 
16
- export const getErrorPayload = (actionSubjectId, error, resourceId, sourceProduct) => ({
67
+ export const getErrorPayload = (actionSubjectId, error, resourceId, sourceProduct, attribution) => ({
17
68
  action: ACTION.ERROR,
18
69
  actionSubject: ACTION_SUBJECT.SYNCED_BLOCK,
19
70
  actionSubjectId,
@@ -25,15 +76,21 @@ export const getErrorPayload = (actionSubjectId, error, resourceId, sourceProduc
25
76
  }),
26
77
  ...(sourceProduct && {
27
78
  sourceProduct
79
+ }),
80
+ ...((attribution === null || attribution === void 0 ? void 0 : attribution.reason) && {
81
+ reason: attribution.reason
82
+ }),
83
+ ...((attribution === null || attribution === void 0 ? void 0 : attribution.statusCode) !== undefined && {
84
+ statusCode: attribution.statusCode
28
85
  })
29
86
  }
30
87
  });
31
88
  export const fetchErrorPayload = (error, resourceId, sourceProduct) => getErrorPayload(ACTION_SUBJECT_ID.SYNCED_BLOCK_FETCH, error, resourceId, sourceProduct);
32
89
  export const getSourceInfoErrorPayload = (error, resourceId, sourceProduct) => getErrorPayload(ACTION_SUBJECT_ID.SYNCED_BLOCK_GET_SOURCE_INFO, error, resourceId, sourceProduct);
33
- export const updateErrorPayload = (error, resourceId, sourceProduct) => getErrorPayload(ACTION_SUBJECT_ID.SYNCED_BLOCK_UPDATE, error, resourceId, sourceProduct);
34
- export const updateReferenceErrorPayload = (error, resourceId, sourceProduct) => getErrorPayload(ACTION_SUBJECT_ID.REFERENCE_SYNCED_BLOCK_UPDATE, error, resourceId, sourceProduct);
35
- export const createErrorPayload = (error, resourceId, sourceProduct) => getErrorPayload(ACTION_SUBJECT_ID.SYNCED_BLOCK_CREATE, error, resourceId, sourceProduct);
36
- export const deleteErrorPayload = (error, resourceId, sourceProduct) => getErrorPayload(ACTION_SUBJECT_ID.SYNCED_BLOCK_DELETE, error, resourceId, sourceProduct);
90
+ export const updateErrorPayload = (error, resourceId, sourceProduct, attribution) => getErrorPayload(ACTION_SUBJECT_ID.SYNCED_BLOCK_UPDATE, error, resourceId, sourceProduct, attribution);
91
+ export const updateReferenceErrorPayload = (error, resourceId, sourceProduct, attribution) => getErrorPayload(ACTION_SUBJECT_ID.REFERENCE_SYNCED_BLOCK_UPDATE, error, resourceId, sourceProduct, attribution);
92
+ export const createErrorPayload = (error, resourceId, sourceProduct, attribution) => getErrorPayload(ACTION_SUBJECT_ID.SYNCED_BLOCK_CREATE, error, resourceId, sourceProduct, attribution);
93
+ export const deleteErrorPayload = (error, resourceId, sourceProduct, attribution) => getErrorPayload(ACTION_SUBJECT_ID.SYNCED_BLOCK_DELETE, error, resourceId, sourceProduct, attribution);
37
94
  export const updateCacheErrorPayload = (error, resourceId, sourceProduct) => getErrorPayload(ACTION_SUBJECT_ID.SYNCED_BLOCK_UPDATE_CACHE, error, resourceId, sourceProduct);
38
95
  /**
39
96
  * Payload for `SYNCED_BLOCK_SOURCE_INFO_ORPHANED`. Fired when source-info
@@ -567,7 +567,8 @@ export var writeDataBatch = /*#__PURE__*/function () {
567
567
  return _context4.abrupt("return", data.map(function (block) {
568
568
  return {
569
569
  error: mapBlockError(_t6),
570
- resourceId: block.resourceId
570
+ resourceId: block.resourceId,
571
+ statusCode: _t6.status
571
572
  };
572
573
  }));
573
574
  case 11:
@@ -913,7 +914,8 @@ var BlockServiceADFWriteProvider = /*#__PURE__*/function () {
913
914
  }
914
915
  return _context8.abrupt("return", {
915
916
  error: mapBlockError(_t0),
916
- resourceId: resourceId
917
+ resourceId: resourceId,
918
+ statusCode: _t0.status
917
919
  });
918
920
  case 8:
919
921
  return _context8.abrupt("return", {
@@ -993,7 +995,8 @@ var BlockServiceADFWriteProvider = /*#__PURE__*/function () {
993
995
  }
994
996
  return _context9.abrupt("return", {
995
997
  error: mapBlockError(_t10),
996
- resourceId: resourceId
998
+ resourceId: resourceId,
999
+ statusCode: _t10.status
997
1000
  });
998
1001
  case 8:
999
1002
  return _context9.abrupt("return", {
@@ -1077,7 +1080,8 @@ var BlockServiceADFWriteProvider = /*#__PURE__*/function () {
1077
1080
  return _context0.abrupt("return", {
1078
1081
  resourceId: resourceId,
1079
1082
  success: false,
1080
- error: mapBlockError(_t11)
1083
+ error: mapBlockError(_t11),
1084
+ statusCode: _t11.status
1081
1085
  });
1082
1086
  case 7:
1083
1087
  return _context0.abrupt("return", {
@@ -1152,7 +1156,8 @@ var BlockServiceADFWriteProvider = /*#__PURE__*/function () {
1152
1156
  return _context1.abrupt("return", {
1153
1157
  resourceId: resourceId,
1154
1158
  success: false,
1155
- error: mapBlockError(_t13)
1159
+ error: mapBlockError(_t13),
1160
+ statusCode: _t13.status
1156
1161
  });
1157
1162
  case 9:
1158
1163
  return _context1.abrupt("return", {
@@ -1182,7 +1187,8 @@ var BlockServiceADFWriteProvider = /*#__PURE__*/function () {
1182
1187
  return _context1.abrupt("return", {
1183
1188
  resourceId: resourceId,
1184
1189
  success: false,
1185
- error: mapBlockError(_t14)
1190
+ error: mapBlockError(_t14),
1191
+ statusCode: _t14.status
1186
1192
  });
1187
1193
  case 13:
1188
1194
  return _context1.abrupt("return", {
@@ -1267,7 +1273,8 @@ var BlockServiceADFWriteProvider = /*#__PURE__*/function () {
1267
1273
  }
1268
1274
  return _context10.abrupt("return", {
1269
1275
  success: false,
1270
- error: mapBlockError(_t15)
1276
+ error: mapBlockError(_t15),
1277
+ statusCode: _t15.status
1271
1278
  });
1272
1279
  case 4:
1273
1280
  return _context10.abrupt("return", {
@@ -1434,7 +1441,8 @@ var BlockServiceADFWriteProvider = /*#__PURE__*/function () {
1434
1441
  return _context12.abrupt("return", data.map(function (block) {
1435
1442
  return {
1436
1443
  error: mapBlockError(_t18),
1437
- resourceId: block.resourceId
1444
+ resourceId: block.resourceId,
1445
+ statusCode: _t18.status
1438
1446
  };
1439
1447
  }));
1440
1448
  case 15:
@@ -13,7 +13,7 @@ import isEqual from 'lodash/isEqual';
13
13
  import { logException } from '@atlaskit/editor-common/monitoring';
14
14
  import { fg } from '@atlaskit/platform-feature-flags';
15
15
  import { SyncBlockError } from '../common/types';
16
- import { cacheDeletionForcedPayload, fetchErrorPayload, fetchSuccessPayload, getSourceInfoErrorPayload, sourceInfoOrphanedPayload, updateReferenceErrorPayload } from '../utils/errorHandling';
16
+ import { buildErrorAttribution, cacheDeletionForcedPayload, fetchErrorPayload, fetchSuccessPayload, getSourceInfoErrorPayload, sourceInfoOrphanedPayload, updateReferenceErrorPayload } from '../utils/errorHandling';
17
17
  import { getFetchExperience, getFetchSourceInfoExperience, getSaveReferenceExperience } from '../utils/experienceTracking';
18
18
  import { resolveSyncBlockInstance } from '../utils/resolveSyncBlockInstance';
19
19
  import { createSyncBlockNode, getSourceProductFromResourceIdSafe, normalizeSyncBlockJSONContent } from '../utils/utils';
@@ -1077,7 +1077,7 @@ export var ReferenceSyncBlockStoreManager = /*#__PURE__*/function () {
1077
1077
  (_this$saveExperience2 = this.saveExperience) === null || _this$saveExperience2 === void 0 || _this$saveExperience2.failure({
1078
1078
  reason: updateResult.error || 'Failed to update reference synced blocks on the document'
1079
1079
  });
1080
- (_this$fireAnalyticsEv8 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv8 === void 0 || _this$fireAnalyticsEv8.call(this, updateReferenceErrorPayload(updateResult.error || 'Failed to update reference synced blocks on the document'));
1080
+ (_this$fireAnalyticsEv8 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv8 === void 0 || _this$fireAnalyticsEv8.call(this, updateReferenceErrorPayload(updateResult.error || 'Failed to update reference synced blocks on the document', undefined, undefined, buildErrorAttribution(fg('platform_editor_blocks_patch_3'), updateResult.error, updateResult.statusCode)));
1081
1081
  }
1082
1082
  _context4.next = 17;
1083
1083
  break;
@@ -1092,7 +1092,9 @@ export var ReferenceSyncBlockStoreManager = /*#__PURE__*/function () {
1092
1092
  reason: _t3.message
1093
1093
  });
1094
1094
  // No `resourceId` available in this catch — sourceProduct is intentionally omitted.
1095
- (_this$fireAnalyticsEv9 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv9 === void 0 || _this$fireAnalyticsEv9.call(this, updateReferenceErrorPayload(_t3.message));
1095
+ // No structured SyncBlockError/status here, so the attribution `reason` falls back
1096
+ // to `unknown` when the gate is on.
1097
+ (_this$fireAnalyticsEv9 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv9 === void 0 || _this$fireAnalyticsEv9.call(this, updateReferenceErrorPayload(_t3.message, undefined, undefined, buildErrorAttribution(fg('platform_editor_blocks_patch_3'))));
1096
1098
  case 17:
1097
1099
  _context4.prev = 17;
1098
1100
  if (!success) {
@@ -10,8 +10,9 @@ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length)
10
10
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
11
11
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
12
12
  import { logException } from '@atlaskit/editor-common/monitoring';
13
+ import { fg } from '@atlaskit/platform-feature-flags';
13
14
  import { SyncBlockError } from '../common/types';
14
- import { updateErrorPayload, createErrorPayload, deleteErrorPayload, updateCacheErrorPayload, getSourceInfoErrorPayload, updateSuccessPayload, createSuccessPayload, deleteSuccessPayload, fetchReferencesErrorPayload } from '../utils/errorHandling';
15
+ import { updateErrorPayload, createErrorPayload, deleteErrorPayload, updateCacheErrorPayload, getSourceInfoErrorPayload, updateSuccessPayload, createSuccessPayload, deleteSuccessPayload, fetchReferencesErrorPayload, buildErrorAttribution } from '../utils/errorHandling';
15
16
  import { getCreateSourceExperience, getDeleteSourceExperience, getSaveSourceExperience, getFetchSourceInfoExperience } from '../utils/experienceTracking';
16
17
  import { convertSyncBlockPMNodeToSyncBlockData, getSourceProductFromResourceIdSafe } from '../utils/utils';
17
18
  /** Maximum time (ms) flush() will wait for in-flight block creations before proceeding. */
@@ -158,7 +159,7 @@ export var SourceSyncBlockStoreManager = /*#__PURE__*/function () {
158
159
  value: (function () {
159
160
  var _flush = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
160
161
  var _this2 = this;
161
- var _this$saveExperience, timedOut, timeoutId, timeout, _iterator, _step, resourceId, bodiedSyncBlockNodes, bodiedSyncBlockData, writeResults, _this$saveExperience2, _this$saveExperience3, _this$fireAnalyticsEv2, _this$flushCompletion, _t;
162
+ var _this$saveExperience, timedOut, timeoutId, timeout, _iterator, _step, resourceId, bodiedSyncBlockNodes, bodiedSyncBlockData, writeResults, _this$saveExperience2, _this$saveExperience3, attributionEnabled, _this$fireAnalyticsEv2, _this$flushCompletion, _t;
162
163
  return _regeneratorRuntime.wrap(function (_context) {
163
164
  while (1) switch (_context.prev = _context.next) {
164
165
  case 0:
@@ -277,11 +278,12 @@ export var SourceSyncBlockStoreManager = /*#__PURE__*/function () {
277
278
  return _context.abrupt("return", true);
278
279
  case 7:
279
280
  (_this$saveExperience3 = this.saveExperience) === null || _this$saveExperience3 === void 0 || _this$saveExperience3.failure();
281
+ attributionEnabled = fg('platform_editor_blocks_patch_3');
280
282
  writeResults.filter(function (result) {
281
283
  return !result.resourceId || result.error;
282
284
  }).forEach(function (result) {
283
285
  var _this2$fireAnalyticsE2;
284
- (_this2$fireAnalyticsE2 = _this2.fireAnalyticsEvent) === null || _this2$fireAnalyticsE2 === void 0 || _this2$fireAnalyticsE2.call(_this2, updateErrorPayload(result.error || 'Failed to write data', result.resourceId, getSourceProductFromResourceIdSafe(result.resourceId)));
286
+ (_this2$fireAnalyticsE2 = _this2.fireAnalyticsEvent) === null || _this2$fireAnalyticsE2 === void 0 || _this2$fireAnalyticsE2.call(_this2, updateErrorPayload(result.error || 'Failed to write data', result.resourceId, getSourceProductFromResourceIdSafe(result.resourceId), buildErrorAttribution(attributionEnabled, result.error, result.statusCode)));
285
287
  });
286
288
  return _context.abrupt("return", false);
287
289
  case 8:
@@ -293,8 +295,9 @@ export var SourceSyncBlockStoreManager = /*#__PURE__*/function () {
293
295
  logException(_t, {
294
296
  location: 'editor-synced-block-provider/sourceSyncBlockStoreManager'
295
297
  });
296
- // Top-level flush failure is not tied to a single resourceId.
297
- (_this$fireAnalyticsEv2 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv2 === void 0 || _this$fireAnalyticsEv2.call(this, updateErrorPayload(_t.message));
298
+ // Top-level flush failure is not tied to a single resourceId. There is no structured
299
+ // SyncBlockError/status here, so the attribution `reason` falls back to `unknown`.
300
+ (_this$fireAnalyticsEv2 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv2 === void 0 || _this$fireAnalyticsEv2.call(this, updateErrorPayload(_t.message, undefined, undefined, buildErrorAttribution(fg('platform_editor_blocks_patch_3'))));
298
301
  return _context.abrupt("return", false);
299
302
  case 10:
300
303
  _context.prev = 10;
@@ -469,7 +472,7 @@ export var SourceSyncBlockStoreManager = /*#__PURE__*/function () {
469
472
  (_this4$createExperien2 = _this4.createExperience) === null || _this4$createExperien2 === void 0 || _this4$createExperien2.failure({
470
473
  reason: result.error || 'Failed to create bodied sync block'
471
474
  });
472
- (_this4$fireAnalyticsE = _this4.fireAnalyticsEvent) === null || _this4$fireAnalyticsE === void 0 || _this4$fireAnalyticsE.call(_this4, createErrorPayload(result.error || 'Failed to create bodied sync block', resourceId, getSourceProductFromResourceIdSafe(resourceId)));
475
+ (_this4$fireAnalyticsE = _this4.fireAnalyticsEvent) === null || _this4$fireAnalyticsE === void 0 || _this4$fireAnalyticsE.call(_this4, createErrorPayload(result.error || 'Failed to create bodied sync block', resourceId, getSourceProductFromResourceIdSafe(resourceId), buildErrorAttribution(fg('platform_editor_blocks_patch_3'), result.error, result.statusCode)));
473
476
  }
474
477
  }).catch(function (error) {
475
478
  var _this4$createExperien3, _this4$fireAnalyticsE2;
@@ -501,7 +504,7 @@ export var SourceSyncBlockStoreManager = /*#__PURE__*/function () {
501
504
  value: function () {
502
505
  var _delete2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2(syncBlockIds, onDelete, onDeleteCompleted, reason) {
503
506
  var _this5 = this;
504
- var _this$deleteExperienc, results, callback, isDeleteSuccessful, _this$deleteExperienc2, _this$deleteExperienc3, _t2;
507
+ var _this$deleteExperienc, results, callback, isDeleteSuccessful, _this$deleteExperienc2, _this$deleteExperienc3, attributionEnabled, attribution, _t2;
505
508
  return _regeneratorRuntime.wrap(function (_context2) {
506
509
  while (1) switch (_context2.prev = _context2.next) {
507
510
  case 0:
@@ -548,13 +551,14 @@ export var SourceSyncBlockStoreManager = /*#__PURE__*/function () {
548
551
  _this5.setPendingDeletion(Ids, false);
549
552
  };
550
553
  (_this$deleteExperienc3 = this.deleteExperience) === null || _this$deleteExperienc3 === void 0 || _this$deleteExperienc3.failure();
554
+ attributionEnabled = fg('platform_editor_blocks_patch_3');
551
555
  results.forEach(function (result) {
552
556
  if (result.success) {
553
557
  var _this5$fireAnalyticsE2;
554
558
  (_this5$fireAnalyticsE2 = _this5.fireAnalyticsEvent) === null || _this5$fireAnalyticsE2 === void 0 || _this5$fireAnalyticsE2.call(_this5, deleteSuccessPayload(result.resourceId, getSourceProductFromResourceIdSafe(result.resourceId)));
555
559
  } else {
556
560
  var _this5$fireAnalyticsE3;
557
- (_this5$fireAnalyticsE3 = _this5.fireAnalyticsEvent) === null || _this5$fireAnalyticsE3 === void 0 || _this5$fireAnalyticsE3.call(_this5, deleteErrorPayload(result.error || 'Failed to delete synced block', result.resourceId, getSourceProductFromResourceIdSafe(result.resourceId)));
561
+ (_this5$fireAnalyticsE3 = _this5.fireAnalyticsEvent) === null || _this5$fireAnalyticsE3 === void 0 || _this5$fireAnalyticsE3.call(_this5, deleteErrorPayload(result.error || 'Failed to delete synced block', result.resourceId, getSourceProductFromResourceIdSafe(result.resourceId), buildErrorAttribution(attributionEnabled, result.error, result.statusCode)));
558
562
  }
559
563
  });
560
564
  }
@@ -563,10 +567,13 @@ export var SourceSyncBlockStoreManager = /*#__PURE__*/function () {
563
567
  case 4:
564
568
  _context2.prev = 4;
565
569
  _t2 = _context2["catch"](0);
570
+ // Thrown (non-result) failure — no structured SyncBlockError/status is available,
571
+ // so the attribution `reason` falls back to `unknown` when the gate is on.
572
+ attribution = buildErrorAttribution(fg('platform_editor_blocks_patch_3'));
566
573
  syncBlockIds.forEach(function (Ids) {
567
574
  var _this5$fireAnalyticsE4;
568
575
  _this5.setPendingDeletion(Ids, false);
569
- (_this5$fireAnalyticsE4 = _this5.fireAnalyticsEvent) === null || _this5$fireAnalyticsE4 === void 0 || _this5$fireAnalyticsE4.call(_this5, deleteErrorPayload(_t2.message, Ids.resourceId, getSourceProductFromResourceIdSafe(Ids.resourceId)));
576
+ (_this5$fireAnalyticsE4 = _this5.fireAnalyticsEvent) === null || _this5$fireAnalyticsE4 === void 0 || _this5$fireAnalyticsE4.call(_this5, deleteErrorPayload(_t2.message, Ids.resourceId, getSourceProductFromResourceIdSafe(Ids.resourceId), attribution));
570
577
  });
571
578
  logException(_t2, {
572
579
  location: 'editor-synced-block-provider/sourceSyncBlockStoreManager'
@@ -2,6 +2,7 @@ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
2
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
3
3
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
4
4
  import { ACTION, ACTION_SUBJECT, EVENT_TYPE, ACTION_SUBJECT_ID } from '@atlaskit/editor-common/analytics';
5
+ import { SyncBlockError } from '../common/types';
5
6
  export var stringifyError = function stringifyError(error) {
6
7
  try {
7
8
  return JSON.stringify(error);
@@ -10,24 +11,77 @@ export var stringifyError = function stringifyError(error) {
10
11
  }
11
12
  };
12
13
 
14
+ /**
15
+ * The set of categorical failure reasons emitted on synced-block operational error
16
+ * events (EDITOR-7796). These let the analytics dashboard break delete/update/create
17
+ * failures down by cause (Block Service / HG / Relay) instead of relying on the
18
+ * free-text `error` blob. Mirrors the {@link SyncBlockError} enum values, plus an
19
+ * `unknown` fallback for unclassifiable errors.
20
+ */
21
+
22
+ var SYNC_BLOCK_ERROR_REASONS = new Set(Object.values(SyncBlockError));
23
+
24
+ /**
25
+ * Maps a result `error` field (which may be a {@link SyncBlockError} enum value such as
26
+ * `'not_found'`, or an arbitrary stringified blob) to a stable categorical
27
+ * {@link SyncBlockErrorReason} for analytics grouping. Anything that is not a known
28
+ * `SyncBlockError` value collapses to `'unknown'` so the dashboard never has to group on
29
+ * free-text error blobs.
30
+ */
31
+ export var classifyErrorReason = function classifyErrorReason(error) {
32
+ if (error && SYNC_BLOCK_ERROR_REASONS.has(error)) {
33
+ return error;
34
+ }
35
+ return 'unknown';
36
+ };
37
+
38
+ /**
39
+ * Extra, optional analytics attributes describing WHY an operational synced-block
40
+ * action failed. Spread conditionally so we never emit `undefined` keys (EDITOR-7796).
41
+ */
42
+
43
+ /**
44
+ * Builds the {@link ErrorAttributionAttributes} for a failed synced-block operation from
45
+ * the raw result `error` field and optional backend `statusCode`. Returns `undefined`
46
+ * when the `platform_editor_blocks_patch_3` gate is OFF, so the new `reason`/`statusCode`
47
+ * attributes are only emitted once the gate is rolled out (EDITOR-7796).
48
+ *
49
+ * `gateEnabled` is injected by the caller (the store managers evaluate `fg(...)`) so this
50
+ * helper stays pure and trivially unit-testable for both gate states.
51
+ */
52
+ export var buildErrorAttribution = function buildErrorAttribution(gateEnabled, error, statusCode) {
53
+ if (!gateEnabled) {
54
+ return undefined;
55
+ }
56
+ return _objectSpread({
57
+ reason: classifyErrorReason(error)
58
+ }, statusCode !== undefined && {
59
+ statusCode: statusCode
60
+ });
61
+ };
62
+
13
63
  // `sourceProduct` is threaded through every helper so analytics
14
64
  // events can be attributed to the source product (`confluence-page` vs `jira-work-item`).
15
65
  // All helpers accept it as an optional trailing argument; the spread-only-when-truthy
16
66
  // pattern below ensures we never emit `sourceProduct: undefined` (which would dirty the
17
67
  // event schema with empty keys).
18
68
 
19
- export var getErrorPayload = function getErrorPayload(actionSubjectId, error, resourceId, sourceProduct) {
69
+ export var getErrorPayload = function getErrorPayload(actionSubjectId, error, resourceId, sourceProduct, attribution) {
20
70
  return {
21
71
  action: ACTION.ERROR,
22
72
  actionSubject: ACTION_SUBJECT.SYNCED_BLOCK,
23
73
  actionSubjectId: actionSubjectId,
24
74
  eventType: EVENT_TYPE.OPERATIONAL,
25
- attributes: _objectSpread(_objectSpread({
75
+ attributes: _objectSpread(_objectSpread(_objectSpread(_objectSpread({
26
76
  error: error
27
77
  }, resourceId && {
28
78
  resourceId: resourceId
29
79
  }), sourceProduct && {
30
80
  sourceProduct: sourceProduct
81
+ }), (attribution === null || attribution === void 0 ? void 0 : attribution.reason) && {
82
+ reason: attribution.reason
83
+ }), (attribution === null || attribution === void 0 ? void 0 : attribution.statusCode) !== undefined && {
84
+ statusCode: attribution.statusCode
31
85
  })
32
86
  };
33
87
  };
@@ -37,17 +91,17 @@ export var fetchErrorPayload = function fetchErrorPayload(error, resourceId, sou
37
91
  export var getSourceInfoErrorPayload = function getSourceInfoErrorPayload(error, resourceId, sourceProduct) {
38
92
  return getErrorPayload(ACTION_SUBJECT_ID.SYNCED_BLOCK_GET_SOURCE_INFO, error, resourceId, sourceProduct);
39
93
  };
40
- export var updateErrorPayload = function updateErrorPayload(error, resourceId, sourceProduct) {
41
- return getErrorPayload(ACTION_SUBJECT_ID.SYNCED_BLOCK_UPDATE, error, resourceId, sourceProduct);
94
+ export var updateErrorPayload = function updateErrorPayload(error, resourceId, sourceProduct, attribution) {
95
+ return getErrorPayload(ACTION_SUBJECT_ID.SYNCED_BLOCK_UPDATE, error, resourceId, sourceProduct, attribution);
42
96
  };
43
- export var updateReferenceErrorPayload = function updateReferenceErrorPayload(error, resourceId, sourceProduct) {
44
- return getErrorPayload(ACTION_SUBJECT_ID.REFERENCE_SYNCED_BLOCK_UPDATE, error, resourceId, sourceProduct);
97
+ export var updateReferenceErrorPayload = function updateReferenceErrorPayload(error, resourceId, sourceProduct, attribution) {
98
+ return getErrorPayload(ACTION_SUBJECT_ID.REFERENCE_SYNCED_BLOCK_UPDATE, error, resourceId, sourceProduct, attribution);
45
99
  };
46
- export var createErrorPayload = function createErrorPayload(error, resourceId, sourceProduct) {
47
- return getErrorPayload(ACTION_SUBJECT_ID.SYNCED_BLOCK_CREATE, error, resourceId, sourceProduct);
100
+ export var createErrorPayload = function createErrorPayload(error, resourceId, sourceProduct, attribution) {
101
+ return getErrorPayload(ACTION_SUBJECT_ID.SYNCED_BLOCK_CREATE, error, resourceId, sourceProduct, attribution);
48
102
  };
49
- export var deleteErrorPayload = function deleteErrorPayload(error, resourceId, sourceProduct) {
50
- return getErrorPayload(ACTION_SUBJECT_ID.SYNCED_BLOCK_DELETE, error, resourceId, sourceProduct);
103
+ export var deleteErrorPayload = function deleteErrorPayload(error, resourceId, sourceProduct, attribution) {
104
+ return getErrorPayload(ACTION_SUBJECT_ID.SYNCED_BLOCK_DELETE, error, resourceId, sourceProduct, attribution);
51
105
  };
52
106
  export var updateCacheErrorPayload = function updateCacheErrorPayload(error, resourceId, sourceProduct) {
53
107
  return getErrorPayload(ACTION_SUBJECT_ID.SYNCED_BLOCK_UPDATE_CACHE, error, resourceId, sourceProduct);
@@ -25,6 +25,12 @@ export type SyncBlockInstance = {
25
25
  export type DeleteSyncBlockResult = {
26
26
  error?: string;
27
27
  resourceId: ResourceId;
28
+ /**
29
+ * HTTP status code from the backend (Block Service) when the failure originated
30
+ * from a `BlockError`. Surfaced so failure analytics can break down delete/update
31
+ * failures by status code (EDITOR-7796). Undefined for non-HTTP failures.
32
+ */
33
+ statusCode?: number;
28
34
  success: boolean;
29
35
  };
30
36
  /**
@@ -66,6 +72,12 @@ export type WriteSyncBlockResult = {
66
72
  error?: string;
67
73
  resourceId?: ResourceId;
68
74
  status?: SyncBlockStatus;
75
+ /**
76
+ * HTTP status code from the backend (Block Service) when the failure originated
77
+ * from a `BlockError`. Surfaced so failure analytics can break down update
78
+ * failures by status code (EDITOR-7796). Undefined for non-HTTP failures.
79
+ */
80
+ statusCode?: number;
69
81
  };
70
82
  export type SourceInfoFetchData = {
71
83
  pageARI: string;
@@ -73,6 +85,12 @@ export type SourceInfoFetchData = {
73
85
  };
74
86
  export type UpdateReferenceSyncBlockResult = {
75
87
  error?: string;
88
+ /**
89
+ * HTTP status code from the backend when the failure originated from a `BlockError`.
90
+ * Surfaced so reference-update failure analytics can break down failures by status
91
+ * code (EDITOR-7796). Undefined for non-HTTP failures.
92
+ */
93
+ statusCode?: number;
76
94
  success: boolean;
77
95
  };
78
96
  export type BlockNodeIdentifiers = {
@@ -1,17 +1,56 @@
1
1
  import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID } from '@atlaskit/editor-common/analytics';
2
2
  import type { RendererSyncBlockEventPayload, OperationalAEP, SyncBlockEventPayload } from '@atlaskit/editor-common/analytics';
3
+ import { SyncBlockError } from '../common/types';
3
4
  export declare const stringifyError: (error: unknown) => string | undefined;
4
- export declare const getErrorPayload: <T extends ACTION_SUBJECT_ID>(actionSubjectId: T, error: string, resourceId?: string, sourceProduct?: string) => OperationalAEP<ACTION.ERROR, ACTION_SUBJECT.SYNCED_BLOCK, T, {
5
+ /**
6
+ * The set of categorical failure reasons emitted on synced-block operational error
7
+ * events (EDITOR-7796). These let the analytics dashboard break delete/update/create
8
+ * failures down by cause (Block Service / HG / Relay) instead of relying on the
9
+ * free-text `error` blob. Mirrors the {@link SyncBlockError} enum values, plus an
10
+ * `unknown` fallback for unclassifiable errors.
11
+ */
12
+ export type SyncBlockErrorReason = `${SyncBlockError}` | 'unknown';
13
+ /**
14
+ * Maps a result `error` field (which may be a {@link SyncBlockError} enum value such as
15
+ * `'not_found'`, or an arbitrary stringified blob) to a stable categorical
16
+ * {@link SyncBlockErrorReason} for analytics grouping. Anything that is not a known
17
+ * `SyncBlockError` value collapses to `'unknown'` so the dashboard never has to group on
18
+ * free-text error blobs.
19
+ */
20
+ export declare const classifyErrorReason: (error?: string) => SyncBlockErrorReason;
21
+ /**
22
+ * Extra, optional analytics attributes describing WHY an operational synced-block
23
+ * action failed. Spread conditionally so we never emit `undefined` keys (EDITOR-7796).
24
+ */
25
+ export type ErrorAttributionAttributes = {
26
+ /** Categorical failure cause for dashboard grouping. */
27
+ reason?: SyncBlockErrorReason;
28
+ /** Backend HTTP status code when the failure came from a `BlockError`. */
29
+ statusCode?: number;
30
+ };
31
+ /**
32
+ * Builds the {@link ErrorAttributionAttributes} for a failed synced-block operation from
33
+ * the raw result `error` field and optional backend `statusCode`. Returns `undefined`
34
+ * when the `platform_editor_blocks_patch_3` gate is OFF, so the new `reason`/`statusCode`
35
+ * attributes are only emitted once the gate is rolled out (EDITOR-7796).
36
+ *
37
+ * `gateEnabled` is injected by the caller (the store managers evaluate `fg(...)`) so this
38
+ * helper stays pure and trivially unit-testable for both gate states.
39
+ */
40
+ export declare const buildErrorAttribution: (gateEnabled: boolean, error?: string, statusCode?: number) => ErrorAttributionAttributes | undefined;
41
+ export declare const getErrorPayload: <T extends ACTION_SUBJECT_ID>(actionSubjectId: T, error: string, resourceId?: string, sourceProduct?: string, attribution?: ErrorAttributionAttributes) => OperationalAEP<ACTION.ERROR, ACTION_SUBJECT.SYNCED_BLOCK, T, {
5
42
  error: string;
6
43
  resourceId?: string;
7
44
  sourceProduct?: string;
45
+ reason?: SyncBlockErrorReason;
46
+ statusCode?: number;
8
47
  }>;
9
48
  export declare const fetchErrorPayload: (error: string, resourceId?: string, sourceProduct?: string) => RendererSyncBlockEventPayload;
10
49
  export declare const getSourceInfoErrorPayload: (error: string, resourceId?: string, sourceProduct?: string) => RendererSyncBlockEventPayload;
11
- export declare const updateErrorPayload: (error: string, resourceId?: string, sourceProduct?: string) => SyncBlockEventPayload;
12
- export declare const updateReferenceErrorPayload: (error: string, resourceId?: string, sourceProduct?: string) => RendererSyncBlockEventPayload;
13
- export declare const createErrorPayload: (error: string, resourceId?: string, sourceProduct?: string) => SyncBlockEventPayload;
14
- export declare const deleteErrorPayload: (error: string, resourceId?: string, sourceProduct?: string) => SyncBlockEventPayload;
50
+ export declare const updateErrorPayload: (error: string, resourceId?: string, sourceProduct?: string, attribution?: ErrorAttributionAttributes) => SyncBlockEventPayload;
51
+ export declare const updateReferenceErrorPayload: (error: string, resourceId?: string, sourceProduct?: string, attribution?: ErrorAttributionAttributes) => RendererSyncBlockEventPayload;
52
+ export declare const createErrorPayload: (error: string, resourceId?: string, sourceProduct?: string, attribution?: ErrorAttributionAttributes) => SyncBlockEventPayload;
53
+ export declare const deleteErrorPayload: (error: string, resourceId?: string, sourceProduct?: string, attribution?: ErrorAttributionAttributes) => SyncBlockEventPayload;
15
54
  export declare const updateCacheErrorPayload: (error: string, resourceId?: string, sourceProduct?: string) => SyncBlockEventPayload;
16
55
  /**
17
56
  * Payload for `SYNCED_BLOCK_SOURCE_INFO_ORPHANED`. Fired when source-info
package/package.json CHANGED
@@ -21,7 +21,7 @@
21
21
  "@atlaskit/editor-prosemirror": "^8.0.0",
22
22
  "@atlaskit/node-data-provider": "^13.0.0",
23
23
  "@atlaskit/platform-feature-flags": "^2.0.0",
24
- "@atlaskit/tmp-editor-statsig": "^113.0.0",
24
+ "@atlaskit/tmp-editor-statsig": "^114.0.0",
25
25
  "@babel/runtime": "^7.0.0",
26
26
  "@compiled/react": "^0.20.0",
27
27
  "graphql-ws": "^5.14.2",
@@ -30,7 +30,7 @@
30
30
  "uuid": "^3.1.0"
31
31
  },
32
32
  "peerDependencies": {
33
- "@atlaskit/editor-common": "^116.12.0",
33
+ "@atlaskit/editor-common": "^116.13.0",
34
34
  "react": "^18.2.0"
35
35
  },
36
36
  "devDependencies": {
@@ -74,7 +74,7 @@
74
74
  }
75
75
  },
76
76
  "name": "@atlaskit/editor-synced-block-provider",
77
- "version": "8.1.7",
77
+ "version": "8.1.9",
78
78
  "description": "Synced Block Provider for @atlaskit/editor-plugin-synced-block",
79
79
  "author": "Atlassian Pty Ltd",
80
80
  "license": "Apache-2.0",
@@ -85,6 +85,9 @@
85
85
  "platform_editor_blocks_patch_2": {
86
86
  "type": "boolean"
87
87
  },
88
+ "platform_editor_blocks_patch_3": {
89
+ "type": "boolean"
90
+ },
88
91
  "platform_synced_block_patch_13": {
89
92
  "type": "boolean"
90
93
  },