@atlaskit/editor-synced-block-provider 8.2.0 → 8.3.0

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 (25) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/cjs/hooks/useFetchSyncBlockData.js +1 -1
  3. package/dist/cjs/providers/block-service/blockServiceAPI.js +7 -4
  4. package/dist/cjs/store-manager/referenceSyncBlockStoreManager.js +9 -4
  5. package/dist/cjs/store-manager/syncBlockBatchFetcher.js +2 -1
  6. package/dist/cjs/store-manager/syncBlockProviderFactoryManager.js +4 -3
  7. package/dist/cjs/store-manager/syncBlockSubscriptionManager.js +11 -4
  8. package/dist/cjs/utils/errorHandling.js +152 -6
  9. package/dist/es2019/hooks/useFetchSyncBlockData.js +2 -2
  10. package/dist/es2019/providers/block-service/blockServiceAPI.js +11 -3
  11. package/dist/es2019/store-manager/referenceSyncBlockStoreManager.js +10 -5
  12. package/dist/es2019/store-manager/syncBlockBatchFetcher.js +3 -2
  13. package/dist/es2019/store-manager/syncBlockProviderFactoryManager.js +5 -4
  14. package/dist/es2019/store-manager/syncBlockSubscriptionManager.js +12 -5
  15. package/dist/es2019/utils/errorHandling.js +167 -22
  16. package/dist/esm/hooks/useFetchSyncBlockData.js +2 -2
  17. package/dist/esm/providers/block-service/blockServiceAPI.js +7 -4
  18. package/dist/esm/store-manager/referenceSyncBlockStoreManager.js +10 -5
  19. package/dist/esm/store-manager/syncBlockBatchFetcher.js +3 -2
  20. package/dist/esm/store-manager/syncBlockProviderFactoryManager.js +5 -4
  21. package/dist/esm/store-manager/syncBlockSubscriptionManager.js +12 -5
  22. package/dist/esm/utils/errorHandling.js +149 -5
  23. package/dist/types/providers/types.d.ts +8 -0
  24. package/dist/types/utils/errorHandling.d.ts +94 -2
  25. package/package.json +3 -3
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @atlaskit/editor-synced-block-provider
2
2
 
3
+ ## 8.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`426eef1e61679`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/426eef1e61679) -
8
+ Add failure `reason`, HTTP `statusCode` and `benign` attributes to synced block fetch/subscribe
9
+ operational error analytics, so fetch failures can be broken down by cause (benign source-gone /
10
+ permission-denied vs genuine system failures) instead of regex-matching free text. Gated behind
11
+ `platform_editor_blocks_patch_3`; gate-off behaviour is unchanged.
12
+
13
+ ### Patch Changes
14
+
15
+ - Updated dependencies
16
+
3
17
  ## 8.2.0
4
18
 
5
19
  ### Minor Changes
@@ -105,7 +105,7 @@ var useFetchSyncBlockData = exports.useFetchSyncBlockData = function useFetchSyn
105
105
  (0, _monitoring.logException)(_t, {
106
106
  location: 'editor-synced-block-provider/useFetchSyncBlockData'
107
107
  });
108
- fireAnalyticsEvent === null || fireAnalyticsEvent === void 0 || fireAnalyticsEvent((0, _errorHandling.fetchErrorPayload)(_t.message, resourceId, (0, _utils.getSourceProductFromResourceIdSafe)(resourceId)));
108
+ fireAnalyticsEvent === null || fireAnalyticsEvent === void 0 || fireAnalyticsEvent((0, _errorHandling.fetchErrorPayload)(_t.message, resourceId, (0, _utils.getSourceProductFromResourceIdSafe)(resourceId), (0, _errorHandling.buildFetchErrorAttribution)((0, _platformFeatureFlags.fg)('platform_editor_blocks_patch_3'), _t.message)));
109
109
 
110
110
  // Set error state if fetching fails
