Hi,
Spent too much time on the topic research and investigation and now need your help since i'm out of ideas and power at all =(
I'm struggling with the slack python app where it offers to a user several actions through the buttons and im handling them by ['type'] from "payload". One button requires further actions - pickup address details from a user.

I'm providing them with the Dialog option to be able to input the address details. If i process the data i received from dialog submission (after user click Submit button) and send address details to another method, then as expected (due to 3 sec limit) dialog window failing with "We had some trouble connecting. Try again?".

I saw many topics regarding this but none of them helped me. I also tried to send "200 OK" to 'response_url' received in "dialog_submission" still with no luck. If i return make_response("", 200) just after handling elif form_json["type"] == "dialog_submission", the dialog closes but i don't know how to proceed further with data received to be able to send back "sending_get_products(form_json, pickup_city, pickup_street, pickup_building, pickup_country)" method results.

I saw python dialog example here:
https://github.com/slackapi/python-dialog-example/blob/master/example.py but it doesn't answer that since the code is not covering example shown on video here:
https://github.com/slackapi/python-dialog-example
Could you please explain what is the best way to handle such scenarios?
slackclient==2.8.0
slackeventsapi==2.2.1
Python 3.8.2
ProductName: Mac OS X
ProductVersion: 10.15.5
BuildVersion: 19F101
Darwin Kernel Version 19.5.0: Tue May 26 20:41:44 PDT 2020; root:xnu-6153.121.2~2/RELEASE_X86_64
That is my code for handling message_actions:
# the endpoint Slack will send the user's menu selection to
@app.route("/slack/message_actions", methods=["POST"])
def message_actions(address_details_dict={}):
# Parse the request payload
form_json = json.loads(request.form["payload"])
# Verify that the request came from Slack
verify_slack_token(form_json["token"])
# Check to see button or interactive message response and process accordingly
# If button was clicked:
if form_json['type'] == "block_actions":
selection = form_json["actions"][0]["action_id"]
#check which one button:
if selection == "get_products_clicked":
# suppling with the Dialog to get user input from dialog for 'city', 'street', 'building', 'country'
address_details = slack_client.api_call("chat.postMessage", json=DialogToSlack(form_json["channel"]["id"],form_json["user"]["id"]).dialog_to_slack())
address_details_dict[form_json["user"]["id"]] = {
"address_channel": address_details["channel"],
"message_ts": "",
"address": {}
}
# if user clicked on a button -> dialog.open
if form_json["type"] == "interactive_message":
# Add the message_ts to the user's address info
address_details_dict[form_json["user"]["id"]]["message_ts"] = form_json["message_ts"]
# Show address details dialog to the user
open_dialog = slack_client.api_call(
"dialog.open",
json={
'trigger_id': form_json["trigger_id"],
'dialog': {
"title": "Enter location address",
"submit_label": "Submit",
"notify_on_cancel": True,
"state": "Limo",
"callback_id": form_json["user"]["id"] + "location_details_form",
"elements": [
{
"type": "text",
"label": "Enter pickup city:",
"name": "pickup_city"
},
{
"type": "text",
"label": "Enter pickup street:",
"name": "pickup_street"
},
{
"type": "text",
"label": "Enter pickup building number:",
"name": "pickup_building"
},
{
"type": "text",
"label": "Enter pickup building country:",
"name": "pickup_country"
}
]
}
}
)
# Update the message to show that we're in the process of taking their address
slack_client.api_call(
"chat.update",
json={
'channel': address_details_dict[form_json["user"]["id"]]["address_channel"],
'ts': form_json["message_ts"],
'text': ":pencil: Taking your address details...",
'attachments': []
}
)
# updating original message confirming that we received address details and process the data
# received
elif form_json["type"] == "dialog_submission":
location_details = address_details_dict[form_json["user"]["id"]]
pickup_city = form_json["submission"]['pickup_city']
pickup_street = form_json["submission"]['pickup_street']
pickup_building = form_json["submission"]['pickup_building']
pickup_country = form_json["submission"]['pickup_country']
# Update the message to show that we've received the address
response = slack_client.api_call(
"chat.update",
json={
'channel': address_details_dict[form_json["user"]["id"]]["address_channel"],
'ts': location_details["message_ts"],
'text': ":white_check_mark: Address received!",
'attachments': []
# 'user': form_json["user"]["id"]
}
)
# below is the method i need to run with received user input data
# sending_get_products(form_json, pickup_city, pickup_street, pickup_building, pickup_country)
# Also tried to send a response to 'response_url'
# url = form_json['response_url']
# slack_data = {'message': '200 OK!'}
#
# response = requests.post(
# url, json=slack_data,
# headers={'Content-Type': 'application/json'}
# )
# Send an HTTP 200 response with empty body so Slack knows we're done here
return make_response("", 200)
Dialog closes, data received are sending to another method that return a result back to slack chat.postMessage
If i run my method (A) that uses user input data before returning empty 200 response dialog failing to close. If i send empty 200 response just after handling "dialog_submission" event type i don't know how to proceed with data received to be able to use them and post to Slack method's A return back.
For general questions/issues about Slack API platform or its server-side, could you submit questions at https://my.slack.com/help/requests/new instead. :bow:
Please read the Contributing guidelines and Code of Conduct before creating this issue or pull request. By submitting, you are agreeing to the those rules.
@alosipov Hi, thank you for asking the question!
As you're already aware of, acknowledging a request from Slack within 3 seconds plus doing something asynchronously can be challenging.
We're actively working on a new framework. It is Bolt for Python: https://github.com/slackapi/bolt-python
The project is still in alpha and is going to be GAed within a few months. That said, all the basic features are already available. Inside the framework, we use concurrent.futures.thread.ThreadPoolExecutor for asynchronous operations. This means the framework starts a new thread execution while immediately returning 200 OK to Slack as acknowledgment.
With Bolt for Python, you can use ack() utility method that immediately returns 200 OK. Then, the listener function continues doing other tasks asynchronously in another thread. Here is a simple working example using dialogs:
https://github.com/slackapi/bolt-python/blob/v0.3.1a0/samples/dialogs_app.py
@app.action({"type": "dialog_submission", "callback_id": "dialog-callback-id"})
def dialog_submission(ack, client):
ack() # returns 200 OK immediately
# continue running the following code in another thread
response = client.chat_postMessage(channel=channel_id, text="The task completed!") # WebClient
You can implement similar using threads or asyncio. If you find useful code in the project, please feel free to reuse it. Bolt is licensed under the MIT license. If you're fine to use Bolt (again, this is still in alpha), it should be the easiest way.
@seratch Hi Kazuhiro! Thank you very much for the response! I will definitely check Bolt framework right now! I have already spent few days as well of puzzling how to achieve that with using threads or asyncio =) Maybe you can point me to the right direction how do that with threads or asyncio for my future knowledge and development in python programming? I really did a lot of research through web, books etc and could not manage by myself. So trying to use the community as hte last instance. I really appreciate all great work you and the team did and doing on this project! Thanks for that!
Thanks
Regards,
Alexey
@alosipov This is how I would open up a Modal (very similar to a dialog) so I'm able to return a response fast while still continuing on in my execution. I haven't delved into async Python myself so I'm not much help there, however using threads like this has helped me improve my response time in FaaS deployments on cold starts
https://github.com/ruberVulpes/slack-modal-test/blob/threaded/app.py
import os
from threading import Thread
from flask import Flask, make_response, request
from slack import WebClient
from slack.web.classes.interactions import SlashCommandInteractiveEvent
from slack.web.classes import views
from slack.web.classes.blocks import InputBlock
from slack.web.classes.elements import PlainTextInputElement, PlainTextObject
slack_client = WebClient(os.environ["SLACK_BOT_TOKEN"])
flask_app = Flask(__name__)
@flask_app.route("/slack/interactions", methods=["POST"])
def interactions():
# Do nothing with the submission
return make_response("", 200)
@flask_app.route("/slack/commands/modal", methods=["POST"])
def modal_post():
command = SlashCommandInteractiveEvent(request.form)
Thread(target=open_modal, args=(command, )).start()
return make_response("", 200)
def open_modal(command: SlashCommandInteractiveEvent):
title = PlainTextObject(text="Color Survey")
color_input_blocks = [InputBlock(label=PlainTextObject(text="What is your favorite color?"),
element=PlainTextInputElement(placeholder="Green")),
InputBlock(label=PlainTextObject(text="Why is that your favorite color?"),
element=PlainTextInputElement(placeholder="It reminds me of my childhood home"),
optional=True)]
modal = views.View(type="modal", title=title, blocks=color_input_blocks, submit="Submit")
slack_client.views_open(trigger_id=command.trigger_id, view=modal)
if __name__ == '__main__':
flask_app.run(port=5000)
@ruberVulpes thanks William! Will try to use your suggestion and example in my code and will report here if I'm finally win this fight :)
@ruberVulpes if you add sleep(5) to your interactions() func - to emulate 5 sec work with data received in submission, then same thing is happening - am i missing something?
@ruberVulpes running it in another Thread helped =)
def interactions():
# process the data from the submission
data = json.loads(request.form["payload"])
Thread(target=run_actions, args=(data, )).start()
return make_response("", 200)
Most helpful comment
@alosipov Hi, thank you for asking the question!
As you're already aware of, acknowledging a request from Slack within 3 seconds plus doing something asynchronously can be challenging.
We're actively working on a new framework. It is Bolt for Python: https://github.com/slackapi/bolt-python
The project is still in alpha and is going to be GAed within a few months. That said, all the basic features are already available. Inside the framework, we use
concurrent.futures.thread.ThreadPoolExecutorfor asynchronous operations. This means the framework starts a new thread execution while immediately returning 200 OK to Slack as acknowledgment.With Bolt for Python, you can use
ack()utility method that immediately returns 200 OK. Then, the listener function continues doing other tasks asynchronously in another thread. Here is a simple working example using dialogs:https://github.com/slackapi/bolt-python/blob/v0.3.1a0/samples/dialogs_app.py
You can implement similar using threads or asyncio. If you find useful code in the project, please feel free to reuse it. Bolt is licensed under the MIT license. If you're fine to use Bolt (again, this is still in alpha), it should be the easiest way.