January 18, 2021

How to send Mass or Bulk SMS Messages from List View in Salesforce?

Note:
1. Digital Engagement license is required to use this feature.
2. SMS Channel should be setup in the org. Follow https://www.infallibletechie.com/2020/04/sms-channel-setup-in-digital-engagement.html for setting up.
 
Follow the below steps to send Mass or Bulk SMS Messages from List View.
 
1. Make sure the user have "Send One-to-Many Messages" Permission.

 
2. Open a List View.

3. Select the records.

4. Click Send Message button.


January 15, 2021

Custom Notification using Apex in Salesforce

In the below example Notification will be sent to the Account owner when the file is attached to their Account records.
 
Notification Type in Setup:
 
 
Sample Code:
trigger ContentDocumentLinkTrigger on ContentDocumentLink ( after insert ) {
    
       
    Set < Id > setAccIds = new Set < Id >();
    Map < Id, Id > mapAccIdContDocId = new Map < Id, Id >();
       
    for ( ContentDocumentLink objCDL : trigger.new ) {
       
        String strEntityId = objCDL.LinkedEntityId;
        
        if ( String.isNotBlank( objCDL.LinkedEntityId ) && strEntityId.left( 3 ) == '001' ) {
       
            setAccIds.add( objCDL.LinkedEntityId );
            mapAccIdContDocId.put( objCDL.LinkedEntityId, objCDL.ContentDocumentId );
           
        }
        
    }
    
    if ( setAccIds.size() > 0 ) {
    
        CustomNotificationType notificationType = [SELECT Id FROM CustomNotificationType WHERE DeveloperName = 'Desktop'];
                   
        Messaging.CustomNotification notification = new Messaging.CustomNotification();
        Set < String > recipientsIds = new Set < String >();
        notification.setTitle( 'File Attached to your Account' );
        notification.setNotificationTypeId( notificationType.Id );
    
        for ( Account objAcc : [ SELECT Id, OwnerId FROM Account WHERE Id IN: setAccIds ] ) {
       
            notification.setBody( 'File Id is ' + mapAccIdContDocId.get( objAcc.Id ) );
            notification.setTargetId( objAcc.Id );
            recipientsIds.add( objAcc.OwnerId );
            notification.send( recipientsIds );
       
        }
    
    }

}
 
Output:


send() is not bulkified. So, remember 150 DML Governor limit.

January 14, 2021

Default value for lightning:combobox in Salesforce Lightning

Sample Code:

Component:

<aura:component implements="force:appHostable" >    
    <aura:attribute name = "defaultVal" type = "String"/>
        <aura:attribute name = "options" type = "List" default = "[
            {'label': 'Apple', 'value': 'apple'},
            {'label': 'Banana', 'value': 'banana'},
            {'label': 'Grapes', 'value': 'grapes'},
        ]"/>    
    <aura:handler name = "init" value = "{!this}" action = "{!c.onInit}"/>
    
    <div class = "slds-box slds-theme_default">
        <lightning:combobox
            name="Fruit"
            label = "Select a Fruit"
            value = "{! v.defaultVal }"
            placeholder = "Select Progress"
            options="{! v.options }"
            onchange="{! c.handleChange }"/>
    </div>
</aura:component>

JavaScript Controller:
({
    onInit : function( component, event, helper ) {    
          
        component.set( "v.defaultVal", "banana" );  
          
    },
    
    handleChange: function (cmp, event) {
        
        var selectedOptionValue = event.getParam( "value" );
        alert( "Value selected is " + selectedOptionValue );
        
    }
    
});


Output:


January 13, 2021

Salesforce Language Settings for Embedded Service Chat

embedded_svc.settings.language should be adjusted to display the Chat in the specified language.

For example, embedded_svc.settings.language = 'fr' will display the information in French.


 

January 12, 2021

Using Wrapper Class in Salesforce Lightning Web Component

Sample Code:

Apex Class:

public with sharing class AccountController {
 
    @AuraEnabled( cacheable = true )
    public static AccountWrapper fetchAccounts() {
        
        AccountWrapper wrap = new AccountWrapper();
        
        wrap.listAcc =  [ SELECT Id, Name, Industry, AccountNumber
                               FROM Account
                             LIMIT 10 ];
        
        wrap.accCount = wrap.listAcc.size();
        return wrap;
         
    }
    
    public class AccountWrapper {
        
        @AuraEnabled
        public List < Account > listAcc;
        @AuraEnabled
        public Integer accCount;
        
    }
     
}

HTML:
<template>

    <div class="slds-box slds-theme--default">
        <template if:true = {records}>          
            No of Accounts : {intCount}        
            <div style = "height: 300px;">
                <table class="slds-table slds-table_cell-buffer slds-table_bordered slds-table_striped">
                    <tr>
                        <th scope = "col">Name</th>
                        <th scope = "col">Industry</th>
                        <th scope = "col">Account Number</th>
                    </tr>
                    <template iterator:it = {records}>
                        <tr key = {it.value.Id}>
                            <td>{it.value.Name}</td>
                            <td>{it.value.Industry}</td>
                            <td>{it.value.AccountNumber}</td>
                        </tr>
                    </template>
                </table>
            </div>                  

        </template>      
        <template if:true = {error}>  
        {error}>                  
        </template>  
    </div>

</template>

JavaScript:
import { LightningElement, wire } from 'lwc';
import fetchAccounts from '@salesforce/apex/AccountController.fetchAccounts';

 

export default class Sample extends LightningElement {

    records;
    error;
    intCount

    @wire( fetchAccounts )  
    wiredAccount( { error, data } ) {

        if ( data ) {

            console.log( 'Data from Apex' + JSON.stringify( data ) );
            this.records = data.listAcc;
            this.intCount = data.accCount;
            this.error = undefined;

        } else if ( error ) {

            this.error = error;
            this.records = undefined;

        }

    }

}

JavaScript-meta.xml:

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>49.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__Tab</target>
    </targets>
</LightningComponentBundle>

Output:
 

January 11, 2021

Time spent at Capacity in Salesforce Chat Session Report

Total amount of time in seconds in which an agent’s queue was full.
This field would capture the value of the time (seconds) the agent is at full capacity which is 100%.
This field will be blank if the agents never reached 100% capacity.

Chat Session Reports - https://help.salesforce.com/articleView?id=reports_live_agent.htm&type=5
 
1. Create a Custom Report Type with Chat Session as primary object.
 

2. Add Time spent at Capacity to the report.


January 9, 2021

Dynamic href to list of records in LWC Salesforce

LWC:

HTML:

<template>

    <div class="slds-box slds-theme--default">
        <template if:true = {records}>                  
                <div style = "height: 300px;">
                    <table class="slds-table slds-table_cell-buffer slds-table_bordered slds-table_striped">
                        <tr>
                            <th scope = "col">Name</th>
                            <th scope = "col">Industry</th>
                            <th scope = "col">Account Number</th>
                        </tr>
                        <template iterator:it = {records}>
                            <tr key = {it.value.Id}>
                                <td><a href={it.value.hrefVal}>{it.value.Name}</a></td>
                                <td>{it.value.Industry}</td>
                                <td>{it.value.AccountNumber}</td>
                            </tr>
                        </template>
                    </table>
                </div>                  
     
            </template>      
            <template if:true = {error}>  
                {error}>                  
            </template>  
    </div>
    
</template>

JavaScript:
import { LightningElement, wire } from 'lwc';
import fetchAccounts from '@salesforce/apex/AccountController.fetchAccounts';
import { NavigationMixin } from 'lightning/navigation';

 

export default class Sample extends NavigationMixin( LightningElement ) {

    records;
    error;

    @wire( fetchAccounts )  
    wiredAccount( { error, data } ) {

        if ( data ) {

            let rows = [];
            let tempRows = JSON.parse( JSON.stringify( data ) );

            for ( let i = 0; i < tempRows.length; i++ ) {

                let row =  tempRows[ i ];
                console.log( "Element value is " + JSON.stringify( row ) );
                row.hrefVal = "/apex/Example?recId=" + row[ "Id" ] + "&checkBool=true";
                rows.push( row );
                
            }

            this.records = rows;
            this.error = undefined;

        } else if ( error ) {

            this.error = error;
            this.records = undefined;

        }

    }

}

JavaScript-meta.xml:
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>49.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__Tab</target>
    </targets>
</LightningComponentBundle>

Visualforce page:
<apex:page >
    
    {!$CurrentPage.parameters.recId}<br/>
    {!$CurrentPage.parameters.checkBool}
    
</apex:page>

Output:



January 8, 2021

