|
| 1 | +--- |
| 2 | +title: Listening and responding to custom steps |
| 3 | +lang: en |
| 4 | +slug: /concepts/custom-steps |
| 5 | +--- |
| 6 | + |
| 7 | +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 `str`. 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. |
| 8 | + |
| 9 | +* `complete()` requires **one** argument: `outputs` of type `dict`. It ends your custom step **successfully** and provides a dictionary containing the outputs of your custom step as per its definition. |
| 10 | +* `fail()` requires **one** argument: `error` of type `str`. It ends your custom step **unsuccessfully** and provides a message containing information regarding why your custom step failed. |
| 11 | + |
| 12 | +You can reference your custom step's inputs using the `inputs` listener argument of type `dict`. |
| 13 | + |
| 14 | +Refer to [the module document](https://slack.dev/bolt-python/api-docs/slack_bolt/kwargs_injection/args.html) to learn about the available listener arguments. |
| 15 | + |
| 16 | +```python |
| 17 | +# This sample custom step formats an input and outputs it |
| 18 | +@app.function("sample_custom_step") |
| 19 | +def sample_step_callback(inputs: dict, fail: Fail, complete: Complete): |
| 20 | + try: |
| 21 | + message = inputs["message"] |
| 22 | + complete( |
| 23 | + outputs={ |
| 24 | + "message": f":wave: You submitted the following message: \n\n>{message}" |
| 25 | + } |
| 26 | + ) |
| 27 | + except Exception as e: |
| 28 | + fail(f"Failed to handle a custom step request (error: {e})") |
| 29 | + raise e |
| 30 | +``` |
| 31 | + |
| 32 | +<details> |
| 33 | +<summary> |
| 34 | +Example app manifest definition |
| 35 | +</summary> |
| 36 | + |
| 37 | +```json |
| 38 | +... |
| 39 | +"functions": { |
| 40 | + "sample_custom_step": { |
| 41 | + "title": "Sample custom step", |
| 42 | + "description": "Run a sample custom step", |
| 43 | + "input_parameters": { |
| 44 | + "message": { |
| 45 | + "type": "string", |
| 46 | + "title": "Message", |
| 47 | + "description": "A message to be formatted by the custom step", |
| 48 | + "is_required": true, |
| 49 | + } |
| 50 | + }, |
| 51 | + "output_parameters": { |
| 52 | + "message": { |
| 53 | + "type": "string", |
| 54 | + "title": "Messge", |
| 55 | + "description": "A formatted message", |
| 56 | + "is_required": true, |
| 57 | + } |
| 58 | + } |
| 59 | + } |
| 60 | +} |
| 61 | +``` |
| 62 | + |
| 63 | +</details> |
| 64 | + |
| 65 | +--- |
| 66 | + |
| 67 | +### Listening to custom step interactivity events |
| 68 | + |
| 69 | +Your app's custom steps may create interactivity points for users, for example: Post a message with a button. |
| 70 | + |
| 71 | +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). |
| 72 | + |
| 73 | +_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. |
| 74 | + |
| 75 | +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. |
| 76 | + |
| 77 | +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). |
| 78 | + |
| 79 | +```python |
| 80 | +# This sample custom step posts a message with a button |
| 81 | +@app.function("custom_step_button") |
| 82 | +def sample_step_callback(inputs, say, fail): |
| 83 | + try: |
| 84 | + say( |
| 85 | + channel=inputs["user_id"], # sending a DM to this user |
| 86 | + text="Click the button to signal the step completion", |
| 87 | + blocks=[ |
| 88 | + { |
| 89 | + "type": "section", |
| 90 | + "text": {"type": "mrkdwn", "text": "Click the button to signal step completion"}, |
| 91 | + "accessory": { |
| 92 | + "type": "button", |
| 93 | + "text": {"type": "plain_text", "text": "Complete step"}, |
| 94 | + "action_id": "sample_click", |
| 95 | + }, |
| 96 | + } |
| 97 | + ], |
| 98 | + ) |
| 99 | + except Exception as e: |
| 100 | + fail(f"Failed to handle a function request (error: {e})") |
| 101 | + |
| 102 | +# Your listener will be called every time a block element with the action_id "sample_click" is triggered |
| 103 | +@app.action("sample_click") |
| 104 | +def handle_sample_click(ack, body, context, client, complete, fail): |
| 105 | + ack() |
| 106 | + try: |
| 107 | + # Since the button no longer works, we should remove it |
| 108 | + client.chat_update( |
| 109 | + channel=context.channel_id, |
| 110 | + ts=body["message"]["ts"], |
| 111 | + text="Congrats! You clicked the button", |
| 112 | + ) |
| 113 | + |
| 114 | + # Signal that the custom step completed successfully |
| 115 | + complete({"user_id": context.actor_user_id}) |
| 116 | + except Exception as e: |
| 117 | + fail(f"Failed to handle a function request (error: {e})") |
| 118 | +``` |
| 119 | + |
| 120 | +<details> |
| 121 | +<summary> |
| 122 | +Example app manifest definition |
| 123 | +</summary> |
| 124 | + |
| 125 | +```json |
| 126 | +... |
| 127 | +"functions": { |
| 128 | + "custom_step_button": { |
| 129 | + "title": "Custom step with a button", |
| 130 | + "description": "Custom step that waits for a button click", |
| 131 | + "input_parameters": { |
| 132 | + "user_id": { |
| 133 | + "type": "slack#/types/user_id", |
| 134 | + "title": "User", |
| 135 | + "description": "The recipient of a message with a button", |
| 136 | + "is_required": true, |
| 137 | + } |
| 138 | + }, |
| 139 | + "output_parameters": { |
| 140 | + "user_id": { |
| 141 | + "type": "slack#/types/user_id", |
| 142 | + "title": "User", |
| 143 | + "description": "The user that completed the function", |
| 144 | + "is_required": true |
| 145 | + } |
| 146 | + } |
| 147 | + } |
| 148 | +} |
| 149 | +``` |
| 150 | + |
| 151 | +</details> |
| 152 | + |
| 153 | +Learn more about responding to interactivity, see the [Slack API documentation](https://api.slack.com/automation/functions/custom-bolt#interactivity). |
0 commit comments