diff --git a/examples/workflow_step/README.md b/examples/workflow_step/README.md deleted file mode 100644 index da378b894..000000000 --- a/examples/workflow_step/README.md +++ /dev/null @@ -1,59 +0,0 @@ -#WorkflowStep - -Have you ever wanted to run an app from a Slack workflow? This sample app shows you how it works. - -Slack describes some of the basics here: -https://api.slack.com/workflows/steps -https://api.slack.com/tutorials/workflow-builder-steps - - -1. Start the example app localy on port 8080 - - -2. Use ngrok to expose your app to the internet - -```shell - ./ngrok http 8080 -``` -Copy the https forwarding URL and paste it into the app manifest down below (event_subscription request_url and interactivity request_url) - - -3. Create a new Slack App at api.slack.com/apps from an app manifest - -The manifest of a sample Slack App looks like this: -```yaml -display_information: - name: Workflowstep-Example -features: - bot_user: - display_name: Workflowstep-Example - always_online: false - workflow_steps: - - name: Example Step - callback_id: example-step -oauth_config: - scopes: - bot: - - workflow.steps:execute -settings: - event_subscriptions: - request_url: https://*****.ngrok.io/api/v1/example-step - bot_events: - - workflow_step_execute - interactivity: - is_enabled: true - request_url: https://*****.ngrok.io/api/v1/interaction - org_deploy_enabled: false - socket_mode_enabled: false - token_rotation_enabled: false -``` - -("Interactivity" and "Enable Events" should be turned on) - -4. Slack Workflow (**paid plan required!**) - 1. Create a new Workflow at app.slack.com/workflow-builder - 2. give it a name - 3. select "Planned date & time" - 4. add another step and select "Example Step" from App Workflowstep-Example - 5. configure your app and hit save - 6. don't forget to publish your workflow \ No newline at end of file diff --git a/examples/workflow_step/go.mod b/examples/workflow_step/go.mod deleted file mode 100644 index 1df243ce5..000000000 --- a/examples/workflow_step/go.mod +++ /dev/null @@ -1,9 +0,0 @@ -module workflowstep-example - -go 1.17 - -require ( - github.com/gorilla/websocket v1.4.2 // indirect - github.com/pkg/errors v0.9.1 // indirect - github.com/slack-go/slack v0.10.1 // indirect -) diff --git a/examples/workflow_step/go.sum b/examples/workflow_step/go.sum deleted file mode 100644 index e95148d32..000000000 --- a/examples/workflow_step/go.sum +++ /dev/null @@ -1,11 +0,0 @@ -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= -github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/slack-go/slack v0.10.1 h1:BGbxa0kMsGEvLOEoZmYs8T1wWfoZXwmQFBb6FgYCXUA= -github.com/slack-go/slack v0.10.1/go.mod h1:wWL//kk0ho+FcQXcBTmEafUI5dz4qz5f4mMk8oIkioQ= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= diff --git a/examples/workflow_step/handler.go b/examples/workflow_step/handler.go deleted file mode 100644 index d0eee101f..000000000 --- a/examples/workflow_step/handler.go +++ /dev/null @@ -1,211 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "io" - "log" - "net/http" - "net/url" - "time" - - "github.com/slack-go/slack" - "github.com/slack-go/slack/slackevents" -) - -const ( - IDSelectOptionBlock = "select-option-block" - IDExampleSelectInput = "example-select-input" -) - -func handleMyWorkflowStep(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - w.WriteHeader(http.StatusMethodNotAllowed) - return - } - - // see: https://github.com/slack-go/slack/blob/master/examples/eventsapi/events.go - body, err := io.ReadAll(r.Body) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - return - } - - eventsAPIEvent, err := slackevents.ParseEvent(json.RawMessage(body), slackevents.OptionNoVerifyToken()) - if err != nil { - log.Printf("[ERROR] Failed on parsing event: %s", err.Error()) - w.WriteHeader(http.StatusInternalServerError) - return - } - - // see: https://api.slack.com/apis/connections/events-api#subscriptions - if eventsAPIEvent.Type == slackevents.URLVerification { - var r *slackevents.ChallengeResponse - err := json.Unmarshal([]byte(body), &r) - if err != nil { - log.Printf("[ERROR] Failed to decode json message on event url_verification: %s", err.Error()) - w.WriteHeader(http.StatusInternalServerError) - return - } - - w.Header().Set("Content-Type", "text") - w.Write([]byte(r.Challenge)) - return - } - - // see: https://api.slack.com/apis/connections/events-api#receiving_events - if eventsAPIEvent.Type == slackevents.CallbackEvent { - innerEvent := eventsAPIEvent.InnerEvent - - switch ev := innerEvent.Data.(type) { - - // see: https://api.slack.com/events/workflow_step_execute - case *slackevents.WorkflowStepExecuteEvent: - if ev.CallbackID == MyExampleWorkflowStepCallbackID { - go doHeavyLoad(ev.WorkflowStep) - - w.WriteHeader(http.StatusOK) - return - } - w.WriteHeader(http.StatusBadRequest) - log.Printf("[WARN] unknown callbackID: %s", ev.CallbackID) - return - - default: - w.WriteHeader(http.StatusBadRequest) - log.Printf("[WARN] unknown inner event type: %s", eventsAPIEvent.InnerEvent.Type) - return - } - } - - w.WriteHeader(http.StatusBadRequest) - log.Printf("[WARN] unknown event type: %s", eventsAPIEvent.Type) -} - -func handleInteraction(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - w.WriteHeader(http.StatusMethodNotAllowed) - return - } - - body, err := io.ReadAll(r.Body) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - return - } - - jsonStr, err := url.QueryUnescape(string(body)[8:]) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - return - } - - var message slack.InteractionCallback - if err := json.Unmarshal([]byte(jsonStr), &message); err != nil { - log.Printf("[ERROR] Failed to decode json message from slack: %s", jsonStr) - w.WriteHeader(http.StatusInternalServerError) - return - } - - switch message.Type { - case slack.InteractionTypeWorkflowStepEdit: - // https://api.slack.com/workflows/steps#handle_config_view - err := replyWithConfigurationView(message, "", "") - if err != nil { - log.Printf("[ERROR] Failed to open configuration modal in slack: %s", err.Error()) - } - - case slack.InteractionTypeViewSubmission: - // https://api.slack.com/workflows/steps#handle_view_submission - - // process user inputs - // this is just for demonstration, so we print it to console only - blockAction := message.View.State.Values - selectedOption := blockAction[IDSelectOptionBlock][IDExampleSelectInput].SelectedOption.Value - log.Println(fmt.Sprintf("user selected: %s", selectedOption)) - - in := &slack.WorkflowStepInputs{ - IDExampleSelectInput: slack.WorkflowStepInputElement{ - Value: selectedOption, - SkipVariableReplacement: false, - }, - } - - err := saveUserSettingsForWorkflowStep(message.WorkflowStep.WorkflowStepEditID, in, nil) - if err != nil { - log.Printf("[ERROR] Failed on doing a POST request to workflows.updateStep: %s", err.Error()) - w.WriteHeader(http.StatusInternalServerError) - } - - default: - log.Printf("[WARN] unknown message type: %s", message.Type) - w.WriteHeader(http.StatusInternalServerError) - } -} - -func replyWithConfigurationView(message slack.InteractionCallback, privateMetaData string, externalID string) error { - headerText := slack.NewTextBlockObject("mrkdwn", "Hello World!\nThis is your workflow step app configuration view", false, false) - headerSection := slack.NewSectionBlock(headerText, nil, nil) - - options := []*slack.OptionBlockObject{} - options = append( - options, - slack.NewOptionBlockObject("one", slack.NewTextBlockObject("plain_text", "One", false, false), nil), - ) - - options = append( - options, - slack.NewOptionBlockObject("two", slack.NewTextBlockObject("plain_text", "Two", false, false), nil), - ) - - options = append( - options, - slack.NewOptionBlockObject("three", slack.NewTextBlockObject("plain_text", "Three", false, false), nil), - ) - - selection := slack.NewOptionsSelectBlockElement( - "static_select", - slack.NewTextBlockObject("plain_text", "your choice", false, false), - IDExampleSelectInput, - options..., - ) - - // preselect option, if workflow step input is defined - initialOption, ok := slack.GetInitialOptionFromWorkflowStepInput(selection, message.WorkflowStep.Inputs, options) - if ok { - selection.InitialOption = initialOption - } - - inputBlock := slack.NewInputBlock( - IDSelectOptionBlock, - slack.NewTextBlockObject("plain_text", "Select an option", false, false), - selection, - ) - - blocks := slack.Blocks{ - BlockSet: []slack.Block{ - headerSection, - inputBlock, - }, - } - - cmr := slack.NewConfigurationModalRequest(blocks, privateMetaData, externalID) - _, err := appCtx.slack.OpenView(message.TriggerID, cmr.ModalViewRequest) - return err -} - -func saveUserSettingsForWorkflowStep(workflowStepEditID string, inputs *slack.WorkflowStepInputs, outputs *[]slack.WorkflowStepOutput) error { - return appCtx.slack.SaveWorkflowStepConfiguration(workflowStepEditID, inputs, outputs) -} - -func doHeavyLoad(workflowStep slackevents.EventWorkflowStep) { - // process user configuration e.g. inputs - log.Printf("Inputs:") - for name, input := range *workflowStep.Inputs { - log.Printf(fmt.Sprintf("%s: %s", name, input.Value)) - } - - // do heavy load - time.Sleep(10 * time.Second) - log.Println("Done") -} diff --git a/examples/workflow_step/main.go b/examples/workflow_step/main.go deleted file mode 100644 index 45494d086..000000000 --- a/examples/workflow_step/main.go +++ /dev/null @@ -1,48 +0,0 @@ -package main - -import ( - "fmt" - "log" - "net/http" - "os" - - "github.com/slack-go/slack" -) - -type ( - appContext struct { - slack *slack.Client - config configuration - } - configuration struct { - botToken string - signingSecret string - } - SecretsVerifierMiddleware struct { - handler http.Handler - } -) - -const ( - APIBaseURL = "/api/v1" - // MyExampleWorkflowStepCallbackID is configured in slack (api.slack.com/apps). - // Select your app or create a new one. Then choose menu "Workflow Steps"... - MyExampleWorkflowStepCallbackID = "example-step" -) - -var appCtx appContext - -func main() { - appCtx.config.botToken = os.Getenv("SLACK_BOT_TOKEN") - appCtx.config.signingSecret = os.Getenv("SLACK_SIGNING_SECRET") - - appCtx.slack = slack.New(appCtx.config.botToken) - - mux := http.NewServeMux() - mux.HandleFunc(fmt.Sprintf("%s/interaction", APIBaseURL), handleInteraction) - mux.HandleFunc(fmt.Sprintf("%s/%s", APIBaseURL, MyExampleWorkflowStepCallbackID), handleMyWorkflowStep) - middleware := NewSecretsVerifierMiddleware(mux) - - log.Printf("starting server on :8080") - log.Fatal(http.ListenAndServe(":8080", middleware)) -} diff --git a/examples/workflow_step/middleware.go b/examples/workflow_step/middleware.go deleted file mode 100644 index 6fe826910..000000000 --- a/examples/workflow_step/middleware.go +++ /dev/null @@ -1,41 +0,0 @@ -package main - -import ( - "bytes" - "io" - "net/http" - - "github.com/slack-go/slack" -) - -func (v *SecretsVerifierMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) { - body, err := io.ReadAll(r.Body) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - return - } - r.Body.Close() - r.Body = io.NopCloser(bytes.NewBuffer(body)) - - sv, err := slack.NewSecretsVerifier(r.Header, appCtx.config.signingSecret) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - return - } - - if _, err := sv.Write(body); err != nil { - w.WriteHeader(http.StatusInternalServerError) - return - } - - if err := sv.Ensure(); err != nil { - w.WriteHeader(http.StatusUnauthorized) - return - } - - v.handler.ServeHTTP(w, r) -} - -func NewSecretsVerifierMiddleware(h http.Handler) *SecretsVerifierMiddleware { - return &SecretsVerifierMiddleware{h} -} diff --git a/interactions.go b/interactions.go index 8c6414707..abd0d55d3 100644 --- a/interactions.go +++ b/interactions.go @@ -33,30 +33,29 @@ const ( // 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"` - IsEnterpriseInstall bool `json:"is_enterprise_install"` - WorkflowStep InteractionWorkflowStep `json:"workflow_step"` + 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"` + IsEnterpriseInstall bool `json:"is_enterprise_install"` DialogSubmissionCallback ViewSubmissionCallback ViewClosedCallback @@ -137,14 +136,6 @@ 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 { diff --git a/slackevents/inner_events.go b/slackevents/inner_events.go index 720640b63..300f2562f 100644 --- a/slackevents/inner_events.go +++ b/slackevents/inner_events.go @@ -403,14 +403,6 @@ 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"` - EventTimestamp string `json:"event_ts"` -} - // MessageMetadataPostedEvent is sent, if a message with metadata is posted type MessageMetadataPostedEvent struct { Type string `json:"type"` @@ -452,15 +444,6 @@ type MessageMetadataDeletedEvent struct { DeletedTimestamp string `json:"deleted_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 @@ -1344,7 +1327,6 @@ var EventsAPIInnerEventMapping = map[EventsAPIType]interface{}{ TeamJoin: TeamJoinEvent{}, TokensRevoked: TokensRevokedEvent{}, EmojiChanged: EmojiChangedEvent{}, - WorkflowStepExecute: WorkflowStepExecuteEvent{}, MessageMetadataPosted: MessageMetadataPostedEvent{}, MessageMetadataUpdated: MessageMetadataUpdatedEvent{}, MessageMetadataDeleted: MessageMetadataDeletedEvent{}, diff --git a/slackevents/inner_events_test.go b/slackevents/inner_events_test.go index baa4e2c08..62bc7a8a6 100644 --- a/slackevents/inner_events_test.go +++ b/slackevents/inner_events_test.go @@ -649,67 +649,6 @@ func TestEmojiChanged(t *testing.T) { } } -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() - } - } -} - func TestMessageMetadataPosted(t *testing.T) { rawE := []byte(` { diff --git a/workflow_step.go b/workflow_step.go deleted file mode 100644 index 747a5dc00..000000000 --- a/workflow_step.go +++ /dev/null @@ -1,101 +0,0 @@ -package slack - -import ( - "context" - "encoding/json" -) - -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, - }, - } -} - -// SaveWorkflowStepConfiguration opens a configuration modal for a workflow step. -// For more information see the SaveWorkflowStepConfigurationContext documentation. -func (api *Client) SaveWorkflowStepConfiguration(workflowStepEditID string, inputs *WorkflowStepInputs, outputs *[]WorkflowStepOutput) error { - return api.SaveWorkflowStepConfigurationContext(context.Background(), workflowStepEditID, inputs, outputs) -} - -// SaveWorkflowStepConfigurationContext saves the configuration of a workflow step with a custom context. -// Slack API docs: https://api.slack.com/methods/workflows.updateStep -func (api *Client) SaveWorkflowStepConfigurationContext(ctx context.Context, workflowStepEditID string, inputs *WorkflowStepInputs, outputs *[]WorkflowStepOutput) error { - 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(ctx, api.httpclient, endpoint, api.token, jsonData, response, api); err != nil { - return err - } - - if !response.Ok { - return response.Err() - } - - 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 -} diff --git a/workflow_step_execute.go b/workflow_step_execute.go deleted file mode 100644 index 32db9ba0b..000000000 --- a/workflow_step_execute.go +++ /dev/null @@ -1,85 +0,0 @@ -package slack - -import ( - "context" - "encoding/json" -) - -type ( - WorkflowStepCompletedRequest struct { - WorkflowStepExecuteID string `json:"workflow_step_execute_id"` - Outputs map[string]string `json:"outputs"` - } - - WorkflowStepFailedRequest struct { - WorkflowStepExecuteID string `json:"workflow_step_execute_id"` - Error struct { - Message string `json:"message"` - } `json:"error"` - } -) - -type WorkflowStepCompletedRequestOption func(opt *WorkflowStepCompletedRequest) error - -func WorkflowStepCompletedRequestOptionOutput(outputs map[string]string) WorkflowStepCompletedRequestOption { - return func(opt *WorkflowStepCompletedRequest) error { - if len(outputs) > 0 { - opt.Outputs = outputs - } - return nil - } -} - -// WorkflowStepCompleted indicates step is completed -func (api *Client) WorkflowStepCompleted(workflowStepExecuteID string, options ...WorkflowStepCompletedRequestOption) error { - // More information: https://api.slack.com/methods/workflows.stepCompleted - r := &WorkflowStepCompletedRequest{ - WorkflowStepExecuteID: workflowStepExecuteID, - } - for _, option := range options { - option(r) - } - - endpoint := api.endpoint + "workflows.stepCompleted" - jsonData, err := json.Marshal(r) - 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 response.Err() - } - - return nil -} - -// WorkflowStepFailed indicates step is failed -func (api *Client) WorkflowStepFailed(workflowStepExecuteID string, errorMessage string) error { - // More information: https://api.slack.com/methods/workflows.stepFailed - r := WorkflowStepFailedRequest{ - WorkflowStepExecuteID: workflowStepExecuteID, - } - r.Error.Message = errorMessage - - endpoint := api.endpoint + "workflows.stepFailed" - jsonData, err := json.Marshal(r) - 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 response.Err() - } - - return nil -} diff --git a/workflow_step_execute_test.go b/workflow_step_execute_test.go deleted file mode 100644 index 60c21c8ec..000000000 --- a/workflow_step_execute_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package slack - -import ( - "encoding/json" - "net/http" - "testing" -) - -func workflowStepHandler(rw http.ResponseWriter, r *http.Request) { - rw.Header().Set("Content-Type", "application/json") - response, _ := json.Marshal(SlackResponse{ - Ok: true, - }) - rw.Write(response) -} - -func TestWorkflowStepCompleted(t *testing.T) { - http.HandleFunc("/workflows.stepCompleted", workflowStepHandler) - once.Do(startServer) - api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/")) - - if err := api.WorkflowStepCompleted("executeID"); err != nil { - t.Errorf("Unexpected error: %s", err) - } -} - -func TestWorkflowStepFailed(t *testing.T) { - http.HandleFunc("/workflows.stepFailed", workflowStepHandler) - once.Do(startServer) - api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/")) - - if err := api.WorkflowStepFailed("executeID", "error message"); err != nil { - t.Errorf("Unexpected error: %s", err) - } -} diff --git a/workflow_step_test.go b/workflow_step_test.go deleted file mode 100644 index f2d2eb733..000000000 --- a/workflow_step_test.go +++ /dev/null @@ -1,190 +0,0 @@ -package slack - -import ( - "testing" - - "github.com/google/go-cmp/cmp" -) - -const ( - IDExampleSelectInput = "ae9642ae-a9ef-4394-904b-a5c7a83bf4a6" - IDSelectOptionBlock = "832bf7af-22ea-4acb-82e3-a0cc3722052b" -) - -func TestNewConfigurationModalRequest(t *testing.T) { - blocks := configModalBlocks() - privateMetaData := "An optional string that will be sent to your app in view_submission and block_actions events. Max length of 3000 characters." - externalID := "c4baf441-fbc1-4131-b349-7c8df0ae7df6" - - result := NewConfigurationModalRequest(blocks, privateMetaData, externalID) - - if result.ModalViewRequest.Title != nil { - t.Fail() - } - if result.PrivateMetadata != privateMetaData { - t.Fail() - } - if result.ExternalID != externalID { - t.Fail() - } -} - -func TestGetInitialOptionFromWorkflowStepInput(t *testing.T) { - options, testOption := createOptionBlockObjects() - selection := createSelection(options) - - scenarios := []struct { - options []*OptionBlockObject - inputs *WorkflowStepInputs - expectedResult *OptionBlockObject - expectedFlag bool - }{ - { - options: options, - inputs: createWorkflowStepInputs1(), - expectedResult: &OptionBlockObject{}, - expectedFlag: false, - }, - { - options: []*OptionBlockObject{}, - inputs: createWorkflowStepInputs4(testOption.Value), - expectedResult: &OptionBlockObject{}, - expectedFlag: false, - }, - { - options: options, - inputs: createWorkflowStepInputs2(), - expectedResult: &OptionBlockObject{}, - expectedFlag: false, - }, - { - options: options, - inputs: createWorkflowStepInputs3(), - expectedResult: &OptionBlockObject{}, - expectedFlag: false, - }, - { - options: options, - inputs: createWorkflowStepInputs4(testOption.Value), - expectedResult: testOption, - expectedFlag: true, - }, - } - - for _, scenario := range scenarios { - result, ok := GetInitialOptionFromWorkflowStepInput(selection, scenario.inputs, scenario.options) - if ok != scenario.expectedFlag { - t.Fail() - } - - if !cmp.Equal(result, scenario.expectedResult) { - t.Fail() - } - } -} - -func createOptionBlockObjects() ([]*OptionBlockObject, *OptionBlockObject) { - var options []*OptionBlockObject - options = append( - options, - NewOptionBlockObject("one", NewTextBlockObject("plain_text", "One", false, false), nil), - ) - - option2 := NewOptionBlockObject("two", NewTextBlockObject("plain_text", "Two", false, false), nil) - options = append( - options, - option2, - ) - - options = append( - options, - NewOptionBlockObject("three", NewTextBlockObject("plain_text", "Three", false, false), nil), - ) - - return options, option2 -} - -func createSelection(options []*OptionBlockObject) *SelectBlockElement { - return NewOptionsSelectBlockElement( - "static_select", - NewTextBlockObject("plain_text", "your choice", false, false), - IDExampleSelectInput, - options..., - ) -} - -func configModalBlocks() Blocks { - headerText := NewTextBlockObject("mrkdwn", "Hello World!\nThis is your workflow step app configuration view", false, false) - headerSection := NewSectionBlock(headerText, nil, nil) - - options, _ := createOptionBlockObjects() - - selection := createSelection(options) - - inputBlock := NewInputBlock( - IDSelectOptionBlock, - NewTextBlockObject("plain_text", "Select an option", false, false), - NewTextBlockObject("plain_text", "Hint", false, false), - selection, - ) - - blocks := Blocks{ - BlockSet: []Block{ - headerSection, - inputBlock, - }, - } - - return blocks -} - -func createWorkflowStepInputs1() *WorkflowStepInputs { - return &WorkflowStepInputs{} -} - -func createWorkflowStepInputs2() *WorkflowStepInputs { - return &WorkflowStepInputs{ - "test": WorkflowStepInputElement{ - Value: "random-string", - SkipVariableReplacement: false, - }, - "123-test": WorkflowStepInputElement{ - Value: "another-string", - SkipVariableReplacement: false, - }, - } -} - -func createWorkflowStepInputs3() *WorkflowStepInputs { - return &WorkflowStepInputs{ - "test": WorkflowStepInputElement{ - Value: "random-string", - SkipVariableReplacement: false, - }, - "123-test": WorkflowStepInputElement{ - Value: "another-string", - SkipVariableReplacement: false, - }, - IDExampleSelectInput: WorkflowStepInputElement{ - Value: "lorem-ipsum", - SkipVariableReplacement: true, - }, - } -} - -func createWorkflowStepInputs4(optionValue string) *WorkflowStepInputs { - return &WorkflowStepInputs{ - "test": WorkflowStepInputElement{ - Value: "random-string", - SkipVariableReplacement: false, - }, - "123-test": WorkflowStepInputElement{ - Value: "another-string", - SkipVariableReplacement: false, - }, - IDExampleSelectInput: WorkflowStepInputElement{ - Value: optionValue, - SkipVariableReplacement: false, - }, - } -}