@atlaskit/editor-synced-block-provider 3.30.5 → 3.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/cjs/clients/block-service/blockService.js +121 -17
  3. package/dist/cjs/clients/block-service/blockSubscription.js +48 -3
  4. package/dist/cjs/clients/confluence/fetchMediaToken.js +7 -5
  5. package/dist/cjs/hooks/useFetchSyncBlockTitle.js +40 -2
  6. package/dist/cjs/hooks/useHandleContentChanges.js +3 -0
  7. package/dist/cjs/index.js +12 -33
  8. package/dist/cjs/providers/block-service/blockServiceAPI.js +509 -185
  9. package/dist/cjs/providers/syncBlockProvider.js +68 -6
  10. package/dist/cjs/store-manager/referenceSyncBlockStoreManager.js +3 -0
  11. package/dist/cjs/store-manager/syncBlockStoreManager.js +20 -5
  12. package/dist/cjs/utils/experienceTracking.js +10 -10
  13. package/dist/cjs/utils/resourceId.js +2 -2
  14. package/dist/cjs/utils/retry.js +33 -7
  15. package/dist/cjs/utils/utils.js +1 -1
  16. package/dist/cjs/utils/validValue.js +2 -1
  17. package/dist/es2019/clients/block-service/blockService.js +108 -13
  18. package/dist/es2019/clients/block-service/blockSubscription.js +62 -2
  19. package/dist/es2019/clients/confluence/fetchMediaToken.js +5 -3
  20. package/dist/es2019/hooks/useFetchSyncBlockTitle.js +36 -3
  21. package/dist/es2019/hooks/useHandleContentChanges.js +3 -0
  22. package/dist/es2019/index.js +2 -6
  23. package/dist/es2019/providers/block-service/blockServiceAPI.js +172 -23
  24. package/dist/es2019/providers/syncBlockProvider.js +56 -3
  25. package/dist/es2019/store-manager/referenceSyncBlockStoreManager.js +3 -0
  26. package/dist/es2019/store-manager/syncBlockStoreManager.js +19 -6
  27. package/dist/es2019/utils/experienceTracking.js +10 -10
  28. package/dist/es2019/utils/resourceId.js +2 -2
  29. package/dist/es2019/utils/retry.js +26 -6
  30. package/dist/es2019/utils/utils.js +1 -1
  31. package/dist/es2019/utils/validValue.js +2 -1
  32. package/dist/esm/clients/block-service/blockService.js +120 -16
  33. package/dist/esm/clients/block-service/blockSubscription.js +47 -2
  34. package/dist/esm/clients/confluence/fetchMediaToken.js +7 -5
  35. package/dist/esm/hooks/useFetchSyncBlockTitle.js +41 -3
  36. package/dist/esm/hooks/useHandleContentChanges.js +3 -0
  37. package/dist/esm/index.js +2 -6
  38. package/dist/esm/providers/block-service/blockServiceAPI.js +513 -189
  39. package/dist/esm/providers/syncBlockProvider.js +69 -7
  40. package/dist/esm/store-manager/referenceSyncBlockStoreManager.js +3 -0
  41. package/dist/esm/store-manager/syncBlockStoreManager.js +21 -6
  42. package/dist/esm/utils/experienceTracking.js +10 -10
  43. package/dist/esm/utils/resourceId.js +2 -2
  44. package/dist/esm/utils/retry.js +33 -7
  45. package/dist/esm/utils/utils.js +1 -1
  46. package/dist/esm/utils/validValue.js +2 -1
  47. package/dist/types/clients/block-service/blockService.d.ts +36 -0
  48. package/dist/types/clients/block-service/blockSubscription.d.ts +26 -1
  49. package/dist/types/index.d.ts +2 -6
  50. package/dist/types/providers/block-service/blockServiceAPI.d.ts +15 -7
  51. package/dist/types/providers/syncBlockProvider.d.ts +1 -1
  52. package/dist/types/providers/types.d.ts +6 -0
  53. package/dist/types/store-manager/syncBlockStoreManager.d.ts +1 -1
  54. package/dist/types/utils/experienceTracking.d.ts +10 -10
  55. package/dist/types-ts4.5/clients/block-service/blockService.d.ts +36 -0
  56. package/dist/types-ts4.5/clients/block-service/blockSubscription.d.ts +26 -1
  57. package/dist/types-ts4.5/index.d.ts +2 -6
  58. package/dist/types-ts4.5/providers/block-service/blockServiceAPI.d.ts +15 -7
  59. package/dist/types-ts4.5/providers/syncBlockProvider.d.ts +1 -1
  60. package/dist/types-ts4.5/providers/types.d.ts +6 -0
  61. package/dist/types-ts4.5/store-manager/syncBlockStoreManager.d.ts +1 -1
  62. package/dist/types-ts4.5/utils/experienceTracking.d.ts +10 -10
  63. package/package.json +5 -2
  64. package/dist/cjs/clients/block-service/sharedSubscriptionUtils.js +0 -82
  65. package/dist/cjs/utils/__generated__/relaySubscriptionUtilsSubscription.graphql.js +0 -94
  66. package/dist/cjs/utils/relayResponseConverter.js +0 -76
  67. package/dist/cjs/utils/relaySubscriptionUtils.js +0 -130
  68. package/dist/es2019/clients/block-service/sharedSubscriptionUtils.js +0 -91
  69. package/dist/es2019/utils/__generated__/relaySubscriptionUtilsSubscription.graphql.js +0 -88
  70. package/dist/es2019/utils/relayResponseConverter.js +0 -69
  71. package/dist/es2019/utils/relaySubscriptionUtils.js +0 -125
  72. package/dist/esm/clients/block-service/sharedSubscriptionUtils.js +0 -76
  73. package/dist/esm/utils/__generated__/relaySubscriptionUtilsSubscription.graphql.js +0 -88
  74. package/dist/esm/utils/relayResponseConverter.js +0 -69
  75. package/dist/esm/utils/relaySubscriptionUtils.js +0 -123
  76. package/dist/types/clients/block-service/sharedSubscriptionUtils.d.ts +0 -61
  77. package/dist/types/utils/__generated__/relaySubscriptionUtilsSubscription.graphql.d.ts +0 -31
  78. package/dist/types/utils/relayResponseConverter.d.ts +0 -47
  79. package/dist/types/utils/relaySubscriptionUtils.d.ts +0 -61
  80. package/dist/types-ts4.5/clients/block-service/sharedSubscriptionUtils.d.ts +0 -61
  81. package/dist/types-ts4.5/utils/__generated__/relaySubscriptionUtilsSubscription.graphql.d.ts +0 -31
  82. package/dist/types-ts4.5/utils/relayResponseConverter.d.ts +0 -47
  83. package/dist/types-ts4.5/utils/relaySubscriptionUtils.d.ts +0 -61