Why .invalid is added to Users email addresses after Sandbox Refresh in Salesforce?

When you create, refresh, or clone a sandbox, user email addresses are modified in your sandbox so that users won't accidentally receive email while doing development and testing in the Sandbox environment.
 
While doing development and testing, the existing Process Builders, Workflow Rules and Triggers may send emails to the users. In order to avoid this chaos, emails sent to email addresses ending with .invalid are ignored by the system. 

Note:
1. User who refreshed the Sandbox email address won't be appended with .invalid.
2. Email addresses configured in Custom Settings, Custom Metadata Types won't be appended. 
3. If you stuck with logging in, check this article - https://help.salesforce.com/articleView?id=000335702&type=1&mode=1

For additional information, please check the below reference link

January 7, 2021

Batch Request in Salesforce

Executes up to 25 subrequests in a single request. The response bodies and HTTP statuses of the subrequests in the batch are returned in a single response body. Each subrequest counts against rate limits unlike Composite Request.

Subrequests execute serially in their order in the request body. When a subrequest executes successfully, it commits its data. Commits are reflected in the output of later subrequests. If a subrequest fails, commits made by previous subrequests are not rolled back. If a batch request doesn’t complete within 10 minutes, the batch times out and the remaining subrequests aren’t executed.

haltOnError
Controls whether Salesforce should stop processing subrequests if a subrequest fails. The default is false.
If the value is false and a subrequest in the batch doesn’t complete, Salesforce attempts to execute the subsequent subrequests in the batch.
If the value is true and a subrequest in the batch doesn’t complete due to an HTTP response in the 400 or 500 range, Salesforce halts execution. It returns an HTTP 412 status code and a BATCH_PROCESSING_HALTED error message for each subsequent subrequest. 
 
Different between Composite and Batch in Composite Resources
Composite

In a subrequest’s body, you specify a reference ID that maps to the subrequest’s response. You can then refer to the ID in the url or body fields of later subrequests by using a JavaScript-like reference notation.

allOrNone flag is available for roll back option.

Batch
Subrequests are independent, and you can’t pass information between them. Subrequests execute serially in their order in the request body.

If a batch request doesn’t complete within 10 minutes, the batch times out and the remaining subrequests aren’t executed.

January 6, 2021

Queue-Based Chat Routing in Salesforce Lightning

1. Go to Omni-Channel Settings under Setup.
 

 
2.  Select Enable Omni-Channel and click Save Button.


3. Go to Chat Settings under Setup.


4. Select Enable Chat and click Save Button.


5. Go to Skills under Setup.


6. Click New Button.


7. Create a new Skill. Make sure to select Users or Profiles for the Skill.


8. Open Routing Configurations under Setup.
 
 
9. Click New button.
 

10.  Create a new Routing Configuration.


11. Open Queues under Setup.
 

12. Click New button.


13. Save the Queue information.

a. Select the Routing configuration created in Step 10.
b. Select Chat Transcript as Supported object.
c. Select the Queue Members.
 

14. Open Chat Buttons & Invitations under Setup.


15. Click New button.


16. Save the Chat Button information.

a. Select Type as Chat Button.
b. Routing type should be Omni-Channel.
c. Select the Queue created in Step 13.


17. Copy the Code from "Chat Button Code" section.

18. Paste it in a text file.

19. Replace <!-- Online Chat Content --> and <!-- Offline Chat Content -->.
 
20. Go to Deployments under Chat Setup.
 
 
21. Click New Button.


23. Save the Deployment information.
 
 
24. Copy the Code from "Deployment Code" section.
 
25. Paste it in the same text file below the Chat Button Code.

26. Save it with any name and add .html as extension.

27. Go to Presence Statuses under setup.


28. Click New button.


29. Save the Presence Status information.


30. Go to App Manager under setup.


31. Edit the App(Console Navigation App).

32. Add Omni-Channel under Utility Items(Desktop Only).


33. Add the Presence Status created in Step 29 to the Profile. Add it to the Service Presence Statuses Access section.


34. Open the App which was edited in Step 22.

35. Select the Omni-Channel widget and select Chat.


36. Make sure you are Online.


37. Open the HTML file which contains Deployment Code and Chat Component Code.

38. Click the Link to chat with the user.


39. Go to the Salesforce App and Accept the Chat.


40. Agent and Visitor can chat with each other.


January 5, 2021

