Skip to content

Commit

Permalink
WIP - organizing things a bit, adding different block types, some tes…
Browse files Browse the repository at this point in the history
…ts etc.
  • Loading branch information
chiefy committed Jan 3, 2020
1 parent bbc9376 commit 0172310
Show file tree
Hide file tree
Showing 16 changed files with 512 additions and 1 deletion.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# go-slack-utils

[![GoDoc](https://godoc.org/github.com/chiefy/go-slack-utils?status.svg)](https://godoc.org/github.com/chiefy/go-slack-utils)

## What this is?

This is a general purpose utility library for using Slack's Block Kit UI, with Go structs corresponding to the blocks used to create UI elements. Also included is middleware for validing Slack requests using HMAC-256 and the Slack secret signing key.
This is a general purpose utility library for using Slack's [Block kit UI](https://api.slack.com/reference/block-kit/blocks), with Go structs corresponding to the blocks used to create UI elements. Also included is middleware for validing Slack requests using HMAC-256 and the Slack secret signing key.


## What this is not?
Expand Down
37 changes: 37 additions & 0 deletions pkg/blockui/actions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package blockui

type ActionElement interface {
GetActionID() string
UsesPlaceholder() bool
GetValue() string
HasOptions() bool
GetOptions() []*BlockOption
}

// ActionsBlock represents a Block Kit actions block UI element
type ActionsBlock struct {
Type string `json:"type"`
BlockID string `json:"block_id,omitempty"`
Elements []ActionElement `json:"elements"`
}

// NewActionsBlock creates a new ActionsBlock struct with appropriate type field
func NewActionsBlock(blockID string) *ActionsBlock {
return &ActionsBlock{
Type: slackBlockActionsType,
BlockID: blockID,
Elements: []ActionElement{},
}
}

func (a ActionsBlock) GetType() string {
return a.Type
}

func (a *ActionsBlock) Push(e ActionElement) {
a.Elements = append(a.Elements, e)
}

func (a ActionsBlock) NumElements() int {
return len(a.Elements)
}
29 changes: 29 additions & 0 deletions pkg/blockui/actions_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package blockui

import (
"encoding/json"
"github.com/stretchr/testify/assert"
"testing"
)

var basicActionsBlock = json.RawMessage(`{
"blocks": [
{
"type": "actions",
"block_id": "actions1",
"elements": []
}
]
}`)

func TestActionsBlock(t *testing.T) {
assert := assert.New(t)
b := NewSlackBlocks()
a := NewActionsBlock("actions1")
b.Push(a)
j, err := json.MarshalIndent(b, "", "\t")
if err != nil {
panic(err)
}
assert.Equal(string(basicActionsBlock), string(j))
}
28 changes: 28 additions & 0 deletions pkg/blockui/blocks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package blockui

// SlackBlock represents a BlockUI element
type SlackBlock interface {
GetType() string
}

// SlackBlocks is a collection of SlackBlock UI elements
type SlackBlocks struct {
Blocks []SlackBlock `json:"blocks"`
}

// NewSlackBlocks creates a new BlockUI root structure
func NewSlackBlocks() *SlackBlocks {
return &SlackBlocks{
Blocks: []SlackBlock{},
}
}

// Push adds a new BlockUI element to the root blocks collection
func (b *SlackBlocks) Push(block SlackBlock) {
b.Blocks = append(b.Blocks, block)
}

// NumBlocks returns the current length of child blocks
func (b SlackBlocks) NumBlocks() int {
return len(b.Blocks)
}
21 changes: 21 additions & 0 deletions pkg/blockui/blocks_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package blockui

import (
"encoding/json"
"github.com/stretchr/testify/assert"
"testing"
)

var basicBlocks = json.RawMessage(`{
"blocks": []
}`)

func TestBlocks(t *testing.T) {
assert := assert.New(t)
b := NewSlackBlocks()
j, err := json.MarshalIndent(b, "", "\t")
if err != nil {
panic(err)
}
assert.Equal(string(basicBlocks), string(j))
}
41 changes: 41 additions & 0 deletions pkg/blockui/button.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package blockui

// BlockButton represents a Block Kit button UI element
type BlockButton struct {
Type string `json:"type"`
*BlockOption
}

// NewBlockButton creates a new Block Kit button UI element
func NewBlockButton() *BlockButton {
return &BlockButton{
slackAccessoryButtonType,
&BlockOption{
Text: nil,
Value: "",
},
}
}

// SetText implements the SectionAccessory interface, sets the text block of element
func (b *BlockButton) SetText(text *BlockTitleText) {
b.BlockOption = &BlockOption{
Text: text,
Value: "",
}
}

// SetValue implements the SectionAccessory interface, sets the button's value
func (b *BlockButton) SetValue(val string) {
b.BlockOption.Value = val
}

// HasInteraction implements the SectionAccessory interface
func (b BlockButton) HasInteraction() bool {
return true
}

// IsImage implements the SectionAccessory interface
func (b BlockButton) IsImage() bool {
return false
}
53 changes: 53 additions & 0 deletions pkg/blockui/button_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package blockui

import (
"encoding/json"
"github.com/stretchr/testify/assert"
"testing"
)

var sectionWithButtonBlock = json.RawMessage(`{
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "You can add a button alongside text in your message. "
},
"accessory": {
"type": "button",
"text": {
"type": "plain_text",
"text": "Button",
"emoji": true
},
"value": "click_me_123"
}
}
]
}`)

func TestButtonAccessory(t *testing.T) {
assert := assert.New(t)
b := NewSlackBlocks()

s, _ := NewSlackBlockSectionWithAccessory(slackAccessoryButtonType)
s.SetText(
slackTextMarkdown,
"You can add a button alongside text in your message. ",
)

tt := NewBlockTitleText(slackTextPlainText)
tt.Text = "Button"
tt.Emoji = true

s.Accessory.SetText(tt)
s.Accessory.SetValue("click_me_123")
b.Push(s)

j, err := json.MarshalIndent(b, "", "\t")
if err != nil {
panic(err)
}
assert.Equal(string(sectionWithButtonBlock), string(j))
}
18 changes: 18 additions & 0 deletions pkg/blockui/constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package blockui

const (
slackBlockSectionType = "section"
slackBlockImageType = "image"
slackBlockContextType = "context"
slackBlockDividerType = "divider"
slackBlockActionsType = "actions"

slackAccessoryButtonType = "button"
slackAccessoryStaticSelectType = "static_select"
slackAccessoryMultiSelectType = "multi_static_select"
slackAccessoryOverflowType = "overflow"
slackAccessoryDatePickerType = "datepicker"

slackTextPlainText = "plain_text"
slackTextMarkdown = "mrkdwn"
)
1 change: 1 addition & 0 deletions pkg/blockui/datepicker.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package blockui
18 changes: 18 additions & 0 deletions pkg/blockui/divider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package blockui

// NewSlackBlockDivider creates a new BlockUI divider element
func NewSlackBlockDivider() *SlackBlockDivider {
return &SlackBlockDivider{
Type: slackBlockDividerType,
}
}

// SlackBlockDivider represents a BlockUI divider element
type SlackBlockDivider struct {
Type string `json:"type"`
}

// GetType implements the SlackBlock interface and returns the type as string
func (d SlackBlockDivider) GetType() string {
return d.Type
}
26 changes: 26 additions & 0 deletions pkg/blockui/divider_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package blockui

import (
"encoding/json"
"github.com/stretchr/testify/assert"
"testing"
)

var basicDivider = json.RawMessage(`{
"blocks": [
{
"type": "divider"
}
]
}`)

func TestDividerBlock(t *testing.T) {
assert := assert.New(t)
b := NewSlackBlocks()
b.Push(NewSlackBlockDivider())
j, err := json.MarshalIndent(b, "", "\t")
if err != nil {
panic(err)
}
assert.Equal(string(basicDivider), string(j))
}
33 changes: 33 additions & 0 deletions pkg/blockui/image.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package blockui

func NewSlackBlockImage() *SlackBlockImage {
return &SlackBlockImage{
Type: slackBlockImageType,
}
}

// SlackBlockImage represents a BlockUI image element
type SlackBlockImage struct {
Type string `json:"type"`
Title *BlockTitleText `json:"title,omitempty"`
ImageURL string `json:"image_url,omitempty"`
AltText string `json:"alt_text,omitempty"`
}

// GetType implements the SlackBlock interface and returns the type as string
func (i SlackBlockImage) GetType() string {
return i.Type
}

func NewSlackBlockAccessoryImage() *SlackBlockAccessoryImage {
return &SlackBlockAccessoryImage{
Type: slackBlockImageType,
}
}

// SlackBlockAccessoryImage represents a BlockUI image accessory element
type SlackBlockAccessoryImage struct {
Type string `json:"type"`
ImageURL string `json:"image_url,omitempty"`
AltText string `json:"alt_text,omitempty"`
}
64 changes: 64 additions & 0 deletions pkg/blockui/section.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package blockui

import (
"fmt"
)

type SectionAccessory interface {
HasInteraction() bool
IsImage() bool
SetText(*BlockTitleText)
SetValue(string)
}

// SlackBlockSection represents a section Block Kit UI element
type SlackBlockSection struct {
Type string `json:"type"`
Text TitleText `json:"text,omitempty"`
Accessory SectionAccessory `json:"accessory,omitempty"`
Fields []*BlockTitleText `json:"fields,omitempty"`
}

// GetType implements SlackBlock interface
func (s SlackBlockSection) GetType() string {
return s.Type
}

// SetText sets the Text field as appropriate depending on if the section has an accessory or not
func (s *SlackBlockSection) SetText(textType string, textVal string) {
var tt TitleText
if s.HasAccessory() {
tt = NewBlockTitleTextEmojiless(textType)
} else {
tt = NewBlockTitleText(textType)
}
tt.SetText(textVal)
s.Text = tt
}

func (s SlackBlockSection) HasAccessory() bool {
return s.Fields != nil || s.Accessory != nil
}

// NewSlackBlockSection creates a new empty section UI element
func NewSlackBlockSection() *SlackBlockSection {
return &SlackBlockSection{
Type: slackBlockSectionType,
}
}

// NewSlackBlockSectionWithAccessory creates a new Block Kit section UI element with provided accesssory type
func NewSlackBlockSectionWithAccessory(accessoryType string) (*SlackBlockSection, error) {
err := fmt.Errorf("NewSlackBlockSectionWithAccessory requires a valid accessory type")
if accessoryType == "" {
return nil, err
}
s := NewSlackBlockSection()

switch accessoryType {
case slackAccessoryButtonType:
s.Accessory = NewBlockButton()
}

return s, nil
}
Loading

0 comments on commit 0172310

Please sign in to comment.