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

Filter not supported verbs by interactive kubectl, improve telemetry for interactive Slack messages #810

Merged
merged 1 commit into from
Oct 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion internal/analytics/noop_reporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"k8s.io/client-go/kubernetes"

"github.com/kubeshop/botkube/pkg/config"
"github.com/kubeshop/botkube/pkg/execute/command"
)

var _ Reporter = &NoopReporter{}
Expand All @@ -24,7 +25,7 @@ func (n NoopReporter) RegisterCurrentIdentity(_ context.Context, _ kubernetes.In
}

// ReportCommand reports a new executed command. The command should be anonymized before using this method.
func (n NoopReporter) ReportCommand(_ config.CommPlatformIntegration, _ string, _ bool) error {
func (n NoopReporter) ReportCommand(_ config.CommPlatformIntegration, _ string, _ command.Origin) error {
return nil
}

Expand Down
3 changes: 2 additions & 1 deletion internal/analytics/reporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"k8s.io/client-go/kubernetes"

"github.com/kubeshop/botkube/pkg/config"
"github.com/kubeshop/botkube/pkg/execute/command"
)

// Reporter defines an analytics reporter implementation.
Expand All @@ -14,7 +15,7 @@ type Reporter interface {
RegisterCurrentIdentity(ctx context.Context, k8sCli kubernetes.Interface) error

// ReportCommand reports a new executed command. The command should be anonymized before using this method.
ReportCommand(platform config.CommPlatformIntegration, command string, isButtonClickOrigin bool) error
ReportCommand(platform config.CommPlatformIntegration, command string, origin command.Origin) error

// ReportBotEnabled reports an enabled bot.
ReportBotEnabled(platform config.CommPlatformIntegration) error
Expand Down
14 changes: 2 additions & 12 deletions internal/analytics/segment_reporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"k8s.io/client-go/kubernetes"

"github.com/kubeshop/botkube/pkg/config"
"github.com/kubeshop/botkube/pkg/execute/command"
"github.com/kubeshop/botkube/pkg/version"
)

Expand All @@ -19,13 +20,6 @@ const (
unknownIdentityID = "00000000-0000-0000-0000-000000000000"
)

type commandOrigin string

const (
buttonClickCommandOrigin commandOrigin = "buttonClick"
typedCommandOrigin commandOrigin = "typed"
)

