AWS Big Data Blog

Embed Amazon QuickSight dashboards in Salesforce

January 2023: This post was reviewed and updated for accuracy.

Amazon QuickSight is a fast, cloud-powered, business intelligence (BI) service that makes it easy to deliver insights to everyone in your organization. With the QuickSight Enterprise edition, you can also embed the QuickSight dashboard into a webpage or your custom application.

Salesforce is an AWS Partner Network Advanced Technology Partner with the AWS DevOps Competency. If you’re using both Salesforce.com CRM and AWS, creating a single front end provides an enhanced experience for users who need to access information they need to take action on in Salesforce (such as updating opportunities, managing accounts and contacts, and logging activities) as well as large aggregated datasets from workloads running in AWS (such as prior purchase history, digital behavior data, and application usage). When using QuickSight within Salesforce, data doesn’t need to be replicated between AWS and Salesforce, which enables organizations to decouple data analytics and management roles from CRM user roles, while providing visibility across datasets.

Embedded QuickSight dashboards allow you to use the QuickSight serverless architecture and easily scale your insights with your growing user base, while ensuring you only pay for usage with the unique pay-per-session pricing model in QuickSight. Serverless architecture and pay-per-session pricing make it cost-effective for large-scale deployments. Quicksight Although data visibility is provided to users in your CRM, all data continues to reside in AWS and is secured using your existing policies. With QuickSight, applications can authenticate the dashboard users with any identity provider of choice (such as Active Directory, Amazon Cognito, or any SAML-based federated single sign-on provider that your organization uses) and act on behalf of the user to get access to QuickSight dashboard. When using QuickSight embedded analytics, you can allow temporary access to a dashboard with a single-use URL. In this solution, we use Salesforce’s named credential functionality to store credentials to generate a unique URL when a Salesforce user attempts to view the dashboard. This means that every user receives a secure, personalized dashboard while requiring no user-facing QuickSight-specific authentication. Dashboard embedding is available in QuickSight Enterprise edition in all supported Regions.

In this post, we demonstrate how we can embed a QuickSight dashboard within Salesforce to enable a unified customer experience.

Solution overview

This solution includes the following high-level steps, broken up into two main sections:

  1. Set up AWS:
    1. Create an AWS Identity and Access Management (IAM) user with QuickSight permissions to generate the embed URL.
    2. Create an IAM role for QuickSight via the AWS Command Line Interface (AWS CLI).
    3. Create a role-backed user in QuickSight via the AWS CLI.
    4. Retrieve the QuickSight dashboard URL.
    5. Test the URL.
    6. Approve the domain to allow embedded dashboards on a webpage.
  2. Set up Salesforce:
    1. Create named credentials to store your AWS user credentials.
    2. Create a controller for your Visualforce page.
    3. Create a Visualforce page.
    4. Embed the page on your Salesforce Layouts.

Prerequisites

Before getting started, make sure you complete the following prerequisites:

  1. Create QuickSight analysis and then publish it as dashboard.
    1. Add parameter to the QuickSight analysis (Optional).
      • If you want to pass parameters from SalesForce to QuickSight later, you can add parameter to the QuickSight analysis by clicking Parameters at the left menu bar:
      • Click on the + sign at the top right corner of the Parameters list. A parameter panel should show up. Fill in the name, data type, and default value (if any) of the parameter then click Create.
    2. Use parameter in the filter.
      • Click on the Filter item at the left menu bar. Click on the ADD FILTER button.
      • There will be a drop box with all the columns available in the analysis. Choose the column you want to filter on. In this case, we choose AccountName, then click the option button (three vertical dots) to the right of the filter name and click Edit.

      • In the filter editor, choose Custom Filter for Filter Type, Equals for Filter Condition, check the Use Parameters option and answer Yes to the verification popup, and select a parameter from the parameter list dropdown. Then click the APPLY button at the bottom of the editor.
      • You now have an analysis with a filter on the AccountName parameter. In this example, the dashboard is created so that when the parameter is Manager, it will display all five rows. If the parameter is one of the five states, the dashboard will only display that state’s value. And if the parameter is anything else, the dashboard will be blank. Please refer to this QuickSight documentation on how to implement this logic.
      • You can now publish the analysis to be a dashboard.
  2. Save dashboard ID. In QuickSight dashboard view screen, the string after /dashboards/ in the URL is the dashboard ID. Here, it is ea94a8a8-9e87-46c7-ae42-de214fba770a.
  3. Enable all account access.
    1. From the dashboard sharing menu on the top right corner, choose Share dashboard.
    2. In the bottom left corner, enable Enable everyone in this account.
  4. Enable QuickSight IAM.
    1. Choose Manage QuickSight under Profile menu:
    2. In the QuickSight management page, choose Account Settings, then check the Enable IAM user access requests to this account option.
  5. Still in the QuickSight management page, go to Domains and Embedding. Enable force.com/visualforce.com/salesforce.com domains in QuickSight with subdomain included.

Credentials and user pricing

QuickSight offers user- or session-based consumption pricing. Because of this, you can perform just in-time provisioning of QuickSight users.

