blob: b6b134d2b233083ad423beb40c2a6d2beb0dc38b [file] [log] [blame]
// Package notify is a package for sending notifications.
package notify
import (
"context"
"go.skia.org/infra/go/skerr"
"go.skia.org/infra/perf/go/alerts"
"go.skia.org/infra/perf/go/clustering2"
"go.skia.org/infra/perf/go/config"
"go.skia.org/infra/perf/go/git/provider"
"go.skia.org/infra/perf/go/notifytypes"
"go.skia.org/infra/perf/go/stepfit"
)
// Formatter has implementations for both HTML and Markdown.
type Formatter interface {
// Return body and subject.
FormatNewRegression(ctx context.Context, commit, previousCommit provider.Commit, alert *alerts.Alert, cl *clustering2.ClusterSummary, URL string) (string, string, error)
FormatRegressionMissing(ctx context.Context, commit, previousCommit provider.Commit, alert *alerts.Alert, cl *clustering2.ClusterSummary, URL string) (string, string, error)
}
// Transport has implementations for email, issuetracker, and the noop implementation.
type Transport interface {
SendNewRegression(ctx context.Context, alert *alerts.Alert, body, subject string) (threadingReference string, err error)
SendRegressionMissing(ctx context.Context, threadingReference string, alert *alerts.Alert, body, subject string) (err error)
}
const (
fromAddress = "alertserver@skia.org"
)
// context is used in expanding the message templates.
type templateContext struct {
URL string
Commit provider.Commit
CommitURL string
Alert *alerts.Alert
Cluster *clustering2.ClusterSummary
}
// Notifier sends notifications.
type Notifier struct {
formatter Formatter
transport Transport
// url is the URL of this instance of Perf.
url string
}
// newNotifier returns a newNotifier Notifier.
func newNotifier(formatter Formatter, transport Transport, url string) *Notifier {
return &Notifier{
formatter: formatter,
transport: transport,
url: url,
}
}
// RegressionFound sends a notification for the given cluster found at the given commit. Where to send it is defined in the alerts.Config.
func (n *Notifier) RegressionFound(ctx context.Context, commit, previousCommit provider.Commit, alert *alerts.Alert, cl *clustering2.ClusterSummary) (string, error) {
body, subject, err := n.formatter.FormatNewRegression(ctx, commit, previousCommit, alert, cl, n.url)
if err != nil {
return "", err
}
threadingReference, err := n.transport.SendNewRegression(ctx, alert, body, subject)
if err != nil {
return "", skerr.Wrapf(err, "sending new regression message")
}
return threadingReference, nil
}
// RegressionMissing sends a notification that a previous regression found for
// the given cluster found at the given commit has disappeared after more data
// has arrived. Where to send it is defined in the alerts.Config.
func (n *Notifier) RegressionMissing(ctx context.Context, commit, previousCommit provider.Commit, alert *alerts.Alert, cl *clustering2.ClusterSummary, threadingReference string) error {
body, subject, err := n.formatter.FormatRegressionMissing(ctx, commit, previousCommit, alert, cl, n.url)
if err != nil {
return err
}
if err := n.transport.SendRegressionMissing(ctx, threadingReference, alert, body, subject); err != nil {
return skerr.Wrapf(err, "sending regression missing message")
}
return nil
}
// ExampleSend sends an example for dummy data for the given alerts.Config.
func (n *Notifier) ExampleSend(ctx context.Context, alert *alerts.Alert) error {
commit := provider.Commit{
Subject: "An example commit use for testing.",
URL: "https://skia.googlesource.com/skia/+show/d261e1075a93677442fdf7fe72aba7e583863664",
GitHash: "d261e1075a93677442fdf7fe72aba7e583863664",
Timestamp: 1498176000,
}
previousCommit := provider.Commit{
Subject: "An example previous commit to use for testing.",
URL: "https://skia.googlesource.com/skia/+/fb49909acafba5e031b90a265a6ce059cda85019",
GitHash: "fb49909acafba5e031b90a265a6ce059cda85019",
Timestamp: 1687824470,
}
cl := &clustering2.ClusterSummary{
Num: 10,
StepFit: &stepfit.StepFit{
Status: stepfit.HIGH,
},
}
threadingReference, err := n.RegressionFound(ctx, commit, previousCommit, alert, cl)
if err != nil {
return skerr.Wrap(err)
}
err = n.RegressionMissing(ctx, commit, previousCommit, alert, cl, threadingReference)
if err != nil {
return skerr.Wrap(err)
}
return nil
}
// New returns a Notifier of the selected type.
func New(ctx context.Context, cfg *config.NotifyConfig, URL, commitRangeURITemplate string) (*Notifier, error) {
switch cfg.Notifications {
case notifytypes.None:
return newNotifier(NewHTMLFormatter(commitRangeURITemplate), NewNoopTransport(), URL), nil
case notifytypes.HTMLEmail:
return newNotifier(NewHTMLFormatter(commitRangeURITemplate), NewEmailTransport(), URL), nil
case notifytypes.MarkdownIssueTracker:
tracker, err := NewIssueTrackerTransport(ctx, cfg)
if err != nil {
return nil, skerr.Wrap(err)
}
return newNotifier(NewMarkdownFormatter(commitRangeURITemplate), tracker, URL), nil
default:
return nil, skerr.Fmt("invalid Notifier type: %s, must be one of: %v", cfg.Notifications, notifytypes.AllNotifierTypes)
}
}