Slack integration for alerts and reports - #4371
Conversation
c305231 to
3fa8b65
Compare
3fa8b65 to
c1d3303
Compare
e1b463c to
00c3859
Compare
|
@AdityaHegde could you please review the change of UI components? The only thing is done there - I renamed api parameters related to email recipients of alerts and reports |
| } | ||
|
|
||
| // Validate recipients | ||
| // Validate email recipients |
There was a problem hiding this comment.
This gives an impression that someone can use both older format as well as newer flow but we really should allow one format.
| return h, true | ||
| } | ||
|
|
||
| func (h *handle) SendScheduledReport(s *drivers.ScheduledReport, r drivers.RecipientOpts) error { |
There was a problem hiding this comment.
Would recommend moving notifier related functions to notifier.go file in this pkg or similar.
| } | ||
|
|
||
| if a.Spec.SlackChannels != nil || a.Spec.SlackEmails != nil { | ||
| conn, release, err := r.C.Runtime.AcquireHandle(ctx, r.C.InstanceID, "slack") |
There was a problem hiding this comment.
This should not be hardcoded but should come from alert spec to support more connectors in future.
| } | ||
| } | ||
|
|
||
| if a.Spec.SlackChannels != nil || a.Spec.SlackEmails != nil { |
There was a problem hiding this comment.
Can also have webhooks. Should also use len(a.Spec.SlackChannels) > 0 instead of nil checks.
| ExportFormat export_format = 8; | ||
| repeated string email_recipients = 9; | ||
| map<string, string> annotations = 10; | ||
| repeated string slack_channels = 10; |
There was a problem hiding this comment.
The spec should just have a NotifierConnector and NotifierProperties instead of separate slack related fields to support different notifiers in future. Refer to parse_api.go and usage of Resolver and ResolverProperties in in APISpec for similar constructs.
| } | ||
|
|
||
| if rep.Spec.SlackChannels != nil || rep.Spec.SlackEmails != nil { | ||
| conn, release, err := r.C.Runtime.AcquireHandle(ctx, r.C.InstanceID, "slack") |
There was a problem hiding this comment.
Same comment as in alert.go i.e. connector should be available from the spec.
| } | ||
|
|
||
| if len(opts.Recipients) == 0 { | ||
| if len(opts.EmailRecipients) == 0 && len(opts.SlackEmails) == 0 && len(opts.SlackChannels) == 0 && len(opts.SlackWebhooks) == 0 { |
There was a problem hiding this comment.
Ideally there should be a deduping of SlackWebhooks too similar to emails.
There was a problem hiding this comment.
I guess the ability to unsubscribe is a requirement for private notifications. As for group notifications (slack webhooks, channels), there is a way to update the corresponding subscription via EditReport
# Conflicts: # go.mod
AdityaHegde
left a comment
There was a problem hiding this comment.
Looks good from a UI perspective
| opts.EmailRecipients = pbutil.ToSliceString(props["recipients"].([]any)) | ||
| case "slack": | ||
| opts.SlackUsers = pbutil.ToSliceString(props[slack.UsersField].([]any)) | ||
| opts.SlackChannels = pbutil.ToSliceString(props[slack.ChannelsField].([]any)) | ||
| opts.SlackWebhooks = pbutil.ToSliceString(props[slack.WebhooksField].([]any)) |
There was a problem hiding this comment.
These type casts might be a little fragile. If one of them are nil, the .([]any) will panic. Probably best to have a small util function that returns nil if the cast fails (i.e. val, ok := props[key].([]any))
| case "email": | ||
| opts.EmailRecipients = pbutil.ToSliceString(props["recipients"].([]any)) | ||
| case "slack": | ||
| opts.SlackUsers = pbutil.ToSliceString(props[slack.UsersField].([]any)) | ||
| opts.SlackChannels = pbutil.ToSliceString(props[slack.ChannelsField].([]any)) | ||
| opts.SlackWebhooks = pbutil.ToSliceString(props[slack.WebhooksField].([]any)) |
| type AlertStatus struct { | ||
| ToEmail string | ||
| ToName string |
There was a problem hiding this comment.
Maybe we should remove these fields from AlertStatus since they won't be available for channels and webhooks? (The actual email to send to will be available in the notifier props for email delivery.)
There was a problem hiding this comment.
Will remove this once email notifier is created analogously to Slack notifier. Placed a todo reminder
| func newNotifier(token string, props map[string]any) *notifier { | ||
| users := pbutil.ToSliceString(props[UsersField].([]any)) | ||
| channels := pbutil.ToSliceString(props[ChannelsField].([]any)) | ||
| webhooks := pbutil.ToSliceString(props[WebhooksField].([]any)) |
There was a problem hiding this comment.
It would be cleaner to decode into a struct here using the mapstructure package (like we do for connector, source and resolver properties). It handles type conversion automatically and makes it easier to see which properties are supported by the notifier. For example:
type notifier {
token string
props *notifierProperties
templates *template.Template
}
type notifierProperties {
Users []string `mapstructure:"users"`
Channels []string `mapstructure:"channels"`
Webhooks []string `mapstructure:"webhooks"`
}
func newNotifier(token string, propsMap map[string]any) *notifier {
props := ¬ifierProperties{}
err := mapstructure.WeakDecode(propsMap, props)
if err != nil {
return nil, err
}
...There was a problem hiding this comment.
Moved Slack props encode/decode into Slack notifier so that it is possible to reuse them
| for _, notifier := range rep.Spec.Notifiers { | ||
| switch notifier.Connector { | ||
| case "email": | ||
| recipients := pbutil.ToSliceString(notifier.Properties.AsMap()["recipients"].([]any)) |
There was a problem hiding this comment.
Would also suggest a val, ok := ... type assertion here to keep it safe from panics
| switch notifier.Connector { | ||
| case "email": | ||
| recipients := pbutil.ToSliceString(notifier.Properties.AsMap()["recipients"].([]any)) | ||
| for _, recipient := range recipients { |
There was a problem hiding this comment.
Same comment about safe assertions
| // AsNotifier returns a Notifier (if the driver can serve as such) to send notifications: alerts, reports, etc. | ||
| // Examples: email notifier, slack notifier. | ||
| AsNotifier(properties *structpb.Struct) (Notifier, error) |
There was a problem hiding this comment.
Pass a map type here (call .AsMap() upstream) – best to deserialize protos early to use native types as much as possible
| func EncodeProps(users, channels, webhooks []string) (*structpb.Struct, error) { | ||
| return structpb.NewStruct(map[string]any{ | ||
| "users": pbutil.ToSliceAny(users), | ||
| "channels": pbutil.ToSliceAny(channels), | ||
| "webhooks": pbutil.ToSliceAny(webhooks), | ||
| }) | ||
| } | ||
|
|
||
| func DecodeProps(propsStruct *structpb.Struct) (*NotifierProperties, error) { |
There was a problem hiding this comment.
Same idea – best to use native types like map internally, and serialize/deserialze to protos only when needed (i.e. in places that read/write proto types).
|
Also note the merge conflicts |
# Conflicts: # runtime/drivers/athena/athena.go # runtime/drivers/redshift/redshift.go
| specHashTrigger := a.State.SpecHash != specHash | ||
| refsTrigger := a.State.RefsHash != refsHash && a.Spec.RefreshSchedule != nil && a.Spec.RefreshSchedule.RefUpdate | ||
| scheduleTrigger := a.State.NextRunOn != nil && !a.State.NextRunOn.AsTime().After(time.Now()) | ||
| scheduleTrigger := a.State.NextRunOn != nil && a.State.NextRunOn.AsTime().Before(time.Now()) |
There was a problem hiding this comment.
This is not equivalent – specifically, if NextRunOn == time.Now(), we should trigger
There was a problem hiding this comment.
Reverted and fixed linter changes
This reverts commit d425870.
alert spec has a
notifysectionreport spec has
notifysectionslack driver only needs a bot token to post messages to channels and "messages" tab
Required scopes for a Slack application