How to implement workflow in Visual Studio Team System(VSTS) – Case of Code Review
There are many occasions when we need to implement workflow in the environment of VSTS. In this article, we will be taking up a case of building a workflow for Code Review. A similar workflow has been explained with example in a CodePlex project. Refer to http://www.codeplex.com/TFSCodeReviewFlow for more details. Although I have seen that project and have liked the concept mentioned in the project, we will be working out an independent solution.
Although code review is one of the most used workflows in all the development organizations, the way each organization may implement code review is totally different. I am taking a generic scenario of code review. In this scenario I am making following assumptions:
1. Code written by programmer should not be checked in without code review. A check in policy will not allow the code to be checked in if the code is not reviewed. This needs to be a custom check-in policy.
2. Code review should be done by the authorized personnel. A custom group of users named ‘Code Reviewers’ needs to be created on TFS in the team project and authorized persons are members of this group.
3. Check in of the code should happen under the name of the programmer and not the reviewer.
4. If the programmer ‘has’ to check in the code without review, it should be permitted with record of valid reason for doing so.
Considering these assumptions, workflow for the code review can be designed as follows:
1. Programmer puts the code to be reviewed in a shelveset and notes the shelveset name.
2. If a programmer tries to check in the code without first getting it reviewed then the custom check-in policy (“Code Review Checkin policy”) will detect that and stop that check-in from proceeding forward. As in case of any check-in policy, user is allowed to override on that check-in policy with comments. In the comments that user can provide a valid reason for override.
3. The programmer now needs to inform the reviewer that some code needs to be reviewed and location of that code. The programmer can pass this information in the form of a workitem. We devise a media for passing such information in the form of a custom workitem type called “Code Review”. Programmer can select name of the reviewer for that project as well as provide information like location of the code (in the form of the shelveset name).
4. The assigned reviewer gets the workitem of the type “Code Review”. Reviewer unshelves the shelveset mentioned in the workitem and does the review of the code. Review comments if any are added in the same workitem. If the code is not approved for the check in, then status of the workitem is set to “Not approved”. For the code which is allowed to be checked in, the status is set to “Approved”. Workitem is re-assigned to the original programmer (This name can be found from the history of the workitem).
5. If the code is not approved for check-in then the programmer can make changes as suggested in review comments and submit the code for review as in step 3.
6. If the code is approved for check-in then the programmer starts the check-in process and attaches the workitem with “Approved” status to that check-in. Now the check-in policy will allow the check-in to succeed.
7. After the check-in is over, the workitem which has approved status will be modified to have “Closed” status. This will be done by an event handler written as the subscriber to the “Checkin” event.
8. Programmer can now delete the shelveset which was created for the code review purpose.
Implementation
We need to customize TFS in various ways to implement this workflow. As a preparation to actually execute the workflow we will need to create a custom group of users, a custom check-in policy, a custom workitem and custom event handler.
Custom group:
This is a group of users who can do the review. We name this group as “Code Reviewers”. This group is created for each team project which is created on the TFS. We will need to modify the process template for creating such a group in each team project. Please refer to my article on Process Template Modification in Visual Studio Team System for more details. Specifically to add a group we should edit the GroupsAndPermissions.xml in Groups and Permissions folder and add a group.
<group name="Code Reviewers" description="Members of this group can set the status of ‘Code Review’ type workitem to ‘Approved’">
- <permissions>
<permission name="GENERIC_READ" class="PROJECT" allow="true" />
<permission name="PUBLISH_TEST_RESULTS" class="PROJECT" allow="true" />
<permission name="GENERIC_READ" class="CSS_NODE" allow="true" />
<permission name="WORK_ITEM_READ" class="CSS_NODE" allow="true" />
<permission name="WORK_ITEM_WRITE" class="CSS_NODE" allow="true" />
<permission name="START_BUILD" class="PROJECT" allow="true" />
</permissions>
</group>
Custom Check-in policy:
Custom Workitem type:
Custom subscriber for eventing and notification service:
There is a need for two subscribers for the events. First one will record which users are overriding the “Code Review Check-in Policy” with reasons mentioned in the comments of the override dialogue. Code for such a service can be as follows:
C#
[WebMethod(MessageName = "Notify")]
[SoapDocumentMethod(Action = "http://schemas.microsoft.com/TeamFoundation/2005/06/
Services/Notification/03/Notify", RequestNamespace = "http://schemas.microsoft.com/TeamFoundation/2005/06/
Services/Notification/03")]
public void Notify(string eventXml)
{
XmlDocument eventXmlStru = new XmlDocument();
eventXmlStru.LoadXml(eventXml);
string OwnerNode = (eventXmlStru.GetElementsByTagName("Owner")[0] as XmlElement).InnerText;
string DateTimeNode = (eventXmlStru.GetElementsByTagName("CreationDate")[0] as XmlElement).InnerText;
string PlcOverrideCmt = (eventXmlStru.GetElementsByTagName("PolicyOverrideComment")[0] as XmlElement).InnerText;
StreamWriter sw = new StreamWriter(@"C:\Documents and Settings\TFSService\My Documents\eventXml.xml", true);
sw.WriteLine(OwnerNode + " : " + DateTimeNode + " : " + PlcOverrideCmt);
sw.Close();
}
VB.NET
<WebMethod(MessageName := "Notify"), SoapDocumentMethod(Action := "http://schemas.microsoft.com/TeamFoundation/2005/06/
Services/Notification/03/Notify", RequestNamespace := "http://schemas.microsoft.com/TeamFoundation/2005/06/
Services/Notification/03")> _
Public Sub Notify(ByVal eventXml As String)
Dim eventXmlStru As XmlDocument = New XmlDocument()
eventXmlStru.LoadXml(eventXml)
Dim OwnerNode As String = (TryCast(eventXmlStru.GetElementsByTagName("Owner")(0), XmlElement)).InnerText
Dim DateTimeNode As String = (TryCast(eventXmlStru.GetElementsByTagName("CreationDate")(0), XmlElement)).InnerText
Dim PlcOverrideCmt As String = (TryCast(eventXmlStru.GetElementsByTagName("PolicyOverrideComment")(0), XmlElement)).InnerText
Dim sw As StreamWriter = New StreamWriter("C:\Documents and Settings\TFSService\My Documents\eventXml.xml", True)
sw.WriteLine(OwnerNode & " : " & DateTimeNode & " : " & PlcOverrideCmt)
sw.Close()
End Sub
The second subscriber will set the status of the “Code Review” workitem from “Approved” to “Closed” so that the same workitem cannot be used again. You can find the general information about how to subscribe to the events at Team Foundation Server – Eventing Service - Part 1 (Subscribing to events). Specific code to change the state of the associated workitem of the type “Code Review” is as follows:
C#
[WebMethod(MessageName = "Notify")]
[SoapDocumentMethod(Action = "http://schemas.microsoft.com/TeamFoundation/2005/06/
Services/Notification/03/Notify", RequestNamespace = "http://schemas.microsoft.com/TeamFoundation/2005/06/
Services/Notification/03")]
public void Notify(string eventXml)
{
XmlDocument eventXmlStru = new XmlDocument();
eventXmlStru.LoadXml(eventXml);
string changestNumber = (eventXmlStru.GetElementsByTagName("Number")[0] as XmlNode).InnerText;
string teamProject = (eventXmlStru.GetElementsByTagName("TeamProject")[0] as XmlNode).InnerText;
NetworkCredential objNetCred = new NetworkCredential("TfsSetup","tfssetup","VSTSSERVER");
TeamFoundationServer tfs = new TeamFoundationServer("VSTSSERVER", objNetCred);
WorkItemStore wiStore = (WorkItemStore)tfs.GetService(typeof(WorkItemStore));
ILinking linking = (ILinking)tfs.GetService(typeof(ILinking));
ArtifactId changesetId = new ArtifactId();
changesetId.Tool = "VersionControl";
changesetId.ArtifactType = "ChangeSet";
changesetId.ToolSpecificId = changestNumber;
string changeSetUri = LinkingUtilities.EncodeUri(changesetId);
Artifact[] artifacts = linking.GetReferencingArtifacts(new string[] { changeSetUri });
List<Microsoft.TeamFoundation.WorkItemTracking.Client
.WorkItem> workItems = new List<Microsoft.TeamFoundation.WorkItemTracking.Client
.WorkItem>();
foreach (Artifact artifact in artifacts)
{
ArtifactId artifactId = LinkingUtilities.DecodeUri(artifact.Uri);
if (String.Equals(artifactId.Tool, "WorkItemTracking", StringComparison.OrdinalIgnoreCase))
{
Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItem wi = wiStore.GetWorkItem(Convert.ToInt32(artifactId.ToolSpecificId));
if (wi.Type.Name == "Code Review")
{
wi.State = "Closed";
wi.Save();
}
}
}
}
VB.NET
<WebMethod(MessageName := "Notify"), SoapDocumentMethod(Action := "http://schemas.microsoft.com/TeamFoundation/2005/06/
Services/Notification/03/Notify", RequestNamespace := "http://schemas.microsoft.com/TeamFoundation/2005/06/
Services/Notification/03")> _
Public Sub Notify(ByVal eventXml As String)
Dim eventXmlStru As XmlDocument = New XmlDocument()
eventXmlStru.LoadXml(eventXml)
Dim changestNumber As String = (TryCast(eventXmlStru.GetElementsByTagName("Number")(0), XmlNode)).InnerText
Dim teamProject As String = (TryCast(eventXmlStru.GetElementsByTagName("TeamProject")(0), XmlNode)).InnerText
Dim objNetCred As NetworkCredential = New NetworkCredential("TfsSetup","tfssetup","VSTSSERVER")
Dim tfs As TeamFoundationServer = New TeamFoundationServer("VSTSSERVER", objNetCred)
Dim wiStore As WorkItemStore = CType(tfs.GetService(GetType(WorkItemStore)), WorkItemStore)
Dim linking As ILinking = CType(tfs.GetService(GetType(ILinking)), ILinking)
Dim changesetId As ArtifactId = New ArtifactId()
changesetId.Tool = "VersionControl"
changesetId.ArtifactType = "ChangeSet"
changesetId.ToolSpecificId = changestNumber
Dim changeSetUri As String = LinkingUtilities.EncodeUri(changesetId)
Dim artifacts As Artifact() = linking.GetReferencingArtifacts(New String() { changeSetUri })
Dim workItems As List(Of Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItem) = New List(Of Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItem)()
For Each artifact As Artifact In artifacts
Dim artifactId As ArtifactId = LinkingUtilities.DecodeUri(artifact.Uri)
If String.Equals(artifactId.Tool, "WorkItemTracking", StringComparison.OrdinalIgnoreCase) Then
Dim wi As Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItem = wiStore.GetWorkItem(Convert.ToInt32(artifactId.ToolSpecificId))
If wi.Type.Name = "Code Review" Then
wi.State = "Closed"
wi.Save()
End If
End If
Next artifact
End Sub
Conclusion
This article provides a solution to implement a typical workflow of code review. The intention was to provide an overview of in how many ways TFS and VSTS can be customized and extended. I thank Naren Datha who has long back published excellent one pager on how to enumerate linked artifacts. My article draws on some code from his published work.
I hope this article was useful and I thank you for viewing it.