diff --git a/PanoramicData.IssueAssessment/Execution/CategoryExecutor.cs b/PanoramicData.IssueAssessment/Execution/CategoryExecutor.cs
index 26adab7..c1a9742 100644
--- a/PanoramicData.IssueAssessment/Execution/CategoryExecutor.cs
+++ b/PanoramicData.IssueAssessment/Execution/CategoryExecutor.cs
@@ -157,12 +157,7 @@ private static CategoryResult BuildCategoryResult(AssessmentCategory category, I
.Zip(category.Assessments)
.Where(pair => pair.First.Outcome != AssessmentOutcome.NotApplicable)];
- double? rawScore = null;
- if (applicable.Count > 0 && applicable.TrueForAll(pair => pair.Result.Score.RawScore.HasValue))
- {
- rawScore = applicable.Sum(pair => pair.Result.Score.RawScore!.Value * pair.Assessment.Weight);
- }
-
+ double? rawScore = WeightedRawScore(applicable);
double? normalizedScore = rawScore.HasValue
? ScoreCalculator.Normalize(rawScore.Value, minPossibleRaw, maxPossibleRaw)
: null;
@@ -176,4 +171,12 @@ private static CategoryResult BuildCategoryResult(AssessmentCategory category, I
string reason = $"Executed {resultList.Count} assessment(s) in category '{category.Name}'.";
return new CategoryResult(category.Name, score, reason, resultList) { Weight = category.Weight };
}
+
+ ///
+ /// The weighted sum of the applicable assessments' raw scores; null when none applies or any of them has no score.
+ ///
+ private static double? WeightedRawScore(List<(AssessmentResult Result, Assessment Assessment)> applicable)
+ => applicable.Count > 0 && applicable.TrueForAll(pair => pair.Result.Score.RawScore.HasValue)
+ ? applicable.Sum(pair => pair.Result.Score.RawScore!.Value * pair.Assessment.Weight)
+ : null;
}
diff --git a/PanoramicData.IssueAssessment/Mapping/GitHubIssueMapper.cs b/PanoramicData.IssueAssessment/Mapping/GitHubIssueMapper.cs
index 10156b3..9cd54e8 100644
--- a/PanoramicData.IssueAssessment/Mapping/GitHubIssueMapper.cs
+++ b/PanoramicData.IssueAssessment/Mapping/GitHubIssueMapper.cs
@@ -1,4 +1,5 @@
using System.Text.RegularExpressions;
+using static PanoramicData.IssueAssessment.Mapping.JsonReading;
namespace PanoramicData.IssueAssessment.Mapping;
@@ -29,30 +30,30 @@ public static NormalizedIssue FromRestJson(JsonElement issue)
}
ImmutableList labels = Labels(issue);
- string body = Text(issue, "body") ?? string.Empty;
+ string body = StringAt(issue, "body") ?? string.Empty;
return new NormalizedIssue
{
Key = Key(issue),
- Title = Text(issue, "title"),
+ Title = StringAt(issue, "title"),
Description = body,
IssueType = IssueType(labels),
Priority = Priority(labels),
- Status = Text(issue, "state"),
+ Status = StringAt(issue, "state"),
Assignee = FirstAssignee(issue),
Components = Components(labels),
Labels = labels,
AttachmentsCount = MarkdownImage().Count(body) + ImageUrl().Count(body),
SupportsFixVersion = false,
- DueDate = issue.TryGetProperty("milestone", out JsonElement milestone) ? Text(milestone, "due_on") : null,
- Url = Text(issue, "html_url"),
+ DueDate = issue.TryGetProperty("milestone", out JsonElement milestone) ? StringAt(milestone, "due_on") : null,
+ Url = StringAt(issue, "html_url"),
Platform = "GitHub",
};
}
private static ImmutableList Labels(JsonElement issue)
=> issue.TryGetProperty("labels", out JsonElement labels) && labels.ValueKind == JsonValueKind.Array
- ? [.. labels.EnumerateArray().Select(label => label.ValueKind == JsonValueKind.String ? label.GetString() : Text(label, "name")).OfType()]
+ ? [.. labels.EnumerateArray().Select(label => label.ValueKind == JsonValueKind.String ? label.GetString() : StringAt(label, "name")).OfType()]
: [];
private static string? Key(JsonElement issue)
@@ -60,7 +61,7 @@ private static ImmutableList Labels(JsonElement issue)
private static string? FirstAssignee(JsonElement issue)
=> issue.TryGetProperty("assignees", out JsonElement assignees) && assignees.ValueKind == JsonValueKind.Array && assignees.GetArrayLength() > 0
- ? Text(assignees[0], "login")
+ ? StringAt(assignees[0], "login")
: null;
private static string IssueType(ImmutableList labels)
@@ -92,10 +93,6 @@ private static ImmutableList Components(ImmutableList labels)
.Select(match => match.Groups[2].Value.Trim())
.Distinct(StringComparer.Ordinal)];
- private static string? Text(JsonElement element, string property)
- => element.ValueKind == JsonValueKind.Object && element.TryGetProperty(property, out JsonElement value) && value.ValueKind == JsonValueKind.String
- ? value.GetString()
- : null;
[GeneratedRegex("enhancement|feature|improvement|story", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant, 1000)]
private static partial Regex FeatureLabel();
diff --git a/PanoramicData.IssueAssessment/Mapping/JiraIssueMapper.cs b/PanoramicData.IssueAssessment/Mapping/JiraIssueMapper.cs
index abba341..aa928fd 100644
--- a/PanoramicData.IssueAssessment/Mapping/JiraIssueMapper.cs
+++ b/PanoramicData.IssueAssessment/Mapping/JiraIssueMapper.cs
@@ -1,5 +1,6 @@
using System.Collections.Frozen;
using System.Text;
+using static PanoramicData.IssueAssessment.Mapping.JsonReading;
namespace PanoramicData.IssueAssessment.Mapping;
@@ -45,22 +46,22 @@ public static NormalizedIssue FromRestJson(JsonElement issue)
public static NormalizedIssue FromRestJson(JsonElement issue, Uri? baseUrl)
{
JsonElement fields = Fields(issue);
- string? key = Text(issue, "key");
+ string? key = StringAt(issue, "key");
return new NormalizedIssue
{
Key = key,
- Project = Text(fields, "project", "key"),
- Title = Text(fields, "summary"),
+ Project = StringAt(fields, "project", "key"),
+ Title = StringAt(fields, "summary"),
Description = Description(fields),
- IssueType = Text(fields, "issuetype", "name"),
- Priority = Text(fields, "priority", "name"),
- Status = Text(fields, "status", "name"),
- Assignee = Text(fields, "assignee", "displayName"),
+ IssueType = StringAt(fields, "issuetype", "name"),
+ Priority = StringAt(fields, "priority", "name"),
+ Status = StringAt(fields, "status", "name"),
+ Assignee = StringAt(fields, "assignee", "displayName"),
Components = Names(fields, "components"),
Labels = Strings(fields, "labels"),
FixVersions = Names(fields, "fixVersions"),
AttachmentsCount = ArrayLength(fields, "attachment"),
- DueDate = Text(fields, "duedate"),
+ DueDate = StringAt(fields, "duedate"),
Url = BrowseUrl(baseUrl, key),
Platform = "Jira",
};
@@ -106,7 +107,7 @@ public static string FlattenAdf(JsonElement document)
private static void AppendBlock(JsonElement node, StringBuilder text, string listPrefix)
{
- string type = Text(node, "type") ?? string.Empty;
+ string type = StringAt(node, "type") ?? string.Empty;
if (ListMarkers.TryGetValue(type, out string? listMarker))
{
AppendList(node, text, listPrefix + listMarker);
@@ -150,7 +151,7 @@ private static void AppendListItem(JsonElement item, StringBuilder text, string
bool first = true;
foreach (JsonElement child in Children(item))
{
- if (Text(child, "type") is "bulletList" or "orderedList")
+ if (StringAt(child, "type") is "bulletList" or "orderedList")
{
AppendBlock(child, text, marker);
}
@@ -162,12 +163,12 @@ private static void AppendListItem(JsonElement item, StringBuilder text, string
}
}
- private static string Inline(JsonElement node) => Text(node, "type") switch
+ private static string Inline(JsonElement node) => StringAt(node, "type") switch
{
- "text" => Text(node, "text") ?? string.Empty,
+ "text" => StringAt(node, "text") ?? string.Empty,
"hardBreak" => "\n",
- "mention" or "emoji" => node.TryGetProperty("attrs", out JsonElement attrs) ? Text(attrs, "text") ?? string.Empty : string.Empty,
- "inlineCard" => node.TryGetProperty("attrs", out JsonElement attrs) ? Text(attrs, "url") ?? string.Empty : string.Empty,
+ "mention" or "emoji" => node.TryGetProperty("attrs", out JsonElement attrs) ? StringAt(attrs, "text") ?? string.Empty : string.Empty,
+ "inlineCard" => node.TryGetProperty("attrs", out JsonElement attrs) ? StringAt(attrs, "url") ?? string.Empty : string.Empty,
_ => string.Concat(Children(node).Select(Inline)),
};
@@ -176,17 +177,10 @@ private static JsonElement[] Children(JsonElement node)
? [.. content.EnumerateArray()]
: [];
- private static string? Text(JsonElement element, string property)
- => element.ValueKind == JsonValueKind.Object && element.TryGetProperty(property, out JsonElement value) && value.ValueKind == JsonValueKind.String
- ? value.GetString()
- : null;
-
- private static string? Text(JsonElement element, string property, string nested)
- => element.ValueKind == JsonValueKind.Object && element.TryGetProperty(property, out JsonElement value) ? Text(value, nested) : null;
private static ImmutableList Names(JsonElement fields, string property)
=> fields.TryGetProperty(property, out JsonElement values) && values.ValueKind == JsonValueKind.Array
- ? [.. values.EnumerateArray().Select(value => Text(value, "name")).OfType()]
+ ? [.. values.EnumerateArray().Select(value => StringAt(value, "name")).OfType()]
: [];
private static ImmutableList Strings(JsonElement fields, string property)
diff --git a/PanoramicData.IssueAssessment/Mapping/JsonReading.cs b/PanoramicData.IssueAssessment/Mapping/JsonReading.cs
new file mode 100644
index 0000000..a7309cb
--- /dev/null
+++ b/PanoramicData.IssueAssessment/Mapping/JsonReading.cs
@@ -0,0 +1,22 @@
+namespace PanoramicData.IssueAssessment.Mapping;
+
+///
+/// Reads values out of the JSON a platform returns, tolerating anything of the wrong shape as absent.
+///
+internal static class JsonReading
+{
+ ///
+ /// The string at of an object; null when it is not an object, the property is missing or
+ /// the value is not a string.
+ ///
+ public static string? StringAt(JsonElement element, string property)
+ => element.ValueKind == JsonValueKind.Object && element.TryGetProperty(property, out JsonElement value) && value.ValueKind == JsonValueKind.String
+ ? value.GetString()
+ : null;
+
+ ///
+ /// The string at of the object at .
+ ///
+ public static string? StringAt(JsonElement element, string property, string nested)
+ => element.ValueKind == JsonValueKind.Object && element.TryGetProperty(property, out JsonElement value) ? StringAt(value, nested) : null;
+}
diff --git a/PanoramicData.IssueAssessment/Text/IssueText.cs b/PanoramicData.IssueAssessment/Text/IssueText.cs
index f1caffb..b22dfed 100644
--- a/PanoramicData.IssueAssessment/Text/IssueText.cs
+++ b/PanoramicData.IssueAssessment/Text/IssueText.cs
@@ -186,23 +186,29 @@ private static void Classify(string? heading, List body, List pa
string? first = body.Find(line => line.Trim().Length > 0);
Match panel = first is null ? Match.Empty : PanelOpening.Match(first);
- bool hasContent = first is not null && !IsPlaceholder(string.Join('\n', body));
- bool isPlain = !body.Exists(line => PanelBlocker.IsMatch(line));
if (panel.Success)
{
panelable.Add(heading);
- if (panel.Groups[1].Value.Contains("title=", StringComparison.OrdinalIgnoreCase))
- {
- titled.Add(heading);
- }
+ AddIf(titled, heading, panel.Groups[1].Value.Contains("title=", StringComparison.OrdinalIgnoreCase));
+ return;
}
- else if (hasContent && isPlain)
+
+ bool hasContent = first is not null && !IsPlaceholder(string.Join('\n', body));
+ if (hasContent && !body.Exists(line => PanelBlocker.IsMatch(line)))
{
panelable.Add(heading);
unpanelled.Add(heading);
}
}
+ private static void AddIf(List list, string item, bool condition)
+ {
+ if (condition)
+ {
+ list.Add(item);
+ }
+ }
+
///
/// True when a section body is a placeholder (TBD, N/A, none, to do, pending, ...) or blank, rather than content.
///