QuickSight billing charges are based on the number of users and sessions, and you only incur charges when dashboards are viewed. In this solution, we use multiple user entities to perform just-in-time provisioning of QuickSight users when a Salesforce user navigates to a page that features an embedded dashboard:

  • AWS admin user – The initial user you sign in as to create all subsequent AWS and the IAM roles to create role-backed QuickSight users
  • AWS API user – The credentials are created in IAM and have its API keys embedded in the Salesforce instance
  • An IAM role – Used to create a role-backed user (in QuickSight) for all users created on behalf of the Salesforce.com application
  • QuickSight users – Created each time a unique Salesforce.com user visits a QuickSight dashboard.
  • Salesforce.com users – Users with logins to Salesforce.com that are visiting pages with embedded dashboards

In this solution, Salesforce.com users don’t need to manage an additional set of credentials for QuickSight. A QuickSight user is automatically created for them, and their access is granted automatically through the embedded dashboard link that Salesforce retrieves from the QuickSight API.

Part 1: Set up AWS

To get started, we use the AWS admin user to configure the AWS API user, whose credentials we embed in the Salesforce instance where we intend to display the embedded dashboards. First, we set up the required permissions for the AWS API user to generate an embed URL and test the required steps in the AWS CLI before we perform the configuration in Salesforce.

Create an IAM user with QuickSight permissions

In this section, we create an IAM user with QuickSight permissions to generate the embed URL and perform just-in-time registration of the new Salesforce users.

Create an IAM policy for QuickSight access

Create an IAM policy in the IAM console with the following permissions, which will allow generating a QuickSight embed URL.

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"quicksight:GetSessionEmbedUrl",
"quicksight:GetAuthCode",
"quicksight:GenerateEmbedUrlForRegisteredUser",
"quicksight:RegisterUser"
],
"Resource": "*"
}

The policy above shows permissions to generate embed url for dashboard or visual or the entire QuickSight console depending on the use case. RegisterUser is required to create users in QuickSight on the fly, if the Salesforce user is not yet activated in QuickSight.

Create an IAM role for accessing QuickSight

Next, we create an IAM role via the AWS CLI that we use to create role-backed users. We will use an AWS CLI script to create this role:

echo '{"Version": "2012-10-17","Statement": [{"Effect": "Allow","Principal": {"AWS": "<ACCOUNT ID>"},"Action": "sts:AssumeRole"}]}' > policy.json
aws iam create-role --role-name QuickSightEmbed --assume-role-policy-document file://policy.json

After this role is successfully created, in the IAM console, attach the policy you created in the previous section (“Create an IAM policy for QuickSight access”) to this role.

Create an IAM policy for assuming the IAM role

Create an IAM policy in the IAM console with the following permissions, which will allow external parties to assume the role for QuickSight access.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "sts:AssumeRole",
            "Resource": "arn:aws:iam::<ACCOUNT ID>:role/QuickSightEmbed"
        }
    ]
}

Create the IAM user for accessing QuickSight from SalesForce

Create the IAM user in the IAM console for Salesforce to use programmatically when interacting with AWS services. When creating the user, we create an access key and make note of the ID and secret to use in a later step when setting up Salesforce. Assign both of the policies you created above to the user.

Create a role-backed User in QuickSight

QuickSight manages a separate table of users than IAM. Although you can invite IAM users in your AWS account to use QuickSight, for this post, we create individual credentials for each unique Salesforce user to access QuickSight dashboards. Any user you invite must have an email address.

A user must exist before you generate an embed URL. We use the sample code later in this post to have Salesforce automatically create a Quicksight user each time a Salesforce user visits a dashboard page. However, to validate the end-to-end flow before relying on an automated process, we need to manually create an IAM user to test that the process functions as expected.

For now, we create a QuickSight user via the following CLI command. Include the ARN and email address that you want to attach to your newly created user. Make sure this user has visibility or access to all the dashboards you wish to embed.

aws quicksight register-user --aws-account-id <ACCOUNT ID> --iam-arn "arn:aws:iam::1111111111:role/QuickSightEmbed" --user-role READER --session-name john.doe@example.com --email john.doe@example.com --identity-type IAM --namespace default 

Optionally, if you’re sharing dashboards to a specific group, you can run the following command to add your newly created user to a QuickSight group:

aws quicksight create-group-membership –member-name <insert-the-name-of-the-quicksight-user-you-previously-created> --group-name <insert-the-name-of-the-group> --aws-account-id <ACCOUNT ID> --namespace default

Retrieve the QuickSight dashboard URL

You can now run the get-dashboard-embed-url command to retrieve the QuickSight dashboard URL:

aws quicksight get-dashboard-embed-url \ 
--aws-account-id <ACCOUNT ID> \ 
--dashboard-id <DASHBOARD ID> \ 
--identity-type QUICKSIGHT
--user-arn arn:aws:quicksight:<your-aws-region>:<ACCOUNT NUMBER>:user/default/QuickSightEmbed/tom.smith@example.com

Test using the URL

You can test the URL you generated in the previous step or host a front-end static page to initialize and load the dashboard. Documentation for the QuickSight Embedding SDK is available on the GitHub repo.

