@atlaskit/editor-synced-block-provider 8.5.3 → 8.5.5

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,27 @@
1
1
  # @atlaskit/editor-synced-block-provider
2
2
 
3
+ ## 8.5.5
4
+
5
+ ### Patch Changes
6
+
7
+ - [`b1ff8c881a4aa`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/b1ff8c881a4aa) -
8
+ [ux] Synced Blocks: de-opaque the renderer error-boundary fetch analytics event. The event now
9
+ routes through the shared fetch-error attribution builder (emitting structured
10
+ `reason`/`benign`/`sourceProduct`/`statusCode`, gated on `platform_editor_blocks_patch_4`) and a
11
+ PII-safe original error message is threaded from the upstream fetch catch so previously-opaque
12
+ `errored` failures can be classified and inspected.
13
+ - Updated dependencies
14
+
15
+ ## 8.5.4
16
+
17
+ ### Patch Changes
18
+
19
+ - [`1b4390dfedd97`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/1b4390dfedd97) -
20
+ Rename the internal analytics payload builder `createSuccessPayloadNew` to
21
+ `createSuccessOperationalPayload` to make the operational-vs-track distinction clear at the call
22
+ site. This is a pure rename with no behavioural or payload change.
23
+ - Updated dependencies
24
+
3
25
  ## 8.5.3
4
26
 
5
27
  ### Patch Changes
@@ -3,10 +3,22 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
+ Object.defineProperty(exports, "buildFetchErrorAttribution", {
7
+ enumerable: true,
8
+ get: function get() {
9
+ return _errorHandling.buildFetchErrorAttribution;
10
+ }
11
+ });
6
12
  Object.defineProperty(exports, "fetchErrorPayload", {
7
13
  enumerable: true,
8
14
  get: function get() {
9
15
  return _errorHandling.fetchErrorPayload;
10
16
  }
11
17
  });
18
+ Object.defineProperty(exports, "getPiiSafeOriginalError", {
19
+ enumerable: true,
20
+ get: function get() {
21
+ return _errorHandling.getPiiSafeOriginalError;
22
+ }
23
+ });
12
24
  var _errorHandling = require("../utils/errorHandling");
@@ -107,13 +107,16 @@ var useFetchSyncBlockData = exports.useFetchSyncBlockData = function useFetchSyn
107
107
  });
108
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
- // Set error state if fetching fails
110
+ // Thread the PII-safe original message/name so the renderer can de-opaque
111
+ // this otherwise-bare `errored` failure. `originalMessage` carries the
112
+ // Error.message and is preferred over `reason` by the renderer, so no
113
+ // separate `reason` is set here.
111
114
  setFetchState({
112
115
  syncBlockInstance: {
113
116
  resourceId: resourceId || '',
114
- error: {
117
+ error: _objectSpread({
115
118
  type: _types.SyncBlockError.Errored
116
- }
119
+ }, (0, _errorHandling.getPiiSafeOriginalError)(_t))
117
120
  },
118
121
  isLoading: false
119
122
  });
