@atlaskit/editor-synced-block-provider 8.4.4 → 8.5.1

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.
@@ -12,12 +12,20 @@ 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, deleteSuccessPayload, fetchReferencesErrorPayload, buildErrorAttribution } from '../utils/errorHandling';
15
+ import { updateErrorPayload, createErrorPayload, deleteErrorPayload, updateCacheErrorPayload, getSourceInfoErrorPayload, updateSuccessPayload, createSuccessPayload, createSuccessPayloadNew, 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. */
19
19
  var FLUSH_CREATION_AWAIT_TIMEOUT_MS = 1000;
20
20
 
21
+ /**
22
+ * Window (ms) within which a repeated `syncedBlockDelete` emission for the same
23
+ * resourceId is suppressed. The same logical removal can re-fire across
24
+ * rebased/remote/redo transactions; a short window collapses those bursts to one
25
+ * event while still allowing a genuine delete→recreate→delete later.
26
+ */
27
+ var DELETE_DEDUPE_WINDOW_MS = 5000;
28
+
21
29
  // A store manager responsible for the lifecycle and state management of source sync blocks in an editor instance.
22
30
  // Designed to manage local in-memory state and synchronize with an external data provider.
23
31
  // Supports create, flush, and delete operations for source sync blocks.
@@ -44,6 +52,12 @@ export var SourceSyncBlockStoreManager = /*#__PURE__*/function () {
44
52
  * multiple blocks are created concurrently. See EDITOR-7112.
45
53
  */
46
54
  _defineProperty(this, "creationsTimedOutDuringFlush", new Set());
55
+ /**
56
+ * resourceId -> timestamp (ms) of the last `syncedBlockDelete` emission, used
57
+ * to suppress duplicates within {@link DELETE_DEDUPE_WINDOW_MS}. Only consulted
58
+ * behind `platform_editor_blocks_patch_4`.
59
+ */
60
+ _defineProperty(this, "recentDeleteEmissions", new Map());
47
61
  _defineProperty(this, "setPendingDeletion", function (Ids, value) {
48
62
  if (_this.viewMode === 'view') {
49
63
  return;
@@ -388,12 +402,18 @@ export var SourceSyncBlockStoreManager = /*#__PURE__*/function () {
388
402
  }
389
403
  if (success) {
390
404
  var _this$fireAnalyticsEv4;
391
- (_this$fireAnalyticsEv4 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv4 === void 0 || _this$fireAnalyticsEv4.call(this, createSuccessPayload(resourceId || '', getSourceProductFromResourceIdSafe(resourceId)));
405
+ var sourceProduct = getSourceProductFromResourceIdSafe(resourceId);
406
+ (_this$fireAnalyticsEv4 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv4 === void 0 || _this$fireAnalyticsEv4.call(this, createSuccessPayload(resourceId || '', sourceProduct));
407
+ // Operational create-success event with the join key.
408
+ if (fg('platform_editor_blocks_patch_4')) {
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));
411
+ }
392
412
  } else {
393
- var _this$fireAnalyticsEv5;
413
+ var _this$fireAnalyticsEv6;
394
414
  // Delete the node from cache if fail to create so it's not flushed to BE
395
415
  this.syncBlockCache.delete(resourceId || '');
396
- (_this$fireAnalyticsEv5 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv5 === void 0 || _this$fireAnalyticsEv5.call(this, createErrorPayload('Fail to create bodied sync block', resourceId, getSourceProductFromResourceIdSafe(resourceId)));
416
+ (_this$fireAnalyticsEv6 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv6 === void 0 || _this$fireAnalyticsEv6.call(this, createErrorPayload('Fail to create bodied sync block', resourceId, getSourceProductFromResourceIdSafe(resourceId)));
397
417
  }
398
418
  }
399
419
  }, {
@@ -493,20 +513,76 @@ export var SourceSyncBlockStoreManager = /*#__PURE__*/function () {
493
513
  });
494
514
  this.pendingCreationPromises.set(resourceId, creationPromise);
495
515
  } catch (error) {
496
- var _this$fireAnalyticsEv6;
516
+ var _this$fireAnalyticsEv7;
497
517
  if (this.isPendingCreation(resourceId)) {
498
518
  this.commitPendingCreation(false, resourceId);
499
519
  }
500
520
  logException(error, {
501
521
  location: 'editor-synced-block-provider/sourceSyncBlockStoreManager'
502
522
  });
503
- (_this$fireAnalyticsEv6 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv6 === void 0 || _this$fireAnalyticsEv6.call(this, createErrorPayload(error.message, resourceId, getSourceProductFromResourceIdSafe(resourceId)));
523
+ (_this$fireAnalyticsEv7 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv7 === void 0 || _this$fireAnalyticsEv7.call(this, createErrorPayload(error.message, resourceId, getSourceProductFromResourceIdSafe(resourceId)));
504
524
  }
505
525
  }
526
+ }, {
527
+ key: "pruneRecentDeleteEmissions",
528
+ value:
529
+ /**
530
+ * Drop dedupe entries older than {@link DELETE_DEDUPE_WINDOW_MS} so
531
+ * `recentDeleteEmissions` stays bounded by the number of recent deletes.
532
+ */
533
+ function pruneRecentDeleteEmissions(now) {
534
+ var _iterator2 = _createForOfIteratorHelper(this.recentDeleteEmissions),
535
+ _step2;
536
+ try {
537
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
538
+ var _step2$value = _slicedToArray(_step2.value, 2),
539
+ resourceId = _step2$value[0],
540
+ emittedAt = _step2$value[1];
541
+ if (now - emittedAt >= DELETE_DEDUPE_WINDOW_MS) {
542
+ this.recentDeleteEmissions.delete(resourceId);
543
+ }
544
+ }
545
+ } catch (err) {
546
+ _iterator2.e(err);
547
+ } finally {
548
+ _iterator2.f();
549
+ }
550
+ }
551
+
552
+ /**
553
+ * Emit the `syncedBlockDelete` success event. Gate off: legacy payload. Gate
554
+ * on: attaches `deletionReason`, `mechanism` and `blockInstanceId`, and
555
+ * suppresses repeat emissions within {@link DELETE_DEDUPE_WINDOW_MS}. Must run
556
+ * while the cache entry still exists so `blockInstanceId` is available.
557
+ */
558
+ }, {
559
+ key: "emitDeleteSuccess",
560
+ value: function emitDeleteSuccess(resourceId, reason, mechanism) {
561
+ var _this$syncBlockCache$2, _this$fireAnalyticsEv9;
562
+ var sourceProduct = getSourceProductFromResourceIdSafe(resourceId);
563
+ if (!fg('platform_editor_blocks_patch_4')) {
564
+ var _this$fireAnalyticsEv8;
565
+ (_this$fireAnalyticsEv8 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv8 === void 0 || _this$fireAnalyticsEv8.call(this, deleteSuccessPayload(resourceId, sourceProduct));
566
+ return;
567
+ }
568
+ var now = Date.now();
569
+ var lastEmittedAt = this.recentDeleteEmissions.get(resourceId);
570
+ if (lastEmittedAt !== undefined && now - lastEmittedAt < DELETE_DEDUPE_WINDOW_MS) {
571
+ return; // duplicate emission for the same logical removal
572
+ }
573
+ this.pruneRecentDeleteEmissions(now);
574
+ this.recentDeleteEmissions.set(resourceId, now);
575
+ var enrichment = {
576
+ blockInstanceId: (_this$syncBlockCache$2 = this.syncBlockCache.get(resourceId)) === null || _this$syncBlockCache$2 === void 0 ? void 0 : _this$syncBlockCache$2.blockInstanceId,
577
+ deletionReason: reason,
578
+ mechanism: mechanism
579
+ };
580
+ (_this$fireAnalyticsEv9 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv9 === void 0 || _this$fireAnalyticsEv9.call(this, deleteSuccessPayload(resourceId, sourceProduct, enrichment));
581
+ }
506
582
  }, {
507
583
  key: "delete",
508
584
  value: function () {
509
- var _delete2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2(syncBlockIds, onDelete, onDeleteCompleted, reason) {
585
+ var _delete2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2(syncBlockIds, onDelete, onDeleteCompleted, reason, mechanism) {
510
586
  var _this5 = this;
511
587
  var _this$deleteExperienc, results, callback, isDeleteSuccessful, _this$deleteExperienc2, _this$deleteExperienc3, attributionEnabled, attribution, _t2;
512
588
  return _regeneratorRuntime.wrap(function (_context2) {
@@ -541,15 +617,16 @@ export var SourceSyncBlockStoreManager = /*#__PURE__*/function () {
541
617
  onDeleteCompleted(isDeleteSuccessful);
542
618
  if (isDeleteSuccessful) {
543
619
  onDelete();
620
+ // Emit before the cache entry is deleted below, so blockInstanceId
621
+ // is still available to emitDeleteSuccess.
622
+ results.forEach(function (result) {
623
+ _this5.emitDeleteSuccess(result.resourceId, reason, mechanism);
624
+ });
544
625
  callback = function callback(Ids) {
545
626
  return _this5.syncBlockCache.delete(Ids.resourceId);
546
627
  };
547
628
  this.clearPendingDeletion();
548
629
  (_this$deleteExperienc2 = this.deleteExperience) === null || _this$deleteExperienc2 === void 0 || _this$deleteExperienc2.success();
549
- results.forEach(function (result) {
550
- var _this5$fireAnalyticsE;
551
- (_this5$fireAnalyticsE = _this5.fireAnalyticsEvent) === null || _this5$fireAnalyticsE === void 0 || _this5$fireAnalyticsE.call(_this5, deleteSuccessPayload(result.resourceId, getSourceProductFromResourceIdSafe(result.resourceId)));
552
- });
553
630
  } else {
554
631
  callback = function callback(Ids) {
555
632
  _this5.setPendingDeletion(Ids, false);
@@ -558,11 +635,10 @@ export var SourceSyncBlockStoreManager = /*#__PURE__*/function () {
558
635
  attributionEnabled = fg('platform_editor_blocks_patch_3');
559
636
  results.forEach(function (result) {
560
637
  if (result.success) {
561
- var _this5$fireAnalyticsE2;
562
- (_this5$fireAnalyticsE2 = _this5.fireAnalyticsEvent) === null || _this5$fireAnalyticsE2 === void 0 || _this5$fireAnalyticsE2.call(_this5, deleteSuccessPayload(result.resourceId, getSourceProductFromResourceIdSafe(result.resourceId)));
638
+ _this5.emitDeleteSuccess(result.resourceId, reason, mechanism);
563
639
  } else {
564
- var _this5$fireAnalyticsE3;
565
- (_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)));
640
+ var _this5$fireAnalyticsE;
641
+ (_this5$fireAnalyticsE = _this5.fireAnalyticsEvent) === null || _this5$fireAnalyticsE === void 0 || _this5$fireAnalyticsE.call(_this5, deleteErrorPayload(result.error || 'Failed to delete synced block', result.resourceId, getSourceProductFromResourceIdSafe(result.resourceId), buildErrorAttribution(attributionEnabled, result.error, result.statusCode)));
566
642
  }
567
643
  });
568
644
  }
@@ -575,9 +651,9 @@ export var SourceSyncBlockStoreManager = /*#__PURE__*/function () {
575
651
  // so the attribution `reason` falls back to `unknown` when the gate is on.
576
652
  attribution = buildErrorAttribution(fg('platform_editor_blocks_patch_3'));
577
653
  syncBlockIds.forEach(function (Ids) {
578
- var _this5$fireAnalyticsE4;
654
+ var _this5$fireAnalyticsE2;
579
655
  _this5.setPendingDeletion(Ids, false);
580
- (_this5$fireAnalyticsE4 = _this5.fireAnalyticsEvent) === null || _this5$fireAnalyticsE4 === void 0 || _this5$fireAnalyticsE4.call(_this5, deleteErrorPayload(_t2.message, Ids.resourceId, getSourceProductFromResourceIdSafe(Ids.resourceId), attribution));
656
+ (_this5$fireAnalyticsE2 = _this5.fireAnalyticsEvent) === null || _this5$fireAnalyticsE2 === void 0 || _this5$fireAnalyticsE2.call(_this5, deleteErrorPayload(_t2.message, Ids.resourceId, getSourceProductFromResourceIdSafe(Ids.resourceId), attribution));
581
657
  });
582
658
  logException(_t2, {
583
659
  location: 'editor-synced-block-provider/sourceSyncBlockStoreManager'
@@ -590,7 +666,7 @@ export var SourceSyncBlockStoreManager = /*#__PURE__*/function () {
590
666
  }
591
667
  }, _callee2, this, [[0, 4]]);
592
668
  }));
593
- function _delete(_x, _x2, _x3, _x4) {
669
+ function _delete(_x, _x2, _x3, _x4, _x5) {
594
670
  return _delete2.apply(this, arguments);
595
671
  }
596
672
  return _delete;
@@ -604,7 +680,7 @@ export var SourceSyncBlockStoreManager = /*#__PURE__*/function () {
604
680
  key: "retryDeletion",
605
681
  value: function () {
606
682
  var _retryDeletion = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee3() {
607
- var _this$deletionRetryIn, syncBlockIds, onDelete, onDeleteCompleted, deletionReason;
683
+ var _this$deletionRetryIn, syncBlockIds, onDelete, onDeleteCompleted, deletionReason, mechanism;
608
684
  return _regeneratorRuntime.wrap(function (_context3) {
609
685
  while (1) switch (_context3.prev = _context3.next) {
610
686
  case 0:
@@ -620,13 +696,13 @@ export var SourceSyncBlockStoreManager = /*#__PURE__*/function () {
620
696
  }
621
697
  return _context3.abrupt("return", Promise.resolve());
622
698
  case 2:
623
- _this$deletionRetryIn = this.deletionRetryInfo, syncBlockIds = _this$deletionRetryIn.syncBlockIds, onDelete = _this$deletionRetryIn.onDelete, onDeleteCompleted = _this$deletionRetryIn.onDeleteCompleted, deletionReason = _this$deletionRetryIn.deletionReason;
699
+ _this$deletionRetryIn = this.deletionRetryInfo, syncBlockIds = _this$deletionRetryIn.syncBlockIds, onDelete = _this$deletionRetryIn.onDelete, onDeleteCompleted = _this$deletionRetryIn.onDeleteCompleted, deletionReason = _this$deletionRetryIn.deletionReason, mechanism = _this$deletionRetryIn.mechanism;
624
700
  if (!this.confirmationCallback) {
625
701
  _context3.next = 3;
626
702
  break;
627
703
  }
628
704
  _context3.next = 3;
629
- return this.delete(syncBlockIds, onDelete, onDeleteCompleted, deletionReason);
705
+ return this.delete(syncBlockIds, onDelete, onDeleteCompleted, deletionReason, mechanism);
630
706
  case 3:
631
707
  case "end":
632
708
  return _context3.stop();
@@ -656,7 +732,7 @@ export var SourceSyncBlockStoreManager = /*#__PURE__*/function () {
656
732
  value: (function () {
657
733
  var _fetchAndCacheStatuses = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee4() {
658
734
  var _this6 = this;
659
- var sourceToReferenceMap, syncBlockNodes, results, _iterator2, _step2, _sourceToReferenceMap, _result$data, result, sourceResourceId, cached, _t3;
735
+ var sourceToReferenceMap, syncBlockNodes, results, _iterator3, _step3, _sourceToReferenceMap, _result$data, result, sourceResourceId, cached, _t3;
660
736
  return _regeneratorRuntime.wrap(function (_context4) {
661
737
  while (1) switch (_context4.prev = _context4.next) {
662
738
  case 0:
@@ -692,10 +768,10 @@ export var SourceSyncBlockStoreManager = /*#__PURE__*/function () {
692
768
  return this.dataProvider.fetchNodesData(syncBlockNodes);
693
769
  case 3:
694
770
  results = _context4.sent;
695
- _iterator2 = _createForOfIteratorHelper(results);
771
+ _iterator3 = _createForOfIteratorHelper(results);
696
772
  try {
697
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
698
- result = _step2.value;
773
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
774
+ result = _step3.value;
699
775
  // Map the reference resourceId back to the source resourceId
700
776
  sourceResourceId = (_sourceToReferenceMap = sourceToReferenceMap.get(result.resourceId)) !== null && _sourceToReferenceMap !== void 0 ? _sourceToReferenceMap : result.resourceId;
701
777
  cached = this.syncBlockCache.get(sourceResourceId);
@@ -704,9 +780,9 @@ export var SourceSyncBlockStoreManager = /*#__PURE__*/function () {
704
780
  }
705
781
  }
706
782
  } catch (err) {
707
- _iterator2.e(err);
783
+ _iterator3.e(err);
708
784
  } finally {
709
- _iterator2.f();
785
+ _iterator3.f();
710
786
  }
711
787
  _context4.next = 5;
712
788
  break;
@@ -756,7 +832,9 @@ export var SourceSyncBlockStoreManager = /*#__PURE__*/function () {
756
832
  // onDelete: no-op, document is being discarded
757
833
  function () {},
758
834
  // onDeleteCompleted: no-op
759
- 'source-block-unpublished');
835
+ 'source-block-unpublished',
836
+ // Cancel/discard cleanup is code-initiated, not a direct user edit.
837
+ 'other');
760
838
  }
761
839
 
762
840
  /**
@@ -770,7 +848,7 @@ export var SourceSyncBlockStoreManager = /*#__PURE__*/function () {
770
848
  }, {
771
849
  key: "deleteSyncBlocksWithConfirmation",
772
850
  value: (function () {
773
- var _deleteSyncBlocksWithConfirmation = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee5(syncBlockIds, deletionReason, onDelete, onDeleteCompleted, destroyCallback) {
851
+ var _deleteSyncBlocksWithConfirmation = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee5(syncBlockIds, deletionReason, onDelete, onDeleteCompleted, destroyCallback, mechanism) {
774
852
  var confirmed, isDeleteSuccessful;
775
853
  return _regeneratorRuntime.wrap(function (_context5) {
776
854
  while (1) switch (_context5.prev = _context5.next) {
@@ -794,7 +872,7 @@ export var SourceSyncBlockStoreManager = /*#__PURE__*/function () {
794
872
  break;
795
873
  }
796
874
  _context5.next = 3;
797
- return this.delete(syncBlockIds, onDelete, onDeleteCompleted, deletionReason);
875
+ return this.delete(syncBlockIds, onDelete, onDeleteCompleted, deletionReason, mechanism);
798
876
  case 3:
799
877
  isDeleteSuccessful = _context5.sent;
800
878
  if (!isDeleteSuccessful) {
@@ -804,7 +882,8 @@ export var SourceSyncBlockStoreManager = /*#__PURE__*/function () {
804
882
  onDelete: onDelete,
805
883
  onDeleteCompleted: onDeleteCompleted,
806
884
  destroyCallback: destroyCallback,
807
- deletionReason: deletionReason
885
+ deletionReason: deletionReason,
886
+ mechanism: mechanism
808
887
  };
809
888
  } else {
810
889
  destroyCallback();
@@ -819,7 +898,7 @@ export var SourceSyncBlockStoreManager = /*#__PURE__*/function () {
819
898
  }
820
899
  }, _callee5, this);
821
900
  }));
822
- function deleteSyncBlocksWithConfirmation(_x5, _x6, _x7, _x8, _x9) {
901
+ function deleteSyncBlocksWithConfirmation(_x6, _x7, _x8, _x9, _x0, _x1) {
823
902
  return _deleteSyncBlocksWithConfirmation.apply(this, arguments);
824
903
  }
825
904
  return deleteSyncBlocksWithConfirmation;
@@ -847,11 +926,11 @@ export var SourceSyncBlockStoreManager = /*#__PURE__*/function () {
847
926
  return sourceInfo;
848
927
  });
849
928
  } catch (error) {
850
- var _this$fireAnalyticsEv7;
929
+ var _this$fireAnalyticsEv0;
851
930
  logException(error, {
852
931
  location: 'editor-synced-block-provider/sourceSyncBlockStoreManager'
853
932
  });
854
- (_this$fireAnalyticsEv7 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv7 === void 0 || _this$fireAnalyticsEv7.call(this, getSourceInfoErrorPayload(error.message));
933
+ (_this$fireAnalyticsEv0 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv0 === void 0 || _this$fireAnalyticsEv0.call(this, getSourceInfoErrorPayload(error.message));
855
934
  return Promise.resolve(undefined);
856
935
  }
857
936
  }
@@ -864,11 +943,11 @@ export var SourceSyncBlockStoreManager = /*#__PURE__*/function () {
864
943
  }
865
944
  return this.dataProvider.fetchReferences(resourceId, true);
866
945
  } catch (error) {
867
- var _this$fireAnalyticsEv8;
946
+ var _this$fireAnalyticsEv1;
868
947
  logException(error, {
869
948
  location: 'editor-synced-block-provider/sourceSyncBlockStoreManager'
870
949
  });
871
- (_this$fireAnalyticsEv8 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv8 === void 0 || _this$fireAnalyticsEv8.call(this, fetchReferencesErrorPayload(error.message, resourceId, getSourceProductFromResourceIdSafe(resourceId)));
950
+ (_this$fireAnalyticsEv1 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv1 === void 0 || _this$fireAnalyticsEv1.call(this, fetchReferencesErrorPayload(error.message, resourceId, getSourceProductFromResourceIdSafe(resourceId)));
872
951
  return Promise.resolve({
873
952
  error: SyncBlockError.Errored
874
953
  });
@@ -885,6 +964,7 @@ export var SourceSyncBlockStoreManager = /*#__PURE__*/function () {
885
964
  this.postCreationFlushCallback = undefined;
886
965
  this.pendingCreationPromises.clear();
887
966
  this.creationsTimedOutDuringFlush.clear();
967
+ this.recentDeleteEmissions.clear();
888
968
  this.dataProvider = undefined;
889
969
  (_this$saveExperience4 = this.saveExperience) === null || _this$saveExperience4 === void 0 || _this$saveExperience4.abort({
890
970
  reason: 'editorDestroyed'
@@ -325,15 +325,25 @@ export var createSuccessPayload = function createSuccessPayload(resourceId, sour
325
325
  })
326
326
  };
327
327
  };
328
- export var createSuccessPayloadNew = function createSuccessPayloadNew(resourceId) {
328
+
329
+ /**
330
+ * Operational `syncedBlockCreate` success event (previously only error rows
331
+ * existed, so dashboards had no create denominator). Fired behind
332
+ * `platform_editor_blocks_patch_4` with the `blockInstanceId` join key.
333
+ */
334
+ export var createSuccessPayloadNew = function createSuccessPayloadNew(resourceId, blockInstanceId, sourceProduct) {
329
335
  return {
330
336
  action: ACTION.INSERTED,
331
337
  actionSubject: ACTION_SUBJECT.SYNCED_BLOCK,
332
338
  actionSubjectId: ACTION_SUBJECT_ID.SYNCED_BLOCK_CREATE,
333
339
  eventType: EVENT_TYPE.OPERATIONAL,
334
- attributes: {
340
+ attributes: _objectSpread(_objectSpread({
335
341
  resourceId: resourceId
336
- }
342
+ }, blockInstanceId && {
343
+ blockInstanceId: blockInstanceId
344
+ }), sourceProduct && {
345
+ sourceProduct: sourceProduct
346
+ })
337
347
  };
338
348
  };
339
349
  export var updateSuccessPayload = function updateSuccessPayload(resourceId, hasReference, sourceProduct) {
@@ -351,16 +361,29 @@ export var updateSuccessPayload = function updateSuccessPayload(resourceId, hasR
351
361
  })
352
362
  };
353
363
  };
354
- export var deleteSuccessPayload = function deleteSuccessPayload(resourceId, sourceProduct) {
364
+
365
+ /**
366
+ * Optional enrichment for the `syncedBlockDelete` success event behind
367
+ * `platform_editor_blocks_patch_4`. All fields optional so the gate-off payload
368
+ * is unchanged; `blockInstanceId` is the bare-uuid join key.
369
+ */
370
+
371
+ export var deleteSuccessPayload = function deleteSuccessPayload(resourceId, sourceProduct, enrichment) {
355
372
  return {
356
373
  action: ACTION.DELETED,
357
374
  actionSubject: ACTION_SUBJECT.SYNCED_BLOCK,
358
375
  actionSubjectId: ACTION_SUBJECT_ID.SYNCED_BLOCK_DELETE,
359
376
  eventType: EVENT_TYPE.OPERATIONAL,
360
- attributes: _objectSpread({
377
+ attributes: _objectSpread(_objectSpread(_objectSpread(_objectSpread({
361
378
  resourceId: resourceId
362
379
  }, sourceProduct && {
363
380
  sourceProduct: sourceProduct
381
+ }), (enrichment === null || enrichment === void 0 ? void 0 : enrichment.blockInstanceId) && {
382
+ blockInstanceId: enrichment.blockInstanceId
383
+ }), (enrichment === null || enrichment === void 0 ? void 0 : enrichment.deletionReason) && {
384
+ deletionReason: enrichment.deletionReason
385
+ }), (enrichment === null || enrichment === void 0 ? void 0 : enrichment.mechanism) && {
386
+ mechanism: enrichment.mechanism
364
387
  })
365
388
  };
366
389
  };
@@ -76,6 +76,21 @@ export type ReferencesSourceInfo = {
76
76
  };
77
77
  export type DeletionReason = 'source-block-deleted' | 'source-block-unsynced' | 'source-block-unpublished';
78
78
  export type DeletionReasonResponse = DeletionReason | 'source-document-deleted';
79
+ /**
80
+ * How a source bodiedSyncBlock removal was performed, derived from the
81
+ * originating transaction. Distinct from {@link DeletionReason} (the outcome):
82
+ * `mechanism` tells deliberate deletes apart from undos and accidental overwrites.
83
+ *
84
+ * Values name the user action that produced the removal:
85
+ * - `undo` / `redo` — removal came from a history undo/redo.
86
+ * - `deleteButton` — the explicit "Delete" control in the synced-block toolbar.
87
+ * - `keyboardDelete` — Backspace/Delete key on a caret or range selection.
88
+ * - `selectionReplaced` — the block was selected and replaced by typing or
89
+ * pasting over it (the accidental-overwrite path).
90
+ * - `other` — any non-direct removal (unsync, conversion, code-dispatched, or a
91
+ * structural wrap/lift/unwrap edit).
92
+ */
93
+ export type DeletionMechanism = 'undo' | 'redo' | 'deleteButton' | 'keyboardDelete' | 'selectionReplaced' | 'other';
79
94
  export type SyncBlockPrefetchData = {
80
95
  prefetchPromise: Promise<SyncBlockInstance[] | undefined>;
81
96
  resourceIds: string[];
@@ -1,2 +1,2 @@
1
1
  export { SyncBlockError } from '../common/types';
2
- export type { ResourceId, SyncBlockData, SyncBlockNode, SyncBlockProduct, SyncBlockStatus, BlockInstanceId, SyncBlockAttrs, ReferenceSyncBlockData, ReferencesSourceInfo, DeletionReason, SyncBlockPrefetchData, } from '../common/types';
2
+ export type { ResourceId, SyncBlockData, SyncBlockNode, SyncBlockProduct, SyncBlockStatus, BlockInstanceId, SyncBlockAttrs, ReferenceSyncBlockData, ReferencesSourceInfo, DeletionReason, DeletionMechanism, SyncBlockPrefetchData, } from '../common/types';
@@ -1,7 +1,7 @@
1
1
  import type { SyncBlockEventPayload } from '@atlaskit/editor-common/analytics';
2
2
  import type { ViewMode } from '@atlaskit/editor-plugin-editor-viewmode';
3
3
  import type { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
4
- import type { ResourceId, SyncBlockAttrs, BlockInstanceId, DeletionReason, ReferenceSyncBlockData } from '../common/types';
4
+ import type { ResourceId, SyncBlockAttrs, BlockInstanceId, DeletionReason, DeletionMechanism, ReferenceSyncBlockData } from '../common/types';
5
5
  import type { SyncBlockDataProviderInterface, SyncBlockSourceInfo } from '../providers/types';
6
6
  export type ConfirmationCallback = (syncBlockIds: SyncBlockAttrs[], deleteReason: DeletionReason | undefined) => Promise<boolean>;
7
7
  type OnDelete = () => void;
@@ -39,6 +39,12 @@ export declare class SourceSyncBlockStoreManager {
39
39
  private saveExperience;
40
40
  private deleteExperience;
41
41
  private fetchSourceInfoExperience;
42
+ /**
43
+ * resourceId -> timestamp (ms) of the last `syncedBlockDelete` emission, used
44
+ * to suppress duplicates within {@link DELETE_DEDUPE_WINDOW_MS}. Only consulted
45
+ * behind `platform_editor_blocks_patch_4`.
46
+ */
47
+ private recentDeleteEmissions;
42
48
  constructor(dataProvider?: SyncBlockDataProviderInterface, viewMode?: ViewMode, isLivePage?: boolean);
43
49
  /**
44
50
  * Register a callback to be invoked after flush() completes.
@@ -94,6 +100,18 @@ export declare class SourceSyncBlockStoreManager {
94
100
  */
95
101
  createBodiedSyncBlockNode(attrs: SyncBlockAttrs, node: PMNode, onCompletion: OnCompletion): void;
96
102
  private setPendingDeletion;
103
+ /**
104
+ * Drop dedupe entries older than {@link DELETE_DEDUPE_WINDOW_MS} so
105
+ * `recentDeleteEmissions` stays bounded by the number of recent deletes.
106
+ */
107
+ private pruneRecentDeleteEmissions;
108
+ /**
109
+ * Emit the `syncedBlockDelete` success event. Gate off: legacy payload. Gate
110
+ * on: attaches `deletionReason`, `mechanism` and `blockInstanceId`, and
111
+ * suppresses repeat emissions within {@link DELETE_DEDUPE_WINDOW_MS}. Must run
112
+ * while the cache entry still exists so `blockInstanceId` is available.
113
+ */
114
+ private emitDeleteSuccess;
97
115
  private delete;
98
116
  isRetryingDeletion(): boolean;
99
117
  retryDeletion(): Promise<void>;
@@ -120,7 +138,7 @@ export declare class SourceSyncBlockStoreManager {
120
138
  * @param onDeleteCompleted - The callback for after the deletion is saved to BE (whether successful or not)
121
139
  * @param destroyCallback - The callback to clear any reference stored for deletion (regardless if deletion is completed or abort)
122
140
  */
123
- deleteSyncBlocksWithConfirmation(syncBlockIds: SyncBlockAttrs[], deletionReason: DeletionReason, onDelete: OnDelete, onDeleteCompleted: OnCompletion, destroyCallback: DestroyCallback): Promise<void>;
141
+ deleteSyncBlocksWithConfirmation(syncBlockIds: SyncBlockAttrs[], deletionReason: DeletionReason, onDelete: OnDelete, onDeleteCompleted: OnCompletion, destroyCallback: DestroyCallback, mechanism?: DeletionMechanism): Promise<void>;
124
142
  getSyncBlockSourceInfo(localId: BlockInstanceId): Promise<SyncBlockSourceInfo | undefined>;
125
143
  fetchReferences(resourceId: string): Promise<ReferenceSyncBlockData>;
126
144
  destroy(): void;
@@ -1,6 +1,7 @@
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
3
  import { SyncBlockError } from '../common/types';
4
+ import type { DeletionMechanism, DeletionReason } from '../common/types';
4
5
  export declare const stringifyError: (error: unknown) => string | undefined;
5
6
  /**
6
7
  * The set of categorical failure reasons emitted on synced-block operational error
@@ -162,6 +163,21 @@ export declare const cacheDeletionForcedPayload: (rescheduleCount: number, resou
162
163
  export declare const fetchReferencesErrorPayload: (error: string, resourceId?: string, sourceProduct?: string) => SyncBlockEventPayload;
163
164
  export declare const fetchSuccessPayload: (resourceId: string, blockInstanceId?: string, sourceProduct?: string) => RendererSyncBlockEventPayload;
164
165
  export declare const createSuccessPayload: (resourceId: string, sourceProduct?: string) => SyncBlockEventPayload;
165
- export declare const createSuccessPayloadNew: (resourceId: string) => SyncBlockEventPayload;
166
+ /**
167
+ * Operational `syncedBlockCreate` success event (previously only error rows
168
+ * existed, so dashboards had no create denominator). Fired behind
169
+ * `platform_editor_blocks_patch_4` with the `blockInstanceId` join key.
170
+ */
171
+ export declare const createSuccessPayloadNew: (resourceId: string, blockInstanceId?: string, sourceProduct?: string) => SyncBlockEventPayload;
166
172
  export declare const updateSuccessPayload: (resourceId: string, hasReference?: boolean, sourceProduct?: string) => SyncBlockEventPayload;
167
- export declare const deleteSuccessPayload: (resourceId: string, sourceProduct?: string) => SyncBlockEventPayload;
173
+ /**
174
+ * Optional enrichment for the `syncedBlockDelete` success event behind
175
+ * `platform_editor_blocks_patch_4`. All fields optional so the gate-off payload
176
+ * is unchanged; `blockInstanceId` is the bare-uuid join key.
177
+ */
178
+ export type DeleteSuccessEnrichment = {
179
+ blockInstanceId?: string;
180
+ deletionReason?: DeletionReason;
181
+ mechanism?: DeletionMechanism;
182
+ };
183
+ export declare const deleteSuccessPayload: (resourceId: string, sourceProduct?: string, enrichment?: DeleteSuccessEnrichment) => SyncBlockEventPayload;
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": "^118.0.0",
24
+ "@atlaskit/tmp-editor-statsig": "^119.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.18.0",
33
+ "@atlaskit/editor-common": "^116.19.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.4.4",
77
+ "version": "8.5.1",
78
78
  "description": "Synced Block Provider for @atlaskit/editor-plugin-synced-block",
79
79
  "author": "Atlassian Pty Ltd",
80
80
  "license": "Apache-2.0",