<!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <title>My Dashboard</title>
        <!-- Rehost the quicksight embedding library below on your web server -->
        <script type="text/javascript" src="https://unpkg.com/amazon-quicksight-embedding-sdk@1.0.11/dist/quicksight-embedding-js-sdk.min.js"></script>
        <script type="text/javascript">
            function embedDashboard() {
                var containerDiv = document.getElementById("dashboardContainer");
                var params = {
                    url: "EMBED URL HERE FROM PREVIOUS STEP",
                    container: containerDiv,
                    parameters: {
                    //Include any parameters required for pre-filtering the dashboard
                    },
                    height: "700px",
                    width: "1000px"
                };
                var dashboard = QuickSightEmbedding.embedDashboard(params);
                dashboard.on('error', function(err) {console.log('dashboard error:', err)});
                dashboard.on('load', function() {});
            }
        </script>
    </head>
    <body onload="embedDashboard()">
        <div id="dashboardContainer"></div>
    </body>
    </html>

Part 2: Set up Salesforce

Now that we have validated the steps to generate and embed a QuickSight dashboard function, we configure the process to run in Salesforce when a user visits a page where the QuickSight dashboard has been embedded.

Create named credentials to store your AWS user credentials

Named credentials is a function of Salesforce.com and isn’t supported by AWS. Salesforce.com has developed functionality to store credentials and format callouts to the AWS API using the AWS Signature v4 signing process.

To create your named credentials, complete the following steps:

  1. Select AWS Signature Version 4 as the Authentication Protocol under Identity Type = Named Principal.
  2. Enter the AWS key ID and secret for the user you created earlier.
  3. Enter the appropriate Region and service name.

For more information about the endpoint you’re integrating with, see AWS service endpoints.

  1. Take note of the name of your named credential; you reference it in the code in your apex class in a later step.

Create a controller for your Visualforce page

In your Salesforce instance, navigate to Apex Classes in the Setup menu and create a new class.

In your class, invoke the Amazon QuickSight API, calling the RegisterUser and GetEmbedDashboardUrl action. Replace the value of the following variables listed in the beginning with your environmental values.

  • yourAwsAccountNumber
  • yourQuickSightRoleNameAUTHOR (optional)
  • yourQuickSightRoleNameREADER
  • yourNamedCredentialName
  • yourQuickSightRegion
  • yourQuickSightDashboardId
public class AC_QS_getEmbedUrl {

    // replace these values with values applicable to your own environment
    private static final String yourAwsAccountNumber = 'ACCOUNT ID';
    private static final String yourQuickSightRoleNameAUTHOR = 'QSS-AUTHOR';
    private static final String yourQuickSightRoleNameREADER = 'QSS-READER';
    private static final String yourNamedCredentialName = 'Your named credential name';
    private static final String yourQuickSightRegion = 'us-east-1';
    private static final String yourQuickSightDashboardId = 'default dashboard id that will be used in classic visualforce page';
    private static final String yourNamespace = 'default';


    // embedUrl of the QuickSight dashboard.  This value is specific to the Salesforce user attempting to display the dashboard
    // and is generated as part of the class consthttps://amazon405-dev-ed.develop.my.salesforce.com/_ui/common/apex/debug/ApexCSIPage#ructor - as soon as it is elaborated by VisualForce.
    public String embedUrl {get; set;}
    // AWS ARN value for the QuickSight user being created.  A new QuickSight user is generated on the fly for each Salesforce user that
    // attempts to display the dashboard.
    public String userArn {get; set;}
    // This is the Contact record associated with the record page hosting the VisualForce page.  For our purposes, this can be ignored, but
    // it could be used to pass context to the QuickSight dashboard as a parameter - for example, to show a dashboard specific to this Contact record.
    public final Contact contact;

    // Constructor
    public AC_QS_getEmbedUrl(ApexPages.StandardController stdController) {
        getDashboardEmbedUrl();
    }    
    
