ABSYZ ABSYZ

  • Home

    Home

  • About us

    Who We Are

  • Our Expertise

    What we Do

  • Our Approach

    How We Do It

  • Products

    What We Made

  • Industries

    Who We Do It For

  • Clients

    Whom We Did It For.

  • Article & Blogs

    What Experts Think

  • Careers

    Join The Team

  • Get In Touch

    Let’s Get Started

ABSYZ

Change Data Capture Events in Salesforce – Keep your UI Updated with latest data

Home / Article & Blogs / Uncategorized / Change Data Capture Events in Salesforce – Keep your UI Updated with latest data
By pravallikadamerla inUncategorized

Change Data Capture(CDC) events :-

This is still a new term in Salesforce world . Do you know how powerful it is ? If not read this post and learn CDC events

Have you ever faced the issue when you are viewing the record and at the same time other user has modified the same record and the data you are seeing is not up to date !! How to tackle the issue ? Here is the solution for that … Let’s start!!

What is CDC ?

  • CDC events are basically used to track the changes in a Salesforce record. This means, it tracks create, update, delete and undelete changes of a Salesforce Record, and fires an event when the change occurred.
  • You can subscribe to CDC channel to listen for these events. You can even subscribe to these events from you your own platform using javascript libraries like cometD.CDC events are basically used to track the changes in a Salesforce record. This means, it tracks create, update, delete and undelete changes of a Salesforce Record, and fires an event when the change occurred.
  • Once you subscribe to these events, you will get a notification whenever a new event is generated, the notification will include a payload in JSON format which will have all the details about the event like:1. SObject name – where the changed happened ex: Account ,opportunity
    2. RecordId’s – List of record id’s which are changed
    3.Change Type – Create/Update/Delete/Undelete
    4.Changed Fields – Fields which are modified
    5.User – The User who performed the change
    6.Timestamp – When the change happened
  • You can use this payload to perform your operation. For example, if you want to keep your in-house application in sync with Salesforce Data, you can use payload information to update your application with data changes happened in Salesforce.

Sample Payload

{
"data":{  
   "event":{  
      "createdDate":"2018-10-18T01:17:26.855Z",
      "replayId":61,
      "type":"updated"
   },
   "sobject":{  
      "Type":"Customer - Channel",
      "Phone":"(785) 241-6201",
      "Website":"dickenson-consulting.com",
      "Id":"001B000000mAT1ZIAW",
      "Name":"Dickenson .inc"
   }
},
"channel":"/topic/AccountSpy"
}

 

CDC Events tracks all changes and all fields in an object and if any of the fields are modified the event will be triggered. This makes it very easy to listen for all type of changes in a record. You can use Streaming API for a similar purpose, but there you specifically need to specify which fields and change type you want to track while creating PushTopics.

Implementation Example

 

Let’s discuss an implementation scenario of CDC events where you want to keep your UI updated with latest changes

  • User “Ron Weasley” is viewing an Opportunity record which is being viewed by the user “Harry Potter” as well at the same time.
  • Harry Potter has changed the record data and has modified Opportunity amount.
  • Ron Weasley who is unaware of this change and still viewing the old record, has to make a very important decision based on Opportunity amount and send the same to higher management.
  • Now in this situation, Ron Weasley may take an incorrect decision and send incorrect data to higher management.

 

How to avoid the above situation? Well, lets leverage Change Data Capture events and lightning:empApi (Winter’19 release)component to track changes in Opportunity Record, and update the data on Ron Weasley’s screen as soon as the data is modified by Harry Potter. Our coding solution will include below components:

RecordChangeEventHandler Component:

The main component to subscribe, unsubscribe and receive messages from streaming channel of Change Data Capture events. Once this component receives a message, this will fire a component event, which would be handled by the parent component. The channel name is like below:

  • Standard Object: “ChangeEvent” appended in the end. Example, AccountChangeEvent, OpportunityChangeEvent etc.
  • Custom Object: “__ChangeEvent” appended in the end. Like for custom object “Car__c”, the change event channel name would be “Car__ChangeEvent”

<aura:component access="global">
    
<!-- ChannelName, which needs to subscribed -->
    
<aura:attribute name="channelName" type="String" required="true"/>
    
<!-- Save the reference of current subscription, which can be unsubscribe later on -->
    
<aura:attribute name="subscription" type="Object"/>
     
    
    
<!-- This event is fired when a component is destroyed. 
		
Handle this event if you need to do custom cleanup when a component is destroyed.-->
    
<aura:handler name="destroy" value="{!this}" action="{!c.unsubscribe}"/>
	
<!-- init event -->
   
 <aura:handler name="init" value="{!this}" action="{!c.subscribe}"/>
    
   
 <!-- empApi component which will be used to subscribe/unsubscribe to a channel -->
    
