In this Cantonese webinar, Chris Chan, Nintex Pre-sales Engineer, and I -Kok Koon Gan, Nintex Technical Evangelist, discussed the solutions on how to easily generate business critical documents, analyse your process portfolio and build powerful mobile apps across the entire Nintex platform. Enhance the value of your Nintex investment from Nintex Analytics, Nintex App Studio and Nintex Document Generation today!
Author: Gan Kok Koon
Crossing the river with Nintex Workflow Cloud
Here is a question for you: “How complex a business process can be solved by Nintex Workflow Cloud?“, well I think I do have an interesting answer for that, it solves business processes that is as complicated as the famous Farmer-Wolf-Goat-Cabbage cross a river puzzle.
There are many programming languages out there, everyone will have its own strengths and focuses. You might find it easy to solve the old classic puzzle such as the “Farmer-Wolf-Goat-Cabbage cross a river” with much lesser code in Prolog or Lisp that is associated with Artificial Intelligence than a Java program. It will be interesting to find out solving the same puzzle without even writing a piece of code with Nintex Workflow Cloud.
For those who have not came across the “Farmer-Wolf-Goat-Cabbage cross a river” puzzle, you could simply do a search on the web to find enough article and solution for it. This is just a perfect puzzle trying to understand a real world business process is, it has:
Objective:
- To move all the objects (Wolf, Goat, and Cabbage) across a river
Rules:
- Wolf and Goat are not to be left alone
- Goat and Cabbage are not to be left alone
- Only farmer roars the boat
- Farmer can only bring one object at a time
Let us define the required terminology or object(s) we can apply to our workflow design,
Terminology | Description |
---|---|
fromBank | The river bank where all the objects of Farmer, Wolf, Goat, and Cabbage are |
toBank | The destination river bank where all the objects to be moved to |
F, W, G, C | The acronyms representing Farmer, Wolf, Goat, and Cabbage |
fromBank=[“F”,”W”,”G”,”C”] | The initial state represents all the objects are at the fromBank river bank |
riskState=[“WGC”,”GC”,”CG”,”WG”,”GW”] | Sets of states both fromBank and toBank is at risk |
toBank=[] | The initial state represents none of the objects are on the toBank river bank |
Embarkation, Disembarkation, ReturnTrip | Represent three movement stages of forward movement from fromBank to toBank, disembarking of object to the toBank, and return trip with object to be brought back to the fromBank. |
Algorithm:
- Initialize objects for:
- fromBank=[F,W,G,C],
- toBank=[],
- embark farmer to boat (i.e. fromBank=[W,G,C]),
- riskState,
- etc.
- Start a loop until toBank=[F,W,G,C],
- Embarkation Branch (i.e. always assuming to start with embarking an object to the boat)
- Check if we suppose to embark an object or return an object, if return object, change to ReturnTrip Branch. else continue
- try to embark the first Item from fromBank collection
- Verify if fromBank is “at risk” state by checking against the riskState collection
- If “at Risk” is true, revert the embarked item back to the fromBank collection’s back of the queue, exit branch
- if “at Risk” is false, remove the Item from the fromBank, switch to Disembarkation branch
- Disembarkation Branch (i.e. this stage is always followed from Embarkation branch)
- Try to disembark the object to toBank
- If toBank items count is equal to the total object, the goal has achieved
- if toBank items count is less than 2, toBank is at Safe state, farmer go back alone to Embarkation stage (i.e. set variable returnTrip=false)
- if toBank is “at Risk” state, farmer need to go back bringing one item to avoid “at Risk” state of toBank (i.e. set variable returnTrip=True)
- ReturnTrip Branch
- Disembarkation was always done at the Disembarkation Branch by disembarking item to the back of the toBank collection queue
- We will try to return the first item from the toBank, and verify if the remaining left alone safe?
- Loop through to get an item to be returned avoiding the conflicts at toBank
- Embarkation Branch (i.e. always assuming to start with embarking an object to the boat)
- Exit of loop (i.e. mission completed), sending the log of the movement result.
You may see the demo by submitting the Public Form at the following URL FWGC Cross the river puzzle form , which you will need to supply an email to receive the result, and the object sequence in the format of e.g. “F,W,G,C”, “W,F,C,G”, etc. to get different movement results. Here is the example of the form:
Note: Nintex Workflow Cloud do not currently support validation of the public form, you will need to fill in a valid email to receive the response, and right syntax for the FWGC Sequence field.
Here is a sample email content you will be getting from the submission of the above form:
Please find below the movement required to move all the objects from fromBank to toBank (i.e. each –> denotes the beginning of a line)—>Initialized: fromBank = [“F”, “W”, “G”, “C”], ristState = [“WGC”, “GC”, “WG”, “CG”, “GW”], toBank = [], initStateCount = 4, —>Embark W: fromBank = [“G”, “C”], toBank = [], atRisk = true, —>Revert W: fromBank= [“G”, “C”, “W”], toBank=[], —>Embark G: fromBank = [“C”, “W”], toBank = [], atRisk = false, —>Disembark G: fromBank = [“C”, “W”], toBank = [“G”], embark option in return trip= false, —>Embark C: fromBank = [“W”], toBank = [“G”], atRisk = false, —>Disembark C: fromBank = [“W”], toBank = [“G”, “C”], embark option in return trip= true, —>Return trip with G: fromBank = [“W”, “G”], toBank = [“C”], —>Embark W: fromBank = [“G”], toBank = [“C”], atRisk = false, —>Disembark W: fromBank = [“G”], toBank = [“C”, “W”], embark option in return trip= false, —>Embark G: fromBank = [], toBank = [“C”, “W”], atRisk = false, —>Disembark G: fromBank = [], toBank = [“C”, “W”, “G”], embark option in return trip= falseCompleted State:fromBank = []toBank = [“C”, “W”, “G”] |
This exercise helps me with list of Asks on features and enhancements that I am looking forward to, I have most of them logged to the uservoice, here are some of them:
1. There is currently no way to construct a rich text variable with formatting I want, so I could compose the “Send email” action’s body by inserting a formatted string/rich text variable at the final stage. (i.e. the email content in the above example will be more readable if I could insert a formatted text of the below example:
—>Initialized: fromBank = [“F”, “W”, “G”, “C”], ristState = [“WGC”, “GC”, “WG”, “CG”, “GW”], toBank = []
—>Embark W: fromBank = [“G”, “C”], toBank = [], atRisk = true,
—>Revert W: fromBank= [“G”, “C”, “W”], toBank=[],
—>Embark G: fromBank = [“C”, “W”], toBank = [], atRisk = false,
—>Disembark G: fromBank = [“C”, “W”], toBank = [“G”], embark option in return trip= false,
—>Embark C: fromBank = [“W”], toBank = [“G”], atRisk = false,
—>Disembark C: fromBank = [“W”], toBank = [“G”, “C”], embark option in return trip= true,
—>Return trip with G: fromBank = [“W”, “G”], toBank = [“C”],
—>Embark W: fromBank = [“G”], toBank = [“C”], atRisk = false,
—>Disembark W: fromBank = [“G”], toBank = [“C”, “W”], embark option in return trip= false,
—>Embark G: fromBank = [], toBank = [“C”, “W”], atRisk = false,
—>Disembark G: fromBank = [], toBank = [“C”, “W”, “G”], embark option in return trip= false
2. Import / Export of workflow design. I will be able to share my workflow design once the feature is available for me to export my workflow and attach it to this blog for sharing..
3. Print workflow design, and Save as.. to export workflow design as JPG, PNG, etc.
4. As I am using a lot of Collection operation for the exercise, there is a long list of collection operations I am looking for that is missing for the time being, the challenge results a workflow with additional actions to solve simple issue, here are some of the features that I think is missing:
- Copy a collection from one to the other
- Store Item for “Remove Item from Collection” action
- Compare if two collections are equivalent
- Dictionary variable
- Concatenate collection items into string
5. Other features such as Log history action, Workflow constant, Go-to node action, sub workflow and/or grouping of actions.
Until my workflow could be exported for sharing, the best I could do for the time being is the captured design of the workflow solving the Farmer-Wolf-Goat-Cabbage puzzle.
Stronger Together – Nintex Workflow Cloud & Office 365
Stronger Together – Nintex Workflow Cloud and Nintex Workflow for Office 365. Many of us have been asking for feature such as scheduled workflow in Sharepoint for Office 365, that has not made available by Nintex Workflow for Office 365 today. Well, a scheduled workflow is pretty straight forward and easy to setup in Nintex Workflow Cloud, we can create a scheduled workflow in Nintex Workflow Cloud to trigger a workflow in Sharepoint for Office 365. That gives us the solution of scheduling a workflow in Sharepoint for Office 365.
To demonstrate how that works,
Nintex Workflow for Office 365 – Site Workflow
1. I have created a Site Workflow in Sharepoint for Office 365 environment. Here is how “MySiteWorkflow” looks like in Nintex workflow for Office 365 environment:
2. The “MySiteWorkflow” takes one Initiation Variable as defined below, when the workflow started manually, it will prompt users with the initiation form for users to provide input for the “listname” initiation variable.
3. I have made the Site Workflow as simple as possible, with only only one “Log to History List” action. When triggered, it will simply write the provided “listname” variable value to the workflow history.
Nintex Workflow Cloud
While waiting for the Sharepoint Connectors to be released in Nintex Workflow Cloud, I have workaround with Azure AD App for Nintex Workflow Cloud to access the Sharepoint for Office 365 environment via the OAuth 2.0 authentication. For those who have not followed my blog on that, here is the link you could refer to on Add Azure AD App from Azure Portal section of the blog post. (Please take note that, instead of using https%3A%2F%2Fgraph.microsoft.com%2F as the value for the resource parameter, I have this time to use <site_host>@<site_reaml instead, as such my example i was using resource=https%3A%2F%2Fntxte07.sharepoint.com@<site_rearm> instead.)
1. Instead of creating a Scheduled Workflow, let us try to call the Sharepoint site workflow from Nintex Workflow Cloud using a Public Web Form start event to start the workflow in Nintex Workflow Cloud, the start event is configured as below to capture the input to be used as “listname”.
2. Before we proceed to define other actions, lets look at the list of workflow variable we will need to create for the workflow to work.
3. Using “Set a variable value” action to assign the Access Token we obtained to the “accessToken” variable as shown below.
4. With the “accessToken” assigned, we going to add a “Branch by Stage” with two branches, one being the normal path to follow, the other to handle if the Access Token is expired, to renew the Access Token. I have named it as “Branch 1” and “Branch 2” as shown below
5. We set the branch to always start from “Branch 1” assuming the token is valid, by calling a “Call a web service” action to call Sharepoint online’s REST API. To start a workflow in Sharepoint online, we will need to, first, get the workflow’s subscription ID, followed by initiate the workflow to start. Define the “Call a web service” with the following values:
Field | Value |
---|---|
URL | https://<YourTenantName>.sharepoint.com/_vti_bin/client.svc/ProcessQuery |
Request Type | HTTP Post |
Request Headers | Authorization: Bearer accessTokenX-Requested-With: XMLHttpRequest |
Request Content | Request Content example:<Request xmlns=”http://schemas.microsoft.com/sharepoint/clientquery/2009” SchemaVersion=”15.0.0.0″ LibraryVersion=”16.0.0.0″ ApplicationName=”Javascript Library”><Actions><ObjectPath Id=”1″ ObjectPathId=”0″ /> <ObjectPath Id=”3″ ObjectPathId=”2″ /> <ObjectPath Id=”5″ ObjectPathId=”4″ /> <ObjectPath Id=”7″ ObjectPathId=”6″ /> <ObjectPath Id=”9″ ObjectPathId=”8″ /> <Query Id=”10″ ObjectPathId=”8″><Query SelectAllProperties=”false”><Properties><Property Name=”PropertyDefinitions” SelectAll=”true” /></Properties></Query></Query></Actions> <ObjectPaths><StaticProperty Id=”0″ TypeId=”{3747adcd-a3c3-41b9-bfab-4a64dd2f1e0a}” Name=”Current” /><Property Id=”2″ ParentId=”0″ Name=”Web” /> <Constructor Id=”4″ TypeId=”{4ccc7f0e-bf7e-4477-999c-6458a73d0039}”><Parameters><Parameter ObjectPathId=”2″ /></Parameters></Constructor> <Method Id=”6″ ParentId=”4″ Name=”GetWorkflowSubscriptionService” /> <Method Id=”8″ ParentId=”6″ Name=”GetSubscription”><Parameters><Parameter Type=”String”>[Workflow Template ID]</Parameter></Parameters></Method></ObjectPaths></Request> |
Response Content | resContent |
Response Headers | resHeader |
Response Status Code | resCode |
[Workflow Template ID] = e.g. {9CE443B7-3583-46C2-AA07-415F5E00C25B}, could be found from the site workflow start page’s workflow link as shown
6. Verify if the “Call a web service” is successful by using the “Branch by condition” to check the “resCode” equals to 401, if yes we will change the stage to “Branch 2” for getting new Access Token with the “Refresh Token” obtained during the steps setting up the “Azure AD App”. We will follow the “No” path if the resCode returns status code other than 401 (i.e. with the assumption the call to get the workflow subscription is successful).
7. The “Query JSON” followed by the “No” path is basically to get the returned the “_ObjectIdentity_” value with XPath search of “$.._ObjectIdentity_” from the resContent JSON object. The “_ObjectIdentity_” to be used in the following “Call a web service” action.
8. “Call a web service” with the following details to initiate the site workflow
Field | Value |
---|---|
URL | https://ntxte07.sharepoint.com/_vti_bin/client.svc/ProcessQuery |
Request type | HTTP Post |
Request headers | Authorization: Bearer accessTokenX-Requested-With: XMLHttpRequest |
Request content | My “Request content” example: (I would advise to use fiddler to capture a manual trigger of your site workflow in Sharepoint online, and capture the two HTTP POST to https://<your site host>/_vti_bin/client.svc/ProcessQuery for the XML request body to be used as your Request content here).<Request xmlns=”http://schemas.microsoft.com/sharepoint/clientquery/2009” SchemaVersion=”15.0.0.0″ LibraryVersion=”16.0.0.0″ ApplicationName=”Javascript Library”><Actions><Method Name=”GetExternalVariable” Id=”12″ ObjectPathId=”8″><Parameters><Parameter Type=”String”>listname</Parameter></Parameters></Method> <ObjectPath Id=”14″ ObjectPathId=”13″ /> <Method Name=”StartWorkflow” Id=”15″ ObjectPathId=”13″><Parameters><Parameter ObjectPathId=”8″ /> <Parameter Type=”Dictionary”><Property Name=”listname” Type=”String”>List Name</Property> <Property Name=”Microsoft.SharePoint.ExternalVariable.listname” Type=”String”>List Name</Property></Parameter></Parameters></Method></Actions> <ObjectPaths><Identity Id=”8″ Name=”subscriptionID” /> <Method Id=”13″ ParentId=”4″ Name=”GetWorkflowInstanceService” /> <Constructor Id=”4″ TypeId=”{4ccc7f0e-bf7e-4477-999c-6458a73d0039}”><Parameters><Parameter ObjectPathId=”2″ /></Parameters></Constructor> <Property Id=”2″ ParentId=”0″ Name=”Web” /> <StaticProperty Id=”0″ TypeId=”{3747adcd-a3c3-41b9-bfab-4a64dd2f1e0a}” Name=”Current” /></ObjectPaths></Request> |
Response content | resContent |
Response headers | resHeader |
Response status code | resCode |
That shows the second web service call to trigger the site workflow to start.
9. Continued from step 4 above branching to “Branch 2” is the same steps in my blog post on the “Azure AD App” setup on how to refresh the access token by calling the REST end point at “https://login.microsoftonline.com/common/oauth2/token” as shown below.
10. The “Query JSON” is to use XPath to search to the access token from the resContent returned from the above action call, and assign the new access token to the accessToken workflow variable.
The above steps demonstrate both the site workflow in Sharepoint for office 365 and the Nintex Workflow Cloud to manually trigger the site workflow in Office 365 to run. With that testing successful, we know we could now schedule a site workflow in Sharepoint for Office 365. To do that, we just need to schedule the Nintex Workflow Cloud using the Nintex connector with start Event of “Scheduled Start” as shown in the screen captured below as an example.
JavaScript to get List Items and User Profiles from Nintex Forms for Office 365
Sharing two pieces of JavaScript getting List Item(s) and User Profiles from Nintex Forms for Office 365. The two JavaScript was tested based on Nintex Forms for Office 365, App Version: NFO 1.2.3.0. Please take note that this code is tested only based on the above mentioned version of Nintex Forms, it might not be optimized in term of performance or simplicity.
Calling Sharepoint Online REST API to query host web’s custom list from within Nintex Forms for Office 365 (i.e. App web).
var pollSP;
NWF.FormFiller.Events.RegisterAfterReady(function (){
pollSP = setInterval(checkSPLoad, 500);
});
function checkSPLoad(){
if (clientContext){
window.clearInterval(pollSP);
var hostweburl = decodeURIComponent(getQueryStringParameter("SPHostUrl"));
var appweburl = decodeURIComponent(getQueryStringParameter("SPAppWebUrl"));
var layoutsPath = "/_layouts/15/";
var scriptbase = appweburl + layoutsPath;
NWF$.getScript(scriptbase + "SP.js",
function () {
NWF$.getScript(scriptbase+ "SP.RequestExecutor.js", execCrossDomainRequest);
}
);
}
function execCrossDomainRequest(){
var deferred = NWF$.Deferred();
var url = appweburl + "/_api/SP.AppContextSite(@target)/web/lists/GetByTitle('MyExpense')/Items?$select=ID,Title&@target='" + hostweburl + "'";
var executor = new SP.RequestExecutor(appweburl);
executor.executeAsync(
{
url: url,
method: "GET",
headers: {"accept": "application/json;odata=verbose"},
success: function (data) {
var jsonobj = JSON.parse(data.body);
var results = jsonobj.d.results;
deferred.resolve(data);
var msg = "";
for (var i = 0; i < results.length; i++){
msg = msg + results[i].Title + ";";
}
alert(msg);
},
error: function (data) {
deferred.reject(data);
alert("data");
}
}
);
}
}
Querying current user’s profile from Nintex Forms for Office 365.
NWF.FormFiller.Events.RegisterAfterReady(function (){
var clientContext;
var hostweburl = decodeURIComponent(getQueryStringParameter("SPHostUrl"));
var appweburl = decodeURIComponent(getQueryStringParameter("SPAppWebUrl"));
var layoutsPath = "/_layouts/15/";
var scriptbase = appweburl + layoutsPath;
NWF$.getScript(scriptbase + "SP.js").then(function(){
return NWF$.getScript(scriptbase + "SP.RequestExecutor.js");
}).then(function(){
return NWF$.getScript(scriptbase + "init.js");
}).then(function(){
return NWF$.getScript(scriptbase + "SP.UserProfiles.js");
}).then(execCrossDomainRequest);
function execCrossDomainRequest(){
var deferred = NWF$.Deferred();
var con = SP.ClientContext.get_current();
clientContext = new SP.ClientContext(appweburl);
var factory = new SP.ProxyWebRequestExecutorFactory(appweburl);
clientContext.set_webRequestExecutorFactory(factory);
var hostWeb = new SP.AppContextSite(clientContext, hostweburl).get_web();
var appWeb = new SP.AppContextSite(clientContext, appweburl).get_web();
var url = appweburl + "/_api/SP.UserProfiles.PeopleManager/GetMyProperties";
var requestHeaders = {
"Accept": "application/json;odata=verbose"
};
var executor = new SP.RequestExecutor(appweburl);
executor.executeAsync(
{
url: url,
contentType: "application/json;odata=verbose",
method: "GET",
headers: requestHeaders,
success: function (data) {
var msg = "";
var parsedData = JSON.parse(data.body);
var properties = parsedData.d.UserProfileProperties.results;
for (var i = 0; i < properties.length; i++){
var property = properties[i];
msg = msg + property.Key + ":" + property.Value + ",";
}
deferred.resolve(parsedData);
alert(msg);
},
error: function (data) {
deferred.reject(data);
alert("failed");
}
}
);
}
});
Start a Nintex Workflow Cloud workflow from the Sharepoint ECB menu
ECB Menu in Sharepoint List or Document Library allows one to manually trigger an action, and this could be an action to start a workflow from Nintex Workflow Cloud. We would love to have the option to manually trigger a workflow to start.
Here is the story line, looking at a document library in Sharepoint, based on the use case we discussed in my previous blog post, a Marketing Coordinator uploads the Excel Attendant List to a Sharepoint folder in Marketing Site’s Document Library, in this case, not all the documents in the library are leads to be generated in Salesforce, only this particular excel file is, and we want to manually trigger a Nintex Workflow Cloud workflow to help collect and upload leads from this file to SalesForce. To do that, we can simply add a custom ECB Menu to the Document Library (i.e. same applies to Custom List in Sharepoint). Diagram below illustrates what you will get:
Before we get into how to add the ECB menu in Sharepoint Document Library, let us take a look at how to start a workflow in Nintex Workflow Cloud. We going to use the “Public web form” Start event option in this exercise, the configuration of a sample workflow Start Event is shown in the diagram below:
This configuration will give us a web form URL with the format of https://{your-tenant}.workflowcloud.com/forms/{workflow-id}. Using Fiddler to trace the form submission, you will find the form is being submitted with the following HTTP POST request.
POST https://{your-tenant}.workflowcloud.com/api/v1/anonymous-workflow/{workflow-id} HTTP/1.1
Host: {your-tenant}.workflowcloud.com
Connection: keep-alive
Content-Length: 165
Accept: application/json, text/plain, */*
Origin: https://{your-tenant}.workflowcloud.com
:
:
Content-Type: application/json;charset=UTF-8
:
:
{“se_file_name1″:”/NWC%20Test/event%20lead.xlsx”}
With the HTTP POST request details we learned, we will be able to include a HTTP POST request in our ECB menu action. Again, there are different ways to achieve that – Sharepoint-Hosted Add-ins, Provider-Hosted Add-ins, and simply the way we going to get that without the need to create an add-in using Visual Studio is the Display Template client side JavaScript.
1. Edit the Document Library page by selecting the Edit Page from the page menu as shown.
2. Insert a Content Editor web part to the page,
3. With the cursor in the Content Editor’s content area, click Edit Source to edit the source of the content
4. Here is the JavaScript code to be included in the Content Editor’s source
<script language="”javascript”" type="text/javascript">
function Custom_AddDocLibMenuItems(m, ctx)
{
var strDisplayText = 'Upload to SalesForce';
var strImagePath = '';
var strAction = 'send2NWC()';
// Add our new menu item
CAMOpt(m, strDisplayText, strAction, strImagePath);
// add a separator to the menu
CAMSep(m);
// false means that the standard menu items should also be rendered
return false;
}
function CAMOpt(p,wzText,wzAct,wzISrc,wzIAlt,wzISeq,wzDesc)
{
var mo=CMOpt(wzText,wzAct,wzISrc,wzIAlt,wzISeq,wzDesc);
if(!mo)return null;
if(wzText != "Delete Item") AChld(p,mo);
return mo;
}
function send2NWC()
{
var url = "https://{your-tenant}.workflowcloud.com/api/v1/anonymous-workflow/{workflow-id}";
var method = "POST";
//depending on the value you passing to the workflow, I am using selected item's Url here
var postData = '{"se_file_name1":"' + currentItemFileUrl + '"}';
var async = true;
var request = new XMLHttpRequest();
request.onload = function () {
var status = request.status; // HTTP response status, e.g., 200 for "200 OK"
var data = request.responseText; // Returned data, e.g., an HTML document.
}
request.open(method, url, async);
request.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
// Sends the request to the server.
request.send(postData);
alert('Leads uploaded');
}
</script>
Tip: Few quick references getting values from Sharepoint Display Template:
currentItemFileUrl (i.e. selected item’s URL)
currentItemID (i.e. selected item’s ID in the list)
_spPageContextInfo.listId (i.e. List Id)
_spPageContextInfo.siteId (i.e. Site ID)
ctx.listName (i.e. List ID)
ctx.ListData.Row[currentItemID-1].UniqueId (i.e. selected item’s GUID)
ctx.ListData.Row[currentItemID-1].{column name} (i.e. list column value)
From Excel to SalesForce in Nintex Workflow Cloud
It’s easy we get into perception that a workflow always start from a form submission, and we’ve been telling the story of that. But in reality it’s not always the case, take Attendance List for instance, event coordinator taking attendance list with Excel at the reception of the event, is something very common and practical. the Attendance List will then be used to create Leads in CRM system for Account Management to follow up. This is just the right scenario we could easily automate in Nintex Workflow Cloud platform, I am going to share how the scenario could be automated in Workflow Cloud, the attendance of an event was taken using the Excel spreadsheet, and saved in OneDrive to trigger a workflow reading the attendance list to create leads in SalesForce.
Instead of explaining every single workflow action, I am just going to explain the key actions used to achieve our objectives here. Later part of this writing, I will share the pictorial flowchart of the workflow that I have created to give the overall view of the workflow design.
1. Microsoft Graph API to read the Excel range of records.
I am using the Excel Range Operations to get the range of cells from the excel file, for more details on the Graph API for Excel, you may refer to Microsoft Graph – Documentation – Excel , here is how the Get request forms:
GET /{version}/me/drive/items/{item-id}/workbook/worksheets(<id|name>)/range(address='<address>’)
authorization: Bearer {access-token}
workbook-session-id: {session-id}
Example of my URL to get excel range from the excel file in a Group’s drive:
‘)
Here is the example on how you configure it in Workflow Cloud:
The Response content of this action is saved to the resContent variable as shown. We will be interested to look at the “Values” of the returned JSON content.
2. Extract “values” from the returned JSON content
We use the “Query JSON” action to extract the “values” from the returned JSON content that was saved to resContent variable. The “Query JSON” action gives us row variable with value of [ [“Alice”, “Kho”, “Licek@mail.com“, “Abc o.” ], [ “Jason “, “Smith”, “Jason.smith@ntxte07.com“, “Ntxte07” ], [ “”, “”, “”, “” ] ] as shown below.
3. Convert “values” string to Collection
“Apply a regular expression” to convert that single string of text (i.e. row variable) into array/collection of string and save it to rows collection variable, The Extract operation of the Regular Expression using the Pattern of (?<=\[)[^\[\]].*?(?=\])+ gives us the outcome of rows collection with three items as shown.
4. Get fields of each row of attendance records
“Start a loop” logic action to loop through the rows collection to “get item from collection” to get each of the item in the rows collection and save it to row variable. The following regular expression with pattern = (?<=\”)[^\,].*?(?=\”)+ extracts each of the field from the row and saved it to fields collection variable.
That shows us how we get the attendance list row by row from the Excel file that was saved in a Group drive.
5. Create leads in SalesForce
We can simply use the SalesForce “Create a record” action to create leads in SalesForce. here is how it’s configured in Workflow Cloud using the “fields” collected from excel.
The diagram below illustrates the complete workflow design in Nintex Workflow Cloud
Microsoft Graph API from NWC
Following my previous post on Using OAuth 2.0 to access other cloud services from NWC, we are going to use the same principal but apply to Microsoft Graph API. Microsoft Graph exposes multiple APIs from Office 365 and other Microsoft cloud services through a single endpoint: https://graph.microsoft.com. That means we will be able to integrate to Office 365 Sharepoint, OneDrive, and other Azure and Office 365 related services from NWC with single Microsoft Graph API.
As you may be aware for now, the key challenge to connect to other cloud services will be more on the setup and getting the Access Token, which I have provided a sample on getting that up with Google OAuth 2.0 API, I will provide the steps to setup access to Microsoft Graph API later in this post.
Let us first take a look from the NWC end, a workflow we going to automate from Nintex Mobile submitting a new user request, the request gets submitted to NWC and triggers the call to Azure Active Directory API via the Microsoft Graph API, to create a new user in Azure Active Directory. From here, that should help us the understanding on all the possible scenarios we might have,
- Getting user details from Azure Active Directory or User profile from Sharepoint Online for CRM record update,
- A new user onboarding request handle by NWC to call Microsoft Graph API creating user(s) in Azure AD or assigning user the Office 365 licenses,
- Marketing event coordinator submits the attendant list in Excel file to OneDrive shared folder, it triggers the NWC workflow to read the metadata of the OneDrive file, determine if it is a file with new leads to be collected, it sends email to get the manager to review the file and for approval, once approve, the NWC workflow continues to read the excel file content and generate leads in CRM such as SalesForce or Microsoft Dynamic CRM, and so on.
Diagram below demonstrates the NWC Request for New User workflow I have created to show how that works, explanation of the actions provided in the table followed.
Action No. | Action | Description | Configuration |
---|---|---|---|
10 | Start Event – Nintex Mobile | This action defines the workflow to be triggered by a Nintex Mobile Form submission.For the demo, I have created four fields for the purpose: First Name, Last Name, Department, and Usage Location | Which adds a Nintex Mobile Form as shown: |
20 | Set a variable value | Assign the obtained “Access Token” to the accessToken variable.Note: Please refer to the later section of this write up on how to obtain the access token. | |
30 | Branch by stage | A two branches of workflow to handle a normal state with initial Access Token, and the second branch to get a new Access Token in case it expired.We’ve select for the workflow to start with Branch 1. | |
40 | Call a web service | We use the “Call a web service” action to make a Microsoft Graph API call. The configuration as shown pretty much explains itself. | |
50 | Query JSON | This is just the action to parse the returned content JSON for the error message if there is, and save the message to the errorCode variable | |
60 | Branch by condition | I am using the Branch by condition action again to check if the errorCode variable equals to the suspected text of “Access token has expired.” | |
70 | Change stage | If the answer is no, will set the next stage to exit the branch, | |
80 | Change stage | or else, we will set the stage to Branch 2 to execute refresh access token | |
90 | Call a web service | This is the action to call the microsoftonline login service to obtain a new/refreshed Access Token. | |
100 | Change stage | Once we got the new Access Token, the Change stage is set to run the Branch 1 to continue the Microsoft Graph API call for creating the new user in Azure Active Directory. |
Note: All the unnumbered actions are used for logging and troubleshooting purpose, you may ignore them.
Add Azure Active Directory App from Azure Portal
As of now, we understand that the OAuth 2.0 involves steps to obtain Credentials, and obtaining of the Access Token to be used in the API calls. In this section of write up, we will look at how to setup a service or daemon app in Azure portal. Unlike client apps, NWC workflows involves workflow actions without user intervention, the service or daemon app will be used to implement the OAuth 2.0 Client Credentials Grant Flow with its own credentials, its client ID and an application key, to authenticate when calling the Microsoft Graph instead of impersonating a user.
You may follow the steps from Microsoft Graph – Documentation – Register your application to register applications. Here are steps I followed creating the application I need for the NWC workflow:
1. Login to Azure Portal and navigate to App Registration to add/register a new application in Azure.
2. Fill up the application details, example as shown
3. Take note of the Application ID, which is the Client ID of the application. Select the Key link to create a key in the following step.
4. Create a key for the application by filling the Key Description, select a duration of the key. Take note of the Key’s value (i.e. will be generated after you clicked to Save) which will be used as the Client Secret for obtaining the Access Token.
Here is the screen shows the key with value after you hit the Save
5. The application is setup, we will need to grant it Required permissions to services we going to access via the application. Click the Add button to add Required permissions to the app.
6. Select services/applications you want to give the permissions to the application, or search for other applications with Service Principal name from the search box. I have selected Microsoft Graph as I am going to use Microsoft Graph API to query or update user object in the Azure Active Directory.
Depending on what API you will need for the Azure AD App to access, in my other example below, I have selected “Office 365 SharePoint Online (Microsoft.SharePoint)” for my test to trigger Nintex Workflow in Sharepoint Online.
Take note of the next screen as shown below, on the “Application permissions” selection, some of the application permissions “REQUIRES ADMIN” right, to which you will need to make sure using the right login for granting access to the resources at the later stage
With the above steps, we have got the Client ID, Client Secret, and we have granted permissions to the newly registered app access to Microsoft Graph API for actions we going to perform. We may proceed to obtain the Access Token to be used for the Microsoft Graph API calls.
Obtaining the Access Token
You may follow steps in Microsoft Graph – Documentation – Azure AD to obtain the required Access Token. Here are the steps I followed to obtain the Access Token.
1. Authorizing an app by submitting an HTTPS GET request using the following URL. I was doing this with an New Incognito Window of Chrome Browser, I find it quite safe to do it over the Incognito browser or you might not get the login and prompt to authorize app, this is due to my normal browser has all the login or cookies cached.
2. You should be getting Microsoft Online login page as shown. Once you login, you will need to accept the request to grant permission to the app. Please take note on what login you are going to use here, if the access to resources require an Admin rights, you will need to make sure you sign in with a user with Admin rights.
You will get below example after you have login, take note of my example here which says “You’re signed in as: kkg@ntxte07.com (admin)” – tells I have admin right to my SharePoint online.
3. Extract the code parameter value from the response URL, we will need to use it to acquire the initial OAuth 2.0 access and refresh tokens in the following steps. Take note to take the code=… portion only as the URL is ended with the session_state=…)
4. Obtain the access token using the following POST request:
POST https://login.microsoftonline.com/common/oauth2/token HTTP/1.1content-type : application/x-www-form-urlencoded content-length : 144
This request requires a URL-encoded payload of the following format:
grant_type=authorization_code&redirect_uri=<uri>&client_id=<id>&client_secret=<secret_key>&code=<code>&resource=https%3A%2F%2Fgraph.microsoft.com%2F
I was using the Advanced REST Client chrome extension for the purpose, you may use your own preferences for the purpose.
Take note for the above example, which is using the “&resource=https%3A%2F%2Fgraph.microsoft.com%2F” for the “Raw Payload“, you will need to specify different string for the &resource=<resource> depending what resource the app is meant to access. If the resource is Sharepoint Online, the example of the &resource=<resource> will be (e.g. &resource=https%3A%2F%2Fntxte07.sharepoint.com@225f091c-3a64-4a82-96be-2cced10088d4) the text after the @ mark is the Site Realm, you can find it by this URL to your sharepoint site https://<tenant>.sharepoint.com/_layouts/15/appprincipals.aspx, it is shown as e.g. i:0i.t|ms.sp.int|ba179145-f946-42c6-8aeb-90d6957af1fe@225f091c-3a64-4a82-96be-2cced10088d4 under the “App Identifier” column).
5. You should get a 200 OK response with response body containing the Access Token, Refresh Token, and others. We will need to supply the Access Token to any ensuing HTTP requests to access Microsoft Graph resources, or the Refresh Token for obtaining new Access Token when it expired.
Here is the example of the response screen of the “Advanced REST Client” with 200 response code, you will need to scroll down to see the JSON content which consists of the “Access Token” and “Refresh Token”
Using OAuth 2.0 to access other cloud services from NWC
Automating business processes on a cloud environment such as Nintex Workflow Cloud (a.k.a NWC) rely heavily on its “connectors” the workflow platform is offering. This could be a connection for an event trigger, reading or writing to an environment that is external to the NWC. In the event if the connector has not yet made available, one could still connect to external environment such as Google, Facebook, Microsoft, etc. via the “call a web service” workflow action. For any platform on a cloud environment to connect to other cloud services, gaining authentication and access to other cloud services becoming critical before one service could communicate to the others. This is where the OAuth becoming common open standard for authorization among the cloud services.
This post provides a step by step sample configuration on how to get the NWC access the REST API of google drive using the OAuth 2.0 authentication.
Before we get into the required setup, let us take a look at a sample endpoint of the Google Drive API, we will look at one fairly straight forward endpoint to start – drive.files. The REST API call might look like the following, though you’ll need to specify your own access token:
GET /drive/v2/files HTTP/1.1Authorization: Bearer ya29.Ci9_A_SXTtG9QpxFS2gqKqHbAhX3QI68AHhTTl4vboNz_hItW2YYzy53FrdoRfBGGQHost: googleapis.com
The Oauth “access_token” to be provided following the “Authentication: Bearer” in this call is the required to gain access to the resources. (Note: A complete OAuth 2.0 steps include Obtain OAuth 2.0 credentials, Obtain access token, sending of access token to API, and refresh access token if necessary could be referenced from Google’s Oauth 2.0 Overview)
As access token to an application has limited life time, the automated process will need to verify if an access token has expired, and will need to refresh the access token with the Refresh Token obtained. Here is how the actual request might look like to exchange or refresh for an Access Token:
HOST /oauth2/v4/token HTTP/1.1Host: www.googleapis.comContent-Type: application/x-www-form-urlencodedclient_id={client_id}&client_secret={client_secret}&refresh_token={refresh_token}&grant_type=refresh_token
Calling Drive API from NWC
The diagram below is a sample NWC workflow I’ve created to call the Google Drive API, I will explain each of the workflow action and its configuration in the following table:
Action Number | Action Name | Description | Sample Configuration |
---|---|---|---|
10 | Set a variable value | Create and set the variable to the Access Token (please follow following sections on how to obtain Access Token and Refresh Token). This variable will be used and included in the API call later. | |
20 | Branch by stage | Defining two branches, one for the normal API call, the other to Refresh Token in case it expired. | |
30 | Call a web service | Calling the Google Drive API endpoint, in this example, I am calling the /drive/v2/files endpoint to my folderID.Request Headers to be included here is the Authorization with format shown:Authorization: Bearer <Access Token>The Response Content will have below error if the Access Token has expired: { “error”: { “errors”: [ { “domain”: “global”, “reason”: “authError”, “message”: “Invalid Credentials”, “locationType”: “header”, “location”: “Authorization” } ], “code”: 401, “message”: “Invalid Credentials” } } | |
40 | Query JSON | In my example, I am using Query JSON to parse or return the “message” of the suspected returned JSON content, which should be the “Invalid Credentials” value from the sample JSON content returned in the workflow action before this. | |
50 | Branch by condition | The “Branch by condition” action to verify if the returned Response Content has the access related error(s). I should be checking against the Error code instead of the Error Message, unfortunately there is a bug the time this workflow is being tested, if I check against the $.error.code with the “equals” operator against value 401. | |
80 | Call a web service | When “Refresh Token” branch is called, we will need to Refresh the Access Token using the Refresh Token obtained in previous steps.Please refer to the Using a refresh token section of the Using OAuth 2.0 for Web Server Applications guide for more details on the Refresh Token API. | |
90 | Query JSON | Parse the returned JSON with Query JSON action and assigned the refreshed Token to the variable to be used by the API Call. |
Obtain OAuth 2.0 credentials from the Google API Console
1. Select Create project link from the Project Menu
2. Give your project a Project Name and fill up other details as necessary
3. On the Credentials page, create an Oauth client ID credential.
4. Fill in the requested information on the Configure consent screen page, and click Save to return to the Credential screen.
5. Select an Application type (i.e. Other in my example), and provide any additional information if required.
6. Click Create
7. The result of this process will be a Credential with Client ID and Client Secret as shown.
Obtain an access token from the Google Authorization Server
- One of the options to get the Access Token is via the OAuth2 Playground at this link, where we will obtain the access token and refresh token.
- Click the gear icon in the upper right corner and check the box labelled Use your own OAuth credentials (if it isn’t already checked).
- Enter the OAuth2 client ID and OAuth2 client secret obtained in previous step.
4. In the section labelled Step 1- Select & authorize APIs, select APIs you would like to access from NWC (i.e. I have selected https://www.googlezpis.com/auth/drive in my case).
5. You will be prompted to grant access and authorization
6. In the tab labelled Step 2 – Exchange authorization code for tokens. Click Exchange authorization code for tokens.
7. This will get you the Refresh token and Access Token.
8. Copy the Refresh token and Access Token, which we## will need it in the API call in NWC workflow.
Displaying a Process Timeline Diagram in Nintex Form
Following my previous post on Displaying a Process Flow Diagram in Nintex Form that I’ve shared how to include a JavaScript based Process Flow Diagram in Nintex Form. In this post, I am going to show how to include a “Timeline” diagram in Nintex Form using just pure CSS technique. As it uses just CSS, the Timeline will be viewable even in Nintex Mobile App. The outcome of this exercise, you will get to see the below form when form is being filled (i.e. New), and middle of the process timeline to show steps that were completed and yet to be completed.
The “Timeline” is presented by simply a CSS formatted HTML list as shown in the HTML code below, which in my example I have a total of 4 list items (i.e. <li>) to display a 4 process stages timeline.
<ul class="timeline" id="timeline">
<li class="li" id=""Request Submitted">
<div class="process">
<span class="name">Request Submitted</span>
</div>
<div class="status">
<span class="author">Initiator</span>
<span class="date">06/06/2016</span>
</div>
</li>
<li class="li" id="Manager Approval">
<div class="process">
<span class="name">Manager Approval</span>
</div>
<div class="status">
<span class="author">Manager</span>
<span class="date">06/06/2016</span>
</div>
</li>
<li class="li" id="Finance Approval">
<div class="process">
<span class="name">Finance Approval</span>
</div>
<div class="status">
<span class="author">Finance Manager</span>
<span class="date">06/06/2016</span>
</div>
</li>
<li class="li" id="Request Processed">
<div class="process">
<span class="name">Procurement</span>
</div>
<div class="status">
<span class="author">Proc Admin</span>
<span class="date">TBD</span>
</div>
</li>
</ul>
For the exercise, I have created a Sharepoint Custom List with just two columns (i.e. Title as Single line of text, and Timeline as Multiple lines of text). Timeline column is used to store the Timeline HTML source shown in the figure above. At each process stage, the Workflow will update the Timeline data to indicate if a process stage is “Complete“, the “author” value is updated with the person who performed/completed the process stage, and the “date” of the process stage being completed.
Create a Nintex Form to be used as the Sharepoint List form of the custom list. Arrange the defaulted two controls (i.e. Title and Timeline) so that it looks similar to the diagram below. drag and drop a “Multi Line Textbox” to the red dotted box (i.e. on top of the “Timeline” list column control) as shown in the diagram below. The red dotted box area should have two form controls (i.e. Timeline and Multi Line Textbox), one on top of the other.
Configure the “Multi Lines Textbox” by copying the Timeline HTML code shown in the previous diagram to the Default value of the control, and configure the Appearance – Visible with value as shown in diagram below. The formula of the Appearance – Visible property is to show the “Multi Line Textbox” control when the List Column “Timeline” is empty or null.
Open the List Column “Timeline” control to add the Appearance – Visible property as shown in diagram below. The formula used in Appearance – Visible property is to show the “Timeline” list column control if it’s not empty or null.
We will need to include our custom CSS to format the Timeline to the format we want. Here is the CSS code I used in my example
.timeline {
list-style-type: none;
display: flex;
align-items: center;
justify-content: center;
}
.li {
transition: all 100ms ease-in;
}
.process {
margin-bottom: 5px;
padding: 10px 10px;
display: flex;
align-items: center;
font-size: 12px;
font-weight: 100;
}
.status {
padding: 15px 10px;
display: flex;
align-items: center;
flex-direction: column;
border-top: 2px solid #CFD2D5;
position: relative;
transition: all 100ms ease-in;
}
.status .author {
font-weight: 200;
}
.status .date {
font-weight: 200;
font-size: 12px;
}
.status:before {
content: "";
width: 25px;
height: 25px;
background-color: white;
border-radius: 25px;
border: 1px solid #8A8A8A;
position: absolute;
top: -15px;;
left: 42%;
transition: all 100ms ease-in;
}
.li.complete .status {
border-top: 2px solid #22BB22;
}
.li.complete .status:before {
background-color: #22BB22;
border: none;
transition: all 100ms ease-in;
}
.li.complete .status .author {
color: #22BB22;
}
.li.complete .status .date {
color: #22BB22;
}
I have appended the code directly to the “Custom CSS” settings of the form as shown below
Now that we are done with the form design. In order to show the timeline with completion/timeline status, we will need to include actions in our workflow to update timeline data reflecting the status of the process stage(s)/timeline. I have attached my sample workflow for reference purposes, which carries the following summarised key actions.
1. Query the “Multi Line of Textbox” control value from Nintex Form.
As the “Timeline” list column control of type multiple lines of text does not allow us to set default value in form, we have introduced the “Multi Line of Textbox” form control without it “Connected” to the “Timeline” list column. We then perform this action (i.e. Query XML) to query the Nintex Form data, so we could save it to the Timeline list column in the workflow. Diagram below shows the configuration of the Query XML action, to which it queries the “/FormVariable/Timeline” and store the result to the workflow variable – “TimelineXML“.
2. State Machine branch – “Decode TimelineXML”
The “Decode TimelineXML” state machine branch performs few actions to decode the TimelineXML data that was encoded, and saved the decoded HTML code back to TimelineXML variable. The outcoe of the actions are being logged in attached workflow, diagram below shows the my workflow performed actions to extract the Timeline data from the NFFormData, and the extracted Timeline data in encoded format, and the outcome of the “Encode TimelineXML” steps.
3. State machine branch – “Extract Timeline”
Performs actions to extract the four process stages list item represented by <li> tag in the html code. The 4 list items will be saved to “ProcessState1“, “ProcessState2“, “ProcessState3“, and “ProcessState4” workflow variables respectively. With the extracted states data save in the workflow variables, we can update the data at different stages of the workflow. This is done by four “Query XML” actions, each to extract the <li> tag of each process timeline. the configuration of the Query XML is shown below.
4. The remaining “State 1” to “State 4” branches.
The remaining 4 branches in my example shows how the data of the “ProcessState1”, “ProcessState2, “ProcessState3”, and “ProcessState4” could be updated to reflect the status of the state. Once a “State” is updated, the reconstruction of the “TimelineXML” could be done by the “Update XML” action to reconstruct the content by replacing it with the updated workflow variables (i.e. ProcessState1, ProcessState2, ProcessState3, and ProcessState4). The “Update XML” configuration is shown below.
We concluded these branches’ with the “Set Field in Current Item” action to set/update the “Timeline” list column with the newly constructed “TimelineXML” value. As “Timeline” list column is now updated with data highlighting the status of the timeline, the Form will now show it’s content instead of the initialized “Timeline” form control data. (i.e. defaulted timeline data).
Displaying a Process Flow Diagram in Nintex Form
As named, if you would like to include a high level “process flow diagram” on a form, here is my sharing on direction and quick start to do that. The concept and requirement I got here is a high level process state diagram of the document/process flow, showing Past, Current, and Future state of the business process on the form. The diagram below shows the sample outcomes of the form on Office 365 with a Process Flow/State Diagram in the top banner of the form.
The solution is accomplished by flowchart.js Javascript Library which is based on Raphaël—JavaScript Library. It draws the “Process Flow Diagram” on the fly when the Form is open for viewing or editing. The data of the diagram is saved as “Multiple lines of text” column of the Sharepoint list, as such it could be updated by Nintex workflow action(s) to change the state of the flowchart when needed. I have named my “Multiple lines of text” in my example as “WorkflowStatus“, which is linked to Nintex Form’s “Multi line textbox” as shown in the below diagram.
The two Javascript libraries (http://github.com/DmitryBaranovskiy/raphael/raw/master/raphael-min.js , http://flowchart.js.org/flowchart-latest.js ) to be included in the Form settings as shown here.
Include the following “Custom Javascript” to the “Form Settings“. Line 3 to 10 in the script defines the diagram to be displayed. Line 37 to line 73 are attributes of the diagram allowing you to define the attributes such as color code, line length, etc. of the diagram. (i.e. you may refer to flowchart.js on all the available settings).
NWF$(document).ready(function(){
var code = 'st=>start: Start\n'
+ 'e=>end\n'
+ 'op1=>operation: Manager Approval\n'
+ 'op2=>operation: Finance Approval\n'
+ 'op3=>operation: Procurement\n'
+ 'st(right)->op1(right)->op2(right)->op3(right)->e\n'
;
if (NWF$('#'+workflowstatus).val()==''){
NWF$('#'+workflowstatus).val(code);
}
NWF$("div.nf-filler-control[data-controlname^='WorkflowStatus']").add("div.nf-filler-control[data-controlname^='workflowstatus']").each(function () {
var codeUI = NWF$(this).find("div.nf-filler-control-inner");
var codeO365View = NWF$(codeUI).find("div");
var codeO365Edit = NWF$(codeUI).find("textarea");
var codeOnPrem = NWF$(codeUI).find("input[type=text]");
var codeContent;
if (codeOnPrem.length != 0) {
codeContent = codeOnPrem.val();
} else if (typeof codeO365Edit.val()== 'undefined') {
codeContent = codeO365View.html().replace(/<br>/g,'').replace(/>/g,'>');
} else {
codeContent = codeO365Edit.val()
}
//codeContent = ((typeof codeO365Edit.val()== 'undefined')? codeO365View.html().replace(/<br>/g,'').replace(/>/g,'>'): codeO365Edit.val());
codeUI.css('visibility','hidden');
codeUI.after('<div id="canvas"></div>');
codeContent = ((codeContent == '') ? code : codeContent);
var chart = flowchart.parse(codeContent);
chart.drawSVG('canvas', {
// 'x': 30,
// 'y': 50,
'line-width': 3,
'line-length': 25,
'text-margin': 5,
'font-size': 14,
'font': 'normal',
'font-family': 'Helvetica',
'font-weight': 'normal',
'font-color': 'black',
'line-color': 'black',
'element-color': 'black',
'fill': 'white',
'yes-text': 'yes',
'no-text': 'no',
'arrow-end': 'block',
'scale': 1,
'symbols': {
'start': {
'font-color': 'red',
'element-color': 'green',
'fill': 'yellow'
},
'end':{
'class': 'end-element'
}
},
'flowstate' : {
'past' : { 'fill' : '#CCCCCC', 'font-size' : 12},
'current' : {'fill' : 'yellow', 'font-color' : 'red', 'font-weight' : 'bold'},
'future' : { 'fill' : '#FFFF99'},
'request' : { 'fill' : 'blue'},
'invalid': {'fill' : '#444444'},
'approved' : { 'fill' : '#58C4A3', 'font-size' : 12, 'yes-text' : 'yes', 'no-text' : 'no' },
'rejected' : { 'fill' : '#C45879', 'font-size' : 12, 'yes-text' : 'n/a', 'no-text' : 'REJECTED' }
}
});
});
});
As my actual workflow is performing a series of complex workflow actions, I do not expect the business users to visit for details such as which state it is executing, I include the following sample workflow actions to update the “Multiple lines of text” item property which will reflect the “Process Flow Diagram” on the Form for business users.
I am leaving the update of the “Multiple lines of text” open as I believe it could be or should be done in a better approach than my sample here….