Skip to content

Commit

Permalink
introduce workflow step app functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
Steffen Mahler committed Feb 28, 2022
1 parent f6658aa commit 486fde5
Show file tree
Hide file tree
Showing 5 changed files with 209 additions and 23 deletions.
54 changes: 32 additions & 22 deletions interactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,32 +28,34 @@ const (
InteractionTypeViewSubmission = InteractionType("view_submission")
InteractionTypeViewClosed = InteractionType("view_closed")
InteractionTypeShortcut = InteractionType("shortcut")
InteractionTypeWorkflowStepEdit = InteractionType("workflow_step_edit")
)

// InteractionCallback is sent from slack when a user interactions with a button or dialog.
type InteractionCallback struct {
Type InteractionType `json:"type"`
Token string `json:"token"`
CallbackID string `json:"callback_id"`
ResponseURL string `json:"response_url"`
TriggerID string `json:"trigger_id"`
ActionTs string `json:"action_ts"`
Team Team `json:"team"`
Channel Channel `json:"channel"`
User User `json:"user"`
OriginalMessage Message `json:"original_message"`
Message Message `json:"message"`
Name string `json:"name"`
Value string `json:"value"`
MessageTs string `json:"message_ts"`
AttachmentID string `json:"attachment_id"`
ActionCallback ActionCallbacks `json:"actions"`
View View `json:"view"`
ActionID string `json:"action_id"`
APIAppID string `json:"api_app_id"`
BlockID string `json:"block_id"`
Container Container `json:"container"`
Enterprise Enterprise `json:"enterprise"`
Type InteractionType `json:"type"`
Token string `json:"token"`
CallbackID string `json:"callback_id"`
ResponseURL string `json:"response_url"`
TriggerID string `json:"trigger_id"`
ActionTs string `json:"action_ts"`
Team Team `json:"team"`
Channel Channel `json:"channel"`
User User `json:"user"`
OriginalMessage Message `json:"original_message"`
Message Message `json:"message"`
Name string `json:"name"`
Value string `json:"value"`
MessageTs string `json:"message_ts"`
AttachmentID string `json:"attachment_id"`
ActionCallback ActionCallbacks `json:"actions"`
View View `json:"view"`
ActionID string `json:"action_id"`
APIAppID string `json:"api_app_id"`
BlockID string `json:"block_id"`
Container Container `json:"container"`
Enterprise Enterprise `json:"enterprise"`
WorkflowStep InteractionWorkflowStep `json:"workflow_step"`
DialogSubmissionCallback
ViewSubmissionCallback
ViewClosedCallback
Expand Down Expand Up @@ -134,6 +136,14 @@ type Enterprise struct {
Name string `json:"name"`
}

type InteractionWorkflowStep struct {
WorkflowStepEditID string `json:"workflow_step_edit_id,omitempty"`
WorkflowID string `json:"workflow_id"`
StepID string `json:"step_id"`
Inputs *WorkflowStepInputs `json:"inputs,omitempty"`
Outputs *[]WorkflowStepOutput `json:"outputs,omitempty"`
}

// ActionCallback is a convenience struct defined to allow dynamic unmarshalling of
// the "actions" value in Slack's JSON response, which varies depending on block type
type ActionCallbacks struct {
Expand Down
20 changes: 20 additions & 0 deletions slackevents/inner_events.go
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,23 @@ type EmojiChangedEvent struct {
Value string `json:"value,omitempty"`
}

// WorkflowStepExecuteEvent is fired, if a workflow step of your app is invoked
type WorkflowStepExecuteEvent struct {
Type string `json:"type"`
CallbackID string `json:"callback_id"`
WorkflowStep EventWorkflowStep `json:"workflow_step"`
EventTS string `json:"event_ts"`
}

type EventWorkflowStep struct {
WorkflowStepExecuteID string `json:"workflow_step_execute_id"`
WorkflowID string `json:"workflow_id"`
WorkflowInstanceID string `json:"workflow_instance_id"`
StepID string `json:"step_id"`
Inputs *slack.WorkflowStepInputs `json:"inputs,omitempty"`
Outputs *[]slack.WorkflowStepOutput `json:"outputs,omitempty"`
}

// JSONTime exists so that we can have a String method converting the date
type JSONTime int64

Expand Down Expand Up @@ -469,6 +486,8 @@ const (
TokensRevoked = "tokens_revoked"
// EmojiChanged A custom emoji has been added or changed
EmojiChanged = "emoji_changed"
// WorkflowStepExecute Happens, if a workflow step of your app is invoked
WorkflowStepExecute = "workflow_step_execute"
)

// EventsAPIInnerEventMapping maps INNER Event API events to their corresponding struct
Expand Down Expand Up @@ -503,4 +522,5 @@ var EventsAPIInnerEventMapping = map[string]interface{}{
TeamJoin: TeamJoinEvent{},
TokensRevoked: TokensRevokedEvent{},
EmojiChanged: EmojiChangedEvent{},
WorkflowStepExecute: WorkflowStepExecuteEvent{},
}
61 changes: 61 additions & 0 deletions slackevents/inner_events_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -432,3 +432,64 @@ func TestEmojiChanged(t *testing.T) {
t.Fail()
}
}

func TestWorkflowStepExecute(t *testing.T) {
// see: https://api.slack.com/events/workflow_step_execute
rawE := []byte(`
{
"type":"workflow_step_execute",
"callback_id":"open_ticket",
"workflow_step":{
"workflow_step_execute_id":"1036669284371.19077474947.c94bcf942e047298d21f89faf24f1326",
"workflow_id":"123456789012345678",
"workflow_instance_id":"987654321098765432",
"step_id":"12a345bc-1a23-4567-8b90-1234a567b8c9",
"inputs":{
"example-select-input":{
"value": "value-two",
"skip_variable_replacement": false
}
},
"outputs":[
]
},
"event_ts":"1643290847.766536"
}
`)

wse := WorkflowStepExecuteEvent{}
err := json.Unmarshal(rawE, &wse)
if err != nil {
t.Error(err)
}

if wse.Type != "workflow_step_execute" {
t.Fail()
}
if wse.CallbackID != "open_ticket" {
t.Fail()
}
if wse.WorkflowStep.WorkflowStepExecuteID != "1036669284371.19077474947.c94bcf942e047298d21f89faf24f1326" {
t.Fail()
}
if wse.WorkflowStep.WorkflowID != "123456789012345678" {
t.Fail()
}
if wse.WorkflowStep.WorkflowInstanceID != "987654321098765432" {
t.Fail()
}
if wse.WorkflowStep.StepID != "12a345bc-1a23-4567-8b90-1234a567b8c9" {
t.Fail()
}
if len(*wse.WorkflowStep.Inputs) == 0 {
t.Fail()
}
if inputElement, ok := (*wse.WorkflowStep.Inputs)["example-select-input"]; ok {
if inputElement.Value != "value-two" {
t.Fail()
}
if inputElement.SkipVariableReplacement != false {
t.Fail()
}
}
}
2 changes: 1 addition & 1 deletion views.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func NewErrorsViewSubmissionResponse(errors map[string]string) *ViewSubmissionRe

type ModalViewRequest struct {
Type ViewType `json:"type"`
Title *TextBlockObject `json:"title"`
Title *TextBlockObject `json:"title,omitempty"`
Blocks Blocks `json:"blocks"`
Close *TextBlockObject `json:"close,omitempty"`
Submit *TextBlockObject `json:"submit,omitempty"`
Expand Down
95 changes: 95 additions & 0 deletions workflowStep.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package slack

import (
"context"
"encoding/json"
"fmt"
)

const VTWorkflowStep ViewType = "workflow_step"

type (
ConfigurationModalRequest struct {
ModalViewRequest
}

WorkflowStepCompleteResponse struct {
WorkflowStepEditID string `json:"workflow_step_edit_id"`
Inputs *WorkflowStepInputs `json:"inputs,omitempty"`
Outputs *[]WorkflowStepOutput `json:"outputs,omitempty"`
}

WorkflowStepInputElement struct {
Value string `json:"value"`
SkipVariableReplacement bool `json:"skip_variable_replacement"`
}

WorkflowStepInputs map[string]WorkflowStepInputElement

WorkflowStepOutput struct {
Name string `json:"name"`
Type string `json:"type"`
Label string `json:"label"`
}
)

func NewConfigurationModalRequest(blocks Blocks, privateMetaData string, externalID string) *ConfigurationModalRequest {
return &ConfigurationModalRequest{
ModalViewRequest{
Type: VTWorkflowStep,
Title: nil, // slack configuration modal must not have a title!
Blocks: blocks,
PrivateMetadata: privateMetaData,
ExternalID: externalID,
},
}
}

func (api *Client) SaveWorkflowStepConfiguration(workflowStepEditID string, inputs *WorkflowStepInputs, outputs *[]WorkflowStepOutput) error {
// More information: https://api.slack.com/methods/workflows.updateStep
wscr := WorkflowStepCompleteResponse{
WorkflowStepEditID: workflowStepEditID,
Inputs: inputs,
Outputs: outputs,
}

endpoint := api.endpoint + "workflows.updateStep"
jsonData, err := json.Marshal(wscr)
if err != nil {
return err
}

response := &SlackResponse{}
if err := postJSON(context.Background(), api.httpclient, endpoint, api.token, jsonData, response, api); err != nil {
return err
}

if !response.Ok {
return fmt.Errorf(" %s", response.Error)
}

return nil
}

func GetInitialOptionFromWorkflowStepInput(selection *SelectBlockElement, inputs *WorkflowStepInputs, options []*OptionBlockObject) (*OptionBlockObject, bool) {
if len(*inputs) == 0 {
return &OptionBlockObject{}, false
}
if len(options) == 0 {
return &OptionBlockObject{}, false
}

if val, ok := (*inputs)[selection.ActionID]; ok {
if val.SkipVariableReplacement {
return &OptionBlockObject{}, false
}

for _, option := range options {
if option.Value == val.Value {
return option, true
}
}
}

return &OptionBlockObject{}, false
}

0 comments on commit 486fde5

Please sign in to comment.