From 22493c61749b6f921f9f615067ce072369383be0 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira da Silva Date: Wed, 10 Jun 2026 15:45:44 -0300 Subject: [PATCH] =?UTF-8?q?Relax=20CVE=20triage=20and=20open=20thresholds?= =?UTF-8?q?=20for=20distributed=20team=20*=20Fix=20timezone=20bug=20in=20D?= =?UTF-8?q?ateUtil.minusBusinessDays()=20=E2=80=94=20was=20using=20system?= =?UTF-8?q?=20default=20timezone=20instead=20of=20UTC,=20producing=20incon?= =?UTF-8?q?sistent=20results=20depending=20on=20which=20GitHub=20Action=20?= =?UTF-8?q?runner=20executed=20the=20job.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix NPE in Bugs.convertToTeamCount() when a flaky test references a team not present in the teams map. * Update GitHubLoader.sanitize() to preserve status/blocked-external and status/missing-information labels for private issues. Avoid penalizing teams when there's an approved pull-request * Added label status/ready for issues with pull-requests reviewed and approved by teams, but not merged yet Separate blocked-external into its own CVE column and show missing-information globally * Blocked-external CVEs are now shown in a dedicated column in both public and private CVE team tables (with -1/-1 thresholds, visible but not color-coded). * CVE Missing Information is shown as a global stat in the Bugs section, sourced from keycloak-private. The row always appears, even when there are no private issues (shows 0). * Also fixes: severity/ labels are now preserved in sanitize() so SeverityTriageOverdueFilter works correctly for private CVEs. Signed-off-by: Bruno Oliveira da Silva --- .../org/keycloak/dashboard/beans/Bugs.java | 14 +++++++++---- .../keycloak/dashboard/beans/CveTeamStat.java | 9 +++++++-- .../dashboard/beans/PrivateCveTeamStat.java | 7 +++++-- .../beans/filters/FilteredIssues.java | 5 +++++ .../dashboard/beans/filters/ReadyFilter.java | 9 +++++++++ .../keycloak/dashboard/gh/GitHubLoader.java | 2 +- .../org/keycloak/dashboard/util/DateUtil.java | 2 +- src/main/resources/config.properties | 20 +++++++++++-------- 8 files changed, 50 insertions(+), 18 deletions(-) create mode 100644 src/main/java/org/keycloak/dashboard/beans/filters/ReadyFilter.java diff --git a/src/main/java/org/keycloak/dashboard/beans/Bugs.java b/src/main/java/org/keycloak/dashboard/beans/Bugs.java index 7bda2113..541ce53a 100644 --- a/src/main/java/org/keycloak/dashboard/beans/Bugs.java +++ b/src/main/java/org/keycloak/dashboard/beans/Bugs.java @@ -13,6 +13,7 @@ public class Bugs { private final List issues; + private final List privateIssues; private final List activeStreams; private String nextRelease; @@ -43,11 +44,13 @@ public Bugs(GitHubData data, Teams teams) { flakyTestCountsByTeam = convertToTeamCount(flakyTests, teams); - stats = convertToBugStat(issues, data, teams); + privateIssues = data.getPrivateIssues() != null ? data.getPrivateIssues() : Collections.emptyList(); + + stats = convertToBugStat(issues, data, teams, privateIssues); areaStats = convertToAreaStats(issues); teamStats = convertToTeamStats(issues, teams); teamCveStats = convertToTeamCveStats(issues, teams); - privateTeamCveStats = convertToPrivateTeamCveStats(data.getPrivateIssues(), teams); + privateTeamCveStats = convertToPrivateTeamCveStats(privateIssues, teams); teamBackportStats = convertToTeamBackportStats(issues, teams); } @@ -66,7 +69,7 @@ private Map convertToTeamCount(List flakyTests, Team return counts; } - private List convertToBugStat(List issues, GitHubData data, Teams teams) { + private List convertToBugStat(List issues, GitHubData data, Teams teams, List privateIssues) { List stats = new LinkedList<>(); FilteredIssues filteredIssues = FilteredIssues.create(issues); @@ -80,6 +83,9 @@ private List convertToBugStat(List issues, GitHubData data .issues(filteredIssues.clone().openBug().triage(true).missingInformation(false).createdBefore(DateUtil.minusdays(Config.getInt("bugs.TriageOverdue.days"))))); stats.add(BugStat.global("CVE") .issues(filteredIssues.clone().openCve())); + FilteredIssues privateCves = FilteredIssues.createPrivate(privateIssues); + stats.add(BugStat.global("CVE: Missing Information").warnErrorKey("CveMissingInformation") + .issues(privateCves.clone().openIssue().missingInformation(true))); stats.add(BugStat.global("Weakness") .issues(filteredIssues.clone().openBug().label("area/weakness"))); stats.add(BugStat.global("Blocker") @@ -194,7 +200,7 @@ private List convertToTeamCveStats(List issues, Teams } private List convertToPrivateTeamCveStats(List privateIssues, Teams teams) { - if (privateIssues == null || privateIssues.isEmpty()) { + if (privateIssues.isEmpty()) { return Collections.emptyList(); } diff --git a/src/main/java/org/keycloak/dashboard/beans/CveTeamStat.java b/src/main/java/org/keycloak/dashboard/beans/CveTeamStat.java index 45ffcfd7..41dc8115 100644 --- a/src/main/java/org/keycloak/dashboard/beans/CveTeamStat.java +++ b/src/main/java/org/keycloak/dashboard/beans/CveTeamStat.java @@ -22,13 +22,18 @@ public CveTeamStat(String team, FilteredIssues issues, String nextRelease) { .issues(issues.clone().triage(true))); columns.add(BugStat.team("Triage Overdue") - .issues(issues.clone().triage(true).createdBefore(DateUtil.minusdays(Config.getInt("bugs.TriageOverdue.days"))))); + .issues(issues.clone().triage(true).ready(false) + .createdBefore(DateUtil.minusdays(Config.getInt("bugs.TriageOverdue.days"))))); columns.add(BugStat.team("Open").warnErrorKey("CveOpen") .issues(issues.clone().triage(false))); columns.add(BugStat.team("Open Overdue").warnErrorKey("CveOpenOverdue") - .issues(issues.clone().triage(false).createdBefore(DateUtil.minusdays(Config.getInt("bugs.CveOverdue.days"))))); + .issues(issues.clone().triage(false).ready(false) + .createdBefore(DateUtil.minusdays(Config.getInt("bugs.CveOverdue.days"))))); + + columns.add(BugStat.team("Blocked External").warnErrorKey("CveBlockedExternal") + .issues(issues.clone().blockedExternal(true))); } diff --git a/src/main/java/org/keycloak/dashboard/beans/PrivateCveTeamStat.java b/src/main/java/org/keycloak/dashboard/beans/PrivateCveTeamStat.java index 90e616d8..549d2625 100644 --- a/src/main/java/org/keycloak/dashboard/beans/PrivateCveTeamStat.java +++ b/src/main/java/org/keycloak/dashboard/beans/PrivateCveTeamStat.java @@ -30,7 +30,7 @@ public PrivateCveTeamStat(String team, FilteredIssues allIssues) { .issues(triageBase.clone())); columns.add(BugStat.team("Triage Overdue").warnErrorKey("PrivateTriageOverdue") - .issues(triageBase.clone().severityTriageOverdue(importantBizDays, moderateBizDays, lowDays))); + .issues(triageBase.clone().ready(false).severityTriageOverdue(importantBizDays, moderateBizDays, lowDays))); FilteredIssues cveOpen = allIssues.clone().openCve().triage(false); @@ -38,8 +38,11 @@ public PrivateCveTeamStat(String team, FilteredIssues allIssues) { .issues(cveOpen.clone())); columns.add(BugStat.team("Open Overdue").warnErrorKey("CveOpenOverdue") - .issues(cveOpen.clone().createdBefore( + .issues(cveOpen.clone().ready(false).createdBefore( DateUtil.minusdays(Config.getInt("bugs.CveOverdue.days"))))); + + columns.add(BugStat.team("Blocked External").warnErrorKey("CveBlockedExternal") + .issues(allIssues.clone().openIssue().blockedExternal(true))); } public String getTitle() { diff --git a/src/main/java/org/keycloak/dashboard/beans/filters/FilteredIssues.java b/src/main/java/org/keycloak/dashboard/beans/filters/FilteredIssues.java index e594aa20..f14e9144 100644 --- a/src/main/java/org/keycloak/dashboard/beans/filters/FilteredIssues.java +++ b/src/main/java/org/keycloak/dashboard/beans/filters/FilteredIssues.java @@ -124,6 +124,11 @@ public FilteredIssues missingInformation(boolean include) { return this; } + public FilteredIssues ready(boolean include) { + filters.add(new ReadyFilter(include)); + return this; + } + public FilteredIssues missingTeam(Teams teams) { filters.add(new MissingTeamFilter(teams)); return this; diff --git a/src/main/java/org/keycloak/dashboard/beans/filters/ReadyFilter.java b/src/main/java/org/keycloak/dashboard/beans/filters/ReadyFilter.java new file mode 100644 index 00000000..e48ff1a8 --- /dev/null +++ b/src/main/java/org/keycloak/dashboard/beans/filters/ReadyFilter.java @@ -0,0 +1,9 @@ +package org.keycloak.dashboard.beans.filters; + +class ReadyFilter extends LabelFilter { + + public ReadyFilter(boolean include) { + super("status/ready", include); + } + +} diff --git a/src/main/java/org/keycloak/dashboard/gh/GitHubLoader.java b/src/main/java/org/keycloak/dashboard/gh/GitHubLoader.java index d91c64f7..23787814 100644 --- a/src/main/java/org/keycloak/dashboard/gh/GitHubLoader.java +++ b/src/main/java/org/keycloak/dashboard/gh/GitHubLoader.java @@ -233,7 +233,7 @@ private static GitHubIssue sanitize(GitHubIssue issue) { sanitized.setNumber(issue.getNumber()); sanitized.setClosedAt(issue.getClosedAt()); sanitized.setCreatedAt(issue.getCreatedAt()); - sanitized.setLabels(issue.getLabels().stream().filter(l -> l.startsWith("team/") || l.startsWith("kind/") || l.equals("status/triage")).toList()); + sanitized.setLabels(issue.getLabels().stream().filter(l -> l.startsWith("team/") || l.startsWith("kind/") || l.startsWith("severity/") || l.equals("status/triage") || l.equals("status/blocked-external") || l.equals("status/missing-information") || l.equals("status/ready")).toList()); sanitized.setCommentsCount(issue.getCommentsCount()); sanitized.setUpdatedAt(issue.getUpdatedAt()); return sanitized; diff --git a/src/main/java/org/keycloak/dashboard/util/DateUtil.java b/src/main/java/org/keycloak/dashboard/util/DateUtil.java index a2912cd1..f9a3a6c9 100644 --- a/src/main/java/org/keycloak/dashboard/util/DateUtil.java +++ b/src/main/java/org/keycloak/dashboard/util/DateUtil.java @@ -63,7 +63,7 @@ private static Calendar getCalendarUTC() { } public static java.util.Date minusBusinessDays(int businessDays) { - Calendar cal = Calendar.getInstance(); + Calendar cal = getCalendarUTC(); cal.set(Calendar.HOUR_OF_DAY, 0); cal.set(Calendar.MINUTE, 0); cal.set(Calendar.SECOND, 0); diff --git a/src/main/resources/config.properties b/src/main/resources/config.properties index 654be321..ea24adf2 100644 --- a/src/main/resources/config.properties +++ b/src/main/resources/config.properties @@ -29,6 +29,8 @@ bugs.Low.warn=-1 bugs.Low.error=-1 bugs.CVE.warn=5 bugs.CVE.error=10 +bugs.CveMissingInformation.warn=-1 +bugs.CveMissingInformation.error=-1 bugs.Milestone.warn=-1 bugs.Milestone.error=-1 bugs.MissingArea.warn=-1 @@ -60,23 +62,25 @@ bugs.team.ImportantOverdue.warn=1 bugs.team.ImportantOverdue.error=25 bugs.team.Normal.warn=-1 bugs.team.Normal.error=-1 -bugs.team.CveOpen.warn=1 +bugs.team.CveOpen.warn=3 bugs.team.CveOpen.error=5 bugs.team.CveOpenOverdue.warn=1 -bugs.team.CveOpenOverdue.error=1 -bugs.team.PrivateTriage.warn=1 -bugs.team.PrivateTriage.error=5 +bugs.team.CveOpenOverdue.error=4 +bugs.team.PrivateTriage.warn=3 +bugs.team.PrivateTriage.error=8 bugs.team.PrivateTriageOverdue.warn=1 -bugs.team.PrivateTriageOverdue.error=1 +bugs.team.PrivateTriageOverdue.error=4 +bugs.team.CveBlockedExternal.warn=-1 +bugs.team.CveBlockedExternal.error=-1 bugs.team.Low.warn=-1 bugs.team.Low.error=-1 bugs.team.Backport.warn=5 bugs.team.Backport.error=10 -private.triage.ImportantOverdue.businessDays=2 -private.triage.ModerateOverdue.businessDays=7 -private.triage.LowOverdue.days=15 +private.triage.ImportantOverdue.businessDays=3 +private.triage.ModerateOverdue.businessDays=10 +private.triage.LowOverdue.days=21 bugs.area.Milestone.warn=10 bugs.area.Milestone.error=20