@salesforce/lds-runtime-aura 1.313.0 → 1.315.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/dist/ldsEngineCreator.js +2181 -2007
- package/dist/types/main.d.ts +16 -0
- package/dist/types/predictive-loading/pages/lex-default-page.d.ts +1 -0
- package/dist/types/predictive-loading/pages/object-home-page.d.ts +5 -3
- package/dist/types/predictive-loading/pages/predictive-prefetch-page.d.ts +1 -0
- package/dist/types/predictive-loading/pages/record-home-page.d.ts +5 -3
- package/dist/types/predictive-loading/prefetcher/lex-predictive-prefetcher.d.ts +3 -3
- package/dist/types/predictive-loading/prefetcher/predictive-prefetcher.d.ts +1 -1
- package/dist/types/predictive-loading/request-runner/lex-request-runner.d.ts +3 -4
- package/dist/types/predictive-loading/request-strategy/get-apex-request-strategy.d.ts +1 -0
- package/dist/types/predictive-loading/request-strategy/get-components-request-strategy.d.ts +1 -0
- package/dist/types/predictive-loading/request-strategy/get-list-object-info-request-strategy.d.ts +2 -0
- package/dist/types/predictive-loading/request-strategy/get-list-records-by-name-request-strategy.d.ts +2 -0
- package/dist/types/predictive-loading/request-strategy/get-object-info-request-strategy.d.ts +1 -0
- package/dist/types/predictive-loading/request-strategy/get-record-actions-request-strategy.d.ts +1 -0
- package/dist/types/predictive-loading/request-strategy/get-record-avatars-request-strategy.d.ts +1 -0
- package/dist/types/predictive-loading/request-strategy/get-record-request-strategy.d.ts +1 -0
- package/dist/types/predictive-loading/request-strategy/get-records-request-strategy.d.ts +1 -0
- package/dist/types/predictive-loading/request-strategy/get-related-list-info-request-strategy.d.ts +1 -0
- package/dist/types/predictive-loading/request-strategy/get-related-list-records-request-strategy.d.ts +1 -0
- package/dist/types/predictive-loading/request-strategy/get-related-lists-actions-request-strategy.d.ts +1 -0
- package/dist/types/predictive-loading/request-strategy-manager/request-strategy-manager.d.ts +12 -0
- package/package.json +30 -30
package/dist/ldsEngineCreator.js
CHANGED
|
@@ -21,8 +21,8 @@ import useCmpDefPredictions from '@salesforce/gate/lds.pdl.useCmpDefPredictions'
|
|
|
21
21
|
import applyPredictionRequestLimit from '@salesforce/gate/lds.pdl.applyRequestLimit';
|
|
22
22
|
import useExactMatchesPlusGate from '@salesforce/gate/lds.pdl.useExactMatchesPlus';
|
|
23
23
|
import { GetApexWireAdapterFactory, registerPrefetcher as registerPrefetcher$1 } from 'force/ldsAdaptersApex';
|
|
24
|
-
import {
|
|
25
|
-
import { BaseCommand,
|
|
24
|
+
import { getRecordAvatarsAdapterFactory, getRecordAdapterFactory, coerceFieldIdArray, getRecordsAdapterFactory, getRecordActionsAdapterFactory, getObjectInfosAdapterFactory, coerceObjectIdArray, getObjectInfoAdapterFactory, coerceObjectId, getRelatedListsActionsAdapterFactory, getRelatedListInfoBatchAdapterFactory, getRelatedListInfoAdapterFactory, getRelatedListRecordsBatchAdapterFactory, getRelatedListRecordsAdapterFactory, getListInfoByNameAdapterFactory, getListInfosByObjectNameAdapterFactory, getListRecordsByNameAdapterFactory, getListObjectInfoAdapterFactory, instrument, configuration, InMemoryRecordRepresentationQueryEvaluator, UiApiNamespace, RecordRepresentationRepresentationType, registerPrefetcher } from 'force/ldsAdaptersUiapi';
|
|
25
|
+
import { BaseCommand, convertFetchResponseToData } from 'force/luvioRuntime5';
|
|
26
26
|
import { serviceBroker } from 'force/luvioServiceBroker5';
|
|
27
27
|
import oneStoreEnabled from '@salesforce/gate/lds.oneStoreEnabled.ltng';
|
|
28
28
|
import oneStoreUiapiEnabled from '@salesforce/gate/lds.oneStoreUiapiEnabled.ltng';
|
|
@@ -74,68 +74,6 @@ function buildNetworkCommandBaseClassService() {
|
|
|
74
74
|
*/
|
|
75
75
|
|
|
76
76
|
|
|
77
|
-
/**
|
|
78
|
-
* An implementation of BaseCommand that makes network requests but does not try to
|
|
79
|
-
* use the store.
|
|
80
|
-
*/
|
|
81
|
-
class AuraNetworkCommand extends NetworkCommand {
|
|
82
|
-
constructor(services) {
|
|
83
|
-
super(services);
|
|
84
|
-
this.services = services;
|
|
85
|
-
this.actionConfig = {
|
|
86
|
-
background: false,
|
|
87
|
-
hotspot: true,
|
|
88
|
-
longRunning: false,
|
|
89
|
-
storable: false,
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
fetch() {
|
|
93
|
-
return convertAuraResponseToData(this.services.auraNetwork(this.endpoint, this.auraParams, this.actionConfig));
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
function buildAuraNetworkCommandBaseClassService() {
|
|
97
|
-
return {
|
|
98
|
-
type: 'auraNetworkCommandBaseClass',
|
|
99
|
-
version: '1.0',
|
|
100
|
-
service: AuraNetworkCommand,
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Copyright (c) 2022, Salesforce, Inc.,
|
|
106
|
-
* All rights reserved.
|
|
107
|
-
* For full license text, see the LICENSE.txt file
|
|
108
|
-
*/
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* An implementation of BaseCommand that makes network requests but does not try to
|
|
113
|
-
* use the store.
|
|
114
|
-
*/
|
|
115
|
-
class FetchNetworkCommand extends NetworkCommand {
|
|
116
|
-
constructor(services) {
|
|
117
|
-
super(services);
|
|
118
|
-
this.services = services;
|
|
119
|
-
}
|
|
120
|
-
fetch() {
|
|
121
|
-
return convertFetchResponseToData(this.services.fetch(...this.fetchParams));
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
function buildFetchNetworkCommandBaseClassService() {
|
|
125
|
-
return {
|
|
126
|
-
type: 'fetchNetworkCommandBaseClass',
|
|
127
|
-
version: '1.0',
|
|
128
|
-
service: FetchNetworkCommand,
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Copyright (c) 2022, Salesforce, Inc.,
|
|
134
|
-
* All rights reserved.
|
|
135
|
-
* For full license text, see the LICENSE.txt file
|
|
136
|
-
*/
|
|
137
|
-
|
|
138
|
-
|
|
139
77
|
const LogLevelMap$1 = {
|
|
140
78
|
TRACE: 4,
|
|
141
79
|
DEBUG: 3,
|
|
@@ -300,6 +238,85 @@ function isCacheHitOrError(value) {
|
|
|
300
238
|
*/
|
|
301
239
|
|
|
302
240
|
|
|
241
|
+
function convertAuraResponseToData(responsePromise) {
|
|
242
|
+
return responsePromise
|
|
243
|
+
.then((response) => {
|
|
244
|
+
return ok(response.getReturnValue());
|
|
245
|
+
})
|
|
246
|
+
.catch((error) => {
|
|
247
|
+
if (!error || !error.getError) {
|
|
248
|
+
return err(toError('Failed to get error from response'));
|
|
249
|
+
}
|
|
250
|
+
const actionErrors = error.getError();
|
|
251
|
+
if (actionErrors.length > 0) {
|
|
252
|
+
return err(toError(actionErrors[0]));
|
|
253
|
+
}
|
|
254
|
+
return err(toError('Error fetching component'));
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* An implementation of BaseCommand that makes network requests but does not try to
|
|
260
|
+
* use the store.
|
|
261
|
+
*/
|
|
262
|
+
class AuraNetworkCommand extends NetworkCommand {
|
|
263
|
+
constructor(services) {
|
|
264
|
+
super(services);
|
|
265
|
+
this.services = services;
|
|
266
|
+
this.actionConfig = {
|
|
267
|
+
background: false,
|
|
268
|
+
hotspot: true,
|
|
269
|
+
longRunning: false,
|
|
270
|
+
storable: false,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
fetch() {
|
|
274
|
+
return convertAuraResponseToData(this.services.auraNetwork(this.endpoint, this.auraParams, this.actionConfig));
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
function buildAuraNetworkCommandBaseClassService() {
|
|
278
|
+
return {
|
|
279
|
+
type: 'auraNetworkCommandBaseClass',
|
|
280
|
+
version: '1.0',
|
|
281
|
+
service: AuraNetworkCommand,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Copyright (c) 2022, Salesforce, Inc.,
|
|
287
|
+
* All rights reserved.
|
|
288
|
+
* For full license text, see the LICENSE.txt file
|
|
289
|
+
*/
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* An implementation of BaseCommand that makes network requests but does not try to
|
|
294
|
+
* use the store.
|
|
295
|
+
*/
|
|
296
|
+
class FetchNetworkCommand extends NetworkCommand {
|
|
297
|
+
constructor(services) {
|
|
298
|
+
super(services);
|
|
299
|
+
this.services = services;
|
|
300
|
+
}
|
|
301
|
+
fetch() {
|
|
302
|
+
return convertFetchResponseToData(this.services.fetch(...this.fetchParams));
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
function buildFetchNetworkCommandBaseClassService() {
|
|
306
|
+
return {
|
|
307
|
+
type: 'fetchNetworkCommandBaseClass',
|
|
308
|
+
version: '1.0',
|
|
309
|
+
service: FetchNetworkCommand,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Copyright (c) 2022, Salesforce, Inc.,
|
|
315
|
+
* All rights reserved.
|
|
316
|
+
* For full license text, see the LICENSE.txt file
|
|
317
|
+
*/
|
|
318
|
+
|
|
319
|
+
|
|
303
320
|
/**
|
|
304
321
|
* An implementation of BaseCommand that supports streaming responses and does not use the store.
|
|
305
322
|
*/
|
|
@@ -1788,6 +1805,9 @@ class LexDefaultPage extends PredictivePrefetchPage {
|
|
|
1788
1805
|
constructor(context) {
|
|
1789
1806
|
super(context);
|
|
1790
1807
|
}
|
|
1808
|
+
supportsRequest(_request) {
|
|
1809
|
+
return true;
|
|
1810
|
+
}
|
|
1791
1811
|
buildSaveRequestData(request) {
|
|
1792
1812
|
return [{ context: this.context, request }];
|
|
1793
1813
|
}
|
|
@@ -1805,2246 +1825,2300 @@ class LexDefaultPage extends PredictivePrefetchPage {
|
|
|
1805
1825
|
}
|
|
1806
1826
|
}
|
|
1807
1827
|
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
return [];
|
|
1825
|
-
}
|
|
1826
|
-
if (matchingRequestStrategy.isContextDependent(this.context, request)) {
|
|
1827
|
-
requestBuckets.push({
|
|
1828
|
-
context: this.similarContext,
|
|
1829
|
-
request: matchingRequestStrategy.transformForSaveSimilarRequest(request),
|
|
1830
|
-
});
|
|
1831
|
-
// When `options.useExactMatchesPlus` is not enabled, we can save this request on the similar bucket only
|
|
1832
|
-
if (!this.options.useExactMatchesPlus) {
|
|
1833
|
-
return requestBuckets;
|
|
1834
|
-
}
|
|
1835
|
-
}
|
|
1836
|
-
if (!matchingRequestStrategy.onlySavedInSimilar) {
|
|
1837
|
-
requestBuckets.push({
|
|
1838
|
-
context: this.context,
|
|
1839
|
-
request: matchingRequestStrategy.transformForSave(request),
|
|
1840
|
-
});
|
|
1841
|
-
}
|
|
1842
|
-
return requestBuckets;
|
|
1843
|
-
}
|
|
1844
|
-
resolveSimilarRequest(similarRequest) {
|
|
1845
|
-
const { adapterName } = similarRequest;
|
|
1846
|
-
const matchingRequestStrategy = this.requestStrategies.get(adapterName);
|
|
1847
|
-
if (matchingRequestStrategy === undefined) {
|
|
1848
|
-
return similarRequest;
|
|
1849
|
-
}
|
|
1850
|
-
return matchingRequestStrategy.buildConcreteRequest(similarRequest, this.context);
|
|
1828
|
+
const { create, keys, hasOwnProperty, entries } = Object;
|
|
1829
|
+
const { isArray, from } = Array;
|
|
1830
|
+
const { stringify } = JSON;
|
|
1831
|
+
|
|
1832
|
+
class RequestStrategy {
|
|
1833
|
+
/**
|
|
1834
|
+
* Perform any transformations required to prepare the request for saving.
|
|
1835
|
+
*
|
|
1836
|
+
* e.g. If the request is for a record, we move all fields in the fields array
|
|
1837
|
+
* into the optionalFields array
|
|
1838
|
+
*
|
|
1839
|
+
* @param request - The request to transform
|
|
1840
|
+
* @returns
|
|
1841
|
+
*/
|
|
1842
|
+
transformForSave(request) {
|
|
1843
|
+
return request;
|
|
1851
1844
|
}
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
recordId,
|
|
1860
|
-
optionalFields: [`${objectApiName}.Id`, `${objectApiName}.RecordTypeId`],
|
|
1861
|
-
},
|
|
1862
|
-
},
|
|
1863
|
-
];
|
|
1845
|
+
/**
|
|
1846
|
+
* Transforms the request for saving similar requests
|
|
1847
|
+
* @param request Request to transform for saving similar requests
|
|
1848
|
+
* @returns Transformed request
|
|
1849
|
+
*/
|
|
1850
|
+
transformForSaveSimilarRequest(request) {
|
|
1851
|
+
return this.transformForSave(request);
|
|
1864
1852
|
}
|
|
1865
1853
|
/**
|
|
1866
|
-
*
|
|
1867
|
-
* with one of the predictions in case some request containing the fields was missed in the predictions.
|
|
1854
|
+
* Filter requests to only those that are for this strategy.
|
|
1868
1855
|
*
|
|
1869
|
-
* @
|
|
1856
|
+
* @param unfilteredRequests array of requests to filter
|
|
1857
|
+
* @returns
|
|
1870
1858
|
*/
|
|
1871
|
-
|
|
1872
|
-
return
|
|
1859
|
+
filterRequests(unfilteredRequests) {
|
|
1860
|
+
return unfilteredRequests.filter((entry) => entry.request.adapterName === this.adapterName);
|
|
1873
1861
|
}
|
|
1874
1862
|
/**
|
|
1875
|
-
*
|
|
1876
|
-
*
|
|
1877
|
-
* @
|
|
1863
|
+
* Reduce requests by combining those based on a strategies implementations
|
|
1864
|
+
* of canCombine and combineRequests.
|
|
1865
|
+
* @param unfilteredRequests array of requests to filter
|
|
1866
|
+
* @returns
|
|
1878
1867
|
*/
|
|
1879
|
-
|
|
1880
|
-
|
|
1868
|
+
reduce(unfilteredRequests) {
|
|
1869
|
+
const requests = this.filterRequests(unfilteredRequests);
|
|
1870
|
+
const visitedRequests = new Set();
|
|
1871
|
+
const reducedRequests = [];
|
|
1872
|
+
for (let i = 0, n = requests.length; i < n; i++) {
|
|
1873
|
+
const currentRequest = requests[i];
|
|
1874
|
+
if (!visitedRequests.has(currentRequest)) {
|
|
1875
|
+
const combinedRequest = { ...currentRequest };
|
|
1876
|
+
for (let j = i + 1; j < n; j++) {
|
|
1877
|
+
const hasNotBeenVisited = !visitedRequests.has(requests[j]);
|
|
1878
|
+
const canCombineConfigs = this.canCombine(combinedRequest.request.config, requests[j].request.config);
|
|
1879
|
+
if (hasNotBeenVisited && canCombineConfigs) {
|
|
1880
|
+
combinedRequest.request.config = this.combineRequests(combinedRequest.request.config, requests[j].request.config);
|
|
1881
|
+
if (combinedRequest.requestMetadata.requestTime >
|
|
1882
|
+
requests[j].requestMetadata.requestTime) {
|
|
1883
|
+
// This logic is debateable - Currently this always assigns the lowest requestTime to a reduced request.
|
|
1884
|
+
combinedRequest.requestMetadata.requestTime =
|
|
1885
|
+
requests[j].requestMetadata.requestTime;
|
|
1886
|
+
}
|
|
1887
|
+
visitedRequests.add(requests[j]);
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
reducedRequests.push(combinedRequest);
|
|
1891
|
+
visitedRequests.add(currentRequest);
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
return reducedRequests;
|
|
1881
1895
|
}
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
constructor(context, requestStrategies, options) {
|
|
1893
|
-
super(context);
|
|
1894
|
-
this.requestStrategies = requestStrategies;
|
|
1895
|
-
this.options = options;
|
|
1896
|
-
this.similarContext = context;
|
|
1896
|
+
/**
|
|
1897
|
+
* Check if two requests can be combined.
|
|
1898
|
+
*
|
|
1899
|
+
* By default, requests are not combinable.
|
|
1900
|
+
* @param reqA config of request A
|
|
1901
|
+
* @param reqB config of request B
|
|
1902
|
+
* @returns
|
|
1903
|
+
*/
|
|
1904
|
+
canCombine(_reqA, _reqB) {
|
|
1905
|
+
return false;
|
|
1897
1906
|
}
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
});
|
|
1910
|
-
// When `options.useExactMatchesPlus` is not enabled, we can save this request on the similar bucket only
|
|
1911
|
-
if (!this.options.useExactMatchesPlus) {
|
|
1912
|
-
return requestBuckets;
|
|
1913
|
-
}
|
|
1907
|
+
/**
|
|
1908
|
+
* Takes two request configs and combines them into a single request config.
|
|
1909
|
+
*
|
|
1910
|
+
* @param reqA config of request A
|
|
1911
|
+
* @param reqB config of request B
|
|
1912
|
+
* @returns
|
|
1913
|
+
*/
|
|
1914
|
+
combineRequests(reqA, _reqB) {
|
|
1915
|
+
// By default, this should never be called since requests aren't combinable
|
|
1916
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
1917
|
+
throw new Error('Not implemented');
|
|
1914
1918
|
}
|
|
1915
|
-
|
|
1916
|
-
context: this.context,
|
|
1917
|
-
request: matchingRequestStrategy.transformForSave(request),
|
|
1918
|
-
});
|
|
1919
|
-
return requestBuckets;
|
|
1920
|
-
}
|
|
1921
|
-
// no similar requests between LVs
|
|
1922
|
-
resolveSimilarRequest(similarRequest) {
|
|
1923
|
-
return similarRequest;
|
|
1924
|
-
}
|
|
1925
|
-
// these are requests that run always regardless of any other request existing
|
|
1926
|
-
getAlwaysRunRequests() {
|
|
1927
|
-
const { listViewApiName, objectApiName } = this.context;
|
|
1928
|
-
return [
|
|
1929
|
-
{
|
|
1930
|
-
adapterName: 'getListInfoByName',
|
|
1931
|
-
config: {
|
|
1932
|
-
objectApiName: objectApiName,
|
|
1933
|
-
listViewApiName: listViewApiName,
|
|
1934
|
-
},
|
|
1935
|
-
},
|
|
1936
|
-
{
|
|
1937
|
-
adapterName: 'getListInfosByObjectName',
|
|
1938
|
-
config: {
|
|
1939
|
-
objectApiName: objectApiName,
|
|
1940
|
-
pageSize: 100,
|
|
1941
|
-
q: '',
|
|
1942
|
-
},
|
|
1943
|
-
},
|
|
1944
|
-
{
|
|
1945
|
-
adapterName: 'getListInfosByObjectName',
|
|
1946
|
-
config: {
|
|
1947
|
-
objectApiName: objectApiName,
|
|
1948
|
-
pageSize: 10,
|
|
1949
|
-
recentListsOnly: true,
|
|
1950
|
-
},
|
|
1951
|
-
},
|
|
1952
|
-
{
|
|
1953
|
-
adapterName: 'getListObjectInfo',
|
|
1954
|
-
config: {
|
|
1955
|
-
objectApiName: objectApiName,
|
|
1956
|
-
},
|
|
1957
|
-
},
|
|
1958
|
-
];
|
|
1919
|
+
return reqA;
|
|
1959
1920
|
}
|
|
1960
1921
|
/**
|
|
1961
|
-
*
|
|
1922
|
+
* Checks adapter config against request context to determine if the request is context dependent.
|
|
1962
1923
|
*
|
|
1963
|
-
*
|
|
1924
|
+
* By default, requests are not context dependent.
|
|
1925
|
+
* @param request
|
|
1926
|
+
* @returns
|
|
1964
1927
|
*/
|
|
1965
|
-
|
|
1966
|
-
return
|
|
1928
|
+
isContextDependent(_context, _request) {
|
|
1929
|
+
return false;
|
|
1967
1930
|
}
|
|
1968
1931
|
/**
|
|
1969
|
-
*
|
|
1970
|
-
* can't be merged with other predictions, they will always run by themself.
|
|
1971
|
-
* This value must be `false`, otherwise we may see repeated requests.
|
|
1932
|
+
* This tells PDL that requests of this strategy can only be saved in the similar bucket.
|
|
1972
1933
|
*
|
|
1973
|
-
* @returns
|
|
1934
|
+
* @returns boolean
|
|
1974
1935
|
*/
|
|
1975
|
-
|
|
1936
|
+
get onlySavedInSimilar() {
|
|
1976
1937
|
return false;
|
|
1977
1938
|
}
|
|
1978
|
-
// Identifies a valid ObjectHomeContext
|
|
1979
|
-
static handlesContext(context) {
|
|
1980
|
-
return (context !== undefined &&
|
|
1981
|
-
context.listViewApiName !== undefined &&
|
|
1982
|
-
context.objectApiName !== undefined &&
|
|
1983
|
-
context.type === 'objectHomePage');
|
|
1984
|
-
}
|
|
1985
1939
|
}
|
|
1986
1940
|
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
*/
|
|
1998
|
-
const OBSERVABILITY_NAMESPACE = 'LIGHTNING.lds.service';
|
|
1999
|
-
const ADAPTER_INVOCATION_COUNT_METRIC_NAME = 'request';
|
|
2000
|
-
const ADAPTER_ERROR_COUNT_METRIC_NAME = 'error';
|
|
2001
|
-
const NETWORK_ADAPTER_RESPONSE_METRIC_NAME = 'network-response';
|
|
2002
|
-
/**
|
|
2003
|
-
* W-8379680
|
|
2004
|
-
* Counter for number of getApex requests.
|
|
2005
|
-
*/
|
|
2006
|
-
const GET_APEX_REQUEST_COUNT = {
|
|
2007
|
-
get() {
|
|
2008
|
-
return {
|
|
2009
|
-
owner: OBSERVABILITY_NAMESPACE,
|
|
2010
|
-
name: ADAPTER_INVOCATION_COUNT_METRIC_NAME + '.' + NORMALIZED_APEX_ADAPTER_NAME,
|
|
2011
|
-
};
|
|
2012
|
-
},
|
|
2013
|
-
};
|
|
2014
|
-
/**
|
|
2015
|
-
* W-8828410
|
|
2016
|
-
* Counter for the number of UnfulfilledSnapshotErrors the luvio engine has.
|
|
2017
|
-
*/
|
|
2018
|
-
const TOTAL_ADAPTER_ERROR_COUNT = {
|
|
2019
|
-
get() {
|
|
2020
|
-
return { owner: OBSERVABILITY_NAMESPACE, name: ADAPTER_ERROR_COUNT_METRIC_NAME };
|
|
2021
|
-
},
|
|
2022
|
-
};
|
|
2023
|
-
/**
|
|
2024
|
-
* W-8828410
|
|
2025
|
-
* Counter for the number of invocations made into LDS by a wire adapter.
|
|
2026
|
-
*/
|
|
2027
|
-
const TOTAL_ADAPTER_REQUEST_SUCCESS_COUNT = {
|
|
2028
|
-
get() {
|
|
2029
|
-
return { owner: OBSERVABILITY_NAMESPACE, name: ADAPTER_INVOCATION_COUNT_METRIC_NAME };
|
|
2030
|
-
},
|
|
2031
|
-
};
|
|
2032
|
-
|
|
2033
|
-
const { create, keys, hasOwnProperty, entries } = Object;
|
|
2034
|
-
const { isArray, from } = Array;
|
|
2035
|
-
const { stringify } = JSON;
|
|
2036
|
-
|
|
2037
|
-
/**
|
|
2038
|
-
* A deterministic JSON stringify implementation. Heavily adapted from https://github.com/epoberezkin/fast-json-stable-stringify.
|
|
2039
|
-
* This is needed because insertion order for JSON.stringify(object) affects output:
|
|
2040
|
-
* JSON.stringify({a: 1, b: 2})
|
|
2041
|
-
* "{"a":1,"b":2}"
|
|
2042
|
-
* JSON.stringify({b: 2, a: 1})
|
|
2043
|
-
* "{"b":2,"a":1}"
|
|
2044
|
-
* Modified from the apex implementation to sort arrays non-destructively.
|
|
2045
|
-
* @param data Data to be JSON-stringified.
|
|
2046
|
-
* @returns JSON.stringified value with consistent ordering of keys.
|
|
2047
|
-
*/
|
|
2048
|
-
function stableJSONStringify$1(node) {
|
|
2049
|
-
// This is for Date values.
|
|
2050
|
-
if (node && node.toJSON && typeof node.toJSON === 'function') {
|
|
2051
|
-
// eslint-disable-next-line no-param-reassign
|
|
2052
|
-
node = node.toJSON();
|
|
2053
|
-
}
|
|
2054
|
-
if (node === undefined) {
|
|
2055
|
-
return;
|
|
2056
|
-
}
|
|
2057
|
-
if (typeof node === 'number') {
|
|
2058
|
-
return isFinite(node) ? '' + node : 'null';
|
|
2059
|
-
}
|
|
2060
|
-
if (typeof node !== 'object') {
|
|
2061
|
-
return stringify(node);
|
|
2062
|
-
}
|
|
2063
|
-
let i;
|
|
2064
|
-
let out;
|
|
2065
|
-
if (isArray(node)) {
|
|
2066
|
-
// copy any array before sorting so we don't mutate the object.
|
|
2067
|
-
// eslint-disable-next-line no-param-reassign
|
|
2068
|
-
node = node.slice(0).sort();
|
|
2069
|
-
out = '[';
|
|
2070
|
-
for (i = 0; i < node.length; i++) {
|
|
2071
|
-
if (i) {
|
|
2072
|
-
out += ',';
|
|
2073
|
-
}
|
|
2074
|
-
out += stableJSONStringify$1(node[i]) || 'null';
|
|
2075
|
-
}
|
|
2076
|
-
return out + ']';
|
|
2077
|
-
}
|
|
2078
|
-
if (node === null) {
|
|
2079
|
-
return 'null';
|
|
2080
|
-
}
|
|
2081
|
-
const keys$1 = keys(node).sort();
|
|
2082
|
-
out = '';
|
|
2083
|
-
for (i = 0; i < keys$1.length; i++) {
|
|
2084
|
-
const key = keys$1[i];
|
|
2085
|
-
const value = stableJSONStringify$1(node[key]);
|
|
2086
|
-
if (!value) {
|
|
2087
|
-
continue;
|
|
2088
|
-
}
|
|
2089
|
-
if (out) {
|
|
2090
|
-
out += ',';
|
|
2091
|
-
}
|
|
2092
|
-
out += stringify(key) + ':' + value;
|
|
1941
|
+
class LexRequestStrategy extends RequestStrategy {
|
|
1942
|
+
/**
|
|
1943
|
+
* Whether or not requests from this strategies can be boxcarred by Aura.
|
|
1944
|
+
* If they are, the lex prefetcher will run predictions of this strategy with a limit,
|
|
1945
|
+
* to avoid predictions being boxcared.
|
|
1946
|
+
*
|
|
1947
|
+
* @returns boolean
|
|
1948
|
+
*/
|
|
1949
|
+
get isBoxcarable() {
|
|
1950
|
+
return true;
|
|
2093
1951
|
}
|
|
2094
|
-
return '{' + out + '}';
|
|
2095
|
-
}
|
|
2096
|
-
function isPromise(value) {
|
|
2097
|
-
// check for Thenable due to test frameworks using custom Promise impls
|
|
2098
|
-
return value !== null && value.then !== undefined;
|
|
2099
1952
|
}
|
|
2100
1953
|
|
|
2101
|
-
const
|
|
2102
|
-
const
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
const
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
/**
|
|
2211
|
-
* 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.
|
|
2212
|
-
* Backed by an LRU Cache implementation to prevent too many record entries from being stored in-memory.
|
|
2213
|
-
* @param name The wire adapter name.
|
|
2214
|
-
* @param config The config passed into wire adapter.
|
|
2215
|
-
* @param ttlMissStats CacheStatsLogger to log misses out of TTL.
|
|
2216
|
-
* @param currentCacheMissTimestamp Timestamp for when the request was made.
|
|
2217
|
-
* @param ttl TTL for the wire adapter.
|
|
2218
|
-
*/
|
|
2219
|
-
logAdapterCacheMissOutOfTtlDuration(name, config, ttlMissStats, currentCacheMissTimestamp, ttl) {
|
|
2220
|
-
const configKey = `${name}:${stableJSONStringify$1(config)}`;
|
|
2221
|
-
const existingCacheMissTimestamp = this.adapterCacheMisses.get(configKey);
|
|
2222
|
-
this.adapterCacheMisses.set(configKey, currentCacheMissTimestamp);
|
|
2223
|
-
if (existingCacheMissTimestamp !== undefined) {
|
|
2224
|
-
const duration = currentCacheMissTimestamp - existingCacheMissTimestamp;
|
|
2225
|
-
if (duration > ttl) {
|
|
2226
|
-
ttlMissStats.logMisses();
|
|
2227
|
-
}
|
|
2228
|
-
}
|
|
2229
|
-
}
|
|
2230
|
-
/**
|
|
2231
|
-
* Injected to LDS for Luvio specific instrumentation.
|
|
2232
|
-
*
|
|
2233
|
-
* @param context The transaction context.
|
|
2234
|
-
*/
|
|
2235
|
-
instrumentLuvio(context) {
|
|
2236
|
-
instrumentLuvio(context);
|
|
2237
|
-
if (this.isRefreshAdapterEvent(context)) {
|
|
2238
|
-
this.aggregateRefreshAdapterEvents(context);
|
|
2239
|
-
}
|
|
2240
|
-
else if (this.isAdapterUnfulfilledError(context)) {
|
|
2241
|
-
this.incrementAdapterRequestErrorCount(context);
|
|
1954
|
+
const GET_COMPONENTS_DEF_ADAPTER_NAME = 'getComponentsDef';
|
|
1955
|
+
const noop = () => { };
|
|
1956
|
+
// Taken from https://sourcegraph.soma.salesforce.com/perforce.soma.salesforce.com/app/main/core/-/blob/ui-global-components/components/one/one/oneController.js?L75
|
|
1957
|
+
// In theory this should not be here, but for now, we don't have an alternative,
|
|
1958
|
+
// given these are started before o11y tells PDL the EPT windows ended (even before the timestamp sent by o11y).
|
|
1959
|
+
const onePreloads = new Set([
|
|
1960
|
+
'markup://emailui:formattedEmailWrapper',
|
|
1961
|
+
'markup://emailui:outputEmail',
|
|
1962
|
+
'markup://flexipage:baseRecordHomeTemplateDesktop',
|
|
1963
|
+
'markup://force:actionWindowLink',
|
|
1964
|
+
'markup://force:inlineEditCell',
|
|
1965
|
+
'markup://force:inputField',
|
|
1966
|
+
'markup://force:recordPreviewItem',
|
|
1967
|
+
'markup://force:relatedListDesktop',
|
|
1968
|
+
'markup://force:relatedListQuickLinksContainer',
|
|
1969
|
+
'markup://lst:relatedListQuickLinksContainer',
|
|
1970
|
+
'markup://lst:secondDegreeRelatedListSingleContainer',
|
|
1971
|
+
'markup://lst:bundle_act_coreListViewManagerDesktop',
|
|
1972
|
+
'markup://lst:bundle_act_coreListViewManagerDesktop_generatedTemplates',
|
|
1973
|
+
'markup://lst:baseFilterPanel',
|
|
1974
|
+
'markup://lst:chartPanel',
|
|
1975
|
+
'markup://force:socialPhotoWrapper',
|
|
1976
|
+
'markup://forceContent:contentVersionsEditWizard',
|
|
1977
|
+
'markup://forceContent:outputTitle',
|
|
1978
|
+
'markup://forceContent:virtualRelatedListStencil',
|
|
1979
|
+
'markup://forceSearch:resultsFilters',
|
|
1980
|
+
'markup://interop:unstable_uiRecordApi',
|
|
1981
|
+
'markup://lightning:formattedPhone',
|
|
1982
|
+
'markup://notes:contentNoteRelatedListStencil',
|
|
1983
|
+
'markup://one:alohaPage',
|
|
1984
|
+
'markup://one:consoleObjectHome',
|
|
1985
|
+
'markup://one:recordActionWrapper',
|
|
1986
|
+
'markup://records:lwcDetailPanel',
|
|
1987
|
+
'markup://records:lwcHighlightsPanel',
|
|
1988
|
+
'markup://records:recordLayoutInputDateTime',
|
|
1989
|
+
'markup://records:recordLayoutInputLocation',
|
|
1990
|
+
'markup://records:recordLayoutItem',
|
|
1991
|
+
'markup://records:recordLayoutLookup',
|
|
1992
|
+
'markup://records:recordLayoutRichText',
|
|
1993
|
+
'markup://records:recordLayoutRow',
|
|
1994
|
+
'markup://records:recordLayoutSection',
|
|
1995
|
+
'markup://records:recordLayoutTextArea',
|
|
1996
|
+
'markup://records:recordPicklist',
|
|
1997
|
+
'markup://sfa:outputNameWithHierarchyIcon',
|
|
1998
|
+
'markup://runtime_platform_actions:actionHeadlessFormCancel',
|
|
1999
|
+
'markup://runtime_platform_actions:actionHeadlessFormSave',
|
|
2000
|
+
'markup://runtime_platform_actions:actionHeadlessFormSaveAndNew',
|
|
2001
|
+
'markup://lightning:iconSvgTemplatesCustom',
|
|
2002
|
+
'markup://lightning:iconSvgTemplatesDocType',
|
|
2003
|
+
'markup://record_flexipage:recordHomeFlexipageUtil',
|
|
2004
|
+
'markup://record_flexipage:recordFieldInstancesHandlers',
|
|
2005
|
+
'markup://force:outputCustomLinkUrl',
|
|
2006
|
+
'markup://force:quickActionRunnable',
|
|
2007
|
+
'markup://force:inputURL',
|
|
2008
|
+
'markup://force:inputMultiPicklist',
|
|
2009
|
+
'markup://runtime_sales_activities:activityPanel',
|
|
2010
|
+
'markup://support:outputLookupWithPreviewForSubject',
|
|
2011
|
+
'markup://runtime_sales_activities:activitySubjectListView',
|
|
2012
|
+
'markup://support:outputCaseSubjectField',
|
|
2013
|
+
'markup://sfa:inputOpportunityAmount',
|
|
2014
|
+
'markup://forceChatter:contentFileSize',
|
|
2015
|
+
'markup://flexipage:column2',
|
|
2016
|
+
'markup://sfa:outputNameWithHierarchyIconAccount',
|
|
2017
|
+
'markup://emailui:formattedEmailAccount',
|
|
2018
|
+
'markup://emailui:formattedEmailContact',
|
|
2019
|
+
'markup://emailui:formattedEmailLead',
|
|
2020
|
+
'markup://e.aura:serverActionError',
|
|
2021
|
+
'markup://records:recordType',
|
|
2022
|
+
'markup://flexipage:recordHomeWithSubheaderTemplateDesktop2',
|
|
2023
|
+
'markup://force:customLinkUrl',
|
|
2024
|
+
'markup://sfa:outputOpportunityAmount',
|
|
2025
|
+
'markup://emailui:formattedEmailCase',
|
|
2026
|
+
'markup://runtime_sales_activities:activitySubject',
|
|
2027
|
+
'markup://lightning:quickActionAPI',
|
|
2028
|
+
'markup://force:listViewManagerGridWrapText',
|
|
2029
|
+
'markup://flexipage:recordHomeSimpleViewTemplate2',
|
|
2030
|
+
'markup://flexipage:accordion2',
|
|
2031
|
+
'markup://flexipage:accordionSection2',
|
|
2032
|
+
'markup://flexipage:field',
|
|
2033
|
+
'markup://runtime_iag_core:onboardingManager',
|
|
2034
|
+
'markup://records:entityLabel',
|
|
2035
|
+
'markup://records:highlightsHeaderRightContent',
|
|
2036
|
+
'markup://records:formattedRichText',
|
|
2037
|
+
'markup://force:socialRecordAvatarWrapper',
|
|
2038
|
+
'markup://runtime_pipeline_inspector:pipelineInspectorHome',
|
|
2039
|
+
'markup://sfa:inspectionDesktopObjectHome',
|
|
2040
|
+
'markup://records:outputPhone',
|
|
2041
|
+
]);
|
|
2042
|
+
function canPreloadDefinition(def) {
|
|
2043
|
+
return (def.startsWith('markup://') &&
|
|
2044
|
+
!(
|
|
2045
|
+
// some "virtual" components from flexipages are with `__` in the name, eg: design templates.
|
|
2046
|
+
// Not filtering out them will not cause errors, but will cause a server request that returns with error.
|
|
2047
|
+
(def.includes('__') ||
|
|
2048
|
+
// any generated template
|
|
2049
|
+
def.includes('forceGenerated') ||
|
|
2050
|
+
// part of onePreload
|
|
2051
|
+
def.includes('one:onePreloads') ||
|
|
2052
|
+
onePreloads.has(def))));
|
|
2053
|
+
}
|
|
2054
|
+
function requestComponents(config) {
|
|
2055
|
+
// Because { foo: undefined } can't be saved in indexedDB (serialization is {})
|
|
2056
|
+
// we need to manually save it as { "foo": "" }, and transform it to { foo: undefined }
|
|
2057
|
+
const descriptorsMap = {};
|
|
2058
|
+
let hasComponentsToLoad = false;
|
|
2059
|
+
for (const [def, uid] of entries(config)) {
|
|
2060
|
+
if (canPreloadDefinition(def)) {
|
|
2061
|
+
hasComponentsToLoad = true;
|
|
2062
|
+
descriptorsMap[def] = uid === '' ? undefined : uid;
|
|
2242
2063
|
}
|
|
2243
|
-
else ;
|
|
2244
|
-
}
|
|
2245
|
-
/**
|
|
2246
|
-
* Returns whether or not this is a RefreshAdapterEvent.
|
|
2247
|
-
* @param context The transaction context.
|
|
2248
|
-
* @returns Whether or not this is a RefreshAdapterEvent.
|
|
2249
|
-
*/
|
|
2250
|
-
isRefreshAdapterEvent(context) {
|
|
2251
|
-
return context[REFRESH_ADAPTER_EVENT] === true;
|
|
2252
2064
|
}
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
* @param context The transaction context.
|
|
2256
|
-
* @returns Whether or not this is an AdapterUnfulfilledError.
|
|
2257
|
-
*/
|
|
2258
|
-
isAdapterUnfulfilledError(context) {
|
|
2259
|
-
return context[ADAPTER_UNFULFILLED_ERROR] === true;
|
|
2260
|
-
}
|
|
2261
|
-
/**
|
|
2262
|
-
* Specific instrumentation for getRecordNotifyChange.
|
|
2263
|
-
* temporary implementation to match existing aura call for now
|
|
2264
|
-
*
|
|
2265
|
-
* @param uniqueWeakEtags whether weakEtags match or not
|
|
2266
|
-
* @param error if dispatchResourceRequest fails for any reason
|
|
2267
|
-
*/
|
|
2268
|
-
notifyChangeNetwork(uniqueWeakEtags, error) {
|
|
2269
|
-
perfStart(NETWORK_TRANSACTION_NAME);
|
|
2270
|
-
if (error === true) {
|
|
2271
|
-
perfEnd(NETWORK_TRANSACTION_NAME, { 'notify-change-network': 'error' });
|
|
2272
|
-
}
|
|
2273
|
-
else {
|
|
2274
|
-
perfEnd(NETWORK_TRANSACTION_NAME, { 'notify-change-network': uniqueWeakEtags });
|
|
2275
|
-
}
|
|
2065
|
+
if (hasComponentsToLoad) {
|
|
2066
|
+
unstable_loadComponentDefs(descriptorsMap, noop);
|
|
2276
2067
|
}
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
const key = 'weaketag-0-' + apiName;
|
|
2283
|
-
if (this.weakEtagZeroEvents[key] === undefined) {
|
|
2284
|
-
this.weakEtagZeroEvents[key] = {
|
|
2285
|
-
[EXISTING_WEAKETAG_0_KEY]: 0,
|
|
2286
|
-
[INCOMING_WEAKETAG_0_KEY]: 0,
|
|
2287
|
-
};
|
|
2288
|
-
}
|
|
2289
|
-
if (existingWeakEtagZero) {
|
|
2290
|
-
this.weakEtagZeroEvents[key][EXISTING_WEAKETAG_0_KEY] += 1;
|
|
2291
|
-
}
|
|
2292
|
-
if (incomingWeakEtagZero) {
|
|
2293
|
-
this.weakEtagZeroEvents[key][INCOMING_WEAKETAG_0_KEY] += 1;
|
|
2294
|
-
}
|
|
2068
|
+
}
|
|
2069
|
+
class GetComponentsDefStrategy extends LexRequestStrategy {
|
|
2070
|
+
constructor() {
|
|
2071
|
+
super(...arguments);
|
|
2072
|
+
this.adapterName = GET_COMPONENTS_DEF_ADAPTER_NAME;
|
|
2295
2073
|
}
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
* - how many times refreshApex is called
|
|
2299
|
-
* - how many times refresh from lightning/uiRecordApi is called
|
|
2300
|
-
* - number of supported calls: refreshApex called on apex adapter
|
|
2301
|
-
* - number of unsupported calls: refreshApex on non-apex adapter
|
|
2302
|
-
* + any use of refresh from uiRecordApi module
|
|
2303
|
-
* - count of refresh calls per adapter
|
|
2304
|
-
* @param context The refresh adapter event.
|
|
2305
|
-
*/
|
|
2306
|
-
aggregateRefreshAdapterEvents(context) {
|
|
2307
|
-
// We are consolidating all apex adapter instrumentation calls under a single key
|
|
2308
|
-
// Adding additional logging that getApex adapters can invoke? Read normalizeAdapterName ts-doc.
|
|
2309
|
-
const adapterName = normalizeAdapterName(context.adapterName);
|
|
2310
|
-
if (this.lastRefreshApiCall === REFRESH_APEX_KEY) {
|
|
2311
|
-
if (isApexAdapter(adapterName)) {
|
|
2312
|
-
this.refreshApiCallEventStats[SUPPORTED_KEY] += 1;
|
|
2313
|
-
}
|
|
2314
|
-
else {
|
|
2315
|
-
this.refreshApiCallEventStats[UNSUPPORTED_KEY] += 1;
|
|
2316
|
-
}
|
|
2317
|
-
}
|
|
2318
|
-
else if (this.lastRefreshApiCall === REFRESH_UIAPI_KEY) {
|
|
2319
|
-
this.refreshApiCallEventStats[UNSUPPORTED_KEY] += 1;
|
|
2320
|
-
}
|
|
2321
|
-
if (this.refreshAdapterEvents[adapterName] === undefined) {
|
|
2322
|
-
this.refreshAdapterEvents[adapterName] = 0;
|
|
2323
|
-
}
|
|
2324
|
-
this.refreshAdapterEvents[adapterName] += 1;
|
|
2325
|
-
this.lastRefreshApiCall = null;
|
|
2074
|
+
execute(config) {
|
|
2075
|
+
return requestComponents(config);
|
|
2326
2076
|
}
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
*/
|
|
2332
|
-
handleRefreshApiCall(apiName) {
|
|
2333
|
-
this.refreshApiCallEventStats[apiName] += 1;
|
|
2334
|
-
// set function call to be used with aggregateRefreshCalls
|
|
2335
|
-
this.lastRefreshApiCall = apiName;
|
|
2077
|
+
buildConcreteRequest(similarRequest, _context) {
|
|
2078
|
+
return {
|
|
2079
|
+
...similarRequest,
|
|
2080
|
+
};
|
|
2336
2081
|
}
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2082
|
+
transformForSave(request) {
|
|
2083
|
+
const normalizedConfig = {};
|
|
2084
|
+
for (const [def, uid] of entries(request.config || {})) {
|
|
2085
|
+
const normalizedDescriptorName = def.indexOf('://') === -1 ? 'markup://' + def : def;
|
|
2086
|
+
// uid can be a string, an object, or undefined.
|
|
2087
|
+
// when is an object or undefined, we can't say anything about the version,
|
|
2088
|
+
// and we can't save it as `undefined` as it can't be persisted to indexed db.
|
|
2089
|
+
normalizedConfig[normalizedDescriptorName] = typeof uid === 'string' ? uid : '';
|
|
2345
2090
|
}
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
*/
|
|
2350
|
-
resetRefreshStats() {
|
|
2351
|
-
this.refreshAdapterEvents = {};
|
|
2352
|
-
this.refreshApiCallEventStats = {
|
|
2353
|
-
[REFRESH_APEX_KEY]: 0,
|
|
2354
|
-
[REFRESH_UIAPI_KEY]: 0,
|
|
2355
|
-
[SUPPORTED_KEY]: 0,
|
|
2356
|
-
[UNSUPPORTED_KEY]: 0,
|
|
2091
|
+
return {
|
|
2092
|
+
...request,
|
|
2093
|
+
config: normalizedConfig,
|
|
2357
2094
|
};
|
|
2358
|
-
this.lastRefreshApiCall = null;
|
|
2359
2095
|
}
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
this.recordApiNameChangeCounters[existingApiName] = apiNameChangeCounter;
|
|
2096
|
+
canCombine() {
|
|
2097
|
+
return true;
|
|
2098
|
+
}
|
|
2099
|
+
combineRequests(reqA, reqB) {
|
|
2100
|
+
const combinedDescriptors = {};
|
|
2101
|
+
// Note the order is important [reqA, reqB], reqB is always after reqA, and we want to keep the last seen uid
|
|
2102
|
+
// of a specific component.
|
|
2103
|
+
for (const descriptorMap of [reqA, reqB]) {
|
|
2104
|
+
for (const [def, uid] of entries(descriptorMap)) {
|
|
2105
|
+
if (canPreloadDefinition(def)) {
|
|
2106
|
+
combinedDescriptors[def] = uid;
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2374
2109
|
}
|
|
2375
|
-
|
|
2110
|
+
return combinedDescriptors;
|
|
2111
|
+
}
|
|
2112
|
+
get onlySavedInSimilar() {
|
|
2113
|
+
// Important: tells PDL to save this request only in the similar buckets.
|
|
2114
|
+
return true;
|
|
2115
|
+
}
|
|
2116
|
+
isContextDependent(_context, _request) {
|
|
2117
|
+
return true;
|
|
2376
2118
|
}
|
|
2377
2119
|
/**
|
|
2378
|
-
*
|
|
2379
|
-
* Increment the counter for an UnfulfilledSnapshotError coming from luvio
|
|
2120
|
+
* Component predictions are not boxcared
|
|
2380
2121
|
*
|
|
2381
|
-
* @
|
|
2122
|
+
* @returns false
|
|
2382
2123
|
*/
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
const adapterName = normalizeAdapterName(context.adapterName);
|
|
2386
|
-
let adapterRequestErrorCounter = this.adapterUnfulfilledErrorCounters[adapterName];
|
|
2387
|
-
if (adapterRequestErrorCounter === undefined) {
|
|
2388
|
-
adapterRequestErrorCounter = counter(createMetricsKey(OBSERVABILITY_NAMESPACE, ADAPTER_ERROR_COUNT_METRIC_NAME, adapterName));
|
|
2389
|
-
this.adapterUnfulfilledErrorCounters[adapterName] = adapterRequestErrorCounter;
|
|
2390
|
-
}
|
|
2391
|
-
adapterRequestErrorCounter.increment(1);
|
|
2392
|
-
totalAdapterErrorMetric.increment(1);
|
|
2124
|
+
get isBoxcarable() {
|
|
2125
|
+
return false;
|
|
2393
2126
|
}
|
|
2394
2127
|
}
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2128
|
+
|
|
2129
|
+
const LDS_PDL_CMP_IDENTIFIER = 'lds:pdl';
|
|
2130
|
+
const DEFAULT_RESOURCE_CONTEXT = {
|
|
2131
|
+
sourceContext: {
|
|
2132
|
+
tagName: LDS_PDL_CMP_IDENTIFIER,
|
|
2133
|
+
actionConfig: {
|
|
2134
|
+
background: false,
|
|
2135
|
+
hotspot: true,
|
|
2136
|
+
longRunning: false,
|
|
2403
2137
|
},
|
|
2404
|
-
}
|
|
2405
|
-
}
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
function isApexAdapter(adapterName) {
|
|
2411
|
-
return adapterName.indexOf(APEX_ADAPTER_NAME) > -1;
|
|
2412
|
-
}
|
|
2413
|
-
/**
|
|
2414
|
-
* Normalizes getApex adapter names to `Apex.getApex`. Non-Apex adapters will be prefixed with
|
|
2415
|
-
* API family, if supplied. Example: `UiApi.getRecord`.
|
|
2416
|
-
*
|
|
2417
|
-
* Note: If you are adding additional logging that can come from getApex adapter contexts that provide
|
|
2418
|
-
* the full getApex adapter name (i.e. getApex_[namespace]_[class]_[function]_[continuation]),
|
|
2419
|
-
* ensure to call this method to normalize all logging to 'getApex'. This
|
|
2420
|
-
* is because Argus has a 50k key cardinality limit. More context: W-8379680.
|
|
2421
|
-
*
|
|
2422
|
-
* @param adapterName The name of the adapter.
|
|
2423
|
-
* @param apiFamily The API family of the adapter.
|
|
2424
|
-
*/
|
|
2425
|
-
function normalizeAdapterName(adapterName, apiFamily) {
|
|
2426
|
-
if (isApexAdapter(adapterName)) {
|
|
2427
|
-
return NORMALIZED_APEX_ADAPTER_NAME;
|
|
2428
|
-
}
|
|
2429
|
-
return apiFamily ? `${apiFamily}.${adapterName}` : adapterName;
|
|
2430
|
-
}
|
|
2431
|
-
const timerMetricTracker = create(null);
|
|
2432
|
-
/**
|
|
2433
|
-
* Calls instrumentation/service telemetry timer
|
|
2434
|
-
* @param name Name of the metric
|
|
2435
|
-
* @param duration number to update backing percentile histogram, negative numbers ignored
|
|
2436
|
-
*/
|
|
2437
|
-
function updateTimerMetric(name, duration) {
|
|
2438
|
-
let metric = timerMetricTracker[name];
|
|
2439
|
-
if (metric === undefined) {
|
|
2440
|
-
metric = timer(createMetricsKey(NAMESPACE, name));
|
|
2441
|
-
timerMetricTracker[name] = metric;
|
|
2442
|
-
}
|
|
2443
|
-
timerMetricAddDuration(metric, duration);
|
|
2444
|
-
}
|
|
2445
|
-
function timerMetricAddDuration(timer, duration) {
|
|
2446
|
-
// Guard against negative values since it causes error to be thrown by MetricsService
|
|
2447
|
-
if (duration >= 0) {
|
|
2448
|
-
timer.addDuration(duration);
|
|
2449
|
-
}
|
|
2450
|
-
}
|
|
2451
|
-
/**
|
|
2452
|
-
* W-10315098
|
|
2453
|
-
* Increments the counter associated with the request response. Counts are bucketed by status.
|
|
2454
|
-
*/
|
|
2455
|
-
const requestResponseMetricTracker = create(null);
|
|
2456
|
-
function incrementRequestResponseCount(cb) {
|
|
2457
|
-
const status = cb().status;
|
|
2458
|
-
let metric = requestResponseMetricTracker[status];
|
|
2459
|
-
if (metric === undefined) {
|
|
2460
|
-
metric = counter(createMetricsKey(OBSERVABILITY_NAMESPACE, NETWORK_ADAPTER_RESPONSE_METRIC_NAME, `${status.valueOf()}`));
|
|
2461
|
-
requestResponseMetricTracker[status] = metric;
|
|
2138
|
+
},
|
|
2139
|
+
};
|
|
2140
|
+
class LuvioAdapterRequestStrategy extends LexRequestStrategy {
|
|
2141
|
+
constructor(luvio) {
|
|
2142
|
+
super();
|
|
2143
|
+
this.luvio = luvio;
|
|
2462
2144
|
}
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
}
|
|
2468
|
-
/**
|
|
2469
|
-
* Create a new instrumentation cache stats and return it.
|
|
2470
|
-
*
|
|
2471
|
-
* @param name The cache logger name.
|
|
2472
|
-
*/
|
|
2473
|
-
function registerLdsCacheStats(name) {
|
|
2474
|
-
return registerCacheStats(`${NAMESPACE}:${name}`);
|
|
2475
|
-
}
|
|
2476
|
-
/**
|
|
2477
|
-
* Add or overwrite hooks that require aura implementations
|
|
2478
|
-
*/
|
|
2479
|
-
function setAuraInstrumentationHooks() {
|
|
2480
|
-
instrument({
|
|
2481
|
-
recordConflictsResolved: (serverRequestCount) => {
|
|
2482
|
-
// Ignore 0 values which can originate from ADS bridge
|
|
2483
|
-
if (serverRequestCount > 0) {
|
|
2484
|
-
updatePercentileHistogramMetric('record-conflicts-resolved', serverRequestCount);
|
|
2485
|
-
}
|
|
2486
|
-
},
|
|
2487
|
-
nullDisplayValueConflict: ({ fieldType, areValuesEqual }) => {
|
|
2488
|
-
const metricName = `merge-null-dv-count.${fieldType}`;
|
|
2489
|
-
if (fieldType === 'scalar') {
|
|
2490
|
-
incrementCounterMetric(`${metricName}.${areValuesEqual}`);
|
|
2491
|
-
}
|
|
2492
|
-
else {
|
|
2493
|
-
incrementCounterMetric(metricName);
|
|
2494
|
-
}
|
|
2495
|
-
},
|
|
2496
|
-
getRecordNotifyChangeAllowed: incrementGetRecordNotifyChangeAllowCount,
|
|
2497
|
-
getRecordNotifyChangeDropped: incrementGetRecordNotifyChangeDropCount,
|
|
2498
|
-
notifyRecordUpdateAvailableAllowed: incrementNotifyRecordUpdateAvailableAllowCount,
|
|
2499
|
-
notifyRecordUpdateAvailableDropped: incrementNotifyRecordUpdateAvailableDropCount,
|
|
2500
|
-
recordApiNameChanged: instrumentation.incrementRecordApiNameChangeCount.bind(instrumentation),
|
|
2501
|
-
weakEtagZero: instrumentation.aggregateWeakETagEvents.bind(instrumentation),
|
|
2502
|
-
getRecordNotifyChangeNetworkResult: instrumentation.notifyChangeNetwork.bind(instrumentation),
|
|
2503
|
-
});
|
|
2504
|
-
withRegistration('@salesforce/lds-adapters-uiapi', (reg) => setLdsAdaptersUiapiInstrumentation(reg));
|
|
2505
|
-
instrument$1({
|
|
2506
|
-
logCrud: logCRUDLightningInteraction,
|
|
2507
|
-
networkResponse: incrementRequestResponseCount,
|
|
2508
|
-
});
|
|
2509
|
-
instrument$2({
|
|
2510
|
-
error: logError,
|
|
2511
|
-
});
|
|
2512
|
-
instrument$3({
|
|
2513
|
-
refreshCalled: instrumentation.handleRefreshApiCall.bind(instrumentation),
|
|
2514
|
-
instrumentAdapter: instrumentation.instrumentAdapter.bind(instrumentation),
|
|
2515
|
-
});
|
|
2516
|
-
instrument$4({
|
|
2517
|
-
timerMetricAddDuration: updateTimerMetric,
|
|
2518
|
-
});
|
|
2519
|
-
// Our getRecord through aggregate-ui CRUD logging has moved
|
|
2520
|
-
// to lds-network-adapter. We still need to respect the
|
|
2521
|
-
// orgs environment setting
|
|
2522
|
-
if (forceRecordTransactionsDisabled$1 === false) {
|
|
2523
|
-
ldsNetworkAdapterInstrument({
|
|
2524
|
-
getRecordAggregateResolve: (cb) => {
|
|
2525
|
-
const { recordId, apiName } = cb();
|
|
2526
|
-
logCRUDLightningInteraction('read', {
|
|
2527
|
-
recordId,
|
|
2528
|
-
recordType: apiName,
|
|
2529
|
-
state: 'SUCCESS',
|
|
2530
|
-
});
|
|
2531
|
-
},
|
|
2532
|
-
getRecordAggregateReject: (cb) => {
|
|
2533
|
-
const recordId = cb();
|
|
2534
|
-
logCRUDLightningInteraction('read', {
|
|
2535
|
-
recordId,
|
|
2536
|
-
state: 'ERROR',
|
|
2537
|
-
});
|
|
2538
|
-
},
|
|
2145
|
+
execute(config, requestContext) {
|
|
2146
|
+
return this.adapterFactory(this.luvio)(config, {
|
|
2147
|
+
...DEFAULT_RESOURCE_CONTEXT,
|
|
2148
|
+
...requestContext,
|
|
2539
2149
|
});
|
|
2540
2150
|
}
|
|
2541
|
-
withRegistration('@salesforce/lds-network-adapter', (reg) => setLdsNetworkAdapterInstrumentation(reg));
|
|
2542
|
-
}
|
|
2543
|
-
/**
|
|
2544
|
-
* Initialize the instrumentation and instrument the LDS instance and the InMemoryStore.
|
|
2545
|
-
*
|
|
2546
|
-
* @param luvio The Luvio instance to instrument.
|
|
2547
|
-
* @param store The InMemoryStore to instrument.
|
|
2548
|
-
*/
|
|
2549
|
-
function setupInstrumentation(luvio, store) {
|
|
2550
|
-
setupInstrumentation$1(luvio, store);
|
|
2551
|
-
setAuraInstrumentationHooks();
|
|
2552
2151
|
}
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
interaction(eventSource, 'force_record', null, eventSource, 'crud', attributes);
|
|
2152
|
+
|
|
2153
|
+
const GET_RECORD_AVATARS_ADAPTER_NAME = 'getRecordAvatars';
|
|
2154
|
+
function normalizeRecordIds$1(recordIds) {
|
|
2155
|
+
if (!Array.isArray(recordIds)) {
|
|
2156
|
+
return [recordIds];
|
|
2157
|
+
}
|
|
2158
|
+
return recordIds;
|
|
2561
2159
|
}
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
this.
|
|
2567
|
-
this.requestRunner = requestRunner;
|
|
2568
|
-
this.isRecording = false;
|
|
2569
|
-
this.totalRequestCount = 0;
|
|
2570
|
-
this.queuedPredictionRequests = [];
|
|
2571
|
-
this._context = context;
|
|
2572
|
-
this.page = this.getPage();
|
|
2160
|
+
class GetRecordAvatarsRequestStrategy extends LuvioAdapterRequestStrategy {
|
|
2161
|
+
constructor() {
|
|
2162
|
+
super(...arguments);
|
|
2163
|
+
this.adapterName = GET_RECORD_AVATARS_ADAPTER_NAME;
|
|
2164
|
+
this.adapterFactory = getRecordAvatarsAdapterFactory;
|
|
2573
2165
|
}
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2166
|
+
buildConcreteRequest(similarRequest, context) {
|
|
2167
|
+
return {
|
|
2168
|
+
...similarRequest,
|
|
2169
|
+
config: {
|
|
2170
|
+
...similarRequest.config,
|
|
2171
|
+
recordIds: [context.recordId],
|
|
2172
|
+
},
|
|
2173
|
+
};
|
|
2577
2174
|
}
|
|
2578
|
-
|
|
2579
|
-
return this.
|
|
2175
|
+
transformForSaveSimilarRequest(request) {
|
|
2176
|
+
return this.transformForSave({
|
|
2177
|
+
...request,
|
|
2178
|
+
config: {
|
|
2179
|
+
...request.config,
|
|
2180
|
+
recordIds: ['*'],
|
|
2181
|
+
},
|
|
2182
|
+
});
|
|
2580
2183
|
}
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2184
|
+
isContextDependent(context, request) {
|
|
2185
|
+
return (request.config.recordIds &&
|
|
2186
|
+
(context.recordId === request.config.recordIds || // some may set this as string instead of array
|
|
2187
|
+
(request.config.recordIds.length === 1 &&
|
|
2188
|
+
request.config.recordIds[0] === context.recordId)));
|
|
2585
2189
|
}
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
this.repository.markPageStart();
|
|
2589
|
-
this.repository.clearRequestBuffer();
|
|
2190
|
+
canCombine(reqA, reqB) {
|
|
2191
|
+
return reqA.formFactor === reqB.formFactor;
|
|
2590
2192
|
}
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
executeAsyncActivity(METRIC_KEYS.PREDICTIVE_DATA_LOADING_SAVE_REQUEST, (_act) => {
|
|
2596
|
-
const saveBuckets = this.page.buildSaveRequestData(request);
|
|
2597
|
-
saveBuckets.forEach((saveBucket) => {
|
|
2598
|
-
const { request: requestToSave, context } = saveBucket;
|
|
2599
|
-
// No need to differentiate from predictions requests because these
|
|
2600
|
-
// are made from the adapters factory, which are not prediction aware.
|
|
2601
|
-
this.repository.saveRequest(context, requestToSave);
|
|
2602
|
-
});
|
|
2603
|
-
return Promise.resolve().then();
|
|
2604
|
-
}, PDL_EXECUTE_ASYNC_OPTIONS);
|
|
2193
|
+
combineRequests(reqA, reqB) {
|
|
2194
|
+
const combined = { ...reqA };
|
|
2195
|
+
combined.recordIds = Array.from(new Set([...normalizeRecordIds$1(reqA.recordIds), ...normalizeRecordIds$1(reqB.recordIds)]));
|
|
2196
|
+
return combined;
|
|
2605
2197
|
}
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
this.queuedPredictionRequests.push(...predictedRequests);
|
|
2616
|
-
this.totalRequestCount = predictedRequests.length;
|
|
2617
|
-
return Promise.all(predictedRequests.map((request) => this.requestRunner.runRequest(request))).then();
|
|
2198
|
+
}
|
|
2199
|
+
|
|
2200
|
+
const GET_RECORD_ADAPTER_NAME = 'getRecord';
|
|
2201
|
+
const COERCE_FIELD_ID_ARRAY_OPTIONS = { onlyQualifiedFieldNames: true };
|
|
2202
|
+
class GetRecordRequestStrategy extends LuvioAdapterRequestStrategy {
|
|
2203
|
+
constructor() {
|
|
2204
|
+
super(...arguments);
|
|
2205
|
+
this.adapterName = GET_RECORD_ADAPTER_NAME;
|
|
2206
|
+
this.adapterFactory = getRecordAdapterFactory;
|
|
2618
2207
|
}
|
|
2619
|
-
|
|
2620
|
-
const exactPageRequests = this.repository.getPageRequests(this.context) || [];
|
|
2621
|
-
const similarPageRequests = this.page.similarContext !== undefined
|
|
2622
|
-
? this.repository.getPageRequests(this.page.similarContext)
|
|
2623
|
-
: [];
|
|
2208
|
+
buildConcreteRequest(similarRequest, context) {
|
|
2624
2209
|
return {
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2210
|
+
...similarRequest,
|
|
2211
|
+
config: {
|
|
2212
|
+
...similarRequest.config,
|
|
2213
|
+
recordId: context.recordId,
|
|
2214
|
+
},
|
|
2628
2215
|
};
|
|
2629
2216
|
}
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
}
|
|
2634
|
-
getSimilarPageRequests() {
|
|
2635
|
-
let resolvedSimilarPageRequests = [];
|
|
2636
|
-
if (this.page.similarContext !== undefined) {
|
|
2637
|
-
const similarPageRequests = this.repository.getPageRequests(this.page.similarContext);
|
|
2638
|
-
if (similarPageRequests !== undefined) {
|
|
2639
|
-
resolvedSimilarPageRequests = similarPageRequests.map((entry) => {
|
|
2640
|
-
return {
|
|
2641
|
-
...entry,
|
|
2642
|
-
request: this.page.resolveSimilarRequest(entry.request),
|
|
2643
|
-
};
|
|
2644
|
-
});
|
|
2645
|
-
}
|
|
2217
|
+
transformForSave(request) {
|
|
2218
|
+
if (request.config.fields === undefined && request.config.optionalFields === undefined) {
|
|
2219
|
+
return request;
|
|
2646
2220
|
}
|
|
2647
|
-
|
|
2221
|
+
let fields = coerceFieldIdArray(request.config.fields, COERCE_FIELD_ID_ARRAY_OPTIONS) || [];
|
|
2222
|
+
let optionalFields = coerceFieldIdArray(request.config.optionalFields, COERCE_FIELD_ID_ARRAY_OPTIONS) || [];
|
|
2223
|
+
return {
|
|
2224
|
+
...request,
|
|
2225
|
+
config: {
|
|
2226
|
+
...request.config,
|
|
2227
|
+
fields: undefined,
|
|
2228
|
+
optionalFields: [...fields, ...optionalFields],
|
|
2229
|
+
},
|
|
2230
|
+
};
|
|
2648
2231
|
}
|
|
2649
|
-
|
|
2650
|
-
|
|
2232
|
+
canCombine(reqA, reqB) {
|
|
2233
|
+
// must be same record and
|
|
2234
|
+
return (reqA.recordId === reqB.recordId &&
|
|
2235
|
+
// both requests are fields requests
|
|
2236
|
+
(reqA.optionalFields !== undefined || reqB.optionalFields !== undefined) &&
|
|
2237
|
+
(reqB.fields !== undefined || reqB.optionalFields !== undefined));
|
|
2651
2238
|
}
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
* @template Request - The type of the requests being processed.
|
|
2658
|
-
* @param {RequestEntry<Request>[]} requests - An array of request entries to be processed in the array order.
|
|
2659
|
-
* @param {RequestRunner<Request>} runner - The runner instance responsible for executing the requests.
|
|
2660
|
-
* @param {number} concurrentRequestsLimit - The maximum number of concurrent requests allowed.
|
|
2661
|
-
* @param {number} pageStartTime - The start time of the page load, used to calculate the time elapsed since the page starts loading.
|
|
2662
|
-
* @returns {Promise<void>} A promise that resolves when all requests have been processed.
|
|
2663
|
-
*
|
|
2664
|
-
* This function manages a queue of pending requests and processes them with a concurrency limit.
|
|
2665
|
-
* Requests are only processed if their `requestTime` is less than the time elapsed since `pageStartTime`.
|
|
2666
|
-
*/
|
|
2667
|
-
async function runRequestsWithLimit(requests, runner, concurrentRequestsLimit, pageStartTime) {
|
|
2668
|
-
// queue for pending prediction requests
|
|
2669
|
-
const requestQueue = [...requests];
|
|
2670
|
-
// Function to process the next request in the queue
|
|
2671
|
-
const processNextRequest = async (verifyPastTime = true) => {
|
|
2672
|
-
const timeInWaterfall = Date.now() - pageStartTime;
|
|
2673
|
-
while (requestQueue.length > 0 &&
|
|
2674
|
-
verifyPastTime &&
|
|
2675
|
-
requestQueue[0].requestMetadata.requestTime <= timeInWaterfall) {
|
|
2676
|
-
requestQueue.shift();
|
|
2239
|
+
combineRequests(reqA, reqB) {
|
|
2240
|
+
const fields = new Set();
|
|
2241
|
+
const optionalFields = new Set();
|
|
2242
|
+
if (reqA.fields !== undefined) {
|
|
2243
|
+
reqA.fields.forEach((field) => fields.add(field));
|
|
2677
2244
|
}
|
|
2678
|
-
if (
|
|
2679
|
-
|
|
2680
|
-
const nextRequest = requestQueue.shift();
|
|
2681
|
-
try {
|
|
2682
|
-
// Run the request and wait for it to complete
|
|
2683
|
-
await runner.runRequest(nextRequest.request);
|
|
2684
|
-
}
|
|
2685
|
-
finally {
|
|
2686
|
-
await processNextRequest();
|
|
2687
|
-
}
|
|
2245
|
+
if (reqB.fields !== undefined) {
|
|
2246
|
+
reqB.fields.forEach((field) => fields.add(field));
|
|
2688
2247
|
}
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2248
|
+
if (reqA.optionalFields !== undefined) {
|
|
2249
|
+
reqA.optionalFields.forEach((field) => optionalFields.add(field));
|
|
2250
|
+
}
|
|
2251
|
+
if (reqB.optionalFields !== undefined) {
|
|
2252
|
+
reqB.optionalFields.forEach((field) => optionalFields.add(field));
|
|
2253
|
+
}
|
|
2254
|
+
return {
|
|
2255
|
+
recordId: reqA.recordId,
|
|
2256
|
+
fields: Array.from(fields),
|
|
2257
|
+
optionalFields: Array.from(optionalFields),
|
|
2258
|
+
};
|
|
2259
|
+
}
|
|
2260
|
+
isContextDependent(context, request) {
|
|
2261
|
+
return request.config.recordId === context.recordId;
|
|
2262
|
+
}
|
|
2263
|
+
transformForSaveSimilarRequest(request) {
|
|
2264
|
+
return this.transformForSave({
|
|
2265
|
+
...request,
|
|
2266
|
+
config: {
|
|
2267
|
+
...request.config,
|
|
2268
|
+
recordId: '*',
|
|
2269
|
+
},
|
|
2270
|
+
});
|
|
2701
2271
|
}
|
|
2702
|
-
// Wait for all initial requests to complete
|
|
2703
|
-
await Promise.all(promises);
|
|
2704
2272
|
}
|
|
2705
2273
|
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
reducedPredictions.map((request) => requestRunner.runRequest(request.request));
|
|
2713
|
-
}
|
|
2714
|
-
class LexPredictivePrefetcher extends ApplicationPredictivePrefetcher {
|
|
2715
|
-
constructor(context, repository, requestRunner,
|
|
2716
|
-
// These strategies need to be in sync with the "predictiveDataLoadCapable" list
|
|
2717
|
-
// from scripts/lds-uiapi-plugin.js
|
|
2718
|
-
requestStrategies, options) {
|
|
2719
|
-
super(context, repository, requestRunner);
|
|
2720
|
-
this.options = options;
|
|
2721
|
-
this.requestStrategyMap = new Map(requestStrategies.map((strategy) => [strategy.adapterName, strategy]));
|
|
2722
|
-
this.page = this.getPage();
|
|
2723
|
-
}
|
|
2724
|
-
getPage() {
|
|
2725
|
-
if (RecordHomePage.handlesContext(this.context)) {
|
|
2726
|
-
return new RecordHomePage(this.context, this.requestStrategyMap, this.options);
|
|
2727
|
-
}
|
|
2728
|
-
else if (ObjectHomePage.handlesContext(this.context)) {
|
|
2729
|
-
return new ObjectHomePage(this.context, this.requestStrategyMap, this.options);
|
|
2730
|
-
}
|
|
2731
|
-
return new LexDefaultPage(this.context);
|
|
2274
|
+
const GET_RECORDS_ADAPTER_NAME = 'getRecords';
|
|
2275
|
+
class GetRecordsRequestStrategy extends LuvioAdapterRequestStrategy {
|
|
2276
|
+
constructor() {
|
|
2277
|
+
super(...arguments);
|
|
2278
|
+
this.adapterName = GET_RECORDS_ADAPTER_NAME;
|
|
2279
|
+
this.adapterFactory = getRecordsAdapterFactory;
|
|
2732
2280
|
}
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
}
|
|
2742
|
-
return [...exactPageRequests, ...similarPageRequests];
|
|
2281
|
+
buildConcreteRequest(similarRequest, context) {
|
|
2282
|
+
return {
|
|
2283
|
+
...similarRequest,
|
|
2284
|
+
config: {
|
|
2285
|
+
...similarRequest.config,
|
|
2286
|
+
records: [{ ...similarRequest.config.records[0], recordIds: [context.recordId] }],
|
|
2287
|
+
},
|
|
2288
|
+
};
|
|
2743
2289
|
}
|
|
2744
|
-
|
|
2745
|
-
const
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2290
|
+
isContextDependent(context, request) {
|
|
2291
|
+
const isSingleRecordRequest = request.config.records.length === 1 && request.config.records[0].recordIds.length === 1;
|
|
2292
|
+
return isSingleRecordRequest && request.config.records[0].recordIds[0] === context.recordId;
|
|
2293
|
+
}
|
|
2294
|
+
transformForSaveSimilarRequest(request) {
|
|
2295
|
+
return this.transformForSave({
|
|
2296
|
+
...request,
|
|
2297
|
+
config: {
|
|
2298
|
+
...request.config,
|
|
2299
|
+
records: [
|
|
2300
|
+
{
|
|
2301
|
+
...request.config.records[0],
|
|
2302
|
+
recordIds: ['*'],
|
|
2303
|
+
},
|
|
2304
|
+
],
|
|
2305
|
+
},
|
|
2757
2306
|
});
|
|
2758
|
-
const boxcarablePredictions = pageRequests.filter((r) => isBoxcarableRequest(r, this.requestStrategyMap));
|
|
2759
|
-
const reducedPredictions = this.page.shouldReduceAlwaysRequestsWithPredictions()
|
|
2760
|
-
? this.requestRunner.reduceRequests([...boxcarablePredictions, ...alwaysRequestEntries])
|
|
2761
|
-
: this.requestRunner.reduceRequests(boxcarablePredictions);
|
|
2762
|
-
const predictedRequestsWithLimit = (this.page.shouldExecuteAlwaysRequestByThemself()
|
|
2763
|
-
? [...alwaysRequestEntries, ...reducedPredictions]
|
|
2764
|
-
: reducedPredictions)
|
|
2765
|
-
// Sorting in order requested
|
|
2766
|
-
.sort((a, b) => a.requestMetadata.requestTime - b.requestMetadata.requestTime);
|
|
2767
|
-
this.totalRequestCount = predictedRequestsWithLimit.length;
|
|
2768
|
-
await runRequestsWithLimit(predictedRequestsWithLimit, this.requestRunner, this.options.inflightRequestLimit,
|
|
2769
|
-
// `this.repository.pageStartTime` would be the correct here,
|
|
2770
|
-
// but when doing predict+watch, it could be (set in watch)
|
|
2771
|
-
// repository.startTime is not set yet, Date.now() is a better alternative,
|
|
2772
|
-
// that is correct, and works on both cases.
|
|
2773
|
-
Date.now());
|
|
2774
2307
|
}
|
|
2775
2308
|
}
|
|
2776
2309
|
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
* JSON.stringify({a: 1, b: 2})
|
|
2782
|
-
* "{"a":1,"b":2}"
|
|
2783
|
-
* JSON.stringify({b: 2, a: 1})
|
|
2784
|
-
* "{"b":2,"a":1}"
|
|
2785
|
-
* @param data Data to be JSON-stringified.
|
|
2786
|
-
* @returns JSON.stringified value with consistent ordering of keys.
|
|
2787
|
-
*/
|
|
2788
|
-
function stableJSONStringify(node) {
|
|
2789
|
-
// This is for Date values.
|
|
2790
|
-
if (node && node.toJSON && typeof node.toJSON === 'function') {
|
|
2791
|
-
// eslint-disable-next-line no-param-reassign
|
|
2792
|
-
node = node.toJSON();
|
|
2310
|
+
const GET_RECORD_ACTIONS_ADAPTER_NAME = 'getRecordActions';
|
|
2311
|
+
function normalizeRecordIds(recordIds) {
|
|
2312
|
+
if (!isArray(recordIds)) {
|
|
2313
|
+
return [recordIds];
|
|
2793
2314
|
}
|
|
2794
|
-
|
|
2795
|
-
|
|
2315
|
+
return recordIds;
|
|
2316
|
+
}
|
|
2317
|
+
function normalizeApiNames(apiNames) {
|
|
2318
|
+
if (apiNames === undefined || apiNames === null) {
|
|
2319
|
+
return [];
|
|
2796
2320
|
}
|
|
2797
|
-
|
|
2798
|
-
|
|
2321
|
+
return isArray(apiNames) ? apiNames : [apiNames];
|
|
2322
|
+
}
|
|
2323
|
+
class GetRecordActionsRequestStrategy extends LuvioAdapterRequestStrategy {
|
|
2324
|
+
constructor() {
|
|
2325
|
+
super(...arguments);
|
|
2326
|
+
this.adapterName = GET_RECORD_ACTIONS_ADAPTER_NAME;
|
|
2327
|
+
this.adapterFactory = getRecordActionsAdapterFactory;
|
|
2799
2328
|
}
|
|
2800
|
-
|
|
2801
|
-
return
|
|
2329
|
+
buildConcreteRequest(similarRequest, context) {
|
|
2330
|
+
return {
|
|
2331
|
+
...similarRequest,
|
|
2332
|
+
config: {
|
|
2333
|
+
...similarRequest.config,
|
|
2334
|
+
recordIds: [context.recordId],
|
|
2335
|
+
},
|
|
2336
|
+
};
|
|
2802
2337
|
}
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
out += stableJSONStringify(node[i]) || 'null';
|
|
2812
|
-
}
|
|
2813
|
-
return out + ']';
|
|
2338
|
+
transformForSaveSimilarRequest(request) {
|
|
2339
|
+
return this.transformForSave({
|
|
2340
|
+
...request,
|
|
2341
|
+
config: {
|
|
2342
|
+
...request.config,
|
|
2343
|
+
recordIds: ['*'],
|
|
2344
|
+
},
|
|
2345
|
+
});
|
|
2814
2346
|
}
|
|
2815
|
-
|
|
2816
|
-
return
|
|
2347
|
+
canCombine(reqA, reqB) {
|
|
2348
|
+
return (reqA.retrievalMode === reqB.retrievalMode &&
|
|
2349
|
+
reqA.formFactor === reqB.formFactor &&
|
|
2350
|
+
(reqA.actionTypes || []).toString() === (reqB.actionTypes || []).toString() &&
|
|
2351
|
+
(reqA.sections || []).toString() === (reqB.sections || []).toString());
|
|
2817
2352
|
}
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2353
|
+
combineRequests(reqA, reqB) {
|
|
2354
|
+
const combined = { ...reqA };
|
|
2355
|
+
// let's merge the recordIds
|
|
2356
|
+
combined.recordIds = Array.from(new Set([...normalizeRecordIds(reqA.recordIds), ...normalizeRecordIds(reqB.recordIds)]));
|
|
2357
|
+
if (combined.retrievalMode === 'ALL') {
|
|
2358
|
+
const combinedSet = new Set([
|
|
2359
|
+
...normalizeApiNames(combined.apiNames),
|
|
2360
|
+
...normalizeApiNames(reqB.apiNames),
|
|
2361
|
+
]);
|
|
2362
|
+
combined.apiNames = Array.from(combinedSet);
|
|
2828
2363
|
}
|
|
2829
|
-
|
|
2364
|
+
return combined;
|
|
2830
2365
|
}
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
function deepEquals(objA, objB) {
|
|
2837
|
-
if (objA === objB)
|
|
2838
|
-
return true;
|
|
2839
|
-
if (objA instanceof Date && objB instanceof Date)
|
|
2840
|
-
return objA.getTime() === objB.getTime();
|
|
2841
|
-
// If one of them is not an object, they are not deeply equal
|
|
2842
|
-
if (!isObject(objA) || !isObject(objB))
|
|
2843
|
-
return false;
|
|
2844
|
-
// Filter out keys set as undefined, we can compare undefined as equals.
|
|
2845
|
-
const keysA = keys(objA).filter((key) => objA[key] !== undefined);
|
|
2846
|
-
const keysB = keys(objB).filter((key) => objB[key] !== undefined);
|
|
2847
|
-
// If the objects do not have the same set of keys, they are not deeply equal
|
|
2848
|
-
if (keysA.length !== keysB.length)
|
|
2849
|
-
return false;
|
|
2850
|
-
for (const key of keysA) {
|
|
2851
|
-
const valA = objA[key];
|
|
2852
|
-
const valB = objB[key];
|
|
2853
|
-
const areObjects = isObject(valA) && isObject(valB);
|
|
2854
|
-
// If both values are objects, recursively compare them
|
|
2855
|
-
if (areObjects && !deepEquals(valA, valB))
|
|
2856
|
-
return false;
|
|
2857
|
-
// If only one value is an object or if the values are not strictly equal, they are not deeply equal
|
|
2858
|
-
if (!areObjects && valA !== valB)
|
|
2859
|
-
return false;
|
|
2366
|
+
isContextDependent(context, request) {
|
|
2367
|
+
return (request.config.recordIds &&
|
|
2368
|
+
(context.recordId === request.config.recordIds || // some may set this as string instead of array
|
|
2369
|
+
(request.config.recordIds.length === 1 &&
|
|
2370
|
+
request.config.recordIds[0] === context.recordId)));
|
|
2860
2371
|
}
|
|
2861
|
-
return true;
|
|
2862
2372
|
}
|
|
2863
2373
|
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2374
|
+
const GET_OBJECT_INFO_BATCH_ADAPTER_NAME = 'getObjectInfos';
|
|
2375
|
+
/**
|
|
2376
|
+
* Returns true if A is a superset of B
|
|
2377
|
+
* @param a
|
|
2378
|
+
* @param b
|
|
2379
|
+
*/
|
|
2380
|
+
function isSuperSet(a, b) {
|
|
2381
|
+
return b.every((oan) => a.has(oan));
|
|
2382
|
+
}
|
|
2383
|
+
class GetObjectInfosRequestStrategy extends LuvioAdapterRequestStrategy {
|
|
2384
|
+
constructor() {
|
|
2385
|
+
super(...arguments);
|
|
2386
|
+
this.adapterName = GET_OBJECT_INFO_BATCH_ADAPTER_NAME;
|
|
2387
|
+
this.adapterFactory = getObjectInfosAdapterFactory;
|
|
2870
2388
|
}
|
|
2871
|
-
|
|
2872
|
-
|
|
2389
|
+
buildConcreteRequest(similarRequest) {
|
|
2390
|
+
return similarRequest;
|
|
2873
2391
|
}
|
|
2874
|
-
|
|
2875
|
-
|
|
2392
|
+
transformForSave(request) {
|
|
2393
|
+
return {
|
|
2394
|
+
...request,
|
|
2395
|
+
config: {
|
|
2396
|
+
...request.config,
|
|
2397
|
+
// (!): if we are saving this request is because the adapter already verified is valid.
|
|
2398
|
+
objectApiNames: coerceObjectIdArray(request.config.objectApiNames),
|
|
2399
|
+
},
|
|
2400
|
+
};
|
|
2876
2401
|
}
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2402
|
+
/**
|
|
2403
|
+
* Reduces the given GetObjectInfosRequest requests by eliminating those for which config.objectApiNames
|
|
2404
|
+
* is a subset of another GetObjectInfosRequest.
|
|
2405
|
+
*
|
|
2406
|
+
* @param unfilteredRequests - Array of unfiltered requests
|
|
2407
|
+
* @returns RequestEntry<GetObjectInfosRequest>[] - Array of reduced requests
|
|
2408
|
+
*/
|
|
2409
|
+
reduce(unfilteredRequests) {
|
|
2410
|
+
// Filter and sort requests by the length of objectApiNames in ascending order.
|
|
2411
|
+
// This ensures a superset of request (i) can only be found in a request (j) such that i < j.
|
|
2412
|
+
const objectInfosRequests = this.filterRequests(unfilteredRequests).sort((a, b) => a.request.config.objectApiNames.length - b.request.config.objectApiNames.length);
|
|
2413
|
+
// Convert request configurations to sets for easier comparison, avoiding a new set construction each iteration.
|
|
2414
|
+
const requestConfigAsSet = objectInfosRequests.map((r) => new Set(r.request.config.objectApiNames));
|
|
2415
|
+
const reducedRequests = [];
|
|
2416
|
+
// Iterate over each request to determine if it is a subset of others
|
|
2417
|
+
for (let i = 0, n = objectInfosRequests.length; i < n; i++) {
|
|
2418
|
+
const current = objectInfosRequests[i];
|
|
2419
|
+
const { request: { config: currentRequestConfig }, requestMetadata: currentRequestMetadata, } = current;
|
|
2420
|
+
let isCurrentSubsetOfOthers = false;
|
|
2421
|
+
// Check if the current request is a subset of any subsequent requests
|
|
2422
|
+
for (let j = i + 1; j < n; j++) {
|
|
2423
|
+
const possibleSuperset = objectInfosRequests[j];
|
|
2424
|
+
if (isSuperSet(requestConfigAsSet[j], currentRequestConfig.objectApiNames)) {
|
|
2425
|
+
isCurrentSubsetOfOthers = true;
|
|
2426
|
+
if (currentRequestMetadata.requestTime <
|
|
2427
|
+
possibleSuperset.requestMetadata.requestTime) {
|
|
2428
|
+
possibleSuperset.requestMetadata.requestTime =
|
|
2429
|
+
currentRequestMetadata.requestTime;
|
|
2430
|
+
}
|
|
2893
2431
|
}
|
|
2894
|
-
});
|
|
2895
|
-
const { modifyBeforeSaveHook } = this.options;
|
|
2896
|
-
if (modifyBeforeSaveHook !== undefined) {
|
|
2897
|
-
page.requests = modifyBeforeSaveHook(page.requests);
|
|
2898
2432
|
}
|
|
2899
|
-
|
|
2433
|
+
if (!isCurrentSubsetOfOthers) {
|
|
2434
|
+
reducedRequests.push(current);
|
|
2435
|
+
}
|
|
2900
2436
|
}
|
|
2901
|
-
|
|
2902
|
-
await Promise.all(setPromises);
|
|
2437
|
+
return reducedRequests;
|
|
2903
2438
|
}
|
|
2904
|
-
|
|
2905
|
-
|
|
2439
|
+
}
|
|
2440
|
+
|
|
2441
|
+
const GET_OBJECT_INFO_ADAPTER_NAME = 'getObjectInfo';
|
|
2442
|
+
class GetObjectInfoRequestStrategy extends LuvioAdapterRequestStrategy {
|
|
2443
|
+
constructor() {
|
|
2444
|
+
super(...arguments);
|
|
2445
|
+
this.adapterName = GET_OBJECT_INFO_ADAPTER_NAME;
|
|
2446
|
+
this.adapterFactory = getObjectInfoAdapterFactory;
|
|
2906
2447
|
}
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2448
|
+
buildConcreteRequest(similarRequest, context) {
|
|
2449
|
+
return {
|
|
2450
|
+
...similarRequest,
|
|
2451
|
+
config: {
|
|
2452
|
+
...similarRequest.config,
|
|
2453
|
+
objectApiName: context.objectApiName,
|
|
2454
|
+
},
|
|
2455
|
+
};
|
|
2915
2456
|
}
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2457
|
+
transformForSave(request) {
|
|
2458
|
+
return {
|
|
2459
|
+
...request,
|
|
2460
|
+
config: {
|
|
2461
|
+
...request.config,
|
|
2462
|
+
// (!): if we are saving this request is because the adapter already verified is valid.
|
|
2463
|
+
objectApiName: coerceObjectId(request.config.objectApiName),
|
|
2464
|
+
},
|
|
2465
|
+
};
|
|
2919
2466
|
}
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2467
|
+
isContextDependent(context, request) {
|
|
2468
|
+
return (request.config.objectApiName !== undefined &&
|
|
2469
|
+
context.objectApiName === request.config.objectApiName);
|
|
2470
|
+
}
|
|
2471
|
+
/**
|
|
2472
|
+
* This method returns GetObjectInfoRequest[] that won't be part of a batch (getObjectInfos) request.
|
|
2473
|
+
*
|
|
2474
|
+
* @param unfilteredRequests all prediction requests
|
|
2475
|
+
* @returns
|
|
2476
|
+
*/
|
|
2477
|
+
reduce(unfilteredRequests) {
|
|
2478
|
+
const objectApiNamesInBatchRequest = unfilteredRequests.filter((entry) => entry.request.adapterName === GET_OBJECT_INFO_BATCH_ADAPTER_NAME).reduce((acc, { request }) => {
|
|
2479
|
+
request.config.objectApiNames.forEach((apiName) => acc.add(apiName));
|
|
2480
|
+
return acc;
|
|
2481
|
+
}, new Set());
|
|
2482
|
+
const singleRequests = this.filterRequests(unfilteredRequests);
|
|
2483
|
+
return singleRequests.filter((singleEntry) => {
|
|
2484
|
+
return !objectApiNamesInBatchRequest.has(singleEntry.request.config.objectApiName);
|
|
2485
|
+
});
|
|
2926
2486
|
}
|
|
2927
2487
|
}
|
|
2928
2488
|
|
|
2929
|
-
|
|
2489
|
+
const GET_RELATED_LISTS_ACTIONS_ADAPTER_NAME = 'getRelatedListsActions';
|
|
2490
|
+
function isReduceAbleRelatedListConfig(config) {
|
|
2491
|
+
return config.relatedListsActionParameters.every((rlReq) => {
|
|
2492
|
+
return rlReq.relatedListId !== undefined && keys(rlReq).length === 1;
|
|
2493
|
+
});
|
|
2494
|
+
}
|
|
2495
|
+
class GetRelatedListsActionsRequestStrategy extends LuvioAdapterRequestStrategy {
|
|
2930
2496
|
constructor() {
|
|
2931
|
-
|
|
2932
|
-
this.
|
|
2497
|
+
super(...arguments);
|
|
2498
|
+
this.adapterName = GET_RELATED_LISTS_ACTIONS_ADAPTER_NAME;
|
|
2499
|
+
this.adapterFactory = getRelatedListsActionsAdapterFactory;
|
|
2933
2500
|
}
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2501
|
+
buildConcreteRequest(similarRequest, context) {
|
|
2502
|
+
return {
|
|
2503
|
+
...similarRequest,
|
|
2504
|
+
config: {
|
|
2505
|
+
...similarRequest.config,
|
|
2506
|
+
recordIds: [context.recordId],
|
|
2507
|
+
},
|
|
2508
|
+
};
|
|
2937
2509
|
}
|
|
2938
|
-
|
|
2939
|
-
return this.
|
|
2510
|
+
transformForSaveSimilarRequest(request) {
|
|
2511
|
+
return this.transformForSave({
|
|
2512
|
+
...request,
|
|
2513
|
+
config: {
|
|
2514
|
+
...request.config,
|
|
2515
|
+
recordIds: ['*'],
|
|
2516
|
+
},
|
|
2517
|
+
});
|
|
2940
2518
|
}
|
|
2941
|
-
|
|
2942
|
-
const
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
return
|
|
2519
|
+
isContextDependent(context, request) {
|
|
2520
|
+
const isForContext = request.config.recordIds &&
|
|
2521
|
+
(context.recordId === request.config.recordIds || // some may set this as string instead of array
|
|
2522
|
+
(request.config.recordIds.length === 1 &&
|
|
2523
|
+
request.config.recordIds[0] === context.recordId));
|
|
2524
|
+
return isForContext && isReduceAbleRelatedListConfig(request.config);
|
|
2947
2525
|
}
|
|
2948
|
-
}
|
|
2949
|
-
|
|
2950
|
-
class RequestStrategy {
|
|
2951
2526
|
/**
|
|
2952
|
-
*
|
|
2953
|
-
*
|
|
2954
|
-
* e.g. If the request is for a record, we move all fields in the fields array
|
|
2955
|
-
* into the optionalFields array
|
|
2527
|
+
* Can only reduce two requests when they have the same recordId, and
|
|
2528
|
+
* the individual relatedListAction config only have relatedListId.
|
|
2956
2529
|
*
|
|
2957
|
-
* @param
|
|
2958
|
-
* @
|
|
2530
|
+
* @param reqA
|
|
2531
|
+
* @param reqB
|
|
2532
|
+
* @returns boolean
|
|
2959
2533
|
*/
|
|
2960
|
-
|
|
2961
|
-
|
|
2534
|
+
canCombine(reqA, reqB) {
|
|
2535
|
+
const [recordIdA, recordIdB] = [reqA.recordIds, reqB.recordIds].map((recordIds) => {
|
|
2536
|
+
return isArray(recordIds)
|
|
2537
|
+
? recordIds.length === 1
|
|
2538
|
+
? recordIds[0]
|
|
2539
|
+
: null
|
|
2540
|
+
: recordIds;
|
|
2541
|
+
});
|
|
2542
|
+
return (recordIdA === recordIdB &&
|
|
2543
|
+
recordIdA !== null &&
|
|
2544
|
+
isReduceAbleRelatedListConfig(reqA) &&
|
|
2545
|
+
isReduceAbleRelatedListConfig(reqB));
|
|
2962
2546
|
}
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2547
|
+
combineRequests(reqA, reqB) {
|
|
2548
|
+
const relatedListsIncluded = new Set();
|
|
2549
|
+
[reqA, reqB].forEach(({ relatedListsActionParameters }) => {
|
|
2550
|
+
relatedListsActionParameters.forEach(({ relatedListId }) => relatedListsIncluded.add(relatedListId));
|
|
2551
|
+
});
|
|
2552
|
+
return {
|
|
2553
|
+
recordIds: reqA.recordIds,
|
|
2554
|
+
relatedListsActionParameters: from(relatedListsIncluded).map((relatedListId) => ({
|
|
2555
|
+
relatedListId,
|
|
2556
|
+
})),
|
|
2557
|
+
};
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
|
|
2561
|
+
const GET_RELATED_LIST_INFO_BATCH_ADAPTER_NAME = 'getRelatedListInfoBatch';
|
|
2562
|
+
class GetRelatedListInfoBatchRequestStrategy extends LuvioAdapterRequestStrategy {
|
|
2563
|
+
constructor() {
|
|
2564
|
+
super(...arguments);
|
|
2565
|
+
this.adapterName = GET_RELATED_LIST_INFO_BATCH_ADAPTER_NAME;
|
|
2566
|
+
this.adapterFactory = getRelatedListInfoBatchAdapterFactory;
|
|
2567
|
+
}
|
|
2568
|
+
buildConcreteRequest(similarRequest, _context) {
|
|
2569
|
+
return similarRequest;
|
|
2970
2570
|
}
|
|
2971
2571
|
/**
|
|
2972
|
-
*
|
|
2973
|
-
*
|
|
2974
|
-
* @param unfilteredRequests array of requests to filter
|
|
2975
|
-
* @returns
|
|
2572
|
+
* @override
|
|
2976
2573
|
*/
|
|
2977
|
-
|
|
2978
|
-
return
|
|
2574
|
+
transformForSave(request) {
|
|
2575
|
+
return {
|
|
2576
|
+
...request,
|
|
2577
|
+
config: {
|
|
2578
|
+
...request.config,
|
|
2579
|
+
parentObjectApiName: coerceObjectId(request.config.parentObjectApiName),
|
|
2580
|
+
},
|
|
2581
|
+
};
|
|
2582
|
+
}
|
|
2583
|
+
canCombine(reqA, reqB) {
|
|
2584
|
+
return reqA.parentObjectApiName === reqB.parentObjectApiName;
|
|
2585
|
+
}
|
|
2586
|
+
combineRequests(reqA, reqB) {
|
|
2587
|
+
const combined = { ...reqA };
|
|
2588
|
+
combined.relatedListNames = Array.from(new Set([...reqA.relatedListNames, ...reqB.relatedListNames]));
|
|
2589
|
+
return combined;
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
|
|
2593
|
+
const GET_RELATED_LIST_INFO_ADAPTER_NAME = 'getRelatedListInfo';
|
|
2594
|
+
class GetRelatedListInfoRequestStrategy extends LuvioAdapterRequestStrategy {
|
|
2595
|
+
constructor() {
|
|
2596
|
+
super(...arguments);
|
|
2597
|
+
this.adapterName = GET_RELATED_LIST_INFO_ADAPTER_NAME;
|
|
2598
|
+
this.adapterFactory = getRelatedListInfoAdapterFactory;
|
|
2979
2599
|
}
|
|
2980
2600
|
/**
|
|
2981
|
-
*
|
|
2982
|
-
* of canCombine and combineRequests.
|
|
2983
|
-
* @param unfilteredRequests array of requests to filter
|
|
2984
|
-
* @returns
|
|
2601
|
+
* @override
|
|
2985
2602
|
*/
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
if (!visitedRequests.has(currentRequest)) {
|
|
2993
|
-
const combinedRequest = { ...currentRequest };
|
|
2994
|
-
for (let j = i + 1; j < n; j++) {
|
|
2995
|
-
const hasNotBeenVisited = !visitedRequests.has(requests[j]);
|
|
2996
|
-
const canCombineConfigs = this.canCombine(combinedRequest.request.config, requests[j].request.config);
|
|
2997
|
-
if (hasNotBeenVisited && canCombineConfigs) {
|
|
2998
|
-
combinedRequest.request.config = this.combineRequests(combinedRequest.request.config, requests[j].request.config);
|
|
2999
|
-
if (combinedRequest.requestMetadata.requestTime >
|
|
3000
|
-
requests[j].requestMetadata.requestTime) {
|
|
3001
|
-
// This logic is debateable - Currently this always assigns the lowest requestTime to a reduced request.
|
|
3002
|
-
combinedRequest.requestMetadata.requestTime =
|
|
3003
|
-
requests[j].requestMetadata.requestTime;
|
|
3004
|
-
}
|
|
3005
|
-
visitedRequests.add(requests[j]);
|
|
3006
|
-
}
|
|
3007
|
-
}
|
|
3008
|
-
reducedRequests.push(combinedRequest);
|
|
3009
|
-
visitedRequests.add(currentRequest);
|
|
3010
|
-
}
|
|
3011
|
-
}
|
|
3012
|
-
return reducedRequests;
|
|
2603
|
+
isContextDependent(context, request) {
|
|
2604
|
+
// we have a higher degree of confidence of being able to use a similar request when
|
|
2605
|
+
// optional config is not set AND the object type of the page context is the same as the
|
|
2606
|
+
// object type related list is for
|
|
2607
|
+
return (context.objectApiName === request.config.parentObjectApiName &&
|
|
2608
|
+
this.isRequiredOnlyConfig(request.config));
|
|
3013
2609
|
}
|
|
3014
2610
|
/**
|
|
3015
|
-
*
|
|
3016
|
-
*
|
|
3017
|
-
* By default, requests are not combinable.
|
|
3018
|
-
* @param reqA config of request A
|
|
3019
|
-
* @param reqB config of request B
|
|
3020
|
-
* @returns
|
|
2611
|
+
* @override
|
|
3021
2612
|
*/
|
|
3022
|
-
|
|
3023
|
-
return
|
|
2613
|
+
buildConcreteRequest(similarRequest, _context) {
|
|
2614
|
+
return similarRequest;
|
|
3024
2615
|
}
|
|
3025
2616
|
/**
|
|
3026
|
-
*
|
|
3027
|
-
*
|
|
3028
|
-
* @param reqA config of request A
|
|
3029
|
-
* @param reqB config of request B
|
|
3030
|
-
* @returns
|
|
2617
|
+
* @override
|
|
3031
2618
|
*/
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
2619
|
+
transformForSave(request) {
|
|
2620
|
+
return {
|
|
2621
|
+
...request,
|
|
2622
|
+
config: {
|
|
2623
|
+
...request.config,
|
|
2624
|
+
parentObjectApiName: coerceObjectId(request.config.parentObjectApiName),
|
|
2625
|
+
},
|
|
2626
|
+
};
|
|
3038
2627
|
}
|
|
3039
2628
|
/**
|
|
3040
|
-
*
|
|
2629
|
+
* For performance reasons (fear of over-fetching), we only want to predict single requests which either are not
|
|
2630
|
+
* part of a batch request OR specify optional parameters (thus requiring data differing from that of a batch
|
|
2631
|
+
* request).
|
|
3041
2632
|
*
|
|
3042
|
-
*
|
|
3043
|
-
*
|
|
3044
|
-
*
|
|
2633
|
+
* ADG currently handles the batching of getRelatedListInfo -> getRelatedListInfoBatch
|
|
2634
|
+
* https://gitcore.soma.salesforce.com/core-2206/core-public/blob/p4/main/core/ui-laf-components/modules/laf/batchingPortable/reducers/RelatedListInfoBatchReducer.js
|
|
2635
|
+
*
|
|
2636
|
+
* @param unfilteredRequests
|
|
2637
|
+
* @returns GetRelatedListInfoRequest[]
|
|
2638
|
+
* @override
|
|
3045
2639
|
*/
|
|
3046
|
-
|
|
3047
|
-
|
|
2640
|
+
reduce(unfilteredRequests) {
|
|
2641
|
+
// using batch versions of request, map keys of parent record to values of related lists
|
|
2642
|
+
const batchRequests = unfilteredRequests.filter((entry) => entry.request.adapterName === GET_RELATED_LIST_INFO_BATCH_ADAPTER_NAME).reduce((result, entry) => {
|
|
2643
|
+
// pull batch request parameters that correspond to single request parameters
|
|
2644
|
+
const { parentObjectApiName, relatedListNames, recordTypeId } = entry.request.config;
|
|
2645
|
+
// key based off of parent object and its type
|
|
2646
|
+
const key = `${parentObjectApiName}_${recordTypeId}`;
|
|
2647
|
+
// make sure an entry exists for the record
|
|
2648
|
+
const relatedListIds = result.get(key) || new Set();
|
|
2649
|
+
result.set(key, relatedListIds);
|
|
2650
|
+
// add each related list to values for the record
|
|
2651
|
+
relatedListNames.forEach((list) => relatedListIds.add(list));
|
|
2652
|
+
return result;
|
|
2653
|
+
}, new Map());
|
|
2654
|
+
// return only the single requests that will NOT be covered by a batch request
|
|
2655
|
+
return this.filterRequests(unfilteredRequests).filter((entry) => {
|
|
2656
|
+
const config = entry.request.config;
|
|
2657
|
+
if (!this.isRequiredOnlyConfig(config, true)) {
|
|
2658
|
+
// requested data is customized beyond default; batch may not cover, so keep single request
|
|
2659
|
+
return true;
|
|
2660
|
+
}
|
|
2661
|
+
// key based off of parent object and its type
|
|
2662
|
+
const key = `${config.parentObjectApiName}_${config.recordTypeId}`;
|
|
2663
|
+
// keep only the lists that are not batched
|
|
2664
|
+
const batchLists = batchRequests.get(key);
|
|
2665
|
+
return !(batchLists && batchLists.has(config.relatedListId));
|
|
2666
|
+
});
|
|
3048
2667
|
}
|
|
3049
2668
|
/**
|
|
3050
|
-
*
|
|
2669
|
+
* Return true if the request config doesn't specify values for any of the optional parameters.
|
|
3051
2670
|
*
|
|
3052
|
-
* @
|
|
2671
|
+
* @param config
|
|
2672
|
+
* @param ignoreRecordType
|
|
3053
2673
|
*/
|
|
3054
|
-
|
|
3055
|
-
return
|
|
2674
|
+
isRequiredOnlyConfig(config, ignoreRecordType = false) {
|
|
2675
|
+
return ((ignoreRecordType || config.recordTypeId === undefined) &&
|
|
2676
|
+
(config.fields || []).length === 0 &&
|
|
2677
|
+
(config.optionalFields || []).length === 0 &&
|
|
2678
|
+
config.restrictColumnsToLayout !== false // default is true
|
|
2679
|
+
);
|
|
3056
2680
|
}
|
|
3057
2681
|
}
|
|
3058
2682
|
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
2683
|
+
// Copy-pasted from adapter-utils. This util should be extracted from generated code and imported in prefetch repository.
|
|
2684
|
+
/**
|
|
2685
|
+
* A deterministic JSON stringify implementation. Heavily adapted from https://github.com/epoberezkin/fast-json-stable-stringify.
|
|
2686
|
+
* This is needed because insertion order for JSON.stringify(object) affects output:
|
|
2687
|
+
* JSON.stringify({a: 1, b: 2})
|
|
2688
|
+
* "{"a":1,"b":2}"
|
|
2689
|
+
* JSON.stringify({b: 2, a: 1})
|
|
2690
|
+
* "{"b":2,"a":1}"
|
|
2691
|
+
* @param data Data to be JSON-stringified.
|
|
2692
|
+
* @returns JSON.stringified value with consistent ordering of keys.
|
|
2693
|
+
*/
|
|
2694
|
+
function stableJSONStringify$1(node) {
|
|
2695
|
+
// This is for Date values.
|
|
2696
|
+
if (node && node.toJSON && typeof node.toJSON === 'function') {
|
|
2697
|
+
// eslint-disable-next-line no-param-reassign
|
|
2698
|
+
node = node.toJSON();
|
|
2699
|
+
}
|
|
2700
|
+
if (node === undefined) {
|
|
2701
|
+
return;
|
|
2702
|
+
}
|
|
2703
|
+
if (typeof node === 'number') {
|
|
2704
|
+
return isFinite(node) ? '' + node : 'null';
|
|
2705
|
+
}
|
|
2706
|
+
if (typeof node !== 'object') {
|
|
2707
|
+
return stringify(node);
|
|
2708
|
+
}
|
|
2709
|
+
let i;
|
|
2710
|
+
let out;
|
|
2711
|
+
if (isArray(node)) {
|
|
2712
|
+
out = '[';
|
|
2713
|
+
for (i = 0; i < node.length; i++) {
|
|
2714
|
+
if (i) {
|
|
2715
|
+
out += ',';
|
|
2716
|
+
}
|
|
2717
|
+
out += stableJSONStringify$1(node[i]) || 'null';
|
|
2718
|
+
}
|
|
2719
|
+
return out + ']';
|
|
2720
|
+
}
|
|
2721
|
+
if (node === null) {
|
|
2722
|
+
return 'null';
|
|
2723
|
+
}
|
|
2724
|
+
const keys$1 = keys(node).sort();
|
|
2725
|
+
out = '';
|
|
2726
|
+
for (i = 0; i < keys$1.length; i++) {
|
|
2727
|
+
const key = keys$1[i];
|
|
2728
|
+
const value = stableJSONStringify$1(node[key]);
|
|
2729
|
+
if (!value) {
|
|
2730
|
+
continue;
|
|
2731
|
+
}
|
|
2732
|
+
if (out) {
|
|
2733
|
+
out += ',';
|
|
2734
|
+
}
|
|
2735
|
+
out += stringify(key) + ':' + value;
|
|
3069
2736
|
}
|
|
2737
|
+
return '{' + out + '}';
|
|
3070
2738
|
}
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
// Taken from https://sourcegraph.soma.salesforce.com/perforce.soma.salesforce.com/app/main/core/-/blob/ui-global-components/components/one/one/oneController.js?L75
|
|
3074
|
-
// In theory this should not be here, but for now, we don't have an alternative,
|
|
3075
|
-
// given these are started before o11y tells PDL the EPT windows ended (even before the timestamp sent by o11y).
|
|
3076
|
-
const onePreloads = new Set([
|
|
3077
|
-
'markup://emailui:formattedEmailWrapper',
|
|
3078
|
-
'markup://emailui:outputEmail',
|
|
3079
|
-
'markup://flexipage:baseRecordHomeTemplateDesktop',
|
|
3080
|
-
'markup://force:actionWindowLink',
|
|
3081
|
-
'markup://force:inlineEditCell',
|
|
3082
|
-
'markup://force:inputField',
|
|
3083
|
-
'markup://force:recordPreviewItem',
|
|
3084
|
-
'markup://force:relatedListDesktop',
|
|
3085
|
-
'markup://force:relatedListQuickLinksContainer',
|
|
3086
|
-
'markup://lst:relatedListQuickLinksContainer',
|
|
3087
|
-
'markup://lst:secondDegreeRelatedListSingleContainer',
|
|
3088
|
-
'markup://lst:bundle_act_coreListViewManagerDesktop',
|
|
3089
|
-
'markup://lst:bundle_act_coreListViewManagerDesktop_generatedTemplates',
|
|
3090
|
-
'markup://lst:baseFilterPanel',
|
|
3091
|
-
'markup://lst:chartPanel',
|
|
3092
|
-
'markup://force:socialPhotoWrapper',
|
|
3093
|
-
'markup://forceContent:contentVersionsEditWizard',
|
|
3094
|
-
'markup://forceContent:outputTitle',
|
|
3095
|
-
'markup://forceContent:virtualRelatedListStencil',
|
|
3096
|
-
'markup://forceSearch:resultsFilters',
|
|
3097
|
-
'markup://interop:unstable_uiRecordApi',
|
|
3098
|
-
'markup://lightning:formattedPhone',
|
|
3099
|
-
'markup://notes:contentNoteRelatedListStencil',
|
|
3100
|
-
'markup://one:alohaPage',
|
|
3101
|
-
'markup://one:consoleObjectHome',
|
|
3102
|
-
'markup://one:recordActionWrapper',
|
|
3103
|
-
'markup://records:lwcDetailPanel',
|
|
3104
|
-
'markup://records:lwcHighlightsPanel',
|
|
3105
|
-
'markup://records:recordLayoutInputDateTime',
|
|
3106
|
-
'markup://records:recordLayoutInputLocation',
|
|
3107
|
-
'markup://records:recordLayoutItem',
|
|
3108
|
-
'markup://records:recordLayoutLookup',
|
|
3109
|
-
'markup://records:recordLayoutRichText',
|
|
3110
|
-
'markup://records:recordLayoutRow',
|
|
3111
|
-
'markup://records:recordLayoutSection',
|
|
3112
|
-
'markup://records:recordLayoutTextArea',
|
|
3113
|
-
'markup://records:recordPicklist',
|
|
3114
|
-
'markup://sfa:outputNameWithHierarchyIcon',
|
|
3115
|
-
'markup://runtime_platform_actions:actionHeadlessFormCancel',
|
|
3116
|
-
'markup://runtime_platform_actions:actionHeadlessFormSave',
|
|
3117
|
-
'markup://runtime_platform_actions:actionHeadlessFormSaveAndNew',
|
|
3118
|
-
'markup://lightning:iconSvgTemplatesCustom',
|
|
3119
|
-
'markup://lightning:iconSvgTemplatesDocType',
|
|
3120
|
-
'markup://record_flexipage:recordHomeFlexipageUtil',
|
|
3121
|
-
'markup://record_flexipage:recordFieldInstancesHandlers',
|
|
3122
|
-
'markup://force:outputCustomLinkUrl',
|
|
3123
|
-
'markup://force:quickActionRunnable',
|
|
3124
|
-
'markup://force:inputURL',
|
|
3125
|
-
'markup://force:inputMultiPicklist',
|
|
3126
|
-
'markup://runtime_sales_activities:activityPanel',
|
|
3127
|
-
'markup://support:outputLookupWithPreviewForSubject',
|
|
3128
|
-
'markup://runtime_sales_activities:activitySubjectListView',
|
|
3129
|
-
'markup://support:outputCaseSubjectField',
|
|
3130
|
-
'markup://sfa:inputOpportunityAmount',
|
|
3131
|
-
'markup://forceChatter:contentFileSize',
|
|
3132
|
-
'markup://flexipage:column2',
|
|
3133
|
-
'markup://sfa:outputNameWithHierarchyIconAccount',
|
|
3134
|
-
'markup://emailui:formattedEmailAccount',
|
|
3135
|
-
'markup://emailui:formattedEmailContact',
|
|
3136
|
-
'markup://emailui:formattedEmailLead',
|
|
3137
|
-
'markup://e.aura:serverActionError',
|
|
3138
|
-
'markup://records:recordType',
|
|
3139
|
-
'markup://flexipage:recordHomeWithSubheaderTemplateDesktop2',
|
|
3140
|
-
'markup://force:customLinkUrl',
|
|
3141
|
-
'markup://sfa:outputOpportunityAmount',
|
|
3142
|
-
'markup://emailui:formattedEmailCase',
|
|
3143
|
-
'markup://runtime_sales_activities:activitySubject',
|
|
3144
|
-
'markup://lightning:quickActionAPI',
|
|
3145
|
-
'markup://force:listViewManagerGridWrapText',
|
|
3146
|
-
'markup://flexipage:recordHomeSimpleViewTemplate2',
|
|
3147
|
-
'markup://flexipage:accordion2',
|
|
3148
|
-
'markup://flexipage:accordionSection2',
|
|
3149
|
-
'markup://flexipage:field',
|
|
3150
|
-
'markup://runtime_iag_core:onboardingManager',
|
|
3151
|
-
'markup://records:entityLabel',
|
|
3152
|
-
'markup://records:highlightsHeaderRightContent',
|
|
3153
|
-
'markup://records:formattedRichText',
|
|
3154
|
-
'markup://force:socialRecordAvatarWrapper',
|
|
3155
|
-
'markup://runtime_pipeline_inspector:pipelineInspectorHome',
|
|
3156
|
-
'markup://sfa:inspectionDesktopObjectHome',
|
|
3157
|
-
'markup://records:outputPhone',
|
|
3158
|
-
]);
|
|
3159
|
-
function canPreloadDefinition(def) {
|
|
3160
|
-
return (def.startsWith('markup://') &&
|
|
3161
|
-
!(
|
|
3162
|
-
// some "virtual" components from flexipages are with `__` in the name, eg: design templates.
|
|
3163
|
-
// Not filtering out them will not cause errors, but will cause a server request that returns with error.
|
|
3164
|
-
(def.includes('__') ||
|
|
3165
|
-
// any generated template
|
|
3166
|
-
def.includes('forceGenerated') ||
|
|
3167
|
-
// part of onePreload
|
|
3168
|
-
def.includes('one:onePreloads') ||
|
|
3169
|
-
onePreloads.has(def))));
|
|
2739
|
+
function isObject(obj) {
|
|
2740
|
+
return obj !== null && typeof obj === 'object';
|
|
3170
2741
|
}
|
|
3171
|
-
function
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
2742
|
+
function deepEquals(objA, objB) {
|
|
2743
|
+
if (objA === objB)
|
|
2744
|
+
return true;
|
|
2745
|
+
if (objA instanceof Date && objB instanceof Date)
|
|
2746
|
+
return objA.getTime() === objB.getTime();
|
|
2747
|
+
// If one of them is not an object, they are not deeply equal
|
|
2748
|
+
if (!isObject(objA) || !isObject(objB))
|
|
2749
|
+
return false;
|
|
2750
|
+
// Filter out keys set as undefined, we can compare undefined as equals.
|
|
2751
|
+
const keysA = keys(objA).filter((key) => objA[key] !== undefined);
|
|
2752
|
+
const keysB = keys(objB).filter((key) => objB[key] !== undefined);
|
|
2753
|
+
// If the objects do not have the same set of keys, they are not deeply equal
|
|
2754
|
+
if (keysA.length !== keysB.length)
|
|
2755
|
+
return false;
|
|
2756
|
+
for (const key of keysA) {
|
|
2757
|
+
const valA = objA[key];
|
|
2758
|
+
const valB = objB[key];
|
|
2759
|
+
const areObjects = isObject(valA) && isObject(valB);
|
|
2760
|
+
// If both values are objects, recursively compare them
|
|
2761
|
+
if (areObjects && !deepEquals(valA, valB))
|
|
2762
|
+
return false;
|
|
2763
|
+
// If only one value is an object or if the values are not strictly equal, they are not deeply equal
|
|
2764
|
+
if (!areObjects && valA !== valB)
|
|
2765
|
+
return false;
|
|
2766
|
+
}
|
|
2767
|
+
return true;
|
|
2768
|
+
}
|
|
2769
|
+
|
|
2770
|
+
class PrefetchRepository {
|
|
2771
|
+
constructor(storage, options = {}) {
|
|
2772
|
+
this.storage = storage;
|
|
2773
|
+
this.options = options;
|
|
2774
|
+
this.requestBuffer = new Map();
|
|
2775
|
+
this.pageStartTime = Date.now();
|
|
2776
|
+
}
|
|
2777
|
+
clearRequestBuffer() {
|
|
2778
|
+
this.requestBuffer.clear();
|
|
2779
|
+
}
|
|
2780
|
+
markPageStart() {
|
|
2781
|
+
this.pageStartTime = Date.now();
|
|
2782
|
+
}
|
|
2783
|
+
async flushRequestsToStorage() {
|
|
2784
|
+
const setPromises = [];
|
|
2785
|
+
for (const [id, batch] of this.requestBuffer) {
|
|
2786
|
+
const page = { id, requests: [] };
|
|
2787
|
+
batch.forEach(({ request, requestTime }) => {
|
|
2788
|
+
const existingRequestEntry = page.requests.find(({ request: storedRequest }) => deepEquals(storedRequest, request));
|
|
2789
|
+
if (existingRequestEntry === undefined) {
|
|
2790
|
+
page.requests.push({
|
|
2791
|
+
request,
|
|
2792
|
+
requestMetadata: {
|
|
2793
|
+
requestTime,
|
|
2794
|
+
},
|
|
2795
|
+
});
|
|
2796
|
+
}
|
|
2797
|
+
else if (requestTime < existingRequestEntry.requestMetadata.requestTime) {
|
|
2798
|
+
existingRequestEntry.requestMetadata.requestTime = requestTime;
|
|
2799
|
+
}
|
|
2800
|
+
});
|
|
2801
|
+
const { modifyBeforeSaveHook } = this.options;
|
|
2802
|
+
if (modifyBeforeSaveHook !== undefined) {
|
|
2803
|
+
page.requests = modifyBeforeSaveHook(page.requests);
|
|
2804
|
+
}
|
|
2805
|
+
setPromises.push(this.storage.set(id, page));
|
|
3180
2806
|
}
|
|
2807
|
+
this.clearRequestBuffer();
|
|
2808
|
+
await Promise.all(setPromises);
|
|
3181
2809
|
}
|
|
3182
|
-
|
|
3183
|
-
|
|
2810
|
+
getKeyId(key) {
|
|
2811
|
+
return stableJSONStringify$1(key);
|
|
2812
|
+
}
|
|
2813
|
+
saveRequest(key, request) {
|
|
2814
|
+
const identifier = this.getKeyId(key);
|
|
2815
|
+
const batchForKey = this.requestBuffer.get(identifier) || [];
|
|
2816
|
+
batchForKey.push({
|
|
2817
|
+
request,
|
|
2818
|
+
requestTime: Date.now() - this.pageStartTime,
|
|
2819
|
+
});
|
|
2820
|
+
this.requestBuffer.set(identifier, batchForKey);
|
|
2821
|
+
}
|
|
2822
|
+
getPage(key) {
|
|
2823
|
+
const identifier = stableJSONStringify$1(key);
|
|
2824
|
+
return this.storage.get(identifier);
|
|
2825
|
+
}
|
|
2826
|
+
getPageRequests(key) {
|
|
2827
|
+
const page = this.getPage(key);
|
|
2828
|
+
if (page === undefined) {
|
|
2829
|
+
return [];
|
|
2830
|
+
}
|
|
2831
|
+
return page.requests;
|
|
3184
2832
|
}
|
|
3185
2833
|
}
|
|
3186
|
-
|
|
2834
|
+
|
|
2835
|
+
const GET_RELATED_LIST_RECORDS_BATCH_ADAPTER_NAME = 'getRelatedListRecordsBatch';
|
|
2836
|
+
class GetRelatedListRecordsBatchRequestStrategy extends LuvioAdapterRequestStrategy {
|
|
3187
2837
|
constructor() {
|
|
3188
2838
|
super(...arguments);
|
|
3189
|
-
this.adapterName =
|
|
3190
|
-
|
|
3191
|
-
execute(config) {
|
|
3192
|
-
return requestComponents(config);
|
|
2839
|
+
this.adapterName = GET_RELATED_LIST_RECORDS_BATCH_ADAPTER_NAME;
|
|
2840
|
+
this.adapterFactory = getRelatedListRecordsBatchAdapterFactory;
|
|
3193
2841
|
}
|
|
3194
|
-
buildConcreteRequest(similarRequest,
|
|
2842
|
+
buildConcreteRequest(similarRequest, context) {
|
|
3195
2843
|
return {
|
|
3196
2844
|
...similarRequest,
|
|
2845
|
+
config: {
|
|
2846
|
+
...similarRequest.config,
|
|
2847
|
+
parentRecordId: context.recordId,
|
|
2848
|
+
},
|
|
3197
2849
|
};
|
|
3198
2850
|
}
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
for (const [def, uid] of entries(request.config || {})) {
|
|
3202
|
-
const normalizedDescriptorName = def.indexOf('://') === -1 ? 'markup://' + def : def;
|
|
3203
|
-
// uid can be a string, an object, or undefined.
|
|
3204
|
-
// when is an object or undefined, we can't say anything about the version,
|
|
3205
|
-
// and we can't save it as `undefined` as it can't be persisted to indexed db.
|
|
3206
|
-
normalizedConfig[normalizedDescriptorName] = typeof uid === 'string' ? uid : '';
|
|
3207
|
-
}
|
|
3208
|
-
return {
|
|
2851
|
+
transformForSaveSimilarRequest(request) {
|
|
2852
|
+
return this.transformForSave({
|
|
3209
2853
|
...request,
|
|
3210
|
-
config:
|
|
3211
|
-
|
|
2854
|
+
config: {
|
|
2855
|
+
...request.config,
|
|
2856
|
+
parentRecordId: '*',
|
|
2857
|
+
},
|
|
2858
|
+
});
|
|
3212
2859
|
}
|
|
3213
|
-
|
|
3214
|
-
return
|
|
2860
|
+
isContextDependent(context, request) {
|
|
2861
|
+
return context.recordId === request.config.parentRecordId;
|
|
2862
|
+
}
|
|
2863
|
+
/**
|
|
2864
|
+
* Can combine two seperate batch requests if the parentRecordId is the same.
|
|
2865
|
+
* @param reqA The first GetRelatedListRecordsBatchConfig.
|
|
2866
|
+
* @param reqB The first GetRelatedListRecordsBatchConfig.
|
|
2867
|
+
* @returns true if the requests can be combined, otherwise false.
|
|
2868
|
+
*/
|
|
2869
|
+
canCombine(reqA, reqB) {
|
|
2870
|
+
return reqA.parentRecordId === reqB.parentRecordId;
|
|
3215
2871
|
}
|
|
2872
|
+
/**
|
|
2873
|
+
* Merge the relatedListParameters together between two combinable batch requests.
|
|
2874
|
+
* @param reqA The first GetRelatedListRecordsBatchConfig.
|
|
2875
|
+
* @param reqB The first GetRelatedListRecordsBatchConfig.
|
|
2876
|
+
* @returns The combined request.
|
|
2877
|
+
*/
|
|
3216
2878
|
combineRequests(reqA, reqB) {
|
|
3217
|
-
const
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
}
|
|
3226
|
-
}
|
|
3227
|
-
return combinedDescriptors;
|
|
2879
|
+
const relatedListParametersMap = new Set(reqA.relatedListParameters.map((relatedListParameter) => {
|
|
2880
|
+
return stableJSONStringify$1(relatedListParameter);
|
|
2881
|
+
}));
|
|
2882
|
+
const reqBRelatedListParametersToAdd = reqB.relatedListParameters.filter((relatedListParameter) => {
|
|
2883
|
+
return !relatedListParametersMap.has(stableJSONStringify$1(relatedListParameter));
|
|
2884
|
+
});
|
|
2885
|
+
reqA.relatedListParameters = reqA.relatedListParameters.concat(reqBRelatedListParametersToAdd);
|
|
2886
|
+
return reqA;
|
|
3228
2887
|
}
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
2888
|
+
}
|
|
2889
|
+
|
|
2890
|
+
const GET_RELATED_LIST_RECORDS_ADAPTER_NAME = 'getRelatedListRecords';
|
|
2891
|
+
class GetRelatedListRecordsRequestStrategy extends LuvioAdapterRequestStrategy {
|
|
2892
|
+
constructor() {
|
|
2893
|
+
super(...arguments);
|
|
2894
|
+
this.adapterName = GET_RELATED_LIST_RECORDS_ADAPTER_NAME;
|
|
2895
|
+
this.adapterFactory = getRelatedListRecordsAdapterFactory;
|
|
3232
2896
|
}
|
|
3233
|
-
|
|
3234
|
-
return
|
|
2897
|
+
buildConcreteRequest(similarRequest, context) {
|
|
2898
|
+
return {
|
|
2899
|
+
...similarRequest,
|
|
2900
|
+
config: {
|
|
2901
|
+
...similarRequest.config,
|
|
2902
|
+
parentRecordId: context.recordId,
|
|
2903
|
+
},
|
|
2904
|
+
};
|
|
3235
2905
|
}
|
|
3236
2906
|
/**
|
|
3237
|
-
* Component predictions are not boxcared
|
|
3238
2907
|
*
|
|
3239
|
-
*
|
|
2908
|
+
* This method returns GetRelatedListRecordsRequest[] that won't be part of a batch request.
|
|
2909
|
+
*
|
|
2910
|
+
* ADG currently handles the batching of GetRelatedListRecords -> GetRelatedListRecordsBatch
|
|
2911
|
+
* https://gitcore.soma.salesforce.com/core-2206/core-public/blob/p4/main/core/ui-laf-components/modules/laf/batchingPortable/reducers/RelatedListRecordsBatchReducer.js
|
|
2912
|
+
*
|
|
2913
|
+
* For performance reasons (fear to overfetch), we only check that the Single relatedListId is not present in any of the Batch requests,
|
|
2914
|
+
* but we don't check for any other parameters.
|
|
2915
|
+
*
|
|
2916
|
+
* @param unfilteredRequests All of the request available for predictions.
|
|
2917
|
+
* @returns GetRelatedListRecordsRequest[] That should be a prediction.
|
|
3240
2918
|
*/
|
|
3241
|
-
|
|
3242
|
-
|
|
2919
|
+
reduce(unfilteredRequests) {
|
|
2920
|
+
// Batch requests by [parentRecordId]->[RelatedListIds]
|
|
2921
|
+
const batchRequests = unfilteredRequests.filter((entry) => entry.request.adapterName === GET_RELATED_LIST_RECORDS_BATCH_ADAPTER_NAME).reduce((acc, entry) => {
|
|
2922
|
+
// required properties, enforced by adapter typecheck
|
|
2923
|
+
const { parentRecordId, relatedListParameters } = entry.request.config;
|
|
2924
|
+
const existingRlSet = acc.get(parentRecordId) || new Set();
|
|
2925
|
+
// relatedListId enforced by adapter typecheck
|
|
2926
|
+
relatedListParameters.forEach((rlParam) => existingRlSet.add(rlParam.relatedListId));
|
|
2927
|
+
acc.set(parentRecordId, existingRlSet);
|
|
2928
|
+
return acc;
|
|
2929
|
+
}, new Map());
|
|
2930
|
+
const singleRequests = unfilteredRequests.filter((entry) => entry.request.adapterName === this.adapterName);
|
|
2931
|
+
return singleRequests.filter((entry) => {
|
|
2932
|
+
// required props enforced by adapter typecheck
|
|
2933
|
+
const { parentRecordId, relatedListId } = entry.request.config;
|
|
2934
|
+
const batchForParentRecordId = batchRequests.get(parentRecordId);
|
|
2935
|
+
return !(batchForParentRecordId && batchForParentRecordId.has(relatedListId));
|
|
2936
|
+
});
|
|
2937
|
+
}
|
|
2938
|
+
transformForSaveSimilarRequest(request) {
|
|
2939
|
+
return this.transformForSave({
|
|
2940
|
+
...request,
|
|
2941
|
+
config: {
|
|
2942
|
+
...request.config,
|
|
2943
|
+
parentRecordId: '*',
|
|
2944
|
+
},
|
|
2945
|
+
});
|
|
2946
|
+
}
|
|
2947
|
+
isContextDependent(context, request) {
|
|
2948
|
+
return context.recordId === request.config.parentRecordId;
|
|
3243
2949
|
}
|
|
3244
2950
|
}
|
|
3245
2951
|
|
|
3246
|
-
const
|
|
3247
|
-
|
|
2952
|
+
const APEX_RESOURCE_CONTEXT = {
|
|
2953
|
+
...DEFAULT_RESOURCE_CONTEXT,
|
|
3248
2954
|
sourceContext: {
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
longRunning: false,
|
|
3254
|
-
},
|
|
2955
|
+
...DEFAULT_RESOURCE_CONTEXT.sourceContext,
|
|
2956
|
+
// We don't want to override anything for Apex, it is not part
|
|
2957
|
+
// of UiApi, and it can cause undesired behavior.
|
|
2958
|
+
actionConfig: undefined,
|
|
3255
2959
|
},
|
|
3256
2960
|
};
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
2961
|
+
function getApexPdlFactory(luvio) {
|
|
2962
|
+
return ({ invokerParams, config }, requestContext) => {
|
|
2963
|
+
return GetApexWireAdapterFactory(luvio, invokerParams)(config, requestContext);
|
|
2964
|
+
};
|
|
2965
|
+
}
|
|
2966
|
+
const GET_APEX_ADAPTER_NAME = 'getApex';
|
|
2967
|
+
class GetApexRequestStrategy extends LuvioAdapterRequestStrategy {
|
|
2968
|
+
constructor() {
|
|
2969
|
+
super(...arguments);
|
|
2970
|
+
this.adapterName = GET_APEX_ADAPTER_NAME;
|
|
2971
|
+
this.adapterFactory = getApexPdlFactory;
|
|
2972
|
+
}
|
|
2973
|
+
buildConcreteRequest(similarRequest) {
|
|
2974
|
+
return similarRequest;
|
|
2975
|
+
}
|
|
2976
|
+
execute(config, _requestContext) {
|
|
2977
|
+
return super.execute(config, APEX_RESOURCE_CONTEXT);
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
|
|
2981
|
+
const GET_LIST_INFO_BY_NAME_ADAPTER_NAME = 'getListInfoByName';
|
|
2982
|
+
class GetListInfoByNameRequestStrategy extends LuvioAdapterRequestStrategy {
|
|
2983
|
+
constructor() {
|
|
2984
|
+
super(...arguments);
|
|
2985
|
+
this.adapterName = GET_LIST_INFO_BY_NAME_ADAPTER_NAME;
|
|
2986
|
+
this.adapterFactory = getListInfoByNameAdapterFactory;
|
|
2987
|
+
}
|
|
2988
|
+
buildConcreteRequest(similarRequest) {
|
|
2989
|
+
return similarRequest;
|
|
2990
|
+
}
|
|
2991
|
+
transformForSave(request) {
|
|
2992
|
+
return {
|
|
2993
|
+
...request,
|
|
2994
|
+
config: {
|
|
2995
|
+
...request.config,
|
|
2996
|
+
// (!): if we are saving this request is because the adapter already verified is valid.
|
|
2997
|
+
objectApiName: coerceObjectId(request.config.objectApiName),
|
|
2998
|
+
},
|
|
2999
|
+
};
|
|
3000
|
+
}
|
|
3001
|
+
canCombine(reqA, reqB) {
|
|
3002
|
+
return (reqA.objectApiName === reqB.objectApiName &&
|
|
3003
|
+
reqA.listViewApiName === reqB.listViewApiName);
|
|
3261
3004
|
}
|
|
3262
|
-
|
|
3263
|
-
return
|
|
3264
|
-
...DEFAULT_RESOURCE_CONTEXT,
|
|
3265
|
-
...requestContext,
|
|
3266
|
-
});
|
|
3005
|
+
combineRequests(reqA, _reqB) {
|
|
3006
|
+
return reqA;
|
|
3267
3007
|
}
|
|
3268
3008
|
}
|
|
3269
3009
|
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
return [recordIds];
|
|
3273
|
-
}
|
|
3274
|
-
return recordIds;
|
|
3275
|
-
}
|
|
3276
|
-
class GetRecordAvatarsRequestStrategy extends LuvioAdapterRequestStrategy {
|
|
3010
|
+
const GET_LIST_INFOS_BY_OBJECT_NAME_ADAPTER_NAME = 'getListInfosByObjectName';
|
|
3011
|
+
class GetListInfosByObjectNameRequestStrategy extends LuvioAdapterRequestStrategy {
|
|
3277
3012
|
constructor() {
|
|
3278
3013
|
super(...arguments);
|
|
3279
|
-
this.adapterName =
|
|
3280
|
-
this.adapterFactory =
|
|
3014
|
+
this.adapterName = GET_LIST_INFOS_BY_OBJECT_NAME_ADAPTER_NAME;
|
|
3015
|
+
this.adapterFactory = getListInfosByObjectNameAdapterFactory;
|
|
3281
3016
|
}
|
|
3282
|
-
buildConcreteRequest(similarRequest
|
|
3283
|
-
return
|
|
3284
|
-
...similarRequest,
|
|
3285
|
-
config: {
|
|
3286
|
-
...similarRequest.config,
|
|
3287
|
-
recordIds: [context.recordId],
|
|
3288
|
-
},
|
|
3289
|
-
};
|
|
3017
|
+
buildConcreteRequest(similarRequest) {
|
|
3018
|
+
return similarRequest;
|
|
3290
3019
|
}
|
|
3291
|
-
|
|
3292
|
-
return
|
|
3020
|
+
transformForSave(request) {
|
|
3021
|
+
return {
|
|
3293
3022
|
...request,
|
|
3294
3023
|
config: {
|
|
3295
3024
|
...request.config,
|
|
3296
|
-
|
|
3025
|
+
// (!): if we are saving this request is because the adapter already verified is valid.
|
|
3026
|
+
objectApiName: coerceObjectId(request.config.objectApiName),
|
|
3297
3027
|
},
|
|
3298
|
-
}
|
|
3299
|
-
}
|
|
3300
|
-
isContextDependent(context, request) {
|
|
3301
|
-
return (request.config.recordIds &&
|
|
3302
|
-
(context.recordId === request.config.recordIds || // some may set this as string instead of array
|
|
3303
|
-
(request.config.recordIds.length === 1 &&
|
|
3304
|
-
request.config.recordIds[0] === context.recordId)));
|
|
3028
|
+
};
|
|
3305
3029
|
}
|
|
3306
3030
|
canCombine(reqA, reqB) {
|
|
3307
|
-
return reqA.
|
|
3031
|
+
return (reqA.objectApiName === reqB.objectApiName &&
|
|
3032
|
+
reqA.q === reqB.q &&
|
|
3033
|
+
reqA.recentListsOnly === reqB.recentListsOnly &&
|
|
3034
|
+
reqA.pageSize === reqB.pageSize);
|
|
3308
3035
|
}
|
|
3309
|
-
combineRequests(reqA,
|
|
3310
|
-
|
|
3311
|
-
combined.recordIds = Array.from(new Set([...normalizeRecordIds$1(reqA.recordIds), ...normalizeRecordIds$1(reqB.recordIds)]));
|
|
3312
|
-
return combined;
|
|
3036
|
+
combineRequests(reqA, _reqB) {
|
|
3037
|
+
return reqA;
|
|
3313
3038
|
}
|
|
3314
3039
|
}
|
|
3315
3040
|
|
|
3316
|
-
const
|
|
3317
|
-
class
|
|
3041
|
+
const GET_LIST_RECORDS_BY_NAME_ADAPTER_NAME = 'getListRecordsByName';
|
|
3042
|
+
class GetListRecordsByNameRequestStrategy extends LuvioAdapterRequestStrategy {
|
|
3318
3043
|
constructor() {
|
|
3319
3044
|
super(...arguments);
|
|
3320
|
-
this.adapterName =
|
|
3321
|
-
this.adapterFactory =
|
|
3045
|
+
this.adapterName = GET_LIST_RECORDS_BY_NAME_ADAPTER_NAME;
|
|
3046
|
+
this.adapterFactory = getListRecordsByNameAdapterFactory;
|
|
3322
3047
|
}
|
|
3323
|
-
buildConcreteRequest(similarRequest
|
|
3324
|
-
return
|
|
3325
|
-
...similarRequest,
|
|
3326
|
-
config: {
|
|
3327
|
-
...similarRequest.config,
|
|
3328
|
-
recordId: context.recordId,
|
|
3329
|
-
},
|
|
3330
|
-
};
|
|
3048
|
+
buildConcreteRequest(similarRequest) {
|
|
3049
|
+
return similarRequest;
|
|
3331
3050
|
}
|
|
3332
3051
|
transformForSave(request) {
|
|
3333
3052
|
if (request.config.fields === undefined && request.config.optionalFields === undefined) {
|
|
3334
3053
|
return request;
|
|
3335
3054
|
}
|
|
3336
|
-
let fields =
|
|
3337
|
-
let optionalFields =
|
|
3055
|
+
let fields = request.config.fields || [];
|
|
3056
|
+
let optionalFields = request.config.optionalFields || [];
|
|
3338
3057
|
return {
|
|
3339
3058
|
...request,
|
|
3340
3059
|
config: {
|
|
3341
3060
|
...request.config,
|
|
3342
|
-
fields:
|
|
3343
|
-
optionalFields: [...fields, ...optionalFields],
|
|
3061
|
+
fields: [],
|
|
3062
|
+
optionalFields: Array.from(new Set([...fields, ...optionalFields])),
|
|
3344
3063
|
},
|
|
3345
3064
|
};
|
|
3346
3065
|
}
|
|
3347
3066
|
canCombine(reqA, reqB) {
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3067
|
+
return (reqA.objectApiName === reqB.objectApiName &&
|
|
3068
|
+
reqA.listViewApiName === reqB.listViewApiName &&
|
|
3069
|
+
reqA.pageSize === reqB.pageSize &&
|
|
3070
|
+
reqA.searchTerm === reqB.searchTerm &&
|
|
3071
|
+
reqA.sortBy === reqB.sortBy &&
|
|
3072
|
+
reqA.where === reqB.where);
|
|
3353
3073
|
}
|
|
3354
3074
|
combineRequests(reqA, reqB) {
|
|
3355
|
-
const fields = new Set();
|
|
3356
|
-
const optionalFields = new Set();
|
|
3357
|
-
if (reqA.fields !== undefined) {
|
|
3358
|
-
reqA.fields.forEach((field) => fields.add(field));
|
|
3359
|
-
}
|
|
3360
|
-
if (reqB.fields !== undefined) {
|
|
3361
|
-
reqB.fields.forEach((field) => fields.add(field));
|
|
3362
|
-
}
|
|
3363
|
-
if (reqA.optionalFields !== undefined) {
|
|
3364
|
-
reqA.optionalFields.forEach((field) => optionalFields.add(field));
|
|
3365
|
-
}
|
|
3366
|
-
if (reqB.optionalFields !== undefined) {
|
|
3367
|
-
reqB.optionalFields.forEach((field) => optionalFields.add(field));
|
|
3368
|
-
}
|
|
3369
3075
|
return {
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
optionalFields: Array.from(optionalFields),
|
|
3076
|
+
...reqA,
|
|
3077
|
+
optionalFields: Array.from(new Set([...(reqA.optionalFields || []), ...(reqB.optionalFields || [])])),
|
|
3373
3078
|
};
|
|
3374
3079
|
}
|
|
3375
|
-
isContextDependent(context, request) {
|
|
3376
|
-
return request.config.recordId === context.recordId;
|
|
3377
|
-
}
|
|
3378
|
-
transformForSaveSimilarRequest(request) {
|
|
3379
|
-
return this.transformForSave({
|
|
3380
|
-
...request,
|
|
3381
|
-
config: {
|
|
3382
|
-
...request.config,
|
|
3383
|
-
recordId: '*',
|
|
3384
|
-
},
|
|
3385
|
-
});
|
|
3386
|
-
}
|
|
3387
3080
|
}
|
|
3388
3081
|
|
|
3389
|
-
|
|
3082
|
+
const GET_LIST_OBJECT_INFO_ADAPTER_NAME = 'getListObjectInfo';
|
|
3083
|
+
class GetListObjectInfoRequestStrategy extends LuvioAdapterRequestStrategy {
|
|
3390
3084
|
constructor() {
|
|
3391
3085
|
super(...arguments);
|
|
3392
|
-
this.adapterName =
|
|
3393
|
-
this.adapterFactory =
|
|
3086
|
+
this.adapterName = GET_LIST_OBJECT_INFO_ADAPTER_NAME;
|
|
3087
|
+
this.adapterFactory = getListObjectInfoAdapterFactory;
|
|
3394
3088
|
}
|
|
3395
|
-
buildConcreteRequest(similarRequest
|
|
3089
|
+
buildConcreteRequest(similarRequest) {
|
|
3090
|
+
return similarRequest;
|
|
3091
|
+
}
|
|
3092
|
+
transformForSave(request) {
|
|
3396
3093
|
return {
|
|
3397
|
-
...
|
|
3094
|
+
...request,
|
|
3398
3095
|
config: {
|
|
3399
|
-
...
|
|
3400
|
-
|
|
3096
|
+
...request.config,
|
|
3097
|
+
// (!): if we are saving this request is because the adapter already verified is valid.
|
|
3098
|
+
objectApiName: coerceObjectId(request.config.objectApiName),
|
|
3401
3099
|
},
|
|
3402
3100
|
};
|
|
3403
3101
|
}
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
return isSingleRecordRequest && request.config.records[0].recordIds[0] === context.recordId;
|
|
3102
|
+
canCombine(reqA, reqB) {
|
|
3103
|
+
return reqA.objectApiName === reqB.objectApiName;
|
|
3407
3104
|
}
|
|
3408
|
-
|
|
3409
|
-
return
|
|
3410
|
-
...request,
|
|
3411
|
-
config: {
|
|
3412
|
-
...request.config,
|
|
3413
|
-
records: [
|
|
3414
|
-
{
|
|
3415
|
-
...request.config.records[0],
|
|
3416
|
-
recordIds: ['*'],
|
|
3417
|
-
},
|
|
3418
|
-
],
|
|
3419
|
-
},
|
|
3420
|
-
});
|
|
3105
|
+
combineRequests(reqA, _reqB) {
|
|
3106
|
+
return reqA;
|
|
3421
3107
|
}
|
|
3422
3108
|
}
|
|
3423
3109
|
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3110
|
+
const RECORD_HOME_SUPPORTED_ADAPTERS = new Set([
|
|
3111
|
+
GET_APEX_ADAPTER_NAME,
|
|
3112
|
+
GET_COMPONENTS_DEF_ADAPTER_NAME,
|
|
3113
|
+
GET_OBJECT_INFO_ADAPTER_NAME,
|
|
3114
|
+
GET_OBJECT_INFO_BATCH_ADAPTER_NAME,
|
|
3115
|
+
GET_RECORD_ACTIONS_ADAPTER_NAME,
|
|
3116
|
+
GET_RECORD_AVATARS_ADAPTER_NAME,
|
|
3117
|
+
GET_RECORD_ADAPTER_NAME,
|
|
3118
|
+
GET_RECORDS_ADAPTER_NAME,
|
|
3119
|
+
GET_RELATED_LIST_INFO_BATCH_ADAPTER_NAME,
|
|
3120
|
+
GET_RELATED_LIST_INFO_ADAPTER_NAME,
|
|
3121
|
+
GET_RELATED_LIST_RECORDS_BATCH_ADAPTER_NAME,
|
|
3122
|
+
GET_RELATED_LIST_RECORDS_ADAPTER_NAME,
|
|
3123
|
+
GET_RELATED_LISTS_ACTIONS_ADAPTER_NAME,
|
|
3124
|
+
]);
|
|
3125
|
+
class RecordHomePage extends LexDefaultPage {
|
|
3126
|
+
constructor(context, requestStrategyManager, options) {
|
|
3127
|
+
super(context);
|
|
3128
|
+
this.requestStrategyManager = requestStrategyManager;
|
|
3129
|
+
this.options = options;
|
|
3130
|
+
const { recordId: _, ...rest } = this.context;
|
|
3131
|
+
this.similarContext = {
|
|
3132
|
+
recordId: '*',
|
|
3133
|
+
...rest,
|
|
3134
|
+
};
|
|
3427
3135
|
}
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
function normalizeApiNames(apiNames) {
|
|
3431
|
-
if (apiNames === undefined || apiNames === null) {
|
|
3432
|
-
return [];
|
|
3136
|
+
supportsRequest(request) {
|
|
3137
|
+
return RECORD_HOME_SUPPORTED_ADAPTERS.has(request.adapterName);
|
|
3433
3138
|
}
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3139
|
+
buildSaveRequestData(request) {
|
|
3140
|
+
const requestBuckets = [];
|
|
3141
|
+
const { adapterName } = request;
|
|
3142
|
+
const matchingRequestStrategy = this.requestStrategyManager.get(adapterName);
|
|
3143
|
+
if (matchingRequestStrategy === undefined) {
|
|
3144
|
+
return [];
|
|
3145
|
+
}
|
|
3146
|
+
if (matchingRequestStrategy.isContextDependent(this.context, request)) {
|
|
3147
|
+
requestBuckets.push({
|
|
3148
|
+
context: this.similarContext,
|
|
3149
|
+
request: matchingRequestStrategy.transformForSaveSimilarRequest(request),
|
|
3150
|
+
});
|
|
3151
|
+
// When `options.useExactMatchesPlus` is not enabled, we can save this request on the similar bucket only
|
|
3152
|
+
if (!this.options.useExactMatchesPlus) {
|
|
3153
|
+
return requestBuckets;
|
|
3154
|
+
}
|
|
3155
|
+
}
|
|
3156
|
+
if (!matchingRequestStrategy.onlySavedInSimilar) {
|
|
3157
|
+
requestBuckets.push({
|
|
3158
|
+
context: this.context,
|
|
3159
|
+
request: matchingRequestStrategy.transformForSave(request),
|
|
3160
|
+
});
|
|
3161
|
+
}
|
|
3162
|
+
return requestBuckets;
|
|
3441
3163
|
}
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
};
|
|
3164
|
+
resolveSimilarRequest(similarRequest) {
|
|
3165
|
+
const { adapterName } = similarRequest;
|
|
3166
|
+
const matchingRequestStrategy = this.requestStrategyManager.get(adapterName);
|
|
3167
|
+
if (matchingRequestStrategy === undefined) {
|
|
3168
|
+
return similarRequest;
|
|
3169
|
+
}
|
|
3170
|
+
return matchingRequestStrategy.buildConcreteRequest(similarRequest, this.context);
|
|
3450
3171
|
}
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3172
|
+
// Record Home performs best when we always do a minimal getRecord alongside the other requests.
|
|
3173
|
+
getAlwaysRunRequests() {
|
|
3174
|
+
const { recordId, objectApiName } = this.context;
|
|
3175
|
+
return [
|
|
3176
|
+
{
|
|
3177
|
+
adapterName: 'getRecord',
|
|
3178
|
+
config: {
|
|
3179
|
+
recordId,
|
|
3180
|
+
optionalFields: [`${objectApiName}.Id`, `${objectApiName}.RecordTypeId`],
|
|
3181
|
+
},
|
|
3457
3182
|
},
|
|
3458
|
-
|
|
3183
|
+
];
|
|
3459
3184
|
}
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3185
|
+
/**
|
|
3186
|
+
* In RH, we know that there will be predictions, and we want to reduce the always requests (getRecord(id, type))
|
|
3187
|
+
* with one of the predictions in case some request containing the fields was missed in the predictions.
|
|
3188
|
+
*
|
|
3189
|
+
* @returns true
|
|
3190
|
+
*/
|
|
3191
|
+
shouldReduceAlwaysRequestsWithPredictions() {
|
|
3192
|
+
return true;
|
|
3465
3193
|
}
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
...normalizeApiNames(reqB.apiNames),
|
|
3474
|
-
]);
|
|
3475
|
-
combined.apiNames = Array.from(combinedSet);
|
|
3476
|
-
}
|
|
3477
|
-
return combined;
|
|
3194
|
+
/**
|
|
3195
|
+
* In RH, we should execute the getRecord(id, type) by itself as we want the result asap, so
|
|
3196
|
+
* it does not stop rendering.
|
|
3197
|
+
* @returns true
|
|
3198
|
+
*/
|
|
3199
|
+
shouldExecuteAlwaysRequestByThemself() {
|
|
3200
|
+
return true;
|
|
3478
3201
|
}
|
|
3479
|
-
|
|
3480
|
-
return (
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3202
|
+
static handlesContext(context) {
|
|
3203
|
+
return (context !== undefined &&
|
|
3204
|
+
context.actionName !== undefined &&
|
|
3205
|
+
context.objectApiName !== undefined &&
|
|
3206
|
+
context.recordId !== undefined &&
|
|
3207
|
+
context.type === 'recordPage');
|
|
3484
3208
|
}
|
|
3485
3209
|
}
|
|
3486
3210
|
|
|
3487
|
-
const
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
this.
|
|
3500
|
-
this.adapterFactory = getObjectInfosAdapterFactory;
|
|
3211
|
+
const OBJECT_HOME_SUPPORTED_ADAPTERS = new Set([
|
|
3212
|
+
GET_LIST_INFO_BY_NAME_ADAPTER_NAME,
|
|
3213
|
+
GET_LIST_OBJECT_INFO_ADAPTER_NAME,
|
|
3214
|
+
GET_LIST_RECORDS_BY_NAME_ADAPTER_NAME,
|
|
3215
|
+
GET_LIST_INFOS_BY_OBJECT_NAME_ADAPTER_NAME,
|
|
3216
|
+
GET_OBJECT_INFO_BATCH_ADAPTER_NAME,
|
|
3217
|
+
]);
|
|
3218
|
+
class ObjectHomePage extends LexDefaultPage {
|
|
3219
|
+
constructor(context, requestStrategyManager, options) {
|
|
3220
|
+
super(context);
|
|
3221
|
+
this.requestStrategyManager = requestStrategyManager;
|
|
3222
|
+
this.options = options;
|
|
3223
|
+
this.similarContext = context;
|
|
3501
3224
|
}
|
|
3502
|
-
|
|
3225
|
+
supportsRequest(request) {
|
|
3226
|
+
return OBJECT_HOME_SUPPORTED_ADAPTERS.has(request.adapterName);
|
|
3227
|
+
}
|
|
3228
|
+
buildSaveRequestData(request) {
|
|
3229
|
+
const requestBuckets = [];
|
|
3230
|
+
const { adapterName } = request;
|
|
3231
|
+
const matchingRequestStrategy = this.requestStrategyManager.get(adapterName);
|
|
3232
|
+
if (matchingRequestStrategy === undefined) {
|
|
3233
|
+
return [];
|
|
3234
|
+
}
|
|
3235
|
+
if (matchingRequestStrategy.isContextDependent(this.context, request)) {
|
|
3236
|
+
requestBuckets.push({
|
|
3237
|
+
context: this.similarContext,
|
|
3238
|
+
request: matchingRequestStrategy.transformForSaveSimilarRequest(request),
|
|
3239
|
+
});
|
|
3240
|
+
// When `options.useExactMatchesPlus` is not enabled, we can save this request on the similar bucket only
|
|
3241
|
+
if (!this.options.useExactMatchesPlus) {
|
|
3242
|
+
return requestBuckets;
|
|
3243
|
+
}
|
|
3244
|
+
}
|
|
3245
|
+
requestBuckets.push({
|
|
3246
|
+
context: this.context,
|
|
3247
|
+
request: matchingRequestStrategy.transformForSave(request),
|
|
3248
|
+
});
|
|
3249
|
+
return requestBuckets;
|
|
3250
|
+
}
|
|
3251
|
+
// no similar requests between LVs
|
|
3252
|
+
resolveSimilarRequest(similarRequest) {
|
|
3503
3253
|
return similarRequest;
|
|
3504
3254
|
}
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3255
|
+
// these are requests that run always regardless of any other request existing
|
|
3256
|
+
getAlwaysRunRequests() {
|
|
3257
|
+
const { listViewApiName, objectApiName } = this.context;
|
|
3258
|
+
return [
|
|
3259
|
+
{
|
|
3260
|
+
adapterName: 'getListInfoByName',
|
|
3261
|
+
config: {
|
|
3262
|
+
objectApiName: objectApiName,
|
|
3263
|
+
listViewApiName: listViewApiName,
|
|
3264
|
+
},
|
|
3512
3265
|
},
|
|
3513
|
-
|
|
3266
|
+
{
|
|
3267
|
+
adapterName: 'getListInfosByObjectName',
|
|
3268
|
+
config: {
|
|
3269
|
+
objectApiName: objectApiName,
|
|
3270
|
+
pageSize: 100,
|
|
3271
|
+
q: '',
|
|
3272
|
+
},
|
|
3273
|
+
},
|
|
3274
|
+
{
|
|
3275
|
+
adapterName: 'getListInfosByObjectName',
|
|
3276
|
+
config: {
|
|
3277
|
+
objectApiName: objectApiName,
|
|
3278
|
+
pageSize: 10,
|
|
3279
|
+
recentListsOnly: true,
|
|
3280
|
+
},
|
|
3281
|
+
},
|
|
3282
|
+
{
|
|
3283
|
+
adapterName: 'getListObjectInfo',
|
|
3284
|
+
config: {
|
|
3285
|
+
objectApiName: objectApiName,
|
|
3286
|
+
},
|
|
3287
|
+
},
|
|
3288
|
+
];
|
|
3514
3289
|
}
|
|
3515
3290
|
/**
|
|
3516
|
-
*
|
|
3517
|
-
* is a subset of another GetObjectInfosRequest.
|
|
3291
|
+
* AlwaysRequests must be reduced with predictions.
|
|
3518
3292
|
*
|
|
3519
|
-
* @
|
|
3520
|
-
* @returns RequestEntry<GetObjectInfosRequest>[] - Array of reduced requests
|
|
3293
|
+
* @returns true
|
|
3521
3294
|
*/
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
possibleSuperset.requestMetadata.requestTime =
|
|
3542
|
-
currentRequestMetadata.requestTime;
|
|
3543
|
-
}
|
|
3544
|
-
}
|
|
3545
|
-
}
|
|
3546
|
-
if (!isCurrentSubsetOfOthers) {
|
|
3547
|
-
reducedRequests.push(current);
|
|
3548
|
-
}
|
|
3549
|
-
}
|
|
3550
|
-
return reducedRequests;
|
|
3295
|
+
shouldReduceAlwaysRequestsWithPredictions() {
|
|
3296
|
+
return true;
|
|
3297
|
+
}
|
|
3298
|
+
/**
|
|
3299
|
+
* In OH, the always requests are reduced with predictions, and because they
|
|
3300
|
+
* can't be merged with other predictions, they will always run by themself.
|
|
3301
|
+
* This value must be `false`, otherwise we may see repeated requests.
|
|
3302
|
+
*
|
|
3303
|
+
* @returns false
|
|
3304
|
+
*/
|
|
3305
|
+
shouldExecuteAlwaysRequestByThemself() {
|
|
3306
|
+
return false;
|
|
3307
|
+
}
|
|
3308
|
+
// Identifies a valid ObjectHomeContext
|
|
3309
|
+
static handlesContext(context) {
|
|
3310
|
+
return (context !== undefined &&
|
|
3311
|
+
context.listViewApiName !== undefined &&
|
|
3312
|
+
context.objectApiName !== undefined &&
|
|
3313
|
+
context.type === 'objectHomePage');
|
|
3551
3314
|
}
|
|
3552
3315
|
}
|
|
3553
3316
|
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3317
|
+
/**
|
|
3318
|
+
* Observability / Critical Availability Program (230+)
|
|
3319
|
+
*
|
|
3320
|
+
* This file is intended to be used as a consolidated place for all definitions, functions,
|
|
3321
|
+
* and helpers related to "M1"[1].
|
|
3322
|
+
*
|
|
3323
|
+
* Below are the R.E.A.D.S. metrics for the Lightning Data Service, defined here[2].
|
|
3324
|
+
*
|
|
3325
|
+
* [1] Search "[M1] Lightning Data Service Design Spike" in Quip
|
|
3326
|
+
* [2] Search "Lightning Data Service R.E.A.D.S. Metrics" in Quip
|
|
3327
|
+
*/
|
|
3328
|
+
const OBSERVABILITY_NAMESPACE = 'LIGHTNING.lds.service';
|
|
3329
|
+
const ADAPTER_INVOCATION_COUNT_METRIC_NAME = 'request';
|
|
3330
|
+
const ADAPTER_ERROR_COUNT_METRIC_NAME = 'error';
|
|
3331
|
+
const NETWORK_ADAPTER_RESPONSE_METRIC_NAME = 'network-response';
|
|
3332
|
+
/**
|
|
3333
|
+
* W-8379680
|
|
3334
|
+
* Counter for number of getApex requests.
|
|
3335
|
+
*/
|
|
3336
|
+
const GET_APEX_REQUEST_COUNT = {
|
|
3337
|
+
get() {
|
|
3561
3338
|
return {
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
...similarRequest.config,
|
|
3565
|
-
objectApiName: context.objectApiName,
|
|
3566
|
-
},
|
|
3339
|
+
owner: OBSERVABILITY_NAMESPACE,
|
|
3340
|
+
name: ADAPTER_INVOCATION_COUNT_METRIC_NAME + '.' + NORMALIZED_APEX_ADAPTER_NAME,
|
|
3567
3341
|
};
|
|
3342
|
+
},
|
|
3343
|
+
};
|
|
3344
|
+
/**
|
|
3345
|
+
* W-8828410
|
|
3346
|
+
* Counter for the number of UnfulfilledSnapshotErrors the luvio engine has.
|
|
3347
|
+
*/
|
|
3348
|
+
const TOTAL_ADAPTER_ERROR_COUNT = {
|
|
3349
|
+
get() {
|
|
3350
|
+
return { owner: OBSERVABILITY_NAMESPACE, name: ADAPTER_ERROR_COUNT_METRIC_NAME };
|
|
3351
|
+
},
|
|
3352
|
+
};
|
|
3353
|
+
/**
|
|
3354
|
+
* W-8828410
|
|
3355
|
+
* Counter for the number of invocations made into LDS by a wire adapter.
|
|
3356
|
+
*/
|
|
3357
|
+
const TOTAL_ADAPTER_REQUEST_SUCCESS_COUNT = {
|
|
3358
|
+
get() {
|
|
3359
|
+
return { owner: OBSERVABILITY_NAMESPACE, name: ADAPTER_INVOCATION_COUNT_METRIC_NAME };
|
|
3360
|
+
},
|
|
3361
|
+
};
|
|
3362
|
+
|
|
3363
|
+
/**
|
|
3364
|
+
* A deterministic JSON stringify implementation. Heavily adapted from https://github.com/epoberezkin/fast-json-stable-stringify.
|
|
3365
|
+
* This is needed because insertion order for JSON.stringify(object) affects output:
|
|
3366
|
+
* JSON.stringify({a: 1, b: 2})
|
|
3367
|
+
* "{"a":1,"b":2}"
|
|
3368
|
+
* JSON.stringify({b: 2, a: 1})
|
|
3369
|
+
* "{"b":2,"a":1}"
|
|
3370
|
+
* Modified from the apex implementation to sort arrays non-destructively.
|
|
3371
|
+
* @param data Data to be JSON-stringified.
|
|
3372
|
+
* @returns JSON.stringified value with consistent ordering of keys.
|
|
3373
|
+
*/
|
|
3374
|
+
function stableJSONStringify(node) {
|
|
3375
|
+
// This is for Date values.
|
|
3376
|
+
if (node && node.toJSON && typeof node.toJSON === 'function') {
|
|
3377
|
+
// eslint-disable-next-line no-param-reassign
|
|
3378
|
+
node = node.toJSON();
|
|
3568
3379
|
}
|
|
3569
|
-
|
|
3570
|
-
return
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3380
|
+
if (node === undefined) {
|
|
3381
|
+
return;
|
|
3382
|
+
}
|
|
3383
|
+
if (typeof node === 'number') {
|
|
3384
|
+
return isFinite(node) ? '' + node : 'null';
|
|
3385
|
+
}
|
|
3386
|
+
if (typeof node !== 'object') {
|
|
3387
|
+
return stringify(node);
|
|
3388
|
+
}
|
|
3389
|
+
let i;
|
|
3390
|
+
let out;
|
|
3391
|
+
if (isArray(node)) {
|
|
3392
|
+
// copy any array before sorting so we don't mutate the object.
|
|
3393
|
+
// eslint-disable-next-line no-param-reassign
|
|
3394
|
+
node = node.slice(0).sort();
|
|
3395
|
+
out = '[';
|
|
3396
|
+
for (i = 0; i < node.length; i++) {
|
|
3397
|
+
if (i) {
|
|
3398
|
+
out += ',';
|
|
3399
|
+
}
|
|
3400
|
+
out += stableJSONStringify(node[i]) || 'null';
|
|
3401
|
+
}
|
|
3402
|
+
return out + ']';
|
|
3578
3403
|
}
|
|
3579
|
-
|
|
3580
|
-
return
|
|
3581
|
-
context.objectApiName === request.config.objectApiName);
|
|
3404
|
+
if (node === null) {
|
|
3405
|
+
return 'null';
|
|
3582
3406
|
}
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
}
|
|
3594
|
-
|
|
3595
|
-
return singleRequests.filter((singleEntry) => {
|
|
3596
|
-
return !objectApiNamesInBatchRequest.has(singleEntry.request.config.objectApiName);
|
|
3597
|
-
});
|
|
3407
|
+
const keys$1 = keys(node).sort();
|
|
3408
|
+
out = '';
|
|
3409
|
+
for (i = 0; i < keys$1.length; i++) {
|
|
3410
|
+
const key = keys$1[i];
|
|
3411
|
+
const value = stableJSONStringify(node[key]);
|
|
3412
|
+
if (!value) {
|
|
3413
|
+
continue;
|
|
3414
|
+
}
|
|
3415
|
+
if (out) {
|
|
3416
|
+
out += ',';
|
|
3417
|
+
}
|
|
3418
|
+
out += stringify(key) + ':' + value;
|
|
3598
3419
|
}
|
|
3420
|
+
return '{' + out + '}';
|
|
3599
3421
|
}
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
return
|
|
3603
|
-
return rlReq.relatedListId !== undefined && keys(rlReq).length === 1;
|
|
3604
|
-
});
|
|
3422
|
+
function isPromise(value) {
|
|
3423
|
+
// check for Thenable due to test frameworks using custom Promise impls
|
|
3424
|
+
return value !== null && value.then !== undefined;
|
|
3605
3425
|
}
|
|
3606
|
-
|
|
3426
|
+
|
|
3427
|
+
const APEX_ADAPTER_NAME = 'getApex';
|
|
3428
|
+
const NORMALIZED_APEX_ADAPTER_NAME = `Apex.${APEX_ADAPTER_NAME}`;
|
|
3429
|
+
const REFRESH_APEX_KEY = 'refreshApex';
|
|
3430
|
+
const REFRESH_UIAPI_KEY = 'refreshUiApi';
|
|
3431
|
+
const SUPPORTED_KEY = 'refreshSupported';
|
|
3432
|
+
const UNSUPPORTED_KEY = 'refreshUnsupported';
|
|
3433
|
+
const REFRESH_EVENTSOURCE = 'lds-refresh-summary';
|
|
3434
|
+
const REFRESH_EVENTTYPE = 'system';
|
|
3435
|
+
const REFRESH_PAYLOAD_TARGET = 'adapters';
|
|
3436
|
+
const REFRESH_PAYLOAD_SCOPE = 'lds';
|
|
3437
|
+
const INCOMING_WEAKETAG_0_KEY = 'incoming-weaketag-0';
|
|
3438
|
+
const EXISTING_WEAKETAG_0_KEY = 'existing-weaketag-0';
|
|
3439
|
+
const RECORD_API_NAME_CHANGE_COUNT_METRIC_NAME = 'record-api-name-change-count';
|
|
3440
|
+
const NAMESPACE = 'lds';
|
|
3441
|
+
const NETWORK_TRANSACTION_NAME = 'lds-network';
|
|
3442
|
+
const CACHE_STATS_OUT_OF_TTL_MISS_POSTFIX = 'out-of-ttl-miss';
|
|
3443
|
+
// Aggregate Cache Stats and Metrics for all getApex invocations
|
|
3444
|
+
const getApexCacheStats = registerLdsCacheStats(NORMALIZED_APEX_ADAPTER_NAME);
|
|
3445
|
+
const getApexTtlCacheStats = registerLdsCacheStats(NORMALIZED_APEX_ADAPTER_NAME + ':' + CACHE_STATS_OUT_OF_TTL_MISS_POSTFIX);
|
|
3446
|
+
// Observability (READS)
|
|
3447
|
+
const getApexRequestCountMetric = counter(GET_APEX_REQUEST_COUNT);
|
|
3448
|
+
const totalAdapterRequestSuccessMetric = counter(TOTAL_ADAPTER_REQUEST_SUCCESS_COUNT);
|
|
3449
|
+
const totalAdapterErrorMetric = counter(TOTAL_ADAPTER_ERROR_COUNT);
|
|
3450
|
+
class Instrumentation {
|
|
3607
3451
|
constructor() {
|
|
3608
|
-
|
|
3609
|
-
this.
|
|
3610
|
-
this.
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
...similarRequest.config,
|
|
3617
|
-
recordIds: [context.recordId],
|
|
3618
|
-
},
|
|
3452
|
+
this.adapterUnfulfilledErrorCounters = {};
|
|
3453
|
+
this.recordApiNameChangeCounters = {};
|
|
3454
|
+
this.refreshAdapterEvents = {};
|
|
3455
|
+
this.refreshApiCallEventStats = {
|
|
3456
|
+
[REFRESH_APEX_KEY]: 0,
|
|
3457
|
+
[REFRESH_UIAPI_KEY]: 0,
|
|
3458
|
+
[SUPPORTED_KEY]: 0,
|
|
3459
|
+
[UNSUPPORTED_KEY]: 0,
|
|
3619
3460
|
};
|
|
3461
|
+
this.lastRefreshApiCall = null;
|
|
3462
|
+
this.weakEtagZeroEvents = {};
|
|
3463
|
+
this.adapterCacheMisses = new LRUCache(250);
|
|
3464
|
+
if (typeof window !== 'undefined' && window.addEventListener) {
|
|
3465
|
+
window.addEventListener('beforeunload', () => {
|
|
3466
|
+
if (keys(this.weakEtagZeroEvents).length > 0) {
|
|
3467
|
+
perfStart(NETWORK_TRANSACTION_NAME);
|
|
3468
|
+
perfEnd(NETWORK_TRANSACTION_NAME, this.weakEtagZeroEvents);
|
|
3469
|
+
}
|
|
3470
|
+
});
|
|
3471
|
+
}
|
|
3472
|
+
registerPeriodicLogger(NAMESPACE, this.logRefreshStats.bind(this));
|
|
3620
3473
|
}
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3474
|
+
/**
|
|
3475
|
+
* Instruments an existing adapter to log argus metrics and cache stats.
|
|
3476
|
+
* @param adapter The adapter function.
|
|
3477
|
+
* @param metadata The adapter metadata.
|
|
3478
|
+
* @param wireConfigKeyFn Optional function to transform wire configs to a unique key.
|
|
3479
|
+
* @returns The wrapped adapter.
|
|
3480
|
+
*/
|
|
3481
|
+
instrumentAdapter(adapter, metadata) {
|
|
3482
|
+
// We are consolidating all apex adapter instrumentation calls under a single key
|
|
3483
|
+
const { apiFamily, name, ttl } = metadata;
|
|
3484
|
+
const adapterName = normalizeAdapterName(name, apiFamily);
|
|
3485
|
+
const isGetApexAdapter = isApexAdapter(name);
|
|
3486
|
+
const stats = isGetApexAdapter ? getApexCacheStats : registerLdsCacheStats(adapterName);
|
|
3487
|
+
const ttlMissStats = isGetApexAdapter
|
|
3488
|
+
? getApexTtlCacheStats
|
|
3489
|
+
: registerLdsCacheStats(adapterName + ':' + CACHE_STATS_OUT_OF_TTL_MISS_POSTFIX);
|
|
3490
|
+
/**
|
|
3491
|
+
* W-8076905
|
|
3492
|
+
* Dynamically generated metric. Simple counter for all requests made by this adapter.
|
|
3493
|
+
*/
|
|
3494
|
+
const wireAdapterRequestMetric = isGetApexAdapter
|
|
3495
|
+
? getApexRequestCountMetric
|
|
3496
|
+
: counter(createMetricsKey(OBSERVABILITY_NAMESPACE, ADAPTER_INVOCATION_COUNT_METRIC_NAME, adapterName));
|
|
3497
|
+
const instrumentedAdapter = (config, requestContext) => {
|
|
3498
|
+
// increment overall and adapter request metrics
|
|
3499
|
+
wireAdapterRequestMetric.increment(1);
|
|
3500
|
+
totalAdapterRequestSuccessMetric.increment(1);
|
|
3501
|
+
// execute adapter logic
|
|
3502
|
+
const result = adapter(config, requestContext);
|
|
3503
|
+
// In the case where the adapter returns a non-Pending Snapshot it is constructed out of the store
|
|
3504
|
+
// (cache hit) whereas a Promise<Snapshot> or Pending Snapshot indicates a network request (cache miss).
|
|
3505
|
+
//
|
|
3506
|
+
// Note: we can't do a plain instanceof check for a promise here since the Promise may
|
|
3507
|
+
// originate from another javascript realm (for example: in jest test). Instead we use a
|
|
3508
|
+
// duck-typing approach by checking if the result has a then property.
|
|
3509
|
+
//
|
|
3510
|
+
// For adapters without persistent store:
|
|
3511
|
+
// - total cache hit ratio:
|
|
3512
|
+
// [in-memory cache hit count] / ([in-memory cache hit count] + [in-memory cache miss count])
|
|
3513
|
+
// For adapters with persistent store:
|
|
3514
|
+
// - in-memory cache hit ratio:
|
|
3515
|
+
// [in-memory cache hit count] / ([in-memory cache hit count] + [in-memory cache miss count])
|
|
3516
|
+
// - total cache hit ratio:
|
|
3517
|
+
// ([in-memory cache hit count] + [store cache hit count]) / ([in-memory cache hit count] + [in-memory cache miss count])
|
|
3518
|
+
// if result === null then config is insufficient/invalid so do not log
|
|
3519
|
+
if (isPromise(result)) {
|
|
3520
|
+
stats.logMisses();
|
|
3521
|
+
if (ttl !== undefined) {
|
|
3522
|
+
this.logAdapterCacheMissOutOfTtlDuration(adapterName, config, ttlMissStats, Date.now(), ttl);
|
|
3523
|
+
}
|
|
3524
|
+
}
|
|
3525
|
+
else if (result !== null) {
|
|
3526
|
+
stats.logHits();
|
|
3527
|
+
}
|
|
3528
|
+
return result;
|
|
3529
|
+
};
|
|
3530
|
+
// Set the name property on the function for debugging purposes.
|
|
3531
|
+
Object.defineProperty(instrumentedAdapter, 'name', {
|
|
3532
|
+
value: name + '__instrumented',
|
|
3628
3533
|
});
|
|
3534
|
+
return instrumentAdapter(instrumentedAdapter, metadata);
|
|
3629
3535
|
}
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3536
|
+
/**
|
|
3537
|
+
* 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.
|
|
3538
|
+
* Backed by an LRU Cache implementation to prevent too many record entries from being stored in-memory.
|
|
3539
|
+
* @param name The wire adapter name.
|
|
3540
|
+
* @param config The config passed into wire adapter.
|
|
3541
|
+
* @param ttlMissStats CacheStatsLogger to log misses out of TTL.
|
|
3542
|
+
* @param currentCacheMissTimestamp Timestamp for when the request was made.
|
|
3543
|
+
* @param ttl TTL for the wire adapter.
|
|
3544
|
+
*/
|
|
3545
|
+
logAdapterCacheMissOutOfTtlDuration(name, config, ttlMissStats, currentCacheMissTimestamp, ttl) {
|
|
3546
|
+
const configKey = `${name}:${stableJSONStringify(config)}`;
|
|
3547
|
+
const existingCacheMissTimestamp = this.adapterCacheMisses.get(configKey);
|
|
3548
|
+
this.adapterCacheMisses.set(configKey, currentCacheMissTimestamp);
|
|
3549
|
+
if (existingCacheMissTimestamp !== undefined) {
|
|
3550
|
+
const duration = currentCacheMissTimestamp - existingCacheMissTimestamp;
|
|
3551
|
+
if (duration > ttl) {
|
|
3552
|
+
ttlMissStats.logMisses();
|
|
3553
|
+
}
|
|
3554
|
+
}
|
|
3636
3555
|
}
|
|
3637
3556
|
/**
|
|
3638
|
-
*
|
|
3639
|
-
* the individual relatedListAction config only have relatedListId.
|
|
3557
|
+
* Injected to LDS for Luvio specific instrumentation.
|
|
3640
3558
|
*
|
|
3641
|
-
* @param
|
|
3642
|
-
* @param reqB
|
|
3643
|
-
* @returns boolean
|
|
3559
|
+
* @param context The transaction context.
|
|
3644
3560
|
*/
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
}
|
|
3653
|
-
|
|
3654
|
-
recordIdA !== null &&
|
|
3655
|
-
isReduceAbleRelatedListConfig(reqA) &&
|
|
3656
|
-
isReduceAbleRelatedListConfig(reqB));
|
|
3561
|
+
instrumentLuvio(context) {
|
|
3562
|
+
instrumentLuvio(context);
|
|
3563
|
+
if (this.isRefreshAdapterEvent(context)) {
|
|
3564
|
+
this.aggregateRefreshAdapterEvents(context);
|
|
3565
|
+
}
|
|
3566
|
+
else if (this.isAdapterUnfulfilledError(context)) {
|
|
3567
|
+
this.incrementAdapterRequestErrorCount(context);
|
|
3568
|
+
}
|
|
3569
|
+
else ;
|
|
3657
3570
|
}
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
relatedListsActionParameters: from(relatedListsIncluded).map((relatedListId) => ({
|
|
3666
|
-
relatedListId,
|
|
3667
|
-
})),
|
|
3668
|
-
};
|
|
3571
|
+
/**
|
|
3572
|
+
* Returns whether or not this is a RefreshAdapterEvent.
|
|
3573
|
+
* @param context The transaction context.
|
|
3574
|
+
* @returns Whether or not this is a RefreshAdapterEvent.
|
|
3575
|
+
*/
|
|
3576
|
+
isRefreshAdapterEvent(context) {
|
|
3577
|
+
return context[REFRESH_ADAPTER_EVENT] === true;
|
|
3669
3578
|
}
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
this.adapterFactory = getRelatedListInfoBatchAdapterFactory;
|
|
3579
|
+
/**
|
|
3580
|
+
* Returns whether or not this is an AdapterUnfulfilledError.
|
|
3581
|
+
* @param context The transaction context.
|
|
3582
|
+
* @returns Whether or not this is an AdapterUnfulfilledError.
|
|
3583
|
+
*/
|
|
3584
|
+
isAdapterUnfulfilledError(context) {
|
|
3585
|
+
return context[ADAPTER_UNFULFILLED_ERROR] === true;
|
|
3678
3586
|
}
|
|
3679
|
-
|
|
3680
|
-
|
|
3587
|
+
/**
|
|
3588
|
+
* Specific instrumentation for getRecordNotifyChange.
|
|
3589
|
+
* temporary implementation to match existing aura call for now
|
|
3590
|
+
*
|
|
3591
|
+
* @param uniqueWeakEtags whether weakEtags match or not
|
|
3592
|
+
* @param error if dispatchResourceRequest fails for any reason
|
|
3593
|
+
*/
|
|
3594
|
+
notifyChangeNetwork(uniqueWeakEtags, error) {
|
|
3595
|
+
perfStart(NETWORK_TRANSACTION_NAME);
|
|
3596
|
+
if (error === true) {
|
|
3597
|
+
perfEnd(NETWORK_TRANSACTION_NAME, { 'notify-change-network': 'error' });
|
|
3598
|
+
}
|
|
3599
|
+
else {
|
|
3600
|
+
perfEnd(NETWORK_TRANSACTION_NAME, { 'notify-change-network': uniqueWeakEtags });
|
|
3601
|
+
}
|
|
3681
3602
|
}
|
|
3682
3603
|
/**
|
|
3683
|
-
*
|
|
3604
|
+
* Parses and aggregates weakETagZero events to be sent in summarized log line.
|
|
3605
|
+
* @param context The transaction context.
|
|
3684
3606
|
*/
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
}
|
|
3692
|
-
}
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
combined.relatedListNames = Array.from(new Set([...reqA.relatedListNames, ...reqB.relatedListNames]));
|
|
3700
|
-
return combined;
|
|
3607
|
+
aggregateWeakETagEvents(incomingWeakEtagZero, existingWeakEtagZero, apiName) {
|
|
3608
|
+
const key = 'weaketag-0-' + apiName;
|
|
3609
|
+
if (this.weakEtagZeroEvents[key] === undefined) {
|
|
3610
|
+
this.weakEtagZeroEvents[key] = {
|
|
3611
|
+
[EXISTING_WEAKETAG_0_KEY]: 0,
|
|
3612
|
+
[INCOMING_WEAKETAG_0_KEY]: 0,
|
|
3613
|
+
};
|
|
3614
|
+
}
|
|
3615
|
+
if (existingWeakEtagZero) {
|
|
3616
|
+
this.weakEtagZeroEvents[key][EXISTING_WEAKETAG_0_KEY] += 1;
|
|
3617
|
+
}
|
|
3618
|
+
if (incomingWeakEtagZero) {
|
|
3619
|
+
this.weakEtagZeroEvents[key][INCOMING_WEAKETAG_0_KEY] += 1;
|
|
3620
|
+
}
|
|
3701
3621
|
}
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3622
|
+
/**
|
|
3623
|
+
* Aggregates refresh adapter events to be sent in summarized log line.
|
|
3624
|
+
* - how many times refreshApex is called
|
|
3625
|
+
* - how many times refresh from lightning/uiRecordApi is called
|
|
3626
|
+
* - number of supported calls: refreshApex called on apex adapter
|
|
3627
|
+
* - number of unsupported calls: refreshApex on non-apex adapter
|
|
3628
|
+
* + any use of refresh from uiRecordApi module
|
|
3629
|
+
* - count of refresh calls per adapter
|
|
3630
|
+
* @param context The refresh adapter event.
|
|
3631
|
+
*/
|
|
3632
|
+
aggregateRefreshAdapterEvents(context) {
|
|
3633
|
+
// We are consolidating all apex adapter instrumentation calls under a single key
|
|
3634
|
+
// Adding additional logging that getApex adapters can invoke? Read normalizeAdapterName ts-doc.
|
|
3635
|
+
const adapterName = normalizeAdapterName(context.adapterName);
|
|
3636
|
+
if (this.lastRefreshApiCall === REFRESH_APEX_KEY) {
|
|
3637
|
+
if (isApexAdapter(adapterName)) {
|
|
3638
|
+
this.refreshApiCallEventStats[SUPPORTED_KEY] += 1;
|
|
3639
|
+
}
|
|
3640
|
+
else {
|
|
3641
|
+
this.refreshApiCallEventStats[UNSUPPORTED_KEY] += 1;
|
|
3642
|
+
}
|
|
3643
|
+
}
|
|
3644
|
+
else if (this.lastRefreshApiCall === REFRESH_UIAPI_KEY) {
|
|
3645
|
+
this.refreshApiCallEventStats[UNSUPPORTED_KEY] += 1;
|
|
3646
|
+
}
|
|
3647
|
+
if (this.refreshAdapterEvents[adapterName] === undefined) {
|
|
3648
|
+
this.refreshAdapterEvents[adapterName] = 0;
|
|
3649
|
+
}
|
|
3650
|
+
this.refreshAdapterEvents[adapterName] += 1;
|
|
3651
|
+
this.lastRefreshApiCall = null;
|
|
3709
3652
|
}
|
|
3710
3653
|
/**
|
|
3711
|
-
*
|
|
3654
|
+
* Increments call stat for incoming refresh api call, and sets the name
|
|
3655
|
+
* to be used in {@link aggregateRefreshCalls}
|
|
3656
|
+
* @param from The name of the refresh function called.
|
|
3712
3657
|
*/
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
//
|
|
3716
|
-
|
|
3717
|
-
return (context.objectApiName === request.config.parentObjectApiName &&
|
|
3718
|
-
this.isRequiredOnlyConfig(request.config));
|
|
3658
|
+
handleRefreshApiCall(apiName) {
|
|
3659
|
+
this.refreshApiCallEventStats[apiName] += 1;
|
|
3660
|
+
// set function call to be used with aggregateRefreshCalls
|
|
3661
|
+
this.lastRefreshApiCall = apiName;
|
|
3719
3662
|
}
|
|
3720
3663
|
/**
|
|
3721
|
-
*
|
|
3664
|
+
* W-7302241
|
|
3665
|
+
* Logs refresh call summary stats as a LightningInteraction.
|
|
3722
3666
|
*/
|
|
3723
|
-
|
|
3724
|
-
|
|
3667
|
+
logRefreshStats() {
|
|
3668
|
+
if (keys(this.refreshAdapterEvents).length > 0) {
|
|
3669
|
+
interaction(REFRESH_PAYLOAD_TARGET, REFRESH_PAYLOAD_SCOPE, this.refreshAdapterEvents, REFRESH_EVENTSOURCE, REFRESH_EVENTTYPE, this.refreshApiCallEventStats);
|
|
3670
|
+
this.resetRefreshStats();
|
|
3671
|
+
}
|
|
3725
3672
|
}
|
|
3726
3673
|
/**
|
|
3727
|
-
*
|
|
3674
|
+
* Resets the stat trackers for refresh call events.
|
|
3728
3675
|
*/
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3676
|
+
resetRefreshStats() {
|
|
3677
|
+
this.refreshAdapterEvents = {};
|
|
3678
|
+
this.refreshApiCallEventStats = {
|
|
3679
|
+
[REFRESH_APEX_KEY]: 0,
|
|
3680
|
+
[REFRESH_UIAPI_KEY]: 0,
|
|
3681
|
+
[SUPPORTED_KEY]: 0,
|
|
3682
|
+
[UNSUPPORTED_KEY]: 0,
|
|
3736
3683
|
};
|
|
3684
|
+
this.lastRefreshApiCall = null;
|
|
3737
3685
|
}
|
|
3738
3686
|
/**
|
|
3739
|
-
*
|
|
3740
|
-
*
|
|
3741
|
-
*
|
|
3687
|
+
* W-7801618
|
|
3688
|
+
* Counter for occurrences where the incoming record to be merged has a different apiName.
|
|
3689
|
+
* Dynamically generated metric, stored in an {@link RecordApiNameChangeCounters} object.
|
|
3742
3690
|
*
|
|
3743
|
-
*
|
|
3744
|
-
* https://gitcore.soma.salesforce.com/core-2206/core-public/blob/p4/main/core/ui-laf-components/modules/laf/batchingPortable/reducers/RelatedListInfoBatchReducer.js
|
|
3691
|
+
* @param context The transaction context.
|
|
3745
3692
|
*
|
|
3746
|
-
*
|
|
3747
|
-
* @returns GetRelatedListInfoRequest[]
|
|
3748
|
-
* @override
|
|
3693
|
+
* Note: Short-lived metric candidate, remove at the end of 230
|
|
3749
3694
|
*/
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
// make sure an entry exists for the record
|
|
3758
|
-
const relatedListIds = result.get(key) || new Set();
|
|
3759
|
-
result.set(key, relatedListIds);
|
|
3760
|
-
// add each related list to values for the record
|
|
3761
|
-
relatedListNames.forEach((list) => relatedListIds.add(list));
|
|
3762
|
-
return result;
|
|
3763
|
-
}, new Map());
|
|
3764
|
-
// return only the single requests that will NOT be covered by a batch request
|
|
3765
|
-
return this.filterRequests(unfilteredRequests).filter((entry) => {
|
|
3766
|
-
const config = entry.request.config;
|
|
3767
|
-
if (!this.isRequiredOnlyConfig(config, true)) {
|
|
3768
|
-
// requested data is customized beyond default; batch may not cover, so keep single request
|
|
3769
|
-
return true;
|
|
3770
|
-
}
|
|
3771
|
-
// key based off of parent object and its type
|
|
3772
|
-
const key = `${config.parentObjectApiName}_${config.recordTypeId}`;
|
|
3773
|
-
// keep only the lists that are not batched
|
|
3774
|
-
const batchLists = batchRequests.get(key);
|
|
3775
|
-
return !(batchLists && batchLists.has(config.relatedListId));
|
|
3776
|
-
});
|
|
3695
|
+
incrementRecordApiNameChangeCount(_incomingApiName, existingApiName) {
|
|
3696
|
+
let apiNameChangeCounter = this.recordApiNameChangeCounters[existingApiName];
|
|
3697
|
+
if (apiNameChangeCounter === undefined) {
|
|
3698
|
+
apiNameChangeCounter = counter(createMetricsKey(NAMESPACE, RECORD_API_NAME_CHANGE_COUNT_METRIC_NAME, existingApiName));
|
|
3699
|
+
this.recordApiNameChangeCounters[existingApiName] = apiNameChangeCounter;
|
|
3700
|
+
}
|
|
3701
|
+
apiNameChangeCounter.increment(1);
|
|
3777
3702
|
}
|
|
3778
3703
|
/**
|
|
3779
|
-
*
|
|
3704
|
+
* W-8620679
|
|
3705
|
+
* Increment the counter for an UnfulfilledSnapshotError coming from luvio
|
|
3780
3706
|
*
|
|
3781
|
-
* @param
|
|
3782
|
-
* @param ignoreRecordType
|
|
3707
|
+
* @param context The transaction context.
|
|
3783
3708
|
*/
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3709
|
+
incrementAdapterRequestErrorCount(context) {
|
|
3710
|
+
// We are consolidating all apex adapter instrumentation calls under a single key
|
|
3711
|
+
const adapterName = normalizeAdapterName(context.adapterName);
|
|
3712
|
+
let adapterRequestErrorCounter = this.adapterUnfulfilledErrorCounters[adapterName];
|
|
3713
|
+
if (adapterRequestErrorCounter === undefined) {
|
|
3714
|
+
adapterRequestErrorCounter = counter(createMetricsKey(OBSERVABILITY_NAMESPACE, ADAPTER_ERROR_COUNT_METRIC_NAME, adapterName));
|
|
3715
|
+
this.adapterUnfulfilledErrorCounters[adapterName] = adapterRequestErrorCounter;
|
|
3716
|
+
}
|
|
3717
|
+
adapterRequestErrorCounter.increment(1);
|
|
3718
|
+
totalAdapterErrorMetric.increment(1);
|
|
3790
3719
|
}
|
|
3791
3720
|
}
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
super(...arguments);
|
|
3797
|
-
this.adapterName = GET_RELATED_LIST_RECORDS_BATCH_ADAPTER_NAME;
|
|
3798
|
-
this.adapterFactory = getRelatedListRecordsBatchAdapterFactory;
|
|
3799
|
-
}
|
|
3800
|
-
buildConcreteRequest(similarRequest, context) {
|
|
3801
|
-
return {
|
|
3802
|
-
...similarRequest,
|
|
3803
|
-
config: {
|
|
3804
|
-
...similarRequest.config,
|
|
3805
|
-
parentRecordId: context.recordId,
|
|
3806
|
-
},
|
|
3807
|
-
};
|
|
3808
|
-
}
|
|
3809
|
-
transformForSaveSimilarRequest(request) {
|
|
3810
|
-
return this.transformForSave({
|
|
3811
|
-
...request,
|
|
3812
|
-
config: {
|
|
3813
|
-
...request.config,
|
|
3814
|
-
parentRecordId: '*',
|
|
3815
|
-
},
|
|
3816
|
-
});
|
|
3817
|
-
}
|
|
3818
|
-
isContextDependent(context, request) {
|
|
3819
|
-
return context.recordId === request.config.parentRecordId;
|
|
3820
|
-
}
|
|
3821
|
-
/**
|
|
3822
|
-
* Can combine two seperate batch requests if the parentRecordId is the same.
|
|
3823
|
-
* @param reqA The first GetRelatedListRecordsBatchConfig.
|
|
3824
|
-
* @param reqB The first GetRelatedListRecordsBatchConfig.
|
|
3825
|
-
* @returns true if the requests can be combined, otherwise false.
|
|
3826
|
-
*/
|
|
3827
|
-
canCombine(reqA, reqB) {
|
|
3828
|
-
return reqA.parentRecordId === reqB.parentRecordId;
|
|
3721
|
+
function createMetricsKey(owner, name, unit) {
|
|
3722
|
+
let metricName = name;
|
|
3723
|
+
if (unit) {
|
|
3724
|
+
metricName = metricName + '.' + unit;
|
|
3829
3725
|
}
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3726
|
+
return {
|
|
3727
|
+
get() {
|
|
3728
|
+
return { owner: owner, name: metricName };
|
|
3729
|
+
},
|
|
3730
|
+
};
|
|
3731
|
+
}
|
|
3732
|
+
/**
|
|
3733
|
+
* Returns whether adapter is an Apex one or not.
|
|
3734
|
+
* @param adapterName The name of the adapter.
|
|
3735
|
+
*/
|
|
3736
|
+
function isApexAdapter(adapterName) {
|
|
3737
|
+
return adapterName.indexOf(APEX_ADAPTER_NAME) > -1;
|
|
3738
|
+
}
|
|
3739
|
+
/**
|
|
3740
|
+
* Normalizes getApex adapter names to `Apex.getApex`. Non-Apex adapters will be prefixed with
|
|
3741
|
+
* API family, if supplied. Example: `UiApi.getRecord`.
|
|
3742
|
+
*
|
|
3743
|
+
* Note: If you are adding additional logging that can come from getApex adapter contexts that provide
|
|
3744
|
+
* the full getApex adapter name (i.e. getApex_[namespace]_[class]_[function]_[continuation]),
|
|
3745
|
+
* ensure to call this method to normalize all logging to 'getApex'. This
|
|
3746
|
+
* is because Argus has a 50k key cardinality limit. More context: W-8379680.
|
|
3747
|
+
*
|
|
3748
|
+
* @param adapterName The name of the adapter.
|
|
3749
|
+
* @param apiFamily The API family of the adapter.
|
|
3750
|
+
*/
|
|
3751
|
+
function normalizeAdapterName(adapterName, apiFamily) {
|
|
3752
|
+
if (isApexAdapter(adapterName)) {
|
|
3753
|
+
return NORMALIZED_APEX_ADAPTER_NAME;
|
|
3845
3754
|
}
|
|
3755
|
+
return apiFamily ? `${apiFamily}.${adapterName}` : adapterName;
|
|
3846
3756
|
}
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3757
|
+
const timerMetricTracker = create(null);
|
|
3758
|
+
/**
|
|
3759
|
+
* Calls instrumentation/service telemetry timer
|
|
3760
|
+
* @param name Name of the metric
|
|
3761
|
+
* @param duration number to update backing percentile histogram, negative numbers ignored
|
|
3762
|
+
*/
|
|
3763
|
+
function updateTimerMetric(name, duration) {
|
|
3764
|
+
let metric = timerMetricTracker[name];
|
|
3765
|
+
if (metric === undefined) {
|
|
3766
|
+
metric = timer(createMetricsKey(NAMESPACE, name));
|
|
3767
|
+
timerMetricTracker[name] = metric;
|
|
3853
3768
|
}
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
},
|
|
3861
|
-
};
|
|
3769
|
+
timerMetricAddDuration(metric, duration);
|
|
3770
|
+
}
|
|
3771
|
+
function timerMetricAddDuration(timer, duration) {
|
|
3772
|
+
// Guard against negative values since it causes error to be thrown by MetricsService
|
|
3773
|
+
if (duration >= 0) {
|
|
3774
|
+
timer.addDuration(duration);
|
|
3862
3775
|
}
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
*/
|
|
3876
|
-
reduce(unfilteredRequests) {
|
|
3877
|
-
// Batch requests by [parentRecordId]->[RelatedListIds]
|
|
3878
|
-
const batchRequests = unfilteredRequests.filter((entry) => entry.request.adapterName === GET_RELATED_LIST_RECORDS_BATCH_ADAPTER_NAME).reduce((acc, entry) => {
|
|
3879
|
-
// required properties, enforced by adapter typecheck
|
|
3880
|
-
const { parentRecordId, relatedListParameters } = entry.request.config;
|
|
3881
|
-
const existingRlSet = acc.get(parentRecordId) || new Set();
|
|
3882
|
-
// relatedListId enforced by adapter typecheck
|
|
3883
|
-
relatedListParameters.forEach((rlParam) => existingRlSet.add(rlParam.relatedListId));
|
|
3884
|
-
acc.set(parentRecordId, existingRlSet);
|
|
3885
|
-
return acc;
|
|
3886
|
-
}, new Map());
|
|
3887
|
-
const singleRequests = unfilteredRequests.filter((entry) => entry.request.adapterName === this.adapterName);
|
|
3888
|
-
return singleRequests.filter((entry) => {
|
|
3889
|
-
// required props enforced by adapter typecheck
|
|
3890
|
-
const { parentRecordId, relatedListId } = entry.request.config;
|
|
3891
|
-
const batchForParentRecordId = batchRequests.get(parentRecordId);
|
|
3892
|
-
return !(batchForParentRecordId && batchForParentRecordId.has(relatedListId));
|
|
3893
|
-
});
|
|
3776
|
+
}
|
|
3777
|
+
/**
|
|
3778
|
+
* W-10315098
|
|
3779
|
+
* Increments the counter associated with the request response. Counts are bucketed by status.
|
|
3780
|
+
*/
|
|
3781
|
+
const requestResponseMetricTracker = create(null);
|
|
3782
|
+
function incrementRequestResponseCount(cb) {
|
|
3783
|
+
const status = cb().status;
|
|
3784
|
+
let metric = requestResponseMetricTracker[status];
|
|
3785
|
+
if (metric === undefined) {
|
|
3786
|
+
metric = counter(createMetricsKey(OBSERVABILITY_NAMESPACE, NETWORK_ADAPTER_RESPONSE_METRIC_NAME, `${status.valueOf()}`));
|
|
3787
|
+
requestResponseMetricTracker[status] = metric;
|
|
3894
3788
|
}
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3789
|
+
metric.increment();
|
|
3790
|
+
}
|
|
3791
|
+
function logObjectInfoChanged() {
|
|
3792
|
+
logObjectInfoChanged$1();
|
|
3793
|
+
}
|
|
3794
|
+
/**
|
|
3795
|
+
* Create a new instrumentation cache stats and return it.
|
|
3796
|
+
*
|
|
3797
|
+
* @param name The cache logger name.
|
|
3798
|
+
*/
|
|
3799
|
+
function registerLdsCacheStats(name) {
|
|
3800
|
+
return registerCacheStats(`${NAMESPACE}:${name}`);
|
|
3801
|
+
}
|
|
3802
|
+
/**
|
|
3803
|
+
* Add or overwrite hooks that require aura implementations
|
|
3804
|
+
*/
|
|
3805
|
+
function setAuraInstrumentationHooks() {
|
|
3806
|
+
instrument({
|
|
3807
|
+
recordConflictsResolved: (serverRequestCount) => {
|
|
3808
|
+
// Ignore 0 values which can originate from ADS bridge
|
|
3809
|
+
if (serverRequestCount > 0) {
|
|
3810
|
+
updatePercentileHistogramMetric('record-conflicts-resolved', serverRequestCount);
|
|
3811
|
+
}
|
|
3812
|
+
},
|
|
3813
|
+
nullDisplayValueConflict: ({ fieldType, areValuesEqual }) => {
|
|
3814
|
+
const metricName = `merge-null-dv-count.${fieldType}`;
|
|
3815
|
+
if (fieldType === 'scalar') {
|
|
3816
|
+
incrementCounterMetric(`${metricName}.${areValuesEqual}`);
|
|
3817
|
+
}
|
|
3818
|
+
else {
|
|
3819
|
+
incrementCounterMetric(metricName);
|
|
3820
|
+
}
|
|
3821
|
+
},
|
|
3822
|
+
getRecordNotifyChangeAllowed: incrementGetRecordNotifyChangeAllowCount,
|
|
3823
|
+
getRecordNotifyChangeDropped: incrementGetRecordNotifyChangeDropCount,
|
|
3824
|
+
notifyRecordUpdateAvailableAllowed: incrementNotifyRecordUpdateAvailableAllowCount,
|
|
3825
|
+
notifyRecordUpdateAvailableDropped: incrementNotifyRecordUpdateAvailableDropCount,
|
|
3826
|
+
recordApiNameChanged: instrumentation.incrementRecordApiNameChangeCount.bind(instrumentation),
|
|
3827
|
+
weakEtagZero: instrumentation.aggregateWeakETagEvents.bind(instrumentation),
|
|
3828
|
+
getRecordNotifyChangeNetworkResult: instrumentation.notifyChangeNetwork.bind(instrumentation),
|
|
3829
|
+
});
|
|
3830
|
+
withRegistration('@salesforce/lds-adapters-uiapi', (reg) => setLdsAdaptersUiapiInstrumentation(reg));
|
|
3831
|
+
instrument$1({
|
|
3832
|
+
logCrud: logCRUDLightningInteraction,
|
|
3833
|
+
networkResponse: incrementRequestResponseCount,
|
|
3834
|
+
});
|
|
3835
|
+
instrument$2({
|
|
3836
|
+
error: logError,
|
|
3837
|
+
});
|
|
3838
|
+
instrument$3({
|
|
3839
|
+
refreshCalled: instrumentation.handleRefreshApiCall.bind(instrumentation),
|
|
3840
|
+
instrumentAdapter: instrumentation.instrumentAdapter.bind(instrumentation),
|
|
3841
|
+
});
|
|
3842
|
+
instrument$4({
|
|
3843
|
+
timerMetricAddDuration: updateTimerMetric,
|
|
3844
|
+
});
|
|
3845
|
+
// Our getRecord through aggregate-ui CRUD logging has moved
|
|
3846
|
+
// to lds-network-adapter. We still need to respect the
|
|
3847
|
+
// orgs environment setting
|
|
3848
|
+
if (forceRecordTransactionsDisabled$1 === false) {
|
|
3849
|
+
ldsNetworkAdapterInstrument({
|
|
3850
|
+
getRecordAggregateResolve: (cb) => {
|
|
3851
|
+
const { recordId, apiName } = cb();
|
|
3852
|
+
logCRUDLightningInteraction('read', {
|
|
3853
|
+
recordId,
|
|
3854
|
+
recordType: apiName,
|
|
3855
|
+
state: 'SUCCESS',
|
|
3856
|
+
});
|
|
3857
|
+
},
|
|
3858
|
+
getRecordAggregateReject: (cb) => {
|
|
3859
|
+
const recordId = cb();
|
|
3860
|
+
logCRUDLightningInteraction('read', {
|
|
3861
|
+
recordId,
|
|
3862
|
+
state: 'ERROR',
|
|
3863
|
+
});
|
|
3901
3864
|
},
|
|
3902
3865
|
});
|
|
3903
3866
|
}
|
|
3904
|
-
|
|
3905
|
-
return context.recordId === request.config.parentRecordId;
|
|
3906
|
-
}
|
|
3867
|
+
withRegistration('@salesforce/lds-network-adapter', (reg) => setLdsNetworkAdapterInstrumentation(reg));
|
|
3907
3868
|
}
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
};
|
|
3918
|
-
function getApexPdlFactory(luvio) {
|
|
3919
|
-
return ({ invokerParams, config }, requestContext) => {
|
|
3920
|
-
return GetApexWireAdapterFactory(luvio, invokerParams)(config, requestContext);
|
|
3921
|
-
};
|
|
3869
|
+
/**
|
|
3870
|
+
* Initialize the instrumentation and instrument the LDS instance and the InMemoryStore.
|
|
3871
|
+
*
|
|
3872
|
+
* @param luvio The Luvio instance to instrument.
|
|
3873
|
+
* @param store The InMemoryStore to instrument.
|
|
3874
|
+
*/
|
|
3875
|
+
function setupInstrumentation(luvio, store) {
|
|
3876
|
+
setupInstrumentation$1(luvio, store);
|
|
3877
|
+
setAuraInstrumentationHooks();
|
|
3922
3878
|
}
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3879
|
+
/**
|
|
3880
|
+
* Note: locator.scope is set to 'force_record' in order for the instrumentation gate to work, which will
|
|
3881
|
+
* disable all crud operations if it is on.
|
|
3882
|
+
* @param eventSource - Source of the logging event.
|
|
3883
|
+
* @param attributes - Free form object of attributes to log.
|
|
3884
|
+
*/
|
|
3885
|
+
function logCRUDLightningInteraction(eventSource, attributes) {
|
|
3886
|
+
interaction(eventSource, 'force_record', null, eventSource, 'crud', attributes);
|
|
3887
|
+
}
|
|
3888
|
+
const instrumentation = new Instrumentation();
|
|
3889
|
+
|
|
3890
|
+
class ApplicationPredictivePrefetcher {
|
|
3891
|
+
constructor(context, repository, requestRunner) {
|
|
3892
|
+
this.repository = repository;
|
|
3893
|
+
this.requestRunner = requestRunner;
|
|
3894
|
+
this.isRecording = false;
|
|
3895
|
+
this.totalRequestCount = 0;
|
|
3896
|
+
this.queuedPredictionRequests = [];
|
|
3897
|
+
this._context = context;
|
|
3898
|
+
this.page = this.getPage();
|
|
3928
3899
|
}
|
|
3929
|
-
|
|
3930
|
-
|
|
3900
|
+
set context(value) {
|
|
3901
|
+
this._context = value;
|
|
3902
|
+
this.page = this.getPage();
|
|
3931
3903
|
}
|
|
3932
|
-
|
|
3933
|
-
return
|
|
3904
|
+
get context() {
|
|
3905
|
+
return this._context;
|
|
3934
3906
|
}
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
this.
|
|
3942
|
-
this.
|
|
3907
|
+
async stopRecording() {
|
|
3908
|
+
this.isRecording = false;
|
|
3909
|
+
this.totalRequestCount = 0;
|
|
3910
|
+
await this.repository.flushRequestsToStorage();
|
|
3911
|
+
}
|
|
3912
|
+
startRecording() {
|
|
3913
|
+
this.isRecording = true;
|
|
3914
|
+
this.repository.markPageStart();
|
|
3915
|
+
this.repository.clearRequestBuffer();
|
|
3916
|
+
}
|
|
3917
|
+
saveRequest(request) {
|
|
3918
|
+
if (!this.isRecording) {
|
|
3919
|
+
return;
|
|
3920
|
+
}
|
|
3921
|
+
executeAsyncActivity(METRIC_KEYS.PREDICTIVE_DATA_LOADING_SAVE_REQUEST, (_act) => {
|
|
3922
|
+
if (this.page.supportsRequest(request)) {
|
|
3923
|
+
const saveBuckets = this.page.buildSaveRequestData(request);
|
|
3924
|
+
saveBuckets.forEach((saveBucket) => {
|
|
3925
|
+
const { request: requestToSave, context } = saveBucket;
|
|
3926
|
+
// No need to differentiate from predictions requests because these
|
|
3927
|
+
// are made from the adapters factory, which are not prediction aware.
|
|
3928
|
+
this.repository.saveRequest(context, requestToSave);
|
|
3929
|
+
});
|
|
3930
|
+
}
|
|
3931
|
+
return Promise.resolve().then();
|
|
3932
|
+
}, PDL_EXECUTE_ASYNC_OPTIONS);
|
|
3943
3933
|
}
|
|
3944
|
-
|
|
3945
|
-
|
|
3934
|
+
async predict() {
|
|
3935
|
+
const alwaysRequests = this.page.getAlwaysRunRequests();
|
|
3936
|
+
const similarPageRequests = await this.getSimilarPageRequests();
|
|
3937
|
+
const exactPageRequests = await this.getExactPageRequest();
|
|
3938
|
+
// Always requests can't be reduced in - Some of them are essential to keep the page rendering at the beginning.
|
|
3939
|
+
const reducedRequests = this.requestRunner
|
|
3940
|
+
.reduceRequests([...exactPageRequests, ...similarPageRequests])
|
|
3941
|
+
.map((entry) => entry.request);
|
|
3942
|
+
const predictedRequests = [...alwaysRequests, ...reducedRequests];
|
|
3943
|
+
this.queuedPredictionRequests.push(...predictedRequests);
|
|
3944
|
+
this.totalRequestCount = predictedRequests.length;
|
|
3945
|
+
return Promise.all(predictedRequests.map((request) => this.requestRunner.runRequest(request))).then();
|
|
3946
3946
|
}
|
|
3947
|
-
|
|
3947
|
+
getPredictionSummary() {
|
|
3948
|
+
const exactPageRequests = this.repository.getPageRequests(this.context) || [];
|
|
3949
|
+
const similarPageRequests = this.page.similarContext !== undefined
|
|
3950
|
+
? this.repository.getPageRequests(this.page.similarContext)
|
|
3951
|
+
: [];
|
|
3948
3952
|
return {
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
// (!): if we are saving this request is because the adapter already verified is valid.
|
|
3953
|
-
objectApiName: coerceObjectId(request.config.objectApiName),
|
|
3954
|
-
},
|
|
3953
|
+
exact: exactPageRequests.length,
|
|
3954
|
+
similar: similarPageRequests.length,
|
|
3955
|
+
totalRequestCount: this.totalRequestCount,
|
|
3955
3956
|
};
|
|
3956
3957
|
}
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3958
|
+
hasPredictions() {
|
|
3959
|
+
const summary = this.getPredictionSummary();
|
|
3960
|
+
return summary.exact > 0 || summary.similar > 0;
|
|
3960
3961
|
}
|
|
3961
|
-
|
|
3962
|
-
|
|
3962
|
+
getSimilarPageRequests() {
|
|
3963
|
+
let resolvedSimilarPageRequests = [];
|
|
3964
|
+
if (this.page.similarContext !== undefined) {
|
|
3965
|
+
const similarPageRequests = this.repository.getPageRequests(this.page.similarContext);
|
|
3966
|
+
if (similarPageRequests !== undefined) {
|
|
3967
|
+
resolvedSimilarPageRequests = similarPageRequests.map((entry) => {
|
|
3968
|
+
return {
|
|
3969
|
+
...entry,
|
|
3970
|
+
request: this.page.resolveSimilarRequest(entry.request),
|
|
3971
|
+
};
|
|
3972
|
+
});
|
|
3973
|
+
}
|
|
3974
|
+
}
|
|
3975
|
+
return resolvedSimilarPageRequests;
|
|
3976
|
+
}
|
|
3977
|
+
getExactPageRequest() {
|
|
3978
|
+
return this.repository.getPageRequests(this.context) || [];
|
|
3963
3979
|
}
|
|
3964
3980
|
}
|
|
3965
3981
|
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3982
|
+
/**
|
|
3983
|
+
* Runs a list of requests with a specified concurrency limit.
|
|
3984
|
+
*
|
|
3985
|
+
* @template Request - The type of the requests being processed.
|
|
3986
|
+
* @param {RequestEntry<Request>[]} requests - An array of request entries to be processed in the array order.
|
|
3987
|
+
* @param {RequestRunner<Request>} runner - The runner instance responsible for executing the requests.
|
|
3988
|
+
* @param {number} concurrentRequestsLimit - The maximum number of concurrent requests allowed.
|
|
3989
|
+
* @param {number} pageStartTime - The start time of the page load, used to calculate the time elapsed since the page starts loading.
|
|
3990
|
+
* @returns {Promise<void>} A promise that resolves when all requests have been processed.
|
|
3991
|
+
*
|
|
3992
|
+
* This function manages a queue of pending requests and processes them with a concurrency limit.
|
|
3993
|
+
* Requests are only processed if their `requestTime` is less than the time elapsed since `pageStartTime`.
|
|
3994
|
+
*/
|
|
3995
|
+
async function runRequestsWithLimit(requests, runner, concurrentRequestsLimit, pageStartTime) {
|
|
3996
|
+
// queue for pending prediction requests
|
|
3997
|
+
const requestQueue = [...requests];
|
|
3998
|
+
// Function to process the next request in the queue
|
|
3999
|
+
const processNextRequest = async (verifyPastTime = true) => {
|
|
4000
|
+
const timeInWaterfall = Date.now() - pageStartTime;
|
|
4001
|
+
while (requestQueue.length > 0 &&
|
|
4002
|
+
verifyPastTime &&
|
|
4003
|
+
requestQueue[0].requestMetadata.requestTime <= timeInWaterfall) {
|
|
4004
|
+
requestQueue.shift();
|
|
4005
|
+
}
|
|
4006
|
+
if (requestQueue.length > 0) {
|
|
4007
|
+
// (!) requestQueue will always have at least one element ensured by the above check.
|
|
4008
|
+
const nextRequest = requestQueue.shift();
|
|
4009
|
+
try {
|
|
4010
|
+
// Run the request and wait for it to complete
|
|
4011
|
+
await runner.runRequest(nextRequest.request);
|
|
4012
|
+
}
|
|
4013
|
+
finally {
|
|
4014
|
+
await processNextRequest();
|
|
4015
|
+
}
|
|
4016
|
+
}
|
|
4017
|
+
};
|
|
4018
|
+
// Start processing requests up to concurrentRequestsLimit
|
|
4019
|
+
const initialRequests = Math.min(concurrentRequestsLimit, requestQueue.length);
|
|
4020
|
+
const promises = [];
|
|
4021
|
+
for (let i = 0; i < initialRequests; i++) {
|
|
4022
|
+
// Initial requests should always execute, without verifying if they are past due.
|
|
4023
|
+
// Reasoning:
|
|
4024
|
+
// It may be that one of the alwaysRequest (with 0 as start time) that is reduced
|
|
4025
|
+
// with the regular requests to make these to have 0 as the initial time in the waterfall.
|
|
4026
|
+
// Because predictions are behind an await (see W-16139321), it could be that when this code is evaluated
|
|
4027
|
+
// is already past time for the request.
|
|
4028
|
+
promises.push(processNextRequest(false));
|
|
3994
4029
|
}
|
|
4030
|
+
// Wait for all initial requests to complete
|
|
4031
|
+
await Promise.all(promises);
|
|
3995
4032
|
}
|
|
3996
4033
|
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4034
|
+
function isBoxcarableRequest({ request: { adapterName } }, requestStrategyManager) {
|
|
4035
|
+
const strategy = requestStrategyManager.get(adapterName);
|
|
4036
|
+
return strategy === undefined || strategy.isBoxcarable;
|
|
4037
|
+
}
|
|
4038
|
+
function predictNonBoxcarableRequest(nonBoxcaredPredictions, requestRunner) {
|
|
4039
|
+
const reducedPredictions = requestRunner.reduceRequests(nonBoxcaredPredictions);
|
|
4040
|
+
reducedPredictions.map((request) => requestRunner.runRequest(request.request));
|
|
4041
|
+
}
|
|
4042
|
+
class LexPredictivePrefetcher extends ApplicationPredictivePrefetcher {
|
|
4043
|
+
constructor(context, repository, requestRunner,
|
|
4044
|
+
// These strategies need to be in sync with the "predictiveDataLoadCapable" list
|
|
4045
|
+
// from scripts/lds-uiapi-plugin.js
|
|
4046
|
+
requestStrategyManager, options) {
|
|
4047
|
+
super(context, repository, requestRunner);
|
|
4048
|
+
this.options = options;
|
|
4049
|
+
this.requestStrategyManager = requestStrategyManager;
|
|
4050
|
+
this.page = this.getPage();
|
|
4003
4051
|
}
|
|
4004
|
-
|
|
4005
|
-
|
|
4052
|
+
getPage() {
|
|
4053
|
+
if (RecordHomePage.handlesContext(this.context)) {
|
|
4054
|
+
return new RecordHomePage(this.context, this.requestStrategyManager, this.options);
|
|
4055
|
+
}
|
|
4056
|
+
else if (ObjectHomePage.handlesContext(this.context)) {
|
|
4057
|
+
return new ObjectHomePage(this.context, this.requestStrategyManager, this.options);
|
|
4058
|
+
}
|
|
4059
|
+
return new LexDefaultPage(this.context);
|
|
4006
4060
|
}
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
//
|
|
4022
|
-
//
|
|
4023
|
-
//
|
|
4024
|
-
//
|
|
4025
|
-
|
|
4061
|
+
getAllPageRequests() {
|
|
4062
|
+
const exactPageRequests = this.getExactPageRequest();
|
|
4063
|
+
let similarPageRequests = this.getSimilarPageRequests();
|
|
4064
|
+
if (exactPageRequests.length > 0 && this.options.useExactMatchesPlus === true) {
|
|
4065
|
+
similarPageRequests = similarPageRequests.filter((requestEntry) => {
|
|
4066
|
+
const strategy = this.requestStrategyManager.get(requestEntry.request.adapterName);
|
|
4067
|
+
return strategy && strategy.onlySavedInSimilar;
|
|
4068
|
+
});
|
|
4069
|
+
}
|
|
4070
|
+
return [...exactPageRequests, ...similarPageRequests];
|
|
4071
|
+
}
|
|
4072
|
+
async predict() {
|
|
4073
|
+
const alwaysRequests = this.page.getAlwaysRunRequests();
|
|
4074
|
+
const pageRequests = this.getAllPageRequests();
|
|
4075
|
+
// IMPORTANT: Because there's no way to diferentiate a cmpDef prediction from the page
|
|
4076
|
+
// requesting the cmpDef, we need to predict cmpDefs before we start watching
|
|
4077
|
+
// for predictions in the page. Having this code after an
|
|
4078
|
+
// await will make the predictions to be saved as predictions too.
|
|
4079
|
+
predictNonBoxcarableRequest(pageRequests.filter((r) => !isBoxcarableRequest(r, this.requestStrategyManager)), this.requestRunner);
|
|
4080
|
+
const alwaysRequestEntries = alwaysRequests.map((request) => {
|
|
4081
|
+
return {
|
|
4082
|
+
request,
|
|
4083
|
+
requestMetadata: { requestTime: 0 }, // ensures always requests are executed, and executed first.
|
|
4084
|
+
};
|
|
4085
|
+
});
|
|
4086
|
+
const boxcarablePredictions = pageRequests.filter((r) => isBoxcarableRequest(r, this.requestStrategyManager));
|
|
4087
|
+
const reducedPredictions = this.page.shouldReduceAlwaysRequestsWithPredictions()
|
|
4088
|
+
? this.requestRunner.reduceRequests([...boxcarablePredictions, ...alwaysRequestEntries])
|
|
4089
|
+
: this.requestRunner.reduceRequests(boxcarablePredictions);
|
|
4090
|
+
const predictedRequestsWithLimit = (this.page.shouldExecuteAlwaysRequestByThemself()
|
|
4091
|
+
? [...alwaysRequestEntries, ...reducedPredictions]
|
|
4092
|
+
: reducedPredictions)
|
|
4093
|
+
// Sorting in order requested
|
|
4094
|
+
.sort((a, b) => a.requestMetadata.requestTime - b.requestMetadata.requestTime);
|
|
4095
|
+
this.totalRequestCount = predictedRequestsWithLimit.length;
|
|
4096
|
+
await runRequestsWithLimit(predictedRequestsWithLimit, this.requestRunner, this.options.inflightRequestLimit,
|
|
4097
|
+
// `this.repository.pageStartTime` would be the correct here,
|
|
4098
|
+
// but when doing predict+watch, it could be (set in watch)
|
|
4099
|
+
// repository.startTime is not set yet, Date.now() is a better alternative,
|
|
4100
|
+
// that is correct, and works on both cases.
|
|
4101
|
+
Date.now());
|
|
4026
4102
|
}
|
|
4027
4103
|
}
|
|
4028
4104
|
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
this.adapterName = GET_LIST_OBJECT_INFO_ADAPTER_NAME;
|
|
4034
|
-
this.adapterFactory = getListObjectInfoAdapterFactory;
|
|
4105
|
+
class LexRequestRunner {
|
|
4106
|
+
constructor(requestStrategyManager) {
|
|
4107
|
+
this.requestStrategyManager = requestStrategyManager;
|
|
4108
|
+
this.requestStrategyManager = requestStrategyManager;
|
|
4035
4109
|
}
|
|
4036
|
-
|
|
4037
|
-
return
|
|
4110
|
+
reduceRequests(requests) {
|
|
4111
|
+
return this.requestStrategyManager
|
|
4112
|
+
.getAll()
|
|
4113
|
+
.map((strategy) => strategy.reduce(requests))
|
|
4114
|
+
.flat();
|
|
4038
4115
|
}
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
config
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
objectApiName: coerceObjectId(request.config.objectApiName),
|
|
4046
|
-
},
|
|
4047
|
-
};
|
|
4116
|
+
runRequest(request) {
|
|
4117
|
+
const strategy = this.requestStrategyManager.get(request.adapterName);
|
|
4118
|
+
if (strategy) {
|
|
4119
|
+
return Promise.resolve(strategy.execute(request.config)).then();
|
|
4120
|
+
}
|
|
4121
|
+
return Promise.resolve(undefined);
|
|
4048
4122
|
}
|
|
4049
4123
|
}
|
|
4050
4124
|
|
|
@@ -4173,7 +4247,7 @@ function getEnvironmentSetting(name) {
|
|
|
4173
4247
|
}
|
|
4174
4248
|
return undefined;
|
|
4175
4249
|
}
|
|
4176
|
-
// version: 1.
|
|
4250
|
+
// version: 1.315.0-b7eff13c6d
|
|
4177
4251
|
|
|
4178
4252
|
const forceRecordTransactionsDisabled = getEnvironmentSetting(EnvironmentSettings.ForceRecordTransactionsDisabled);
|
|
4179
4253
|
//TODO: Some duplication here that can be most likely moved to a util class
|
|
@@ -4388,12 +4462,40 @@ function setRejectConfig(request, error) {
|
|
|
4388
4462
|
};
|
|
4389
4463
|
}
|
|
4390
4464
|
|
|
4391
|
-
const
|
|
4392
|
-
//
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4465
|
+
const API_PATHS = [
|
|
4466
|
+
// getLookupActions
|
|
4467
|
+
'/ui-api/actions/lookup/{objectApiNames}',
|
|
4468
|
+
// getRecordActions
|
|
4469
|
+
'/ui-api/actions/record/{recordIds}',
|
|
4470
|
+
// getRelatedListActions
|
|
4471
|
+
'/ui-api/actions/record/{recordIds}/related-list/{relatedListId}',
|
|
4472
|
+
// getDuplicateConfiguration
|
|
4473
|
+
'/ui-api/duplicates/{objectApiName}',
|
|
4474
|
+
// getListInfosByObjectName
|
|
4475
|
+
'/ui-api/list-info/{objectApiName}',
|
|
4476
|
+
// getLookupRecords
|
|
4477
|
+
'/ui-api/lookups/{objectApiName}/{fieldApiName}',
|
|
4478
|
+
// getObjectInfo
|
|
4479
|
+
'/ui-api/object-info/{objectApiName}',
|
|
4480
|
+
// getObjectInfos
|
|
4481
|
+
'/ui-api/object-info/batch/{objectApiNames}',
|
|
4482
|
+
// getPicklistValuesByRecordType
|
|
4483
|
+
'/ui-api/object-info/{objectApiName}/picklist-values/{recordTypeId}',
|
|
4484
|
+
// getDuplicates
|
|
4485
|
+
'/ui-api/predupe',
|
|
4486
|
+
// getRecord
|
|
4487
|
+
'/ui-api/records/{recordId}',
|
|
4488
|
+
// getRecordUi
|
|
4489
|
+
'/ui-api/record-ui/{recordIds}',
|
|
4490
|
+
// getRelatedListInfo
|
|
4491
|
+
'/ui-api/related-list-info/{parentObjectApiName}/{relatedListId}',
|
|
4492
|
+
// getRelatedListRecords
|
|
4493
|
+
'/ui-api/related-list-records/{parentRecordId}/{relatedListId}',
|
|
4494
|
+
];
|
|
4495
|
+
const API_PATH_MATCHERS = API_PATHS.map((path) => {
|
|
4496
|
+
const regexString = path.replace(/\{.+?\}/g, '[^/]+');
|
|
4497
|
+
return new RegExp(`^${regexString}$`);
|
|
4498
|
+
});
|
|
4397
4499
|
const modifyLexResourceRequest = function (resourceRequest, jwtToken) {
|
|
4398
4500
|
const jwtBaseUri = jwtToken.decodedInfo.iss;
|
|
4399
4501
|
return {
|
|
@@ -4419,8 +4521,11 @@ const requestLogger = {
|
|
|
4419
4521
|
};
|
|
4420
4522
|
const composedFetchNetworkAdapter = {
|
|
4421
4523
|
shouldHandleRequest(resourceRequest) {
|
|
4422
|
-
|
|
4423
|
-
|
|
4524
|
+
let path = resourceRequest.basePath.trim();
|
|
4525
|
+
if (path.endsWith('/')) {
|
|
4526
|
+
path = path.substring(0, path.length - 1);
|
|
4527
|
+
}
|
|
4528
|
+
return API_PATH_MATCHERS.some((matcher) => matcher.test(path));
|
|
4424
4529
|
},
|
|
4425
4530
|
adapter: setupLexJwtNetworkAdapter(auraNetworkAdapter, modifyLexResourceRequest, requestTracker, requestLogger),
|
|
4426
4531
|
};
|
|
@@ -4468,6 +4573,51 @@ function setupMetadataWatcher(luvio) {
|
|
|
4468
4573
|
});
|
|
4469
4574
|
}
|
|
4470
4575
|
|
|
4576
|
+
class RequestStrategyManager {
|
|
4577
|
+
constructor(requestStrategies = []) {
|
|
4578
|
+
this.requestStrategies = [];
|
|
4579
|
+
this.requestStrategiesByAdapterName = new Map();
|
|
4580
|
+
this.register(requestStrategies);
|
|
4581
|
+
}
|
|
4582
|
+
setRequestStrategy(requestStrategy) {
|
|
4583
|
+
const adapterName = requestStrategy.adapterName;
|
|
4584
|
+
if (!this.requestStrategiesByAdapterName.has(adapterName)) {
|
|
4585
|
+
this.requestStrategiesByAdapterName.set(adapterName, requestStrategy);
|
|
4586
|
+
this.requestStrategies.push(requestStrategy);
|
|
4587
|
+
return true;
|
|
4588
|
+
}
|
|
4589
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
4590
|
+
throw new Error(`Registered an already existing request strategy: ${adapterName}`);
|
|
4591
|
+
}
|
|
4592
|
+
return false;
|
|
4593
|
+
}
|
|
4594
|
+
deleteRequestStrategy(requestStrategy) {
|
|
4595
|
+
const adapterName = requestStrategy.adapterName;
|
|
4596
|
+
if (this.requestStrategiesByAdapterName.has(adapterName)) {
|
|
4597
|
+
this.requestStrategiesByAdapterName.delete(adapterName);
|
|
4598
|
+
const indexToRemove = this.requestStrategies.findIndex((strategy) => strategy === requestStrategy);
|
|
4599
|
+
this.requestStrategies.splice(indexToRemove, 1);
|
|
4600
|
+
return true;
|
|
4601
|
+
}
|
|
4602
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
4603
|
+
throw new Error(`Could not find existing request strategy to unregister: ${adapterName}`);
|
|
4604
|
+
}
|
|
4605
|
+
return false;
|
|
4606
|
+
}
|
|
4607
|
+
register(requestStrategies) {
|
|
4608
|
+
return requestStrategies.map((requestStrategy) => this.setRequestStrategy(requestStrategy));
|
|
4609
|
+
}
|
|
4610
|
+
unregister(requestStrategies) {
|
|
4611
|
+
return requestStrategies.map((requestStrategy) => this.deleteRequestStrategy(requestStrategy));
|
|
4612
|
+
}
|
|
4613
|
+
get(adapterName) {
|
|
4614
|
+
return this.requestStrategiesByAdapterName.get(adapterName);
|
|
4615
|
+
}
|
|
4616
|
+
getAll() {
|
|
4617
|
+
return this.requestStrategies;
|
|
4618
|
+
}
|
|
4619
|
+
}
|
|
4620
|
+
|
|
4471
4621
|
// This code *should* be in lds-network-adapter, but when combined with the Aura
|
|
4472
4622
|
// component test workaround in lds-default-luvio it creates a circular dependecy
|
|
4473
4623
|
// between lds-default-luvio and lds-network-adapter. We do the register on behalf
|
|
@@ -4504,6 +4654,25 @@ function getInflightRequestLimit() {
|
|
|
4504
4654
|
return HARDCODED_REQUEST_LIMIT;
|
|
4505
4655
|
}
|
|
4506
4656
|
}
|
|
4657
|
+
const requestStrategyManager = new RequestStrategyManager();
|
|
4658
|
+
/**
|
|
4659
|
+
* Registers a request strategy to be utilized by PDL.
|
|
4660
|
+
* @param requestStrategy - {@link LexRequestStrategy} The request strategy/strategies to register.
|
|
4661
|
+
* @returns boolean | boolean[] - true/false depending on registration success.
|
|
4662
|
+
*/
|
|
4663
|
+
function registerRequestStrategy(...requestStrategy) {
|
|
4664
|
+
const result = requestStrategyManager.register(requestStrategy);
|
|
4665
|
+
return result.length === 1 ? result[0] : result;
|
|
4666
|
+
}
|
|
4667
|
+
/**
|
|
4668
|
+
* Unregisters a request strategy to be utilized by PDL.
|
|
4669
|
+
* @param requestStrategy - {@link LexRequestStrategy} The request strategy to unregister.
|
|
4670
|
+
* @returns boolean | boolean[] - true/false depending on unregistration success.
|
|
4671
|
+
*/
|
|
4672
|
+
function unregisterRequestStrategy(...requestStrategy) {
|
|
4673
|
+
const result = requestStrategyManager.unregister(requestStrategy);
|
|
4674
|
+
return result.length === 1 ? result[0] : result;
|
|
4675
|
+
}
|
|
4507
4676
|
function setupPredictivePrefetcher(luvio) {
|
|
4508
4677
|
const allStrategies = [
|
|
4509
4678
|
new GetRecordRequestStrategy(luvio),
|
|
@@ -4524,9 +4693,9 @@ function setupPredictivePrefetcher(luvio) {
|
|
|
4524
4693
|
new GetListInfosByObjectNameRequestStrategy(luvio),
|
|
4525
4694
|
new GetListObjectInfoRequestStrategy(luvio),
|
|
4526
4695
|
];
|
|
4696
|
+
requestStrategyManager.register(allStrategies);
|
|
4527
4697
|
const storage = buildAuraPrefetchStorage();
|
|
4528
|
-
const requestRunner = new LexRequestRunner();
|
|
4529
|
-
requestRunner.setRequestStrategies(allStrategies);
|
|
4698
|
+
const requestRunner = new LexRequestRunner(requestStrategyManager);
|
|
4530
4699
|
const repository = new PrefetchRepository(storage, {
|
|
4531
4700
|
modifyBeforeSaveHook: (requests) => requestRunner.reduceRequests(requests),
|
|
4532
4701
|
});
|
|
@@ -4538,7 +4707,7 @@ function setupPredictivePrefetcher(luvio) {
|
|
|
4538
4707
|
inflightRequestLimit,
|
|
4539
4708
|
useExactMatchesPlus,
|
|
4540
4709
|
};
|
|
4541
|
-
const prefetcher = new LexPredictivePrefetcher({ context: 'unknown' }, repository, requestRunner,
|
|
4710
|
+
const prefetcher = new LexPredictivePrefetcher({ context: 'unknown' }, repository, requestRunner, requestStrategyManager, prefetcherOptions);
|
|
4542
4711
|
registerPrefetcher(luvio, prefetcher);
|
|
4543
4712
|
if (useApexPredictions.isOpen({ fallback: false })) {
|
|
4544
4713
|
registerPrefetcher$1(luvio, prefetcher);
|
|
@@ -4570,6 +4739,11 @@ function loadComponentsDefStartedOverride(...args) {
|
|
|
4570
4739
|
}
|
|
4571
4740
|
return ret;
|
|
4572
4741
|
}
|
|
4742
|
+
function saveRequestAsPrediction(request) {
|
|
4743
|
+
if (__lexPrefetcher !== undefined) {
|
|
4744
|
+
__lexPrefetcher.saveRequest(request);
|
|
4745
|
+
}
|
|
4746
|
+
}
|
|
4573
4747
|
/**
|
|
4574
4748
|
* @typedef {Object} RecordHomePageContext
|
|
4575
4749
|
* @property {string} objectApiName - The API name of the object.
|
|
@@ -4738,5 +4912,5 @@ function ldsEngineCreator() {
|
|
|
4738
4912
|
return { name: 'ldsEngineCreator' };
|
|
4739
4913
|
}
|
|
4740
4914
|
|
|
4741
|
-
export { buildPredictorForContext, ldsEngineCreator as default, initializeLDS, initializeOneStore };
|
|
4742
|
-
// version: 1.
|
|
4915
|
+
export { LexRequestStrategy, buildPredictorForContext, ldsEngineCreator as default, initializeLDS, initializeOneStore, registerRequestStrategy, saveRequestAsPrediction, unregisterRequestStrategy };
|
|
4916
|
+
// version: 1.315.0-8ef4c90baf
|