diff --git a/internal/remote/graphql/connected_platforms.go b/internal/remote/graphql/connected_platforms.go index b57215147..1dc990052 100644 --- a/internal/remote/graphql/connected_platforms.go +++ b/internal/remote/graphql/connected_platforms.go @@ -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"` @@ -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 } diff --git a/internal/remote/graphql/models_gen.go b/internal/remote/graphql/models_gen.go index 25b539c40..3a76a7fdc 100644 --- a/internal/remote/graphql/models_gen.go +++ b/internal/remote/graphql/models_gen.go @@ -333,7 +333,6 @@ type DeletePlatformInput struct { Discord *DeleteByIDInput `json:"discord"` Mattermost *DeleteByIDInput `json:"mattermost"` Webhook *DeleteByIDInput `json:"webhook"` - MsTeams *DeleteByIDInput `json:"msTeams"` CloudMsTeams *DeleteByIDInput `json:"cloudMsTeams"` Elasticsearch *DeleteByIDInput `json:"elasticsearch"` } @@ -599,40 +598,6 @@ type MattermostUpdateInput struct { Channels []*ChannelBindingsByNameUpdateInput `json:"channels"` } -type MsTeams struct { - ID string `json:"id"` - Name string `json:"name"` - BotName string `json:"botName"` - AppID string `json:"appId"` - AppPassword string `json:"appPassword"` - Port string `json:"port"` - MessagePath string `json:"messagePath"` - NotificationsDisabled *bool `json:"notificationsDisabled"` - Bindings *BotBindings `json:"bindings"` -} - -type MsTeamsCreateInput struct { - Name string `json:"name"` - BotName string `json:"botName"` - AppID string `json:"appId"` - AppPassword string `json:"appPassword"` - Port string `json:"port"` - MessagePath string `json:"messagePath"` - NotificationsDisabled *bool `json:"notificationsDisabled"` - Bindings *BotBindingsCreateInput `json:"bindings"` -} - -type MsTeamsUpdateInput struct { - ID *string `json:"id"` - Name string `json:"name"` - BotName string `json:"botName"` - AppID string `json:"appId"` - AppPassword string `json:"appPassword"` - Port string `json:"port"` - MessagePath string `json:"messagePath"` - Bindings *BotBindingsUpdateInput `json:"bindings"` -} - type NotificationPatchDeploymentConfigInput struct { CommunicationGroupName string `json:"communicationGroupName"` Platform BotPlatform `json:"platform"` @@ -696,7 +661,6 @@ type PlatformsCreateInput struct { CloudSlacks []*CloudSlackCreateInput `json:"cloudSlacks"` Mattermosts []*MattermostCreateInput `json:"mattermosts"` Webhooks []*WebhookCreateInput `json:"webhooks"` - MsTeams []*MsTeamsCreateInput `json:"msTeams"` Elasticsearches []*ElasticsearchCreateInput `json:"elasticsearches"` } @@ -706,7 +670,6 @@ type PlatformsUpdateInput struct { Discords []*DiscordUpdateInput `json:"discords"` Mattermosts []*MattermostUpdateInput `json:"mattermosts"` Webhooks []*WebhookUpdateInput `json:"webhooks"` - MsTeams []*MsTeamsUpdateInput `json:"msTeams"` CloudMsTeams []*CloudMsTeamsUpdateInput `json:"cloudMsTeams"` Elasticsearches []*ElasticsearchUpdateInput `json:"elasticsearches"` } @@ -760,11 +723,12 @@ func (this PluginPage) GetPageInfo() *PageInfo { return this.PageInfo } func (this PluginPage) GetTotalCount() int { return this.TotalCount } type PluginTemplate struct { - Name string `json:"name"` - Title string `json:"title"` - Description string `json:"description"` - Type PluginType `json:"type"` - Schema interface{} `json:"schema"` + Name string `json:"name"` + Title string `json:"title"` + Description string `json:"description"` + DocumentationURL *string `json:"documentationUrl"` + Type PluginType `json:"type"` + Schema interface{} `json:"schema"` } type PluginTemplatePage struct { diff --git a/internal/remote/graphql/platforms.go b/internal/remote/graphql/platforms.go index 9752f3943..0191899f5 100644 --- a/internal/remote/graphql/platforms.go +++ b/internal/remote/graphql/platforms.go @@ -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 } diff --git a/internal/remote/graphql/usage.go b/internal/remote/graphql/usage.go index 807c6ea89..4e277b5c5 100644 --- a/internal/remote/graphql/usage.go +++ b/internal/remote/graphql/usage.go @@ -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 } diff --git a/pkg/bot/teams_cloud.go b/pkg/bot/teams_cloud.go index c16f648b4..1a2e3ec84 100644 --- a/pkg/bot/teams_cloud.go +++ b/pkg/bot/teams_cloud.go @@ -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. @@ -302,13 +306,14 @@ 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, @@ -316,6 +321,57 @@ func (b *CloudTeams) processMessage(ctx context.Context, act schema.Activity, ch 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 { diff --git a/pkg/bot/teams_cloud_test.go b/pkg/bot/teams_cloud_test.go new file mode 100644 index 000000000..3eff5762b --- /dev/null +++ b/pkg/bot/teams_cloud_test.go @@ -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, + } +} diff --git a/pkg/execute/command/origin.go b/pkg/execute/command/origin.go index f1b2df6c0..2059eaadb 100644 --- a/pkg/execute/command/origin.go +++ b/pkg/execute/command/origin.go @@ -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 + } +}