Thursday, 8 April 2021

Salesforce Lightning TreeGrid with pagination

INTRODUCTION/SCENARIO

While working on one of the user stories of Pharma sector for a client based on Chicago, USA; there was a requirement to display Contact records under their parent account record. Also, pagination was needed with a picklist to choose number of records to be displayed on a page.

CHALLENGE

Lightning:treeGrid is useful component for displaying structured data such as hierarchy or forecasting data while dealing with the same object. But there is no functionality available to display records of 2 different objects (In our case, Account and Contact) in the same table while using Lightning:treeGrid.

APPROACH / SOLUTION

To overcome this limitation, I've implemented custom Javascript in the lightning component containing the mechanism to have customized list of Accounts and Contacts with pagination.

Note: This component requires API version 42.0 and later.

treeGridController is a controller class of the lightning components treeGrid utilized to fetch Account object data which has having child Contact records.

treeGridController.apxc
public class treeGridController {
    
    @AuraEnabled
    public static List <Account> getAccountList() {
        return [Select Id, Name,
                    (SELECT Name, Phone, Email FROM Contacts) 
                    From Account
                    Where Id IN (Select AccountId From Contact)
                    ORDER BY Name ASC];
    }
}

Below are Component & JavaScript files for the reference which i've used to meet the requirement..

treeGrid.cmp
<aura:component controller="treeGridController" implements="flexipage:availableForAllPageTypes" >
    <aura:attribute name="resultData" type="Object" access="private"/>
    <aura:attribute name="gridColumns" type="List" />
    <aura:attribute name="gridData" type="Object" />
    <aura:attribute name="gridExpandedRows" type="Object" />
    <aura:attribute name="PageNumber" type="Integer" />
    <aura:attribute name="TotalPages" type="Integer"/>
    <aura:attribute name="currentPage" type="Integer" default="0" />
    <aura:attribute name="limit" type="Integer" default="5" />
    <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
    <div class="slds-page-header" role="banner">
        <ui:inputSelect aura:id="pageSize" label="Display Records Per Page: " change="{!c.onSelectChange}">
            <ui:inputSelectOption label="5" text="5" value="true"/>
            <ui:inputSelectOption label="10" text="10"/>
            <ui:inputSelectOption label="50" text="50"/>
        </ui:inputSelect>
    </div>
    <lightning:treeGrid aura:id="accTree"
                        columns="{!v.gridColumns}"
                        data="{!v.gridData}"
                        expandedRows="{!v.gridExpandedRows}"
                        keyField="Id"
                        hideCheckboxColumn = "true"
                        />
    <div class="slds-clearfix">
        <div class="slds-page-header" role="banner">
            <div class="slds-float_right">            
                <lightning:button disabled="{!v.PageNumber == 1}" variant="brand" aura:id="prevPage" label="Prev" onclick="{!c.handlePrev}" />            
                <lightning:button disabled="{!v.PageNumber == v.TotalPages}" aura:id="nextPage" variant="brand" label="Next" onclick="{!c.handleNext}"/>
            </div>
            <p class="slds-page-header__title">Page {!v.PageNumber} of {!v.TotalPages}</p>
        </div>
    </div>
</aura:component>

treeGridController.js
({
    doInit : function(component, event, helper) {
        var columns = [
            {
                type: 'url',
                fieldName: 'AccountURL',
                label: 'Account Name',
                typeAttributes: {
                    label: { fieldName: 'accountName' }
                }
            },
            {
                type: 'text',
                fieldName: 'Name',
                label: 'Contact Name'
            },
            {
                type: 'phone',
                fieldName: 'Phone',
                label: 'Phone Number'
            },
            {
                type: 'email',
                fieldName: 'Email',
                label: 'Email'
            }
        ];
        component.set('v.gridColumns', columns);
        var action = component.get("c.getAccountList");
        action.setCallback(this, function(response){
            var state = response.getState();
            if (state === "SUCCESS" ) {
                var resultData = response.getReturnValue();
                component.set('v.resultData', resultData);
                helper.bindTableData(component, event);
            }
        });
        $A.enqueueAction(action);
    },
    handleNext : function(component, event, helper){
        component.set('v.currentPage',component.get('v.currentPage')+1);
        helper.buildTable(component, event);
    },
    handlePrev : function(component, event, helper){
        component.set('v.currentPage',component.get('v.currentPage')-1);
        helper.buildTable(component, event);
    },
    onSelectChange : function(component, event, helper){
        component.set('v.currentPage',0);
        var pageSize = component.find('pageSize').get('v.value');
        component.set('v.limit',pageSize);
        helper.buildTable(component, event);
    }
})