@@ -1,5 +1,7 @@
1
- import { useEffect, useState } from 'react';
2
- export const useFetchSyncBlockTitle = (manager, syncBlockNode) => {
1
+ import { useEffect, useRef, useState } from 'react';
2
+ import { fg } from '@atlaskit/platform-feature-flags';
3
+ import { conditionalHooksFactory } from '@atlaskit/platform-feature-flags-react';
4
+ const useFetchSyncBlockTitleBase = (manager, syncBlockNode) => {
3
5
  // Initialize state from cache to prevent flickering during re-renders
4
6
  const [sourceTitle, setSourceTitle] = useState(() => {
5
7
  var _cachedData$data;
@@ -24,4 +26,35 @@ export const useFetchSyncBlockTitle = (manager, syncBlockNode) => {
24
26
  };
25
27
  }, [manager, syncBlockNode]);
26
28
  return sourceTitle;
27
- };
29
+ };
30
+ const useFetchSyncBlockTitlePatched = (manager, syncBlockNode) => {
31
+ var _syncBlockNode$attrs, _syncBlockNode$attrs2;
32
+ const nodeRef = useRef(syncBlockNode);
33
+ nodeRef.current = syncBlockNode;
34
+ const nodeTypeName = syncBlockNode.type.name;
35
+ const resourceId = (_syncBlockNode$attrs = syncBlockNode.attrs) === null || _syncBlockNode$attrs === void 0 ? void 0 : _syncBlockNode$attrs.resourceId;
36
+ const localId = (_syncBlockNode$attrs2 = syncBlockNode.attrs) === null || _syncBlockNode$attrs2 === void 0 ? void 0 : _syncBlockNode$attrs2.localId;
37
+
38
+ // Initialize state from cache to prevent flickering during re-renders
39
+ const [sourceTitle, setSourceTitle] = useState(() => {
40
+ var _cachedData$data2;
41
+ if (nodeTypeName !== 'syncBlock') {
42
+ return undefined;
43
+ }
44
+ if (!resourceId) {
45
+ return undefined;
46
+ }
47
+ const cachedData = manager.referenceManager.getFromCache(resourceId);
48
+ return cachedData === null || cachedData === void 0 ? void 0 : (_cachedData$data2 = cachedData.data) === null || _cachedData$data2 === void 0 ? void 0 : _cachedData$data2.sourceTitle;
49
+ });
50
+ useEffect(() => {
51
+ const unsubscribe = manager.referenceManager.subscribeToSourceTitle(nodeRef.current, title => {
52
+ setSourceTitle(title);
53
+ });
54
+ return () => {
55
+ unsubscribe();
56
+ };
57
+ }, [manager, nodeTypeName, resourceId, localId]);
58
+ return sourceTitle;
59
+ };
60
+ export const useFetchSyncBlockTitle = conditionalHooksFactory(() => fg('platform_synced_block_patch_4'), useFetchSyncBlockTitlePatched, useFetchSyncBlockTitleBase);
@@ -1,5 +1,8 @@
1
1
  import { useEffect } from 'react';
