Skip to content

Commit f49c615

Browse files
authored
Merge pull request #496 from danilo-gemoli/feat/tide/improve-blockers-err-handling
Tide: Improve error handling when getting PR blockers
2 parents ccc9c53 + 5b539dc commit f49c615

File tree

4 files changed

+266
-42
lines changed

4 files changed

+266
-42
lines changed

pkg/tide/blockers/blockers.go

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,27 @@ type OrgRepoBranch struct {
5454
Org, Repo, Branch string
5555
}
5656

57+
type OrgError struct {
58+
Orgs sets.Set[string]
59+
inner error
60+
}
61+
62+
func (e *OrgError) Error() string {
63+
return e.inner.Error()
64+
}
65+
66+
func (e *OrgError) Unwrap() error {
67+
return e.inner
68+
}
69+
70+
func (e *OrgError) Is(other error) bool {
71+
if other == nil {
72+
return false
73+
}
74+
_, ok := other.(*OrgError)
75+
return ok
76+
}
77+
5778
// Blockers holds maps of issues that are blocking various repos/branches.
5879
type Blockers struct {
5980
Repo map[OrgRepo][]Blocker `json:"repo,omitempty"`
@@ -77,7 +98,7 @@ func FindAll(ghc githubClient, log *logrus.Entry, label string, orgRepoTokensByO
7798
queries := map[string]sets.Set[string]{}
7899
for org, query := range orgRepoTokensByOrg {
79100
if splitQueryByOrg {
80-
queries[org] = sets.New[string](blockerQuery(label, query)...)
101+
queries[org] = sets.New(blockerQuery(label, query)...)
81102
} else {
82103
if queries[""] == nil {
83104
queries[""] = sets.Set[string]{}
@@ -90,6 +111,7 @@ func FindAll(ghc githubClient, log *logrus.Entry, label string, orgRepoTokensByO
90111
var errs []error
91112
var lock sync.Mutex
92113
var wg sync.WaitGroup
114+
var orgErrs = sets.New[string]()
93115

94116
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
95117
defer cancel()
@@ -110,6 +132,7 @@ func FindAll(ghc githubClient, log *logrus.Entry, label string, orgRepoTokensByO
110132
defer lock.Unlock()
111133
if err != nil {
112134
errs = append(errs, err)
135+
orgErrs.Insert(org)
113136
return
114137
}
115138
issues = append(issues, result...)
@@ -119,11 +142,16 @@ func FindAll(ghc githubClient, log *logrus.Entry, label string, orgRepoTokensByO
119142
}
120143
wg.Wait()
121144

145+
var orgErr error
122146
if err := utilerrors.NewAggregate(errs); err != nil {
123-
return Blockers{}, fmt.Errorf("error searching for blocker issues: %w", err)
147+
if splitQueryByOrg {
148+
orgErr = &OrgError{Orgs: orgErrs, inner: fmt.Errorf("error searching for blocker issues: %w", err)}
149+
} else {
150+
return Blockers{}, fmt.Errorf("error searching for blocker issues: %w", err)
151+
}
124152
}
125153

126-
return fromIssues(issues, log), nil
154+
return fromIssues(issues, log), orgErr
127155
}
128156

129157
func fromIssues(issues []Issue, log *logrus.Entry) Blockers {

pkg/tide/blockers/blockers_test.go

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package blockers
1818

1919
import (
2020
"context"
21+
"errors"
2122
"fmt"
2223
"reflect"
2324
"strconv"
@@ -435,11 +436,18 @@ func TestBlockers(t *testing.T) {
435436
}
436437

437438
type fakeGitHubClient struct {
438-
lock sync.Mutex
439-
queries map[string][]string
439+
lock sync.Mutex
440+
queries map[string][]string
441+
injectErr func(ctx context.Context, q interface{}, vars map[string]interface{}, org string) error
440442
}
441443

442444
func (fghc *fakeGitHubClient) QueryWithGitHubAppsSupport(ctx context.Context, q interface{}, vars map[string]interface{}, org string) error {
445+
if fghc.injectErr != nil {
446+
if err := fghc.injectErr(ctx, q, vars, org); err != nil {
447+
return err
448+
}
449+
}
450+
443451
if query := vars["query"]; query == nil || string(query.(githubql.String)) == "" {
444452
return fmt.Errorf("query variable was unset, variables: %+v", vars)
445453
}
@@ -461,37 +469,61 @@ func TestBlockersFindAll(t *testing.T) {
461469
orgRepoTokensByOrg := map[string]string{
462470
"org-a": `org:"org-a" -repo:"org-a/repo-b"`,
463471
"org-b": `org:"org-b" -repo:"org-b/repo-b"`,
472+
"org-c": `org:"org-c" -repo:"org-c/repo-c"`,
464473
}
465474
const blockerLabel = "tide/merge-blocker"
466475
testCases := []struct {
467-
name string
468-
usesAppsAuth bool
469-
476+
name string
477+
usesAppsAuth bool
478+
injectQueryErr func(ctx context.Context, q interface{}, vars map[string]interface{}, org string) error
470479
expectedQueries map[string][]string
480+
expectedErr error
471481
}{
472482
{
473483
name: "Apps auth, query is split by org",
474484
usesAppsAuth: true,
475485
expectedQueries: map[string][]string{
476486
"org-a": {`-repo:"org-a/repo-b" is:issue label:"tide/merge-blocker" org:"org-a" state:open`},
477487
"org-b": {`-repo:"org-b/repo-b" is:issue label:"tide/merge-blocker" org:"org-b" state:open`},
488+
"org-c": {`-repo:"org-c/repo-c" is:issue label:"tide/merge-blocker" org:"org-c" state:open`},
478489
},
479490
},
480491
{
481492
name: "No apps auth, one query",
482493
usesAppsAuth: false,
483494
expectedQueries: map[string][]string{
484-
"": {`-repo:"org-a/repo-b" -repo:"org-b/repo-b" is:issue label:"tide/merge-blocker" org:"org-a" org:"org-b" state:open`},
495+
"": {`-repo:"org-a/repo-b" -repo:"org-b/repo-b" -repo:"org-c/repo-c" is:issue label:"tide/merge-blocker" org:"org-a" org:"org-b" org:"org-c" state:open`},
496+
},
497+
},
498+
{
499+
name: "One org query fails, the other succeed",
500+
usesAppsAuth: true,
501+
injectQueryErr: func(ctx context.Context, q interface{}, vars map[string]interface{}, org string) error {
502+
if org == "org-c" {
503+
return errors.New("fault")
504+
}
505+
return nil
506+
},
507+
expectedQueries: map[string][]string{
508+
"org-a": {`-repo:"org-a/repo-b" is:issue label:"tide/merge-blocker" org:"org-a" state:open`},
509+
"org-b": {`-repo:"org-b/repo-b" is:issue label:"tide/merge-blocker" org:"org-b" state:open`},
485510
},
511+
expectedErr: &OrgError{},
486512
},
487513
}
488514

489515
for _, tc := range testCases {
490516
t.Run(tc.name, func(t *testing.T) {
491-
ghc := &fakeGitHubClient{}
517+
ghc := &fakeGitHubClient{injectErr: tc.injectQueryErr}
492518

493519
if _, err := FindAll(ghc, logrus.WithField("tc", tc.name), blockerLabel, orgRepoTokensByOrg, tc.usesAppsAuth); err != nil {
494-
t.Fatalf("FindAll: %v", err)
520+
if tc.expectedErr != nil {
521+
if !errors.Is(err, tc.expectedErr) {
522+
t.Errorf("want error %T but got %T", tc.expectedErr, err)
523+
}
524+
} else {
525+
t.Fatalf("FindAll: %v", err)
526+
}
495527
}
496528

497529
if diff := cmp.Diff(ghc.queries, tc.expectedQueries, cmpopts.SortSlices(func(a, b string) bool { return a < b })); diff != "" {

pkg/tide/tide.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"encoding/json"
2525
"errors"
2626
"fmt"
27+
"maps"
2728
"net/http"
2829
"sort"
2930
"strconv"
@@ -500,7 +501,18 @@ func (c *syncController) Sync() error {
500501
var blocks blockers.Blockers
501502
if len(prs) > 0 {
502503
blocks, err = c.provider.blockers()
503-
if err != nil {
504+
505+
// When the GitHubApp authentication is enabled, the blocker issues query is split by organization.
506+
// Each query might fail individually, and if this happens, we continue with the rest of the work
507+
// but we remove all PRs that belong to any of the affected organizations. This is why we don't
508+
// want to take into account PRs for which we don't have all the necessary information to process them.
509+
if orgBlockersErr := (&blockers.OrgError{}); errors.As(err, &orgBlockersErr) {
510+
failedBlockerOrgs := orgBlockersErr.Orgs.UnsortedList()
511+
c.logger.Warnf("failed getting blockers for organizations %q - won't merge any PR coming from those", strings.Join(failedBlockerOrgs, ","))
512+
maps.DeleteFunc(prs, func(_ string, pr CodeReviewCommon) bool {
513+
return orgBlockersErr.Orgs.Has(pr.Org)
514+
})
515+
} else if err != nil {
504516
return fmt.Errorf("failed getting blockers: %v", err)
505517
}
506518
}

0 commit comments

Comments
 (0)