var (
// APIKey contains the API key for external analytics service. It is set during application build.
APIKey string
Expand Down Expand Up @@ -66,11 +60,7 @@ func (r *SegmentReporter) RegisterCurrentIdentity(ctx context.Context, k8sCli ku

// ReportCommand reports a new executed command. The command should be anonymized before using this method.
// The RegisterCurrentIdentity needs to be called first.
func (r *SegmentReporter) ReportCommand(platform config.CommPlatformIntegration, command string, isButtonClickOrigin bool) error {
origin := typedCommandOrigin
if isButtonClickOrigin {
origin = buttonClickCommandOrigin
}
func (r *SegmentReporter) ReportCommand(platform config.CommPlatformIntegration, command string, origin command.Origin) error {
return r.reportEvent("Command executed", map[string]interface{}{
"platform": platform,
"command": command,
Expand Down
7 changes: 4 additions & 3 deletions internal/analytics/segment_reporter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (

"github.com/kubeshop/botkube/internal/analytics"
"github.com/kubeshop/botkube/pkg/config"
"github.com/kubeshop/botkube/pkg/execute/command"
"github.com/kubeshop/botkube/pkg/version"
)

Expand Down Expand Up @@ -65,13 +66,13 @@ func TestSegmentReporter_ReportCommand(t *testing.T) {
segmentReporter, segmentCli := fakeSegmentReporterWithIdentity(identity)

// when
err := segmentReporter.ReportCommand(config.DiscordCommPlatformIntegration, "notifier stop", false)
err := segmentReporter.ReportCommand(config.DiscordCommPlatformIntegration, "notifier stop", command.TypedOrigin)
require.NoError(t, err)

err = segmentReporter.ReportCommand(config.SlackCommPlatformIntegration, "get", false)
err = segmentReporter.ReportCommand(config.SlackCommPlatformIntegration, "get", command.ButtonClickOrigin)
require.NoError(t, err)

err = segmentReporter.ReportCommand(config.TeamsCommPlatformIntegration, "notifier start", true)
err = segmentReporter.ReportCommand(config.TeamsCommPlatformIntegration, "notifier start", command.SelectValueChangeOrigin)
require.NoError(t, err)

// then
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"timestamp": "2009-11-17T20:34:58.651387237Z",
"properties": {
"command": "get",
"origin": "typed",
"origin": "buttonClick",
"platform": "slack"
}
},
Expand All @@ -31,7 +31,7 @@
"timestamp": "2009-11-17T20:34:58.651387237Z",
"properties": {
"command": "notifier start",
"origin": "buttonClick",
"origin": "selectValueChange",
"platform": "teams"
}
}
Expand Down
2 changes: 2 additions & 0 deletions pkg/bot/discord.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/kubeshop/botkube/pkg/config"
"github.com/kubeshop/botkube/pkg/events"
"github.com/kubeshop/botkube/pkg/execute"
"github.com/kubeshop/botkube/pkg/execute/command"
"github.com/kubeshop/botkube/pkg/multierror"
"github.com/kubeshop/botkube/pkg/sliceutil"
)
Expand Down Expand Up @@ -245,6 +246,7 @@ func (b *Discord) handleMessage(dm discordMessage) error {
ID: channel.Identifier(),
ExecutorBindings: channel.Bindings.Executors,
IsAuthenticated: isAuthChannel,
CommandOrigin: command.TypedOrigin,
},
Message: req,
User: fmt.Sprintf("<@%s>", dm.Event.Author.ID),
Expand Down
2 changes: 2 additions & 0 deletions pkg/bot/mattermost.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/kubeshop/botkube/pkg/config"
"github.com/kubeshop/botkube/pkg/events"
"github.com/kubeshop/botkube/pkg/execute"
"github.com/kubeshop/botkube/pkg/execute/command"
"github.com/kubeshop/botkube/pkg/multierror"
"github.com/kubeshop/botkube/pkg/sliceutil"
)
Expand Down Expand Up @@ -234,6 +235,7 @@ func (mm *mattermostMessage) handleMessage(b *Mattermost) {
ID: channel.Identifier(),
ExecutorBindings: channel.Bindings.Executors,
IsAuthenticated: mm.IsAuthChannel,
CommandOrigin: command.TypedOrigin,
},
Message: mm.Request,
})
Expand Down
2 changes: 2 additions & 0 deletions pkg/bot/slack.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/kubeshop/botkube/pkg/config"
"github.com/kubeshop/botkube/pkg/events"
"github.com/kubeshop/botkube/pkg/execute"
"github.com/kubeshop/botkube/pkg/execute/command"
"github.com/kubeshop/botkube/pkg/multierror"
"github.com/kubeshop/botkube/pkg/sliceutil"
)
Expand Down Expand Up @@ -236,6 +237,7 @@ func (b *Slack) handleMessage(msg slackMessage) error {
ID: channel.Identifier(),
ExecutorBindings: channel.Bindings.Executors,
IsAuthenticated: isAuthChannel,
CommandOrigin: command.TypedOrigin,
},
Message: request,
User: fmt.Sprintf("<@%s>", msg.User),
Expand Down
84 changes: 48 additions & 36 deletions pkg/bot/socketslack.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/kubeshop/botkube/pkg/config"
"github.com/kubeshop/botkube/pkg/events"
"github.com/kubeshop/botkube/pkg/execute"
"github.com/kubeshop/botkube/pkg/execute/command"
"github.com/kubeshop/botkube/pkg/execute/kubectl"
"github.com/kubeshop/botkube/pkg/multierror"
"github.com/kubeshop/botkube/pkg/sliceutil"
Expand Down Expand Up @@ -53,21 +54,21 @@ type SocketSlack struct {
}

type socketSlackMessage struct {
Text string
Channel string
ThreadTimeStamp string
User string
TriggerID string
IsButtonClickOrigin bool
State *slack.BlockActionStates
ResponseURL string
BlockID string
Text string
Channel string
ThreadTimeStamp string
User string
TriggerID string
CommandOrigin command.Origin
State *slack.BlockActionStates
ResponseURL string
BlockID string
}