2
2
  export const useHandleContentChanges = (manager, syncBlockNode) => {
3
+ // syncBlockNode is intentionally in deps — its reference changes when the
4
+ // node content is modified by a ProseMirror transaction, which is exactly
5
+ // when the source manager cache needs to be updated.
3
6
  useEffect(() => {
4
7
  manager.sourceManager.updateSyncBlockData(syncBlockNode);
5
8
  }, [manager, syncBlockNode]);
@@ -10,13 +10,13 @@ export { useHandleContentChanges } from './hooks/useHandleContentChanges';
10
10
 
11
11
  // clients
12
12
  export { generateBlockAri, generateBlockAriFromReference, getLocalIdFromBlockResourceId } from './clients/block-service/ari';
13
- export { BlockError } from './clients/block-service/blockService';
13
+ export { BlockError, updateSyncedBlocks } from './clients/block-service/blockService';
14
14
  export { getConfluencePageAri, getPageIdAndTypeFromConfluencePageAri } from './clients/confluence/ari';
15
15
  export { fetchMediaToken } from './clients/confluence/fetchMediaToken';
16
16
  export { getJiraWorkItemAri, getJiraWorkItemIdFromAri } from './clients/jira/ari';
17
17
 
18
18
  // providers
19
- export { useMemoizedBlockServiceAPIProviders, useMemoizedBlockServiceFetchOnlyAPIProvider, fetchReferences, batchFetchData, blockAriToResourceId, convertToSyncBlockData, extractResourceIdFromBlockAri } from './providers/block-service/blockServiceAPI';
19
+ export { useMemoizedBlockServiceAPIProviders, useMemoizedBlockServiceFetchOnlyAPIProvider, fetchReferences, batchFetchData, writeDataBatch, blockAriToResourceId, convertToSyncBlockData, extractResourceIdFromBlockAri } from './providers/block-service/blockServiceAPI';
20
20
  export { fetchConfluencePageInfo } from './clients/confluence/sourceInfo';
21
21
  export { SyncedBlockProvider, useMemoizedSyncedBlockProvider } from './providers/syncBlockProvider';
22
22
  // store managers
@@ -25,10 +25,6 @@ export { SyncBlockInMemorySessionCache, syncBlockInMemorySessionCache } from './
25
25
  export { SyncBlockStoreManager, useMemoizedSyncBlockStoreManager } from './store-manager/syncBlockStoreManager';
26
26
 
27
27
  // utils
28
- export { BLOCK_SERVICE_SUBSCRIPTION_QUERY } from './clients/block-service/sharedSubscriptionUtils';
29
- export { parseSubscriptionPayload } from './clients/block-service/sharedSubscriptionUtils';
30
- export { convertRelayResponseToSyncBlockInstance } from './utils/relayResponseConverter';
31
- export { createRelayBlockSubscription, createRelaySubscriptionFunction } from './utils/relaySubscriptionUtils';
32
28
  export { resolveSyncBlockInstance } from './utils/resolveSyncBlockInstance';
33
29
  export { parseResourceId, createResourceIdForReference } from './utils/resourceId';
34
30
  export { createSyncBlockNode, convertSyncBlockPMNodeToSyncBlockData, convertSyncBlockJSONNodeToSyncBlockNode, convertPMNodesToSyncBlockNodes, getContentIdAndProductFromResourceId } from './utils/utils';
@@ -2,11 +2,10 @@
2
2
  import { useMemo } from 'react';
3
3
  import { fg } from '@atlaskit/platform-feature-flags';
4
4
  import { generateBlockAri, generateBlockAriFromReference } from '../../clients/block-service/ari';
5
- import { batchRetrieveSyncedBlocks, BlockError, createSyncedBlock, deleteSyncedBlock, getReferenceSyncedBlocks, getReferenceSyncedBlocksByBlockAri, getSyncedBlockContent, updateReferenceSyncedBlockOnDocument, updateSyncedBlock } from '../../clients/block-service/blockService';
5
+ import { batchRetrieveSyncedBlocks, BlockError, createSyncedBlock, deleteSyncedBlock, getReferenceSyncedBlocks, getReferenceSyncedBlocksByBlockAri, getSyncedBlockContent, updateReferenceSyncedBlockOnDocument, updateSyncedBlock, updateSyncedBlocks } from '../../clients/block-service/blockService';
6
6
  import { subscribeToBlockUpdates as subscribeToBlockUpdatesWS } from '../../clients/block-service/blockSubscription';
7
7
  import { SyncBlockError } from '../../common/types';
8
8
  import { stringifyError } from '../../utils/errorHandling';
9
- import { createRelaySubscriptionFunction } from '../../utils/relaySubscriptionUtils';
10
9
  import { createResourceIdForReference } from '../../utils/resourceId';
11
10
  import { convertContentUpdatedAt } from '../../utils/utils';
12
11
  const mapBlockError = error => {
@@ -308,18 +307,102 @@ export const batchFetchData = async (cloudId, parentAri, blockNodeIdentifiers) =
308
307
  }));
309
308
  }
310
309
  };
