The Top 10 Things You Want to Know About the New Flow Builder

1. It’s faster and more intuitive

Flow Builder has a brand new user interface that is similar to Lightning App Builder and Process Builder, so if you’re familiar with those tools you’ll feel right at home here. Clean lines and intuitive shapes make it easy to navigate around your flows, and the streamlined toolbox makes it easier to find the element or resource you’re looking for. On top of that, Flow Builder is built with Salesforce’s latest front-end technologies for fast performance – say goodbye to kludgy old Flash!

A complex flow in Flow Builder

2. Some things have been renamed

If you’re an old-school Flownatic, you might have noticed that some things have been renamed or moved around in Flow Builder. For example, the sObject data type is now a Record data type; and static, local and quick actions can all now be found under the Core Action element. As part of the streamlined toolbox, we replaced the Fast Lookup and Record Lookup elements with Get Records, and the other Data elements have received a similar treatment. For a full list of differences, see the Flow Builder vs. Cloud Flow Designer document in Salesforce Help.

3. We couldn’t get to these old Cloud Flow Designer features, but we expect to deliver them in Summer ’19

We know that undo/redo, copy and paste, and the ability to find elements and resources in the canvas are important to you. We’re working hard on these features and expect to release them in Summer ’19. (As always, please make purchasing decisions based on current functionality and not this forward-looking statement!) We just ran out of time to get features into Spring ’19 and decided it was better to release Flow Builder with all the goodness it does have, than to wait another release.

4. You can continue to use Cloud Flow Designer side-by-side with Flow Builder

Starting with Spring ’19, Flow Builder is the default experience when creating new flows and opening existing flows. If you need to use Cloud Flow Designer – perhaps because a feature you need isn’t yet available in Flow Builder – you can re-enable it: in Setup, go to the Process Automation Settings page and deselect the Disable access to Cloud Flow Designer option. With this deselected, flow versions that were saved in Cloud Flow Designer will open in Cloud Flow Designer, and versions that were saved in Flow Builder will open in Flow Builder.

If you have a flow that you saved in Flow Builder and you decide you really need to go back to Cloud Flow Designer, there’s an unofficial solution for that too: see Converting a Flow Builder Flow Version Back to Cloud Flow Designer.

5. Pan the canvas using Space+Click

To pan the canvas, you can press and hold down the Space key, then click anywhere in the canvas and drag.

Animation showing how to toggle pan mode with the space+click combination.

6. Select multiple elements using Shift+Click

We’re working on marquee selection (clicking and dragging to select multiple elements) for a near-term release. Until then, to select multiple elements on the canvas, press and hold down the Shift key, then click on each element you want to add to the selection. Then click on any selected element to move the entire selection.

Animation showing how to select multiple elements to move them.

7. Make a variable a collection variable with a single click

To turn a variable of any data type into a collection variable, select the Allow multiple values (collection) checkbox next to the Data Type field.

The New Resource modal with the Allow multiple values (collection) checkbox selected

8. There are new Lightning Components available out of the box

We added the ability to use Lightning components in flows in Winter ’19, and actually included a number of out-of-the-box components then, like Dependent Picklists, Phone, Email, and Name – but since they were hidden under Extensions in Cloud Flow Designer, you might have missed them. Flow Builder elevates these components to first-class screen components, and with Spring ’19 we have two new components: Address and Display Image.

Remember, when you’re using Lightning components with input fields in them (like Name) you’ll need to specify a variable for the field in the Store Output Values section in the component properties in order to use the field’s value elsewhere in the flow. (This is different from the way screen components like Text work, where the screen component can be used directly as a resource.)

9. New Trailhead content is live!

All of the Trailhead modules and projects that previously referred to Cloud Flow Designer have been rewritten with instructions on how to use Flow Builder. If you’ve previously completed these modules and projects, maybe you’d like a refresher! Otherwise, hold tight until March – brand new flow content will be coming your way soon.

10. There are 2 hours of fresh video tutorials that you can watch

All the Trailhead content related to flows is now up to date with the latest on Flow Builder. In addition, Alex Edelstein ‘s Get Started with the New Flow Builder post on the Salesforce Admins blog has two hours of video tutorial content to help both new and old school flow users get up to speed. From sending an email to building a screen to working with multiple records, these tutorials have you covered!

Adding Validation Functions to Flow Screen Components

