-
Notifications
You must be signed in to change notification settings - Fork 1.3k
status-indicator: Display external service validation and syncing errors #4804
Changes from 11 commits
1190140
62cadf3
82c7c0a
9e64810
45dec7f
be183c6
bec69b3
0cad5ee
18622eb
62d2f41
4e9e86c
ee52f03
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,7 +26,7 @@ type Sourcer func(...*ExternalService) (Sources, error) | |
func NewSourcer(cf *httpcli.Factory, decs ...func(Source) Source) Sourcer { | ||
return func(svcs ...*ExternalService) (Sources, error) { | ||
srcs := make([]Source, 0, len(svcs)) | ||
errs := new(multierror.Error) | ||
errs := new(MultiSourceError) | ||
|
||
for _, svc := range svcs { | ||
if svc.IsDeleted() { | ||
|
@@ -35,7 +35,7 @@ func NewSourcer(cf *httpcli.Factory, decs ...func(Source) Source) Sourcer { | |
|
||
src, err := NewSource(svc, cf) | ||
if err != nil { | ||
errs = multierror.Append(errs, err) | ||
errs.Append(&SourceError{Err: err, ExtSvc: svc}) | ||
continue | ||
} | ||
|
||
|
@@ -84,6 +84,60 @@ type Source interface { | |
ExternalServices() ExternalServices | ||
} | ||
|
||
type SourceError struct { | ||
Err error | ||
ExtSvc *ExternalService | ||
} | ||
|
||
func (s *SourceError) Error() string { | ||
if multiErr, ok := s.Err.(*multierror.Error); ok { | ||
multiErr.ErrorFormat = sourceErrorFormatFunc | ||
return multiErr.Error() | ||
} | ||
return s.Err.Error() | ||
} | ||
|
||
func sourceErrorFormatFunc(es []error) string { | ||
if len(es) == 1 { | ||
return es[0].Error() | ||
} | ||
|
||
points := make([]string, len(es)) | ||
mrnugget marked this conversation as resolved.
Show resolved
Hide resolved
|
||
for i, err := range es { | ||
points[i] = fmt.Sprintf("* %s", err) | ||
} | ||
|
||
return fmt.Sprintf( | ||
"%d errors occurred:\n\t%s\n\n", | ||
len(es), strings.Join(points, "\n\t")) | ||
} | ||
|
||
type MultiSourceError struct { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What makes this type necessary? Can't we work with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's what I did first, but it lead to the code looking like this: if multiErr, ok := err.(*multierror.Error); ok {
for _, sub := range multiErr.Errors {
if sourceErr, ok := sub.(*SourceError); ok {
m := newSyncErrMessage(sourceErr)
resp.Messages = append(resp.Messages, m)
} else {
// drop error? add generic message?
}
}
} else {
// drop error? add generic message?
} That seems a bit unwieldy and I think the general problem that needs to be solved is the association of errors to external services and making those accessible to consumers. I'm also thinking that maybe we need a map that gets updated with each sync run, kinda like And another problem is that, of course, the |
||
Errors []*SourceError | ||
} | ||
|
||
func (s *MultiSourceError) Error() string { | ||
errs := new(multierror.Error) | ||
for _, e := range s.Errors { | ||
errs = multierror.Append(errs, e) | ||
} | ||
return errs.Error() | ||
} | ||
|
||
func (s *MultiSourceError) Append(errs ...*SourceError) { | ||
s.Errors = append(s.Errors, errs...) | ||
} | ||
|
||
func (s *MultiSourceError) ErrorOrNil() error { | ||
if s == nil { | ||
return nil | ||
} | ||
if len(s.Errors) == 0 { | ||
return nil | ||
} | ||
return s | ||
} | ||
|
||
// Sources is a list of Sources that implements the Source interface. | ||
type Sources []Source | ||
|
||
|
@@ -97,7 +151,7 @@ func (srcs Sources) ListRepos(ctx context.Context) ([]*Repo, error) { | |
type result struct { | ||
src Source | ||
repos []*Repo | ||
err error | ||
errs []*SourceError | ||
} | ||
|
||
// Group sources by external service kind so that we execute requests | ||
|
@@ -113,7 +167,11 @@ func (srcs Sources) ListRepos(ctx context.Context) ([]*Repo, error) { | |
defer wg.Done() | ||
for _, src := range sources { | ||
if repos, err := src.ListRepos(ctx); err != nil { | ||
ch <- result{src: src, err: err} | ||
var errs []*SourceError | ||
for _, extSvc := range src.ExternalServices() { | ||
errs = append(errs, &SourceError{Err: err, ExtSvc: extSvc}) | ||
} | ||
ch <- result{src: src, errs: errs} | ||
} else { | ||
ch <- result{src: src, repos: repos} | ||
} | ||
|
@@ -127,11 +185,11 @@ func (srcs Sources) ListRepos(ctx context.Context) ([]*Repo, error) { | |
}() | ||
|
||
var repos []*Repo | ||
errs := new(multierror.Error) | ||
errs := new(MultiSourceError) | ||
|
||
for r := range ch { | ||
if r.err != nil { | ||
errs = multierror.Append(errs, r.err) | ||
if len(r.errs) != 0 { | ||
errs.Append(r.errs...) | ||
} else { | ||
repos = append(repos, r.repos...) | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So is this type just a way to get an untyped key/value bag? Why can't this metadata be typed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because the metadata differs depending on which type the
StatusMessage
has. AFAIK GraphQL doesn't support union types, which is what I'd want here.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GraphQL does support union types
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, interesting! 🤔