treeGridHelper.js
({
    bindTableData : function(component, event) {
        var resultData = component.get('v.resultData');
        for (var i=0; i<resultData.length; i++ ) {
            resultData[i].accountName = resultData[i]['Name'];
            delete resultData[i]['Name'];
            resultData[i]._children = resultData[i]['Contacts'];
            delete resultData[i].Contacts;
            resultData[i].AccountURL = '/'+resultData[i].Id;                
        }
        component.set('v.resultData',resultData);
        this.buildTable(component, event);
    },
    buildTable : function(component, event){
        var resultData = component.get('v.resultData');
        var limit = component.get('v.limit');
        var currentPage = component.get('v.currentPage');
        var totalPage = Math.ceil(resultData.length / limit);
        var startIndex = currentPage * limit;
        var row = [];
        var expandedRows = [];        
        for (var i = startIndex; i < parseInt(startIndex)+parseInt(limit); i++) {
            if(resultData[i]){
                expandedRows.push(resultData[i].Id);
                row.push(resultData[i]);
            }
        }
        component.set('v.gridData', row);
        component.set('v.PageNumber',currentPage+1);
        component.set('v.TotalPages',totalPage);
        component.set('v.gridExpandedRows', expandedRows);
    }
})

OUTPUT: 


If you have any questions you can reach out our Salesforce Consulting team here.

Thursday, 1 April 2021

Automatically create new contact from Email-To-Case

Requirement : 
While working on one of the requirement of Commerce sector project for a client based out GA, USA; there was a requirement to build support system which can automatically create new case and task for Contact. For new Contact,  it should create the new contact first in Salesforce and then create new case and task under it. 


Challenge : 
Salesforce automatically creates Case from Email. It is called Email-to-Case. This feature also creates tasks along with the Case. Hence the first half of the requirement is solved using the Salesforce standard feature provided by Salesforce Service Cloud. The main challenge for us was to create a new contact whenever we receive an email from a new email address because Salesforce doesn't provide this feature in Email-to-Case. Although, we can install the Email-to-Case Premium package as additional subscription which requires license.

Solution : 
Instead of subscription, We have used the Salesforce Service Cloud provided Email-to-Case feature to create a new case from an e-mail and Apex trigger to create new contact if email address is not exist. 

We can use Email-to-Case in 2 different ways : 
  1. Email-to-Case 
  2. On-demand Email-to-Case
The main difference between these two is Email-to-Case accepts the emails larger than 25 MB while On-Demand Email-to-Case accepts the emails less than 25 MB.

Note : Once you enable Email-to-Case, you cannot disable it. However, you can disable the On-Demand Service.

To setup Email-to-Case service follow the link Email-to-Case. Once setup is completed you will have configuration as shown below.


Now, you also need to configure routing address for the account which is being used as Customer Support in your organization.






Now, you will be having a Salesforce generated dynamic email address that can be used to create the Case.

Since the generated email address is too long to remember, you can configure an email routing address at your support account, so that the email received to your customer support Account, will be forwarded to the Salesforce.

Please refer the Email routing to configure forwarding the email to Salesforce generated dynamic email address. 


For second part of the requirement, to create new contact for the unknown/new email address received by salesforce, we have created a Apex trigger on Case Object. Refer below code for that.  

