Viewing Platform Events in Salesforce using a Lightning Web Component

Author: Bruce Tollefson Published: April 10, 2021; Modified: May 9, 2022
lightning_bolts

Platform events can be a pain to work with when trying to view/troubleshoot. You can either create a trigger, subscribe with an external application, or use the emp-api. Using a trigger is difficult to read in the debug, an external application creates other barriers such as connecting to Salesforce along with taking events, which leaves the emp-api. My personal choice, the emp-api, is not without its draw backs.

Using this api in either a LWC or aura component will still count towards the external platform event limit as it uses the CometD protocol. So if using in a production environment caution should be taken. This also requires developing a component.

Although the api requires some development and can use events personally I think it is the most clear way to identify what is being published on the event bus. The output of the events will be the raw output that is coming from the event bus, this will give a clear look at the structure of the message and provide the ability to easily send and grab samples if need be. This also provides you with abilities to customize how you would like the event to be presented.

Custom Component Functionalities

This will go through a lightning web component example using the emp-api. In the component you can:

  1. Subscribe to single or multiple channel
  2. Unsubscribe to those same channels
  3. Clear the list of events from the component
  4. Switch between viewing a long list of events or the last single event
  5. Select where to replay in the event bus from and/or put in a replayId

Lightning Web Component HTML

Starting with the HTML of the emp-api LWC we will create the replay picklist type and input field. This is all wrapped in a lightning-card.

<template> <lightning-card title="CDC/Platform Event Subscriber" icon-name="custom:custom14"> <div class="slds-m-around_medium"> <!-- Pick Replay Event Type --> <lightning-combobox name="replayType" label="Select where you would like to Replay from" placeholder="Select Replay From" value="-1" options={options} onchange={handleReplayIdList} required ></lightning-combobox> <!-- Add Replay Event Id if applicable --> <template if:true={hasCustomReplay}> <div class="slds-p-around_medium lgc-bg"> <lightning-input type="text" label="Enter some text" required onkeyup={updateReplayId}> </lightning-input> </div> </template>

Now we will add the channel input, optional checkbox and group of buttons for subscribing, switching modes, and clearing the event messages.

<!-- Add Subscription Channel --> <div class="slds-p-top_small"> <p>Add your streaming channel</p> <lightning-input label="Channel Name" value={channelName} onchange={handleChannelName}> </lightning-input> </div> <!-- Ask if only the single Replay Id should be used --> <div class="slds-p-top_small"> <template if:true={hasCustomReplay}> <div class="slds-p-around_small"> <lightning-input type="checkbox" label="Only get single replay event and not all after" name="input1" onchange={handleSingleReplayId}></lightning-input> </div> <!-- Subscribe, Single Event, Multiple Event, Clear --> </template> <lightning-button variant="success" label="Subscribe" title="Subscribe" onclick={handleSubscribe} class="slds-m-left_x-small"> </lightning-button> <lightning-button-group> <lightning-button variant="Brand" label="Single Event" title="Single Event" onclick={handleSingleEvent} disabled={singleEvent} class="slds-m-left_x-small"></lightning-button> <lightning-button variant="Brand" label={multiEventStr} title="Multiple Events" onclick={handleMultiEvent} disabled={multiEvent} class="slds-m-left_x-small"></lightning-button> </lightning-button-group> <lightning-button variant="neutral" label="Clear" title="Clear" onclick={handleClear} class="slds-m-left_x-small"> </lightning-button> </div> </div>

After adding the buttons we will add the ability to select and unsubscribe to subscribed channels.

<!-- Subscribed --> <template if:true={isSubscribedChannels}> <lightning-radio-group name="radioGroup" class="slds-m-left_medium" label="Select to unsubscribe" options={subscribedChannels} type="radio" onchange={handleUnsubChannelChange}> </lightning-radio-group> <!-- Unsubscribe --> <div class="slds-m-around_medium"> <lightning-button variant="destructive" label="Unsubscribe" title="Unsubscribe" onclick={handleUnsubscribe} disabled={isUnsubscribeDisabled}> </lightning-button> </div> </template> </lightning-card> </template>

Now to add the output, this will create a border, add a spinner, and output either a list of the events or a single message to view.

