Loupe

VSTS Api - Envoyer une release note automatiquement

Cela fait quelques temps maintenant que nous travaillons sur Visual Studio Team System (VSTS) chez Infinite Square. Cet outil regorge de fonctionnalités et permet de réaliser beaucoup de choses. Cependant, quand on commence à avoir un projet/produit piloté avec VSTS, avec plusieurs branches de travail, plusieurs releases et donc plusieurs environnements de déploiement, il devient assez difficile de savoir si telle ou telle fonctionnalité/résolution de bug a été déployée sur un environnement précis. Mon besoin est donc d'envoyer par mail un récapitulatif du nouveau contenu apporté lorsqu'une release est déployée. Après avoir scruté ce que propose VSTS, au niveau de la partie release, j'ai vu deux fonctionnalités qui me paraissaient intéressantes et qui permettent conjointement de répondre à ce besoin et donc de gagner du temps au niveau de la traçabilité du travail réalisé.

La première fonctionnalité est l'envoi d'un mail manuel qui résume le contenu de la release. Outre son caractère manuel, la fonctionnalité présente le désavantage de ne donner que la différence avec la release précédente, sans même tenir compte de son déploiement.

Résumé du contenu d'un release

La seconde fonctionnalité est l'écran de visualisation du différentiel entre la version courante et la version déployée.

Différentiel release

Après avoir analysé le fonctionnement de cet écran de visualisation (en scrutant les appels réseaux), il apparaît possible de recréer ce différentiel en passant par les apis que propose VSTS. Je me suis donc lancé dans la création d'une application console  qui va obtenir ce différentiel et l'envoyer par mail.

Pour commencer, il faut ajouter les packages NuGet suivants :

  • Microsoft.VisualStudio.Services.Client
  • Microsoft.VisualStudio.Services.Release.Client
  • Microsoft.TeamFoundationServer.Client

Nous pouvons commencer à développer. Tout d'abord nous allons ouvrir une connexion pour pouvoir appeler les apis REST VSTS.

var serverUrl = "https://{server}.visualstudio.com";
var projectName = "";
var userName = "";
var accessKey = "";

var connection = new VssConnection(new Uri(serverUrl), new VssBasicCredential(userName, accessKey));

Pour obtenir une clé d'accès personnel, vous pouvez suivre la documentation de Microsoft.

Ensuite, avant de commencer les appels aux apis, nous avons besoin d'obtenir des informations sur le contexte de notre release (id de la release, id de la définition de la release, ...). Etant dans le contexte d’exécution d'une release, nous avons accès aux variables d'environnement. Voici la liste exhaustive des variables d'une release.

var releaseDefinitionId = int.Parse(Environment.GetEnvironmentVariable("RELEASE_DEFINITIONID"));
var releaseId = int.Parse(Environment.GetEnvironmentVariable("RELEASE_RELEASEID"));
var releaseName = int.Parse(Environment.GetEnvironmentVariable("RELEASE_RELEASENAME"));
var envName = Environment.GetEnvironmentVariable("RELEASE_ENVIRONMENTNAME");
var envId = int.Parse(Environment.GetEnvironmentVariable("RELEASE_ENVIRONMENTID"));

La connexion est prête, les variables d’environnement sont là, nous sommes donc maintenant fin prêts pour faire appel aux apis ! Commençons par obtenir les 2 dernières releases actives de l'environnement courant. Pourquoi les deux dernières, car la dernière est la release courante sur laquelle nous sommes en train de travailler, il nous faut donc obtenir celle juste avant, afin de faire notre différentiel. Nous faisons donc appel a la méthode "GetReleasesAsync" qui demande un certain nombre de paramètres notamment le nom de votre projet VSTS, id de la définition de la release, et id de l'environnement.

var releases = await rmClient.GetReleasesAsync(projectName, definitionId: releaseDefinitionId, definitionEnvironmentId: envId, statusFilter: Microsoft.VisualStudio.Services.ReleaseManagement.WebApi.ReleaseStatus.Active, top: 2);

var lastReleaseId = releases.Last().Id;

Maintenant que nous avons obtenu l'id de la dernière release déployée sur l'environnement cible, il nous faut avoir le différentiel de workitems (taches, bugs, ...), ainsi que les changes (commits). Pour ce faire, il n'existait pas de méthode exposée dans le "ReleaseHttpClient" fourni par la librairie. L'api disposant de ces services, j'ai donc créé mon propre "ReleaseHttpClient" exposant 2 nouvelles méthodes permettant de faire appel aux services (ainsi que les classes nécessaires de convertir le résultat). "GetReleaseWorkItemsAsync" permet d'obtenir le différentiel de workitems, et "GetReleaseChangesAsync" permet d'obtenir le différentiel de changes.

class MyReleaseHttpClient : ReleaseHttpClient
{
    public MyReleaseHttpClient(Uri baseUrl, VssCredentials credentials) : base(baseUrl, credentials)
    {
    }

    public MyReleaseHttpClient(Uri baseUrl, VssCredentials credentials, VssHttpRequestSettings settings) : base(baseUrl, credentials, settings)
    {
    }

    public MyReleaseHttpClient(Uri baseUrl, VssCredentials credentials, params DelegatingHandler[] handlers) : base(baseUrl, credentials, handlers)
    {
    }

    public MyReleaseHttpClient(Uri baseUrl, HttpMessageHandler pipeline, bool disposeHandler) : base(baseUrl, pipeline, disposeHandler)
    {
    }

    public MyReleaseHttpClient(Uri baseUrl, VssCredentials credentials, VssHttpRequestSettings settings, params DelegatingHandler[] handlers) : base(baseUrl, credentials, settings, handlers)
    {
    }

    public async Task<ReleaseWorkItems> GetReleaseWorkItemsAsync(string projet, int releaseId, int? baseReleaseId = null, int top = 2500)
    {
        var res = await Client.GetAsync($"{projet}/_apis/Release/releases/{releaseId}/workitems?top={top}{(baseReleaseId != null ? ("&baseReleaseId=" + baseReleaseId.Value) : "")}");
        res.EnsureSuccessStatusCode();

        return await res.Content.ReadAsAsync<ReleaseWorkItems>();
    }

    public async Task<ReleaseWorkChanges> GetReleaseChangesAsync(string projet, int releaseId, int? baseReleaseId = null, int top = 2500)
    {
        var res = await Client.GetAsync($"{projet}/_apis/Release/releases/{releaseId}/changes?top={top}{(baseReleaseId != null ? ("&baseReleaseId=" + baseReleaseId.Value) : "")}");
        res.EnsureSuccessStatusCode();

        return await res.Content.ReadAsAsync<ReleaseWorkChanges>();
    }

    public class ReleaseWorkItems
    {
        public int Count { get; set; }
        public ICollection<ReleaseWorkItem> Value { get; set; }
    }

    public class ReleaseWorkItem
    {
        public string Id { get; set; }
        public string Url { get; set; }
    }

    public class ReleaseWorkChanges
    {
        public int Count { get; set; }
        public ICollection<ReleaseWorkChange> Value { get; set; }
    }

    public class ReleaseWorkChange
    {
        public string Id { get; set; }
        public string ChangeType { get; set; }
        public string Message { get; set; }
        public string Location { get; set; }
        public ReleaseWorkChangeAuthor Author { get; set; }

        public class ReleaseWorkChangeAuthor
        {
            public string Id { get; set; }
            public string ImageUrl { get; set; }
            public string DisplayName { get; set; }
            public string UniqueName { get; set; }
        }
    }
}

Pour utiliser ce "MyReleaseHttpClient", il suffit, depuis la connexion, d'obtenir le client correspondant, et ensuite faire les appels aux 2 nouvelles méthodes.

var rmClient = connection.GetClient<MyReleaseHttpClient>();

var releaseDifWorkItems = await rmClient.GetReleaseWorkItemsAsync(projectName, releaseId, lastReleaseId);

var releaseDifChanges = await rmClient.GetReleaseChangesAsync(projectName, releaseId, lastReleaseId);

Pour les changes, je n'ai pas dû faire autre chose que cela, les informations renvoyées sont suffisantes, à savoir le message (qui est celui saisi par le développeur lors du commit), ainsi que Author.DisplayName qui est le nom du développeur qui a effectué le commit. Concernant les workitems, il est nécessaire d'effectuer un dernier appel : en effet la méthode "GetReleaseWorkItemsAsync" renvoie uniquement la liste des id des workitems. Pour obtenir le détail de ces workitems, il suffit d'utiliser un autre HttpClient de la librairie, "WorkItemTrackingHttpClient", qui expose la méthode "GetWorkItemsAsync" et permet, en donnant une liste d'id et le nom de champs que nous souhaitons obtenir, le détail de chacun des workitems.

var witClient = connection.GetClient<WorkItemTrackingHttpClient>();

var workItems = await witClient.GetWorkItemsAsync(releaseDifWorkItems.Value.Select(w => int.Parse(w.Id)), fields: new List<string>
{
    "System.AssignedTo",
    "System.State",
    "System.Title",
    "System.WorkItemType"
});

Ensuite, vous n'avez plus qu'a formater ces résultats dans un beau mail, et envoyer avec un service de mailing (pour ma part, j'ai opté pour SendGrid) aux personnes désireuses d'avoir ces informations.

J'espère que, comme moi, cette astuce va vous faire gagner du temps, et améliorer la traçabilité. Merci à Vivien pour son expertise sur le sujet.

Photo de profil

Ces billets pourraient aussi vous intéresser

Vous nous direz ?!

Commentaires

comments powered by Disqus