Skip to content

Commit

Permalink
Add ID() to the Block interface (#1359)
Browse files Browse the repository at this point in the history
At incident.io, we often iterate over the contents of Slack views, and
often want to add the block ID to various bits of observability (logs,
traces). Doing this is quite difficult, because the `Block` interface
doesn't allow easy access to the ID — we have to cast each possible kind
of block to pull this out.

This PR introduces an `ID()` function to the `Block` interface, making
this kind of thing much easier.

I added some tests along the way, and also noticed that `CallBlock`
doesn't allow the ID to be set. This doesn't affect us, but you might
wish to fix that.
  • Loading branch information
nlopes authored Feb 6, 2025
2 parents 76e627a + 3d65d90 commit 9672f44
Show file tree
Hide file tree
Showing 26 changed files with 102 additions and 46 deletions.
1 change: 1 addition & 0 deletions block.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const (
// to ensure consistency between blocks.
type Block interface {
BlockType() MessageBlockType
ID() string
}

// Blocks is a convenience struct defined to allow dynamic unmarshalling of
Expand Down
5 changes: 5 additions & 0 deletions block_action.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ func (s ActionBlock) BlockType() MessageBlockType {
return s.Type
}

// ID returns the ID of the block
func (s ActionBlock) ID() string {
return s.BlockID
}

// NewActionBlock returns a new instance of an Action Block
func NewActionBlock(blockID string, elements ...BlockElement) *ActionBlock {
return &ActionBlock{
Expand Down
6 changes: 3 additions & 3 deletions block_action_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ import (
)

func TestNewActionBlock(t *testing.T) {

approveBtnTxt := NewTextBlockObject("plain_text", "Approve", false, false)
approveBtn := NewButtonBlockElement("", "click_me_123", approveBtnTxt)

actionBlock := NewActionBlock("test", approveBtn)

assert.Equal(t, actionBlock.BlockType(), MBTAction)
assert.Equal(t, string(actionBlock.Type), "actions")
assert.Equal(t, actionBlock.BlockID, "test")
assert.Equal(t, actionBlock.ID(), "test")
assert.Equal(t, len(actionBlock.Elements.ElementSet), 1)

}
7 changes: 6 additions & 1 deletion block_call.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ func (s CallBlock) BlockType() MessageBlockType {
return s.Type
}

// NewFileBlock returns a new instance of a file block
// ID returns the ID of the block
func (s CallBlock) ID() string {
return s.BlockID
}

// NewCallBlock returns a new instance of a call block
func NewCallBlock(callID string) *CallBlock {
return &CallBlock{
Type: MBTCall,
Expand Down
4 changes: 4 additions & 0 deletions block_call_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import (

func TestNewCallBlock(t *testing.T) {
callBlock := NewCallBlock("ACallID")

assert.Equal(t, callBlock.BlockType(), MBTCall)
assert.Equal(t, string(callBlock.Type), "call")
assert.Equal(t, callBlock.CallID, "ACallID")
assert.Equal(t, callBlock.BlockID, "")
assert.Equal(t, callBlock.ID(), "")
}
5 changes: 5 additions & 0 deletions block_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ func (s ContextBlock) BlockType() MessageBlockType {
return s.Type
}

// ID returns the ID of the block
func (s ContextBlock) ID() string {
return s.BlockID
}

type ContextElements struct {
Elements []MixedElement
}
Expand Down
7 changes: 3 additions & 4 deletions block_context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,14 @@ import (
)

func TestNewContextBlock(t *testing.T) {

locationPinImage := NewImageBlockElement("https://api.slack.com/img/blocks/bkb_template_images/tripAgentLocationMarker.png", "Location Pin Icon")
textExample := NewTextBlockObject("plain_text", "Location: Central Business District", true, false)

elements := []MixedElement{locationPinImage, textExample}

contextBlock := NewContextBlock("test", elements...)

assert.Equal(t, contextBlock.BlockType(), MBTContext)
assert.Equal(t, string(contextBlock.Type), "context")
assert.Equal(t, contextBlock.BlockID, "test")
assert.Equal(t, contextBlock.ID(), "test")
assert.Equal(t, len(contextBlock.ContextElements.Elements), 2)

}
6 changes: 5 additions & 1 deletion block_divider.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,14 @@ func (s DividerBlock) BlockType() MessageBlockType {
return s.Type
}

// ID returns the ID of the block
func (s DividerBlock) ID() string {
return s.BlockID
}

// NewDividerBlock returns a new instance of a divider block
func NewDividerBlock() *DividerBlock {
return &DividerBlock{
Type: MBTDivider,
}

}
6 changes: 4 additions & 2 deletions block_divider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import (
)

func TestNewDividerBlock(t *testing.T) {

dividerBlock := NewDividerBlock()
assert.Equal(t, string(dividerBlock.Type), "divider")

assert.Equal(t, dividerBlock.BlockType(), MBTDivider)
assert.Equal(t, string(dividerBlock.Type), "divider")
assert.Equal(t, dividerBlock.BlockID, "")
assert.Equal(t, dividerBlock.ID(), "")
}
14 changes: 0 additions & 14 deletions block_element_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,24 @@ import (
)

func TestNewImageBlockElement(t *testing.T) {

imageElement := NewImageBlockElement("https://api.slack.com/img/blocks/bkb_template_images/tripAgentLocationMarker.png", "Location Pin Icon")

assert.Equal(t, string(imageElement.Type), "image")
assert.Contains(t, imageElement.ImageURL, "tripAgentLocationMarker")
assert.Equal(t, imageElement.AltText, "Location Pin Icon")

}

func TestNewButtonBlockElement(t *testing.T) {

btnTxt := NewTextBlockObject("plain_text", "Next 2 Results", false, false)
btnElement := NewButtonBlockElement("test", "click_me_123", btnTxt)

assert.Equal(t, string(btnElement.Type), "button")
assert.Equal(t, btnElement.ActionID, "test")
assert.Equal(t, btnElement.Value, "click_me_123")
assert.Equal(t, btnElement.Text.Text, "Next 2 Results")

}

func TestWithStyleForButtonElement(t *testing.T) {

// these values are irrelevant in this test
btnTxt := NewTextBlockObject("plain_text", "Next 2 Results", false, false)
btnElement := NewButtonBlockElement("test", "click_me_123", btnTxt)
Expand All @@ -40,33 +35,27 @@ func TestWithStyleForButtonElement(t *testing.T) {
assert.Equal(t, btnElement.Style, Style("primary"))
btnElement.WithStyle(StyleDanger)
assert.Equal(t, btnElement.Style, Style("danger"))

}

func TestWithURLForButtonElement(t *testing.T) {

btnTxt := NewTextBlockObject("plain_text", "Next 2 Results", false, false)
btnElement := NewButtonBlockElement("test", "click_me_123", btnTxt)

btnElement.WithURL("https://foo.bar")
assert.Equal(t, btnElement.URL, "https://foo.bar")

}

func TestNewOptionsSelectBlockElement(t *testing.T) {

testOptionText := NewTextBlockObject("plain_text", "Option One", false, false)
testOption := NewOptionBlockObject("test", testOptionText, nil)

option := NewOptionsSelectBlockElement("static_select", nil, "test", testOption)
assert.Equal(t, option.Type, "static_select")
assert.Equal(t, len(option.Options), 1)
assert.Nil(t, option.OptionGroups)

}

func TestNewOptionsGroupSelectBlockElement(t *testing.T) {

testOptionText := NewTextBlockObject("plain_text", "Option One", false, false)
testOption := NewOptionBlockObject("test", testOptionText, nil)
testLabel := NewTextBlockObject("plain_text", "Test Label", false, false)
Expand All @@ -77,11 +66,9 @@ func TestNewOptionsGroupSelectBlockElement(t *testing.T) {
assert.Equal(t, optGroup.Type, "static_select")
assert.Equal(t, optGroup.ActionID, "test")
assert.Equal(t, len(optGroup.OptionGroups), 1)

}

func TestNewOptionsMultiSelectBlockElement(t *testing.T) {

testOptionText := NewTextBlockObject("plain_text", "Option One", false, false)
testDescriptionText := NewTextBlockObject("plain_text", "Description One", false, false)
testOption := NewOptionBlockObject("test", testOptionText, testDescriptionText)
Expand All @@ -90,7 +77,6 @@ func TestNewOptionsMultiSelectBlockElement(t *testing.T) {
assert.Equal(t, option.Type, "static_select")
assert.Equal(t, len(option.Options), 1)
assert.Nil(t, option.OptionGroups)

}

func TestNewOptionsGroupMultiSelectBlockElement(t *testing.T) {
Expand Down
5 changes: 5 additions & 0 deletions block_file.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ func (s FileBlock) BlockType() MessageBlockType {
return s.Type
}

// ID returns the ID of the block
func (s FileBlock) ID() string {
return s.BlockID
}

// NewFileBlock returns a new instance of a file block
func NewFileBlock(blockID string, externalID string, source string) *FileBlock {
return &FileBlock{
Expand Down
3 changes: 3 additions & 0 deletions block_file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ import (

func TestNewFileBlock(t *testing.T) {
fileBlock := NewFileBlock("test", "external_id", "source")

assert.Equal(t, fileBlock.BlockType(), MBTFile)
assert.Equal(t, string(fileBlock.Type), "file")
assert.Equal(t, fileBlock.BlockID, "test")
assert.Equal(t, fileBlock.ID(), "test")
assert.Equal(t, fileBlock.ExternalID, "external_id")
assert.Equal(t, fileBlock.Source, "source")
}
5 changes: 5 additions & 0 deletions block_header.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ func (s HeaderBlock) BlockType() MessageBlockType {
return s.Type
}

// ID returns the ID of the block
func (s HeaderBlock) ID() string {
return s.BlockID
}

// HeaderBlockOption allows configuration of options for a new header block
type HeaderBlockOption func(*HeaderBlock)

Expand Down
5 changes: 3 additions & 2 deletions block_header_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ import (
)

func TestNewHeaderBlock(t *testing.T) {

textInfo := NewTextBlockObject("plain_text", "This is quite the header", false, false)

headerBlock := NewHeaderBlock(textInfo, HeaderBlockOptionBlockID("test_block"))

assert.Equal(t, headerBlock.BlockType(), MBTHeader)
assert.Equal(t, string(headerBlock.Type), "header")
assert.Equal(t, headerBlock.ID(), "test_block")
assert.Equal(t, headerBlock.BlockID, "test_block")
assert.Equal(t, headerBlock.Text.Type, "plain_text")
assert.Contains(t, headerBlock.Text.Text, "quite the header")
Expand Down
5 changes: 5 additions & 0 deletions block_image.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ type ImageBlock struct {
SlackFile *SlackFileObject `json:"slack_file,omitempty"`
}

// ID returns the ID of the block
func (s ImageBlock) ID() string {
return s.BlockID
}

// SlackFileObject Defines an object containing Slack file information to be used in an
// image block or image element.
//
Expand Down
4 changes: 2 additions & 2 deletions block_image_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ import (
)

func TestNewImageBlock(t *testing.T) {

imageText := NewTextBlockObject("plain_text", "Location", false, false)
imageBlock := NewImageBlock("https://api.slack.com/img/blocks/bkb_template_images/tripAgentLocationMarker.png", "Marker", "test", imageText)

assert.Equal(t, imageBlock.BlockType(), MBTImage)
assert.Equal(t, string(imageBlock.Type), "image")
assert.Equal(t, imageBlock.Title.Type, "plain_text")
assert.Equal(t, imageBlock.ID(), "test")
assert.Equal(t, imageBlock.BlockID, "test")
assert.Contains(t, imageBlock.Title.Text, "Location")
assert.Contains(t, imageBlock.ImageURL, "tripAgentLocationMarker.png")

}
5 changes: 5 additions & 0 deletions block_input.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ func (s InputBlock) BlockType() MessageBlockType {
return s.Type
}

// ID returns the ID of the block
func (s InputBlock) ID() string {
return s.BlockID
}

// NewInputBlock returns a new instance of an input block
func NewInputBlock(blockID string, label, hint *TextBlockObject, element BlockElement) *InputBlock {
return &InputBlock{
Expand Down
3 changes: 3 additions & 0 deletions block_input_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ func TestNewInputBlock(t *testing.T) {
element := NewDatePickerBlockElement("action_id")
hint := NewTextBlockObject("plain_text", "hint", false, false)
inputBlock := NewInputBlock("test", label, hint, element)

assert.Equal(t, inputBlock.BlockType(), MBTInput)
assert.Equal(t, string(inputBlock.Type), "input")
assert.Equal(t, inputBlock.ID(), "test")
assert.Equal(t, inputBlock.BlockID, "test")
assert.Equal(t, inputBlock.Label, label)
assert.Equal(t, inputBlock.Element, element)
Expand Down
12 changes: 0 additions & 12 deletions block_object_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,23 @@ import (
)

func TestNewImageBlockObject(t *testing.T) {

imageObject := NewImageBlockElement("https://api.slack.com/img/blocks/bkb_template_images/beagle.png", "Beagle")

assert.Equal(t, string(imageObject.Type), "image")
assert.Equal(t, imageObject.AltText, "Beagle")
assert.Contains(t, imageObject.ImageURL, "beagle.png")

}

func TestNewTextBlockObject(t *testing.T) {

textObject := NewTextBlockObject("plain_text", "test", true, false)

assert.Equal(t, textObject.Type, "plain_text")
assert.Equal(t, textObject.Text, "test")
assert.True(t, textObject.Emoji, "Emoji property should be true")
assert.False(t, textObject.Verbatim, "Verbatim should be false")

}

func TestNewConfirmationBlockObject(t *testing.T) {

titleObj := NewTextBlockObject("plain_text", "testTitle", false, false)
textObj := NewTextBlockObject("plain_text", "testText", false, false)
confirmObj := NewTextBlockObject("plain_text", "testConfirm", false, false)
Expand All @@ -41,11 +36,9 @@ func TestNewConfirmationBlockObject(t *testing.T) {
assert.Equal(t, confirmation.Text.Text, "testText")
assert.Equal(t, confirmation.Confirm.Text, "testConfirm")
assert.Nil(t, confirmation.Deny, "Deny should be nil")

}

func TestWithStyleForConfirmation(t *testing.T) {

// these values are irrelevant in this test
titleObj := NewTextBlockObject("plain_text", "testTitle", false, false)
textObj := NewTextBlockObject("plain_text", "testText", false, false)
Expand All @@ -58,23 +51,19 @@ func TestWithStyleForConfirmation(t *testing.T) {
assert.Equal(t, confirmation.Style, Style("primary"))
confirmation.WithStyle(StyleDanger)
assert.Equal(t, confirmation.Style, Style("danger"))

}

func TestNewOptionBlockObject(t *testing.T) {

valTextObj := NewTextBlockObject("plain_text", "testText", false, false)
valDescriptionObj := NewTextBlockObject("plain_text", "testDescription", false, false)
optObj := NewOptionBlockObject("testOpt", valTextObj, valDescriptionObj)

assert.Equal(t, optObj.Text.Text, "testText")
assert.Equal(t, optObj.Description.Text, "testDescription")
assert.Equal(t, optObj.Value, "testOpt")

}

func TestNewOptionGroupBlockElement(t *testing.T) {

labelObj := NewTextBlockObject("plain_text", "testLabel", false, false)
valTextObj := NewTextBlockObject("plain_text", "testText", false, false)
optObj := NewOptionBlockObject("testOpt", valTextObj, nil)
Expand All @@ -83,7 +72,6 @@ func TestNewOptionGroupBlockElement(t *testing.T) {

assert.Equal(t, optGroup.Label.Text, "testLabel")
assert.Len(t, optGroup.Options, 1, "Options should contain one element")

}

func TestValidateTextBlockObject(t *testing.T) {
Expand Down
5 changes: 5 additions & 0 deletions block_rich_text.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ func (b RichTextBlock) BlockType() MessageBlockType {
return b.Type
}

// ID returns the ID of the block
func (s RichTextBlock) ID() string {
return s.BlockID
}

func (e *RichTextBlock) UnmarshalJSON(b []byte) error {
var raw struct {
Type MessageBlockType `json:"type"`
Expand Down
Loading

0 comments on commit 9672f44

Please sign in to comment.