@atlaskit/editor-synced-block-provider 8.5.7 → 8.6.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # @atlaskit/editor-synced-block-provider
2
2
 
3
+ ## 8.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`c0b0b98789f7a`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/c0b0b98789f7a) -
8
+ Add analytics for synced block source creation: record the creation input method and whether the
9
+ block was created empty, and emit a one-off event the first time an empty source block gains
10
+ content.
11
+
12
+ ### Patch Changes
13
+
14
+ - Updated dependencies
15
+
3
16
  ## 8.5.7
4
17
 
5
18
  ### Patch Changes
@@ -59,6 +59,20 @@ var SourceSyncBlockStoreManager = exports.SourceSyncBlockStoreManager = /*#__PUR
59
59
  * multiple blocks are created concurrently. See EDITOR-7112.
60
60
  */
61
61
  (0, _defineProperty2.default)(this, "creationsTimedOutDuringFlush", new Set());
62
+ /**
63
+ * Creation-type signals captured at `createBodiedSyncBlockNode` time and read
64
+ * back in `commitPendingCreation` to enrich the `syncedBlockCreate` event.
65
+ * Kept out of the flushed cache record so it never leaks into Block Service /
66
+ * ADF; removed once consumed.
67
+ */
68
+ (0, _defineProperty2.default)(this, "creationEnrichment", new Map());
69
+ /**
70
+ * Resource IDs of blocks created empty this session that have not yet fired
71
+ * the first-content-added event. Added on empty creation, removed when the
72
+ * block first gains content (the event fires once). Blocks created with
73
+ * content or not created this session are never tracked.
74
+ */
75
+ (0, _defineProperty2.default)(this, "awaitingFirstContent", new Set());
62
76
  /**
63
77
  * resourceId -> timestamp (ms) of the last `syncedBlockDelete` emission, used
64
78
  * to suppress duplicates within {@link DELETE_DEDUPE_WINDOW_MS}. Only consulted
@@ -411,17 +425,48 @@ var SourceSyncBlockStoreManager = exports.SourceSyncBlockStoreManager = /*#__PUR
411
425
  var _this$fireAnalyticsEv4;
412
426
  var sourceProduct = (0, _utils.getSourceProductFromResourceIdSafe)(resourceId);
413
427
  (_this$fireAnalyticsEv4 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv4 === void 0 || _this$fireAnalyticsEv4.call(this, (0, _errorHandling.createSuccessPayload)(resourceId || '', sourceProduct));
414
- // Operational create-success event with the join key.
428
+ // Operational create-success event with the join key + creation-type
429
+ // signals (inputMethod / createdEmpty) captured at the command layer.
415
430
  if ((0, _platformFeatureFlags.fg)('platform_editor_blocks_patch_4')) {
416
431
  var _this$fireAnalyticsEv5, _this$syncBlockCache$;
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));
432
+ (_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, this.creationEnrichment.get(resourceId)));
418
433
  }
419
434
  } else {
420
435
  var _this$fireAnalyticsEv6;
421
436
  // Delete the node from cache if fail to create so it's not flushed to BE
422
437
  this.syncBlockCache.delete(resourceId || '');
438
+ // Creation failed, so there is no block to add content to — drop the
439
+ // first-content tracking entry so a later unrelated edit cannot fire it.
440
+ this.awaitingFirstContent.delete(resourceId || '');
423
441
  (_this$fireAnalyticsEv6 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv6 === void 0 || _this$fireAnalyticsEv6.call(this, (0, _errorHandling.createErrorPayload)('Fail to create bodied sync block', resourceId, (0, _utils.getSourceProductFromResourceIdSafe)(resourceId)));
424
442
  }
443
+ // Enrichment is single-use — drop it once the create has resolved either way.
444
+ this.creationEnrichment.delete(resourceId || '');
445
+ }
446
+
447
+ /**
448
+ * Fire the first-content-added event once when a block created empty this
449
+ * session first gains content. The caller detects the in-block edit; this owns
450
+ * dedupe + emission (fires at most once per block, only for empty→content).
451
+ * Gated behind `platform_editor_blocks_patch_4`.
452
+ */
453
+ }, {
454
+ key: "maybeEmitFirstContentAdded",
455
+ value: function maybeEmitFirstContentAdded(resourceId, blockInstanceId) {
456
+ var _this$fireAnalyticsEv7;
457
+ if (this.viewMode === 'view') {
458
+ return;
459
+ }
460
+ if (!(0, _platformFeatureFlags.fg)('platform_editor_blocks_patch_4')) {
461
+ return;
462
+ }
463
+ // `delete` returns false when the id was not tracked (already emitted, or
464
+ // the block was not created empty this session), so this both dedupes and
465
+ // scopes emission to genuine first-content transitions in one check.
466
+ if (!this.awaitingFirstContent.delete(resourceId)) {
467
+ return;
468
+ }
469
+ (_this$fireAnalyticsEv7 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv7 === void 0 || _this$fireAnalyticsEv7.call(this, (0, _errorHandling.addContentSuccessPayload)(resourceId, blockInstanceId, (0, _utils.getSourceProductFromResourceIdSafe)(resourceId)));
425
470
  }
426
471
  }, {
427
472
  key: "registerConfirmationCallback",
@@ -461,16 +506,28 @@ var SourceSyncBlockStoreManager = exports.SourceSyncBlockStoreManager = /*#__PUR
461
506
  * @param attrs attributes Ids of the node
462
507
  * @param node the ProseMirror node to cache
463
508
  * @param onCompletion callback invoked when creation completes
509
+ * @param enrichment optional creation-type signals stashed and attached to the
510
+ * `syncedBlockCreate` event when the async create resolves. When
511
+ * `createdEmpty` is true the block is also registered for the
512
+ * first-content-added event.
464
513
  */
465
514
  }, {
466
515
  key: "createBodiedSyncBlockNode",
467
- value: function createBodiedSyncBlockNode(attrs, node, onCompletion) {
516
+ value: function createBodiedSyncBlockNode(attrs, node, onCompletion, enrichment) {
468
517
  var _this4 = this;
469
518
  if (this.viewMode === 'view') {
470
519
  return;
471
520
  }
472
521
  var resourceId = attrs.resourceId,
473
522
  blockInstanceId = attrs.localId;
523
+ // Stash creation-type signals for commitPendingCreation to read.
524
+ if (enrichment) {
525
+ this.creationEnrichment.set(resourceId, enrichment);
526
+ // Only empty-created blocks can produce an empty→content transition.
527
+ if (enrichment.createdEmpty) {
528
+ this.awaitingFirstContent.add(resourceId);
529
+ }
530
+ }
474
531
  try {
475
532
  var _this$createExperienc;
476
533
  if (!this.dataProvider) {
@@ -520,14 +577,14 @@ var SourceSyncBlockStoreManager = exports.SourceSyncBlockStoreManager = /*#__PUR
520
577
  });
521
578
  this.pendingCreationPromises.set(resourceId, creationPromise);
522
579
  } catch (error) {
523
- var _this$fireAnalyticsEv7;
580
+ var _this$fireAnalyticsEv8;
524
581
  if (this.isPendingCreation(resourceId)) {
525
582
  this.commitPendingCreation(false, resourceId);
526
583
  }
527
584
  (0, _monitoring.logException)(error, {
528
585
  location: 'editor-synced-block-provider/sourceSyncBlockStoreManager'
529
586
  });
530
- (_this$fireAnalyticsEv7 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv7 === void 0 || _this$fireAnalyticsEv7.call(this, (0, _errorHandling.createErrorPayload)(error.message, resourceId, (0, _utils.getSourceProductFromResourceIdSafe)(resourceId)));
587
+ (_this$fireAnalyticsEv8 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv8 === void 0 || _this$fireAnalyticsEv8.call(this, (0, _errorHandling.createErrorPayload)(error.message, resourceId, (0, _utils.getSourceProductFromResourceIdSafe)(resourceId)));
531
588
  }
532
589
  }
533
590
  }, {
@@ -565,11 +622,11 @@ var SourceSyncBlockStoreManager = exports.SourceSyncBlockStoreManager = /*#__PUR
565
622
  }, {
566
623
  key: "emitDeleteSuccess",
567
624
  value: function emitDeleteSuccess(resourceId, reason, mechanism) {
568
- var _this$syncBlockCache$2, _this$fireAnalyticsEv9;
625
+ var _this$syncBlockCache$2, _this$fireAnalyticsEv0;
569
626
  var sourceProduct = (0, _utils.getSourceProductFromResourceIdSafe)(resourceId);
570
627
  if (!(0, _platformFeatureFlags.fg)('platform_editor_blocks_patch_4')) {
571
- var _this$fireAnalyticsEv8;
572
- (_this$fireAnalyticsEv8 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv8 === void 0 || _this$fireAnalyticsEv8.call(this, (0, _errorHandling.deleteSuccessPayload)(resourceId, sourceProduct));
628
+ var _this$fireAnalyticsEv9;
629
+ (_this$fireAnalyticsEv9 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv9 === void 0 || _this$fireAnalyticsEv9.call(this, (0, _errorHandling.deleteSuccessPayload)(resourceId, sourceProduct));
573
630
  return;
574
631
  }
575
632
  var now = Date.now();
@@ -584,7 +641,7 @@ var SourceSyncBlockStoreManager = exports.SourceSyncBlockStoreManager = /*#__PUR
584
641
  deletionReason: reason,
585
642
  mechanism: mechanism
586
643
  };
587
- (_this$fireAnalyticsEv9 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv9 === void 0 || _this$fireAnalyticsEv9.call(this, (0, _errorHandling.deleteSuccessPayload)(resourceId, sourceProduct, enrichment));
644
+ (_this$fireAnalyticsEv0 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv0 === void 0 || _this$fireAnalyticsEv0.call(this, (0, _errorHandling.deleteSuccessPayload)(resourceId, sourceProduct, enrichment));
588
645
  }
589
646
  }, {
590
647
  key: "delete",
@@ -933,11 +990,11 @@ var SourceSyncBlockStoreManager = exports.SourceSyncBlockStoreManager = /*#__PUR
933
990
  return sourceInfo;
934
991
  });
935
992
  } catch (error) {
936
- var _this$fireAnalyticsEv0;
993
+ var _this$fireAnalyticsEv1;
937
994
  (0, _monitoring.logException)(error, {
938
995
  location: 'editor-synced-block-provider/sourceSyncBlockStoreManager'
939
996
  });
940
- (_this$fireAnalyticsEv0 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv0 === void 0 || _this$fireAnalyticsEv0.call(this, (0, _errorHandling.getSourceInfoErrorPayload)(error.message));
997
+ (_this$fireAnalyticsEv1 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv1 === void 0 || _this$fireAnalyticsEv1.call(this, (0, _errorHandling.getSourceInfoErrorPayload)(error.message));
941
998
  return Promise.resolve(undefined);
942
999
  }
943
1000
  }
@@ -950,11 +1007,11 @@ var SourceSyncBlockStoreManager = exports.SourceSyncBlockStoreManager = /*#__PUR
950
1007
  }
951
1008
  return this.dataProvider.fetchReferences(resourceId, true);
952
1009
  } catch (error) {
953
- var _this$fireAnalyticsEv1;
1010
+ var _this$fireAnalyticsEv10;
954
1011
  (0, _monitoring.logException)(error, {
955
1012
  location: 'editor-synced-block-provider/sourceSyncBlockStoreManager'
956
1013
  });
957
- (_this$fireAnalyticsEv1 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv1 === void 0 || _this$fireAnalyticsEv1.call(this, (0, _errorHandling.fetchReferencesErrorPayload)(error.message, resourceId, (0, _utils.getSourceProductFromResourceIdSafe)(resourceId)));
1014
+ (_this$fireAnalyticsEv10 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv10 === void 0 || _this$fireAnalyticsEv10.call(this, (0, _errorHandling.fetchReferencesErrorPayload)(error.message, resourceId, (0, _utils.getSourceProductFromResourceIdSafe)(resourceId)));
958
1015
  return Promise.resolve({
959
1016
  error: _types.SyncBlockError.Errored
960
1017
  });
@@ -971,6 +1028,8 @@ var SourceSyncBlockStoreManager = exports.SourceSyncBlockStoreManager = /*#__PUR
971
1028
  this.postCreationFlushCallback = undefined;
972
1029
  this.pendingCreationPromises.clear();
973
1030
  this.creationsTimedOutDuringFlush.clear();
1031
+ this.creationEnrichment.clear();
1032
+ this.awaitingFirstContent.clear();
974
1033
  this.recentDeleteEmissions.clear();
975
1034
  this.dataProvider = undefined;
976
1035
  (_this$saveExperience4 = this.saveExperience) === null || _this$saveExperience4 === void 0 || _this$saveExperience4.abort({
@@ -4,7 +4,7 @@ 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.createSuccessPayload = exports.createSuccessOperationalPayload = 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.addContentSuccessPayload = exports.FETCH_BENIGN_REASONS = void 0;
8
8
  exports.getErrorPayload = getErrorPayload;
9
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"));
@@ -357,16 +357,49 @@ var createSuccessPayload = exports.createSuccessPayload = function createSuccess
357
357
  };
358
358
 
359
359
  /**
360
- * Operational `syncedBlockCreate` success event (previously only error rows
361
- * existed, so dashboards had no create denominator). Fired behind
362
- * `platform_editor_blocks_patch_4` with the `blockInstanceId` join key.
360
+ * Optional enrichment for the `syncedBlockCreate` success event. All fields
361
+ * optional so gate-off/legacy payloads are unchanged. `inputMethod`: creating
362
+ * surface (enum, PII-safe). `createdEmpty`: true from an empty selection, false
363
+ * when content was converted.
363
364
  */
364
- var createSuccessOperationalPayload = exports.createSuccessOperationalPayload = function createSuccessOperationalPayload(resourceId, blockInstanceId, sourceProduct) {
365
+
366
+ /**
367
+ * Operational `syncedBlockCreate` success event, behind
368
+ * `platform_editor_blocks_patch_4`, with the `blockInstanceId` join key and,
369
+ * when available, the `inputMethod` + `createdEmpty` creation-type signals.
370
+ */
371
+ var createSuccessOperationalPayload = exports.createSuccessOperationalPayload = function createSuccessOperationalPayload(resourceId, blockInstanceId, sourceProduct, enrichment) {
365
372
  return {
366
373
  action: _analytics.ACTION.INSERTED,
367
374
  actionSubject: _analytics.ACTION_SUBJECT.SYNCED_BLOCK,
368
375
  actionSubjectId: _analytics.ACTION_SUBJECT_ID.SYNCED_BLOCK_CREATE,
369
376
  eventType: _analytics.EVENT_TYPE.OPERATIONAL,
377
+ attributes: _objectSpread(_objectSpread(_objectSpread(_objectSpread({
378
+ resourceId: resourceId
379
+ }, blockInstanceId && {
380
+ blockInstanceId: blockInstanceId
381
+ }), sourceProduct && {
382
+ sourceProduct: sourceProduct
383
+ }), (enrichment === null || enrichment === void 0 ? void 0 : enrichment.inputMethod) && {
384
+ inputMethod: enrichment.inputMethod
385
+ }), (enrichment === null || enrichment === void 0 ? void 0 : enrichment.createdEmpty) !== undefined && {
386
+ createdEmpty: enrichment.createdEmpty
387
+ })
388
+ };
389
+ };
390
+
391
+ /**
392
+ * Operational first-content-added event, behind
393
+ * `platform_editor_blocks_patch_4`. Fired once when a block created empty first
394
+ * gains user content. Join keys only (`resourceId` + `blockInstanceId`), no user
395
+ * content (PII-safe).
396
+ */
397
+ var addContentSuccessPayload = exports.addContentSuccessPayload = function addContentSuccessPayload(resourceId, blockInstanceId, sourceProduct) {
398
+ return {
399
+ action: _analytics.ACTION.ADDED,
400
+ actionSubject: _analytics.ACTION_SUBJECT.SYNCED_BLOCK,
401
+ actionSubjectId: _analytics.ACTION_SUBJECT_ID.SYNCED_BLOCK_ADD_CONTENT,
402
+ eventType: _analytics.EVENT_TYPE.OPERATIONAL,
370
403
  attributes: _objectSpread(_objectSpread({
371
404
  resourceId: resourceId
372
405
  }, blockInstanceId && {
@@ -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, createSuccessOperationalPayload, deleteSuccessPayload, fetchReferencesErrorPayload, buildErrorAttribution } from '../utils/errorHandling';
5
+ import { updateErrorPayload, createErrorPayload, deleteErrorPayload, updateCacheErrorPayload, getSourceInfoErrorPayload, updateSuccessPayload, createSuccessPayload, createSuccessOperationalPayload, addContentSuccessPayload, 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. */
@@ -40,6 +40,20 @@ export class SourceSyncBlockStoreManager {
40
40
  * multiple blocks are created concurrently. See EDITOR-7112.
41
41
  */
42
42
  _defineProperty(this, "creationsTimedOutDuringFlush", new Set());
43
+ /**
44
+ * Creation-type signals captured at `createBodiedSyncBlockNode` time and read
45
+ * back in `commitPendingCreation` to enrich the `syncedBlockCreate` event.
46
+ * Kept out of the flushed cache record so it never leaks into Block Service /
47
+ * ADF; removed once consumed.
48
+ */
49
+ _defineProperty(this, "creationEnrichment", new Map());
50
+ /**
51
+ * Resource IDs of blocks created empty this session that have not yet fired
52
+ * the first-content-added event. Added on empty creation, removed when the
53
+ * block first gains content (the event fires once). Blocks created with
54
+ * content or not created this session are never tracked.
55
+ */
56
+ _defineProperty(this, "awaitingFirstContent", new Set());
43
57
  /**
44
58
  * resourceId -> timestamp (ms) of the last `syncedBlockDelete` emission, used
45
59
  * to suppress duplicates within {@link DELETE_DEDUPE_WINDOW_MS}. Only consulted
@@ -338,17 +352,46 @@ export class SourceSyncBlockStoreManager {
338
352
  var _this$fireAnalyticsEv6;
339
353
  const sourceProduct = getSourceProductFromResourceIdSafe(resourceId);
340
354
  (_this$fireAnalyticsEv6 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv6 === void 0 ? void 0 : _this$fireAnalyticsEv6.call(this, createSuccessPayload(resourceId || '', sourceProduct));
341
- // Operational create-success event with the join key.
355
+ // Operational create-success event with the join key + creation-type
356
+ // signals (inputMethod / createdEmpty) captured at the command layer.
342
357
  if (fg('platform_editor_blocks_patch_4')) {
343
358
  var _this$fireAnalyticsEv7, _this$syncBlockCache$;
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));
359
+ (_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, this.creationEnrichment.get(resourceId)));
345
360
  }
346
361
  } else {
347
362
  var _this$fireAnalyticsEv8;
348
363
  // Delete the node from cache if fail to create so it's not flushed to BE
349
364
  this.syncBlockCache.delete(resourceId || '');
365
+ // Creation failed, so there is no block to add content to — drop the
366
+ // first-content tracking entry so a later unrelated edit cannot fire it.
367
+ this.awaitingFirstContent.delete(resourceId || '');
350
368
  (_this$fireAnalyticsEv8 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv8 === void 0 ? void 0 : _this$fireAnalyticsEv8.call(this, createErrorPayload('Fail to create bodied sync block', resourceId, getSourceProductFromResourceIdSafe(resourceId)));
351
369
  }
370
+ // Enrichment is single-use — drop it once the create has resolved either way.
371
+ this.creationEnrichment.delete(resourceId || '');
372
+ }
373
+
374
+ /**
375
+ * Fire the first-content-added event once when a block created empty this
376
+ * session first gains content. The caller detects the in-block edit; this owns
377
+ * dedupe + emission (fires at most once per block, only for empty→content).
378
+ * Gated behind `platform_editor_blocks_patch_4`.
379
+ */
380
+ maybeEmitFirstContentAdded(resourceId, blockInstanceId) {
381
+ var _this$fireAnalyticsEv9;
382
+ if (this.viewMode === 'view') {
383
+ return;
384
+ }
385
+ if (!fg('platform_editor_blocks_patch_4')) {
386
+ return;
387
+ }
388
+ // `delete` returns false when the id was not tracked (already emitted, or
389
+ // the block was not created empty this session), so this both dedupes and
390
+ // scopes emission to genuine first-content transitions in one check.
391
+ if (!this.awaitingFirstContent.delete(resourceId)) {
392
+ return;
393
+ }
394
+ (_this$fireAnalyticsEv9 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv9 === void 0 ? void 0 : _this$fireAnalyticsEv9.call(this, addContentSuccessPayload(resourceId, blockInstanceId, getSourceProductFromResourceIdSafe(resourceId)));
352
395
  }
353
396
  registerConfirmationCallback(callback) {
354
397
  this.confirmationCallback = callback;
@@ -382,8 +425,12 @@ export class SourceSyncBlockStoreManager {
382
425
  * @param attrs attributes Ids of the node
383
426
  * @param node the ProseMirror node to cache
384
427
  * @param onCompletion callback invoked when creation completes
428
+ * @param enrichment optional creation-type signals stashed and attached to the
429
+ * `syncedBlockCreate` event when the async create resolves. When
430
+ * `createdEmpty` is true the block is also registered for the
431
+ * first-content-added event.
385
432
  */
386
- createBodiedSyncBlockNode(attrs, node, onCompletion) {
433
+ createBodiedSyncBlockNode(attrs, node, onCompletion, enrichment) {
387
434
  if (this.viewMode === 'view') {
388
435
  return;
389
436
  }
@@ -391,6 +438,14 @@ export class SourceSyncBlockStoreManager {
391
438
  resourceId,
392
439
  localId: blockInstanceId
393
440
  } = attrs;
441
+ // Stash creation-type signals for commitPendingCreation to read.
442
+ if (enrichment) {
443
+ this.creationEnrichment.set(resourceId, enrichment);
444
+ // Only empty-created blocks can produce an empty→content transition.
445
+ if (enrichment.createdEmpty) {
446
+ this.awaitingFirstContent.add(resourceId);
447
+ }
448
+ }
394
449
  try {
395
450
  var _this$createExperienc;
396
451
  if (!this.dataProvider) {
@@ -418,15 +473,15 @@ export class SourceSyncBlockStoreManager {
418
473
  this.commitPendingCreation(true, resourceId);
419
474
  (_this$createExperienc2 = this.createExperience) === null || _this$createExperienc2 === void 0 ? void 0 : _this$createExperienc2.success();
420
475
  } else {
421
- var _this$createExperienc3, _this$fireAnalyticsEv9;
476
+ var _this$createExperienc3, _this$fireAnalyticsEv0;
422
477
  this.commitPendingCreation(false, resourceId);
423
478
  (_this$createExperienc3 = this.createExperience) === null || _this$createExperienc3 === void 0 ? void 0 : _this$createExperienc3.failure({
424
479
  reason: result.error || 'Failed to create bodied sync block'
425
480
  });
426
- (_this$fireAnalyticsEv9 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv9 === void 0 ? void 0 : _this$fireAnalyticsEv9.call(this, createErrorPayload(result.error || 'Failed to create bodied sync block', resourceId, getSourceProductFromResourceIdSafe(resourceId), buildErrorAttribution(fg('platform_editor_blocks_patch_3'), result.error, result.statusCode)));
481
+ (_this$fireAnalyticsEv0 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv0 === void 0 ? void 0 : _this$fireAnalyticsEv0.call(this, createErrorPayload(result.error || 'Failed to create bodied sync block', resourceId, getSourceProductFromResourceIdSafe(resourceId), buildErrorAttribution(fg('platform_editor_blocks_patch_3'), result.error, result.statusCode)));
427
482
  }
428
483
  }).catch(error => {
429
- var _this$createExperienc4, _this$fireAnalyticsEv0;
484
+ var _this$createExperienc4, _this$fireAnalyticsEv1;
430
485
  this.commitPendingCreation(false, resourceId);
431
486
  logException(error, {
432
487
  location: 'editor-synced-block-provider/sourceSyncBlockStoreManager'
@@ -434,20 +489,20 @@ export class SourceSyncBlockStoreManager {
434
489
  (_this$createExperienc4 = this.createExperience) === null || _this$createExperienc4 === void 0 ? void 0 : _this$createExperienc4.failure({
435
490
  reason: error.message
436
491
  });
437
- (_this$fireAnalyticsEv0 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv0 === void 0 ? void 0 : _this$fireAnalyticsEv0.call(this, createErrorPayload(error.message, resourceId, getSourceProductFromResourceIdSafe(resourceId)));
492
+ (_this$fireAnalyticsEv1 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv1 === void 0 ? void 0 : _this$fireAnalyticsEv1.call(this, createErrorPayload(error.message, resourceId, getSourceProductFromResourceIdSafe(resourceId)));
438
493
  }).finally(() => {
439
494
  this.pendingCreationPromises.delete(resourceId);
440
495
  });
441
496
  this.pendingCreationPromises.set(resourceId, creationPromise);
442
497
  } catch (error) {
443
- var _this$fireAnalyticsEv1;
498
+ var _this$fireAnalyticsEv10;
444
499
  if (this.isPendingCreation(resourceId)) {
445
500
  this.commitPendingCreation(false, resourceId);
446
501
  }
447
502
  logException(error, {
448
503
  location: 'editor-synced-block-provider/sourceSyncBlockStoreManager'
449
504
  });
450
- (_this$fireAnalyticsEv1 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv1 === void 0 ? void 0 : _this$fireAnalyticsEv1.call(this, createErrorPayload(error.message, resourceId, getSourceProductFromResourceIdSafe(resourceId)));
505
+ (_this$fireAnalyticsEv10 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv10 === void 0 ? void 0 : _this$fireAnalyticsEv10.call(this, createErrorPayload(error.message, resourceId, getSourceProductFromResourceIdSafe(resourceId)));
451
506
  }
452
507
  }
453
508
  /**
@@ -469,11 +524,11 @@ export class SourceSyncBlockStoreManager {
469
524
  * while the cache entry still exists so `blockInstanceId` is available.
470
525
  */
471
526
  emitDeleteSuccess(resourceId, reason, mechanism) {
472
- var _this$syncBlockCache$2, _this$fireAnalyticsEv11;
527
+ var _this$syncBlockCache$2, _this$fireAnalyticsEv12;
473
528
  const sourceProduct = getSourceProductFromResourceIdSafe(resourceId);
474
529
  if (!fg('platform_editor_blocks_patch_4')) {
475
- var _this$fireAnalyticsEv10;
476
- (_this$fireAnalyticsEv10 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv10 === void 0 ? void 0 : _this$fireAnalyticsEv10.call(this, deleteSuccessPayload(resourceId, sourceProduct));
530
+ var _this$fireAnalyticsEv11;
531
+ (_this$fireAnalyticsEv11 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv11 === void 0 ? void 0 : _this$fireAnalyticsEv11.call(this, deleteSuccessPayload(resourceId, sourceProduct));
477
532
  return;
478
533
  }
479
534
  const now = Date.now();
@@ -488,7 +543,7 @@ export class SourceSyncBlockStoreManager {
488
543
  deletionReason: reason,
489
544
  mechanism
490
545
  };
491
- (_this$fireAnalyticsEv11 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv11 === void 0 ? void 0 : _this$fireAnalyticsEv11.call(this, deleteSuccessPayload(resourceId, sourceProduct, enrichment));
546
+ (_this$fireAnalyticsEv12 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv12 === void 0 ? void 0 : _this$fireAnalyticsEv12.call(this, deleteSuccessPayload(resourceId, sourceProduct, enrichment));
492
547
  }
493
548
  async delete(syncBlockIds, onDelete, onDeleteCompleted, reason, mechanism) {
494
549
  try {
@@ -529,8 +584,8 @@ export class SourceSyncBlockStoreManager {
529
584
  if (result.success) {
530
585
  this.emitDeleteSuccess(result.resourceId, reason, mechanism);
531
586
  } else {
532
- var _this$fireAnalyticsEv12;
533
- (_this$fireAnalyticsEv12 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv12 === void 0 ? void 0 : _this$fireAnalyticsEv12.call(this, deleteErrorPayload(result.error || 'Failed to delete synced block', result.resourceId, getSourceProductFromResourceIdSafe(result.resourceId), buildErrorAttribution(attributionEnabled, result.error, result.statusCode)));
587
+ var _this$fireAnalyticsEv13;
588
+ (_this$fireAnalyticsEv13 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv13 === void 0 ? void 0 : _this$fireAnalyticsEv13.call(this, deleteErrorPayload(result.error || 'Failed to delete synced block', result.resourceId, getSourceProductFromResourceIdSafe(result.resourceId), buildErrorAttribution(attributionEnabled, result.error, result.statusCode)));
534
589
  }
535
590
  });
536
591
  }
@@ -541,9 +596,9 @@ export class SourceSyncBlockStoreManager {
541
596
  // so the attribution `reason` falls back to `unknown` when the gate is on.
542
597
  const attribution = buildErrorAttribution(fg('platform_editor_blocks_patch_3'));
543
598
  syncBlockIds.forEach(Ids => {
544
- var _this$fireAnalyticsEv13;
599
+ var _this$fireAnalyticsEv14;
545
600
  this.setPendingDeletion(Ids, false);
546
- (_this$fireAnalyticsEv13 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv13 === void 0 ? void 0 : _this$fireAnalyticsEv13.call(this, deleteErrorPayload(error.message, Ids.resourceId, getSourceProductFromResourceIdSafe(Ids.resourceId), attribution));
601
+ (_this$fireAnalyticsEv14 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv14 === void 0 ? void 0 : _this$fireAnalyticsEv14.call(this, deleteErrorPayload(error.message, Ids.resourceId, getSourceProductFromResourceIdSafe(Ids.resourceId), attribution));
547
602
  });
548
603
  logException(error, {
549
604
  location: 'editor-synced-block-provider/sourceSyncBlockStoreManager'
@@ -703,11 +758,11 @@ export class SourceSyncBlockStoreManager {
703
758
  return sourceInfo;
704
759
  });
705
760
  } catch (error) {
706
- var _this$fireAnalyticsEv14;
761
+ var _this$fireAnalyticsEv15;
707
762
  logException(error, {
708
763
  location: 'editor-synced-block-provider/sourceSyncBlockStoreManager'
709
764
  });
710
- (_this$fireAnalyticsEv14 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv14 === void 0 ? void 0 : _this$fireAnalyticsEv14.call(this, getSourceInfoErrorPayload(error.message));
765
+ (_this$fireAnalyticsEv15 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv15 === void 0 ? void 0 : _this$fireAnalyticsEv15.call(this, getSourceInfoErrorPayload(error.message));
711
766
  return Promise.resolve(undefined);
712
767
  }
713
768
  }
@@ -718,11 +773,11 @@ export class SourceSyncBlockStoreManager {
718
773
  }
719
774
  return this.dataProvider.fetchReferences(resourceId, true);
720
775
  } catch (error) {
721
- var _this$fireAnalyticsEv15;
776
+ var _this$fireAnalyticsEv16;
722
777
  logException(error, {
723
778
  location: 'editor-synced-block-provider/sourceSyncBlockStoreManager'
724
779
  });
725
- (_this$fireAnalyticsEv15 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv15 === void 0 ? void 0 : _this$fireAnalyticsEv15.call(this, fetchReferencesErrorPayload(error.message, resourceId, getSourceProductFromResourceIdSafe(resourceId)));
780
+ (_this$fireAnalyticsEv16 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv16 === void 0 ? void 0 : _this$fireAnalyticsEv16.call(this, fetchReferencesErrorPayload(error.message, resourceId, getSourceProductFromResourceIdSafe(resourceId)));
726
781
  return Promise.resolve({
727
782
  error: SyncBlockError.Errored
728
783
  });
@@ -737,6 +792,8 @@ export class SourceSyncBlockStoreManager {
737
792
  this.postCreationFlushCallback = undefined;
738
793
  this.pendingCreationPromises.clear();
739
794
  this.creationsTimedOutDuringFlush.clear();
795
+ this.creationEnrichment.clear();
796
+ this.awaitingFirstContent.clear();
740
797
  this.recentDeleteEmissions.clear();
741
798
  this.dataProvider = undefined;
742
799
  (_this$saveExperience4 = this.saveExperience) === null || _this$saveExperience4 === void 0 ? void 0 : _this$saveExperience4.abort({
@@ -339,15 +339,50 @@ export const createSuccessPayload = (resourceId, sourceProduct) => {
339
339
  };
340
340
 
341
341
  /**
342
- * Operational `syncedBlockCreate` success event (previously only error rows
343
- * existed, so dashboards had no create denominator). Fired behind
344
- * `platform_editor_blocks_patch_4` with the `blockInstanceId` join key.
342
+ * Optional enrichment for the `syncedBlockCreate` success event. All fields
343
+ * optional so gate-off/legacy payloads are unchanged. `inputMethod`: creating
344
+ * surface (enum, PII-safe). `createdEmpty`: true from an empty selection, false
345
+ * when content was converted.
345
346
  */
346
- export const createSuccessOperationalPayload = (resourceId, blockInstanceId, sourceProduct) => ({
347
+
348
+ /**
349
+ * Operational `syncedBlockCreate` success event, behind
350
+ * `platform_editor_blocks_patch_4`, with the `blockInstanceId` join key and,
351
+ * when available, the `inputMethod` + `createdEmpty` creation-type signals.
352
+ */
353
+ export const createSuccessOperationalPayload = (resourceId, blockInstanceId, sourceProduct, enrichment) => ({
347
354
  action: ACTION.INSERTED,
348
355
  actionSubject: ACTION_SUBJECT.SYNCED_BLOCK,
349
356
  actionSubjectId: ACTION_SUBJECT_ID.SYNCED_BLOCK_CREATE,
350
357
  eventType: EVENT_TYPE.OPERATIONAL,
358
+ attributes: {
359
+ resourceId,
360
+ ...(blockInstanceId && {
361
+ blockInstanceId
362
+ }),
363
+ ...(sourceProduct && {
364
+ sourceProduct
365
+ }),
366
+ ...((enrichment === null || enrichment === void 0 ? void 0 : enrichment.inputMethod) && {
367
+ inputMethod: enrichment.inputMethod
368
+ }),
369
+ ...((enrichment === null || enrichment === void 0 ? void 0 : enrichment.createdEmpty) !== undefined && {
370
+ createdEmpty: enrichment.createdEmpty
371
+ })
372
+ }
373
+ });
374
+
375
+ /**
376
+ * Operational first-content-added event, behind
377
+ * `platform_editor_blocks_patch_4`. Fired once when a block created empty first
378
+ * gains user content. Join keys only (`resourceId` + `blockInstanceId`), no user
379
+ * content (PII-safe).
380
+ */
381
+ export const addContentSuccessPayload = (resourceId, blockInstanceId, sourceProduct) => ({
382
+ action: ACTION.ADDED,
383
+ actionSubject: ACTION_SUBJECT.SYNCED_BLOCK,
384
+ actionSubjectId: ACTION_SUBJECT_ID.SYNCED_BLOCK_ADD_CONTENT,
385
+ eventType: EVENT_TYPE.OPERATIONAL,
351
386
  attributes: {
352
387
  resourceId,
353
388
  ...(blockInstanceId && {
@@ -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, createSuccessOperationalPayload, deleteSuccessPayload, fetchReferencesErrorPayload, buildErrorAttribution } from '../utils/errorHandling';
15
+ import { updateErrorPayload, createErrorPayload, deleteErrorPayload, updateCacheErrorPayload, getSourceInfoErrorPayload, updateSuccessPayload, createSuccessPayload, createSuccessOperationalPayload, addContentSuccessPayload, 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. */
@@ -52,6 +52,20 @@ export var SourceSyncBlockStoreManager = /*#__PURE__*/function () {
52
52
  * multiple blocks are created concurrently. See EDITOR-7112.
53
53
  */
54
54
  _defineProperty(this, "creationsTimedOutDuringFlush", new Set());
55
+ /**
56
+ * Creation-type signals captured at `createBodiedSyncBlockNode` time and read
57
+ * back in `commitPendingCreation` to enrich the `syncedBlockCreate` event.
58
+ * Kept out of the flushed cache record so it never leaks into Block Service /
59
+ * ADF; removed once consumed.
60
+ */
61
+ _defineProperty(this, "creationEnrichment", new Map());
62
+ /**
63
+ * Resource IDs of blocks created empty this session that have not yet fired
64
+ * the first-content-added event. Added on empty creation, removed when the
65
+ * block first gains content (the event fires once). Blocks created with
66
+ * content or not created this session are never tracked.
67
+ */
68
+ _defineProperty(this, "awaitingFirstContent", new Set());
55
69
  /**
56
70
  * resourceId -> timestamp (ms) of the last `syncedBlockDelete` emission, used
57
71
  * to suppress duplicates within {@link DELETE_DEDUPE_WINDOW_MS}. Only consulted
@@ -404,17 +418,48 @@ export var SourceSyncBlockStoreManager = /*#__PURE__*/function () {
404
418
  var _this$fireAnalyticsEv4;
405
419
  var sourceProduct = getSourceProductFromResourceIdSafe(resourceId);
406
420
  (_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.
421
+ // Operational create-success event with the join key + creation-type
422
+ // signals (inputMethod / createdEmpty) captured at the command layer.
408
423
  if (fg('platform_editor_blocks_patch_4')) {
409
424
  var _this$fireAnalyticsEv5, _this$syncBlockCache$;
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));
425
+ (_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, this.creationEnrichment.get(resourceId)));
411
426
  }
412
427
  } else {
413
428
  var _this$fireAnalyticsEv6;
414
429
  // Delete the node from cache if fail to create so it's not flushed to BE
415
430
  this.syncBlockCache.delete(resourceId || '');
431
+ // Creation failed, so there is no block to add content to — drop the
432
+ // first-content tracking entry so a later unrelated edit cannot fire it.
433
+ this.awaitingFirstContent.delete(resourceId || '');
416
434
  (_this$fireAnalyticsEv6 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv6 === void 0 || _this$fireAnalyticsEv6.call(this, createErrorPayload('Fail to create bodied sync block', resourceId, getSourceProductFromResourceIdSafe(resourceId)));
417
435
  }
436
+ // Enrichment is single-use — drop it once the create has resolved either way.
437
+ this.creationEnrichment.delete(resourceId || '');
438
+ }
439
+
440
+ /**
441
+ * Fire the first-content-added event once when a block created empty this
442
+ * session first gains content. The caller detects the in-block edit; this owns
443
+ * dedupe + emission (fires at most once per block, only for empty→content).
444
+ * Gated behind `platform_editor_blocks_patch_4`.
445
+ */
446
+ }, {
447
+ key: "maybeEmitFirstContentAdded",
448
+ value: function maybeEmitFirstContentAdded(resourceId, blockInstanceId) {
449
+ var _this$fireAnalyticsEv7;
450
+ if (this.viewMode === 'view') {
451
+ return;
452
+ }
453
+ if (!fg('platform_editor_blocks_patch_4')) {
454
+ return;
455
+ }
456
+ // `delete` returns false when the id was not tracked (already emitted, or
457
+ // the block was not created empty this session), so this both dedupes and
458
+ // scopes emission to genuine first-content transitions in one check.
459
+ if (!this.awaitingFirstContent.delete(resourceId)) {
460
+ return;
461
+ }
462
+ (_this$fireAnalyticsEv7 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv7 === void 0 || _this$fireAnalyticsEv7.call(this, addContentSuccessPayload(resourceId, blockInstanceId, getSourceProductFromResourceIdSafe(resourceId)));
418
463
  }
419
464
  }, {
420
465
  key: "registerConfirmationCallback",
@@ -454,16 +499,28 @@ export var SourceSyncBlockStoreManager = /*#__PURE__*/function () {
454
499
  * @param attrs attributes Ids of the node
455
500
  * @param node the ProseMirror node to cache
456
501
  * @param onCompletion callback invoked when creation completes
502
+ * @param enrichment optional creation-type signals stashed and attached to the
503
+ * `syncedBlockCreate` event when the async create resolves. When
504
+ * `createdEmpty` is true the block is also registered for the
505
+ * first-content-added event.
457
506
  */
458
507
  }, {
459
508
  key: "createBodiedSyncBlockNode",
460
- value: function createBodiedSyncBlockNode(attrs, node, onCompletion) {
509
+ value: function createBodiedSyncBlockNode(attrs, node, onCompletion, enrichment) {
461
510
  var _this4 = this;
462
511
  if (this.viewMode === 'view') {
463
512
  return;
464
513
  }
465
514
  var resourceId = attrs.resourceId,
466
515
  blockInstanceId = attrs.localId;
516
+ // Stash creation-type signals for commitPendingCreation to read.
517
+ if (enrichment) {
518
+ this.creationEnrichment.set(resourceId, enrichment);
519
+ // Only empty-created blocks can produce an empty→content transition.
520
+ if (enrichment.createdEmpty) {
521
+ this.awaitingFirstContent.add(resourceId);
522
+ }
523
+ }
467
524
  try {
468
525
  var _this$createExperienc;
469
526
  if (!this.dataProvider) {
@@ -513,14 +570,14 @@ export var SourceSyncBlockStoreManager = /*#__PURE__*/function () {
513
570
  });
514
571
  this.pendingCreationPromises.set(resourceId, creationPromise);
515
572
  } catch (error) {
516
- var _this$fireAnalyticsEv7;
573
+ var _this$fireAnalyticsEv8;
517
574
  if (this.isPendingCreation(resourceId)) {
518
575
  this.commitPendingCreation(false, resourceId);
519
576
  }
520
577
  logException(error, {
521
578
  location: 'editor-synced-block-provider/sourceSyncBlockStoreManager'
522
579
  });
523
- (_this$fireAnalyticsEv7 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv7 === void 0 || _this$fireAnalyticsEv7.call(this, createErrorPayload(error.message, resourceId, getSourceProductFromResourceIdSafe(resourceId)));
580
+ (_this$fireAnalyticsEv8 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv8 === void 0 || _this$fireAnalyticsEv8.call(this, createErrorPayload(error.message, resourceId, getSourceProductFromResourceIdSafe(resourceId)));
524
581
  }
525
582
  }
526
583
  }, {
@@ -558,11 +615,11 @@ export var SourceSyncBlockStoreManager = /*#__PURE__*/function () {
558
615
  }, {
559
616
  key: "emitDeleteSuccess",
560
617
  value: function emitDeleteSuccess(resourceId, reason, mechanism) {
561
- var _this$syncBlockCache$2, _this$fireAnalyticsEv9;
618
+ var _this$syncBlockCache$2, _this$fireAnalyticsEv0;
562
619
  var sourceProduct = getSourceProductFromResourceIdSafe(resourceId);
563
620
  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));
621
+ var _this$fireAnalyticsEv9;
622
+ (_this$fireAnalyticsEv9 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv9 === void 0 || _this$fireAnalyticsEv9.call(this, deleteSuccessPayload(resourceId, sourceProduct));
566
623
  return;
567
624
  }
568
625
  var now = Date.now();
@@ -577,7 +634,7 @@ export var SourceSyncBlockStoreManager = /*#__PURE__*/function () {
577
634
  deletionReason: reason,
578
635
  mechanism: mechanism
579
636
  };
580
- (_this$fireAnalyticsEv9 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv9 === void 0 || _this$fireAnalyticsEv9.call(this, deleteSuccessPayload(resourceId, sourceProduct, enrichment));
637
+ (_this$fireAnalyticsEv0 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv0 === void 0 || _this$fireAnalyticsEv0.call(this, deleteSuccessPayload(resourceId, sourceProduct, enrichment));
581
638
  }
582
639
  }, {
583
640
  key: "delete",
@@ -926,11 +983,11 @@ export var SourceSyncBlockStoreManager = /*#__PURE__*/function () {
926
983
  return sourceInfo;
927
984
  });
928
985
  } catch (error) {
929
- var _this$fireAnalyticsEv0;
986
+ var _this$fireAnalyticsEv1;
930
987
  logException(error, {
931
988
  location: 'editor-synced-block-provider/sourceSyncBlockStoreManager'
932
989
  });
933
- (_this$fireAnalyticsEv0 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv0 === void 0 || _this$fireAnalyticsEv0.call(this, getSourceInfoErrorPayload(error.message));
990
+ (_this$fireAnalyticsEv1 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv1 === void 0 || _this$fireAnalyticsEv1.call(this, getSourceInfoErrorPayload(error.message));
934
991
  return Promise.resolve(undefined);
935
992
  }
936
993
  }
@@ -943,11 +1000,11 @@ export var SourceSyncBlockStoreManager = /*#__PURE__*/function () {
943
1000
  }
944
1001
  return this.dataProvider.fetchReferences(resourceId, true);
945
1002
  } catch (error) {
946
- var _this$fireAnalyticsEv1;
1003
+ var _this$fireAnalyticsEv10;
947
1004
  logException(error, {
948
1005
  location: 'editor-synced-block-provider/sourceSyncBlockStoreManager'
949
1006
  });
950
- (_this$fireAnalyticsEv1 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv1 === void 0 || _this$fireAnalyticsEv1.call(this, fetchReferencesErrorPayload(error.message, resourceId, getSourceProductFromResourceIdSafe(resourceId)));
1007
+ (_this$fireAnalyticsEv10 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv10 === void 0 || _this$fireAnalyticsEv10.call(this, fetchReferencesErrorPayload(error.message, resourceId, getSourceProductFromResourceIdSafe(resourceId)));
951
1008
  return Promise.resolve({
952
1009
  error: SyncBlockError.Errored
953
1010
  });
@@ -964,6 +1021,8 @@ export var SourceSyncBlockStoreManager = /*#__PURE__*/function () {
964
1021
  this.postCreationFlushCallback = undefined;
965
1022
  this.pendingCreationPromises.clear();
966
1023
  this.creationsTimedOutDuringFlush.clear();
1024
+ this.creationEnrichment.clear();
1025
+ this.awaitingFirstContent.clear();
967
1026
  this.recentDeleteEmissions.clear();
968
1027
  this.dataProvider = undefined;
969
1028
  (_this$saveExperience4 = this.saveExperience) === null || _this$saveExperience4 === void 0 || _this$saveExperience4.abort({
@@ -348,16 +348,49 @@ export var createSuccessPayload = function createSuccessPayload(resourceId, sour
348
348
  };
349
349
 
350
350
  /**
351
- * Operational `syncedBlockCreate` success event (previously only error rows
352
- * existed, so dashboards had no create denominator). Fired behind
353
- * `platform_editor_blocks_patch_4` with the `blockInstanceId` join key.
351
+ * Optional enrichment for the `syncedBlockCreate` success event. All fields
352
+ * optional so gate-off/legacy payloads are unchanged. `inputMethod`: creating
353
+ * surface (enum, PII-safe). `createdEmpty`: true from an empty selection, false
354
+ * when content was converted.
354
355
  */
355
- export var createSuccessOperationalPayload = function createSuccessOperationalPayload(resourceId, blockInstanceId, sourceProduct) {
356
+
357
+ /**
358
+ * Operational `syncedBlockCreate` success event, behind
359
+ * `platform_editor_blocks_patch_4`, with the `blockInstanceId` join key and,
360
+ * when available, the `inputMethod` + `createdEmpty` creation-type signals.
361
+ */
362
+ export var createSuccessOperationalPayload = function createSuccessOperationalPayload(resourceId, blockInstanceId, sourceProduct, enrichment) {
356
363
  return {
357
364
  action: ACTION.INSERTED,
358
365
  actionSubject: ACTION_SUBJECT.SYNCED_BLOCK,
359
366
  actionSubjectId: ACTION_SUBJECT_ID.SYNCED_BLOCK_CREATE,
360
367
  eventType: EVENT_TYPE.OPERATIONAL,
368
+ attributes: _objectSpread(_objectSpread(_objectSpread(_objectSpread({
369
+ resourceId: resourceId
370
+ }, blockInstanceId && {
371
+ blockInstanceId: blockInstanceId
372
+ }), sourceProduct && {
373
+ sourceProduct: sourceProduct
374
+ }), (enrichment === null || enrichment === void 0 ? void 0 : enrichment.inputMethod) && {
375
+ inputMethod: enrichment.inputMethod
376
+ }), (enrichment === null || enrichment === void 0 ? void 0 : enrichment.createdEmpty) !== undefined && {
377
+ createdEmpty: enrichment.createdEmpty
378
+ })
379
+ };
380
+ };
381
+
382
+ /**
383
+ * Operational first-content-added event, behind
384
+ * `platform_editor_blocks_patch_4`. Fired once when a block created empty first
385
+ * gains user content. Join keys only (`resourceId` + `blockInstanceId`), no user
386
+ * content (PII-safe).
387
+ */
388
+ export var addContentSuccessPayload = function addContentSuccessPayload(resourceId, blockInstanceId, sourceProduct) {
389
+ return {
390
+ action: ACTION.ADDED,
391
+ actionSubject: ACTION_SUBJECT.SYNCED_BLOCK,
392
+ actionSubjectId: ACTION_SUBJECT_ID.SYNCED_BLOCK_ADD_CONTENT,
393
+ eventType: EVENT_TYPE.OPERATIONAL,
361
394
  attributes: _objectSpread(_objectSpread({
362
395
  resourceId: resourceId
363
396
  }, blockInstanceId && {
@@ -1 +1,2 @@
1
1
  export { buildFetchErrorAttribution, fetchErrorPayload, getPiiSafeOriginalError, } from '../utils/errorHandling';
2
+ export type { CreateSuccessEnrichment } from '../utils/errorHandling';
@@ -3,6 +3,7 @@ import type { ViewMode } from '@atlaskit/editor-plugin-editor-viewmode';
3
3
  import type { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
4
4
  import type { ResourceId, SyncBlockAttrs, BlockInstanceId, DeletionReason, DeletionMechanism, ReferenceSyncBlockData } from '../common/types';
5
5
  import type { SyncBlockDataProviderInterface, SyncBlockSourceInfo } from '../providers/types';
6
+ import type { CreateSuccessEnrichment } from '../utils/errorHandling';
6
7
  export type ConfirmationCallback = (syncBlockIds: SyncBlockAttrs[], deleteReason: DeletionReason | undefined) => Promise<boolean>;
7
8
  type OnDelete = () => void;
8
9
  type OnCompletion = (success: boolean) => void;
@@ -35,6 +36,20 @@ export declare class SourceSyncBlockStoreManager {
35
36
  * multiple blocks are created concurrently. See EDITOR-7112.
36
37
  */
37
38
  private creationsTimedOutDuringFlush;
39
+ /**
40
+ * Creation-type signals captured at `createBodiedSyncBlockNode` time and read
41
+ * back in `commitPendingCreation` to enrich the `syncedBlockCreate` event.
42
+ * Kept out of the flushed cache record so it never leaks into Block Service /
43
+ * ADF; removed once consumed.
44
+ */
45
+ private creationEnrichment;
46
+ /**
47
+ * Resource IDs of blocks created empty this session that have not yet fired
48
+ * the first-content-added event. Added on empty creation, removed when the
49
+ * block first gains content (the event fires once). Blocks created with
50
+ * content or not created this session are never tracked.
51
+ */
52
+ private awaitingFirstContent;
38
53
  private createExperience;
39
54
  private saveExperience;
40
55
  private deleteExperience;
@@ -86,6 +101,13 @@ export declare class SourceSyncBlockStoreManager {
86
101
  * @param success
87
102
  */
88
103
  commitPendingCreation(success: boolean, resourceId: ResourceId): void;
104
+ /**
105
+ * Fire the first-content-added event once when a block created empty this
106
+ * session first gains content. The caller detects the in-block edit; this owns
107
+ * dedupe + emission (fires at most once per block, only for empty→content).
108
+ * Gated behind `platform_editor_blocks_patch_4`.
109
+ */
110
+ maybeEmitFirstContentAdded(resourceId: ResourceId, blockInstanceId?: BlockInstanceId): void;
89
111
  registerConfirmationCallback(callback: ConfirmationCallback): () => void;
90
112
  requireConfirmationBeforeDelete(): boolean;
91
113
  /**
@@ -97,8 +119,12 @@ export declare class SourceSyncBlockStoreManager {
97
119
  * @param attrs attributes Ids of the node
98
120
  * @param node the ProseMirror node to cache
99
121
  * @param onCompletion callback invoked when creation completes
122
+ * @param enrichment optional creation-type signals stashed and attached to the
123
+ * `syncedBlockCreate` event when the async create resolves. When
124
+ * `createdEmpty` is true the block is also registered for the
125
+ * first-content-added event.
100
126
  */
101
- createBodiedSyncBlockNode(attrs: SyncBlockAttrs, node: PMNode, onCompletion: OnCompletion): void;
127
+ createBodiedSyncBlockNode(attrs: SyncBlockAttrs, node: PMNode, onCompletion: OnCompletion, enrichment?: CreateSuccessEnrichment): void;
102
128
  private setPendingDeletion;
103
129
  /**
104
130
  * Drop dedupe entries older than {@link DELETE_DEDUPE_WINDOW_MS} so
@@ -1,5 +1,5 @@
1
1
  import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID } from '@atlaskit/editor-common/analytics';
2
- import type { RendererSyncBlockEventPayload, OperationalAEP, SyncBlockEventPayload } from '@atlaskit/editor-common/analytics';
2
+ import type { INPUT_METHOD, RendererSyncBlockEventPayload, OperationalAEP, SyncBlockEventPayload } from '@atlaskit/editor-common/analytics';
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;
@@ -175,11 +175,28 @@ export declare const fetchReferencesErrorPayload: (error: string, resourceId?: s
175
175
  export declare const fetchSuccessPayload: (resourceId: string, blockInstanceId?: string, sourceProduct?: string) => RendererSyncBlockEventPayload;
176
176
  export declare const createSuccessPayload: (resourceId: string, sourceProduct?: string) => SyncBlockEventPayload;
177
177
  /**
178
- * Operational `syncedBlockCreate` success event (previously only error rows
179
- * existed, so dashboards had no create denominator). Fired behind
180
- * `platform_editor_blocks_patch_4` with the `blockInstanceId` join key.
178
+ * Optional enrichment for the `syncedBlockCreate` success event. All fields
179
+ * optional so gate-off/legacy payloads are unchanged. `inputMethod`: creating
180
+ * surface (enum, PII-safe). `createdEmpty`: true from an empty selection, false
181
+ * when content was converted.
181
182
  */
182
- export declare const createSuccessOperationalPayload: (resourceId: string, blockInstanceId?: string, sourceProduct?: string) => SyncBlockEventPayload;
183
+ export type CreateSuccessEnrichment = {
184
+ createdEmpty?: boolean;
185
+ inputMethod?: INPUT_METHOD;
186
+ };
187
+ /**
188
+ * Operational `syncedBlockCreate` success event, behind
189
+ * `platform_editor_blocks_patch_4`, with the `blockInstanceId` join key and,
190
+ * when available, the `inputMethod` + `createdEmpty` creation-type signals.
191
+ */
192
+ export declare const createSuccessOperationalPayload: (resourceId: string, blockInstanceId?: string, sourceProduct?: string, enrichment?: CreateSuccessEnrichment) => SyncBlockEventPayload;
193
+ /**
194
+ * Operational first-content-added event, behind
195
+ * `platform_editor_blocks_patch_4`. Fired once when a block created empty first
196
+ * gains user content. Join keys only (`resourceId` + `blockInstanceId`), no user
197
+ * content (PII-safe).
198
+ */
199
+ export declare const addContentSuccessPayload: (resourceId: string, blockInstanceId?: string, sourceProduct?: string) => SyncBlockEventPayload;
183
200
  export declare const updateSuccessPayload: (resourceId: string, hasReference?: boolean, sourceProduct?: string) => SyncBlockEventPayload;
184
201
  /**
185
202
  * Optional enrichment for the `syncedBlockDelete` success event behind
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": "^124.0.0",
24
+ "@atlaskit/tmp-editor-statsig": "^124.1.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.24.0",
33
+ "@atlaskit/editor-common": "^116.25.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.7",
77
+ "version": "8.6.0",
78
78
  "description": "Synced Block Provider for @atlaskit/editor-plugin-synced-block",
79
79
  "author": "Atlassian Pty Ltd",
80
80
  "license": "Apache-2.0",