From ceb4d7c7a4a4662356b8476f10d234d452804b87 Mon Sep 17 00:00:00 2001 From: Leandro Monaco Date: Fri, 4 Feb 2022 10:02:31 +1000 Subject: [PATCH] [Tools] Added JIRA Reporting --- Tools/Tools.sln | 6 ++ Tools/src/JiraReporting/JiraReporting.csproj | 18 ++++ Tools/src/JiraReporting/Model/Assignee.cs | 7 ++ Tools/src/JiraReporting/Model/Field.cs | 6 ++ Tools/src/JiraReporting/Model/Fields.cs | 24 +++++ Tools/src/JiraReporting/Model/Issue.cs | 10 ++ Tools/src/JiraReporting/Model/IssueType.cs | 7 ++ .../src/JiraReporting/Model/JqlQueryResult.cs | 12 +++ Tools/src/JiraReporting/Model/Parent.cs | 7 ++ Tools/src/JiraReporting/Model/Points.cs | 7 ++ Tools/src/JiraReporting/Model/Severity.cs | 7 ++ Tools/src/JiraReporting/Model/Sprint.cs | 10 ++ Tools/src/JiraReporting/Model/Status.cs | 7 ++ Tools/src/JiraReporting/Options.cs | 23 +++++ Tools/src/JiraReporting/Program.cs | 92 +++++++++++++++++++ .../JiraReporting/Report/BacklogReportRow.cs | 23 +++++ Tools/src/JiraReporting/Report/Helper.cs | 57 ++++++++++++ 17 files changed, 323 insertions(+) create mode 100644 Tools/src/JiraReporting/JiraReporting.csproj create mode 100644 Tools/src/JiraReporting/Model/Assignee.cs create mode 100644 Tools/src/JiraReporting/Model/Field.cs create mode 100644 Tools/src/JiraReporting/Model/Fields.cs create mode 100644 Tools/src/JiraReporting/Model/Issue.cs create mode 100644 Tools/src/JiraReporting/Model/IssueType.cs create mode 100644 Tools/src/JiraReporting/Model/JqlQueryResult.cs create mode 100644 Tools/src/JiraReporting/Model/Parent.cs create mode 100644 Tools/src/JiraReporting/Model/Points.cs create mode 100644 Tools/src/JiraReporting/Model/Severity.cs create mode 100644 Tools/src/JiraReporting/Model/Sprint.cs create mode 100644 Tools/src/JiraReporting/Model/Status.cs create mode 100644 Tools/src/JiraReporting/Options.cs create mode 100644 Tools/src/JiraReporting/Program.cs create mode 100644 Tools/src/JiraReporting/Report/BacklogReportRow.cs create mode 100644 Tools/src/JiraReporting/Report/Helper.cs diff --git a/Tools/Tools.sln b/Tools/Tools.sln index 3b02c0c..f1c721e 100644 --- a/Tools/Tools.sln +++ b/Tools/Tools.sln @@ -15,6 +15,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IntegrationConnectors.Commo EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OctopusCertificateReferenceFinder", "src\OctopusCertificateReferenceFinder\OctopusCertificateReferenceFinder.csproj", "{41DB617A-081F-496E-9DDB-93948488FD21}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JiraReporting", "src\JiraReporting\JiraReporting.csproj", "{EB5D47D5-6702-4EC0-A1E3-CD48A16C1ECE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -41,6 +43,10 @@ Global {41DB617A-081F-496E-9DDB-93948488FD21}.Debug|Any CPU.Build.0 = Debug|Any CPU {41DB617A-081F-496E-9DDB-93948488FD21}.Release|Any CPU.ActiveCfg = Release|Any CPU {41DB617A-081F-496E-9DDB-93948488FD21}.Release|Any CPU.Build.0 = Release|Any CPU + {EB5D47D5-6702-4EC0-A1E3-CD48A16C1ECE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EB5D47D5-6702-4EC0-A1E3-CD48A16C1ECE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EB5D47D5-6702-4EC0-A1E3-CD48A16C1ECE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EB5D47D5-6702-4EC0-A1E3-CD48A16C1ECE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Tools/src/JiraReporting/JiraReporting.csproj b/Tools/src/JiraReporting/JiraReporting.csproj new file mode 100644 index 0000000..40d2c0c --- /dev/null +++ b/Tools/src/JiraReporting/JiraReporting.csproj @@ -0,0 +1,18 @@ + + + + Exe + net5.0 + + + + + + + + + + + + + diff --git a/Tools/src/JiraReporting/Model/Assignee.cs b/Tools/src/JiraReporting/Model/Assignee.cs new file mode 100644 index 0000000..e4e1ffe --- /dev/null +++ b/Tools/src/JiraReporting/Model/Assignee.cs @@ -0,0 +1,7 @@ +namespace JiraReport +{ + public class Assignee + { + public string DisplayName { get; set; } + } +} \ No newline at end of file diff --git a/Tools/src/JiraReporting/Model/Field.cs b/Tools/src/JiraReporting/Model/Field.cs new file mode 100644 index 0000000..4e7ef5f --- /dev/null +++ b/Tools/src/JiraReporting/Model/Field.cs @@ -0,0 +1,6 @@ +namespace JiraReport +{ + public class Field + { + } +} \ No newline at end of file diff --git a/Tools/src/JiraReporting/Model/Fields.cs b/Tools/src/JiraReporting/Model/Fields.cs new file mode 100644 index 0000000..8e7c64e --- /dev/null +++ b/Tools/src/JiraReporting/Model/Fields.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace JiraReport +{ + public class Fields + { + public string Summary { get; set; } + public Parent Parent { get; set; } + public Status Status { get; set; } + public IssueType IssueType { get; set; } + public Assignee Assignee { get; set; } + + [JsonPropertyName("customfield_10263")] + public Severity Severity { get; set; } + + [JsonPropertyName("customfield_10018")] + public List Sprints { get; set; } + + [JsonPropertyName("customfield_10281")] + public Points Points { get; set; } + + } +} \ No newline at end of file diff --git a/Tools/src/JiraReporting/Model/Issue.cs b/Tools/src/JiraReporting/Model/Issue.cs new file mode 100644 index 0000000..ea3c94e --- /dev/null +++ b/Tools/src/JiraReporting/Model/Issue.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace JiraReport +{ + public class Issue + { + public Fields Fields { get; set; } + public string Key { get; set; } + } +} \ No newline at end of file diff --git a/Tools/src/JiraReporting/Model/IssueType.cs b/Tools/src/JiraReporting/Model/IssueType.cs new file mode 100644 index 0000000..f57fa41 --- /dev/null +++ b/Tools/src/JiraReporting/Model/IssueType.cs @@ -0,0 +1,7 @@ +namespace JiraReport +{ + public class IssueType + { + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/Tools/src/JiraReporting/Model/JqlQueryResult.cs b/Tools/src/JiraReporting/Model/JqlQueryResult.cs new file mode 100644 index 0000000..a08d546 --- /dev/null +++ b/Tools/src/JiraReporting/Model/JqlQueryResult.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace JiraReport +{ + internal class JqlQueryResult + { + public int StartAt { get; set; } + public int MaxResults { get; set; } + public int Total { get; set; } + public List Issues { get; set; } + } +} \ No newline at end of file diff --git a/Tools/src/JiraReporting/Model/Parent.cs b/Tools/src/JiraReporting/Model/Parent.cs new file mode 100644 index 0000000..444baba --- /dev/null +++ b/Tools/src/JiraReporting/Model/Parent.cs @@ -0,0 +1,7 @@ +namespace JiraReport +{ + public class Parent + { + public Fields Fields { get; set; } + } +} \ No newline at end of file diff --git a/Tools/src/JiraReporting/Model/Points.cs b/Tools/src/JiraReporting/Model/Points.cs new file mode 100644 index 0000000..f8d078c --- /dev/null +++ b/Tools/src/JiraReporting/Model/Points.cs @@ -0,0 +1,7 @@ +namespace JiraReport +{ + public class Points + { + public string Value { get; set; } + } +} \ No newline at end of file diff --git a/Tools/src/JiraReporting/Model/Severity.cs b/Tools/src/JiraReporting/Model/Severity.cs new file mode 100644 index 0000000..d322221 --- /dev/null +++ b/Tools/src/JiraReporting/Model/Severity.cs @@ -0,0 +1,7 @@ +namespace JiraReport +{ + public class Severity + { + public string Value { get; set; } + } +} \ No newline at end of file diff --git a/Tools/src/JiraReporting/Model/Sprint.cs b/Tools/src/JiraReporting/Model/Sprint.cs new file mode 100644 index 0000000..6f3cf54 --- /dev/null +++ b/Tools/src/JiraReporting/Model/Sprint.cs @@ -0,0 +1,10 @@ +namespace JiraReport +{ + public class Sprint + { + public string Name { get; set; } + public string State { get; set; } + public string StartDate { get; set; } + public string EndDate { get; set; } + } +} \ No newline at end of file diff --git a/Tools/src/JiraReporting/Model/Status.cs b/Tools/src/JiraReporting/Model/Status.cs new file mode 100644 index 0000000..db62d3a --- /dev/null +++ b/Tools/src/JiraReporting/Model/Status.cs @@ -0,0 +1,7 @@ +namespace JiraReport +{ + public class Status + { + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/Tools/src/JiraReporting/Options.cs b/Tools/src/JiraReporting/Options.cs new file mode 100644 index 0000000..8604f8f --- /dev/null +++ b/Tools/src/JiraReporting/Options.cs @@ -0,0 +1,23 @@ +using CommandLine; + +namespace JiraReport +{ + public class Options + { + [Option("JiraEndpoint", Required = true, HelpText = "JIRA Endpoint")] + public string JiraEndpoint { get; set; } + + [Option("JiraProject", Required = true, HelpText = "JIRA Project")] + public string JiraProject { get; set; } + + [Option("JiraUsername", Required = true, HelpText = "JIRA Username")] + public string JiraUsername { get; set; } + + [Option("JiraAuthenticationToken", Required = true, HelpText = "JIRA Authentication Token")] + public string JiraAuthenticationToken { get; set; } + + [Option("PowerBiDatasetEndpoint", Required = true, HelpText = "PowerBI Endpoint")] + public string PowerBiDatasetEndpoint { get; set; } + + } +} \ No newline at end of file diff --git a/Tools/src/JiraReporting/Program.cs b/Tools/src/JiraReporting/Program.cs new file mode 100644 index 0000000..fbc1f2b --- /dev/null +++ b/Tools/src/JiraReporting/Program.cs @@ -0,0 +1,92 @@ +using Ardalis.GuardClauses; +using CommandLine; +using IntegrationConnectors.Common; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace JiraReport +{ + class Program + { + static void Main(string[] args) + { + string jiraEndpoint = string.Empty; + string jiraProject = string.Empty; + string jiraUsername = string.Empty; + string jiraAuthenticationToken = string.Empty; + string powerBiDatasetEndpoint = string.Empty; + + Parser.Default.ParseArguments(args) + .WithParsed(o => + { + jiraEndpoint = Guard.Against.NullOrEmpty(o.JiraEndpoint, nameof(o.JiraEndpoint)); + jiraProject = Guard.Against.NullOrEmpty(o.JiraProject, nameof(o.JiraProject)); + jiraUsername = Guard.Against.NullOrEmpty(o.JiraUsername, nameof(o.JiraUsername)); + jiraAuthenticationToken = Guard.Against.NullOrEmpty(o.JiraAuthenticationToken, nameof(o.JiraAuthenticationToken)); + powerBiDatasetEndpoint = Guard.Against.NullOrEmpty(o.PowerBiDatasetEndpoint, nameof(o.PowerBiDatasetEndpoint)); + }); + + var credentials = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{jiraUsername}:{jiraAuthenticationToken}")); + + HttpConnector httpConnector = new("", credentials, AuthenticationType.Basic); + + var _jsonSerializerOptions = new JsonSerializerOptions() + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + PropertyNameCaseInsensitive = true, + MaxDepth = 0 + }; + + var report = new List(); + + var increment = 100; + var startAt = 0; + var finishAt = 1; + + while (startAt <= finishAt) + { + var query = $@"{{ + ""jql"": ""project={jiraProject}"", + ""maxResults"": {increment}, + ""startAt"": {startAt} + }}"; + + var response = httpConnector.PostAsync($"{jiraEndpoint}/rest/api/2/search", query).Result; + var jqlQueryResult = JsonSerializer.Deserialize(response, _jsonSerializerOptions); + + startAt += increment; + finishAt = jqlQueryResult.Total - 1; + + foreach (var issue in jqlQueryResult.Issues) + { + var row = new BacklogReportRow + { + Date = DateTime.Now.Date, + JiraId = issue.Key, + Sprint = issue.Fields.Sprints?.OrderByDescending(s => s.StartDate).FirstOrDefault().Name, + JiraDescription = issue.Fields.Summary, + Epic = issue.Fields.Parent?.Fields.Summary, + IssueType = issue.Fields.IssueType.Name, + Severity = issue.Fields.Severity?.Value, + Status = issue.Fields.Status.Name, + Points = issue.Fields.Points?.Value, + AssignedTo = issue.Fields.Assignee == null ? "Unassigned" : issue.Fields.Assignee.DisplayName + }; + + report.Add(row); + } + } + + Helper.ExportExcel(report); + + File.WriteAllText($"{Environment.CurrentDirectory}\\report_{DateTime.Now.Date.ToString("yyyy-MM-dd")}.json", JsonSerializer.Serialize(report)); + + var result = httpConnector.PostAsync(powerBiDatasetEndpoint, JsonSerializer.Serialize(report)).Result; + } + } +} diff --git a/Tools/src/JiraReporting/Report/BacklogReportRow.cs b/Tools/src/JiraReporting/Report/BacklogReportRow.cs new file mode 100644 index 0000000..014bffd --- /dev/null +++ b/Tools/src/JiraReporting/Report/BacklogReportRow.cs @@ -0,0 +1,23 @@ +using System; + +namespace JiraReport +{ + internal class BacklogReportRow + { + public BacklogReportRow() + { + } + + public string Epic { get; internal set; } + public string IssueType { get; internal set; } + public string Sprint { get; internal set; } + public string Status { get; internal set; } + public string Points { get; internal set; } + public string AssignedTo { get; internal set; } + public DateTime Date { get; internal set; } + public string JiraId { get; internal set; } + public string JiraDescription { get; internal set; } + public string Severity { get; internal set; } + + } +} \ No newline at end of file diff --git a/Tools/src/JiraReporting/Report/Helper.cs b/Tools/src/JiraReporting/Report/Helper.cs new file mode 100644 index 0000000..33240d5 --- /dev/null +++ b/Tools/src/JiraReporting/Report/Helper.cs @@ -0,0 +1,57 @@ +using NPOI.SS.UserModel; +using NPOI.XSSF.UserModel; +using System; +using System.Collections.Generic; +using System.IO; + +namespace JiraReport +{ + class Helper + { + + public static void ExportExcel(List backlogReportRows) + { + var outputFile = Path.Combine(Directory.GetCurrentDirectory(), "JiraData.xlsx"); + using (var fs = new FileStream(outputFile, FileMode.Create, FileAccess.Write)) + { + IWorkbook workbook; + workbook = new XSSFWorkbook(); + ISheet excelSheet = workbook.CreateSheet("Variables"); + + IRow row = excelSheet.CreateRow(0); + row.CreateCell(0).SetCellValue("Date"); + row.CreateCell(1).SetCellValue("Epic"); + row.CreateCell(2).SetCellValue("JIRA ID"); + row.CreateCell(3).SetCellValue("Description"); + row.CreateCell(4).SetCellValue("IssueType"); + row.CreateCell(5).SetCellValue("Severity"); + row.CreateCell(6).SetCellValue("Status"); + row.CreateCell(7).SetCellValue("Sprint"); + row.CreateCell(8).SetCellValue("Points"); + row.CreateCell(9).SetCellValue("Assigned To"); + + var rowCount = 1; + + + foreach (var backlogReportRow in backlogReportRows) + { + row = excelSheet.CreateRow(rowCount); + row.CreateCell(0).SetCellValue(backlogReportRow.Date); + row.CreateCell(1).SetCellValue(backlogReportRow.Epic); + row.CreateCell(2).SetCellValue(backlogReportRow.JiraId); + row.CreateCell(3).SetCellValue(backlogReportRow.JiraDescription); + row.CreateCell(4).SetCellValue(backlogReportRow.IssueType); + row.CreateCell(5).SetCellValue(backlogReportRow.Severity); + row.CreateCell(6).SetCellValue(backlogReportRow.Status); + row.CreateCell(7).SetCellValue(backlogReportRow.Sprint); + row.CreateCell(8).SetCellValue(Convert.ToDouble(backlogReportRow.Points)); + row.CreateCell(9).SetCellValue(backlogReportRow.AssignedTo); + rowCount++; + } + + workbook.Write(fs); + + } + } + } +}