blob: 5b82a9f47ed1dc6e11b293c20b52c3c94752e403 [file] [log] [blame]
// notify is a package for sending notification.
package notify
import (
"bytes"
"fmt"
"html/template"
"regexp"
"go.skia.org/infra/go/sklog"
"go.skia.org/infra/perf/go/alerts"
"go.skia.org/infra/perf/go/cid"
"go.skia.org/infra/perf/go/clustering2"
)
const (
fromAddress = "alertserver@skia.org"
email = `<b>Alert</b><br><br>
<p>
A Perf Regression has been found at:
</p>
<p style="padding: 1em;">
<a href="{{.URL}}/g/t/{{.Commit.Hash}}">{{.URL}}/g/t/{{.Commit.Hash}}</a>
</p>
<p>
For:
</p>
<p style="padding: 1em;">
<a href="{{.Commit.URL}}">{{.Commit.URL}}</a>
</p>
<p>
With {{.Cluster.Num}} matching traces.
</p>`
)
var (
emailTemplate = template.Must(template.New("email").Parse(email))
emailAddressSplitter = regexp.MustCompile("[, ]+")
)
// Email sending interface. Note that email.GMail implements this interface.
type Email interface {
Send(from string, to []string, subject string, body string, threadingReference string) (string, error)
}
// NoEmail implements Email but only logs the information without sending email.
type NoEmail struct{}
// Send implements the Email interface.
func (n NoEmail) Send(from string, to []string, subject string, body string, threadingReference string) (string, error) {
sklog.Infof("Not sending email: From: %q To: %q Subject: %q Body: %q ThreadingReference: %q", from, to, subject, body, threadingReference)
return "", nil
}
// Notifier sends notifications.
type Notifier struct {
// email is the thing that sends email.
email Email
// url is the URL of this instance of Perf.
url string
}
// New returns a new Notifier.
func New(email Email, url string) *Notifier {
return &Notifier{
email: email,
url: url,
}
}
// context is used in expanding the emailTemplate.
type context struct {
URL string
Commit *cid.CommitDetail
Alert *alerts.Alert
Cluster *clustering2.ClusterSummary
}
func (n *Notifier) formatEmail(c *cid.CommitDetail, alert *alerts.Alert, cl *clustering2.ClusterSummary) (string, error) {
templateContext := &context{
URL: n.url,
Commit: c,
Alert: alert,
Cluster: cl,
}
var b bytes.Buffer
if err := emailTemplate.Execute(&b, templateContext); err != nil {
return "", fmt.Errorf("Failed to format email body: %s", err)
}
return b.String(), nil
}
func splitEmails(s string) []string {
ret := []string{}
for _, e := range emailAddressSplitter.Split(s, -1) {
if e != "" {
ret = append(ret, e)
}
}
return ret
}
// Send a notification for the given cluster found at the given commit. Where to send it is defined in the alerts.Config.
func (n *Notifier) Send(c *cid.CommitDetail, alert *alerts.Alert, cl *clustering2.ClusterSummary) error {
if alert.Alert == "" {
return fmt.Errorf("No notification sent. No email address set for alert #%d", alert.ID)
}
body, err := n.formatEmail(c, alert, cl)
if err != nil {
return err
}
subject := fmt.Sprintf("%s - Regression found for %q", alert.DisplayName, c.Message)
if _, err := n.email.Send(fromAddress, splitEmails(alert.Alert), subject, body, ""); err != nil {
return fmt.Errorf("Failed to send email: %s", err)
}
return nil
}
// ExampleSend sends an example for dummy data for the given alerts.Config.
func (n *Notifier) ExampleSend(alert *alerts.Alert) error {
c := &cid.CommitDetail{
Message: "Re-enable opList dependency tracking",
URL: "https://skia.googlesource.com/skia/+show/d261e1075a93677442fdf7fe72aba7e583863664",
Hash: "d261e1075a93677442fdf7fe72aba7e583863664",
}
cl := &clustering2.ClusterSummary{
Num: 10,
}
return n.Send(c, alert, cl)
}