github.com/web-platform-tests/wpt.fyi@v0.0.0-20240530210107-70cf978996f1/api/checks/api.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  //go:generate mockgen -destination mock_checks/api_mock.go github.com/web-platform-tests/wpt.fyi/api/checks API
     6  
     7  package checks
     8  
     9  import (
    10  	"context"
    11  	"fmt"
    12  	"net/url"
    13  	"time"
    14  
    15  	"github.com/google/go-github/v47/github"
    16  	"github.com/web-platform-tests/wpt.fyi/api/checks/summaries"
    17  	"github.com/web-platform-tests/wpt.fyi/shared"
    18  )
    19  
    20  const (
    21  	wptfyiCheckAppID        = int64(23318) // https://github.com/apps/wpt-fyi-status-check
    22  	wptfyiStagingCheckAppID = int64(19965) // https://github.com/apps/staging-wpt-fyi-status-check
    23  
    24  	wptRepoInstallationID        = int64(577173)
    25  	wptRepoStagingInstallationID = int64(449270)
    26  
    27  	wptRepoID                = int64(3618133)
    28  	checksForAllUsersFeature = "checksAllUsers"
    29  )
    30  
    31  // API abstracts all the API calls used externally.
    32  type API interface {
    33  	shared.AppEngineAPI
    34  
    35  	ScheduleResultsProcessing(sha string, browser shared.ProductSpec) error
    36  	GetSuitesForSHA(sha string) ([]shared.CheckSuite, error)
    37  	IgnoreFailure(sender, owner, repo string, run *github.CheckRun, installation *github.Installation) error
    38  	CancelRun(sender, owner, repo string, run *github.CheckRun, installation *github.Installation) error
    39  	CreateWPTCheckSuite(appID, installationID int64, sha string, prNumbers ...int) (bool, error)
    40  	GetWPTRepoAppInstallationIDs() (appID, installationID int64)
    41  }
    42  
    43  type checksAPIImpl struct {
    44  	shared.AppEngineAPI
    45  
    46  	queue string
    47  }
    48  
    49  // NewAPI returns a real implementation of the API.
    50  // nolint:ireturn // TODO: Fix ireturn lint error
    51  func NewAPI(ctx context.Context) API {
    52  	return checksAPIImpl{
    53  		AppEngineAPI: shared.NewAppEngineAPI(ctx),
    54  		queue:        CheckProcessingQueue,
    55  	}
    56  }
    57  
    58  // ScheduleResultsProcessing adds a URL for callback to TaskQueue for the given sha and
    59  // product, which will actually interpret the results and summarize the outcome.
    60  func (s checksAPIImpl) ScheduleResultsProcessing(sha string, product shared.ProductSpec) error {
    61  	log := shared.GetLogger(s.Context())
    62  	target := fmt.Sprintf("/api/checks/%s", sha)
    63  	q := url.Values{}
    64  	q.Set("product", product.String())
    65  	_, err := s.ScheduleTask(s.queue, "", target, q)
    66  	if err != nil {
    67  		log.Warningf("Failed to queue %s @ %s: %s", product.String(), sha[:7], err.Error())
    68  	} else {
    69  		log.Infof("Added %s @ %s to checks processing queue", product.String(), sha[:7])
    70  	}
    71  
    72  	return err
    73  }
    74  
    75  // GetSuitesForSHA gets all existing check suites for the given Head SHA.
    76  func (s checksAPIImpl) GetSuitesForSHA(sha string) ([]shared.CheckSuite, error) {
    77  	var suites []shared.CheckSuite
    78  	store := shared.NewAppEngineDatastore(s.Context(), false)
    79  	_, err := store.GetAll(store.NewQuery("CheckSuite").Filter("SHA =", sha), &suites)
    80  
    81  	return suites, err
    82  }
    83  
    84  // IgnoreFailure updates the given CheckRun's outcome to success, even if it failed.
    85  func (s checksAPIImpl) IgnoreFailure(
    86  	sender,
    87  	owner, repo string,
    88  	run *github.CheckRun,
    89  	installation *github.Installation,
    90  ) error {
    91  	client, err := getGitHubClient(s.Context(), run.GetApp().GetID(), installation.GetID())
    92  	if err != nil {
    93  		return err
    94  	}
    95  
    96  	// Keep the previous output, if applicable, but prefix it with an indication that
    97  	// somebody ignored the failure.
    98  	output := run.GetOutput()
    99  	if output == nil {
   100  		output = &github.CheckRunOutput{}
   101  	}
   102  	prepend := fmt.Sprintf("This check was marked as a success by @%s via the _Ignore_ action.\n\n", sender)
   103  	summary := prepend + output.GetSummary()
   104  	output.Summary = &summary
   105  
   106  	success := "success"
   107  	// nolint:exhaustruct // WONTFIX: Name only required.
   108  	opts := github.UpdateCheckRunOptions{
   109  		Name:        run.GetName(),
   110  		Output:      output,
   111  		Conclusion:  &success,
   112  		CompletedAt: &github.Timestamp{Time: time.Now()},
   113  		Actions: []*github.CheckRunAction{
   114  			summaries.RecomputeAction(),
   115  		},
   116  	}
   117  	_, _, err = client.Checks.UpdateCheckRun(s.Context(), owner, repo, run.GetID(), opts)
   118  
   119  	return err
   120  }
   121  
   122  // CancelRun updates the given CheckRun's outcome to cancelled, even if it failed.
   123  func (s checksAPIImpl) CancelRun(
   124  	sender,
   125  	owner,
   126  	repo string,
   127  	run *github.CheckRun,
   128  	installation *github.Installation,
   129  ) error {
   130  	client, err := getGitHubClient(s.Context(), run.GetApp().GetID(), installation.GetID())
   131  	if err != nil {
   132  		return err
   133  	}
   134  
   135  	// Keep the previous output, if applicable, but prefix it with an indication that
   136  	// somebody ignored the failure.
   137  	summary := fmt.Sprintf("This check was cancelled by @%s via the _Cancel_ action.", sender)
   138  	title := run.GetOutput().GetTitle()
   139  	output := &github.CheckRunOutput{
   140  		Title:   &title,
   141  		Summary: &summary,
   142  	}
   143  
   144  	cancelled := "cancelled"
   145  	// nolint:exhaustruct // WONTFIX: Name only required.
   146  	opts := github.UpdateCheckRunOptions{
   147  		Name:        run.GetName(),
   148  		Output:      output,
   149  		Conclusion:  &cancelled,
   150  		CompletedAt: &github.Timestamp{Time: time.Now()},
   151  		Actions: []*github.CheckRunAction{
   152  			summaries.RecomputeAction(),
   153  			summaries.IgnoreAction(),
   154  		},
   155  	}
   156  	_, _, err = client.Checks.UpdateCheckRun(s.Context(), owner, repo, run.GetID(), opts)
   157  
   158  	return err
   159  }
   160  
   161  // CreateWPTCheckSuite creates a check_suite on the main wpt repo for the given
   162  // SHA. This is needed when a PR comes from a different fork of the repo.
   163  func (s checksAPIImpl) CreateWPTCheckSuite(appID, installationID int64, sha string, prNumbers ...int) (bool, error) {
   164  	log := shared.GetLogger(s.Context())
   165  	log.Debugf("Creating check_suite for web-platform-tests/wpt @ %s", sha)
   166  
   167  	client, err := getGitHubClient(s.Context(), appID, installationID)
   168  	if err != nil {
   169  		return false, err
   170  	}
   171  
   172  	// nolint:exhaustruct // WONTFIX: HeadSHA only required.
   173  	opts := github.CreateCheckSuiteOptions{
   174  		HeadSHA: sha,
   175  	}
   176  	suite, _, err := client.Checks.CreateCheckSuite(s.Context(), shared.WPTRepoOwner, shared.WPTRepoName, opts)
   177  	if err != nil {
   178  		log.Errorf("Failed to create GitHub check suite: %s", err.Error())
   179  	} else if suite != nil {
   180  		log.Infof("check_suite %v created", suite.GetID())
   181  		_, err = getOrCreateCheckSuite(
   182  			s.Context(),
   183  			sha,
   184  			shared.WPTRepoOwner,
   185  			shared.WPTRepoName,
   186  			appID,
   187  			installationID,
   188  			prNumbers...,
   189  		)
   190  		if err != nil {
   191  			log.Infof("Error while getting check suite: %s", err.Error())
   192  		}
   193  	}
   194  
   195  	return suite != nil, err
   196  }
   197  
   198  func (s checksAPIImpl) GetWPTRepoAppInstallationIDs() (appID, installationID int64) {
   199  	// Production
   200  	if s.GetHostname() == "wpt.fyi" {
   201  		return wptfyiCheckAppID, wptRepoInstallationID
   202  	}
   203  	// Default to staging
   204  	return wptfyiStagingCheckAppID, wptRepoStagingInstallationID
   205  }