Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add functions.completeError and functions.completeSuccess #1301

Closed
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
60 changes: 60 additions & 0 deletions examples/function/function.go
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()
}
56 changes: 56 additions & 0 deletions examples/function/manifest.json
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"
}
}
}
}
}
83 changes: 83 additions & 0 deletions function_execute.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package slack
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please ensure that the WebAPI functions accept a context as the first function parameter and rename this FunctionCompleteErrorContext. You can create a second function called FunctionCompleteError that explicitly sets contact.Background() if you'd like.

You can look at ListEventAuthorizationsContext and ListEventAuthorizations for an example of how to implement it.


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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the API method returns a customer_id, please add it as a function output to most closely mirror the published API specification.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The customer_id mentioned in the documentation you linked is purely an example. The output payload is required to mirror what is specified on the evnet.

// 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(context.Background(), 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 {
// 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(context.Background(), api.httpclient, endpoint, api.token, jsonData, response, api); err != nil {
return err
}

if !response.Ok {
return response.Err()
}

return nil
}
48 changes: 48 additions & 0 deletions slackevents/inner_events.go
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,51 @@ type SharedInvite struct {
IsExternalLimited bool `json:"is_external_limited,omitempty"`
}

// FunctionExecutedEvent is sent if a function was executed - https://api.slack.com/events/function_executed
type FunctionExecutedEvent struct {
Type string `json:"type"`
Function Function `json:"function"`
Inputs map[string]string `json:"inputs"`
FunctionExecutionID string `json:"function_execution_id"`
WorkflowExecutionID string `json:"workflow_execution_id"`
EventTs string `json:"event_ts"`
BotAccessToken string `json:"bot_access_token"`
}

// Function is a struct for functions in FunctionExecuted events
type Function struct {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where did you find the documentation for FormEnabled and DateReleased? I don't see it on the event documentation page.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got it from the debug mode of the slack-go socket mode, because before that I had not found the official documentation
image
https://gist.github.com/yogasw/65d68c081bfd5bea14520cedc907a979

Id string `json:"id"`
CallbackID string `json:"callback_id"`
Title string `json:"title"`
Description string `json:"description"`
Type string `json:"type"`
InputParameters []InputParameters `json:"input_parameters"`
OutputParameters []OutputParameters `json:"output_parameters"`
AppId string `json:"app_id"`
DateCreated int `json:"date_created"`
DateReleased int `json:"date_released"`
DateUpdated int `json:"date_updated"`
DateDeleted int `json:"date_deleted"`
FormEnabled bool `json:"form_enabled"`
}

// InputParameters is a struct for input parameters in Function events
type InputParameters struct {
Type string `json:"type"`
Name string `json:"name"`
Description string `json:"description"`
Title string `json:"title"`
IsRequired bool `json:"is_required"`
}

// OutputParameters is a struct for output parameters in Function events
type OutputParameters struct {
Type string `json:"type"`
Name string `json:"name"`
Description string `json:"description"`
Title string `json:"title"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe there is a IsRequired parameter in the OutputParameter returned in the Function Event based on the example event provided in the documentation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i will update it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but why when I activate debug mode this value is not there?
https://gist.github.com/yogasw/65d68c081bfd5bea14520cedc907a979

}

type EventsAPIType string

const (
Expand Down Expand Up @@ -738,6 +783,8 @@ const (
TeamAccessRevoked = EventsAPIType("team_access_revoked")
// UserProfileChanged is sent if a user's profile information has changed.
UserProfileChanged = EventsAPIType("user_profile_changed")
// FunctionExecuted is sent if a function was executed
FunctionExecuted = EventsAPIType("function_executed")
)

// EventsAPIInnerEventMapping maps INNER Event API events to their corresponding struct
Expand Down Expand Up @@ -787,4 +834,5 @@ var EventsAPIInnerEventMapping = map[EventsAPIType]interface{}{
TeamAccessGranted: TeamAccessGrantedEvent{},
TeamAccessRevoked: TeamAccessRevokedEvent{},
UserProfileChanged: UserProfileChangedEvent{},
FunctionExecuted: FunctionExecutedEvent{},
}
Loading