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