JIRA integration with C# and REST API

JIRA integration with C# and REST API

JIRA is one of the most popular tools for IT teams to manage work and it’ll be next integration for TeamScreen. Documentation is straightforward, so It was easy for me to do it. Same as with TeamCity integration I’ll leverage the power of REST API and RestEase library. For authentication, you have two options – basic HTTP authentication and oath. Recommended is OAuth, but for now, I’ll use basic HTTP authentication, because at the moment the priority is just to connect and display issues from JIRA.

JIRA data model consists of several entities:

  • Project
  • Issue – a task, bug, improvement attached to project
  • Board – used to view actively developed issues, can be either Scrum or Kanban
  • Sprint – in Scrum clearly defined period (usually two weeks) to organize team’s work
  • Status – all issues need to have status in which they are, most popular – To Do, In Progress, Testing, Done
  • Workflow – describes available statuses and transitions between them

Given that, my plan at the moment is to do the simplest view where I display scrum board and issues from current sprint grouped by status.

Ok, let’s start with the code, first IJiraClient – interface, that RestEase library uses as an abstraction to connect to REST API endpoint:

using System.Net.Http.Headers;
using System.Threading.Tasks;
using RestEase;

namespace TeamScreen.Jira
{
    public interface IJiraClient
    {
        [Header("Authorization")]
        AuthenticationHeaderValue Authorization { get; set; }

        [Get("rest/agile/1.0/board/{boardId}/sprint/{sprintId}/issue")]
        Task<GetIssuesForSprintResponse> GetIssuesForSprint([Path]int boardId, [Path]int sprintId);

        [Get("rest/agile/1.0/board/{boardId}/sprint?state=active")]
        Task<GetActiveSprintResponse> GetActiveSprint([Path]int boardId);
    }
}

This interface has three members – Authorization header, method for retrieving active sprint and method for retrieving issues for given sprint. Responses from those methods are mapped to POCO classes representing json data:

using System;
using Newtonsoft.Json;

namespace TeamScreen.Jira
{
    public class GetActiveSprintResponse
    {
        [JsonProperty(PropertyName = "values")]
        public Sprint[] Sprints { get; set; }
    }

    public class Sprint
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public DateTime StartDate { get; set; }
        public DateTime EndDate { get; set; }
    }
}
namespace TeamScreen.Jira
{
    public class GetIssuesForSprintResponse
    {
        public Issue[] Issues { get; set; }
    }

    public class Issue
    {
        public string Key { get; set; }
        public Fields Fields { get; set; }
    }

    public class Fields
    {
        public string Summary { get; set; }
        public Status Status { get; set; }
    }

    public class Status
    {
        public string Name { get; set; }
    }
}

JiraService creates actual RestEase object and passes authorization data to it. After that it retrieves active sprint and uses it to get the list of issues for it:

using System;
using System.Linq;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using RestEase;

namespace TeamScreen.Jira
{
    public interface IJiraService
    {
        Task<GetIssuesForSprintResponse> GetIssuesForActiveSprint(string path, string username, string password, int boardId);
    }

    public class JiraService : IJiraService
    {
        public async Task<GetIssuesForSprintResponse> GetIssuesForActiveSprint(string path, string username, string password, int boardId)
        {
            var api = RestClient.For<IJiraClient>(path);
            var credentials = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{username}:{password}"));
            api.Authorization = new AuthenticationHeaderValue("Basic", credentials);

            var activeSprint = await api.GetActiveSprint(boardId);
            return await api.GetIssuesForSprint(boardId, activeSprint.Sprints.First().Id);
        }
    }
}

I use configuration mechanism described in previous post with secret manager to store JIRA connection data. In JiraController I use them and JiraService to get list of issues. Issues are then grouped by status and passed to view.

using System.Linq;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using TeamScreen.Jira;
using TeamScreen.Models.Jira;

namespace TeamScreen.Controllers
{
    public class JiraController : Controller
    {
        private readonly IJiraService _jiraService;
        private readonly IConfigurationRoot _configurationRoot;

        public JiraController(IJiraService jiraService, IConfigurationRoot configurationRoot)
        {
            _jiraService = jiraService;
            _configurationRoot = configurationRoot;
        }

        public async Task<IActionResult> Index()
        {
            var url = _configurationRoot["JiraUrl"];
            var username = _configurationRoot["JiraUsername"];
            var password = _configurationRoot["JiraPassword"];
            var boardId = int.Parse(_configurationRoot["JiraBoardId"]);
            var response = await _jiraService.GetIssuesForActiveSprint(url, username, password, boardId);

            var issuesByStatus = response.Issues
                .GroupBy(x => x.Fields.Status.Name)
                .ToDictionary(x => x.Key, x => x.ToArray());
            return View(new JiraIssuesModel { Issues = issuesByStatus });
        }
    }
}

View is simple, right now its role is just to display data without any formatting:

@model TeamScreen.Models.Jira.JiraIssuesModel

<h1>JIRA status</h1>

@foreach (var status in Model.Issues)
{
    <h2>@status.Key</h2>
    foreach (var issue in status.Value)
    {
        <p>
            @issue.Key
            <br />
            @issue.Fields.Summary
        </p>
    }
}

@section Scripts{
    <script type="text/javascript">
        window.setTimeout(function () {
            window.location.href = '@Url.Action("Index","TeamCity")';
        }, 60000);
    </script>
}

All this code produces this html page:

I removed also standard navigation bar, which is not needed and I added simple javascript function, which role is to cycle between TeamCity and JIRA pages every minute.

Right now I have a very simple page which allows me to display data from integrations. As I described in a post about perfectionism it’s the first milestone to the finished product. I’m fully aware that I cut some corners during development and now it’s time to make it right – for the second milestone I plan to polish what I have and create an initial structure for plugin architecture. For now, I’m done with version 0.1. Hope you enjoyed reading and see you next time!

Leave a Reply

Your email address will not be published. Required fields are marked *