Skip to content

Commit

Permalink
Install MessageFilter in app and notifier
Browse files Browse the repository at this point in the history
  • Loading branch information
Kuixz committed Aug 16, 2024
1 parent 1b5aea8 commit 4bbc282
Show file tree
Hide file tree
Showing 3 changed files with 266 additions and 14 deletions.
33 changes: 21 additions & 12 deletions pkg/slack/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,26 +27,26 @@ type App struct {
}

type ChannelInfo struct {
channelID string
conclusions []string
channelID string
filter *MessageFilter
}

type exposedChannelInfo struct {
ChannelID string `json:"channelID"`
Conclusions []string `json:"messageFilters"`
ChannelID string `json:"channelID"`
Filter *MessageFilter `json:"messageFilters"`
}

func (f ChannelInfo) String() string {
if len(f.conclusions) == 0 {
if f.filter.Length() == 0 {
return f.channelID
}
return fmt.Sprintf("%s with %s", f.channelID, f.conclusions)
return fmt.Sprintf("%s with %s", f.channelID, f.filter)
}

func (f ChannelInfo) MarshalJSON() ([]byte, error) {
return json.Marshal(exposedChannelInfo{
ChannelID: f.channelID,
Conclusions: f.conclusions,
ChannelID: f.channelID,
Filter: f.filter,
})
}

Expand All @@ -56,7 +56,7 @@ func (f *ChannelInfo) UnmarshalJSON(data []byte) error {
return err
}
f.channelID = aux.ChannelID
f.conclusions = aux.Conclusions
f.filter = aux.Filter
return nil
}

