Baseline C# Objects to Populate Jira DevInfo Pt. 2

on Monday, July 6, 2020

From the previous post, Baseline C# Objects to Populate Jira DevInfo Pt. 1:

Jira has this great “Development Information” (DevInfo) that can be associated with your work items. Which has an API described here. The information provided in the development tabs are for Branches, Commits, Pull Requests, Builds, Deployments and Feature Flags. Which is a way to have visibility into all the development/code activity that is related to a particular work item. It’s a great way to connect everything together.

On the previous post, there’s also a list of “gotcha’s” with the Jira documentation and a list of things could be improved.

But, this post is about the baseline C# objects which can be used to push information to the Atlassian/Jira DevInfo API.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Atlassian.Jira;
namespace YourProject
{
public class JiraDevInfoBulkRequest
{
public List<JiraRepository> Repositories { get; set; }
public bool PreventTransitions { get; set; }
public Dictionary<string,string> Properties { get; set; }
public JiraProviderMetadata ProviderMetadata { get; set; }
}
public class JiraRepository
{
public string Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Url { get; set; }
public string Avatar { get; set; }
public string AvatarDescription { get; set; }
public string ForkOf { get; set; }
public List<JiraCommit> Commits { get; set; } = new List<JiraCommit>();
public List<JiraPullRequest> PullRequests { get; set; } = new List<JiraPullRequest>();
public List<JiraBranch> Branches { get; set; } = new List<JiraBranch>();
public long UpdateSequenceId { get; set; }
}
public class JiraCommit
{
public string Id { get; set; }
public string Hash { get; set; }
public string Message { get; set; }
public List<string> IssueKeys { get; set; }
public string Url { get; set; }
public string DisplayId { get; set; }
public DateTime AuthorTimestamp { get; set; }
public JiraAuthor Author { get; set; }
public int FileCount { get; set; }
public List<string> Flags { get; set; }
public List<JiraCommitFile> Files { get; set; }
public long UpdateSequenceId { get; set; }
}
public class JiraAuthor
{
public string Name { get; set; }
public string Email { get; set; }
public string Username { get; set; }
public string Url { get; set; }
public string Avatar { get; set; }
}
public class JiraCommitFile
{
public string Path { get; set; }
public string Url { get; set; }
public string ChangeType { get; set; }
public string LinesAdded { get; set; }
public string LinesRemoved { get; set; }
}
public class JiraProviderMetadata
{
public string Product { get; set; }
}
public class JiraPullRequest
{
public string Id { get; set; }
public List<string> IssueKeys { get; set; }
public string Status { get; set; }
public string Title { get; set; }
public string DisplayId { get; set; }
public string Url { get; set; }
public JiraAuthor Author { get; set; }
public int CommentCount { get; set; }
public string SourceBranch { get; set; }
public string SourceBranchUrl { get; set; }
public DateTime LastUpdate { get; set; }
public string DestinationBranch { get; set; }
public List<JiraReviewer> Reviewers { get; set; }
public string Avatar { get; set; }
public string AvatarDescription { get; set; }
public long UpdateSequenceId { get; set; }
}
public class JiraReviewer
{
public string Name { get; set; }
public string ApprovalStatus { get; set; }
public string Url { get; set; }
public string Avatar { get; set; }
}
public class JiraBranch
{
public string Id { get; set; }
public List<string> IssueKeys { get; set; }
public string Name { get; set; }
public JiraCommit LastCommit { get; set; }
public string CreatePullRequestUrl { get; set; }
public string Url { get; set; }
public long UpdateSequenceId { get; set; }
}
}
using System.Collections.Generic;
namespace YourProject
{
public class JiraDevInfoResponse
{
public Dictionary<string, JiraDevInfoResponseEntitySet> AcceptedDevInfoEntities { get; set; }
public Dictionary<string, JiraDevInfoResponseEntitySet> FailedDevInfoEntities { get; set; }
public List<string> UnknownIssueKeys { get; set; }
}
public class JiraDevInfoResponseEntitySet
{
public List<string> Branches { get; set; }
public List<string> Commits { get; set; }
public List<string> PullRequests { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.TeamFoundation.SourceControl.WebApi;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using JsonSerializer = System.Text.Json.JsonSerializer;
namespace YourProject
{
public class JiraDevInfoHttpClient : IJiraDevInfoHttpClient
{
private readonly HttpClient _client;
private readonly IUpdateSequenceIdManager _idManager;
public JiraDevInfoHttpClient(
HttpClient client,
IUpdateSequenceIdManager idManager
)
{
_client = client;
_idManager = idManager;
_client.SetApiBaseUri(AtlassianConstants.YourInstanceCloudId, "devinfo");
}
private JiraDevInfoBulkRequest BuildBulkRequest(JiraRepository repository)
{
var bulkRequest = new JiraDevInfoBulkRequest()
{
Repositories = new List<JiraRepository>() { repository },
PreventTransitions = true,
ProviderMetadata = AtlassianConstants.AzDProviderMetadata
};
return bulkRequest;
}
public async Task<JiraDevInfoResponse> BulkUpdateAsync(
JiraRepository repository
) {
var updateSequenceId = await _idManager.NextAsync();
repository.UpdateSequenceId = updateSequenceId;
foreach (var c in repository.Commits)
{
c.UpdateSequenceId = updateSequenceId;
}
foreach (var b in repository.Branches)
{
b.UpdateSequenceId = updateSequenceId;
}
foreach (var pr in repository.PullRequests)
{
pr.UpdateSequenceId = updateSequenceId;
}
var bulkRequest = BuildBulkRequest(repository);
var content = bulkRequest.ToJsonContent();
var response = await _client.PostAsync("bulk", content);
var result = await response.DeserializeAsync<JiraDevInfoResponse>();
return result;
}
}
public static class HttpClientExtensions
{
public static void SetApiBaseUri(this HttpClient client, string cloudId, string entityType)
{
client.BaseAddress = ApiBaseUri(cloudId, entityType);
}
public static Uri ApiBaseUri(string cloudId, string entityType)
{
// example:
// https://api.atlassian.com/jira/devinfo/0.1/cloud/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/bulk
var baseUrl = string.Format("{0}jira/{1}/0.1/cloud/{2}/",
AtlassianConstants.AtlassianApiRootUrl,
entityType,
cloudId
);
var baseUri = new Uri(baseUrl);
return baseUri;
}
}
public static class JiraOAuthHttpClientExtensions
{
public static readonly JsonSerializerOptions SerializerOptions =
new JsonSerializerOptions() {PropertyNamingPolicy = JsonNamingPolicy.CamelCase};
public static HttpContent ToJsonContent<T>(this T value)
{
var json = JsonSerializer.Serialize(value, SerializerOptions);
var content = new StringContent(json, Encoding.UTF8, "application/json");
return content;
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Nito.AsyncEx;
namespace YourProject
{
public interface IUpdateSequenceIdManager // you need to write your own implementation of this
{
Task<long> NextAsync();
Task<long> CurrentAsync();
}
// This uses a MSSQL database backend with a very simple table to manage sequence number generation.
// But, you need to write your own implementation of this. This one is not great.
// CREATE TABLE [dbo].[tbl_UpdateSequenceIds](
// [Name] [varchar](50) NULL,
// [Id] [bigint] NULL
// ) ON [PRIMARY]
// GO
// INSERT INTO [dbo].[tbl_UpdateSequenceIds] ([Name], [Id]) VALUES ('Current', 0)
// GO
public class UpdateSequenceIdManager : IUpdateSequenceIdManager
{
private readonly AzDPullRequestsDbContext _dbContext;
private readonly AsyncReaderWriterLock _seqLock = new AsyncReaderWriterLock();
public UpdateSequenceIdManager(YourProjectsDbContext dbContext)
{
_dbContext = dbContext;
}
public async Task<long> NextAsync()
{
using (await _seqLock.WriterLockAsync())
{
var sequenceRecord = await _dbContext.UpdateSequenceIds.FirstAsync(i => i.Name == "Current");
var next = ++sequenceRecord.Id;
await _dbContext.SaveChangesAsync();
return next;
}
}
public async Task<long> CurrentAsync()
{
using (await _seqLock.ReaderLockAsync())
{
var sequenceRecord = await _dbContext.UpdateSequenceIds.FirstAsync(i => i.Name == "Current");
return sequenceRecord.Id;
}
}
}
}

0 comments:

Post a Comment


Creative Commons License
This site uses Alex Gorbatchev's SyntaxHighlighter, and hosted by herdingcode.com's Jon Galloway.