You shall not forget - reminding about pull requests in VSTS #2

In the previous post I presented you how easily we can determine whether a PR has been reviewed or not. Now we'll create a real solution, which will send a notification to a Slack channel containing information about waiting pull request.

Creating an Azure Function

To handle our solution we'll develop a function, which will do following:

  • call VSTS API to check whether there're active PRs
  • filter those PRs, which haven't been reviewed during a specific interval
  • send a message to a Slack channel with specific information regarding forgotten PRs

Now let's create a function. Go to Azure Portal and create a new Function App(if you don't have one). Once created add a function as a TimerTrigger(schedule doesn't matter in this moment). Once you're done, you should get following function:

Now - to authenticate our calls to the VSTS API we'll need a personal access token. To create one go to the instance of VSTS you're going to use and go to Security screen.

On the left you should see Personal access tokens tab. Go there and create a new personal access token, which we'll use in our function.

Once we have a PAT we can write add some code to our function - for this moment we'll try to check whether there're active PRs.

Fetching active PRs from VSTS

This is a simple task - we have to call an API with a generated PAT. Consider following code:

/
using System;
using System.Net;
using System.Net.Http.Headers;

public static async Task Run(TimerInfo myTimer, TraceWriter log)
{
    log.Info($"C# Timer trigger function executed at: {DateTime.Now}");

    try
    {
        var personalaccesstoken = "THIS_IS_YOUR_PAT";

        using (HttpClient client = new HttpClient())
        {
            client.DefaultRequestHeaders.Accept.Add(
                new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));

            client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic",
                Convert.ToBase64String(
                    System.Text.ASCIIEncoding.ASCII.GetBytes(
                        string.Format("{0}:{1}", "", personalaccesstoken))));

            using (HttpResponseMessage response = client.GetAsync(
                        "https://{instance}.visualstudio.com/{project}/_apis/git/repositories/{repository}/pullRequests?api-version=3.0").Result)
            {
                response.EnsureSuccessStatusCode();
                string responseBody = await response.Content.ReadAsStringAsync();
                log.Info(responseBody);
            }
        }
    }
    catch (Exception ex)
    {
        log.Info(ex.ToString());
    }
}

When you replace dummy values with values of your own, you should receive in the Logs window a serialized response containing active pull requests(of course if there's any). So far so good - let's try to find if any requires our attention.

Finding outdated PRs

To find outdate PRs we'll have to change our code a little - for now we have a raw string and are unable to query any of its properties. For the purpose of this post I decided to use a dynamic object, just not to trouble with creating a DTO for a response. To do so just create responseBody variable to:

/
dynamic data = await response.Content.ReadAsAsync<object>();

No we can easily query PRs and find those, which haven't been reviewed during a specific interval(let's say 24 hours). Let's add following code to our function:

/
using (HttpResponseMessage response = client.GetAsync(
			"https://{instance}.visualstudio.com/{project}/_apis/git/repositories/{project}/pullRequests?api-version=3.0").Result)
{
	response.EnsureSuccessStatusCode();
	dynamic data = await response.Content.ReadAsAsync<object>();

	foreach(var pr in data.value) {
		foreach(var reviewer in pr.reviewers) {
			if(reviewer.vote == 0 && (DateTime.Now - DateTime.Parse(pr.creationDate.ToString())).Hours > 24) {
				log.Info($"Reviewer {reviewer.displayName} still hasn't reviewed a PR!");
			}
		}
	}
}

With the above code we're able to find reviewers, which haven't got a chance to take a look and review a pull request. In the last part we'll send this information to a Slack channel.

Notifying in a Slack channel

To send something to a Slack channel we have to set up a webhook integration which is described here. Once you have added a webhook to your channel and got an endpoint, which can be used to send messages to a channel, we can extend our function. Here you have the complete code:

/
using System;
using System.Net;
using System.Net.Http.Headers;

public static async Task Run(TimerInfo myTimer, TraceWriter log)
{
    log.Info($"C# Timer trigger function executed at: {DateTime.Now}");

    try
    {
        var personalaccesstoken = "YOUR_PAT";

        using (HttpClient client = new HttpClient())
        {
            client.DefaultRequestHeaders.Accept.Add(
                new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));

            client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic",
                Convert.ToBase64String(
                    System.Text.ASCIIEncoding.ASCII.GetBytes(
                        string.Format("{0}:{1}", "", personalaccesstoken))));

            dynamic data = null;
            using (HttpResponseMessage response = await client.GetAsync(
                        "https://{instance}.visualstudio.com/{project}/_apis/git/repositories/{repository}/pullRequests?api-version=3.0"))
            {
                response.EnsureSuccessStatusCode();
                data = await response.Content.ReadAsAsync<object>();
            }

            foreach(var pr in data.value) {
                foreach(var reviewer in pr.reviewers) {
                    if(reviewer.vote == 0 && (DateTime.Now - DateTime.Parse(pr.creationDate.ToString())).Hours > 24) {
                        var textToSend = $"Reviewer {reviewer.displayName} still hasn't reviewed a PR!";

                        await client.PostAsync("https://hooks.slack.com/{...}", new StringContent("{\"text\":\"" + textToSend + "\"}"));
                    }
                }
            }
        }
    }
    catch (Exception ex)
    {
        log.Info(ex.ToString());
    }
}

Summary

VSTS and its API give you many flexibility and are really fun to play with. The solution presented here is not ideal - there's still a possibility to improve things. Anyway, I'd like to encourage you to your own experiments with VSTS and different integrations - you'll be surprised what you can achieve with a few lines of code.

 

Monitoring your Function App with ease

This post was created thanks to great support from people directly involved in Azure Functions in Microsoft - Donna Malayeri and Paul Batum

Introduction

Azure Functions make developing small services easier, especially with all those triggers and bindings, which allow you to skip writing boilerplate code and focus directly on your business needs. They are also provided with a possibility to "pay-as-you-go" with Consumption Plan supported. Currently many people take advantage of this feature and try to make the most of it by not exceeding a monthly free cap of executions and execution time. What if you'd like to monitor somehow how many times you function's been executed? Fortunately there're two ways of doing it and both are a piece of cake.

A graph presenting executions of a function triggered once per 5 minutes

Using Azure Portal

The easiest way to monitor your Function App(unfortunately you cannot monitor each function separately, at least not now) is to go to portal and check metrics of an App service plan, which is used to host it(to be more specific - its metrics). To do so I'll quote Paul directly here:

/
> There appears to be a bug that is making this harder than it should be. Try the following steps...
> Open Function App. Platform Features -> All Settings -> Click on Function Execution Count graph -> uncheck count, check units.

By doing those steps, you should be able to see a chart with a metric selected:

Weekly metrics of my function

There's one gotcha here however:

/
> Function Execution units are in mbmilliseconds, you'll need to divide by 1024000 to get gbsec.

One more thing - there's now way to know what's the aggregated value e.g. from a month - for now you have to do it on your own.

Using API

There's one more way to check the metrics - using a REST API of Azure Monitoring. This method is described on StackOverflow also by Paul and with a direct reference to walkthrough of Azure Monitoring REST API. The main idea is to call API, which will return to you something similar to following:

/
{
  "value": [
    {
      "data": [
        {
          "timeStamp": "2016-12-10T00:00:00Z",
          "total": 0
        },
        {
          "timeStamp": "2016-12-10T00:01:00Z",
          "total": 140544
        },
        {
          "timeStamp": "2016-12-10T00:02:00Z",
          "total": 0
        },
        {
          "timeStamp": "2016-12-10T00:03:00Z",
          "total": 0
        },
        {
          "timeStamp": "2016-12-10T00:04:00Z",
          "total": 0
        }
      ],      
      "name": {
        "value": "FunctionExecutionUnits",
        "localizedValue": "Function Execution Units"
      },
      "type": "Microsoft.Insights/metrics",
      "unit": "0"
    }
  ]
}

Once you have the result, it's easy to write a custom tool, which will calculate all metrics and give you an insight into your's functions performance.