<lightning:empApi aura:id="empApi"/>
    
    
<!-- Component event, which will be fired once the message is received
       
  This event will be handled by parent component to perform needful action on stream event -->
    
<aura:registerEvent name="onRecordChange" type="c:RecordChangeEvent"/>

</aura:component>
RecordChangeEventHandlerController.js :

({
    
    subscribe: function(component, event, helper) {
        
       
 // Get the empApi component.
       
 var empApi = component.find("empApi");
       
 // Get the channel name from attribute
      
  var channel = component.get("v.channelName");
      
  //fetch latest events
      
  var replayId = -1;
        
      
  // Callback function to be passed in the subscribe call.
       
 // After an event is received, this callback fire a custom
       
 // event to notify parent component and pass payload object
      
  var subscribeCallback = function (message) {
        
    
//Fire the component event to notify parent component
          
  var messageEvent = component.getEvent("onRecordChange");
           
 if(messageEvent!=null) {
            
    messageEvent.setParam("recordData", message.data);
                messageEvent.fire();                            
          
  }
           
 //Display event data in browser console
           
 console.log("Received [" + message.channel +
                        " : " + message.data.event.replayId + "] payload=" +
                    
    JSON.stringify(message.data.payload));
       
 }.bind(this);
    
        
        
       
 // Register error listener and pass in the error handler function.
     
   empApi.onError(function(error){
         
   console.log("Received error ", error);
       
 }.bind(this));
        
      
  // Subscribe to the channel and save the returned subscription object.
       
 empApi.subscribe(channel, replayId, subscribeCallback).then(function(value) {
          
  console.log("Subscribed to channel " + channel);
            component.set("v.subscription", value);
        });
    },
    
   
   
 unsubscribe : function (component, event, helper) {
      
  try{
          
  // Get the empApi component.
         
   var empApi = component.find("empApi");
         
   // Get the channel name from attribute
           
 var channel = component.get("v.channelName");
            
          
  // Callback function to be passed in the unsubscribe call.
           
 var unsubscribeCallback = function (message) {
             
   console.log("Unsubscribed from channel " + channel);
           
 }.bind(this);
            
            // Error handler function that prints the error to the console.
            
var errorHandler = function (message) {
           
     console.log("Received error ", message);
           
 }.bind(this);
            
          
  // Object that contains subscription attributes used to
           
 // unsubscribe.
           
 var subscription = {"id": component.get("v.subscription")["id"],
                                "channel": component.get("v.subscription")["channel"]};
            
           
 // Register error listener and pass in the error handler function.
           
 empApi.onError(function (error) {
            
    console.log("Received error ", error);
           
 }.bind(this));
            
           
 // Unsubscribe from the channel using the sub object.
           
 empApi.unsubscribe(subscription, unsubscribeCallback);
       
 }catch(e){}
   
 },

})

RecordChangeEvent Component Event:-

This event will be fired by RecordChangeEventHandler component whenever it receives a message from Change Event. This component event will be handled by the parent component.

<!-- This event will be fired whenever new record change has been captured by RecordChangeEventHandler component -->
<aura:event type="COMPONENT" description="Event template">
    <aura:attribute name="recordData" type="Object" />
</aura:event>

Record Change Capture:-

This component is parent component which will include actual markup and handle the above-mentioned component event fired my RecordChangeEventHandler component.

<aura:component implements="force:appHostable,flexipage:availableForRecordHome,force:hasRecordId,force:hasSObjectName" 
                access="global" 
                controller="RecordChangeCaptureLightningController">
    
    
<aura:attribute name="channelName" type="String" default="" />
   
 <aura:attribute name="autoRefresh" type="String" default="Yes" />
   
 <aura:attribute name="isSupported" type="Boolean" default="false" />
    
   
 <!--Loading list of supported object for change events
   
  - This can be configured in a custom setting or custom metadata type also
     
- however, in that case you need to make a server side call to get the data
     
- which i am trying to avoid here -->
   
 <ltng:require scripts="{!$Resource.SupportedObjectsForChangeEvents}" afterScriptsLoaded="{!c.checkCompatibility}" />
    
    
<!--Include EmpApiDemo child component and pass channel name to subscribe
     
 ex: "/topic/AccountSpy" is my pushtopic channel name
    
once the event is fired, it will handled in handleMessage controller method -->
    
<aura:if isTrue="{!v.isSupported}">
       
 <c:RecordChangeEventHandler channelName="{!v.channelName}" onRecordChange="{!c.handleMessage}" />
      
  <aura:set attribute="else">
           
 <div style="color:red;font-weight: bold;">Record Change Capture does not support this object/record page.</div>
       
 </aura:set>
   
 </aura:if>

</aura:component>



RecordChangeCaptureController.js:-