This feature shipped quietly back in Winter ’19.

The official docs are somewhat hidden away here. This blog post is mainly an effort to raise the profile of this feature a bit.

If you wrap a base component that has a “required” attribute, even if you expose the attribute, Flow will not know to check unless you implement a validate function as described here.

Flow Screen Component: Dependent Picklists with RecordType and MultiSelect Picklist support by Narender Singh

Narender Singh created a flow screen component for a dependent picklist tha supports picklist value selection based on the record type.

https://forcepanda.wordpress.com/2019/06/11/flow-screen-component-dependent-picklists-with-recordtype-and-multiselect-picklist-support/

How to Declaratively Handle with Flow When Files are Shared to Records by DOUGLAS C. AYERS

Douglas Ayers and Michelle Hansen developed an Apex trigger that could know when a file was being shared to a record and then call an autolaunched flow where the business logic could be implemented declaratively.

How To: Use Collection Operators in Assignment Element

This video was done in 2018, but it’s still one of the best demonstrations of how to make use of the Collection Operators in the Assignment element.

Summer 19 Release Readiness Video

Templates, Apex-defined Types, and more. Check it out.

Use the Next Best Action REST API to execute a strategy and retrieve recommendation reactions

Quick guide on how to use the NBA REST API.  

Executing an NBA Strategy for a Given Record

Official documentation: https://developer.salesforce.com/docs/atlas.en-us.chatterapi.meta/chatterapi/connect_resources_nba_recommendations.htm

Example:

POST

/services/data/v45.0/connect/recommendation-strategies/Jasmine_Yogurt_Service/recommendations

Body:

{
"contextRecordId" : "5001U000006XEpjQAG", 
"maxResults" : 4,
"strategyContext":{}
}

Response:

Raw Response (in JSON):

{
   "executionId":"23e8f474-6fec-4b5d-b2b5-667ef7ffb03a",
   "onBehalfOfId":"5001U000006XEpj",
   "recommendations":[
      {
         "acceptanceLabel":"Add Hot Waffle Press",
         "description":"Add a new Stanley Waffle Cone Press",
         "imageUrl":"https://isnba--c.documentforce.com/file-asset-public/WaffleMaker1?oid=00D1U0000012w03&v=1",
         "rejectionLabel":"No Thanks",
         "target":{
            "id":"0pr1U00000057IYQAY",
            "name":"Add a Hot Waffle Press",
            "type":"Recommendation",
            "url":"/services/data/v45.0/connect/recommendations/0pr1U00000057IY"
         },
         "targetAction":{
            "id":"3001U000000oZixQAE",
            "name":"Dispatch_Field_Technician",
            "parameters":[

            ],
            "type":"Flow"
         }
      },
      {
         "acceptanceLabel":"Upgrade to an X120 Soft Serve",
         "description":"Upgrade to a Twenty Gallon Single Nozzle Soft Serve Machine",
         "imageUrl":"https://isnba--c.documentforce.com/file-asset-public/SoftServeMaker1?oid=00D1U0000012w03&v=1",
         "rejectionLabel":"No Thanks",
         "target":{
            "id":"0pr1U00000057IXQAY",
            "name":"Upgrade to an X120 Soft Serve",
            "type":"Recommendation",
            "url":"/services/data/v45.0/connect/recommendations/0pr1U00000057IX"
         },
         "targetAction":{
            "id":"3001U000000oZixQAE",
            "name":"Dispatch_Field_Technician",
            "parameters":[

            ],
            "type":"Flow"
         }
      },
      {
         "acceptanceLabel":"Offer refinance",
         "description":"0% Refinancing on existing equipment",
         "imageUrl":"https://isnba--c.documentforce.com/file-asset-public/refinance?oid=00D1U0000012w03&v=1",
         "rejectionLabel":"No Thanks",
         "target":{
            "id":"0pr1U00000057IZQAY",
            "name":"0% Refinancing!",
            "type":"Recommendation",
            "url":"/services/data/v45.0/connect/recommendations/0pr1U00000057IZ"
         },
         "targetAction":{
            "id":"3001U000000oZixQAE",
            "name":"Dispatch_Field_Technician",
            "parameters":[

            ],
            "type":"Flow"
         }
      }
   ],
   "trace":{
      "messages":[

      ],
      "nodes":[

      ]
   }
}

Retrieving Recommendation Reactions