The trigger has the mechanism to check whether received email address is exist in contacts objects or not.  

If the contact exists with that email address, the case will be assigned to that contact.  

If the contact doesn’t exist, a new contact will be created and then the case will be assigned to that contact. 

Trigger to create contact : 

trigger TriggertoCreateContactforCase on Case (before insert) {
    if(Trigger.isBefore){
        if(Trigger.isInsert){
            List<String> insertedEmailAddresses = new List<String>();
            // Create a list of email addresses for newly inserted case where contact is not set
            for (Case cs:Trigger.new) {
                if (cs.ContactId==null && cs.SuppliedEmail!='' || cs.SuppliedEmail!=null) {
                    insertedEmailAddresses.add(cs.SuppliedEmail);
                }
            }          
            
            Set<String> exstingEmailSet = new Set<String>();
            if(insertedEmailAddresses.size() > 0){
                // Create a set of Email address which are already available in contact
                for (Contact c:[Select Id,Email From Contact Where Email in:insertedEmailAddresses]) {
                    exstingEmailSet.add(c.Email);
                }
            }
            
            Contact contTemp = new Contact();
            List<String> Emailheader = new List<String>();
            List<Case> casesToUpdateList = new List<Case>();
            Map<String,Contact> emailToContactMap = new Map<String,Contact>();
            
            // For all the cases with a null contactId, We will create a contact
            for (Case c:Trigger.new) {
                if (c.ContactId==null && c.SuppliedName!=null && c.SuppliedEmail!=null && c.SuppliedName!='' &&
                    !c.SuppliedName.contains('@') && c.SuppliedEmail!='' && !exstingEmailSet.contains(c.SuppliedEmail)) {
                        Emailheader = c.SuppliedName.split(' ',2);
                        if (Emailheader.size() == 2) {
                            contTemp.FirstName=Emailheader[0];
                            contTemp.LastName=Emailheader[1];
                            contTemp.Email=c.SuppliedEmail;
                            emailToContactMap.put(c.SuppliedEmail,contTemp);
                            casesToUpdateList.add(c);
                        }
                    }
            }
            
            // Inserting Contact
            List<Contact> contactList = new List<Contact>();
            if(emailToContactMap.keyset().size() > 0){
                contactList = emailToContactMap.values();
                insert contactList;
            }
            
            // Updating Cases
            if(contactList.size()>0) {
                for (Case cs:casesToUpdateList) {
                    contTemp = emailToContactMap.get(cs.SuppliedEmail);
                    cs.ContactId = contTemp.Id;
                }
            }
        }
    }
}

Summary : 

By following the above steps, we have achieved the requirement. Now, whenever an email is received to the routing email address, it will automatically create a case and task for that case. While creating the case if the contact is not exist for that email address, the trigger will create a contact and then create a case for that contact. 

If you have any questions you can reach out our Salesforce Consulting team here.

Thursday, 25 March 2021

Overview of Governor Limits in Salesforce

INTRODUCTION

Salesforce is a cloud computing service as a software (SaaS) company. It is specialized in customer relationship management (CRM). Salesforce provides a range of cloud solutions that allow businesses to connect with different types of data and deliver services to their customers in various ways.

Multitenancy is a fundamental technology that operates alongside cloud computing. Since Salesforce and Apex operate in a multitenant setting, the term "Salesforce Governor Limits" is used.

In order to ensure the practical use of the resources available on the Force.com platform, Salesforce has regulatory restrictions. Salesforce sets certain limitations in order to effectively use the code. 

What are Salesforce Governor Limits?

As we understand, Apex runs in a multi-tenant environment, meaning that all customers and organizations share a single resource. It is important to ensure that no one monopolizes the resources, which is why Salesforce.com has provided a series of restrictions that monitor and limit code execution. Salesforce sets limitations in order to effectively use the resources, and they are known as Governor limits in Salesforce.  

The governor can throw errors and suspend program execution if any of its limitations are exceeded. As a result, we must ensure that our code is flexible and does not surpass the Governor's limits. All these limits are applied on a per-transaction basis.

