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

docs: document custom step usage #2198

Merged
merged 34 commits into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
a050256
[WIP] Add function listener support
misscoded Nov 8, 2023
188813c
[WIP] Add foundation of CLI hooks support
misscoded Nov 10, 2023
f31d8df
[WIP] Add support for interactivity
misscoded Dec 8, 2023
3faab87
[WIP] Add attachFunctionToken opt-out option
misscoded Dec 15, 2023
b46f470
[WIP] Revert CLI hook support within Bolt
zimeg Jan 5, 2024
1b79873
Incorporate PR comments
misscoded Jan 25, 2024
630f188
Add CustomFunction test + adjust naming
misscoded Jan 25, 2024
caec4b1
Update error description
misscoded Jan 25, 2024
247b4c7
Bump the @slack/web-api version to v6.12.0
zimeg Jan 26, 2024
11a42c0
v3.17.1-customFunctionBeta.0
zimeg Jan 26, 2024
e933eda
chore: merge w main
zimeg Jun 8, 2024
87730ef
chore: merge w main
zimeg Jun 19, 2024
bea900d
Fixes and polish for stable release (#2128)
misscoded Aug 12, 2024
b10bbb0
chore: merge main
Aug 12, 2024
e3ac1eb
docs: document custom step usage
WilliamBergamin Aug 13, 2024
717028f
Remove old docs
WilliamBergamin Aug 13, 2024
f15413a
Improve readme based on feedback
WilliamBergamin Aug 13, 2024
60261d0
Merge branch 'feat-functions' into document-custom-steps
WilliamBergamin Aug 13, 2024
23ef516
[Bug] Fix chained function actions (#2200)
misscoded Aug 14, 2024
3aa9f34
Update docs/content/basic/custom-steps.md
WilliamBergamin Aug 14, 2024
5132ad4
Update docs/content/basic/custom-steps.md
WilliamBergamin Aug 14, 2024
7f14da1
Update docs/content/basic/custom-steps.md
WilliamBergamin Aug 14, 2024
90ce812
Remote functions: typescript integration test with bolt-ts-starter-te…
filmaj Aug 14, 2024
0451502
Improve based on feedback
WilliamBergamin Aug 14, 2024
0110bf2
Merge branch 'main' into feat-functions
Aug 14, 2024
960d18e
Merge branch 'feat-functions' into document-custom-steps
WilliamBergamin Aug 14, 2024
d73f717
Update docs/content/basic/custom-steps.md
WilliamBergamin Aug 14, 2024
5fa091a
Update I18
WilliamBergamin Aug 14, 2024
70240f9
Merge branch 'document-custom-steps' of https://github.com/slackapi/b…
WilliamBergamin Aug 14, 2024
56ab543
Add the definition in a dropdown
WilliamBergamin Aug 14, 2024
44da1d1
Improve example
WilliamBergamin Aug 14, 2024
b353056
Update I18 version
WilliamBergamin Aug 14, 2024
34fefa2
Remove ack from example
WilliamBergamin Aug 14, 2024
6779dbb
Merge branch 'main' into document-custom-steps
WilliamBergamin Aug 14, 2024
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
26 changes: 16 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,29 +45,33 @@ Apps typically react to a collection of incoming events, which can correspond [E
request, there's a method to build a listener function.

```js
// Listen for an action from a Block Kit element (buttons, select menus, date pickers, etc)
app.action(actionId, fn);

// Listen for dialog submissions
app.action({ callback_id: callbackId }, fn);

// Listen for slash commands
app.command(commandName, fn);

// Listen for an event from the Events API
app.event(eventType, fn);

// Listen for a custom step execution from a workflow
app.function(callbackId, fn)

// Convenience method to listen to only `message` events using a string or RegExp
app.message([pattern ,] fn);

// Listen for an action from a Block Kit element (buttons, select menus, date pickers, etc)
app.action(actionId, fn);

// Listen for dialog submissions
app.action({ callback_id: callbackId }, fn);
// Listen for options requests (from select menus with an external data source)
app.options(actionId, fn);

// Listen for a global or message shortcuts
app.shortcut(callbackId, fn);

// Listen for slash commands
app.command(commandName, fn);

// Listen for view_submission modal events
app.view(callbackId, fn);

// Listen for options requests (from select menus with an external data source)
app.options(actionId, fn);
```

## Making things happen
Expand All @@ -83,6 +87,8 @@ Most of the app's functionality will be inside listener functions (the `fn` para
| `respond` | Function that responds to an incoming event **if** it contains a `response_url` (actions, shortcuts, view submissions, and slash commands). `respond` returns a promise that resolves with the results of responding using the `response_url`.
| `context` | Event context. This object contains data about the event and the app, such as the `botId`. Middleware can add additional context before the event is passed to listeners.
| `body` | Object that contains the entire body of the request (superset of `payload`). Some accessory data is only available outside of the payload (such as `trigger_id` and `authorizations`).
| `complete` | Function used to signal the successful completion of a custom step execution. This tells Slack to proceed with the next steps in the workflow. This argument is only available with the `.function` and `.action` listener when handling custom workflow step executions.
| `fail` | Function used to signal that a custom step failed to complete. This tells Slack to stop the workflow execution. This argument is only available with the `.function` and `.action` listener when handling custom workflow step executions.


The arguments are grouped into properties of one object, so that it's easier to pick just the ones your listener needs (using
Expand Down
167 changes: 167 additions & 0 deletions docs/content/basic/custom-steps.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
---
title: Listening and responding to custom steps
lang: en
slug: /concepts/custom-steps
---

Your app can use the `function()` method to listen to incoming [custom step requests](https://api.slack.com/automation/functions/custom-bolt). Custom steps are used in Workflow Builder to build workflows. The method requires a step `callback_id` of type string. This `callback_id` must also be defined in your [Function](https://api.slack.com/concepts/manifests#functions) definition. Custom steps must be finalized using the `complete()` or `fail()` listener arguments to notify Slack that your app has processed the request.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
Your app can use the `function()` method to listen to incoming [custom step requests](https://api.slack.com/automation/functions/custom-bolt). Custom steps are used in Workflow Builder to build workflows. The method requires a step `callback_id` of type string. This `callback_id` must also be defined in your [Function](https://api.slack.com/concepts/manifests#functions) definition. Custom steps must be finalized using the `complete()` or `fail()` listener arguments to notify Slack that your app has processed the request.
Your app can use the `function()` listener to listen to incoming [custom step requests](https://api.slack.com/automation/functions/custom-bolt). Custom steps are used in Workflow Builder to build workflows.
Adding a custom step starts with a definition in [the app manifest](https://api.slack.com/concepts/manifests#functions) and a `function()` listener listening for the `callback_id` of that function. Upon function execution the listener is called and can run whatever code, but must finish with either the `complete()` or `fail()` listener argument to end the step:

Hoping to set a bit more context for custom steps in just the first paragraph! Also wanting to frame the following one as more instruction instead of reference, but feel free to change it or ignore 🙏

Copy link
Member

Choose a reason for hiding this comment

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

Also not certain about the "method" vs "listener" naming!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The rest of the documentation uses the method terminology instead of the listener

I think its better to be consistent and stick to method

We can send a follow up PR to update all the terms 👍


* `complete()` requires one argument: an `outputs` object. It ends your custom step **successfully** and provides an object containing the outputs of your custom step as per its definition.
* `fail()` requires **one** argument: `error` of type string. It ends your custom step **unsuccessfully** and provides a message containing information regarding why your custom step failed.

You can reference your custom step's inputs using the `inputs` listener argument.

```js
// This sample custom step formats an input and outputs it
app.function('sample_custom_step', async ({ inputs, complete, fail, logger }) => {
try {
const { message } = inputs;

await complete({
outputs: {
message: `:wave: You submitted the following message: \n\n>${message}`
}
});
} catch (error) {
logger.error(error);
await fail({ error: `Failed to handle a function request: ${error}` });
}
});
```

<details>
filmaj marked this conversation as resolved.
Show resolved Hide resolved
<summary>
Example app manifest definition
</summary>

```json
...
"functions": {
"sample_custom_step": {
"title": "Sample custom step",
"description": "Run a sample custom step",
"input_parameters": {
"message": {
"type": "string",
"title": "Message",
"description": "A message to be formatted by a custom step",
"is_required": true,
}
},
"output_parameters": {
"message": {
"type": "string",
"title": "Messge",
"description": "A formatted message",
"is_required": true,
}
}
}
}
```

</details>

---

### Listening to custom step interactivity events

Your app's custom steps may create interactivity points for users, for example: Post a message with a button

If such interaction points originate from a custom step execution, the events sent to your app representing the end-user interaction with these points are considered to be _function-scoped interactivity events_. These interactivity events can be handled by your app using the same concepts we covered earlier, such as [Listening to actions](/concepts/action-listening).

_function-scoped interactivity events_ will contain data related to the custom step (`function_executed` event) they were spawned from, such as custom step `inputs` and access to `complete()` and `fail()` listener arguments.

Your app can skip calling `complete()` or `fail()` in the `function()` handler method if the custom step creates an interaction point that requires user interaction before the step can end. However, in the relevant interactivity handler method, your app must invoke `complete()` or `fail()` to notify Slack that the custom step has been processed.

You’ll notice in all interactivity handler examples, `ack()` is used. It is required to call the `ack()` function within an interactivity listener to acknowledge that the request was received from Slack. This is discussed in the [acknowledging requests section](/concepts/acknowledge).

```js
/** This sample custom step posts a message with a button */
app.function('custom_step_button', async ({ client, inputs, fail, logger }) => {
try {
const { user_id } = inputs;

await client.chat.postMessage({
channel: user_id,
text: 'Click the button to signal the function has completed',
blocks: [
{
type: 'section',
text: {
type: 'mrkdwn',
text: 'Click the button to signal the function has completed',
},
accessory: {
type: 'button',
text: {
type: 'plain_text',
text: 'Complete function',
},
action_id: 'sample_button',
},
},
],
});
} catch (error) {
logger.error(error);
await fail({ error: `Failed to handle a function request: ${error}` });
}
});

/** Your listener will be called every time a block element with the action_id "sample_button" is triggered */
app.action('sample_button', async ({ ack, body, client, complete, fail, logger }) => {
try {
await ack();

const { channel, message, user } = body;
// Functions should be marked as successfully completed using `complete` or
// as having failed using `fail`, else they'll remain in an 'In progress' state.
await complete({ outputs: { user_id: user.id } });

await client.chat.update({
channel: channel.id,
ts: message.ts,
text: 'Function completed successfully!',
});
} catch (error) {
logger.error(error);
await fail({ error: `Failed to handle a function request: ${error}` });
}
});
```

<details>
<summary>
Example app manifest definition
</summary>

```json
...
"functions": {
"custom_step_button": {
"title": "Custom step with a button",
"description": "Custom step that waits for a button click",
"input_parameters": {
"user_id": {
"type": "slack#/types/user_id",
"title": "User",
"description": "The recipient of a message with a button",
"is_required": true,
}
},
"output_parameters": {
"user_id": {
"type": "slack#/types/user_id",
"title": "User",
"description": "The user that completed the function",
"is_required": true
}
}
}
}
```

</details>

Learn more about responding to interactivity, see the [Slack API documentation](https://api.slack.com/automation/functions/custom-bolt#interactivity).
16 changes: 0 additions & 16 deletions docs/content/custom-functions/creating-custom-functions.md

This file was deleted.

39 changes: 0 additions & 39 deletions docs/content/custom-functions/defining-custom-functions.md

This file was deleted.

25 changes: 0 additions & 25 deletions docs/content/custom-functions/listening-to-custom-functions.md

This file was deleted.

19 changes: 0 additions & 19 deletions docs/content/custom-functions/responding-to-interactivity.md

This file was deleted.

Loading