Official documentation– https://developer.salesforce.com/docs/atlas.en-us.chatterapi.meta/chatterapi/connect_resources_nba_reactions.htm#connect_resources_nba_reactions

Example:

GET

/services/data/v45.0/connect/recommendation-strategies/reactions?targetId=0pr1U00000057IUQAY

This will return back all of the recommendations reactions for the recommendation with ID 0pr1U00000057IUQAY.

Similarly you can also query for the contextRecordId (e.g. Case with ID 5001U000006XEpjQAG, account, etc.)

GET

/services/data/v45.0/connect/recommendation-strategies/reactions/?contextRecordId=5001U000006XEpjQAG

RAW RESPONSE:

{
   "currentPageUrl":"/services/data/v45.0/connect/recommendation-strategies/reactions?page=0&pageSize=100",
   "nextPageUrl":"/services/data/v45.0/connect/recommendation-strategies/reactions?page=1&pageSize=100",
   "reactions":[
      {
         "contextRecord":{
            "id":"5001U000006XEpjQAG",
            "url":"/services/data/v45.0/chatter/records/5001U000006XEpjQAG"
         },
         "createdBy":{
            "id":"0051U000004fPpPQAU",
            "url":"/services/data/v45.0/chatter/users/0051U000004fPpPQAU"
         },
         "createdDate":"2019-02-28T19:13:43.000Z",
         "id":"0ps1U000000TNzxQAG",
         "onBehalfOf":{
            "id":"5001U000006XEpjQAG",
            "url":"/services/data/v45.0/chatter/records/5001U000006XEpjQAG"
         },
         "reactionType":"Accepted",
         "strategy":{
            "id":"0sg1U0000004edzQAA",
            "nameAtSnapshot":"Jasmine_Yogurt_Service"
         },
         "targetAction":{
            "id":"3001U000000oZixQAE",
            "nameAtSnapshot":"Dispatch_Field_Technician"
         },
         "targetRecord":{
            "id":"0pr1U00000057IUQAY",
            "url":"/services/data/v45.0/connect/recommendations/0pr1U00000057IUQAY"
         },
         "url":"/services/data/v45.0/connect/recommendation-strategies/reactions/0ps1U000000TNzxQAG"
      },
      {
         "contextRecord":{
            "id":"5001U000006XEpjQAG",
            "url":"/services/data/v45.0/chatter/records/5001U000006XEpjQAG"
         },
         "createdBy":{
            "id":"0051U000004fPpPQAU",
            "url":"/services/data/v45.0/chatter/users/0051U000004fPpPQAU"
         },
         "createdDate":"2019-02-28T19:13:43.000Z",
         "id":"0ps1U000000TNzyQAG",
         "onBehalfOf":{
            "id":"5001U000006XEpjQAG",
            "url":"/services/data/v45.0/chatter/records/5001U000006XEpjQAG"
         },
         "reactionType":"Accepted",
         "strategy":{
            "id":"0sg1U0000004edzQAA",
            "nameAtSnapshot":"Jasmine_Yogurt_Service"
         },
         "targetAction":{
            "id":"3001U000000oZixQAE",
            "nameAtSnapshot":"Dispatch_Field_Technician"
         },
         "targetRecord":{
            "id":"0pr1U00000057IUQAY",
            "url":"/services/data/v45.0/connect/recommendations/0pr1U00000057IUQAY"
         },
         "url":"/services/data/v45.0/connect/recommendation-strategies/reactions/0ps1U000000TNzyQAG"
      },
      {
         "contextRecord":{
            "id":"5001U000006XEpjQAG",
            "url":"/services/data/v45.0/chatter/records/5001U000006XEpjQAG"
         },
         "createdBy":{
            "id":"0051U000004fPpPQAU",
            "url":"/services/data/v45.0/chatter/users/0051U000004fPpPQAU"
         },
         "createdDate":"2019-02-28T19:13:43.000Z",
         "id":"0ps1U000000TNzzQAG",
         "onBehalfOf":{
            "id":"5001U000006XEpjQAG",
            "url":"/services/data/v45.0/chatter/records/5001U000006XEpjQAG"
         },
         "reactionType":"Accepted",
         "strategy":{
            "id":"0sg1U0000004edzQAA",
            "nameAtSnapshot":"Jasmine_Yogurt_Service"
         },
         "targetAction":{
            "id":"3001U000000oZixQAE",
            "nameAtSnapshot":"Dispatch_Field_Technician"
         },
         "targetRecord":{
            "id":"0pr1U00000057IUQAY",
            "url":"/services/data/v45.0/connect/recommendations/0pr1U00000057IUQAY"
         },
         "url":"/services/data/v45.0/connect/recommendation-strategies/reactions/0ps1U000000TNzzQAG"
      },
      {
         "contextRecord":{
            "id":"5001U000006XEpjQAG",
            "url":"/services/data/v45.0/chatter/records/5001U000006XEpjQAG"
         },
         "createdBy":{
            "id":"0051U000004fPpPQAU",
            "url":"/services/data/v45.0/chatter/users/0051U000004fPpPQAU"
         },
         "createdDate":"2019-02-28T19:13:43.000Z",
         "id":"0ps1U000000TO00QAG",
         "onBehalfOf":{
            "id":"5001U000006XEpjQAG",
            "url":"/services/data/v45.0/chatter/records/5001U000006XEpjQAG"
         },
         "reactionType":"Accepted",
         "strategy":{
            "id":"0sg1U0000004edzQAA",
            "nameAtSnapshot":"Jasmine_Yogurt_Service"
         },
         "targetAction":{
            "id":"3001U000000oZixQAE",
            "nameAtSnapshot":"Dispatch_Field_Technician"
         },
         "targetRecord":{
            "id":"0pr1U00000057IUQAY",
            "url":"/services/data/v45.0/connect/recommendations/0pr1U00000057IUQAY"
         },
         "url":"/services/data/v45.0/connect/recommendation-strategies/reactions/0ps1U000000TO00QAG"
      },
      {
         "contextRecord":{
            "id":"5001U000006XEpjQAG",
            "url":"/services/data/v45.0/chatter/records/5001U000006XEpjQAG"
         },
         "createdBy":{
            "id":"0051U000004fPpPQAU",
            "url":"/services/data/v45.0/chatter/users/0051U000004fPpPQAU"
         },
         "createdDate":"2019-03-07T05:11:29.000Z",
         "id":"0ps1U000000TQzqQAG",
         "onBehalfOf":{
            "id":"5001U000006XEpjQAG",
            "url":"/services/data/v45.0/chatter/records/5001U000006XEpjQAG"
         },
         "reactionType":"Accepted",
         "strategy":{
            "id":"0sg1U0000004edzQAA",
            "nameAtSnapshot":"Jasmine_Yogurt_Service"
         },
         "targetAction":{
            "id":"3001U000000oZixQAE",
            "nameAtSnapshot":"Dispatch_Field_Technician"
         },
         "targetRecord":{
            "id":"0pr1U00000057IUQAY",
            "url":"/services/data/v45.0/connect/recommendations/0pr1U00000057IUQAY"
         },
         "url":"/services/data/v45.0/connect/recommendation-strategies/reactions/0ps1U000000TQzqQAG"
      },
      {
         "contextRecord":{
            "id":"5001U000006XEpjQAG",
            "url":"/services/data/v45.0/chatter/records/5001U000006XEpjQAG"
         },
         "createdBy":{
            "id":"0051U000004fPpPQAU",
            "url":"/services/data/v45.0/chatter/users/0051U000004fPpPQAU"
         },
         "createdDate":"2019-03-18T18:21:15.000Z",
         "id":"0ps1U000000TYoYQAW",
         "onBehalfOf":{
            "id":"5001U000006XEpjQAG",
            "url":"/services/data/v45.0/chatter/records/5001U000006XEpjQAG"
         },
         "reactionType":"Accepted",
         "strategy":{
            "id":"0sg1U0000004edzQAA",
            "nameAtSnapshot":"Jasmine_Yogurt_Service"
         },
         "targetAction":{
            "id":"3001U000000oZixQAE",
            "nameAtSnapshot":"Dispatch_Field_Technician"
         },
         "targetRecord":{
            "id":"0pr1U00000057IUQAY",
            "url":"/services/data/v45.0/connect/recommendations/0pr1U00000057IUQAY"
         },
         "url":"/services/data/v45.0/connect/recommendation-strategies/reactions/0ps1U000000TYoYQAW"
      }
   ]
}

