1071 lines
30 KiB
C#
1071 lines
30 KiB
C#
<Query Kind="Program">
|
|
<NuGetReference>Newtonsoft.Json</NuGetReference>
|
|
<NuGetReference>RestSharp</NuGetReference>
|
|
<Namespace>RestSharp</Namespace>
|
|
<Namespace>Newtonsoft.Json</Namespace>
|
|
<Namespace>Newtonsoft.Json.Linq</Namespace>
|
|
<Namespace>RestSharp.Authenticators</Namespace>
|
|
<Namespace>Newtonsoft.Json.Converters</Namespace>
|
|
<Namespace>System.Dynamic</Namespace>
|
|
<Namespace>System</Namespace>
|
|
<Namespace>System.Linq.Dynamic</Namespace>
|
|
<Namespace>System.Web</Namespace>
|
|
</Query>
|
|
|
|
private string BaseUrl = "https://sonarqube.sdt.local";
|
|
private Dictionary<string, string> 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<string, string> Parameters = new Dictionary<string, string>
|
|
{
|
|
{ "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<string, List<string>> GroupUsers = new Dictionary<string, System.Collections.Generic.List<string>>();
|
|
|
|
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<Parameter> {
|
|
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<Issue> 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<string> { });
|
|
GroupUsers.Add("The A-Team", new List<string> { "tlamb", "tdebolt" });
|
|
GroupUsers.Add("Ryan King", new List<string> { "rreimer", "jpollard", "chelm", "aschey" });
|
|
GroupUsers.Add("Cube Killers", new List<string> { "yrhee", "dforero", "lbehmer" });
|
|
GroupUsers.Add("Kobra Kaizer", new List<string> { "mschmidt", "nbuckle", "fsweis", "jmorales" });
|
|
GroupUsers.Add("Team Sloth", new List<string> { "myoung", "bkrehl", "nnawale", "lrenadesouza", "nleroy" });
|
|
GroupUsers.Add("Team Thanos", new List<string> { "bwells", "zbruin", "zhare", "mwynne", "mdeboer", "jnarofsky" });
|
|
GroupUsers.Add("Platform", new List<string> { "mleitch", "awolek", "jrapp", "cbello", "jchau", "hgutta", "ljian" });
|
|
GroupUsers.Add("Boo", new List<string> { "kferchuk", "akuten", "hvladyka", "vfay" });
|
|
GroupUsers.Add("Jedi", new List<string> { "akost", "sdominska", "tparashchak", "opalamar" });
|
|
GroupUsers.Add("Pokemon", new List<string> { "okrainyk", "mstepanov", "yantoniuk" });
|
|
GroupUsers.Add("Tropic Thunder", new List<string> { "ainzhyievskyi", "ihalenok", "dpanteliuk", "akorchynskyi" });
|
|
GroupUsers.Add("Bee Swarmers", new List<string> { "tscanlan", "cburke", "sxie" });
|
|
GroupUsers.Add("The Moustache", new List<string> { "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<ProjectGrouper> Projects { get; set; }
|
|
|
|
public SolutionGrouper(string solution, IOrderedEnumerable<ProjectGrouper> projects)
|
|
{
|
|
Solution = solution;
|
|
Projects = projects.ToList();
|
|
}
|
|
}
|
|
|
|
public class ProjectGrouper
|
|
{
|
|
public string Project { get; set; }
|
|
public IOrderedEnumerable<ComponentGrouper> Issues { get; set; }
|
|
|
|
public ProjectGrouper(string project, IOrderedEnumerable<ComponentGrouper> issues)
|
|
{
|
|
Project = project;
|
|
Issues = issues;
|
|
}
|
|
}
|
|
|
|
public class TeamGrouper
|
|
{
|
|
public string Team { get; set; }
|
|
public List<SolutionGrouper> Solutions { get; set; }
|
|
|
|
public TeamGrouper(string team, IOrderedEnumerable<SolutionGrouper> solutions)
|
|
{
|
|
Team = team;
|
|
Solutions = solutions.ToList();
|
|
}
|
|
}
|
|
|
|
public class ComponentGrouper
|
|
{
|
|
public string Solution { get; set; }
|
|
public List<SummaryGrouper> 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<SummaryGrouper> 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<Issue> 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<Rule> GetRules(RestClient client)
|
|
{
|
|
List<Rule> rules = new List<Rule>();
|
|
var pageNo = 1;
|
|
while (true)
|
|
{
|
|
var info = ListRules(client, pageNo);
|
|
if (info == null) break;
|
|
var deserialized = JsonConvert.DeserializeObject<RootObject>(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<Parameter> {
|
|
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<object> tags { get; set; }
|
|
public List<object> sysTags { get; set; }
|
|
public string lang { get; set; }
|
|
public string langName { get; set; }
|
|
public List<object> @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<RootObject>(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<WebServices>(JsonConvert.SerializeObject(ListWebServices(client))).webServices;
|
|
services.Dump();
|
|
}
|
|
|
|
JObject ListWebServices(RestClient client)
|
|
{
|
|
return GetJson(client, "/api/webservices/list");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Issue Management
|
|
|
|
List<Issue> GetIssues(RestClient client, List<Parameter> parameters = null)
|
|
{
|
|
List<Issue> issues = new List<Issue>();
|
|
var pageNo = 1;
|
|
while (true)
|
|
{
|
|
var info = ListIssues(client, pageNo, parameters);
|
|
if (info == null) break;
|
|
pageNo += 1;
|
|
var root = JsonConvert.DeserializeObject<RootObject>(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<Parameter> parameters = null, int pageSize = 500)
|
|
{
|
|
var queryParameters = new List<Parameter> {
|
|
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<Project> GetProjects(RestClient client, List<Parameter> parameters = null)
|
|
//{
|
|
// List<Project> issues = new List<Project>();
|
|
// var pageNo = 1;
|
|
// while (true)
|
|
// {
|
|
// var info = ListProjects(client, pageNo, parameters);
|
|
// if (info == null) break;
|
|
// pageNo += 1;
|
|
// var root = JsonConvert.DeserializeObject<RootObject>(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<Parameter> parameters = null, int pageSize = 500)
|
|
{
|
|
var queryParameters = new List<Parameter> {
|
|
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<IssueComment> 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<Flow> 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<string> 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<IssueReportIssue> issues;
|
|
}
|
|
|
|
#endregion
|
|
|
|
void Other(RestClient client)
|
|
{
|
|
var issues = GetJson(client, "/api/issues/search?assignees=tlamb&severities=BLOCKER,CRITICAL");
|
|
JsonConvert.DeserializeObject<RootObject>(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<Parameter> 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<ExpandoObject>(client.Execute(request).Content, converter);
|
|
}
|
|
|
|
IEnumerable<T> GetList<T>(RestClient client, string something)
|
|
{
|
|
return Get<List<T>>(client, something);
|
|
}
|
|
|
|
T Get<T>(RestClient client, string something)
|
|
{
|
|
client.BaseUrl = new Uri(BaseUrl + something);
|
|
RestRequest request = new RestRequest();
|
|
request.Method = Method.GET;
|
|
return JsonConvert.DeserializeObject<T>(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<FlowComponent> 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<SonarError> errors { get; set; }
|
|
public List<UserQuery.Issue> issues { get; set; }
|
|
public List<Component> components { get; set; }
|
|
public List<object> facets { get; set; }
|
|
public List<Rule> rules { get; set; }
|
|
public List<User> 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<string> 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<object> changlog { get; set; }
|
|
public List<WSActionParam> Params { get; set; }
|
|
public string deprecatedSince { get; set; }
|
|
}
|
|
|
|
public class WebService
|
|
{
|
|
public string path { get; set; }
|
|
public string description { get; set; }
|
|
public List<WSAction> actions { get; set; }
|
|
public string since { get; set; }
|
|
}
|
|
|
|
public class WebServices
|
|
{
|
|
public List<WebService> webServices { get; set; }
|
|
}
|
|
|
|
#endregion |