What it does:
Retrieves the workflow history items associated with a workflow instance and shows them in a Grid in InfoPath form.
How is it useful:
For a given custom workflow in SharePoint - the InfoPath task form and associated workflow history are 2 different pages. If you already have a custom task form in IP, then using this method we can display workflow history so that every approver can see what has happened so far in the workflow (with latest on the top so that they can quickly see the task history prior to their approval), and this could help in making informed decision on task approval. Additionally the dates are shown similar to SharePoint 2013 format i.e. for example:
Today at 3:45 PM
Yesterday at 12:10 PM
Day before yesterday at 12:15 PM
Sample output:
Skills needed to implement this:
SharePoint workflows, InfoPath code behind knowledge
Can it be used on ASPX task forms?
Yes, it can be. Please change the yellow highlighted lines below which are specific to InfoPath
Our User Feedback:
They love it.
How to configure (please refer the image below):
a) Select main data source in InfoPath
b) Create a group element as shown below with name HistParent
c) Under HistParent group create an element with name "hs". Data type is text and check on "repeating"
d) Remaining i.e. pos, un, oc, cm, dt are all text type and are "attributes" (not element)
e) Copy and paste the function below in code behind of your InfoPath task form.
f) Call the method from form load passing in the WorkflowInstanceId and HistoryList Id
How to pass in the above values:
f1) Assuming you are using itemMetadata.xml for the task form - create 2 new fields for
workflowInstanceId and historyListId
f2) from the custom code workflow pass in the values like
taskProperties.ExtendedProperties["workflowInstanceId"] = workflowproperties.WorkflowId.ToString("B") ; // Important to put B here since we need { } to be included in string
taskProperties.ExtendedProperties["historyListId"] = workflowProperties.HistoryList.ID.ToString() ;
Source Code (included as is - no warranties)
/// <summary>
/// Function to display Workflowhistory for a given workflow instance in reverse chronological order
/// </summary>
/// <param name="workflowId">Contains the workflow instance guid with { } in string format</param>
/// <param name="historyListId">GUID for history list without { } </param>
public void GetWorkflowHistory(string workflowId, string historyListId, string siteURL, string webRelativeURL)
{
SPList historyList = null;
// Open site collection
using (SPSite site = new SPSite(siteURL))
{
// Open web
using (SPWeb web = site.OpenWeb(webRelativeURL))
{
try
{
// Check for existence of history list
historyList = web.Lists[new Guid(historyListId)];
}
catch // History list has been deleted or renamed
{
throw new ApplicationException("History List not found");
}
SPQuery qry = new SPQuery();
qry.ViewFieldsOnly = true; // Optimize performance
qry.ViewFields = "<FieldRef Name='User'/><FieldRef Name='Occurred'/><FieldRef Name='Outcome'/><FieldRef Name='Description'/>"; // Get specific fields only - user name, date, task outcome and, task comments
// WorkflowInstance field contains the GUID of the workflow instance in history
qry.Query = string.Format("<Where><Eq><FieldRef Name=\"WorkflowInstance\"/><Value Type=\"Text\">{0}</Value></Eq></Where><OrderBy><FieldRef Name=\"ID\" Ascending='False' /></OrderBy>", workflowId);
qry.DatesInUtc = false;
SPListItemCollection coll = historyList.GetItems(qry);
SPListItem item = null;
StringBuilder sb = new StringBuilder();
string userName, outcome, comments, sDT;
DateTime dt;
SPUserCollection uColl = web.AllUsers;
SPFieldUserValue uVal;
SPFieldUser uField = historyList.Fields.GetFieldByInternalName("User") as SPFieldUser;
int count = coll.Count;
for (int ctr = 0; ctr < count; ctr++)
{
item = coll[ctr];
uVal = uField.GetFieldValue(item["User"] as string) as SPFieldUserValue; // get SPFieldUserValue object using User from history list
if (uVal != null)
{
userName = uVal.User.Name;
}
else
{
userName = "Not known";
}
outcome = item["Outcome"] as string;
dt = DateTime.Parse(Convert.ToString(item["Occurred"])); // Convert date occurred
/*The following block displays the date in SP 2013 kind of format i.e.
* when current date == Today then displays "Today at 3:45PM" (assuming time is 3:45 in occurred field).
* Similary for Yesteray and Day before yesterday.
* When none of the if are satisfied - display the Long Date string format
*/
if (dt.Date == DateTime.Today)
{
sDT = "Today at " + dt.ToShortTimeString();
}
else if (dt.AddDays(1).Date == DateTime.Today)
{
sDT = "Yesterday at " + dt.ToShortTimeString();
}
else if (dt.AddDays(2).Date == DateTime.Today)
{
sDT = "Day before yesterday at " + dt.ToShortTimeString();
}
else
{
sDT = dt.ToLongDateString();
}
comments = item["Description"] as string;
comments = (comments == null ? string.Empty : comments);
/* The assumption here is that we have fields in our InfoPath main data source with these names.
* Abbreviations are:
* pos -- position (in reverse)
* hs -- History
* un -- UserName (display name)
* oc -- Task outcome
* cm -- Comments by approving user or system
* dt -- date of history item - in reverse chronological order
*/
sb.AppendFormat("<my:hs my:pos='{0}' my:un='{1}' my:oc='{2}' my:cm='{3}' my:dt='{4}' />", count - ctr, userName, outcome, comments, sDT);
}
// If there are any history items
if (sb.Length > 1)
{
XPathNavigator nav = CreateNavigator();
// Set the generated XML to InfoPath main datasource "Group" element i.e. History Parent
nav.SelectSingleNode("/my:myFields/my:HistParent", NamespaceManager).InnerXml = sb.ToString(); }
}
}
}
Finally please let me know your feedback: