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  }