Sunday, February 13, 2022

Time saving way of preparing Salesforce certifications with value addition to projects

 

Motivation behind this


Many a times, I receive requests from many trailblazers on which certification to go next. After passing 30 certifications spanning across different domains, I thought of bringing this path with time saving options which will not only to prepare in lesser time but actually it will help in adding value to projects.

The path has been created not just looking into the pre-requisite to complete certain certification but actually the gathered knowledge will add value in project execution.


Effective Certification Path




Legends used

All green boxes will help to become System Architect for project executions.
All orange boxes will help to become project oriented Application Architect.
Respective blue boxes are good for B2C and B2B Solution Architect.
Solid arrow is most preferable to go next.
Dashed arrow is optional.

Justifications behind this path

  • Salesforce knowledge starts with preparing Admin certification which is the base certification for anyone. Aspirants can choose Developer role to become Architect or Admin to become Consultant and down the line to Domain or Solution Architect.
  • After gaining knowledge on Administration, aspirant can choose either PDI or App Builder
  • JavaScript Developer I certification plays crucial role on Lightning development so this needs to completed first before going to PDII or OmniStudio Developer or B2C Commerce Developer.
  • After completing PDII, it will be better to go for Integration Architect as 25% of the course content is similar.
  • Though Admin is pre-requisite for Sales Cloud, Service Cloud or Experience Cloud, but before that App Builder certification with hands-on experience is necessary before attempting those certifications.
  • Aspirant can prioritize Sales or Service Cloud based on project requirements but Sales Cloud gives bigger horizon on CRM perspective is become mandatory even if aspirant is taking Architect path.
  • Knowledge of Experience Cloud is vital now-a-days, it should help not only on Sharing Architect exam but also Identify & Access Architect.
  • A person can be better architect in a project, if he completes UX Designer during his/her journey.
  • CPQ Specialist and Field Service should help on B2B Solution Architect.
  • For marketers, Marketing Cloud Admin should be first certification to gain knowledge and work on projects instead of MC Email Specialist certification. 

Conclusion


Hope it will help our community members to reach their own destination. Again, this path will vary based on own target and it is not universal to be followed but it is indicative one.

For all certifications, refer http://certification.salesforce.com/


Friday, February 4, 2022

Display data from CMS using Lightning Web Component

 

Motivation behind this


We know that from B2B Commerce Lightning we can use readymade Lightning Web Components available through B2B Lex and use it for many purposes like Product Detail Card, Product Detail List, Cart Component, Order Summary etc. at Experience Cloud.
Refer B2B Commerce on Lightning Experience Components and it also fetches data from CMS.

What if I have to build similar LWC component which can be placed in Lightning RecordDetail page for internal users and data to be retrieved from CMS. This drives me to do a proof-of-concept and sharing you the steps to develop.





Use Case


Business wants to see the list of featured products for internal users and those product images are placed at CMS.

Developer wants to build LWC component to fetch records from CMS.

For sake of simplicity, those images will be displayed using lightning-carousel component.

Solution Approach


To start with, lets configure CMS and store images on CMS side.

Create CMS Workspace

From CMS Home Tab, Create CMS Workspace. Here given a name as Capricorn Store. Screen as follows:


Create a Channel

From Capricorn Store workspace, create a channel naming Capricorn Channel.




Add Content

Add 3 product related pictures as follows. For demo purpose I have used Coffee Machines pictures.


Sample product image and details will look like this:


Now let's create LWC component.

displayMediaFilesFromCMS.html

HTML will like below here lightning-carousel has been used.

<template>
    <lightning-card  title="Featured Products">
<div class="slds-medium-size_1-of-4 slds-align_absolute-center"> <lightning-carousel>
<template for:each={results} for:item="item"> <lightning-carousel-image key={item.title} src= {item.url} header= {item.title} href= {item.url}> </lightning-carousel-image>
</template> </lightning-carousel>
</div> </lightning-card>
</template>

