@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.
- package/CHANGELOG.md +21 -0
- package/dist/cjs/clients/block-service/blockService.js +121 -17
- package/dist/cjs/clients/block-service/blockSubscription.js +48 -3
- package/dist/cjs/clients/confluence/fetchMediaToken.js +7 -5
- package/dist/cjs/hooks/useFetchSyncBlockTitle.js +40 -2
- package/dist/cjs/hooks/useHandleContentChanges.js +3 -0
- package/dist/cjs/index.js +12 -33
- package/dist/cjs/providers/block-service/blockServiceAPI.js +509 -185
- package/dist/cjs/providers/syncBlockProvider.js +68 -6
- package/dist/cjs/store-manager/referenceSyncBlockStoreManager.js +3 -0
- package/dist/cjs/store-manager/syncBlockStoreManager.js +20 -5
- package/dist/cjs/utils/experienceTracking.js +10 -10
- package/dist/cjs/utils/resourceId.js +2 -2
- package/dist/cjs/utils/retry.js +33 -7
- package/dist/cjs/utils/utils.js +1 -1
- package/dist/cjs/utils/validValue.js +2 -1
- package/dist/es2019/clients/block-service/blockService.js +108 -13
- package/dist/es2019/clients/block-service/blockSubscription.js +62 -2
- package/dist/es2019/clients/confluence/fetchMediaToken.js +5 -3
- package/dist/es2019/hooks/useFetchSyncBlockTitle.js +36 -3
- package/dist/es2019/hooks/useHandleContentChanges.js +3 -0
- package/dist/es2019/index.js +2 -6
- package/dist/es2019/providers/block-service/blockServiceAPI.js +172 -23
- package/dist/es2019/providers/syncBlockProvider.js +56 -3
- package/dist/es2019/store-manager/referenceSyncBlockStoreManager.js +3 -0
- package/dist/es2019/store-manager/syncBlockStoreManager.js +19 -6
- package/dist/es2019/utils/experienceTracking.js +10 -10
- package/dist/es2019/utils/resourceId.js +2 -2
- package/dist/es2019/utils/retry.js +26 -6
- package/dist/es2019/utils/utils.js +1 -1
- package/dist/es2019/utils/validValue.js +2 -1
- package/dist/esm/clients/block-service/blockService.js +120 -16
- package/dist/esm/clients/block-service/blockSubscription.js +47 -2
- package/dist/esm/clients/confluence/fetchMediaToken.js +7 -5
- package/dist/esm/hooks/useFetchSyncBlockTitle.js +41 -3
- package/dist/esm/hooks/useHandleContentChanges.js +3 -0
- package/dist/esm/index.js +2 -6
- package/dist/esm/providers/block-service/blockServiceAPI.js +513 -189
- package/dist/esm/providers/syncBlockProvider.js +69 -7
- package/dist/esm/store-manager/referenceSyncBlockStoreManager.js +3 -0
- package/dist/esm/store-manager/syncBlockStoreManager.js +21 -6
- package/dist/esm/utils/experienceTracking.js +10 -10
- package/dist/esm/utils/resourceId.js +2 -2
- package/dist/esm/utils/retry.js +33 -7
- package/dist/esm/utils/utils.js +1 -1
- package/dist/esm/utils/validValue.js +2 -1
- package/dist/types/clients/block-service/blockService.d.ts +36 -0
- package/dist/types/clients/block-service/blockSubscription.d.ts +26 -1
- package/dist/types/index.d.ts +2 -6
- package/dist/types/providers/block-service/blockServiceAPI.d.ts +15 -7
- package/dist/types/providers/syncBlockProvider.d.ts +1 -1
- package/dist/types/providers/types.d.ts +6 -0
- package/dist/types/store-manager/syncBlockStoreManager.d.ts +1 -1
- package/dist/types/utils/experienceTracking.d.ts +10 -10
- package/dist/types-ts4.5/clients/block-service/blockService.d.ts +36 -0
- package/dist/types-ts4.5/clients/block-service/blockSubscription.d.ts +26 -1
- package/dist/types-ts4.5/index.d.ts +2 -6
- package/dist/types-ts4.5/providers/block-service/blockServiceAPI.d.ts +15 -7
- package/dist/types-ts4.5/providers/syncBlockProvider.d.ts +1 -1
- package/dist/types-ts4.5/providers/types.d.ts +6 -0
- package/dist/types-ts4.5/store-manager/syncBlockStoreManager.d.ts +1 -1
- package/dist/types-ts4.5/utils/experienceTracking.d.ts +10 -10
- package/package.json +5 -2
- package/dist/cjs/clients/block-service/sharedSubscriptionUtils.js +0 -82
- package/dist/cjs/utils/__generated__/relaySubscriptionUtilsSubscription.graphql.js +0 -94
- package/dist/cjs/utils/relayResponseConverter.js +0 -76
- package/dist/cjs/utils/relaySubscriptionUtils.js +0 -130
- package/dist/es2019/clients/block-service/sharedSubscriptionUtils.js +0 -91
- package/dist/es2019/utils/__generated__/relaySubscriptionUtilsSubscription.graphql.js +0 -88
- package/dist/es2019/utils/relayResponseConverter.js +0 -69
- package/dist/es2019/utils/relaySubscriptionUtils.js +0 -125
- package/dist/esm/clients/block-service/sharedSubscriptionUtils.js +0 -76
- package/dist/esm/utils/__generated__/relaySubscriptionUtilsSubscription.graphql.js +0 -88
- package/dist/esm/utils/relayResponseConverter.js +0 -69
- package/dist/esm/utils/relaySubscriptionUtils.js +0 -123
- package/dist/types/clients/block-service/sharedSubscriptionUtils.d.ts +0 -61
- package/dist/types/utils/__generated__/relaySubscriptionUtilsSubscription.graphql.d.ts +0 -31
- package/dist/types/utils/relayResponseConverter.d.ts +0 -47
- package/dist/types/utils/relaySubscriptionUtils.d.ts +0 -61
- package/dist/types-ts4.5/clients/block-service/sharedSubscriptionUtils.d.ts +0 -61
- package/dist/types-ts4.5/utils/__generated__/relaySubscriptionUtilsSubscription.graphql.d.ts +0 -31
- package/dist/types-ts4.5/utils/relayResponseConverter.d.ts +0 -47
- package/dist/types-ts4.5/utils/relaySubscriptionUtils.d.ts +0 -61
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { useEffect, useState } from 'react';
|
|
2
|
-
|
|
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]);
|
package/dist/es2019/index.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
133
|
+
const useMemoizedSyncBlockStoreManagerBase = (dataProvider, fireAnalyticsEvent) => {
|
|
132
134
|
const syncBlockStoreManager = useMemo(() => {
|
|
133
|
-
|
|
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
|
|
85
|
-
* Success: When the
|
|
86
|
-
* Failure: When the timeout duration passes without the
|
|
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
|
|
102
|
-
* Success: When the
|
|
103
|
-
* Failure: When the timeout duration passes without the
|
|
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
|
|
116
|
+
* This experience tracks when references for a sync block are fetched from the BE.
|
|
117
117
|
*
|
|
118
|
-
* Start: When the
|
|
119
|
-
* Success: When the fetching
|
|
120
|
-
* Failure: When the timeout duration passes without
|
|
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-
|
|
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-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
|
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;
|