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