https://unofficialsf.com/wp-content/uploads/2020/05/USFlogo2-300x111.png00Tamar Erlichhttps://unofficialsf.com/wp-content/uploads/2020/05/USFlogo2-300x111.pngTamar Erlich2020-10-23 06:47:492020-10-26 04:40:08Salesforce Record Automation Benchmarking by Luke Freeland
https://unofficialsf.com/wp-content/uploads/2020/05/USFlogo2-300x111.png00Tamar Erlichhttps://unofficialsf.com/wp-content/uploads/2020/05/USFlogo2-300x111.pngTamar Erlich2020-10-16 12:49:182020-10-18 12:02:33Use Flows to Track Unread Emails on Cases and Increase Case Management Efficiency by Vibhor Goel
I was recently tasked with a requirement to display a list of opportunity partners on a custom object related to opportunity. I planned to use a flow to get the opportunity partner records and then display them using the Datatablev2 Flow Screen Component available on this site. When I tried to create the flow, I encountered some issues: 1. The Opportunity Partners object is a legacy object and cannot be customized. I could not create formulas for getting the account name or link to the account 2. I could not use dot notation to get the account name from the account ID in the get records component in the flow 3. I could not use the partner account ID as a filter criteria on a new get records element to get the account records.
Not Possible
I knew I had to create a custom way to get the data I needed to display. Eric Smith referred me to his blog post on how to use Apex defined data types and an invocable action to create data for the datatable component. How to Use an Apex-Defined Object with my Datatable Flow Component I create my Apex defined data type for opportunity partners.
/**
* @author : Tamar Erlich
* @date : October 07, 2020
* @description : Wrapper class for opportunity partners
* Note : This class is called by the GetOpportunityPartnersAction.
* *************************************************
* <runTest><runTest>
* *************************************************
* @History
* -------
* VERSION | AUTHOR | DATE | DESCRIPTION
* 1.0 | Tamar Erlich | October 07, 2020 | Created
**/
public with sharing class OpportunityPartnersWrapper {
// @AuraEnabled annotation exposes the methods to Lightning Components and Flows
@AuraEnabled
public String accountName;
@AuraEnabled
public String partnerRole;
@AuraEnabled
public Boolean isPrimary;
@AuraEnabled
public String accountLink;
// Define the structure of the Apex-Defined Variable
public OpportunityPartnersWrapper(
String accountName,
String partnerRole,
Boolean isPrimary,
String accountLink
) {
this.accountName = accountName;
this.partnerRole = partnerRole;
this.isPrimary = isPrimary;
this.accountLink = accountLink;
}
// Required no-argument constructor
public OpportunityPartnersWrapper() {}
}
I then created an invocable action that received the opportunity and account IDs and returns a JSON string that can be displayed in my flow table.
/**
* @author : Tamar Erlich
* @date : October 07, 2020
* @description : Invocable method that given opportunityId and accountId, returns a list of opportunity partners to display on opportunity plan record page
* Note : This class is called by the Flow.
* *************************************************
* <runTest>GetOpportunityPartnersActionTest<runTest>
* *************************************************
* @History
* -------
* VERSION | AUTHOR | DATE | DESCRIPTION
* 1.0 | Tamar Erlich | October 07, 2020 | Created
**/
global with sharing class GetOpportunityPartnersAction {
// Expose this Action to the Flow
@InvocableMethod
global static List<Results> get(List<Requests> requestList) {
// initialize variables
Results response = new Results();
List<Results> responseWrapper = new List<Results>();
String errors;
String success = 'true';
String stringOutput;
List<OpportunityPartner> opportunityPartners = new List<OpportunityPartner>();
List<OpportunityPartnersWrapper> opportunityPartnersSet = new List<OpportunityPartnersWrapper>();
Set<Id> setOppIds = new Set<Id>();
Set<Id> setAccIDs = new Set<Id>();
Map<Id, List<OpportunityPartner>> mapOppOppPartners = new Map<Id, List<OpportunityPartner>>();
// create sets of Ids to use as bind variables in the query
for (Requests rq : requestList) {
setOppIds.add(rq.opportunityId);
setAccIds.add(rq.accountId);
}
// query all request opportunities and their partners and create a map with opportunityId as the key and a list of parrtners as the values
for (Opportunity opp : [
SELECT
Id,
(
SELECT AccountTo.Name, IsPrimary, Role
FROM OpportunityPartnersFrom
WHERE OpportunityId = :setOppIds AND AccountToId != :setAccIds
)
FROM Opportunity
]) {
mapOppOppPartners.put(opp.Id, opp.OpportunityPartnersFrom);
}
for (Requests curRequest : requestList) {
String accountId = curRequest.accountId;
String opportunityId = curRequest.opportunityId;
try {
if (accountId == null || opportunityId == null) {
throw new InvocableActionException(
'When using the GetOpportunityPartners action, you need to provide BOTH an Account Id AND an Opportunity Id'
);
}
if (!accountId.startsWith('001') || !opportunityId.startsWith('006')) {
throw new InvocableActionException(
'Invalid Account or Opportunity ID'
);
}
// get the list of opportunity partners matching the current opportunityId from the map
if (accountId != null && opportunityId != null) {
opportunityPartners = mapOppOppPartners.get(opportunityId);
}
// populate the Apex defined wrapper type with opportunity partner information
for (opportunityPartner op : opportunityPartners) {
OpportunityPartnersWrapper opw = new OpportunityPartnersWrapper();
opw.accountName = op.AccountTo.Name;
opw.partnerRole = op.Role;
opw.isPrimary = op.IsPrimary;
opw.accountLink = '/' + op.AccountToId;
opportunityPartnersSet.add(opw);
}
// Convert Record Collection to Serialized String
stringOutput = JSON.serialize(opportunityPartnersSet);
} catch (InvocableActionException e) {
System.debug('exception occured: ' + e.getMessage());
errors = e.getMessage();
success = 'false - custom exception occured';
} catch (exception ex) {
System.debug('exception occured: ' + ex.getMessage());
errors = ex.getMessage();
success = 'false - exception occured';
}
// Prepare the response to send back to the Flow
// Set Output Values
response.errors = errors;
response.successful = success;
response.outputCollection = opportunityPartnersSet;
response.outputString = stringOutput;
responseWrapper.add(response);
}
// Return values back to the Flow
return responseWrapper;
}
// Attributes passed in from the Flow
global class Requests {
@InvocableVariable(label='Input the Related OpportunityId' required=true)
global String opportunityId;
@InvocableVariable(label='Input the Opportunity\'s AccountId' required=true)
global String accountId;
}
// Attributes passed back to the Flow
global class Results {
@InvocableVariable
global String errors;
@InvocableVariable
global String successful;
@InvocableVariable
public List<OpportunityPartnersWrapper> outputCollection;
@InvocableVariable
public String outputString;
}
// custom exception class
global class InvocableActionException extends Exception {
}
}
/**
* @author : Tamar Erlich
* @date : October 07, 2020
* @description : Test class for GetOpportunityPartnersAction Invocable method
* Note :
* *************************************************
* <runTest>GetOpportunityPartnersActionTest<runTest>
* *************************************************
* @History
* -------
* VERSION | AUTHOR | DATE | DESCRIPTION
* 1.0 | Tamar Erlich | October 07, 2020 | created
**/
@IsTest
public with sharing class GetOpportunityPartnersActionTest {
@isTest
public static void opportunityPartnersFound(){
// initialize variables
List<GetOpportunityPartnersAction.Requests> requestList = new List<GetOpportunityPartnersAction.Requests>();
String accountId;
String opportunityId;
// create test account
List<Account> accounts = new List<Account>();
for (Integer j = 0; j < 1; j++) {
Account a = new Account(
Name = 'email' + j + '.com'
);
accounts.add(a);
}
insert accounts;
// create test opportunity
Date closeDate = System.today();
Opportunity testOpp;
testOpp = new Opportunity(
Name = 'test opp',
StageName = 'Open In-Progress',
Amount = 100,
CloseDate = closeDate
);
insert testOpp;
accountId = accounts[0].Id;
opportunityId = testOpp.Id;
// create test opportunity partner
OpportunityPartner testPartner;
testPartner = new OpportunityPartner(
OpportunityId = opportunityId,
AccountToId = accountId,
Role = 'Dealer',
IsPrimary = false
);
insert testPartner;
// prepare request for GetOpportunityPartnersAction
GetOpportunityPartnersAction.Requests request = new GetOpportunityPartnersAction.Requests();
request.accountId = accountId;
request.opportunityId = opportunityId;
requestList.add(request);
// run test and assert results
List <GetOpportunityPartnersAction.Results> results = GetOpportunityPartnersAction.get(requestList);
System.assertEquals(results[0].errors, null,'Errors not expected');
System.assertNotEquals(results[0].successful, 'false', 'Success expected');
System.assertEquals(true, results[0].outputCollection.size()>0, 'Opportunity Partners expected');
}
}
I then used my new action in the flow to get the opportunity partner records.
Invocable action
I then configured my datatable on the flow screen
Flow Screen
Some notes on the datatable configuration: 1. User Defined had to bet set to true 2. The Datatable Record String must be set to the variable returned from the action 3. The column field types must be set for all columns. I set the first column to the url type 4. My first column has special type attributes for displaying as a clickable link: 1:{label: { fieldName: accountName}, target: ‘_blank’} 5. Since this is for display only, I hid the checkbox columns 6. I used the new features available in v2.46 to apply an icon and title to the table
The View All link uses a formula to create the link
View All Formula
Here is the entire flow
And here is the end result
Opportunity Partners on custom object
This use case can be easily adapted to display other opportunity related information like opportunity team members or opportunity contact roles.
There is now a new Output Parameter that passes back the number of Selected Records. This helps with being able to easily check later in your Flow to see if any records were selected. This feature can also be used to set Component Visibility for components on the same Screen as the Datatable.
Another new Parameter lets you set the Datatable as Required. This will keep the User from advancing past the Screen if no records are selected.
When just a single record is selected in the Datatable, a new SObject Output Parameter will be populated with the single selected record in addition to the standard SObject Collection Output Parameter.
Another new feature for Datatables displaying just a single record is the appearance of a Clear Selection button. This button will be made available once the single record has been selected using the standard Radio Button.
You can now add a Header Icon and Header Label to the entire Datatable.
Additional Enhancements and Bug Fixes include:
Spinners will be displayed when sorting & filtering data
Picklist and Multi-Picklist Labels will be displayed instead of the API Names
The link to the record for the object’s standard Name field can be suppressed
The Configuration Helper now uses the Flow Base Components version of the DualListBox component
Column Field Names attributes are now recognized with case insensitivity and even if missing the __c suffix
https://unofficialsf.com/wp-content/uploads/2020/05/USFlogo2-300x111.png00Eric Smithhttps://unofficialsf.com/wp-content/uploads/2020/05/USFlogo2-300x111.pngEric Smith2020-10-07 09:19:432020-12-14 07:46:03New features have just been added to the Datatable component
Trying to report on user login history using standard Salesforce reporting can be tricky. If you would like to see each users’s last login and his total login count for the last 6 months, sorted by login count, you have to use some external Excel manipulation. I was looking to get a simple report I could use to monitor login usage.
Simple Login Report
I was able to provide a nice solution using the new before save flow record trigger. The first step is to create a number field on the user object to hold the login count. Then create this simple before save flow. This flow updates the custom field on the User object. By incrementing a counter every time a login history is generated, each user shows a login count. Then, at any time, you can generate the report by simply reporting on your users.
User Login History FlowGet Login History RecordsConfirm records are returned (Null Check)Use the record count operator to assign the record count to the user fieldIf no records are returned, assign zero login count
This use case can be easily adapted to rollup any related records on other objects. I can think of several examples: count of opportunity contact roles on opportunity, count open cases on a contact, count of won opportunities on an account.
https://unofficialsf.com/wp-content/uploads/2020/05/USFlogo2-300x111.png00Tamar Erlichhttps://unofficialsf.com/wp-content/uploads/2020/05/USFlogo2-300x111.pngTamar Erlich2020-08-12 15:43:282020-08-12 16:39:53Flow Use Case - Use Before Save flow to update the total login count on the user record
GORAV SETH demonstrates how you can use ISCHANGED and PRIORVALUE in Before Save Flows Check it out
https://unofficialsf.com/wp-content/uploads/2020/05/USFlogo2-300x111.png00Tamar Erlichhttps://unofficialsf.com/wp-content/uploads/2020/05/USFlogo2-300x111.pngTamar Erlich2020-05-12 12:46:322020-05-12 12:46:33How to use ISCHANGED and PRIORVALUE in Before Save Flows by GORAV SETH
ABOUT This is a Lightning Component that plays a sound effect. It can be used on Flow Screens and Lightning Pages. In order to play a sound, you have to upload the sound file as a Static Resource and then enter the name of the file to the “Sound File” parameter.
PARAMETERS Sound File – Enter the name of the sound file.
CONSIDERATIONS This component loads the sound file from Static Resources and plays it. So it is recommended to use smaller file sizes, otherwise it might take some time to load the file.
HOW TO USE 1- Upload the sound file as a Static Resource.
2- Add “SoundCMP” to your Flow Screen. 3- Enter the name of the sound file to the parameter called “Sound File”.
https://unofficialsf.com/wp-content/uploads/2020/05/USFlogo2-300x111.png00Yumi Ibrahimzadehttps://unofficialsf.com/wp-content/uploads/2020/05/USFlogo2-300x111.pngYumi Ibrahimzade2020-03-06 07:25:082020-03-06 07:39:29A Flow Screen Component that plays sounds
https://unofficialsf.com/wp-content/uploads/2020/05/USFlogo2-300x111.png00Tamar Erlichhttps://unofficialsf.com/wp-content/uploads/2020/05/USFlogo2-300x111.pngTamar Erlich2020-03-02 09:59:152020-03-02 09:59:17Update N Records in Flow Without a Loop by Jessie Rymph
The first post in a new series on the Salesforce admin blog demonstrates a novel way to launch auoto-launched flows from a formula field on a report. This has several very interesting use cases Check it out
https://unofficialsf.com/wp-content/uploads/2020/05/USFlogo2-300x111.png00Tamar Erlichhttps://unofficialsf.com/wp-content/uploads/2020/05/USFlogo2-300x111.pngTamar Erlich2020-02-27 06:00:192020-02-27 06:00:20Launch a flow from a formula field
Lightning Flow already empowers you to declaratively automate your business processes within Salesforce. But what if your business processes require integrations between Salesforce and other non-Salesforce services? Chances are that customer service agents who use Salesforce to track and resolve support cases would also need to access information from and/or submit changes to another backend system. Or perhaps you want a Flow to create a new record in Salesforce as well as post a message in Slack to notify the team. These are scenarios where you can leverage External Services to extend the ability of Flow to automate processes beyond Salesforce!
External Services provides a declarative way to access external business processes, whether they are proprietary APIs, public APIs, or other Salesforce APIs:
For any REST API that you want available as a callout from Lightning Flow, you just need to register the OpenAPI 2.0 specification for that API, so that it will be available as an action in Flow.
Configuring Flow to Integrate with an External Service
To support this integration, you just need to provide Salesforce with the following information:
Named Credentials
Where to configure this: Setup -> Named Credentials
What these configurations tell Salesforce: Where is the external service being hosted (domain URL), and what credentials do we need to send for authentication?
External Services:
Where to configure this: Setup -> External Services
What these configurations tell Salesforce: Where the OpenAPI 2.0 specification is located (relative to the domain URL of the Named Credential), and/or what the API specification is. The specification describes what API callouts Salesforce can make to the external service.
Flow Action:
Where to configure this: Setup -> Flow
What these configurations tell Salesforce: Which action / callout you want to make to the external API, what input parameters you want to send, and what variable you want to use to store the response.
Example
This example shows how to the register the OpenAPI 2.0 specification for the Swagger PetStore API, as well as configure a callout to the PetStore API in Flow:
Named Credential:
External Service:
Flow – Add “findPetsByStatus” action:
Flow – Configure Input Parameters and Store Response:
Learn More
Here are some more resources for you to learn more about External Services:
https://unofficialsf.com/wp-content/uploads/2020/05/USFlogo2-300x111.png00Liz Skaateshttps://unofficialsf.com/wp-content/uploads/2020/05/USFlogo2-300x111.pngLiz Skaates2020-02-13 17:58:592020-02-20 11:21:25Extend the Power of Flow with External Services