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
I am having around 8 years of experience in SharePoint development, mostly on Client side coding using Angularjs, JQuery, ReactJs. Experience in Nintex forms and workflows for SharePoint online (O365). Worked in different domains like Banking, Automotive, Oil & Gas etc.Now Working on SPFx in SharePoint online using React and SharePoint modern experience.