From 13c620657b0d4ee4df294dfa48c3771a35889884 Mon Sep 17 00:00:00 2001 From: Simon Prochazka Date: Thu, 28 Nov 2019 16:43:27 +0100 Subject: [PATCH] feat(slack): Use blocks for releasing to Slack This feature should make the Slack output much nicer and will be iterated upon to add more detail. --- internal/slack/block.go | 20 +++++++++++++ internal/slack/publish.go | 8 ++--- internal/slack/publish_test.go | 6 ++-- internal/slack/release_notes.go | 33 ++++++++++----------- internal/slack/release_notes_test.go | 10 ++++++- internal/slack/testdata/expected_format.md | 15 ---------- internal/slack/testdata/expected_output.txt | 1 + 7 files changed, 52 insertions(+), 41 deletions(-) create mode 100644 internal/slack/block.go delete mode 100644 internal/slack/testdata/expected_format.md create mode 100644 internal/slack/testdata/expected_output.txt diff --git a/internal/slack/block.go b/internal/slack/block.go new file mode 100644 index 00000000..98b53b59 --- /dev/null +++ b/internal/slack/block.go @@ -0,0 +1,20 @@ +package slack + +type content struct { + // expected type is "mrkdwn" + Type string `json:"type"` + // markdown compliant message + Text string `json:"text"` +} + +// Block holds the different blocks uses in the Slack block API, due to some unfortunate naming json:"text" actually contains subsections. +type Block struct { + // expected type is "section" + Type string `json:"type"` + Section content `json:"text"` +} + +// WebhookMessage is the specific structure that Slack uses for the Webhook API +type WebhookMessage struct { + Blocks []Block `json:"blocks"` +} diff --git a/internal/slack/publish.go b/internal/slack/publish.go index 6e3085c8..ad43eb9b 100644 --- a/internal/slack/publish.go +++ b/internal/slack/publish.go @@ -5,16 +5,12 @@ import ( "net/http" "time" - "github.com/outillage/release-notary/internal/text" jsoniter "github.com/json-iterator/go" + "github.com/outillage/release-notary/internal/text" ) var json = jsoniter.ConfigCompatibleWithStandardLibrary -type request struct { - Text string `json:"text"` -} - // Publish pushes the release notes to Slack via provided Webhook. https://api.slack.com/reference/messaging/payload func (s *Slack) Publish(commits map[string][]text.Commit) error { releaseNotes := GenerateReleaseNotes(commits) @@ -23,7 +19,7 @@ func (s *Slack) Publish(commits map[string][]text.Commit) error { Timeout: time.Second * 5, } - jsonBody, err := json.Marshal(request{Text: releaseNotes}) + jsonBody, err := json.Marshal(releaseNotes) if err != nil { return err diff --git a/internal/slack/publish_test.go b/internal/slack/publish_test.go index 83141ed5..8ce06829 100644 --- a/internal/slack/publish_test.go +++ b/internal/slack/publish_test.go @@ -19,9 +19,11 @@ func TestPublish(t *testing.T) { assert.NoError(t, err) - expectedBody := "{\"text\":\"*Features*\\r\\nci test\\r\\n\\r\\n*Bug fixes*\\r\\nhuge bug\\r\\nbug fix\\r\\n\\r\\n*Chores and Improvements*\\r\\ntesting\\r\\nthis should end up in chores\\r\\n\\r\\n*Other*\\r\\nmerge master in something\\r\\nrandom\\r\\n\\r\\n\"}" + expectedBody, err := ioutil.ReadFile("./testdata/expected_output.txt") - assert.Equal(t, expectedBody, string(body)) + assert.NoError(t, err) + + assert.Equal(t, string(expectedBody), string(body)) _, err = rw.Write([]byte(`ok`)) diff --git a/internal/slack/release_notes.go b/internal/slack/release_notes.go index dfc0358c..b749aaf0 100644 --- a/internal/slack/release_notes.go +++ b/internal/slack/release_notes.go @@ -7,43 +7,42 @@ import ( ) // GenerateReleaseNotes creates a string from release notes that conforms with the Slack formatting. Expected format can be found in testdata. -func GenerateReleaseNotes(sections map[string][]text.Commit) string { - builder := strings.Builder{} +func GenerateReleaseNotes(sections map[string][]text.Commit) WebhookMessage { + var blocks []Block if len(sections["features"]) > 0 { - builder.WriteString("*Features*\r\n") - builder.WriteString(buildSection(sections["features"])) - builder.WriteString("\r\n") + section := Block{Type: "section", Section: buildSection("Features", sections["features"])} + blocks = append(blocks, section) } if len(sections["bugs"]) > 0 { - builder.WriteString("*Bug fixes*\r\n") - builder.WriteString(buildSection(sections["bugs"])) - builder.WriteString("\r\n") + section := Block{Type: "section", Section: buildSection("Bug fixes", sections["bugs"])} + blocks = append(blocks, section) } if len(sections["chores"]) > 0 { - builder.WriteString("*Chores and Improvements*\r\n") - builder.WriteString(buildSection(sections["chores"])) - builder.WriteString("\r\n") + section := Block{Type: "section", Section: buildSection("Chores and Improvements", sections["chores"])} + blocks = append(blocks, section) } if len(sections["others"]) > 0 { - builder.WriteString("*Other*\r\n") - builder.WriteString(buildSection(sections["others"])) - builder.WriteString("\r\n") + section := Block{Type: "section", Section: buildSection("Other", sections["others"])} + blocks = append(blocks, section) } - return builder.String() + return WebhookMessage{Blocks: blocks} } -func buildSection(commits []text.Commit) string { +func buildSection(heading string, commits []text.Commit) content { builder := strings.Builder{} + builder.WriteString("*" + heading + "*\r\n") for _, commit := range commits { builder.WriteString(commit.Heading) builder.WriteString("\r\n") } - return builder.String() + section := content{Type: "mrkdwn", Text: builder.String()} + + return section } diff --git a/internal/slack/release_notes_test.go b/internal/slack/release_notes_test.go index b9100083..06997fb5 100644 --- a/internal/slack/release_notes_test.go +++ b/internal/slack/release_notes_test.go @@ -15,5 +15,13 @@ func TestGenerateReleaseNotes(t *testing.T) { "others": []text.Commit{text.Commit{Category: "other", Scope: "", Heading: "merge master in something"}, text.Commit{Category: "bs", Scope: "", Heading: "random"}}, } - assert.Equal(t, "*Features*\r\nci test\r\n\r\n*Bug fixes*\r\nhuge bug\r\nbug fix\r\n\r\n*Chores and Improvements*\r\ntesting\r\nthis should end up in chores\r\n\r\n*Other*\r\nmerge master in something\r\nrandom\r\n\r\n", GenerateReleaseNotes(testData)) + expectedOutput := WebhookMessage(WebhookMessage{ + Blocks: []Block{ + Block{Type: "section", Section: content{Type: "mrkdwn", Text: "*Features*\r\nci test\r\n"}}, + Block{Type: "section", Section: content{Type: "mrkdwn", Text: "*Bug fixes*\r\nhuge bug\r\nbug fix\r\n"}}, + Block{Type: "section", Section: content{Type: "mrkdwn", Text: "*Chores and Improvements*\r\ntesting\r\nthis should end up in chores\r\n"}}, + Block{Type: "section", Section: content{Type: "mrkdwn", Text: "*Other*\r\nmerge master in something\r\nrandom\r\n"}}}, + }) + + assert.Equal(t, expectedOutput, GenerateReleaseNotes(testData)) } diff --git a/internal/slack/testdata/expected_format.md b/internal/slack/testdata/expected_format.md deleted file mode 100644 index dc6b2393..00000000 --- a/internal/slack/testdata/expected_format.md +++ /dev/null @@ -1,15 +0,0 @@ -*Features* -ci test - -*Bug fixes* -huge bug -bug fix - -*Chores and Improvements* -testing -this should end up in chores - -*Other* -merge master in something -random - diff --git a/internal/slack/testdata/expected_output.txt b/internal/slack/testdata/expected_output.txt new file mode 100644 index 00000000..98105912 --- /dev/null +++ b/internal/slack/testdata/expected_output.txt @@ -0,0 +1 @@ +{"blocks":[{"type":"section","text":{"type":"mrkdwn","text":"*Features*\r\nci test\r\n"}},{"type":"section","text":{"type":"mrkdwn","text":"*Bug fixes*\r\nhuge bug\r\nbug fix\r\n"}},{"type":"section","text":{"type":"mrkdwn","text":"*Chores and Improvements*\r\ntesting\r\nthis should end up in chores\r\n"}},{"type":"section","text":{"type":"mrkdwn","text":"*Other*\r\nmerge master in something\r\nrandom\r\n"}}]} \ No newline at end of file