github.com/web-platform-tests/wpt.fyi@v0.0.0-20240530210107-70cf978996f1/api/checks/runs.go (about) 1 // Copyright 2018 The WPT Dashboard Project. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package checks 6 7 import ( 8 "context" 9 "fmt" 10 "time" 11 12 "github.com/google/go-github/v47/github" 13 "github.com/web-platform-tests/wpt.fyi/api/checks/summaries" 14 "github.com/web-platform-tests/wpt.fyi/shared" 15 ) 16 17 func updateCheckRunSummary(ctx context.Context, summary summaries.Summary, suite shared.CheckSuite) (bool, error) { 18 log := shared.GetLogger(ctx) 19 product := summary.GetCheckState().Product 20 testRun := summary.GetCheckState().TestRun 21 22 // Attempt to update any existing check runs for this SHA. 23 checkRuns, err := getExistingCheckRuns(ctx, suite) 24 if err != nil { 25 log.Warningf("Failed to load existing check runs for %s: %s", suite.SHA[:7], err.Error()) 26 } 27 28 // Update, not create, if a run name matches this completed TestRun. 29 var existing *github.CheckRun 30 if testRun != nil { 31 for _, run := range checkRuns { 32 if run.GetApp().GetID() != suite.AppID { 33 continue 34 } 35 if spec, _ := shared.ParseProductSpec(run.GetName()); spec.Matches(*testRun) { 36 log.Debugf("Found existing run %v for %s @ %s", run.GetID(), run.GetName(), suite.SHA[:7]) 37 existing = run 38 39 break 40 } 41 } 42 } 43 44 var created bool 45 // nolint:nestif // TODO: Fix nestif lint error 46 if existing != nil { 47 created, err = updateExistingCheckRunSummary(ctx, summary, suite, existing) 48 if err != nil { 49 log.Warningf("Failed to update existing check run summary for %s: %s", *existing.HeadSHA, err.Error()) 50 } 51 } else { 52 state := summary.GetCheckState() 53 actions := summary.GetActions() 54 55 var summaryStr string 56 summaryStr, err = summary.GetSummary() 57 if err != nil { 58 log.Warningf("Failed to generate summary for %s: %s", state.HeadSHA, err.Error()) 59 60 return false, err 61 } 62 63 detailsURLStr := state.DetailsURL.String() 64 title := state.Title() 65 // nolint:exhaustruct // WONTFIX: Name, HeadSHA only required. 66 opts := github.CreateCheckRunOptions{ 67 Name: state.Name(), 68 HeadSHA: state.HeadSHA, 69 DetailsURL: &detailsURLStr, 70 Status: &state.Status, 71 Conclusion: state.Conclusion, 72 Output: &github.CheckRunOutput{ 73 Title: &title, 74 Summary: &summaryStr, 75 }, 76 Actions: actions, 77 } 78 if state.Conclusion != nil { 79 opts.CompletedAt = &github.Timestamp{Time: time.Now()} 80 } 81 created, err = createCheckRun(ctx, suite, opts) 82 if err != nil { 83 log.Warningf("Failed to create check run summary for %s: %s", suite.SHA, err.Error()) 84 } 85 } 86 if created { 87 log.Debugf("Check for %s/%s @ %s (%s) updated", suite.Owner, suite.Repo, suite.SHA[:7], product.String()) 88 } 89 90 return created, nil 91 } 92 93 func getExistingCheckRuns(ctx context.Context, suite shared.CheckSuite) ([]*github.CheckRun, error) { 94 log := shared.GetLogger(ctx) 95 client, err := getGitHubClient(ctx, suite.AppID, suite.InstallationID) 96 if err != nil { 97 log.Errorf("Failed to fetch runs for suite: %s", err.Error()) 98 99 return nil, err 100 } 101 102 var runs []*github.CheckRun 103 options := github.ListCheckRunsOptions{ 104 ListOptions: github.ListOptions{ 105 // 100 is the maximum allowed items per page; see 106 // https://developer.github.com/v3/guides/traversing-with-pagination/#changing-the-number-of-items-received 107 PerPage: 100, 108 }, 109 } 110 111 // As a safety-check, we will not do more than 10 iterations (at 100 112 // check runs per page, this gives us a 1000 run upper limit). 113 for i := 0; i < 10; i++ { 114 result, response, err := client.Checks.ListCheckRunsForRef(ctx, suite.Owner, suite.Repo, suite.SHA, &options) 115 if err != nil { 116 return nil, err 117 } 118 119 runs = append(runs, result.CheckRuns...) 120 121 // GitHub APIs indicate being on the last page by not returning any 122 // value for NextPage, which go-github translates into zero. 123 // See https://gowalker.org/github.com/google/go-github/github#Response 124 if response.NextPage == 0 { 125 return runs, nil 126 } 127 128 // Setup for the next call. 129 options.ListOptions.Page = response.NextPage 130 } 131 132 return nil, fmt.Errorf("More than 10 pages of CheckRuns returned for ref %s", suite.SHA) 133 } 134 135 func updateExistingCheckRunSummary( 136 ctx context.Context, 137 summary summaries.Summary, 138 suite shared.CheckSuite, 139 run *github.CheckRun, 140 ) (bool, error) { 141 log := shared.GetLogger(ctx) 142 143 state := summary.GetCheckState() 144 actions := summary.GetActions() 145 146 summaryStr, err := summary.GetSummary() 147 if err != nil { 148 log.Warningf("Failed to generate summary for %s: %s", state.HeadSHA, err.Error()) 149 150 return false, err 151 } 152 153 detailsURLStr := state.DetailsURL.String() 154 title := state.Title() 155 // nolint:exhaustruct // WONTFIX: Name, HeadSHA only required. 156 opts := github.UpdateCheckRunOptions{ 157 Name: state.Name(), 158 DetailsURL: &detailsURLStr, 159 Status: &state.Status, 160 Conclusion: state.Conclusion, 161 Output: &github.CheckRunOutput{ 162 Title: &title, 163 Summary: &summaryStr, 164 }, 165 Actions: actions, 166 } 167 if state.Conclusion != nil { 168 opts.CompletedAt = &github.Timestamp{Time: time.Now()} 169 } 170 171 client, err := getGitHubClient(ctx, suite.AppID, suite.InstallationID) 172 if err != nil { 173 return false, err 174 } 175 176 _, _, err = client.Checks.UpdateCheckRun(ctx, suite.Owner, suite.Repo, run.GetID(), opts) 177 if err != nil { 178 log.Errorf("Failed to update run %v: %s", run.GetID(), err.Error()) 179 180 return false, err 181 } 182 183 return true, err 184 }