Integration Guide for Next Best Action

In this guide, we’ll walk you through all the different ways that you can connect to sources of insight from Next Best Action to generate and enhance recommendations and to pull in relevant properties for decision making. Note – the Generate and Enhance elements are new updates, shipping with the Summer ’19 Release.

There are 3 main integration points, all of which require Apex invocable actions:

  1. Generate
  2. Enhance
  3. External connections

The Generate element enables you to create new in-memory, on-the-fly recommendations, either from an external data source or from other Salesforce objects.

The Enhance element enables you to modify a given set of recommendations to include real-time updates, personalized information, or AI driven prediction scores.

And finally you can create an external connection to fetch properties in JSON format from external services. For example, you can connect to a Heroku endpoint to obtain a credit score for a given customer.

Let’s dive deeper into each one. You can install the strategies and invocable actions used in the examples by installing this package in your org. Note – your org must be using the Summer ’19 (or later) release.

Generate

Instead of defining recommendations manually as records in Salesforce, you can use the Generate element to dynamically create temporary, in-memory recommendations as a part of the current strategy execution. These dynamic recommendations can be sourced from external data sources like Commerce Cloud or a SQL database or from other Salesforce objects like Accounts or Products.

For example, you can build a dashboard of prioritized accounts to connect with.

Or create recommendations sourced from the Products object.

The Generate element has the following characteristics:

@InvocableMethod(
label='Related Wikipedia Pages'
description='Recommend wikipages that are related to the named input wikipage')
  • It can pass any number of inputs to the Apex Action, either as lists or list of lists of primitives, SObjects, and user-defined Apex objects. To provide more than one input, the input parameter MUST be a list or list of lists of a user-defined Apex object (e.g. a custom class called DataContainer).
List<String> relatedTo

OR

global class DataContainer {
@InvocableVariable
public string accountId;
}
____
global static List<List<Recommendation>> invocableMethod(List<DataContainer> inputData)
  • It returns a list of recommendations. InvocableMethods support returning either a list of an SObject type or a list of lists of an SObject type. As the Enhance element operates on a list of recommendations and not a single recommendation, the method must return a List<List<Recommendation>>.
global static List<List<Recommendation>> invocableMethod(List<DataContainer> inputData)

Example: Recommend accounts the current user should follow up on today
Suppose you want to recommend the top accounts your salespeople should meet with today because the last interaction was more than 90 days ago. With the Generate element, you can call an Apex Action which does a SOQL query for Accounts where the Owner is the logged in user (the salesperson) and identify those accounts where the last contact date was more than 90 days ago. The relevant accounts can be returned in the form of recommendations.

The strategy can be as simple as the Generate element with an Output element.

In the configuration dialog of the Generate element, select Accounts to Follow Up Today as the Apex Action and pass in ‘$User.id’ as an input parameter.

The Generate element, in turn, will call the getAccounts invocable method in the Generate_GetAccountsToFollowUp Apex class. Notice how you can fetch the relevant accounts and create a new list of recommendations where the Description includes the name of the account (account.Name) and the number of days since last contact (daysSinceLastContact).

global class Generate_GetAccountsToFollowUp {  
//NOTE: You can get the user id of the current user by also doing UserInfo.getUserId(). 
    @InvocableMethod(label='Accounts to Follow Up Today' 
                     description='Recommend accounts the current user should follow up on today')
    global static List<List<Recommendation>> getAccounts(List<String> inputData){
        List<List<Recommendation>> outputs = new List<List<Recommendation>>();
        Integer daysSinceLastContact;
        Account[] accounts = [SELECT Name, Description, LastContactDate__c, OwnerId FROM Account WHERE OwnerId = :inputData[0]];
        List<Recommendation> recs = new List<Recommendation>(); 
        for (Account account:accounts) {
            if (account.LastContactDate__c != null){
                daysSinceLastContact = account.LastContactDate__c.daysBetween(date.today());
                if (daysSinceLastContact > 90){
                    Recommendation rec = new Recommendation(
                        Name = account.Name,
                        Description = 'Connect with the ' + account.Name + ' account, the last interaction was '+ daysSinceLastContact + ' days ago.',
                        //Pre-req: Create a screen flow with the name simpleFlow                        
                        ActionReference = 'simpleFlow',
                        AcceptanceLabel = 'View'
                    );
                    recs.add(rec);
                }
            }
        }
        outputs.add(recs);
        return outputs; 
    }
}

Example: Get related Wikipedia pages as recommendations
You can also use the Generate node to create recommendations for external data sources. For example, consider a scenario where you want to show related Wikipedia pages, as recommendations, for a given topic. The Generate element enables you to call an Apex Action which in turn makes a GET request to a Wikipedia endpoint. The pages are returned in the form of recommendations.

A strategy with the Generate element can be as simple as the Generate element with an Output element.

In the configuration dialog of the Generate element, select Related Wikipedia Pages as the Apex Action and pass in ‘India’ as an input parameter.

The Generate element will call the getPages invocable method in the Generate_GetWikiPages Apex class. Notice how there is a mapping of Wikipedia articles to recommendation fields.

//Guidance around Apex class definitions: https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_classes_defining.htm
global class Generate_GetWikiPages {
    //InvocableMethod Guidance: https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_classes_annotation_InvocableMethod.htm
    //Input parameter must be a list or list of lists of either primitive type or Object type
    @InvocableMethod(
        label='Related Wikipedia Pages'
        description='Recommend wikipages that are related to the named input wikipage')
    global static List<List<Recommendation>> getPages(List<String> relatedTo){
        
        List<List<Recommendation>> outputs = new List<List<Recommendation>>();
        Http http = new Http();
        HttpRequest request = new HttpRequest();
        String urlName = relatedTo[0].replace(' ','_');
        // Pre-req: Set up a Remote Site Setting for this to work.
        request.setEndpoint('https://en.wikipedia.org/api/rest_v1/page/related/'+urlName);
        request.setMethod('GET');
        HttpResponse response = http.send(request);
        if(response.getStatusCode() == 200) {
            Map<String, Object> result = (Map<String, Object>) 
                JSON.deserializeUntyped(response.getBody());
            List<Recommendation> recs = new List<Recommendation>();
            for(Object item : (List<Object>)result.get('pages')) {
                Map<String, Object> page = (Map<String, Object>) item;
                Recommendation rec = new Recommendation(
                    Name = (String)page.get('displaytitle'),
                    Description = (String)page.get('extract'),
                    //Pre-req: Create a screen flow with the name simpleFlow
                    ActionReference = 'simpleFlow',
                    AcceptanceLabel = 'View'
                );
                recs.add(rec);
            }
            outputs.add(recs);
        }
        return outputs;
    }
}

Enhance

The Enhance element allows you to modify a given set of recommendations on-the-fly, every time the strategy is executed. These recommendations could be static recommendations that live as records in Salesforce or dynamic recommendations sourced from external data sources or other Salesforce objects. For example, you can use the enhance element to calculate a discount % for your customers based on how long the account has been with your company or you can use it as a means for A-B testing two branches of recommendations.

The Enhance element has the following characteristics:

@InvocableMethod(
label='Enhance with Discounts Based on Age' 
description='Returns an enhanced set of recommendations with appropriate discounts')
  • IMPORTANT – It can pass any number of inputs to the Apex Action. The input parameter MUST be a list or list of lists of a user-defined Apex object (e.g. a custom class called DataContainer) and the user-defined Apex object MUST include a List<Recommendation> variable. The List<Recommendation> variable will be automatically set with the recommendations flowing into the Enhance element.
global class DataContainer {
    @InvocableVariable
    public string accountId; 
    
    @InvocableVariable
    public List<Recommendation> recommendations;
}
________
global static List<List<Recommendation>> invocableMethod(List<DataContainer> inputData)
  • It returns a list of recommendations, List<List<Recommendation>>. Note – these recommendation edits are only in-memory and are not persisted after the strategy execution.
global static List<List<Recommendation>> invocableMethod(List<DataContainer> inputData)

Example: Enhance Recommendations with Discounts Based on Customer Age

Suppose you use Next Best Action to provide upsell recommendations. You’d like to reward your loyal customers by adding a 5 % discount to your product recommendations if the customer has been with your company for more than 1 year, 10% for more than 2 years, and 20% for more than 5 years. With the Enhance element, you can call an Apex Action which does a SOQL query to fetch the Account age and append it to the description of all incoming recommendations.

The strategy with the Enhance element can be as simple as Load → Enhance → Output. All recommendations retrieved or loaded by the Load element are implicitly passed as a list of recommendations to the underlying invocable method.

In the configuration dialog of the Enhance element, select Enhance with Discounts Based on Age as the Apex Action and pass in ‘$Record.id’ as the input parameter.

The Enhance element will in turn call the getDiscounts invocable method in the Enhance_GetAccountDiscount class. Notice how Description of each recommendation has a discount value appended to it (r.Description + ‘ with a 5% discount’).

global class Enhance_GetAccountDiscount {
    @InvocableMethod(label='Enhance with Discounts Based on Age' description='Returns an enhanced set of recommendations with appropriate discounts')
    global static List<List<Recommendation>> getDiscounts(List<DataContainer> inputData){
        
        List<Recommendation> recommendations = inputData[0].recommendations; 
        List<List<Recommendation>> outputs = new List<List<Recommendation>>();       
        Account[] accounts = [SELECT Name, Description,CreatedDate, id FROM Account WHERE id = :inputData[0].accountId];
           Double ageAccountMonths = accounts[0].CreatedDate.date().monthsBetween(date.today()); 
        Double ageAccount = ageAccountMonths/12;       
        List<Recommendation> returnedRecommendations = new List<Recommendation>(); 
        for (Recommendation r:recommendations){
            if(ageAccount > 1){
                r.Description = r.Description + ' with a 5% discount';
            }
            else if (ageAccount > 2){
                r.Description = r.Description + ' with a 10% discount';
            }
            else if (ageAccount > 5){
                r.Description = r.Description + ' with a 20% discount';
            }
            returnedRecommendations.add(r);
        }
        outputs.add(returnedRecommendations);                 
        return outputs;
        
    }

}

External Connections

External Connections have the following characteristics:

  • Like Enhance and Generate elements, they work in tandem with an Apex Action created using by adding the Invocable Action annotation to an Apex Class.
  • Unlike Enhance, External Connections do not take a set of recommendations as inputs. They return a single set of properties that can be referenced in expressions but not mapped to recommendations. Unlike Generate, External Connections do not result in the creation of new, temporary recommendations.
  • They run before the strategy is processed, and not during the processing of the strategy.
  • Their values are globally accessible (i.e you can use them in every element of the strategy).
  • They can return a broad range of different properties (Generate and Enhance always return a List of Recommendations).

Example: Calling a Heroku endpoint to fetch a credit score
Let’s suppose that you make certain upsell offers to your customers based on their credit-worthiness. For example, you offer them a 0% refinancing only if their credit score is above 750. Credit scores aren’t typically something you would store in Salesforce because they change dynamically and frequently. Using an external connection, you can call an Apex Action which will call an external endpoint and return the credit score given some information about the user.

global with sharing class GetFranchiseeData {
    @InvocableMethod
    public static List<Results> getCreditScore(List<Requests> requests)
       {
            Http http = new Http();
            HttpRequest request = new HttpRequest();
               //Make sure you add the below endpoint as a remote site setting
            request.setEndpoint('https://repair-service.herokuapp.com/api/creditScore?customerId='+ requests[0].custId);
            request.setMethod('GET');
            HttpResponse response = http.send(request);
             Results curResult = new Results();
            curResult.creditScore = Integer.valueOf(response.getBody());
           
            List<Results> resultsList = new List<results>();
            resultsList.add(curResult);
            return resultsList;
       }
    
    global class Requests {
        @InvocableVariable
      global String custId;
        
    }
    
    global class Results {
        @InvocableVariable
      global Integer creditScore;
    }
}

To call the above Apex Action, create an external connection and pass the Account’s External ID (If you pass values less than 4000, the API will return 550. It will return 810 for values greater than or equal to 4000.)

You can filter the Zero % Refinancing Offer based on the Credit Score by referencing the connection in the Filter node.

What’s New with Next Best Action in the Summer ’19 Release?

We have a bevy of new features in the Summer ’19 Release with 3 new elements – Generate, Enhance, and Map, the Actions and Recommendations Lightning component, and support for packaging strategies. Check out the video to learn more!

Mass Action Scheduler 2.2 by Doug Ayers

Declaratively schedule Process Builder, Flows, Quick Actions, Email Alerts, Workflow Rules, or Apex to process records from Reports, List Views, SOQL, or Apex.

Click here for release notes, source code and install package

How to do more with Custom Permissions in Salesforce Lightning Flows by Scott McClung

Apex Action to make Custom Permissions even more useful in flows.

  • Detailed write up available here
  • To install the package click here