Skip to content
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
112 changes: 112 additions & 0 deletions x/blocks/blocks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package blocks

// New creates a new QueryResponse with the given blocks. This is the top level block.
// and should be used to return all subsections unless you wish to create the struct manually.
func New(blocks ...Block) QueryResponse {
return QueryResponse{
CadenceResponseType: "formattedData",
Format: "blocks",
Blocks: blocks,
}
}

// Creates a markdown block with the given text.
func NewMarkdownSection(markdownText string) Block {
return Block{
Type: "section",
Format: "text/markdown",
ComponentOptions: &ComponentOptions{
Text: markdownText,
},
}
}

// NewDivider creates a divider in the UI.
func NewDivider() Block {
return Block{
Type: "divider",
}
}

// Creates a set of actions for signalling the workflow.
func NewSignalActions(elements ...Element) Block {
return Block{
Type: "actions",
Elements: elements,
}
}

// NewSignalButton creates a button that will signal the workflow with the given signal name and value.
// the signal value can be nil if no value is needed.
func NewSignalButton(text string, signalName string, signalValue interface{}) Element {
return Element{
Type: "button",
ComponentOptions: &ComponentOptions{
Type: "plain_text",
Text: text,
},
Action: &Action{
Type: "signal",
SignalName: signalName,
SignalValue: signalValue,
},
}
}

// NewSignalButtonWithExternalWorkflow creates a button that will signal the workflow with the given signal name and value,
// and will also start the external workflow with the given workflow ID and run ID.
//
// The RunID should be optional and the latest workflow will be selected i it's not provided
func NewSignalButtonWithExternalWorkflow(text string, signalName string, signalValue interface{}, workflowID string, runID string) Element {
return Element{
Type: "button",
ComponentOptions: &ComponentOptions{
Type: "plain_text",
Text: text,
},
Action: &Action{
Type: "signal",
SignalName: signalName,
SignalValue: signalValue,
WorkflowID: workflowID,
RunID: runID,
},
}
}

// Query response is the overall wrapper struct that will be returned to the client.
// There's nothing special about this Go code specifically, the actual UI
// only cares about the JSON structure returned, but this is a helpful wrapper
type QueryResponse struct {
CadenceResponseType string `json:"cadenceResponseType"`
Format string `json:"format"`
Blocks []Block `json:"blocks"`
}

// A section in the workflow Query response that will be rendered
type Block struct {
Type string `json:"type"`
Format string `json:"format,omitempty"`
ComponentOptions *ComponentOptions `json:"componentOptions,omitempty"`
Elements []Element `json:"elements,omitempty"`
}

type Element struct {
Type string `json:"type"`
ComponentOptions *ComponentOptions `json:"componentOptions,omitempty"`
Action *Action `json:"action,omitempty"`
}

type ComponentOptions struct {
Type string `json:"type,omitempty"`
Text string `json:"text,omitempty"`
}

// Action signifying something such as a button
type Action struct {
Type string `json:"type"`
SignalName string `json:"signal_name,omitempty"`
SignalValue interface{} `json:"signal_value,omitempty"`
WorkflowID string `json:"workflow_id,omitempty"`
RunID string `json:"run_id,omitempty"`
}
121 changes: 121 additions & 0 deletions x/blocks/blocks_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package blocks

import (
"encoding/json"
"testing"

"github.com/stretchr/testify/assert"
)

func make() string {

r := New(
NewMarkdownSection("## Lunch options\nWe're voting on where to order lunch today. Select the option you want to vote for."),
NewDivider(),
NewMarkdownSection("## Votes\n ... vote table"),
NewMarkdownSection("## Menu\n ... menu options"),
NewSignalActions(
NewSignalButton("Farmhouse", "lunch_order", map[string]string{"location": "farmhouse - red thai curry", "requests": "spicy"}),
NewSignalButtonWithExternalWorkflow("Ethiopian", "no_lunch_order_walk_in_person", nil, "in-person-order-workflow", ""),
NewSignalButton("Ler Ros", "lunch_order", map[string]string{"location": "Ler Ros", "meal": "tofo Bahn Mi"}),
),
)

d, err := json.Marshal(r)
if err != nil {
panic(err)
}
return string(d)
}

func TestExample(t *testing.T) {
expectedJSON := `
{
"cadenceResponseType": "formattedData",
"format": "blocks",
"blocks": [
{
"type": "section",
"format": "text/markdown",
"componentOptions": {
"text": "## Lunch options\nWe're voting on where to order lunch today. Select the option you want to vote for."
}
},
{
"type": "divider"
},
{
"type": "section",
"format": "text/markdown",
"componentOptions": {
"text": "## Votes\n ... vote table"
}
},
{
"type": "section",
"format": "text/markdown",
"componentOptions": {
"text": "## Menu\n ... menu options"
}
},
{
"type": "actions",
"elements": [
{
"type": "button",
"componentOptions": {
"type": "plain_text",
"text": "Farmhouse"
},
"action": {
"type": "signal",
"signal_name": "lunch_order",
"signal_value": {
"location": "farmhouse - red thai curry",
"requests": "spicy"
}
}
},
{
"type": "button",
"componentOptions": {
"type": "plain_text",
"text": "Ethiopian"
},
"action": {
"type": "signal",
"signal_name": "no_lunch_order_walk_in_person",
"workflow_id": "in-person-order-workflow"
}
},
{
"type": "button",
"componentOptions": {
"type": "plain_text",
"text": "Ler Ros"
},
"action": {
"type": "signal",
"signal_name": "lunch_order",
"signal_value": {
"location": "Ler Ros",
"meal": "tofo Bahn Mi"
}
}
}
]
}
]
}
`

var expected interface{}
_ = json.Unmarshal([]byte(expectedJSON), &expected)

var actual interface{}

example := make()
_ = json.Unmarshal([]byte(example), &actual)

assert.Equal(t, expected, actual)
}
Loading
Loading