Category: dynamics 365

Event Execution Pipeline in Dataverse/Dynamics 365

When the standard low-code options cannot handle your business logic in an expected manner, as a developer, you can use custom event handlers in Microsoft Dataverse. The Event Framework provides the capability to register custom code to be run in response to specific events.

The custom code options available are Plugins, Azure integrations, virtual table data providers, and Webhooks, etc… When configured correctly using the plugin registration tool, these custom code extensions will respond to events that are provided by the event framework.

When an event occurs a message is sent to the organization Web service to process the event. The message contains information about the event, such as details of the table where the event occurred, dataverse organization details, details of the user who triggered the event, etc… There is a specific message for each event such as create, update, retrieve, retrieve multiple, associate, disassociate, delete, etc… Each message is processed in a series of 4 stages called the event execution pipeline and your custom code extension can be registered at any of these stages based on your business need. See the below table for the details about each stage.

Stage NameDescription
PreValidationThis stage will occur before the main system operation. This provides an opportunity to include logic to cancel the operation before the database transaction.
This stage occurs before any security checks are performed to verify that the calling or logged-on user has the correct permission to perform the intended operation.
PreOperationOccurs before the main system operation and within the database transaction. If you want to change any values for an entity included in the message, you should do it here.

Avoid canceling an operation here. Canceling will trigger a rollback of the transaction and have a significant performance impact.
MainOperationFor internal use only except for Custom API and Custom virtual table data providers.
More information:
Create and use Custom APIs
Custom Virtual table data providers
PostOperationOccurs after the main system operation and within the database transaction. Use this stage to modify any properties of the message before it is returned to the caller.

Avoid applying changes to an entity included in the message because this will trigger a new Update event.

Within the PostOperation stage, you can register steps to use the asynchronous execution mode. These steps will run outside of the database transaction using the asynchronous service. More information Asynchronous service.

Hope this helps.

Authenticate Dynamics 365 in Azure Functions using MSAL

As you all know, ADAL is deprecated and will be unsupported by June 30th 2022, Microsoft recommends to use the MSAL (Microsoft Authentication Library). MS has also advised to migrate your existing applications to MSAL.

MSAL makes it easy for developers to add identity capabilities to their applications. With just a few lines of code, developers can authenticate users and applications, as well as acquire tokens to access resources. MSAL also enables developers to integrate with the latest capabilities in our platform—like passwordless and Conditional Access.

Microsoft Build 2020

Read the official FAQ for migrating your applications to MSAL here

MSAL is now the recommended official authentication library for use with the Microsoft identity platform.

Photo by Ilargian Faus on Pexels.com

In my pervious post Authenticate Dynamics 365 in Azure Functions Version 3 , I used ADAL for authentication. The below code authenticates D365 using MSAL, and use WEBAPI to fetch the contact record with the email address passed in the request body.

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Microsoft.Identity.Client;
using System.Net.Http.Headers;
using System.Net.Http;

namespace AzureFunctionMSAL
{
    public static class ConnectMSAL
    {
       

        [FunctionName("MSALFunction")]

        // change get or post based on your scenario

        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");

            string contactEmail = req.Query["eamil"];
            string contacts = string.Empty;
            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            dynamic data = JsonConvert.DeserializeObject(requestBody);
            contactEmail = contactEmail ?? data?.email;


            // MSAL Authentication
            string clientId = "*********583";
            string secret = "zDC******";
            string[] scope = new string[] { "https://******.crm.dynamics.com/.default" };
            string webAPI = " https://***.crm.dynamics.com/api/data/v9.1/";
            string authority = "https://login.microsoftonline.com/69e9641e-4be0-4f4c-9ae4-06fdc1160c34";

            var clientApp = ConfidentialClientApplicationBuilder.Create(clientId: clientId)
            .WithClientSecret(clientSecret: secret)
            .WithAuthority(new Uri(authority))
            .Build();

