diff --git a/pkg/api/job_runs.go b/pkg/api/job_runs.go index c6cebebe5..b3b294bb2 100644 --- a/pkg/api/job_runs.go +++ b/pkg/api/job_runs.go @@ -80,7 +80,33 @@ type apiRunResults []apitype.JobRun func JobsRunsReportFromDB(dbc *db.DB, filterOpts *filter.FilterOptions, release string, pagination *apitype.Pagination, reportEnd time.Time) (*apitype.PaginationResult, error) { jobsResult := make([]apitype.JobRun, 0) table := "prow_job_runs_report_matview" - q, err := filter.FilterableDBResult(dbc.DB.Table(table), filterOpts, apitype.JobRun{}) + + dbQuery := dbc.DB.Table(table) + + // Split out ran_test_names filters — these are handled via a subquery + // against prow_job_run_tests rather than a column on the matview. + if filterOpts.Filter != nil { + ranTestFilter, remainingFilter := filterOpts.Filter.Split([]string{"ran_test_names"}) + filterOpts.Filter = remainingFilter + for _, item := range ranTestFilter.Items { + baseSubquery := "EXISTS (SELECT 1 FROM prow_job_run_tests JOIN tests ON tests.id = prow_job_run_tests.test_id WHERE prow_job_run_tests.prow_job_run_id = prow_job_runs_report_matview.id AND tests.name %s ?)" + var pattern string + switch item.Operator { + case filter.OperatorHasEntry, filter.OperatorEquals: + baseSubquery = fmt.Sprintf(baseSubquery, "=") + pattern = item.Value + default: + baseSubquery = fmt.Sprintf(baseSubquery, "ILIKE") + pattern = fmt.Sprintf("%%%s%%", item.Value) + } + if item.Not { + baseSubquery = "NOT " + baseSubquery + } + dbQuery = dbQuery.Where(baseSubquery, pattern) + } + } + + q, err := filter.FilterableDBResult(dbQuery, filterOpts, apitype.JobRun{}) if err != nil { return nil, err } diff --git a/pkg/apis/api/types.go b/pkg/apis/api/types.go index 6ddea3c28..280cf262b 100644 --- a/pkg/apis/api/types.go +++ b/pkg/apis/api/types.go @@ -374,6 +374,8 @@ func (run JobRun) GetFieldType(param string) ColumnType { return ColumnTypeArray case "flaked_test_names": return ColumnTypeArray + case "ran_test_names": + return ColumnTypeArray case "labels": return ColumnTypeArray case "variants": diff --git a/sippy-ng/src/helpers.js b/sippy-ng/src/helpers.js index 181983d3a..051eec591 100644 --- a/sippy-ng/src/helpers.js +++ b/sippy-ng/src/helpers.js @@ -193,6 +193,20 @@ export function pathForJobRunsWithTestFailure(release, test, filter) { return `/jobs/${release}/runs?${multiple(...filters)}` } +export function pathForJobRunsWithTest(release, test, filter) { + let filters = [] + filters.push(filterFor('ran_test_names', 'has entry', test)) + if (filter && filter.items) { + filter.items.forEach((item) => { + if (item.columnField === 'variants') { + filters.push(item) + } + }) + } + + return `/jobs/${release}/runs?${multiple(...filters)}` +} + export function pathForJobRunsWithTestFlake(release, test, filter) { let filters = [] filters.push(filterFor('flaked_test_names', 'has entry', test)) diff --git a/sippy-ng/src/jobs/JobRunsTable.js b/sippy-ng/src/jobs/JobRunsTable.js index 9e6c3bc0e..400351b56 100644 --- a/sippy-ng/src/jobs/JobRunsTable.js +++ b/sippy-ng/src/jobs/JobRunsTable.js @@ -255,6 +255,13 @@ export default function JobRunsTable(props) { headerName: 'Flaked tests', hide: true, }, + { + field: 'ran_test_names', + type: 'array', + autocomplete: 'tests', + headerName: 'Tests ran', + hide: true, + }, { field: 'pull_request_author', autocomplete: 'authors', diff --git a/sippy-ng/src/tests/TestAnalysis.js b/sippy-ng/src/tests/TestAnalysis.js index 12078a55b..dd5611469 100644 --- a/sippy-ng/src/tests/TestAnalysis.js +++ b/sippy-ng/src/tests/TestAnalysis.js @@ -14,7 +14,7 @@ import { DirectionsRun } from '@mui/icons-material' import { filterFor, not, - pathForJobRunsWithTestFailure, + pathForJobRunsWithTest, safeEncodeURIComponent, SafeJSONParam, SafeStringParam, @@ -287,7 +287,7 @@ export function TestAnalysis(props) { startIcon={} component={Link} to={withSort( - pathForJobRunsWithTestFailure(props.release, testName, { + pathForJobRunsWithTest(props.release, testName, { items: [ ...filterModel.items.filter( (f) => f.columnField === 'variants'