Types of Salesforce Governor Limits:

  • Per-transaction Apex Limits  
  • Per-transaction Certified Managed Package Limits 
  • Size-specific Apex Limits 
  • Static Apex Limits 
  • Lightning Platform Apex Limits 
  • Miscellaneous Apex Limits

Per-transaction Apex Limits

Per-transaction Apex Limits count for each Apex transaction. While we discuss Batch Apex, for every execution of a batch of records in the Execute method, these limits are reset. For the synchronous and asynchronous Apex, the following table lists the limits.

Description 

Synchronous Limit 

Asynchronous Limit 

Total number of SOQL queries issued

100 

200 

Total number of records retrieved by SOQL queries 

50,000 

Total number of records retrieved by  
Database.getQueryLocator 

10,000 

Total number of SOSL queries issued 

20 

Total number of records retrieved by a single SOSL query 

2,000 

Total number of DML statements issued

150 

The total number of records processed as a result of DML statements, Approval Process, or database.emptyRecycleBin 

10,000 

Total stack depth for any Apex invocation that recursively fires triggers due to insertupdate, or delete statements

16 

Total number of callouts (HTTP requests or Web services calls) in a transaction 

100 

Maximum cumulative timeout for all callouts (HTTP requests or Web services calls) in a transaction 

120 seconds 

Maximum number of methods with the future annotation allowed per Apex invocation 

50 

0 in batch and future contexts; 1 in queueable context 

Maximum number of Apex jobs added to the queue with System.enqueueJob 

50 

1 

Total number of send email methods allowed 

10 

Total heap size

6 MB 

12 MB 

Maximum CPU time on the Salesforce servers

10,000 milliseconds 

60,000 milliseconds 

Maximum execution time for each Apex transaction 

10 minutes 

Maximum number of push notification method calls allowed per Apex transaction 

10 

Maximum number of push notifications that can be sent in each push notification method call 

2,000 

Maximum number of EventBus.publish calls for events configured to publish immediately 

150 


Per Transaction Certified Managed Package Limits

Certified Managed packaged
are the managed packages that have passed the AppExchange security review. Salesforce ISV Partners develop certified managed packages that have specific namespaces and are installed in your org. They get their own set of limits for most limits per transaction. 

The number of certified namespaces that can be invoked in a single transaction does not have a limit. The precondition, however, is that the number of operations should be carried out in a separate namespace that should not reach the per-transaction limits.  
And there's a cap on the total number of operations that can be performed in a transaction through namespaces. The overall limit is 11 times the limit per namespace. 

This table summarizes Collective limits for cross-namespace.

Description 

Collective Cross-Namespace Limit 

Total number of SOQL queries issued 

1,100 

Total number of records retrieved by Database.getQueryLocator 

110,000 

Total number of SOSL queries issued 

220 

Total number of DML statements issued 

1,650 

Total number of callouts (HTTP requests or Web services calls) in a transaction 

1,100 

Total number of send email methods allowed 

110 


For certified managed packages, all per-transaction thresholds count individually, except for: 
  • The maximum transaction execution time 
  • The maximum number of unique namespaces  
  • The maximum CPU time  
  • The total heap size
Irrespective of how many certified managed packages are running in the same transaction, these caps count for the whole transaction. We have to understand that the script from an AppExchange package, not produced and not approved by a Salesforce ISV Partner, does not have its own separate governor limits. The overall org governor limits are counted against any services used by the package. Based on managed package namespaces, collective resource messages and alert emails are created as well. 

Size-specific Apex Limits

These limits related to the code. These size-dependent limitations are specifically intended to ensure that classes, causes, organs, etc. do not contain oversized items.

Description 

Limit 

Maximum number of characters for a class 

1 million 

Maximum number of characters for a trigger 

1 million 

The maximum amount of code used by all Apex code in an org

6 MB 

Method size limit

65,535 bytecode instructions in compiled form 

Static Apex Limits