Expand Down Expand Up @@ -233,11 +233,20 @@ func (a *App) messageLoop(ctx context.Context, client *socketmode.Client) {

switch subcommand {
case "subscribe":
filterLayers := array.Unique(args[2:])
filter, err := NewFilter(filterLayers)
if err != nil {
a.logger.Warn("failed to subscribe", zap.Error(err))
client.Ack(*e.Request, map[string]interface{}{
"text": fmt.Sprintf("Failed to subscribe '%s': %s\n", repo, err),
})
}

channelInfo := ChannelInfo{
channelID: data.ChannelID,
conclusions: conclusions,
channelID: data.ChannelID,
filter: filter,
}
err := a.AddChannel(ctx, repo, channelInfo)
err = a.AddChannel(ctx, repo, channelInfo)
if err != nil {
a.logger.Warn("failed to subscribe", zap.Error(err))
client.Ack(*e.Request, map[string]interface{}{
Expand Down
244 changes: 244 additions & 0 deletions pkg/slack/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
package slack

import (
"context"
"fmt"
"strings"

"github.com/oursky/github-actions-manager/pkg/utils/array"
"github.com/slack-go/slack"
"go.uber.org/zap"
)

type Argument struct {
name string
required bool
acceptsMany bool
description string
}

func NewArgument(name string, required bool, acceptsMany bool, description string) Argument {
return Argument{name: name, required: required, acceptsMany: acceptsMany, description: description}
}

type Command struct {
trigger string
arguments []Argument
description string
execute func(env *CLI, args []string) CLIResult
}

type CLIResult struct {
printToChannel bool
message string
}

func NewCLIResult(printToChannel bool, message string) CLIResult {
return CLIResult{printToChannel: printToChannel, message: message}
}

type CLI struct {
commands *[]Command
app *App
ctx context.Context
channelID string
logger *zap.Logger
}

func (c Command) String() string {
output := fmt.Sprintf("`%s`: %s", c.trigger, c.description)
output += fmt.Sprintf("\nUsage of `%s`: `%s", c.trigger, c.trigger)
for _, arg := range c.arguments {
argname := arg.name
if arg.acceptsMany {
argname += "..."
}
if arg.required {
output += " " + argname
} else {
output += " [" + argname + "]"
}
}
output += "`"
for _, arg := range c.arguments {
output += fmt.Sprintf("\n\t`%s`: %s", arg.name, arg.description)
}

return output
}

func (cli *CLI) SetContext(ctx context.Context, a *App) {
cli.app = a
cli.ctx = ctx
}

func (cli *CLI) Execute(subcommand string, args []string) CLIResult {
commands := *cli.commands
for _, command := range commands {
if subcommand != command.trigger {
continue
}
return command.execute(
cli,
args,
)
}
return NewCLIResult(false, fmt.Sprintf("Unknown command: %s", subcommand))
}

func (cli *CLI) Parse(data slack.SlashCommand) map[string]interface{} {
cli.channelID = data.ChannelID

if data.Command != "/"+cli.app.commandName {
return map[string]interface{}{"text": fmt.Sprintf("Unknown command '%s'\n", data.Command)}
}

args := strings.Split(data.Text, " ")
if len(args) < 1 {
return map[string]interface{}{"text": fmt.Sprintf("Please specify subcommand")}
}

result := cli.Execute(args[0], args[1:])
response := map[string]interface{}{"text": result.message}
if result.printToChannel {
response["response_type"] = "in_channel"
}
return response
}

func NewCLI(logger *zap.Logger) *CLI {
cli := CLI{
commands: &[]Command{},
logger: logger,
}
cli.commands = &[]Command{
{
trigger: "help",
arguments: []Argument{
NewArgument("subcommand", false, false, "The subcommand to get help about."),
},
description: "Get help about a command.",
execute: func(env *CLI, args []string) CLIResult {
output := ""
commands := (*cli.commands)
if len(args) == 0 {
output = "The known commands are:"
for _, command := range commands {
output += fmt.Sprintf(" `%s`", command.trigger)
}
return NewCLIResult(false, output)
}
subcommand := args[0]
for _, command := range commands {
if command.trigger != subcommand {
continue
}
return NewCLIResult(false, command.String())
}
return NewCLIResult(false, fmt.Sprintf("No such command: %s", subcommand))
},
},
{
trigger: "list",
arguments: []Argument{
NewArgument("repo", true, false, "The repo to get the subscription data for."),
},
description: "List the channels subscribed to a given repo.",
execute: func(env *CLI, args []string) CLIResult {
if len(args) < 1 {
return NewCLIResult(false, "Please specify repo")
}

repo := args[0]
if !repoRegex.MatchString(repo) {
return NewCLIResult(false, fmt.Sprintf("Invalid repo *%s*\n", repo))
}

channels, err := env.app.GetChannels(env.ctx, repo)
if err != nil {
env.logger.Warn("failed to list channels", zap.Error(err))
return NewCLIResult(false, fmt.Sprintf("Failed to get list of subscribed channels: '%s'", err))
} else {
if len(channels) == 0 {
return NewCLIResult(true, fmt.Sprintf("*%s* is sending updates to no channels", repo))
}
channelStrings := array.Cast(channels)
return NewCLIResult(true, fmt.Sprintf("*%s* is sending updates to: %s\n", repo, strings.Join(channelStrings, "; ")))
}
},
},
{
trigger: "subscribe",
arguments: []Argument{
NewArgument("repo", true, false, "The repo to subscribe to."),
NewArgument("filters", false, true, "In the format of filter_key:value1,value2,...:conclusion1,conclusion2,..., one of the supported filter keys (workflows, branches)"),
},
description: "Subscribe this channel to a given repo.",
execute: func(env *CLI, args []string) CLIResult {
if len(args) < 1 {
return NewCLIResult(false, fmt.Sprintf("Please specify repo"))
}

repo := args[0]
if !repoRegex.MatchString(repo) {
return NewCLIResult(false, fmt.Sprintf("Invalid repo *%s*\n", repo))
}

filterLayers := array.Unique(args[1:])
filter, err := NewFilter(filterLayers)
if err != nil {
env.logger.Warn("failed to subscribe", zap.Error(err))
return NewCLIResult(false, fmt.Sprintf("Failed to subscribe to *%s*: '%s'\n", repo, err))
}

channelInfo := ChannelInfo{
channelID: env.channelID,
filter: filter,
}
err = env.app.AddChannel(env.ctx, repo, channelInfo)
if err != nil {
env.logger.Warn("failed to subscribe", zap.Error(err))
return NewCLIResult(false, fmt.Sprintf("Failed to subscribe to *%s*: '%s'\n", repo, err))
}
if len(filterLayers) > 0 {
return NewCLIResult(true, fmt.Sprintf("Subscribed to *%s* with filter layers %s", repo, filter.whitelists))
} else {
return NewCLIResult(true, fmt.Sprintf("Subscribed to *%s*\n", repo))
}
},
},
{
trigger: "unsubscribe",
arguments: []Argument{
NewArgument("repo", true, false, "The repo to unsubscribe from."),
},
description: "Unsubscribe this channel from a given repo.",
execute: func(env *CLI, args []string) CLIResult {
if len(args) < 1 {
return NewCLIResult(false, fmt.Sprintf("Please specify repo"))
}

repo := args[0]
if !repoRegex.MatchString(repo) {
return NewCLIResult(false, fmt.Sprintf("Invalid repo *%s*\n", repo))
}

err := env.app.DelChannel(env.ctx, repo, env.channelID)
if err != nil {
env.logger.Warn("failed to unsubscribe", zap.Error(err))
return NewCLIResult(false, fmt.Sprintf("Failed to unsubscribe from *%s*: '%s'\n", repo, err))
} else {
return NewCLIResult(true, fmt.Sprintf("Unsubscribed from *%s*\n", repo))
}
},
},
{
trigger: "meow",
description: "Meow.",
execute: func(env *CLI, args []string) CLIResult {
return NewCLIResult(false, "meow")
},
},
}
return &cli
}
3 changes: 1 addition & 2 deletions pkg/slack/notifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"github.com/slack-go/slack/slackutilsx"
"go.uber.org/zap"
"golang.org/x/sync/errgroup"
"k8s.io/utils/strings/slices"
)

type JobsState interface {
Expand Down Expand Up @@ -159,7 +158,7 @@ func (n *Notifier) notify(ctx context.Context, run *jobs.WorkflowRun) {
}

for _, channel := range channels {
if len(channel.conclusions) > 0 && !slices.Contains(channel.conclusions, run.Conclusion) {
if !channel.filter.Any(run) {
return
}
err := n.app.SendMessage(ctx, channel.channelID, slack.MsgOptionAttachments(slackMsg))
Expand Down

0 comments on commit 4bbc282

Please sign in to comment.