// socketSlackAnalyticsReporter defines a reporter that collects analytics data.
type socketSlackAnalyticsReporter interface {
FatalErrorAnalyticsReporter
ReportCommand(platform config.CommPlatformIntegration, command string, isButtonClickOrigin bool) error
ReportCommand(platform config.CommPlatformIntegration, command string, origin command.Origin) error
}

// NewSocketSlack creates a new SocketSlack instance.
Expand Down Expand Up @@ -155,6 +156,7 @@ func (b *SocketSlack) Start(ctx context.Context) error {
Channel: ev.Channel,
ThreadTimeStamp: ev.ThreadTimeStamp,
User: ev.User,
CommandOrigin: command.TypedOrigin,
}
if err := b.handleMessage(msg); err != nil {
b.log.Errorf("Message handling error: %s", err.Error())
Expand All @@ -181,7 +183,7 @@ func (b *SocketSlack) Start(ctx context.Context) error {

act := callback.ActionCallback.BlockActions[0]
if act == nil || strings.HasPrefix(act.ActionID, urlButtonActionIDPrefix) {
reportErr := b.reporter.ReportCommand(b.IntegrationName(), act.ActionID, true)
reportErr := b.reporter.ReportCommand(b.IntegrationName(), act.ActionID, command.ButtonClickOrigin)
if reportErr != nil {
b.log.Errorf("while reporting URL command, error: %s", reportErr.Error())
}
Expand All @@ -197,21 +199,23 @@ func (b *SocketSlack) Start(ctx context.Context) error {
b.log.Debug("Ignoring callback as its source is an active modal")
continue
}

cmd, cmdOrigin := resolveBlockActionCommand(*act)
// Use thread's TS if interactive call triggered within thread.
threadTs := callback.MessageTs
if callback.Message.Msg.ThreadTimestamp != "" {
threadTs = callback.Message.Msg.ThreadTimestamp
}
msg := socketSlackMessage{
Text: b.resolveBlockActionCommand(*act),
Channel: channelID,
ThreadTimeStamp: threadTs,
TriggerID: callback.TriggerID,
User: callback.User.ID,
IsButtonClickOrigin: true,
State: callback.BlockActionState,
ResponseURL: callback.ResponseURL,
BlockID: act.BlockID,
Text: cmd,
Channel: channelID,
ThreadTimeStamp: threadTs,
TriggerID: callback.TriggerID,
User: callback.User.ID,
CommandOrigin: cmdOrigin,
State: callback.BlockActionState,
ResponseURL: callback.ResponseURL,
BlockID: act.BlockID,
}
if err := b.handleMessage(msg); err != nil {
b.log.Errorf("Message handling error: %s", err.Error())
Expand All @@ -223,11 +227,12 @@ func (b *SocketSlack) Start(ctx context.Context) error {
for actID, act := range item {
act.ActionID = actID // normalize event

cmd, cmdOrigin := resolveBlockActionCommand(act)
msg := socketSlackMessage{
Text: b.resolveBlockActionCommand(act),
Channel: callback.View.PrivateMetadata,
User: callback.User.ID,
IsButtonClickOrigin: true,
Text: cmd,
Channel: callback.View.PrivateMetadata,
User: callback.User.ID,
CommandOrigin: cmdOrigin,
}

if err := b.handleMessage(msg); err != nil {
Expand Down Expand Up @@ -313,12 +318,12 @@ func (b *SocketSlack) handleMessage(event socketSlackMessage) error {
Platform: b.IntegrationName(),
NotifierHandler: b,
Conversation: execute.Conversation{
Alias: channel.alias,
ID: channel.Identifier(),
ExecutorBindings: channel.Bindings.Executors,
IsAuthenticated: isAuthChannel,
IsButtonClickOrigin: event.IsButtonClickOrigin,
State: event.State,
Alias: channel.alias,
ID: channel.Identifier(),
ExecutorBindings: channel.Bindings.Executors,
IsAuthenticated: isAuthChannel,
CommandOrigin: event.CommandOrigin,
State: event.State,
},
Message: request,
User: fmt.Sprintf("<@%s>", event.User),
Expand Down Expand Up @@ -516,26 +521,33 @@ func (b *SocketSlack) findAndTrimBotMention(msg string) (string, bool) {
return b.botMentionRegex.ReplaceAllString(msg, ""), true
}

func (b *SocketSlack) resolveBlockActionCommand(act slack.BlockAction) string {
command := act.Value
func resolveBlockActionCommand(act slack.BlockAction) (string, command.Origin) {
cmd := act.Value
cmdOrigin := command.UnknownOrigin

switch act.Type {
// currently we support only interactive.MultiSelect option
case "multi_static_select":
var items []string
for _, item := range act.SelectedOptions {
items = append(items, item.Value)
}
command = fmt.Sprintf("%s %s", act.ActionID, strings.Join(items, ","))
cmd = fmt.Sprintf("%s %s", act.ActionID, strings.Join(items, ","))
cmdOrigin = command.MultiSelectValueChangeOrigin
case "static_select":
// Example of commands that are handled here:
// @Botkube kcc --verbs get
// @Botkube kcc --resource-type
command = fmt.Sprintf("%s %s", act.ActionID, act.SelectedOption.Value)
cmd = fmt.Sprintf("%s %s", act.ActionID, act.SelectedOption.Value)
cmdOrigin = command.SelectValueChangeOrigin
case "button":
cmdOrigin = command.ButtonClickOrigin
case "plain_text_input":
command = fmt.Sprintf("%s%s", act.BlockID, strings.TrimSpace(act.Value))
cmd = fmt.Sprintf("%s%s", act.BlockID, strings.TrimSpace(act.Value))
cmdOrigin = command.PlainTextInputOrigin
}

return command
return cmd, cmdOrigin
}

func (b *SocketSlack) getThreadOptionIfNeeded(event socketSlackMessage, file *slack.File) slack.MsgOption {
Expand Down
2 changes: 2 additions & 0 deletions pkg/bot/teams.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/kubeshop/botkube/pkg/config"
"github.com/kubeshop/botkube/pkg/events"
"github.com/kubeshop/botkube/pkg/execute"
"github.com/kubeshop/botkube/pkg/execute/command"
"github.com/kubeshop/botkube/pkg/httpsrv"
"github.com/kubeshop/botkube/pkg/multierror"
"github.com/kubeshop/botkube/pkg/sliceutil"
Expand Down Expand Up @@ -295,6 +296,7 @@ func (b *Teams) processMessage(activity schema.Activity) (int, string) {
IsAuthenticated: true,
ID: ref.ChannelID,
ExecutorBindings: b.bindings.Executors,
CommandOrigin: command.TypedOrigin,
},
Message: trimmedMsg,
})
Expand Down
24 changes: 24 additions & 0 deletions pkg/execute/command/origin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package command

// Origin defines the origin of the command.
type Origin string

const (
// UnknownOrigin is the default value for Origin.
UnknownOrigin Origin = "unknown"

// TypedOrigin is the value for Origin when the command was typed by the user.
TypedOrigin Origin = "typed"

// ButtonClickOrigin is the value for Origin when the command was triggered by a button click.
ButtonClickOrigin Origin = "buttonClick"

// SelectValueChangeOrigin is the value for Origin when the command was triggered by a select value change.
SelectValueChangeOrigin Origin = "selectValueChange"

// MultiSelectValueChangeOrigin is the value for Origin when the command was triggered by a multi-select value change.
MultiSelectValueChangeOrigin Origin = "multiSelectValueChange"

// PlainTextInputOrigin is the value for Origin when the command was triggered by a plain text input.
PlainTextInputOrigin Origin = "plainTextInput"
)
6 changes: 4 additions & 2 deletions pkg/execute/edit.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ func (e *EditExecutor) Do(args []string, commGroupName string, platform config.C

defer func() {
cmdToReport := fmt.Sprintf("%s %s", cmdName, cmdVerb)
err := e.analyticsReporter.ReportCommand(platform, cmdToReport, conversation.IsButtonClickOrigin)
err := e.analyticsReporter.ReportCommand(platform, cmdToReport, conversation.CommandOrigin)
if err != nil {
e.log.Errorf("while reporting edit command: %s", err.Error())
}
Expand All @@ -99,7 +99,9 @@ func (e *EditExecutor) Do(args []string, commGroupName string, platform config.C

msg, err := cmds.SelectAndRun(cmdVerb)
if err != nil {
cmdVerb = anonymizedInvalidVerb // prevent passing any personal information
if err == errUnsupportedCommand {
cmdVerb = anonymizedInvalidVerb // prevent passing any personal information
}
return empty, err
}
return msg, nil
Expand Down
Loading