〰️LWC, Apex and Flows

In this article we will take a look at a more complex logging scenario involving an LWC component, a corresponding Apex controller, and an autolaunched Flow.

In the previous article we covered an LWC and Apex use case. Let's now kick it up a notch and add one more Salesforce technology to the mix - Flows. In particular, we'll take a look at an autolaunched flow that invokes after a DML operation is performed in Apex. Our goal, like before, is to produce a unified log trace across all three - LWC, Apex and Flow. If this sort of use case seems interesting, consider what would happen if the Flow were to fail with an unhandled fault! Turns out Pharos Triton handles Flow failures like this and is even able to pull this error under the right parent log to give you a complete view of the execution.

We'll assume, like in the previous article, that the start of our user flow originates from a button click (an LWC action). The full sequence of steps is as follows:

  1. LWC action

  2. Apex controller performs a DML

  3. Autolaunched flow is triggered

  4. Autolaunched flow fails (no fault handler)

The last step typically gets lost

Let's begin by examining the LWC side.

LWC Component

Our LWC component will be very basic for this scenario. All we need is to add a button to invoke the click handler.

logDemo.html
<template>
    .....
    
    <lightning-button 
        variant="brand" 
        label="DML Error" 
        title="DML Error" 
        onclick={handleClick} 
        class="slds-m-left_x-small">
    </lightning-button>
    
    ....
</template>

LWC Controller

This snippet is very similar to the one in our previous article. There are 2 minor changes:

  1. The summary and details of our LWC debug log records on line 34

  2. A different Apex action is invoked this time. Instead of a DML error we're simply performing an update that executes an autolaunched flow, line 49

logDemo.js
//this apex action will generate a transaction Id on the back end 
//that will be used across js and apex
import getTransactionId from '@salesforce/apex/LogDemoController.getTransactionId';
//our apex action that will perform some logging and kick off a flow
import apexActionThatTriggersFlow from '@salesforce/apex/LogDemoController.apexActionThatTriggersFlow';
//Triton logger component
import Triton from 'c/triton';
//import Triton enums 
import { AREA, TYPE } from 'c/triton';

export default class LogDemo extends LightningElement {

    tritonLogger;
    @track transactionId;
    
    connectedCallback() {
        //create the logger
        this.tritonLogger = new Triton();
    }
    
    handleClick(event) {
        //note down some timing values for later
        this.startTime = performance.now();
        this.createdTimestamp = Date.now();
    
        //generate a transaction Id and start our UI execution
        getTransactionId({
        }).then((data) => {
            this.transactionId = data;
            this.executeScenario();
        }
    }
    
    executeScenario() {
        this.tritonLogger.debug(
            TYPE.FRONTEND, /* type */
            AREA.LWC, /* functional area */
            'LWC action (from js) - Flow Error', /* summary */
            'LWC action (from js) - Flow Error - Execution Start', /* details */
            this.transactionId, /* transaction Id generated earlier */
            {name: 'LogUtilDemo', function: 'executeScenario'}, /* component info: name and function */
            performance.now() - this.startTime, /* duration of execution up to this point */
            this.createdTimestamp /* starting time of our execution */
        );
    
        //invoke our Apex action passing in the transaction Id 
        //for the apex controller to re-use
        apexActionThatTriggersFlow({
            transactionId: this.transactionId
        }).then(() => {
        }).catch((error) => {
            //log this exception
            this.tritonLogger.exception(error, null);
        });
    }

    ....
}    
    

The handleClick() method, as in the previous example will generate a single LWC debug log and an error log.

Apex Controller

Next, let's look at our Apex controller. The action that we're calling from LWC is also doing some debug and error logging with the provided transaction Id.

LogDemoController.cls
public with sharing class LogDemoController {

//this method will use our logger to create a new transaction Id
@AuraEnabled
public static String getTransactionId() {
	return Triton.instance.startTransaction();
}

@AuraEnabled
public static void apexActionThatTriggersFlow(String transactionId) {
	//letting our logger know to re-use an existing transaction Id
	Triton.instance.resumeTransaction(transactionId);
	Triton.instance.debug(TritonTypes.Type.Backend, /* type */
			TritonTypes.Area.Community, /* functional area */
			'LWC controller (apex) - Flow Error', /* summary */
			'LWC controller (apex) - Flow Error - Apex Execution Start' /* details */
	);
	
	try {
		//update a random account to kick off our flow
		List<Account> accounts = [SELECT Id, Name FROM Account LIMIT 1];
		if (!accounts.isEmpty()) {
			accounts[0].Name = 'Log Demo (Flow)';
			update accounts;
		}
	} catch (Exception e) {
		//let's log this exception
		Triton.instance.error(TritonTypes.Area.Community, e);
	}
}
....

}

The key difference from our previous example is that there's now an extra step involved in the execution. We still expect to catch an Exception on line 28 but not until our Flow fails further down the road. Now, let's have a look at the autolaunched flow we'll be using.

The Flow

For this example we've created a very simple Flow that is triggered to execute on Account Name update. Using this Flow, we'd also like to illustrate another Pharos Triton capability to associate unhandled flow errors to the same parent log that started the original transaction. In order to do that, we will need to have this Flow fail in the second step. Your own Flows don't necessarily need to fail (in fact, we prefer they don't) in order for Pharos Triton to save your logs.

