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

Render plain text for messages sent in attachments and remove the header #773

Merged
merged 21 commits into from
Sep 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
50bfe80
Slack bot now uploads a long msg as a plaintext file with a related d…
ezodude Sep 27, 2022
bad1598
Added interactive message to plaintext tests.
ezodude Sep 27, 2022
0063ebb
Long SocketSlack interactive messages now sent as message and file up…
ezodude Sep 27, 2022
0fbca32
Clean up.
ezodude Sep 27, 2022
d24cb59
Discore and MatterMost long messages now uploaded with a message and …
ezodude Sep 27, 2022
861ae64
MatterMost message response field no longer used.
ezodude Sep 27, 2022
bf561f3
MS Teams long messages now uploaded as a plaintext file.
ezodude Sep 27, 2022
f88fc01
Added E2E test to verify large output is received as plaintext file w…
ezodude Sep 28, 2022
8907fef
Fixed unit tests.
ezodude Sep 28, 2022
2fcbd98
For MS Teams we may need to force markdown regardless of outgoing mes…
ezodude Sep 28, 2022
ffe6e18
Merge branch 'main' into render-plain
huseyinbabal Sep 28, 2022
a6eda16
Merge branch 'main' into render-plain
ezodude Sep 29, 2022
fa751ad
Remove description messages and attach cmd description to actual uplo…
ezodude Sep 29, 2022
ef7d01c
Factor out max message size to consts.
ezodude Sep 29, 2022
819f545
Merge branch 'main' into render-plain
ezodude Sep 29, 2022
3c0cfb7
Fix incorrect spacing in helm services template.
ezodude Sep 30, 2022
acddc90
Generalised Markdown formatting to be used for plaintext formatting.
ezodude Sep 30, 2022
ee3c269
Linter fixes.
ezodude Sep 30, 2022
7c9419d
Merge branch 'main' into render-plain
ezodude Sep 30, 2022
5b16bbc
Update test golden text to match the latest from main.
ezodude Sep 30, 2022
f7691d3
Remove dead code.
ezodude Sep 30, 2022
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
6 changes: 3 additions & 3 deletions helm/botkube/templates/service.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ spec:
type: ClusterIP
ports:
{{- if .Values.settings.lifecycleServer.enabled }}
- name: "lifecycle"
port: {{ .Values.settings.lifecycleServer.port }}
targetPort: {{ .Values.settings.lifecycleServer.port }}
- name: "lifecycle"
port: {{ .Values.settings.lifecycleServer.port }}
targetPort: {{ .Values.settings.lifecycleServer.port }}
{{- end }}
{{- if .Values.serviceMonitor.enabled }}
- name: {{ .Values.service.name }}
Expand Down
28 changes: 16 additions & 12 deletions pkg/bot/discord.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,16 @@ import (

var _ Bot = &Discord{}

// customTimeFormat holds custom time format string.
const (
// customTimeFormat holds custom time format string.
customTimeFormat = "2006-01-02T15:04:05Z"

// discordBotMentionRegexFmt supports also nicknames (the exclamation mark).
// Read more: https://discordjs.guide/miscellaneous/parsing-mention-arguments.html#how-discord-mentions-work
discordBotMentionRegexFmt = "^<@!?%s>"

// discordMaxMessageSize max size before a message should be uploaded as a file.
discordMaxMessageSize = 2000
)

var embedColor = map[config.Level]int{
Expand Down Expand Up @@ -154,7 +157,7 @@ func (b *Discord) SendMessage(_ context.Context, msg interactive.Message) error
errs := multierror.New()
for _, channel := range b.getChannels() {
channelID := channel.ID
plaintext := interactive.MessageToMarkdown(b.mdFormatter, msg)
plaintext := interactive.RenderMessage(b.mdFormatter, msg)
b.log.Debugf("Sending message to channel %q: %s", channelID, plaintext)

if _, err := b.api.ChannelMessageSend(channelID, plaintext); err != nil {
Expand Down Expand Up @@ -247,32 +250,33 @@ func (b *Discord) handleMessage(dm discordMessage) error {
User: fmt.Sprintf("<@%s>", dm.Event.Author.ID),
})

out := e.Execute()
resp := interactive.MessageToMarkdown(b.mdFormatter, out)
err := b.send(dm.Event, req, resp)
response := e.Execute()
err := b.send(dm.Event, req, response)
if err != nil {
return fmt.Errorf("while sending message: %w", err)
}

return nil
}

func (b *Discord) send(event *discordgo.MessageCreate, req, resp string) error {
func (b *Discord) send(event *discordgo.MessageCreate, req string, resp interactive.Message) error {
b.log.Debugf("Discord incoming Request: %s", req)
b.log.Debugf("Discord Response: %s", resp)

if len(resp) == 0 {
markdown := interactive.RenderMessage(b.mdFormatter, resp)

if len(markdown) == 0 {
return fmt.Errorf("while reading Slack response: empty response for request %q", req)
}

// Upload message as a file if too long
if len(resp) >= 2000 {
if len(markdown) >= discordMaxMessageSize {
params := &discordgo.MessageSend{
Content: req,
Content: resp.Description,
Files: []*discordgo.File{
{
Name: "Response",
Reader: strings.NewReader(resp),
Name: "Response.txt",
Reader: strings.NewReader(interactive.MessageToPlaintext(resp, interactive.NewlineFormatter)),
},
},
}
Expand All @@ -282,7 +286,7 @@ func (b *Discord) send(event *discordgo.MessageCreate, req, resp string) error {
return nil
}

if _, err := b.api.ChannelMessageSend(event.ChannelID, resp); err != nil {
if _, err := b.api.ChannelMessageSend(event.ChannelID, markdown); err != nil {
return fmt.Errorf("while sending message: %w", err)
}
return nil
Expand Down
20 changes: 20 additions & 0 deletions pkg/bot/interactive/formatters.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package interactive

import (
"fmt"
)

// NewlineFormatter adds new line formatting.
func NewlineFormatter(msg string) string {
return fmt.Sprintf("%s\n", msg)
}

// MdHeaderFormatter adds Markdown header formatting.
func MdHeaderFormatter(msg string) string {
return fmt.Sprintf("**%s**", msg)
}

// NoFormatting does not apply any formatting.
func NoFormatting(msg string) string {
return msg
}
42 changes: 18 additions & 24 deletions pkg/bot/interactive/markdown.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,40 +7,34 @@ import (
formatx "github.com/kubeshop/botkube/pkg/format"
)

// DefaultMDLineFormatter represents a Markdown new line formatting.
func DefaultMDLineFormatter(msg string) string {
return fmt.Sprintf("%s\n", msg)
}

// DefaultMDHeaderFormatter represents a Markdown header formatting.
func DefaultMDHeaderFormatter(msg string) string {
return fmt.Sprintf("**%s**", msg)
}

// MDFormatter represents the capability of Markdown Formatter
type MDFormatter struct {
lineFormatter func(msg string) string
headerFormatter func(msg string) string
newlineFormatter func(msg string) string
headerFormatter func(msg string) string
codeBlockFormatter func(msg string) string
adaptiveCodeBlockFormatter func(msg string) string
}

// NewMDFormatter is for initializing custom Markdown formatter
func NewMDFormatter(lineFormatter, headerFormatter func(msg string) string) MDFormatter {
func NewMDFormatter(newlineFormatter, headerFormatter func(msg string) string) MDFormatter {
return MDFormatter{
lineFormatter: lineFormatter,
headerFormatter: headerFormatter,
newlineFormatter: newlineFormatter,
headerFormatter: headerFormatter,
codeBlockFormatter: formatx.CodeBlock,
adaptiveCodeBlockFormatter: formatx.AdaptiveCodeBlock,
}
}

// DefaultMDFormatter is for initializing built-in Markdown formatter
func DefaultMDFormatter() MDFormatter {
return NewMDFormatter(DefaultMDLineFormatter, DefaultMDHeaderFormatter)
return NewMDFormatter(NewlineFormatter, MdHeaderFormatter)
}

// MessageToMarkdown returns interactive message as a plaintext with Markdown syntax.
func MessageToMarkdown(mdFormatter MDFormatter, msg Message) string {
// RenderMessage returns interactive message as a plaintext with Markdown syntax.
func RenderMessage(mdFormatter MDFormatter, msg Message) string {
var out strings.Builder
addLine := func(in string) {
out.WriteString(mdFormatter.lineFormatter(in))
out.WriteString(mdFormatter.newlineFormatter(in))
}

if msg.Header != "" {
Expand All @@ -55,7 +49,7 @@ func MessageToMarkdown(mdFormatter MDFormatter, msg Message) string {
}

if msg.Body.CodeBlock != "" {
addLine(formatx.CodeBlock(msg.Body.CodeBlock))
addLine(mdFormatter.codeBlockFormatter(msg.Body.CodeBlock))
}

for _, section := range msg.Sections {
Expand All @@ -75,7 +69,7 @@ func MessageToMarkdown(mdFormatter MDFormatter, msg Message) string {
if section.Body.CodeBlock != "" {
// not using the adaptive code block is on purpose, we always want to have
// a multiline code block to improve readability
addLine(formatx.CodeBlock(section.Body.CodeBlock))
addLine(mdFormatter.codeBlockFormatter(section.Body.CodeBlock))
}

if section.MultiSelect.AreOptionsDefined() {
Expand All @@ -86,14 +80,14 @@ func MessageToMarkdown(mdFormatter MDFormatter, msg Message) string {
}

if ms.Description.CodeBlock != "" {
addLine(formatx.AdaptiveCodeBlock(ms.Description.CodeBlock))
addLine(mdFormatter.adaptiveCodeBlockFormatter(ms.Description.CodeBlock))
}

addLine("") // new line
addLine("Available options:")

for _, opt := range ms.Options {
addLine(fmt.Sprintf(" - %s", formatx.AdaptiveCodeBlock(opt.Value)))
addLine(fmt.Sprintf(" - %s", mdFormatter.adaptiveCodeBlockFormatter(opt.Value)))
}
}

Expand All @@ -103,7 +97,7 @@ func MessageToMarkdown(mdFormatter MDFormatter, msg Message) string {
continue
}
if btn.Command != "" {
addLine(fmt.Sprintf(" - %s", formatx.AdaptiveCodeBlock(btn.Command)))
addLine(fmt.Sprintf(" - %s", mdFormatter.adaptiveCodeBlockFormatter(btn.Command)))
continue
}
}
Expand Down
16 changes: 11 additions & 5 deletions pkg/bot/interactive/markdown_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"testing"

"gotest.tools/v3/golden"

formatx "github.com/kubeshop/botkube/pkg/format"
)

// go test -run=TestInteractiveMessageToMarkdownMultiSelect ./pkg/bot/interactive/... -test.update-golden
Expand Down Expand Up @@ -39,7 +41,7 @@ func TestInteractiveMessageToMarkdownMultiSelect(t *testing.T) {
}

// when
out := MessageToMarkdown(DefaultMDFormatter(), message)
out := RenderMessage(DefaultMDFormatter(), message)

// then
golden.Assert(t, out, fmt.Sprintf("%s.golden.txt", t.Name()))
Expand All @@ -48,17 +50,21 @@ func TestInteractiveMessageToMarkdownMultiSelect(t *testing.T) {
// go test -run=TestInteractiveMessageToMarkdown ./pkg/bot/interactive/... -test.update-golden
func TestInteractiveMessageToMarkdown(t *testing.T) {
formatterForCustomNewLines := MDFormatter{
lineFormatter: func(msg string) string {
newlineFormatter: func(msg string) string {
return fmt.Sprintf("%s<br>", msg)
},
headerFormatter: DefaultMDHeaderFormatter,
headerFormatter: MdHeaderFormatter,
codeBlockFormatter: formatx.CodeBlock,
adaptiveCodeBlockFormatter: formatx.AdaptiveCodeBlock,
}

formatterForCustomHeaders := MDFormatter{
lineFormatter: DefaultMDLineFormatter,
newlineFormatter: NewlineFormatter,
headerFormatter: func(msg string) string {
return fmt.Sprintf("*%s*", msg)
},
codeBlockFormatter: formatx.CodeBlock,
adaptiveCodeBlockFormatter: formatx.AdaptiveCodeBlock,
}
tests := []struct {
name string
Expand All @@ -79,7 +85,7 @@ func TestInteractiveMessageToMarkdown(t *testing.T) {
given := Help("platform", "testing", "@BotKube")

// when
out := MessageToMarkdown(tc.mdFormatter, given)
out := RenderMessage(tc.mdFormatter, given)

// then
golden.Assert(t, out, fmt.Sprintf("%s.golden.txt", t.Name()))
Expand Down
14 changes: 14 additions & 0 deletions pkg/bot/interactive/plaintext.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package interactive

// MessageToPlaintext returns interactive message as a plaintext.
func MessageToPlaintext(msg Message, newlineFormatter func(in string) string) string {
mszostok marked this conversation as resolved.
Show resolved Hide resolved
msg.Description = ""

fmt := MDFormatter{
newlineFormatter: newlineFormatter,
headerFormatter: NoFormatting,
codeBlockFormatter: NoFormatting,
adaptiveCodeBlockFormatter: NoFormatting,
}
return RenderMessage(fmt, msg)
}
64 changes: 64 additions & 0 deletions pkg/bot/interactive/plaintext_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package interactive

import (
"fmt"
"testing"

"gotest.tools/v3/assert"
"gotest.tools/v3/golden"
)

// go test -run=TestInteractiveMessageToMarkdownMultiSelect ./pkg/bot/interactive/... -test.update-golden
func TestInteractiveMessageToPlaintextMultiSelect(t *testing.T) {
// given
message := Message{
Base: Base{
Header: "Adjust notifications",
Description: "Adjust notifications description",
},

Sections: []Section{
{
MultiSelect: MultiSelect{
Name: "Adjust notifications",
Description: Body{
Plaintext: "Select notification sources",
},
Command: "@BotKube edit SourceBindings",
Options: []OptionItem{
{
Name: "K8s all events",
Value: "k8s-all-events",
},
{
Name: "K8s recommendations",
Value: "k8s-recommendations",
},
},
},
},
},
}

// when
out := MessageToPlaintext(message, NewlineFormatter)

// then
golden.Assert(t, out, fmt.Sprintf("%s.golden.txt", t.Name()))
}

// go test -run=TestInteractiveMessageToMarkdown ./pkg/bot/interactive/... -test.update-golden
func TestInteractiveMessageToPlaintext(t *testing.T) {
customNewlineFormatter := func(msg string) string {
return fmt.Sprintf("%s\r\n", msg)
}

// given
help := Help("platform", "testing", "@BotKube")

// when
out := MessageToPlaintext(help, customNewlineFormatter)

// then
assert.Assert(t, golden.String(out, fmt.Sprintf("%s.golden.txt", t.Name())))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@

Using multiple instances
If you are running multiple BotKube instances in the same channel to interact with testing, make sure to specify the cluster name when typing commands.
--cluster-name="testing"


Manage incoming notifications
@BotKube notifier [start|stop|status]

- @BotKube notifier start
- @BotKube notifier stop
- @BotKube notifier status

Notification settings for this channel
By default, BotKube will notify only about cluster errors and recommendations.
- @BotKube edit SourceBindings

Ping your cluster
Check the status of connected Kubernetes cluster(s).
- @BotKube ping

Run kubectl commands (if enabled)
You can run kubectl commands directly from Platform!
- @BotKube get services
- @BotKube get pods
- @BotKube get deployments

To list all supported kubectl commands
- @BotKube commands list

Filters (advanced)
You can extend BotKube functionality by writing additional filters that can check resource specs, validate some checks and add messages to the Event struct. Learn more at https://botkube.io/filters

Angry? Amazed?
Give feedback: https://feedback.botkube.io

Read our docs: https://botkube.io/docs
Join our Slack: https://join.botkube.io
Follow us on Twitter: https://twitter.com/botkube_io
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Adjust notifications

Select notification sources

Available options:
- k8s-all-events
- k8s-recommendations
Loading