@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
|
@@ -58,34 +58,179 @@ export const buildErrorAttribution = (gateEnabled, error, statusCode) => {
|
|
|
58
58
|
};
|
|
59
59
|
};
|
|
60
60
|
|
|
61
|
+
/**
|
|
62
|
+
* The set of categorical failure reasons emitted on synced-block fetch/subscribe
|
|
63
|
+
* operational error events (EDITOR-7862). Extends the write-path
|
|
64
|
+
* {@link SyncBlockErrorReason} set with read-path-specific buckets so the analytics
|
|
65
|
+
* dashboard can break fetch failures down by cause instead of regex-matching the
|
|
66
|
+
* free-text `error` blob.
|
|
67
|
+
*
|
|
68
|
+
* The read path surfaces several causes the write-path `SyncBlockError` enum does not
|
|
69
|
+
* model (benign source-state transitions, permission denials, WebSocket lifecycle, and
|
|
70
|
+
* client-side readiness errors), so those are added here. Known `SyncBlockError` enum
|
|
71
|
+
* values (`not_found`, `forbidden`, ...) still pass through unchanged.
|
|
72
|
+
*/
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Fetch reasons that represent benign (working-as-designed) outcomes rather than genuine
|
|
76
|
+
* system failures. The dashboard uses this to compute a "true error rate" by excluding
|
|
77
|
+
* benign reasons via the structured `reason` attribute instead of brittle free-text regex
|
|
78
|
+
* (EDITOR-7862).
|
|
79
|
+
*/
|
|
80
|
+
export const FETCH_BENIGN_REASONS = new Set(['source_deleted', 'source_unpublished', 'source_unsynced', 'source_not_found', 'permission_denied', 'unauthenticated',
|
|
81
|
+
// Mirror the benign write-path enum equivalents so already-classified errors are
|
|
82
|
+
// treated consistently.
|
|
83
|
+
'not_found', 'forbidden', 'unpublished'
|
|
84
|
+
// NOTE: `entity_not_found` and `offline` are intentionally NOT benign.
|
|
85
|
+
// - `entity_not_found` is retried up to ENTITY_NOT_FOUND_MAX_RETRIES (analytics are
|
|
86
|
+
// suppressed during retries); by the time the error event fires, retries are
|
|
87
|
+
// exhausted and it is a genuine failure.
|
|
88
|
+
// - `offline` is genuine client connectivity loss, not a working-as-designed outcome.
|
|
89
|
+
// Both still emit `reason` (so they can be inspected) but are counted as true errors.
|
|
90
|
+
]);
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Ordered list of substring matchers mapping known free-text fetch/subscribe error
|
|
94
|
+
* strings to a categorical {@link SyncBlockFetchErrorReason}. Each entry is a list of
|
|
95
|
+
* lowercase needles; if ANY needle is found (case-insensitively) in the raw `error`
|
|
96
|
+
* string, the entry's reason is used. Order matters: more specific entries must precede
|
|
97
|
+
* more general ones.
|
|
98
|
+
*
|
|
99
|
+
* Plain `String.includes` substring matching is used deliberately instead of `RegExp`:
|
|
100
|
+
* it sidesteps the `require-unicode-regexp` lint rule AND the declaration build's lower
|
|
101
|
+
* compile target (which rejects the regex `u` flag with TS1501), while being faster and
|
|
102
|
+
* easier to read. Doing this classification in code (rather than in dashboard SQL) keeps
|
|
103
|
+
* the buckets versioned with the source strings that produce them (EDITOR-7862).
|
|
104
|
+
*/
|
|
105
|
+
const FETCH_REASON_MATCHERS = [
|
|
106
|
+
// Benign: source-state transitions (deletionReason values + free text).
|
|
107
|
+
[['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'],
|
|
108
|
+
// Working-as-designed permission outcomes.
|
|
109
|
+
[['unauthenticated'], 'unauthenticated'], [['not permitted to read synced block'], 'permission_denied'], [['bulk permission check failed'], 'permission_denied'], [['permission denied'], 'permission_denied'],
|
|
110
|
+
// Genuine system failures.
|
|
111
|
+
[['reconnection failed after'], 'websocket_exhausted'],
|
|
112
|
+
// `reconnect to the subscription` (not a bare `reconnect`) avoids false positives on
|
|
113
|
+
// unrelated DB/GraphQL "reconnect" messages while still matching the WS-drop string.
|
|
114
|
+
[['websocket', 'reconnect to the subscription', 'subscription terminated'], 'websocket_drop'], [['429', 'rate limit', 'rate-limit', 'ratelimit'], 'rate_limited'],
|
|
115
|
+
// `econnrefused`/`econnreset` (not a bare `econn`, which would also match `reconnect`).
|
|
116
|
+
[['network', 'failed to fetch', 'econnrefused', 'econnreset', 'timed out', 'timeout'], 'network'], [['data provider not set'], 'data_provider_not_ready']];
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Maps a fetch/subscribe `error` field — which may be a {@link SyncBlockError} enum
|
|
120
|
+
* value, a {@link DeletionReason} value, or an arbitrary free-text/JSON blob — to a
|
|
121
|
+
* stable categorical {@link SyncBlockFetchErrorReason} for analytics grouping.
|
|
122
|
+
*
|
|
123
|
+
* Resolution order:
|
|
124
|
+
* 1. Known `SyncBlockError` enum value → passed through (via {@link classifyErrorReason}).
|
|
125
|
+
* 2. Known free-text substring → mapped to a fetch-specific bucket.
|
|
126
|
+
* 3. Anything else (including opaque blobs like `errored`-only payloads or
|
|
127
|
+
* `ErrorEvent: "undefined"`) → `'unknown'`, so the dashboard never groups on free text.
|
|
128
|
+
*
|
|
129
|
+
* Note: the bare string `'errored'` IS a `SyncBlockError` enum value and therefore
|
|
130
|
+
* classifies as `'errored'` (not `'unknown'`); only genuinely unrecognised strings
|
|
131
|
+
* collapse to `'unknown'`.
|
|
132
|
+
*/
|
|
133
|
+
export const classifyFetchErrorReason = error => {
|
|
134
|
+
const enumReason = classifyErrorReason(error);
|
|
135
|
+
if (enumReason !== 'unknown') {
|
|
136
|
+
return enumReason;
|
|
137
|
+
}
|
|
138
|
+
if (error) {
|
|
139
|
+
const haystack = error.toLowerCase();
|
|
140
|
+
for (const [needles, reason] of FETCH_REASON_MATCHERS) {
|
|
141
|
+
if (needles.some(needle => haystack.includes(needle))) {
|
|
142
|
+
return reason;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return 'unknown';
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Extra, optional analytics attributes describing WHY a fetch/subscribe synced-block
|
|
151
|
+
* action failed. Spread conditionally so we never emit `undefined` keys (EDITOR-7862).
|
|
152
|
+
*
|
|
153
|
+
* Adds `benign` on top of the shared {@link ErrorAttributionAttributes} so the dashboard
|
|
154
|
+
* can compute a true error rate (`genuine / total`) without any free-text regex.
|
|
155
|
+
*/
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Builds the {@link FetchErrorAttributionAttributes} for a failed fetch/subscribe
|
|
159
|
+
* synced-block operation from the raw `error` field and optional backend `statusCode`.
|
|
160
|
+
* Returns `undefined` when the `platform_editor_blocks_patch_3` gate is OFF, so the new
|
|
161
|
+
* `reason`/`statusCode`/`benign` attributes are only emitted once the gate is rolled out
|
|
162
|
+
* (EDITOR-7862). The existing free-text `error` attribute is always left unchanged.
|
|
163
|
+
*
|
|
164
|
+
* `gateEnabled` is injected by the caller (the store managers evaluate `fg(...)`) so this
|
|
165
|
+
* helper stays pure and trivially unit-testable for both gate states.
|
|
166
|
+
*/
|
|
167
|
+
export const buildFetchErrorAttribution = (gateEnabled, error, statusCode) => {
|
|
168
|
+
if (!gateEnabled) {
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
const reason = classifyFetchErrorReason(error);
|
|
172
|
+
return {
|
|
173
|
+
reason,
|
|
174
|
+
benign: FETCH_BENIGN_REASONS.has(reason),
|
|
175
|
+
...(statusCode !== undefined && {
|
|
176
|
+
statusCode
|
|
177
|
+
})
|
|
178
|
+
};
|
|
179
|
+
};
|
|
180
|
+
|
|
61
181
|
// `sourceProduct` is threaded through every helper so analytics
|
|
62
182
|
// events can be attributed to the source product (`confluence-page` vs `jira-work-item`).
|
|
63
183
|
// All helpers accept it as an optional trailing argument; the spread-only-when-truthy
|
|
64
184
|
// pattern below ensures we never emit `sourceProduct: undefined` (which would dirty the
|
|
65
185
|
// event schema with empty keys).
|
|
66
186
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
187
|
+
/**
|
|
188
|
+
* Shared operational ERROR payload builder for synced-block events.
|
|
189
|
+
*
|
|
190
|
+
* Overloaded so the emitted `reason` type stays accurate per path (raised in review on
|
|
191
|
+
* EDITOR-7862): fetch/subscribe callers pass a {@link FetchErrorAttributionAttributes}
|
|
192
|
+
* (discriminated by its required `benign` field) and get the wider
|
|
193
|
+
* {@link SyncBlockFetchErrorReason}; write-path callers pass an
|
|
194
|
+
* {@link ErrorAttributionAttributes} (or nothing) and keep the narrower
|
|
195
|
+
* {@link SyncBlockErrorReason}, so write-path payloads never claim to carry fetch-only
|
|
196
|
+
* buckets like `source_deleted` they never emit.
|
|
197
|
+
*/
|
|
198
|
+
|
|
199
|
+
export function getErrorPayload(actionSubjectId, error, resourceId, sourceProduct, attribution) {
|
|
200
|
+
return {
|
|
201
|
+
action: ACTION.ERROR,
|
|
202
|
+
actionSubject: ACTION_SUBJECT.SYNCED_BLOCK,
|
|
203
|
+
actionSubjectId,
|
|
204
|
+
eventType: EVENT_TYPE.OPERATIONAL,
|
|
205
|
+
attributes: {
|
|
206
|
+
error,
|
|
207
|
+
...(resourceId && {
|
|
208
|
+
resourceId
|
|
209
|
+
}),
|
|
210
|
+
...(sourceProduct && {
|
|
211
|
+
sourceProduct
|
|
212
|
+
}),
|
|
213
|
+
...((attribution === null || attribution === void 0 ? void 0 : attribution.reason) && {
|
|
214
|
+
reason: attribution.reason
|
|
215
|
+
}),
|
|
216
|
+
...((attribution === null || attribution === void 0 ? void 0 : attribution.statusCode) !== undefined && {
|
|
217
|
+
statusCode: attribution.statusCode
|
|
218
|
+
}),
|
|
219
|
+
// `benign` is only present on fetch/subscribe attribution (EDITOR-7862). It is a
|
|
220
|
+
// boolean (never UGC); the `'benign' in attribution` check both guards the
|
|
221
|
+
// write-path attribution (which has no `benign`) and narrows the union so
|
|
222
|
+
// `attribution.benign` is accessible.
|
|
223
|
+
...(attribution && 'benign' in attribution && {
|
|
224
|
+
benign: attribution.benign
|
|
225
|
+
})
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
export const fetchErrorPayload = (error, resourceId, sourceProduct, attribution) =>
|
|
230
|
+
// Branch on attribution presence so each call resolves to a concrete overload: with
|
|
231
|
+
// attribution it hits the fetch overload (wider `reason`); without it (gate OFF) it
|
|
232
|
+
// hits the no-attribution overload. Both produce a fetch event regardless.
|
|
233
|
+
attribution ? getErrorPayload(ACTION_SUBJECT_ID.SYNCED_BLOCK_FETCH, error, resourceId, sourceProduct, attribution) : getErrorPayload(ACTION_SUBJECT_ID.SYNCED_BLOCK_FETCH, error, resourceId, sourceProduct);
|
|
89
234
|
export const getSourceInfoErrorPayload = (error, resourceId, sourceProduct) => getErrorPayload(ACTION_SUBJECT_ID.SYNCED_BLOCK_GET_SOURCE_INFO, error, resourceId, sourceProduct);
|
|
90
235
|
export const updateErrorPayload = (error, resourceId, sourceProduct, attribution) => getErrorPayload(ACTION_SUBJECT_ID.SYNCED_BLOCK_UPDATE, error, resourceId, sourceProduct, attribution);
|
|
91
236
|
export const updateReferenceErrorPayload = (error, resourceId, sourceProduct, attribution) => getErrorPayload(ACTION_SUBJECT_ID.REFERENCE_SYNCED_BLOCK_UPDATE, error, resourceId, sourceProduct, attribution);
|
package/dist/esm/common/types.js
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
import _typeof from "@babel/runtime/helpers/typeof";
|
|
2
|
+
import _createClass from "@babel/runtime/helpers/createClass";
|
|
3
|
+
import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
|
|
4
|
+
import _possibleConstructorReturn from "@babel/runtime/helpers/possibleConstructorReturn";
|
|
5
|
+
import _getPrototypeOf from "@babel/runtime/helpers/getPrototypeOf";
|
|
6
|
+
import _inherits from "@babel/runtime/helpers/inherits";
|
|
7
|
+
import _wrapNativeSuper from "@babel/runtime/helpers/wrapNativeSuper";
|
|
8
|
+
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
9
|
+
function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); }
|
|
10
|
+
function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }
|
|
1
11
|
export var SyncBlockError = /*#__PURE__*/function (SyncBlockError) {
|
|
2
12
|
SyncBlockError["Errored"] = "errored";
|
|
3
13
|
SyncBlockError["NotFound"] = "not_found";
|
|
@@ -16,4 +26,55 @@ export var SyncBlockError = /*#__PURE__*/function (SyncBlockError) {
|
|
|
16
26
|
// block does not exist on this site (e.g. cross-site reference or hard deleted)
|
|
17
27
|
SyncBlockError["EntityNotFound"] = "entity_not_found";
|
|
18
28
|
return SyncBlockError;
|
|
19
|
-
}({});
|
|
29
|
+
}({});
|
|
30
|
+
/**
|
|
31
|
+
* Helpers to distinguish "data provider not ready / torn down" from a genuine
|
|
32
|
+
* fetch/subscribe failure (EDITOR-7860). On Jira the provider is wired
|
|
33
|
+
* asynchronously and `destroy()` nulls it on orphaned managers, so queued/
|
|
34
|
+
* in-flight ops throw `Data provider not set` — previously mis-logged as a real
|
|
35
|
+
* error. These let throw and catch sites agree on one non-string-matched signal
|
|
36
|
+
* so the residual false errors are suppressed. Gated by
|
|
37
|
+
* `platform_editor_blocks_patch_3`.
|
|
38
|
+
*
|
|
39
|
+
* NB: these intentionally live here rather than in a dedicated module to avoid
|
|
40
|
+
* adding a downstream file to consuming Jira packages' Thunderstone complexity.
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
/** Legacy message — kept identical for gate-off and historical events. */
|
|
44
|
+
export var PROVIDER_NOT_READY_MESSAGE = 'Data provider not set';
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Thrown when a fetch/subscribe runs against a manager whose provider is not
|
|
48
|
+
* (yet) available or torn down. Keeps the legacy message + name so string
|
|
49
|
+
* matchers still work, while new code uses {@link isProviderNotReadyError}.
|
|
50
|
+
*/
|
|
51
|
+
export var ProviderNotReadyError = /*#__PURE__*/function (_Error) {
|
|
52
|
+
function ProviderNotReadyError() {
|
|
53
|
+
var _this;
|
|
54
|
+
var message = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : PROVIDER_NOT_READY_MESSAGE;
|
|
55
|
+
_classCallCheck(this, ProviderNotReadyError);
|
|
56
|
+
_this = _callSuper(this, ProviderNotReadyError, [message]);
|
|
57
|
+
_defineProperty(_this, "isProviderNotReadyError", true);
|
|
58
|
+
_this.name = 'ProviderNotReadyError';
|
|
59
|
+
// Restore prototype chain so instanceof works after transpilation.
|
|
60
|
+
Object.setPrototypeOf(_this, ProviderNotReadyError.prototype);
|
|
61
|
+
return _this;
|
|
62
|
+
}
|
|
63
|
+
_inherits(ProviderNotReadyError, _Error);
|
|
64
|
+
return _createClass(ProviderNotReadyError);
|
|
65
|
+
}( /*#__PURE__*/_wrapNativeSuper(Error));
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* True when the value is a "provider not ready / torn down" condition, not a
|
|
69
|
+
* genuine failure. Recognises the tagged {@link ProviderNotReadyError} and the
|
|
70
|
+
* legacy message (for errors thrown before rollout).
|
|
71
|
+
*/
|
|
72
|
+
export var isProviderNotReadyError = function isProviderNotReadyError(error) {
|
|
73
|
+
if (error instanceof ProviderNotReadyError) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
if (_typeof(error) === 'object' && error !== null && error.isProviderNotReadyError === true) {
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
return error instanceof Error && error.message === PROVIDER_NOT_READY_MESSAGE;
|
|
80
|
+
};
|
|
@@ -7,10 +7,12 @@ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t =
|
|
|
7
7
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
8
8
|
import { isSSR } from '@atlaskit/editor-common/core-utils';
|
|
9
9
|
import { logException } from '@atlaskit/editor-common/monitoring';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
10
|
+
import { fg } from '@atlaskit/platform-feature-flags';
|
|
11
|
+
import { isProviderNotReadyError, SyncBlockError } from '../common/types';
|
|
12
|
+
import { buildFetchErrorAttribution, fetchErrorPayload } from '../utils/errorHandling';
|
|
12
13
|
import { createSyncBlockNode, getSourceProductFromResourceIdSafe } from '../utils/utils';
|
|
13
14
|
export var useFetchSyncBlockData = function useFetchSyncBlockData(manager, resourceId, localId, fireAnalyticsEvent) {
|
|
15
|
+
var _manager$referenceMan2, _manager$referenceMan3, _manager$referenceMan4;
|
|
14
16
|
// Initialize both states from a single cache lookup to avoid race conditions.
|
|
15
17
|
// When a block is moved/remounted, the old component's cleanup may clear the cache
|
|
16
18
|
// before or after the new component mounts. By doing a single lookup, we ensure
|
|
@@ -34,6 +36,14 @@ export var useFetchSyncBlockData = function useFetchSyncBlockData(manager, resou
|
|
|
34
36
|
syncBlockInstance = _useState2$.syncBlockInstance,
|
|
35
37
|
isLoading = _useState2$.isLoading,
|
|
36
38
|
setFetchState = _useState2[1];
|
|
39
|
+
|
|
40
|
+
// On Jira the data provider is wired asynchronously, so the manager can be
|
|
41
|
+
// constructed with `dataProvider === undefined`. Fetching/subscribing in that
|
|
42
|
+
// window throws `Data provider not set`, logged as a false fetch error
|
|
43
|
+
// (EDITOR-7860). Gate on readiness; once the provider resolves a new manager
|
|
44
|
+
// instance is created so `referenceManager` changes identity and the effect
|
|
45
|
+
// below re-runs, subscribing exactly once.
|
|
46
|
+
var 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;
|
|
37
47
|
var reloadData = useCallback( /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
|
|
38
48
|
var syncBlockNode, _t;
|
|
39
49
|
return _regeneratorRuntime.wrap(function (_context) {
|
|
@@ -45,14 +55,20 @@ export var useFetchSyncBlockData = function useFetchSyncBlockData(manager, resou
|
|
|
45
55
|
}
|
|
46
56
|
return _context.abrupt("return");
|
|
47
57
|
case 1:
|
|
48
|
-
|
|
58
|
+
if (isDataProviderReady) {
|
|
59
|
+
_context.next = 2;
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
return _context.abrupt("return");
|
|
63
|
+
case 2:
|
|
64
|
+
_context.prev = 2;
|
|
49
65
|
syncBlockNode = resourceId && localId ? createSyncBlockNode(localId, resourceId) : null;
|
|
50
66
|
if (syncBlockNode) {
|
|
51
|
-
_context.next =
|
|
67
|
+
_context.next = 3;
|
|
52
68
|
break;
|
|
53
69
|
}
|
|
54
70
|
throw new Error('Failed to create sync block node from resourceid and localid');
|
|
55
|
-
case
|
|
71
|
+
case 3:
|
|
56
72
|
setFetchState(function (prev) {
|
|
57
73
|
return _objectSpread(_objectSpread({}, prev), {}, {
|
|
58
74
|
isLoading: true
|
|
@@ -60,18 +76,29 @@ export var useFetchSyncBlockData = function useFetchSyncBlockData(manager, resou
|
|
|
60
76
|
});
|
|
61
77
|
|
|
62
78
|
// Fetch sync block data, the `subscribeToSyncBlock` will update the state once data is fetched
|
|
63
|
-
_context.next =
|
|
79
|
+
_context.next = 4;
|
|
64
80
|
return manager.referenceManager.fetchSyncBlocksData([syncBlockNode]);
|
|
65
|
-
case 3:
|
|
66
|
-
_context.next = 5;
|
|
67
|
-
break;
|
|
68
81
|
case 4:
|
|
69
|
-
_context.
|
|
70
|
-
|
|
82
|
+
_context.next = 7;
|
|
83
|
+
break;
|
|
84
|
+
case 5:
|
|
85
|
+
_context.prev = 5;
|
|
86
|
+
_t = _context["catch"](2);
|
|
87
|
+
if (!(isProviderNotReadyError(_t) && fg('platform_editor_blocks_patch_3'))) {
|
|
88
|
+
_context.next = 6;
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
setFetchState(function (prev) {
|
|
92
|
+
return _objectSpread(_objectSpread({}, prev), {}, {
|
|
93
|
+
isLoading: true
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
return _context.abrupt("return");
|
|
97
|
+
case 6:
|
|
71
98
|
logException(_t, {
|
|
72
99
|
location: 'editor-synced-block-provider/useFetchSyncBlockData'
|
|
73
100
|
});
|
|
74
|
-
fireAnalyticsEvent === null || fireAnalyticsEvent === void 0 || fireAnalyticsEvent(fetchErrorPayload(_t.message, resourceId, getSourceProductFromResourceIdSafe(resourceId)));
|
|
101
|
+
fireAnalyticsEvent === null || fireAnalyticsEvent === void 0 || fireAnalyticsEvent(fetchErrorPayload(_t.message, resourceId, getSourceProductFromResourceIdSafe(resourceId), buildFetchErrorAttribution(fg('platform_editor_blocks_patch_3'), _t.message)));
|
|
75
102
|
|
|
76
103
|
// Set error state if fetching fails
|
|
77
104
|
setFetchState({
|
|
@@ -84,24 +111,31 @@ export var useFetchSyncBlockData = function useFetchSyncBlockData(manager, resou
|
|
|
84
111
|
isLoading: false
|
|
85
112
|
});
|
|
86
113
|
return _context.abrupt("return");
|
|
87
|
-
case
|
|
114
|
+
case 7:
|
|
88
115
|
setFetchState(function (prev) {
|
|
89
116
|
return _objectSpread(_objectSpread({}, prev), {}, {
|
|
90
117
|
isLoading: false
|
|
91
118
|
});
|
|
92
119
|
});
|
|
93
|
-
case
|
|
120
|
+
case 8:
|
|
94
121
|
case "end":
|
|
95
122
|
return _context.stop();
|
|
96
123
|
}
|
|
97
|
-
}, _callee, null, [[
|
|
98
|
-
})), [isLoading, localId, manager.referenceManager, resourceId, fireAnalyticsEvent]);
|
|
124
|
+
}, _callee, null, [[2, 5]]);
|
|
125
|
+
})), [isLoading, isDataProviderReady, localId, manager.referenceManager, resourceId, fireAnalyticsEvent]);
|
|
99
126
|
useEffect(function () {
|
|
100
127
|
if (isSSR()) {
|
|
101
128
|
// in SSR, we don't need to subscribe to updates,
|
|
102
129
|
// instead we rely on pre-fetched data ONLY, see initialization of syncBlockInstance above
|
|
103
130
|
return;
|
|
104
131
|
}
|
|
132
|
+
|
|
133
|
+
// Not ready: skip subscribe (it would trigger a batched fetch and a false
|
|
134
|
+
// error) and keep `isLoading: true`. `isDataProviderReady` is in the deps,
|
|
135
|
+
// so the effect re-runs and subscribes once the provider resolves.
|
|
136
|
+
if (!isDataProviderReady) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
105
139
|
var unsubscribe = manager.referenceManager.subscribeToSyncBlock(resourceId || '', localId || '', function (data) {
|
|
106
140
|
setFetchState({
|
|
107
141
|
syncBlockInstance: data,
|
|
@@ -111,7 +145,7 @@ export var useFetchSyncBlockData = function useFetchSyncBlockData(manager, resou
|
|
|
111
145
|
return function () {
|
|
112
146
|
unsubscribe();
|
|
113
147
|
};
|
|
114
|
-
}, [localId, manager.referenceManager, resourceId]);
|
|
148
|
+
}, [isDataProviderReady, localId, manager.referenceManager, resourceId]);
|
|
115
149
|
var ssrProviders = useMemo(function () {
|
|
116
150
|
return resourceId ? manager.referenceManager.getSSRProviders(resourceId) : null;
|
|
117
151
|
}, [resourceId, manager.referenceManager]);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
2
1
|
import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
|
|
3
2
|
import _createClass from "@babel/runtime/helpers/createClass";
|
|
3
|
+
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
4
4
|
import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
|
|
5
5
|
import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
|
|
6
6
|
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; }
|
|
@@ -405,10 +405,12 @@ var _batchFetchData = /*#__PURE__*/function () {
|
|
|
405
405
|
case 21:
|
|
406
406
|
return _context2.abrupt("return", blockNodeIdentifiers.map(function (blockNodeIdentifier) {
|
|
407
407
|
return {
|
|
408
|
-
error: {
|
|
408
|
+
error: _objectSpread({
|
|
409
409
|
type: _t4 instanceof BlockError ? mapBlockError(_t4) : SyncBlockError.Errored,
|
|
410
410
|
reason: _t4.message
|
|
411
|
-
},
|
|
411
|
+
}, _t4 instanceof BlockError && {
|
|
412
|
+
statusCode: _t4.status
|
|
413
|
+
}),
|
|
412
414
|
resourceId: blockNodeIdentifier.resourceId
|
|
413
415
|
};
|
|
414
416
|
}));
|
|
@@ -661,7 +663,8 @@ var BlockServiceADFFetchProvider = /*#__PURE__*/function () {
|
|
|
661
663
|
return _context5.abrupt("return", {
|
|
662
664
|
error: {
|
|
663
665
|
type: mapBlockError(_t7),
|
|
664
|
-
reason: _t7.message
|
|
666
|
+
reason: _t7.message,
|
|
667
|
+
statusCode: _t7.status
|
|
665
668
|
},
|
|
666
669
|
resourceId: resourceId
|
|
667
670
|
});
|
|
@@ -12,8 +12,8 @@ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t =
|
|
|
12
12
|
import isEqual from 'lodash/isEqual';
|
|
13
13
|
import { logException } from '@atlaskit/editor-common/monitoring';
|
|
14
14
|
import { fg } from '@atlaskit/platform-feature-flags';
|
|
15
|
-
import { SyncBlockError } from '../common/types';
|
|
16
|
-
import { buildErrorAttribution, cacheDeletionForcedPayload, fetchErrorPayload, fetchSuccessPayload, getSourceInfoErrorPayload, sourceInfoOrphanedPayload, updateReferenceErrorPayload } from '../utils/errorHandling';
|
|
15
|
+
import { isProviderNotReadyError, ProviderNotReadyError, SyncBlockError } from '../common/types';
|
|
16
|
+
import { buildErrorAttribution, buildFetchErrorAttribution, 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';
|
|
@@ -121,6 +121,10 @@ export var ReferenceSyncBlockStoreManager = /*#__PURE__*/function () {
|
|
|
121
121
|
},
|
|
122
122
|
getFireAnalyticsEvent: function getFireAnalyticsEvent() {
|
|
123
123
|
return _this.fireAnalyticsEvent;
|
|
124
|
+
},
|
|
125
|
+
// EDITOR-7860: skip + re-queue fetches while not ready / torn down.
|
|
126
|
+
isProviderReady: function isProviderReady() {
|
|
127
|
+
return _this.hasDataProvider();
|
|
124
128
|
}
|
|
125
129
|
});
|
|
126
130
|
|
|
@@ -133,6 +137,19 @@ export var ReferenceSyncBlockStoreManager = /*#__PURE__*/function () {
|
|
|
133
137
|
return node.type.name === 'syncBlock';
|
|
134
138
|
}
|
|
135
139
|
|
|
140
|
+
/**
|
|
141
|
+
* Whether the async data provider is wired and the manager is not torn down.
|
|
142
|
+
* Consumers gate fetch/subscribe on this to avoid a false `Data provider not
|
|
143
|
+
* set` fetch error (EDITOR-7860). The `isDestroyed` check covers managers
|
|
144
|
+
* orphaned mid-flight during an async provider swap, where `dataProvider`
|
|
145
|
+
* alone is insufficient.
|
|
146
|
+
*/
|
|
147
|
+
}, {
|
|
148
|
+
key: "hasDataProvider",
|
|
149
|
+
value: function hasDataProvider() {
|
|
150
|
+
return !this.isDestroyed && !!this.dataProvider;
|
|
151
|
+
}
|
|
152
|
+
|
|
136
153
|
/**
|
|
137
154
|
* Enables or disables real-time GraphQL subscriptions for block updates.
|
|
138
155
|
* When enabled, the store manager will subscribe to real-time updates
|
|
@@ -524,22 +541,28 @@ export var ReferenceSyncBlockStoreManager = /*#__PURE__*/function () {
|
|
|
524
541
|
return _context2.abrupt("return");
|
|
525
542
|
case 2:
|
|
526
543
|
if (this.dataProvider) {
|
|
544
|
+
_context2.next = 4;
|
|
545
|
+
break;
|
|
546
|
+
}
|
|
547
|
+
if (!fg('platform_editor_blocks_patch_3')) {
|
|
527
548
|
_context2.next = 3;
|
|
528
549
|
break;
|
|
529
550
|
}
|
|
530
|
-
throw new
|
|
551
|
+
throw new ProviderNotReadyError();
|
|
531
552
|
case 3:
|
|
553
|
+
throw new Error('Data provider not set');
|
|
554
|
+
case 4:
|
|
532
555
|
nodesToFetch.forEach(function (node) {
|
|
533
556
|
_this4.syncBlockFetchDataRequests.set(node.attrs.resourceId, true);
|
|
534
557
|
});
|
|
535
558
|
(_this$fetchExperience6 = this.fetchExperience) === null || _this$fetchExperience6 === void 0 || _this$fetchExperience6.start({});
|
|
536
|
-
_context2.next =
|
|
559
|
+
_context2.next = 5;
|
|
537
560
|
return this.dataProvider.fetchNodesData(nodesToFetch).finally(function () {
|
|
538
561
|
nodesToFetch.forEach(function (node) {
|
|
539
562
|
_this4.syncBlockFetchDataRequests.delete(node.attrs.resourceId);
|
|
540
563
|
});
|
|
541
564
|
});
|
|
542
|
-
case
|
|
565
|
+
case 5:
|
|
543
566
|
data = _context2.sent;
|
|
544
567
|
_this$processFetchedD2 = this.processFetchedData(data), hasUnexpectedError = _this$processFetchedD2.hasUnexpectedError, hasExpectedError = _this$processFetchedD2.hasExpectedError;
|
|
545
568
|
if (hasUnexpectedError) {
|
|
@@ -553,7 +576,7 @@ export var ReferenceSyncBlockStoreManager = /*#__PURE__*/function () {
|
|
|
553
576
|
} else {
|
|
554
577
|
(_this$fetchExperience9 = this.fetchExperience) === null || _this$fetchExperience9 === void 0 || _this$fetchExperience9.success();
|
|
555
578
|
}
|
|
556
|
-
case
|
|
579
|
+
case 6:
|
|
557
580
|
case "end":
|
|
558
581
|
return _context2.stop();
|
|
559
582
|
}
|
|
@@ -579,10 +602,12 @@ export var ReferenceSyncBlockStoreManager = /*#__PURE__*/function () {
|
|
|
579
602
|
data.forEach(function (syncBlockInstance) {
|
|
580
603
|
var _resolvedSyncBlockIns;
|
|
581
604
|
if (!syncBlockInstance.resourceId) {
|
|
582
|
-
var _syncBlockInstance$er, _syncBlockInstance$er2, _this5$fireAnalyticsE;
|
|
605
|
+
var _syncBlockInstance$er, _syncBlockInstance$er2, _this5$fireAnalyticsE, _syncBlockInstance$er3, _syncBlockInstance$er4, _syncBlockInstance$er5;
|
|
583
606
|
var 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';
|
|
584
607
|
// No resourceId means we cannot derive a sourceProduct here; intentionally omit.
|
|
585
|
-
|
|
608
|
+
// Classify on the structured `type` first, falling back to the free-text
|
|
609
|
+
// `reason`/payload (EDITOR-7862).
|
|
610
|
+
(_this5$fireAnalyticsE = _this5.fireAnalyticsEvent) === null || _this5$fireAnalyticsE === void 0 || _this5$fireAnalyticsE.call(_this5, 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)));
|
|
586
611
|
return;
|
|
587
612
|
}
|
|
588
613
|
var existingSyncBlock = _this5.getFromCache(syncBlockInstance.resourceId);
|
|
@@ -616,7 +641,10 @@ export var ReferenceSyncBlockStoreManager = /*#__PURE__*/function () {
|
|
|
616
641
|
var isRetryingEntityNotFound = syncBlockInstance.error.type === SyncBlockError.EntityNotFound && ((_this5$entityNotFound = _this5.entityNotFoundRetryCount.get(syncBlockInstance.resourceId)) !== null && _this5$entityNotFound !== void 0 ? _this5$entityNotFound : 0) < ENTITY_NOT_FOUND_MAX_RETRIES && fg('platform_synced_block_patch_13');
|
|
617
642
|
if (!isRetryingEntityNotFound) {
|
|
618
643
|
var _this5$fireAnalyticsE2, _syncBlockInstance$da, _syncBlockInstance$da2;
|
|
619
|
-
|
|
644
|
+
// Classify on the structured `type` (a `SyncBlockError` enum value) first,
|
|
645
|
+
// falling back to the free-text `reason` so source-state/permission strings
|
|
646
|
+
// are still bucketed (EDITOR-7862). The emitted `error` attribute is unchanged.
|
|
647
|
+
(_this5$fireAnalyticsE2 = _this5.fireAnalyticsEvent) === null || _this5$fireAnalyticsE2 === void 0 || _this5$fireAnalyticsE2.call(_this5, 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)));
|
|
620
648
|
}
|
|
621
649
|
if (syncBlockInstance.error.type === SyncBlockError.NotFound || syncBlockInstance.error.type === SyncBlockError.Forbidden) {
|
|
622
650
|
hasExpectedError = true;
|
|
@@ -931,10 +959,16 @@ export var ReferenceSyncBlockStoreManager = /*#__PURE__*/function () {
|
|
|
931
959
|
return this._subscriptionManager.subscribeToSyncBlock(resourceId, localId, callback);
|
|
932
960
|
} catch (error) {
|
|
933
961
|
var _this$fireAnalyticsEv7;
|
|
962
|
+
// EDITOR-7860: benign not-ready/torn-down case — suppress both the
|
|
963
|
+
// exception-tracker log and the analytics event (checked first so the
|
|
964
|
+
// benign case stays fully silent). Gate-off behaviour is unchanged.
|
|
965
|
+
if (isProviderNotReadyError(error) && fg('platform_editor_blocks_patch_3')) {
|
|
966
|
+
return function () {};
|
|
967
|
+
}
|
|
934
968
|
logException(error, {
|
|
935
969
|
location: 'editor-synced-block-provider/referenceSyncBlockStoreManager'
|
|
936
970
|
});
|
|
937
|
-
(_this$fireAnalyticsEv7 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv7 === void 0 || _this$fireAnalyticsEv7.call(this, fetchErrorPayload(error.message));
|
|
971
|
+
(_this$fireAnalyticsEv7 = this.fireAnalyticsEvent) === null || _this$fireAnalyticsEv7 === void 0 || _this$fireAnalyticsEv7.call(this, fetchErrorPayload(error.message, undefined, undefined, buildFetchErrorAttribution(fg('platform_editor_blocks_patch_3'), error.message)));
|
|
938
972
|
return function () {};
|
|
939
973
|
}
|
|
940
974
|
}
|
|
@@ -3,7 +3,9 @@ import _createClass from "@babel/runtime/helpers/createClass";
|
|
|
3
3
|
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
4
4
|
import rafSchedule from 'raf-schd';
|
|
5
5
|
import { logException } from '@atlaskit/editor-common/monitoring';
|
|
6
|
-
import {
|
|
6
|
+
import { fg } from '@atlaskit/platform-feature-flags';
|
|
7
|
+
import { isProviderNotReadyError } from '../common/types';
|
|
8
|
+
import { buildFetchErrorAttribution, fetchErrorPayload } from '../utils/errorHandling';
|
|
7
9
|
import { createSyncBlockNode, getSourceProductFromResourceIdSafe } from '../utils/utils';
|
|
8
10
|
/**
|
|
9
11
|
* Handles debounced batch-fetching of sync block data via `raf-schd`.
|
|
@@ -25,6 +27,17 @@ export var SyncBlockBatchFetcher = /*#__PURE__*/function () {
|
|
|
25
27
|
if (_this.pendingFetchRequests.size === 0) {
|
|
26
28
|
return;
|
|
27
29
|
}
|
|
30
|
+
|
|
31
|
+
// EDITOR-7860: not ready — skip and leave resourceIds queued for the
|
|
32
|
+
// next batch once the provider resolves. Gate-off is unchanged (the
|
|
33
|
+
// readiness check is nested under the gate so it is not consulted when
|
|
34
|
+
// the gate is off, and `fg()` stays a standalone condition so gate
|
|
35
|
+
// exposure is still tracked — satisfies @atlaskit/platform/no-preconditioning).
|
|
36
|
+
if (fg('platform_editor_blocks_patch_3')) {
|
|
37
|
+
if (_this.deps.isProviderReady && !_this.deps.isProviderReady()) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
28
41
|
var resourceIds = Array.from(_this.pendingFetchRequests);
|
|
29
42
|
var syncBlockNodes = resourceIds.map(function (resId) {
|
|
30
43
|
var subscriptions = _this.deps.getSubscriptions().get(resId) || {};
|
|
@@ -38,12 +51,27 @@ export var SyncBlockBatchFetcher = /*#__PURE__*/function () {
|
|
|
38
51
|
return _this.inFlightFetches.add(resId);
|
|
39
52
|
});
|
|
40
53
|
_this.deps.fetchSyncBlocksData(syncBlockNodes).catch(function (error) {
|
|
54
|
+
// EDITOR-7860: benign not-ready throw — re-queue for retry and emit
|
|
55
|
+
// nothing (no analytics, no exception-tracker noise). Checked before
|
|
56
|
+
// `logException` so the benign case stays silent. Re-schedule so the
|
|
57
|
+
// re-queued IDs are retried on the next frame even if no further
|
|
58
|
+
// `queueFetch()` arrives. Gate-off behaviour is unchanged.
|
|
59
|
+
if (isProviderNotReadyError(error) && fg('platform_editor_blocks_patch_3')) {
|
|
60
|
+
resourceIds.forEach(function (resId) {
|
|
61
|
+
return _this.pendingFetchRequests.add(resId);
|
|
62
|
+
});
|
|
63
|
+
if (!_this.isDestroyed) {
|
|
64
|
+
_this.scheduledBatchFetch();
|
|
65
|
+
}
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
41
68
|
logException(error, {
|
|
42
69
|
location: 'editor-synced-block-provider/syncBlockBatchFetcher/batchedFetchSyncBlocks'
|
|
43
70
|
});
|
|
71
|
+
var attribution = buildFetchErrorAttribution(fg('platform_editor_blocks_patch_3'), error.message);
|
|
44
72
|
resourceIds.forEach(function (resId) {
|
|
45
73
|
var _this$deps$getFireAna;
|
|
46
|
-
(_this$deps$getFireAna = _this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna === void 0 || _this$deps$getFireAna(fetchErrorPayload(error.message, resId, getSourceProductFromResourceIdSafe(resId)));
|
|
74
|
+
(_this$deps$getFireAna = _this.deps.getFireAnalyticsEvent()) === null || _this$deps$getFireAna === void 0 || _this$deps$getFireAna(fetchErrorPayload(error.message, resId, getSourceProductFromResourceIdSafe(resId), attribution));
|
|
47
75
|
});
|
|
48
76
|
}).finally(function () {
|
|
49
77
|
// If the fetcher was destroyed while the request was in flight,
|