Motivation behind this
Earlier, I have written posts about pub-sub event mechanism like: Lightning Message Service: Communication between Lightning Web Components and Drag and Drop functionality using Salesforce Lightning Web Components leveraging pub-sub event propagation.
I have been looking for a real-life use case on Change Data Capture which drives me to build a quick poc and writing this post.
So, lets get started!
Use Case
Business has a requirement to approve or reject a record when anyone of the approvers approves/rejects the request. When a record is submitted for approval there can be multiple approvers in the queue and anyone can take necessary action. There is a possibility that, multiple users can open the record for approval at the same time and the approver needs to spend time reading each items minutely, putting comments and so on. It might take some time to approve.
In the meantime, other approver can approve the same request which current user may not know that the record is stale. Normally, during approving/rejecting user can be alerted, but this is not a good user experience as he has to spend time on completing the form.
Business wants a pro-active alert on the screen when the record status gets changed without refreshing the screen.
You can also think about similar use case for seat reservation etc.
Possible End Results
After building the use case, it will perform the functionality as following screen. When record gets changed, the alert will be shown pro-actively as below without refreshing the screen. Also, clicking on refresh icon, record will be refreshed instantly. Don't miss the video at the end.
Flow Diagram
Create a flow diagram like this way to understand the functionalities, flow of control to meet the requirement.
All custom objects and few Standard Objects like Account, Contact, Lead, User, Order, OrderItem, Product2, and others supports CDC.
Configuring CDC
Let's start with creating an object and configure CDC. Here for the sake of poc, I have created a custom object called Financial with few attributes as shown in the first picture.
{ "data": { "schema": "WnzuUutBoNEU0Qbh60KQRg", "payload": { "LastModifiedDate": "2021-07-04T18:34:04Z", "ChangeEventHeader": { "commitNumber": 11105108170443, "commitUser": "0052v00000dRntzAAC", "sequenceNumber": 1, "entityName": "Financial__c", "changeType": "UPDATE", "changedFields": [ "LastModifiedDate", "Status__c" ], "changeOrigin": "com/salesforce/api/soap/52.0;client=SfdcInternalAPI/", "transactionKey": "000282f7-ee3a-ad08-3fd0-29b2549ab4a8", "commitTimestamp": 1625423644000, "recordIds": [ "a072v00001UkmEKAAZ" ] }, "Status__c": "Approved" }, "event": { "replayId": 7805409 } }, "channel": "/data/Financial__ChangeEvent" }
Lightning Web Component as Subscriber
changeDataNotificationComponent.html:
This UI displays notification on the screen.
<!-- @description : This component shows the message when current record changes. @author : Santanu Boral --> <template> <lightning-card title="Change Data Capture with EmpApi" icon-name="custom:custom14"> <template if:true = {isDisplayMsg}> <div class="slds-notify slds-notify_alert slds-alert_warning" role="alert"> <span class="slds-assistive-text">warning</span> <lightning-icon icon-name="utility:warning" alternative-text="Warning!" title="Warning"> </lightning-icon>
<h2>{responseMessage} <lightning-button-icon icon-name="utility:refresh" variant="bare" alternative-text="refresh" title="refresh" onclick={handleRefresh}> </lightning-button-icon>
</h2> </div> </template> </lightning-card>
</template>
changeDataNotificationComponent.js:
Few notable points in this js file:
- Importing lightning/empApi for subscribing and unsubscribing.
- Use of arrow function so that class level method (handleNotification) can be called using this operator.
const messageCallback = (response) => { console.log('New message received: ', JSON.stringify(response)); // Response contains the payload of the new message received this.handleNotification(response); };
- handleNotification checks if current record is updated and shows the message.
- refreshing the record using getRecordNotifyChange of lightning/uiRecordApi, which is efficient way of refreshing the record. Isn't it?
Entire js file as below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | import { LightningElement,api } from 'lwc'; import { subscribe, unsubscribe, onError, setDebugFlag, isEmpEnabled } from 'lightning/empApi'; import { getRecordNotifyChange } from 'lightning/uiRecordApi'; export default class ChangeDataNotificationComponent extends LightningElement { @api recordId; @api channelName; //'/data/Financial__ChangeEvent' subscription = {}; //subscription information responseMessage; //message to be shown at UI isDisplayMsg; //indicator for message to be displayed // Initializes the component connectedCallback() { this.handleSubscribe(); // Register error listener this.registerErrorListener(); this.isDisplayMsg = false; } // Handles subscribing handleSubscribe() { // Callback invoked whenever a new event message is received const messageCallback = (response) => { console.log('New message received: ', JSON.stringify(response)); // Response contains the payload of the new message received this.handleNotification(response); }; // Invoke subscribe method of empApi. Pass reference to messageCallback subscribe(this.channelName, -1, messageCallback).then(response => { // Response contains the subscription information on subscribe call console.log('Subscription request sent to: ', JSON.stringify(response.channel)); this.subscription = response; this.handleNotification(response); }); } // Handles unsubscribing handleUnsubscribe() { // Invoke unsubscribe method of empApi unsubscribe(this.subscription, response => { console.log('unsubscribe() response: ', JSON.stringify(response)); // Response is true for successful unsubscribe }); } registerErrorListener() { // Invoke onError empApi method onError(error => { console.log('Received error from server: ', JSON.stringify(error)); // Error contains the server-side error }); } //this method checks if current record got updated and shows message on UI handleNotification(response){ if(response.hasOwnProperty('data')){ let jsonObj = response.data; if(jsonObj.hasOwnProperty('payload')){ let payload = response.data.payload; let recordIds = payload.ChangeEventHeader.recordIds; //find the current recordId in the array and if found then display message const recId = recordIds.find(element=> element == this.recordId); if(recId !=undefined){ this.isDisplayMsg = true; this.responseMessage = 'Current record has changed, please refresh the page'; } } } } //this method refreshes current record page handleRefresh(){ getRecordNotifyChange([{recordId: this.recordId}]); this.isDisplayMsg = false; } } |
In the meta.xml create channelName as property which can be used to set design attribute and assign '/data/Financial__ChangeEvent' when placing this component on the record detail page.
Let's Test this!
Refer following video to simulate the functionality. Here the record has been opened in a record detail page and in a separate tab same record is edited from List View. The alert message will be displayed on record detail page.
Finally we are done and thanks for reading.
Nice use case.. Thanq for posting
ReplyDeleteThanks Santanu da..
ReplyDeleteGood one. But i guess cdc is used instead of platform event to simply solution.
ReplyDeleteI think instead of showing the message to refresh the page and forcing user to click icon if we can show a push message in the ui or a toast message that would give better user experience i feel.
ReplyDeletevery nice article
ReplyDelete