slack_code_review_future_processing

Using Slack with .NET to speed up code review process

data: 1 września, 2016
czas czytania: 6 min
autor: Maciej Nowak

Code review is an important part of development process. Various teams use different styles from “over the shoulder” to the ones based on tools like Gerrit. However, more and more Git servers (like GitLab) give merge request functionality, which makes incorporating code review process easier.

Regardless of used approach two things are still hard i.e. review process itself and reminding others that there is code to review. Automated reminding is main subject of this post. We’re going to use a popular and powerful messaging app – Slack and GitLab to create a code-review bot that can list all merge requests and assign people to review them.

Slack integration

Slack is a popular messaging app for teams, which is very developer-friendly. By exposing API and various integration points it is possible to adapt its behaviour to needs of a team. Integrations with other systems such as GitHub, Jenkins or TeamCity are available in app directory. If our needs cannot be satisfied by what is already available, we can write our own app.

Slack allows to:

  • Send message via incoming webhooks
  • Add slash commands (messages with special meaning like /remind or /giphy)
  • Interact with Slack using Web API
  • Write custom client using Real Time Messaging

For needs of our bot we will use incoming webhooks.

Incoming webhooks

Incoming webhooks allow sending messages to configured channel (or user) on Slack. It is achieved by making simple HTTP POST request. Creating incoming webhook integration starts by adding one to Slack domain
webhook

We can configure recipient of a message and set custom icon for out bot. What is the most interesting for us is Webhook URL which defines endpoint where we should send messages.
After configuring integration, let’s create a very simple application that will send message to Slack:

using System.Net;
using Newtonsoft.Json;

namespace CRBot
{
    class Program
    {
        static void Main(string[] args)
        {
            var message = new
            {
                text = "Hello Slack. This is CR Bot";
            };

            var json = JsonConvert.SerializeObject(message);

            var webClient = new WebClient();
            webClient.Headers[HttpRequestHeader.ContentType] = "application/json";
            webClient.UploadString("https://hooks.slack.com/services/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", json);
        }
    }
}

After running this program we’ll receive a message on channel configured before:

srbot

Now, let’s add some more mentions and links to the message:

var message = new
{
     text = "Hey @maciej.nowak\nClick <http://example.com|here>",
     link_names = 1
};

Any user can be mentioned in a message by including user name prepended with @ and adding parameter link_names to request. Adding links is simpler; it’s enough to just place URL in a message or, if different title is required, use format <URL|title>.

After getting familiar with basics, we can make our bot make something useful and integrate it with GitLab.

GitLab integration

GitLab is a project management system built around Git. Feature that we need is called merge requests. Each merge request can be awarded with emoji like thumbsup or +1. For simplicity, let’s assume that we use only thumbs up and thumbs down to mark merge request as good or bad. Also, following best practice, each merge request must be reviewed by two developers.

Listing merge requests

Fortunately for us, GitLab exposes HTTP API that gives access to all required information. Moreover, there is .NET library, called NGitLab, which covers a part of API and allows to quite easily add support for the rest of it. Let’s start by listing all merge requests and sending it via Slack.

class Program
{
	static void Main(string[] args)
	{            
		var generator = new Generator(
			ConfigurationManager.AppSettings["GitLabUrl"], 
			ConfigurationManager.AppSettings["GitLabToken"], 
			ConfigurationManager.AppSettings["GitLabProject"]
		);

		var text = generator.GenerateMessage();

		var message = new
		{
			text = text,
			link_names = 1
		};

		var json = JsonConvert.SerializeObject(message);

		var webClient = new WebClient();
		webClient.Headers[HttpRequestHeader.ContentType] = "application/json";
		webClient.UploadString("https://hooks.slack.com/services/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", json);
	}
}

public class Generator
{
	private readonly string _gitLabUrl;
	private readonly string _projectName;
	private readonly GitLabClient _gitLabClient;

	public Generator(string gitLabUrl, string gitLabToken, string projectName)
	{
		_gitLabUrl = gitLabUrl;
		_projectName = projectName;
		_gitLabClient = GitLabClient.Connect(gitLabUrl, gitLabToken);
	}

	public string GenerateMessage()
	{
		var text = new StringBuilder();

		var project = _gitLabClient.Projects.Accessible.Single(x => x.Name == _projectName);

		var mergeRequestClient = _gitLabClient.GetMergeRequest(project.Id);            

		var openedMergeRequests = mergeRequestClient.AllInState(MergeRequestState.opened);

		foreach (var review in openedMergeRequests)
		{
			text.AppendLine($"<{_gitLabUrl}/{project.PathWithNamespace}/merge_requests/{review.Iid}|{review.Title}>");
		}

		return text.ToString();
	}
} 

