Salesforce.com - Custom Real-time UX insight
Customer
United Healthcare
Description
Here is an example of how this Client is getting more UX insights with Germain that monitors its SalesForce.com Users, 24x7.
This client has built a custom applet on SalesForce.com (Summer 21 release), to allow SFDC users to do a “quick search”. they needed specific insights from that applet, which they are now getting 24x7 and in near real-time.
Custom Quick Search applet:

Germain dashboard reporting realtime UX insights about this Quick Search applet:
(These are not production data. these are data coming from a testing environment)


Note
Similar benefits can be provide to other apps, this is not specific to SalesForce.com.
Configuration
These lines were added to germainapm-salesforce-init.js:
/* global settings */
// typeof settings = { agent: { servicesUrl; monitoringProfileName; appName; serverHost; }; profile: UxMonitoringProfile; plugins; constants; application; }
settings.plugins = {
RequestMonitoring: {
enabled: settings.profile.networkRequestsMonitoring,
queryStringGenerator: xhrQueryStringGenerator,
taggingEnabled: false,
headerProcessor: GermainAPMSalesforceUtils.headerProcessor
},
ChangeMonitoring: {
enabled: true,
eventInit: "page_ready"
},
ClickMonitoring: {
enabled: true,
fullMonitoringEnabled: true,
frameMonitoringEnabled: false,
clickContainerLookup: GermainAPMSalesforceUtils.clickContainerLookup,
additionalInfoLookup: clickAdditionalInfoLookup,
eventInit: "page_ready"
},
ConsoleMonitoring: {
enabled: true,
errorEventListenerEnabled: false,
consoleMethods: ['log', 'error', 'warn'],
additionalInfoLookup: GermainAPMSalesforceUtils.consoleAdditionalInfoLookup
},
CpuMonitoring: { enabled: false, repeatSeconds: 60, samplesAveragedPerRound: 12, sampleTimeMillis: 2000, eventInit: "page_ready" },
DomMonitoring: {
enabled: settings.profile.sessionReplayMonitoring, compressContent: false,
pushInterval: 10, pushFullInterval: 300, dataTimeout: 30000,
excludeAriaAttributes: true /,excludeAttributes:[],excludeIds:[]/
},
FocusMonitoring: { enabled: settings.profile.sessionReplayMonitoring, pushInterval: 15, eventInit: "page_ready" },
IframeMonitoring: { enabled: settings.profile.networkRequestsMonitoring, eventInit: "page_ready" },
InactivityMonitoring: { enabled: true, eventInit: "page_ready", threshold: 30 },
InputMonitoring: { enabled: settings.profile.sessionReplayMonitoring },
KeyboardMonitoring: { enabled: true, eventInit: "page_ready" },
MediaPlayback: { enabled: settings.profile.sessionReplayMonitoring },
MemoryMonitoring: { enabled: true, eventInit: "page_ready" },
MouseMonitoring: {
enabled: true, frameMonitoringEnabled: true, snapshotInterval: 100,
pushInterval: 15, eventInit: "page_ready"
},
HoverStyles: { enabled: settings.profile.sessionReplayMonitoring },
PopupDialogMonitoring: { enabled: true },
RenderingMonitoring: { enabled: settings.profile.renderingTimeMonitoring },
MediaStateMonitoring: { enabled: true },
RT: { enabled: true },
ScriptingMonitoring: { enabled: settings.profile.scriptTimeMonitoring },
DebuggerMonitoring: {
enabled: false,
profilePageLoad: true,
profilerMaxSeconds: 30,
chrome: { // Requires the Germain Chrome extension
useWebSocket: true, // True requires Chrome to be launched with remote-debugging-port arg. False causes a warning banner in the UI.
webSocketPort: 9922, // chrome.exe --remote-debugging-port=9922
profilerSamplingInterval: 20 // milliseconds
}
},
ScrollMonitoring: { enabled: true, snapshotInterval: 1000, pushInterval: 15, eventInit: "page_ready" },
StaticResourcesMonitoring: { enabled: settings.profile.sessionReplayMonitoring, eventInit: "page_ready" },
VisibilityMonitoring: { enabled: true, eventInit: "page_ready" },
HangMonitoring: { enabled: true, pingInterval: 10, minHangSeconds: 15 },
ContentIndex: { enabled: true, includeVisibleText: true, includeInputFields: true }
};
settings.constants = {
CORS_PROXY_URL: null,
DATA_QUEUE_PUSH_INTERVAL: 10,
USE_WEB_WORKER: false,
DATA_TIMEOUT: 10000, // how long we can try to send collect data back (in ms)
EVENT_SOURCE_ELEMENT_LOOKUP: GermainAPMSalesforceUtils.eventSourceElementLookup,
PAGE_TITLE: GermainAPMSalesforceUtils.titleLookup, // extract request title
RESPONSE_BODY_MONITORING: true, // catch response body
RESPONSE_BODY_PARSER: GermainAPMSalesforceUtils.responseBodyParser,
REQUEST_BODY_MONITORING: true, // catch POST request body
REQUEST_BODY_PARSER: function (fact, reqBody) { return decodeURIComponent(reqBody); },
SEND_SYNC_ON_UNLOAD: true, // this only applies when the navigator.sendBeacon is unavailable (IE)
WITH_CREDENTIALS: false, // send requests with credentials/cookies
USER_CLICK: {
count: 0,
refreshInterval: 15, // (in seconds) we check periodically if we can close current user click txn and send current cum. txn
sequence: new Date().getTime() + Math.random().toString(36).substring(6),
queryStringGenerator: GermainAPMSalesforceUtils.userClickQueryStringGenerator, // user click txn query string extractor
excludeUrls: [// exclude http request from user click txn
/cometd/replay/
],
labelGenerator: GermainAPMSalesforceUtils.labelLookup,
nameGenerator: GermainAPMSalesforceUtils.nameLookup
},
EXCLUDE_URLS: [// exclude data points from monitoring by full URL (including query string)
/germainapm.*.js/i,
/ingestion/beacon/i,
/uxprofile/uxloader/i,
/uxprofile?monitoringProfile/i,
/config/agent/lookup/i,
/cometd/replay/
]
};
settings.application = {
appName: settings.agent.appName || 'Salesforce',
serverHost: settings.agent.serverHost,
username: function(){
var username = GermainAPMSalesforceUtils.usernameLookup();
if(username) return username;
var counter = 0;
var intervalId = setInterval(function(){
counter++;
var username = GermainAPMSalesforceUtils.usernameLookup();
if(username){
BOOMR.data.username = username;
clearInterval(intervalId);
}
if(counter > 20){
clearInterval(intervalId);
}
}, 1000);
return null;
},
session: GermainAPMSalesforceUtils.sessionLookup,
sequence: BOOMR.utils.session.getSequence
};
GermainAPM.init(settings);
// SFDC integration
function sfdcHook(){
window.$A.logger.subscribe("WARNING", function log(level, message, e) {
BOOMR.plugins.ConsoleMonitoring.sendMessage("warn", message, e ? e.message : null);
});
window.$A.logger.subscribe("ASSERT", function(level, message) {
BOOMR.plugins.ConsoleMonitoring.sendMessage("assert", message, null);
});
window.$A.logger.subscribe("ERROR", function(level, message, e) {
BOOMR.plugins.ConsoleMonitoring.sendMessage("error", message, e ? (e.stack && e.stack !== null) ? e.stack : e.message : null);
});
if(window.$A.message){
BOOMR.utils.hookCall(window.$A, 'message',
function(msg, error) {
BOOMR.plugins.PopupDialogMonitoring.sendMessage('Message Error', new Date().getTime(), error, msg);
}
);
}
if(window.$A.createComponent){
BOOMR.utils.hookCall(window.$A, 'createComponent',
function(type, attributes) {
if(type === "markup://one:applicationError"){
BOOMR.plugins.PopupDialogMonitoring.sendMessage('App Error', new Date().getTime(), attributes.error, attributes.error ? attributes.error.message : null);
} else if (type === "ltng:developerError"){
BOOMR.plugins.PopupDialogMonitoring.sendMessage('Developer Error', new Date().getTime(), attributes.stackTrace || attributes.messageText, attributes.messageTitle);
}
}
);
}
}
var counter = 0;
var intervalId = setInterval(function(){
counter++;
if(window.$A && window.$A.logger && window.$A.createComponent && window.$A.message){
sfdcHook();
clearInterval(intervalId);
}
if(counter > 20){
clearInterval(intervalId);
}
}, 1000);