            try
            {
                AuthenticationResult authResult = await clientApp.AcquireTokenForClient(scope).ExecuteAsync();

    // WEB API
                var httpClient = new HttpClient();
                httpClient.DefaultRequestHeaders.Add("OData-MaxVersion", "4.0");
                httpClient.DefaultRequestHeaders.Add("OData-Version", "4.0");
                httpClient.Timeout = new TimeSpan(0, 2, 0);
                httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
                httpClient.BaseAddress = new Uri(webAPI);
                var queryOptions = "contacts?$select=fullname,mobilephone&$filter=emailaddress1 eq '" + contactEmail + "'";
                var response =  httpClient.GetAsync(queryOptions).Result;
                
                if (response.IsSuccessStatusCode)
                {
                    var contactsresult = response.Content.ReadAsStringAsync().Result;
                    contacts = contactsresult;
                    log.LogInformation("webapi executed");
               
                }

            }
            catch (Exception ex)
            {
                // log error 
                string errorMessage = ex.Message;
                log.LogError(errorMessage);
                //response
                return new BadRequestObjectResult(JsonConvert.SerializeObject("an error occured while retriving contacts:"));
            }



            //
            string responseMessage = "WEB API Response: " + contacts;

            return new OkObjectResult(responseMessage);
        }
    }
}

Test your function from post man.

Request
Response

Please note, the response in the code is not well formatted, you may format the response as a proper json object based on your business need. You might also need to add required exception handling in the code.

A short explanation on the code.

Hope this helps.

Use Dynamics 365 WEB API in Azure Functions Version 3

In the previous post, we saw how to authenticate Dynamics 365 in Azure Functions runtime version 3 (.NET Core). Now let’s see how to use Dynamics 365 WEB API after acquiring the bearer token.

The following sample code accepts email as input in the request body, and uses Web api to return the contacts records with the passed email.

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System.Net.Http;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System.Net;
using System.Net.Http.Headers;

namespace SampleFunctionApp
{
   

    public static class D365
    {
        [FunctionName("ConnecttoD365")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");

            string contactEmail = req.Query["email"];
            string responseMessage = string.Empty;
            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            dynamic data = JsonConvert.DeserializeObject(requestBody);
            contactEmail = contactEmail ?? data?.email;
            #region Auth Code
            //
            string cloud = "https://login.microsoftonline.com";

            //This is the Domain!

            string tenantId = "*******";

            string authority = $"{cloud}/{tenantId}";

            // ApplicationID in the new UI

            string clientId = "************";

            //Created from scratch in Keys

            string clientsecret = "*************";

            ClientCredential clientcred = new ClientCredential(clientId, clientsecret);

            // Application ID of the Resource (could also be the Resource URI)

            string resource = "https://*****.crm.dynamics.com/";

            AuthenticationContext ac = new AuthenticationContext(authority);
            AuthenticationResult result = null;
            var bearerToken = string.Empty;
            string ErrorMessege = string.Empty;
            try

            {
                //already having token
                result = await ac.AcquireTokenSilentAsync(resource, clientId);
                if (result != null)
                {
                    bearerToken = result.AccessToken;
                    log.LogInformation("Token Acquired:" );
                }
            }

            catch (AdalException adalException)

            {//Acquire token
                if (adalException.ErrorCode == AdalError.FailedToAcquireTokenSilently

                || adalException.ErrorCode == AdalError.InteractionRequired)

                {

                    result = await ac.AcquireTokenAsync(resource, clientcred);
                    if (result != null)
                    {
                        bearerToken = result.AccessToken;
                        log.LogInformation("Token Acquired");

                    }
                }
                else
                {
                    log.LogWarning("Failed to acquire Bearer Token :-" + adalException.Message);
                    var AdalException = new { adalexception = "Failed to acquire Bearer Token :-" + adalException.Message };
                    return new BadRequestObjectResult(JsonConvert.SerializeObject(AdalException));
                    throw adalException;

                }

            }

            #endregion

            #region getContact
           


            string outputString = string.Empty;
            //Next use a HttpClient object to connect to specified CRM Web service.
            var httpClient = new HttpClient();
            //Define the Web API base address, the max period of execute time, the 
            // default OData version, and the default response payload format.
            httpClient.BaseAddress = new Uri("https://*********.crm.dynamics.com/api/data/v9.1/");
            httpClient.Timeout = new TimeSpan(0, 2, 0);
            httpClient.DefaultRequestHeaders.Add("OData-MaxVersion", "4.0");
            httpClient.DefaultRequestHeaders.Add("OData-Version", "4.0");
            httpClient.DefaultRequestHeaders.Add("Prefer", "odata.include-annotations=\"*\"");
            httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", (bearerToken));

            //Query to retrieve contacts
            var queryOptions = "contacts?$select=fullname,mobilephone&$filter=emailaddress1 eq '" + contactEmail + "'";
            HttpResponseMessage retrieveResponse1 = httpClient.GetAsync(queryOptions).Result;


            if (retrieveResponse1.StatusCode == HttpStatusCode.OK)
            {


              

                    responseMessage = retrieveResponse1.Content.ReadAsStringAsync().Result.ToString();
               

            }
            else
            {

                return new BadRequestObjectResult(JsonConvert.SerializeObject("an error occured while retriving contacts"));
            }



            #endregion

            if (string.IsNullOrEmpty(contactEmail) && responseMessage== String.Empty)
            {
                responseMessage = "Pass an email in the query string or in the request body";
            }        

            return new OkObjectResult(responseMessage);
        }
    }
}

Test your function from post man.

Request
Response

Please note, the response in the code is not well formatted, you may format the response as a proper json object based on your business need.

Hope this helps.

Authenticate Dynamics 365 in Azure Functions Version 3

I consider Azure functions as a powerful weapon in the armory in numerous scenarios, but not limited to the following:

  • Expose Dynamics 365 APIs to third-part apps in a well-wrapped manner.
  • Delegate some of the computation load from plugins to outside D365.
  • Create scheduled custom code to run on specific intervals.
  • Insufficient infrastructure to host custom services or APIs for integration.

Read more about Azure Functions here.

One challenge we face when creating a new azure function with Dynamics 365 is that the current runtime version which is 3, it uses .Net core(runtime version 2 also). It was much easier with version 1 as it uses the .Net framework and we can directly use D365 SDK for authentication and consuming 365 services. The power apps .Net core SDK is still under the alpha version and cannot be utilized for production purposes. One easy option here is to use ADAL and WEB API. In this post, let’s see how we can authenticate an application user in Azure functions V3.

Before we start, if you are not familiar with the following, please have a glance at the links provided,

  1. Create Application users in Dynamics
  2. Creating an Azure function
  3. Creating Azure functions from visual Studio / VS Code

Create your Azure function with the help of links added in point 3, and add following NuGet packages if they are not there in your function app.

Now use the following code to authenticate your Dynamics 365 application user.

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System.Net.Http;
using Microsoft.IdentityModel.Clients.ActiveDirectory;

namespace SampleFunctionApp
{
    public static class D365
    {
        [FunctionName("ConnecttoD365")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");

            string name = req.Query["name"];

            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            dynamic data = JsonConvert.DeserializeObject(requestBody);
            name = name ?? data?.name;
            #region Auth Code
            //
            string cloud = "https://login.microsoftonline.com";

            //This is the Domain!

            string tenantId = "**********";

            string authority = $"{cloud}/{tenantId}";

            // ApplicationID in the new UI

            string clientId = "*********";

            //Azure App secret Key

            string clientsecret = "******";

            ClientCredential clientcred = new ClientCredential(clientId, clientsecret);

            // Application ID of the Resource (could also be the Resource URI)

            string resource = "https://******.crm.dynamics.com/";

            AuthenticationContext ac = new AuthenticationContext(authority);
            AuthenticationResult result = null;
            var bearerToken = string.Empty;
            string ErrorMessege = string.Empty;
            try

            {
                //already having token
                result = await ac.AcquireTokenSilentAsync(resource, clientId);
                if (result != null)
                {
                    bearerToken = result.AccessToken;
                    log.LogInformation("Token Acquired:"+ bearerToken);
                }
            }

            catch (AdalException adalException)

            {//Acquire token
                if (adalException.ErrorCode == AdalError.FailedToAcquireTokenSilently

                || adalException.ErrorCode == AdalError.InteractionRequired)

                {

                    result = await ac.AcquireTokenAsync(resource, clientcred);
                    if (result != null)
                    {
                        bearerToken = result.AccessToken;
                        log.LogInformation("Token Acquired="+ bearerToken);

                    }
                }
                else
                {
                    log.LogWarning("Failed to acquire Bearer Token :-" + adalException.Message);
                    var AdalException = new { adalexception = "Failed to acquire Bearer Token :-" + adalException.Message };
                    return new BadRequestObjectResult(JsonConvert.SerializeObject(AdalException));
                    throw adalException;

                }

            }

            #endregion

            string responseMessage = string.IsNullOrEmpty(name)
                ? "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a better response."
                : $"Hello, {name}. This HTTP triggered function executed successfully.";

            return new OkObjectResult(responseMessage);
        }
    }
}

