@atlaskit/editor-synced-block-provider 8.1.8 → 8.1.10
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 +19 -0
- package/dist/cjs/providers/block-service/blockServiceAPI.js +16 -8
- package/dist/cjs/store-manager/referenceSyncBlockStoreManager.js +4 -2
- package/dist/cjs/store-manager/sourceSyncBlockStoreManager.js +15 -8
- package/dist/cjs/store-manager/syncBlockSubscriptionManager.js +46 -11
- package/dist/cjs/utils/errorHandling.js +65 -11
- package/dist/es2019/providers/block-service/blockServiceAPI.js +16 -8
- package/dist/es2019/store-manager/referenceSyncBlockStoreManager.js +5 -3
- package/dist/es2019/store-manager/sourceSyncBlockStoreManager.js +14 -7
- package/dist/es2019/store-manager/syncBlockSubscriptionManager.js +41 -11
- package/dist/es2019/utils/errorHandling.js +62 -5
- package/dist/esm/providers/block-service/blockServiceAPI.js +16 -8
- package/dist/esm/store-manager/referenceSyncBlockStoreManager.js +5 -3
- package/dist/esm/store-manager/sourceSyncBlockStoreManager.js +16 -9
- package/dist/esm/store-manager/syncBlockSubscriptionManager.js +46 -11
- package/dist/esm/utils/errorHandling.js +64 -10
- package/dist/types/providers/types.d.ts +18 -0
- package/dist/types/store-manager/syncBlockSubscriptionManager.d.ts +4 -5
- package/dist/types/utils/errorHandling.d.ts +44 -5
- package/package.json +5 -2
|
@@ -12,6 +12,25 @@ import { getSourceProductFromResourceIdSafe } from '../utils/utils';
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
export class SyncBlockSubscriptionManager {
|
|
15
|
+
// EDITOR-7861: higher ceiling lets transient WS-gateway drops self-heal
|
|
16
|
+
// before a terminal failure is surfaced.
|
|
17
|
+
getMaxRetryAttempts() {
|
|
18
|
+
return fg('platform_editor_blocks_patch_3') ? SyncBlockSubscriptionManager.MAX_RETRY_ATTEMPTS_HARDENED : SyncBlockSubscriptionManager.MAX_RETRY_ATTEMPTS;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Backoff delay for the given attempt.
|
|
22
|
+
// Gate OFF: pure exponential (1s, 2s, 4s, 8s, 16s).
|
|
23
|
+
// Gate ON (EDITOR-7861): exponential capped at MAX_RETRY_DELAY_MS with equal
|
|
24
|
+
// jitter (capped/2 + random*capped/2) — de-synchronises simultaneous
|
|
25
|
+
// reconnects while guaranteeing a non-zero delay (full jitter could hit 0).
|
|
26
|
+
getReconnectionDelay(attempts) {
|
|
27
|
+
const exponential = SyncBlockSubscriptionManager.INITIAL_RETRY_DELAY_MS * Math.pow(SyncBlockSubscriptionManager.RETRY_BACKOFF_MULTIPLIER, attempts);
|
|
28
|
+
if (!fg('platform_editor_blocks_patch_3')) {
|
|
29
|
+
return exponential;
|
|
30
|
+
}
|
|
31
|
+
const half = Math.min(exponential, SyncBlockSubscriptionManager.MAX_RETRY_DELAY_MS) / 2;
|
|
32
|
+
return Math.round(half + Math.random() * half);
|
|
33
|
+
}
|
|
15
34
|
constructor(deps) {
|
|
16
35
|
_defineProperty(this, "subscriptions", new Map());
|
|
17
36
|
_defineProperty(this, "titleSubscriptions", new Map());
|
|
@@ -23,6 +42,7 @@ export class SyncBlockSubscriptionManager {
|
|
|
23
42
|
// causing the cache to be deleted prematurely. We delay deletion to allow
|
|
24
43
|
// the new component to subscribe and cancel the pending deletion.
|
|
25
44
|
_defineProperty(this, "pendingCacheDeletions", new Map());
|
|
45
|
+
// backoff cap (gate ON)
|
|
26
46
|
_defineProperty(this, "retryAttempts", new Map());
|
|
27
47
|
_defineProperty(this, "pendingRetries", new Map());
|
|
28
48
|
this.deps = deps;
|
|
@@ -237,11 +257,17 @@ export class SyncBlockSubscriptionManager {
|
|
|
237
257
|
const unsubscribe = dataProvider.subscribeToBlockUpdates(resourceId, syncBlockInstance => {
|
|
238
258
|
this.handleGraphQLUpdate(syncBlockInstance);
|
|
239
259
|
}, error => {
|
|
240
|
-
var _this$deps$getFireAna2;
|
|
241
260
|
logException(error, {
|
|
242
261
|
location: 'editor-synced-block-provider/syncBlockSubscriptionManager/graphql-subscription'
|
|
243
262
|
});
|
|
244
|
-
|
|
263
|
+
// EDITOR-7861: a single socket drop is usually transient and
|
|
264
|
+
// recovers on reconnect, so under the gate we don't fire a
|
|
265
|
+
// user-facing error here — it's only surfaced on exhaustion (see
|
|
266
|
+
// scheduleReconnection). Gate OFF keeps the legacy fire-on-drop.
|
|
267
|
+
if (!fg('platform_editor_blocks_patch_3')) {
|
|
268
|
+
var _this$deps$getFireAna2;
|
|
269
|
+
(_this$deps$getFireAna2 = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna2 === void 0 ? void 0 : _this$deps$getFireAna2(fetchErrorPayload(error.message, resourceId, getSourceProductFromResourceIdSafe(resourceId)));
|
|
270
|
+
}
|
|
245
271
|
this.handleSubscriptionTerminated(resourceId);
|
|
246
272
|
}, () => {
|
|
247
273
|
this.handleSubscriptionTerminated(resourceId);
|
|
@@ -270,16 +296,16 @@ export class SyncBlockSubscriptionManager {
|
|
|
270
296
|
}
|
|
271
297
|
}
|
|
272
298
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
* Delay = INITIAL_RETRY_DELAY_MS * (RETRY_BACKOFF_MULTIPLIER ^ attempts)
|
|
276
|
-
* e.g. 1s, 2s, 4s, 8s, 16s
|
|
277
|
-
*/
|
|
299
|
+
// Schedules a reconnection with backoff (see getMaxRetryAttempts /
|
|
300
|
+
// getReconnectionDelay for the gated behaviour).
|
|
278
301
|
scheduleReconnection(resourceId) {
|
|
279
302
|
var _this$retryAttempts$g;
|
|
280
303
|
const attempts = (_this$retryAttempts$g = this.retryAttempts.get(resourceId)) !== null && _this$retryAttempts$g !== void 0 ? _this$retryAttempts$g : 0;
|
|
281
|
-
|
|
304
|
+
const maxAttempts = this.getMaxRetryAttempts();
|
|
305
|
+
if (attempts >= maxAttempts) {
|
|
282
306
|
var _this$deps$getFireAna3;
|
|
307
|
+
// Exhausted all attempts — the only place a WS drop surfaces as a
|
|
308
|
+
// fetch error under the gate (EDITOR-7861).
|
|
283
309
|
const errorMessage = `Subscription reconnection failed after ${attempts} attempts`;
|
|
284
310
|
logException(new Error(errorMessage), {
|
|
285
311
|
location: 'editor-synced-block-provider/syncBlockSubscriptionManager/max-retries-exhausted'
|
|
@@ -287,7 +313,7 @@ export class SyncBlockSubscriptionManager {
|
|
|
287
313
|
(_this$deps$getFireAna3 = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna3 === void 0 ? void 0 : _this$deps$getFireAna3(fetchErrorPayload(errorMessage, resourceId, getSourceProductFromResourceIdSafe(resourceId)));
|
|
288
314
|
return;
|
|
289
315
|
}
|
|
290
|
-
const delay =
|
|
316
|
+
const delay = this.getReconnectionDelay(attempts);
|
|
291
317
|
const timer = setTimeout(() => {
|
|
292
318
|
this.pendingRetries.delete(resourceId);
|
|
293
319
|
|
|
@@ -389,7 +415,11 @@ export class SyncBlockSubscriptionManager {
|
|
|
389
415
|
}
|
|
390
416
|
}
|
|
391
417
|
}
|
|
392
|
-
// Reconnection with exponential backoff
|
|
418
|
+
// Reconnection with exponential backoff.
|
|
393
419
|
_defineProperty(SyncBlockSubscriptionManager, "INITIAL_RETRY_DELAY_MS", 1000);
|
|
394
420
|
_defineProperty(SyncBlockSubscriptionManager, "RETRY_BACKOFF_MULTIPLIER", 2);
|
|
395
|
-
_defineProperty(SyncBlockSubscriptionManager, "MAX_RETRY_ATTEMPTS", 5);
|
|
421
|
+
_defineProperty(SyncBlockSubscriptionManager, "MAX_RETRY_ATTEMPTS", 5);
|
|
422
|
+
// legacy (gate OFF)
|
|
423
|
+
_defineProperty(SyncBlockSubscriptionManager, "MAX_RETRY_ATTEMPTS_HARDENED", 8);
|
|
424
|
+
// gate ON (EDITOR-7861)
|
|
425
|
+
_defineProperty(SyncBlockSubscriptionManager, "MAX_RETRY_DELAY_MS", 30000);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ACTION, ACTION_SUBJECT, EVENT_TYPE, ACTION_SUBJECT_ID } from '@atlaskit/editor-common/analytics';
|
|
2
|
+
import { SyncBlockError } from '../common/types';
|
|
2
3
|
export const stringifyError = error => {
|
|
3
4
|
try {
|
|
4
5
|
return JSON.stringify(error);
|
|
@@ -7,13 +8,63 @@ export const stringifyError = error => {
|
|
|
7
8
|
}
|
|
8
9
|
};
|
|
9
10
|
|
|
11
|
+
/**
|
|
12
|
+
* The set of categorical failure reasons emitted on synced-block operational error
|
|
13
|
+
* events (EDITOR-7796). These let the analytics dashboard break delete/update/create
|
|
14
|
+
* failures down by cause (Block Service / HG / Relay) instead of relying on the
|
|
15
|
+
* free-text `error` blob. Mirrors the {@link SyncBlockError} enum values, plus an
|
|
16
|
+
* `unknown` fallback for unclassifiable errors.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const SYNC_BLOCK_ERROR_REASONS = new Set(Object.values(SyncBlockError));
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Maps a result `error` field (which may be a {@link SyncBlockError} enum value such as
|
|
23
|
+
* `'not_found'`, or an arbitrary stringified blob) to a stable categorical
|
|
24
|
+
* {@link SyncBlockErrorReason} for analytics grouping. Anything that is not a known
|
|
25
|
+
* `SyncBlockError` value collapses to `'unknown'` so the dashboard never has to group on
|
|
26
|
+
* free-text error blobs.
|
|
27
|
+
*/
|
|
28
|
+
export const classifyErrorReason = error => {
|
|
29
|
+
if (error && SYNC_BLOCK_ERROR_REASONS.has(error)) {
|
|
30
|
+
return error;
|
|
31
|
+
}
|
|
32
|
+
return 'unknown';
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Extra, optional analytics attributes describing WHY an operational synced-block
|
|
37
|
+
* action failed. Spread conditionally so we never emit `undefined` keys (EDITOR-7796).
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Builds the {@link ErrorAttributionAttributes} for a failed synced-block operation from
|
|
42
|
+
* the raw result `error` field and optional backend `statusCode`. Returns `undefined`
|
|
43
|
+
* when the `platform_editor_blocks_patch_3` gate is OFF, so the new `reason`/`statusCode`
|
|
44
|
+
* attributes are only emitted once the gate is rolled out (EDITOR-7796).
|
|
45
|
+
*
|
|
46
|
+
* `gateEnabled` is injected by the caller (the store managers evaluate `fg(...)`) so this
|
|
47
|
+
* helper stays pure and trivially unit-testable for both gate states.
|
|
48
|
+
*/
|
|
49
|
+
export const buildErrorAttribution = (gateEnabled, error, statusCode) => {
|
|
50
|
+
if (!gateEnabled) {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
reason: classifyErrorReason(error),
|
|
55
|
+
...(statusCode !== undefined && {
|
|
56
|
+
statusCode
|
|
57
|
+
})
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
|
|
10
61
|
// `sourceProduct` is threaded through every helper so analytics
|
|
11
62
|
// events can be attributed to the source product (`confluence-page` vs `jira-work-item`).
|
|
12
63
|
// All helpers accept it as an optional trailing argument; the spread-only-when-truthy
|
|
13
64
|
// pattern below ensures we never emit `sourceProduct: undefined` (which would dirty the
|
|
14
65
|
// event schema with empty keys).
|
|
15
66
|
|
|
16
|
-
export const getErrorPayload = (actionSubjectId, error, resourceId, sourceProduct) => ({
|
|
67
|
+
export const getErrorPayload = (actionSubjectId, error, resourceId, sourceProduct, attribution) => ({
|
|
17
68
|
action: ACTION.ERROR,
|
|
18
69
|
actionSubject: ACTION_SUBJECT.SYNCED_BLOCK,
|
|
19
70
|
actionSubjectId,
|
|
@@ -25,15 +76,21 @@ export const getErrorPayload = (actionSubjectId, error, resourceId, sourceProduc
|
|
|
25
76
|
}),
|
|
26
77
|
...(sourceProduct && {
|
|
27
78
|
sourceProduct
|
|
79
|
+
}),
|
|
80
|
+
...((attribution === null || attribution === void 0 ? void 0 : attribution.reason) && {
|
|
81
|
+
reason: attribution.reason
|
|
82
|
+
}),
|
|
83
|
+
...((attribution === null || attribution === void 0 ? void 0 : attribution.statusCode) !== undefined && {
|
|
84
|
+
statusCode: attribution.statusCode
|
|
28
85
|
})
|
|
29
86
|
}
|
|
30
87
|
});
|
|
31
88
|
export const fetchErrorPayload = (error, resourceId, sourceProduct) => getErrorPayload(ACTION_SUBJECT_ID.SYNCED_BLOCK_FETCH, error, resourceId, sourceProduct);
|
|
32
89
|
export const getSourceInfoErrorPayload = (error, resourceId, sourceProduct) => getErrorPayload(ACTION_SUBJECT_ID.SYNCED_BLOCK_GET_SOURCE_INFO, error, resourceId, sourceProduct);
|
|
33
|
-
export const updateErrorPayload = (error, resourceId, sourceProduct) => getErrorPayload(ACTION_SUBJECT_ID.SYNCED_BLOCK_UPDATE, error, resourceId, sourceProduct);
|
|
34
|
-
export const updateReferenceErrorPayload = (error, resourceId, sourceProduct) => getErrorPayload(ACTION_SUBJECT_ID.REFERENCE_SYNCED_BLOCK_UPDATE, error, resourceId, sourceProduct);
|
|
35
|
-
export const createErrorPayload = (error, resourceId, sourceProduct) => getErrorPayload(ACTION_SUBJECT_ID.SYNCED_BLOCK_CREATE, error, resourceId, sourceProduct);
|
|
36
|
-
export const deleteErrorPayload = (error, resourceId, sourceProduct) => getErrorPayload(ACTION_SUBJECT_ID.SYNCED_BLOCK_DELETE, error, resourceId, sourceProduct);
|
|
90
|
+
export const updateErrorPayload = (error, resourceId, sourceProduct, attribution) => getErrorPayload(ACTION_SUBJECT_ID.SYNCED_BLOCK_UPDATE, error, resourceId, sourceProduct, attribution);
|
|
91
|
+
export const updateReferenceErrorPayload = (error, resourceId, sourceProduct, attribution) => getErrorPayload(ACTION_SUBJECT_ID.REFERENCE_SYNCED_BLOCK_UPDATE, error, resourceId, sourceProduct, attribution);
|
|
92
|
+
export const createErrorPayload = (error, resourceId, sourceProduct, attribution) => getErrorPayload(ACTION_SUBJECT_ID.SYNCED_BLOCK_CREATE, error, resourceId, sourceProduct, attribution);
|
|
93
|
+
export const deleteErrorPayload = (error, resourceId, sourceProduct, attribution) => getErrorPayload(ACTION_SUBJECT_ID.SYNCED_BLOCK_DELETE, error, resourceId, sourceProduct, attribution);
|
|
37
94
|
export const updateCacheErrorPayload = (error, resourceId, sourceProduct) => getErrorPayload(ACTION_SUBJECT_ID.SYNCED_BLOCK_UPDATE_CACHE, error, resourceId, sourceProduct);
|
|
38
95
|
/**
|
|
39
96
|
* Payload for `SYNCED_BLOCK_SOURCE_INFO_ORPHANED`. Fired when source-info
|
|
@@ -567,7 +567,8 @@ export var writeDataBatch = /*#__PURE__*/function () {
|
|
|
567
567
|
return _context4.abrupt("return", data.map(function (block) {
|
|
568
568
|
return {
|
|
569
569
|
error: mapBlockError(_t6),
|
|
570
|
-
resourceId: block.resourceId
|
|
570
|
+
resourceId: block.resourceId,
|
|
571
|
+
statusCode: _t6.status
|
|
571
572
|
};
|
|
572
573
|
}));
|
|
573
574
|
case 11:
|
|
@@ -913,7 +914,8 @@ var BlockServiceADFWriteProvider = /*#__PURE__*/function () {
|
|
|
913
914
|
}
|
|
914
915
|
return _context8.abrupt("return", {
|
|
915
916
|
error: mapBlockError(_t0),
|
|
916
|
-
resourceId: resourceId
|
|
917
|
+
resourceId: resourceId,
|
|
918
|
+
statusCode: _t0.status
|
|
917
919
|
});
|
|
918
920
|
case 8:
|
|
919
921
|
return _context8.abrupt("return", {
|
|
@@ -993,7 +995,8 @@ var BlockServiceADFWriteProvider = /*#__PURE__*/function () {
|
|
|
993
995
|
}
|
|
994
996
|
return _context9.abrupt("return", {
|
|
995
997
|
error: mapBlockError(_t10),
|
|
996
|
-
resourceId: resourceId
|
|
998
|
+
resourceId: resourceId,
|
|
999
|
+
statusCode: _t10.status
|
|
997
1000
|
});
|
|
998
1001
|
case 8:
|
|
999
1002
|
return _context9.abrupt("return", {
|
|
@@ -1077,7 +1080,8 @@ var BlockServiceADFWriteProvider = /*#__PURE__*/function () {
|
|
|
1077
1080
|
return _context0.abrupt("return", {
|
|
1078
1081
|
resourceId: resourceId,
|
|
1079
1082
|
success: false,
|
|
1080
|
-
error: mapBlockError(_t11)
|
|
1083
|
+
error: mapBlockError(_t11),
|
|
1084
|
+
statusCode: _t11.status
|
|
1081
1085
|
});
|
|
1082
1086
|
case 7:
|
|
1083
1087
|
return _context0.abrupt("return", {
|
|
@@ -1152,7 +1156,8 @@ var BlockServiceADFWriteProvider = /*#__PURE__*/function () {
|
|
|
1152
1156
|
return _context1.abrupt("return", {
|
|
1153
1157
|
resourceId: resourceId,
|
|
1154
1158
|
success: false,
|
|
1155
|
-
error: mapBlockError(_t13)
|
|
1159
|
+
error: mapBlockError(_t13),
|
|
1160
|
+
statusCode: _t13.status
|
|
1156
1161
|
});
|
|
1157
1162
|
case 9:
|
|
1158
1163
|
return _context1.abrupt("return", {
|
|
@@ -1182,7 +1187,8 @@ var BlockServiceADFWriteProvider = /*#__PURE__*/function () {
|
|
|
1182
1187
|
return _context1.abrupt("return", {
|
|
1183
1188
|
resourceId: resourceId,
|
|
1184
1189
|
success: false,
|
|
1185
|
-
error: mapBlockError(_t14)
|
|
1190
|
+
error: mapBlockError(_t14),
|
|
1191
|
+
statusCode: _t14.status
|
|
1186
1192
|
});
|
|
1187
1193
|
case 13:
|
|
1188
1194
|
return _context1.abrupt("return", {
|
|
@@ -1267,7 +1273,8 @@ var BlockServiceADFWriteProvider = /*#__PURE__*/function () {
|
|
|
1267
1273
|
}
|
|
1268
1274
|
return _context10.abrupt("return", {
|
|
1269
1275
|
success: false,
|
|
1270
|
-
error: mapBlockError(_t15)
|
|
1276
|
+
error: mapBlockError(_t15),
|
|
1277
|
+
statusCode: _t15.status
|
|
1271
1278
|
});
|
|
1272
1279
|
case 4:
|
|
1273
1280
|
return _context10.abrupt("return", {
|
|
@@ -1434,7 +1441,8 @@ var BlockServiceADFWriteProvider = /*#__PURE__*/function () {
|
|
|
1434
1441
|
return _context12.abrupt("return", data.map(function (block) {
|
|
1435
1442
|
return {
|
|
1436
1443
|
error: mapBlockError(_t18),
|
|
1437
|
-
resourceId: block.resourceId
|
|
1444
|
+
resourceId: block.resourceId,
|
|
1445
|
+
statusCode: _t18.status
|
|
1438
1446
|
};
|
|
1439
1447
|
}));
|
|
1440
1448
|
case 15:
|
|
@@ -13,7 +13,7 @@ import isEqual from 'lodash/isEqual';
|
|
|
13
13
|
import { logException } from '@atlaskit/editor-common/monitoring';
|
|
14
14
|
import { fg } from '@atlaskit/platform-feature-flags';
|
|
15
15
|
import { SyncBlockError } from '../common/types';
|
|
16
|
-
import { cacheDeletionForcedPayload, fetchErrorPayload, fetchSuccessPayload, getSourceInfoErrorPayload, sourceInfoOrphanedPayload, updateReferenceErrorPayload } from '../utils/errorHandling';
|
|
16
|
+
import { buildErrorAttribution, cacheDeletionForcedPayload, fetchErrorPayload, fetchSuccessPayload, getSourceInfoErrorPayload, sourceInfoOrphanedPayload, updateReferenceErrorPayload } from '../utils/errorHandling';
|
|
17
17
|
import { getFetchExperience, getFetchSourceInfoExperience, getSaveReferenceExperience } from '../utils/experienceTracking';
|
|
18
18
|
import { resolveSyncBlockInstance } from '../utils/resolveSyncBlockInstance';
|
|
19
19
|
import { createSyncBlockNode, getSourceProductFromResourceIdSafe, normalizeSyncBlockJSONContent } from '../utils/utils';
|
|
@@ -1077,7 +1077,7 @@ export var ReferenceSyncBlockStoreManager = /*#__PURE__*/function () {
|
|
|
1077
1077
|
(_this$saveExperience2 = this.saveExperience) === null || _this$saveExperience2 === void 0 || _this$saveExperience2.failure({
|
|
1078
1078
|
reason: updateResult.error || 'Failed to update reference synced blocks on the document'
|
|
1079
1079
|
});
|
|
1080
|
-
(_this$fireAnalyticsEv8 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv8 === void 0 || _this$fireAnalyticsEv8.call(this, updateReferenceErrorPayload(updateResult.error || 'Failed to update reference synced blocks on the document'));
|
|
1080
|
+
(_this$fireAnalyticsEv8 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv8 === void 0 || _this$fireAnalyticsEv8.call(this, updateReferenceErrorPayload(updateResult.error || 'Failed to update reference synced blocks on the document', undefined, undefined, buildErrorAttribution(fg('platform_editor_blocks_patch_3'), updateResult.error, updateResult.statusCode)));
|
|
1081
1081
|
}
|
|
1082
1082
|
_context4.next = 17;
|
|
1083
1083
|
break;
|
|
@@ -1092,7 +1092,9 @@ export var ReferenceSyncBlockStoreManager = /*#__PURE__*/function () {
|
|
|
1092
1092
|
reason: _t3.message
|
|
1093
1093
|
});
|
|
1094
1094
|
// No `resourceId` available in this catch — sourceProduct is intentionally omitted.
|
|
1095
|
-
|
|
1095
|
+
// No structured SyncBlockError/status here, so the attribution `reason` falls back
|
|
1096
|
+
// to `unknown` when the gate is on.
|
|
1097
|
+
(_this$fireAnalyticsEv9 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv9 === void 0 || _this$fireAnalyticsEv9.call(this, updateReferenceErrorPayload(_t3.message, undefined, undefined, buildErrorAttribution(fg('platform_editor_blocks_patch_3'))));
|
|
1096
1098
|
case 17:
|
|
1097
1099
|
_context4.prev = 17;
|
|
1098
1100
|
if (!success) {
|
|
@@ -10,8 +10,9 @@ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length)
|
|
|
10
10
|
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
11
11
|
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
12
12
|
import { logException } from '@atlaskit/editor-common/monitoring';
|
|
13
|
+
import { fg } from '@atlaskit/platform-feature-flags';
|
|
13
14
|
import { SyncBlockError } from '../common/types';
|
|
14
|
-
import { updateErrorPayload, createErrorPayload, deleteErrorPayload, updateCacheErrorPayload, getSourceInfoErrorPayload, updateSuccessPayload, createSuccessPayload, deleteSuccessPayload, fetchReferencesErrorPayload } from '../utils/errorHandling';
|
|
15
|
+
import { updateErrorPayload, createErrorPayload, deleteErrorPayload, updateCacheErrorPayload, getSourceInfoErrorPayload, updateSuccessPayload, createSuccessPayload, deleteSuccessPayload, fetchReferencesErrorPayload, buildErrorAttribution } from '../utils/errorHandling';
|
|
15
16
|
import { getCreateSourceExperience, getDeleteSourceExperience, getSaveSourceExperience, getFetchSourceInfoExperience } from '../utils/experienceTracking';
|
|
16
17
|
import { convertSyncBlockPMNodeToSyncBlockData, getSourceProductFromResourceIdSafe } from '../utils/utils';
|
|
17
18
|
/** Maximum time (ms) flush() will wait for in-flight block creations before proceeding. */
|
|
@@ -158,7 +159,7 @@ export var SourceSyncBlockStoreManager = /*#__PURE__*/function () {
|
|
|
158
159
|
value: (function () {
|
|
159
160
|
var _flush = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
|
|
160
161
|
var _this2 = this;
|
|
161
|
-
var _this$saveExperience, timedOut, timeoutId, timeout, _iterator, _step, resourceId, bodiedSyncBlockNodes, bodiedSyncBlockData, writeResults, _this$saveExperience2, _this$saveExperience3, _this$fireAnalyticsEv2, _this$flushCompletion, _t;
|
|
162
|
+
var _this$saveExperience, timedOut, timeoutId, timeout, _iterator, _step, resourceId, bodiedSyncBlockNodes, bodiedSyncBlockData, writeResults, _this$saveExperience2, _this$saveExperience3, attributionEnabled, _this$fireAnalyticsEv2, _this$flushCompletion, _t;
|
|
162
163
|
return _regeneratorRuntime.wrap(function (_context) {
|
|
163
164
|
while (1) switch (_context.prev = _context.next) {
|
|
164
165
|
case 0:
|
|
@@ -277,11 +278,12 @@ export var SourceSyncBlockStoreManager = /*#__PURE__*/function () {
|
|
|
277
278
|
return _context.abrupt("return", true);
|
|
278
279
|
case 7:
|
|
279
280
|
(_this$saveExperience3 = this.saveExperience) === null || _this$saveExperience3 === void 0 || _this$saveExperience3.failure();
|
|
281
|
+
attributionEnabled = fg('platform_editor_blocks_patch_3');
|
|
280
282
|
writeResults.filter(function (result) {
|
|
281
283
|
return !result.resourceId || result.error;
|
|
282
284
|
}).forEach(function (result) {
|
|
283
285
|
var _this2$fireAnalyticsE2;
|
|
284
|
-
(_this2$fireAnalyticsE2 = _this2.fireAnalyticsEvent) === null || _this2$fireAnalyticsE2 === void 0 || _this2$fireAnalyticsE2.call(_this2, updateErrorPayload(result.error || 'Failed to write data', result.resourceId, getSourceProductFromResourceIdSafe(result.resourceId)));
|
|
286
|
+
(_this2$fireAnalyticsE2 = _this2.fireAnalyticsEvent) === null || _this2$fireAnalyticsE2 === void 0 || _this2$fireAnalyticsE2.call(_this2, updateErrorPayload(result.error || 'Failed to write data', result.resourceId, getSourceProductFromResourceIdSafe(result.resourceId), buildErrorAttribution(attributionEnabled, result.error, result.statusCode)));
|
|
285
287
|
});
|
|
286
288
|
return _context.abrupt("return", false);
|
|
287
289
|
case 8:
|
|
@@ -293,8 +295,9 @@ export var SourceSyncBlockStoreManager = /*#__PURE__*/function () {
|
|
|
293
295
|
logException(_t, {
|
|
294
296
|
location: 'editor-synced-block-provider/sourceSyncBlockStoreManager'
|
|
295
297
|
});
|
|
296
|
-
// Top-level flush failure is not tied to a single resourceId.
|
|
297
|
-
|
|
298
|
+
// Top-level flush failure is not tied to a single resourceId. There is no structured
|
|
299
|
+
// SyncBlockError/status here, so the attribution `reason` falls back to `unknown`.
|
|
300
|
+
(_this$fireAnalyticsEv2 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv2 === void 0 || _this$fireAnalyticsEv2.call(this, updateErrorPayload(_t.message, undefined, undefined, buildErrorAttribution(fg('platform_editor_blocks_patch_3'))));
|
|
298
301
|
return _context.abrupt("return", false);
|
|
299
302
|
case 10:
|
|
300
303
|
_context.prev = 10;
|
|
@@ -469,7 +472,7 @@ export var SourceSyncBlockStoreManager = /*#__PURE__*/function () {
|
|
|
469
472
|
(_this4$createExperien2 = _this4.createExperience) === null || _this4$createExperien2 === void 0 || _this4$createExperien2.failure({
|
|
470
473
|
reason: result.error || 'Failed to create bodied sync block'
|
|
471
474
|
});
|
|
472
|
-
(_this4$fireAnalyticsE = _this4.fireAnalyticsEvent) === null || _this4$fireAnalyticsE === void 0 || _this4$fireAnalyticsE.call(_this4, createErrorPayload(result.error || 'Failed to create bodied sync block', resourceId, getSourceProductFromResourceIdSafe(resourceId)));
|
|
475
|
+
(_this4$fireAnalyticsE = _this4.fireAnalyticsEvent) === null || _this4$fireAnalyticsE === void 0 || _this4$fireAnalyticsE.call(_this4, createErrorPayload(result.error || 'Failed to create bodied sync block', resourceId, getSourceProductFromResourceIdSafe(resourceId), buildErrorAttribution(fg('platform_editor_blocks_patch_3'), result.error, result.statusCode)));
|
|
473
476
|
}
|
|
474
477
|
}).catch(function (error) {
|
|
475
478
|
var _this4$createExperien3, _this4$fireAnalyticsE2;
|
|
@@ -501,7 +504,7 @@ export var SourceSyncBlockStoreManager = /*#__PURE__*/function () {
|
|
|
501
504
|
value: function () {
|
|
502
505
|
var _delete2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2(syncBlockIds, onDelete, onDeleteCompleted, reason) {
|
|
503
506
|
var _this5 = this;
|
|
504
|
-
var _this$deleteExperienc, results, callback, isDeleteSuccessful, _this$deleteExperienc2, _this$deleteExperienc3, _t2;
|
|
507
|
+
var _this$deleteExperienc, results, callback, isDeleteSuccessful, _this$deleteExperienc2, _this$deleteExperienc3, attributionEnabled, attribution, _t2;
|
|
505
508
|
return _regeneratorRuntime.wrap(function (_context2) {
|
|
506
509
|
while (1) switch (_context2.prev = _context2.next) {
|
|
507
510
|
case 0:
|
|
@@ -548,13 +551,14 @@ export var SourceSyncBlockStoreManager = /*#__PURE__*/function () {
|
|
|
548
551
|
_this5.setPendingDeletion(Ids, false);
|
|
549
552
|
};
|
|
550
553
|
(_this$deleteExperienc3 = this.deleteExperience) === null || _this$deleteExperienc3 === void 0 || _this$deleteExperienc3.failure();
|
|
554
|
+
attributionEnabled = fg('platform_editor_blocks_patch_3');
|
|
551
555
|
results.forEach(function (result) {
|
|
552
556
|
if (result.success) {
|
|
553
557
|
var _this5$fireAnalyticsE2;
|
|
554
558
|
(_this5$fireAnalyticsE2 = _this5.fireAnalyticsEvent) === null || _this5$fireAnalyticsE2 === void 0 || _this5$fireAnalyticsE2.call(_this5, deleteSuccessPayload(result.resourceId, getSourceProductFromResourceIdSafe(result.resourceId)));
|
|
555
559
|
} else {
|
|
556
560
|
var _this5$fireAnalyticsE3;
|
|
557
|
-
(_this5$fireAnalyticsE3 = _this5.fireAnalyticsEvent) === null || _this5$fireAnalyticsE3 === void 0 || _this5$fireAnalyticsE3.call(_this5, deleteErrorPayload(result.error || 'Failed to delete synced block', result.resourceId, getSourceProductFromResourceIdSafe(result.resourceId)));
|
|
561
|
+
(_this5$fireAnalyticsE3 = _this5.fireAnalyticsEvent) === null || _this5$fireAnalyticsE3 === void 0 || _this5$fireAnalyticsE3.call(_this5, deleteErrorPayload(result.error || 'Failed to delete synced block', result.resourceId, getSourceProductFromResourceIdSafe(result.resourceId), buildErrorAttribution(attributionEnabled, result.error, result.statusCode)));
|
|
558
562
|
}
|
|
559
563
|
});
|
|
560
564
|
}
|
|
@@ -563,10 +567,13 @@ export var SourceSyncBlockStoreManager = /*#__PURE__*/function () {
|
|
|
563
567
|
case 4:
|
|
564
568
|
_context2.prev = 4;
|
|
565
569
|
_t2 = _context2["catch"](0);
|
|
570
|
+
// Thrown (non-result) failure — no structured SyncBlockError/status is available,
|
|
571
|
+
// so the attribution `reason` falls back to `unknown` when the gate is on.
|
|
572
|
+
attribution = buildErrorAttribution(fg('platform_editor_blocks_patch_3'));
|
|
566
573
|
syncBlockIds.forEach(function (Ids) {
|
|
567
574
|
var _this5$fireAnalyticsE4;
|
|
568
575
|
_this5.setPendingDeletion(Ids, false);
|
|
569
|
-
(_this5$fireAnalyticsE4 = _this5.fireAnalyticsEvent) === null || _this5$fireAnalyticsE4 === void 0 || _this5$fireAnalyticsE4.call(_this5, deleteErrorPayload(_t2.message, Ids.resourceId, getSourceProductFromResourceIdSafe(Ids.resourceId)));
|
|
576
|
+
(_this5$fireAnalyticsE4 = _this5.fireAnalyticsEvent) === null || _this5$fireAnalyticsE4 === void 0 || _this5$fireAnalyticsE4.call(_this5, deleteErrorPayload(_t2.message, Ids.resourceId, getSourceProductFromResourceIdSafe(Ids.resourceId), attribution));
|
|
570
577
|
});
|
|
571
578
|
logException(_t2, {
|
|
572
579
|
location: 'editor-synced-block-provider/sourceSyncBlockStoreManager'
|
|
@@ -31,6 +31,7 @@ export var SyncBlockSubscriptionManager = /*#__PURE__*/function () {
|
|
|
31
31
|
// causing the cache to be deleted prematurely. We delay deletion to allow
|
|
32
32
|
// the new component to subscribe and cancel the pending deletion.
|
|
33
33
|
_defineProperty(this, "pendingCacheDeletions", new Map());
|
|
34
|
+
// backoff cap (gate ON)
|
|
34
35
|
_defineProperty(this, "retryAttempts", new Map());
|
|
35
36
|
_defineProperty(this, "pendingRetries", new Map());
|
|
36
37
|
this.deps = deps;
|
|
@@ -41,6 +42,30 @@ export var SyncBlockSubscriptionManager = /*#__PURE__*/function () {
|
|
|
41
42
|
* that need to read the current subscription state.
|
|
42
43
|
*/
|
|
43
44
|
return _createClass(SyncBlockSubscriptionManager, [{
|
|
45
|
+
key: "getMaxRetryAttempts",
|
|
46
|
+
value:
|
|
47
|
+
// EDITOR-7861: higher ceiling lets transient WS-gateway drops self-heal
|
|
48
|
+
// before a terminal failure is surfaced.
|
|
49
|
+
function getMaxRetryAttempts() {
|
|
50
|
+
return fg('platform_editor_blocks_patch_3') ? SyncBlockSubscriptionManager.MAX_RETRY_ATTEMPTS_HARDENED : SyncBlockSubscriptionManager.MAX_RETRY_ATTEMPTS;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Backoff delay for the given attempt.
|
|
54
|
+
// Gate OFF: pure exponential (1s, 2s, 4s, 8s, 16s).
|
|
55
|
+
// Gate ON (EDITOR-7861): exponential capped at MAX_RETRY_DELAY_MS with equal
|
|
56
|
+
// jitter (capped/2 + random*capped/2) — de-synchronises simultaneous
|
|
57
|
+
// reconnects while guaranteeing a non-zero delay (full jitter could hit 0).
|
|
58
|
+
}, {
|
|
59
|
+
key: "getReconnectionDelay",
|
|
60
|
+
value: function getReconnectionDelay(attempts) {
|
|
61
|
+
var exponential = SyncBlockSubscriptionManager.INITIAL_RETRY_DELAY_MS * Math.pow(SyncBlockSubscriptionManager.RETRY_BACKOFF_MULTIPLIER, attempts);
|
|
62
|
+
if (!fg('platform_editor_blocks_patch_3')) {
|
|
63
|
+
return exponential;
|
|
64
|
+
}
|
|
65
|
+
var half = Math.min(exponential, SyncBlockSubscriptionManager.MAX_RETRY_DELAY_MS) / 2;
|
|
66
|
+
return Math.round(half + Math.random() * half);
|
|
67
|
+
}
|
|
68
|
+
}, {
|
|
44
69
|
key: "getSubscriptions",
|
|
45
70
|
value: function getSubscriptions() {
|
|
46
71
|
return this.subscriptions;
|
|
@@ -267,11 +292,17 @@ export var SyncBlockSubscriptionManager = /*#__PURE__*/function () {
|
|
|
267
292
|
var unsubscribe = dataProvider.subscribeToBlockUpdates(resourceId, function (syncBlockInstance) {
|
|
268
293
|
_this5.handleGraphQLUpdate(syncBlockInstance);
|
|
269
294
|
}, function (error) {
|
|
270
|
-
var _this5$deps$getFireAn;
|
|
271
295
|
logException(error, {
|
|
272
296
|
location: 'editor-synced-block-provider/syncBlockSubscriptionManager/graphql-subscription'
|
|
273
297
|
});
|
|
274
|
-
|
|
298
|
+
// EDITOR-7861: a single socket drop is usually transient and
|
|
299
|
+
// recovers on reconnect, so under the gate we don't fire a
|
|
300
|
+
// user-facing error here — it's only surfaced on exhaustion (see
|
|
301
|
+
// scheduleReconnection). Gate OFF keeps the legacy fire-on-drop.
|
|
302
|
+
if (!fg('platform_editor_blocks_patch_3')) {
|
|
303
|
+
var _this5$deps$getFireAn;
|
|
304
|
+
(_this5$deps$getFireAn = _this5.deps.getFireAnalyticsEvent()) === null || _this5$deps$getFireAn === void 0 || _this5$deps$getFireAn(fetchErrorPayload(error.message, resourceId, getSourceProductFromResourceIdSafe(resourceId)));
|
|
305
|
+
}
|
|
275
306
|
_this5.handleSubscriptionTerminated(resourceId);
|
|
276
307
|
}, function () {
|
|
277
308
|
_this5.handleSubscriptionTerminated(resourceId);
|
|
@@ -302,19 +333,19 @@ export var SyncBlockSubscriptionManager = /*#__PURE__*/function () {
|
|
|
302
333
|
}
|
|
303
334
|
}
|
|
304
335
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
* Delay = INITIAL_RETRY_DELAY_MS * (RETRY_BACKOFF_MULTIPLIER ^ attempts)
|
|
308
|
-
* e.g. 1s, 2s, 4s, 8s, 16s
|
|
309
|
-
*/
|
|
336
|
+
// Schedules a reconnection with backoff (see getMaxRetryAttempts /
|
|
337
|
+
// getReconnectionDelay for the gated behaviour).
|
|
310
338
|
}, {
|
|
311
339
|
key: "scheduleReconnection",
|
|
312
340
|
value: function scheduleReconnection(resourceId) {
|
|
313
341
|
var _this$retryAttempts$g,
|
|
314
342
|
_this6 = this;
|
|
315
343
|
var attempts = (_this$retryAttempts$g = this.retryAttempts.get(resourceId)) !== null && _this$retryAttempts$g !== void 0 ? _this$retryAttempts$g : 0;
|
|
316
|
-
|
|
344
|
+
var maxAttempts = this.getMaxRetryAttempts();
|
|
345
|
+
if (attempts >= maxAttempts) {
|
|
317
346
|
var _this$deps$getFireAna;
|
|
347
|
+
// Exhausted all attempts — the only place a WS drop surfaces as a
|
|
348
|
+
// fetch error under the gate (EDITOR-7861).
|
|
318
349
|
var errorMessage = "Subscription reconnection failed after ".concat(attempts, " attempts");
|
|
319
350
|
logException(new Error(errorMessage), {
|
|
320
351
|
location: 'editor-synced-block-provider/syncBlockSubscriptionManager/max-retries-exhausted'
|
|
@@ -322,7 +353,7 @@ export var SyncBlockSubscriptionManager = /*#__PURE__*/function () {
|
|
|
322
353
|
(_this$deps$getFireAna = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna === void 0 || _this$deps$getFireAna(fetchErrorPayload(errorMessage, resourceId, getSourceProductFromResourceIdSafe(resourceId)));
|
|
323
354
|
return;
|
|
324
355
|
}
|
|
325
|
-
var delay =
|
|
356
|
+
var delay = this.getReconnectionDelay(attempts);
|
|
326
357
|
var timer = setTimeout(function () {
|
|
327
358
|
_this6.pendingRetries.delete(resourceId);
|
|
328
359
|
|
|
@@ -480,7 +511,11 @@ export var SyncBlockSubscriptionManager = /*#__PURE__*/function () {
|
|
|
480
511
|
}
|
|
481
512
|
}]);
|
|
482
513
|
}();
|
|
483
|
-
// Reconnection with exponential backoff
|
|
514
|
+
// Reconnection with exponential backoff.
|
|
484
515
|
_defineProperty(SyncBlockSubscriptionManager, "INITIAL_RETRY_DELAY_MS", 1000);
|
|
485
516
|
_defineProperty(SyncBlockSubscriptionManager, "RETRY_BACKOFF_MULTIPLIER", 2);
|
|
486
|
-
_defineProperty(SyncBlockSubscriptionManager, "MAX_RETRY_ATTEMPTS", 5);
|
|
517
|
+
_defineProperty(SyncBlockSubscriptionManager, "MAX_RETRY_ATTEMPTS", 5);
|
|
518
|
+
// legacy (gate OFF)
|
|
519
|
+
_defineProperty(SyncBlockSubscriptionManager, "MAX_RETRY_ATTEMPTS_HARDENED", 8);
|
|
520
|
+
// gate ON (EDITOR-7861)
|
|
521
|
+
_defineProperty(SyncBlockSubscriptionManager, "MAX_RETRY_DELAY_MS", 30000);
|
|
@@ -2,6 +2,7 @@ import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
|
2
2
|
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
3
3
|
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
4
4
|
import { ACTION, ACTION_SUBJECT, EVENT_TYPE, ACTION_SUBJECT_ID } from '@atlaskit/editor-common/analytics';
|
|
5
|
+
import { SyncBlockError } from '../common/types';
|
|
5
6
|
export var stringifyError = function stringifyError(error) {
|
|
6
7
|
try {
|
|
7
8
|
return JSON.stringify(error);
|
|
@@ -10,24 +11,77 @@ export var stringifyError = function stringifyError(error) {
|
|
|
10
11
|
}
|
|
11
12
|
};
|
|
12
13
|
|
|
14
|
+
/**
|
|
15
|
+
* The set of categorical failure reasons emitted on synced-block operational error
|
|
16
|
+
* events (EDITOR-7796). These let the analytics dashboard break delete/update/create
|
|
17
|
+
* failures down by cause (Block Service / HG / Relay) instead of relying on the
|
|
18
|
+
* free-text `error` blob. Mirrors the {@link SyncBlockError} enum values, plus an
|
|
19
|
+
* `unknown` fallback for unclassifiable errors.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
var SYNC_BLOCK_ERROR_REASONS = new Set(Object.values(SyncBlockError));
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Maps a result `error` field (which may be a {@link SyncBlockError} enum value such as
|
|
26
|
+
* `'not_found'`, or an arbitrary stringified blob) to a stable categorical
|
|
27
|
+
* {@link SyncBlockErrorReason} for analytics grouping. Anything that is not a known
|
|
28
|
+
* `SyncBlockError` value collapses to `'unknown'` so the dashboard never has to group on
|
|
29
|
+
* free-text error blobs.
|
|
30
|
+
*/
|
|
31
|
+
export var classifyErrorReason = function classifyErrorReason(error) {
|
|
32
|
+
if (error && SYNC_BLOCK_ERROR_REASONS.has(error)) {
|
|
33
|
+
return error;
|
|
34
|
+
}
|
|
35
|
+
return 'unknown';
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Extra, optional analytics attributes describing WHY an operational synced-block
|
|
40
|
+
* action failed. Spread conditionally so we never emit `undefined` keys (EDITOR-7796).
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Builds the {@link ErrorAttributionAttributes} for a failed synced-block operation from
|
|
45
|
+
* the raw result `error` field and optional backend `statusCode`. Returns `undefined`
|
|
46
|
+
* when the `platform_editor_blocks_patch_3` gate is OFF, so the new `reason`/`statusCode`
|
|
47
|
+
* attributes are only emitted once the gate is rolled out (EDITOR-7796).
|
|
48
|
+
*
|
|
49
|
+
* `gateEnabled` is injected by the caller (the store managers evaluate `fg(...)`) so this
|
|
50
|
+
* helper stays pure and trivially unit-testable for both gate states.
|
|
51
|
+
*/
|
|
52
|
+
export var buildErrorAttribution = function buildErrorAttribution(gateEnabled, error, statusCode) {
|
|
53
|
+
if (!gateEnabled) {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
return _objectSpread({
|
|
57
|
+
reason: classifyErrorReason(error)
|
|
58
|
+
}, statusCode !== undefined && {
|
|
59
|
+
statusCode: statusCode
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
|
|
13
63
|
// `sourceProduct` is threaded through every helper so analytics
|
|
14
64
|
// events can be attributed to the source product (`confluence-page` vs `jira-work-item`).
|
|
15
65
|
// All helpers accept it as an optional trailing argument; the spread-only-when-truthy
|
|
16
66
|
// pattern below ensures we never emit `sourceProduct: undefined` (which would dirty the
|
|
17
67
|
// event schema with empty keys).
|
|
18
68
|
|
|
19
|
-
export var getErrorPayload = function getErrorPayload(actionSubjectId, error, resourceId, sourceProduct) {
|
|
69
|
+
export var getErrorPayload = function getErrorPayload(actionSubjectId, error, resourceId, sourceProduct, attribution) {
|
|
20
70
|
return {
|
|
21
71
|
action: ACTION.ERROR,
|
|
22
72
|
actionSubject: ACTION_SUBJECT.SYNCED_BLOCK,
|
|
23
73
|
actionSubjectId: actionSubjectId,
|
|
24
74
|
eventType: EVENT_TYPE.OPERATIONAL,
|
|
25
|
-
attributes: _objectSpread(_objectSpread({
|
|
75
|
+
attributes: _objectSpread(_objectSpread(_objectSpread(_objectSpread({
|
|
26
76
|
error: error
|
|
27
77
|
}, resourceId && {
|
|
28
78
|
resourceId: resourceId
|
|
29
79
|
}), sourceProduct && {
|
|
30
80
|
sourceProduct: sourceProduct
|
|
81
|
+
}), (attribution === null || attribution === void 0 ? void 0 : attribution.reason) && {
|
|
82
|
+
reason: attribution.reason
|
|
83
|
+
}), (attribution === null || attribution === void 0 ? void 0 : attribution.statusCode) !== undefined && {
|
|
84
|
+
statusCode: attribution.statusCode
|
|
31
85
|
})
|
|
32
86
|
};
|
|
33
87
|
};
|
|
@@ -37,17 +91,17 @@ export var fetchErrorPayload = function fetchErrorPayload(error, resourceId, sou
|
|
|
37
91
|
export var getSourceInfoErrorPayload = function getSourceInfoErrorPayload(error, resourceId, sourceProduct) {
|
|
38
92
|
return getErrorPayload(ACTION_SUBJECT_ID.SYNCED_BLOCK_GET_SOURCE_INFO, error, resourceId, sourceProduct);
|
|
39
93
|
};
|
|
40
|
-
export var updateErrorPayload = function updateErrorPayload(error, resourceId, sourceProduct) {
|
|
41
|
-
return getErrorPayload(ACTION_SUBJECT_ID.SYNCED_BLOCK_UPDATE, error, resourceId, sourceProduct);
|
|
94
|
+
export var updateErrorPayload = function updateErrorPayload(error, resourceId, sourceProduct, attribution) {
|
|
95
|
+
return getErrorPayload(ACTION_SUBJECT_ID.SYNCED_BLOCK_UPDATE, error, resourceId, sourceProduct, attribution);
|
|
42
96
|
};
|
|
43
|
-
export var updateReferenceErrorPayload = function updateReferenceErrorPayload(error, resourceId, sourceProduct) {
|
|
44
|
-
return getErrorPayload(ACTION_SUBJECT_ID.REFERENCE_SYNCED_BLOCK_UPDATE, error, resourceId, sourceProduct);
|
|
97
|
+
export var updateReferenceErrorPayload = function updateReferenceErrorPayload(error, resourceId, sourceProduct, attribution) {
|
|
98
|
+
return getErrorPayload(ACTION_SUBJECT_ID.REFERENCE_SYNCED_BLOCK_UPDATE, error, resourceId, sourceProduct, attribution);
|
|
45
99
|
};
|
|
46
|
-
export var createErrorPayload = function createErrorPayload(error, resourceId, sourceProduct) {
|
|
47
|
-
return getErrorPayload(ACTION_SUBJECT_ID.SYNCED_BLOCK_CREATE, error, resourceId, sourceProduct);
|
|
100
|
+
export var createErrorPayload = function createErrorPayload(error, resourceId, sourceProduct, attribution) {
|
|
101
|
+
return getErrorPayload(ACTION_SUBJECT_ID.SYNCED_BLOCK_CREATE, error, resourceId, sourceProduct, attribution);
|
|
48
102
|
};
|
|
49
|
-
export var deleteErrorPayload = function deleteErrorPayload(error, resourceId, sourceProduct) {
|
|
50
|
-
return getErrorPayload(ACTION_SUBJECT_ID.SYNCED_BLOCK_DELETE, error, resourceId, sourceProduct);
|
|
103
|
+
export var deleteErrorPayload = function deleteErrorPayload(error, resourceId, sourceProduct, attribution) {
|
|
104
|
+
return getErrorPayload(ACTION_SUBJECT_ID.SYNCED_BLOCK_DELETE, error, resourceId, sourceProduct, attribution);
|
|
51
105
|
};
|
|
52
106
|
export var updateCacheErrorPayload = function updateCacheErrorPayload(error, resourceId, sourceProduct) {
|
|
53
107
|
return getErrorPayload(ACTION_SUBJECT_ID.SYNCED_BLOCK_UPDATE_CACHE, error, resourceId, sourceProduct);
|