    @AuraEnabled
    public static String generateAuthorEmbedUrl(String pInitialPath){
        //First, we will get the active Salesforce user's email address in order to create a QuickSight user for each unique Salesforce User that will be viewing the dashboard. 
        //This provisioning will be done just in time, and will only create one Quicksight user for each unique Salesforce User ID.
        String userName = UserInfo.getUserName();
        User activeUser = [Select Email From User where Username = : userName limit 1];
        String userEmail = activeUser.Email;
        String embedUrl='';
        System.debug('Inside generateAuthorEmbedUrl function for Initial Path' + pInitialPath );
        // Attempt to register the new QuickSight user
        HttpRequest registerUser = new HttpRequest();
        String registerRequestBody = '{' + 
                                        '"IamArn"       : "arn:aws:iam::' + ACCOUNTNUMBER + ':role/' + yourQuickSightRoleNameAUTHOR + '",' +
                                        '"UserRole"     : "AUTHOR",' +
                                        '"SessionName"  : "' + userEmail + '",' + 
                                        '"Email"        : "' + userEmail + '",' + 
                          //'"Namespace"        : "' + yourNamespace + '",' + 
                                        '"IdentityType" : "IAM"' +
                                    '}';
        System.debug('registerRequestBody: ' + registerRequestBody);
        registerUser.setEndpoint('callout:' + yourNamedCredentialName + '/accounts/' + ACCOUNT NUMBER + '/namespaces/'+ yourNamespace +'/users');
        registerUser.setMethod('POST');
        registerUser.setHeader('Content-Type', 'application/json;charset=UTF-8');
        registerUser.setbody(registerRequestBody);
        Http http = new Http();
        HTTPResponse registrationResult = http.send(registerUser);
        System.debug('registrationResult: ' + registrationResult);
        if ((registrationResult.getStatusCode() != 201) && (registrationResult.getStatusCode() != 200) && (registrationResult.getStatusCode() != 409) ) {
            ApexPages.Message msg = new ApexPages.Message(ApexPages.Severity.Error, registrationResult.getBody());
            ApexPages.addMessage(msg);
            return 'error';
        }

        AC_QS_getEmbedUrl regResponseData = (AC_QS_getEmbedUrl) System.JSON.deserialize(registrationResult.getBody(), AC_QS_getEmbedUrl.class);
        if(registrationResult.getStatus() == 'Conflict' || registrationResult.getStatus() == 'Created' ){
            // Status of Conflict indicates this Salesforce user has viewed a dashboard in the past and a QuickSight user already exists.
            //In either case, if the Quicksight user already exists, or needs to be created, we will proceed to generate the dashboard for that user.
            String userArn = 'arn:aws:quicksight:' + yourQuickSightRegion + ':' + ACCOUNT NUMBER + ':user/'+ yourNamespace+'/' + yourQuickSightRoleNameAUTHOR + '/'+userEmail;

            // Attempt to generate the embedUrl for this user
            HttpRequest getEmbed = new HttpRequest();         
            //getEmbed.setEndpoint('callout:' + yourNamedCredentialName + '/accounts/' + ACCOUNT NUMBER + '/dashboards/' + yourQuickSightDashboardId + '/embed-url?creds-type=QUICKSIGHT&user-arn='+EncodingUtil.urlEncode(userArn, 'UTF-8'));
            String embedUrlRequestBody ='{' +                                         
                                           '"ExperienceConfiguration": { '+
                                              '"QuickSightConsole": {' + 
                                                 '"InitialPath":"' +  pInitialPath + '"'+
                                              '}'+
                                           '},'+
                                           '"SessionLifetimeInMinutes": 60,'+
                                           '"UserArn":"' + userArn + '"' +
                                        '}';
            
            getEmbed.setEndpoint('callout:' + yourNamedCredentialName + '/accounts/' + ACCOUNT NUMBER + '/embed-url/registered-user');
            getEmbed.setMethod('POST');
            getEmbed.setHeader('Content-Type', 'application/json;charset=UTF-8');
            getEmbed.setbody(embedUrlRequestBody);
            System.debug(getEmbed.getBody());
            HTTPResponse res = http.send(getEmbed);
      
            System.debug(res.getStatusCode());
            System.debug(res.getBody());
            if (res.getStatusCode() > 200) {
                ApexPages.Message msg = new ApexPages.Message(ApexPages.Severity.Error, res.getBody());
                ApexPages.addMessage(msg);
            }
       
            AC_QS_getEmbedUrl embedResponseData  = (AC_QS_getEmbedUrl) System.JSON.deserialize(res.getbody(), AC_QS_getEmbedUrl.class);
            if(embedResponseData != null && embedResponseData.EmbedUrl != null){
                embedUrl = (embedResponseData.EmbedUrl);
           }
        }
        return embedUrl;
    }
    
