How to implement workflow in VSTS – Case of Code Review

Posted by: Subodh Sohoni , on 7/9/2008, in Category VSTS & TFS (Azure DevOps)
Views: 41126
Abstract: 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.
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:
Details of “Code Review Policy” are provided in How to create and deploy custom check-in policy. That article provides the code of check-in policy which is used to check that check-in is associated with a workitem of the type “Code Review” having “Approved” status. I have also described how to deploy that check-in policy at all client machines. Code for this policy is provided at http://cid-2c5f5b0560e374cb.skydrive.live.com/self.aspx/.Public/Uploads/CheckinPolicyCode.zip
Custom Workitem type:
“Code Review” workitem type is similar to “Task” workitem type with the status ‘Approved’ or ‘Not Approved’ and field for storing the location of the code (the shelveset name). Details of how to create such a workitem are provided in the article at Workitem Architecture, Design Considerations and Customization: Part 2. XML for such a workitem type is provided here.
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.

Absolutely Awesome Book on C# and .NET

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!

What Others Are Reading!
Was this article worth reading? Share it with fellow developers too. Thanks!
Share on LinkedIn
Share on Google+

Author

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 .


Page copy protected against web site content infringement 	by Copyscape




Feedback - Leave us some adulation, criticism and everything in between!
Comment posted by Brent Hill on Wednesday, July 9, 2008 10:58 PM
Guys,
I must admit that this site has got some great content..especially on the asp ajax and vsts front. I have been visiting your site regularly from quiet some time and this article (and the related ones) are absolutely amazing.
Keep them coming!!

Also can you fix up this line http://schemas.microsoft.com/TeamFoundation/2005/06/
Services/Notification/???  Some text seems to be lost!!

Brent
Comment posted by Subodh Sohoni on Thursday, July 10, 2008 5:00 AM
Thanks Brent,
for your comments, appriciation as well as for pointing out the error.

I have requested the master of the site to correct those truncated lines.

Subodh
Comment posted by Suprotim Agarwal on Thursday, July 10, 2008 8:19 AM
Thanks Brent. The truncation has been corrected.

Comment posted by Brent Hill on Saturday, July 12, 2008 1:34 AM
Awesome. Glad to see that the comments are read on this site and actions are taken.

I will be looking forward for your next article. Happy weekend!!
Comment posted by Adam on Thursday, January 8, 2009 5:59 PM
I'm having a hard time getting the event subscriber to close the workitem. Is the event to subscribe to 'CheckinEvent'? Does the workflow from http://www.dotnetcurry.com/ShowArticle.aspx?ID=154 need to be modified to transition from 'Approved' to 'Closed' for it to work? I can't think of what else I'd be missing.
Comment posted by Dave on Thursday, January 21, 2010 5:15 PM
This looks great!  One question though... for your Code Reviewers group you specify that only members of that group have permissions to set a Code Review work item to state Approved.  How do restrict it so only members of this group are able to do this?  I don't see anything in your WIT example.

Thanks!
Dave
Comment posted by Dave on Thursday, January 21, 2010 5:43 PM
This looks great!  One question though... for your Code Reviewers group you specify that only members of that group have permissions to set a Code Review work item to state Approved.  How do restrict it so only members of this group are able to do this?  I don't see anything in your WIT example.

Thanks!
Dave
Comment posted by Subodh Sohoni on Monday, January 25, 2010 4:58 AM
Hi Dave,
When we are designing any field in a workitem type we can set constraints on accesability of that field by setting the 'For' clause. We can set the 'State' field with 'For' and the value is 'Code Reviewers' global group. For others it is a sort of Read Only field. Unfortunately it does not allow us to set any groups of the team project, it has to be a Global group at the TFS level. Similar to 'For clause there is a 'Not For' clause also which tells TFS not to allow the group members to access that field.
Thanks for your comments.