Simple lightningsnapin/baseChatMessage LWC Component

Sample Code:

HTML:

<template>
    <h1>Welcome to the Chat</h1>
    <div class={messageStyle}>
        <lightning-formatted-rich-text
            value={messageContent.value}>
        </lightning-formatted-rich-text>
    </div>
</template>

JavaScript:
import BaseChatMessage from 'lightningsnapin/baseChatMessage';

const CHAT_CONTENT_CLASS = 'chat-content';
const AGENT_USER_TYPE = 'agent';
const CHASITOR_USER_TYPE = 'chasitor';
const SUPPORTED_USER_TYPES = [AGENT_USER_TYPE, CHASITOR_USER_TYPE];

export default class ChatLWC extends BaseChatMessage  {
    
    messageStyle = '';

    isSupportedUserType(userType) {

        return SUPPORTED_USER_TYPES.some( ( supportedUserType ) => supportedUserType === userType );

    }

    connectedCallback() {

        console.log( 'Message Content is ' + JSON.stringify( this.messageContent ) );

        if ( this.isSupportedUserType( this.userType ) ) {

            this.messageStyle = `${CHAT_CONTENT_CLASS} ${this.userType}`;

        } else {

            throw new Error( `Unsupported user type passed in: ${this.userType}` );

        }
    }

}

CSS:
.chat-content {
    position: relative;
    font-weight: 400;
    line-height: 1.3;
    max-width: 70%;
    padding: 10px;
    font-size: 0.875em;
    border-radius: 10px 10px 0;
    float: right;
    margin-left: 40px;
    white-space: pre-wrap;
}

.agent.chat-content {
    color: rgb(192, 192, 192);
    background: #ff6060;
    border-radius: 10px 10px 10px 0;
    float: left;
}

.chasitor.chat-content {
    background: rgb(206, 51, 203);
    color: #fff;
    margin-left: auto;
}

JavaScript-meta.xml:
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>50.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
      <target>lightningSnapin__ChatMessage</target>
    </targets>
</LightningComponentBundle>

Embedded Chat Configuration:
 

Output:


January 4, 2021

Chat Agent is in an 'Online' status but the Chat button shows offline Salesforce Community

1. Go to Community Builder.

2. Select the Setting icon.

3. Open Security.

4. Set the Security Level to "Relaxed CSP: Permit Access to Inline Scripts and Allowed Hosts".

5. Make sure the activate the Chat URLs. Status should be Active.



January 3, 2021

How to delete a Project in Google Cloud or Gcloud?

1. Open the Terminal using the Terminal icon on the top right corner.


2. Use the below command. Project Id or Number will be available in the Project Dashboard. Check the Project Info section.

gcloud projects delete PROJECT_ID_OR_NUMBER

To know more about the above command, check the below

January 1, 2021

getRecord using LWC in Salesforce

Sample code:

HTML:
<template>
    <lightning-card>
        Contact Name is {name}<br/><br/>
        Contact Phone Number is {phone}
    </lightning-card>
</template>

JavaScript:
import { LightningElement, wire, api } from 'lwc';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import { getRecord } from 'lightning/uiRecordApi';
import SystemModstamp from '@salesforce/schema/Account.SystemModstamp';

const FIELDS = ['Contact.Name', 'Contact.Phone'];

export default class GetRecordLwc extends LightningElement {

    @api recordId;
    contact;
    name;
    phone;

    @wire( getRecord, { recordId: '$recordId', fields: FIELDS } )
    wiredRecord({ error, data }) {

        if ( error ) {

            let message = 'Unknown error';
            if (Array.isArray(error.body)) {
                message = error.body.map(e => e.message).join(', ');
            } else if (typeof error.body.message === 'string') {
                message = error.body.message;
            }
            this.dispatchEvent(
                new ShowToastEvent({
                    title: 'Error loading contact',
                    message,
                    variant: 'error',
                }),
            );

        } else if ( data ) {

            this.contact = data;
            console.log( 'Contact is ' + JSON.stringify( this.contact ) );
            this.name = this.contact.fields.Name.value;
            this.phone = this.contact.fields.Phone.value;

        }
    }

}

JavaScript-meta.xml:
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>50.0</apiVersion>
    <isExposed>true</isExposed>  
    <targets>  
        <target>lightning__RecordPage</target>  
    </targets>  
</LightningComponentBundle>

Output: