Categories
Common Data Service Dynamics 365 Power Automate Power Platform

Power Automate out of Cherry-Picking

Often times I have come across a requirement where a customer wants to prevent users Cherry-Picking the items they work on (queue items) in Dynamics 365 by having some sort of “Get Next” button.

I have implemented this functionality many times in many different ways over the years, usually with client-side scripting. This time I wanted to create a mechanism where it is easier for a “citizen developer” to be able to adjust the logic of the query that determines which queue items and what priority to apply to the “Get Next” function

So, I went down the path of creating a Flow using Microsoft Power Automate to achieve this goal.

My solution has a number of components :

  • Add a “Get Next” button to the Queue Item Ribbon
  • Add JavaScript library for “Get Next” button
  • Create a Custom Action that the JavaScript can call
  • Create a Power Automate Flow that the Custom Action triggers

I like the approach of using Custom Actions to trigger the Flow (or other integration logic) as this allows a great degree of re-usability across different services.

Add “Get Next” button to Queue Item Ribbon

Using Ribbon Workbench via XrmToolbox you can add a button to the Mscrm.Homepagegrid.queueitem.MainTab section of the “Home” Command Bar for the queue item entity, which calls our queueitemribbon.GetNext JavaScript function (created in the next step)

Add JavaScript library for “Get Next” button

Next I created the JavaScript functions to get the current user id and call the Custom Action via the Dynamics 365 WebAPI (note: I use TypeScript to create my JavaScript libraries, so there’ll be some extra syntax in the code below)

/// <reference path="../../../typings/xrm/xrm.d.ts" />
/// <reference path="../common/JSHelper.ts" />

namespace queueitemribbon {
    let context: Xrm.context = Xrm.Utility.getGlobalContext();

    export function GetNext() {
        if (!context) {
            return;
        }
        var fromUser = {
            systemuserid: context.userSettings.userId
        };

        var data = {
            "User": fromUser
        };
        JSHelper.ExecuteAction("crabf_GetNext", true, null, null, window.JSON.stringify(data),
            function (response) {
                var result = JSON.parse(response);
                let lookup = {
                    "entityType": "queueitem",
                    "id": result.queueitemid,
                    "name": "",
                };
                Xrm.Utility.refreshParentGrid(lookup);
                var entityFormOptions = {};
                entityFormOptions["entityName"] = "queueitem";
                entityFormOptions["entityId"] = result.queueitemid;
            },
            function (response) {
                Xrm.Navigation.openErrorDialog({ message: response });                
            });
    }
}

You’ll notice the above code uses an ExecuteAction method from a JSHelper library. The code for the Helper is as follows :

/// <reference path="../../../typings/xrm/xrm.d.ts" />

namespace JSHelper {
    export function ExecuteAction(action: string, isGlobal: boolean, resource: string, resourceId: string, data: any, successCallBack: Function, errorCallBack: Function) {
        let url = Xrm.Utility.getGlobalContext().getClientUrl();
        if (isGlobal == false) {
            url += ("/api/data/v9.1/" + resource + "(" + resourceId + ")/Microsoft.Dynamics.CRM." + action);
        }
        else {
            url += ("/api/data/v9.1/" + action);
        }
        let req = new XMLHttpRequest();
        req.open("POST", url, false);
        req.setRequestHeader("Accept", "application/json");
        req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
        req.setRequestHeader("OData-MaxVersion", "4.0");
        req.setRequestHeader("OData-Version", "4.0");
        req.onreadystatechange = function () {
            if (this.readyState == 4 /* complete */) {
                req.onreadystatechange = null;
                if (this.status == 200 || this.status == 204) {
                    successCallBack(this.response)
                } else {
                    errorCallBack(this.response);
                }
            }
        };
        if (data) {
            req.send(data);
        } else {
            req.send();
        }
    }
}

Create a Custom Action that the JavaScript can call

I create a Global Custom Action to facilitate the call to Power Automate and it has a User Id Input parameter and an Output Parameter that returns the queue item that was added (This is to facilitate the Grid Refresh automatically – Xrm.Utility.refreshParentGrid(lookup) in the code above)

The code for the Custom action is as follows :