The above function simply authenticates and write bearer to the log. Ensure you do not log this in your actual code.

In the next post, we will see how we can use this token to call D365 Web API from Azure Function App.

Hope this helps.

Call JavaScript Methods Globally Over Dashboards, Home Grids, and Record Forms.

Recently, I received a strange requirement to show a dialogue to users as soon as they login to Dynamics 365. The dialogue can be an HTML page with company events or a video stream (exact requirement not mentioned here). The pop-up should be shown over dashboards as well as entity home grids. It is easy to show the dialogue on a record form as I can register JavaScript on the record form, or show an application-level notification using addGlobalNotification client API, if it was just a notification only. I was stuck on showing the dialogue on dashboards, as most model driven apps in D365 will land on some dashboard when the user logs-in, and we do not have any event triggers there. I shared the requirement with the community experts and finally Linn Zaw Win, Microsoft Business Application MVP, gave me a lead to try using the application ribbon buttons. Booom!!! It worked.

Photo by Andrea Piacquadio on Pexels.com

Here is what I tried.

  1. Added a hidden button in Global Tab (where the Advanced Find button is located) using Ribbon Workbench.
  2. Added JavaScript function using CustomRule of the button Enable Rule.
  3. Triggered dialogue by adding required logic inside my enable rule JavaScript.
  4. Since the global tab is there on all screens of the model driven app (including dashboard and grids), the button enable rule JavaScript method will be called and so is our dialogue logic.

Add a new button in the global tab.

Create a new solution and add application ribbon and one entity (any entity is fine) to the solution. Now open your solution using ribbon workbench. Select ApplicationRibbon in the entity dropdown as shown below.

Add a new button in the Global Tab by scrolling the Home area to the right until you find the Mscrm.GlobalTab. Drag a button and drop into  Mscrm.GlobalTab.

Add enable rule for the button.

In the EnableRule, add a new step as CustomRule and select the JavaScript web resource in the library and enter the function name. Set default value as False so that button will be hidden (assuming your JavaScript function is not returning “True” value), you can also use display rules to keep the button always hidden.

Create a new Command and add the new enable rule we just created.

Now associate this command with the newly created button.

Publish your button

Once you have published, whenever a Dynamics 365 page is loaded, system will call your enable JavaScript. Below is the sample code I have used for showing a simple alert.

 function  showGlobalMessage()
	{
		 var alertStrings = { confirmButtonLabel: "Yes", text: "Go to Todays Video?", title: "Your Daily Message" };
var alertOptions = { height: 120, width: 260 };
Xrm.Navigation.openAlertDialog(alertStrings, alertOptions)
return false; 
}

Note: As per the above logic, the dialogue shows (enable rule will be triggered) every time the user opens a new tab/window. To show the dialogue only once or with specific intervals, you can use a new custom entity to keep track of the notification setting for each user using Xrm.WebApi, or you can use  localStorage (but using local storage may not be an officially supported D365 customization)

