slackapi / bolt-python

A framework to build Slack apps using Python
https://slack.dev/bolt-python/
MIT License
1.02k stars 236 forks source link

Can't seem to pass back errors for modal field during `.action()` callback method execution #1092

Closed macintacos closed 3 weeks ago

macintacos commented 3 weeks ago

I'm trying to build a modal with a simple input block, and pass back errors to the user after an API call. It looks something like this:

# This is what the input is described in the view
## Note: this is an excerpt, the modal + input renders fine. These are
## built with the slack_sdk's models here: https://slack.dev/python-slack-sdk/api-docs/slack_sdk/models/blocks/index.html
            blocks.InputBlock(
                label="ID(s)",
                block_id="ids",
                dispatch_action=True,
                element=blocks.PlainTextInputElement(
                    action_id="contents",
                ),
            ),
# ...

# Elsewhere, I have defined a callback function that gets hit when the user hits "enter" after 
# typing something in the above input field. This method is registered as an "action" for this the 
# above block ID. That looks something like the following (simplified for brevity):

async def check_ids(
    ack: AsyncAck,
    body: BodyViewDict,
    action: dict,
    client: AsyncWebClient,
) -> None:
    try:
        response = httpx.get("/some/endpoint/that/will/fail").raise_for_status()
    except httpx.HTTPStatusError as err:
        errors: dict = {}
        match err.response.status_code:
            case 404:
                # This is DEFINITELY being executed, I can see it while debugging
                errors[action["block_id]] = "foo"
            case _:
                pass

        await ack(response_action="errors", errors=errors)
        return
    # do something with the response here if we didn't run into any problems
    ack ()

# Here we make sure that it responds to the input with the above method
app.action({"block_id": "ids", "action_id": "contents"})(self.check_ids)

The API call itself isn't the point, I can debug the code and see everything being executed perfectly fine. However, when the ack(response_action="errors", errors=errors) method gets called, nothing in the UI gets updated, but the action is indeed ack'd. I even set breakpoints in the Slack Bolt SDK here and can see that the response is being sent, and that no other errors are being reported. It just... silently doesn't do anything (except successfully ack() without updating the UI).

I even removed everything in check_ids() and just immediately respond with await ack(response_action="errors", errors={"ids": "foo!"}) and that doesn't work. I turned on debug logging and can even see the response from Slack:

2024-06-12T18:53:30.489939Z [debug    ] Responding with status: 200 body: "{"response_action": "errors", "errors": {"ids": "foo!"}}" (13 millis) filename=asyncio_runner.py func_name=_debug_log_completion lineno=187 module=asyncio_runner thread=140737472996544 thread_name=MainThread

...but again, the modal is not updated with any annotations to say that there is a problem with the field

Can someone tell me what I'm doing wrong here? According to the documentation this feels like the right thing to do, so either something isn't documented and I'm doing it wrong or there's a problem in the Slack backend or something that I can't figure out. Any help would be very much appreciated, thanks!

Reproducible in:

I'm using Poetry, so copy/pasting from my poetry.lock file here are the versions:

[[package]]
name = "slack-bolt"
version = "1.19.0"
description = "The Bolt Framework for Python"
optional = false
python-versions = ">=3.6"
files = [
    {file = "slack_bolt-1.19.0-py2.py3-none-any.whl", hash = "sha256:810891cc110e0fb3948f26c044302ed90abda2a25e9ec1689e179da8bb2747cf"},
    {file = "slack_bolt-1.19.0.tar.gz", hash = "sha256:45135b8a1dea40abeb20b9b9d1973953f6755b76388156ee218d6c61d96f992a"},
]

[package.dependencies]
slack-sdk = ">=3.25.0,<4"

[[package]]
name = "slack-sdk"
version = "3.27.2"
description = "The Slack API Platform SDK for Python"
optional = false
python-versions = ">=3.6"
files = [
    {file = "slack_sdk-3.27.2-py2.py3-none-any.whl", hash = "sha256:af97158e6ac7f667e158e8036e63dc1f79db9bd36216a33c10fcc49be7c2f30c"},
    {file = "slack_sdk-3.27.2.tar.gz", hash = "sha256:bb145bf2bd93b60a17cd55c05cb15868c9a07d845b6fb608c798b50bce21cb99"},
]

[package.extras]
optional = ["SQLAlchemy (>=1.4,<3)", "aiodns (>1.0)", "aiohttp (>=3.7.3,<4)", "boto3 (<=2)", "websocket-client (>=1,<2)", "websockets (>=9.1,<13)"]

Python runtime version

Python 3.10.6

OS info

(Paste the output of sw_vers && uname -v on macOS/Linux or ver on Windows OS)

$ sw_vers && uname -v
ProductName:        macOS
ProductVersion:     14.5
BuildVersion:       23F79
Darwin Kernel Version 23.5.0: Wed May  1 20:12:58 PDT 2024; root:xnu-10063.121.3~5/RELEASE_ARM64_T6000

Requirements

Please read the Contributing guidelines and Code of Conduct before creating this issue or pull request. By submitting, you are agreeing to those rules.

seratch commented 3 weeks ago

Hi @macintacos, thanks for asking the question! Unfortunately, response_action="errors" works only with view_submission request patterns (meaning @app.view listeners in Bolt). Thus, you cannot set the input block validation error in response to @app.action requests. I understand that this is not so convenient for your use case, but hope this clarifies.

macintacos commented 3 weeks ago

@seratch Understood. Some follow up questions:

seratch commented 3 weeks ago

Is this documented anywhere? If it's not, can it be?

The Bolt documentation does not clearly mention it, but the platform documentation states: "Upon receiving a view_submission event" (https://api.slack.com/surfaces/modals#displaying_errors). More detailed Bolt documentation for this scenario would be more developer-friendly, though.

How do I get across to a user in a modal that what they've entered is incorrect? It's meant to be a dynamic field isn't it? Otherwise I don't understand why someone would want to use this capability when you can't give a user much feedback.

The "block_actions" interactions on a modal tend to be used for asynchronously calling views.update API to dynamically updating the whole view. For instance, placing only the first select menu and then, when a user selects an item, displaying the second select menu with the consideration of the first item selection. This example app demonstrates the pattern well: https://github.com/seratch/apidays-workshop-2020?tab=readme-ov-file#multiple-steps-in-a-modal

Therefore, when you need to display validation errors, having multiple pages for a single modal view and asking a user to click the standard "Submit" button for each view is more common approach.

I'm trying to make an input field such that, when a user provides input and presses "enter", a new field will appear below it with some options. Just want to make sure - this is possible, right?

For this need, as I mentioned above, using only "view_submission" (@app.view) would be the best approach. It's still possible to display both already selected blocks and new select menus on the same view, which is updated in response to "view_submission" (@app.view) event. Alternative way would be to store the already selected items in "private_metadata" field instead of displaying them again, but either is fine.

I hope this helps.

macintacos commented 3 weeks ago

Thank you @seratch, that helps. In the meantime, I'll just "build in" my own validation and provide a custom error when passing a view back for Slack to render.

Can this be considered a feature request for the ability to pass back errors for dispatched block actions? Is that in the roadmap at all? It feels like this is something natural that one would want when handling one of these actions, considering that it's already supported in form validation.

seratch commented 3 weeks ago

Can this be considered a feature request for the ability to pass back errors for dispatched block actions? Is that in the roadmap at all?

I understand that enabling input validations, even with messages and home tabs (in addition to just modals), would be useful for many use cases, but this seems to be a limitation by design. Therefore, I don't anticipate any immediate enhancements for it. That being said, we can share your feedback with the product teams. If you would like to know about any updates on the platform, please refer to https://api.slack.com/changelog and contact our customer support team either via /feedback in your Slack workspace or https://my.slack.com/help/requests/new.

Since our team does not have anything further to share on this topic, please let us close this issue now. Thank you so much again for asking the question and providing the insightful feedback!