protected override void Execute(LocalPluginContext localcontext)
        {
            if (localcontext == null)
            {
                throw new ArgumentNullException("localcontext");
            }

            if (localcontext.PluginExecutionContext.InputParameters.Contains("User") && localcontext.PluginExecutionContext.InputParameters["User"] is EntityReference)
            {
                EntityReference uEntity = localcontext.PluginExecutionContext.InputParameters["User"] as EntityReference;
                if (uEntity.LogicalName.ToLower().Equals("systemuser"))
                {
                    Payload keyData = new Payload
                    {
                        systemuserid = uEntity.Id.ToString()
                    };

                    UriBuilder uriBuilder = new UriBuilder("https://prod-06.australiasoutheast.logic.azure.com:443/workflows/......"); //Flow HTTP URL
                    string pl2Send = JsonHelper.SerializeJSon<Payload>(keyData);
                    HttpResponseMessage result = Post2Flow(uriBuilder, localcontext.TracingService, pl2Send).Result;
                    localcontext.PluginExecutionContext.OutputParameters["ItemtoWork"] = new EntityReference("queueitem", new Guid(queueresult));
                }
            }
        }

[DataContract]
internal class Payload
{
    [DataMember]
    public string systemuserid;
}

private static async Task<HttpResponseMessage> Post2Flow(UriBuilder uriBuilder, ITracingService tracingService, string JSON)
        {
            string result = string.Empty;
            //create http client object
            HttpClient client = new HttpClient();

            //data to send
            var data = JSON;

            //send in byte format
            var buffer = System.Text.Encoding.UTF8.GetBytes(data);
            var byteContent = new ByteArrayContent(buffer);
            byteContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");

            //execute request
            HttpResponseMessage response = client.PostAsync(uriBuilder.Uri, byteContent).Result;

            if (response == null)
            {
                //no response
                throw new InvalidOperationException("Failed to obtain the httpResponse");
            }

            result = await response.Content.ReadAsStringAsync();

            queueresult = result;

            return response;
        }

Create a Power Automate Flow that the Custom Action triggers

The final part of the process is to create a Power Automate Flow to get the record that we are after.

The first step is to add an HTTP request trigger that accepts the SystemUserID that we are passing in from our Custom Action

Next I Initialise a variable to store the queue item id of the record that we retrieve

I then use a Common Data Services (Current Environment) “List Records” action to get the queue record we want using a FetchXML query which can be created via the Advanced Find Editor in Dynamics 365 (This will allow for a “Citizen Developer” to easily update the query in the Flow to change the parameters of the “Get Next” function)

The query above gets the oldest queue item from all the queues of which the user is a member. You’ll also not I have set Top Count = 1 to only get the first record (Next Item)

I then use an Apply to each to iterate through the results (there will be only 1 result though) and update the Worked By field to the current user (This moves the queue item into their queue)

Finally I set the Variable to the Queue Item Id that we retrieved and return it to the Custom Action via an HTTP Response Action.

And that is all we need for our “Get Next” function. Happy not Cherry-Picking. Until next time….

3 replies on “Power Automate out of Cherry-Picking”

Hi, I am new to this and i am looking for same very solution for the functionality. I have few queries – if you could please answer kindly that would be of great help.
1. Are you using all these codes in VS solutions? or the javascripts added into the web resources of D365 solutions?
2. Where is the queueresult declared/initiated in above code – as when i created a VS solution it gave error as it failed to recognize it in both the procedures in custom action code.
3. what the string “crabf_GetNext” in first code snippet suggests?
4. the last screenshot of your power automate flow i thought is repetitive.

Hey Nandini,

Thanks for the questions, my responses are as follows :

1. The C# code obviously needs to be in a VS Solution, but the JavaScript can be added directly to Web Resources or done in Visual Studio and deployed as a Web Resource using your preferred mechanism of deployment. I use Spkl

2. Apologies, queueresult is just an empty string variable That can be declared at the beginning of the class

3. crabf_GetNext Is the schema name of the Custom Action that you create in D365

4. Thanks, you are correct. I have removed it 🙂

Hello Dylan,

Many thanks for your reply and resolving all my queries. 🙂

I am still facing problem and the errors as below:
1. ‘MyCustomAction’ does not implement inherited abstract member ‘CodeActivity.Execute(CodeActivityContext)’
where MyCustomAction is the CS which I have created.
2. The type or namespace name ‘LocalPluginContext’ could not be found (are you missing a using directive or an assembly reference?)
3. ‘JsonHelper’ does not contain a definition for ‘SerializeJSon’

Am I missing any particular package/assembly or directive?

Could you please kindly guide?

Thanks,

Nandini

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.