310
+
311
+ /**
312
+ * Batch writes multiple synced blocks.
313
+ * @param cloudId - The cloudId of the block. E.G the cloudId of the confluence page, or the cloudId of the Jira instance
314
+ * @param parentAri - The ARI of the parent of the block. E.G the ARI of the confluence page, or the ARI of the Jira work item
315
+ * @param parentId - The parentId of the block. E.G the pageId for a confluence page, or the issueId for a Jira work item
316
+ * @param product - The product of the block. E.G 'confluence-page', 'jira-work-item'
317
+ * @param data - Array of SyncBlockData to write
318
+ * @param stepVersion - Optional version number
319
+ * @returns Array of WriteSyncBlockResult results
320
+ */
321
+ export const writeDataBatch = async (cloudId, parentAri, parentId, product, data, stepVersion) => {
322
+ if (!parentAri || !parentId) {
323
+ return data.map(block => ({
324
+ error: SyncBlockError.Errored,
325
+ resourceId: block.resourceId
326
+ }));
327
+ }
328
+ try {
329
+ // Create a map from blockAri to original resourceId for matching responses
330
+ const blockAriToResourceIdMap = new Map();
331
+ const blocks = data.map(block => {
332
+ const blockAri = generateBlockAri({
333
+ cloudId,
334
+ parentId,
335
+ product,
336
+ resourceId: block.resourceId
337
+ });
338
+ blockAriToResourceIdMap.set(blockAri, block.resourceId);
339
+ return {
340
+ blockAri,
341
+ content: JSON.stringify(block.content),
342
+ status: block.status,
343
+ stepVersion
344
+ };
345
+ });
346
+ const response = await updateSyncedBlocks({
347
+ blocks
348
+ });
349
+ const results = [];
350
+
351
+ // Process successful updates
352
+ if (response.success) {
353
+ const successResourceIds = new Set(response.success.map(block => blockAriToResourceIdMap.get(block.blockAri)));
354
+ for (const block of data) {
355
+ if (successResourceIds.has(block.resourceId)) {
356
+ results.push({
357
+ resourceId: block.resourceId
358
+ });
359
+ }
360
+ }
361
+ }
362
+ if (response.error) {
363
+ const errorResourceIds = new Map(response.error.map(err => [
364
+ // Use the map to get the original resourceId
365
+ blockAriToResourceIdMap.get(err.blockAri) || '', mapErrorResponseCode(err.code)]));
366
+ for (const block of data) {
367
+ const error = errorResourceIds.get(block.resourceId);
368
+ if (error) {
369
+ results.push({
370
+ error,
371
+ resourceId: block.resourceId
372
+ });
373
+ } else if (!results.some(r => r.resourceId === block.resourceId)) {
374
+ // If not in success or error lists, mark as errored
375
+ results.push({
376
+ error: SyncBlockError.Errored,
377
+ resourceId: block.resourceId
378
+ });
379
+ }
380
+ }
381
+ }
382
+ return results;
383
+ } catch (error) {
384
+ if (error instanceof BlockError) {
385
+ return data.map(block => ({
386
+ error: mapBlockError(error),
387
+ resourceId: block.resourceId
388
+ }));
389
+ }
390
+ return data.map(block => ({
391
+ error: stringifyError(error),
392
+ resourceId: block.resourceId
393
+ }));
394
+ }
395
+ };
311
396
  /**
312
397
  * ADFFetchProvider implementation that fetches synced block data from Block Service API
313
398
  */
