Skip to content

Commit

Permalink
Merge pull request #2809 from target/feat/webhook-notification-channel
Browse files Browse the repository at this point in the history
feat: adding webhooks as a notification channel type
  • Loading branch information
mastercactapus authored Feb 24, 2023
2 parents 4cbad91 + b36b519 commit e447cac
Show file tree
Hide file tree
Showing 14 changed files with 91 additions and 24 deletions.
4 changes: 4 additions & 0 deletions alert/alertlog/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,8 @@ func (s *Store) logAny(ctx context.Context, tx *sql.Tx, insertStmt *sql.Stmt, id
switch ncType {
case notificationchannel.TypeSlack:
r.subject.classifier = "Slack"
case notificationchannel.TypeWebhook:
r.subject.classifier = "Webhook"
}
r.subject.channelID.String = src.ID
r.subject.channelID.Valid = true
Expand Down Expand Up @@ -344,6 +346,8 @@ func (s *Store) logAny(ctx context.Context, tx *sql.Tx, insertStmt *sql.Stmt, id
r.subject.classifier = "SMS"
case notification.DestTypeUserEmail:
r.subject.classifier = "Email"
case notification.DestTypeChanWebhook:
fallthrough
case notification.DestTypeUserWebhook:
r.subject.classifier = "Webhook"
case notification.DestTypeSlackChannel:
Expand Down
5 changes: 4 additions & 1 deletion app/startup.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,10 @@ func (app *App) startup(ctx context.Context) error {

app.initStartup(ctx, "Startup.Slack", app.initSlack)
app.notificationManager.RegisterSender(notification.DestTypeUserEmail, "smtp", email.NewSender(ctx))
app.notificationManager.RegisterSender(notification.DestTypeUserWebhook, "webhook", webhook.NewSender(ctx))
app.notificationManager.RegisterSender(notification.DestTypeUserWebhook, "webhook-user", webhook.NewSender(ctx))
if expflag.ContextHas(ctx, expflag.ChanWebhook) {
app.notificationManager.RegisterSender(notification.DestTypeChanWebhook, "webhook-channel", webhook.NewSender(ctx))
}

app.initStartup(ctx, "Startup.Engine", app.initEngine)
app.initStartup(ctx, "Startup.Auth", app.initAuth)
Expand Down
5 changes: 5 additions & 0 deletions assignment/targettype.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const (
TargetTypeUser
TargetTypeNotificationChannel
TargetTypeSlackChannel
TargetTypeChanWebhook
TargetTypeIntegrationKey
TargetTypeUserOverride
TargetTypeNotificationRule
Expand Down Expand Up @@ -61,6 +62,8 @@ func (tt *TargetType) UnmarshalText(data []byte) error {
*tt = TargetTypeNotificationChannel
case "slackChannel":
*tt = TargetTypeSlackChannel
case "chanWebhook":
*tt = TargetTypeChanWebhook
case "userOverride":
*tt = TargetTypeUserOverride
case "contactMethod":
Expand Down Expand Up @@ -111,6 +114,8 @@ func (tt TargetType) MarshalText() ([]byte, error) {
return []byte("notificationChannel"), nil
case TargetTypeSlackChannel:
return []byte("slackChannel"), nil
case TargetTypeChanWebhook:
return []byte("chanWebhook"), nil
case TargetTypeContactMethod:
return []byte("contactMethod"), nil
case TargetTypeNotificationRule:
Expand Down
17 changes: 9 additions & 8 deletions assignment/targettype_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

52 changes: 45 additions & 7 deletions escalation/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,18 @@ package escalation
import (
"context"
"database/sql"
"net/url"

"github.com/target/goalert/alert/alertlog"
"github.com/target/goalert/assignment"
"github.com/target/goalert/expflag"
"github.com/target/goalert/notification/slack"
"github.com/target/goalert/notificationchannel"
"github.com/target/goalert/permission"
"github.com/target/goalert/util"
"github.com/target/goalert/util/log"
"github.com/target/goalert/util/sqlutil"
"github.com/target/goalert/validation"
"github.com/target/goalert/validation/validate"

"github.com/google/uuid"
Expand All @@ -31,7 +34,7 @@ type Store struct {
ncStore *notificationchannel.Store
slackFn func(ctx context.Context, channelID string) (*slack.Channel, error)

findSlackChan *sql.Stmt
findNotifChan *sql.Stmt

findOnePolicy *sql.Stmt
findOnePolicyForUpdate *sql.Stmt
Expand Down Expand Up @@ -64,13 +67,13 @@ func NewStore(ctx context.Context, db *sql.DB, cfg Config) (*Store, error) {
slackFn: cfg.SlackLookupFunc,
ncStore: cfg.NCStore,

findSlackChan: p.P(`
findNotifChan: p.P(`
SELECT chan.id
FROM notification_channels chan
JOIN escalation_policy_actions act ON
act.escalation_policy_step_id = $1 AND
act.channel_id = chan.id
WHERE chan.value = $2 and chan.type = 'SLACK'
WHERE chan.value = $2 and chan.type = $3
`),

findOnePolicy: p.P(`
Expand Down Expand Up @@ -286,6 +289,22 @@ func (s *Store) _updateStepTarget(ctx context.Context, stepID string, tgt assign
return err
}

func (s *Store) chanWebhook(ctx context.Context, tx *sql.Tx, webhookTarget assignment.Target) (assignment.Target, error) {
webhookUrl, err := url.Parse(webhookTarget.TargetID())
if err != nil {
return nil, err
}
notifID, err := s.ncStore.MapToID(ctx, tx, &notificationchannel.Channel{
Type: notificationchannel.TypeWebhook,
Name: webhookUrl.Hostname(),
Value: webhookTarget.TargetID(),
})
if err != nil {
return nil, err
}
return assignment.NotificationChannelTarget(notifID.String()), nil
}

func (s *Store) newSlackChannel(ctx context.Context, tx *sql.Tx, slackChanID string) (assignment.Target, error) {
ch, err := s.slackFn(ctx, slackChanID)
if err != nil {
Expand All @@ -304,9 +323,9 @@ func (s *Store) newSlackChannel(ctx context.Context, tx *sql.Tx, slackChanID str
return assignment.NotificationChannelTarget(notifID.String()), nil
}

func (s *Store) lookupSlackChannel(ctx context.Context, tx *sql.Tx, stepID, slackChanID string) (assignment.Target, error) {
func (s *Store) lookupNotifChannel(ctx context.Context, tx *sql.Tx, stepID, chanID, chanType string) (assignment.Target, error) {
var notifChanID string
err := tx.StmtContext(ctx, s.findSlackChan).QueryRowContext(ctx, stepID, slackChanID).Scan(&notifChanID)
err := tx.StmtContext(ctx, s.findNotifChan).QueryRowContext(ctx, stepID, chanID, chanType).Scan(&notifChanID)
if err != nil {
return nil, err
}
Expand All @@ -323,14 +342,31 @@ func (s *Store) AddStepTargetTx(ctx context.Context, tx *sql.Tx, stepID string,
return err
}
}
if tgt.TargetType() == assignment.TargetTypeChanWebhook {
if !expflag.ContextHas(ctx, expflag.ChanWebhook) {
return validation.NewFieldError("type", "Webhook notification channels are not enabled")
}
var err error
tgt, err = s.chanWebhook(ctx, tx, tgt)
if err != nil {
return err
}
}
return s._updateStepTarget(ctx, stepID, tgt, tx.StmtContext(ctx, s.addStepTarget), true)
}

// DeleteStepTargetTx removes the target from the step.
func (s *Store) DeleteStepTargetTx(ctx context.Context, tx *sql.Tx, stepID string, tgt assignment.Target) error {
if tgt.TargetType() == assignment.TargetTypeSlackChannel {
var err error
tgt, err = s.lookupSlackChannel(ctx, tx, stepID, tgt.TargetID())
tgt, err = s.lookupNotifChannel(ctx, tx, stepID, tgt.TargetID(), "SLACK")
if err != nil {
return err
}
}
if tgt.TargetType() == assignment.TargetTypeChanWebhook {
var err error
tgt, err = s.lookupNotifChannel(ctx, tx, stepID, tgt.TargetID(), "WEBHOOK")
if err != nil {
return err
}
Expand Down Expand Up @@ -389,6 +425,9 @@ func (s *Store) FindAllStepTargetsTx(ctx context.Context, tx *sql.Tx, stepID str
case notificationchannel.TypeSlack:
tgt.ID = chValue.String
tgt.Type = assignment.TargetTypeSlackChannel
case notificationchannel.TypeWebhook:
tgt.ID = chValue.String
tgt.Type = assignment.TargetTypeChanWebhook
default:
tgt.ID = ch.String
tgt.Type = assignment.TargetTypeNotificationChannel
Expand Down Expand Up @@ -678,7 +717,6 @@ func (s *Store) CreateStepTx(ctx context.Context, tx *sql.Tx, st *Step) (*Step,
}

s.logChange(ctx, tx, st.PolicyID)

return n, nil
}

Expand Down
10 changes: 6 additions & 4 deletions expflag/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import "sort"
type Flag string

const (
Example Flag = "example"
SlackDM Flag = "slack-dm"
Example Flag = "example"
SlackDM Flag = "slack-dm"
ChanWebhook Flag = "chan-webhook"
)

var desc = map[Flag]string{
Example: "An example experimental flag to demonstrate usage.",
SlackDM: "Enables sending notifications to Slack DMs as a user contact method.",
Example: "An example experimental flag to demonstrate usage.",
SlackDM: "Enables sending notifications to Slack DMs as a user contact method.",
ChanWebhook: "Enables webhooks as a notification channel type",
}

// AllFlags returns a slice of all experimental flags sorted by name.
Expand Down
1 change: 1 addition & 0 deletions graphql2/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -1176,6 +1176,7 @@ enum TargetType {
service
schedule
user
chanWebhook
integrationKey
userOverride
notificationRule
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-- +migrate Up

ALTER TYPE enum_notif_channel_type ADD VALUE IF NOT EXISTS 'WEBHOOK';

-- +migrate Down
5 changes: 5 additions & 0 deletions notification/desttype.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const (
DestTypeSlackDM
DestTypeUserEmail
DestTypeUserWebhook
DestTypeChanWebhook
)

func (d Dest) String() string { return fmt.Sprintf("%s(%s)", d.Type.String(), d.ID) }
Expand Down Expand Up @@ -77,6 +78,8 @@ func (t ScannableDestType) DestType() DestType {
switch t.NC {
case notificationchannel.TypeSlack:
return DestTypeSlackChannel
case notificationchannel.TypeWebhook:
return DestTypeChanWebhook
}

return DestTypeUnknown
Expand All @@ -87,6 +90,8 @@ func (t DestType) NCType() notificationchannel.Type {
switch t {
case DestTypeSlackChannel:
return notificationchannel.TypeSlack
case DestTypeChanWebhook:
return notificationchannel.TypeWebhook
}

return notificationchannel.TypeUnknown
Expand Down
5 changes: 3 additions & 2 deletions notification/desttype_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion notificationchannel/channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func (c Channel) Normalize() (*Channel, error) {
err := validate.Many(
validate.UUID("ID", c.ID),
validate.Text("Name", c.Name, 1, 255),
validate.OneOf("Type", c.Type, TypeSlack),
validate.OneOf("Type", c.Type, TypeSlack, TypeWebhook),
)

switch c.Type {
Expand Down
1 change: 1 addition & 0 deletions notificationchannel/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ type Type string
const (
TypeUnknown Type = ""
TypeSlack Type = "SLACK"
TypeWebhook Type = "WEBHOOK"
)

// Valid returns true if t is a known Type.
Expand Down
2 changes: 1 addition & 1 deletion web/src/expflag.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// Code generated by expflag/cmd/tsgen DO NOT EDIT.

type ExpFlag = 'example' | 'slack-dm'
type ExpFlag = 'chan-webhook' | 'example' | 'slack-dm'
1 change: 1 addition & 0 deletions web/src/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -935,6 +935,7 @@ export type TargetType =
| 'service'
| 'schedule'
| 'user'
| 'chanWebhook'
| 'integrationKey'
| 'userOverride'
| 'notificationRule'
Expand Down

0 comments on commit e447cac

Please sign in to comment.