I know this is not a common scenario or ideal use case, but I hope it will save you some time when you come across strange requirements 🙂

Image Resizer PCF

Save Your Cloud Storage Space

Wanna save some cloud storage space? here is a PCF to Help you. Nowadays, even a budget smartphone’s camera will also capture images with 3-5 MB size. Imagine a Dynamics 365 environment with 50 users and each user saving 10 images per day.

Photo by Gustavo Fring on Pexels.com

Though we can save these images to Azure Blob or SharePoint or even purchase additional storage, in most cases these images do not need a high definition quality. The Image Resizer PCF is my attempt to resize images on the fly. This control allows you to upload multiple images to Dynamics 365 notes or email and the PCF resizes the image files before saving to dynamics 365. Watch below video to view the PCF in action.

How to configure

  • Download the managed or unmanaged solution file from Git and import to your environment.
  • Insert a section with a single column on the form.
  • Add a field you would like to use that will not be used on the form.
  • Uncheck ‘Display label on the form’ for the field.
  • Add image resizer PCF control to your field.
  • Give parameter values
    • Image Height: Height of the resized image.
    • Image Width: Width of the resized image.
    • Image Quality Percentage: Resizing quality.
  • Save and publish your changes.

Technical Details

The PCF uses React Image File Resizer to resize the images and React Drop Zone for the file upload control. For quick development, I have reused Rama Rao’s Attachment Uploader for file upload logic. There are many improvement options like image preview, on screen resizing etc. You are always welcome to branch git repo here.

Do you want to be a Dynamics 365/Power Platform expert? then docs.microsoft.com is the Key.

When my colleagues are stuck on something, I always encourage them to do their best in Google search and then ask for help in the respective tech community, because good research is always important to nurture knowledge. But having said that, I always ensure that they check the Microsoft docs, if they haven’t, I suggest them to look into docs.microsoft.com.

Googling is a mandatory skill. BUT

Most developers start their career at the same pace and depend on random Google Searches. Even I started my career with a trust in blogs and communities. The blogs and videos are always helpful to make you understand the topics easily, but when you need deep technical knowledge, official documents or SDK documentation is the best source. These documents let you know the capabilities and limitations of the platform you work on.

Let’s take an example, if you are stuck at some point while using Power Apps Functions or D365APIs, what will you generally do? You might search it randomly and copy/paste the available piece of code (like we all do).

Can you imagine what can happen later to the system?

Is the code you copied supported by Microsoft?

Is it the optimal solution for your system and users?

SDK documentation is the answer to all these questions. It is your responsibility to ensure that you are not doing any unsupported customization.

We all know that the official documents are helpful but, do you know why people are not looking into these documents while working on something? It’s because we are all lazy…. These documents contain a broad range of technical information and are not as simple and easy to understand as individual blogs. But for technical knowledge and long-term future in D365 and Power Apps, it is advisable not to go with easy solutions. Reading through the official documents will definitely pay back your efforts. Once you start reading these documents and comprehend the topics, they become your true consultant.

In the start of our career, people may be short on time or lazy to check and read the SDK Documents. It wasn’t different for me either. But back in those days, I came across the blog of Ben Hosk and one of his statements struck me so hard “The CRM SDK can be stubborn, difficult, and renowned for not suffering fools gladly.” Reading this was the time when I decided to look into the Microsoft Documents along with the solutions from other Blogs and Videos available.

Now, referring the official documents for solution has more or less turned into a practice for me. If time does not permit to go through the documents, I would rather expand the deadline rather than compromise on the authenticity and quality of the solution. Moreover, I try to cover the topics during the weekend and learn something new from the official Microsoft Documents. So, if you are not familiar with these documents or fail to refer them even after knowing its benefits, it’s definitely a loss of opportunity to learn new things while at work. 

So, you should never bypass “learning something new with every customization you do”!!!!

Replacing Dialog in Model Driven Apps Part-1: Using Main Form Modal