      public void getDashboardEmbedUrl(){
    
        //First, we will get the active Salesforce user's email address in order to create a QuickSight user for each unique Salesforce User that will be viewing the dashboard. 
        //This provisioning will be done just in time, and will only create one Quicksight user for each unique Salesforce User ID.
        String userName = UserInfo.getUserName();
        User activeUser = [Select Email From User where Username = : userName limit 1];
        String userEmail = activeUser.Email;
        //String embedUrl='';
        
        // Attempt to register the new QuickSight user
        HttpRequest registerUser = new HttpRequest();
        String registerRequestBody = '{' + 
                                        '"IamArn"       : "arn:aws:iam::' + ACCOUNT NUMBER + ':role/' + yourQuickSightRoleNameREADER + '",' +
                                        '"UserRole"     : "READER",' +
                                        '"SessionName"  : "' + userEmail + '",' + 
                                        '"Email"        : "' + userEmail + '",' + 
                          //'"Namespace"        : "' + yourNamespace + '",' + 
                                        '"IdentityType" : "IAM"' +
                                    '}';
        System.debug('registerRequestBody: ' + registerRequestBody);
        registerUser.setEndpoint('callout:' + yourNamedCredentialName + '/accounts/' + ACCOUNT NUMBER + '/namespaces/'+ yourNamespace +'/users');
        registerUser.setMethod('POST');
        registerUser.setHeader('Content-Type', 'application/json;charset=UTF-8');
        registerUser.setbody(registerRequestBody);
        Http http = new Http();
        HTTPResponse registrationResult = http.send(registerUser);
        System.debug('registrationResult: ' + registrationResult);
        if ((registrationResult.getStatusCode() != 201) && (registrationResult.getStatusCode() != 200) && (registrationResult.getStatusCode() != 409) ) {
            ApexPages.Message msg = new ApexPages.Message(ApexPages.Severity.Error, registrationResult.getBody());
            ApexPages.addMessage(msg);
            //return 'error';
        }

        AC_QS_getEmbedUrl regResponseData = (AC_QS_getEmbedUrl) System.JSON.deserialize(registrationResult.getBody(), AC_QS_getEmbedUrl.class);
        if(registrationResult.getStatus() == 'Conflict' || registrationResult.getStatus() == 'Created' ){
            // Status of Conflict indicates this Salesforce user has viewed a dashboard in the past and a QuickSight user already exists.
            //In either case, if the Quicksight user already exists, or needs to be created, we will proceed to generate the dashboard for that user.
            String userArn = 'arn:aws:quicksight:' + yourQuickSightRegion + ':' + ACCOUNT NUMBER + ':user/'+ yourNamespace+'/' + yourQuickSightRoleNameREADER + '/'+userEmail;

            // Attempt to generate the embedUrl for this user
            HttpRequest getEmbed = new HttpRequest();         
            //getEmbed.setEndpoint('callout:' + yourNamedCredentialName + '/accounts/' + ACCOUNT NUMBER + '/dashboards/' + yourQuickSightDashboardId + '/embed-url?creds-type=QUICKSIGHT&user-arn='+EncodingUtil.urlEncode(userArn, 'UTF-8'));
            String embedUrlRequestBody ='{' +                                         
                                           '"ExperienceConfiguration": { '+
                                              '"Dashboard": {' + 
                                                 '"InitialDashboardId":"' +  yourQuickSightDashboardId + '"'+
                                              '}'+
                                           '},'+
                                           '"SessionLifetimeInMinutes": 60,'+
                                           '"UserArn":"' + userArn + '"' +
                                        '}';
            
            getEmbed.setEndpoint('callout:' + yourNamedCredentialName + '/accounts/' + ACCOUNT NUMBER + '/embed-url/registered-user');
            getEmbed.setMethod('POST');
            getEmbed.setHeader('Content-Type', 'application/json;charset=UTF-8');
            getEmbed.setbody(embedUrlRequestBody);
            System.debug(getEmbed.getBody());
            HTTPResponse res = http.send(getEmbed);

            
            System.debug(res.getStatusCode());
            System.debug(res.getBody());
            if (res.getStatusCode() > 200) {
                ApexPages.Message msg = new ApexPages.Message(ApexPages.Severity.Error, res.getBody());
                ApexPages.addMessage(msg);
            }
       
            AC_QS_getEmbedUrl embedResponseData  = (AC_QS_getEmbedUrl) System.JSON.deserialize(res.getbody(), AC_QS_getEmbedUrl.class);
            if(embedResponseData != null && embedResponseData.EmbedUrl != null){
                embedUrl = (embedResponseData.EmbedUrl);
           }
        }
        //return embedUrl;
    }

    @AuraEnabled
    public static String generateDashboardEmbedUrl(String pDashboardId){
    
        //First, we will get the active Salesforce user's email address in order to create a QuickSight user for each unique Salesforce User that will be viewing the dashboard. 
        //This provisioning will be done just in time, and will only create one Quicksight user for each unique Salesforce User ID.
        String userName = UserInfo.getUserName();
        User activeUser = [Select Email From User where Username = : userName limit 1];
        String userEmail = activeUser.Email;
        String embedUrl='';
        
        // Attempt to register the new QuickSight user
        HttpRequest registerUser = new HttpRequest();
        String registerRequestBody = '{' + 
                                        '"IamArn"       : "arn:aws:iam::' + ACCOUNT NUMBER + ':role/' + yourQuickSightRoleNameREADER + '",' +
                                        '"UserRole"     : "READER",' +
                                        '"SessionName"  : "' + userEmail + '",' + 
                                        '"Email"        : "' + userEmail + '",' + 
                          //'"Namespace"        : "' + yourNamespace + '",' + 
                                        '"IdentityType" : "IAM"' +
                                    '}';
        System.debug('registerRequestBody: ' + registerRequestBody);
        registerUser.setEndpoint('callout:' + yourNamedCredentialName + '/accounts/' + ACCOUNT NUMBER + '/namespaces/'+ yourNamespace +'/users');
        registerUser.setMethod('POST');
        registerUser.setHeader('Content-Type', 'application/json;charset=UTF-8');
        registerUser.setbody(registerRequestBody);
        Http http = new Http();
        HTTPResponse registrationResult = http.send(registerUser);
        System.debug('registrationResult: ' + registrationResult);
        if ((registrationResult.getStatusCode() != 201) && (registrationResult.getStatusCode() != 200) && (registrationResult.getStatusCode() != 409) ) {
            ApexPages.Message msg = new ApexPages.Message(ApexPages.Severity.Error, registrationResult.getBody());
            ApexPages.addMessage(msg);
            return 'error';
        }

        AC_QS_getEmbedUrl regResponseData = (AC_QS_getEmbedUrl) System.JSON.deserialize(registrationResult.getBody(), AC_QS_getEmbedUrl.class);
        if(registrationResult.getStatus() == 'Conflict' || registrationResult.getStatus() == 'Created' ){
            // Status of Conflict indicates this Salesforce user has viewed a dashboard in the past and a QuickSight user already exists.
            //In either case, if the Quicksight user already exists, or needs to be created, we will proceed to generate the dashboard for that user.
            String userArn = 'arn:aws:quicksight:' + yourQuickSightRegion + ':' + ACCOUNT NUMBER + ':user/'+ yourNamespace+'/' + yourQuickSightRoleNameREADER + '/'+userEmail;

            // Attempt to generate the embedUrl for this user
            HttpRequest getEmbed = new HttpRequest();         
            //getEmbed.setEndpoint('callout:' + yourNamedCredentialName + '/accounts/' + ACCOUNT NUMBER + '/dashboards/' + yourQuickSightDashboardId + '/embed-url?creds-type=QUICKSIGHT&user-arn='+EncodingUtil.urlEncode(userArn, 'UTF-8'));
            String embedUrlRequestBody ='{' +                                         
                                           '"ExperienceConfiguration": { '+
                                              '"Dashboard": {' + 
                                                 '"InitialDashboardId":"' +  pDashboardId + '"'+
                                              '}'+
                                           '},'+
                                           '"SessionLifetimeInMinutes": 60,'+
                                           '"UserArn":"' + userArn + '"' +
                                        '}';
            
            getEmbed.setEndpoint('callout:' + yourNamedCredentialName + '/accounts/' + ACCOUNT NUMBER + '/embed-url/registered-user');
            getEmbed.setMethod('POST');
            getEmbed.setHeader('Content-Type', 'application/json;charset=UTF-8');
            getEmbed.setbody(embedUrlRequestBody);
            System.debug(getEmbed.getBody());
            HTTPResponse res = http.send(getEmbed);            
            System.debug(res.getStatusCode());
            System.debug(res.getBody());
            if (res.getStatusCode() > 200) {
                ApexPages.Message msg = new ApexPages.Message(ApexPages.Severity.Error, res.getBody());
                ApexPages.addMessage(msg);
            }
       
            AC_QS_getEmbedUrl embedResponseData  = (AC_QS_getEmbedUrl) System.JSON.deserialize(res.getbody(), AC_QS_getEmbedUrl.class);
            if(embedResponseData != null && embedResponseData.EmbedUrl != null){
                embedUrl = (embedResponseData.EmbedUrl);
           }
        }
        return embedUrl;
    }

