Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: on call notification channel separation #3046

Merged
merged 16 commits into from
Jun 5, 2023
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ ensure-yarn: # Yarn ensures the correct version of yarn is installed

yarn:
corepack enable
corepack prepare yarn@stable --activate
corepack prepare yarn@$(YARN_VERSION) --activate

check-js: generate $(NODE_DEPS)
$(MAKE) ensure-yarn
Expand Down
14 changes: 3 additions & 11 deletions notification/slack/usergrouperror.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ type userGroupError struct {
ScheduleName string
Missing []notification.User

OnCall string

callbackFunc func(string, ...url.Values) string
}

Expand Down Expand Up @@ -56,23 +54,17 @@ func slackLink(url, label string) string {
}

// userGroupErrorMissing is a template for when a user-group update fails because one or more users are missing from Slack.
var userGroupErrorMissing = template.Must(template.New("userGroupErrorMissing").Parse(`{{.OnCall}}

Hey everyone! I couldn't update {{.GroupRef}} because I couldn't find the following user(s) in Slack: {{.MissingUserRefs}}
var userGroupErrorMissing = template.Must(template.New("userGroupErrorMissing").Parse(`Hey everyone! I couldn't update {{.GroupRef}} because I couldn't find the following user(s) in Slack: {{.MissingUserRefs}}

If you could have them add a SLACK_DM contact method from their respective GoAlert profile page(s), that would be great! Hopefully I'll be able to update the user-group next time.`))

// userGroupErrorEmpty is a template for when a user-group update fails because there are no users on-call.
var userGroupErrorEmpty = template.Must(template.New("userGroupErrorEmpty").Parse(`{{.OnCall}}

Hey everyone! I couldn't update {{.GroupRef}} because there is nobody on-call for {{.ScheduleRef}}.
var userGroupErrorEmpty = template.Must(template.New("userGroupErrorEmpty").Parse(`Hey everyone! I couldn't update {{.GroupRef}} because there is nobody on-call for {{.ScheduleRef}}.

Since a Slack user-group cannot be empty, I'm going to leave it as-is for now.`))

// userGroupErrorUpdate is a template for when a user-group update fails for any reason other than missing users.
var userGroupErrorUpdate = template.Must(template.New("userGroupErrorUpdate").Parse(`{{.OnCall}}

Hey everyone! I couldn't update {{.GroupRef}} because I ran into a problem. Maybe touch base with the GoAlert admin(s) to see if they can help? I'm sorry for the inconvenience!
var userGroupErrorUpdate = template.Must(template.New("userGroupErrorUpdate").Parse(`Hey everyone! I couldn't update {{.GroupRef}} because I ran into a problem. Maybe touch base with the GoAlert admin(s) to see if they can help? I'm sorry for the inconvenience!

Here's the ID I left with the error in my logs so they can find it:
{{.ErrorRef}}`))
26 changes: 12 additions & 14 deletions notification/slack/usergroupsender.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,7 @@ func (s *UserGroupSender) Send(ctx context.Context, msg notification.Message) (*
ugID, chanID, _ := strings.Cut(t.Dest.Value, ":")
cfg := config.FromContext(ctx)

onCallMsg := renderOnCallNotificationMessage(t, userSlackIDs)
var stateDetails string
var errorMsg, stateDetails string

// If any users are missing, we need to abort and let the channel know.
switch {
Expand All @@ -90,12 +89,11 @@ func (s *UserGroupSender) Send(ctx context.Context, msg notification.Message) (*
GroupID: ugID,
Missing: missing,
callbackFunc: cfg.CallbackURL,
OnCall: onCallMsg,
})
if err != nil {
return nil, fmt.Errorf("execute template: %w", err)
}
onCallMsg = buf.String()
errorMsg = buf.String()
stateDetails = "missing users, sent error to channel"

// If no users are on-call, we need to abort and let the channel know.
Expand All @@ -108,12 +106,11 @@ func (s *UserGroupSender) Send(ctx context.Context, msg notification.Message) (*
ScheduleID: t.ScheduleID,
ScheduleName: t.ScheduleName,
callbackFunc: cfg.CallbackURL,
OnCall: onCallMsg,
})
if err != nil {
return nil, fmt.Errorf("execute template: %w", err)
}
onCallMsg = buf.String()
errorMsg = buf.String()
stateDetails = "empty user-group, sent error to channel"
default:
err = s.withClient(ctx, func(c *slack.Client) error {
Expand All @@ -134,25 +131,26 @@ func (s *UserGroupSender) Send(ctx context.Context, msg notification.Message) (*
err := userGroupErrorUpdate.Execute(&buf, userGroupError{
ErrorID: errID,
GroupID: ugID,
OnCall: onCallMsg,
})
if err != nil {
return nil, fmt.Errorf("execute template: %w", err)
}
onCallMsg = buf.String()
errorMsg = buf.String()
stateDetails = "failed to update user-group, sent error to channel and log"
}

// Only send to the channel if an error occurred
if stateDetails == "" {
return &notification.SentMessage{State: notification.StateDelivered}, nil
}

var ts string
err = s.withClient(ctx, func(c *slack.Client) error {
_, ts, err = c.PostMessageContext(ctx, chanID, slack.MsgOptionText(onCallMsg, false))
if err != nil {
return fmt.Errorf("post message to channel '%s': %w", chanID, err)
}
return nil
_, ts, err = c.PostMessageContext(ctx, chanID, slack.MsgOptionText(errorMsg, false))
return err
})
if err != nil {
return nil, err
return nil, fmt.Errorf("post message to channel '%s': %w", chanID, err)
}

return &notification.SentMessage{State: notification.StateDelivered, ExternalID: ts, StateDetails: stateDetails}, nil
Expand Down
Original file line number Diff line number Diff line change
@@ -1,35 +1,33 @@
import React, { useState } from 'react'
import { mapOnCallErrors, NO_DAY, Value } from './util'
import FormDialog from '../../dialogs/FormDialog'
import ScheduleOnCallNotificationsForm from './ScheduleOnCallNotificationsForm'
import { useOnCallRulesData, useSetOnCallRulesSubmit } from './hooks'
import { NO_DAY, Value, mapOnCallErrors } from './util'

interface ScheduleOnCallNotificationsCreateDialogProps {
onClose: () => void
scheduleID: string
}

const defaultValue: Value = {
time: null,
weekdayFilter: NO_DAY,
type: 'slackChannel',
targetID: null,
}

export default function ScheduleOnCallNotificationsCreateDialog(
props: ScheduleOnCallNotificationsCreateDialogProps,
): JSX.Element {
const { onClose, scheduleID } = props
const [value, setValue] = useState<Value | null>(null)
const [slackType, setSlackType] = useState('channel')
const [value, setValue] = useState<Value>(defaultValue)

const { q, zone, rules } = useOnCallRulesData(scheduleID)

const newValue: Value = value || {
time: null,
weekdayFilter: NO_DAY,
slackChannelID: null,
slackUserGroup: null,
}
if (!newValue.slackChannelID) delete newValue.slackChannelID
if (!newValue.slackUserGroup) delete newValue.slackUserGroup
const { m, submit } = useSetOnCallRulesSubmit(
scheduleID,
zone,
newValue,
value,
...rules,
)

Expand All @@ -47,10 +45,8 @@ export default function ScheduleOnCallNotificationsCreateDialog(
<ScheduleOnCallNotificationsForm
scheduleID={scheduleID}
errors={fieldErrors}
value={newValue}
onChange={(value) => setValue(value)}
slackType={slackType}
setSlackType={setSlackType}
value={value}
onChange={setValue}
/>
}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React, { useState } from 'react'

import { EVERY_DAY, mapOnCallErrors, NO_DAY, Value } from './util'
import { useOnCallRulesData, useSetOnCallRulesSubmit } from './hooks'
import { DateTime } from 'luxon'
import FormDialog from '../../dialogs/FormDialog'
import { useOnCallRulesData, useSetOnCallRulesSubmit } from './hooks'
import ScheduleOnCallNotificationsForm from './ScheduleOnCallNotificationsForm'
import { DateTime } from 'luxon'
import { EVERY_DAY, mapOnCallErrors, NO_DAY, Value } from './util'

interface ScheduleOnCallNotificationsEditDialogProps {
onClose: () => void
Expand All @@ -17,7 +17,6 @@ export default function ScheduleOnCallNotificationsEditDialog(
p: ScheduleOnCallNotificationsEditDialogProps,
): JSX.Element {
const [value, setValue] = useState<Value | null>(null)
const [slackType, setSlackType] = useState('channel')

const { q, zone, rules } = useOnCallRulesData(p.scheduleID)

Expand All @@ -27,14 +26,8 @@ export default function ScheduleOnCallNotificationsEditDialog(
? DateTime.fromFormat(rule.time, 'HH:mm', { zone }).toISO()
: null,
weekdayFilter: rule?.time ? rule.weekdayFilter || EVERY_DAY : NO_DAY,
slackChannelID:
rule?.target.type === 'slackChannel'
? rule?.target.id
: rule?.target.id.split(':')[1],
slackUserGroup:
rule?.target.type === 'slackUserGroup'
? rule?.target.id.split(':')[0]
: null,
type: rule?.target?.type ?? 'slackChannel',
targetID: rule?.target?.id ?? null,
}
const { m, submit } = useSetOnCallRulesSubmit(
p.scheduleID,
Expand All @@ -57,9 +50,7 @@ export default function ScheduleOnCallNotificationsEditDialog(
scheduleID={p.scheduleID}
errors={fieldErrors}
value={newValue}
onChange={(value) => setValue(value)}
slackType={slackType}
setSlackType={setSlackType}
onChange={setValue}
/>
}
/>
Expand Down
Loading