({
    checkCompatibility : function(component, event, helper){
        //get current object
        var objectName = component.get("v.sObjectName");
        
        //Check is object name is not undefined/null
        if(objectName){
            //Get channel name for change event
            var channelName = helper.getChannelName(objectName);
            
            //Check if channel name is not null or undefined
            if(channelName){
                component.set("v.channelName", channelName);
                //setting supported varibale true
                component.set("v.isSupported", true);
            } else{
                //Object does not support change event
                component.set("v.isSupported", false);
            }
            
        } else{
            //Object name is undefined or null, hence component will not support this page
            component.set("v.isSupported", false);
        }
    },
    
    /**
     * Handling the message when change event is fired.
     * */
    handleMessage : function(component, event, helper) {
        const message = event.getParam('recordData');
        const eventType = message.payload.ChangeEventHeader.changeType;
        const entityName = message.payload.ChangeEventHeader.entityName;
        const userId = message.payload.ChangeEventHeader.commitUser.substring(0,15); //15 digit id of transaction commit user
        const signedInUser= $A.get("$SObjectType.CurrentUser.Id").substring(0,15); //15 digit id of logged in user
        /**
         * Conditions:
         * - Change Type should not be create
         * - Record Id must present in modified recordIds
         * - User who modified the record should not be logged in user
         * */
        
        if(!(eventType === "CREATE")){
            //Condition 1 - Change type is not "created"
            Array.from(message.payload.ChangeEventHeader.recordIds).forEach( recordId => {
                if(recordId === component.get("v.recordId") && !(signedInUser === userId)){
                    //Condition 2 - Record Id match found &&
                    //Condition 3 - commit user is not logged in user
                
                    //Display console log with changed values
                    console.log(`${eventType} event captured on ${entityName} by user id ${userId}`);
                    /*console.log("Values changed are:");
                    for(k in message.payload){
                        if(k){
                            console.log(`Field Name: ${k} | New Value:${message.payload[k]}`);
                        }
                    }*/
            
                    //Now call helper function to get user name and display toast
                    helper.getUser(component, userId, eventType, entityName);
            	}
             });
        }
    }
})

RecordChangeCaptureHelper.js:-

({
    
    /*
     * This method will call the server side action to get user name
     * Once user name retrieved, it will show a warning toast to the user
     * */
    getUser : function(component, userId, eventType, entityName) {
        var action = component.get("c.getUserName");
        action.setParams({
            "userId" : userId
        });
        
        
        action.setCallback(this,function(response) {
            var state = response.getState();
            if (state === "SUCCESS") { 
                // pass returned value to callback function
                var userName = response.getReturnValue(); 
                this.showToast({
                    "title":`Record ${eventType}ED`,
                    "type": "warning",
                    "message": `This record has been ${eventType}D by ${userName}`
                });
                
                //Auto refresh the page to get latest details if auto refresh is selected
                if(component.get("v.autoRefresh") === "Yes"){
                    this.autoRefresh();
                }
                 
            } else if (state === "ERROR") {
                // generic error handler
                var errors = response.getError();
                if (errors) {
                    console.error("Error is getting username: ", errors);
                }
                return null;
            }
        });
        
        $A.enqueueAction(action);
    },
    
    /**
     * Get change event object name from 
     * current page's object
     * */
    getChannelName : function(objectName) {
        var isSupported = false;
        //If it is custom object, then it is supported
        if(objectName.includes("__c")){//Custom Object
            objectName = objectName.slice(0, -3); //removing __c from the end
            objectName += "__ChangeEvent"; //appending __ChangeEvent in the end of custom object
            isSupported = true;
        } 
        //check if it is one of the supported standard object from static resource
        else {//Standard Object
            //iterate over supported object list
            window.supportedObjectForChangeEvents.forEach(obj => {
                if(obj.toLowerCase().indexOf(objectName.toLowerCase()) != -1){ 
                //Match found, Object is supported
                objectName += "ChangeEvent" //appending ChangeEvent in the end of standard object
                isSupported = true;
            } 
            });
            
        }
        if(isSupported === true){//is object supported, return channel name
            return `/data/${objectName}`;
        } else{//if object not supported, return null
            return null;
        }
        
    },
    
    /*
     * This function displays toast based on the parameter values passed to it
     * */
    showToast : function(params) {
        var toastEvent = $A.get("e.force:showToast");
        if(toastEvent){
            toastEvent.setParams(params);
            toastEvent.fire();
        } else{
            alert(params.message);
        }
    },
    
    /**
     * Auto refresh the page to get latets details
     * */
    autoRefresh : function(){
        var refreshEvent = $A.get('e.force:refreshView');
        if(refreshEvent){
            refreshEvent.fire();
        } else{
            console.error("Auto refresh is not supported in current context");
        }
    }
})

