@salesforce/lds-runtime-aura 1.124.2 → 1.124.4
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/dist/ldsEngineCreator.js +644 -644
- package/dist/{__mocks__ → types/__mocks__}/@salesforce/gate/lds.useNewTrackedFieldBehavior.d.ts +4 -4
- package/dist/{__mocks__ → types/__mocks__}/@salesforce/lds-ads-bridge.d.ts +2 -2
- package/dist/{__mocks__ → types/__mocks__}/@salesforce/lds-aura-storage.d.ts +1 -1
- package/dist/{__mocks__ → types/__mocks__}/@salesforce/lds-instrumentation.d.ts +9 -9
- package/dist/{__mocks__ → types/__mocks__}/@salesforce/lds-network-aura.d.ts +2 -2
- package/dist/{__mocks__ → types/__mocks__}/force/ldsGraphqlParser.d.ts +3 -3
- package/dist/{__mocks__ → types/__mocks__}/instrumentation/service.d.ts +33 -33
- package/dist/{aura-instrumentation → types/aura-instrumentation}/main.d.ts +178 -178
- package/dist/{aura-instrumentation → types/aura-instrumentation}/utils/language.d.ts +13 -13
- package/dist/{aura-instrumentation → types/aura-instrumentation}/utils/observability.d.ts +36 -36
- package/dist/{aura-instrumentation → types/aura-instrumentation}/utils/utils.d.ts +13 -13
- package/dist/{main.d.ts → types/main.d.ts} +5 -5
- package/dist/{metadata.d.ts → types/metadata.d.ts} +5 -5
- package/package.json +3 -3
package/dist/ldsEngineCreator.js
CHANGED
|
@@ -23,660 +23,660 @@ import networkAdapter, { instrument as instrument$1, forceRecordTransactionsDisa
|
|
|
23
23
|
import { instrument as instrument$3 } from 'force/adsBridge';
|
|
24
24
|
import { clearStorages } from 'force/ldsStorage';
|
|
25
25
|
|
|
26
|
-
/**
|
|
27
|
-
* Observability / Critical Availability Program (230+)
|
|
28
|
-
*
|
|
29
|
-
* This file is intended to be used as a consolidated place for all definitions, functions,
|
|
30
|
-
* and helpers related to "M1"[1].
|
|
31
|
-
*
|
|
32
|
-
* Below are the R.E.A.D.S. metrics for the Lightning Data Service, defined here[2].
|
|
33
|
-
*
|
|
34
|
-
* [1] https://salesforce.quip.com/NfW9AsbGEaTY
|
|
35
|
-
* [2] https://salesforce.quip.com/1dFvAba1b0eq
|
|
36
|
-
*/
|
|
37
|
-
const OBSERVABILITY_NAMESPACE = 'LIGHTNING.lds.service';
|
|
38
|
-
const ADAPTER_INVOCATION_COUNT_METRIC_NAME = 'request';
|
|
39
|
-
const ADAPTER_ERROR_COUNT_METRIC_NAME = 'error';
|
|
40
|
-
const NETWORK_ADAPTER_RESPONSE_METRIC_NAME = 'network-response';
|
|
41
|
-
/**
|
|
42
|
-
* W-8379680
|
|
43
|
-
* Counter for number of getApex requests.
|
|
44
|
-
*/
|
|
45
|
-
const GET_APEX_REQUEST_COUNT = {
|
|
46
|
-
get() {
|
|
47
|
-
return {
|
|
48
|
-
owner: OBSERVABILITY_NAMESPACE,
|
|
49
|
-
name: ADAPTER_INVOCATION_COUNT_METRIC_NAME + '.' + NORMALIZED_APEX_ADAPTER_NAME,
|
|
50
|
-
};
|
|
51
|
-
},
|
|
52
|
-
};
|
|
53
|
-
/**
|
|
54
|
-
* W-8828410
|
|
55
|
-
* Counter for the number of UnfulfilledSnapshotErrors the luvio engine has.
|
|
56
|
-
*/
|
|
57
|
-
const TOTAL_ADAPTER_ERROR_COUNT = {
|
|
58
|
-
get() {
|
|
59
|
-
return { owner: OBSERVABILITY_NAMESPACE, name: ADAPTER_ERROR_COUNT_METRIC_NAME };
|
|
60
|
-
},
|
|
61
|
-
};
|
|
62
|
-
/**
|
|
63
|
-
* W-8828410
|
|
64
|
-
* Counter for the number of invocations made into LDS by a wire adapter.
|
|
65
|
-
*/
|
|
66
|
-
const TOTAL_ADAPTER_REQUEST_SUCCESS_COUNT = {
|
|
67
|
-
get() {
|
|
68
|
-
return { owner: OBSERVABILITY_NAMESPACE, name: ADAPTER_INVOCATION_COUNT_METRIC_NAME };
|
|
69
|
-
},
|
|
26
|
+
/**
|
|
27
|
+
* Observability / Critical Availability Program (230+)
|
|
28
|
+
*
|
|
29
|
+
* This file is intended to be used as a consolidated place for all definitions, functions,
|
|
30
|
+
* and helpers related to "M1"[1].
|
|
31
|
+
*
|
|
32
|
+
* Below are the R.E.A.D.S. metrics for the Lightning Data Service, defined here[2].
|
|
33
|
+
*
|
|
34
|
+
* [1] https://salesforce.quip.com/NfW9AsbGEaTY
|
|
35
|
+
* [2] https://salesforce.quip.com/1dFvAba1b0eq
|
|
36
|
+
*/
|
|
37
|
+
const OBSERVABILITY_NAMESPACE = 'LIGHTNING.lds.service';
|
|
38
|
+
const ADAPTER_INVOCATION_COUNT_METRIC_NAME = 'request';
|
|
39
|
+
const ADAPTER_ERROR_COUNT_METRIC_NAME = 'error';
|
|
40
|
+
const NETWORK_ADAPTER_RESPONSE_METRIC_NAME = 'network-response';
|
|
41
|
+
/**
|
|
42
|
+
* W-8379680
|
|
43
|
+
* Counter for number of getApex requests.
|
|
44
|
+
*/
|
|
45
|
+
const GET_APEX_REQUEST_COUNT = {
|
|
46
|
+
get() {
|
|
47
|
+
return {
|
|
48
|
+
owner: OBSERVABILITY_NAMESPACE,
|
|
49
|
+
name: ADAPTER_INVOCATION_COUNT_METRIC_NAME + '.' + NORMALIZED_APEX_ADAPTER_NAME,
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* W-8828410
|
|
55
|
+
* Counter for the number of UnfulfilledSnapshotErrors the luvio engine has.
|
|
56
|
+
*/
|
|
57
|
+
const TOTAL_ADAPTER_ERROR_COUNT = {
|
|
58
|
+
get() {
|
|
59
|
+
return { owner: OBSERVABILITY_NAMESPACE, name: ADAPTER_ERROR_COUNT_METRIC_NAME };
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* W-8828410
|
|
64
|
+
* Counter for the number of invocations made into LDS by a wire adapter.
|
|
65
|
+
*/
|
|
66
|
+
const TOTAL_ADAPTER_REQUEST_SUCCESS_COUNT = {
|
|
67
|
+
get() {
|
|
68
|
+
return { owner: OBSERVABILITY_NAMESPACE, name: ADAPTER_INVOCATION_COUNT_METRIC_NAME };
|
|
69
|
+
},
|
|
70
70
|
};
|
|
71
71
|
|
|
72
|
-
const { create, keys } = Object;
|
|
73
|
-
const { isArray } = Array;
|
|
72
|
+
const { create, keys } = Object;
|
|
73
|
+
const { isArray } = Array;
|
|
74
74
|
const { stringify } = JSON;
|
|
75
75
|
|
|
76
|
-
/**
|
|
77
|
-
* A deterministic JSON stringify implementation. Heavily adapted from https://github.com/epoberezkin/fast-json-stable-stringify.
|
|
78
|
-
* This is needed because insertion order for JSON.stringify(object) affects output:
|
|
79
|
-
* JSON.stringify({a: 1, b: 2})
|
|
80
|
-
* "{"a":1,"b":2}"
|
|
81
|
-
* JSON.stringify({b: 2, a: 1})
|
|
82
|
-
* "{"b":2,"a":1}"
|
|
83
|
-
* Modified from the apex implementation to sort arrays non-destructively.
|
|
84
|
-
* @param data Data to be JSON-stringified.
|
|
85
|
-
* @returns JSON.stringified value with consistent ordering of keys.
|
|
86
|
-
*/
|
|
87
|
-
function stableJSONStringify(node) {
|
|
88
|
-
// This is for Date values.
|
|
89
|
-
if (node && node.toJSON && typeof node.toJSON === 'function') {
|
|
90
|
-
// eslint-disable-next-line no-param-reassign
|
|
91
|
-
node = node.toJSON();
|
|
92
|
-
}
|
|
93
|
-
if (node === undefined) {
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
if (typeof node === 'number') {
|
|
97
|
-
return isFinite(node) ? '' + node : 'null';
|
|
98
|
-
}
|
|
99
|
-
if (typeof node !== 'object') {
|
|
100
|
-
return stringify(node);
|
|
101
|
-
}
|
|
102
|
-
let i;
|
|
103
|
-
let out;
|
|
104
|
-
if (isArray(node)) {
|
|
105
|
-
// copy any array before sorting so we don't mutate the object.
|
|
106
|
-
// eslint-disable-next-line no-param-reassign
|
|
107
|
-
node = node.slice(0).sort();
|
|
108
|
-
out = '[';
|
|
109
|
-
for (i = 0; i < node.length; i++) {
|
|
110
|
-
if (i) {
|
|
111
|
-
out += ',';
|
|
112
|
-
}
|
|
113
|
-
out += stableJSONStringify(node[i]) || 'null';
|
|
114
|
-
}
|
|
115
|
-
return out + ']';
|
|
116
|
-
}
|
|
117
|
-
if (node === null) {
|
|
118
|
-
return 'null';
|
|
119
|
-
}
|
|
120
|
-
const keys$1 = keys(node).sort();
|
|
121
|
-
out = '';
|
|
122
|
-
for (i = 0; i < keys$1.length; i++) {
|
|
123
|
-
const key = keys$1[i];
|
|
124
|
-
const value = stableJSONStringify(node[key]);
|
|
125
|
-
if (!value) {
|
|
126
|
-
continue;
|
|
127
|
-
}
|
|
128
|
-
if (out) {
|
|
129
|
-
out += ',';
|
|
130
|
-
}
|
|
131
|
-
out += stringify(key) + ':' + value;
|
|
132
|
-
}
|
|
133
|
-
return '{' + out + '}';
|
|
134
|
-
}
|
|
135
|
-
function isPromise(value) {
|
|
136
|
-
// check for Thenable due to test frameworks using custom Promise impls
|
|
137
|
-
return value !== null && value.then !== undefined;
|
|
76
|
+
/**
|
|
77
|
+
* A deterministic JSON stringify implementation. Heavily adapted from https://github.com/epoberezkin/fast-json-stable-stringify.
|
|
78
|
+
* This is needed because insertion order for JSON.stringify(object) affects output:
|
|
79
|
+
* JSON.stringify({a: 1, b: 2})
|
|
80
|
+
* "{"a":1,"b":2}"
|
|
81
|
+
* JSON.stringify({b: 2, a: 1})
|
|
82
|
+
* "{"b":2,"a":1}"
|
|
83
|
+
* Modified from the apex implementation to sort arrays non-destructively.
|
|
84
|
+
* @param data Data to be JSON-stringified.
|
|
85
|
+
* @returns JSON.stringified value with consistent ordering of keys.
|
|
86
|
+
*/
|
|
87
|
+
function stableJSONStringify(node) {
|
|
88
|
+
// This is for Date values.
|
|
89
|
+
if (node && node.toJSON && typeof node.toJSON === 'function') {
|
|
90
|
+
// eslint-disable-next-line no-param-reassign
|
|
91
|
+
node = node.toJSON();
|
|
92
|
+
}
|
|
93
|
+
if (node === undefined) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (typeof node === 'number') {
|
|
97
|
+
return isFinite(node) ? '' + node : 'null';
|
|
98
|
+
}
|
|
99
|
+
if (typeof node !== 'object') {
|
|
100
|
+
return stringify(node);
|
|
101
|
+
}
|
|
102
|
+
let i;
|
|
103
|
+
let out;
|
|
104
|
+
if (isArray(node)) {
|
|
105
|
+
// copy any array before sorting so we don't mutate the object.
|
|
106
|
+
// eslint-disable-next-line no-param-reassign
|
|
107
|
+
node = node.slice(0).sort();
|
|
108
|
+
out = '[';
|
|
109
|
+
for (i = 0; i < node.length; i++) {
|
|
110
|
+
if (i) {
|
|
111
|
+
out += ',';
|
|
112
|
+
}
|
|
113
|
+
out += stableJSONStringify(node[i]) || 'null';
|
|
114
|
+
}
|
|
115
|
+
return out + ']';
|
|
116
|
+
}
|
|
117
|
+
if (node === null) {
|
|
118
|
+
return 'null';
|
|
119
|
+
}
|
|
120
|
+
const keys$1 = keys(node).sort();
|
|
121
|
+
out = '';
|
|
122
|
+
for (i = 0; i < keys$1.length; i++) {
|
|
123
|
+
const key = keys$1[i];
|
|
124
|
+
const value = stableJSONStringify(node[key]);
|
|
125
|
+
if (!value) {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (out) {
|
|
129
|
+
out += ',';
|
|
130
|
+
}
|
|
131
|
+
out += stringify(key) + ':' + value;
|
|
132
|
+
}
|
|
133
|
+
return '{' + out + '}';
|
|
134
|
+
}
|
|
135
|
+
function isPromise(value) {
|
|
136
|
+
// check for Thenable due to test frameworks using custom Promise impls
|
|
137
|
+
return value !== null && value.then !== undefined;
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
const APEX_ADAPTER_NAME = 'getApex';
|
|
141
|
-
const NORMALIZED_APEX_ADAPTER_NAME = `Apex.${APEX_ADAPTER_NAME}`;
|
|
142
|
-
const REFRESH_APEX_KEY = 'refreshApex';
|
|
143
|
-
const REFRESH_UIAPI_KEY = 'refreshUiApi';
|
|
144
|
-
const SUPPORTED_KEY = 'refreshSupported';
|
|
145
|
-
const UNSUPPORTED_KEY = 'refreshUnsupported';
|
|
146
|
-
const REFRESH_EVENTSOURCE = 'lds-refresh-summary';
|
|
147
|
-
const REFRESH_EVENTTYPE = 'system';
|
|
148
|
-
const REFRESH_PAYLOAD_TARGET = 'adapters';
|
|
149
|
-
const REFRESH_PAYLOAD_SCOPE = 'lds';
|
|
150
|
-
const INCOMING_WEAKETAG_0_KEY = 'incoming-weaketag-0';
|
|
151
|
-
const EXISTING_WEAKETAG_0_KEY = 'existing-weaketag-0';
|
|
152
|
-
const RECORD_API_NAME_CHANGE_COUNT_METRIC_NAME = 'record-api-name-change-count';
|
|
153
|
-
const NAMESPACE = 'lds';
|
|
154
|
-
const NETWORK_TRANSACTION_NAME = 'lds-network';
|
|
155
|
-
const CACHE_STATS_OUT_OF_TTL_MISS_POSTFIX = 'out-of-ttl-miss';
|
|
156
|
-
// Aggregate Cache Stats and Metrics for all getApex invocations
|
|
157
|
-
const getApexCacheStats = registerLdsCacheStats(NORMALIZED_APEX_ADAPTER_NAME);
|
|
158
|
-
const getApexTtlCacheStats = registerLdsCacheStats(NORMALIZED_APEX_ADAPTER_NAME + ':' + CACHE_STATS_OUT_OF_TTL_MISS_POSTFIX);
|
|
159
|
-
// Observability (READS)
|
|
160
|
-
const getApexRequestCountMetric = counter(GET_APEX_REQUEST_COUNT);
|
|
161
|
-
const totalAdapterRequestSuccessMetric = counter(TOTAL_ADAPTER_REQUEST_SUCCESS_COUNT);
|
|
162
|
-
const totalAdapterErrorMetric = counter(TOTAL_ADAPTER_ERROR_COUNT);
|
|
163
|
-
class Instrumentation {
|
|
164
|
-
constructor() {
|
|
165
|
-
this.adapterUnfulfilledErrorCounters = {};
|
|
166
|
-
this.recordApiNameChangeCounters = {};
|
|
167
|
-
this.refreshAdapterEvents = {};
|
|
168
|
-
this.refreshApiCallEventStats = {
|
|
169
|
-
[REFRESH_APEX_KEY]: 0,
|
|
170
|
-
[REFRESH_UIAPI_KEY]: 0,
|
|
171
|
-
[SUPPORTED_KEY]: 0,
|
|
172
|
-
[UNSUPPORTED_KEY]: 0,
|
|
173
|
-
};
|
|
174
|
-
this.lastRefreshApiCall = null;
|
|
175
|
-
this.weakEtagZeroEvents = {};
|
|
176
|
-
this.adapterCacheMisses = new LRUCache(250);
|
|
177
|
-
if (typeof window !== 'undefined' && window.addEventListener) {
|
|
178
|
-
window.addEventListener('beforeunload', () => {
|
|
179
|
-
if (keys(this.weakEtagZeroEvents).length > 0) {
|
|
180
|
-
perfStart(NETWORK_TRANSACTION_NAME);
|
|
181
|
-
perfEnd(NETWORK_TRANSACTION_NAME, this.weakEtagZeroEvents);
|
|
182
|
-
}
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
registerPeriodicLogger(NAMESPACE, this.logRefreshStats.bind(this));
|
|
186
|
-
}
|
|
187
|
-
/**
|
|
188
|
-
* Instruments an existing adapter to log argus metrics and cache stats.
|
|
189
|
-
* @param adapter The adapter function.
|
|
190
|
-
* @param metadata The adapter metadata.
|
|
191
|
-
* @param wireConfigKeyFn Optional function to transform wire configs to a unique key.
|
|
192
|
-
* @returns The wrapped adapter.
|
|
193
|
-
*/
|
|
194
|
-
instrumentAdapter(adapter, metadata) {
|
|
195
|
-
// We are consolidating all apex adapter instrumentation calls under a single key
|
|
196
|
-
const { apiFamily, name, ttl } = metadata;
|
|
197
|
-
const adapterName = normalizeAdapterName(name, apiFamily);
|
|
198
|
-
const isGetApexAdapter = isApexAdapter(name);
|
|
199
|
-
const stats = isGetApexAdapter ? getApexCacheStats : registerLdsCacheStats(adapterName);
|
|
200
|
-
const ttlMissStats = isGetApexAdapter
|
|
201
|
-
? getApexTtlCacheStats
|
|
202
|
-
: registerLdsCacheStats(adapterName + ':' + CACHE_STATS_OUT_OF_TTL_MISS_POSTFIX);
|
|
203
|
-
/**
|
|
204
|
-
* W-8076905
|
|
205
|
-
* Dynamically generated metric. Simple counter for all requests made by this adapter.
|
|
206
|
-
*/
|
|
207
|
-
const wireAdapterRequestMetric = isGetApexAdapter
|
|
208
|
-
? getApexRequestCountMetric
|
|
209
|
-
: counter(createMetricsKey(OBSERVABILITY_NAMESPACE, ADAPTER_INVOCATION_COUNT_METRIC_NAME, adapterName));
|
|
210
|
-
const instrumentedAdapter = (config, requestContext) => {
|
|
211
|
-
// increment overall and adapter request metrics
|
|
212
|
-
wireAdapterRequestMetric.increment(1);
|
|
213
|
-
totalAdapterRequestSuccessMetric.increment(1);
|
|
214
|
-
// execute adapter logic
|
|
215
|
-
const result = adapter(config, requestContext);
|
|
216
|
-
// In the case where the adapter returns a non-Pending Snapshot it is constructed out of the store
|
|
217
|
-
// (cache hit) whereas a Promise<Snapshot> or Pending Snapshot indicates a network request (cache miss).
|
|
218
|
-
//
|
|
219
|
-
// Note: we can't do a plain instanceof check for a promise here since the Promise may
|
|
220
|
-
// originate from another javascript realm (for example: in jest test). Instead we use a
|
|
221
|
-
// duck-typing approach by checking if the result has a then property.
|
|
222
|
-
//
|
|
223
|
-
// For adapters without persistent store:
|
|
224
|
-
// - total cache hit ratio:
|
|
225
|
-
// [in-memory cache hit count] / ([in-memory cache hit count] + [in-memory cache miss count])
|
|
226
|
-
// For adapters with persistent store:
|
|
227
|
-
// - in-memory cache hit ratio:
|
|
228
|
-
// [in-memory cache hit count] / ([in-memory cache hit count] + [in-memory cache miss count])
|
|
229
|
-
// - total cache hit ratio:
|
|
230
|
-
// ([in-memory cache hit count] + [store cache hit count]) / ([in-memory cache hit count] + [in-memory cache miss count])
|
|
231
|
-
// if result === null then config is insufficient/invalid so do not log
|
|
232
|
-
if (isPromise(result)) {
|
|
233
|
-
stats.logMisses();
|
|
234
|
-
if (ttl !== undefined) {
|
|
235
|
-
this.logAdapterCacheMissOutOfTtlDuration(adapterName, config, ttlMissStats, Date.now(), ttl);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
else if (result !== null) {
|
|
239
|
-
stats.logHits();
|
|
240
|
-
}
|
|
241
|
-
return result;
|
|
242
|
-
};
|
|
243
|
-
// Set the name property on the function for debugging purposes.
|
|
244
|
-
Object.defineProperty(instrumentedAdapter, 'name', {
|
|
245
|
-
value: name + '__instrumented',
|
|
246
|
-
});
|
|
247
|
-
return instrumentAdapter(instrumentedAdapter, metadata);
|
|
248
|
-
}
|
|
249
|
-
/**
|
|
250
|
-
* Logs when adapter requests come in. If we have subsequent cache misses on a given config, beyond its TTL then log the duration to metrics.
|
|
251
|
-
* Backed by an LRU Cache implementation to prevent too many record entries from being stored in-memory.
|
|
252
|
-
* @param name The wire adapter name.
|
|
253
|
-
* @param config The config passed into wire adapter.
|
|
254
|
-
* @param ttlMissStats CacheStatsLogger to log misses out of TTL.
|
|
255
|
-
* @param currentCacheMissTimestamp Timestamp for when the request was made.
|
|
256
|
-
* @param ttl TTL for the wire adapter.
|
|
257
|
-
*/
|
|
258
|
-
logAdapterCacheMissOutOfTtlDuration(name, config, ttlMissStats, currentCacheMissTimestamp, ttl) {
|
|
259
|
-
const configKey = `${name}:${stableJSONStringify(config)}`;
|
|
260
|
-
const existingCacheMissTimestamp = this.adapterCacheMisses.get(configKey);
|
|
261
|
-
this.adapterCacheMisses.set(configKey, currentCacheMissTimestamp);
|
|
262
|
-
if (existingCacheMissTimestamp !== undefined) {
|
|
263
|
-
const duration = currentCacheMissTimestamp - existingCacheMissTimestamp;
|
|
264
|
-
if (duration > ttl) {
|
|
265
|
-
ttlMissStats.logMisses();
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
/**
|
|
270
|
-
* Injected to LDS for Luvio specific instrumentation.
|
|
271
|
-
*
|
|
272
|
-
* @param context The transaction context.
|
|
273
|
-
*/
|
|
274
|
-
instrumentLuvio(context) {
|
|
275
|
-
instrumentLuvio(context);
|
|
276
|
-
if (this.isRefreshAdapterEvent(context)) {
|
|
277
|
-
this.aggregateRefreshAdapterEvents(context);
|
|
278
|
-
}
|
|
279
|
-
else if (this.isAdapterUnfulfilledError(context)) {
|
|
280
|
-
this.incrementAdapterRequestErrorCount(context);
|
|
281
|
-
}
|
|
282
|
-
else ;
|
|
283
|
-
}
|
|
284
|
-
/**
|
|
285
|
-
* Returns whether or not this is a RefreshAdapterEvent.
|
|
286
|
-
* @param context The transaction context.
|
|
287
|
-
* @returns Whether or not this is a RefreshAdapterEvent.
|
|
288
|
-
*/
|
|
289
|
-
isRefreshAdapterEvent(context) {
|
|
290
|
-
return context[REFRESH_ADAPTER_EVENT] === true;
|
|
291
|
-
}
|
|
292
|
-
/**
|
|
293
|
-
* Returns whether or not this is an AdapterUnfulfilledError.
|
|
294
|
-
* @param context The transaction context.
|
|
295
|
-
* @returns Whether or not this is an AdapterUnfulfilledError.
|
|
296
|
-
*/
|
|
297
|
-
isAdapterUnfulfilledError(context) {
|
|
298
|
-
return context[ADAPTER_UNFULFILLED_ERROR] === true;
|
|
299
|
-
}
|
|
300
|
-
/**
|
|
301
|
-
* Specific instrumentation for getRecordNotifyChange.
|
|
302
|
-
* temporary implementation to match existing aura call for now
|
|
303
|
-
*
|
|
304
|
-
* @param uniqueWeakEtags whether weakEtags match or not
|
|
305
|
-
* @param error if dispatchResourceRequest fails for any reason
|
|
306
|
-
*/
|
|
307
|
-
notifyChangeNetwork(uniqueWeakEtags, error) {
|
|
308
|
-
perfStart(NETWORK_TRANSACTION_NAME);
|
|
309
|
-
if (error === true) {
|
|
310
|
-
perfEnd(NETWORK_TRANSACTION_NAME, { 'notify-change-network': 'error' });
|
|
311
|
-
}
|
|
312
|
-
else {
|
|
313
|
-
perfEnd(NETWORK_TRANSACTION_NAME, { 'notify-change-network': uniqueWeakEtags });
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
/**
|
|
317
|
-
* Parses and aggregates weakETagZero events to be sent in summarized log line.
|
|
318
|
-
* @param context The transaction context.
|
|
319
|
-
*/
|
|
320
|
-
aggregateWeakETagEvents(incomingWeakEtagZero, existingWeakEtagZero, apiName) {
|
|
321
|
-
const key = 'weaketag-0-' + apiName;
|
|
322
|
-
if (this.weakEtagZeroEvents[key] === undefined) {
|
|
323
|
-
this.weakEtagZeroEvents[key] = {
|
|
324
|
-
[EXISTING_WEAKETAG_0_KEY]: 0,
|
|
325
|
-
[INCOMING_WEAKETAG_0_KEY]: 0,
|
|
326
|
-
};
|
|
327
|
-
}
|
|
328
|
-
if (existingWeakEtagZero) {
|
|
329
|
-
this.weakEtagZeroEvents[key][EXISTING_WEAKETAG_0_KEY] += 1;
|
|
330
|
-
}
|
|
331
|
-
if (incomingWeakEtagZero) {
|
|
332
|
-
this.weakEtagZeroEvents[key][INCOMING_WEAKETAG_0_KEY] += 1;
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
/**
|
|
336
|
-
* Aggregates refresh adapter events to be sent in summarized log line.
|
|
337
|
-
* - how many times refreshApex is called
|
|
338
|
-
* - how many times refresh from lightning/uiRecordApi is called
|
|
339
|
-
* - number of supported calls: refreshApex called on apex adapter
|
|
340
|
-
* - number of unsupported calls: refreshApex on non-apex adapter
|
|
341
|
-
* + any use of refresh from uiRecordApi module
|
|
342
|
-
* - count of refresh calls per adapter
|
|
343
|
-
* @param context The refresh adapter event.
|
|
344
|
-
*/
|
|
345
|
-
aggregateRefreshAdapterEvents(context) {
|
|
346
|
-
// We are consolidating all apex adapter instrumentation calls under a single key
|
|
347
|
-
// Adding additional logging that getApex adapters can invoke? Read normalizeAdapterName ts-doc.
|
|
348
|
-
const adapterName = normalizeAdapterName(context.adapterName);
|
|
349
|
-
if (this.lastRefreshApiCall === REFRESH_APEX_KEY) {
|
|
350
|
-
if (isApexAdapter(adapterName)) {
|
|
351
|
-
this.refreshApiCallEventStats[SUPPORTED_KEY] += 1;
|
|
352
|
-
}
|
|
353
|
-
else {
|
|
354
|
-
this.refreshApiCallEventStats[UNSUPPORTED_KEY] += 1;
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
else if (this.lastRefreshApiCall === REFRESH_UIAPI_KEY) {
|
|
358
|
-
this.refreshApiCallEventStats[UNSUPPORTED_KEY] += 1;
|
|
359
|
-
}
|
|
360
|
-
if (this.refreshAdapterEvents[adapterName] === undefined) {
|
|
361
|
-
this.refreshAdapterEvents[adapterName] = 0;
|
|
362
|
-
}
|
|
363
|
-
this.refreshAdapterEvents[adapterName] += 1;
|
|
364
|
-
this.lastRefreshApiCall = null;
|
|
365
|
-
}
|
|
366
|
-
/**
|
|
367
|
-
* Increments call stat for incoming refresh api call, and sets the name
|
|
368
|
-
* to be used in {@link aggregateRefreshCalls}
|
|
369
|
-
* @param from The name of the refresh function called.
|
|
370
|
-
*/
|
|
371
|
-
handleRefreshApiCall(apiName) {
|
|
372
|
-
this.refreshApiCallEventStats[apiName] += 1;
|
|
373
|
-
// set function call to be used with aggregateRefreshCalls
|
|
374
|
-
this.lastRefreshApiCall = apiName;
|
|
375
|
-
}
|
|
376
|
-
/**
|
|
377
|
-
* W-7302241
|
|
378
|
-
* Logs refresh call summary stats as a LightningInteraction.
|
|
379
|
-
*/
|
|
380
|
-
logRefreshStats() {
|
|
381
|
-
if (keys(this.refreshAdapterEvents).length > 0) {
|
|
382
|
-
interaction(REFRESH_PAYLOAD_TARGET, REFRESH_PAYLOAD_SCOPE, this.refreshAdapterEvents, REFRESH_EVENTSOURCE, REFRESH_EVENTTYPE, this.refreshApiCallEventStats);
|
|
383
|
-
this.resetRefreshStats();
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
/**
|
|
387
|
-
* Resets the stat trackers for refresh call events.
|
|
388
|
-
*/
|
|
389
|
-
resetRefreshStats() {
|
|
390
|
-
this.refreshAdapterEvents = {};
|
|
391
|
-
this.refreshApiCallEventStats = {
|
|
392
|
-
[REFRESH_APEX_KEY]: 0,
|
|
393
|
-
[REFRESH_UIAPI_KEY]: 0,
|
|
394
|
-
[SUPPORTED_KEY]: 0,
|
|
395
|
-
[UNSUPPORTED_KEY]: 0,
|
|
396
|
-
};
|
|
397
|
-
this.lastRefreshApiCall = null;
|
|
398
|
-
}
|
|
399
|
-
/**
|
|
400
|
-
* W-7801618
|
|
401
|
-
* Counter for occurrences where the incoming record to be merged has a different apiName.
|
|
402
|
-
* Dynamically generated metric, stored in an {@link RecordApiNameChangeCounters} object.
|
|
403
|
-
*
|
|
404
|
-
* @param context The transaction context.
|
|
405
|
-
*
|
|
406
|
-
* Note: Short-lived metric candidate, remove at the end of 230
|
|
407
|
-
*/
|
|
408
|
-
incrementRecordApiNameChangeCount(_incomingApiName, existingApiName) {
|
|
409
|
-
let apiNameChangeCounter = this.recordApiNameChangeCounters[existingApiName];
|
|
410
|
-
if (apiNameChangeCounter === undefined) {
|
|
411
|
-
apiNameChangeCounter = counter(createMetricsKey(NAMESPACE, RECORD_API_NAME_CHANGE_COUNT_METRIC_NAME, existingApiName));
|
|
412
|
-
this.recordApiNameChangeCounters[existingApiName] = apiNameChangeCounter;
|
|
413
|
-
}
|
|
414
|
-
apiNameChangeCounter.increment(1);
|
|
415
|
-
}
|
|
416
|
-
/**
|
|
417
|
-
* W-8620679
|
|
418
|
-
* Increment the counter for an UnfulfilledSnapshotError coming from luvio
|
|
419
|
-
*
|
|
420
|
-
* @param context The transaction context.
|
|
421
|
-
*/
|
|
422
|
-
incrementAdapterRequestErrorCount(context) {
|
|
423
|
-
// We are consolidating all apex adapter instrumentation calls under a single key
|
|
424
|
-
const adapterName = normalizeAdapterName(context.adapterName);
|
|
425
|
-
let adapterRequestErrorCounter = this.adapterUnfulfilledErrorCounters[adapterName];
|
|
426
|
-
if (adapterRequestErrorCounter === undefined) {
|
|
427
|
-
adapterRequestErrorCounter = counter(createMetricsKey(OBSERVABILITY_NAMESPACE, ADAPTER_ERROR_COUNT_METRIC_NAME, adapterName));
|
|
428
|
-
this.adapterUnfulfilledErrorCounters[adapterName] = adapterRequestErrorCounter;
|
|
429
|
-
}
|
|
430
|
-
adapterRequestErrorCounter.increment(1);
|
|
431
|
-
totalAdapterErrorMetric.increment(1);
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
function createMetricsKey(owner, name, unit) {
|
|
435
|
-
let metricName = name;
|
|
436
|
-
if (unit) {
|
|
437
|
-
metricName = metricName + '.' + unit;
|
|
438
|
-
}
|
|
439
|
-
return {
|
|
440
|
-
get() {
|
|
441
|
-
return { owner: owner, name: metricName };
|
|
442
|
-
},
|
|
443
|
-
};
|
|
444
|
-
}
|
|
445
|
-
/**
|
|
446
|
-
* Returns whether adapter is an Apex one or not.
|
|
447
|
-
* @param adapterName The name of the adapter.
|
|
448
|
-
*/
|
|
449
|
-
function isApexAdapter(adapterName) {
|
|
450
|
-
return adapterName.indexOf(APEX_ADAPTER_NAME) > -1;
|
|
451
|
-
}
|
|
452
|
-
/**
|
|
453
|
-
* Normalizes getApex adapter names to `Apex.getApex`. Non-Apex adapters will be prefixed with
|
|
454
|
-
* API family, if supplied. Example: `UiApi.getRecord`.
|
|
455
|
-
*
|
|
456
|
-
* Note: If you are adding additional logging that can come from getApex adapter contexts that provide
|
|
457
|
-
* the full getApex adapter name (i.e. getApex_[namespace]_[class]_[function]_[continuation]),
|
|
458
|
-
* ensure to call this method to normalize all logging to 'getApex'. This
|
|
459
|
-
* is because Argus has a 50k key cardinality limit. More context: W-8379680.
|
|
460
|
-
*
|
|
461
|
-
* @param adapterName The name of the adapter.
|
|
462
|
-
* @param apiFamily The API family of the adapter.
|
|
463
|
-
*/
|
|
464
|
-
function normalizeAdapterName(adapterName, apiFamily) {
|
|
465
|
-
if (isApexAdapter(adapterName)) {
|
|
466
|
-
return NORMALIZED_APEX_ADAPTER_NAME;
|
|
467
|
-
}
|
|
468
|
-
return apiFamily ? `${apiFamily}.${adapterName}` : adapterName;
|
|
469
|
-
}
|
|
470
|
-
const timerMetricTracker = create(null);
|
|
471
|
-
/**
|
|
472
|
-
* Calls instrumentation/service telemetry timer
|
|
473
|
-
* @param name Name of the metric
|
|
474
|
-
* @param duration number to update backing percentile histogram, negative numbers ignored
|
|
475
|
-
*/
|
|
476
|
-
function updateTimerMetric(name, duration) {
|
|
477
|
-
let metric = timerMetricTracker[name];
|
|
478
|
-
if (metric === undefined) {
|
|
479
|
-
metric = timer(createMetricsKey(NAMESPACE, name));
|
|
480
|
-
timerMetricTracker[name] = metric;
|
|
481
|
-
}
|
|
482
|
-
timerMetricAddDuration(metric, duration);
|
|
483
|
-
}
|
|
484
|
-
function timerMetricAddDuration(timer, duration) {
|
|
485
|
-
// Guard against negative values since it causes error to be thrown by MetricsService
|
|
486
|
-
if (duration >= 0) {
|
|
487
|
-
timer.addDuration(duration);
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
/**
|
|
491
|
-
* W-10315098
|
|
492
|
-
* Increments the counter associated with the request response. Counts are bucketed by status.
|
|
493
|
-
*/
|
|
494
|
-
const requestResponseMetricTracker = create(null);
|
|
495
|
-
function incrementRequestResponseCount(cb) {
|
|
496
|
-
const status = cb().status;
|
|
497
|
-
let metric = requestResponseMetricTracker[status];
|
|
498
|
-
if (metric === undefined) {
|
|
499
|
-
metric = counter(createMetricsKey(OBSERVABILITY_NAMESPACE, NETWORK_ADAPTER_RESPONSE_METRIC_NAME, `${status.valueOf()}`));
|
|
500
|
-
requestResponseMetricTracker[status] = metric;
|
|
501
|
-
}
|
|
502
|
-
metric.increment();
|
|
503
|
-
}
|
|
504
|
-
/**
|
|
505
|
-
* Add a mark to the metrics service.
|
|
506
|
-
*
|
|
507
|
-
* @param name The mark name.
|
|
508
|
-
* @param content The mark content.
|
|
509
|
-
*/
|
|
510
|
-
function mark(name, content) {
|
|
511
|
-
mark$1(NAMESPACE, name, content);
|
|
512
|
-
}
|
|
513
|
-
/**
|
|
514
|
-
* Create a new instrumentation cache stats and return it.
|
|
515
|
-
*
|
|
516
|
-
* @param name The cache logger name.
|
|
517
|
-
*/
|
|
518
|
-
function registerLdsCacheStats(name) {
|
|
519
|
-
return registerCacheStats(`${NAMESPACE}:${name}`);
|
|
520
|
-
}
|
|
521
|
-
/**
|
|
522
|
-
* Add or overwrite hooks that require aura implementations
|
|
523
|
-
*/
|
|
524
|
-
function setAuraInstrumentationHooks() {
|
|
525
|
-
instrument({
|
|
526
|
-
recordConflictsResolved: (serverRequestCount) => {
|
|
527
|
-
// Ignore 0 values which can originate from ADS bridge
|
|
528
|
-
if (serverRequestCount > 0) {
|
|
529
|
-
updatePercentileHistogramMetric('record-conflicts-resolved', serverRequestCount);
|
|
530
|
-
}
|
|
531
|
-
},
|
|
532
|
-
nullDisplayValueConflict: ({ fieldType, areValuesEqual }) => {
|
|
533
|
-
const metricName = `merge-null-dv-count.${fieldType}`;
|
|
534
|
-
if (fieldType === 'scalar') {
|
|
535
|
-
incrementCounterMetric(`${metricName}.${areValuesEqual}`);
|
|
536
|
-
}
|
|
537
|
-
else {
|
|
538
|
-
incrementCounterMetric(metricName);
|
|
539
|
-
}
|
|
540
|
-
},
|
|
541
|
-
getRecordNotifyChangeAllowed: incrementGetRecordNotifyChangeAllowCount,
|
|
542
|
-
getRecordNotifyChangeDropped: incrementGetRecordNotifyChangeDropCount,
|
|
543
|
-
notifyRecordUpdateAvailableAllowed: incrementNotifyRecordUpdateAvailableAllowCount,
|
|
544
|
-
notifyRecordUpdateAvailableDropped: incrementNotifyRecordUpdateAvailableDropCount,
|
|
545
|
-
recordApiNameChanged: instrumentation.incrementRecordApiNameChangeCount.bind(instrumentation),
|
|
546
|
-
weakEtagZero: instrumentation.aggregateWeakETagEvents.bind(instrumentation),
|
|
547
|
-
getRecordNotifyChangeNetworkResult: instrumentation.notifyChangeNetwork.bind(instrumentation),
|
|
548
|
-
});
|
|
549
|
-
withRegistration('@salesforce/lds-adapters-uiapi', (reg) => setLdsAdaptersUiapiInstrumentation(reg));
|
|
550
|
-
instrument$1({
|
|
551
|
-
logCrud: logCRUDLightningInteraction,
|
|
552
|
-
networkResponse: incrementRequestResponseCount,
|
|
553
|
-
});
|
|
554
|
-
instrument$2({
|
|
555
|
-
refreshCalled: instrumentation.handleRefreshApiCall.bind(instrumentation),
|
|
556
|
-
instrumentAdapter: instrumentation.instrumentAdapter.bind(instrumentation),
|
|
557
|
-
});
|
|
558
|
-
instrument$3({
|
|
559
|
-
timerMetricAddDuration: updateTimerMetric,
|
|
560
|
-
});
|
|
561
|
-
// Our getRecord through aggregate-ui CRUD logging has moved
|
|
562
|
-
// to lds-network-adapter. We still need to respect the
|
|
563
|
-
// orgs environment setting
|
|
564
|
-
if (forceRecordTransactionsDisabled === false) {
|
|
565
|
-
ldsNetworkAdapterInstrument({
|
|
566
|
-
getRecordAggregateResolve: (cb) => {
|
|
567
|
-
const { recordId, apiName } = cb();
|
|
568
|
-
logCRUDLightningInteraction('read', {
|
|
569
|
-
recordId,
|
|
570
|
-
recordType: apiName,
|
|
571
|
-
state: 'SUCCESS',
|
|
572
|
-
});
|
|
573
|
-
},
|
|
574
|
-
getRecordAggregateReject: (cb) => {
|
|
575
|
-
const recordId = cb();
|
|
576
|
-
logCRUDLightningInteraction('read', {
|
|
577
|
-
recordId,
|
|
578
|
-
state: 'ERROR',
|
|
579
|
-
});
|
|
580
|
-
},
|
|
581
|
-
});
|
|
582
|
-
}
|
|
583
|
-
withRegistration('@salesforce/lds-network-adapter', (reg) => setLdsNetworkAdapterInstrumentation(reg));
|
|
584
|
-
}
|
|
585
|
-
/**
|
|
586
|
-
* Initialize the instrumentation and instrument the LDS instance and the InMemoryStore.
|
|
587
|
-
*
|
|
588
|
-
* @param luvio The Luvio instance to instrument.
|
|
589
|
-
* @param store The InMemoryStore to instrument.
|
|
590
|
-
*/
|
|
591
|
-
function setupInstrumentation(luvio, store) {
|
|
592
|
-
setupInstrumentation$1(luvio, store);
|
|
593
|
-
setAuraInstrumentationHooks();
|
|
594
|
-
}
|
|
595
|
-
/**
|
|
596
|
-
* Note: locator.scope is set to 'force_record' in order for the instrumentation gate to work, which will
|
|
597
|
-
* disable all crud operations if it is on.
|
|
598
|
-
* @param eventSource - Source of the logging event.
|
|
599
|
-
* @param attributes - Free form object of attributes to log.
|
|
600
|
-
*/
|
|
601
|
-
function logCRUDLightningInteraction(eventSource, attributes) {
|
|
602
|
-
interaction(eventSource, 'force_record', null, eventSource, 'crud', attributes);
|
|
603
|
-
}
|
|
140
|
+
const APEX_ADAPTER_NAME = 'getApex';
|
|
141
|
+
const NORMALIZED_APEX_ADAPTER_NAME = `Apex.${APEX_ADAPTER_NAME}`;
|
|
142
|
+
const REFRESH_APEX_KEY = 'refreshApex';
|
|
143
|
+
const REFRESH_UIAPI_KEY = 'refreshUiApi';
|
|
144
|
+
const SUPPORTED_KEY = 'refreshSupported';
|
|
145
|
+
const UNSUPPORTED_KEY = 'refreshUnsupported';
|
|
146
|
+
const REFRESH_EVENTSOURCE = 'lds-refresh-summary';
|
|
147
|
+
const REFRESH_EVENTTYPE = 'system';
|
|
148
|
+
const REFRESH_PAYLOAD_TARGET = 'adapters';
|
|
149
|
+
const REFRESH_PAYLOAD_SCOPE = 'lds';
|
|
150
|
+
const INCOMING_WEAKETAG_0_KEY = 'incoming-weaketag-0';
|
|
151
|
+
const EXISTING_WEAKETAG_0_KEY = 'existing-weaketag-0';
|
|
152
|
+
const RECORD_API_NAME_CHANGE_COUNT_METRIC_NAME = 'record-api-name-change-count';
|
|
153
|
+
const NAMESPACE = 'lds';
|
|
154
|
+
const NETWORK_TRANSACTION_NAME = 'lds-network';
|
|
155
|
+
const CACHE_STATS_OUT_OF_TTL_MISS_POSTFIX = 'out-of-ttl-miss';
|
|
156
|
+
// Aggregate Cache Stats and Metrics for all getApex invocations
|
|
157
|
+
const getApexCacheStats = registerLdsCacheStats(NORMALIZED_APEX_ADAPTER_NAME);
|
|
158
|
+
const getApexTtlCacheStats = registerLdsCacheStats(NORMALIZED_APEX_ADAPTER_NAME + ':' + CACHE_STATS_OUT_OF_TTL_MISS_POSTFIX);
|
|
159
|
+
// Observability (READS)
|
|
160
|
+
const getApexRequestCountMetric = counter(GET_APEX_REQUEST_COUNT);
|
|
161
|
+
const totalAdapterRequestSuccessMetric = counter(TOTAL_ADAPTER_REQUEST_SUCCESS_COUNT);
|
|
162
|
+
const totalAdapterErrorMetric = counter(TOTAL_ADAPTER_ERROR_COUNT);
|
|
163
|
+
class Instrumentation {
|
|
164
|
+
constructor() {
|
|
165
|
+
this.adapterUnfulfilledErrorCounters = {};
|
|
166
|
+
this.recordApiNameChangeCounters = {};
|
|
167
|
+
this.refreshAdapterEvents = {};
|
|
168
|
+
this.refreshApiCallEventStats = {
|
|
169
|
+
[REFRESH_APEX_KEY]: 0,
|
|
170
|
+
[REFRESH_UIAPI_KEY]: 0,
|
|
171
|
+
[SUPPORTED_KEY]: 0,
|
|
172
|
+
[UNSUPPORTED_KEY]: 0,
|
|
173
|
+
};
|
|
174
|
+
this.lastRefreshApiCall = null;
|
|
175
|
+
this.weakEtagZeroEvents = {};
|
|
176
|
+
this.adapterCacheMisses = new LRUCache(250);
|
|
177
|
+
if (typeof window !== 'undefined' && window.addEventListener) {
|
|
178
|
+
window.addEventListener('beforeunload', () => {
|
|
179
|
+
if (keys(this.weakEtagZeroEvents).length > 0) {
|
|
180
|
+
perfStart(NETWORK_TRANSACTION_NAME);
|
|
181
|
+
perfEnd(NETWORK_TRANSACTION_NAME, this.weakEtagZeroEvents);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
registerPeriodicLogger(NAMESPACE, this.logRefreshStats.bind(this));
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Instruments an existing adapter to log argus metrics and cache stats.
|
|
189
|
+
* @param adapter The adapter function.
|
|
190
|
+
* @param metadata The adapter metadata.
|
|
191
|
+
* @param wireConfigKeyFn Optional function to transform wire configs to a unique key.
|
|
192
|
+
* @returns The wrapped adapter.
|
|
193
|
+
*/
|
|
194
|
+
instrumentAdapter(adapter, metadata) {
|
|
195
|
+
// We are consolidating all apex adapter instrumentation calls under a single key
|
|
196
|
+
const { apiFamily, name, ttl } = metadata;
|
|
197
|
+
const adapterName = normalizeAdapterName(name, apiFamily);
|
|
198
|
+
const isGetApexAdapter = isApexAdapter(name);
|
|
199
|
+
const stats = isGetApexAdapter ? getApexCacheStats : registerLdsCacheStats(adapterName);
|
|
200
|
+
const ttlMissStats = isGetApexAdapter
|
|
201
|
+
? getApexTtlCacheStats
|
|
202
|
+
: registerLdsCacheStats(adapterName + ':' + CACHE_STATS_OUT_OF_TTL_MISS_POSTFIX);
|
|
203
|
+
/**
|
|
204
|
+
* W-8076905
|
|
205
|
+
* Dynamically generated metric. Simple counter for all requests made by this adapter.
|
|
206
|
+
*/
|
|
207
|
+
const wireAdapterRequestMetric = isGetApexAdapter
|
|
208
|
+
? getApexRequestCountMetric
|
|
209
|
+
: counter(createMetricsKey(OBSERVABILITY_NAMESPACE, ADAPTER_INVOCATION_COUNT_METRIC_NAME, adapterName));
|
|
210
|
+
const instrumentedAdapter = (config, requestContext) => {
|
|
211
|
+
// increment overall and adapter request metrics
|
|
212
|
+
wireAdapterRequestMetric.increment(1);
|
|
213
|
+
totalAdapterRequestSuccessMetric.increment(1);
|
|
214
|
+
// execute adapter logic
|
|
215
|
+
const result = adapter(config, requestContext);
|
|
216
|
+
// In the case where the adapter returns a non-Pending Snapshot it is constructed out of the store
|
|
217
|
+
// (cache hit) whereas a Promise<Snapshot> or Pending Snapshot indicates a network request (cache miss).
|
|
218
|
+
//
|
|
219
|
+
// Note: we can't do a plain instanceof check for a promise here since the Promise may
|
|
220
|
+
// originate from another javascript realm (for example: in jest test). Instead we use a
|
|
221
|
+
// duck-typing approach by checking if the result has a then property.
|
|
222
|
+
//
|
|
223
|
+
// For adapters without persistent store:
|
|
224
|
+
// - total cache hit ratio:
|
|
225
|
+
// [in-memory cache hit count] / ([in-memory cache hit count] + [in-memory cache miss count])
|
|
226
|
+
// For adapters with persistent store:
|
|
227
|
+
// - in-memory cache hit ratio:
|
|
228
|
+
// [in-memory cache hit count] / ([in-memory cache hit count] + [in-memory cache miss count])
|
|
229
|
+
// - total cache hit ratio:
|
|
230
|
+
// ([in-memory cache hit count] + [store cache hit count]) / ([in-memory cache hit count] + [in-memory cache miss count])
|
|
231
|
+
// if result === null then config is insufficient/invalid so do not log
|
|
232
|
+
if (isPromise(result)) {
|
|
233
|
+
stats.logMisses();
|
|
234
|
+
if (ttl !== undefined) {
|
|
235
|
+
this.logAdapterCacheMissOutOfTtlDuration(adapterName, config, ttlMissStats, Date.now(), ttl);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
else if (result !== null) {
|
|
239
|
+
stats.logHits();
|
|
240
|
+
}
|
|
241
|
+
return result;
|
|
242
|
+
};
|
|
243
|
+
// Set the name property on the function for debugging purposes.
|
|
244
|
+
Object.defineProperty(instrumentedAdapter, 'name', {
|
|
245
|
+
value: name + '__instrumented',
|
|
246
|
+
});
|
|
247
|
+
return instrumentAdapter(instrumentedAdapter, metadata);
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Logs when adapter requests come in. If we have subsequent cache misses on a given config, beyond its TTL then log the duration to metrics.
|
|
251
|
+
* Backed by an LRU Cache implementation to prevent too many record entries from being stored in-memory.
|
|
252
|
+
* @param name The wire adapter name.
|
|
253
|
+
* @param config The config passed into wire adapter.
|
|
254
|
+
* @param ttlMissStats CacheStatsLogger to log misses out of TTL.
|
|
255
|
+
* @param currentCacheMissTimestamp Timestamp for when the request was made.
|
|
256
|
+
* @param ttl TTL for the wire adapter.
|
|
257
|
+
*/
|
|
258
|
+
logAdapterCacheMissOutOfTtlDuration(name, config, ttlMissStats, currentCacheMissTimestamp, ttl) {
|
|
259
|
+
const configKey = `${name}:${stableJSONStringify(config)}`;
|
|
260
|
+
const existingCacheMissTimestamp = this.adapterCacheMisses.get(configKey);
|
|
261
|
+
this.adapterCacheMisses.set(configKey, currentCacheMissTimestamp);
|
|
262
|
+
if (existingCacheMissTimestamp !== undefined) {
|
|
263
|
+
const duration = currentCacheMissTimestamp - existingCacheMissTimestamp;
|
|
264
|
+
if (duration > ttl) {
|
|
265
|
+
ttlMissStats.logMisses();
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Injected to LDS for Luvio specific instrumentation.
|
|
271
|
+
*
|
|
272
|
+
* @param context The transaction context.
|
|
273
|
+
*/
|
|
274
|
+
instrumentLuvio(context) {
|
|
275
|
+
instrumentLuvio(context);
|
|
276
|
+
if (this.isRefreshAdapterEvent(context)) {
|
|
277
|
+
this.aggregateRefreshAdapterEvents(context);
|
|
278
|
+
}
|
|
279
|
+
else if (this.isAdapterUnfulfilledError(context)) {
|
|
280
|
+
this.incrementAdapterRequestErrorCount(context);
|
|
281
|
+
}
|
|
282
|
+
else ;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Returns whether or not this is a RefreshAdapterEvent.
|
|
286
|
+
* @param context The transaction context.
|
|
287
|
+
* @returns Whether or not this is a RefreshAdapterEvent.
|
|
288
|
+
*/
|
|
289
|
+
isRefreshAdapterEvent(context) {
|
|
290
|
+
return context[REFRESH_ADAPTER_EVENT] === true;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Returns whether or not this is an AdapterUnfulfilledError.
|
|
294
|
+
* @param context The transaction context.
|
|
295
|
+
* @returns Whether or not this is an AdapterUnfulfilledError.
|
|
296
|
+
*/
|
|
297
|
+
isAdapterUnfulfilledError(context) {
|
|
298
|
+
return context[ADAPTER_UNFULFILLED_ERROR] === true;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Specific instrumentation for getRecordNotifyChange.
|
|
302
|
+
* temporary implementation to match existing aura call for now
|
|
303
|
+
*
|
|
304
|
+
* @param uniqueWeakEtags whether weakEtags match or not
|
|
305
|
+
* @param error if dispatchResourceRequest fails for any reason
|
|
306
|
+
*/
|
|
307
|
+
notifyChangeNetwork(uniqueWeakEtags, error) {
|
|
308
|
+
perfStart(NETWORK_TRANSACTION_NAME);
|
|
309
|
+
if (error === true) {
|
|
310
|
+
perfEnd(NETWORK_TRANSACTION_NAME, { 'notify-change-network': 'error' });
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
perfEnd(NETWORK_TRANSACTION_NAME, { 'notify-change-network': uniqueWeakEtags });
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Parses and aggregates weakETagZero events to be sent in summarized log line.
|
|
318
|
+
* @param context The transaction context.
|
|
319
|
+
*/
|
|
320
|
+
aggregateWeakETagEvents(incomingWeakEtagZero, existingWeakEtagZero, apiName) {
|
|
321
|
+
const key = 'weaketag-0-' + apiName;
|
|
322
|
+
if (this.weakEtagZeroEvents[key] === undefined) {
|
|
323
|
+
this.weakEtagZeroEvents[key] = {
|
|
324
|
+
[EXISTING_WEAKETAG_0_KEY]: 0,
|
|
325
|
+
[INCOMING_WEAKETAG_0_KEY]: 0,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
if (existingWeakEtagZero) {
|
|
329
|
+
this.weakEtagZeroEvents[key][EXISTING_WEAKETAG_0_KEY] += 1;
|
|
330
|
+
}
|
|
331
|
+
if (incomingWeakEtagZero) {
|
|
332
|
+
this.weakEtagZeroEvents[key][INCOMING_WEAKETAG_0_KEY] += 1;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Aggregates refresh adapter events to be sent in summarized log line.
|
|
337
|
+
* - how many times refreshApex is called
|
|
338
|
+
* - how many times refresh from lightning/uiRecordApi is called
|
|
339
|
+
* - number of supported calls: refreshApex called on apex adapter
|
|
340
|
+
* - number of unsupported calls: refreshApex on non-apex adapter
|
|
341
|
+
* + any use of refresh from uiRecordApi module
|
|
342
|
+
* - count of refresh calls per adapter
|
|
343
|
+
* @param context The refresh adapter event.
|
|
344
|
+
*/
|
|
345
|
+
aggregateRefreshAdapterEvents(context) {
|
|
346
|
+
// We are consolidating all apex adapter instrumentation calls under a single key
|
|
347
|
+
// Adding additional logging that getApex adapters can invoke? Read normalizeAdapterName ts-doc.
|
|
348
|
+
const adapterName = normalizeAdapterName(context.adapterName);
|
|
349
|
+
if (this.lastRefreshApiCall === REFRESH_APEX_KEY) {
|
|
350
|
+
if (isApexAdapter(adapterName)) {
|
|
351
|
+
this.refreshApiCallEventStats[SUPPORTED_KEY] += 1;
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
this.refreshApiCallEventStats[UNSUPPORTED_KEY] += 1;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
else if (this.lastRefreshApiCall === REFRESH_UIAPI_KEY) {
|
|
358
|
+
this.refreshApiCallEventStats[UNSUPPORTED_KEY] += 1;
|
|
359
|
+
}
|
|
360
|
+
if (this.refreshAdapterEvents[adapterName] === undefined) {
|
|
361
|
+
this.refreshAdapterEvents[adapterName] = 0;
|
|
362
|
+
}
|
|
363
|
+
this.refreshAdapterEvents[adapterName] += 1;
|
|
364
|
+
this.lastRefreshApiCall = null;
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Increments call stat for incoming refresh api call, and sets the name
|
|
368
|
+
* to be used in {@link aggregateRefreshCalls}
|
|
369
|
+
* @param from The name of the refresh function called.
|
|
370
|
+
*/
|
|
371
|
+
handleRefreshApiCall(apiName) {
|
|
372
|
+
this.refreshApiCallEventStats[apiName] += 1;
|
|
373
|
+
// set function call to be used with aggregateRefreshCalls
|
|
374
|
+
this.lastRefreshApiCall = apiName;
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* W-7302241
|
|
378
|
+
* Logs refresh call summary stats as a LightningInteraction.
|
|
379
|
+
*/
|
|
380
|
+
logRefreshStats() {
|
|
381
|
+
if (keys(this.refreshAdapterEvents).length > 0) {
|
|
382
|
+
interaction(REFRESH_PAYLOAD_TARGET, REFRESH_PAYLOAD_SCOPE, this.refreshAdapterEvents, REFRESH_EVENTSOURCE, REFRESH_EVENTTYPE, this.refreshApiCallEventStats);
|
|
383
|
+
this.resetRefreshStats();
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Resets the stat trackers for refresh call events.
|
|
388
|
+
*/
|
|
389
|
+
resetRefreshStats() {
|
|
390
|
+
this.refreshAdapterEvents = {};
|
|
391
|
+
this.refreshApiCallEventStats = {
|
|
392
|
+
[REFRESH_APEX_KEY]: 0,
|
|
393
|
+
[REFRESH_UIAPI_KEY]: 0,
|
|
394
|
+
[SUPPORTED_KEY]: 0,
|
|
395
|
+
[UNSUPPORTED_KEY]: 0,
|
|
396
|
+
};
|
|
397
|
+
this.lastRefreshApiCall = null;
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* W-7801618
|
|
401
|
+
* Counter for occurrences where the incoming record to be merged has a different apiName.
|
|
402
|
+
* Dynamically generated metric, stored in an {@link RecordApiNameChangeCounters} object.
|
|
403
|
+
*
|
|
404
|
+
* @param context The transaction context.
|
|
405
|
+
*
|
|
406
|
+
* Note: Short-lived metric candidate, remove at the end of 230
|
|
407
|
+
*/
|
|
408
|
+
incrementRecordApiNameChangeCount(_incomingApiName, existingApiName) {
|
|
409
|
+
let apiNameChangeCounter = this.recordApiNameChangeCounters[existingApiName];
|
|
410
|
+
if (apiNameChangeCounter === undefined) {
|
|
411
|
+
apiNameChangeCounter = counter(createMetricsKey(NAMESPACE, RECORD_API_NAME_CHANGE_COUNT_METRIC_NAME, existingApiName));
|
|
412
|
+
this.recordApiNameChangeCounters[existingApiName] = apiNameChangeCounter;
|
|
413
|
+
}
|
|
414
|
+
apiNameChangeCounter.increment(1);
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* W-8620679
|
|
418
|
+
* Increment the counter for an UnfulfilledSnapshotError coming from luvio
|
|
419
|
+
*
|
|
420
|
+
* @param context The transaction context.
|
|
421
|
+
*/
|
|
422
|
+
incrementAdapterRequestErrorCount(context) {
|
|
423
|
+
// We are consolidating all apex adapter instrumentation calls under a single key
|
|
424
|
+
const adapterName = normalizeAdapterName(context.adapterName);
|
|
425
|
+
let adapterRequestErrorCounter = this.adapterUnfulfilledErrorCounters[adapterName];
|
|
426
|
+
if (adapterRequestErrorCounter === undefined) {
|
|
427
|
+
adapterRequestErrorCounter = counter(createMetricsKey(OBSERVABILITY_NAMESPACE, ADAPTER_ERROR_COUNT_METRIC_NAME, adapterName));
|
|
428
|
+
this.adapterUnfulfilledErrorCounters[adapterName] = adapterRequestErrorCounter;
|
|
429
|
+
}
|
|
430
|
+
adapterRequestErrorCounter.increment(1);
|
|
431
|
+
totalAdapterErrorMetric.increment(1);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
function createMetricsKey(owner, name, unit) {
|
|
435
|
+
let metricName = name;
|
|
436
|
+
if (unit) {
|
|
437
|
+
metricName = metricName + '.' + unit;
|
|
438
|
+
}
|
|
439
|
+
return {
|
|
440
|
+
get() {
|
|
441
|
+
return { owner: owner, name: metricName };
|
|
442
|
+
},
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Returns whether adapter is an Apex one or not.
|
|
447
|
+
* @param adapterName The name of the adapter.
|
|
448
|
+
*/
|
|
449
|
+
function isApexAdapter(adapterName) {
|
|
450
|
+
return adapterName.indexOf(APEX_ADAPTER_NAME) > -1;
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Normalizes getApex adapter names to `Apex.getApex`. Non-Apex adapters will be prefixed with
|
|
454
|
+
* API family, if supplied. Example: `UiApi.getRecord`.
|
|
455
|
+
*
|
|
456
|
+
* Note: If you are adding additional logging that can come from getApex adapter contexts that provide
|
|
457
|
+
* the full getApex adapter name (i.e. getApex_[namespace]_[class]_[function]_[continuation]),
|
|
458
|
+
* ensure to call this method to normalize all logging to 'getApex'. This
|
|
459
|
+
* is because Argus has a 50k key cardinality limit. More context: W-8379680.
|
|
460
|
+
*
|
|
461
|
+
* @param adapterName The name of the adapter.
|
|
462
|
+
* @param apiFamily The API family of the adapter.
|
|
463
|
+
*/
|
|
464
|
+
function normalizeAdapterName(adapterName, apiFamily) {
|
|
465
|
+
if (isApexAdapter(adapterName)) {
|
|
466
|
+
return NORMALIZED_APEX_ADAPTER_NAME;
|
|
467
|
+
}
|
|
468
|
+
return apiFamily ? `${apiFamily}.${adapterName}` : adapterName;
|
|
469
|
+
}
|
|
470
|
+
const timerMetricTracker = create(null);
|
|
471
|
+
/**
|
|
472
|
+
* Calls instrumentation/service telemetry timer
|
|
473
|
+
* @param name Name of the metric
|
|
474
|
+
* @param duration number to update backing percentile histogram, negative numbers ignored
|
|
475
|
+
*/
|
|
476
|
+
function updateTimerMetric(name, duration) {
|
|
477
|
+
let metric = timerMetricTracker[name];
|
|
478
|
+
if (metric === undefined) {
|
|
479
|
+
metric = timer(createMetricsKey(NAMESPACE, name));
|
|
480
|
+
timerMetricTracker[name] = metric;
|
|
481
|
+
}
|
|
482
|
+
timerMetricAddDuration(metric, duration);
|
|
483
|
+
}
|
|
484
|
+
function timerMetricAddDuration(timer, duration) {
|
|
485
|
+
// Guard against negative values since it causes error to be thrown by MetricsService
|
|
486
|
+
if (duration >= 0) {
|
|
487
|
+
timer.addDuration(duration);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* W-10315098
|
|
492
|
+
* Increments the counter associated with the request response. Counts are bucketed by status.
|
|
493
|
+
*/
|
|
494
|
+
const requestResponseMetricTracker = create(null);
|
|
495
|
+
function incrementRequestResponseCount(cb) {
|
|
496
|
+
const status = cb().status;
|
|
497
|
+
let metric = requestResponseMetricTracker[status];
|
|
498
|
+
if (metric === undefined) {
|
|
499
|
+
metric = counter(createMetricsKey(OBSERVABILITY_NAMESPACE, NETWORK_ADAPTER_RESPONSE_METRIC_NAME, `${status.valueOf()}`));
|
|
500
|
+
requestResponseMetricTracker[status] = metric;
|
|
501
|
+
}
|
|
502
|
+
metric.increment();
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Add a mark to the metrics service.
|
|
506
|
+
*
|
|
507
|
+
* @param name The mark name.
|
|
508
|
+
* @param content The mark content.
|
|
509
|
+
*/
|
|
510
|
+
function mark(name, content) {
|
|
511
|
+
mark$1(NAMESPACE, name, content);
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Create a new instrumentation cache stats and return it.
|
|
515
|
+
*
|
|
516
|
+
* @param name The cache logger name.
|
|
517
|
+
*/
|
|
518
|
+
function registerLdsCacheStats(name) {
|
|
519
|
+
return registerCacheStats(`${NAMESPACE}:${name}`);
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Add or overwrite hooks that require aura implementations
|
|
523
|
+
*/
|
|
524
|
+
function setAuraInstrumentationHooks() {
|
|
525
|
+
instrument({
|
|
526
|
+
recordConflictsResolved: (serverRequestCount) => {
|
|
527
|
+
// Ignore 0 values which can originate from ADS bridge
|
|
528
|
+
if (serverRequestCount > 0) {
|
|
529
|
+
updatePercentileHistogramMetric('record-conflicts-resolved', serverRequestCount);
|
|
530
|
+
}
|
|
531
|
+
},
|
|
532
|
+
nullDisplayValueConflict: ({ fieldType, areValuesEqual }) => {
|
|
533
|
+
const metricName = `merge-null-dv-count.${fieldType}`;
|
|
534
|
+
if (fieldType === 'scalar') {
|
|
535
|
+
incrementCounterMetric(`${metricName}.${areValuesEqual}`);
|
|
536
|
+
}
|
|
537
|
+
else {
|
|
538
|
+
incrementCounterMetric(metricName);
|
|
539
|
+
}
|
|
540
|
+
},
|
|
541
|
+
getRecordNotifyChangeAllowed: incrementGetRecordNotifyChangeAllowCount,
|
|
542
|
+
getRecordNotifyChangeDropped: incrementGetRecordNotifyChangeDropCount,
|
|
543
|
+
notifyRecordUpdateAvailableAllowed: incrementNotifyRecordUpdateAvailableAllowCount,
|
|
544
|
+
notifyRecordUpdateAvailableDropped: incrementNotifyRecordUpdateAvailableDropCount,
|
|
545
|
+
recordApiNameChanged: instrumentation.incrementRecordApiNameChangeCount.bind(instrumentation),
|
|
546
|
+
weakEtagZero: instrumentation.aggregateWeakETagEvents.bind(instrumentation),
|
|
547
|
+
getRecordNotifyChangeNetworkResult: instrumentation.notifyChangeNetwork.bind(instrumentation),
|
|
548
|
+
});
|
|
549
|
+
withRegistration('@salesforce/lds-adapters-uiapi', (reg) => setLdsAdaptersUiapiInstrumentation(reg));
|
|
550
|
+
instrument$1({
|
|
551
|
+
logCrud: logCRUDLightningInteraction,
|
|
552
|
+
networkResponse: incrementRequestResponseCount,
|
|
553
|
+
});
|
|
554
|
+
instrument$2({
|
|
555
|
+
refreshCalled: instrumentation.handleRefreshApiCall.bind(instrumentation),
|
|
556
|
+
instrumentAdapter: instrumentation.instrumentAdapter.bind(instrumentation),
|
|
557
|
+
});
|
|
558
|
+
instrument$3({
|
|
559
|
+
timerMetricAddDuration: updateTimerMetric,
|
|
560
|
+
});
|
|
561
|
+
// Our getRecord through aggregate-ui CRUD logging has moved
|
|
562
|
+
// to lds-network-adapter. We still need to respect the
|
|
563
|
+
// orgs environment setting
|
|
564
|
+
if (forceRecordTransactionsDisabled === false) {
|
|
565
|
+
ldsNetworkAdapterInstrument({
|
|
566
|
+
getRecordAggregateResolve: (cb) => {
|
|
567
|
+
const { recordId, apiName } = cb();
|
|
568
|
+
logCRUDLightningInteraction('read', {
|
|
569
|
+
recordId,
|
|
570
|
+
recordType: apiName,
|
|
571
|
+
state: 'SUCCESS',
|
|
572
|
+
});
|
|
573
|
+
},
|
|
574
|
+
getRecordAggregateReject: (cb) => {
|
|
575
|
+
const recordId = cb();
|
|
576
|
+
logCRUDLightningInteraction('read', {
|
|
577
|
+
recordId,
|
|
578
|
+
state: 'ERROR',
|
|
579
|
+
});
|
|
580
|
+
},
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
withRegistration('@salesforce/lds-network-adapter', (reg) => setLdsNetworkAdapterInstrumentation(reg));
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Initialize the instrumentation and instrument the LDS instance and the InMemoryStore.
|
|
587
|
+
*
|
|
588
|
+
* @param luvio The Luvio instance to instrument.
|
|
589
|
+
* @param store The InMemoryStore to instrument.
|
|
590
|
+
*/
|
|
591
|
+
function setupInstrumentation(luvio, store) {
|
|
592
|
+
setupInstrumentation$1(luvio, store);
|
|
593
|
+
setAuraInstrumentationHooks();
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Note: locator.scope is set to 'force_record' in order for the instrumentation gate to work, which will
|
|
597
|
+
* disable all crud operations if it is on.
|
|
598
|
+
* @param eventSource - Source of the logging event.
|
|
599
|
+
* @param attributes - Free form object of attributes to log.
|
|
600
|
+
*/
|
|
601
|
+
function logCRUDLightningInteraction(eventSource, attributes) {
|
|
602
|
+
interaction(eventSource, 'force_record', null, eventSource, 'crud', attributes);
|
|
603
|
+
}
|
|
604
604
|
const instrumentation = new Instrumentation();
|
|
605
605
|
|
|
606
|
-
const OBJECT_INFO_PREFIX = 'UiApi::ObjectInfoRepresentation:';
|
|
607
|
-
const STORAGE_DROP_MARK_NAME = 'storage-drop';
|
|
608
|
-
const STORAGE_DROP_MARK_CONTEXT = {
|
|
609
|
-
reason: 'Object info changed',
|
|
610
|
-
};
|
|
611
|
-
/**
|
|
612
|
-
* Watch a Luvio instance for metadata changes.
|
|
613
|
-
*/
|
|
614
|
-
function setupMetadataWatcher(luvio) {
|
|
615
|
-
// Watch for object info changes. Since we don't have enough information to understand to which
|
|
616
|
-
// extent an object info change may impact the application the only thing we do is to clear all
|
|
617
|
-
// the persistent storages.
|
|
618
|
-
luvio.storeWatch(OBJECT_INFO_PREFIX, (entries) => {
|
|
619
|
-
for (let i = 0, len = entries.length; i < len; i++) {
|
|
620
|
-
const entry = entries[i];
|
|
621
|
-
const isObjectInfoUpdated = entry.inserted === false;
|
|
622
|
-
if (isObjectInfoUpdated) {
|
|
623
|
-
mark(STORAGE_DROP_MARK_NAME, STORAGE_DROP_MARK_CONTEXT);
|
|
624
|
-
clearStorages().catch(() => {
|
|
625
|
-
/* noop */
|
|
626
|
-
});
|
|
627
|
-
break;
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
});
|
|
606
|
+
const OBJECT_INFO_PREFIX = 'UiApi::ObjectInfoRepresentation:';
|
|
607
|
+
const STORAGE_DROP_MARK_NAME = 'storage-drop';
|
|
608
|
+
const STORAGE_DROP_MARK_CONTEXT = {
|
|
609
|
+
reason: 'Object info changed',
|
|
610
|
+
};
|
|
611
|
+
/**
|
|
612
|
+
* Watch a Luvio instance for metadata changes.
|
|
613
|
+
*/
|
|
614
|
+
function setupMetadataWatcher(luvio) {
|
|
615
|
+
// Watch for object info changes. Since we don't have enough information to understand to which
|
|
616
|
+
// extent an object info change may impact the application the only thing we do is to clear all
|
|
617
|
+
// the persistent storages.
|
|
618
|
+
luvio.storeWatch(OBJECT_INFO_PREFIX, (entries) => {
|
|
619
|
+
for (let i = 0, len = entries.length; i < len; i++) {
|
|
620
|
+
const entry = entries[i];
|
|
621
|
+
const isObjectInfoUpdated = entry.inserted === false;
|
|
622
|
+
if (isObjectInfoUpdated) {
|
|
623
|
+
mark(STORAGE_DROP_MARK_NAME, STORAGE_DROP_MARK_CONTEXT);
|
|
624
|
+
clearStorages().catch(() => {
|
|
625
|
+
/* noop */
|
|
626
|
+
});
|
|
627
|
+
break;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
});
|
|
631
631
|
}
|
|
632
632
|
|
|
633
|
-
// This code *should* be in lds-network-adapter, but when combined with the Aura
|
|
634
|
-
// component test workaround in lds-default-luvio it creates a circular dependecy
|
|
635
|
-
// between lds-default-luvio and lds-network-adapter. We do the register on behalf
|
|
636
|
-
// of lds-network-adapter here to avoid the cycle.
|
|
637
|
-
register({
|
|
638
|
-
id: '@salesforce/lds-network-adapter',
|
|
639
|
-
instrument: ldsNetworkAdapterInstrument,
|
|
640
|
-
});
|
|
641
|
-
function setTrackedFieldsConfig(includeLeafNodeIdAndNameOnly) {
|
|
642
|
-
const depth = includeLeafNodeIdAndNameOnly ? 1 : 5;
|
|
643
|
-
configuration.setTrackedFieldLeafNodeIdAndNameOnly(includeLeafNodeIdAndNameOnly);
|
|
644
|
-
configuration.setTrackedFieldDepthOnCacheMiss(depth);
|
|
645
|
-
configuration.setTrackedFieldDepthOnCacheMergeConflict(depth);
|
|
646
|
-
configuration.setTrackedFieldDepthOnNotifyChange(depth);
|
|
647
|
-
}
|
|
648
|
-
function setupQueryEvaluators(luvio, store) {
|
|
649
|
-
const baseQueryEvaluator = new InMemoryStoreQueryEvaluator(store);
|
|
650
|
-
const recordRepresentationQueryEvaluator = new InMemoryRecordRepresentationQueryEvaluator(baseQueryEvaluator);
|
|
651
|
-
store.addStoreEventObserver({
|
|
652
|
-
onStorePublish: (event) => {
|
|
653
|
-
baseQueryEvaluator.registerKey(store, event.key, event.keySchema);
|
|
654
|
-
},
|
|
655
|
-
});
|
|
656
|
-
luvio.registerStoreQueryEvaluator(baseQueryEvaluator);
|
|
657
|
-
luvio.registerTypeQueryEvaluator(UiApiNamespace, RecordRepresentationRepresentationType, recordRepresentationQueryEvaluator);
|
|
658
|
-
}
|
|
659
|
-
// LDS initialization logic, invoked directly by Aura component tests
|
|
660
|
-
function initializeLDS() {
|
|
661
|
-
const storeOptions = {
|
|
662
|
-
scheduler: () => { },
|
|
663
|
-
};
|
|
664
|
-
const store = new InMemoryStore(storeOptions);
|
|
665
|
-
const environment = new Environment(store, networkAdapter);
|
|
666
|
-
const luvio = new Luvio(environment, {
|
|
667
|
-
instrument: instrumentation.instrumentLuvio.bind(instrumentation),
|
|
668
|
-
});
|
|
669
|
-
setupInstrumentation(luvio, store);
|
|
670
|
-
setupMetadataWatcher(luvio);
|
|
671
|
-
setupQueryEvaluators(luvio, store);
|
|
672
|
-
setDefaultLuvio({ luvio });
|
|
673
|
-
setTrackedFieldsConfig(ldsTrackedFieldsBehaviorGate.isOpen({ fallback: false }));
|
|
674
|
-
}
|
|
675
|
-
// service function to be invoked by Aura
|
|
676
|
-
function ldsEngineCreator() {
|
|
677
|
-
initializeLDS();
|
|
678
|
-
return { name: 'ldsEngineCreator' };
|
|
633
|
+
// This code *should* be in lds-network-adapter, but when combined with the Aura
|
|
634
|
+
// component test workaround in lds-default-luvio it creates a circular dependecy
|
|
635
|
+
// between lds-default-luvio and lds-network-adapter. We do the register on behalf
|
|
636
|
+
// of lds-network-adapter here to avoid the cycle.
|
|
637
|
+
register({
|
|
638
|
+
id: '@salesforce/lds-network-adapter',
|
|
639
|
+
instrument: ldsNetworkAdapterInstrument,
|
|
640
|
+
});
|
|
641
|
+
function setTrackedFieldsConfig(includeLeafNodeIdAndNameOnly) {
|
|
642
|
+
const depth = includeLeafNodeIdAndNameOnly ? 1 : 5;
|
|
643
|
+
configuration.setTrackedFieldLeafNodeIdAndNameOnly(includeLeafNodeIdAndNameOnly);
|
|
644
|
+
configuration.setTrackedFieldDepthOnCacheMiss(depth);
|
|
645
|
+
configuration.setTrackedFieldDepthOnCacheMergeConflict(depth);
|
|
646
|
+
configuration.setTrackedFieldDepthOnNotifyChange(depth);
|
|
647
|
+
}
|
|
648
|
+
function setupQueryEvaluators(luvio, store) {
|
|
649
|
+
const baseQueryEvaluator = new InMemoryStoreQueryEvaluator(store);
|
|
650
|
+
const recordRepresentationQueryEvaluator = new InMemoryRecordRepresentationQueryEvaluator(baseQueryEvaluator);
|
|
651
|
+
store.addStoreEventObserver({
|
|
652
|
+
onStorePublish: (event) => {
|
|
653
|
+
baseQueryEvaluator.registerKey(store, event.key, event.keySchema);
|
|
654
|
+
},
|
|
655
|
+
});
|
|
656
|
+
luvio.registerStoreQueryEvaluator(baseQueryEvaluator);
|
|
657
|
+
luvio.registerTypeQueryEvaluator(UiApiNamespace, RecordRepresentationRepresentationType, recordRepresentationQueryEvaluator);
|
|
658
|
+
}
|
|
659
|
+
// LDS initialization logic, invoked directly by Aura component tests
|
|
660
|
+
function initializeLDS() {
|
|
661
|
+
const storeOptions = {
|
|
662
|
+
scheduler: () => { },
|
|
663
|
+
};
|
|
664
|
+
const store = new InMemoryStore(storeOptions);
|
|
665
|
+
const environment = new Environment(store, networkAdapter);
|
|
666
|
+
const luvio = new Luvio(environment, {
|
|
667
|
+
instrument: instrumentation.instrumentLuvio.bind(instrumentation),
|
|
668
|
+
});
|
|
669
|
+
setupInstrumentation(luvio, store);
|
|
670
|
+
setupMetadataWatcher(luvio);
|
|
671
|
+
setupQueryEvaluators(luvio, store);
|
|
672
|
+
setDefaultLuvio({ luvio });
|
|
673
|
+
setTrackedFieldsConfig(ldsTrackedFieldsBehaviorGate.isOpen({ fallback: false }));
|
|
674
|
+
}
|
|
675
|
+
// service function to be invoked by Aura
|
|
676
|
+
function ldsEngineCreator() {
|
|
677
|
+
initializeLDS();
|
|
678
|
+
return { name: 'ldsEngineCreator' };
|
|
679
679
|
}
|
|
680
680
|
|
|
681
681
|
export { ldsEngineCreator as default, initializeLDS };
|
|
682
|
-
// version: 1.124.
|
|
682
|
+
// version: 1.124.4-f07bfc14d
|