As part of the Salesforce Solutions Team at WebResults I spend some time training myself on new products and trying to build POCs (prove of concepts) to give value to the training and promote the knowledge inside my company.
I recently was playing with Einstein Bots and got stuck when needed to identify the running user, when dealing with queries to get actual user info from the CRM.
I though it was enough to call the UserInfo.getUserId()
method and I could get who is the user calling the bot, but unfortunately the running user of an Apex InvocableMethod is the AutomatedProcess user, that you can monitor from Setup > Debug Logs > User Trace Flags [New] as shown below:
I believe that Salesforce will add this feature in the next feature but I had to find a workaround.
The Workaround
I started a thread on Stackoverflow, where I got some ideas but no solution at all.
After a while I came up with a dirty solution that allowed me to know the exact user logged into the community. I won’t explain all the trial and errors but the whole solution, with some code samples.
Here is the list of actions:
- Enable Pre-Chat
- Override Pre-Chat with a Lightning Component
- The Pre-chat Lightning component calls its controller to get if there is a valid running user and returns back all the data needed for the prechat
- The Lightning component compiles all the needed inputs for the pre-chat (e.g. name, email and username) and submits for the chat: if the info are found, the component hides the pre-chat fields (that may be filled by a non authenticated user)
- The info submitted are written on the Transcript object
- An Einstein Bot dialog examines the Transcript object using an IncobaleMethod of an Apex class and determines which is the running user
- The bot can now change its behavior depending on the fact that the user is logged or not
To get all of this working you need 2 custom fields:
- A custom field on the Contact/Lead record (we’ll call it
Username__c
) that is used to configure the prechat on the Snap-In - A custom field on the LiveChatTranscript object (we’ll call it
Username__c
as well) used to store the info got from the Pre-Chat component
Once fields are created, we need to setup the Pre-Chat page on the Snap-in settings related to the bot:
And setup the Pre-chat page:
Now let’s create the Lightning component that will replace the Snap-in chat (configured in the picture above):
prechat.cmp
For the what & whys plese refer to the official documentation in this link.
The component uses the lightningsnapin:prechatAPI
that is needed to trigger the chat once the info are filled in.
<aura:component implements="lightningsnapin:prechatUI" controller="Bot_PreChatCmpCnt"> <aura:attribute name="userId" access="PRIVATE" type="string" default="-"/> <aura:attribute name="firstName" access="PRIVATE" type="string" /> <aura:attribute name="lastName" access="PRIVATE" type="string" /> <aura:attribute name="email" access="PRIVATE" type="string" /> <!-- Contains methods for getting pre-chat fields, starting a chat, and validating fields --> <lightningsnapin:prechatAPI aura:id="prechatAPI"/> <!-- After this component has rendered, call the controller's onRender function --> <aura:handler name="init" value="{!this}" action="{!c.doInit}"/> <aura:renderIf isTrue="{!empty(v.userId)}"> <lightning:input type="text" value="{!v.firstName}" label="Name *"> <lightning:input type="text" value="{!v.lastName}" label="Lastname *"> <lightning:input type="text" value="{!v.email}" label="Email *"> <lightning:button label="Start chat!" onclick="{!c.onStartButtonClick}"/> </aura:renderIf> </aura:component>
The list of input fields are only shown when the v.userId
attribute is blank (you can note that it is initialized with “-“, and in the lightning controller is blanked only when no logged user is found).
prechatController.js
The controller calls the Apex controller to get the main user info: if they are found, the chat is started.
({ doInit: function(component, event, helper) { var action = component.get("c.getCurrentUser"); action.setCallback(this, function(response) { var state = response.getState(); if (state === "SUCCESS") { var result = JSON.parse(response.getReturnValue()); console.log(result, embedded_svc); component.set('v.userId', result.userId); if(result.userId){ component.set('v.firstName', result.firstName); component.set('v.lastName', result.lastName); component.set('v.email', result.email); helper.startChat(component, event, helper); } }else if (state === "ERROR") { var errors = response.getError(); if (errors) { if (errors[0] && errors[0].message) { console.log("Error message: " + errors[0].message); } } else { console.log("Unknown error"); } } }); $A.enqueueAction(action); }, onStartButtonClick: function(component, event, helper) { //handling errors if(!component.get('v.firstName') || !component.get('v.lastName') || !component.get('v.email')) return alert('Missing fields.'); helper.startChat(component, event, helper); } });
prechatHelper.js
The startChat()
method compiles the array of fields to be passed to the chat plugin, that will start the chat with the Bot.
({ startChat: function(component, event, helper){ var fields = [ { label: 'FirstName', name: 'FirstName', value: component.get('v.firstName') } , { label: 'LastName', name: 'LastName', value: component.get('v.lastName') } , { label: 'Email', name: 'Email', value: component.get('v.email') },{ label: 'Username', name: 'Username__c', value: component.get('v.userId'), } ]; if(component.find("prechatAPI").validateFields(fields).valid) { component.find("prechatAPI").startChat(fields); } } });
prechatHelper.js
The Apex controller simply makes a query on the user object and considers it a loggedin user if the ContactId
field is not null
(but you can use your own conditions):
public class Bot_PreChatCmpCnt { @auraenabled public static String getCurrentUser(){ Map<String,Object> output = new Map<String,Object>(); User u = [Select Username, FirstName, LastName, Email, contactId From User Where Id = :UserInfo.getUserId()]; if(u.ContactId != null){ output.put('userId', u.UserName); output.put('firstName', u.FirstName); output.put('lastName', u.LastName); output.put('email', u.Email); }else{ output.put('userId', ''); } return JSON.serialize(output); } }
That said we need to instruct the Pre-Chat plugin to write the fields on the LiveChatTranscript object, which is the only thing that grants the Bot’s dialogs to be aware of the running context. Create a new Static Resource and remember to set the Cache Control to Public.
embedded_svc.snippetSettingsFile.extraPrechatFormDetails = [ { "label":"Username", "transcriptFields":[ "Username__c" ], },{ "label":"Cognome", "transcriptFields": ["LastName__c"] },{ "label":"Nome", "transcriptFields": ["FirstName__c"] },{ "label":"Email", "transcriptFields": ["Email__c"] }];
This Static Resource must be configured in the Snap-in Component on the Community builder:
Einstein Bot Dialog Apex action
Last step, before configuring the bot, is to create a new Apex Class that will retrieve the users information coming from the current transcript:
public without sharing class Bot_GetSnapInsPreChatData { public class PrechatOutput{ @InvocableVariable public String sFirstName; @InvocableVariable public String sLastName; @InvocableVariable public String sEmail; @InvocableVariable public String sContactID; @InvocableVariable public String sLoggedUser; } public class PrechatInput{ @InvocableVariable public String sChatKey; } @InvocableMethod(label='Get SnapIns Prechat Data') public static List<PrechatOutput> getSnapInsPrechatData(List<PrechatInput> inputParameters) { System.debug('######## Input Parameters: '+inputParameters); String sChatKey = inputParameters[0].sChatKey; String sContactId = null; ListoutputParameters = new List (); PrechatOutput outputParameter = new PrechatOutput(); if (sChatKey != null && sChatKey != '') { List<LiveChatTranscript> transcripts = [SELECT Id, CaseId, ContactId, Username__c FROM LiveChatTranscript WHERE ChatKey = :sChatKey]; if (transcripts.size()>0) { sContactId = transcripts[0].ContactId; outputParameter.sLoggedUser = transcripts[0].Username__c; } } if (sContactId != null && sContactId != '') { List<Contact> contacts = [SELECT Id, FirstName, LastName, Email, Username__c FROM Contact WHERE Id = :sContactId]; if (contacts.size()>0) { outputParameter.sFirstName = contacts[0].FirstName; outputParameter.sLastName = contacts[0].LastName; outputParameter.sEmail = contacts[0].Email; outputParameter.sContactId = contacts[0].Id; } } outputParameters.add(outputParameter); return outputParameters; } }
This class read the coming sChatKey
value to query the LiveChatTranscript
object that stores all the informations coming from the PreChat.
This LiveChatTranscript
can be further manipulated to add more and more data for your context.
You must also define a hidden Bot variable containing the Live chat transcript key, that must be called LiveAgentSessionId
(and is populated by the Live Agent engine automatically):
Now you can safely configure your dialog and be sure to know if the user is logged or not, and change the Bot behavior consequently: