Flow Masterclass: Create Declarative Flow Actions with Metaprogramming

Flow’s ability to directly use custom data types gives it the ability to work with data structured as apex objects. This is extremely powerful but still requires that the data structure exist in the form of an Apex Class. Usually that’s not a problem. But some interesting use cases involve the creation of lots of different data structures.

Suppose you have an AI that processes PDF documents that are derived from scans of paper forms. As part of the configuration, your AI is trained to recognize different form types. One ‘Mailing List’ form type might generate firstnames, lastnames, and addresses while an ‘Order’ form type might generate orderNumbers and orderQuantities.

You now want the ability to take the data from those scanned forms and use Flow to process it. That means that the user might, on flow screen 1, upload a PDF file. Then the file would get passed to the AI and get back a set of keys and values (example: ‘firstname’ : ‘Stephen’). If we can output these keys and values to Flow, then we can have the flow do things like create Contact records or send emails.

The straightforward way to do this would be to create one Apex class for the Mailing List form, and ‘auraEnable’ it so that it shows up in Flow as an Apex-defined Type:

We’d create a similar one for the Order form, and when the flow designer creates their flow, they’d simply select the one that was a match for the current job.

But we have a tougher challenge here, because we want the use to be able to freely (and without code) create lots of unique custom form types. In order to support that, we need to trigger each time a form type is changed, and automatically generate the appropriate Apex data type in the background.

This isn’t easy to do. It requires the use of a multistep process that involves:

  1. starting with a list of the names of the fields, do string manipulation in Apex to build up a string representing the Apex Class you want to generate
  2. programmatically assemble a zip file package of metadata, including a package.xml file
  3. Base64 encode the package and deploy it from apex using standard metadata deployment.

Fortunately, Andrew Fawcett yoda’ed the hell out of this problem back in the 2010’s. His MetadataService forms the backbone of our solution.

The solution has 2 parts:

  1. You use the GenerateCustomClasses code to take a list of string keys (technically it expects a Map<String, String> but only the keys are used) and generate two Apex classes.
  2. The two classes are a custom datatype (aka an Apex-defined type) and an invocable action called ProcessForm_[foo], where the full name is determined by an input to the GenerateCustomClasses code.

The GenerateCustomClasses code is set up as an invocable action but in practice it’s unlikely to be used declaratively. A more realistic scenario is to have the creation of some kind of data structure trigger this code to run and generate the two classes so that the data structure can be manipulated declaratively in Flow.

Note that because the generation process involves a metadata deployment call back into your org, you will need to add a remote site setting. The easiest way to find out what you need is to just run the flow and then copy out of the error message:

Install or View Source

To install, clone the repo at https://github.com/alexed1/FormToFlowMapper

Updates to Dedupe Record Collections Action & ExtractStringsFromCollection Action

Update 5/18/20

The ‘Dedupe Fields From Record Collection‘ action is being retired and the functionality is now being moved into the CollectionsProcessors ‘ExtractFieldsFromCollection’ action:

Ragan Walker added several improvements to the action:

  • Parameterizes dedupeValues functionality (defaults to true)
  • You can now output to a comma-separated text variable instead of just a text collection variable
  • Supports extracting + deduping of multi-select picklist fields
  • Adds flow documentation
  • Supports bulkification
  • Adds exception handling to make action more robust
  • Adds further unit testing

You can still download the ‘DedupeRecordsFromCollection’ action below.

Intro

A few months back I posted about an action Danny Tilton and I over at CapTech Consulting worked on that has simplified a ton of my Flows. You can find that on my LinkedIn profile here (sorry, shameless plug: Here)

Code files can be found here https://github.com/alexed1/LightningFlowComponents/tree/master/flow_action_components

New Version Highlights

  • We separated the action into two classes: DedupeFieldsFromRecordCollection and DedupeRecordCollection to simplify its usage.
  • Added a couple error exceptions – just in case!
  • You can now output results from DedupeFieldsFromRecordCollection to a comma-separated text variable (instead of just a collection)!

Dedupe Fields From Record Collection

NOTE: THIS ACTION IS NOW RETIRED IN FAVOR OF REVAMPING THE ‘ExtractStringFromCollection’ action. See the update at the beginning of this post.

Summary

This class takes in a Record Collection and spits out all of the de-duped values of a field of your choosing across the records in the collection.

I used this in my LinkedIn Post, but say you wanted to get a list of all of the Owners of a bunch of records, but you didnt want a bunch of duplicate IDs. Run this bad boy on your collection and you’ll get a collection or text string of unique IDs! You can that loop on that to send an email alert, for example.

Inputs

  • Object Type
  • InputRecordCollection – The collection you want to get the de-duped field from
  • fieldToDedupeOn – The API Name of the unique field values you want to return in your text collection

Outputs

Dedupe Record Collection

Installation Link: https://login.salesforce.com/packaging/installPackage.apexp?p0=04t4T000000x9LA

Sandbox Link (recommended): https://test.salesforce.com/packaging/installPackage.apexp?p0=04t4T000000x9LA

Summary

This is quite similar to the other class – instead of the action returning text, it just returns a set of de-duped records based on the field you specify.

In the example above you could de-dupe a set of Accounts based on OwnerId, then loop over each of the Accounts to grab its Owner to email on a nightly schedule. I think where this would come in handy is if you needed to grab other details about the record you’re de-duping on. Let’s say in your email to the Record Owner you wanted to give a few details about the record – that’s where you’d want this action.

Inputs

  • Object Type
  • inputRecordCollection – The collection you want to de-dupe
  • fieldToDedupeOn – The API Name of the unique field values you want to de-dupe all of the records on

Outputs

  • Object Type
  • outputRecordCollection

How to use ISCHANGED and PRIORVALUE in Before Save Flows by GORAV SETH

GORAV SETH demonstrates how you can use ISCHANGED and PRIORVALUE in Before Save Flows
Check it out

Get Information About A Flow with GetFlowMetadata

The Automation Components repo that was published last week features a useful action that I don’t think I ever created a blog post for.

GetFlowMetadata (what’s it’s called in the Automation Components repo) provides metadata about a Flow. Just provide it with a name OR a recordId and it returns:


        public String FlowApiName;

        public String FlowDefinitionId; 
       
        public String ActiveVersionId; 
    
        public String Description; 

        public String Label; 

        public String LatestVersionId; 

        public String NamespacePrefix; 
   
        public String FlowType; 

Install

Install this from the new Automation Components repo via a managed package

Install

View Source

Automation Component Source Docs

How to launch Flows from Your Own Lightning Web Component

Target Persona: Developer

If you’re working in Aura, you can insert the lighting:flow component. This is the same component that you can insert into pages in App Builder. However, Salesforce has not yet made lightning:flow available in LWC. Because LWC does not allow the embedding of Aura components, you can’t just add the lightning:flow component to your LWC. Why isn’t it available yet? lightning:flow is probably the most complex page component and has to be completely rewritten to work in LWC. It’s on the way, but it’s taking time.

The current options for working around this depend on whether you want to execute a visual flow that has screens and requires a user session with a browser, or whether you want to execute a screen-less ‘Autolaunched’ flow.

For Screen Flows

Wrapping lightning:flow and your LWC in an Aura wrapper component, and sending messages from your LWC to control the Flow component. Check it out here.

You can have your component load the Flow by URL. This will either load the Flow into a new tab or a new window, or you can have it overwrite your existing page (and your existing LWC).

For Autolaunched Flows

If the Flow is an Autolaunched flow (and thus doesn’t have screens) you can dynamically launch it from your LWC code by using an Apex call to the Flow invocation point.

Flows can be launched from platform events as of Summer ’20, so you can have your LWC fire a platform event. This also only works with screen-less flows.

A Big Honor For UnofficialSF: Some of Our Components Just Got ‘Officialized’!

As you can see from this new post from Salesforce evangelism, Salesforce has officially recognized the work being done by the Flowhana, and in particular by Eric Smith (Quick Choice), Chris Van Der Merwe (Calculate Business Hours), David Entremont (Save Records Async), Enrico Murru (Evaluate Formula), and Andrii Kraiev (many of the actions). I worked with Philippe Ozil from Developer Evangelism, as he picked a selection of components, polished them up, added tests, and then created a new Automation Components repo.

I’m pleased with this on several levels:

  • It will attract new members to the Flowhana and introduce people to UnofficialSF
  • The code has gotten a polish and review by some very seasoned Salesforce developers who know a lot of useful things
  • The components have been made available as managed packages. Usually, we haven’t budgeted the time to create managed package versions on UnofficialSF

Managing Things Going Forward

Developers can contribute code changes via pull requests to the Automation Components actions, and they can continue to contribute to UnofficialSF. The process for Automation Components is more formal than what we do here. Philippe, the repository manager, says “it’s good to validate interest with a question first then have a PR. Then let’s be clear about best practices and code coverage reqs.”

Convert Records to a CSV File

Original work done by Ragan Walker and Adam White with additional credit due to Narender Singh’s ‘Create Records from CSV‘ action and CapTech Consulting. This was built this to fully utilize some of UnofficialSF’s awesome custom actions like ExcecuteSOQL, Send Rich Text Email, and Get Field Names from a Layout.

Video

There’s a video showing off the examples at the bottom of the post – Check it out

Install

This component is now part of Flow Screen Action Base Pack as of version 2.36

A Note on Files

If you arent familiar with the data model / structure of Salesforce Files (ContentDocumentLink,ContentDocument,ContentVersion) I STRONGLY recommend getting familiar with it. Not only because it will help you understand this action better, but because every admin and developer should know how they function.

Alex Edelstein also wrote a very helpful article on the matter here: https://unofficialsf.com/using-files-and-attachments-in-flow/ We built this to easily support providing a collection of ContentDocumentLinks to feed straight into the Send Rich Text Email action!

Use Cases

  • Generate a CSV file and Email it to a group of people, even non-SF Users using Send Rich Text Email
  • Generate a CSV file and post it to a Chatter Group or a Document Library
  • Create a screen flow triggered from a Related List, List View, or Quick Action that lets users pick records using Datatable and output their selections to a CSV file, then email that file to a set of users that they pick.
  • Send a CSV of records to an another system using invoked Apex Actions
  • Generate a CSV file from a ‘Complicated Collection’ – Utilize UnofficialSF’s Collections Processors to create collections of records that would be impossible with Salesforce Reports or even SOQL

Features

  • Create Multiple ContentDocumentLinks – Linkages between File and Records – Specify a string or text collection of record IDs to associate the file with those records
  • Sharing / Collaborator Rights – Specify the sharing and visibility of each ContentDocumentLink
  • Limit the Fields Exported to CSV – Customize the order and fields included in your CSV via text collection or comma-seperated string. This is optional, if left blank, it will export all fields that were in the collection / query.
  • Aggregate Queries and Custom Relationship Support* – Support for relationship fields provided by ExcecuteSOQL Example: Contact__r.Custom_Field__c.
  • Run Asynchronously – Can execute ‘asynchronously’ if CPU timing is an issue.
  • Generate a Platform Event if ran asynchronously – If ran asynchronously, supports generating a platform event of type ‘CSV_Document__e’. The platform event does not come packaged with this, you will need to create it manually for now. See below for when and why you’d want to use this.
  • Error Handling – We added some error handling for a variety of scenarios – including if you hit CPU limits we suggest switching you into asynchronous mode

Inputs

Required:

  • recordCollection
    • Record (sObject) Collection – The collection you want to turn into a CSV
  • documentTitle
    • Text – The title of the File when generated

Optional:

  • documentShareType
    • Text – Sharing permissions for the file. Available values: “V” (viewer), “C” (collaborator), “I” (inferred); Default value: “V”
  • documentVisibility
    • Text – Specifies whether the document is available to all users, internal users, or shared users. Available values: “AllUsers”, “InternalUsers”, “SharedUsers”; Default value: “AllUsers”
  • linkedRecordIds
    • Text Collection – List of recordIds to link generated document to. If this and linkedRecordIdsString are left blank, will only relate to the running user
  • linkedRecordIdsString
    • Text – Comma-separated string of recordIds to link generated document to. If this and linkedRecordIds are left blank, will only relate to the running user
  • fieldsCollection
    • Text – Collection of strings of record fields names to print as columns in CSV. If this and fieldsString are empty, all populated fields on the record collection will be displayed.
    • IMPORTANT: If you want to include related fields from a SOQL query (Custom_Object__r.Custom__c) this parameter is required.
    • If you used field aliasing in an aggregate query, alias names can be provided rather than the API names.
  • fieldsString
    • Text – Comma-separated string of record fields names to print as columns in CSV. If this and fieldsCollection are empty, all populated fields on the record collection will be displayed.
    • IMPORTANT: If you want to include related fields from a SOQL query (Custom_Object__r.Custom__c) this parameter is required.
    • If you used field aliasing in an aggregate query, alias names can be provided rather than the API names.
  • executeAsyncIdentifier
    • Text – If provided, the action will execute asynchronously and return values will be posted to a platform event: CSV_Document__e. Use this identifier to listen for the platform event in a ‘Wait’ element in flow or use Summer 20’s ‘Invoke Flow from a Platform Event’.
    • Note: You generally want to pass in the Flow’s Interview ID (Done by accessing the Flow global variable $Flow.InterviewGuid

Outputs (Optional)

If the action is executed asynchronously (indicated via executeAsyncIdentifier parameter), then the job id(s) for the execution are returned immediately and the document information is published via platform event if configured. Otherwise when run synchronously the CSV File information is returned immediately.

  • ContentDocumentID
    • Text – ID of the ContentDocument generated
  • ContentVersionID
    • Text – ID of the ContentVersion generated
  • ContentDocumentLinkIDs
    • Text Collection – IDs of the ContentDocumentLinks generated
  • asyncJobIDs
    • Text Collection – Id of the asynchronous job queued (AsyncApexJob). If the action was executed asynchronously all other return values will be null. Use this to query for status of the job.

Considerations

When using this action, you are still subject to normal governor limits, even when running this action asynchronously. We’ve added customized error handling should you input a collection of records that exceed the available heap limit.

Examples

Create a CSV file then post it to a Chatter Group

One potential use case here could be generating a CSV from a Flow lookup, then posting the file generated to a Chatter Group AND to a specific Account. Here’s how we’ll relate the file to multiple entities using a collection of ContentDocumentLinks.

Here’s how you’d do it:

  • Generate whatever Record Collection you want from a Flow Get step. Feel free to choose ‘All fields’ but beware – you will want to limit the fields when you configure the action or you’ll get every field on the object in your file.
  • Do another Lookup on ‘CollaborationGroup’ using the group name to get the ID of the Chatter Group
  • Create a Text variable and check ‘Allow multiple values (collection)’ This is what you’ll be adding your related IDs to.
  • Create an assignment step to add both the ‘recordId’ variable used to launch the flow and the results of the Chatter Group lookup Id {!Look_up_Chatter_Group_ID.Id}.
  • We’re ready to generate our CSV! See below for how you’d configure it. For documentTitle I recommend a formula field and grab the related record’s details and the TODAY() function to add to the file name. Check out the ContentDocumentLink documentation linked above for a good description on what each of the values mean for ShareType and Visibility.
  • Your CSV will now be in the ‘Files’ related list of any records you put into the linkedRecordIDs String/Collection!
    • Note: If you don’t specify any Linked Records, the File will simply be associated to the user that runs the Flow.
  • If you want to create a chatter post to the group then relate the file to the post as well, you can use the out of the box ‘post to chatter’ action and then do a ‘Create’ step on ‘FeeedAttachment’ using the output ID of the chatter step and the outputs from the CSV action:

Output aggregate queries with ExecuteSOQL

This action is also supports aggregate querying with Alias support with ExecuteSOQL.

Aggregate functions in SOQL, such as SUM() and MAX(), allow you to roll up and summarize your data in a query. For more information on aggregate functions, see “Aggregate Functions” in the Salesforce SOQL and SOSL Reference Guide.

Aggregate Queries

Let’s try passing in a query string from ExecuteSOQL into the CSV action – including an Alias for our Count expression. By providing an alias for our count, the CSV will have a nice clean column name instead of some nasty ‘expr0′ label.

SELECT count(Id) NumberOfAccounts, Type
FROM Account
WHERE type != null
Group by Type

  • Run the ExecuteSOQL action using the query above in a Text Template variable as the ‘soqlQuery’ input.
  • NOTE: MAKE SURE YOU USE PLAIN TEXT! There’s also a bug in flow where if you make any changes to the text and save it, it will revert to rich text. Every time you edit it, you will need to flip it to plain text.
  • Run the action with the output from the ExecuteSOQL action and voila!

And the output file:

Related Fields

When paired with ExecuteSOQL you can also output related fields! Check out the video for how to do this. The key thing here is that you need to make sure all the fields in your SELECT statement are in the ‘fieldsString’ input. So if the query you pass into ExecuteSOQL is:

select id, name,Type, Ultimate_Parent_Company__r.Name from Account where Id = ‘{!recordId}’

You should be putting this into the fieldsString input for the CSV action:

TIP: It might be easier and more maintainable if you store your fields in a text variable so you can reference them in one place.

Wait for a Platform Event and Send an Email with the File Attached.

  • Create a new Platform Event type – call it ‘CSV Export Event’ and create the fields exactly as shown in the code below.
  • Comment back in the code referencing the platform event in the Apex Class (aka remove all the backslashes)

Your uncommented code should look like this:

  • You’re now ready to utilize the Platform Event functionality! The next step is really up to you and your process. In Summer ’20 you can choose to launch a flow based on this platform event type, or you can incorporate a ‘Wait’ element and resume on a platform event being posted. See below for an example.
  • For the CSV action you want to populate the executeAsyncIdentifier input variable with the Flow Interview ID:
  • Here’s how you’d configure it the Resume piece of your ‘Pause’ action, :
  • You can store the Platform Event Response in a ‘record variable’ for use later in the flow. Just be sure to assign anything you might want from it in a separate variable like below:
  • Using the response from the platform event, you can use the Response’s ContentDocumentID to then do a ‘Get’ on ContentDocumentLink using the response ID and store it in a Record Collection Variable to be used in the handy dandy Send HTML Action. Make sure you store All Records.
  • In your Send HTML Action configuration just throw in the results of the ‘Get’ above and you’re all set to send attachments!

Here’s the full picture:

The SOQL Wins Keep Coming: Execute SOQL action now supports date literals and ID literals

With version 1.3.2 of Execute SOQL, you can now pass in queries like these in your Flow and get back records:

SELECT LastName from Contact WHERE Id = '1123xxx333e3e'

Query on date literal:
SELECT Id from Contact WHERE CreatedDate = LAST_n_DAYS:14

Kudos to new contributor Ragan Walker, part of the impressive operation at Captech Consulting. Keep an eye out: I hear they’re about to post a crazy CSV utility.

Check it out!

Easy SOSL Searching with ‘Search with Apex’ from GravityLab

The community has done some great work bringing SOQL tools to Flow, but this recently published action from GravityLab demonstrates how you can use the recently shipped dynamic SObject support to allow for the first time, record searches in flow to leverage the same search Salesforce offers in the Omnisearch bar, complete with fuzzy logic and incomplete words.

It also supports SOQL queries. Some of its distinctive features:

  1. Bypass sharing rules
  2. Pass in a List of Ids.

They’ve provided a video, source code and an installable package. Check it Out!