Skip to content

Commit

Permalink
Transition OnCallNotificatonRule to use 'Dest' field (#4004)
Browse files Browse the repository at this point in the history
* Transition OnCallNotificatonRule to use 'Dest' field

- Changed OnCallNotificationRuleInput to use 'Dest' instead of 'Target'
- Refactored resolver and mutation logic for new field
- Deprecated 'Target' in the schema and added relevant annotations
- Removed unused imports and redundant validation code

* Add a comment for MapDestToID validation context

- Added a comment to clarify the validation purpose of MapDestToID in the notification rules function.

* Remove unused function and streamline notification lookups

- Removed `CompatNCToDest` function and related imports
- Added SQL query for fetching notification channel destination
- Updated `schedule.go` to use `FindDestByID` for destination resolution
- Introduced `FindDestByID` method in store for notification channels

* remove unused query

* remove more unused code

* update `formatNC` to use dest registry

* Refactored CompatTargetToDest for context support

- Added context parameter to CompatTargetToDest
- Updated calls to CompatTargetToDest with context
- Handled TargetTypeNotificationChannel in CompatTargetToDest
- Included validation for TargetID parsing and checking
  • Loading branch information
mastercactapus authored Jul 18, 2024
1 parent 3f29e67 commit f004df3
Show file tree
Hide file tree
Showing 13 changed files with 90 additions and 351 deletions.
71 changes: 0 additions & 71 deletions gadb/queries.sql.go

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

24 changes: 12 additions & 12 deletions graphql2/generated.go

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

53 changes: 13 additions & 40 deletions graphql2/graphqlapp/compat.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,20 @@ package graphqlapp
import (
"context"
"fmt"
"strings"

"github.com/google/uuid"
"github.com/target/goalert/assignment"
"github.com/target/goalert/gadb"
"github.com/target/goalert/notification/slack"
"github.com/target/goalert/notification/webhook"
"github.com/target/goalert/notificationchannel"
"github.com/target/goalert/schedule"
"github.com/target/goalert/schedule/rotation"
"github.com/target/goalert/user"
"github.com/target/goalert/user/contactmethod"
"github.com/target/goalert/validation/validate"
)

// CompatTargetToDest converts an assignment.Target to a gadb.DestV1.
func CompatTargetToDest(tgt assignment.Target) (gadb.DestV1, error) {
func (a *App) CompatTargetToDest(ctx context.Context, tgt assignment.Target) (gadb.DestV1, error) {
switch tgt.TargetType() {
case assignment.TargetTypeUser:
return gadb.DestV1{
Expand All @@ -45,45 +43,20 @@ func CompatTargetToDest(tgt assignment.Target) (gadb.DestV1, error) {
Type: slack.DestTypeSlackChannel,
Args: map[string]string{slack.FieldSlackChannelID: tgt.TargetID()},
}, nil
}

return gadb.DestV1{}, fmt.Errorf("unknown target type: %s", tgt.TargetType())
}

// CompatNCToDest converts a notification channel to a destination.
func (a *App) CompatNCToDest(ctx context.Context, ncID uuid.UUID) (*gadb.DestV1, error) {
nc, err := a.FindOneNC(ctx, ncID)
if err != nil {
return nil, err
}

switch nc.Type {
case notificationchannel.TypeSlackChan:
return &gadb.DestV1{
Type: slack.DestTypeSlackChannel,
Args: map[string]string{slack.FieldSlackChannelID: nc.Value},
}, nil
case notificationchannel.TypeSlackUG:
ugID, chanID, ok := strings.Cut(nc.Value, ":")
if !ok {
return nil, fmt.Errorf("invalid slack usergroup pair: %s", nc.Value)
case assignment.TargetTypeNotificationChannel:
id, err := validate.ParseUUID("TargetID", tgt.TargetID())
if err != nil {
return gadb.DestV1{}, err
}
dest, err := a.NCStore.FindDestByID(ctx, nil, id)
if err != nil {
return gadb.DestV1{}, err
}

return &gadb.DestV1{
Type: slack.DestTypeSlackUsergroup,
Args: map[string]string{
slack.FieldSlackUsergroupID: ugID,
slack.FieldSlackChannelID: chanID,
},
}, nil
case notificationchannel.TypeWebhook:
return &gadb.DestV1{
Type: webhook.DestTypeWebhook,
Args: map[string]string{webhook.FieldWebhookURL: nc.Value},
}, nil
default:
return nil, fmt.Errorf("unsupported notification channel type: %s", nc.Type)
return dest, nil
}

return gadb.DestV1{}, fmt.Errorf("unknown target type: %s", tgt.TargetType())
}

// CompatDestToCMTypeVal converts a gadb.DestV1 to a contactmethod.Type and string value
Expand Down
4 changes: 2 additions & 2 deletions graphql2/graphqlapp/escalationpolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func (a *CreateEscalationPolicyStepInput) Targets(ctx context.Context, input *gr
input.Actions = make([]gadb.DestV1, len(targets))
for i, tgt := range targets {
var err error
input.Actions[i], err = CompatTargetToDest(tgt)
input.Actions[i], err = (*App)(a).CompatTargetToDest(ctx, tgt)
if err != nil {
return validation.NewFieldError(fmt.Sprintf("Targets[%d]", i), err.Error())
}
Expand All @@ -60,7 +60,7 @@ func (a *UpdateEscalationPolicyStepInput) Targets(ctx context.Context, input *gr
input.Actions = make([]gadb.DestV1, len(targets))
for i, tgt := range targets {
var err error
input.Actions[i], err = CompatTargetToDest(tgt)
input.Actions[i], err = (*App)(a).CompatTargetToDest(ctx, tgt)
if err != nil {
return validation.NewFieldError(fmt.Sprintf("Targets[%d]", i), err.Error())
}
Expand Down
20 changes: 11 additions & 9 deletions graphql2/graphqlapp/messagelog.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"github.com/google/uuid"
"github.com/target/goalert/graphql2"
"github.com/target/goalert/notification"
"github.com/target/goalert/notificationchannel"
"github.com/target/goalert/search"
"github.com/target/goalert/validation/validate"
)
Expand All @@ -21,19 +20,22 @@ func (a *App) formatNC(ctx context.Context, id uuid.UUID) (string, error) {
return "", nil
}

n, err := a.FindOneNC(ctx, id)
dest, err := a.NCStore.FindDestByID(ctx, nil, id)
if err != nil {
return "", err
}
var typeName string
switch n.Type {
case notificationchannel.TypeSlackChan:
typeName = "Slack"
default:
typeName = string(n.Type)

typeInfo, err := a.DestReg.TypeInfo(ctx, dest.Type)
if err != nil {
return "", err
}

dispInfo, err := a.DestReg.DisplayInfo(ctx, dest)
if err != nil {
return "", err
}

return fmt.Sprintf("%s (%s)", n.Name, typeName), nil
return fmt.Sprintf("%s (%s)", dispInfo.Text, typeInfo.Name), nil
}

func (q *Query) formatDest(ctx context.Context, dst notification.Dest) (string, error) {
Expand Down
68 changes: 8 additions & 60 deletions graphql2/graphqlapp/mutation.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,16 @@ package graphqlapp
import (
context "context"
"database/sql"
"fmt"
"net/url"
"strings"
"time"

"github.com/target/goalert/assignment"
"github.com/target/goalert/config"
"github.com/target/goalert/graphql2"
"github.com/target/goalert/notification/webhook"
"github.com/target/goalert/notificationchannel"
"github.com/target/goalert/permission"
"github.com/target/goalert/retry"
"github.com/target/goalert/schedule"
"github.com/target/goalert/user"
"github.com/target/goalert/util/sqlutil"
"github.com/target/goalert/validation"
"github.com/target/goalert/validation/validate"

"github.com/pkg/errors"
)
Expand Down Expand Up @@ -55,67 +48,22 @@ func (a *Mutation) SetScheduleOnCallNotificationRules(ctx context.Context, input

err = withContextTx(ctx, a.DB, func(ctx context.Context, tx *sql.Tx) error {
rules := make([]schedule.OnCallNotificationRule, 0, len(input.Rules))
for i, r := range input.Rules {
err := validate.OneOf(fmt.Sprintf("Rules[%d].Target.Type", i), r.Target.Type, assignment.TargetTypeSlackChannel, assignment.TargetTypeSlackUserGroup, assignment.TargetTypeChanWebhook)
for _, r := range input.Rules {
info, err := a.DestReg.TypeInfo(ctx, r.Dest.Type)
if err != nil {
return err
}

var nfyChan *notificationchannel.Channel
switch r.Target.Type {
case assignment.TargetTypeSlackUserGroup:
grpID, chanID, _ := strings.Cut(r.Target.ID, ":")
grp, err := a.SlackStore.UserGroup(ctx, grpID)
if err != nil {
return validation.WrapError(err)
}
ch, err := a.SlackStore.Channel(ctx, chanID)
if err != nil {
return validation.WrapError(err)
}

nfyChan = &notificationchannel.Channel{
Type: notificationchannel.TypeSlackUG,
Name: fmt.Sprintf("%s (%s)", grp.Handle, ch.Name),
Value: r.Target.ID,
}
case assignment.TargetTypeSlackChannel:
ch, err := a.SlackStore.Channel(ctx, r.Target.ID)
if err != nil {
return err
}

nfyChan = &notificationchannel.Channel{
Type: notificationchannel.TypeSlackChan,
Name: ch.Name,
Value: ch.ID,
}
case assignment.TargetTypeChanWebhook:
url, err := url.Parse(r.Target.ID)
if err != nil {
return validation.NewFieldError("Rules[%d].Target.ID", "Invalid URL format")
}
url.RawQuery = ""
if len(url.Path) > 15 {
url.Path = url.Path[:12] + "..."
}

cfg := config.FromContext(ctx)
if !cfg.ValidWebhookURL(r.Target.ID) {
return validation.NewFieldError("Rules[%d].Target.ID", "URL not allowed by administrator")
}

nfyChan = &notificationchannel.Channel{
Type: notificationchannel.TypeWebhook,
Name: webhook.MaskURLPass(url),
Value: r.Target.ID,
}
if !info.IsSchedOnCallNotify() {
return validation.NewFieldError("Rules[%d].Dest.Type", "unsupported destination type")
}

r.ChannelID, err = a.NCStore.MapToID(ctx, tx, nfyChan)
// MapDestToID handles the appropriate validation checks, outside of the IsSchedOnCallNotify check done above.
chID, err := a.NCStore.MapDestToID(ctx, tx, r.Dest)
if err != nil {
return err
}

r.OnCallNotificationRule.ChannelID = chID
rules = append(rules, r.OnCallNotificationRule)
}

Expand Down
Loading

0 comments on commit f004df3

Please sign in to comment.