        @AuraEnabled
    public static String generateVisualEmbedUrl(String pDashboardId, String pSheetId, String pVisualId){
        //First, we will get the active Salesforce user's email address in order to create a QuickSight user for each unique Salesforce User that will be viewing the dashboard. 
        //This provisioning will be done just in time, and will only create one Quicksight user for each unique Salesforce User ID.
        String userName = UserInfo.getUserName();
        User activeUser = [Select Email From User where Username = : userName limit 1];
        String userEmail = activeUser.Email;
        String embedUrl='';
        System.debug('Inside generateAuthorEmbedUrl function for Dashboard Id' + pDashboardId );
        // Attempt to register the new QuickSight user
        HttpRequest registerUser = new HttpRequest();
        String registerRequestBody = '{' + 
                                        '"IamArn"       : "arn:aws:iam::' + ACCOUNT NUMBER + ':role/' + yourQuickSightRoleNameREADER + '",' +
                                        '"UserRole"     : "READER",' +
                                        '"SessionName"  : "' + userEmail + '",' + 
                                        '"Email"        : "' + userEmail + '",' + 
                          //'"Namespace"        : "' + yourNamespace + '",' + 
                                        '"IdentityType" : "IAM"' +
                                    '}';
        System.debug('registerRequestBody: ' + registerRequestBody);
        registerUser.setEndpoint('callout:' + yourNamedCredentialName + '/accounts/' + ACCOUNT NUMBER + '/namespaces/'+ yourNamespace +'/users');
        registerUser.setMethod('POST');
        registerUser.setHeader('Content-Type', 'application/json;charset=UTF-8');
        registerUser.setbody(registerRequestBody);
        Http http = new Http();
        HTTPResponse registrationResult = http.send(registerUser);
        System.debug('registrationResult: ' + registrationResult);
        if ((registrationResult.getStatusCode() != 201) && (registrationResult.getStatusCode() != 200) && (registrationResult.getStatusCode() != 409) ) {
            ApexPages.Message msg = new ApexPages.Message(ApexPages.Severity.Error, registrationResult.getBody());
            ApexPages.addMessage(msg);
            return 'error';
        }

        AC_QS_getEmbedUrl regResponseData = (AC_QS_getEmbedUrl) System.JSON.deserialize(registrationResult.getBody(), AC_QS_getEmbedUrl.class);
        if(registrationResult.getStatus() == 'Conflict' || registrationResult.getStatus() == 'Created' ){
            // Status of Conflict indicates this Salesforce user has viewed a dashboard in the past and a QuickSight user already exists.
            //In either case, if the Quicksight user already exists, or needs to be created, we will proceed to generate the dashboard for that user.
            String userArn = 'arn:aws:quicksight:' + yourQuickSightRegion + ':' + ACCOUNT NUMBER + ':user/'+ yourNamespace+'/' + yourQuickSightRoleNameREADER + '/'+userEmail;

            // Attempt to generate the embedUrl for this user
            HttpRequest getEmbed = new HttpRequest();         
            //getEmbed.setEndpoint('callout:' + yourNamedCredentialName + '/accounts/' + ACCOUNT NUMBER + '/dashboards/' + yourQuickSightDashboardId + '/embed-url?creds-type=QUICKSIGHT&user-arn='+EncodingUtil.urlEncode(userArn, 'UTF-8'));
            String embedUrlRequestBody ='{' +                                         
                                           '"ExperienceConfiguration": { '+
                                              '"DashboardVisual": {' + 
                                  '"InitialDashboardVisualId":{' +   
                                      '"DashboardId":"' + pDashboardId + '",'+
                                      '"SheetId":"' + pSheetId + '",'+
                              '"VisualId":"' + pVisualId + '"'+   
                                  
                                  '}'+
                                              '}'+
                                           '},'+
                                           '"SessionLifetimeInMinutes": 60,'+
                                           '"UserArn":"' + userArn + '"' +
                                        '}';
            
            getEmbed.setEndpoint('callout:' + yourNamedCredentialName + '/accounts/' + ACCOUNT NUMBER + '/embed-url/registered-user');
            getEmbed.setMethod('POST');
            getEmbed.setHeader('Content-Type', 'application/json;charset=UTF-8');
            getEmbed.setbody(embedUrlRequestBody);
            System.debug(getEmbed.getBody());
            HTTPResponse res = http.send(getEmbed);
            
            System.debug(res.getStatusCode());
            System.debug(res.getBody());
            if (res.getStatusCode() > 200) {
                ApexPages.Message msg = new ApexPages.Message(ApexPages.Severity.Error, res.getBody());
                ApexPages.addMessage(msg);
            }
       
            AC_QS_getEmbedUrl embedResponseData  = (AC_QS_getEmbedUrl) System.JSON.deserialize(res.getbody(), AC_QS_getEmbedUrl.class);
            if(embedResponseData != null && embedResponseData.EmbedUrl != null){
                embedUrl = (embedResponseData.EmbedUrl);
           }
        }
        return embedUrl;
    }
    
    @AuraEnabled
    public static String getUserDivision(){
        String userName = UserInfo.getUserName();
        User activeUser = [Select Division From User where Username = : userName limit 1];
        String userDivision = activeUser.Division;
        return userDivision;
    }
    
    
}

Create a Visualforce page

You now create a Visualforce page with the AC_QS_getEmbedUrl class as the page controller.

  1. In your Salesforce instance, navigate to Visualforce Pages and create a new page.
  2. Reference the controller you created earlier. This calls the QuickSight API server side before loading the Visualforce page.
  3. On the client side, the included QuickSight embedding JavaScript file initializes and injects the QuickSight dashboard that was generated by the page controller.

If desired, you can update the params variable in the getDashboardEmbedUrl function to resize the dashboard based on the size of the Salesforce page, pass filter parameters, hide or show the navigation buttons, perform event-triggered actions, or allow printing of the dashboard. For more information about the supported parameters, Amazon QuickSight Embedded Analytics.

The following sample code initiates the embedded dashboard on the front-end page:

<apex:page standardController="Contact" extensions="AC_QS_getEmbedUrl" showHeader="false" sidebar="false">  <h1>Quicksight Salesforce Embed Demo</h1><br/>

  <script src="https://unpkg.com/amazon-quicksight-embedding-sdk@1.20.0/dist/quicksight-embedding-js-sdk.min.js"></script>
  <script type="text/javascript">
            function embedDashboard() {
                var containerDiv = document.getElementById("dashboardContainer");
                var params = {
                    url: "<apex:outputText value="{!embedUrl}"/>",
                    container: containerDiv,
                    parameters: { 
                      AccountNameParameter: "{!$User.Division}",
                      ContactId: "{!Contact}"
                    },
                    height: "700px",
                    width: "1000px" //Resize according to your page layout
                };
                var dashboard = QuickSightEmbedding.embedDashboard(params);
                dashboard.on('error', function(err) {console.log('dashboard error:', err)});
                dashboard.on('load', function() {});
            }
  </script>
  <body>
    <div id="dashboardContainer"></div>
  </body>
  <script type="text/javascript">
    embedDashboard();
  </script>
</apex:page>

Note the following code in line 11. It is passing the {!$User.Division} value from SalesForce to the AccountNameParameter parameter of the QuickSight dashboard.

AccountNameParameter: "{!$User.Division}",

You can pick any desired SalesForce value to pass to QuickSight, but the parameter key (which is AccountNameParameter in this example) must be the same as the parameter name in the QuickSight dashboard, otherwise QuickSight will use the default value of the parameter.

On the VisualForce page, click on the Preview button:

You should see the dashboard showing up at this point. Note down the URL of the preview page.

Create a Lightning component

For Lightning component to call APEX class methods, we have created static methods in the APEX class and have them AuraEnabled. The methods are:

generateDashboardEmbedUrl
generateAuthorEmbedUrl
generateVisualEmbedUrl

Now we will embed the dashboard by calling generateDashboardEmbedUrl. You can follow the same process to implement the other visual or session (QuickSight console) embedding as well. The Lightning component consists of two sections

  1. HTML code component that creates the necessary html elements to render dashboard.
  2. Controller component that actually makes a call to the Apex class created above to fetch the url.