Since the announcement of dialogue depreciation, I have been trying out different alternatives to replace dialog as I use them extensively in my projects. The first option I tried was using canvas apps as described in this post ,but I was not happy with that approach and tested other options and implemented those in my projects based on customer scenarios. I will be publishing these options as a series. This is the first post and let’s see how we can replace dialog with entity main form.

There are many gaps when we replace dialog with other alternatives, even though these gaps can be filled with workarounds, the maintainability is high when compared to a classic dialog. These issues can be handled to a great extend using main forms.

With the April 2020 preview release of the Unified Interface for model-driven apps in Power Apps you can now open a record in a dialog. The record will open in a modal dialog and users will have access to the command bar, header and tabs that you defined for the records main form.

We can us the above option replace classic dialog.

Scenario:

I have to request HR team to verify certain details about prospects, but HR team is not allowed to access contact details. I also need to capture feedback and remarks from HR team.

So I created an entity called “Requests(nj_dialoguebatchone)” , modified the main form as per my requirements and removed all the unwanted buttons using ribbon-workbench.

Next step is to call this form using JavaScript from contact form.

function loadDialogForm(executionContext) {

    formContext = executionContext.getFormContext();
    var pageInput = {
        pageType: "entityrecord",
        entityName: "nj_dialoguebatchone",
        formType:  2
// formType 2 opens a new record.
    };
    var navigationOptions = {
        target: 2,
        height: {value: 70, unit:"%"},
        width: {value: 35, unit:"%"},
        position: 1
//target: Number. Specify 2 to open the page in a dialog. 
//position:Number. Specify 1 to open the dialog in center.
    };
    Xrm.Navigation.navigateTo(pageInput, navigationOptions).then(
        function success(result) {
               
                // Handle dialog closed
        },
        function error() {
                // Handle errors
        }
    );
    

   

}

I hope the code is self explanatory, more details can be found in the following links.

Now you can bind this script with ribbon button or other form events Boom dialog is ready.

You can also use quick create forms, but main form gives you more flexibility in terms using business rule and other form level formatting and validations. You can also easily configure Workflows, FLOWS, or Plugins based on user input in this as record create and update events are available.

See the dialog in Action

P.S. Am a classic dialog fan 🙂

Activity Summary Pro

So busy with year ending and 2020 plans. AsvI promised “The pro version will be released in two months”, I have to release the pro version today. Really sorry for the poor CSS, am actually bad at styling stuffs 😦 . But you can always improve the style and features in the Github, any updates are appreciated. Here is the github repo link.

New features added.

  • Click on Activity Name to Expand the list.
  • View activities as a chart
  1. Get the unmanaged solution file  Here
  2. Import the solution to your organization.
  3. Create a view with the required filters. If you do not require the control to consider certain activity types, you can exclude them in your view filter. The control will consider only the records returned by the view selected.
  4. Add sub-grid to the form with the created view.
  5. Add Activity Summary control to the sub-grid.
alt text
alt text

Note: If you need to exclude certain activity types, apply the same logic in the view filter.

Special mentions

Hari Narayanan Kumar for his Activity Analyzer which helped me to include the chart easily.

Andrew Butenko for his quick repleys and answers in the power users community 🙂

Happy New Year, let this be the year of Power Users

Activity Summary PCF for Model Driven Apps

It’s been a while since PCF controls were released for general availability. I was busy with my project schedules and finally, I managed to try out one. The Activity Summary grid. We can have activity timelines or sub-grids to view the activities under a record but these do not provide a high-level glance on activities. So I created one PCF to show activity summary, count of each type of activity under a record. This comes with Zero configuration. Whattt?? Yes, Zero configuration.

Follow the steps below to set yours.

  1. Download the managed solution from here.
  2. Import the solution to your organization.
  3. Create a view with the required filters. If you do not require the control to consider certain activity types, you can exclude them in your view filter. The control will consider only the records returned by the view selected.
  4. Add sub-grid to the form with the view we created.
  5. Add Activity Summary control to the sub-grid.

Boom! Done.

Wait is there an advanced version? Yes I will be releasing Activity Summary Pro in next 2 months :).