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.
This article has been editorially reviewed by Suprotim Agarwal.
C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.
We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).
Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.
Click here to Explore the Table of Contents or Download Sample Chapters!
Was this article worth reading? Share it with fellow developers too. Thanks!
Subodh is a Trainer and consultant on Azure DevOps and Scrum. He has an experience of over 33 years in team management, training, consulting, sales, production, software development and deployment. He is an engineer from Pune University and has done his post-graduation from IIT, Madras. He is a Microsoft Most Valuable Professional (MVP) - Developer Technologies (Azure DevOps), Microsoft Certified Trainer (MCT), Microsoft Certified Azure DevOps Engineer Expert, Professional Scrum Developer and Professional Scrum Master (II). He has conducted more than 300 corporate trainings on Microsoft technologies in India, USA, Malaysia, Australia, New Zealand, Singapore, UAE, Philippines and Sri Lanka. He has also completed over 50 consulting assignments - some of which included entire Azure DevOps implementation for the organizations.
He has authored more than 85 tutorials on Azure DevOps, Scrum, TFS and VS ALM which are published on
www.dotnetcurry.com.Subodh is a regular speaker at Microsoft events including Partner Leadership Conclave.You can connect with him on
LinkedIn .