Let's take a look at this Flow in more detail:

Entry Criteria

Before we examine each element, let's have a quick look at the entry criteria. Our goal is to kick off the flow on Account Name update. Here's what the conditions look like:

Saving a Debug Log

Next, let's examine the Debug Apex (invocable) Action that is the very first element of this Flow. This step is very important and allows Pharos Triton to link an unhandled Flow failure to the larger execution trace later on.

Below is the data we're passing to the invocable method. Note especially the Interview GUID parameter that is being passed in:

Couple things to note here:

  1. Category = Debug. Setting the category value will render an appropriate view on the log record and will asign a debug icon.

  2. Type = Autolaunched Flow. While this value could be anything, in theory, it's best to stick with the convention Pharos uses to identify Flow logs. By default the automated error capture will stamp the Type field according to the type of Flow that fails: autolaunched or screen.

Now let's take a look at the highlighted Interview GUID more closely. This value identifies a running instance of a Flow. This is somewhat similar to Request Id in Apex, if you happen to be familiar with that. The main takeaway here is that by passing this parameter to Pharos Triton, you can ensure that any subsequent unhandled errors in this Flow will be associated to the log records you may have created earlier. If this value is not passed in, Pharos will still capture any unhandled Flow failures, but they will not be linked to any parent logs and simply exist standalone.

We recommend that you add a Pharos Triton invocable Apex Action as the very first element of your Flows if you intend to associate unhandled failures together with your larger execution traces. If the initial invocable method is not placed at the very beginning of the Flow, and an unhandled failure does occur earlier on, it may not be properly linked to the right parent log.

Forcing an Unhandled Failure

And finally, the very last flow element is the Approval Action. This element is unremarkable for our purposes and is only needed to actually force an unhandled error to occur. Here is how it's defined:

The reason this Flow will fail is that there's no approval process defined for Accounts.

The Result

Altogether, both LWC, Apex, and Flow executions will generate the following log structure:

  • Parent LWC Debug Log from executeScenario(), line 36

    1. Child Apex Debug Log from our apexActionThatTriggersFlow() action, line 13

    2. Child Flow Debug Log from our Debug invocable Apex Action

    3. Child Apex Error Log from the try/catch block in apexActionThatTriggersFlow(), line 28

    4. Child Flow Error Log (autogenerated)

The last autogenerated log may not be the last entry in the structure above. Depending on how quickly Salesforce reports this failure, it could potentially end up ahead of the Apex error log (3).

Last updated