111
111
  setFetchState({
@@ -7,9 +7,9 @@ Object.defineProperty(exports, "__esModule", {
7
7
  });
8
8
  exports.writeDataBatch = exports.useMemoizedBlockServiceFetchOnlyAPIProvider = exports.useMemoizedBlockServiceAPIProviders = exports.fetchReferences = exports.extractResourceIdFromBlockAri = exports.convertToSyncBlockData = exports.blockAriToResourceId = exports.batchFetchData = void 0;
9
9
  var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
10
- var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
11
10
  var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
12
11
  var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
12
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
13
13
  var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
14
14
  var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
15
15
  var _react = require("react");
@@ -413,10 +413,12 @@ var _batchFetchData = exports.batchFetchData = /*#__PURE__*/function () {
413
413
  case 21:
414
414
  return _context2.abrupt("return", blockNodeIdentifiers.map(function (blockNodeIdentifier) {
415
415
  return {
416
- error: {
416
+ error: _objectSpread({
417
417
  type: _t4 instanceof _blockService.BlockError ? mapBlockError(_t4) : _types.SyncBlockError.Errored,
418
418
  reason: _t4.message
419
- },
419
+ }, _t4 instanceof _blockService.BlockError && {
420
+ statusCode: _t4.status
421
+ }),
420
422
  resourceId: blockNodeIdentifier.resourceId
421
423
  };
422
424
  }));
@@ -668,7 +670,8 @@ var BlockServiceADFFetchProvider = /*#__PURE__*/function () {
668
670
  return _context5.abrupt("return", {
669
671
  error: {
670
672
  type: mapBlockError(_t7),
671
- reason: _t7.message
673
+ reason: _t7.message,
674
+ statusCode: _t7.status
672
675
  },
673
676
  resourceId: resourceId
674
677
  });
@@ -609,10 +609,12 @@ var ReferenceSyncBlockStoreManager = exports.ReferenceSyncBlockStoreManager = /*
609
609
  data.forEach(function (syncBlockInstance) {
610
610
  var _resolvedSyncBlockIns;
611
611
  if (!syncBlockInstance.resourceId) {
612
- var _syncBlockInstance$er, _syncBlockInstance$er2, _this5$fireAnalyticsE;
612
+ var _syncBlockInstance$er, _syncBlockInstance$er2, _this5$fireAnalyticsE, _syncBlockInstance$er3, _syncBlockInstance$er4, _syncBlockInstance$er5;
613
613
  var payload = ((_syncBlockInstance$er = syncBlockInstance.error) === null || _syncBlockInstance$er === void 0 ? void 0 : _syncBlockInstance$er.reason) || ((_syncBlockInstance$er2 = syncBlockInstance.error) === null || _syncBlockInstance$er2 === void 0 ? void 0 : _syncBlockInstance$er2.type) || 'Returned sync block instance does not have resource id';
614
614
  // No resourceId means we cannot derive a sourceProduct here; intentionally omit.
615
- (_this5$fireAnalyticsE = _this5.fireAnalyticsEvent) === null || _this5$fireAnalyticsE === void 0 || _this5$fireAnalyticsE.call(_this5, (0, _errorHandling.fetchErrorPayload)(payload));
615
+ // Classify on the structured `type` first, falling back to the free-text
616
+ // `reason`/payload (EDITOR-7862).
617
+ (_this5$fireAnalyticsE = _this5.fireAnalyticsEvent) === null || _this5$fireAnalyticsE === void 0 || _this5$fireAnalyticsE.call(_this5, (0, _errorHandling.fetchErrorPayload)(payload, undefined, undefined, (0, _errorHandling.buildFetchErrorAttribution)((0, _platformFeatureFlags.fg)('platform_editor_blocks_patch_3'), ((_syncBlockInstance$er3 = syncBlockInstance.error) === null || _syncBlockInstance$er3 === void 0 ? void 0 : _syncBlockInstance$er3.type) || ((_syncBlockInstance$er4 = syncBlockInstance.error) === null || _syncBlockInstance$er4 === void 0 ? void 0 : _syncBlockInstance$er4.reason) || payload, (_syncBlockInstance$er5 = syncBlockInstance.error) === null || _syncBlockInstance$er5 === void 0 ? void 0 : _syncBlockInstance$er5.statusCode)));
616
618
  return;
617
619
  }
618
620
  var existingSyncBlock = _this5.getFromCache(syncBlockInstance.resourceId);
@@ -646,7 +648,10 @@ var ReferenceSyncBlockStoreManager = exports.ReferenceSyncBlockStoreManager = /*
646
648
  var isRetryingEntityNotFound = syncBlockInstance.error.type === _types.SyncBlockError.EntityNotFound && ((_this5$entityNotFound = _this5.entityNotFoundRetryCount.get(syncBlockInstance.resourceId)) !== null && _this5$entityNotFound !== void 0 ? _this5$entityNotFound : 0) < ENTITY_NOT_FOUND_MAX_RETRIES && (0, _platformFeatureFlags.fg)('platform_synced_block_patch_13');
647
649
  if (!isRetryingEntityNotFound) {
648
650
  var _this5$fireAnalyticsE2, _syncBlockInstance$da, _syncBlockInstance$da2;
649
- (_this5$fireAnalyticsE2 = _this5.fireAnalyticsEvent) === null || _this5$fireAnalyticsE2 === void 0 || _this5$fireAnalyticsE2.call(_this5, (0, _errorHandling.fetchErrorPayload)(syncBlockInstance.error.reason || syncBlockInstance.error.type, syncBlockInstance.resourceId, (_syncBlockInstance$da = (_syncBlockInstance$da2 = syncBlockInstance.data) === null || _syncBlockInstance$da2 === void 0 ? void 0 : _syncBlockInstance$da2.product) !== null && _syncBlockInstance$da !== void 0 ? _syncBlockInstance$da : (0, _utils.getSourceProductFromResourceIdSafe)(syncBlockInstance.resourceId)));
651
+ // Classify on the structured `type` (a `SyncBlockError` enum value) first,
652
+ // falling back to the free-text `reason` so source-state/permission strings
653
+ // are still bucketed (EDITOR-7862). The emitted `error` attribute is unchanged.
654
+ (_this5$fireAnalyticsE2 = _this5.fireAnalyticsEvent) === null || _this5$fireAnalyticsE2 === void 0 || _this5$fireAnalyticsE2.call(_this5, (0, _errorHandling.fetchErrorPayload)(syncBlockInstance.error.reason || syncBlockInstance.error.type, syncBlockInstance.resourceId, (_syncBlockInstance$da = (_syncBlockInstance$da2 = syncBlockInstance.data) === null || _syncBlockInstance$da2 === void 0 ? void 0 : _syncBlockInstance$da2.product) !== null && _syncBlockInstance$da !== void 0 ? _syncBlockInstance$da : (0, _utils.getSourceProductFromResourceIdSafe)(syncBlockInstance.resourceId), (0, _errorHandling.buildFetchErrorAttribution)((0, _platformFeatureFlags.fg)('platform_editor_blocks_patch_3'), syncBlockInstance.error.type || syncBlockInstance.error.reason, syncBlockInstance.error.statusCode)));
650
655
  }
651
656
  if (syncBlockInstance.error.type === _types.SyncBlockError.NotFound || syncBlockInstance.error.type === _types.SyncBlockError.Forbidden) {
652
657
  hasExpectedError = true;
@@ -970,7 +975,7 @@ var ReferenceSyncBlockStoreManager = exports.ReferenceSyncBlockStoreManager = /*
970
975
  (0, _monitoring.logException)(error, {
971
976
  location: 'editor-synced-block-provider/referenceSyncBlockStoreManager'
972
977
  });
973
- (_this$fireAnalyticsEv7 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv7 === void 0 || _this$fireAnalyticsEv7.call(this, (0, _errorHandling.fetchErrorPayload)(error.message));
978
+ (_this$fireAnalyticsEv7 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv7 === void 0 || _this$fireAnalyticsEv7.call(this, (0, _errorHandling.fetchErrorPayload)(error.message, undefined, undefined, (0, _errorHandling.buildFetchErrorAttribution)((0, _platformFeatureFlags.fg)('platform_editor_blocks_patch_3'), error.message)));
974
979
  return function () {};
975
980
  }
976
981
  }
@@ -75,9 +75,10 @@ var SyncBlockBatchFetcher = exports.SyncBlockBatchFetcher = /*#__PURE__*/functio
75
75
  (0, _monitoring.logException)(error, {
76
76
  location: 'editor-synced-block-provider/syncBlockBatchFetcher/batchedFetchSyncBlocks'
77
77
  });
78
+ var attribution = (0, _errorHandling.buildFetchErrorAttribution)((0, _platformFeatureFlags.fg)('platform_editor_blocks_patch_3'), error.message);
78
79
  resourceIds.forEach(function (resId) {
79
80
  var _this$deps$getFireAna;
80
- (_this$deps$getFireAna = _this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna === void 0 || _this$deps$getFireAna((0, _errorHandling.fetchErrorPayload)(error.message, resId, (0, _utils.getSourceProductFromResourceIdSafe)(resId)));
81
+ (_this$deps$getFireAna = _this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna === void 0 || _this$deps$getFireAna((0, _errorHandling.fetchErrorPayload)(error.message, resId, (0, _utils.getSourceProductFromResourceIdSafe)(resId), attribution));
81
82
  });
82
83
  }).finally(function () {
83
84
  // If the fetcher was destroyed while the request was in flight,
@@ -10,6 +10,7 @@ var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/creat
10
10
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
11
11
  var _monitoring = require("@atlaskit/editor-common/monitoring");
12
12
  var _providerFactory = require("@atlaskit/editor-common/provider-factory");
13
+ var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
13
14
  var _errorHandling = require("../utils/errorHandling");
14
15
  var _resourceId = require("../utils/resourceId");
15
16
  var _utils = require("../utils/utils");
@@ -33,7 +34,7 @@ var SyncBlockProviderFactoryManager = exports.SyncBlockProviderFactoryManager =
33
34
  (0, _monitoring.logException)(error, {
34
35
  location: 'editor-synced-block-provider/syncBlockProviderFactoryManager'
35
36
  });
36
- (_this$deps$getFireAna = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna === void 0 || _this$deps$getFireAna((0, _errorHandling.fetchErrorPayload)(error.message, resourceId, (0, _utils.getSourceProductFromResourceIdSafe)(resourceId)));
37
+ (_this$deps$getFireAna = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna === void 0 || _this$deps$getFireAna((0, _errorHandling.fetchErrorPayload)(error.message, resourceId, (0, _utils.getSourceProductFromResourceIdSafe)(resourceId), (0, _errorHandling.buildFetchErrorAttribution)((0, _platformFeatureFlags.fg)('platform_editor_blocks_patch_3'), error.message)));
37
38
  return undefined;
38
39
  }
39
40
  var _dataProvider$getSync = dataProvider.getSyncedBlockRendererProviderOptions(),
@@ -66,7 +67,7 @@ var SyncBlockProviderFactoryManager = exports.SyncBlockProviderFactoryManager =
66
67
  (0, _monitoring.logException)(error, {
67
68
  location: 'editor-synced-block-provider/syncBlockProviderFactoryManager'
68
69
  });
69
- (_this$deps$getFireAna2 = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna2 === void 0 || _this$deps$getFireAna2((0, _errorHandling.fetchErrorPayload)(error.message, resourceId, (0, _utils.getSourceProductFromResourceIdSafe)(resourceId)));
70
+ (_this$deps$getFireAna2 = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna2 === void 0 || _this$deps$getFireAna2((0, _errorHandling.fetchErrorPayload)(error.message, resourceId, (0, _utils.getSourceProductFromResourceIdSafe)(resourceId), (0, _errorHandling.buildFetchErrorAttribution)((0, _platformFeatureFlags.fg)('platform_editor_blocks_patch_3'), error.message)));
70
71
  }
71
72
  }
72
73
  return providerFactory;
@@ -151,7 +152,7 @@ var SyncBlockProviderFactoryManager = exports.SyncBlockProviderFactoryManager =
151
152
  if (!syncBlock.data.sourceAri || !syncBlock.data.product) {
152
153
  var _this$deps$getFireAna3, _syncBlock$data$produ;
153
154
  (_this$deps$getFireAna3 = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna3 === void 0 || _this$deps$getFireAna3((0, _errorHandling.fetchErrorPayload)('Sync block source ari or product not found', resourceId, // Prefer cached product when available; fall back to parsing resourceId.
154
- (_syncBlock$data$produ = syncBlock.data.product) !== null && _syncBlock$data$produ !== void 0 ? _syncBlock$data$produ : (0, _utils.getSourceProductFromResourceIdSafe)(resourceId)));
155
+ (_syncBlock$data$produ = syncBlock.data.product) !== null && _syncBlock$data$produ !== void 0 ? _syncBlock$data$produ : (0, _utils.getSourceProductFromResourceIdSafe)(resourceId), (0, _errorHandling.buildFetchErrorAttribution)((0, _platformFeatureFlags.fg)('platform_editor_blocks_patch_3'), 'Sync block source ari or product not found')));
155
156
  return;
156
157
  }
157
158
  var parentInfo = dataProvider.retrieveSyncBlockParentInfo(syncBlock.data.sourceAri, syncBlock.data.product);
@@ -120,7 +120,7 @@ var SyncBlockSubscriptionManager = exports.SyncBlockSubscriptionManager = /*#__P
120
120
  (0, _monitoring.logException)(error, {
121
121
  location: 'editor-synced-block-provider/syncBlockSubscriptionManager/notifySubscriptionChangeListeners'
122
122
  });
123
- (_this2$deps$getFireAn = _this2.deps.getFireAnalyticsEvent()) === null || _this2$deps$getFireAn === void 0 || _this2$deps$getFireAn((0, _errorHandling.fetchErrorPayload)(error.message));
123
+ (_this2$deps$getFireAn = _this2.deps.getFireAnalyticsEvent()) === null || _this2$deps$getFireAn === void 0 || _this2$deps$getFireAn((0, _errorHandling.fetchErrorPayload)(error.message, undefined, undefined, (0, _errorHandling.buildFetchErrorAttribution)((0, _platformFeatureFlags.fg)('platform_editor_blocks_patch_3'), error.message)));
124
124
  }
125
125
  });
126
126
  }
@@ -305,6 +305,9 @@ var SyncBlockSubscriptionManager = exports.SyncBlockSubscriptionManager = /*#__P
305
305
  // recovers on reconnect, so under the gate we don't fire a
306
306
  // user-facing error here — it's only surfaced on exhaustion (see
307
307
  // scheduleReconnection). Gate OFF keeps the legacy fire-on-drop.
308
+ // This branch only runs when the gate is OFF, so buildFetchErrorAttribution
309
+ // would return undefined; the structured attribution (EDITOR-7862) is therefore
310
+ // applied at the gate-ON exhaustion site in scheduleReconnection instead.
308
311
  if (!(0, _platformFeatureFlags.fg)('platform_editor_blocks_patch_3')) {
309
312
  var _this5$deps$getFireAn;
310
313
  (_this5$deps$getFireAn = _this5.deps.getFireAnalyticsEvent()) === null || _this5$deps$getFireAn === void 0 || _this5$deps$getFireAn((0, _errorHandling.fetchErrorPayload)(error.message, resourceId, (0, _utils.getSourceProductFromResourceIdSafe)(resourceId)));
@@ -356,7 +359,7 @@ var SyncBlockSubscriptionManager = exports.SyncBlockSubscriptionManager = /*#__P
356
359
  (0, _monitoring.logException)(new Error(errorMessage), {
357
360
  location: 'editor-synced-block-provider/syncBlockSubscriptionManager/max-retries-exhausted'
358
361
  });
359
- (_this$deps$getFireAna = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna === void 0 || _this$deps$getFireAna((0, _errorHandling.fetchErrorPayload)(errorMessage, resourceId, (0, _utils.getSourceProductFromResourceIdSafe)(resourceId)));
362
+ (_this$deps$getFireAna = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna === void 0 || _this$deps$getFireAna((0, _errorHandling.fetchErrorPayload)(errorMessage, resourceId, (0, _utils.getSourceProductFromResourceIdSafe)(resourceId), (0, _errorHandling.buildFetchErrorAttribution)((0, _platformFeatureFlags.fg)('platform_editor_blocks_patch_3'), errorMessage)));
360
363
  return;
361
364
  }
362
365
  var delay = this.getReconnectionDelay(attempts);
@@ -510,9 +513,13 @@ var SyncBlockSubscriptionManager = exports.SyncBlockSubscriptionManager = /*#__P
510
513
  });
511
514
  this.deps.fetchSyncBlockSourceInfo(resolved.resourceId);
512
515
  } else {
513
- var _syncBlockInstance$er, _syncBlockInstance$er2, _this$deps$getFireAna2, _syncBlockInstance$da3, _syncBlockInstance$da4;
516
+ var _syncBlockInstance$er, _syncBlockInstance$er2, _this$deps$getFireAna2, _syncBlockInstance$da3, _syncBlockInstance$da4, _syncBlockInstance$er3, _syncBlockInstance$er4, _syncBlockInstance$er5;
514
517
  var errorMessage = ((_syncBlockInstance$er = syncBlockInstance.error) === null || _syncBlockInstance$er === void 0 ? void 0 : _syncBlockInstance$er.reason) || ((_syncBlockInstance$er2 = syncBlockInstance.error) === null || _syncBlockInstance$er2 === void 0 ? void 0 : _syncBlockInstance$er2.type);
515
- (_this$deps$getFireAna2 = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna2 === void 0 || _this$deps$getFireAna2((0, _errorHandling.fetchErrorPayload)(errorMessage, syncBlockInstance.resourceId, (_syncBlockInstance$da3 = (_syncBlockInstance$da4 = syncBlockInstance.data) === null || _syncBlockInstance$da4 === void 0 ? void 0 : _syncBlockInstance$da4.product) !== null && _syncBlockInstance$da3 !== void 0 ? _syncBlockInstance$da3 : (0, _utils.getSourceProductFromResourceIdSafe)(syncBlockInstance.resourceId)));
518
+
519
+ // Prefer the structured `type` (a `SyncBlockError` enum value) for classification
520
+ // and fall back to the free-text `reason` so source-state/permission strings are
521
+ // still bucketed (EDITOR-7862). The emitted free-text `error` attribute is unchanged.
522
+ (_this$deps$getFireAna2 = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna2 === void 0 || _this$deps$getFireAna2((0, _errorHandling.fetchErrorPayload)(errorMessage, syncBlockInstance.resourceId, (_syncBlockInstance$da3 = (_syncBlockInstance$da4 = syncBlockInstance.data) === null || _syncBlockInstance$da4 === void 0 ? void 0 : _syncBlockInstance$da4.product) !== null && _syncBlockInstance$da3 !== void 0 ? _syncBlockInstance$da3 : (0, _utils.getSourceProductFromResourceIdSafe)(syncBlockInstance.resourceId), (0, _errorHandling.buildFetchErrorAttribution)((0, _platformFeatureFlags.fg)('platform_editor_blocks_patch_3'), ((_syncBlockInstance$er3 = syncBlockInstance.error) === null || _syncBlockInstance$er3 === void 0 ? void 0 : _syncBlockInstance$er3.type) || ((_syncBlockInstance$er4 = syncBlockInstance.error) === null || _syncBlockInstance$er4 === void 0 ? void 0 : _syncBlockInstance$er4.reason), (_syncBlockInstance$er5 = syncBlockInstance.error) === null || _syncBlockInstance$er5 === void 0 ? void 0 : _syncBlockInstance$er5.statusCode)));
516
523
  }
517
524
  }
518
525
  }]);
@@ -4,7 +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.classifyErrorReason = exports.cacheDeletionForcedPayload = exports.buildErrorAttribution = void 0;
7
+ exports.fetchSuccessPayload = exports.fetchReferencesErrorPayload = exports.fetchErrorPayload = exports.deleteSuccessPayload = exports.deleteErrorPayload = exports.createSuccessPayloadNew = exports.createSuccessPayload = exports.createErrorPayload = exports.classifyFetchErrorReason = exports.classifyErrorReason = exports.cacheDeletionForcedPayload = exports.buildFetchErrorAttribution = exports.buildErrorAttribution = exports.FETCH_BENIGN_REASONS = void 0;
8
+ exports.getErrorPayload = getErrorPayload;
9
+ exports.updateSuccessPayload = exports.updateReferenceErrorPayload = exports.updateErrorPayload = exports.updateCacheErrorPayload = exports.stringifyError = exports.sourceInfoOrphanedPayload = exports.getSourceInfoErrorPayload = void 0;
10
+ var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
8
11
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
9
12
  var _analytics = require("@atlaskit/editor-common/analytics");
10
13
  var _types = require("../common/types");
@@ -67,19 +70,155 @@ var buildErrorAttribution = exports.buildErrorAttribution = function buildErrorA
67
70
  });
68
71
  };
69
72
 
73
+ /**
74
+ * The set of categorical failure reasons emitted on synced-block fetch/subscribe
75
+ * operational error events (EDITOR-7862). Extends the write-path
76
+ * {@link SyncBlockErrorReason} set with read-path-specific buckets so the analytics
77
+ * dashboard can break fetch failures down by cause instead of regex-matching the
78
+ * free-text `error` blob.
79
+ *
80
+ * The read path surfaces several causes the write-path `SyncBlockError` enum does not
81
+ * model (benign source-state transitions, permission denials, WebSocket lifecycle, and
82
+ * client-side readiness errors), so those are added here. Known `SyncBlockError` enum
83
+ * values (`not_found`, `forbidden`, ...) still pass through unchanged.
84
+ */
85
+
86
+ /**
87
+ * Fetch reasons that represent benign (working-as-designed) outcomes rather than genuine
88
+ * system failures. The dashboard uses this to compute a "true error rate" by excluding
89
+ * benign reasons via the structured `reason` attribute instead of brittle free-text regex
90
+ * (EDITOR-7862).
91
+ */
92
+ var FETCH_BENIGN_REASONS = exports.FETCH_BENIGN_REASONS = new Set(['source_deleted', 'source_unpublished', 'source_unsynced', 'source_not_found', 'permission_denied', 'unauthenticated',
93
+ // Mirror the benign write-path enum equivalents so already-classified errors are
94
+ // treated consistently.
95
+ 'not_found', 'forbidden', 'unpublished'
96
+ // NOTE: `entity_not_found` and `offline` are intentionally NOT benign.
97
+ // - `entity_not_found` is retried up to ENTITY_NOT_FOUND_MAX_RETRIES (analytics are
98
+ // suppressed during retries); by the time the error event fires, retries are
99
+ // exhausted and it is a genuine failure.
100
+ // - `offline` is genuine client connectivity loss, not a working-as-designed outcome.
101
+ // Both still emit `reason` (so they can be inspected) but are counted as true errors.
102
+ ]);
103
+
104
+ /**
105
+ * Ordered list of substring matchers mapping known free-text fetch/subscribe error
106
+ * strings to a categorical {@link SyncBlockFetchErrorReason}. Each entry is a list of
107
+ * lowercase needles; if ANY needle is found (case-insensitively) in the raw `error`
108
+ * string, the entry's reason is used. Order matters: more specific entries must precede
109
+ * more general ones.
110
+ *
111
+ * Plain `String.includes` substring matching is used deliberately instead of `RegExp`:
112
+ * it sidesteps the `require-unicode-regexp` lint rule AND the declaration build's lower
113
+ * compile target (which rejects the regex `u` flag with TS1501), while being faster and
114
+ * easier to read. Doing this classification in code (rather than in dashboard SQL) keeps
115
+ * the buckets versioned with the source strings that produce them (EDITOR-7862).
116
+ */
117
+ var FETCH_REASON_MATCHERS = [
118
+ // Benign: source-state transitions (deletionReason values + free text).
119
+ [['source-document-deleted'], 'source_deleted'], [['source-block-deleted'], 'source_deleted'], [['source-block-unpublished'], 'source_unpublished'], [['source-block-unsynced'], 'source_unsynced'], [['does not exist'], 'source_not_found'], [['unpublished'], 'source_unpublished'],
120
+ // Working-as-designed permission outcomes.
121
+ [['unauthenticated'], 'unauthenticated'], [['not permitted to read synced block'], 'permission_denied'], [['bulk permission check failed'], 'permission_denied'], [['permission denied'], 'permission_denied'],
122
+ // Genuine system failures.
123
+ [['reconnection failed after'], 'websocket_exhausted'],
124
+ // `reconnect to the subscription` (not a bare `reconnect`) avoids false positives on
125
+ // unrelated DB/GraphQL "reconnect" messages while still matching the WS-drop string.
126
+ [['websocket', 'reconnect to the subscription', 'subscription terminated'], 'websocket_drop'], [['429', 'rate limit', 'rate-limit', 'ratelimit'], 'rate_limited'],
127
+ // `econnrefused`/`econnreset` (not a bare `econn`, which would also match `reconnect`).
128
+ [['network', 'failed to fetch', 'econnrefused', 'econnreset', 'timed out', 'timeout'], 'network'], [['data provider not set'], 'data_provider_not_ready']];
129
+
130
+ /**
131
+ * Maps a fetch/subscribe `error` field — which may be a {@link SyncBlockError} enum
132
+ * value, a {@link DeletionReason} value, or an arbitrary free-text/JSON blob — to a
133
+ * stable categorical {@link SyncBlockFetchErrorReason} for analytics grouping.
134
+ *
135
+ * Resolution order:
136
+ * 1. Known `SyncBlockError` enum value → passed through (via {@link classifyErrorReason}).
137
+ * 2. Known free-text substring → mapped to a fetch-specific bucket.
138
+ * 3. Anything else (including opaque blobs like `errored`-only payloads or
139
+ * `ErrorEvent: "undefined"`) → `'unknown'`, so the dashboard never groups on free text.
140
+ *
141
+ * Note: the bare string `'errored'` IS a `SyncBlockError` enum value and therefore
142
+ * classifies as `'errored'` (not `'unknown'`); only genuinely unrecognised strings
143
+ * collapse to `'unknown'`.
144
+ */
145
+ var classifyFetchErrorReason = exports.classifyFetchErrorReason = function classifyFetchErrorReason(error) {
146
+ var enumReason = classifyErrorReason(error);
147
+ if (enumReason !== 'unknown') {
148
+ return enumReason;
149
+ }
150
+ if (error) {
151
+ var haystack = error.toLowerCase();
152
+ for (var _i = 0, _FETCH_REASON_MATCHER = FETCH_REASON_MATCHERS; _i < _FETCH_REASON_MATCHER.length; _i++) {
153
+ var _FETCH_REASON_MATCHER2 = (0, _slicedToArray2.default)(_FETCH_REASON_MATCHER[_i], 2),
154
+ needles = _FETCH_REASON_MATCHER2[0],
155
+ reason = _FETCH_REASON_MATCHER2[1];
156
+ if (needles.some(function (needle) {
157
+ return haystack.includes(needle);
158
+ })) {
159
+ return reason;
160
+ }
161
+ }
162
+ }
163
+ return 'unknown';
164
+ };
165
+
166
+ /**
167
+ * Extra, optional analytics attributes describing WHY a fetch/subscribe synced-block
168
+ * action failed. Spread conditionally so we never emit `undefined` keys (EDITOR-7862).
169
+ *
170
+ * Adds `benign` on top of the shared {@link ErrorAttributionAttributes} so the dashboard
171
+ * can compute a true error rate (`genuine / total`) without any free-text regex.
172
+ */
173
+
174
+ /**
175
+ * Builds the {@link FetchErrorAttributionAttributes} for a failed fetch/subscribe
176
+ * synced-block operation from the raw `error` field and optional backend `statusCode`.
177
+ * Returns `undefined` when the `platform_editor_blocks_patch_3` gate is OFF, so the new
178
+ * `reason`/`statusCode`/`benign` attributes are only emitted once the gate is rolled out
179
+ * (EDITOR-7862). The existing free-text `error` attribute is always left unchanged.
180
+ *
181
+ * `gateEnabled` is injected by the caller (the store managers evaluate `fg(...)`) so this
182
+ * helper stays pure and trivially unit-testable for both gate states.
183
+ */
184
+ var buildFetchErrorAttribution = exports.buildFetchErrorAttribution = function buildFetchErrorAttribution(gateEnabled, error, statusCode) {
185
+ if (!gateEnabled) {
186
+ return undefined;
187
+ }
188
+ var reason = classifyFetchErrorReason(error);
189
+ return _objectSpread({
190
+ reason: reason,
191
+ benign: FETCH_BENIGN_REASONS.has(reason)
192
+ }, statusCode !== undefined && {
193
+ statusCode: statusCode
194
+ });
195
+ };
196
+
70
197
  // `sourceProduct` is threaded through every helper so analytics
71
198
  // events can be attributed to the source product (`confluence-page` vs `jira-work-item`).
72
199
  // All helpers accept it as an optional trailing argument; the spread-only-when-truthy
73
200
  // pattern below ensures we never emit `sourceProduct: undefined` (which would dirty the
74
201
  // event schema with empty keys).
75
202
 
76
- var getErrorPayload = exports.getErrorPayload = function getErrorPayload(actionSubjectId, error, resourceId, sourceProduct, attribution) {
203
+ /**
204
+ * Shared operational ERROR payload builder for synced-block events.
205
+ *
206
+ * Overloaded so the emitted `reason` type stays accurate per path (raised in review on
207
+ * EDITOR-7862): fetch/subscribe callers pass a {@link FetchErrorAttributionAttributes}
208
+ * (discriminated by its required `benign` field) and get the wider
209
+ * {@link SyncBlockFetchErrorReason}; write-path callers pass an
210
+ * {@link ErrorAttributionAttributes} (or nothing) and keep the narrower
211
+ * {@link SyncBlockErrorReason}, so write-path payloads never claim to carry fetch-only
212
+ * buckets like `source_deleted` they never emit.
213
+ */
214
+
215
+ function getErrorPayload(actionSubjectId, error, resourceId, sourceProduct, attribution) {
77
216
  return {
78
217
  action: _analytics.ACTION.ERROR,
79
218
  actionSubject: _analytics.ACTION_SUBJECT.SYNCED_BLOCK,
80
219
  actionSubjectId: actionSubjectId,
81
220
  eventType: _analytics.EVENT_TYPE.OPERATIONAL,
82
- attributes: _objectSpread(_objectSpread(_objectSpread(_objectSpread({
221
+ attributes: _objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread({
83
222
  error: error
84
223
  }, resourceId && {
85
224
  resourceId: resourceId
@@ -89,11 +228,18 @@ var getErrorPayload = exports.getErrorPayload = function getErrorPayload(actionS
89
228
  reason: attribution.reason
90
229
  }), (attribution === null || attribution === void 0 ? void 0 : attribution.statusCode) !== undefined && {
91
230
  statusCode: attribution.statusCode
231
+ }), attribution && 'benign' in attribution && {
232
+ benign: attribution.benign
92
233
  })
93
234
  };
94
- };
95
- var fetchErrorPayload = exports.fetchErrorPayload = function fetchErrorPayload(error, resourceId, sourceProduct) {
96
- return getErrorPayload(_analytics.ACTION_SUBJECT_ID.SYNCED_BLOCK_FETCH, error, resourceId, sourceProduct);
235
+ }
236
+ var fetchErrorPayload = exports.fetchErrorPayload = function fetchErrorPayload(error, resourceId, sourceProduct, attribution) {
237
+ return (
238
+ // Branch on attribution presence so each call resolves to a concrete overload: with
239
+ // attribution it hits the fetch overload (wider `reason`); without it (gate OFF) it
240
+ // hits the no-attribution overload. Both produce a fetch event regardless.
241
+ attribution ? getErrorPayload(_analytics.ACTION_SUBJECT_ID.SYNCED_BLOCK_FETCH, error, resourceId, sourceProduct, attribution) : getErrorPayload(_analytics.ACTION_SUBJECT_ID.SYNCED_BLOCK_FETCH, error, resourceId, sourceProduct)
242
+ );
97
243
  };
98
244
  var getSourceInfoErrorPayload = exports.getSourceInfoErrorPayload = function getSourceInfoErrorPayload(error, resourceId, sourceProduct) {
99
245
  return getErrorPayload(_analytics.ACTION_SUBJECT_ID.SYNCED_BLOCK_GET_SOURCE_INFO, error, resourceId, sourceProduct);
@@ -3,7 +3,7 @@ import { isSSR } from '@atlaskit/editor-common/core-utils';
3
3
  import { logException } from '@atlaskit/editor-common/monitoring';
4
4
  import { fg } from '@atlaskit/platform-feature-flags';
5
5
  import { isProviderNotReadyError, SyncBlockError } from '../common/types';
6
- import { fetchErrorPayload } from '../utils/errorHandling';
6
+ import { buildFetchErrorAttribution, fetchErrorPayload } from '../utils/errorHandling';
7
7
  import { createSyncBlockNode, getSourceProductFromResourceIdSafe } from '../utils/utils';
8
8
  export const useFetchSyncBlockData = (manager, resourceId, localId, fireAnalyticsEvent) => {
9
9
  var _manager$referenceMan2, _manager$referenceMan3, _manager$referenceMan4;
@@ -72,7 +72,7 @@ export const useFetchSyncBlockData = (manager, resourceId, localId, fireAnalytic
72
72
  logException(error, {
73
73
  location: 'editor-synced-block-provider/useFetchSyncBlockData'
74
74
  });
75
- fireAnalyticsEvent === null || fireAnalyticsEvent === void 0 ? void 0 : fireAnalyticsEvent(fetchErrorPayload(error.message, resourceId, getSourceProductFromResourceIdSafe(resourceId)));
75
+ fireAnalyticsEvent === null || fireAnalyticsEvent === void 0 ? void 0 : fireAnalyticsEvent(fetchErrorPayload(error.message, resourceId, getSourceProductFromResourceIdSafe(resourceId), buildFetchErrorAttribution(fg('platform_editor_blocks_patch_3'), error.message)));
76
76
 
77
77
  // Set error state if fetching fails
78
78
  setFetchState({
@@ -300,11 +300,16 @@ export const batchFetchData = async (cloudId, parentAri, blockNodeIdentifiers, c
300
300
  }));
301
301
  }
302
302
 
303
- // If batch request fails, return error for all resourceIds
303
+ // If batch request fails, return error for all resourceIds. Capture the HTTP
304
+ // status from `BlockError` so fetch analytics can break failures down by
305
+ // statusCode (EDITOR-7862); undefined for non-HTTP failures.
304
306
  return blockNodeIdentifiers.map(blockNodeIdentifier => ({
305
307
  error: {
306
308
  type: error instanceof BlockError ? mapBlockError(error) : SyncBlockError.Errored,
307
- reason: error.message
309
+ reason: error.message,
310
+ ...(error instanceof BlockError && {
311
+ statusCode: error.status
312
+ })
308
313
  },
309
314
  resourceId: blockNodeIdentifier.resourceId
310
315
  }));
@@ -458,10 +463,13 @@ class BlockServiceADFFetchProvider {
458
463
  };
459
464
  } catch (error) {
460
465
  if (error instanceof BlockError) {
466
+ // Capture the HTTP status so fetch analytics can break failures down by
467
+ // statusCode (EDITOR-7862).
461
468
  return {
462
469
  error: {
463
470
  type: mapBlockError(error),
464
- reason: error.message
471
+ reason: error.message,
472
+ statusCode: error.status
465
473
  },
466
474
  resourceId
467
475
  };
@@ -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 { isProviderNotReadyError, ProviderNotReadyError, SyncBlockError } from '../common/types';
6
- import { buildErrorAttribution, cacheDeletionForcedPayload, fetchErrorPayload, fetchSuccessPayload, getSourceInfoErrorPayload, sourceInfoOrphanedPayload, updateReferenceErrorPayload } from '../utils/errorHandling';
6
+ import { buildErrorAttribution, buildFetchErrorAttribution, 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';
@@ -472,10 +472,12 @@ export class ReferenceSyncBlockStoreManager {
472
472
  data.forEach(syncBlockInstance => {
473
473
  var _resolvedSyncBlockIns;
474
474
  if (!syncBlockInstance.resourceId) {
475
- var _syncBlockInstance$er, _syncBlockInstance$er2, _this$fireAnalyticsEv8;
475
+ var _syncBlockInstance$er, _syncBlockInstance$er2, _this$fireAnalyticsEv8, _syncBlockInstance$er3, _syncBlockInstance$er4, _syncBlockInstance$er5;
476
476
  const payload = ((_syncBlockInstance$er = syncBlockInstance.error) === null || _syncBlockInstance$er === void 0 ? void 0 : _syncBlockInstance$er.reason) || ((_syncBlockInstance$er2 = syncBlockInstance.error) === null || _syncBlockInstance$er2 === void 0 ? void 0 : _syncBlockInstance$er2.type) || 'Returned sync block instance does not have resource id';
477
477
  // No resourceId means we cannot derive a sourceProduct here; intentionally omit.
478
- (_this$fireAnalyticsEv8 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv8 === void 0 ? void 0 : _this$fireAnalyticsEv8.call(this, fetchErrorPayload(payload));
478
+ // Classify on the structured `type` first, falling back to the free-text
479
+ // `reason`/payload (EDITOR-7862).
480
+ (_this$fireAnalyticsEv8 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv8 === void 0 ? void 0 : _this$fireAnalyticsEv8.call(this, fetchErrorPayload(payload, undefined, undefined, buildFetchErrorAttribution(fg('platform_editor_blocks_patch_3'), ((_syncBlockInstance$er3 = syncBlockInstance.error) === null || _syncBlockInstance$er3 === void 0 ? void 0 : _syncBlockInstance$er3.type) || ((_syncBlockInstance$er4 = syncBlockInstance.error) === null || _syncBlockInstance$er4 === void 0 ? void 0 : _syncBlockInstance$er4.reason) || payload, (_syncBlockInstance$er5 = syncBlockInstance.error) === null || _syncBlockInstance$er5 === void 0 ? void 0 : _syncBlockInstance$er5.statusCode)));
479
481
  return;
480
482
  }
481
483
  const existingSyncBlock = this.getFromCache(syncBlockInstance.resourceId);
@@ -509,7 +511,10 @@ export class ReferenceSyncBlockStoreManager {
509
511
  const isRetryingEntityNotFound = syncBlockInstance.error.type === SyncBlockError.EntityNotFound && ((_this$entityNotFoundR = this.entityNotFoundRetryCount.get(syncBlockInstance.resourceId)) !== null && _this$entityNotFoundR !== void 0 ? _this$entityNotFoundR : 0) < ENTITY_NOT_FOUND_MAX_RETRIES && fg('platform_synced_block_patch_13');
510
512
  if (!isRetryingEntityNotFound) {
511
513
  var _this$fireAnalyticsEv9, _syncBlockInstance$da, _syncBlockInstance$da2;
512
- (_this$fireAnalyticsEv9 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv9 === void 0 ? void 0 : _this$fireAnalyticsEv9.call(this, fetchErrorPayload(syncBlockInstance.error.reason || syncBlockInstance.error.type, syncBlockInstance.resourceId, (_syncBlockInstance$da = (_syncBlockInstance$da2 = syncBlockInstance.data) === null || _syncBlockInstance$da2 === void 0 ? void 0 : _syncBlockInstance$da2.product) !== null && _syncBlockInstance$da !== void 0 ? _syncBlockInstance$da : getSourceProductFromResourceIdSafe(syncBlockInstance.resourceId)));
514
+ // Classify on the structured `type` (a `SyncBlockError` enum value) first,
515
+ // falling back to the free-text `reason` so source-state/permission strings
516
+ // are still bucketed (EDITOR-7862). The emitted `error` attribute is unchanged.
517
+ (_this$fireAnalyticsEv9 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv9 === void 0 ? void 0 : _this$fireAnalyticsEv9.call(this, fetchErrorPayload(syncBlockInstance.error.reason || syncBlockInstance.error.type, syncBlockInstance.resourceId, (_syncBlockInstance$da = (_syncBlockInstance$da2 = syncBlockInstance.data) === null || _syncBlockInstance$da2 === void 0 ? void 0 : _syncBlockInstance$da2.product) !== null && _syncBlockInstance$da !== void 0 ? _syncBlockInstance$da : getSourceProductFromResourceIdSafe(syncBlockInstance.resourceId), buildFetchErrorAttribution(fg('platform_editor_blocks_patch_3'), syncBlockInstance.error.type || syncBlockInstance.error.reason, syncBlockInstance.error.statusCode)));
513
518
  }
514
519
  if (syncBlockInstance.error.type === SyncBlockError.NotFound || syncBlockInstance.error.type === SyncBlockError.Forbidden) {
515
520
  hasExpectedError = true;
@@ -806,7 +811,7 @@ export class ReferenceSyncBlockStoreManager {
806
811
  logException(error, {
807
812
  location: 'editor-synced-block-provider/referenceSyncBlockStoreManager'
808
813
  });
809
- (_this$fireAnalyticsEv11 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv11 === void 0 ? void 0 : _this$fireAnalyticsEv11.call(this, fetchErrorPayload(error.message));
814
+ (_this$fireAnalyticsEv11 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv11 === void 0 ? void 0 : _this$fireAnalyticsEv11.call(this, fetchErrorPayload(error.message, undefined, undefined, buildFetchErrorAttribution(fg('platform_editor_blocks_patch_3'), error.message)));
810
815
  return () => {};
811
816
  }
812
817
  }
@@ -3,7 +3,7 @@ import rafSchedule from 'raf-schd';
3
3
  import { logException } from '@atlaskit/editor-common/monitoring';
4
4
  import { fg } from '@atlaskit/platform-feature-flags';
5
5
  import { isProviderNotReadyError } from '../common/types';
6
- import { fetchErrorPayload } from '../utils/errorHandling';
6
+ import { buildFetchErrorAttribution, fetchErrorPayload } from '../utils/errorHandling';
7
7
  import { createSyncBlockNode, getSourceProductFromResourceIdSafe } from '../utils/utils';
8
8
  /**
9
9
  * Handles debounced batch-fetching of sync block data via `raf-schd`.
@@ -60,9 +60,10 @@ export class SyncBlockBatchFetcher {
60
60
  logException(error, {
61
61
  location: 'editor-synced-block-provider/syncBlockBatchFetcher/batchedFetchSyncBlocks'
62
62
  });
63
+ const attribution = buildFetchErrorAttribution(fg('platform_editor_blocks_patch_3'), error.message);
63
64
  resourceIds.forEach(resId => {
64
65
  var _this$deps$getFireAna;
65
- (_this$deps$getFireAna = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna === void 0 ? void 0 : _this$deps$getFireAna(fetchErrorPayload(error.message, resId, getSourceProductFromResourceIdSafe(resId)));
66
+ (_this$deps$getFireAna = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna === void 0 ? void 0 : _this$deps$getFireAna(fetchErrorPayload(error.message, resId, getSourceProductFromResourceIdSafe(resId), attribution));
66
67
  });
67
68
  }).finally(() => {
68
69
  // If the fetcher was destroyed while the request was in flight,
@@ -1,7 +1,8 @@
1
1
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
2
  import { logException } from '@atlaskit/editor-common/monitoring';
3
3
  import { ProviderFactory } from '@atlaskit/editor-common/provider-factory';
4
- import { fetchErrorPayload } from '../utils/errorHandling';
4
+ import { fg } from '@atlaskit/platform-feature-flags';
5
+ import { buildFetchErrorAttribution, fetchErrorPayload } from '../utils/errorHandling';
5
6
  import { parseResourceId } from '../utils/resourceId';
6
7
  import { getSourceProductFromResourceIdSafe } from '../utils/utils';
7
8
  /**
@@ -21,7 +22,7 @@ export class SyncBlockProviderFactoryManager {
21
22
  logException(error, {
22
23
  location: 'editor-synced-block-provider/syncBlockProviderFactoryManager'
23
24
  });
24
- (_this$deps$getFireAna = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna === void 0 ? void 0 : _this$deps$getFireAna(fetchErrorPayload(error.message, resourceId, getSourceProductFromResourceIdSafe(resourceId)));
25
+ (_this$deps$getFireAna = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna === void 0 ? void 0 : _this$deps$getFireAna(fetchErrorPayload(error.message, resourceId, getSourceProductFromResourceIdSafe(resourceId), buildFetchErrorAttribution(fg('platform_editor_blocks_patch_3'), error.message)));
25
26
  return undefined;
26
27
  }
27
28
  const {
@@ -55,7 +56,7 @@ export class SyncBlockProviderFactoryManager {
55
56
  logException(error, {
56
57
  location: 'editor-synced-block-provider/syncBlockProviderFactoryManager'
57
58
  });
58
- (_this$deps$getFireAna2 = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna2 === void 0 ? void 0 : _this$deps$getFireAna2(fetchErrorPayload(error.message, resourceId, getSourceProductFromResourceIdSafe(resourceId)));
59
+ (_this$deps$getFireAna2 = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna2 === void 0 ? void 0 : _this$deps$getFireAna2(fetchErrorPayload(error.message, resourceId, getSourceProductFromResourceIdSafe(resourceId), buildFetchErrorAttribution(fg('platform_editor_blocks_patch_3'), error.message)));
59
60
  }
60
61
  }
61
62
  return providerFactory;
@@ -133,7 +134,7 @@ export class SyncBlockProviderFactoryManager {
133
134
  if (!syncBlock.data.sourceAri || !syncBlock.data.product) {
134
135
  var _this$deps$getFireAna3, _syncBlock$data$produ;
135
136
  (_this$deps$getFireAna3 = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna3 === void 0 ? void 0 : _this$deps$getFireAna3(fetchErrorPayload('Sync block source ari or product not found', resourceId, // Prefer cached product when available; fall back to parsing resourceId.
136
- (_syncBlock$data$produ = syncBlock.data.product) !== null && _syncBlock$data$produ !== void 0 ? _syncBlock$data$produ : getSourceProductFromResourceIdSafe(resourceId)));
137
+ (_syncBlock$data$produ = syncBlock.data.product) !== null && _syncBlock$data$produ !== void 0 ? _syncBlock$data$produ : getSourceProductFromResourceIdSafe(resourceId), buildFetchErrorAttribution(fg('platform_editor_blocks_patch_3'), 'Sync block source ari or product not found')));
137
138
  return;
138
139
  }
139
140
  const parentInfo = dataProvider.retrieveSyncBlockParentInfo(syncBlock.data.sourceAri, syncBlock.data.product);
@@ -1,7 +1,7 @@
1
1
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
2
  import { logException } from '@atlaskit/editor-common/monitoring';
3
3
  import { fg } from '@atlaskit/platform-feature-flags';
4
- import { fetchErrorPayload, fetchSuccessPayload } from '../utils/errorHandling';
4
+ import { buildFetchErrorAttribution, fetchErrorPayload, fetchSuccessPayload } from '../utils/errorHandling';
5
5
  import { resolveSyncBlockInstance } from '../utils/resolveSyncBlockInstance';
6
6
  import { getSourceProductFromResourceIdSafe } from '../utils/utils';
7
7
  /**
@@ -87,7 +87,7 @@ export class SyncBlockSubscriptionManager {
87
87
  logException(error, {
88
88
  location: 'editor-synced-block-provider/syncBlockSubscriptionManager/notifySubscriptionChangeListeners'
89
89
  });
90
- (_this$deps$getFireAna = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna === void 0 ? void 0 : _this$deps$getFireAna(fetchErrorPayload(error.message));
90
+ (_this$deps$getFireAna = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna === void 0 ? void 0 : _this$deps$getFireAna(fetchErrorPayload(error.message, undefined, undefined, buildFetchErrorAttribution(fg('platform_editor_blocks_patch_3'), error.message)));
91
91
  }
92
92
  });
93
93
  }
@@ -264,6 +264,9 @@ export class SyncBlockSubscriptionManager {
264
264
  // recovers on reconnect, so under the gate we don't fire a
265
265
  // user-facing error here — it's only surfaced on exhaustion (see
266
266
  // scheduleReconnection). Gate OFF keeps the legacy fire-on-drop.
267
+ // This branch only runs when the gate is OFF, so buildFetchErrorAttribution
268
+ // would return undefined; the structured attribution (EDITOR-7862) is therefore
269
+ // applied at the gate-ON exhaustion site in scheduleReconnection instead.
267
270
  if (!fg('platform_editor_blocks_patch_3')) {
268
271
  var _this$deps$getFireAna2;
269
272
  (_this$deps$getFireAna2 = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna2 === void 0 ? void 0 : _this$deps$getFireAna2(fetchErrorPayload(error.message, resourceId, getSourceProductFromResourceIdSafe(resourceId)));
@@ -310,7 +313,7 @@ export class SyncBlockSubscriptionManager {
310
313
  logException(new Error(errorMessage), {
311
314
  location: 'editor-synced-block-provider/syncBlockSubscriptionManager/max-retries-exhausted'
312
315
  });
313
- (_this$deps$getFireAna3 = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna3 === void 0 ? void 0 : _this$deps$getFireAna3(fetchErrorPayload(errorMessage, resourceId, getSourceProductFromResourceIdSafe(resourceId)));
316
+ (_this$deps$getFireAna3 = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna3 === void 0 ? void 0 : _this$deps$getFireAna3(fetchErrorPayload(errorMessage, resourceId, getSourceProductFromResourceIdSafe(resourceId), buildFetchErrorAttribution(fg('platform_editor_blocks_patch_3'), errorMessage)));
314
317
  return;
315
318
  }
316
319
  const delay = this.getReconnectionDelay(attempts);
@@ -409,9 +412,13 @@ export class SyncBlockSubscriptionManager {
409
412
  });
410
413
  this.deps.fetchSyncBlockSourceInfo(resolved.resourceId);
411
414
  } else {
412
- var _syncBlockInstance$er, _syncBlockInstance$er2, _this$deps$getFireAna5, _syncBlockInstance$da3, _syncBlockInstance$da4;
415
+ var _syncBlockInstance$er, _syncBlockInstance$er2, _this$deps$getFireAna5, _syncBlockInstance$da3, _syncBlockInstance$da4, _syncBlockInstance$er3, _syncBlockInstance$er4, _syncBlockInstance$er5;
413
416
  const errorMessage = ((_syncBlockInstance$er = syncBlockInstance.error) === null || _syncBlockInstance$er === void 0 ? void 0 : _syncBlockInstance$er.reason) || ((_syncBlockInstance$er2 = syncBlockInstance.error) === null || _syncBlockInstance$er2 === void 0 ? void 0 : _syncBlockInstance$er2.type);
414
- (_this$deps$getFireAna5 = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna5 === void 0 ? void 0 : _this$deps$getFireAna5(fetchErrorPayload(errorMessage, syncBlockInstance.resourceId, (_syncBlockInstance$da3 = (_syncBlockInstance$da4 = syncBlockInstance.data) === null || _syncBlockInstance$da4 === void 0 ? void 0 : _syncBlockInstance$da4.product) !== null && _syncBlockInstance$da3 !== void 0 ? _syncBlockInstance$da3 : getSourceProductFromResourceIdSafe(syncBlockInstance.resourceId)));
417
+
418
+ // Prefer the structured `type` (a `SyncBlockError` enum value) for classification
419
+ // and fall back to the free-text `reason` so source-state/permission strings are
420
+ // still bucketed (EDITOR-7862). The emitted free-text `error` attribute is unchanged.
421
+ (_this$deps$getFireAna5 = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna5 === void 0 ? void 0 : _this$deps$getFireAna5(fetchErrorPayload(errorMessage, syncBlockInstance.resourceId, (_syncBlockInstance$da3 = (_syncBlockInstance$da4 = syncBlockInstance.data) === null || _syncBlockInstance$da4 === void 0 ? void 0 : _syncBlockInstance$da4.product) !== null && _syncBlockInstance$da3 !== void 0 ? _syncBlockInstance$da3 : getSourceProductFromResourceIdSafe(syncBlockInstance.resourceId), buildFetchErrorAttribution(fg('platform_editor_blocks_patch_3'), ((_syncBlockInstance$er3 = syncBlockInstance.error) === null || _syncBlockInstance$er3 === void 0 ? void 0 : _syncBlockInstance$er3.type) || ((_syncBlockInstance$er4 = syncBlockInstance.error) === null || _syncBlockInstance$er4 === void 0 ? void 0 : _syncBlockInstance$er4.reason), (_syncBlockInstance$er5 = syncBlockInstance.error) === null || _syncBlockInstance$er5 === void 0 ? void 0 : _syncBlockInstance$er5.statusCode)));
415
422
  }
416
423
  }
417
424
  }