diff --git a/pkg/jobrunaggregator/jobrunaggregatoranalyzer/aggregation-testrun-summary.gohtml b/pkg/jobrunaggregator/jobrunaggregatoranalyzer/aggregation-testrun-summary.gohtml index 2d8fb3c66f7..de7effb50b6 100644 --- a/pkg/jobrunaggregator/jobrunaggregatoranalyzer/aggregation-testrun-summary.gohtml +++ b/pkg/jobrunaggregator/jobrunaggregatoranalyzer/aggregation-testrun-summary.gohtml @@ -1,10 +1,12 @@ {{define "printTable"}} + {{ $jobRunIDToNumber := .JobRunIDToNumber }} + {{ $data := .Data }} - {{ range . }} + {{ range $data }} {{ $jobRuns := .JobRuns }} {{ $formatedSummary := (.Summary | formatSummary)}} {{ $failedQuantileData := ( "" | parseQuantileValues) }} @@ -49,12 +51,15 @@ {{ range $jobRuns }} {{ $jobName := .JobName}} {{ $jobID := .JobRunID}} + {{ $jobNumber := getJobRunNumber $jobRunIDToNumber $jobID }}
+ {{ $jobNumber }}

{{ $jobName }}

ID: {{ $jobID }}

+

Number: {{ $jobNumber }}

{{ with $failedQuantileData }} {{ if mapHasKey . $jobID }}
Quantile Value: {{ (index . $jobID) }}
@@ -112,8 +117,8 @@ position: relative; display: inline-block; background-color: #0c3; - min-width: 20px; - min-height: 20px; + min-width: 24px; + min-height: 24px; max-width: 500px; color: white; border-radius: 5px; @@ -189,9 +194,22 @@ display: inline-block; margin: 5px 0px; } + .job-number { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + color: white; + font-weight: bold; + font-size: 11px; + text-shadow: 0 0 2px rgba(0, 0, 0, 0.8); + pointer-events: none; + user-select: none; + } +{{ $jobRunIDToNumber := .JobRunIDToNumber }} {{ $failedData := infoForTestSuite .JobName .InitialParents .Suite isFailed }} {{ $skippedData := infoForTestSuite .JobName .InitialParents .Suite isSkipped }} {{ $successData := infoForTestSuite .JobName .InitialParents .Suite isSuccess }} @@ -208,7 +226,7 @@

Failed Tests

-{{ template "printTable" .}} +{{ template "printTable" (dict "Data" . "JobRunIDToNumber" $jobRunIDToNumber)}}
{{end}} @@ -217,7 +235,7 @@

Skipped Tests

-{{ template "printTable" .}} +{{ template "printTable" (dict "Data" . "JobRunIDToNumber" $jobRunIDToNumber)}}
{{end}} @@ -227,7 +245,7 @@

Passed Tests

-{{ template "printTable" .}} +{{ template "printTable" (dict "Data" . "JobRunIDToNumber" $jobRunIDToNumber)}}
{{end}} diff --git a/pkg/jobrunaggregator/jobrunaggregatoranalyzer/spyglass_summary.go b/pkg/jobrunaggregator/jobrunaggregatoranalyzer/spyglass_summary.go index 9b327a1b3e4..172e334bf30 100644 --- a/pkg/jobrunaggregator/jobrunaggregatoranalyzer/spyglass_summary.go +++ b/pkg/jobrunaggregator/jobrunaggregatoranalyzer/spyglass_summary.go @@ -6,6 +6,7 @@ import ( "fmt" "html/template" "regexp" + "sort" "strings" "time" @@ -35,6 +36,23 @@ var ( "isSuccess": func() testCaseFilterFunc { return isSuccess }, "infoForTestSuite": infoForTestSuite, "toLower": strings.ToLower, + "getJobRunNumber": func(jobRunIDToNumber map[string]int, jobRunID string) int { + return jobRunIDToNumber[jobRunID] + }, + "dict": func(values ...interface{}) (map[string]interface{}, error) { + if len(values)%2 != 0 { + return nil, fmt.Errorf("dict requires an even number of arguments") + } + dict := make(map[string]interface{}, len(values)/2) + for i := 0; i < len(values); i += 2 { + key, ok := values[i].(string) + if !ok { + return nil, fmt.Errorf("dict keys must be strings") + } + dict[key] = values[i+1] + } + return dict, nil + }, "mapHasKey": func(value map[string]string, key string) bool { _, found := value[key] return found @@ -145,14 +163,23 @@ var ( // if someone has the HTML skills, making this a mini-test grid would be awesome. func htmlForTestRuns(jobName string, suite *junit.TestSuite) (string, error) { + // Collect and number all job runs + allJobRunIDs := collectAllJobRunIDs(suite) + jobRunIDToNumber := make(map[string]int) + for i, jobRunID := range allJobRunIDs { + jobRunIDToNumber[jobRunID] = i + 1 + } + data := struct { - JobName string - Suite *junit.TestSuite - InitialParents []string + JobName string + Suite *junit.TestSuite + InitialParents []string + JobRunIDToNumber map[string]int }{ - JobName: jobName, - Suite: suite, - InitialParents: []string{}, + JobName: jobName, + Suite: suite, + InitialParents: []string{}, + JobRunIDToNumber: jobRunIDToNumber, } buff := bytes.Buffer{} err := htmlTemplate.Execute(&buff, data) @@ -265,3 +292,41 @@ func infoForTestCase(jobName string, parents []string, testCase *junit.TestCase, return &testInfo } + +// collectAllJobRunIDs traverses the test suite tree and collects all unique JobRunIDs +func collectAllJobRunIDs(suite *junit.TestSuite) []string { + jobRunIDSet := sets.Set[string]{} + collectJobRunIDsFromSuite(suite, jobRunIDSet) + + // Sort alphabetically for stable ordering + jobRunIDs := sets.List(jobRunIDSet) + sort.Strings(jobRunIDs) + + return jobRunIDs +} + +// collectJobRunIDsFromSuite recursively extracts JobRunIDs from test cases +func collectJobRunIDsFromSuite(suite *junit.TestSuite, jobRunIDs sets.Set[string]) { + // Extract JobRunIDs from test case details (passes, failures, skips) + for _, testCase := range suite.TestCases { + currDetails := &jobrunaggregatorlib.TestCaseDetails{} + if len(testCase.SystemOut) > 0 { + _ = yaml.Unmarshal([]byte(testCase.SystemOut), currDetails) + } + + for _, failure := range currDetails.Failures { + jobRunIDs.Insert(failure.JobRunID) + } + for _, pass := range currDetails.Passes { + jobRunIDs.Insert(pass.JobRunID) + } + for _, skip := range currDetails.Skips { + jobRunIDs.Insert(skip.JobRunID) + } + } + + // Recursively process child suites + for _, child := range suite.Children { + collectJobRunIDsFromSuite(child, jobRunIDs) + } +}
Name Job Runs