-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add functions.completeError and functions.completeSuccess (#1328)
Completion of #1301 - Adds the new complete functions for the Function Execution Event - Adds the context version of those methods --- > this PR to handle event [function_executed](https://api.slack.com/events/function_executed) and response the function with [functions.completeSuccess](https://api.slack.com/methods/functions.completeSuccess) and [functions.completeError](https://api.slack.com/methods/functions.completeError) --------- Co-authored-by: Yoga Setiawan <[email protected]>
- Loading branch information
Showing
4 changed files
with
289 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"github.com/slack-go/slack" | ||
"github.com/slack-go/slack/slackevents" | ||
"github.com/slack-go/slack/socketmode" | ||
"os" | ||
) | ||
|
||
func main() { | ||
api := slack.New( | ||
os.Getenv("SLACK_BOT_TOKEN"), | ||
slack.OptionDebug(true), | ||
slack.OptionAppLevelToken(os.Getenv("SLACK_APP_TOKEN")), | ||
) | ||
client := socketmode.New(api, socketmode.OptionDebug(true)) | ||
|
||
go func() { | ||
for evt := range client.Events { | ||
switch evt.Type { | ||
case socketmode.EventTypeEventsAPI: | ||
eventsAPIEvent, ok := evt.Data.(slackevents.EventsAPIEvent) | ||
if !ok { | ||
fmt.Printf("Ignored %+v\n", evt) | ||
continue | ||
} | ||
|
||
fmt.Printf("Event received: %+v\n", eventsAPIEvent) | ||
client.Ack(*evt.Request) | ||
|
||
switch eventsAPIEvent.Type { | ||
case slackevents.CallbackEvent: | ||
innerEvent := eventsAPIEvent.InnerEvent | ||
switch ev := innerEvent.Data.(type) { | ||
case *slackevents.FunctionExecutedEvent: | ||
callbackID := ev.Function.CallbackID | ||
if callbackID == "sample_function" { | ||
userId := ev.Inputs["user_id"] | ||
payload := map[string]string{ | ||
"user_id": userId, | ||
} | ||
|
||
err := api.FunctionCompleteSuccess(ev.FunctionExecutionID, slack.FunctionCompleteSuccessRequestOptionOutput(payload)) | ||
if err != nil { | ||
fmt.Printf("failed posting message: %v \n", err) | ||
} | ||
} | ||
} | ||
default: | ||
client.Debugf("unsupported Events API event received\n") | ||
} | ||
|
||
default: | ||
fmt.Fprintf(os.Stderr, "Unexpected event type received: %s\n", evt.Type) | ||
} | ||
} | ||
}() | ||
client.Run() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
{ | ||
"display_information": { | ||
"name": "Function Example" | ||
}, | ||
"features": { | ||
"app_home": { | ||
"home_tab_enabled": false, | ||
"messages_tab_enabled": true, | ||
"messages_tab_read_only_enabled": true | ||
}, | ||
"bot_user": { | ||
"display_name": "Function Example", | ||
"always_online": true | ||
} | ||
}, | ||
"oauth_config": { | ||
"scopes": { | ||
"bot": [ | ||
"chat:write" | ||
] | ||
} | ||
}, | ||
"settings": { | ||
"interactivity": { | ||
"is_enabled": true | ||
}, | ||
"org_deploy_enabled": true, | ||
"socket_mode_enabled": true, | ||
"token_rotation_enabled": false | ||
}, | ||
"functions": { | ||
"sample_function": { | ||
"title": "Sample function", | ||
"description": "Runs sample function", | ||
"input_parameters": { | ||
"user_id": { | ||
"type": "slack#/types/user_id", | ||
"title": "User", | ||
"description": "Message recipient", | ||
"is_required": true, | ||
"hint": "Select a user in the workspace", | ||
"name": "user_id" | ||
} | ||
}, | ||
"output_parameters": { | ||
"user_id": { | ||
"type": "slack#/types/user_id", | ||
"title": "User", | ||
"description": "User that completed the function", | ||
"is_required": true, | ||
"name": "user_id" | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
package slack | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
) | ||
|
||
type ( | ||
FunctionCompleteSuccessRequest struct { | ||
FunctionExecutionID string `json:"function_execution_id"` | ||
Outputs map[string]string `json:"outputs"` | ||
} | ||
|
||
FunctionCompleteErrorRequest struct { | ||
FunctionExecutionID string `json:"function_execution_id"` | ||
Error string `json:"error"` | ||
} | ||
) | ||
|
||
type FunctionCompleteSuccessRequestOption func(opt *FunctionCompleteSuccessRequest) error | ||
|
||
func FunctionCompleteSuccessRequestOptionOutput(outputs map[string]string) FunctionCompleteSuccessRequestOption { | ||
return func(opt *FunctionCompleteSuccessRequest) error { | ||
if len(outputs) > 0 { | ||
opt.Outputs = outputs | ||
} | ||
return nil | ||
} | ||
} | ||
|
||
// FunctionCompleteSuccess indicates function is completed | ||
func (api *Client) FunctionCompleteSuccess(functionExecutionId string, options ...FunctionCompleteSuccessRequestOption) error { | ||
return api.FunctionCompleteSuccessContext(context.Background(), functionExecutionId, options...) | ||
} | ||
|
||
// FunctionCompleteSuccess indicates function is completed | ||
func (api *Client) FunctionCompleteSuccessContext(ctx context.Context, functionExecutionId string, options ...FunctionCompleteSuccessRequestOption) error { | ||
// More information: https://api.slack.com/methods/functions.completeSuccess | ||
r := &FunctionCompleteSuccessRequest{ | ||
FunctionExecutionID: functionExecutionId, | ||
} | ||
for _, option := range options { | ||
option(r) | ||
} | ||
|
||
endpoint := api.endpoint + "functions.completeSuccess" | ||
jsonData, err := json.Marshal(r) | ||
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 | ||
} | ||
|
||
// FunctionCompleteError indicates function is completed with error | ||
func (api *Client) FunctionCompleteError(functionExecutionID string, errorMessage string) error { | ||
return api.FunctionCompleteErrorContext(context.Background(), functionExecutionID, errorMessage) | ||
} | ||
|
||
// FunctionCompleteErrorContext indicates function is completed with error | ||
func (api *Client) FunctionCompleteErrorContext(ctx context.Context, functionExecutionID string, errorMessage string) error { | ||
// More information: https://api.slack.com/methods/functions.completeError | ||
r := FunctionCompleteErrorRequest{ | ||
FunctionExecutionID: functionExecutionID, | ||
} | ||
r.Error = errorMessage | ||
|
||
endpoint := api.endpoint + "functions.completeError" | ||
jsonData, err := json.Marshal(r) | ||
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
package slack | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"io" | ||
"net/http" | ||
"testing" | ||
) | ||
|
||
func postHandler(t *testing.T) func(rw http.ResponseWriter, r *http.Request) { | ||
return func(rw http.ResponseWriter, r *http.Request) { | ||
body, err := io.ReadAll(r.Body) | ||
defer r.Body.Close() | ||
if err != nil { | ||
t.Error(err) | ||
return | ||
} | ||
|
||
var req FunctionCompleteSuccessRequest | ||
err = json.Unmarshal(body, &req) | ||
if err != nil { | ||
t.Error(err) | ||
return | ||
} | ||
|
||
switch req.FunctionExecutionID { | ||
case "function-success": | ||
postSuccess(rw, r) | ||
case "function-failure": | ||
postFailure(rw, r) | ||
} | ||
} | ||
} | ||
|
||
func postSuccess(rw http.ResponseWriter, r *http.Request) { | ||
rw.Header().Set("Content-Type", "application/json") | ||
response := []byte(`{ | ||
"ok": true | ||
}`) | ||
rw.Write(response) | ||
} | ||
|
||
func postFailure(rw http.ResponseWriter, r *http.Request) { | ||
rw.Header().Set("Content-Type", "application/json") | ||
response := []byte(`{ | ||
"ok": false, | ||
"error": "function_execution_not_found" | ||
}`) | ||
rw.Write(response) | ||
rw.WriteHeader(500) | ||
} | ||
|
||
func TestFunctionComplete(t *testing.T) { | ||
http.HandleFunc("/functions.completeSuccess", postHandler(t)) | ||
|
||
once.Do(startServer) | ||
|
||
api := New("testing-token", OptionAPIURL("http://"+serverAddr+"/")) | ||
|
||
err := api.FunctionCompleteSuccess("function-success") | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
|
||
err = api.FunctionCompleteSuccess("function-failure") | ||
if err == nil { | ||
t.Fail() | ||
} | ||
|
||
err = api.FunctionCompleteSuccessContext(context.Background(), "function-success") | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
|
||
err = api.FunctionCompleteSuccessContext(context.Background(), "function-failure") | ||
if err == nil { | ||
t.Fail() | ||
} | ||
} |