Introduction
This tip will help developers to find the list of all suspended/errored list/site workflows (Nintex/Designer) in a single page, also list down the inventory of workflows with basic details. I have tested the below solution in SharePoint, but I feel that this should work in SharePoint 2013/2016/2019.
Background
Generally, users need to go to each list item to identify the suspended workflows, it is time consuming and also difficult to identify issues.
Using the Code
I am using SharePoint JSOM and Angularjs with the help of content editor webpart to display the data in table format.
Steps
- Create an empty HTML page and add the below HTML.
- Update js references:
<html>
<script type="text/javascript"
src="/_layouts/15/sp.runtime.js"></script>
<script type="text/javascript"
src="/_layouts/15/sp.js"></script>
<script src="/_layouts/15/sp.workflowservices.js"></script>
<script type="text/javascript"
src="SiteURL/SiteAssets/JS/angular1.7.5.min.js"></script>
<script type="text/javascript"
src="SiteURL/SiteAssets/JS/angular-route.min.js"></script>
<script type="text/javascript"
src="SiteURL/SiteAssets/JS/jquery-3.2.1.min.js"></script>
<script type="text/javascript"
src="SiteURL/SiteAssets/JS/moment.js"></script>
<style>
.ms-webpartPage-root{
border-spacing:2px;
}
</style>
<body >
<div ng-app="WFApp" ng-controller="myCtrl">
<h3 style="
color: blue;">List Workflow Errored in last {{QueryPastDays}} days</h3>
<div id="ProgressDiv">Please wait...</div>
<div style="padding:5px">Filter by Status:
<input ng-model="SearchStatus"
style=""></div>
<table style="margin:10px"
border="1" class="table table-bordered">
<tr>
<td style="color:black;font-weight:bold;
padding:5px;padding:5px">List Name</td>
<td style="color:black;font-weight:bold;
padding:5px">WF Name</td>
<td style="color:black;font-weight:bold;
padding:5px">Item ID</td>
<td style="color:black;font-weight:bold;
padding:5px">Title</td>
<td style="color:black;font-weight:bold;
padding:5px">Status Code</td>
<td style="color:black;font-weight:bold;
padding:5px">Status</td>
<td style="color:black;font-weight:bold;
padding:5px">Item Modified</td>
<td style="color:black;font-weight:bold;
padding:5px">Workflow Link</td>
<td style="color:black;font-weight:bold;
padding:5px">Workflow Instance Link</td>
<td style="color:black;font-weight:bold">ErrorDetails</td>
<tr ng-repeat="x in WFWithListNames | orderBy:'-Modified' |
filter:{ StatusName:SearchStatus || undefined}">
<td>{{ x.ListName}}</td>
<td>{{ x.WFName}}</td>
<td><a href="{{x.ItemLink}}"
target="_blank">{{ x.ItemId}}</a></td>
<td>{{ x.Title}}</td>
<td>{{x.Status}}</td>
<td><span ng-show="x.StatusName=='Suspended'"
style="color:red">{{x.StatusName}}</span>
<span ng-show="x.StatusName!='Suspended'">
{{x.StatusName}}</span></td>
<td>{{x.Modified | date : "dd-MMM-y h:mm a" }}</td>
<td><a href="{{x.WorkflowLink}}"
target="_blank">Click here</a></td>
<td><a href="{{x.WorkflowInstanceLink}}"
target="_blank">{{x.InstanceID}}</a></td>
<td>{{x.ErrorDetails}}</td>
</tr>
</table>
<h3 style=" color: blue;">Site Workflow Errored as of Today
<a href="{{SiteWorflowLink}}"
target="_blank">(Show all)</a></h3>
<table style="margin:10px"
border="1" class="table table-bordered">
<tr>
<td style="color:black;font-weight:bold;padding:5px">WF Id</td>
<td style="color:black;font-weight:bold;padding:5px">WF Name</td>
<td style="color:black;font-weight:bold;padding:5px">Status Code</td>
<td style="color:black;font-weight:bold;padding:5px">Status</td>
<td style="color:black;font-weight:bold;padding:5px">Last Run</td>
<td style="color:black;font-weight:bold;padding:5px">Workflow Instance Link</td>
<td style="color:black;font-weight:bold">ErrorDetails</td>
<tr ng-repeat="x in SiteWorkflowInstances | orderBy:'-LastRun' |
filter:{ StatusName:SearchStatus || undefined}">
<td>{{ x.WFId}}</td>
<td>{{ x.WFName}}</td>
<td>{{x.StatusCode}}</td>
<td><span ng-show="x.StatusName=='Suspended'"
style="color:red">{{x.StatusName}}</span>
<span ng-show="x.StatusName!='Suspended'">
{{x.StatusName}}</span></td>
<td>{{x.LastRun | date : "dd-MMM-y h:mm a" }}</td>
<td><a href="{{x.WorkflowInstanceLink}}"
target="_blank">{{x.InstanceID}}</a></td>
<td>{{x.ErrorDetails}}</td>
</tr>
</table>
<h3 style="color:blue">Workflow Inventory</h3>
<table style="margin:10px" border="1" class="table table-bordered">
<tr>
<td style="color:black;font-weight:bold;padding:5px">WF Name</td>
<td style="color:black;font-weight:bold;padding:5px">WF Desc</td>
<td style="color:black;font-weight:bold;padding:5px">WF ID</td>
<td style="color:black;font-weight:bold;padding:5px">Published</td>
<td style="color:black;font-weight:bold;padding:5px">WF Status</td>
<td style="color:black;font-weight:bold;padding:5px">RestrictToType</td>
<td style="color:black;font-weight:bold;padding:5px">ModifiedBy</td>
<td style="color:black;font-weight:bold;padding:5px">RestrictToScope</td>
<td style="color:black;font-weight:bold;padding:5px">ListName</td>
<td style="color:black;font-weight:bold;padding:5px">AppAuthor</td>
<td style="color:black;font-weight:bold;padding:5px">SubId</td>
</tr>
<tr ng-repeat="x in WFInstances |
orderBy:['-RestrictToType','-WorkflowEntitlementType']">
<td>{{ x.WFName}}</td>
<td>{{ x.WFDesc}}</td>
<td>{{x.WFId}}</td>
<td>{{x.Published}}</td>
<td>{{x.WorkflowEntitlementType}}</td>
<td>{{x.RestrictToType}}</td>
<td>{{x.ModifiedBy}}</td>
<td>{{x.RestrictToScope}}</td>
<td>{{x.ListName}}</td>
<td>{{x.AppAuthor}}</td>
<td>{{x.SubId}}</td>
</tr>
</table>
</div>
</body>
</html>
JavaScript - You can put the below JavaScript in the same page or add a separate js file and provide reference in your HTML.
<script>
var app = angular.module("WFApp", []);
app.controller("myCtrl", function($scope,$timeout) {
$scope.WFInstances= [];
$scope.WFWithListNames= [];
$scope.ProgressDiv=true;
$scope.QueryPastDays= GetQueryStringParams('QueryPastDays');
if(!$scope.QueryPastDays)
{
$scope.QueryPastDays=30;
}
$scope.SiteWorflowLink = _spPageContextInfo.webAbsoluteUrl +
"/_layouts/15/workflow.aspx?ShowAll=1";
var WorkflowStatus =
{
0 : "NotStarted",
1 : "Started",
2 : "Suspended",
3 : "Canceling",
4 : "Canceled",
5 : "Terminated",
6 : "Completed",
7 : "NotSpecified",
8 : "Invalid"
};
var context = new SP.ClientContext.get_current();
var web = context.get_web();
var sMgr =new SP.WorkflowServices.WorkflowServicesManager(context, web);
var wDefs = sMgr.getWorkflowDeploymentService().enumerateDefinitions();
context.load(wDefs,'Include(DisplayName, Description,
Id,Published,AssociationUrl,Properties)');
context.executeQueryAsync(
function(){
var WFInstances=[];
var SiteWFInstances=[];
var e = wDefs.getEnumerator();
while (e.moveNext()) {
var def = e.get_current();
var data={};
data.WFName=def.get_displayName();
data.WFDesc=def.get_description()
data.WFId=def.get_id();
data.Published=def.get_published()
data.URL=def.get_associationUrl();
data.ModifiedBy=def.get_properties().vti_modifiedby.split
('i:0#.f|membership|')[1];
data.AppAuthor=def.get_properties().AppAuthor;
data.SubId=def.get_properties().SubscriptionId;
data.RestrictToScope=def.get_properties().RestrictToScope;
data.RestrictToType=def.get_properties().RestrictToType;
if(def.get_properties()['NWConfig.WorkflowEntitlementType']){
data.WorkflowEntitlementType=def.get_properties()
['NWConfig.WorkflowEntitlementType'];
}
else {
data.WorkflowEntitlementType="N/A";
}
if(data.RestrictToScope){
data.ListName= GetListName(data.RestrictToScope);
}
else {
data.ListName= "N/A";
}
if(data.WFName=="TestSiteWF" || data.WFName=="MovementRequestWF"){
}
if(data.RestrictToType){
WFInstances.push(data);
if(data.RestrictToType=="Site"){
SiteWFInstances.push(data);
}
}
}
$scope.WFInstances=WFInstances;
$scope.GetSiteWFInstances(SiteWFInstances)
var AllItems=[];
$.each(WFInstances, function(index, dataRec) {
if(dataRec.ListName && dataRec.ListName!="N/A")
{
var filter = "Modified ge '" + new moment().subtract
($scope.QueryPastDays,'days').format('YYYY-MM-DD') + "T00:00:00.000Z'";
var GetListData= GetListItemByList
(dataRec.ListName,"ID,Modified,Title", filter);
if(GetListData.length>0)
{
var WFWithListNames=[];
$.each(GetListData, function(indexitem, item) {
var workflowServicesManager =
SP.WorkflowServices.WorkflowServicesManager.newObject(context, web);
var workflowSubscriptionService =
workflowServicesManager.getWorkflowSubscriptionService();
var workflowInstanceService =
workflowServicesManager.getWorkflowInstanceService();
var wfInstances =
workflowInstanceService.enumerateInstancesForListItem
(dataRec.RestrictToScope, item.ID);
context.load(wfInstances,'Include
(Status, Id,FaultInfo,WorkflowSubscriptionId)');
window.count = 0;
context.executeQueryAsync(
function(){
var instancesEnum = wfInstances.getEnumerator();
while (instancesEnum.moveNext()) {
if(instancesEnum.get_current().
get_workflowSubscriptionId()==dataRec.SubId)
{
var WFStatusCode = instancesEnum.get_current().
get_status();
var ErrorDetails=instancesEnum.get_current().
get_faultInfo();
var InstanceID=instancesEnum.get_current().get_id();
var InstanceURL=_spPageContextInfo.webAbsoluteUrl +
"/_layouts/15/wrkstat.aspx?List={" +
dataRec.RestrictToScope + "&WorkflowInstanceName=" +
InstanceID;
var ItemLink=_spPageContextInfo.webAbsoluteUrl +
"/Lists/" + dataRec.ListName +
"/DispForm.aspx?ID=" + item.ID;
var IntanceSubscriptionID=
instancesEnum.get_current().
get_workflowSubscriptionId();
var WFInstanceData={};
WFInstanceData.ListName=dataRec.ListName;
WFInstanceData.ItemId=item.ID;
WFInstanceData.Title=item.Title;
WFInstanceData.Modified=item.Modified;
WFInstanceData.Status=WFStatusCode;
WFInstanceData.StatusName=WorkflowStatus[WFStatusCode];
WFInstanceData.WFName=dataRec.WFName;
WFInstanceData.WorkflowLink=
_spPageContextInfo.webAbsoluteUrl +
"/_layouts/15/workflow.aspx?List={" +
dataRec.RestrictToScope + "}&ID=" + item.ID;
WFInstanceData.WorkflowInstanceLink=InstanceURL;
WFInstanceData.InstanceID=InstanceID;
WFInstanceData.ErrorDetails=ErrorDetails;
WFInstanceData.ItemLink=ItemLink;
if(WFStatusCode>1 && WFStatusCode!=6)
{
AllItems.push(WFInstanceData);
$scope.WFWithListNames=AllItems;
$scope.$apply();
}
}
}
},
function(a,b){
alert("Error happened when trying to get
workflow subscriptions for list");
console.error(message);
}
);
});
}
}
});
$scope.$apply();
},
function(a,b){
alert(args.get_message());
}
);
$scope.GetSiteWFInstances = function(SiteWFInstances)
{
$scope.SiteWorkflowInstances=[];
var workflowServicesManager =
new SP.WorkflowServices.WorkflowServicesManager(context, web);
var workflowInstanceService =
workflowServicesManager.getWorkflowInstanceService();
var workflowInstances = workflowInstanceService.enumerateInstancesForSite();
context.load(workflowInstances);
context.executeQueryAsync(function (sender, args) {
var definitionsEnum = workflowInstances.getEnumerator();
while (definitionsEnum.moveNext()) {
var def = definitionsEnum.get_current();
var SiteInstance={};
SiteInstance.WFId= def.get_workflowSubscriptionId();
$.each(SiteWFInstances, function(indexitem, item) {
if(SiteInstance.WFId==item.SubId) {
SiteInstance.WFName= item.WFName;
}
});
SiteInstance.InstanceID=def.get_id();
SiteInstance.ErrorDetails=def.get_faultInfo();
SiteInstance.LastRun=def.get_instanceCreated();
SiteInstance.WorkflowInstanceLink=_spPageContextInfo.webAbsoluteUrl +
"/_layouts/15/wrkstat.aspx?WorkflowInstanceName=" + def.get_id();
SiteInstance.StatusCode= def.get_status();
SiteInstance.StatusName= WorkflowStatus[SiteInstance.StatusCode];
if(SiteInstance.StatusCode>1 && SiteInstance.StatusCode!=6) {
$scope.SiteWorkflowInstances.push(SiteInstance);
}
$scope.$apply();
" Subsc: " + def.get_workflowSubscriptionId()
" Status: " + def.get_status() + " Stage: " + def.get_userStatus());
}
}, function (sender, args) {
alert('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
});
}
$('#ProgressDiv').hide();
$scope.ProgressDiv=false;
});
function GetListItemByList(ListTitle, FieldTitle, filter, link) {
if (link) {
SiteLink = link;
} else {
SiteLink = _spPageContextInfo.webAbsoluteUrl;
}
var DataList = [];
if (FieldTitle) {
var URL = SiteLink + "/_vti_bin/client.svc/web/lists/getByTitle
('" + ListTitle + "')/Items?$top=5000&$select=" + FieldTitle;
} else {
var URL = SiteURL + "/_vti_bin/client.svc/web/lists/getByTitle
('" + ListTitle + "')/Items?$top=5000&$select= *";
}
if (filter) {
URL += "&$filter=" + filter;
}
$.ajax({
url: URL,
type: "GET",
async: false,
headers: {
"accept": "application/json;odata=verbose",
},
success: function(data) {
DataList = data.d.results;
},
error: function(error) {
alert(JSON.stringify(error));
}
});
return DataList;
}
function GetListName(guid)
{
var ListTitle="";
$.ajax({
url: _spPageContextInfo.webAbsoluteUrl +
"/_api/web/lists('" + guid + "')",
method: "GET",
async:false,
headers: { "Accept": "application/json; odata=verbose" },
success: function (data) {
ListTitle=data.d.Title;
},
error: function (data) {
}
});
return ListTitle;
}
function GetQueryStringParams(sParam) {
var sPageURL = window.location.search.substring(1);
var sURLVariables = sPageURL.split('&');
for (var i = 0; i < sURLVariables.length; i++) {
var sParameterName = sURLVariables[i].split('=');
if (sParameterName[0] == sParam) {
return sParameterName[1];
}
}
}
</script>
I have attached the HTML file with HTML and JS, just update the JS references and add CEWP to display the report.
History
- 27th January, 2020: Initial version