@atlaskit/editor-synced-block-provider 3.2.2 → 3.4.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.
@@ -6,8 +6,8 @@ Object.defineProperty(exports, "__esModule", {
6
6
  });
7
7
  exports.useMemoizedSyncedBlockProvider = exports.createAndInitializeSyncedBlockProvider = exports.SyncBlockProvider = void 0;
8
8
  var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
9
- var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
10
9
  var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
10
+ var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
11
11
  var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
12
12
  var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
13
13
  var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn"));
@@ -15,6 +15,7 @@ var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/ge
15
15
  var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits"));
16
16
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
17
17
  var _react = require("react");
18
+ var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
18
19
  var _ari = require("../clients/confluence/ari");
19
20
  var _sourceInfo = require("../clients/confluence/sourceInfo");
20
21
  var _types = require("../common/types");
@@ -73,7 +74,7 @@ var SyncBlockProvider = exports.SyncBlockProvider = /*#__PURE__*/function (_Sync
73
74
  }
74
75
 
75
76
  /**
76
- * Fetch the data from the fetch provider
77
+ * Fetch the data from the fetch provider using batch API
77
78
  *
78
79
  * @param nodes
79
80
  *
@@ -81,30 +82,66 @@ var SyncBlockProvider = exports.SyncBlockProvider = /*#__PURE__*/function (_Sync
81
82
  */
82
83
  }, {
83
84
  key: "fetchNodesData",
84
- value: function fetchNodesData(nodes) {
85
- var _this2 = this;
86
- var resourceIdSet = new Set(nodes.map(function (node) {
87
- return node.attrs.resourceId;
85
+ value: (function () {
86
+ var _fetchNodesData = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(nodes) {
87
+ var _this2 = this;
88
+ var resourceIdSet, resourceIds;
89
+ return _regenerator.default.wrap(function _callee$(_context) {
90
+ while (1) switch (_context.prev = _context.next) {
91
+ case 0:
92
+ resourceIdSet = new Set(nodes.map(function (node) {
93
+ return node.attrs.resourceId;
94
+ }));
95
+ resourceIds = (0, _toConsumableArray2.default)(resourceIdSet);
96
+ if (!(0, _platformFeatureFlags.fg)('platform_synced_block_dogfooding')) {
97
+ _context.next = 14;
98
+ break;
99
+ }
100
+ _context.prev = 3;
101
+ _context.next = 6;
102
+ return this.fetchProvider.batchFetchData(resourceIds);
103
+ case 6:
104
+ return _context.abrupt("return", _context.sent);
105
+ case 9:
106
+ _context.prev = 9;
107
+ _context.t0 = _context["catch"](3);
108
+ return _context.abrupt("return", resourceIds.map(function (resourceId) {
109
+ return {
110
+ error: _types.SyncBlockError.Errored,
111
+ resourceId: resourceId
112
+ };
113
+ }));
114
+ case 12:
115
+ _context.next = 15;
116
+ break;
117
+ case 14:
118
+ return _context.abrupt("return", Promise.allSettled(resourceIds.map(function (resourceId) {
119
+ return _this2.fetchProvider.fetchData(resourceId).then(function (data) {
120
+ return data;
121
+ }, function () {
122
+ return {
123
+ error: _types.SyncBlockError.Errored,
124
+ resourceId: resourceId
125
+ };
126
+ });
127
+ })).then(function (results) {
128
+ return results.filter(function (result) {
129
+ return result.status === 'fulfilled';
130
+ }).map(function (result) {
131
+ return result.value;
132
+ });
133
+ }));
134
+ case 15:
135
+ case "end":
136
+ return _context.stop();
137
+ }
138
+ }, _callee, this, [[3, 9]]);
88
139
  }));
89
- var resourceIds = (0, _toConsumableArray2.default)(resourceIdSet);
90
- return Promise.allSettled(resourceIds.map(function (resourceId) {
91
- return _this2.fetchProvider.fetchData(resourceId).then(function (data) {
92
- return data;
93
- }, function () {
94
- return {
95
- error: _types.SyncBlockError.Errored,
96
- resourceId: resourceId
97
- };
98
- });
99
- })).then(function (results) {
100
- return results.filter(function (result) {
101
- return result.status === 'fulfilled';
102
- }).map(function (result) {
103
- return result.value;
104
- });
105
- });
106
- }
107
-
140
+ function fetchNodesData(_x) {
141
+ return _fetchNodesData.apply(this, arguments);
142
+ }
143
+ return fetchNodesData;
144
+ }()
108
145
  /**
109
146
  * Write the data to the write provider
110
147
  *
@@ -114,22 +151,23 @@ var SyncBlockProvider = exports.SyncBlockProvider = /*#__PURE__*/function (_Sync
114
151
  * @returns Array of {resourceId?: string, error?: string}.
115
152
  * resourceId: resource id of the node if write successfully , error: reason for when write failed
116
153
  */
154
+ )
117
155
  }, {
118
156
  key: "writeNodesData",
119
157
  value: (function () {
120
- var _writeNodesData = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(nodes, data) {
158
+ var _writeNodesData = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee2(nodes, data) {
121
159
  var _this3 = this;
122
160
  var results;
123
- return _regenerator.default.wrap(function _callee$(_context) {
124
- while (1) switch (_context.prev = _context.next) {
161
+ return _regenerator.default.wrap(function _callee2$(_context2) {
162
+ while (1) switch (_context2.prev = _context2.next) {
125
163
  case 0:
126
164
  if (this.writeProvider) {
127
- _context.next = 2;
165
+ _context2.next = 2;
128
166
  break;
129
167
  }
130
- return _context.abrupt("return", Promise.reject(new Error('Write provider not set')));
168
+ return _context2.abrupt("return", Promise.reject(new Error('Write provider not set')));
131
169
  case 2:
132
- _context.next = 4;
170
+ _context2.next = 4;
133
171
  return Promise.allSettled(nodes.map(function (_node, index) {
134
172
  var _this3$writeProvider;
135
173
  if (!_this3.writeProvider) {
@@ -141,8 +179,8 @@ var SyncBlockProvider = exports.SyncBlockProvider = /*#__PURE__*/function (_Sync
141
179
  return (_this3$writeProvider = _this3.writeProvider) === null || _this3$writeProvider === void 0 ? void 0 : _this3$writeProvider.writeData(data[index]);
142
180
  }));
143
181
  case 4:
144
- results = _context.sent;
145
- return _context.abrupt("return", results.map(function (result) {
182
+ results = _context2.sent;
183
+ return _context2.abrupt("return", results.map(function (result) {
146
184
  if (result.status === 'fulfilled') {
147
185
  return result.value;
148
186
  } else {
@@ -153,11 +191,11 @@ var SyncBlockProvider = exports.SyncBlockProvider = /*#__PURE__*/function (_Sync
153
191
  }));
154
192
  case 6:
155
193
  case "end":
156
- return _context.stop();
194
+ return _context2.stop();
157
195
  }
158
- }, _callee, this);
196
+ }, _callee2, this);
159
197
  }));
160
- function writeNodesData(_x, _x2) {
198
+ function writeNodesData(_x2, _x3) {
161
199
  return _writeNodesData.apply(this, arguments);
162
200
  }
163
201
  return writeNodesData;
@@ -187,19 +225,19 @@ var SyncBlockProvider = exports.SyncBlockProvider = /*#__PURE__*/function (_Sync
187
225
  }, {
188
226
  key: "deleteNodesData",
189
227
  value: (function () {
190
- var _deleteNodesData = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee2(resourceIds) {
228
+ var _deleteNodesData = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee3(resourceIds) {
191
229
  var _this4 = this;
192
230
  var results;
193
- return _regenerator.default.wrap(function _callee2$(_context2) {
194
- while (1) switch (_context2.prev = _context2.next) {
231
+ return _regenerator.default.wrap(function _callee3$(_context3) {
232
+ while (1) switch (_context3.prev = _context3.next) {
195
233
  case 0:
196
234
  if (this.writeProvider) {
197
- _context2.next = 2;
235
+ _context3.next = 2;
198
236
  break;
199
237
  }
200
- return _context2.abrupt("return", Promise.reject(new Error('Write provider not set')));
238
+ return _context3.abrupt("return", Promise.reject(new Error('Write provider not set')));
201
239
  case 2:
202
- _context2.next = 4;
240
+ _context3.next = 4;
203
241
  return Promise.allSettled(resourceIds.map(function (resourceId) {
204
242
  if (!_this4.writeProvider) {
205
243
  return Promise.reject('Write provider not set');
@@ -207,8 +245,8 @@ var SyncBlockProvider = exports.SyncBlockProvider = /*#__PURE__*/function (_Sync
207
245
  return _this4.writeProvider.deleteData(resourceId);
208
246
  }));
209
247
  case 4:
210
- results = _context2.sent;
211
- return _context2.abrupt("return", results.map(function (result, index) {
248
+ results = _context3.sent;
249
+ return _context3.abrupt("return", results.map(function (result, index) {
212
250
  if (result.status === 'fulfilled') {
213
251
  return result.value;
214
252
  } else {
@@ -221,11 +259,11 @@ var SyncBlockProvider = exports.SyncBlockProvider = /*#__PURE__*/function (_Sync
221
259
  }));
222
260
  case 6:
223
261
  case "end":
224
- return _context2.stop();
262
+ return _context3.stop();
225
263
  }
226
- }, _callee2, this);
264
+ }, _callee3, this);
227
265
  }));
228
- function deleteNodesData(_x3) {
266
+ function deleteNodesData(_x4) {
229
267
  return _deleteNodesData.apply(this, arguments);
230
268
  }
231
269
  return deleteNodesData;
@@ -111,6 +111,30 @@ export const getSyncedBlockContent = async ({
111
111
  }
112
112
  return await response.json();
113
113
  };
114
+
115
+ /**
116
+ * Batch retrieves multiple synced blocks by their ARIs.
117
+ *
118
+ * Calls the Block Service API endpoint: `POST /v1/block/batch-retrieve`
119
+ *
120
+ * @param blockAris - Array of block ARIs to retrieve
121
+ * @returns A promise containing arrays of successfully fetched blocks and any errors encountered
122
+ */
123
+ export const batchRetrieveSyncedBlocks = async ({
124
+ blockAris
125
+ }) => {
126
+ const response = await fetchWithRetry(`${BLOCK_SERVICE_API_URL}/block/batch-retrieve`, {
127
+ method: 'POST',
128
+ headers: COMMON_HEADERS,
129
+ body: JSON.stringify({
130
+ blockAris
131
+ })
132
+ });
133
+ if (!response.ok) {
134
+ throw new BlockError(response.status);
135
+ }
136
+ return await response.json();
137
+ };
114
138
  export const deleteSyncedBlock = async ({
115
139
  blockAri
116
140
  }) => {
@@ -1,7 +1,7 @@
1
1
  /* eslint-disable require-unicode-regexp */
2
2
  import { useMemo } from 'react';
3
3
  import { generateBlockAri, generateBlockAriFromReference } from '../../clients/block-service/ari';
4
- import { BlockError, createSyncedBlock, deleteSyncedBlock, getReferenceSyncedBlocks, getSyncedBlockContent, updateReferenceSyncedBlockOnDocument, updateSyncedBlock } from '../../clients/block-service/blockService';
4
+ import { batchRetrieveSyncedBlocks, BlockError, createSyncedBlock, deleteSyncedBlock, getReferenceSyncedBlocks, getSyncedBlockContent, updateReferenceSyncedBlockOnDocument, updateSyncedBlock } from '../../clients/block-service/blockService';
5
5
  import { SyncBlockError } from '../../common/types';
6
6
  import { stringifyError } from '../../utils/errorHandling';
7
7
  const mapBlockError = error => {
@@ -33,10 +33,22 @@ const mapBlockError = error => {
33
33
  // - sourceTitle
34
34
  // - isSynced
35
35
  const convertToSyncBlockData = data => {
36
+ let createdAt;
37
+ if (data.createdAt) {
38
+ try {
39
+ // BE returns microseconds, convert to milliseconds
40
+ // BE should fix this in the future
41
+ createdAt = new Date(data.createdAt / 1000).toISOString();
42
+ } catch (e) {
43
+ // fallback to undefined
44
+ // as we don't want to block the whole process due to invalid date
45
+ createdAt = undefined;
46
+ }
47
+ }
36
48
  return {
37
49
  blockInstanceId: data.blockInstanceId,
38
50
  content: JSON.parse(data.content),
39
- createdAt: new Date(data.createdAt).toISOString(),
51
+ createdAt,
40
52
  createdBy: data.createdBy,
41
53
  product: data.product,
42
54
  resourceId: data.blockAri,
@@ -132,6 +144,110 @@ class BlockServiceADFFetchProvider {
132
144
  };
133
145
  }
134
146
  }
147
+
148
+ /**
149
+ * Extracts the resourceId from a block ARI.
150
+ * Block ARI format: ari:cloud:blocks:<cloudId>:synced-block/<resourceId>
151
+ */
152
+ extractResourceIdFromBlockAri(blockAri) {
153
+ const match = blockAri.match(/ari:cloud:blocks:[^:]+:synced-block\/(.+)$/);
154
+ return match === null || match === void 0 ? void 0 : match[1];
155
+ }
156
+
157
+ /**
158
+ * Batch fetches multiple synced blocks by their resource IDs.
159
+ * @param resourceIds - Array of resource IDs to fetch
160
+ * @returns Array of SyncBlockInstance results
161
+ */
162
+ async batchFetchData(resourceIds) {
163
+ const blockAris = resourceIds.map(resourceId => generateBlockAriFromReference({
164
+ cloudId: this.cloudId,
165
+ resourceId
166
+ }));
167
+
168
+ // Create a set of valid resourceIds for validation
169
+ const validResourceIds = new Set(resourceIds);
170
+
171
+ // Track which resourceIds have been processed
172
+ const processedResourceIds = new Set();
173
+ try {
174
+ const response = await batchRetrieveSyncedBlocks({
175
+ blockAris
176
+ });
177
+ const results = [];
178
+
179
+ // Process successful blocks
180
+ if (response.success) {
181
+ for (const blockContentResponse of response.success) {
182
+ // Extract resourceId from the returned blockAri
183
+ const resourceId = this.extractResourceIdFromBlockAri(blockContentResponse.blockAri);
184
+ if (!resourceId || !validResourceIds.has(resourceId)) {
185
+ continue;
186
+ }
187
+ processedResourceIds.add(resourceId);
188
+ const value = blockContentResponse.content;
189
+ if (!value) {
190
+ results.push({
191
+ error: SyncBlockError.NotFound,
192
+ resourceId
193
+ });
194
+ continue;
195
+ }
196
+ try {
197
+ const syncedBlockData = JSON.parse(value);
198
+ results.push({
199
+ data: {
200
+ content: syncedBlockData,
201
+ resourceId: blockContentResponse.blockAri,
202
+ blockInstanceId: blockContentResponse.blockInstanceId,
203
+ sourceAri: blockContentResponse.sourceAri,
204
+ product: blockContentResponse.product
205
+ },
206
+ resourceId
207
+ });
208
+ } catch {
209
+ results.push({
210
+ error: SyncBlockError.Errored,
211
+ resourceId
212
+ });
213
+ }
214
+ }
215
+ }
216
+
217
+ // Process errors
218
+ if (response.error) {
219
+ for (const errorResponse of response.error) {
220
+ // Extract resourceId from the returned blockAri
221
+ const resourceId = this.extractResourceIdFromBlockAri(errorResponse.blockAri);
222
+ if (!resourceId || !validResourceIds.has(resourceId)) {
223
+ continue;
224
+ }
225
+ processedResourceIds.add(resourceId);
226
+ results.push({
227
+ error: SyncBlockError.Errored,
228
+ resourceId
229
+ });
230
+ }
231
+ }
232
+
233
+ // Ensure all resourceIds have a result - return NotFound for any missing ones
234
+ for (const resourceId of resourceIds) {
235
+ if (!processedResourceIds.has(resourceId)) {
236
+ results.push({
237
+ error: SyncBlockError.NotFound,
238
+ resourceId
239
+ });
240
+ }
241
+ }
242
+ return results;
243
+ } catch (error) {
244
+ // If batch request fails, return error for all resourceIds
245
+ return resourceIds.map(resourceId => ({
246
+ error: error instanceof BlockError ? mapBlockError(error) : SyncBlockError.Errored,
247
+ resourceId
248
+ }));
249
+ }
250
+ }
135
251
  }
136
252
  /**
137
253
  * ADFWriteProvider implementation that writes synced block data to Block Service API
@@ -1,5 +1,6 @@
1
1
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
2
  import { useMemo } from 'react';
3
+ import { fg } from '@atlaskit/platform-feature-flags';
3
4
  import { getPageIdAndTypeFromConfluencePageAri } from '../clients/confluence/ari';
4
5
  import { fetchConfluencePageInfo } from '../clients/confluence/sourceInfo';
5
6
  import { SyncBlockError } from '../common/types';
@@ -46,29 +47,41 @@ export class SyncBlockProvider extends SyncBlockDataProvider {
46
47
  }
47
48
 
48
49
  /**
49
- * Fetch the data from the fetch provider
50
+ * Fetch the data from the fetch provider using batch API
50
51
  *
51
52
  * @param nodes
52
53
  *
53
54
  * @returns Array of {resourceId?: string, error?: string}.
54
55
  */
55
- fetchNodesData(nodes) {
56
+ async fetchNodesData(nodes) {
56
57
  const resourceIdSet = new Set(nodes.map(node => node.attrs.resourceId));
57
58
  const resourceIds = [...resourceIdSet];
58
- return Promise.allSettled(resourceIds.map(resourceId => {
59
- return this.fetchProvider.fetchData(resourceId).then(data => {
60
- return data;
61
- }, () => {
62
- return {
59
+ if (fg('platform_synced_block_dogfooding')) {
60
+ try {
61
+ return await this.fetchProvider.batchFetchData(resourceIds);
62
+ } catch {
63
+ // If batch fetch fails, return error for all resourceIds
64
+ return resourceIds.map(resourceId => ({
63
65
  error: SyncBlockError.Errored,
64
66
  resourceId
65
- };
67
+ }));
68
+ }
69
+ } else {
70
+ return Promise.allSettled(resourceIds.map(resourceId => {
71
+ return this.fetchProvider.fetchData(resourceId).then(data => {
72
+ return data;
73
+ }, () => {
74
+ return {
75
+ error: SyncBlockError.Errored,
76
+ resourceId
77
+ };
78
+ });
79
+ })).then(results => {
80
+ return results.filter(result => {
81
+ return result.status === 'fulfilled';
82
+ }).map(result => result.value);
66
83
  });
67
- })).then(results => {
68
- return results.filter(result => {
69
- return result.status === 'fulfilled';
70
- }).map(result => result.value);
71
- });
84
+ }
72
85
  }
73
86
 
74
87
  /**