-
Notifications
You must be signed in to change notification settings - Fork 399
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
Changes from 32 commits
a050256
188813c
f31d8df
3faab87
b46f470
1b79873
630f188
caec4b1
247b4c7
11a42c0
e933eda
87730ef
bea900d
b10bbb0
e3ac1eb
717028f
f15413a
60261d0
23ef516
3aa9f34
5132ad4
7f14da1
90ce812
0451502
0110bf2
960d18e
d73f717
5fa091a
70240f9
56ab543
44da1d1
b353056
34fefa2
6779dbb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
# This workflow runs a TypeScript compilation against slack sample apps built on top of bolt-js | ||
name: Samples Integration Type-checking | ||
|
||
on: | ||
push: | ||
branches: [main] | ||
pull_request: | ||
|
||
jobs: | ||
samples: | ||
runs-on: ubuntu-latest | ||
strategy: | ||
matrix: | ||
node-version: [18.x, 20.x, 22.x] | ||
sample: | ||
- slack-samples/bolt-ts-starter-template | ||
|
||
steps: | ||
- name: Use Node.js ${{ matrix.node-version }} | ||
uses: actions/setup-node@v4 | ||
with: | ||
node-version: ${{ matrix.node-version }} | ||
- name: Checkout bolt-js | ||
uses: actions/checkout@v4 | ||
with: | ||
path: ./bolt-js | ||
- name: Install and link bolt-js | ||
working-directory: ./bolt-js | ||
run: npm i && npm link . | ||
- name: Checkout ${{ matrix.sample }} | ||
uses: actions/checkout@v4 | ||
with: | ||
repository: ${{ matrix.sample }} | ||
path: ./sample | ||
- name: Install sample dependencies and link bolt-js | ||
working-directory: ./sample | ||
run: npm i && npm link @slack/bolt | ||
- name: Compile sample | ||
working-directory: ./sample | ||
run: npx tsc | ||
|
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,169 @@ | ||||||||||||
--- | ||||||||||||
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. | ||||||||||||
|
||||||||||||
* `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 ({ ack, inputs, complete, fail, logger }) => { | ||||||||||||
try { | ||||||||||||
await ack(); | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Removing the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I prefer to keep it in the docs, there are a number of issues opened related to calling ack before executing custom logic Keeping this in hopes that it will help developers avoid a bug 🙏 We can remove it in a follow up PR as well 🤔 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||
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> | ||||||||||||
definition | ||||||||||||
WilliamBergamin marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||
</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 ({ ack, client, inputs, fail, logger }) => { | ||||||||||||
try { | ||||||||||||
await ack(); | ||||||||||||
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> | ||||||||||||
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). |
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 🙏
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
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 thelistener
I think its better to be consistent and stick to
method
We can send a follow up PR to update all the terms 👍