@@ -415,12 +415,12 @@ var _batchFetchData = exports.batchFetchData = /*#__PURE__*/function () {
415
415
  case 21:
416
416
  return _context2.abrupt("return", blockNodeIdentifiers.map(function (blockNodeIdentifier) {
417
417
  return {
418
- error: _objectSpread({
418
+ error: _objectSpread(_objectSpread({
419
419
  type: _t4 instanceof _blockService.BlockError ? mapBlockError(_t4) : _types.SyncBlockError.Errored,
420
420
  reason: _t4.message
421
421
  }, _t4 instanceof _blockService.BlockError && {
422
422
  statusCode: _t4.status
423
- }),
423
+ }), (0, _errorHandling.getPiiSafeOriginalError)(_t4)),
424
424
  resourceId: blockNodeIdentifier.resourceId
425
425
  };
426
426
  }));
@@ -670,19 +670,19 @@ var BlockServiceADFFetchProvider = /*#__PURE__*/function () {
670
670
  break;
671
671
  }
672
672
  return _context5.abrupt("return", {
673
- error: {
673
+ error: _objectSpread({
674
674
  type: mapBlockError(_t7),
675
675
  reason: _t7.message,
676
676
  statusCode: _t7.status
677
- },
677
+ }, (0, _errorHandling.getPiiSafeOriginalError)(_t7)),
678
678
  resourceId: resourceId
679
679
  });
680
680
  case 5:
681
681
  return _context5.abrupt("return", {
682
- error: {
682
+ error: _objectSpread({
683
683
  type: _types.SyncBlockError.Errored,
684
684
  reason: _t7.message
685
- },
685
+ }, (0, _errorHandling.getPiiSafeOriginalError)(_t7)),
686
686
  resourceId: resourceId
687
687
  });
688
688
  case 6:
@@ -414,7 +414,7 @@ var SourceSyncBlockStoreManager = exports.SourceSyncBlockStoreManager = /*#__PUR
414
414
  // Operational create-success event with the join key.
415
415
  if ((0, _platformFeatureFlags.fg)('platform_editor_blocks_patch_4')) {
416
416
  var _this$fireAnalyticsEv5, _this$syncBlockCache$;
417
- (_this$fireAnalyticsEv5 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv5 === void 0 || _this$fireAnalyticsEv5.call(this, (0, _errorHandling.createSuccessPayloadNew)(resourceId || '', (_this$syncBlockCache$ = this.syncBlockCache.get(resourceId)) === null || _this$syncBlockCache$ === void 0 ? void 0 : _this$syncBlockCache$.blockInstanceId, sourceProduct));
417
+ (_this$fireAnalyticsEv5 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv5 === void 0 || _this$fireAnalyticsEv5.call(this, (0, _errorHandling.createSuccessOperationalPayload)(resourceId || '', (_this$syncBlockCache$ = this.syncBlockCache.get(resourceId)) === null || _this$syncBlockCache$ === void 0 ? void 0 : _this$syncBlockCache$.blockInstanceId, sourceProduct));
418
418
  }
419
419
  } else {
420
420
  var _this$fireAnalyticsEv6;
@@ -4,9 +4,9 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
4
4
  Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
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;
7
+ exports.fetchSuccessPayload = exports.fetchReferencesErrorPayload = exports.fetchErrorPayload = exports.deleteSuccessPayload = exports.deleteErrorPayload = exports.createSuccessPayload = exports.createSuccessOperationalPayload = exports.createErrorPayload = exports.classifyFetchErrorReason = exports.classifyErrorReason = exports.cacheDeletionForcedPayload = exports.buildFetchErrorAttribution = exports.buildErrorAttribution = exports.FETCH_BENIGN_REASONS = void 0;
8
8
  exports.getErrorPayload = getErrorPayload;
9
- exports.updateSuccessPayload = exports.updateReferenceErrorPayload = exports.updateErrorPayload = exports.updateCacheErrorPayload = exports.stringifyError = exports.sourceInfoOrphanedPayload = exports.getSourceInfoErrorPayload = void 0;
9
+ exports.updateSuccessPayload = exports.updateReferenceErrorPayload = exports.updateErrorPayload = exports.updateCacheErrorPayload = exports.stringifyError = exports.sourceInfoOrphanedPayload = exports.getSourceInfoErrorPayload = exports.getPiiSafeOriginalError = void 0;
10
10
  var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
11
11
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
12
12
  var _analytics = require("@atlaskit/editor-common/analytics");
@@ -21,6 +21,24 @@ var stringifyError = exports.stringifyError = function stringifyError(error) {
21
21
  }
22
22
  };
23
23
 
24
+ /**
25
+ * PII-safe extraction of `message`/`name` from an unknown caught error, used to
26
+ * de-opaque `errored` failures. Only ever reads `Error.message` / `Error.name` — it
27
+ * never stringifies arbitrary objects (which could pull in node content / UGC), and
28
+ * non-`Error` values yield `{}` so no `[object Object]` or serialized payload leaks
29
+ * into analytics.
30
+ */
31
+ var getPiiSafeOriginalError = exports.getPiiSafeOriginalError = function getPiiSafeOriginalError(error) {
32
+ if (error instanceof Error) {
33
+ return _objectSpread(_objectSpread({}, error.message && {
34
+ originalMessage: error.message
35
+ }), error.name && {
36
+ originalName: error.name
37
+ });
38
+ }
39
+ return {};
40
+ };
41
+
24
42
  /**
25
43
  * The set of categorical failure reasons emitted on synced-block operational error
26
44
  * events (EDITOR-7796). These let the analytics dashboard break delete/update/create
@@ -92,13 +110,16 @@ var buildErrorAttribution = exports.buildErrorAttribution = function buildErrorA
92
110
  var FETCH_BENIGN_REASONS = exports.FETCH_BENIGN_REASONS = new Set(['source_deleted', 'source_unpublished', 'source_unsynced', 'source_not_found', 'permission_denied', 'unauthenticated',
93
111
  // Mirror the benign write-path enum equivalents so already-classified errors are
94
112
  // 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.
113
+ 'not_found', 'forbidden', 'unpublished',
114
+ // `offline` is an expected client connectivity state, not a read-path system failure:
115
+ // the error component is rendered for the offline experience, so it should not inflate
116
+ // the true error rate. It still emits `reason` (so its volume can be inspected) but is
117
+ // excluded from true-error counts.
118
+ 'offline'
119
+ // NOTE: `entity_not_found` is intentionally NOT benign it is retried up to
120
+ // ENTITY_NOT_FOUND_MAX_RETRIES (analytics are suppressed during retries); by the time
121
+ // the error event fires, retries are exhausted and it is a genuine failure. It still
122
+ // emits `reason` (so it can be inspected) but is counted as a true error.
102
123
  ]);
103
124
 
104
125
  /**
@@ -340,7 +361,7 @@ var createSuccessPayload = exports.createSuccessPayload = function createSuccess
340
361
  * existed, so dashboards had no create denominator). Fired behind
341
362
  * `platform_editor_blocks_patch_4` with the `blockInstanceId` join key.
342
363
  */
343
- var createSuccessPayloadNew = exports.createSuccessPayloadNew = function createSuccessPayloadNew(resourceId, blockInstanceId, sourceProduct) {
364
+ var createSuccessOperationalPayload = exports.createSuccessOperationalPayload = function createSuccessOperationalPayload(resourceId, blockInstanceId, sourceProduct) {
344
365
  return {
345
366
  action: _analytics.ACTION.INSERTED,
346
367
  actionSubject: _analytics.ACTION_SUBJECT.SYNCED_BLOCK,
@@ -1,2 +1,2 @@
1
1
  /* eslint-disable @atlaskit/editor/no-re-export */
2
- export { fetchErrorPayload } from '../utils/errorHandling';
2
+ export { buildFetchErrorAttribution, fetchErrorPayload, getPiiSafeOriginalError } from '../utils/errorHandling';
@@ -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 { buildFetchErrorAttribution, fetchErrorPayload } from '../utils/errorHandling';
6
+ import { buildFetchErrorAttribution, fetchErrorPayload, getPiiSafeOriginalError } 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;
@@ -74,12 +74,16 @@ export const useFetchSyncBlockData = (manager, resourceId, localId, fireAnalytic
74
74
  });
75
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
- // Set error state if fetching fails
77
+ // Thread the PII-safe original message/name so the renderer can de-opaque
78
+ // this otherwise-bare `errored` failure. `originalMessage` carries the
79
+ // Error.message and is preferred over `reason` by the renderer, so no
80
+ // separate `reason` is set here.
78
81
  setFetchState({
79
82
  syncBlockInstance: {
80
83
  resourceId: resourceId || '',
81
84
  error: {
82
- type: SyncBlockError.Errored
85
+ type: SyncBlockError.Errored,
86
+ ...getPiiSafeOriginalError(error)
83
87
  }
84
88
  },
85
89
  isLoading: false
@@ -4,7 +4,7 @@ import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
4
4
  import { generateBlockAri, generateBlockAriFromReference } from '../../clients/block-service/ari';
5
5
  import { batchRetrieveSyncedBlocks, BlockError, BlockNotFoundError, BlockTimeoutError, createSyncedBlock, deleteSyncedBlock, getReferenceSyncedBlocks, getReferenceSyncedBlocksByBlockAri, getSyncedBlockContent, updateReferenceSyncedBlockOnDocument, updateSyncedBlock, updateSyncedBlocks } from '../../clients/block-service/blockService';
6
6
  import { SyncBlockError } from '../../common/types';
7
- import { stringifyError } from '../../utils/errorHandling';
7
+ import { getPiiSafeOriginalError, stringifyError } from '../../utils/errorHandling';
8
8
  import { createResourceIdForReference } from '../../utils/resourceId';
9
9
  import { convertContentUpdatedAt } from '../../utils/utils';
10
10
  const BLOCK_ARI_TO_RESOURCE_ID_REGEX = /^ari:cloud:blocks:.*:synced-block\/(.+)$/;
@@ -304,14 +304,16 @@ export const batchFetchData = async (cloudId, parentAri, blockNodeIdentifiers, c
304
304
 
305
305
  // If batch request fails, return error for all resourceIds. Capture the HTTP
306
306
  // status from `BlockError` so fetch analytics can break failures down by
307
- // statusCode (EDITOR-7862); undefined for non-HTTP failures.
307
+ // statusCode (EDITOR-7862); undefined for non-HTTP failures. Thread the PII-safe
308
+ // original message/name so the renderer can de-opaque the `errored` bucket.
308
309
  return blockNodeIdentifiers.map(blockNodeIdentifier => ({
309
310
  error: {
310
311
  type: error instanceof BlockError ? mapBlockError(error) : SyncBlockError.Errored,
311
312
  reason: error.message,
312
313
  ...(error instanceof BlockError && {
313
314
  statusCode: error.status
314
- })
315
+ }),
316
+ ...getPiiSafeOriginalError(error)
315
317
  },
316
318
  resourceId: blockNodeIdentifier.resourceId
317
319
  }));
@@ -466,12 +468,14 @@ class BlockServiceADFFetchProvider {
466
468
  } catch (error) {
467
469
  if (error instanceof BlockError) {
468
470
  // Capture the HTTP status so fetch analytics can break failures down by
469
- // statusCode (EDITOR-7862).
471
+ // statusCode (EDITOR-7862). Thread the PII-safe original message/name so
472
+ // the renderer can de-opaque failures.
470
473
  return {
471
474
  error: {
472
475
  type: mapBlockError(error),
473
476
  reason: error.message,
474
- statusCode: error.status
477
+ statusCode: error.status,
478
+ ...getPiiSafeOriginalError(error)
475
479
  },
476
480
  resourceId
477
481
  };
@@ -479,7 +483,8 @@ class BlockServiceADFFetchProvider {
479
483
  return {
480
484
  error: {
481
485
  type: SyncBlockError.Errored,
482
- reason: error.message
486
+ reason: error.message,
487
+ ...getPiiSafeOriginalError(error)
483
488
  },
484
489
  resourceId
485
490
  };
@@ -2,7 +2,7 @@ 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
4
  import { SyncBlockError } from '../common/types';
5
- import { updateErrorPayload, createErrorPayload, deleteErrorPayload, updateCacheErrorPayload, getSourceInfoErrorPayload, updateSuccessPayload, createSuccessPayload, createSuccessPayloadNew, deleteSuccessPayload, fetchReferencesErrorPayload, buildErrorAttribution } from '../utils/errorHandling';
5
+ import { updateErrorPayload, createErrorPayload, deleteErrorPayload, updateCacheErrorPayload, getSourceInfoErrorPayload, updateSuccessPayload, createSuccessPayload, createSuccessOperationalPayload, deleteSuccessPayload, fetchReferencesErrorPayload, buildErrorAttribution } from '../utils/errorHandling';
6
6
  import { getCreateSourceExperience, getDeleteSourceExperience, getSaveSourceExperience, getFetchSourceInfoExperience } from '../utils/experienceTracking';
7
7
  import { convertSyncBlockPMNodeToSyncBlockData, getSourceProductFromResourceIdSafe } from '../utils/utils';
8
8
  /** Maximum time (ms) flush() will wait for in-flight block creations before proceeding. */
@@ -341,7 +341,7 @@ export class SourceSyncBlockStoreManager {
341
341
  // Operational create-success event with the join key.
342
342
  if (fg('platform_editor_blocks_patch_4')) {
343
343
  var _this$fireAnalyticsEv7, _this$syncBlockCache$;
344
- (_this$fireAnalyticsEv7 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv7 === void 0 ? void 0 : _this$fireAnalyticsEv7.call(this, createSuccessPayloadNew(resourceId || '', (_this$syncBlockCache$ = this.syncBlockCache.get(resourceId)) === null || _this$syncBlockCache$ === void 0 ? void 0 : _this$syncBlockCache$.blockInstanceId, sourceProduct));
344
+ (_this$fireAnalyticsEv7 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv7 === void 0 ? void 0 : _this$fireAnalyticsEv7.call(this, createSuccessOperationalPayload(resourceId || '', (_this$syncBlockCache$ = this.syncBlockCache.get(resourceId)) === null || _this$syncBlockCache$ === void 0 ? void 0 : _this$syncBlockCache$.blockInstanceId, sourceProduct));
345
345
  }
346
346
  } else {
347
347
  var _this$fireAnalyticsEv8;
@@ -8,6 +8,27 @@ export const stringifyError = error => {
8
8
  }
9
9
  };
10
10
 
11
+ /**
12
+ * PII-safe extraction of `message`/`name` from an unknown caught error, used to
13
+ * de-opaque `errored` failures. Only ever reads `Error.message` / `Error.name` — it
14
+ * never stringifies arbitrary objects (which could pull in node content / UGC), and
15
+ * non-`Error` values yield `{}` so no `[object Object]` or serialized payload leaks
16
+ * into analytics.
17
+ */
18
+ export const getPiiSafeOriginalError = error => {
19
+ if (error instanceof Error) {
20
+ return {
21
+ ...(error.message && {
22
+ originalMessage: error.message
23
+ }),
24
+ ...(error.name && {
25
+ originalName: error.name
26
+ })
27
+ };
28
+ }
29
+ return {};
30
+ };
31
+
11
32
  /**
12
33
  * The set of categorical failure reasons emitted on synced-block operational error
13
34
  * events (EDITOR-7796). These let the analytics dashboard break delete/update/create
@@ -80,13 +101,16 @@ export const buildErrorAttribution = (gateEnabled, error, statusCode) => {
80
101
  export const FETCH_BENIGN_REASONS = new Set(['source_deleted', 'source_unpublished', 'source_unsynced', 'source_not_found', 'permission_denied', 'unauthenticated',
81
102
  // Mirror the benign write-path enum equivalents so already-classified errors are
82
103
  // treated consistently.
83
- 'not_found', 'forbidden', 'unpublished'
84
- // NOTE: `entity_not_found` and `offline` are intentionally NOT benign.
85
- // - `entity_not_found` is retried up to ENTITY_NOT_FOUND_MAX_RETRIES (analytics are
86
- // suppressed during retries); by the time the error event fires, retries are
87
- // exhausted and it is a genuine failure.
88
- // - `offline` is genuine client connectivity loss, not a working-as-designed outcome.
89
- // Both still emit `reason` (so they can be inspected) but are counted as true errors.
104
+ 'not_found', 'forbidden', 'unpublished',
105
+ // `offline` is an expected client connectivity state, not a read-path system failure:
106
+ // the error component is rendered for the offline experience, so it should not inflate
107
+ // the true error rate. It still emits `reason` (so its volume can be inspected) but is
108
+ // excluded from true-error counts.
109
+ 'offline'
110
+ // NOTE: `entity_not_found` is intentionally NOT benign it is retried up to
111
+ // ENTITY_NOT_FOUND_MAX_RETRIES (analytics are suppressed during retries); by the time
112
+ // the error event fires, retries are exhausted and it is a genuine failure. It still
113
+ // emits `reason` (so it can be inspected) but is counted as a true error.
90
114
  ]);
91
115
 
92
116
  /**
@@ -319,7 +343,7 @@ export const createSuccessPayload = (resourceId, sourceProduct) => {
319
343
  * existed, so dashboards had no create denominator). Fired behind
320
344
  * `platform_editor_blocks_patch_4` with the `blockInstanceId` join key.
321
345
  */
322
- export const createSuccessPayloadNew = (resourceId, blockInstanceId, sourceProduct) => ({
346
+ export const createSuccessOperationalPayload = (resourceId, blockInstanceId, sourceProduct) => ({
323
347
  action: ACTION.INSERTED,
324
348
  actionSubject: ACTION_SUBJECT.SYNCED_BLOCK,
325
349
  actionSubjectId: ACTION_SUBJECT_ID.SYNCED_BLOCK_CREATE,
@@ -1,2 +1,2 @@
1
1
  /* eslint-disable @atlaskit/editor/no-re-export */
2
- export { fetchErrorPayload } from '../utils/errorHandling';
2
+ export { buildFetchErrorAttribution, fetchErrorPayload, getPiiSafeOriginalError } from '../utils/errorHandling';
@@ -9,7 +9,7 @@ import { isSSR } from '@atlaskit/editor-common/core-utils';
9
9
  import { logException } from '@atlaskit/editor-common/monitoring';
10
10
  import { fg } from '@atlaskit/platform-feature-flags';
11
11
  import { isProviderNotReadyError, SyncBlockError } from '../common/types';
12
- import { buildFetchErrorAttribution, fetchErrorPayload } from '../utils/errorHandling';
12
+ import { buildFetchErrorAttribution, fetchErrorPayload, getPiiSafeOriginalError } from '../utils/errorHandling';
13
13
  import { createSyncBlockNode, getSourceProductFromResourceIdSafe } from '../utils/utils';
14
14
  export var useFetchSyncBlockData = function useFetchSyncBlockData(manager, resourceId, localId, fireAnalyticsEvent) {
15
15
  var _manager$referenceMan2, _manager$referenceMan3, _manager$referenceMan4;
@@ -100,13 +100,16 @@ export var useFetchSyncBlockData = function useFetchSyncBlockData(manager, resou
100
100
  });
101
101
  fireAnalyticsEvent === null || fireAnalyticsEvent === void 0 || fireAnalyticsEvent(fetchErrorPayload(_t.message, resourceId, getSourceProductFromResourceIdSafe(resourceId), buildFetchErrorAttribution(fg('platform_editor_blocks_patch_3'), _t.message)));
102
102
 
103
- // Set error state if fetching fails
103
+ // Thread the PII-safe original message/name so the renderer can de-opaque
104
+ // this otherwise-bare `errored` failure. `originalMessage` carries the
105
+ // Error.message and is preferred over `reason` by the renderer, so no
106
+ // separate `reason` is set here.
104
107
  setFetchState({
105
108
  syncBlockInstance: {
106
109
  resourceId: resourceId || '',
107
- error: {
110
+ error: _objectSpread({
108
111
  type: SyncBlockError.Errored
109
- }
112
+ }, getPiiSafeOriginalError(_t))
110
113
  },
111
114
  isLoading: false
112
115
  });
@@ -15,7 +15,7 @@ import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
15
15
  import { generateBlockAri as _generateBlockAri, generateBlockAriFromReference } from '../../clients/block-service/ari';
16
16
  import { batchRetrieveSyncedBlocks, BlockError, BlockNotFoundError, BlockTimeoutError, createSyncedBlock, deleteSyncedBlock, getReferenceSyncedBlocks, getReferenceSyncedBlocksByBlockAri, getSyncedBlockContent, updateReferenceSyncedBlockOnDocument, updateSyncedBlock, updateSyncedBlocks } from '../../clients/block-service/blockService';
17
17
  import { SyncBlockError } from '../../common/types';
18
- import { stringifyError } from '../../utils/errorHandling';
18
+ import { getPiiSafeOriginalError, stringifyError } from '../../utils/errorHandling';
19
19
  import { createResourceIdForReference } from '../../utils/resourceId';
20
20
  import { convertContentUpdatedAt } from '../../utils/utils';
21
21
  var BLOCK_ARI_TO_RESOURCE_ID_REGEX = /^ari:cloud:blocks:.*:synced-block\/(.+)$/;
@@ -407,12 +407,12 @@ var _batchFetchData = /*#__PURE__*/function () {
407
407
  case 21:
408
408
  return _context2.abrupt("return", blockNodeIdentifiers.map(function (blockNodeIdentifier) {
409
409
  return {
410
- error: _objectSpread({
410
+ error: _objectSpread(_objectSpread({
411
411
  type: _t4 instanceof BlockError ? mapBlockError(_t4) : SyncBlockError.Errored,
412
412
  reason: _t4.message
413
413
  }, _t4 instanceof BlockError && {
414
414
  statusCode: _t4.status
415
- }),
415
+ }), getPiiSafeOriginalError(_t4)),
416
416
  resourceId: blockNodeIdentifier.resourceId
417
417
  };
418
418
  }));
@@ -663,19 +663,19 @@ var BlockServiceADFFetchProvider = /*#__PURE__*/function () {
663
663
  break;
664
664
  }
665
665
  return _context5.abrupt("return", {
666
- error: {
666
+ error: _objectSpread({
667
667
  type: mapBlockError(_t7),
668
668
  reason: _t7.message,
669
669
  statusCode: _t7.status
670
- },
670
+ }, getPiiSafeOriginalError(_t7)),
671
671
  resourceId: resourceId
672
672
  });
673
673
  case 5:
674
674
  return _context5.abrupt("return", {
675
- error: {
675
+ error: _objectSpread({
676
676
  type: SyncBlockError.Errored,
677
677
  reason: _t7.message
678
- },
678
+ }, getPiiSafeOriginalError(_t7)),
679
679
  resourceId: resourceId
680
680
  });
681
681
  case 6:
@@ -12,7 +12,7 @@ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t =
12
12
  import { logException } from '@atlaskit/editor-common/monitoring';
13
13
  import { fg } from '@atlaskit/platform-feature-flags';
14
14
  import { SyncBlockError } from '../common/types';
15
- import { updateErrorPayload, createErrorPayload, deleteErrorPayload, updateCacheErrorPayload, getSourceInfoErrorPayload, updateSuccessPayload, createSuccessPayload, createSuccessPayloadNew, deleteSuccessPayload, fetchReferencesErrorPayload, buildErrorAttribution } from '../utils/errorHandling';
15
+ import { updateErrorPayload, createErrorPayload, deleteErrorPayload, updateCacheErrorPayload, getSourceInfoErrorPayload, updateSuccessPayload, createSuccessPayload, createSuccessOperationalPayload, deleteSuccessPayload, fetchReferencesErrorPayload, buildErrorAttribution } from '../utils/errorHandling';
16
16
  import { getCreateSourceExperience, getDeleteSourceExperience, getSaveSourceExperience, getFetchSourceInfoExperience } from '../utils/experienceTracking';
17
17
  import { convertSyncBlockPMNodeToSyncBlockData, getSourceProductFromResourceIdSafe } from '../utils/utils';
18
18
  /** Maximum time (ms) flush() will wait for in-flight block creations before proceeding. */
@@ -407,7 +407,7 @@ export var SourceSyncBlockStoreManager = /*#__PURE__*/function () {
407
407
  // Operational create-success event with the join key.
408
408
  if (fg('platform_editor_blocks_patch_4')) {
409
409
  var _this$fireAnalyticsEv5, _this$syncBlockCache$;
410
- (_this$fireAnalyticsEv5 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv5 === void 0 || _this$fireAnalyticsEv5.call(this, createSuccessPayloadNew(resourceId || '', (_this$syncBlockCache$ = this.syncBlockCache.get(resourceId)) === null || _this$syncBlockCache$ === void 0 ? void 0 : _this$syncBlockCache$.blockInstanceId, sourceProduct));
410
+ (_this$fireAnalyticsEv5 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv5 === void 0 || _this$fireAnalyticsEv5.call(this, createSuccessOperationalPayload(resourceId || '', (_this$syncBlockCache$ = this.syncBlockCache.get(resourceId)) === null || _this$syncBlockCache$ === void 0 ? void 0 : _this$syncBlockCache$.blockInstanceId, sourceProduct));
411
411
  }
412
412
  } else {
413
413
  var _this$fireAnalyticsEv6;
@@ -12,6 +12,24 @@ export var stringifyError = function stringifyError(error) {
12
12
  }
13
13
  };
14
14
 
15
+ /**
16
+ * PII-safe extraction of `message`/`name` from an unknown caught error, used to
17
+ * de-opaque `errored` failures. Only ever reads `Error.message` / `Error.name` — it
18
+ * never stringifies arbitrary objects (which could pull in node content / UGC), and
19
+ * non-`Error` values yield `{}` so no `[object Object]` or serialized payload leaks
20
+ * into analytics.
21
+ */
22
+ export var getPiiSafeOriginalError = function getPiiSafeOriginalError(error) {
23
+ if (error instanceof Error) {
24
+ return _objectSpread(_objectSpread({}, error.message && {
25
+ originalMessage: error.message
26
+ }), error.name && {
27
+ originalName: error.name
28
+ });
29
+ }
30
+ return {};
31
+ };
32
+
15
33
  /**
16
34
  * The set of categorical failure reasons emitted on synced-block operational error
17
35
  * events (EDITOR-7796). These let the analytics dashboard break delete/update/create
@@ -83,13 +101,16 @@ export var buildErrorAttribution = function buildErrorAttribution(gateEnabled, e
83
101
  export var FETCH_BENIGN_REASONS = new Set(['source_deleted', 'source_unpublished', 'source_unsynced', 'source_not_found', 'permission_denied', 'unauthenticated',
84
102
  // Mirror the benign write-path enum equivalents so already-classified errors are
85
103
  // treated consistently.
86
- 'not_found', 'forbidden', 'unpublished'
87
- // NOTE: `entity_not_found` and `offline` are intentionally NOT benign.
88
- // - `entity_not_found` is retried up to ENTITY_NOT_FOUND_MAX_RETRIES (analytics are
89
- // suppressed during retries); by the time the error event fires, retries are
90
- // exhausted and it is a genuine failure.
91
- // - `offline` is genuine client connectivity loss, not a working-as-designed outcome.
92
- // Both still emit `reason` (so they can be inspected) but are counted as true errors.
104
+ 'not_found', 'forbidden', 'unpublished',
105
+ // `offline` is an expected client connectivity state, not a read-path system failure:
106
+ // the error component is rendered for the offline experience, so it should not inflate
107
+ // the true error rate. It still emits `reason` (so its volume can be inspected) but is
108
+ // excluded from true-error counts.
109
+ 'offline'
110
+ // NOTE: `entity_not_found` is intentionally NOT benign it is retried up to
111
+ // ENTITY_NOT_FOUND_MAX_RETRIES (analytics are suppressed during retries); by the time
112
+ // the error event fires, retries are exhausted and it is a genuine failure. It still
113
+ // emits `reason` (so it can be inspected) but is counted as a true error.
93
114
  ]);
94
115
 
95
116
  /**
@@ -331,7 +352,7 @@ export var createSuccessPayload = function createSuccessPayload(resourceId, sour
331
352
  * existed, so dashboards had no create denominator). Fired behind
332
353
  * `platform_editor_blocks_patch_4` with the `blockInstanceId` join key.
333
354
  */
334
- export var createSuccessPayloadNew = function createSuccessPayloadNew(resourceId, blockInstanceId, sourceProduct) {
355
+ export var createSuccessOperationalPayload = function createSuccessOperationalPayload(resourceId, blockInstanceId, sourceProduct) {
335
356
  return {
336
357
  action: ACTION.INSERTED,
337
358
  actionSubject: ACTION_SUBJECT.SYNCED_BLOCK,
@@ -1 +1 @@
1
- export { fetchErrorPayload } from '../utils/errorHandling';
1
+ export { buildFetchErrorAttribution, fetchErrorPayload, getPiiSafeOriginalError, } from '../utils/errorHandling';
@@ -5,6 +5,17 @@ import { NodeDataProvider } from '@atlaskit/node-data-provider';
5
5
  import type { TaskDecisionProvider } from '@atlaskit/task-decision/types';
6
6
  import type { SyncBlockData, SyncBlockStatus, ResourceId, SyncBlockError, SyncBlockNode, SyncBlockProduct, BlockInstanceId, SyncBlockAttrs, ReferenceSyncBlockData, DeletionReason } from '../common/types';
7
7
  type SyncBlockErrorInfo = {
8
+ /**
9
+ * PII-safe `Error.name` from the upstream catch, used to de-opaque `errored`
10
+ * failures. Only ever `Error.name` — never node content, titles, or any UGC.
11
+ */
12
+ originalName?: string;
13
+ /**
14
+ * PII-safe `Error.message` from the upstream catch, so the classifier can bucket
15
+ * the real cause. Distinct from `reason` (may be synthetic/enum): carries the raw
16
+ * framework/HTTP text. Only ever `Error.message` — never node content or UGC.
17
+ */
18
+ originalMessage?: string;
8
19
  reason?: string;
9
20
  sourceAri?: string;
10
21
  /**
@@ -3,6 +3,17 @@ import type { RendererSyncBlockEventPayload, OperationalAEP, SyncBlockEventPaylo
3
3
  import { SyncBlockError } from '../common/types';
4
4
  import type { DeletionMechanism, DeletionReason } from '../common/types';
5
5
  export declare const stringifyError: (error: unknown) => string | undefined;
6
+ /**
7
+ * PII-safe extraction of `message`/`name` from an unknown caught error, used to
8
+ * de-opaque `errored` failures. Only ever reads `Error.message` / `Error.name` — it
9
+ * never stringifies arbitrary objects (which could pull in node content / UGC), and
10
+ * non-`Error` values yield `{}` so no `[object Object]` or serialized payload leaks
11
+ * into analytics.
12
+ */
13
+ export declare const getPiiSafeOriginalError: (error: unknown) => {
14
+ originalMessage?: string;
15
+ originalName?: string;
16
+ };
6
17
  /**
7
18
  * The set of categorical failure reasons emitted on synced-block operational error
8
19
  * events (EDITOR-7796). These let the analytics dashboard break delete/update/create
@@ -168,7 +179,7 @@ export declare const createSuccessPayload: (resourceId: string, sourceProduct?:
168
179
  * existed, so dashboards had no create denominator). Fired behind
169
180
  * `platform_editor_blocks_patch_4` with the `blockInstanceId` join key.
170
181
  */
171
- export declare const createSuccessPayloadNew: (resourceId: string, blockInstanceId?: string, sourceProduct?: string) => SyncBlockEventPayload;
182
+ export declare const createSuccessOperationalPayload: (resourceId: string, blockInstanceId?: string, sourceProduct?: string) => SyncBlockEventPayload;
172
183
  export declare const updateSuccessPayload: (resourceId: string, hasReference?: boolean, sourceProduct?: string) => SyncBlockEventPayload;
173
184
  /**
174
185
  * Optional enrichment for the `syncedBlockDelete` success event behind
package/package.json CHANGED
@@ -16,12 +16,12 @@
16
16
  ],
17
17
  "atlaskit:src": "src/index.ts",
18
18
  "dependencies": {
19
- "@atlaskit/adf-utils": "^20.2.0",
19
+ "@atlaskit/adf-utils": "^20.3.0",
20
20
  "@atlaskit/editor-json-transformer": "^9.0.0",
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": "^121.0.0",
24
+ "@atlaskit/tmp-editor-statsig": "^122.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.22.0",
33
+ "@atlaskit/editor-common": "^116.24.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.5.3",
77
+ "version": "8.5.5",
78
78
  "description": "Synced Block Provider for @atlaskit/editor-plugin-synced-block",
79
79
  "author": "Atlassian Pty Ltd",
80
80
  "license": "Apache-2.0",