Following are the steps to create a Lightning component.

  1. Log into Salesforce as an admin or developer with access to setup page.
  2. Launch Developer console by clicking on the setup icon on the top right and clicking Developer console from the menu. The developer console will be launched.
  3. In the developer console, under file menu, Click File menu.
    1. Select New > Lightning Component.
    2. In the pop-up enter the name QuickSightDashboard.
  4. A new Lightning Component will be created and you will see several sections on the right as shown in the image below. Click Component and paste the following code.

    <aura:component controller="AC_QS_getEmbedUrl" implements="force:appHostable,flexipage:availableForAllPageTypes" > 
    <aura:attribute name="embedurl" type="String"/>
    <aura:attribute name="userDivision" type="String"/>
    <aura:handler name="init" value="{!this}" action="{!c.getEmbedUrl}"/>
    <iframe src="{!v.embedurl}" width="100%" height="800px;" frameBorder="0"/>
    </aura:component>
  5. Save the component.
  6. Click Controller on the right section and past the following code.

    ({
        getEmbedUrl : function(component, event, helper) {
            var action=component.get("c.generateDashboardEmbedUrl")
            action.setParams({
                              pDashboardId: "<Your Dashboard ID>"
                             });
            action.setCallback(this, function(response) {           
                var responseValue = response.getReturnValue(); 
                component.set("v.embedurl",responseValue + "#p.AccountNameParameter=" + component.get("v.userDivision") );
            });
            
            var getDivision=component.get("c.getUserDivision")
            getDivision.setCallback(this, function(response) {           
                var responseValue = response.getReturnValue(); 
                component.set("v.userDivision",responseValue);
    
                $A.enqueueAction(action);
            });
            // Enqueue Action
            $A.enqueueAction(getDivision);
            
        }
    })
  7. Save the controller.

Note that this post is not a comprehensive guide for creating a Lightning component in a production environment. Please follow this Salesforce’s documentation for creating a Lightning component. For more information, see What Is the Lightning Component Framework? and Use Visualforce in Lightning Experience.

Add Lightning component to a Contacts page

From Setup, find Object Manager. Choose Contact > Lightning Record Page. Choose the page you want to modify and click Edit.

Your newly created Lightning component is at the bottom of the Components panel that usually is on the left side of the page. You can drag and drop it onto anywhere you like.

In the display above, we are seeing the whole 5 rows of the dashboard because we set the SalesForce user’s Division to be Manager, which, based on the QuickSight dashboard’s definition, will display all rows. If we set the user Division to some other values, such as Texas, it should only display Texas. To do this, click on the account icon on the top right corner of the page, choose Settings right under the user name. Once you are in the user settings page, change the Division field to Texas.

Once we save the change and go back to Sales console and refresh the page, we will only see Texas data. That’s because the new value of user Division (which is Texas) is passed from SalesForce to QuickSight, and the dashboard used this parameter to filter the data.

Once you finish editing, you can save the page. The first time you save, you may run into a notice of activation:

Make sure the page is assigned as the Org Default, not the standard one. Check this link for further details: https://help.salesforce.com/s/articleView?id=000350524&type=1

You have now embedded your QuickSight dashboard within Salesforce!

Summary

In this post, we demonstrated how to embed QuickSight dashboards in the Salesforce CRM platform. We began by creating the required IAM user for Salesforce to invoke the QuickSight API. On the Salesforce platform, we set up the named credentials that are later used on the Visualforce page. We then configured the Salesforce apex class to register a QuickSight user and registered the QuickSight user to generate the dashboard URL.

Our goal with this blog post is to demonstrate how Quicksight dashboards can be embedded into various platforms such as Salesforce.  You can also embed Quicksight dashboards into your own applications, websites, and wikis without replicating any data between AWS and Salesforce. QuickSight is serverless and can automatically scale to tens of thousands of users without any infrastructure to manage or capacity to plan for. It is also the first BI service to offer pay-per-session pricing, where you only pay when your users access their dashboards or reports, making it cost-effective for large scale deployments.

We encourage you to explore this solution and customize it according to your use case. If you have any questions or suggestions, please leave a comment.


About the Authors

Vijul Patel is a Partner Solutions Architect based in Toronto, Canada. He is passionate about helping partners and customers leverage AWS to accelerate their cloud migration journey.

Nicholas Loek is a Partner Solutions Architect with AWS. In addition to helping AWS Partners leverage new services and design patterns, he is an enthusiast for experimenting and building new experiences in the areas of customer engagement, IoT, and serverless application architectures.

Jun Shan is a Senior Solutions Architect at AWS. He works with customers to leverage the power of AWS to release their potentials to the maximum degree, enabling them to be more efficient, secure, reliable, and optimized for cost. Jun also teaches SQL and Relational Database in several New York City colleges and is the author of “SQL for Data Analytics, 3rd Edition”.

Vetri Natarajan is a Specialist Solutions Architect for Amazon QuickSight. Vetri has 15 years of experience implementing enterprise Business Intelligence (BI) solutions and greenfield data products. Vetri specializes in integration of BI solutions with business applications and enable data-driven decisions.