314
399
  class BlockServiceADFFetchProvider {
315
400
  constructor({
316
401
  cloudId,
317
- parentAri,
318
- relayEnvironment
402
+ parentAri
319
403
  }) {
320
404
  this.cloudId = cloudId;
321
405
  this.parentAri = parentAri;
322
- this.relayEnvironment = relayEnvironment;
323
406
  }
324
407
 
325
408
  // resourceId of the reference synced block.
@@ -445,7 +528,6 @@ class BlockServiceADFFetchProvider {
445
528
 
446
529
  /**
447
530
  * Subscribes to real-time updates for a specific block using GraphQL WebSocket subscriptions.
448
- * If a Relay environment is provided, uses Relay subscriptions; otherwise falls back to WebSocket.
449
531
  * @param resourceId - The resource ID of the block to subscribe to
450
532
  * @param onUpdate - Callback function invoked when the block is updated
451
533
  * @param onError - Optional callback function invoked on subscription errors
@@ -456,14 +538,6 @@ class BlockServiceADFFetchProvider {
456
538
  cloudId: this.cloudId,
457
539
  resourceId
458
540
  });
459
-
460
- // If Relay environment is available, use Relay subscriptions
461
- if (this.relayEnvironment && fg('platform_synced_block_patch_3')) {
462
- const relaySubscribeToBlockUpdates = createRelaySubscriptionFunction(this.cloudId, this.relayEnvironment);
463
- return relaySubscribeToBlockUpdates(resourceId, onUpdate, onError);
464
- }
465
-
466
- // Fall back to WebSocket subscriptions
467
541
  return subscribeToBlockUpdatesWS(blockAri, parsedData => {
468
542
  // Convert ParsedBlockSubscriptionData to SyncBlockInstance
469
543
  const syncBlockInstance = {
@@ -643,6 +717,14 @@ class BlockServiceADFWriteProvider {
643
717
  generateResourceId() {
644
718
  return crypto.randomUUID();
645
719
  }
720
+ generateBlockAri(resourceId) {
721
+ return generateBlockAri({
722
+ cloudId: this.cloudId,
723
+ parentId: this.parentId || '',
724
+ product: this.product,
725
+ resourceId
726
+ });
727
+ }
646
728
  async updateReferenceData(blocks, noContent) {
647
729
  if (!this.parentAri) {
648
730
  return {
@@ -678,20 +760,89 @@ class BlockServiceADFWriteProvider {
678
760
  };
679
761
  }
680
762
  }
763
+ async writeDataBatch(data) {
764
+ if (!this.parentAri || !this.parentId) {
765
+ return data.map(block => ({
766
+ error: SyncBlockError.Errored,
767
+ resourceId: block.resourceId
768
+ }));
769
+ }
770
+ const stepVersion = this.getVersion ? await this.getVersion() : undefined;
771
+ try {
772
+ // Create a map from blockAri to original resourceId for matching responses
773
+ const blockAriToResourceIdMap = new Map();
774
+ const blocks = data.map(block => {
775
+ const blockAri = this.generateBlockAri(block.resourceId);
776
+ blockAriToResourceIdMap.set(blockAri, block.resourceId);
777
+ return {
778
+ blockAri,
779
+ content: JSON.stringify(block.content),
780
+ status: block.status,
781
+ stepVersion
782
+ };
783
+ });
784
+ const response = await updateSyncedBlocks({
785
+ blocks
786
+ });
787
+ const results = [];
788
+
789
+ // Process successful updates
790
+ if (response.success) {
791
+ const successResourceIds = new Set(response.success.map(block => blockAriToResourceIdMap.get(block.blockAri)));
792
+ for (const block of data) {
793
+ if (successResourceIds.has(block.resourceId)) {
794
+ results.push({
795
+ resourceId: block.resourceId
796
+ });
797
+ }
798
+ }
799
+ }
800
+ if (response.error) {
801
+ const errorResourceIds = new Map(response.error.map(err => [
802
+ // Use the map to get the original resourceId
803
+ blockAriToResourceIdMap.get(err.blockAri) || '', mapErrorResponseCode(err.code)]));
804
+ for (const block of data) {
805
+ const error = errorResourceIds.get(block.resourceId);
806
+ if (error) {
807
+ results.push({
808
+ error,
809
+ resourceId: block.resourceId
810
+ });
811
+ } else if (!results.some(r => r.resourceId === block.resourceId)) {
812
+ // If not in success or error lists, mark as errored
813
+ results.push({
814
+ error: SyncBlockError.Errored,
815
+ resourceId: block.resourceId
816
+ });
817
+ }
818
+ }
819
+ }
820
+ return results;
821
+ } catch (error) {
822
+ if (error instanceof BlockError) {
823
+ return data.map(block => ({
824
+ error: mapBlockError(error),
825
+ resourceId: block.resourceId
826
+ }));
827
+ }
828
+ return data.map(block => ({
829
+ error: stringifyError(error),
830
+ resourceId: block.resourceId
831
+ }));
832
+ }
833
+ }
681
834
  }
682
835
  const createBlockServiceAPIProviders = ({
683
836
  cloudId,
684
837
  parentAri,
685
838
  parentId,
686
839
  product,
687
- getVersion,
688
- relayEnvironment
840
+ getVersion
689
841
  }) => {
690
842
  return {
691
843
  fetchProvider: new BlockServiceADFFetchProvider({
692
844
  cloudId,
693
- parentAri,
694
- relayEnvironment
845
+ parentAri
695
846
  }),
696
847
  writeProvider: new BlockServiceADFWriteProvider({
697
848
  cloudId,
@@ -707,8 +858,7 @@ export const useMemoizedBlockServiceAPIProviders = ({
707
858
  parentAri,
708
859
  parentId,
709
860
  product,
710
- getVersion,
711
- relayEnvironment
861
+ getVersion
712
862
  }) => {
713
863
  return useMemo(() => {
714
864
  return createBlockServiceAPIProviders({
@@ -716,10 +866,9 @@ export const useMemoizedBlockServiceAPIProviders = ({
716
866
  parentAri,
717
867
  parentId,
718
868
  product,
719
- getVersion,
720
- relayEnvironment
869
+ getVersion
721
870
  });
722
- }, [cloudId, parentAri, parentId, product, getVersion, relayEnvironment]);
871
+ }, [cloudId, parentAri, parentId, product, getVersion]);
723
872
  };
724
873
  const createBlockServiceFetchOnlyAPIProvider = ({
725
874
  cloudId,
@@ -1,6 +1,7 @@
1
1
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
- import { useMemo } from 'react';
2
+ import { useMemo, useRef } from 'react';
3
3
  import { fg } from '@atlaskit/platform-feature-flags';
4
+ import { conditionalHooksFactory } from '@atlaskit/platform-feature-flags-react';
4
5
  import { getProductFromSourceAri } from '../clients/block-service/ari';
5
6
  import { getPageIdAndTypeFromConfluencePageAri } from '../clients/confluence/ari';
6
7
  import { fetchConfluencePageInfo } from '../clients/confluence/sourceInfo';
@@ -103,6 +104,32 @@ export class SyncedBlockProvider extends SyncBlockDataProviderInterface {
103
104
  if (!this.writeProvider) {
104
105
  return Promise.reject(new Error('Write provider not set'));
105
106
  }
107
+
108
+ // Use batch write only when feature flag is enabled and method is available
109
+ if (this.writeProvider.writeDataBatch && fg('platform_synced_block_patch_4')) {
110
+ // Separate data into valid (with content) and invalid (without content)
111
+ const validDataWithIndices = [];
112
+ const invalidResults = [];
113
+ data.forEach(blockData => {
114
+ if (blockData.content) {
115
+ validDataWithIndices.push(blockData);
116
+ } else {
117
+ invalidResults.push({
118
+ error: 'No Synced Block content to write',
119
+ resourceId: blockData.resourceId
120
+ });
121
+ }
122
+ });
123
+
124
+ // Process valid data in batch
125
+ let batchResults = [];
126
+ if (validDataWithIndices.length > 0) {
127
+ batchResults = await this.writeProvider.writeDataBatch(validDataWithIndices);
128
+ }
129
+ return [...batchResults, ...invalidResults];
130
+ }
131
+
132
+ // Fall back to individual writes
106
133
  const results = await Promise.allSettled(nodes.map((_node, index) => {
107
134
  var _this$writeProvider;
108
135
  if (!this.writeProvider) {
@@ -289,7 +316,7 @@ const createSyncedBlockProvider = ({
289
316
  }) => {
290
317
  return new SyncedBlockProvider(fetchProvider, writeProvider);
291
318
  };
292
- export const useMemoizedSyncedBlockProvider = ({
319
+ const useMemoizedSyncedBlockProviderBase = ({
293
320
  fetchProvider,
294
321
  writeProvider,
295
322
  providerOptions,
@@ -305,4 +332,30 @@ export const useMemoizedSyncedBlockProvider = ({
305
332
  syncBlockProvider.setSSRData(ssrData);
306
333
  }
307
334
  return syncBlockProvider;
308
- };
335
+ };
336
+ const useMemoizedSyncedBlockProviderPatched = ({
337
+ fetchProvider,
338
+ writeProvider,
339
+ providerOptions,
340
+ getSSRData
341
+ }) => {
342
+ const syncBlockProvider = useMemo(() => createSyncedBlockProvider({
343
+ fetchProvider,
344
+ writeProvider
345
+ }), [fetchProvider, writeProvider]);
346
+ const prevProviderOptionsRef = useRef(undefined);
347
+ if (providerOptions !== prevProviderOptionsRef.current) {
348
+ prevProviderOptionsRef.current = providerOptions;
349
+ syncBlockProvider.setProviderOptions(providerOptions);
350
+ }
351
+ const prevSSRDataRef = useRef(undefined);
352
+ const ssrData = getSSRData === null || getSSRData === void 0 ? void 0 : getSSRData();
353
+ if (ssrData !== prevSSRDataRef.current) {
354
+ prevSSRDataRef.current = ssrData;
355
+ if (ssrData) {
356
+ syncBlockProvider.setSSRData(ssrData);
357
+ }
358
+ }
359
+ return syncBlockProvider;
360
+ };
361
+ export const useMemoizedSyncedBlockProvider = conditionalHooksFactory(() => fg('platform_synced_block_patch_4'), useMemoizedSyncedBlockProviderPatched, useMemoizedSyncedBlockProviderBase);
@@ -277,6 +277,9 @@ export class ReferenceSyncBlockStoreManager {
277
277
  */
278
278
  handleGraphQLSubscriptionUpdate(syncBlockInstance) {
279
279
  if (!syncBlockInstance.resourceId) {
280
+ if (fg('platform_synced_block_patch_4')) {
281
+ return;
282
+ }
280
283
  throw new Error('Sync block instance provided to graphql subscription update missing resource id');
281
284
  }
282
285
  const existingSyncBlock = this.getFromCache(syncBlockInstance.resourceId);
@@ -1,5 +1,7 @@
1
- import { useMemo } from 'react';
1
+ import { useMemo, useRef } from 'react';
2
2
  import { logException } from '@atlaskit/editor-common/monitoring';
3
+ import { fg } from '@atlaskit/platform-feature-flags';
4
+ import { conditionalHooksFactory } from '@atlaskit/platform-feature-flags-react';
3
5
  import { getProductFromSourceAri } from '../clients/block-service/ari';
4
6
  import { SyncBlockError } from '../common/types';
5
7
  import { fetchReferencesErrorPayload } from '../utils/errorHandling';
@@ -14,7 +16,7 @@ import { SourceSyncBlockStoreManager } from './sourceSyncBlockStoreManager';
14
16
  // Can be used in both editor and renderer contexts.
15
17
  export class SyncBlockStoreManager {
16
18
  constructor(dataProvider) {
17
- // In future, if reference manager needs to reach to source manager and read it's current in memorey cache
19
+ // In future, if reference manager needs to reach to source manager and read its current in memory cache
18
20
  // we can pass the source manager as a parameter to the reference manager constructor
19
21
  this.sourceSyncBlockStoreManager = new SourceSyncBlockStoreManager(dataProvider);
20
22
  this.referenceSyncBlockStoreManager = new ReferenceSyncBlockStoreManager(dataProvider);
@@ -128,11 +130,22 @@ export class SyncBlockStoreManager {
128
130
  const createSyncBlockStoreManager = dataProvider => {
129
131
  return new SyncBlockStoreManager(dataProvider);
130
132
  };
131
- export const useMemoizedSyncBlockStoreManager = (dataProvider, fireAnalyticsEvent) => {
133
+ const useMemoizedSyncBlockStoreManagerBase = (dataProvider, fireAnalyticsEvent) => {
132
134
  const syncBlockStoreManager = useMemo(() => {
133
- const syncBlockStoreManager = createSyncBlockStoreManager(dataProvider);
134
- return syncBlockStoreManager;
135
+ return createSyncBlockStoreManager(dataProvider);
135
136
  }, [dataProvider]);
136
137
  syncBlockStoreManager.setFireAnalyticsEvent(fireAnalyticsEvent);
137
138
  return syncBlockStoreManager;
138
- };
139
+ };
140
+ const useMemoizedSyncBlockStoreManagerPatched = (dataProvider, fireAnalyticsEvent) => {
141
+ const syncBlockStoreManager = useMemo(() => {
142
+ return createSyncBlockStoreManager(dataProvider);
143
+ }, [dataProvider]);
144
+ const prevFireAnalyticsEventRef = useRef(undefined);
145
+ if (fireAnalyticsEvent !== prevFireAnalyticsEventRef.current) {
146
+ prevFireAnalyticsEventRef.current = fireAnalyticsEvent;
147
+ syncBlockStoreManager.setFireAnalyticsEvent(fireAnalyticsEvent);
148
+ }
149
+ return syncBlockStoreManager;
150
+ };
151
+ export const useMemoizedSyncBlockStoreManager = conditionalHooksFactory(() => fg('platform_synced_block_patch_4'), useMemoizedSyncBlockStoreManagerPatched, useMemoizedSyncBlockStoreManagerBase);
@@ -81,9 +81,9 @@ export const getFetchSourceInfoExperience = fireAnalyticsEvent => {
81
81
  /**
82
82
  * This experience tracks when a source sync block is deleted from the BE.
83
83
  *
84
- * Start: When the fetchSourceInfo function is called.
85
- * Success: When the fetching the data is successful within the timeout duration of start.
86
- * Failure: When the timeout duration passes without the data being successfully fetched, or the fetch fails
84
+ * Start: When the delete source sync block function is called.
85
+ * Success: When the sync block deletion is successful within the timeout duration of start.
86
+ * Failure: When the timeout duration passes without the sync block being successfully deleted, or the deletion fails
87
87
  */
88
88
  export const getDeleteSourceExperience = fireAnalyticsEvent => {
89
89
  return new Experience(EXPERIENCE_ID.ASYNC_OPERATION, {
@@ -98,9 +98,9 @@ export const getDeleteSourceExperience = fireAnalyticsEvent => {
98
98
  /**
99
99
  * This experience tracks when a source sync block is created and registered to the BE.
100
100
  *
101
- * Start: When the fetchSourceInfo function is called.
102
- * Success: When the fetching the data is successful within the timeout duration of start.
103
- * Failure: When the timeout duration passes without the data being successfully fetched, or the fetch fails
101
+ * Start: When the create source sync block function is called.
102
+ * Success: When the sync block creation is successful within the timeout duration of start.
103
+ * Failure: When the timeout duration passes without the sync block being successfully created, or the creation fails
104
104
  */
105
105
  export const getCreateSourceExperience = fireAnalyticsEvent => {
106
106
  return new Experience(EXPERIENCE_ID.ASYNC_OPERATION, {
@@ -113,11 +113,11 @@ export const getCreateSourceExperience = fireAnalyticsEvent => {
113
113
  };
114
114
 
115
115
  /**
116
- * This experience tracks when a source sync block is created and registered to the BE.
116
+ * This experience tracks when references for a sync block are fetched from the BE.
117
117
  *
118
- * Start: When the fetchSourceInfo function is called.
119
- * Success: When the fetching the data is successful within the timeout duration of start.
120
- * Failure: When the timeout duration passes without the data being successfully fetched, or the fetch fails
118
+ * Start: When the fetchReferences function is called.
119
+ * Success: When the fetching of references is successful within the timeout duration of start.
120
+ * Failure: When the timeout duration passes without references being successfully fetched, or the fetch fails
121
121
  */
122
122
  export const getFetchReferencesExperience = fireAnalyticsEvent => {
123
123
  return new Experience(EXPERIENCE_ID.ASYNC_OPERATION, {
@@ -11,7 +11,7 @@ const isSyncBlockProduct = product => {
11
11
  *
12
12
  * Format
13
13
  * - {product}/{contentId}/{uuid}
14
- * - product: a recognized `SyncBlockProduct` (e.g. 'confluence-page', 'jira-issue')
14
+ * - product: a recognized `SyncBlockProduct` (e.g. 'confluence-page', 'jira-work-item')
15
15
  * - contentId: the host content identifier (e.g. page ID or issue ID)
16
16
  * - uuid: the UUID for the specific synced block instance
17
17
  *
@@ -24,7 +24,7 @@ const isSyncBlockProduct = product => {
24
24
  * - No extra segments; returns `undefined` on any invalid input
25
25
  *
26
26
  * Notes
27
- * - `product` is a qualified domain like 'confluence-page' or 'jira-issue',
27
+ * - `product` is a qualified domain like 'confluence-page' or 'jira-work-item',
28
28
  * not just 'confluence' or 'jira'.
29
29
  */
30
30
 
@@ -1,7 +1,10 @@
1
- const parseRetryAfter = retryAfter => {
1
+ import { fg } from '@atlaskit/platform-feature-flags';
2
+ import { functionWithCondition } from '@atlaskit/platform-feature-flags-react';
3
+ const MAX_RETRY_DELAY = 30000;
4
+ const parseRetryAfterBase = retryAfter => {
2
5
  let newDelay;
3
6
 
4
- // retryAfter can either be in ms or HTTP date
7
+ // retryAfter can either be in seconds or HTTP date
5
8
  const parsedRetryAfter = parseInt(retryAfter);
6
9
  if (!isNaN(parsedRetryAfter)) {
7
10
  newDelay = parsedRetryAfter * 1000;
@@ -14,16 +17,33 @@ const parseRetryAfter = retryAfter => {
14
17
  }
15
18
  return newDelay;
16
19
  };
20
+ const parseRetryAfterPatched = retryAfter => {
21
+ // retryAfter can either be in seconds or HTTP date
22
+ const parsedRetryAfter = parseInt(retryAfter, 10);
23
+ if (!isNaN(parsedRetryAfter) && parsedRetryAfter > 0) {
24
+ return parsedRetryAfter * 1000;
25
+ }
26
+ const retryDate = new Date(retryAfter);
27
+ if (isNaN(retryDate.getTime())) {
28
+ return undefined;
29
+ }
30
+ const delayFromDate = retryDate.getTime() - Date.now();
31
+ if (delayFromDate > 0) {
32
+ return delayFromDate;
33
+ }
34
+ return undefined;
35
+ };
36
+ const parseRetryAfter = functionWithCondition(() => fg('platform_synced_block_patch_4'), parseRetryAfterPatched, parseRetryAfterBase);
17
37
  export const fetchWithRetry = async (url, options, retriesRemaining = 3, delay = 1000) => {
38
+ var _ref;
18
39
  const response = await fetch(url, options);
19
40
  const shouldRetry = !response.ok && response.status === 429 && retriesRemaining > 1;
20
41
  if (!shouldRetry) {
21
42
  return response;
22
43
  }
23
44
  const retryAfter = response.headers.get('Retry-After');
24
- await new Promise(resolve => {
25
- var _ref;
26
- return setTimeout(resolve, (_ref = retryAfter ? parseRetryAfter(retryAfter) : undefined) !== null && _ref !== void 0 ? _ref : delay);
27
- });
45
+ const parsedDelay = (_ref = retryAfter ? parseRetryAfter(retryAfter) : undefined) !== null && _ref !== void 0 ? _ref : delay;
46
+ const retryDelay = fg('platform_synced_block_patch_4') ? Math.min(parsedDelay, MAX_RETRY_DELAY) : parsedDelay;
47
+ await new Promise(resolve => setTimeout(resolve, retryDelay));
28
48
  return fetchWithRetry(url, options, retriesRemaining - 1, delay * 2);
29
49
  };
@@ -30,7 +30,7 @@ export const convertPMNodeToSyncBlockNode = node => {
30
30
  return createSyncBlockNode(node.attrs.localId, node.attrs.resourceId);
31
31
  };
32
32
  export const convertPMNodesToSyncBlockNodes = nodes => {
33
- return nodes.map(node => convertPMNodeToSyncBlockNode(node)).filter(node => node !== undefined) || [];
33
+ return nodes.map(node => convertPMNodeToSyncBlockNode(node)).filter(node => node !== undefined);
34
34
  };
35
35
 
36
36
  /*
@@ -1,5 +1,6 @@
1
+ import { SYNC_BLOCK_PRODUCTS } from '../common/consts';
1
2
  export const normaliseSyncBlockProduct = value => {
2
- return value === 'confluence-page' || value === 'jira-work-item' ? value : undefined;
3
+ return SYNC_BLOCK_PRODUCTS.includes(value) ? value : undefined;
3
4
  };
4
5
  export const normaliseSyncBlockStatus = value => {
5
6
  return value === 'active' || value === 'unpublished' || value === 'deleted' ? value : undefined;