vacoder

Display Workflow History on Task List

Blog Post created by vacoder on Feb 25, 2017

We use a third party workflow solution as part of our core client offering. One of our clients recently asked that they be able to see the previous assignee on a task that has been delegated or escalated to them. The click path (4 clicks) from the item to the correct workflow history where the information is displayed was a little too cumbersome.


Our third party workflow solution uses a custom workflow task page which is not editable so adding a web part was out of the question. So the only viable option in this case was to use a JavaScript only solution embedded in the master page. This solution allows me to deploy the solution on a client by client basis and make client specific customizations a relatively trivial task.

 

The solution uses the Nintex Web Service method GetWorkflowHistoryForListItem and parses the return data to display only the delegation information, puts it into a table (ugly stuff here) and appends it to an element at the bottom of the page that is a core piece of the providers solution.


The code consists of some variables and 4 function calls. First here are the variables I use:

 // name of the list passed to the web service
var wsList = "";
// the item id parsed from the workflowLink
var itemID="";
// the list that will be passed to the Nintex Web Service call.
var listName="";
// get the Task ID
var id = getUrlParameter('ID');
// Get the list guid
var list = getUrlParameter('List');


getUrlParameter is just a utility script I grabbed somewhere that does what the name suggests. I use it to get the taskID and the list guid from the querystring of the task page where I want the history displayed.

  function getUrlParameter(name) {
   name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
   var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
   var results = regex.exec(location.search);
   return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
  };


Once I've got the basic info I need for my function call (id and list) I'm ready to call getListInfo. This gets the item ID of the list that the workflow is running against by grabbing the Workflowlink URL and pulling off the item ID. It also pulls the name of the list, since in my solution there could be multiple lists using the same custom task form and I'll need to pass that to the Nintex web service.

function getListInfo(list) {
    var requestUri = _spPageContextInfo.webAbsoluteUrl + "/_api/web/lists(guid'" + list + "')/items("+id+")";
    var requestHeaders = { "accept": "application/json;odata=verbose" };
    $.ajax({
        url: requestUri,
        contentType: "application/json;odata=verbose",
        headers: requestHeaders,
        success: onSuccess,
        error: onError
    });
       function onSuccess(data, request) {
           RelatedContent = data.d.WorkflowLink.Url;
           var Start = RelatedContent.indexOf('Lists/')+6;
           var End = RelatedContent.indexOf('/DispForm');
           wsList = RelatedContent.slice(Start, End);
           itemID = RelatedContent.slice(RelatedContent.lastIndexOf("=")+1)
           // call the web service to get the hitory
           getWorkflowHistory(itemID, wsList)
        }

        function onError(error) {//handle error if you need to.}
}

 

The getWorkflowHistory function is what calls the GetWorkflowHistoryForListItem web service. It's soap call and it returns XML. If successful I call processResults where things get really ugly and I apologize in advance for that. 

function getWorkflowHistory(itemID, wsList){
          wsUrl =  _spPageContextInfo.webAbsoluteUrl + "/_vti_bin/NintexWorkflow/Workflow.asmx";
          var soapEnv = '<?xml version="1.0" encoding="utf-8"?>'
          + '<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:m="http://nintex.com">'
          + '  <soap:Header>'
          + '  </soap:Header>'
          + '  <soap:Body>'
          + '    <m:GetWorkflowHistoryForListItem>'
          + '      <m:itemId>'+itemID+'</m:itemId>'
          + '      <m:listName>'+wsList+'</m:listName>'
          + '      <m:stateFilter>Running</m:stateFilter>'
          + '      <m:workflowNameFilter></m:workflowNameFilter>'
          + '    </m:GetWorkflowHistoryForListItem>'
          + '  </soap:Body>'
          + '</soap:Envelope>';               

          $.ajax({
        url: wsUrl,
        type: "POST",
        dataType: "xml",
        data: soapEnv,
        complete: processResult     ,
        contentType: "text/xml; charset=\"utf-8\""
    });     
}

In processResults I parse the response xml and format the specific bits that satisfy my requirements, saving them to a variable called dataX. If there's bits in there then I append the table to a div element that is on the task page in our solution.

function processResult(xData, status) {
  //alert((xData.responseText));
  var dataX = "";
  var CompletedDateTime ="";
  var escalatesub = "No response to approval request";
  var escalatesub2 = " has not responded with approval";
  var dataHeader = '<tr><td colspan="5" valign="top">Delegation / Escalation History</td></tr>'+
                    '<tr><td style="border-width: 1px; padding: 8px; border-style: solid; border-color: #666666; background-color: #dedede;">Previous Assignee</td>' +
                    '<td style="border-width: 1px; padding: 8px; border-style: solid; border-color: #666666; background-color: #dedede;">Assigned Time</td>'+                  
                    '<td style="border-width: 1px; padding: 8px; border-style: solid; border-color: #666666; background-color: #dedede;">Outcome</td>'+
                    '<td style="border-width: 1px; padding: 8px; border-style: solid; border-color: #666666; background-color: #dedede;">Comments</td></tr>';
  if($(xData.responseText).find('GetWorkflowHistoryForListItemResult').text().length != 0){
 
     $(xData.responseText).find('GetWorkflowHistoryForListItemResult').find('WorkflowLog').find('HumanTasks').find('HumanTaskLogInfo').each(function() {
     var outcome = $(this).find('Outcome').text();
     if(outcome == 'Delegated'){
     var enteredDate = $(this).find('UserActionDateShort').text(); //assigned date
     var enteredTime = $(this).find('UserActionTimeShort').text(); //assigned time
     var user = $(this).find('DisplayName').text(); // The user assigned / performed the task action.
     var userComments = $(this).find('UserComments').text().replace('i:0#.w|','');  
     switch(outcome){                
        case 'Delegated':
       if(userComments.indexOf(escalatesub) > 0){outcome = "Auto-Escalated";}       if(userComments.indexOf(escalatesub2) > 0){outcome = "Auto-Escalated";}       break;
     case 'Pending':
     case 'Not Required':
        case 'Completed Task':
        case 'Continue':
            break;
        default:
        }
     dataX += '<tr><td style="border-width: 1px; padding: 8px; border-style: solid; border-color: #666666; background-color: #ffffff;">' + user +'</td>'+
     '<td style="border-width: 1px; padding: 8px; border-style: solid; border-color: #666666; background-color: #ffffff;">' +enteredDate + ' ' + enteredTime +'</td>'+
     '<td style="border-width: 1px; padding: 8px; border-style: solid; border-color: #666666; background-color: #ffffff;">' + outcome +'</td>'+
     '<td style="border-width: 1px; padding: 8px; border-style: solid; border-color: #666666; background-color: #ffffff; word-wrap:break-word;">' + userComments + '</td></tr>';
   }
     });
}
if(dataX.length > 0){
dataX += "<table class='WFHist' style='font-family: verdana,arial,sans-serif; font-size:11px; color:#333333; border-width: 1px; border-color: #666666; border-collapse: collapse; table-layout:fixed;'>" + dataHeader + dataX + "</table>";
if(document.getElementById('ctl00_PlaceHolderMain_invoiceFieldPanel') != null) {   
$('#ctl00_PlaceHolderMain_invoiceFieldPanel').append("<tr><td nowrap='true' valign='top'> </td><td>" +  dataX + "</td></tr>");
}}}

 

 

 

Finally, to kick it off I use the following bit.

if(list.length > 0){
getListInfo(list);
}

Outcomes