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

Introduce global bot name placeholder #979

Merged
merged 2 commits into from
Feb 14, 2023
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
2 changes: 1 addition & 1 deletion cmd/botkube/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,7 @@ func sendHelp(ctx context.Context, s *storage.Help, clusterName string, executor
continue
}

help := interactive.NewHelpMessage(notifier.IntegrationName(), clusterName, notifier.BotName(), executors).Build()
help := interactive.NewHelpMessage(notifier.IntegrationName(), clusterName, executors).Build()
err := notifier.SendMessageToAll(ctx, help)
if err != nil {
return fmt.Errorf("while sending help message for %s: %w", notifier.IntegrationName(), err)
Expand Down
11 changes: 1 addition & 10 deletions internal/source/dispatcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,19 +71,10 @@ func (d *Dispatcher) dispatch(ctx context.Context, event []byte, sources []strin
msg := interactive.CoreMessage{
Description: string(event),
}
err := n.SendGenericMessage(ctx, &genericMessage{response: msg}, sources)
err := n.SendMessage(ctx, msg, sources)
if err != nil {
d.log.Errorf("while sending event: %s", err.Error())
}
}(n)
}
}

type genericMessage struct {
response interactive.CoreMessage
}

// ForBot returns interactive message.
func (g *genericMessage) ForBot(string) interactive.CoreMessage {
return g.response
}
27 changes: 5 additions & 22 deletions pkg/action/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
sprig "github.com/go-task/slim-sprig"
"github.com/sirupsen/logrus"

"github.com/kubeshop/botkube/pkg/api"
"github.com/kubeshop/botkube/pkg/bot/interactive"
"github.com/kubeshop/botkube/pkg/config"
"github.com/kubeshop/botkube/pkg/event"
Expand All @@ -21,9 +22,6 @@ import (
)

const (
// universalBotNamePlaceholder is a cross-platform placeholder for bot name in commands.
universalBotNamePlaceholder = "{{BotName}}"

// unknownValue defines an unknown string value.
unknownValue = "unknown"
)
Expand Down Expand Up @@ -72,7 +70,7 @@ func (p *Provider) RenderedActionsForEvent(e event.Event, sourceBindings []strin

actions = append(actions, event.Action{
DisplayName: action.DisplayName,
Command: fmt.Sprintf("%s %s", universalBotNamePlaceholder, renderedCmd),
Command: fmt.Sprintf("%s %s", api.MessageBotNamePlaceholder, renderedCmd),
ExecutorBindings: action.Bindings.Executors,
})
}
Expand All @@ -81,8 +79,7 @@ func (p *Provider) RenderedActionsForEvent(e event.Event, sourceBindings []strin
}