RecordChangeCapture.design

<design:component>
   
 <design:attribute name="autoRefresh" label="Auto refresh the page?" 

datasource="Yes,No" default="Yes" />

</design:component>


SupportedObjectsForChangeEvents.js:-

//Add standard object list here if any new standard object supports change data events

//At the time of winter'19 below object support change data events

window.supportedObjectForChangeEvents = 
['Account','Asset','Campaign','Case','Contact','ContractLineItem','Entitlement','Lead',
                                        'LiveChatTranscript','Opportunity','Order','OrderItem','Product2','Quote', 'QuoteLineItem','ServiceContract'];



RecordChangeCaptureLightningController.apxc:-

public class RecordChangeCaptureLightningController {
    
    @AuraEnabled
    public static String getUserName(Id userId){
        User u = [SELECT Name FROM User WHERE Id=:userId];
        return u.Name;
    }

}


Happy Coding!!

 

CDCChange Data Capture EventsLightning componentSalesforce
148
Like this post
4 Posts
pravallikadamerla

Search Posts

Archives

Categories

Recent posts

Meet OmniStudio – Revolutionize Your Creative Work

Meet OmniStudio – Revolutionize Your Creative Work

BioAsia 2023 in Hyderabad: An Annual International Event

BioAsia 2023 in Hyderabad: An Annual International Event

The Role Of Marketing in Small & Medium Enterprises

The Role Of Marketing in Small & Medium Enterprises

Salesforce For Retail: How Salesforce CRM Can Help Retailers

Salesforce For Retail: How Salesforce CRM Can Help Retailers

What is ChatGPT & How Does It Work?

What is ChatGPT & How Does It Work?

  • Previous PostBulk Import Email from Gmail to Salesforce
  • Next PostLightning Testing Service

Related Posts

HOW THE TALENTS ARE TRAINED AT ABSYZ
Uncategorized

HOW THE TALENTS ARE TRAINED AT ABSYZ

Why choose Salesforce Integration Partners?
Salesforce Implementation Uncategorized

Why choose Salesforce Integration Partners?

Leave a Reply (Cancel reply)

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

*
*

ABSYZ Logo

INDIA | USA | UAE

  • About us
  • Article & Blogs
  • Careers
  • Get In Touch
  • Our Expertise
  • Our Approach
  • Products
  • Industries
  • Clients
  • White Papers

Copyright ©2022 Absyz Inc. All Rights Reserved.

youngsoft
Copy
We use cookies on our website to give you the most relevant experience by remembering your preferences and repeat visits. By clicking “ACCEPT ALL”, you consent to the use of ALL the cookies. However, you may visit "Cookie Settings" to provide a controlled consent. Privacy Policy
Cookie SettingsREJECT ALLACCEPT ALL
Manage consent

Privacy Overview

This website uses cookies to improve your experience while you navigate through the website. Out of these, the cookies that are categorized as necessary are stored on your browser as they are essential for the working of basic functionalities of the website. We also use third-party cookies that help us analyze and understand how you use this website. These cookies will be stored in your browser only with your consent. You also have the option to opt-out of these cookies. But opting out of some of these cookies may affect your browsing experience.
Necessary
Always Enabled

Necessary cookies are absolutely essential for the website to function properly. These cookies ensure basic functionalities and security features of the website, anonymously.

CookieDurationDescription
cookielawinfo-checkbox-analytics11 monthsThis cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Analytics".
cookielawinfo-checkbox-functional11 monthsThe cookie is set by GDPR cookie consent to record the user consent for the cookies in the category "Functional".
cookielawinfo-checkbox-necessary11 monthsThis cookie is set by GDPR Cookie Consent plugin. The cookies is used to store the user consent for the cookies in the category "Necessary".
cookielawinfo-checkbox-others11 monthsThis cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Other.
cookielawinfo-checkbox-performance11 monthsThis cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Performance".
viewed_cookie_policy11 monthsThe cookie is set by the GDPR Cookie Consent plugin and is used to store whether or not user has consented to the use of cookies. It does not store any personal data.

Functional

Functional cookies help to perform certain functionalities like sharing the content of the website on social media platforms, collect feedbacks, and other third-party features.

Performance

Performance cookies are used to understand and analyze the key performance indexes of the website which helps in delivering a better user experience for the visitors.

Analytics

Analytical cookies are used to understand how visitors interact with the website. These cookies help provide information on metrics the number of visitors, bounce rate, traffic source, etc.

Advertisement

Advertisement cookies are used to provide visitors with relevant ads and marketing campaigns. These cookies track visitors across websites and collect information to provide customized ads.

Others

Other uncategorized cookies are those that are being analyzed and have not been classified into a category as yet.

SAVE & ACCEPT