Building a MVP Notifier using Flow and Azure Functions

Since MVPs are now announced every month, I have found it hard to track down the new awardees in Business Applications area before 3rd of every month. So, I thought I will build a notifier using Flow and Functions. This is the logic:

  1. Seed the inital MVP data from mvp.microsoft.com, to figure out the new MVPs every month into Azure Tables
  2. Schedule the Flow at 1st of every month at 5 p.m. PDT. Hopefully by this time, everyone has filled out atleast their name in the MVP profile.
  3. Retrieve the MVP data again from mvp.microsoft.com
  4. Figure out the new MVP and post a message to Slack. Add the new MVP details to Azure Tables

The logic that retrieves the details from mvp.microsoft.com uses Azure Functions. Below is the project.json for that Function App.

{
  "frameworks": {
    "net46":{
      "dependencies": {
        "AngleSharp": "0.9.10",
        "Newtonsoft.Json": "11.0.0.2"
      }
    }
   }
}

Next, is the actual logic that returns MVP list for the category passed in the URL.

using System.Net;
using AngleSharp;
using AngleSharp.Dom;
using AngleSharp.Dom.Html;
using System;
using System.Runtime.InteropServices;
using System.Text;
using Newtonsoft.Json;

public static async Task Run(HttpRequestMessage req, TraceWriter log)
{
    log.Info("C# HTTP trigger function processed a request.");

    // parse query parameter
    string mvpCategory = req.GetQueryNameValuePairs()
        .FirstOrDefault(q => string.Compare(q.Key, "category", true) == 0)
        .Value;

    if (mvpCategory == null)
    {
        // Get request body
        dynamic data = await req.Content.ReadAsAsync();
        mvpCategory = data?.category;
    }

	var pageNumber = 1;
	var webClient = new WebClient();
	IHtmlDocument doc;
	IHtmlCollection mvpProfiles = null;
	List mvps = new List();
	var htmlParser = new AngleSharp.Parser.Html.HtmlParser(new Configuration().WithDefaultLoader());
	do
	{
		var data = webClient.DownloadData(new Uri($"https://mvp.microsoft.com/en-us/MvpSearch?ex={WebUtility.UrlEncode(mvpCategory)}&sc=s&pn={pageNumber++}&ps=48"));
		doc = htmlParser.Parse(Encoding.UTF8.GetString(data));

		mvpProfiles = doc.QuerySelectorAll(".profileListItem");
		var currentPage = mvpProfiles
		.Select(d =>
		{
			var nameAndLocation = d.QuerySelector(".profileListItemFullName a");
            var mvpUrl = nameAndLocation.GetAttribute("href");
			return new MVPInfo
			{
				Name = nameAndLocation.TextContent,
				Url = $"https://mvp.microsoft.com{mvpUrl}",
				Country = d.QuerySelector(".profileListItemLocation .subItemContent").TextContent,
                Id = mvpUrl.Substring(mvpUrl.LastIndexOf("-")+1)
			};
		})
		.ToList();
		mvps.AddRange(currentPage);
	} while (mvpProfiles.Any());

    var json = JsonConvert.SerializeObject(mvps, Formatting.Indented);
    return mvpCategory == null
        ? req.CreateResponse(HttpStatusCode.BadRequest, "Please pass a MVP category on the query string")
        : new HttpResponseMessage(HttpStatusCode.OK)
          {
                Content = new StringContent(json, Encoding.UTF8, "application/json")
          };
}

public class MVPInfo
{
	public string Name { get; set; }
	public string Url { get; set; }
	public string Country { get; set; }
    public string Id { get; set; }
}

It uses the AngleSharp library to parse the response returned by mvp.microsoft.com search.

Below is the Flow that uses this Function, to populate the Azure Table.

Flow Overview.png

As you can see below the Flow runs at 5 p.m on 1st of every month, and gets the current MVP list from Azure Table, MVPAwards.

Azure Tables.png

The partion key for the table is month and year and the row key is the actual MVP Id.

Azure Tables Definition.png

Since this Flow and the associated Function were built for my personal use, I call the Function directly without using a custom connector. I pass the award category on the URL of the Functions itself. If you want to use a custom connector, refer one of my earlier posts.

Azure Function Call.png

After parsing the JSON returned by the Azure Function, I can use the “select” function to map the returned data, so that I can compare the data based on the MVP Id and insert the new MVPs into the Azure Table, if needed. The partion key will the current year and month (yyyyMM).

Flow Select.png

Next is the crucial bit, where I do the actual comparison.

Compare MVPs.png

The “filter” function can be used to see whether there are any matches in MVP list that was retrieved from Azure Table i.e. the previous month MVPs. If no results were returned, that means they were not a MVP last month. So, they are a new MVP and this can be stored in Azure Tables and can also be posted to Slack.

IF Then Logic.png

Post to Slack is using the Webhooks registered on the channel.

Slack Web Hooks.png

After the Flow has finished running, the output gets posted to Slack.

MVP awards.png

References:

  1. https://powerusers.microsoft.com/t5/Building-Flows/Comparing-File-Lists/td-p/64178
  2. https://api.slack.com/incoming-webhooks
Advertisements