<!-- Divider --> <div style="border-top-style: dotted; border-top-width: 2px; border-top-color: coral;"></div> <!-- Spinner --> <div if:true={showSpinner} class="slds-p-around_large"> <div if:true={showSpinner} class="slds-is-relative"> <lightning-spinner variant="brand" alternative-text="Loading..."> </lightning-spinner> </div> </div> <!-- Single Output --> <template if:true={singleEvent}> <pre> <code> {lastMessage} </code> </pre> </template> <!-- Multi-Output --> <template if:true={lastMessage}> <template if:false={singleEvent}> <template if:true={events}> <template for:each={events} for:item="evt"> <pre key={evt.ReplayId} class="slds-border_bottom slds-p-bottom_small"> <code> {evt.message} </code> </pre> </template> </template> </template> </template>

Lightning Web Component Javascript

Moving from the HTML we will create all of the javascript methods and fields. This is to communicate with the HTML and display the necessary values in the component. Start by importing the LWC, emp-api, toast components, and setting all of the fields.

import { LightningElement } from 'lwc'; import { subscribe, unsubscribe, onError, setDebugFlag, isEmpEnabled } from 'lightning/empApi'; import { ShowToastEvent } from 'lightning/platformShowToastEvent'; export default class EmpApiLWC extends LightningElement { channelName = '/data/ChangeEvents'; //subscribing channel isSubscribeDisabled = false; //disable subscribe button unsubOption = ''; isUnsubscribeDisabled = true; //disable unsubscribe button lastMessage = ''; //last message read singleEvent = true; //single event mode - only shows last read message multiEvent = false; //multiple event mode - shows all read messages hasCustomReplay = false; //if the custom replay option was selected isSingleReplayId = false; //if the custom replay option was selected and the user only wants to view the one replay id instead of viewing everythign from this event replayIdNumber = '-1'; //initial replay Id -1 is new events from the sibscribed point in time showSpinner = false; // for spinner events = []; //array of events subscribedChannels = [];//array of subscribed channels isSubscribedChannels = false; //used for showing unsubscribe button and channels mapSubscription; //map of subscriptions based on replayId --> subscription proxy object used for unsubscribing subChannelMap; //map of subscriptions based on replayId --> subsctiption option label and values used for tracking current channels multiEventStr = 'Multiple Events'; //button label get options() { // different replay options return [ { label: 'First Event on Bus', value: '-2' }, //get events from the beginning of the last 72 hours { label: 'New Events', value: '-1' }, //get only current events after subscribing { label: 'I have a Replay Id', value: 'hasId' }, //start from a specified replay id ]; }

Next add the connectedCallBack hook, create the channelName change method, and the subscriber method.

// Fires right away and creates the maps and register listener connectedCallback() { // Register error listener this.registerErrorListener(); this.mapSubscription = new Map();//instantiate map this.subChannelMap = new Map();//instantiate map } // Tracks changes to channelName text field handleChannelName(event) { this.channelName = event.target.value; } // Handles subscribe button click - Subscribe to the specified channel handleSubscribe() { this.showSpinner = true; if(!this.subChannelMap.get(this.channelName)){ //check to see if this is a new channel from the subscribed channel map // Callback invoked whenever a new event message is received const messageCallback = (response) => { this.showSpinner = false; let sEvent = () => { return{ ReplayId : '', message : '' } } // create new event object sEvent.ReplayId = response.data.event.replayId; //get replayId sEvent.message = JSON.stringify(response, null, 2); //get the event this.events.push({ReplayId: `${sEvent.ReplayId}${Date.now()}`, message: sEvent.message}); //push event object this.multiEventStr = `Multiple Events (${this.events.length})`; //add length to the button this.lastMessage = JSON.stringify(response, null, 2); //put last event into the string //if the single replay id is checked and there is a custom replay id - used to unsubscribe and not view/use a bunch of other events if(this.isSingleReplayId === true && sEvent.ReplayId === (this.replayIdNumber + 1)){ this.unsubOption = this.channelName; //get current channel name this.handleUnsubscribe(); //unsubscribe } }; // Invoke subscribe method of empApi. Pass reference to messageCallback subscribe(this.channelName, this.replayIdNumber, messageCallback).then(response => { // Response contains the subscription information on successful subscribe call this.isSubscribedChannels = true; if(response){ //create subscribed channel object for the list of channels this.subchannel = { label : response.channel, value : response.channel }; this.subChannelMap.set(response.channel,this.subchannel);//add channel to map this.subscribedChannels = Array.from(this.subChannelMap.values());//convert values to array this.mapSubscription.set(response.channel, response);//add subscription(returned as response) to map //send toast successful subscribe const evt = new ShowToastEvent({ message: `Successfully subscribed to : ${JSON.stringify(response.channel)}`, variant: 'success', }); this.dispatchEvent(evt); } }); }else{ //send notification already subscribed const evt = new ShowToastEvent({ message: `${JSON.stringify(this.channelName)} has already been subscribed if you are trying to change Replay Type unsubscribe from channel then resubscribe`, variant: 'error', }); this.dispatchEvent(evt); this.showSpinner = true; } }

The bulk of the work is done in the subscriber and the unsubscriber. The subscriber will subscribe a channel and provide a toast while also checking to see if the channel has already previously been subscribed. Add the unsubscriber along with the handlers for toggling the buttons between single event mode and multiple event mode.

// Handles unsubscribe button click handleUnsubscribe() { this.showSpinner = false; // Invoke unsubscribe method of empApi unsubscribe(this.mapSubscription.get(this.unsubOption), response => { // send unsubscribe toast const evt = new ShowToastEvent({ message: `Successfully unsubscribed from : ${JSON.stringify(response.subscription)}`, variant: 'warning', }); this.dispatchEvent(evt); this.mapSubscription.delete(this.unsubOption);// delete from the subscription map this.deleteSubChannel(this.unsubOption); //delete from the subscribe channel map }); } //when single event is clicked change to single event mode handleSingleEvent(){ this.toggleSingleEvent(true); } //when multi event is clicked change to mulit event mode handleMultiEvent(){ this.toggleSingleEvent(false); } //toggle handler toggleSingleEvent(singleEvent){ this.singleEvent = singleEvent; this.multiEvent = !singleEvent; }

Moving to the other events now we need to handle when the replay picklist changes, update the replayId number if a custom Id is input, and register an error listener for subscriber errors and other channel errors.

//Replay Event Type picklist has been clicked, make sure the values are changed to determine what to show for subscribing handleReplayIdList(evt){ if(evt.detail.value === 'hasId'){ this.hasCustomReplay = true; //shows the replay id input field }else{ this.replayIdNumber = Number(evt.target.value); //get the replay id type number and make sure it is a number this.hasCustomReplay = false; //don't show custom replay input field } } updateReplayId(evt){ this.replayIdNumber = Number(evt.target.value) - 1;//subtract 1 from the replayId when you put the replay id in the subscriber it will start from this number, as in it will } registerErrorListener() { // Invoke onError empApi method // Error contains the server-side error onError(error => { //send error toast console.log('Received error from server: ', JSON.stringify(error)); const evt = new ShowToastEvent({ title: error.subscription, message: error.error, variant: 'error', }); this.dispatchEvent(evt); this.deleteSubChannel(error.subscription);//delete from subscribe channel map this.showSpinner = true; }); }

To finish up the javascript of the lightning web component we need to provide a method for deleting the subscribed channels when needed, clear the platform event messages, check to see if the single custom replay Id checkbox is checked, and identify when the unsubscribe radio button changes.

//delete subscribe channel map handler deleteSubChannel(unsubChannel){ this.subChannelMap.delete(unsubChannel); this.subscribedChannels = Array.from(this.subChannelMap.values());//convert values to array for new list of channels this.isUnsubscribeDisabled = true; if(this.subscribedChannels.length <=0 ){ this.isSubscribedChannels = false; } } //clear events array for multi event mode, single event for single event mode, clear the number of events in the multiple events button handleClear(){ this.events = []; this.lastMessage = ''; this.multiEventStr = `Multiple Events`; } //only get the single replay id otherwise the rest of the events on the bus that occured after this will fire handleSingleReplayId(){ this.isSingleReplayId = !this.isSingleReplayId; } //unsubscribe channel list button has been clicked/changed handleUnsubChannelChange(evt){ this.unsubOption = evt.detail.value; this.isUnsubscribeDisabled = false; } }

Now that you have the javascript file completed that last thing to do is decide where you would like to place the component. I would suggest putting this on a full page lightning record page.

Meta XML

<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata" fqn="cdcEventListener"> <apiVersion>51.0</apiVersion> <isExposed>true</isExposed> <targets> <target>lightning__AppPage</target> <target>lightning__HomePage</target> </targets> </LightningComponentBundle>

TL;DR

The above is a breakdown of a lightning web component that uses the emp-api to view events on Salesforce’s event bus. This has been a useful tool to help troubleshoot, when platform and change data capture messages fire and what is in the messages. This is an alternative option to using a debug log which can be cumbersome or creating an external subscriber to view the messages. Just be aware this will count towards the platform event CometD limit.

Here is a link to the repo.

Leave a Reply

Your email address will not be published. Required fields are marked *