Now, running this program will result in a nice list of all opened merge requests.

Award Emoji

Next thing to do is to get current awards for all merge requests. Unfortunately, NGitLab doesn’t cover that area of API and we need to do it ourselves. Luckily for us, it is pretty straightforward and based on API class that hides all hard work:

public class AwardEmojiClient
{
	private readonly API _api;
	private readonly int _projectId;

	public AwardEmojiClient(API api, int projectId)
	{
		_api = api;
		_projectId = projectId;
	}

	public IEnumerable GetAwardsForMergeRequest(int mergeRequestId)
	{
		return _api.Get().GetAll($"projects/{_projectId}/merge_requests/{mergeRequestId}/award_emoji");
	}
}

[DataContract]
public class AwardEmoji
{
	private static readonly string[] Downvotes = new[] {"-1", "thumbsdown"};
	private static readonly string[] Upvotes = new[] { "+1", "thumbsup" };

	[DataMember(Name = "name")]
	public string Name { get; set; }

	[DataMember(Name = "user")]
	public User User { get; set; }

	public bool IsDownvote => Downvotes.Contains(Name);
	public bool IsUpvote => Upvotes.Contains(Name);
}

Using that classes we can access award emojis for any merge request. Using that and a list of developers in teams (for example, read from config file) we can assign people to each merge request. For simplicity we’ll do it randomly. After implementing that and a small refactoring, we get fully functional code-review bot which relieves us from deciding who should do each code review.

public string GenerateMessage()
{
	var text = new StringBuilder();
 
	var project = _gitLabClient.Projects.Accessible.Single(x => x.Name == _projectName);
 
	var mergeRequestClient = _gitLabClient.GetMergeRequest(project.Id);
 
	var awardEmojiClient = new AwardEmojiClient(_api, project.Id);
 
	var openedMergeRequests = mergeRequestClient.AllInState(MergeRequestState.opened);
 
	foreach (var review in openedMergeRequests)
	{
    	var awards = awardEmojiClient.GetAwardsForMergeRequest(review.Id).ToList();
 
    	var downvotes = awards.Where(x => x.IsDownvote).ToList();
    	var upvotes = awards.Where(x => x.IsUpvote).ToList();
 
    	if (downvotes.Any())
    	{
        	text.AppendLine($"{ReviewUrl(project, review)} - still :-1: ({UserRefs(downvotes.Select(x => x.User.Username))})");
    	}
    	else if (upvotes.Count < 3) { var reviewsNeeded = 3 - upvotes.Count; var canReview = _developers .Except(new[] { review.Author.Username }) .Except(upvotes.Select(x => x.User.Username));
 
        	var reviewers = TakeRandom(canReview, Random, reviewsNeeded);
 
        	text.AppendLine($"{ReviewUrl(project, review)} - {reviewsNeeded} reviews needed ({UserRefs(reviewers)})");
    	}
	}
 
	return text.ToString();
}

Summary

We’ve built a simple application that uses Slack and GitLab to automate part of code-review process. Setting it up as repeated job in Task Scheduler will further automate that process. However, the whole solution can be made a bit better by implementing smarter algorithm that assigns people to merge requests.
Our team have been using similar solution for a few months and there is visible difference. Selecting people by mentioning them make them feel obligated to do review and there is no “someone else will do it” syndrome or postponing it forever.

References

CTA_windows

Newsletter IT leaks

Dzielimy się inspiracjami i nowinkami z branży IT. Szanujemy Twój czas - obiecujemy nie spamować i wysyłać wiadomości raz na dwa miesiące.

Subscribe to our newsletter

Administratorem Twoich danych osobowych jest Future Processing S.A. z siedzibą w Gliwicach. Twoje dane będziemy przetwarzać w celu przesyłania cyklicznego newslettera dot. branży IT. W każdej chwili możesz się wypisać lub edytować swoje dane. Więcej informacji znajdziesz w naszej polityce prywatności.

Subscribe to our newsletter

Administratorem Twoich danych osobowych jest Future Processing S.A. z siedzibą w Gliwicach. Twoje dane będziemy przetwarzać w celu przesyłania cyklicznego newslettera dot. branży IT. W każdej chwili możesz się wypisać lub edytować swoje dane. Więcej informacji znajdziesz w naszej polityce prywatności.