displayMediaFilesFromCMS.js

Few notable things as follows:
  • Channel Name to be passed to Apex class's  retrieveMediaFromCMS() method using wire. For sake of simplicity channel name is hardcoded, it can be captured from design attributes too and using @api variable.
  • Loop through the array returned from Apex method and create new array with title and url as elements which will be used by carousel.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import { LightningElement, track, wire } from 'lwc';
import retrieveMediaFromCMS from '@salesforce/apex/CMSConnectHelper.retrieveMediaFromCMS';

export default class DisplayMediaFilesFromCMS extends LightningElement {
    channelName = 'Capricorn Channel';
    @track results=[];
    @wire(retrieveMediaFromCMS,{channelName: '$channelName'})
    wiredData({ error, data }) {
        if (data) {
            let objStr = JSON.parse(data);
            objStr.map(element=>{
                this.results = [...this.results,{title:element.title,
                                                url:element.url}]
            });  
            this.error = undefined;            
        } else if (error) {
            this.error = error;
            this.results = undefined;
        }
    }
}


CMSConnectHelper.cls


Salesforce provides ConnectApi.ManagedContent class and its method to retrieve data from CMS. No need to fetch data using REST APIs. Refer documentation

 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
/**
 * @description       : This class is used to retrieve data from CMS
 * @author            : Santanu Boral
 * 
**/
public with sharing class CMSConnectHelper {
    
    @AuraEnabled (cacheable=true)
    public static String retrieveMediaFromCMS(String channelName){
        String channelId = getChannelId(channelName);

        //get the image content
        ConnectApi.ManagedContentVersionCollection obj = 
            ConnectApi.ManagedContent.getAllContent(channelId, 0, 5, 'en_US', 
                                                    'cms_image',false,
                                                    '2011-02-25T18:24:31.000Z','2021-09-25T18:24:31.000Z',true);
        
        List<ReturnWrapper> wrapperList = new List<ReturnWrapper>();
        System.debug('json value=' + JSON.serialize(obj));

        //loop through each item and prepare a wrapper list
        for(ConnectApi.ManagedContentVersion versionObj: obj.items){
            ReturnWrapper wrapper = new ReturnWrapper();
            wrapper.title = versionObj.title;
            
            //get the url
            Map<String,ConnectApi.ManagedContentNodeValue> contentNodesMap = versionObj.contentNodes;
            for(String str:contentNodesMap.keySet()){                
                if(str=='source'){
                    wrapper.url= ((ConnectApi.ManagedContentMediaSourceNodeValue)contentNodesMap.get(str)).url;
                }		
            }
            wrapperList.add(wrapper);	
        }
        return JSON.serialize(wrapperList);
    }

    @AuraEnabled (cacheable=true)
    public static String getChannelId(String channelName){
        ConnectApi.ManagedContentChannelCollection channelRepObj = 
                ConnectApi.ManagedContent.getAllDeliveryChannels(0,2);        

        //loop through the channels and return the channel Id
        for(ConnectApi.ManagedContentChannel channelObj: channelRepObj.channels){
            if(channelObj.channelName == channelName){
                return channelObj.channelId;
            }
        }
        return null;
    }

    public class ReturnWrapper{
        String title {get;set;}
        String url {get;set;}
    }
}

