Friday, June 12, 2020

Reusable/Configurable Data table in Lightning Web Component (LWC) using TargetConfigs Attributes

Thought Process behind this implementation


In Past month we got almost two same requirements to showcase list of records based on certain criteria. Salesforce has their in built feature , but there is limitation of showcasing maximum 6 records at a time and to show all the records user has to click one more time. So minimize the User efforts and make it handy for Admin as well, this component has been built which can be reuse in any place from Home Page,Record page and in App Page as well.


Use Case


In the Home Page User want to see the list of opportunities which has the profitability greater that 10% . The list of fields would be Name, Stage Name, Profitability, Close Date.

And the Same user want to see the list of opportunities in the Record Page , where only Closed Won opportunities will be shown. The list of fields would be Name, Type and Lead Source.

Expected Outcome






    How Architecture works behind


       1.     Dynamically Populated the List of Objects using Global class by extending                  
               VisualEditor.DynamicPicklist.
       2.     Create LWC component which Includes :
    1.     html File that will help you to show the dynamic data table.
    2.     Java Script file that will help you build the Logic.
    3.     Each Lightning web component folder must include a configuration file named <component>.js-meta.xml. In which there is attribute called  "targetConfigs" help you to Configure the component for different page types and define component properties.

        3.   Create a class to prepare list the data which needs to be shown based on certain attributes.

    Process to implement


    • As discussed First the Global class needs to created which will prepare and return the list of Object currently present in the Environment. Class name is ObjectList
       Code as follows :


     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    global class ObjectList extends VisualEditor.DynamicPickList{
        //Default method which can be ignored , kept that for your better understanding of stucture of VisualEditor Namespace
        global override VisualEditor.DataRow getDefaultValue(){
            VisualEditor.DataRow defaultValue = new VisualEditor.DataRow('red', 'RED');
            return defaultValue;
        }
        
        //Prepare the list of Object and return
        global override VisualEditor.DynamicPickListRows getValues() {
            Map <String, Schema.SObjectType> schemaMap = Schema.getGlobalDescribe();
            
            VisualEditor.DynamicPickListRows  objValues = new VisualEditor.DynamicPickListRows();
            List<String> entities = new List<String>(schemaMap.keySet());
            entities.sort();
            for(String name : entities)
            {
                objValues.addRow( new VisualEditor.DataRow(name, name));
            }     
            return objValues;
        }
    }
       
    Few points to be remembered :
    • In getValues method we preparing the list of object with the help of salesforce in-build method by extending the VisualEditor Namespace which provides classes and methods for interacting with the Lightning App Builder, in our POC we will be using it in meta.xml file in some time.

    1
    global class ObjectList extends VisualEditor.DynamicPickList{
    


     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
     global override VisualEditor.DynamicPickListRows getValues() {
            Map <String, Schema.SObjectType> schemaMap = Schema.getGlobalDescribe();
            
            VisualEditor.DynamicPickListRows  objValues = new VisualEditor.DynamicPickListRows();
            List<String> entities = new List<String>(schemaMap.keySet());
            entities.sort();
            for(String name : entities)
            {
                objValues.addRow( new VisualEditor.DataRow(name, name));
            }     
            return objValues;
        }
    
    Another class needs to be created as follows :

    Preview:
     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
    /*
    Class Name : ReUsecls
    Developer Name : Avijit Das
    */
    
    public with sharing class ReUsecls {
        public ReUsecls() {
        }
     /*
      Method Name : getFieldTypes
      Input Parameters : List of Columns Name, Object Name.
      OutPut Parameter : Map<String, List<String>> | Api Name=(data type,Label Name) | example : MainCompetitors__c=(STRING, Main Competitor(s))
      purpose : It will taka Object and Columns name as input parameter and by using Schema.getGlobalDescribe preparing the Map as utput already given with the example.
     */
        @AuraEnabled
        public static Map<String, List<String>> getFieldTypes(String selectedFields,String obj){
            String objectName = obj;
            List<String> lst_str = new List<String>();
            lst_str = selectedFields.split(',');
            Map<String, List<String>> fieldTypeMap = new Map<String,  List<String>>();
    
            for(String field : lst_str){
                List<String> lst_objtypelab  = new List<String>();
                Schema.DisplayType fieldType = Schema.getGlobalDescribe().get(objectName).getDescribe().fields.getMap().get(field).getDescribe().getType();
                lst_objtypelab.add(String.valueOf(fieldType));
                lst_objtypelab.add(String.valueOf(String.valueOf(Schema.getGlobalDescribe().get(objectName).getDescribe().fields.getMap().get(field).getDescribe().getLabel())));
                fieldTypeMap.put(field, lst_objtypelab);
            }
            return fieldTypeMap;
        }
    
     /*
      Method Name : getObjectRecords
      Input Parameters : List of Columns Name, Object Name, where contions
      OutPut Parameter : List<sObject> as it might be any object
      purpose : It will taka Object,Columns name and filtered conditions (optional) as input parameter and after forming the query retunr the list of records
     */
        @AuraEnabled(cacheable=true)
        public static List<sObject> getObjectRecords(List<String> selectedFields,String obj,String whcon){
            System.debug('Selected---'+selectedFields+'---'+obj);
            String stemp = '';
            Integer i = 0;
            String query ='';
            for(String field : selectedFields){
                if(i == 0){
                    stemp+= field;
                }else{
                    stemp+= ','+field;
                }
                i++;
    
            }
            query = 'SELECT ' + stemp + ' FROM '+obj;
            if(whcon!=''){
                query = query+' where '+whcon;
            }
            return Database.query(query);
        }    
    
    }
    

    All the functions has been described in comment section.

    meta.xml file as follows : 

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <?xml version="1.0" encoding="UTF-8"?>
    <LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
        <apiVersion>48.0</apiVersion>
        <isExposed>true</isExposed>
        <targets>
            <target>lightning__AppPage</target>
            <target>lightning__RecordPage</target>
            <target>lightning__HomePage</target>
        </targets>
        <targetConfigs>
            <targetConfig targets="lightning__HomePage,lightning__RecordPage">
                <property name="objName" type="String" datasource="apex://ObjectList" label="Object Name"/>
                <property name="fldShn" type="String" default="" label="Fields To be shown"/>
                <property name="whcon" type="String" default="" label="Condition"/>
            </targetConfig>
        </targetConfigs>
    </LightningComponentBundle>
    

    For this POC meta.xml file is pretty important. Points to be remembered :
    • In targetConfigs target attribute we are specifying one or more page type where we will be using the component, In the POC we are trying to configure the component such a way that we can use it in Home page and record page as well.
    1
     <targetConfig targets="lightning__HomePage,lightning__RecordPage">
    

    • By Using the property attribute we are defining the input parameters for the Component author to be given at the time of adding the component in the Pages and based on the input parameter the component should work dynamically. There are three properties will be needed 
      • Object which will be pre-populated dynamically by using the visual editor and can be used by datasource attribute "datasource="apex://ObjectList""
      • Number of fields which will reflect as column .
      • Where condition, based on which we will be filtering the data.
    <targetConfig targets="lightning__HomePage,lightning__RecordPage">
                <property name="objName" type="String" datasource="apex://ObjectList" label="Object Name"/>
                <property name="fldShn" type="String" default="" label="Fields To be shown"/>
                <property name="whcon" type="String" default="" label="Condition"/>
            </targetConfig>


    Js file as follows


     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
    /*
    Name : ReUse
    Developer Name : Avijit Das
    */
    
    import { LightningElement, track, wire, api } from 'lwc';
    import getObjectRecords from '@salesforce/apex/ReUsecls.getObjectRecords';
    import getFieldTypes from '@salesforce/apex/ReUsecls.getFieldTypes';
    
    
    export default class ReUse extends LightningElement {
        @api objName;
        @api fldShn;
        @api whcon;
     
     //default Id and Name will be assigned if nothing has been given.
        @track columns = [
            { label: 'Id', fieldName: 'Id' },
            { label: 'Name', fieldName: 'Name' } 
        ];
     
     //Passing the default columns
        @track selected = ['Id', 'Name'];
        @track fetchedRecords = [];   
    
     //based on the chnages in the input parameters in the Meta.xml file the functions will fire
        @wire(getObjectRecords, { selectedFields: '$fldShn', obj: '$objName', whcon: '$whcon'})
        wiredContacts({ error, data }) {
            if (data) {
                this.fetchedRecords = data;
       //This will prepare the list of columns
                this.handleFetchFieldTypes();            
            } else if (error) {
                console.log(error);
            }
        }
    
     
        handleFetchFieldTypes() {        
            var fieldarry  = this.fldShn.split(',');
            this.selected.splice(0, this.selected.length)
      
      //Prepare the input in the form of string array  
            for (var j = 0; j < fieldarry.length; j++) {
                console.log(fieldarry[j]);
                this.selected.push(fieldarry[j]);
            }
    
      //once the functions has been called it will return Map<String, List<String>>
            getFieldTypes({ selectedFields: this.fldShn, obj: this.objName})
                .then(result => {
                    this.columns = this.selected.map(field => {
                        
         //assigning the data type
         const dType = result[field][0];
         
         //assigning the lable
                        const dlabel = result[field][1];
                        if (dType === 'STRING' || dType === 'ID') {
                            return { label: dlabel, fieldName: field };
                        } else if (dType === 'DATE') {
                            return { label: dlabel, fieldName: field, type: 'date' };
                        } else if (dType === 'DATETIME') {
                            return {
                                label: dlabel,
                                fieldName: field,
                                type: 'datetime'
                            };
                        } else if (dType === 'Integer') {
                            return {
                                label: dlabel,
                                fieldName: field,
                                type: 'Integer'
                            };
                        } else if (dType === 'BOOLEAN') {
                            return {
                                label: dlabel,
                                fieldName: field,
                                type: 'text'
                            };
                        } else {
                            return { label: dlabel, fieldName: field };
                        }
                    });
                })
                .catch(error => {
                    console.log(error);
                });
                
        }
    
    }
    


    html code as follows :


     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    <template>
        <lightning-card title="ReUsable Component" icon-name="custom:custom19">
            <lightning-datatable
                key-field="id"
                data={fetchedRecords}
                columns={columns}
                >
            </lightning-datatable>  
        </lightning-card>
        
    </template>
    


    References:








    Saturday, May 16, 2020

    Einstein Analytics Security Predicate

    Thought Process behind this implementation


    Last month I finished two super-badges related to Einstein Analytics to learn something about it due to some project related work. While completing the modules the area which I felt was the most needful that is the security. As most of you know that we will be dealing with the Customer Data which should be very secure, not only from the outsiders also from their employees as well, so I thought of to write something. 


    How Architecture works behind




      Use Case

      In our current world one of the most important parameter is the sales data , let's talk about Opportunity Data which is the most important thing when it's about sales data.  Let say ABC organization are maintaining the below hierarchy : -






      Now we are going to talk about that after implementing Einstein Analytics security predicate on sales data how the visibility works for each and every employee. 



      How Security Works : 

      1. When we are enabling the Einstein Analytics in Salesforce  org, Analytics gains access to Salesforce data based on permissions of two internal Analytics users: Integration User Profile and Security User Profile.
      2. Analytics uses the permissions of the Integration User to extract data from Salesforce objects and fields when a data-flow job runs. Because the Integration User has View All Data access.
      3. When you query a Dataset that has row-level security based on the User object, Analytics uses the permissions of the Security User to access the User object and its fields. The Security User must have at least read permission on each User object field included in a predicate. A predicate is a filter condition that defines row-level security for a Dataset. By default, the Security User has read permission on all standard fields of the User object.
      4. So to use Salesforce data in Analytics we need both of the Profiles.
      5. Analytics App are like salesforce folder which can be Private and shared , by doing that it can control sharing of everything what are there in the App.
      6.  App can be shared with  users, groups, or roles.
      7. If an Analytics user has access to a dataset, the user has access to all records in the dataset by default. However, you can implement row-level security on a dataset to restrict access to records or turn on sharing inheritance, or do both. 
                            Turn On the Sharing Settings:
        • From Setup, in the Quick Find box, enter Analytics, and then click Settings.
        • Select Inherit sharing from Salesforce, and click Save.               

           8.     When setting up sharing inheritance, you specify from which objects to migrate the sharing rules. In Our Example that will be Opportunity sharing rule. It has some limitations in its Own.

          9.    To overcome the limitations we have to introduce Security Predicate. which might be : -
      • Security Predicates for Datasets .
      • Row-Level Security based on Record Ownership 
      • Row-Level Security Based on Territory Management     
      • Row-Level Security based on Opportunity Teams
      • Row-Level Security Based on Territory Management
           10.    Please consider the below table and try to understand the how data accessibility works after implement of security predicates :-


        Refer the role hierarchy declared under the section of "Use Case".


        • Observation 1 :  Once the data set gets created and nothing will be imposed in terms of security predicate. Then CEO, Sales Rep1  and Sales Rep2 will see all the records as long as they have the access to those fields.
        • Observation 1 :  Suppose once sharing rule already created in salesforce and once you are going to enable "inherit sharing" it will start following the security access mentioned in the sharing rule. Based on the Sales Rep1 and Sales Rep2 will see their records. 
        • Observation 1 : if you want to narrow down bit more then you might enable security predicate based on your need. Everything is described very clearly in salesforce documents So I am not getting into details , I will put that in reference link, please find some examples below : - 
          • 'UserId' == "$User.Id"
          • 'Territory' == "$User.Territory__c" || 'Executive_ProfileId' == "$User.ProfileId"

        References :




        Wednesday, May 6, 2020

        Integration between Salesforce and QUIP Where Salesforce Work as Identity Provider (SP-Initiated SAML)


        Thought Process behind this implementation


        Two days ago I got one requirement of integrating between Salesforce and Quip, where Salesforce will act as Identity Provider and Quip will be acting like Service Provider.
        To achieve this functionality I have used Self Signed Certificate for authentication and Single Sign on is being made by Security Assertion Markup language (SAML).


        Use Case


        Once Quip has been set up and connection is being made with Salesforce user will be able to see all the documents (whatever they have created in Quip) in Account Layout (took the Account as example, you can add the quip component to other layouts as well).
        Now the problem is while working into Salesforce environment if User wants to open that document in Quip by clicking it from Salesforce, every-time users’ needs to login in to Quip with their credentials. They don’t want login every time as it is time taking and at the same time monotonous as well. So we have to create some integration between them to achieve this functionality.

        How Architecture works behind

        • The user tries to access a service provider already defined in Salesforce.
        • Salesforce sends a SAML response to the service provider.
        • The service provider identifies the user and authenticates the certificate.
        • If the user is identified, the user’s logged in to the service provider.




        Things needs to be checked before start the development

        1. You need to be a Salesforce Admin for your instance.
        2. You need to have a Quip for Salesforce license or be on a Quip for Salesforce Trial.
        3. You need to be a Quip Admin if your Quip Site already exists.
        4. Configure a domain using My Domain and deploy it to all users. For instructions, see Set up a My Domain Name.
        5. Your Salesforce email/username/Federation ID must match the email you use within Quip.
        6. Double check that your permission sets have been set to allow access to Single Sign On to all necessary users. This can be found within Salesforce under the Quip Permission Sets.

        Process to Implement:

        Download the Quip Metadata File

        1. Open the Quip Admin Console, and go to the “Accounts and Access” tab
        2. Click on the “For entity ID and destination URL, download Quip’s metadata” to Download the Quip metadata file. It will look like below :  
        From the metadata.xml we need the Entity Id and the Location URL to configure the Connected App in Salesforce. We will discuss on it in sometime.

        Once above steps are completed, you can then continue to configure Salesforce as an Identity Provider.


        Configuration needs to be done in Salesforce:


        1. Select the gear icon within your Salesforce instance, and select “Setup”. Search for Certificate and Key Management and click “Create Self-Signed Certificate.”




         [Note: By default, a Salesforce identity provider uses a self-signed certificate generated with the SHA-256 signature algorithm. Use the certificates when securely communicating with other services. If you want to use a CA-signed certificate instead of self-signed certificate, you can use that also. For this POC have used self-signed certificate.]
        • Label Name <Give any name> (have given as “QuipTest”)
        • Key Size  <keep it default as 2048>
        • Keep all the others attribute as it is.

        Click “Save”.



             2.     Once you save this, it will look like below:-






                   3. On the left hand side of the screen, within the Quick Find search bar, enter Identity Provider, select Identity Provider, and click Enable Identity Provider.

        4.   Select the certificate which you just created (in our case it will be QuipTest) and click Save.


        5.    Once you click Save, you will then see at the bottom of the page “Use Connected Apps to create a Service Provider”



        [Note :As a reminder, your Identity Provider (Salesforce) would be what your Service Provider (Quip or any other connected App Program) authenticates into when logging in.]

        6.  Within the Service Provider configuration, you can then enter the following items.

        • Name: Name your Service Provider, (i.e. Quipconnect)
        • Include your contact email
          • Click the “Enable SAML” checkbox to fill the below parameters: -
        • Entity ID: (This can be found within the Quip metadata file. Refer section Download the Quip Metadata File)
        • Start URL: (This would be the location URL within your Quip metadata file.Refer section Download the Quip Metadata File)
        • ACS URL: (This would be the location URL within your Quip metadata.Refer section Download the Quip Metadata File)
        • Subject Type  : It should be the attribute which is unique in the your Salesforce org , and match with the email id of Quip)
        • Name ID Format  : urn:oasis:names:tc:SAML:2.0:nameid-format:persistent
        • IdP Certificate  : Select the self-signed Certificate which you have created (in your case it will be “QuipTest” )
        Keep all the other filed as it is and save.








        7.   Make sure to check that your users have been correctly assigned the permission sets within Salesforce. You can do this by going to the newly created Quip Service Provider, and assign Quip and the specified users that are within Salesforce and your Quip Site.

        8.   After the Service Provider section is completed, you can then download the metadata from the Identity Provider page.


        9.    Once done please go to the Profile for the User (by which the configuration is being done) in Salesforce and give the permission for the newly created connected App. 


        10.    Open the Quip Admin Console, and go to the “Accounts and Access” tab.


         11.    Create a new SAML configuration, by naming the configuration and uploading the Salesforce Metadata (Just downloaded from Identity provider section) into the "Upload File" section.


         12.   Uploading the correct file you will get a Successful message:


        13.    Enter your email that is being used to log you into the Quip Admin portal as the initial “Test Email”.




        14.      After this, you should then see a pop-up window for Salesforce, use your specified credentials to log into Salesforce and complete the authentication process.



        15.      You will then be prompted to “Configure for Test Users” or to “Configure for Entire Company”. If you would like to test users, you can use any users within your Quip instance that has Salesforce credentials set up. Then hit “Enable”.




        16.       You should then see that the configuration is set to “Enabled” for SAML! You can always go into the configuration and “Disable” if you would like to turn it off or change the configuration to either “Enable for entire company” or “Enable for Select Users”.


        [Note: If you would like to exempt certain users from your site for SAML, add their domain to the “Exempted Domain” option within the configuration. The correct way to enter the exempted domain is to follow this format website.com, you will not need to include the ‘@’ symbol or www. Format.]

        Your setup is ready to use.