@atlaskit/editor-synced-block-provider 8.1.10 → 8.3.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 +30 -0
- package/dist/cjs/common/types.js +63 -2
- package/dist/cjs/hooks/useFetchSyncBlockData.js +49 -15
- package/dist/cjs/providers/block-service/blockServiceAPI.js +7 -4
- package/dist/cjs/store-manager/referenceSyncBlockStoreManager.js +42 -8
- package/dist/cjs/store-manager/syncBlockBatchFetcher.js +29 -1
- package/dist/cjs/store-manager/syncBlockProviderFactoryManager.js +4 -3
- package/dist/cjs/store-manager/syncBlockSubscriptionManager.js +11 -4
- package/dist/cjs/utils/errorHandling.js +152 -6
- package/dist/es2019/common/types.js +47 -1
- package/dist/es2019/hooks/useFetchSyncBlockData.js +38 -5
- package/dist/es2019/providers/block-service/blockServiceAPI.js +11 -3
- package/dist/es2019/store-manager/referenceSyncBlockStoreManager.js +36 -7
- package/dist/es2019/store-manager/syncBlockBatchFetcher.js +28 -2
- package/dist/es2019/store-manager/syncBlockProviderFactoryManager.js +5 -4
- package/dist/es2019/store-manager/syncBlockSubscriptionManager.js +12 -5
- package/dist/es2019/utils/errorHandling.js +167 -22
- package/dist/esm/common/types.js +62 -1
- package/dist/esm/hooks/useFetchSyncBlockData.js +51 -17
- package/dist/esm/providers/block-service/blockServiceAPI.js +7 -4
- package/dist/esm/store-manager/referenceSyncBlockStoreManager.js +44 -10
- package/dist/esm/store-manager/syncBlockBatchFetcher.js +30 -2
- package/dist/esm/store-manager/syncBlockProviderFactoryManager.js +5 -4
- package/dist/esm/store-manager/syncBlockSubscriptionManager.js +12 -5
- package/dist/esm/utils/errorHandling.js +149 -5
- package/dist/types/common/types.d.ts +29 -0
- package/dist/types/providers/types.d.ts +8 -0
- package/dist/types/store-manager/referenceSyncBlockStoreManager.d.ts +8 -0
- package/dist/types/store-manager/syncBlockBatchFetcher.d.ts +7 -0
- package/dist/types/utils/errorHandling.d.ts +94 -2
- package/package.json +3 -3
|
@@ -4,7 +4,10 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
|
|
|
4
4
|
Object.defineProperty(exports, "__esModule", {
|
|
5
5
|
value: true
|
|
6
6
|
});
|
|
7
|
-
exports.
|
|
7
|
+
exports.fetchSuccessPayload = exports.fetchReferencesErrorPayload = exports.fetchErrorPayload = exports.deleteSuccessPayload = exports.deleteErrorPayload = exports.createSuccessPayloadNew = exports.createSuccessPayload = exports.createErrorPayload = exports.classifyFetchErrorReason = exports.classifyErrorReason = exports.cacheDeletionForcedPayload = exports.buildFetchErrorAttribution = exports.buildErrorAttribution = exports.FETCH_BENIGN_REASONS = void 0;
|
|
8
|
+
exports.getErrorPayload = getErrorPayload;
|
|
9
|
+
exports.updateSuccessPayload = exports.updateReferenceErrorPayload = exports.updateErrorPayload = exports.updateCacheErrorPayload = exports.stringifyError = exports.sourceInfoOrphanedPayload = exports.getSourceInfoErrorPayload = void 0;
|
|
10
|
+
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
|
|
8
11
|
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
9
12
|
var _analytics = require("@atlaskit/editor-common/analytics");
|
|
10
13
|
var _types = require("../common/types");
|
|
@@ -67,19 +70,155 @@ var buildErrorAttribution = exports.buildErrorAttribution = function buildErrorA
|
|
|
67
70
|
});
|
|
68
71
|
};
|
|
69
72
|
|
|
73
|
+
/**
|
|
74
|
+
* The set of categorical failure reasons emitted on synced-block fetch/subscribe
|
|
75
|
+
* operational error events (EDITOR-7862). Extends the write-path
|
|
76
|
+
* {@link SyncBlockErrorReason} set with read-path-specific buckets so the analytics
|
|
77
|
+
* dashboard can break fetch failures down by cause instead of regex-matching the
|
|
78
|
+
* free-text `error` blob.
|
|
79
|
+
*
|
|
80
|
+
* The read path surfaces several causes the write-path `SyncBlockError` enum does not
|
|
81
|
+
* model (benign source-state transitions, permission denials, WebSocket lifecycle, and
|
|
82
|
+
* client-side readiness errors), so those are added here. Known `SyncBlockError` enum
|
|
83
|
+
* values (`not_found`, `forbidden`, ...) still pass through unchanged.
|
|
84
|
+
*/
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Fetch reasons that represent benign (working-as-designed) outcomes rather than genuine
|
|
88
|
+
* system failures. The dashboard uses this to compute a "true error rate" by excluding
|
|
89
|
+
* benign reasons via the structured `reason` attribute instead of brittle free-text regex
|
|
90
|
+
* (EDITOR-7862).
|
|
91
|
+
*/
|
|
92
|
+
var FETCH_BENIGN_REASONS = exports.FETCH_BENIGN_REASONS = new Set(['source_deleted', 'source_unpublished', 'source_unsynced', 'source_not_found', 'permission_denied', 'unauthenticated',
|
|
93
|
+
// Mirror the benign write-path enum equivalents so already-classified errors are
|
|
94
|
+
// treated consistently.
|
|
95
|
+
'not_found', 'forbidden', 'unpublished'
|
|
96
|
+
// NOTE: `entity_not_found` and `offline` are intentionally NOT benign.
|
|
97
|
+
// - `entity_not_found` is retried up to ENTITY_NOT_FOUND_MAX_RETRIES (analytics are
|
|
98
|
+
// suppressed during retries); by the time the error event fires, retries are
|
|
99
|
+
// exhausted and it is a genuine failure.
|
|
100
|
+
// - `offline` is genuine client connectivity loss, not a working-as-designed outcome.
|
|
101
|
+
// Both still emit `reason` (so they can be inspected) but are counted as true errors.
|
|
102
|
+
]);
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Ordered list of substring matchers mapping known free-text fetch/subscribe error
|
|
106
|
+
* strings to a categorical {@link SyncBlockFetchErrorReason}. Each entry is a list of
|
|
107
|
+
* lowercase needles; if ANY needle is found (case-insensitively) in the raw `error`
|
|
108
|
+
* string, the entry's reason is used. Order matters: more specific entries must precede
|
|
109
|
+
* more general ones.
|
|
110
|
+
*
|
|
111
|
+
* Plain `String.includes` substring matching is used deliberately instead of `RegExp`:
|
|
112
|
+
* it sidesteps the `require-unicode-regexp` lint rule AND the declaration build's lower
|
|
113
|
+
* compile target (which rejects the regex `u` flag with TS1501), while being faster and
|
|
114
|
+
* easier to read. Doing this classification in code (rather than in dashboard SQL) keeps
|
|
115
|
+
* the buckets versioned with the source strings that produce them (EDITOR-7862).
|
|
116
|
+
*/
|
|
117
|
+
var FETCH_REASON_MATCHERS = [
|
|
118
|
+
// Benign: source-state transitions (deletionReason values + free text).
|
|
119
|
+
[['source-document-deleted'], 'source_deleted'], [['source-block-deleted'], 'source_deleted'], [['source-block-unpublished'], 'source_unpublished'], [['source-block-unsynced'], 'source_unsynced'], [['does not exist'], 'source_not_found'], [['unpublished'], 'source_unpublished'],
|
|
120
|
+
// Working-as-designed permission outcomes.
|
|
121
|
+
[['unauthenticated'], 'unauthenticated'], [['not permitted to read synced block'], 'permission_denied'], [['bulk permission check failed'], 'permission_denied'], [['permission denied'], 'permission_denied'],
|
|
122
|
+
// Genuine system failures.
|
|
123
|
+
[['reconnection failed after'], 'websocket_exhausted'],
|
|
124
|
+
// `reconnect to the subscription` (not a bare `reconnect`) avoids false positives on
|
|
125
|
+
// unrelated DB/GraphQL "reconnect" messages while still matching the WS-drop string.
|
|
126
|
+
[['websocket', 'reconnect to the subscription', 'subscription terminated'], 'websocket_drop'], [['429', 'rate limit', 'rate-limit', 'ratelimit'], 'rate_limited'],
|
|
127
|
+
// `econnrefused`/`econnreset` (not a bare `econn`, which would also match `reconnect`).
|
|
128
|
+
[['network', 'failed to fetch', 'econnrefused', 'econnreset', 'timed out', 'timeout'], 'network'], [['data provider not set'], 'data_provider_not_ready']];
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Maps a fetch/subscribe `error` field — which may be a {@link SyncBlockError} enum
|
|
132
|
+
* value, a {@link DeletionReason} value, or an arbitrary free-text/JSON blob — to a
|
|
133
|
+
* stable categorical {@link SyncBlockFetchErrorReason} for analytics grouping.
|
|
134
|
+
*
|
|
135
|
+
* Resolution order:
|
|
136
|
+
* 1. Known `SyncBlockError` enum value → passed through (via {@link classifyErrorReason}).
|
|
137
|
+
* 2. Known free-text substring → mapped to a fetch-specific bucket.
|
|
138
|
+
* 3. Anything else (including opaque blobs like `errored`-only payloads or
|
|
139
|
+
* `ErrorEvent: "undefined"`) → `'unknown'`, so the dashboard never groups on free text.
|
|
140
|
+
*
|
|
141
|
+
* Note: the bare string `'errored'` IS a `SyncBlockError` enum value and therefore
|
|
142
|
+
* classifies as `'errored'` (not `'unknown'`); only genuinely unrecognised strings
|
|
143
|
+
* collapse to `'unknown'`.
|
|
144
|
+
*/
|
|
145
|
+
var classifyFetchErrorReason = exports.classifyFetchErrorReason = function classifyFetchErrorReason(error) {
|
|
146
|
+
var enumReason = classifyErrorReason(error);
|
|
147
|
+
if (enumReason !== 'unknown') {
|
|
148
|
+
return enumReason;
|
|
149
|
+
}
|
|
150
|
+
if (error) {
|
|
151
|
+
var haystack = error.toLowerCase();
|
|
152
|
+
for (var _i = 0, _FETCH_REASON_MATCHER = FETCH_REASON_MATCHERS; _i < _FETCH_REASON_MATCHER.length; _i++) {
|
|
153
|
+
var _FETCH_REASON_MATCHER2 = (0, _slicedToArray2.default)(_FETCH_REASON_MATCHER[_i], 2),
|
|
154
|
+
needles = _FETCH_REASON_MATCHER2[0],
|
|
155
|
+
reason = _FETCH_REASON_MATCHER2[1];
|
|
156
|
+
if (needles.some(function (needle) {
|
|
157
|
+
return haystack.includes(needle);
|
|
158
|
+
})) {
|
|
159
|
+
return reason;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return 'unknown';
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Extra, optional analytics attributes describing WHY a fetch/subscribe synced-block
|
|
168
|
+
* action failed. Spread conditionally so we never emit `undefined` keys (EDITOR-7862).
|
|
169
|
+
*
|
|
170
|
+
* Adds `benign` on top of the shared {@link ErrorAttributionAttributes} so the dashboard
|
|
171
|
+
* can compute a true error rate (`genuine / total`) without any free-text regex.
|
|
172
|
+
*/
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Builds the {@link FetchErrorAttributionAttributes} for a failed fetch/subscribe
|
|
176
|
+
* synced-block operation from the raw `error` field and optional backend `statusCode`.
|
|
177
|
+
* Returns `undefined` when the `platform_editor_blocks_patch_3` gate is OFF, so the new
|
|
178
|
+
* `reason`/`statusCode`/`benign` attributes are only emitted once the gate is rolled out
|
|
179
|
+
* (EDITOR-7862). The existing free-text `error` attribute is always left unchanged.
|
|
180
|
+
*
|
|
181
|
+
* `gateEnabled` is injected by the caller (the store managers evaluate `fg(...)`) so this
|
|
182
|
+
* helper stays pure and trivially unit-testable for both gate states.
|
|
183
|
+
*/
|
|
184
|
+
var buildFetchErrorAttribution = exports.buildFetchErrorAttribution = function buildFetchErrorAttribution(gateEnabled, error, statusCode) {
|
|
185
|
+
if (!gateEnabled) {
|
|
186
|
+
return undefined;
|
|
187
|
+
}
|
|
188
|
+
var reason = classifyFetchErrorReason(error);
|
|
189
|
+
return _objectSpread({
|
|
190
|
+
reason: reason,
|
|
191
|
+
benign: FETCH_BENIGN_REASONS.has(reason)
|
|
192
|
+
}, statusCode !== undefined && {
|
|
193
|
+
statusCode: statusCode
|
|
194
|
+
});
|
|
195
|
+
};
|
|
196
|
+
|
|
70
197
|
// `sourceProduct` is threaded through every helper so analytics
|
|
71
198
|
// events can be attributed to the source product (`confluence-page` vs `jira-work-item`).
|
|
72
199
|
// All helpers accept it as an optional trailing argument; the spread-only-when-truthy
|
|
73
200
|
// pattern below ensures we never emit `sourceProduct: undefined` (which would dirty the
|
|
74
201
|
// event schema with empty keys).
|
|
75
202
|
|
|
76
|
-
|
|
203
|
+
/**
|
|
204
|
+
* Shared operational ERROR payload builder for synced-block events.
|
|
205
|
+
*
|
|
206
|
+
* Overloaded so the emitted `reason` type stays accurate per path (raised in review on
|
|
207
|
+
* EDITOR-7862): fetch/subscribe callers pass a {@link FetchErrorAttributionAttributes}
|
|
208
|
+
* (discriminated by its required `benign` field) and get the wider
|
|
209
|
+
* {@link SyncBlockFetchErrorReason}; write-path callers pass an
|
|
210
|
+
* {@link ErrorAttributionAttributes} (or nothing) and keep the narrower
|
|
211
|
+
* {@link SyncBlockErrorReason}, so write-path payloads never claim to carry fetch-only
|
|
212
|
+
* buckets like `source_deleted` they never emit.
|
|
213
|
+
*/
|
|
214
|
+
|
|
215
|
+
function getErrorPayload(actionSubjectId, error, resourceId, sourceProduct, attribution) {
|
|
77
216
|
return {
|
|
78
217
|
action: _analytics.ACTION.ERROR,
|
|
79
218
|
actionSubject: _analytics.ACTION_SUBJECT.SYNCED_BLOCK,
|
|
80
219
|
actionSubjectId: actionSubjectId,
|
|
81
220
|
eventType: _analytics.EVENT_TYPE.OPERATIONAL,
|
|
82
|
-
attributes: _objectSpread(_objectSpread(_objectSpread(_objectSpread({
|
|
221
|
+
attributes: _objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread({
|
|
83
222
|
error: error
|
|
84
223
|
}, resourceId && {
|
|
85
224
|
resourceId: resourceId
|
|
@@ -89,11 +228,18 @@ var getErrorPayload = exports.getErrorPayload = function getErrorPayload(actionS
|
|
|
89
228
|
reason: attribution.reason
|
|
90
229
|
}), (attribution === null || attribution === void 0 ? void 0 : attribution.statusCode) !== undefined && {
|
|
91
230
|
statusCode: attribution.statusCode
|
|
231
|
+
}), attribution && 'benign' in attribution && {
|
|
232
|
+
benign: attribution.benign
|
|
92
233
|
})
|
|
93
234
|
};
|
|
94
|
-
}
|
|
95
|
-
var fetchErrorPayload = exports.fetchErrorPayload = function fetchErrorPayload(error, resourceId, sourceProduct) {
|
|
96
|
-
return
|
|
235
|
+
}
|
|
236
|
+
var fetchErrorPayload = exports.fetchErrorPayload = function fetchErrorPayload(error, resourceId, sourceProduct, attribution) {
|
|
237
|
+
return (
|
|
238
|
+
// Branch on attribution presence so each call resolves to a concrete overload: with
|
|
239
|
+
// attribution it hits the fetch overload (wider `reason`); without it (gate OFF) it
|
|
240
|
+
// hits the no-attribution overload. Both produce a fetch event regardless.
|
|
241
|
+
attribution ? getErrorPayload(_analytics.ACTION_SUBJECT_ID.SYNCED_BLOCK_FETCH, error, resourceId, sourceProduct, attribution) : getErrorPayload(_analytics.ACTION_SUBJECT_ID.SYNCED_BLOCK_FETCH, error, resourceId, sourceProduct)
|
|
242
|
+
);
|
|
97
243
|
};
|
|
98
244
|
var getSourceInfoErrorPayload = exports.getSourceInfoErrorPayload = function getSourceInfoErrorPayload(error, resourceId, sourceProduct) {
|
|
99
245
|
return getErrorPayload(_analytics.ACTION_SUBJECT_ID.SYNCED_BLOCK_GET_SOURCE_INFO, error, resourceId, sourceProduct);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
1
2
|
export let SyncBlockError = /*#__PURE__*/function (SyncBlockError) {
|
|
2
3
|
SyncBlockError["Errored"] = "errored";
|
|
3
4
|
SyncBlockError["NotFound"] = "not_found";
|
|
@@ -16,4 +17,49 @@ export let SyncBlockError = /*#__PURE__*/function (SyncBlockError) {
|
|
|
16
17
|
// block does not exist on this site (e.g. cross-site reference or hard deleted)
|
|
17
18
|
SyncBlockError["EntityNotFound"] = "entity_not_found";
|
|
18
19
|
return SyncBlockError;
|
|
19
|
-
}({});
|
|
20
|
+
}({});
|
|
21
|
+
/**
|
|
22
|
+
* Helpers to distinguish "data provider not ready / torn down" from a genuine
|
|
23
|
+
* fetch/subscribe failure (EDITOR-7860). On Jira the provider is wired
|
|
24
|
+
* asynchronously and `destroy()` nulls it on orphaned managers, so queued/
|
|
25
|
+
* in-flight ops throw `Data provider not set` — previously mis-logged as a real
|
|
26
|
+
* error. These let throw and catch sites agree on one non-string-matched signal
|
|
27
|
+
* so the residual false errors are suppressed. Gated by
|
|
28
|
+
* `platform_editor_blocks_patch_3`.
|
|
29
|
+
*
|
|
30
|
+
* NB: these intentionally live here rather than in a dedicated module to avoid
|
|
31
|
+
* adding a downstream file to consuming Jira packages' Thunderstone complexity.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/** Legacy message — kept identical for gate-off and historical events. */
|
|
35
|
+
export const PROVIDER_NOT_READY_MESSAGE = 'Data provider not set';
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Thrown when a fetch/subscribe runs against a manager whose provider is not
|
|
39
|
+
* (yet) available or torn down. Keeps the legacy message + name so string
|
|
40
|
+
* matchers still work, while new code uses {@link isProviderNotReadyError}.
|
|
41
|
+
*/
|
|
42
|
+
export class ProviderNotReadyError extends Error {
|
|
43
|
+
constructor(message = PROVIDER_NOT_READY_MESSAGE) {
|
|
44
|
+
super(message);
|
|
45
|
+
_defineProperty(this, "isProviderNotReadyError", true);
|
|
46
|
+
this.name = 'ProviderNotReadyError';
|
|
47
|
+
// Restore prototype chain so instanceof works after transpilation.
|
|
48
|
+
Object.setPrototypeOf(this, ProviderNotReadyError.prototype);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* True when the value is a "provider not ready / torn down" condition, not a
|
|
54
|
+
* genuine failure. Recognises the tagged {@link ProviderNotReadyError} and the
|
|
55
|
+
* legacy message (for errors thrown before rollout).
|
|
56
|
+
*/
|
|
57
|
+
export const isProviderNotReadyError = error => {
|
|
58
|
+
if (error instanceof ProviderNotReadyError) {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
if (typeof error === 'object' && error !== null && error.isProviderNotReadyError === true) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
return error instanceof Error && error.message === PROVIDER_NOT_READY_MESSAGE;
|
|
65
|
+
};
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
2
2
|
import { isSSR } from '@atlaskit/editor-common/core-utils';
|
|
3
3
|
import { logException } from '@atlaskit/editor-common/monitoring';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { fg } from '@atlaskit/platform-feature-flags';
|
|
5
|
+
import { isProviderNotReadyError, SyncBlockError } from '../common/types';
|
|
6
|
+
import { buildFetchErrorAttribution, fetchErrorPayload } from '../utils/errorHandling';
|
|
6
7
|
import { createSyncBlockNode, getSourceProductFromResourceIdSafe } from '../utils/utils';
|
|
7
8
|
export const useFetchSyncBlockData = (manager, resourceId, localId, fireAnalyticsEvent) => {
|
|
9
|
+
var _manager$referenceMan2, _manager$referenceMan3, _manager$referenceMan4;
|
|
8
10
|
// Initialize both states from a single cache lookup to avoid race conditions.
|
|
9
11
|
// When a block is moved/remounted, the old component's cleanup may clear the cache
|
|
10
12
|
// before or after the new component mounts. By doing a single lookup, we ensure
|
|
@@ -26,10 +28,23 @@ export const useFetchSyncBlockData = (manager, resourceId, localId, fireAnalytic
|
|
|
26
28
|
isLoading: true
|
|
27
29
|
};
|
|
28
30
|
});
|
|
31
|
+
|
|
32
|
+
// On Jira the data provider is wired asynchronously, so the manager can be
|
|
33
|
+
// constructed with `dataProvider === undefined`. Fetching/subscribing in that
|
|
34
|
+
// window throws `Data provider not set`, logged as a false fetch error
|
|
35
|
+
// (EDITOR-7860). Gate on readiness; once the provider resolves a new manager
|
|
36
|
+
// instance is created so `referenceManager` changes identity and the effect
|
|
37
|
+
// below re-runs, subscribing exactly once.
|
|
38
|
+
const isDataProviderReady = fg('platform_editor_blocks_patch_3') ? (_manager$referenceMan2 = (_manager$referenceMan3 = (_manager$referenceMan4 = manager.referenceManager).hasDataProvider) === null || _manager$referenceMan3 === void 0 ? void 0 : _manager$referenceMan3.call(_manager$referenceMan4)) !== null && _manager$referenceMan2 !== void 0 ? _manager$referenceMan2 : false : true;
|
|
29
39
|
const reloadData = useCallback(async () => {
|
|
30
40
|
if (isLoading) {
|
|
31
41
|
return;
|
|
32
42
|
}
|
|
43
|
+
|
|
44
|
+
// Not ready: skip fetch, emit no error (see readiness note above).
|
|
45
|
+
if (!isDataProviderReady) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
33
48
|
try {
|
|
34
49
|
const syncBlockNode = resourceId && localId ? createSyncBlockNode(localId, resourceId) : null;
|
|
35
50
|
if (!syncBlockNode) {
|
|
@@ -43,10 +58,21 @@ export const useFetchSyncBlockData = (manager, resourceId, localId, fireAnalytic
|
|
|
43
58
|
// Fetch sync block data, the `subscribeToSyncBlock` will update the state once data is fetched
|
|
44
59
|
await manager.referenceManager.fetchSyncBlocksData([syncBlockNode]);
|
|
45
60
|
} catch (error) {
|
|
61
|
+
// EDITOR-7860: benign not-ready throw — emit no error and stay loading
|
|
62
|
+
// so it resolves on retry once the provider is wired. Checked before
|
|
63
|
+
// `logException` so the benign case produces no exception-tracker noise.
|
|
64
|
+
// Gate-off behaviour is unchanged.
|
|
65
|
+
if (isProviderNotReadyError(error) && fg('platform_editor_blocks_patch_3')) {
|
|
66
|
+
setFetchState(prev => ({
|
|
67
|
+
...prev,
|
|
68
|
+
isLoading: true
|
|
69
|
+
}));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
46
72
|
logException(error, {
|
|
47
73
|
location: 'editor-synced-block-provider/useFetchSyncBlockData'
|
|
48
74
|
});
|
|
49
|
-
fireAnalyticsEvent === null || fireAnalyticsEvent === void 0 ? void 0 : fireAnalyticsEvent(fetchErrorPayload(error.message, resourceId, getSourceProductFromResourceIdSafe(resourceId)));
|
|
75
|
+
fireAnalyticsEvent === null || fireAnalyticsEvent === void 0 ? void 0 : fireAnalyticsEvent(fetchErrorPayload(error.message, resourceId, getSourceProductFromResourceIdSafe(resourceId), buildFetchErrorAttribution(fg('platform_editor_blocks_patch_3'), error.message)));
|
|
50
76
|
|
|
51
77
|
// Set error state if fetching fails
|
|
52
78
|
setFetchState({
|
|
@@ -64,13 +90,20 @@ export const useFetchSyncBlockData = (manager, resourceId, localId, fireAnalytic
|
|
|
64
90
|
...prev,
|
|
65
91
|
isLoading: false
|
|
66
92
|
}));
|
|
67
|
-
}, [isLoading, localId, manager.referenceManager, resourceId, fireAnalyticsEvent]);
|
|
93
|
+
}, [isLoading, isDataProviderReady, localId, manager.referenceManager, resourceId, fireAnalyticsEvent]);
|
|
68
94
|
useEffect(() => {
|
|
69
95
|
if (isSSR()) {
|
|
70
96
|
// in SSR, we don't need to subscribe to updates,
|
|
71
97
|
// instead we rely on pre-fetched data ONLY, see initialization of syncBlockInstance above
|
|
72
98
|
return;
|
|
73
99
|
}
|
|
100
|
+
|
|
101
|
+
// Not ready: skip subscribe (it would trigger a batched fetch and a false
|
|
102
|
+
// error) and keep `isLoading: true`. `isDataProviderReady` is in the deps,
|
|
103
|
+
// so the effect re-runs and subscribes once the provider resolves.
|
|
104
|
+
if (!isDataProviderReady) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
74
107
|
const unsubscribe = manager.referenceManager.subscribeToSyncBlock(resourceId || '', localId || '', data => {
|
|
75
108
|
setFetchState({
|
|
76
109
|
syncBlockInstance: data,
|
|
@@ -80,7 +113,7 @@ export const useFetchSyncBlockData = (manager, resourceId, localId, fireAnalytic
|
|
|
80
113
|
return () => {
|
|
81
114
|
unsubscribe();
|
|
82
115
|
};
|
|
83
|
-
}, [localId, manager.referenceManager, resourceId]);
|
|
116
|
+
}, [isDataProviderReady, localId, manager.referenceManager, resourceId]);
|
|
84
117
|
const ssrProviders = useMemo(() => {
|
|
85
118
|
return resourceId ? manager.referenceManager.getSSRProviders(resourceId) : null;
|
|
86
119
|
}, [resourceId, manager.referenceManager]);
|
|
@@ -300,11 +300,16 @@ export const batchFetchData = async (cloudId, parentAri, blockNodeIdentifiers, c
|
|
|
300
300
|
}));
|
|
301
301
|
}
|
|
302
302
|
|
|
303
|
-
// If batch request fails, return error for all resourceIds
|
|
303
|
+
// If batch request fails, return error for all resourceIds. Capture the HTTP
|
|
304
|
+
// status from `BlockError` so fetch analytics can break failures down by
|
|
305
|
+
// statusCode (EDITOR-7862); undefined for non-HTTP failures.
|
|
304
306
|
return blockNodeIdentifiers.map(blockNodeIdentifier => ({
|
|
305
307
|
error: {
|
|
306
308
|
type: error instanceof BlockError ? mapBlockError(error) : SyncBlockError.Errored,
|
|
307
|
-
reason: error.message
|
|
309
|
+
reason: error.message,
|
|
310
|
+
...(error instanceof BlockError && {
|
|
311
|
+
statusCode: error.status
|
|
312
|
+
})
|
|
308
313
|
},
|
|
309
314
|
resourceId: blockNodeIdentifier.resourceId
|
|
310
315
|
}));
|
|
@@ -458,10 +463,13 @@ class BlockServiceADFFetchProvider {
|
|
|
458
463
|
};
|
|
459
464
|
} catch (error) {
|
|
460
465
|
if (error instanceof BlockError) {
|
|
466
|
+
// Capture the HTTP status so fetch analytics can break failures down by
|
|
467
|
+
// statusCode (EDITOR-7862).
|
|
461
468
|
return {
|
|
462
469
|
error: {
|
|
463
470
|
type: mapBlockError(error),
|
|
464
|
-
reason: error.message
|
|
471
|
+
reason: error.message,
|
|
472
|
+
statusCode: error.status
|
|
465
473
|
},
|
|
466
474
|
resourceId
|
|
467
475
|
};
|
|
@@ -2,8 +2,8 @@ import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
|
2
2
|
import isEqual from 'lodash/isEqual';
|
|
3
3
|
import { logException } from '@atlaskit/editor-common/monitoring';
|
|
4
4
|
import { fg } from '@atlaskit/platform-feature-flags';
|
|
5
|
-
import { SyncBlockError } from '../common/types';
|
|
6
|
-
import { buildErrorAttribution, cacheDeletionForcedPayload, fetchErrorPayload, fetchSuccessPayload, getSourceInfoErrorPayload, sourceInfoOrphanedPayload, updateReferenceErrorPayload } from '../utils/errorHandling';
|
|
5
|
+
import { isProviderNotReadyError, ProviderNotReadyError, SyncBlockError } from '../common/types';
|
|
6
|
+
import { buildErrorAttribution, buildFetchErrorAttribution, cacheDeletionForcedPayload, fetchErrorPayload, fetchSuccessPayload, getSourceInfoErrorPayload, sourceInfoOrphanedPayload, updateReferenceErrorPayload } from '../utils/errorHandling';
|
|
7
7
|
import { getFetchExperience, getFetchSourceInfoExperience, getSaveReferenceExperience } from '../utils/experienceTracking';
|
|
8
8
|
import { resolveSyncBlockInstance } from '../utils/resolveSyncBlockInstance';
|
|
9
9
|
import { createSyncBlockNode, getSourceProductFromResourceIdSafe, normalizeSyncBlockJSONContent } from '../utils/utils';
|
|
@@ -79,7 +79,9 @@ export class ReferenceSyncBlockStoreManager {
|
|
|
79
79
|
this._batchFetcher = new SyncBlockBatchFetcher({
|
|
80
80
|
getSubscriptions: () => this._subscriptionManager.getSubscriptions(),
|
|
81
81
|
fetchSyncBlocksData: nodes => this.fetchSyncBlocksData(nodes),
|
|
82
|
-
getFireAnalyticsEvent: () => this.fireAnalyticsEvent
|
|
82
|
+
getFireAnalyticsEvent: () => this.fireAnalyticsEvent,
|
|
83
|
+
// EDITOR-7860: skip + re-queue fetches while not ready / torn down.
|
|
84
|
+
isProviderReady: () => this.hasDataProvider()
|
|
83
85
|
});
|
|
84
86
|
|
|
85
87
|
// The provider might have SSR data cache already set, so we need to update the cache in session memory storage
|
|
@@ -89,6 +91,17 @@ export class ReferenceSyncBlockStoreManager {
|
|
|
89
91
|
return node.type.name === 'syncBlock';
|
|
90
92
|
}
|
|
91
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Whether the async data provider is wired and the manager is not torn down.
|
|
96
|
+
* Consumers gate fetch/subscribe on this to avoid a false `Data provider not
|
|
97
|
+
* set` fetch error (EDITOR-7860). The `isDestroyed` check covers managers
|
|
98
|
+
* orphaned mid-flight during an async provider swap, where `dataProvider`
|
|
99
|
+
* alone is insufficient.
|
|
100
|
+
*/
|
|
101
|
+
hasDataProvider() {
|
|
102
|
+
return !this.isDestroyed && !!this.dataProvider;
|
|
103
|
+
}
|
|
104
|
+
|
|
92
105
|
/**
|
|
93
106
|
* Enables or disables real-time GraphQL subscriptions for block updates.
|
|
94
107
|
* When enabled, the store manager will subscribe to real-time updates
|
|
@@ -412,6 +425,11 @@ export class ReferenceSyncBlockStoreManager {
|
|
|
412
425
|
return;
|
|
413
426
|
}
|
|
414
427
|
if (!this.dataProvider) {
|
|
428
|
+
// EDITOR-7860: tag the throw so catch sites can suppress the benign
|
|
429
|
+
// not-ready/torn-down case. Gate-off keeps the legacy throw + message.
|
|
430
|
+
if (fg('platform_editor_blocks_patch_3')) {
|
|
431
|
+
throw new ProviderNotReadyError();
|
|
432
|
+
}
|
|
415
433
|
throw new Error('Data provider not set');
|
|
416
434
|
}
|
|
417
435
|
nodesToFetch.forEach(node => {
|
|
@@ -454,10 +472,12 @@ export class ReferenceSyncBlockStoreManager {
|
|
|
454
472
|
data.forEach(syncBlockInstance => {
|
|
455
473
|
var _resolvedSyncBlockIns;
|
|
456
474
|
if (!syncBlockInstance.resourceId) {
|
|
457
|
-
var _syncBlockInstance$er, _syncBlockInstance$er2, _this$fireAnalyticsEv8;
|
|
475
|
+
var _syncBlockInstance$er, _syncBlockInstance$er2, _this$fireAnalyticsEv8, _syncBlockInstance$er3, _syncBlockInstance$er4, _syncBlockInstance$er5;
|
|
458
476
|
const payload = ((_syncBlockInstance$er = syncBlockInstance.error) === null || _syncBlockInstance$er === void 0 ? void 0 : _syncBlockInstance$er.reason) || ((_syncBlockInstance$er2 = syncBlockInstance.error) === null || _syncBlockInstance$er2 === void 0 ? void 0 : _syncBlockInstance$er2.type) || 'Returned sync block instance does not have resource id';
|
|
459
477
|
// No resourceId means we cannot derive a sourceProduct here; intentionally omit.
|
|
460
|
-
|
|
478
|
+
// Classify on the structured `type` first, falling back to the free-text
|
|
479
|
+
// `reason`/payload (EDITOR-7862).
|
|
480
|
+
(_this$fireAnalyticsEv8 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv8 === void 0 ? void 0 : _this$fireAnalyticsEv8.call(this, fetchErrorPayload(payload, undefined, undefined, buildFetchErrorAttribution(fg('platform_editor_blocks_patch_3'), ((_syncBlockInstance$er3 = syncBlockInstance.error) === null || _syncBlockInstance$er3 === void 0 ? void 0 : _syncBlockInstance$er3.type) || ((_syncBlockInstance$er4 = syncBlockInstance.error) === null || _syncBlockInstance$er4 === void 0 ? void 0 : _syncBlockInstance$er4.reason) || payload, (_syncBlockInstance$er5 = syncBlockInstance.error) === null || _syncBlockInstance$er5 === void 0 ? void 0 : _syncBlockInstance$er5.statusCode)));
|
|
461
481
|
return;
|
|
462
482
|
}
|
|
463
483
|
const existingSyncBlock = this.getFromCache(syncBlockInstance.resourceId);
|
|
@@ -491,7 +511,10 @@ export class ReferenceSyncBlockStoreManager {
|
|
|
491
511
|
const isRetryingEntityNotFound = syncBlockInstance.error.type === SyncBlockError.EntityNotFound && ((_this$entityNotFoundR = this.entityNotFoundRetryCount.get(syncBlockInstance.resourceId)) !== null && _this$entityNotFoundR !== void 0 ? _this$entityNotFoundR : 0) < ENTITY_NOT_FOUND_MAX_RETRIES && fg('platform_synced_block_patch_13');
|
|
492
512
|
if (!isRetryingEntityNotFound) {
|
|
493
513
|
var _this$fireAnalyticsEv9, _syncBlockInstance$da, _syncBlockInstance$da2;
|
|
494
|
-
|
|
514
|
+
// Classify on the structured `type` (a `SyncBlockError` enum value) first,
|
|
515
|
+
// falling back to the free-text `reason` so source-state/permission strings
|
|
516
|
+
// are still bucketed (EDITOR-7862). The emitted `error` attribute is unchanged.
|
|
517
|
+
(_this$fireAnalyticsEv9 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv9 === void 0 ? void 0 : _this$fireAnalyticsEv9.call(this, fetchErrorPayload(syncBlockInstance.error.reason || syncBlockInstance.error.type, syncBlockInstance.resourceId, (_syncBlockInstance$da = (_syncBlockInstance$da2 = syncBlockInstance.data) === null || _syncBlockInstance$da2 === void 0 ? void 0 : _syncBlockInstance$da2.product) !== null && _syncBlockInstance$da !== void 0 ? _syncBlockInstance$da : getSourceProductFromResourceIdSafe(syncBlockInstance.resourceId), buildFetchErrorAttribution(fg('platform_editor_blocks_patch_3'), syncBlockInstance.error.type || syncBlockInstance.error.reason, syncBlockInstance.error.statusCode)));
|
|
495
518
|
}
|
|
496
519
|
if (syncBlockInstance.error.type === SyncBlockError.NotFound || syncBlockInstance.error.type === SyncBlockError.Forbidden) {
|
|
497
520
|
hasExpectedError = true;
|
|
@@ -779,10 +802,16 @@ export class ReferenceSyncBlockStoreManager {
|
|
|
779
802
|
return this._subscriptionManager.subscribeToSyncBlock(resourceId, localId, callback);
|
|
780
803
|
} catch (error) {
|
|
781
804
|
var _this$fireAnalyticsEv11;
|
|
805
|
+
// EDITOR-7860: benign not-ready/torn-down case — suppress both the
|
|
806
|
+
// exception-tracker log and the analytics event (checked first so the
|
|
807
|
+
// benign case stays fully silent). Gate-off behaviour is unchanged.
|
|
808
|
+
if (isProviderNotReadyError(error) && fg('platform_editor_blocks_patch_3')) {
|
|
809
|
+
return () => {};
|
|
810
|
+
}
|
|
782
811
|
logException(error, {
|
|
783
812
|
location: 'editor-synced-block-provider/referenceSyncBlockStoreManager'
|
|
784
813
|
});
|
|
785
|
-
(_this$fireAnalyticsEv11 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv11 === void 0 ? void 0 : _this$fireAnalyticsEv11.call(this, fetchErrorPayload(error.message));
|
|
814
|
+
(_this$fireAnalyticsEv11 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv11 === void 0 ? void 0 : _this$fireAnalyticsEv11.call(this, fetchErrorPayload(error.message, undefined, undefined, buildFetchErrorAttribution(fg('platform_editor_blocks_patch_3'), error.message)));
|
|
786
815
|
return () => {};
|
|
787
816
|
}
|
|
788
817
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
2
2
|
import rafSchedule from 'raf-schd';
|
|
3
3
|
import { logException } from '@atlaskit/editor-common/monitoring';
|
|
4
|
-
import {
|
|
4
|
+
import { fg } from '@atlaskit/platform-feature-flags';
|
|
5
|
+
import { isProviderNotReadyError } from '../common/types';
|
|
6
|
+
import { buildFetchErrorAttribution, fetchErrorPayload } from '../utils/errorHandling';
|
|
5
7
|
import { createSyncBlockNode, getSourceProductFromResourceIdSafe } from '../utils/utils';
|
|
6
8
|
/**
|
|
7
9
|
* Handles debounced batch-fetching of sync block data via `raf-schd`.
|
|
@@ -21,6 +23,17 @@ export class SyncBlockBatchFetcher {
|
|
|
21
23
|
if (this.pendingFetchRequests.size === 0) {
|
|
22
24
|
return;
|
|
23
25
|
}
|
|
26
|
+
|
|
27
|
+
// EDITOR-7860: not ready — skip and leave resourceIds queued for the
|
|
28
|
+
// next batch once the provider resolves. Gate-off is unchanged (the
|
|
29
|
+
// readiness check is nested under the gate so it is not consulted when
|
|
30
|
+
// the gate is off, and `fg()` stays a standalone condition so gate
|
|
31
|
+
// exposure is still tracked — satisfies @atlaskit/platform/no-preconditioning).
|
|
32
|
+
if (fg('platform_editor_blocks_patch_3')) {
|
|
33
|
+
if (this.deps.isProviderReady && !this.deps.isProviderReady()) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
24
37
|
const resourceIds = Array.from(this.pendingFetchRequests);
|
|
25
38
|
const syncBlockNodes = resourceIds.map(resId => {
|
|
26
39
|
const subscriptions = this.deps.getSubscriptions().get(resId) || {};
|
|
@@ -32,12 +45,25 @@ export class SyncBlockBatchFetcher {
|
|
|
32
45
|
// Track in-flight before the fetch so guards remain positive.
|
|
33
46
|
resourceIds.forEach(resId => this.inFlightFetches.add(resId));
|
|
34
47
|
this.deps.fetchSyncBlocksData(syncBlockNodes).catch(error => {
|
|
48
|
+
// EDITOR-7860: benign not-ready throw — re-queue for retry and emit
|
|
49
|
+
// nothing (no analytics, no exception-tracker noise). Checked before
|
|
50
|
+
// `logException` so the benign case stays silent. Re-schedule so the
|
|
51
|
+
// re-queued IDs are retried on the next frame even if no further
|
|
52
|
+
// `queueFetch()` arrives. Gate-off behaviour is unchanged.
|
|
53
|
+
if (isProviderNotReadyError(error) && fg('platform_editor_blocks_patch_3')) {
|
|
54
|
+
resourceIds.forEach(resId => this.pendingFetchRequests.add(resId));
|
|
55
|
+
if (!this.isDestroyed) {
|
|
56
|
+
this.scheduledBatchFetch();
|
|
57
|
+
}
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
35
60
|
logException(error, {
|
|
36
61
|
location: 'editor-synced-block-provider/syncBlockBatchFetcher/batchedFetchSyncBlocks'
|
|
37
62
|
});
|
|
63
|
+
const attribution = buildFetchErrorAttribution(fg('platform_editor_blocks_patch_3'), error.message);
|
|
38
64
|
resourceIds.forEach(resId => {
|
|
39
65
|
var _this$deps$getFireAna;
|
|
40
|
-
(_this$deps$getFireAna = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna === void 0 ? void 0 : _this$deps$getFireAna(fetchErrorPayload(error.message, resId, getSourceProductFromResourceIdSafe(resId)));
|
|
66
|
+
(_this$deps$getFireAna = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna === void 0 ? void 0 : _this$deps$getFireAna(fetchErrorPayload(error.message, resId, getSourceProductFromResourceIdSafe(resId), attribution));
|
|
41
67
|
});
|
|
42
68
|
}).finally(() => {
|
|
43
69
|
// If the fetcher was destroyed while the request was in flight,
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
2
2
|
import { logException } from '@atlaskit/editor-common/monitoring';
|
|
3
3
|
import { ProviderFactory } from '@atlaskit/editor-common/provider-factory';
|
|
4
|
-
import {
|
|
4
|
+
import { fg } from '@atlaskit/platform-feature-flags';
|
|
5
|
+
import { buildFetchErrorAttribution, fetchErrorPayload } from '../utils/errorHandling';
|
|
5
6
|
import { parseResourceId } from '../utils/resourceId';
|
|
6
7
|
import { getSourceProductFromResourceIdSafe } from '../utils/utils';
|
|
7
8
|
/**
|
|
@@ -21,7 +22,7 @@ export class SyncBlockProviderFactoryManager {
|
|
|
21
22
|
logException(error, {
|
|
22
23
|
location: 'editor-synced-block-provider/syncBlockProviderFactoryManager'
|
|
23
24
|
});
|
|
24
|
-
(_this$deps$getFireAna = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna === void 0 ? void 0 : _this$deps$getFireAna(fetchErrorPayload(error.message, resourceId, getSourceProductFromResourceIdSafe(resourceId)));
|
|
25
|
+
(_this$deps$getFireAna = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna === void 0 ? void 0 : _this$deps$getFireAna(fetchErrorPayload(error.message, resourceId, getSourceProductFromResourceIdSafe(resourceId), buildFetchErrorAttribution(fg('platform_editor_blocks_patch_3'), error.message)));
|
|
25
26
|
return undefined;
|
|
26
27
|
}
|
|
27
28
|
const {
|
|
@@ -55,7 +56,7 @@ export class SyncBlockProviderFactoryManager {
|
|
|
55
56
|
logException(error, {
|
|
56
57
|
location: 'editor-synced-block-provider/syncBlockProviderFactoryManager'
|
|
57
58
|
});
|
|
58
|
-
(_this$deps$getFireAna2 = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna2 === void 0 ? void 0 : _this$deps$getFireAna2(fetchErrorPayload(error.message, resourceId, getSourceProductFromResourceIdSafe(resourceId)));
|
|
59
|
+
(_this$deps$getFireAna2 = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna2 === void 0 ? void 0 : _this$deps$getFireAna2(fetchErrorPayload(error.message, resourceId, getSourceProductFromResourceIdSafe(resourceId), buildFetchErrorAttribution(fg('platform_editor_blocks_patch_3'), error.message)));
|
|
59
60
|
}
|
|
60
61
|
}
|
|
61
62
|
return providerFactory;
|
|
@@ -133,7 +134,7 @@ export class SyncBlockProviderFactoryManager {
|
|
|
133
134
|
if (!syncBlock.data.sourceAri || !syncBlock.data.product) {
|
|
134
135
|
var _this$deps$getFireAna3, _syncBlock$data$produ;
|
|
135
136
|
(_this$deps$getFireAna3 = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna3 === void 0 ? void 0 : _this$deps$getFireAna3(fetchErrorPayload('Sync block source ari or product not found', resourceId, // Prefer cached product when available; fall back to parsing resourceId.
|
|
136
|
-
(_syncBlock$data$produ = syncBlock.data.product) !== null && _syncBlock$data$produ !== void 0 ? _syncBlock$data$produ : getSourceProductFromResourceIdSafe(resourceId)));
|
|
137
|
+
(_syncBlock$data$produ = syncBlock.data.product) !== null && _syncBlock$data$produ !== void 0 ? _syncBlock$data$produ : getSourceProductFromResourceIdSafe(resourceId), buildFetchErrorAttribution(fg('platform_editor_blocks_patch_3'), 'Sync block source ari or product not found')));
|
|
137
138
|
return;
|
|
138
139
|
}
|
|
139
140
|
const parentInfo = dataProvider.retrieveSyncBlockParentInfo(syncBlock.data.sourceAri, syncBlock.data.product);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
2
2
|
import { logException } from '@atlaskit/editor-common/monitoring';
|
|
3
3
|
import { fg } from '@atlaskit/platform-feature-flags';
|
|
4
|
-
import { fetchErrorPayload, fetchSuccessPayload } from '../utils/errorHandling';
|
|
4
|
+
import { buildFetchErrorAttribution, fetchErrorPayload, fetchSuccessPayload } from '../utils/errorHandling';
|
|
5
5
|
import { resolveSyncBlockInstance } from '../utils/resolveSyncBlockInstance';
|
|
6
6
|
import { getSourceProductFromResourceIdSafe } from '../utils/utils';
|
|
7
7
|
/**
|
|
@@ -87,7 +87,7 @@ export class SyncBlockSubscriptionManager {
|
|
|
87
87
|
logException(error, {
|
|
88
88
|
location: 'editor-synced-block-provider/syncBlockSubscriptionManager/notifySubscriptionChangeListeners'
|
|
89
89
|
});
|
|
90
|
-
(_this$deps$getFireAna = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna === void 0 ? void 0 : _this$deps$getFireAna(fetchErrorPayload(error.message));
|
|
90
|
+
(_this$deps$getFireAna = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna === void 0 ? void 0 : _this$deps$getFireAna(fetchErrorPayload(error.message, undefined, undefined, buildFetchErrorAttribution(fg('platform_editor_blocks_patch_3'), error.message)));
|
|
91
91
|
}
|
|
92
92
|
});
|
|
93
93
|
}
|
|
@@ -264,6 +264,9 @@ export class SyncBlockSubscriptionManager {
|
|
|
264
264
|
// recovers on reconnect, so under the gate we don't fire a
|
|
265
265
|
// user-facing error here — it's only surfaced on exhaustion (see
|
|
266
266
|
// scheduleReconnection). Gate OFF keeps the legacy fire-on-drop.
|
|
267
|
+
// This branch only runs when the gate is OFF, so buildFetchErrorAttribution
|
|
268
|
+
// would return undefined; the structured attribution (EDITOR-7862) is therefore
|
|
269
|
+
// applied at the gate-ON exhaustion site in scheduleReconnection instead.
|
|
267
270
|
if (!fg('platform_editor_blocks_patch_3')) {
|
|
268
271
|
var _this$deps$getFireAna2;
|
|
269
272
|
(_this$deps$getFireAna2 = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna2 === void 0 ? void 0 : _this$deps$getFireAna2(fetchErrorPayload(error.message, resourceId, getSourceProductFromResourceIdSafe(resourceId)));
|
|
@@ -310,7 +313,7 @@ export class SyncBlockSubscriptionManager {
|
|
|
310
313
|
logException(new Error(errorMessage), {
|
|
311
314
|
location: 'editor-synced-block-provider/syncBlockSubscriptionManager/max-retries-exhausted'
|
|
312
315
|
});
|
|
313
|
-
(_this$deps$getFireAna3 = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna3 === void 0 ? void 0 : _this$deps$getFireAna3(fetchErrorPayload(errorMessage, resourceId, getSourceProductFromResourceIdSafe(resourceId)));
|
|
316
|
+
(_this$deps$getFireAna3 = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna3 === void 0 ? void 0 : _this$deps$getFireAna3(fetchErrorPayload(errorMessage, resourceId, getSourceProductFromResourceIdSafe(resourceId), buildFetchErrorAttribution(fg('platform_editor_blocks_patch_3'), errorMessage)));
|
|
314
317
|
return;
|
|
315
318
|
}
|
|
316
319
|
const delay = this.getReconnectionDelay(attempts);
|
|
@@ -409,9 +412,13 @@ export class SyncBlockSubscriptionManager {
|
|
|
409
412
|
});
|
|
410
413
|
this.deps.fetchSyncBlockSourceInfo(resolved.resourceId);
|
|
411
414
|
} else {
|
|
412
|
-
var _syncBlockInstance$er, _syncBlockInstance$er2, _this$deps$getFireAna5, _syncBlockInstance$da3, _syncBlockInstance$da4;
|
|
415
|
+
var _syncBlockInstance$er, _syncBlockInstance$er2, _this$deps$getFireAna5, _syncBlockInstance$da3, _syncBlockInstance$da4, _syncBlockInstance$er3, _syncBlockInstance$er4, _syncBlockInstance$er5;
|
|
413
416
|
const errorMessage = ((_syncBlockInstance$er = syncBlockInstance.error) === null || _syncBlockInstance$er === void 0 ? void 0 : _syncBlockInstance$er.reason) || ((_syncBlockInstance$er2 = syncBlockInstance.error) === null || _syncBlockInstance$er2 === void 0 ? void 0 : _syncBlockInstance$er2.type);
|
|
414
|
-
|
|
417
|
+
|
|
418
|
+
// Prefer the structured `type` (a `SyncBlockError` enum value) for classification
|
|
419
|
+
// and fall back to the free-text `reason` so source-state/permission strings are
|
|
420
|
+
// still bucketed (EDITOR-7862). The emitted free-text `error` attribute is unchanged.
|
|
421
|
+
(_this$deps$getFireAna5 = this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna5 === void 0 ? void 0 : _this$deps$getFireAna5(fetchErrorPayload(errorMessage, syncBlockInstance.resourceId, (_syncBlockInstance$da3 = (_syncBlockInstance$da4 = syncBlockInstance.data) === null || _syncBlockInstance$da4 === void 0 ? void 0 : _syncBlockInstance$da4.product) !== null && _syncBlockInstance$da3 !== void 0 ? _syncBlockInstance$da3 : getSourceProductFromResourceIdSafe(syncBlockInstance.resourceId), buildFetchErrorAttribution(fg('platform_editor_blocks_patch_3'), ((_syncBlockInstance$er3 = syncBlockInstance.error) === null || _syncBlockInstance$er3 === void 0 ? void 0 : _syncBlockInstance$er3.type) || ((_syncBlockInstance$er4 = syncBlockInstance.error) === null || _syncBlockInstance$er4 === void 0 ? void 0 : _syncBlockInstance$er4.reason), (_syncBlockInstance$er5 = syncBlockInstance.error) === null || _syncBlockInstance$er5 === void 0 ? void 0 : _syncBlockInstance$er5.statusCode)));
|
|
415
422
|
}
|
|
416
423
|
}
|
|
417
424
|
}
|