// ExecuteEventAction executes action for given event.
// WARNING: The result interactive.CoreMessage contains BotNamePlaceholder, which should be replaced before sending the message.
func (p *Provider) ExecuteEventAction(ctx context.Context, action event.Action) interactive.GenericMessage {
func (p *Provider) ExecuteEventAction(ctx context.Context, action event.Action) interactive.CoreMessage {
e := p.executorFactory.NewDefault(execute.NewDefaultInput{
Conversation: execute.Conversation{
IsAuthenticated: true,
Expand All @@ -94,12 +91,12 @@ func (p *Provider) ExecuteEventAction(ctx context.Context, action event.Action)
CommGroupName: unknownValue,
Platform: unknownValue,
NotifierHandler: &universalNotifierHandler{},
Message: strings.TrimSpace(strings.TrimPrefix(action.Command, universalBotNamePlaceholder)),
Message: strings.TrimSpace(strings.TrimPrefix(action.Command, api.MessageBotNamePlaceholder)),
User: fmt.Sprintf("Automation %q", action.DisplayName),
})
response := e.Execute(ctx)

return &genericMessage{response: response}
return response
}

type renderingData struct {
Expand All @@ -122,16 +119,6 @@ func (p *Provider) renderActionCommand(action config.Action, data renderingData)
return result.String(), nil
}

type genericMessage struct {
response interactive.CoreMessage
}

// ForBot returns message prepared for a bot with a given name.
func (g *genericMessage) ForBot(botName string) interactive.CoreMessage {
g.response.ReplaceBotNameInCommands(universalBotNamePlaceholder, botName)
return g.response
}

type universalNotifierHandler struct{}

func (n *universalNotifierHandler) NotificationsEnabled(_ string) bool {
Expand All @@ -141,7 +128,3 @@ func (n *universalNotifierHandler) NotificationsEnabled(_ string) bool {
func (n *universalNotifierHandler) SetNotificationsEnabled(_ string, _ bool) error {
return errors.New("setting notification from automated action is not supported. Use Botkube commands on a specific channel to set notifications")
}

func (n *universalNotifierHandler) BotName() string {
return universalBotNamePlaceholder
}
6 changes: 2 additions & 4 deletions pkg/action/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@ func TestProvider_ExecuteEventAction(t *testing.T) {
ExecutorBindings: executorBindings,
IsAuthenticated: true,
CommandOrigin: command.AutomationOrigin,
State: nil,
},
Message: "kubectl get po foo",
User: `Automation "Test"`,
Expand All @@ -115,9 +114,8 @@ func TestProvider_ExecuteEventAction(t *testing.T) {
provider := action.NewProvider(loggerx.NewNoop(), config.Actions{}, execFactory)

// when
res := provider.ExecuteEventAction(context.Background(), eventAction)

msg := res.ForBot(botName)
msg := provider.ExecuteEventAction(context.Background(), eventAction)
msg.ReplaceBotNamePlaceholder(botName)

// then
assert.Equal(t, fixInteractiveMessage(botName), msg)
Expand Down
61 changes: 11 additions & 50 deletions pkg/api/message.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package api

import (
"fmt"
"strings"
)
import "fmt"

// ButtonStyle is a style of Button element.
type ButtonStyle string
Expand Down Expand Up @@ -73,18 +70,6 @@ func (msg *Message) HasInputs() bool {
return len(msg.PlaintextInputs) != 0
}

// ReplaceBotNameInCommands replaces bot name in commands.
func (msg *Message) ReplaceBotNameInCommands(old, new string) {
for i := range msg.Sections {
msg.Sections[i].Buttons.ReplaceBotNameInCommands(old, new)
msg.Sections[i].MultiSelect.ReplaceBotNameInCommands(old, new)
msg.Sections[i].Selects.ReplaceBotNameInCommands(old, new)
msg.Sections[i].PlaintextInputs.ReplaceBotNameInCommands(old, new)
}

msg.PlaintextInputs.ReplaceBotNameInCommands(old, new)
}

// Select holds data related to the select drop-down.
type Select struct {
Type SelectType
Expand Down Expand Up @@ -123,13 +108,6 @@ type Section struct {
// LabelInputs holds the plain text input items.
type LabelInputs []LabelInput

// ReplaceBotNameInCommands replaces bot name in commands.
func (l *LabelInputs) ReplaceBotNameInCommands(old, new string) {
for i, labelInput := range *l {
(*l)[i].Command = strings.Replace(labelInput.Command, old, new, 1)
}
}

// ContextItems holds context items.
type ContextItems []ContextItem

Expand Down Expand Up @@ -158,13 +136,6 @@ type Selects struct {
Items []Select
}

// ReplaceBotNameInCommands replaces bot name in commands.
func (s *Selects) ReplaceBotNameInCommands(old, new string) {
for i, item := range s.Items {
s.Items[i].Command = strings.Replace(item.Command, old, new, 1)
}
}

// DispatchedInputAction defines when the action should be sent to our backend.
type DispatchedInputAction string

Expand Down Expand Up @@ -227,11 +198,6 @@ func (m *MultiSelect) AreOptionsDefined() bool {
return true
}

// ReplaceBotNameInCommands replaces bot name in commands.
func (m *MultiSelect) ReplaceBotNameInCommands(old, new string) {
m.Command = strings.Replace(m.Command, old, new, 1)
}

// Buttons holds definition of interactive buttons.
type Buttons []Button

Expand All @@ -249,13 +215,6 @@ func (s *Buttons) AtLeastOneButtonHasDescription() bool {
return false
}

// ReplaceBotNameInCommands replaces bot name in commands.
func (s *Buttons) ReplaceBotNameInCommands(old, new string) {
for i, item := range *s {
(*s)[i].Command = strings.Replace(item.Command, old, new, 1)
}
}

// Button holds definition of action button.
type Button struct {
Description string
Expand All @@ -266,8 +225,10 @@ type Button struct {
}

// ButtonBuilder provides a simplified way to construct a Button model.
type ButtonBuilder struct {
BotName string
type ButtonBuilder struct{}

func NewMessageButtonBuilder() *ButtonBuilder {
return &ButtonBuilder{}
}

// ForCommandWithDescCmd returns button command where description and command are the same.
Expand All @@ -288,7 +249,7 @@ func (b *ButtonBuilder) DescriptionURL(name, cmd string, url string, style ...Bu

return Button{
Name: name,
Description: fmt.Sprintf("%s %s", b.BotName, cmd),
Description: fmt.Sprintf("%s %s", MessageBotNamePlaceholder, cmd),
URL: url,
Style: bt,
}
Expand All @@ -300,7 +261,7 @@ func (b *ButtonBuilder) ForCommandWithoutDesc(name, cmd string, style ...ButtonS
if len(style) > 0 {
bt = style[0]
}
cmd = fmt.Sprintf("%s %s", b.BotName, cmd)
cmd = fmt.Sprintf("%s %s", MessageBotNamePlaceholder, cmd)
return Button{
Name: name,
Command: cmd,
Expand All @@ -314,8 +275,8 @@ func (b *ButtonBuilder) ForCommand(name, cmd, desc string, style ...ButtonStyle)
if len(style) > 0 {
bt = style[0]
}
cmd = fmt.Sprintf("%s %s", b.BotName, cmd)
desc = fmt.Sprintf("%s %s", b.BotName, desc)
cmd = fmt.Sprintf("%s %s", MessageBotNamePlaceholder, cmd)
desc = fmt.Sprintf("%s %s", MessageBotNamePlaceholder, desc)
return Button{
Name: name,
Command: cmd,
Expand All @@ -339,8 +300,8 @@ func (b *ButtonBuilder) ForURL(name, url string, style ...ButtonStyle) Button {
}

func (b *ButtonBuilder) commandWithDesc(name, cmd, desc string, style ButtonStyle) Button {
cmd = fmt.Sprintf("%s %s", b.BotName, cmd)
desc = fmt.Sprintf("%s %s", b.BotName, desc)
cmd = fmt.Sprintf("%s %s", MessageBotNamePlaceholder, cmd)
desc = fmt.Sprintf("%s %s", MessageBotNamePlaceholder, desc)
return Button{
Name: name,
Command: cmd,
Expand Down
131 changes: 131 additions & 0 deletions pkg/api/message_bot_name.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package api

import (
"strings"
)

const (
// MessageBotNamePlaceholder is a cross-platform placeholder for bot name.
MessageBotNamePlaceholder = "{{BotName}}"
maxReplaceNo = 100
)

// ReplaceBotNamePlaceholder replaces bot name placeholder with a given name.
func (msg *Message) ReplaceBotNamePlaceholder(new string) {
for idx, item := range msg.Sections {
msg.Sections[idx].Buttons = ReplaceBotNameInButtons(item.Buttons, new)
msg.Sections[idx].PlaintextInputs = ReplaceBotNameInLabels(item.PlaintextInputs, new)
msg.Sections[idx].Selects = ReplaceBotNameInSelects(item.Selects, new)
msg.Sections[idx].MultiSelect = ReplaceBotNameInMultiSelect(item.MultiSelect, new)

msg.Sections[idx].Base = ReplaceBotNameInBase(item.Base, new)
msg.Sections[idx].TextFields = ReplaceBotNameInTextFields(item.TextFields, new)
msg.Sections[idx].Context = ReplaceBotNameInContextItems(item.Context, new)
}
msg.PlaintextInputs = ReplaceBotNameInLabels(msg.PlaintextInputs, new)
msg.BaseBody = ReplaceBotNameInBody(msg.BaseBody, new)
}

// ReplaceBotNameInButtons replaces bot name placeholder with a given name.
func ReplaceBotNameInButtons(btns Buttons, name string) Buttons {
for i, item := range btns {
btns[i].Command = replace(item.Command, name)
btns[i].Description = replace(btns[i].Description, name)
btns[i].Name = replace(btns[i].Name, name)
}
return btns
}

// ReplaceBotNameInLabels replaces bot name placeholder with a given name.
func ReplaceBotNameInLabels(labels LabelInputs, name string) LabelInputs {
for i, item := range labels {
labels[i].Command = replace(item.Command, name)
labels[i].Text = replace(item.Text, name)
labels[i].Placeholder = replace(item.Placeholder, name)
}
return labels
}

// ReplaceBotNameInSelects replaces bot name placeholder with a given name.
func ReplaceBotNameInSelects(selects Selects, name string) Selects {
for i, item := range selects.Items {
selects.Items[i].Command = replace(item.Command, name)
selects.Items[i].Name = replace(item.Name, name)
selects.Items[i].OptionGroups = ReplaceBotNameInOptionGroups(item.OptionGroups, name)
selects.Items[i].InitialOption = ReplaceBotNameInOptionItem(item.InitialOption, name)
}
return selects
}

// ReplaceBotNameInMultiSelect replaces bot name placeholder with a given name.
func ReplaceBotNameInMultiSelect(ms MultiSelect, name string) MultiSelect {
ms.Command = replace(ms.Command, name)
ms.Name = replace(ms.Name, name)
ms.Description = ReplaceBotNameInBody(ms.Description, name)
ms.InitialOptions = ReplaceBotNameInOptions(ms.InitialOptions, name)
ms.Options = ReplaceBotNameInOptions(ms.Options, name)
return ms
}

// ReplaceBotNameInBase replaces bot name placeholder with a given name.
func ReplaceBotNameInBase(base Base, name string) Base {
base.Description = replace(base.Description, name)
base.Header = replace(base.Header, name)
base.Body = ReplaceBotNameInBody(base.Body, name)
return base
}

// ReplaceBotNameInBody replaces bot name placeholder with a given name.
func ReplaceBotNameInBody(body Body, name string) Body {
body.Plaintext = replace(body.Plaintext, name)
body.CodeBlock = replace(body.CodeBlock, name)
return body
}

// ReplaceBotNameInTextFields replaces bot name placeholder with a given name.
func ReplaceBotNameInTextFields(fields TextFields, name string) TextFields {
for i, item := range fields {
fields[i].Text = replace(item.Text, name)
}
return fields
}

// ReplaceBotNameInContextItems replaces bot name placeholder with a given name.
func ReplaceBotNameInContextItems(items ContextItems, name string) ContextItems {
for i, item := range items {
items[i].Text = replace(item.Text, name)
}
return items
}

// ReplaceBotNameInOptionItem replaces bot name placeholder with a given name.
func ReplaceBotNameInOptionItem(item *OptionItem, name string) *OptionItem {
if item == nil {
return nil
}
item.Name = replace(item.Name, name)
item.Value = replace(item.Value, name)
return item
}

// ReplaceBotNameInOptions replaces bot name placeholder with a given name.
func ReplaceBotNameInOptions(items []OptionItem, name string) []OptionItem {
for i, item := range items {
items[i].Name = replace(item.Name, name)
items[i].Value = replace(item.Value, name)
}
return items
}

// ReplaceBotNameInOptionGroups replaces bot name placeholder with a given name.
func ReplaceBotNameInOptionGroups(groups []OptionGroup, name string) []OptionGroup {
for i, item := range groups {
groups[i].Name = replace(item.Name, name)
groups[i].Options = ReplaceBotNameInOptions(item.Options, name)
}
return groups
}

func replace(text, new string) string {
return strings.Replace(text, MessageBotNamePlaceholder, new, maxReplaceNo)
}
Loading