From b35d7df08c5b72e75ece1eaeea675622c053657d Mon Sep 17 00:00:00 2001 From: Haley Elmendorf Date: Thu, 2 Oct 2025 17:02:11 -0500 Subject: [PATCH 01/11] salesforce order confirmation tutorial --- docs/english/_sidebar.json | 1 + .../order-confirmation/order-confirmation.md | 377 ++++++++++++++++++ docs/img/delivery-tracker-main.png | Bin 0 -> 65799 bytes 3 files changed, 378 insertions(+) create mode 100644 docs/english/tutorial/order-confirmation/order-confirmation.md create mode 100644 docs/img/delivery-tracker-main.png diff --git a/docs/english/_sidebar.json b/docs/english/_sidebar.json index d42868543..9a549d345 100644 --- a/docs/english/_sidebar.json +++ b/docs/english/_sidebar.json @@ -96,6 +96,7 @@ "label": "Tutorials", "items": [ "tools/bolt-python/tutorial/ai-chatbot/ai-chatbot", + "tools/bolt-python/tutorial/order-confirmation", "tools/bolt-python/tutorial/custom-steps", "tools/bolt-python/tutorial/custom-steps-for-jira/custom-steps-for-jira", "tools/bolt-python/tutorial/custom-steps-workflow-builder-new/custom-steps-workflow-builder-new", diff --git a/docs/english/tutorial/order-confirmation/order-confirmation.md b/docs/english/tutorial/order-confirmation/order-confirmation.md new file mode 100644 index 000000000..0f56c342d --- /dev/null +++ b/docs/english/tutorial/order-confirmation/order-confirmation.md @@ -0,0 +1,377 @@ +--- +title: Create a Salesforce order confirmation app +--- + + + + Learn how to use the Bolt for Python template and create a simple order confirmation app that links to systems of record—like Salesforce—in this tutorial. + + + ![Image of delivery tracker app](/img/delivery-tracker-main.png) + + + +The scenario: you work for a large e-commerce company that employs many delivery workers. Those delivery workers don’t have access to a laptop when they’re on the go, but they do have access to Slack on their mobile devices. This tutorial teaches you how to create a simple Slack app that is geared towards these workers. Delivery drivers will enter order numbers on their mobile, along with some additional information about the order, and have that information sent to a channel in Slack and to a system of record. In this tutorial, Salesforce is our system of record. + +You’ll also learn how to use the Bolt for Python starter app template and modify it to fit your needs. Note that this app is meant to be used for educational purposes and has not been tested rigorously enough to be used in production. + +## Getting started + +### Installing the Slack CLI + +If you don't already have it, install the Slack CLI from your terminal. Navigate to [the installation guide](/slack-cli/guides/installing-the-slack-cli-for-mac-and-linux/) and follow the steps. + +### Cloning the starter app + +Once installed, use the command `slack create` in your terminal and find the `bolt-python-starter-template`. Alternatively, you can clone the [Bolt for Python template](https://github.com/slack-samples/bolt-python-starter-template) using git. + +Optionally, you can remove the portions from the template that are not used within this tutorial to make things a bit cleaner for yourself. To do this, open your project and delete the commands, events, and shortcuts folders from the `/listeners` folder. You can also do the same to the corresponding folders within the `/listeners/tests` folder as well. Finally, remove the imports of these files from the `/listeners/__init__.py` file. + +## Creating your app + +We’ll use the contents of the `manifest.json` file below, which can also be found [here](https://github.com/wongjas/delivery-confirmation-app/blob/main/manifest.json). This file describes the metadata associated with your app, like its name and permissions that it requests. + +Copy the contents of the file and [create a new app](https://api.slack.com/apps/new). Next, choose **From a manifest** and follow the prompts, pasting the manifest file contents you copied. + +```json +{ + "_metadata": { + "major_version": 1, + "minor_version": 1 + }, + "display_information": { + "name": "Name your app here!" + }, + "features": { + "bot_user": { + "display_name": "Name your app here!", + "always_online": false + } + }, + "oauth_config": { + "scopes": { + "bot": [ + "channels:history", + "chat:write" + ] + } + }, + "settings": { + "event_subscriptions": { + "bot_events": [ + "message.channels" + ] + }, + "interactivity": { + "is_enabled": true + }, + "org_deploy_enabled": false, + "socket_mode_enabled": true, + "token_rotation_enabled": false + } +} +``` + +Customize your app with a name of your own instead of the default in the `display_name` and `name` fields. + +### Tokens + +Once your app has been created, scroll down to **App-Level Tokens** on the **Basic Information** page and create a token that requests the [`connections:write`](/reference/scopes/connections.write) scope. This token will allow you to use [Socket Mode](/apis/events-api/using-socket-mode), which is a secure way to develop on Slack through the use of WebSockets. Save the value of your app token and store it in a safe place (we’ll use it in the next step). + +### Install app + +Install your app by navigating to **Install App** in the left sidebar. When you press **Allow**, this means you’re agreeing to install your app with the permissions that it’s requesting. Copy the bot token that you receive as well and store this in a safe place as well for subsequent steps. + +## Starting your app's server + +Within a terminal of your choice, set the two tokens from the previous step as environment variables using the commands below. Make sure not to mix these two up, `SLACK_APP_TOKEN` will start with “xapp-“ and `SLACK_BOT_TOKEN` will start with “xoxb-“. + +```bash +export SLACK_APP_TOKEN= +export SLACK_BOT_TOKEN= +``` + +Run the following commands to activate a virtual environment for your Python packages to be installed, install the dependencies, and start your app. + +```bash +# Setup your python virtual environment +python3 -m venv .venv +source .venv/bin/activate + +# Install the dependencies +pip install -r requirements.txt + +# Start your local server +python3 app.py +``` + +Now that your app is running, you should be able to see it within Slack. In Slack, create a channel that you can test in and try inviting your bot to it using the `/invite @Your-app-name-here` command. Check that your app works by saying “hi” in the channel where your app is, and you should receive a message back from it. If you don’t, ensure you completed all the steps above. + +## Coding the app + +There will be four major steps needed to get from the template to the finish line: + +1. Update the “hi” message to something more interesting and interactive +2. Handle when the wrong delivery ID button is pressed +3. Handle when the correct delivery IDs are sent and bring up a modal for more information +4. Send the information to all of the places needed when the form is submitted (including third-party locations) + +All of these steps require you to use [Block Kit Builder](https://app.slack.com/block-kit-builder), a tool that helps you create messages, modals and other surfaces within Slack. Open [Block Kit Builder](https://app.slack.com/block-kit-builder), take a look and play around! We’ll create some views next. + +### Updating the "hi" message + +The first thing we want to do is change the “hi, how are you?” message from our app into something more useful. Here’s something that you can use to start off with, but you can make it your own within Block Kit Builder. Once you have something you like, copy the blocks by clicking the **Copy Payload** button in the top right. + +Take the function below and place your blocks within the blocks dictionary `[]`. Update the payload: +* Remove the initial blocks key and convert any boolean true values to `True` to fit with Python conventions. +* If you see variables within `{}` brackets, this is part of an f-string, which allows you to insert variables within strings in a clean manner. Place the `f` character before these strings like this: + +```python +{ + "type": "section", + "text": { + "type": "mrkdwn", + "text": f"Confirm *{delivery_id}* is correct?", # place the "f" character here at the beginning of the string + }, +}, +``` + +Place all of this in the `sample_message.py` file. + +```python +def delivery_message_callback(context: BoltContext, say: Say, logger: Logger): + try: + delivery_id = context["matches"][0] + say( + blocks=[] # insert your blocks here + ) + except Exception as e: + logger.error(e) +``` + +Next, you’ll need to make some connections so that this function is called when a message is sent in the channel where your app is. Head to `messages/__init__.py` and add the line below to the register function. Don’t forget to add the import to the callback function as well! + +```python + +from .sample_message import delivery_message_callback ## import the function to this file + +def register(app: App): + app.message(re.compile("(hi|hello|hey)"))(sample_message_callback) # This can be deleted! + # This regex will capture any number letters followed by dash + # and then any number of digits, our "confirmation number" e.g. ASDF-1234 + app.message(re.compile(r"[A-Za-z]+-\d+"))(delivery_message_callback) ## add this line! + +``` + +Now, restart your server to bring in the new code and test that your function works by sending an order confirmation ID, like `HWOA-1524`, in your testing channel. Your app should respond with the message you created within Block Kit Builder. + +## Handling an incorrect delivery ID + +Notice that if you try to click on either of the buttons within your message, nothing will happen. This is because we have yet to create a function to handle the button click. Let’s start with the `Not correct` button first. + +1. Head to Block Kit Builder once again. We want to build a message that lets the user know that the wrong order ID has been submitted. Here's [something to get you started](https://app.slack.com/block-kit-builder/#%7B%22blocks%22:%5B%7B%22type%22:%22section%22,%22text%22:%7B%22type%22:%22mrkdwn%22,%22text%22:%22Delivery%20*%7Bdelivery_id%7D*%20was%20incorrect%20%E2%9D%8C%22%7D%7D%5D%7D). + +2. Once you have something that you like, add it to the function below and place the function within the `actions/sample_action.py` file. Remember to make any strings with variables into f-strings! + +```python +def deny_delivery_callback(ack, body, client, logger: Logger): + try: + ack() + delivery_id = body["message"]["text"].split("*")[1] + + # Calls the chat.update function to replace the message, + # preventing it from being pressed more than once. + client.chat_update( + channel=body["container"]["channel_id"], + ts=body["container"]["message_ts"], + blocks=[], # Add your blocks here! + ) + + logger.info(f"Delivery denied by user {body['user']['id']}") + except Exception as e: + logger.error(e) +``` + +This function will call the [`chat.update`](/methods/chat.update) Web API method, which will update the original message with buttons, to the one that we created previously. This will also prevent the message from being pressed more than once. + +3. Make the connection to this function again within the `actions/__init__.py` folder with the following code: + +```python + +from slack_bolt import App +from .sample_action import sample_action_callback # This can be deleted +from .sample_action import deny_delivery_callback + +def register(app: App): + app.action("sample_action_id")(sample_action_callback) # This can be deleted + app.action("deny_delivery")(deny_delivery_callback) # Add this line + +``` + +Test out your code by sending in a confirmation number into your channel and clicking the `Not correct` button. If the message is updated, then you’re good to go onto the next step. + +## Handling a correct delivery ID + +The next step is to handle the `Confirm` button. In this case, we’re going to pull up a modal instead of just a message. + +1. Using the following [modal](https://app.slack.com/block-kit-builder/#%7B%22type%22:%22modal%22,%22callback_id%22:%22approve_delivery_view%22,%22title%22:%7B%22type%22:%22plain_text%22,%22text%22:%22Approve%20Delivery%22%7D,%22private_metadata%22:%22%7Bdelivery_id%7D%22,%22blocks%22:%5B%7B%22type%22:%22section%22,%22text%22:%7B%22type%22:%22mrkdwn%22,%22text%22:%22Approving%20delivery%20*%7Bdelivery_id%7D*%22%7D%7D,%7B%22type%22:%22input%22,%22block_id%22:%22notes%22,%22label%22:%7B%22type%22:%22plain_text%22,%22text%22:%22Additional%20delivery%20notes%22%7D,%22element%22:%7B%22type%22:%22plain_text_input%22,%22action_id%22:%22notes_input%22,%22multiline%22:true,%22placeholder%22:%7B%22type%22:%22plain_text%22,%22text%22:%22Add%20notes...%22%7D%7D,%22optional%22:true%7D,%7B%22type%22:%22input%22,%22block_id%22:%22location%22,%22label%22:%7B%22type%22:%22plain_text%22,%22text%22:%22Delivery%20Location%22%7D,%22element%22:%7B%22type%22:%22plain_text_input%22,%22action_id%22:%22location_input%22,%22placeholder%22:%7B%22type%22:%22plain_text%22,%22text%22:%22Enter%20the%20location%20details...%22%7D%7D,%22optional%22:true%7D,%7B%22type%22:%22input%22,%22block_id%22:%22channel%22,%22label%22:%7B%22type%22:%22plain_text%22,%22text%22:%22Notification%20Channel%22%7D,%22element%22:%7B%22type%22:%22channels_select%22,%22action_id%22:%22channel_select%22,%22placeholder%22:%7B%22type%22:%22plain_text%22,%22text%22:%22Select%20channel%20for%20notifications%22%7D%7D,%22optional%22:false%7D%5D,%22submit%22:%7B%22type%22:%22plain_text%22,%22text%22:%22Approve%22%7D%7D) as a base, create a modal that captures the kind of information that you need. + +2. Within the `actions/sample_action.pyfile`, add the following function, replacing the view with the one you created above. Again, any strings with variables will be updated to f-strings and also any booleans will need to be capitalized. + +```python + +def approve_delivery_callback(ack, body, client, logger: Logger): + try: + ack() + + delivery_id = body["message"]["text"].split("*")[1] + # Updates the original message so you can't press it twice + client.chat_update( + channel=body["container"]["channel_id"], + ts=body["container"]["message_ts"], + blocks=[ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": f"Processed delivery *{delivery_id}*...", + }, + } + ], + ) + + # Open a modal to gather information from the user + client.views_open( + trigger_id=body["trigger_id"], + view={} # Add your view here + ) + + logger.info(f"Approval modal opened by user {body['user']['id']}") + except Exception as e: + logger.error(e) + +``` + +Similar to the `deny` button, we need to hook up all the connections. Your `actions/__init__.py` should look something like this: + +```python + +from slack_bolt import App +from .sample_action import deny_delivery_callback +from .sample_action import approve_delivery_callback + + +def register(app: App): + app.action("approve_delivery")(approve_delivery_callback) + app.action("deny_delivery")(deny_delivery_callback) + +``` + +Test your app by typing in a confirmation number in channel, click the confirm button and see if the modal comes up and you are able to capture information from the user. + +## Submitting the form + +Lastly, we’ll handle the submission of the form, which will trigger two things. We want to send the information into the specified channel, which will let the user know that the form was successful, as well as send the information into our system of record, Salesforce. + +1. Here’s a [simple example](https://app.slack.com/block-kit-builder/?1#%7B%22blocks%22:%5B%7B%22type%22:%22section%22,%22text%22:%7B%22type%22:%22mrkdwn%22,%22text%22:%22%E2%9C%85%20Delivery%20*%7Bdelivery_id%7D*%20approved:%22%7D%7D,%7B%22type%22:%22section%22,%22text%22:%7B%22type%22:%22mrkdwn%22,%22text%22:%22*Delivery%20Notes:*%5Cn%7Bnotes%20or%20'None'%7D%22%7D%7D,%7B%22type%22:%22section%22,%22text%22:%7B%22type%22:%22mrkdwn%22,%22text%22:%22*Delivery%20Location:*%5Cn%7Bloc%20or%20'None'%7D%22%7D%7D%5D%7D) of a message that you can use to present the information in channel. Modify it however you like and then place it within the code below in the `/views/sample_views.py` file. + +```python + +def handle_approve_delivery_view(ack, client, view, logger: Logger): + try: + ack() + + delivery_id = view["private_metadata"] + values = view["state"]["values"] + notes = values["notes"]["notes_input"]["value"] + loc = values["location"]["location_input"]["value"] + channel = values["channel"]["channel_select"]["selected_channel"] + + client.chat_postMessage( + channel=channel, + blocks=[], ## Add your message here + ) + + except Exception as e: + logger.error(f"Error in approve_delivery_view: {e}") + +``` + +2. Making the connections in the `/views/__init__.py `file, we can test that this works by sending a message once again in our test channel. + +```python + +from slack_bolt import App +from .sample_view import handle_approve_delivery_view + +def register(app: App): + app.view("sample_view_id")(sample_view_callback) # This can be deleted + app.view("approve_delivery_view")(handle_approve_delivery_view) ## Add this line + +``` + +3. Let’s also send the information to Salesforce. There are [several ways](https://github.com/simple-salesforce/simple-salesforce?tab=readme-ov-file#examples) for you to access Salesforce through its API, but in this workshop, we’ve utilized `username`, `password` and `token`. If you need help with getting your API token for Salesforce, take a look at [this article](https://help.salesforce.com/s/articleView?id=xcloud.user_security_token.htm&type=5). You’ll need to add these values as environment variables like we did earlier with our Slack tokens. You can use the following commands: + +```bash + +export SF_USERNAME= +export SF_PASSWORD= +export SF_TOKEN= + +``` + +4. We’re going to use assume that order information is stored in the Order object and that the confirmation IDs map to the 8-digit Order numbers within Salesforce. Given that assumption, all we need to do is make a query to find the correct object, add the inputted information, and we’re done. Place this code before the last except within the `/views/sample_views.py` file. + +```python + +# Extract just the numeric portion from delivery_id + delivery_number = "".join(filter(str.isdigit, delivery_id)) + + # Update Salesforce order object + try: + sf = Salesforce( + username=os.environ.get("SF_USERNAME"), + password=os.environ.get("SF_PASSWORD"), + security_token=os.environ.get("SF_TOKEN"), + ) + + # Assuming delivery_id maps to Salesforce Order number + order = sf.query(f"SELECT Id FROM Order WHERE OrderNumber = '{delivery_number}'") # noqa: E501 + if order["records"]: + order_id = order["records"][0]["Id"] + sf.Order.update( + order_id, + { + "Status": "Delivered", + "Description": notes, + "Shipping_Location__c": loc, + }, + ) + logger.info(f"Updated order {delivery_id}") + else: + logger.warning(f"No order found for {delivery_id}") + + except Exception as sf_error: + logger.error(f"Update failed for order {delivery_id}: {sf_error}") + # Continue execution even if Salesforce update fails + +``` + +You’ll also need to add the two imports that are found within this code to the top of the file. + +```python +import os +from simple_salesforce import Salesforce +``` + +With these imports, add `simple_salesforce` to your `requirements.txt` file, then install that package with the following command once again. + +```bash +pip install -r requirements.txt +``` + +## Testing your app + +Test your app one last time, and you’re done! + +Congratulations! You’ve built an app using [Bolt for Python](/bolt-python/) that allows you to send information into Slack, as well as into a third-party service. While there are more features you can add to make this a more robust app, we hope that this serves as a good introduction into connecting services like Salesforce using Slack as a conduit. \ No newline at end of file diff --git a/docs/img/delivery-tracker-main.png b/docs/img/delivery-tracker-main.png new file mode 100644 index 0000000000000000000000000000000000000000..b8d2e885c9ed00d1537203192beb5f7228c6ebef GIT binary patch literal 65799 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%0A3K0U$bGe{Ox5WC%Nw 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%0D9_<|{L1-${Mz}&0e3f!dsLgTY1)_> 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 Awg3PC literal 0 HcmV?d00001 From e8c50566ed4180eca7fc0cfbd53ab27b1f762092 Mon Sep 17 00:00:00 2001 From: Haley Elmendorf Date: Thu, 2 Oct 2025 17:07:40 -0500 Subject: [PATCH 02/11] corrected path --- docs/english/_sidebar.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/english/_sidebar.json b/docs/english/_sidebar.json index 9a549d345..859c4b52f 100644 --- a/docs/english/_sidebar.json +++ b/docs/english/_sidebar.json @@ -96,7 +96,7 @@ "label": "Tutorials", "items": [ "tools/bolt-python/tutorial/ai-chatbot/ai-chatbot", - "tools/bolt-python/tutorial/order-confirmation", + "tools/bolt-python/tutorial/order-confirmation/order-confirmation", "tools/bolt-python/tutorial/custom-steps", "tools/bolt-python/tutorial/custom-steps-for-jira/custom-steps-for-jira", "tools/bolt-python/tutorial/custom-steps-workflow-builder-new/custom-steps-workflow-builder-new", From 450385ec2dcda515d243ed5d49b5fa139daea338 Mon Sep 17 00:00:00 2001 From: Haley Elmendorf Date: Thu, 2 Oct 2025 17:12:21 -0500 Subject: [PATCH 03/11] corrected img path --- docs/english/tutorial/order-confirmation/order-confirmation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/english/tutorial/order-confirmation/order-confirmation.md b/docs/english/tutorial/order-confirmation/order-confirmation.md index 0f56c342d..1de0b5906 100644 --- a/docs/english/tutorial/order-confirmation/order-confirmation.md +++ b/docs/english/tutorial/order-confirmation/order-confirmation.md @@ -7,7 +7,7 @@ title: Create a Salesforce order confirmation app Learn how to use the Bolt for Python template and create a simple order confirmation app that links to systems of record—like Salesforce—in this tutorial. - ![Image of delivery tracker app](/img/delivery-tracker-main.png) + ![Image of delivery tracker app](/img/bolt-python/delivery-tracker-main.png) From 981522708aabfd5ac13d4f0592f0fee895081767 Mon Sep 17 00:00:00 2001 From: Haley Elmendorf Date: Thu, 2 Oct 2025 17:26:30 -0500 Subject: [PATCH 04/11] copy edits --- .../order-confirmation/order-confirmation.md | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/docs/english/tutorial/order-confirmation/order-confirmation.md b/docs/english/tutorial/order-confirmation/order-confirmation.md index 1de0b5906..f3abc7c2e 100644 --- a/docs/english/tutorial/order-confirmation/order-confirmation.md +++ b/docs/english/tutorial/order-confirmation/order-confirmation.md @@ -4,28 +4,30 @@ title: Create a Salesforce order confirmation app - Learn how to use the Bolt for Python template and create a simple order confirmation app that links to systems of record—like Salesforce—in this tutorial. + Use the Bolt for Python starter template to create a simple order confirmation app that links to a system of record—like Salesforce—in this tutorial. + + The scenario: you work for a large e-commerce company that employs many delivery workers. Those delivery workers don’t have access to a laptop when they’re on the go, but they do have access to Slack on their mobile devices. This tutorial teaches you how to create a simple Slack app that is geared towards these workers. Delivery drivers will enter order numbers on their mobile, along with some additional information about the order. That information is then sent to a channel in Slack and to a system of record. In this tutorial, Salesforce is our system of record. + + You’ll also learn how to use the Bolt for Python starter app template and modify it to fit your needs. Note that this app is meant to be used for educational purposes and has not been tested rigorously enough to be used in production. + ![Image of delivery tracker app](/img/bolt-python/delivery-tracker-main.png) -The scenario: you work for a large e-commerce company that employs many delivery workers. Those delivery workers don’t have access to a laptop when they’re on the go, but they do have access to Slack on their mobile devices. This tutorial teaches you how to create a simple Slack app that is geared towards these workers. Delivery drivers will enter order numbers on their mobile, along with some additional information about the order, and have that information sent to a channel in Slack and to a system of record. In this tutorial, Salesforce is our system of record. - -You’ll also learn how to use the Bolt for Python starter app template and modify it to fit your needs. Note that this app is meant to be used for educational purposes and has not been tested rigorously enough to be used in production. ## Getting started ### Installing the Slack CLI -If you don't already have it, install the Slack CLI from your terminal. Navigate to [the installation guide](/slack-cli/guides/installing-the-slack-cli-for-mac-and-linux/) and follow the steps. +If you don't already have it, install the Slack CLI from your terminal. Navigate to the installation guide ([for Mac and Linux](/tools/slack-cli/guides/installing-the-slack-cli-for-mac-and-linux) or [for Windows](/tools/slack-cli/guides/installing-the-slack-cli-for-windows)) and follow the steps. ### Cloning the starter app Once installed, use the command `slack create` in your terminal and find the `bolt-python-starter-template`. Alternatively, you can clone the [Bolt for Python template](https://github.com/slack-samples/bolt-python-starter-template) using git. -Optionally, you can remove the portions from the template that are not used within this tutorial to make things a bit cleaner for yourself. To do this, open your project and delete the commands, events, and shortcuts folders from the `/listeners` folder. You can also do the same to the corresponding folders within the `/listeners/tests` folder as well. Finally, remove the imports of these files from the `/listeners/__init__.py` file. +You can remove the portions from the template that are not used within this tutorial to make things a bit cleaner for yourself. To do this, open your project and delete the commands, events, and shortcuts folders from the `/listeners` folder. You can also do the same to the corresponding folders within the `/listeners/tests` folder as well. Finally, remove the imports of these files from the `/listeners/__init__.py` file. ## Creating your app @@ -165,7 +167,7 @@ def register(app: App): Now, restart your server to bring in the new code and test that your function works by sending an order confirmation ID, like `HWOA-1524`, in your testing channel. Your app should respond with the message you created within Block Kit Builder. -## Handling an incorrect delivery ID +### Handling an incorrect delivery ID Notice that if you try to click on either of the buttons within your message, nothing will happen. This is because we have yet to create a function to handle the button click. Let’s start with the `Not correct` button first. @@ -192,7 +194,7 @@ def deny_delivery_callback(ack, body, client, logger: Logger): logger.error(e) ``` -This function will call the [`chat.update`](/methods/chat.update) Web API method, which will update the original message with buttons, to the one that we created previously. This will also prevent the message from being pressed more than once. +This function will call the [`chat.update`](/reference/methods/chat.update) Web API method, which will update the original message with buttons, to the one that we created previously. This will also prevent the message from being pressed more than once. 3. Make the connection to this function again within the `actions/__init__.py` folder with the following code: @@ -269,7 +271,7 @@ def register(app: App): Test your app by typing in a confirmation number in channel, click the confirm button and see if the modal comes up and you are able to capture information from the user. -## Submitting the form +### Submitting the form Lastly, we’ll handle the submission of the form, which will trigger two things. We want to send the information into the specified channel, which will let the user know that the form was successful, as well as send the information into our system of record, Salesforce. @@ -374,4 +376,4 @@ pip install -r requirements.txt Test your app one last time, and you’re done! -Congratulations! You’ve built an app using [Bolt for Python](/bolt-python/) that allows you to send information into Slack, as well as into a third-party service. While there are more features you can add to make this a more robust app, we hope that this serves as a good introduction into connecting services like Salesforce using Slack as a conduit. \ No newline at end of file +Congratulations! You’ve built an app using [Bolt for Python](/tools/bolt-python/) that allows you to send information into Slack, as well as into a third-party service. While there are more features you can add to make this a more robust app, we hope that this serves as a good introduction into connecting services like Salesforce using Slack as a conduit. \ No newline at end of file From 0531a5a7a22ed5083ae8f90b2eceedca2344e053 Mon Sep 17 00:00:00 2001 From: Haley Elmendorf Date: Thu, 2 Oct 2025 17:30:16 -0500 Subject: [PATCH 05/11] couple more edits --- .../tutorial/order-confirmation/order-confirmation.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/english/tutorial/order-confirmation/order-confirmation.md b/docs/english/tutorial/order-confirmation/order-confirmation.md index f3abc7c2e..e8749cc17 100644 --- a/docs/english/tutorial/order-confirmation/order-confirmation.md +++ b/docs/english/tutorial/order-confirmation/order-confirmation.md @@ -6,9 +6,7 @@ title: Create a Salesforce order confirmation app Use the Bolt for Python starter template to create a simple order confirmation app that links to a system of record—like Salesforce—in this tutorial. - The scenario: you work for a large e-commerce company that employs many delivery workers. Those delivery workers don’t have access to a laptop when they’re on the go, but they do have access to Slack on their mobile devices. This tutorial teaches you how to create a simple Slack app that is geared towards these workers. Delivery drivers will enter order numbers on their mobile, along with some additional information about the order. That information is then sent to a channel in Slack and to a system of record. In this tutorial, Salesforce is our system of record. - - You’ll also learn how to use the Bolt for Python starter app template and modify it to fit your needs. Note that this app is meant to be used for educational purposes and has not been tested rigorously enough to be used in production. + The scenario: you work for a large e-commerce company that employs many delivery workers. Those delivery workers don’t have access to a laptop when they’re on the go, but they do have access to Slack on their mobile devices. This tutorial teaches you how to create a simple Slack app that is geared towards these workers. Delivery drivers will enter order numbers on their mobile, along with some additional information about the order. That information is then sent to a channel in Slack and to a system of record. In this tutorial, Salesforce is our system of record.. @@ -16,6 +14,8 @@ title: Create a Salesforce order confirmation app +You’ll also learn how to use the Bolt for Python starter app template and modify it to fit your needs. Note that this app is meant to be used for educational purposes and has not been tested rigorously enough to be used in production + ## Getting started @@ -212,7 +212,7 @@ def register(app: App): Test out your code by sending in a confirmation number into your channel and clicking the `Not correct` button. If the message is updated, then you’re good to go onto the next step. -## Handling a correct delivery ID +### Handling a correct delivery ID The next step is to handle the `Confirm` button. In this case, we’re going to pull up a modal instead of just a message. From adf42eefb6ae2a877366d90f23e6fe0d3a5188b5 Mon Sep 17 00:00:00 2001 From: Haley Elmendorf Date: Tue, 7 Oct 2025 14:41:17 -0500 Subject: [PATCH 06/11] addressed pr feedback --- .../order-confirmation/order-confirmation.md | 219 ++++++++++++++---- 1 file changed, 179 insertions(+), 40 deletions(-) diff --git a/docs/english/tutorial/order-confirmation/order-confirmation.md b/docs/english/tutorial/order-confirmation/order-confirmation.md index e8749cc17..5c70346e1 100644 --- a/docs/english/tutorial/order-confirmation/order-confirmation.md +++ b/docs/english/tutorial/order-confirmation/order-confirmation.md @@ -2,36 +2,38 @@ title: Create a Salesforce order confirmation app --- - - - Use the Bolt for Python starter template to create a simple order confirmation app that links to a system of record—like Salesforce—in this tutorial. - - The scenario: you work for a large e-commerce company that employs many delivery workers. Those delivery workers don’t have access to a laptop when they’re on the go, but they do have access to Slack on their mobile devices. This tutorial teaches you how to create a simple Slack app that is geared towards these workers. Delivery drivers will enter order numbers on their mobile, along with some additional information about the order. That information is then sent to a channel in Slack and to a system of record. In this tutorial, Salesforce is our system of record.. - - - - ![Image of delivery tracker app](/img/bolt-python/delivery-tracker-main.png) - - - -You’ll also learn how to use the Bolt for Python starter app template and modify it to fit your needs. Note that this app is meant to be used for educational purposes and has not been tested rigorously enough to be used in production - +In this tutorial, you'll use the [Bolt for Python](/tools/bolt-python/) framework and [Block Kit Builder](https://app.slack.com/block-kit-builder) to create an order confirmation app that links to a system of record, like Salesforce. + +The Slack app will: +* allow users to enter order numbers from within Slack, along with some additional order information, +* post that information to a Slack channel, and +* send the information to the system of record. + +End users will be able to enter information across devices, as many will likely be using a mobile device. + +Along the way, you'll learn how to use the Bolt for Python starter app template as a jumping off point for your own custom apps. Let's begin! + +:::warning[Consider the following] + +This tutorial was created for educational purposes within a Slack workshop. As a result, it has not been tested quite as rigorously as our sample apps. Proceed carefully if you'd like to use a similar app in production. + +::: ## Getting started ### Installing the Slack CLI -If you don't already have it, install the Slack CLI from your terminal. Navigate to the installation guide ([for Mac and Linux](/tools/slack-cli/guides/installing-the-slack-cli-for-mac-and-linux) or [for Windows](/tools/slack-cli/guides/installing-the-slack-cli-for-windows)) and follow the steps. +If you don't already have the Slack CLI, install it from your terminal: navigate to the installation guide ([for Mac and Linux](/tools/slack-cli/guides/installing-the-slack-cli-for-mac-and-linux) or [for Windows](/tools/slack-cli/guides/installing-the-slack-cli-for-windows)) and follow the steps. ### Cloning the starter app -Once installed, use the command `slack create` in your terminal and find the `bolt-python-starter-template`. Alternatively, you can clone the [Bolt for Python template](https://github.com/slack-samples/bolt-python-starter-template) using git. +Once installed, use the command `slack create` in your terminal, select the `bolt-python-starter-template`, then choose your preferred language (this tutorial shows Python). Alternatively, you can clone the [Bolt for Python template](https://github.com/slack-samples/bolt-python-starter-template) using git. -You can remove the portions from the template that are not used within this tutorial to make things a bit cleaner for yourself. To do this, open your project and delete the commands, events, and shortcuts folders from the `/listeners` folder. You can also do the same to the corresponding folders within the `/listeners/tests` folder as well. Finally, remove the imports of these files from the `/listeners/__init__.py` file. +You can remove the portions from the template that are not used within this tutorial to make things a bit cleaner for yourself. To do this, open your project in VS Code (you can do this from the terminal with the `code .` command) and delete the `commands`, `events`, and `shortcuts` folders from the `/listeners` folder. You can also do the same to the corresponding folders within the `/listeners/tests` folder as well. Finally, remove the imports of these files from the `/listeners/__init__.py` file. ## Creating your app -We’ll use the contents of the `manifest.json` file below, which can also be found [here](https://github.com/wongjas/delivery-confirmation-app/blob/main/manifest.json). This file describes the metadata associated with your app, like its name and permissions that it requests. +We’ll use the contents of the `manifest.json` file below. This file describes the metadata associated with your app, like its name and permissions that it requests. Copy the contents of the file and [create a new app](https://api.slack.com/apps/new). Next, choose **From a manifest** and follow the prompts, pasting the manifest file contents you copied. @@ -42,11 +44,11 @@ Copy the contents of the file and [create a new app](https://api.slack.com/apps/ "minor_version": 1 }, "display_information": { - "name": "Name your app here!" + "name": "Delivery Tracker App" }, "features": { "bot_user": { - "display_name": "Name your app here!", + "display_name": "Delivery Tracker App", "always_online": false } }, @@ -74,24 +76,38 @@ Copy the contents of the file and [create a new app](https://api.slack.com/apps/ } ``` -Customize your app with a name of your own instead of the default in the `display_name` and `name` fields. - ### Tokens Once your app has been created, scroll down to **App-Level Tokens** on the **Basic Information** page and create a token that requests the [`connections:write`](/reference/scopes/connections.write) scope. This token will allow you to use [Socket Mode](/apis/events-api/using-socket-mode), which is a secure way to develop on Slack through the use of WebSockets. Save the value of your app token and store it in a safe place (we’ll use it in the next step). ### Install app -Install your app by navigating to **Install App** in the left sidebar. When you press **Allow**, this means you’re agreeing to install your app with the permissions that it’s requesting. Copy the bot token that you receive as well and store this in a safe place as well for subsequent steps. +Still in the app settings, navigate to the **Install App** page in the left sidebar. Install your app. When you press **Allow**, this means you’re agreeing to install your app with the permissions that it’s requesting. Copy the bot token that you receive as well and store this in a safe place as well for subsequent steps. ## Starting your app's server Within a terminal of your choice, set the two tokens from the previous step as environment variables using the commands below. Make sure not to mix these two up, `SLACK_APP_TOKEN` will start with “xapp-“ and `SLACK_BOT_TOKEN` will start with “xoxb-“. -```bash -export SLACK_APP_TOKEN= -export SLACK_BOT_TOKEN= -``` + + + ```bash + export SLACK_APP_TOKEN= + export SLACK_BOT_TOKEN= + ``` + + + ``` + set SLACK_APP_TOKEN= + set SLACK_BOT_TOKEN= + ``` + + + ''' + $env:SLACK_APP_TOKEN="YOUR-APP-TOKEN-HERE" + $env:SLACK_BOT_TOKEN="YOUR-BOT-TOKEN-HERE" + ``` + + Run the following commands to activate a virtual environment for your Python packages to be installed, install the dependencies, and start your app. @@ -111,12 +127,12 @@ Now that your app is running, you should be able to see it within Slack. In Slac ## Coding the app -There will be four major steps needed to get from the template to the finish line: +We'll make four changes to the app: -1. Update the “hi” message to something more interesting and interactive -2. Handle when the wrong delivery ID button is pressed -3. Handle when the correct delivery IDs are sent and bring up a modal for more information -4. Send the information to all of the places needed when the form is submitted (including third-party locations) +* Update the “hi” message to something more interesting and interactive +* Handle when the wrong delivery ID button is pressed +* Handle when the correct delivery IDs are sent and bring up a modal for more information +* Send the information to all of the places needed when the form is submitted (including third-party locations) All of these steps require you to use [Block Kit Builder](https://app.slack.com/block-kit-builder), a tool that helps you create messages, modals and other surfaces within Slack. Open [Block Kit Builder](https://app.slack.com/block-kit-builder), take a look and play around! We’ll create some views next. @@ -155,7 +171,7 @@ Next, you’ll need to make some connections so that this function is called whe ```python -from .sample_message import delivery_message_callback ## import the function to this file +from .sample_message import delivery_message_callback # import the function to this file def register(app: App): app.message(re.compile("(hi|hello|hey)"))(sample_message_callback) # This can be deleted! @@ -171,7 +187,21 @@ Now, restart your server to bring in the new code and test that your function wo Notice that if you try to click on either of the buttons within your message, nothing will happen. This is because we have yet to create a function to handle the button click. Let’s start with the `Not correct` button first. -1. Head to Block Kit Builder once again. We want to build a message that lets the user know that the wrong order ID has been submitted. Here's [something to get you started](https://app.slack.com/block-kit-builder/#%7B%22blocks%22:%5B%7B%22type%22:%22section%22,%22text%22:%7B%22type%22:%22mrkdwn%22,%22text%22:%22Delivery%20*%7Bdelivery_id%7D*%20was%20incorrect%20%E2%9D%8C%22%7D%7D%5D%7D). +1. Head to Block Kit Builder once again. We want to build a message that lets the user know that the wrong order ID has been submitted. Here's a [section](/reference/block-kit/blocks/section-block) block to get you started: + +```json + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "Delivery *{delivery_id}* was incorrect ❌" + } + } + ] +``` + +View this block in Block Kit Builder [here](https://app.slack.com/block-kit-builder/#%7B%22blocks%22:%5B%7B%22type%22:%22section%22,%22text%22:%7B%22type%22:%22mrkdwn%22,%22text%22:%22Delivery%20*%7Bdelivery_id%7D*%20was%20incorrect%20%E2%9D%8C%22%7D%7D%5D%7D). 2. Once you have something that you like, add it to the function below and place the function within the `actions/sample_action.py` file. Remember to make any strings with variables into f-strings! @@ -210,15 +240,94 @@ def register(app: App): ``` -Test out your code by sending in a confirmation number into your channel and clicking the `Not correct` button. If the message is updated, then you’re good to go onto the next step. +Test out your app by sending in a confirmation number into your channel and clicking the `Not correct` button. If the message is updated, then you’re good to go onto the next step. ### Handling a correct delivery ID The next step is to handle the `Confirm` button. In this case, we’re going to pull up a modal instead of just a message. -1. Using the following [modal](https://app.slack.com/block-kit-builder/#%7B%22type%22:%22modal%22,%22callback_id%22:%22approve_delivery_view%22,%22title%22:%7B%22type%22:%22plain_text%22,%22text%22:%22Approve%20Delivery%22%7D,%22private_metadata%22:%22%7Bdelivery_id%7D%22,%22blocks%22:%5B%7B%22type%22:%22section%22,%22text%22:%7B%22type%22:%22mrkdwn%22,%22text%22:%22Approving%20delivery%20*%7Bdelivery_id%7D*%22%7D%7D,%7B%22type%22:%22input%22,%22block_id%22:%22notes%22,%22label%22:%7B%22type%22:%22plain_text%22,%22text%22:%22Additional%20delivery%20notes%22%7D,%22element%22:%7B%22type%22:%22plain_text_input%22,%22action_id%22:%22notes_input%22,%22multiline%22:true,%22placeholder%22:%7B%22type%22:%22plain_text%22,%22text%22:%22Add%20notes...%22%7D%7D,%22optional%22:true%7D,%7B%22type%22:%22input%22,%22block_id%22:%22location%22,%22label%22:%7B%22type%22:%22plain_text%22,%22text%22:%22Delivery%20Location%22%7D,%22element%22:%7B%22type%22:%22plain_text_input%22,%22action_id%22:%22location_input%22,%22placeholder%22:%7B%22type%22:%22plain_text%22,%22text%22:%22Enter%20the%20location%20details...%22%7D%7D,%22optional%22:true%7D,%7B%22type%22:%22input%22,%22block_id%22:%22channel%22,%22label%22:%7B%22type%22:%22plain_text%22,%22text%22:%22Notification%20Channel%22%7D,%22element%22:%7B%22type%22:%22channels_select%22,%22action_id%22:%22channel_select%22,%22placeholder%22:%7B%22type%22:%22plain_text%22,%22text%22:%22Select%20channel%20for%20notifications%22%7D%7D,%22optional%22:false%7D%5D,%22submit%22:%7B%22type%22:%22plain_text%22,%22text%22:%22Approve%22%7D%7D) as a base, create a modal that captures the kind of information that you need. +1. Using the following modal as a base; create a modal that captures the kind of information that you need. -2. Within the `actions/sample_action.pyfile`, add the following function, replacing the view with the one you created above. Again, any strings with variables will be updated to f-strings and also any booleans will need to be capitalized. +```json +{ + "title": { + "type": "plain_text", + "text": "Approve Delivery" + }, + "submit": { + "type": "plain_text", + "text": "Approve" + }, + "type": "modal", + "callback_id": "approve_delivery_view", + "private_metadata": "{delivery_id}", + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "Approving delivery *{delivery_id}*" + } + }, + { + "type": "input", + "block_id": "notes", + "label": { + "type": "plain_text", + "text": "Additional delivery notes" + }, + "element": { + "type": "plain_text_input", + "action_id": "notes_input", + "multiline": true, + "placeholder": { + "type": "plain_text", + "text": "Add notes..." + } + }, + "optional": true + }, + { + "type": "input", + "block_id": "location", + "label": { + "type": "plain_text", + "text": "Delivery Location" + }, + "element": { + "type": "plain_text_input", + "action_id": "location_input", + "placeholder": { + "type": "plain_text", + "text": "Enter the location details..." + } + }, + "optional": true + }, + { + "type": "input", + "block_id": "channel", + "label": { + "type": "plain_text", + "text": "Notification Channel" + }, + "element": { + "type": "channels_select", + "action_id": "channel_select", + "placeholder": { + "type": "plain_text", + "text": "Select channel for notifications" + } + }, + "optional": false + } + ] +} +``` + +View this modal in Block Kit Builder [here](https://app.slack.com/block-kit-builder/#%7B%22type%22:%22modal%22,%22callback_id%22:%22approve_delivery_view%22,%22title%22:%7B%22type%22:%22plain_text%22,%22text%22:%22Approve%20Delivery%22%7D,%22private_metadata%22:%22%7Bdelivery_id%7D%22,%22blocks%22:%5B%7B%22type%22:%22section%22,%22text%22:%7B%22type%22:%22mrkdwn%22,%22text%22:%22Approving%20delivery%20*%7Bdelivery_id%7D*%22%7D%7D,%7B%22type%22:%22input%22,%22block_id%22:%22notes%22,%22label%22:%7B%22type%22:%22plain_text%22,%22text%22:%22Additional%20delivery%20notes%22%7D,%22element%22:%7B%22type%22:%22plain_text_input%22,%22action_id%22:%22notes_input%22,%22multiline%22:true,%22placeholder%22:%7B%22type%22:%22plain_text%22,%22text%22:%22Add%20notes...%22%7D%7D,%22optional%22:true%7D,%7B%22type%22:%22input%22,%22block_id%22:%22location%22,%22label%22:%7B%22type%22:%22plain_text%22,%22text%22:%22Delivery%20Location%22%7D,%22element%22:%7B%22type%22:%22plain_text_input%22,%22action_id%22:%22location_input%22,%22placeholder%22:%7B%22type%22:%22plain_text%22,%22text%22:%22Enter%20the%20location%20details...%22%7D%7D,%22optional%22:true%7D,%7B%22type%22:%22input%22,%22block_id%22:%22channel%22,%22label%22:%7B%22type%22:%22plain_text%22,%22text%22:%22Notification%20Channel%22%7D,%22element%22:%7B%22type%22:%22channels_select%22,%22action_id%22:%22channel_select%22,%22placeholder%22:%7B%22type%22:%22plain_text%22,%22text%22:%22Select%20channel%20for%20notifications%22%7D%7D,%22optional%22:false%7D%5D,%22submit%22:%7B%22type%22:%22plain_text%22,%22text%22:%22Approve%22%7D%7D). + +2. Within the `actions/sample_action.py` file, add the following function, replacing the view with the one you created above. Again, any strings with variables will be updated to f-strings and also any booleans will need to be capitalized. ```python @@ -275,7 +384,35 @@ Test your app by typing in a confirmation number in channel, click the confirm b Lastly, we’ll handle the submission of the form, which will trigger two things. We want to send the information into the specified channel, which will let the user know that the form was successful, as well as send the information into our system of record, Salesforce. -1. Here’s a [simple example](https://app.slack.com/block-kit-builder/?1#%7B%22blocks%22:%5B%7B%22type%22:%22section%22,%22text%22:%7B%22type%22:%22mrkdwn%22,%22text%22:%22%E2%9C%85%20Delivery%20*%7Bdelivery_id%7D*%20approved:%22%7D%7D,%7B%22type%22:%22section%22,%22text%22:%7B%22type%22:%22mrkdwn%22,%22text%22:%22*Delivery%20Notes:*%5Cn%7Bnotes%20or%20'None'%7D%22%7D%7D,%7B%22type%22:%22section%22,%22text%22:%7B%22type%22:%22mrkdwn%22,%22text%22:%22*Delivery%20Location:*%5Cn%7Bloc%20or%20'None'%7D%22%7D%7D%5D%7D) of a message that you can use to present the information in channel. Modify it however you like and then place it within the code below in the `/views/sample_views.py` file. +1. Here’s a simple example of a message that you can use to present the information in channel. + +```json + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "✅ Delivery *{delivery_id}* approved:" + } + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*Delivery Notes:*\n{notes or 'None'}" + } + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*Delivery Location:*\n{loc or 'None'}" + } + } + ] +``` + +View this in Block Kit Builder [here](https://app.slack.com/block-kit-builder/?1#%7B%22blocks%22:%5B%7B%22type%22:%22section%22,%22text%22:%7B%22type%22:%22mrkdwn%22,%22text%22:%22%E2%9C%85%20Delivery%20*%7Bdelivery_id%7D*%20approved:%22%7D%7D,%7B%22type%22:%22section%22,%22text%22:%7B%22type%22:%22mrkdwn%22,%22text%22:%22*Delivery%20Notes:*%5Cn%7Bnotes%20or%20'None'%7D%22%7D%7D,%7B%22type%22:%22section%22,%22text%22:%7B%22type%22:%22mrkdwn%22,%22text%22:%22*Delivery%20Location:*%5Cn%7Bloc%20or%20'None'%7D%22%7D%7D%5D%7D). Modify it however you like and then place it within the code below in the `/views/sample_views.py` file. ```python @@ -312,7 +449,7 @@ def register(app: App): ``` -3. Let’s also send the information to Salesforce. There are [several ways](https://github.com/simple-salesforce/simple-salesforce?tab=readme-ov-file#examples) for you to access Salesforce through its API, but in this workshop, we’ve utilized `username`, `password` and `token`. If you need help with getting your API token for Salesforce, take a look at [this article](https://help.salesforce.com/s/articleView?id=xcloud.user_security_token.htm&type=5). You’ll need to add these values as environment variables like we did earlier with our Slack tokens. You can use the following commands: +3. Let’s also send the information to Salesforce. There are [several ways](https://github.com/simple-salesforce/simple-salesforce?tab=readme-ov-file#examples) for you to access Salesforce through its API, but in this example, we’ve utilized `username`, `password` and `token` parameters. If you need help with getting your API token for Salesforce, take a look at [this article](https://help.salesforce.com/s/articleView?id=xcloud.user_security_token.htm&type=5). You’ll need to add these values as environment variables like we did earlier with our Slack tokens. You can use the following commands: ```bash @@ -322,7 +459,7 @@ export SF_TOKEN= ``` -4. We’re going to use assume that order information is stored in the Order object and that the confirmation IDs map to the 8-digit Order numbers within Salesforce. Given that assumption, all we need to do is make a query to find the correct object, add the inputted information, and we’re done. Place this code before the last except within the `/views/sample_views.py` file. +4. We’re going to use assume that order information is stored in the Order object and that the confirmation IDs map to the 8-digit Order numbers within Salesforce. Given that assumption, we need to make a query to find the correct object, add the inputted information, and we’re done. Place this functionality before the last excerpt within the `/views/sample_views.py` file. ```python @@ -372,6 +509,8 @@ With these imports, add `simple_salesforce` to your `requirements.txt` file, the pip install -r requirements.txt ``` +![Image of delivery tracker app](/img/bolt-python/delivery-tracker-main.png) + ## Testing your app Test your app one last time, and you’re done! From 6226c3355c7aa7bd12a108eca3ded379cdb453b9 Mon Sep 17 00:00:00 2001 From: Haley Elmendorf Date: Tue, 7 Oct 2025 14:49:20 -0500 Subject: [PATCH 07/11] fix tabs --- .../order-confirmation/order-confirmation.md | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/docs/english/tutorial/order-confirmation/order-confirmation.md b/docs/english/tutorial/order-confirmation/order-confirmation.md index 5c70346e1..d4fefc778 100644 --- a/docs/english/tutorial/order-confirmation/order-confirmation.md +++ b/docs/english/tutorial/order-confirmation/order-confirmation.md @@ -89,24 +89,30 @@ Still in the app settings, navigate to the **Install App** page in the left side Within a terminal of your choice, set the two tokens from the previous step as environment variables using the commands below. Make sure not to mix these two up, `SLACK_APP_TOKEN` will start with “xapp-“ and `SLACK_BOT_TOKEN` will start with “xoxb-“. - + + ```bash export SLACK_APP_TOKEN= export SLACK_BOT_TOKEN= ``` - - + + + + ``` set SLACK_APP_TOKEN= set SLACK_BOT_TOKEN= ``` - - + + + + ''' $env:SLACK_APP_TOKEN="YOUR-APP-TOKEN-HERE" $env:SLACK_BOT_TOKEN="YOUR-BOT-TOKEN-HERE" ``` - + + Run the following commands to activate a virtual environment for your Python packages to be installed, install the dependencies, and start your app. From 18ffa013b2e380d231e9d61751e398e21fb53255 Mon Sep 17 00:00:00 2001 From: Haley Elmendorf Date: Tue, 7 Oct 2025 14:50:37 -0500 Subject: [PATCH 08/11] does this fix tabs? --- .../tutorial/order-confirmation/order-confirmation.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/docs/english/tutorial/order-confirmation/order-confirmation.md b/docs/english/tutorial/order-confirmation/order-confirmation.md index d4fefc778..25bf67783 100644 --- a/docs/english/tutorial/order-confirmation/order-confirmation.md +++ b/docs/english/tutorial/order-confirmation/order-confirmation.md @@ -90,28 +90,22 @@ Within a terminal of your choice, set the two tokens from the previous step as e - ```bash export SLACK_APP_TOKEN= export SLACK_BOT_TOKEN= ``` - - ``` set SLACK_APP_TOKEN= set SLACK_BOT_TOKEN= ``` - - ''' $env:SLACK_APP_TOKEN="YOUR-APP-TOKEN-HERE" $env:SLACK_BOT_TOKEN="YOUR-BOT-TOKEN-HERE" ``` - From 08ef2df19755965d2bb3c376da997356721c3e72 Mon Sep 17 00:00:00 2001 From: Haley Elmendorf Date: Tue, 7 Oct 2025 14:54:14 -0500 Subject: [PATCH 09/11] remove tabs --- .../order-confirmation/order-confirmation.md | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/docs/english/tutorial/order-confirmation/order-confirmation.md b/docs/english/tutorial/order-confirmation/order-confirmation.md index 25bf67783..f2917abf1 100644 --- a/docs/english/tutorial/order-confirmation/order-confirmation.md +++ b/docs/english/tutorial/order-confirmation/order-confirmation.md @@ -88,26 +88,26 @@ Still in the app settings, navigate to the **Install App** page in the left side Within a terminal of your choice, set the two tokens from the previous step as environment variables using the commands below. Make sure not to mix these two up, `SLACK_APP_TOKEN` will start with “xapp-“ and `SLACK_BOT_TOKEN` will start with “xoxb-“. - - - ```bash - export SLACK_APP_TOKEN= - export SLACK_BOT_TOKEN= - ``` - - - ``` - set SLACK_APP_TOKEN= - set SLACK_BOT_TOKEN= - ``` - - - ''' - $env:SLACK_APP_TOKEN="YOUR-APP-TOKEN-HERE" - $env:SLACK_BOT_TOKEN="YOUR-BOT-TOKEN-HERE" - ``` - - +For macOS: + +```bash +export SLACK_APP_TOKEN= +export SLACK_BOT_TOKEN= +``` + +For Windows Command Prompt: + +```cmd +set SLACK_APP_TOKEN= +set SLACK_BOT_TOKEN= +``` + +For Windows PowerShell: + +```powershell +$env:SLACK_APP_TOKEN="YOUR-APP-TOKEN-HERE" +$env:SLACK_BOT_TOKEN="YOUR-BOT-TOKEN-HERE" +``` Run the following commands to activate a virtual environment for your Python packages to be installed, install the dependencies, and start your app. From 88358f6345c4ffcdb88a13de3539c4f7ad85a810 Mon Sep 17 00:00:00 2001 From: Haley Elmendorf Date: Tue, 7 Oct 2025 15:16:28 -0500 Subject: [PATCH 10/11] more formatting tweaks --- .../order-confirmation/order-confirmation.md | 226 ++++++++---------- 1 file changed, 105 insertions(+), 121 deletions(-) diff --git a/docs/english/tutorial/order-confirmation/order-confirmation.md b/docs/english/tutorial/order-confirmation/order-confirmation.md index f2917abf1..fa392e926 100644 --- a/docs/english/tutorial/order-confirmation/order-confirmation.md +++ b/docs/english/tutorial/order-confirmation/order-confirmation.md @@ -134,7 +134,7 @@ We'll make four changes to the app: * Handle when the correct delivery IDs are sent and bring up a modal for more information * Send the information to all of the places needed when the form is submitted (including third-party locations) -All of these steps require you to use [Block Kit Builder](https://app.slack.com/block-kit-builder), a tool that helps you create messages, modals and other surfaces within Slack. Open [Block Kit Builder](https://app.slack.com/block-kit-builder), take a look and play around! We’ll create some views next. +For all of these steps, we will use [Block Kit Builder](https://app.slack.com/block-kit-builder), a tool that helps you create messages, modals and other surfaces within Slack. Open [Block Kit Builder](https://app.slack.com/block-kit-builder), take a look, and play around! We’ll create some views next. ### Updating the "hi" message @@ -170,7 +170,6 @@ def delivery_message_callback(context: BoltContext, say: Say, logger: Logger): Next, you’ll need to make some connections so that this function is called when a message is sent in the channel where your app is. Head to `messages/__init__.py` and add the line below to the register function. Don’t forget to add the import to the callback function as well! ```python - from .sample_message import delivery_message_callback # import the function to this file def register(app: App): @@ -178,7 +177,6 @@ def register(app: App): # This regex will capture any number letters followed by dash # and then any number of digits, our "confirmation number" e.g. ASDF-1234 app.message(re.compile(r"[A-Za-z]+-\d+"))(delivery_message_callback) ## add this line! - ``` Now, restart your server to bring in the new code and test that your function works by sending an order confirmation ID, like `HWOA-1524`, in your testing channel. Your app should respond with the message you created within Block Kit Builder. @@ -190,15 +188,15 @@ Notice that if you try to click on either of the buttons within your message, no 1. Head to Block Kit Builder once again. We want to build a message that lets the user know that the wrong order ID has been submitted. Here's a [section](/reference/block-kit/blocks/section-block) block to get you started: ```json - "blocks": [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "Delivery *{delivery_id}* was incorrect ❌" - } - } - ] + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "Delivery *{delivery_id}* was incorrect ❌" + } + } + ] ``` View this block in Block Kit Builder [here](https://app.slack.com/block-kit-builder/#%7B%22blocks%22:%5B%7B%22type%22:%22section%22,%22text%22:%7B%22type%22:%22mrkdwn%22,%22text%22:%22Delivery%20*%7Bdelivery_id%7D*%20was%20incorrect%20%E2%9D%8C%22%7D%7D%5D%7D). @@ -229,7 +227,6 @@ This function will call the [`chat.update`](/reference/methods/chat.update) Web 3. Make the connection to this function again within the `actions/__init__.py` folder with the following code: ```python - from slack_bolt import App from .sample_action import sample_action_callback # This can be deleted from .sample_action import deny_delivery_callback @@ -237,7 +234,6 @@ from .sample_action import deny_delivery_callback def register(app: App): app.action("sample_action_id")(sample_action_callback) # This can be deleted app.action("deny_delivery")(deny_delivery_callback) # Add this line - ``` Test out your app by sending in a confirmation number into your channel and clicking the `Not correct` button. If the message is updated, then you’re good to go onto the next step. @@ -250,78 +246,78 @@ The next step is to handle the `Confirm` button. In this case, we’re going to ```json { - "title": { - "type": "plain_text", - "text": "Approve Delivery" - }, - "submit": { - "type": "plain_text", - "text": "Approve" - }, - "type": "modal", - "callback_id": "approve_delivery_view", - "private_metadata": "{delivery_id}", - "blocks": [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "Approving delivery *{delivery_id}*" - } - }, - { - "type": "input", - "block_id": "notes", - "label": { - "type": "plain_text", - "text": "Additional delivery notes" - }, - "element": { - "type": "plain_text_input", - "action_id": "notes_input", - "multiline": true, - "placeholder": { - "type": "plain_text", - "text": "Add notes..." - } - }, - "optional": true - }, - { - "type": "input", - "block_id": "location", - "label": { - "type": "plain_text", - "text": "Delivery Location" - }, - "element": { - "type": "plain_text_input", - "action_id": "location_input", - "placeholder": { - "type": "plain_text", - "text": "Enter the location details..." - } - }, - "optional": true - }, - { - "type": "input", - "block_id": "channel", - "label": { - "type": "plain_text", - "text": "Notification Channel" - }, - "element": { - "type": "channels_select", - "action_id": "channel_select", - "placeholder": { - "type": "plain_text", - "text": "Select channel for notifications" - } - }, - "optional": false - } - ] + "title": { + "type": "plain_text", + "text": "Approve Delivery" + }, + "submit": { + "type": "plain_text", + "text": "Approve" + }, + "type": "modal", + "callback_id": "approve_delivery_view", + "private_metadata": "{delivery_id}", + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "Approving delivery *{delivery_id}*" + } + }, + { + "type": "input", + "block_id": "notes", + "label": { + "type": "plain_text", + "text": "Additional delivery notes" + }, + "element": { + "type": "plain_text_input", + "action_id": "notes_input", + "multiline": true, + "placeholder": { + "type": "plain_text", + "text": "Add notes..." + } + }, + "optional": true + }, + { + "type": "input", + "block_id": "location", + "label": { + "type": "plain_text", + "text": "Delivery Location" + }, + "element": { + "type": "plain_text_input", + "action_id": "location_input", + "placeholder": { + "type": "plain_text", + "text": "Enter the location details..." + } + }, + "optional": true + }, + { + "type": "input", + "block_id": "channel", + "label": { + "type": "plain_text", + "text": "Notification Channel" + }, + "element": { + "type": "channels_select", + "action_id": "channel_select", + "placeholder": { + "type": "plain_text", + "text": "Select channel for notifications" + } + }, + "optional": false + } + ] } ``` @@ -330,7 +326,6 @@ View this modal in Block Kit Builder [here](https://app.slack.com/block-kit-buil 2. Within the `actions/sample_action.py` file, add the following function, replacing the view with the one you created above. Again, any strings with variables will be updated to f-strings and also any booleans will need to be capitalized. ```python - def approve_delivery_callback(ack, body, client, logger: Logger): try: ack() @@ -360,13 +355,11 @@ def approve_delivery_callback(ack, body, client, logger: Logger): logger.info(f"Approval modal opened by user {body['user']['id']}") except Exception as e: logger.error(e) - ``` Similar to the `deny` button, we need to hook up all the connections. Your `actions/__init__.py` should look something like this: ```python - from slack_bolt import App from .sample_action import deny_delivery_callback from .sample_action import approve_delivery_callback @@ -375,7 +368,6 @@ from .sample_action import approve_delivery_callback def register(app: App): app.action("approve_delivery")(approve_delivery_callback) app.action("deny_delivery")(deny_delivery_callback) - ``` Test your app by typing in a confirmation number in channel, click the confirm button and see if the modal comes up and you are able to capture information from the user. @@ -387,35 +379,34 @@ Lastly, we’ll handle the submission of the form, which will trigger two things 1. Here’s a simple example of a message that you can use to present the information in channel. ```json - "blocks": [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "✅ Delivery *{delivery_id}* approved:" - } - }, - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "*Delivery Notes:*\n{notes or 'None'}" - } - }, - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "*Delivery Location:*\n{loc or 'None'}" - } - } - ] + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "✅ Delivery *{delivery_id}* approved:" + } + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*Delivery Notes:*\n{notes or 'None'}" + } + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*Delivery Location:*\n{loc or 'None'}" + } + } + ] ``` View this in Block Kit Builder [here](https://app.slack.com/block-kit-builder/?1#%7B%22blocks%22:%5B%7B%22type%22:%22section%22,%22text%22:%7B%22type%22:%22mrkdwn%22,%22text%22:%22%E2%9C%85%20Delivery%20*%7Bdelivery_id%7D*%20approved:%22%7D%7D,%7B%22type%22:%22section%22,%22text%22:%7B%22type%22:%22mrkdwn%22,%22text%22:%22*Delivery%20Notes:*%5Cn%7Bnotes%20or%20'None'%7D%22%7D%7D,%7B%22type%22:%22section%22,%22text%22:%7B%22type%22:%22mrkdwn%22,%22text%22:%22*Delivery%20Location:*%5Cn%7Bloc%20or%20'None'%7D%22%7D%7D%5D%7D). Modify it however you like and then place it within the code below in the `/views/sample_views.py` file. ```python - def handle_approve_delivery_view(ack, client, view, logger: Logger): try: ack() @@ -433,36 +424,30 @@ def handle_approve_delivery_view(ack, client, view, logger: Logger): except Exception as e: logger.error(f"Error in approve_delivery_view: {e}") - ``` 2. Making the connections in the `/views/__init__.py `file, we can test that this works by sending a message once again in our test channel. ```python - from slack_bolt import App from .sample_view import handle_approve_delivery_view def register(app: App): app.view("sample_view_id")(sample_view_callback) # This can be deleted app.view("approve_delivery_view")(handle_approve_delivery_view) ## Add this line - ``` 3. Let’s also send the information to Salesforce. There are [several ways](https://github.com/simple-salesforce/simple-salesforce?tab=readme-ov-file#examples) for you to access Salesforce through its API, but in this example, we’ve utilized `username`, `password` and `token` parameters. If you need help with getting your API token for Salesforce, take a look at [this article](https://help.salesforce.com/s/articleView?id=xcloud.user_security_token.htm&type=5). You’ll need to add these values as environment variables like we did earlier with our Slack tokens. You can use the following commands: ```bash - export SF_USERNAME= export SF_PASSWORD= export SF_TOKEN= - ``` 4. We’re going to use assume that order information is stored in the Order object and that the confirmation IDs map to the 8-digit Order numbers within Salesforce. Given that assumption, we need to make a query to find the correct object, add the inputted information, and we’re done. Place this functionality before the last excerpt within the `/views/sample_views.py` file. ```python - # Extract just the numeric portion from delivery_id delivery_number = "".join(filter(str.isdigit, delivery_id)) @@ -493,7 +478,6 @@ export SF_TOKEN= except Exception as sf_error: logger.error(f"Update failed for order {delivery_id}: {sf_error}") # Continue execution even if Salesforce update fails - ``` You’ll also need to add the two imports that are found within this code to the top of the file. From 7ff065601db624eb7a685c845237b43945630aa7 Mon Sep 17 00:00:00 2001 From: Haley Elmendorf Date: Tue, 21 Oct 2025 16:43:20 -0500 Subject: [PATCH 11/11] pr feedback --- .../order-confirmation/order-confirmation.md | 91 +++++++++++++++---- 1 file changed, 71 insertions(+), 20 deletions(-) diff --git a/docs/english/tutorial/order-confirmation/order-confirmation.md b/docs/english/tutorial/order-confirmation/order-confirmation.md index fa392e926..695d6965a 100644 --- a/docs/english/tutorial/order-confirmation/order-confirmation.md +++ b/docs/english/tutorial/order-confirmation/order-confirmation.md @@ -27,7 +27,7 @@ If you don't already have the Slack CLI, install it from your terminal: navigate ### Cloning the starter app -Once installed, use the command `slack create` in your terminal, select the `bolt-python-starter-template`, then choose your preferred language (this tutorial shows Python). Alternatively, you can clone the [Bolt for Python template](https://github.com/slack-samples/bolt-python-starter-template) using git. +Once installed, use the command `slack create` to get started with the Bolt for Python [starter template](https://github.com/slack-samples/bolt-python-starter-template). Alternatively, you can clone the template using Git. You can remove the portions from the template that are not used within this tutorial to make things a bit cleaner for yourself. To do this, open your project in VS Code (you can do this from the terminal with the `code .` command) and delete the `commands`, `events`, and `shortcuts` folders from the `/listeners` folder. You can also do the same to the corresponding folders within the `/listeners/tests` folder as well. Finally, remove the imports of these files from the `/listeners/__init__.py` file. @@ -35,7 +35,10 @@ You can remove the portions from the template that are not used within this tuto We’ll use the contents of the `manifest.json` file below. This file describes the metadata associated with your app, like its name and permissions that it requests. -Copy the contents of the file and [create a new app](https://api.slack.com/apps/new). Next, choose **From a manifest** and follow the prompts, pasting the manifest file contents you copied. +These values are used to create an app in one of two ways: + +- **With the Slack CLI**: Save the contents of the file to your project's `manifest.json` file then skip ahead to [starting your app](#starting-your-app). +- **With app settings**: Copy the contents of the file and [create a new app](https://api.slack.com/apps/new). Next, choose **From a manifest** and follow the prompts, pasting the manifest file contents you copied. ```json { @@ -84,7 +87,7 @@ Once your app has been created, scroll down to **App-Level Tokens** on the **Bas Still in the app settings, navigate to the **Install App** page in the left sidebar. Install your app. When you press **Allow**, this means you’re agreeing to install your app with the permissions that it’s requesting. Copy the bot token that you receive as well and store this in a safe place as well for subsequent steps. -## Starting your app's server +## Saving credentials Within a terminal of your choice, set the two tokens from the previous step as environment variables using the commands below. Make sure not to mix these two up, `SLACK_APP_TOKEN` will start with “xapp-“ and `SLACK_BOT_TOKEN` will start with “xoxb-“. @@ -109,18 +112,26 @@ $env:SLACK_APP_TOKEN="YOUR-APP-TOKEN-HERE" $env:SLACK_BOT_TOKEN="YOUR-BOT-TOKEN-HERE" ``` +## Starting your app {#starting-your-app} + Run the following commands to activate a virtual environment for your Python packages to be installed, install the dependencies, and start your app. ```bash # Setup your python virtual environment -python3 -m venv .venv +python -m venv .venv source .venv/bin/activate # Install the dependencies pip install -r requirements.txt # Start your local server -python3 app.py +slack run +``` + +If you're not using the Slack CLI, a different `python` command can be used to start your app instead: + +```sh +python app.py ``` Now that your app is running, you should be able to see it within Slack. In Slack, create a channel that you can test in and try inviting your bot to it using the `/invite @Your-app-name-here` command. Check that your app works by saying “hi” in the channel where your app is, and you should receive a message back from it. If you don’t, ensure you completed all the steps above. @@ -138,23 +149,48 @@ For all of these steps, we will use [Block Kit Builder](https://app.slack.com/bl ### Updating the "hi" message -The first thing we want to do is change the “hi, how are you?” message from our app into something more useful. Here’s something that you can use to start off with, but you can make it your own within Block Kit Builder. Once you have something you like, copy the blocks by clicking the **Copy Payload** button in the top right. +The first thing we want to do is change the “hi, how are you?” message from our app into something more useful. Here’s a `blocks` object built with Block Kit Builder: -Take the function below and place your blocks within the blocks dictionary `[]`. Update the payload: -* Remove the initial blocks key and convert any boolean true values to `True` to fit with Python conventions. -* If you see variables within `{}` brackets, this is part of an f-string, which allows you to insert variables within strings in a clean manner. Place the `f` character before these strings like this: +```json + + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "Confirm *{delivery_id}* is correct?" + } + }, + { + "type": "actions", + "elements": [ + { + "type": "button", + "text": { + "type": "plain_text", + "text": "Correct", + "emoji": true + }, + "style": "primary", + "action_id": "approve_delivery" + }, + { + "type": "button", + "text": { + "type": "plain_text", + "text": "Not correct", + "emoji": true + }, + "style": "danger", + "action_id": "deny_delivery" + } + ] + } + ] -```python -{ - "type": "section", - "text": { - "type": "mrkdwn", - "text": f"Confirm *{delivery_id}* is correct?", # place the "f" character here at the beginning of the string - }, -}, ``` -Place all of this in the `sample_message.py` file. +Take the function below and place your blocks within the blocks dictionary `[]`. ```python def delivery_message_callback(context: BoltContext, say: Say, logger: Logger): @@ -167,13 +203,28 @@ def delivery_message_callback(context: BoltContext, say: Say, logger: Logger): logger.error(e) ``` -Next, you’ll need to make some connections so that this function is called when a message is sent in the channel where your app is. Head to `messages/__init__.py` and add the line below to the register function. Don’t forget to add the import to the callback function as well! +Update the payload: +* Remove the initial blocks key and convert any boolean true values to `True` to fit with Python conventions. +* If you see variables within `{}` brackets, this is part of an f-string, which allows you to insert variables within strings in a clean manner. Place the `f` character before these strings like this: + +```python +{ + "type": "section", + "text": { + "type": "mrkdwn", + "text": f"Confirm *{delivery_id}* is correct?", # place the "f" character here at the beginning of the string + }, +}, +``` + +Place all of this in the `sample_message.py` file. + +Next, you’ll need to register this listener to respond when a message is sent in the channel with your app. Head to `messages/__init__.py` and overwrite the function there with the one below, which registers the function. Don’t forget to add the import to the callback function as well! ```python from .sample_message import delivery_message_callback # import the function to this file def register(app: App): - app.message(re.compile("(hi|hello|hey)"))(sample_message_callback) # This can be deleted! # This regex will capture any number letters followed by dash # and then any number of digits, our "confirmation number" e.g. ASDF-1234 app.message(re.compile(r"[A-Za-z]+-\d+"))(delivery_message_callback) ## add this line!