Skip to content

Commit

Permalink
Add origin extraction for cmd execution, add user display name for in…
Browse files Browse the repository at this point in the history
…teractive command (#1330)

The changes introduce a feature that allows explicit origin extraction for the execution of commands. Furthermore, for commands that do not have an explicit origin, a fallback strategy has been implemented to determine the command origin based on activity type.
  • Loading branch information
mszostok authored Dec 13, 2023
1 parent b920662 commit 8c46be5
Show file tree
Hide file tree
Showing 7 changed files with 139 additions and 57 deletions.
9 changes: 2 additions & 7 deletions internal/remote/graphql/connected_platforms.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ package graphql

// OrganizationConnectedPlatforms represents connected platforms.
type OrganizationConnectedPlatforms struct {
OrganizationID string `graphql:"-"`
Slacks []*SlackWorkspace `json:"slacks"`
Slack *SlackWorkspace `json:"slack"`
Slacks []*SlackWorkspace `json:"slacks"`
Slack *SlackWorkspace `json:"slack"`

TeamsOrganizations []*TeamsOrganization `json:"teamsOrganizations"`
TeamsOrganization *TeamsOrganization `json:"teamsOrganization"`
Expand All @@ -15,14 +14,10 @@ type TeamsOrganization struct {
TenantID string `json:"tenantId"`
ConsentGiven bool `json:"consentGiven"`
IsReConsentingRequired bool `json:"isReConsentingRequired"`

// All internal, ignored fields are removed
}

type TeamsOrganizationTeam struct {
ID string `json:"id"`
AADGroupID string `json:"aadGroupId"`
DefaultConversationID string `json:"defaultConversationId"`

// All internal, ignored fields are removed
}
48 changes: 6 additions & 42 deletions internal/remote/graphql/models_gen.go

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

3 changes: 0 additions & 3 deletions internal/remote/graphql/platforms.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@ type Platforms struct {
Discords []*Discord `json:"discords"`
Mattermosts []*Mattermost `json:"mattermosts"`
Webhooks []*Webhook `json:"webhooks"`
MsTeams []*MsTeams `json:"msTeams"`
Elasticsearches []*Elasticsearch `json:"elasticsearches"`
CloudMsTeams []*CloudMsTeams `json:"cloudTeams"`

// All internal, ignored fields are removed
}
2 changes: 0 additions & 2 deletions internal/remote/graphql/usage.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,4 @@ type Usage struct {
MemberCount *int `json:"memberCount"`
NodeCount *int `json:"nodeCount"`
CloudSlackUseCount *int `json:"cloudSlackUseCount"`

// All internal, ignored fields are removed
}
62 changes: 59 additions & 3 deletions pkg/bot/teams_cloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ import (
"github.com/kubeshop/botkube/pkg/sliceutil"
)

const (
originKeyName = "originName"
)

var _ Bot = &CloudTeams{}

// CloudTeams listens for user's messages, execute commands and sends back the response.
Expand Down Expand Up @@ -302,20 +306,72 @@ func (b *CloudTeams) processMessage(ctx context.Context, act schema.Activity, ch
ID: channel.Identifier(),
ExecutorBindings: channel.Bindings.Executors,
SourceBindings: channel.Bindings.Sources,
CommandOrigin: command.TypedOrigin,
CommandOrigin: b.mapToCommandOrigin(act),
DisplayName: channelDisplayName,
},
Message: trimmedMsg,
User: execute.UserInput{
// TODO: we need to add support for mentions on cloud side.
//Mention: "",
// Note: this is a plain text mention, a native mentions will be provided as a part of:
// https://github.com/kubeshop/botkube/issues/1331
Mention: act.From.Name,
DisplayName: act.From.Name,
},
AuditContext: act.ChannelData,
})
return e.Execute(ctx)
}

// generate a function code tab based on in type will return proper command origin.
func (b *CloudTeams) mapToCommandOrigin(act schema.Activity) command.Origin {
// in the newer Botkube Cloud version, the origin is explicitly set
c, found := b.extractExplicitOrigin(act)
if found {
return c
}

// fallback to default strategy
switch act.Type {
case schema.Message:
return command.TypedOrigin
case schema.Invoke:
return command.ButtonClickOrigin
default:
return command.UnknownOrigin
}
}

type activityMetadata struct {
// OriginName is inlined for e.g. 'Action.Submit'. We are not able to force the unified approach.
OriginName string `mapstructure:"originName"`
Action struct {
Data struct {
// OriginName is nested for e.g. 'Action.Execute'.
OriginName string `mapstructure:"originName"`
} `mapstructure:"data"`
} `mapstructure:"action"`
}

func (b *CloudTeams) extractExplicitOrigin(act schema.Activity) (command.Origin, bool) {
if act.Value == nil {
return "", false
}
var data activityMetadata
err := mapstructure.Decode(act.Value, &data)
if err != nil {
b.log.WithError(err).Debug("Cannot decode activity value to extract command origin")
return "", false
}

origin := data.OriginName
if origin == "" {
origin = data.Action.Data.OriginName
}
if command.IsValidOrigin(origin) {
return command.Origin(origin), true
}
return "", false
}

func (b *CloudTeams) sendAgentActivity(ctx context.Context, msg interactive.CoreMessage, channels []teamsCloudChannelConfigByID) error {
errs := multierror.New()
for _, channel := range channels {
Expand Down
62 changes: 62 additions & 0 deletions pkg/bot/teams_cloud_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package bot

import (
"testing"

"github.com/infracloudio/msbotbuilder-go/schema"
"github.com/stretchr/testify/assert"

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

func TestExtractExplicitOrigin(t *testing.T) {
tests := []struct {
name string
givenAct schema.Activity
wantOrigin command.Origin
}{
{
name: "Should return explicit origin",
givenAct: schema.Activity{
Type: schema.Message,
Value: explicitOriginValue(string(command.MultiSelectValueChangeOrigin)),
},
wantOrigin: command.MultiSelectValueChangeOrigin,
},
{
name: "Should return typed message resolved from type due to invalid value",
givenAct: schema.Activity{
Type: schema.Message,
Value: explicitOriginValue("malformed-or-unknown-origin"),
},
wantOrigin: command.TypedOrigin,
},
{
name: "Should return btn click origin resolved from type because value is nil",
givenAct: schema.Activity{
Type: schema.Invoke,
Value: nil,
},
wantOrigin: command.ButtonClickOrigin,
},
{
name: "Should return unknown origin because value does not contain origin key and type is empty",
givenAct: schema.Activity{Value: map[string]any{}},
wantOrigin: command.UnknownOrigin,
},
}

cloudTeam := &CloudTeams{}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
gotOrigin := cloudTeam.mapToCommandOrigin(tc.givenAct)
assert.Equal(t, tc.wantOrigin, gotOrigin)
})
}
}

func explicitOriginValue(in string) map[string]any {
return map[string]any{
originKeyName: in,
}
}
10 changes: 10 additions & 0 deletions pkg/execute/command/origin.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,13 @@ const (
// AutomationOrigin is the value for Origin when the command was triggered by an automation.
AutomationOrigin Origin = "automation"
)

// IsValidOrigin returns true if the given string is a valid Origin.
func IsValidOrigin(in string) bool {
switch Origin(in) {
case TypedOrigin, ButtonClickOrigin, SelectValueChangeOrigin, MultiSelectValueChangeOrigin, PlainTextInputOrigin, AutomationOrigin:
return true
default:
return false
}
}

0 comments on commit 8c46be5

Please sign in to comment.