From a5382eb6ee076585231a6a7936a8d1d129c49a43 Mon Sep 17 00:00:00 2001 From: Dustin Row Date: Thu, 23 Apr 2026 12:42:40 -0700 Subject: [PATCH 1/2] Add config-driven overview customization for managed service releases Adds an `overview` config block to ReleaseConfig so that any release can customize its overview page display via openshift-customizations.yaml. For rosa-stage, this enables: - Multi-version install test queries (both old synthetic and new-style names) - Wider time windows for "New Test Failures" (48h/168h vs 24h/72h) - A "Top Failing Tests" section showing all failures in the past 7 days The overview config flows from the YAML config through the /api/health endpoint to the React frontend, so no release names are hardcoded. --- config/openshift-customizations.yaml | 5 +++ config/openshift.yaml | 5 +++ pkg/api/health.go | 38 +++++++++++++++++++-- pkg/apis/api/types.go | 9 +++++ pkg/apis/config/v1/types.go | 23 +++++++++++++ pkg/sippyserver/server.go | 9 +++-- sippy-ng/src/releases/RecentTestFailures.js | 22 ++++++++---- sippy-ng/src/releases/ReleaseOverview.js | 22 +++++++++++- 8 files changed, 121 insertions(+), 12 deletions(-) diff --git a/config/openshift-customizations.yaml b/config/openshift-customizations.yaml index 8fb55ec950..88ab2fc89d 100644 --- a/config/openshift-customizations.yaml +++ b/config/openshift-customizations.yaml @@ -36,6 +36,11 @@ releases: periodic-ci-Azure-ARO-HCP-main-periodic-prod-uksouth-e2e-parallel: true periodic-ci-Azure-ARO-HCP-main-periodic-prod-e2e-parallel-ocp-nightly: true rosa-stage: + overview: + multiVersionInstallTests: true + recentFailuresPeriod: "48h" + recentFailuresPreviousPeriod: "168h" + topFailingTestsPeriod: "168h" jobs: # ROSA Classic/STS nightly (stage) periodic-ci-openshift-osde2e-main-nightly-4.16-rosa-classic-sts: true diff --git a/config/openshift.yaml b/config/openshift.yaml index cb69a0145f..3fb4764b0e 100644 --- a/config/openshift.yaml +++ b/config/openshift.yaml @@ -17064,6 +17064,11 @@ releases: rosa-integration: {} rosa-production: {} rosa-stage: + overview: + multiVersionInstallTests: true + recentFailuresPeriod: 48h + recentFailuresPreviousPeriod: 168h + topFailingTestsPeriod: 168h jobs: periodic-ci-openshift-osde2e-main-aws-stage-informing-default: true periodic-ci-openshift-osde2e-main-nightly-4.16-osd-aws: true diff --git a/pkg/api/health.go b/pkg/api/health.go index 9a9d2a9c2e..1c5e9c6099 100644 --- a/pkg/api/health.go +++ b/pkg/api/health.go @@ -11,6 +11,7 @@ import ( log "github.com/sirupsen/logrus" apitype "github.com/openshift/sippy/pkg/apis/api" + configv1 "github.com/openshift/sippy/pkg/apis/config/v1" sippyprocessingv1 "github.com/openshift/sippy/pkg/apis/sippyprocessing/v1" "github.com/openshift/sippy/pkg/db" "github.com/openshift/sippy/pkg/db/query" @@ -44,7 +45,7 @@ func useNewInstallTest(release string) bool { // PrintOverallReleaseHealthFromDB gives a summarized status of the overall health, including // infrastructure, install, upgrade, and variant success rates. -func PrintOverallReleaseHealthFromDB(w http.ResponseWriter, dbc *db.DB, release string, reportEnd time.Time) { +func PrintOverallReleaseHealthFromDB(w http.ResponseWriter, dbc *db.DB, release string, reportEnd time.Time, overviewCfg *configv1.OverviewConfig) { excludedVariants := testidentification.DefaultExcludedVariants // Minor upgrades install a previous version and should not be counted against the current version's install stat. excludedInstallVariants := testidentification.DefaultExcludedVariants @@ -74,6 +75,28 @@ func PrintOverallReleaseHealthFromDB(w http.ResponseWriter, dbc *db.DB, release if installIndicator, found := query.TestReportExcludeVariants(dbc, release, installTestName, excludedInstallVariants); found { indicators["install"] = installIndicator } + + // When configured, try both old-style synthetic and new-style install test + // names and keep whichever has more data. Useful for releases that span + // multiple OCP versions. + if overviewCfg != nil && overviewCfg.MultiVersionInstallTests { + altInfra := testidentification.NewInfrastructureTestName + altInstall := testidentification.NewInstallTestName + if useNewInstallTest(release) { + altInfra = testidentification.InfrastructureTestName + altInstall = testidentification.InstallTestName + } + if altInfraIndicator, found := query.TestReportExcludeVariants(dbc, release, altInfra, excludedVariants); found { + if existing, exists := indicators["infrastructure"]; !exists || altInfraIndicator.CurrentRuns > existing.CurrentRuns { + indicators["infrastructure"] = altInfraIndicator + } + } + if altInstallIndicator, found := query.TestReportExcludeVariants(dbc, release, altInstall, excludedInstallVariants); found { + if existing, exists := indicators["install"]; !exists || altInstallIndicator.CurrentRuns > existing.CurrentRuns { + indicators["install"] = altInstallIndicator + } + } + } if upgradeIndicator, found := query.TestReportExcludeVariants(dbc, release, testidentification.UpgradeTestName, excludedVariants); found { indicators["upgrade"] = upgradeIndicator } @@ -114,13 +137,22 @@ func PrintOverallReleaseHealthFromDB(w http.ResponseWriter, dbc *db.DB, release // TODO: use or remove this logic var warnings []string - RespondWithJSON(http.StatusOK, w, apitype.Health{ + health := apitype.Health{ Indicators: indicators, LastUpdated: lastUpdated, Current: currStats, Previous: prevStats, Warnings: warnings, - }) + } + if overviewCfg != nil { + health.Overview = &apitype.OverviewConfig{ + MultiVersionInstallTests: overviewCfg.MultiVersionInstallTests, + RecentFailuresPeriod: overviewCfg.RecentFailuresPeriod, + RecentFailuresPreviousPeriod: overviewCfg.RecentFailuresPreviousPeriod, + TopFailingTestsPeriod: overviewCfg.TopFailingTestsPeriod, + } + } + RespondWithJSON(http.StatusOK, w, health) } func calculateJobResultStatistics(results []apitype.Job) (currStats, prevStats sippyprocessingv1.Statistics) { diff --git a/pkg/apis/api/types.go b/pkg/apis/api/types.go index a0eab6f2cc..30bf506731 100644 --- a/pkg/apis/api/types.go +++ b/pkg/apis/api/types.go @@ -935,6 +935,15 @@ type Health struct { Warnings []string `json:"warnings"` Current v1.Statistics `json:"current_statistics"` Previous v1.Statistics `json:"previous_statistics"` + Overview *OverviewConfig `json:"overview,omitempty"` +} + +// OverviewConfig is passed to the frontend to customize release overview rendering. +type OverviewConfig struct { + MultiVersionInstallTests bool `json:"multi_version_install_tests,omitempty"` + RecentFailuresPeriod string `json:"recent_failures_period,omitempty"` + RecentFailuresPreviousPeriod string `json:"recent_failures_previous_period,omitempty"` + TopFailingTestsPeriod string `json:"top_failing_tests_period,omitempty"` } type ProwJobRunRiskAnalysis struct { diff --git a/pkg/apis/config/v1/types.go b/pkg/apis/config/v1/types.go index 2a609730f7..e78ea76f66 100644 --- a/pkg/apis/config/v1/types.go +++ b/pkg/apis/config/v1/types.go @@ -24,6 +24,29 @@ type ReleaseConfig struct { // InformingJobs is the list of informing payload jobs InformingJobs []string `yaml:"informingJobs,omitempty"` + + // Overview configures the release overview page display behavior. + Overview *OverviewConfig `yaml:"overview,omitempty"` +} + +// OverviewConfig controls how the release overview page is rendered. +type OverviewConfig struct { + // MultiVersionInstallTests queries both old-style synthetic and new-style + // install test names, keeping whichever has more data. Useful for releases + // that span multiple OCP versions. + MultiVersionInstallTests bool `yaml:"multiVersionInstallTests,omitempty" json:"multi_version_install_tests,omitempty"` + + // RecentFailuresPeriod overrides the default "current" window for new test + // failure detection (default: "24h"). + RecentFailuresPeriod string `yaml:"recentFailuresPeriod,omitempty" json:"recent_failures_period,omitempty"` + + // RecentFailuresPreviousPeriod overrides the default "previous" window for + // new test failure comparison (default: "72h"). + RecentFailuresPreviousPeriod string `yaml:"recentFailuresPreviousPeriod,omitempty" json:"recent_failures_previous_period,omitempty"` + + // TopFailingTestsPeriod, when set, adds a "Top Failing Tests" section + // showing all test failures within this window (e.g. "168h"). + TopFailingTestsPeriod string `yaml:"topFailingTestsPeriod,omitempty" json:"top_failing_tests_period,omitempty"` } type ComponentReadinessConfig struct { diff --git a/pkg/sippyserver/server.go b/pkg/sippyserver/server.go index 6a1be7efd2..2f8e00524e 100644 --- a/pkg/sippyserver/server.go +++ b/pkg/sippyserver/server.go @@ -1224,9 +1224,14 @@ func (s *Server) jsonTestLifecyclesFromDB(w http.ResponseWriter, req *http.Reque func (s *Server) jsonHealthReportFromDB(w http.ResponseWriter, req *http.Request) { release := s.getParamOrFail(w, req, "release") - if release != "" { - api.PrintOverallReleaseHealthFromDB(w, s.db, release, s.GetReportEnd()) + if release == "" { + return + } + var overviewCfg *v1.OverviewConfig + if cfg, ok := s.config.Releases[release]; ok { + overviewCfg = cfg.Overview } + api.PrintOverallReleaseHealthFromDB(w, s.db, release, s.GetReportEnd(), overviewCfg) } func (s *Server) jsonBuildClusterHealth(w http.ResponseWriter, req *http.Request) { diff --git a/sippy-ng/src/releases/RecentTestFailures.js b/sippy-ng/src/releases/RecentTestFailures.js index ba2d961b31..f706cf3736 100644 --- a/sippy-ng/src/releases/RecentTestFailures.js +++ b/sippy-ng/src/releases/RecentTestFailures.js @@ -357,7 +357,8 @@ export default function RecentTestFailures(props) { const [rowsPerPage, setRowsPerPage] = React.useState(props.limit || 5) const period = props.period || '24h' - const previousPeriod = props.previousPeriod || '72h' + const previousPeriod = + props.previousPeriod !== undefined ? props.previousPeriod : '72h' const includeOutputs = props.includeOutputs !== undefined ? props.includeOutputs : true @@ -369,7 +370,9 @@ export default function RecentTestFailures(props) { '/api/tests/recent_failures' + `?release=${safeEncodeURIComponent(props.release)}` + `&period=${safeEncodeURIComponent(period)}` + - `&previousPeriod=${safeEncodeURIComponent(previousPeriod)}` + + (previousPeriod + ? `&previousPeriod=${safeEncodeURIComponent(previousPeriod)}` + : '') + `&includeOutputs=${includeOutputs}` + `&sortField=${safeEncodeURIComponent(orderBy)}` + `&sort=${safeEncodeURIComponent(order)}` + @@ -427,7 +430,11 @@ export default function RecentTestFailures(props) { {title} @@ -456,11 +463,14 @@ export default function RecentTestFailures(props) { - No new test failures detected + {previousPeriod + ? 'No new test failures detected' + : 'No test failures detected'} - No tests started failing in the last {period} that were not already - failing in the prior {previousPeriod}. + {previousPeriod + ? `No tests started failing in the last ${period} that were not already failing in the prior ${previousPeriod}.` + : `No tests failed in the last ${period}.`} ) : ( diff --git a/sippy-ng/src/releases/ReleaseOverview.js b/sippy-ng/src/releases/ReleaseOverview.js index 1a75e8842f..8f3b1ccd76 100644 --- a/sippy-ng/src/releases/ReleaseOverview.js +++ b/sippy-ng/src/releases/ReleaseOverview.js @@ -264,9 +264,29 @@ export default function ReleaseOverview(props) { /> - + + {data.overview?.top_failing_tests_period && ( + + + + )} + {releases?.release_attrs?.[props.release]?.capabilities ?.payloadTags && ( From f320aa5a4e02c361d78d3984f29a0001bf66ca88 Mon Sep 17 00:00:00 2001 From: Dustin Row Date: Thu, 23 Apr 2026 13:55:43 -0700 Subject: [PATCH 2/2] Fix prettier formatting and add unit tests - Fix prettier violation in ReleaseOverview.js (single-line expression) - Add inline comment explaining multi-version install test query logic - Add unit tests for useNewInstallTest covering versioned and non-versioned release names --- pkg/api/health.go | 8 +++--- pkg/api/health_test.go | 31 ++++++++++++++++++++++++ sippy-ng/src/releases/ReleaseOverview.js | 4 +-- 3 files changed, 37 insertions(+), 6 deletions(-) create mode 100644 pkg/api/health_test.go diff --git a/pkg/api/health.go b/pkg/api/health.go index 1c5e9c6099..0fe3b5ad82 100644 --- a/pkg/api/health.go +++ b/pkg/api/health.go @@ -76,9 +76,11 @@ func PrintOverallReleaseHealthFromDB(w http.ResponseWriter, dbc *db.DB, release indicators["install"] = installIndicator } - // When configured, try both old-style synthetic and new-style install test - // names and keep whichever has more data. Useful for releases that span - // multiple OCP versions. + // Releases spanning OCP version boundaries (e.g. rosa-stage) may have jobs + // producing old-style synthetic tests ([sig-sippy] install should work) and + // jobs producing new-style tests (install should succeed: overall). Query + // the alternate name set and keep whichever indicator has more CurrentRuns + // so the overview cards use the most-populated metric. if overviewCfg != nil && overviewCfg.MultiVersionInstallTests { altInfra := testidentification.NewInfrastructureTestName altInstall := testidentification.NewInstallTestName diff --git a/pkg/api/health_test.go b/pkg/api/health_test.go new file mode 100644 index 0000000000..630106fa35 --- /dev/null +++ b/pkg/api/health_test.go @@ -0,0 +1,31 @@ +package api + +import ( + "testing" +) + +func TestUseNewInstallTest(t *testing.T) { + tests := []struct { + release string + expected bool + }{ + {"4.17", true}, + {"4.11", true}, + {"4.10", false}, + {"4.8", false}, + {"3.11", false}, + {"5.0", true}, + // Non-numeric releases return false (use old synthetic names) + {"rosa-stage", false}, + {"aro-stage", false}, + {"Presubmits", false}, + } + + for _, tt := range tests { + t.Run(tt.release, func(t *testing.T) { + if got := useNewInstallTest(tt.release); got != tt.expected { + t.Errorf("useNewInstallTest(%q) = %v, want %v", tt.release, got, tt.expected) + } + }) + } +} diff --git a/sippy-ng/src/releases/ReleaseOverview.js b/sippy-ng/src/releases/ReleaseOverview.js index 8f3b1ccd76..6c3ad120e4 100644 --- a/sippy-ng/src/releases/ReleaseOverview.js +++ b/sippy-ng/src/releases/ReleaseOverview.js @@ -266,9 +266,7 @@ export default function ReleaseOverview(props) {