getAllContent() returns a JSON as follows which needs to be parsed to form title and url combination for each item elements.

 
  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
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
{
  "currentPageUrl": "/services/data/v51.0/connect/cms/delivery/channels/0ap2v000000Pu1w/contents/query?endDate=2021-09-25T18%3A24%3A31.000Z&language=en_US&managedContentType=cms_image&page=0&pageSize=5&startDate=2011-02-25T18%3A24%3A31.000Z",
  "items": [
    {
      "contentKey": "MCR6FJVMX4BRHSVJD4LPWUPPN3OI",
      "contentNodes": {
        "thumbUrl": {
          "nodeType": "Url",
          "value": "https://live.staticflickr.com/65535/49815666003_4973d84842.jpg"
        },
        "title": {
          "nodeType": "NameField",
          "value": "TR-COFMAC-001"
        },
        "source": {
          "fileName": null,
          "isExternal": true,
          "mediaType": "Image",
          "mimeType": null,
          "nodeType": "MediaSource",
          "referenceId": null,
          "resourceUrl": null,
          "unauthenticatedUrl": null,
          "url": "https://live.staticflickr.com/65535/49815666003_4973d84842.jpg"
        }
      },
      "contentUrlName": "tr-cofmac-001",
      "language": "en_US",
      "managedContentId": "20Y2v000000HbISEA0",
      "publishedDate": "2021-05-09T16:32:44.000Z",
      "title": "TR-COFMAC-001",
      "type": "cms_image",
      "typeLabel": "Image",
      "unauthenticatedUrl": "https://myinstance.my.salesforce.com/cms/delivery/v51.0/0ap2v000000Pu1wAAC/contents/20Y2v000000HbISEA0?oid=00D2v000002A3VoEAK"
    },
    {
      "contentKey": "MCSC6EEJVMZZB5HDHRKMTEQH6TIQ",
      "contentNodes": {
        "thumbUrl": {
          "nodeType": "Url",
          "value": "https://live.staticflickr.com/65535/49815637818_d892f8127d.jpg"
        },
        "title": {
          "nodeType": "NameField",
          "value": "E-ESP-001-1"
        },
        "source": {
          "fileName": null,
          "isExternal": true,
          "mediaType": "Image",
          "mimeType": null,
          "nodeType": "MediaSource",
          "referenceId": null,
          "resourceUrl": null,
          "unauthenticatedUrl": null,
          "url": "https://live.staticflickr.com/65535/49815637818_d892f8127d.jpg"
        }
      },
      "contentUrlName": "e-esp-001-1",
      "language": "en_US",
      "managedContentId": "20Y2v000000HbIIEA0",
      "publishedDate": "2021-05-09T16:32:06.000Z",
      "title": "E-ESP-001-1",
      "type": "cms_image",
      "typeLabel": "Image",
      "unauthenticatedUrl": "https://myinstance.my.salesforce.com/cms/delivery/v51.0/0ap2v000000Pu1wAAC/contents/20Y2v000000HbIIEA0?oid=00D2v000002A3VoEAK"
    },
    {
      "contentKey": "MCKPXLBGW36RDYXN75NDWFVTB5FA",
      "contentNodes": {
        "thumbUrl": {
          "nodeType": "Url",
          "value": "https://live.staticflickr.com/65535/49816090811_622af115a8.jpg"
        },
        "title": {
          "nodeType": "NameField",
          "value": "B-C-COFMAC-001"
        },
        "source": {
          "fileName": null,
          "isExternal": true,
          "mediaType": "Image",
          "mimeType": null,
          "nodeType": "MediaSource",
          "referenceId": null,
          "resourceUrl": null,
          "unauthenticatedUrl": null,
          "url": "https://live.staticflickr.com/65535/49816090811_622af115a8.jpg"
        }
      },
      "contentUrlName": "b-c-cofmac-001",
      "language": "en_US",
      "managedContentId": "20Y2v000000HbIDEA0",
      "publishedDate": "2021-05-09T16:28:55.000Z",
      "title": "B-C-COFMAC-001",
      "type": "cms_image",
      "typeLabel": "Image",
      "unauthenticatedUrl": "https://myinstance.my.salesforce.com/cms/delivery/v51.0/0ap2v000000Pu1wAAC/contents/20Y2v000000HbIDEA0?oid=00D2v000002A3VoEAK"
    }
  ],
  "total": 3
}

Finally, Create a Lightning App Builder page and place the component, it will look like this:


We are done and thanks for reading.

You can also extend this code to expose this component to Experience Cloud with some minor changes.


Further Reading