Newtonsoft.Json RestSharp RestSharp Newtonsoft.Json Newtonsoft.Json.Linq RestSharp.Authenticators Newtonsoft.Json.Converters System.Dynamic System System.Linq.Dynamic System.Web private string BaseUrl = "https://sonarqube.sdt.local"; private Dictionary ruleDictionary; //private DateTime BeginningOfTheYear = new DateTime(DateTime.Now.Year, 1, 1); private static DateTime CreatedBefore = new DateTime(2020, 8, 13); private static DateTime StartOfSprint = CreatedBefore.AddDays(1); private static DateTime EndOfSprint = CreatedBefore.AddDays(15); private Dictionary Parameters = new Dictionary { { "projects", ""}, // {"types","BUG,VULNERABILITY,SECURITY_HOTSPOT"}, // {"severities","BLOCKER,CRITICAL,MAJOR"}, // {"statuses", "OPEN,CONFIRMED,REOPENED,TO_REVIEW,IN_REVIEW"}, {"resolutions", "FIXED"}, {"s", "CLOSE_DATE"}, {"languages", null}, {"createdBefore", $"{CreatedBefore:yyyy-MM-dd}"}, //{"createdAfter", null}, // $"{BeginningOfTheYear:yyyy-MM-dd}" {"assignees", null} }; #region Enumerations public string GetName(Type enumType, int value) { return Enum.GetName(enumType, value); } public enum SonarType { BUG, VULNERABILITY, CODE_SMELL, SECURITY_HOTSPOT } public enum SonarSeverity { BLOCKER, CRITICAL, MAJOR, MINOR, INFO } public enum QubooSeverity { BLOCKER = 10, CRITICAL = 7, MAJOR = 5, MINOR = 2, INFO = 1 } public enum SonarResolution { UNRESOLVED, FIXED, WONTFIX, FALSE_POSITIVE, REMOVED } public enum SonarStatus { OPEN, TO_REVIEW, IN_REVIEW, REVIEWED, REOPENED, CONFIRMED, RESOLVED, CLOSED } #endregion public Dictionary> GroupUsers = new Dictionary>(); void Main() { RestClient client = new RestClient(); // var projects = ListProjects(client); // projects.Dump(); // return; new { CreatedBefore, StartOfSprint, EndOfSprint}.Dump("Campaign"); PopulateUserGroups(); var value = GetName(typeof(SonarType), (int)SonarType.BUG); //GetJson(client, "/api/languages/list").Dump(); //ListWebServices(client).Dump("List WebServices"); var rules = GetRules(client) .OrderBy(l => l.repo).ThenBy(l => l.key); ruleDictionary = rules .ToDictionary(r => r.key, r => $"{r.name} ({r.severity})"); var parameters = new List { new RestSharp.Parameter("additionalFields","_all", ParameterType.QueryString) }; parameters.AddRange(Parameters.Where(p => !string.IsNullOrEmpty(p.Value)) .Select(p => new RestSharp.Parameter(p.Key, p.Value, ParameterType.QueryString))); var issues = GetIssues(client, parameters) .Where(i => i.closeDate >= StartOfSprint && i.closeDate <= EndOfSprint) // .Where(i => i.closeDate >= CreatedBefore.AddDays(-14) && i.closeDate <= CreatedBefore) ; if (1 == 0) { if (parameters.Select(p => p.Name).Contains("projects")) { $"{parameters.First(p => p.Name == "projects").Value} ({issues.Count()} Issues)".Dump(); } else { $"{issues.Count()} Issues".Dump(); } } issues .Where(i => i.assignee != null) .Select(i => new { i.severity, i.project, i.type, i.status, i.resolution, i.assignee, team = GetUserGroup(i.assignee), i.debt, i.effort, i.closeDate, score = Score(i.severity, i.debt) }) //.GroupBy(i => new {i.assignee, i.severity, i.type}, (g,d) => d) .OrderBy(i => i.team) .ThenBy(i => i.project) .ThenBy(i => i.assignee) .ThenBy(i => i.severity) .ThenBy(i => i.type) .Dump(); //issues.Take(10).Dump("Dump 10 issues"); //ReportIssues(issues); } public int Score(string severity, string debt, int items = 1) { if (debt == null || severity == null) return 0; var qSeverity = (QubooSeverity)Enum.Parse(typeof(QubooSeverity), severity); var iDebt = int.Parse(Regex.Replace(debt, @"[^\d]", "")); return Score(qSeverity, iDebt, items); } public int Score(QubooSeverity severity, int debt, int items = 1) { //$"Math.Max(1, {debt} / 10 * {(int)severity}) * {items} items".Dump(); return Math.Max(1, debt / 10 * (int)severity) * items; } void ReportIssues(IEnumerable issues) { var byTeam = issues .OrderBy(i => GetUserGroup(i.assignee ?? i.author ?? "")) .ThenBy(i => i.component) .ThenBy(i => i.type) .ThenBy(i => i.severity) .Select(i => new { assignee = GetUserGroup(i.assignee ?? i.author ?? "Not assigned"), i.component, i.message, i.type, i.status, i.severity, i.effort, i.creationDate, i.closeDate, lineNumber = i.line, i.taglist }) .GroupBy(i => i.assignee, (g, d) => new TeamGrouper( g, d.GroupBy(dd => new { type = Path.GetExtension(dd.component), dd.component }, (gg, gd) => new ComponentGrouper( gg.type, gg.component, gd.GroupBy(yy => new { yy.creationDate.Year, yy.creationDate.Month, yy.type, yy.status, yy.severity }, (gdk, gdd) => new SummaryGrouper( gdk.Year, gdk.Month, gdk.status, gdk.type, gdk.severity, gdd.Count() )).OrderByDescending(yy => yy.Year) .ThenByDescending(yy => yy.Month) )) .GroupBy(dd => dd.Solution, (xg, xd) => new SolutionGrouper ( xg, xd.GroupBy(y => y.Project, (yg, yd) => new ProjectGrouper ( yg, yd.OrderBy(x => x.Path).ThenBy(x => x.FileName) )).OrderBy(x => x.Project) )) .OrderBy(dd => dd.Solution) )) .OrderBy(i => i.Team).ToList(); byTeam.ForEach(team => { team.Solutions.ForEach(solution => { solution.Solution.Dump().DumpX(team.Team); ; solution.Projects.Dump(10).DumpX(team.Team); }); }); } #region User Group Definition public void PopulateUserGroups() { GroupUsers.Add("Architecture", new List { }); GroupUsers.Add("The A-Team", new List { "tlamb", "tdebolt" }); GroupUsers.Add("Ryan King", new List { "rreimer", "jpollard", "chelm", "aschey" }); GroupUsers.Add("Cube Killers", new List { "yrhee", "dforero", "lbehmer" }); GroupUsers.Add("Kobra Kaizer", new List { "mschmidt", "nbuckle", "fsweis", "jmorales" }); GroupUsers.Add("Team Sloth", new List { "myoung", "bkrehl", "nnawale", "lrenadesouza", "nleroy" }); GroupUsers.Add("Team Thanos", new List { "bwells", "zbruin", "zhare", "mwynne", "mdeboer", "jnarofsky" }); GroupUsers.Add("Platform", new List { "mleitch", "awolek", "jrapp", "cbello", "jchau", "hgutta", "ljian" }); GroupUsers.Add("Boo", new List { "kferchuk", "akuten", "hvladyka", "vfay" }); GroupUsers.Add("Jedi", new List { "akost", "sdominska", "tparashchak", "opalamar" }); GroupUsers.Add("Pokemon", new List { "okrainyk", "mstepanov", "yantoniuk" }); GroupUsers.Add("Tropic Thunder", new List { "ainzhyievskyi", "ihalenok", "dpanteliuk", "akorchynskyi" }); GroupUsers.Add("Bee Swarmers", new List { "tscanlan", "cburke", "sxie" }); GroupUsers.Add("The Moustache", new List { "wrusinko" }); } public string GetUserGroup(string user) { var group = GroupUsers.Where(gu => gu.Value.Contains(user)).FirstOrDefault(); return group.Key ?? $"unknown {user}"; } #endregion #region Issues Report public class SolutionGrouper { public string Solution { get; set; } public List Projects { get; set; } public SolutionGrouper(string solution, IOrderedEnumerable projects) { Solution = solution; Projects = projects.ToList(); } } public class ProjectGrouper { public string Project { get; set; } public IOrderedEnumerable Issues { get; set; } public ProjectGrouper(string project, IOrderedEnumerable issues) { Project = project; Issues = issues; } } public class TeamGrouper { public string Team { get; set; } public List Solutions { get; set; } public TeamGrouper(string team, IOrderedEnumerable solutions) { Team = team; Solutions = solutions.ToList(); } } public class ComponentGrouper { public string Solution { get; set; } public List Issues { get; set; } public string Component { get; set; } public string Path { get; set; } public string Project { get; set; } public string FileName { get; set; } public string Extension { get; set; } public string Url { get; set; } public ComponentGrouper() { } public ComponentGrouper(string type, string component, IOrderedEnumerable issues) { Component = component.Split(new[] { ':' }).Last().Replace('/', '\\'); Solution = component.Split(new[] { ':' }).First(); Extension = type.TrimStart(new[] { '.' }); var filePath = $@"D:\{Component}"; Path = System.IO.Path.GetDirectoryName(filePath); var pathParts = Path.Split(System.IO.Path.DirectorySeparatorChar); //pathParts.Dump(); if (new[] { "Code", "Internal Tools", "src" }.Contains(pathParts[1])) { Project = pathParts.Skip(2).First(); Path = string.Join($"{System.IO.Path.DirectorySeparatorChar}", pathParts.Skip(2)); } else { Project = pathParts.Skip(1).First(); Path = string.Join($"{System.IO.Path.DirectorySeparatorChar}", pathParts.Skip(1)); } FileName = System.IO.Path.GetFileName(filePath); Issues = issues.ToList(); Url = HttpUtility.HtmlEncode($@"{component}"); } object ToDump() => new { Issues, Path, FileName = new Hyperlinq($@"https://sonarqube.sdt.local/code?id={Url}", FileName) }; } public class SummaryGrouper { public int Year { get; set; } public int Month { get; set; } public string Status { get; set; } public string Type { get; set; } public string Severity { get; set; } public int Items { get; set; } public SummaryGrouper() { } public SummaryGrouper(int year, int month, string status, string type, string severity, int count) { Year = year; Month = month; Status = status; Type = type; Severity = severity; Items = count; } object ToDump() => new { Date = $"{(new DateTime(Year, Month, 1)):yyyy/MM}", Status, Type, Severity, Count = $"{Items}" }; } #endregion public void WriteReport(List issues) { var reports = issues .GroupBy(sdm => new { author = sdm.author ?? sdm.assignee ?? "", project = sdm.project, componentKey = sdm.componentKey, status = sdm.status ?? "", severity = sdm.severity ?? "", resolution = sdm.resolution ?? "", type = sdm.type, rule = sdm.rule }, (g, d) => new IssueReport { group = new IssueGroup { author = g.author, project = g.project, componentKey = g.componentKey, status = g.status, severity = g.severity, resolution = g.resolution, type = g.type, rule = g.rule, issues = d.Count() }, issues = d.Select(issue => (IssueReportIssue)issue).ToList() }) .OrderBy(r => r.group.author) .ThenBy(r => r.group.project) .ToList(); reports.ForEach(report => { //"".PadLeft(40, '―').Dump(); // report.group.Dump(); var issueList = report.issues .GroupBy(i => new { i.message, i.debt, i.effort, i.fileType, i.assignee, i.comment }, (g, d) => new { g.message, debt = g.debt * d.Count(), effort = g.effort * d.Count(), g.fileType, g.assignee, g.comment, createdAt = $"{d.Min(i => i.createdAt):d}", closedAt = $"{d.Max(i => i.closedAt):d}", daysToClose = (int)d.Max(i => i.closedAt).Subtract(d.Min(i => i.createdAt)).TotalDays, occurred = d.Count() }); new { report.group, issueList }.Dump(); // if (issueList.Count() > 1000) // { // issueList // .Select((issue, index) => new { issue, index }) // .GroupBy(x => x.index / 1000, (key, y) => y.Select(z => z.issue)) // .Dump(); // } // else // { // issueList.Dump(); // } }); } public void SummarizeIssues(RestClient client) { var types = new[] { "VULNERABILITY", "BUG", "CODE_SMELL" }; var severities = new[] { "BLOCKER", "CRITICAL", "MAJOR" }; var issues = GetIssues(client); var CiStarterSet = issues .Where(i => i.status != "CLOSED" && new[] { "BLOCKER", "CRITICAL", "MAJOR" }.Contains(i.severity) ); CiStarterSet .OrderBy(i => i.rule == "csharpsquid:S3776" ? 99 : i.severity == "MAJOR" ? 2 : i.severity == "BLOCKER" ? 0 : i.severity == "CRITICAL" ? 1 : 99) .GroupBy(i => i.component , (g, d) => new { key = g, critical = d.Count(x => x.severity == "CRITICAL"), ageInYears = (int)(d.Min(x => DateTime.Now.Subtract(x.creationDate).TotalDays) / 365), team = "" }) .OrderByDescending(i => i.ageInYears) .ThenBy(i => i.critical) .ThenBy(i => i.key) //.ThenBy(i => i.key) .Dump(); } JObject GetVersion(RestClient client) { return GetJson(client, "/api/server/system"); } #region Rule Management List GetRules(RestClient client) { List rules = new List(); var pageNo = 1; while (true) { var info = ListRules(client, pageNo); if (info == null) break; var deserialized = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(info)).rules; if (deserialized == null || !deserialized.Any()) break; pageNo += 1; var page = deserialized; //.Where(i => i.rule.StartsWith("csharpsquid:")); if (!page.Any()) continue; rules.AddRange(page); } return rules; } JObject ListRules(RestClient client, int page = 1, int pageSize = 500) { return GetJson(client, "/api/rules/search", new List { new Parameter("ps", pageSize, ParameterType.QueryString), new Parameter("p", page, ParameterType.QueryString) }); } public class Rule { public string key { get; set; } public string repo { get; set; } public string name { get; set; } public DateTime createdAt { get; set; } public string htmlDesc { get; set; } public string mdDesc { get; set; } public string severity { get; set; } public bool isBlockerIssue => severity == "BLOCKER"; public bool isCriticalIssue => severity == "CRITICAL"; public bool isMajorIssue => severity == "MAJOR"; public string status { get; set; } public string internalKey { get; set; } public bool isTemplate { get; set; } public List tags { get; set; } public List sysTags { get; set; } public string lang { get; set; } public string langName { get; set; } public List @params { get; set; } public string defaultDebtRemFnType { get; set; } public string defaultDebtRemFnCoeff { get; set; } public bool debtOverloaded { get; set; } public string debtRemFnType { get; set; } public string debtRemFnCoeff { get; set; } public string defaultRemFnType { get; set; } public string defaultRemFnGapMultiplier { get; set; } public string remFnType { get; set; } public string remFnGapMultiplier { get; set; } public bool remFnOverloaded { get; set; } public string scope { get; set; } public bool isExternal { get; set; } public string type { get; set; } public bool isVulnerability => type == "VULNERABILITY"; public bool isBug => type == "BUG"; public bool isCodeSmell => type == "CODE_SMELL"; } #endregion #region User Management JObject ListUsers(RestClient client) { return GetJson(client, "/api/users/search"); } public void AddNewUsers(RestClient client) { var names = @"User Name Email First Name Last Name sdt\ainzhyievskyi ainzhyievskyi@stratadecision.com Artur Inzhyievskyi sdt\dpanteliuk dpanteliuk@stratadecision.com Dmytro Panteliuk sdt\vivanov vivanov@stratadecision.com Vadym Ivanov sdt\akorchynskyi akorchynskyi@stratadecision.com Andrii Korchynskyi sdt\akost aksot@stratadecision.com Andrii Kost sdt\tparashchak tparashchak@stratadecision.com Taras Parashchak sdt\sdominska sdominska@stratadecision.com Svitlana Dominska sdt\opalamar opalamar@stratadecision.com Oleksii Palamar sdt\vfay vfay@stratadecision.com Vasyl Fay sdt\kferchuk kferchuk@stratadecision.com Kostyantyn Ferchuk sdt\hvladyka hvladyka@stratadecision.com Halyna Vladyka sdt\ihalenok ihalenok@stratadecision.com Iryna Halenok sdt\akuten akuten@stratadecision.com Andrii Kuten sdt\yantoniuk yantoniuk@stratadecision.com Yurii Antoniuk sdt\mstepanov mstepanov@stratadecision.com Mykhailo Stepanov" .Replace("\r\n", "\r").Split('\r').Skip(1) .Select(x => x.Split('\t')).ToList() .ConvertAll(x => new NewUser { name = $"{x[2]} {x[3]}", login = x[0].Replace(@"sdt\", ""), password = "Password1", email = x[1] }); var users = ListUsers(client); var logins = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(users)).users; names.Where(n => !logins.Select(l => l.login).Contains(n.login)).Dump(); names.Where(n => !logins.Select(l => l.login).Contains(n.login)).ToList() .ForEach(n => CreateUser(client, n)); } void CreateUser(RestClient client, NewUser user) { client.BaseUrl = new Uri(BaseUrl); client.Authenticator = new HttpBasicAuthenticator("admin", "admin"); var request = new RestRequest(); request.Resource = "/api/users/create"; request.Method = Method.POST; request.RequestFormat = DataFormat.Json; request.AddParameter(new Parameter("login", user.login, ParameterType.QueryString)); request.AddParameter(new Parameter("password", user.password, ParameterType.QueryString)); request.AddParameter(new Parameter("password_confirmation", user.password, ParameterType.QueryString)); request.AddParameter(new Parameter("name", user.name, ParameterType.QueryString)); request.AddParameter(new Parameter("email", user.email, ParameterType.QueryString)); request.Dump(); var response = client.Execute(request); response.Dump(); } public class User { public string login { get; set; } public string name { get; set; } } public class NewUser : User { public string email { get; set; } public string password { get; set; } } #endregion #region Web Services public void DumpWebServices(RestClient client) { var services = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(ListWebServices(client))).webServices; services.Dump(); } JObject ListWebServices(RestClient client) { return GetJson(client, "/api/webservices/list"); } #endregion #region Issue Management List GetIssues(RestClient client, List parameters = null) { List issues = new List(); var pageNo = 1; while (true) { var info = ListIssues(client, pageNo, parameters); if (info == null) break; pageNo += 1; var root = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(info)); if (root.errors?.Any() ?? false) { root.errors.Dump(); break; } var deserialized = root.issues; if (deserialized == null || !deserialized.Any()) break; issues.AddRange(deserialized); } return issues; } JObject ListIssues(RestClient client, int page = 1, List parameters = null, int pageSize = 500) { var queryParameters = new List { new Parameter("ps", pageSize, ParameterType.QueryString), new Parameter("p", page, ParameterType.QueryString), new Parameter("additionalFields", "comments", ParameterType.QueryString) }; if (parameters?.Any() ?? false) queryParameters.RemoveAll(qp => parameters.Select(p => p.Name).Contains(qp.Name)); if (parameters != null) queryParameters.AddRange(parameters); return GetJson(client, "/api/issues/search", queryParameters); } #endregion #region Project Management //List GetProjects(RestClient client, List parameters = null) //{ // List issues = new List(); // var pageNo = 1; // while (true) // { // var info = ListProjects(client, pageNo, parameters); // if (info == null) break; // pageNo += 1; // var root = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(info)); // if (root.errors?.Any() ?? false) // { // root.errors.Dump(); // break; // } // var deserialized = root.issues; // if (deserialized == null || !deserialized.Any()) break; // issues.AddRange(deserialized); // } // return issues; //} JObject ListProjects(RestClient client, int page = 1, List parameters = null, int pageSize = 500) { var queryParameters = new List { new Parameter("ps", pageSize, ParameterType.QueryString), new Parameter("p", page, ParameterType.QueryString) }; if (parameters?.Any() ?? false) queryParameters.RemoveAll(qp => parameters.Select(p => p.Name).Contains(qp.Name)); if (parameters != null) queryParameters.AddRange(parameters); return GetJson(client, "/api/projects/search", queryParameters); } #endregion #region Data Classes public class IssueComment { public string key { get; set; } public string login { get; set; } public string htmlText { get; set; } public string markdown { get; set; } public bool updatable { get; set; } public DateTime createdAt { get; set; } } public class IssueGroup { public string project { get; set; } public string componentKey { get; set; } public string author { get; set; } public string severity { get; set; } public string status { get; set; } public string type { get; set; } public string resolution { get; set; } public string rule { get; set; } public int issues { get; set; } public object ToDump() => new[] { this }; } public class Issue { public string severity { get; set; } public SonarSeverity severityId => (SonarSeverity)Enum.Parse(typeof(SonarSeverity), severity); public bool isBlocker => severityId == SonarSeverity.BLOCKER; public bool isCritical => severityId == SonarSeverity.CRITICAL; public bool isMajor => severityId == SonarSeverity.MAJOR; public bool isMinor => severityId == SonarSeverity.MINOR; public bool isInfo => severityId == SonarSeverity.INFO; public string status { get; set; } public SonarStatus statusId => (SonarStatus)Enum.Parse(typeof(SonarStatus), status); public bool isOpen => statusId == SonarStatus.OPEN; public bool isClosed => statusId == SonarStatus.CLOSED; public bool isReviewed => statusId == SonarStatus.REVIEWED; public bool isResolved => statusId == SonarStatus.RESOLVED; public bool isInReview => statusId == SonarStatus.TO_REVIEW; public string type { get; set; } public SonarType typeId => (SonarType)Enum.Parse(typeof(SonarType), type); public bool isVulnerability => typeId == SonarType.VULNERABILITY; public bool isSecurityHotspot => typeId == SonarType.SECURITY_HOTSPOT; public bool isBug => typeId == SonarType.BUG; public bool isCodeSmell => typeId == SonarType.CODE_SMELL; public string resolution { get; set; } public SonarResolution resolutionId => (SonarResolution)Enum.Parse(typeof(SonarResolution), resolution); public bool wontfix => resolutionId == SonarResolution.WONTFIX; public List comments { get; set; } public string message { get; set; } public string key { get; set; } public string rule { get; set; } public string debt { get; set; } public string project { get; set; } public string component { get; set; } internal string[] componentParts => component.Split('/'); public string componentKey => componentParts.Count() > 3 && componentParts[1] == "Strata.Jazz.Web" && componentParts[2] == "Areas" ? Regex.Replace(componentParts[3], "^Strata.", "") : componentParts.Count() > 3 && componentParts[1] == "Strata.Jazz.Web" && componentParts[2] != "Areas" ? Regex.Replace(componentParts[2], "^Strata.", "") : componentParts.Count() > 1 ? Regex.Replace(componentParts[1], "^Strata.", "") : component; public string fileType => component.Substring(component.LastIndexOf('.') + 1); public int line { get; set; } public TextRange textRange { get; set; } public List flows { get; set; } public string effort { get; set; } public string assignee { get; set; } public string author { get; set; } // public string hash { get; set; } public List tags { get; set; } public string taglist => string.Join(", ", tags); public DateTime creationDate { get; set; } public int ageInYears => (int)DateTime.Now.Subtract(creationDate).TotalDays / 365; public DateTime updateDate { get; set; } // public string organization { get; set; } public bool fromHotspot { get; set; } public DateTime? closeDate { get; set; } // public string __invalid_name__effort { get; set; } public static explicit operator UserQuery.IssueReportIssue(Issue issue) { var debt = issue.debt ?? "0"; var effort = issue.effort ?? "0"; var r1 = new Regex(@"[^\d]*"); var r2 = new Regex(@"[\d]*"); var debtInterval = 1; switch (r2.Replace(debt, "")) { case "min": debtInterval = 1; break; case "day": debtInterval = 60 * 24; break; default: debtInterval = Int32.MaxValue; break; } var effortInterval = 1; switch (r2.Replace(effort, "")) { case "min": effortInterval = 1; break; case "day": effortInterval = 60 * 24; break; default: effortInterval = Int32.MaxValue; break; } var i = new UserQuery.IssueReportIssue { message = issue.message, debt = Convert.ToInt32(r1.Replace(debt, "")) * debtInterval, effort = Convert.ToInt32(r1.Replace(effort, "")) * effortInterval, fileType = issue.fileType, assignee = issue.comments.FirstOrDefault()?.login ?? "", comment = issue.comments.FirstOrDefault()?.markdown ?? "", createdAt = issue.creationDate, closedAt = issue.comments.FirstOrDefault()?.createdAt ?? issue.closeDate ?? DateTime.Now }; return i; } } public class IssueReportGroup { public string message { get; set; } public int debt { get; set; } public int effort { get; set; } public string fileType { get; set; } public string assignee { get; set; } public string comment { get; set; } } public class IssueReportIssue : IssueReportGroup { public DateTime createdAt { get; set; } public DateTime closedAt { get; set; } public int daysToClose { get; set; } public int occurred { get; set; } } public class IssueReport { public IssueGroup group; public List issues; } #endregion void Other(RestClient client) { var issues = GetJson(client, "/api/issues/search?assignees=tlamb&severities=BLOCKER,CRITICAL"); JsonConvert.DeserializeObject(JsonConvert.SerializeObject(issues)).issues .GroupBy(i => new { i.rule, i.project, i.type }, (g, d) => new { issue = new { type = g.type, id = g.rule, project = g.project, severity = d.First().severity, message = d.First().message, issues = d.Count() }, issues = d.Select(i => new { i.status, i.component, i.line, i.message, i.textRange, i.effort, i.debt, i.key }) }) .Dump(); } // Define other methods and classes here #region RestSharp Helpers JObject GetJson(RestClient client, string something, List parameters = null) { client.BaseUrl = new Uri(BaseUrl); RestRequest request = new RestRequest(); request.Resource = something; request.Method = Method.GET; if (parameters?.Any() ?? false) request.Parameters.AddRange(parameters); return JObject.Parse(client.Execute(request).Content); } dynamic GetList(RestClient client, string something) { client.BaseUrl = new Uri(BaseUrl + something); RestRequest request = new RestRequest(); request.Method = Method.GET; var converter = new ExpandoObjectConverter(); return JsonConvert.DeserializeObject(client.Execute(request).Content, converter); } IEnumerable GetList(RestClient client, string something) { return Get>(client, something); } T Get(RestClient client, string something) { client.BaseUrl = new Uri(BaseUrl + something); RestRequest request = new RestRequest(); request.Method = Method.GET; return JsonConvert.DeserializeObject(client.Execute(request).Content, new JsonSerializerSettings { Error = delegate (object sender, Newtonsoft.Json.Serialization.ErrorEventArgs args) { args.ErrorContext.Error.Message.Dump(); args.ErrorContext.Handled = true; } }); } #endregion #region json2csharp public class Paging { public int pageIndex { get; set; } public int pageSize { get; set; } public int total { get; set; } } public class TextRange { public int startLine { get; set; } public int endLine { get; set; } public int startOffset { get; set; } public int endOffset { get; set; } object ToDump() => startLine != endLine ? $"L{startLine}:{endLine},O{startOffset}:{endOffset}" : $"L{startLine},O{startOffset}:{endOffset}"; } public class FlowComponent { public string component { get; set; } public TextRange textRange { get; set; } public string msg { get; set; } } public class Flow { public List locations { get; set; } } public class SonarError { public string msg { get; set; } } public class Component { public string organization { get; set; } public string key { get; set; } public string uuid { get; set; } public bool enabled { get; set; } public string qualifier { get; set; } public string name { get; set; } public string longName { get; set; } public string path { get; set; } } public class RootObject { public int total { get; set; } public int p { get; set; } public int ps { get; set; } public UserQuery.Paging paging { get; set; } public int effortTotal { get; set; } public int debtTotal { get; set; } // public int majorIssues => issues.Count(i => i.severity == "MAJOR"); // public int criticalIssues => issues.Count(i => i.severity == "CRITICAL"); public int vulnerabilityIssues => issues.Count(i => i.type == "VULNERABILITY"); public int bugIssues => issues.Count(i => i.type == "BUG"); public int codeSmells => issues.Count(i => i.type == "CODE_SMELL"); public List errors { get; set; } public List issues { get; set; } public List components { get; set; } public List facets { get; set; } public List rules { get; set; } public List users { get; set; } } #endregion #region WebServices public class WSActionParam { public string key { get; set; } public string description { get; set; } public bool required { get; set; } public bool @internal { get; set; } public object exampleValue { get; set; } public string defaultValue { get; set; } public List possibleValues { get; set; } public string deprecatedKey { get; set; } public string deprecatedKeySince { get; set; } public int? maximumValue { get; set; } public string since { get; set; } public string deprecatedSince { get; set; } public int? minimumLength { get; set; } public int? maxValuesAllowed { get; set; } public int? maximumLength { get; set; } } public class WSAction { public string key { get; set; } public string description { get; set; } public string since { get; set; } public bool @internal { get; set; } public bool post { get; set; } public bool hasResponseExample { get; set; } public List changlog { get; set; } public List Params { get; set; } public string deprecatedSince { get; set; } } public class WebService { public string path { get; set; } public string description { get; set; } public List actions { get; set; } public string since { get; set; } } public class WebServices { public List webServices { get; set; } } #endregion