There are also more additional Apex governor limits, i.e., along with various transactions, for various forms of callouts, queries, loops, records, and batch sizes. These Limits are applied across all transactions. 

Description 

Limit 

Default timeout of callouts (HTTP requests or Web services calls) in a transaction 

10 seconds 

Maximum size of callout request or response (HTTP request or Web services call)

6 MB for synchronous Apex or 12 MB for asynchronous Apex 

Maximum SOQL query run time before Salesforce cancels the transaction 

120 seconds 

Maximum number of class and trigger code units in a deployment of Apex 

5,000 

Apex trigger batch size

200 

For loop list batch size 

200 

Maximum number of records returned for a Batch Apex query in Database.QueryLocator 

50 million 

Lightning Platform Apex Limits

For apex transactions, the limits listed below are not valid, so the lightning platform manages these limits.
 

Description 

Limit 

The maximum number of asynchronous Apex method executions (batch Apex, future methods, Queueable Apex, and scheduled Apex) per a 24-hour period

250,000 or the number of user licenses in your org multiplied by 200, whichever is greater 

Number of synchronous concurrent transactions for long-running transactions that last longer than 5 seconds for each org.

10 

Maximum number of Apex classes scheduled concurrently 

100. In Developer Edition orgs, the limit is 5. 

Maximum number of batch Apex jobs in the Apex flex queue that are in Holding status 

100 

Maximum number of batch Apex jobs queued or active concurrently

5 

Maximum number of batch Apex job start method concurrent executions

1 

Maximum number of batch jobs that can be submitted in a running test 

5 

Maximum number of test classes that can be queued per 24-hour period (production orgs other than Developer Edition)

The greater of 500 or 10 multiplied by the number of test classes in the org 

Maximum number of test classes that can be queued per 24-hour period (sandbox and Developer Edition orgs)

The greater of 500 or 20 multiplied by the number of test classes in the org 

Maximum number of query cursors open concurrently per user

50 

Maximum number of query cursors open concurrently per user for the Batch Apex start method 

15 

Maximum number of query cursors open concurrently per user for the Batch Apex execute and finish methods 

5 

Miscellaneous Apex Limits

Connect in Apex 
Each writing operation counts one DML statement against the governor limit for classes in the ConnectApi namespace. Calls to the ConnectApi method are also subject to rate limiting. A ConnectApi.RateLimitException is given when you reach the rate cap. 

Event Reports 
For a user who is not a system administrator, the maximum number of records that an event report returns is 20,000; for system administrators, 100,000. 

MAX_DML_ROWS limit in Apex testing 
In a single, synchronous Apex test execution context, the maximum number of rows that can be inserted, updated, or deleted is limited to 450,000. If you have reached the cap, you will see this error: Your run all tests is consuming too many DB resources. 

SOQL Query Performance 
The system will terminate nonselective SOQL queries to avoid long execution times. When a non-selective query in a trigger executes against an object that contains more than 200,000 documents, developers receive an error message.

Advantages of Governor Limits

  • Apex has coding limitations that are entirely different or unique. 
  • Governor limits help us remain inside the required apex coding place. 
  • Governor limits prevent other org to take up a lot of space in memory. 

How to avoid Governor Limits

We need to make sure that our code is flexible and does not surpass the governor limits. But sometimes we need to avoid these limits to fulfill the requirements. We can avoid salesforce governor limits using the following process: 
  • In For Loops, do not use SOQL queries and DML statements
  • Ensure that the Apex code manage more than one record at a time 
  • Querying vast amounts of data
  • Use Batch Apex for 50,000 records 
  • Organize multiple triggers on the same page 
  • Use collection for loops 
  • Use @future annotation
  • Use of the Limits Apex Methods to Avoid Hitting Governor Limits 

SuMMARY

Salesforce has several different forms of governor limits, and which help us to become more effective developers and administrators. We can avoid them using the best approaches. Click Here for more details.

If you have any questions you can reach out our Salesforce Consulting team here.