slackapi / bolt-python

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

Issue in modal submission (We had some trouble connecting.Try again ?) #1026

Closed devikaTV closed 5 months ago

devikaTV commented 8 months ago

I am using socket mode python for a modal triggering with slash command

It has three stages

The error message is displayed when hitting the submit in first modal , sometimes the modal flow closes at that point or continue to next modal with the same error, sometimes the modal doesn't closes even after submit but the changes intended are reflected

Failed to run listener function (error: The request to the Slack API failed. (url: https://www.slack.com/api/views.update)
The server responded with: {'ok': False, 'error': 'not_found'})

Please help me in figuring out what is going wrong Attaching the code

@app.command("/editTicket")
def open_ticket_modal(ack, body, client):
    ack()
    client.views_open(

        trigger_id=body["trigger_id"],
        view={
            "type": "modal",
            "callback_id": "submit_ticket",
            "title": {"type": "plain_text", "text": "Edit  Ticket"},
            "submit": {"type": "plain_text", "text": "Submit"},
            "blocks": [
                {
                    "type": "input",
                    "block_id": "gitissue",
                    "element": {
                        "type": "plain_text_input",
                        "multiline": True,
                        "action_id": "gitissue-value",
                        "placeholder": {
                        "type": "plain_text",
                        "text": "Enter ticket link",
                        }
                    },
                    "label": {
                        "type": "plain_text",
                        "text": " Ticket Link(s):",
                        "emoji": True
                    }
                },
            ]
        }
    )

@app.view("submit_ticket")
def handle_ticket_submission(ack,body,client):
    print(body)
    slack_user_id = body["user"]["id"] 
    values = body["view"]["state"]["values"]
    values[""][""]["value"] if "" in values else None
    viewID = body["view"]["id"]
    hashvalue = body["view"]["hash"]

    gitissue = values["gitissue"]["gitissue-value"]["value"] if "gitissue" in values else None

    errors = {}
    gh = issue.J_Git(gh_token)
    exist , issueno = gh.check_if_valid_ticket(gitissue) 
    if not exist :
        print("Enter a valid  ticket link")
        errors["gitissue"] = "Enter a valid  ticket link"
    if slack_user_id not in editAccessGroup:
        print("You do not have permission to edit this ticket")
        errors["gitissue"] = "You do not have permission to edit this ticket"

    if len(errors) > 0: 
        ack(response_action="errors", errors=errors)    
    else:

        ack(response_action = "update") 
        #fetching the current value of issue
        values = gh.stripBody(gitissue)
        # global variable - editing ticket- to access it in next callback id function
        global ticket
        ticket = gitissue

        client.views_update(
        view_id = viewID,
        hash = hashvalue,
        view={
            "type": "modal",
            "callback_id": "submit_edit_ticket",
            "submit": {
                "type": "plain_text",
                "text": "Submit",
                "emoji": True
            },
            "close": {
                "type": "plain_text",
                "text": "Cancel",
                "emoji": True
            },
            "title": {
                "type": "plain_text",
                "text": "Risk Intake Edit Form",
                "emoji": True
            },
            "blocks": [
            ...

        ]
    }
}           

@app.view("submit_edit_ticket")
def handle_edit_submission_event(ack,body,client):
    ack()
    print(body)
    print(ticket)
    slack_user_id = body["user"]["id"] 
    values = body["view"]["state"]["values"]
    values[""][""]["value"] if "" in values else None
    #retrive values of the input fields
    if len(errors) > 0:

        ack(response_action="errors", errors=errors)

    else:
        ack(response_action = "clear" )
        gh = issue.J_Git(gh_token)
        url = gh.update_issue_body(ticket,Body)
        message = f"Hey \nYou can check the updated issue below \n {ticket}"
filmaj commented 8 months ago

I believe the problem is here:

else:
        ack(response_action = "update") 
        ...
        client.views_update()

You are essentially making two calls to the views.update API in this code block: once via ack(response_action="update") and then again view client.views_update. Both of these calls are doing the same thing: updating the view in response to the user clicking the submit button.

It seems in the ack(response_action="update") case, you are not using this API correctly. Have a look at Updating View Modals documentation. This part of the documentation has two subsections:

  1. Update a view via response_action
  2. Update a view view API

As you can see in the Update a view via response_action, as part of this API, you should provide the updated view. In your code, you are not doing this - you are telling Slack "please update the view" but you are not providing what to update the view to.

My suggestion is to use either of these calls, but not both. Your call to the views.update API via the client looks correct to me: it provides the view ID, the hash (to prevent race conditions) and the updated view contents.

devikaTV commented 7 months ago

Hi @filmaj I am still having the same issue even after only usingviews.update only

seratch commented 7 months ago

This might be confusing, but in response to view_submission requests (meaning within @app.view listeners in a Bolt app), you must use only ack() to manipulates modals. Calling views.update API etc. does not work stably for you.

seratch commented 7 months ago

It is also feasible to update a modal with ack() method first, and then call views.update API for async update again.

Here is a simple code example I've shared in the past: https://github.com/seratch/deepl-for-slack/blob/d8d3e30f2cc032b77d2da3ac9bdead61b875200f/src/index.ts#L40-L58

If this approach does not work for you, perhaps, your code may not use a valid view_id for views.update API call.

devikaTV commented 7 months ago

I see But in the docs I can only see examples of views.update API https://slack.dev/bolt-python/concepts Could you please provide me any examples of using ack() to manipulate modals ?

seratch commented 7 months ago

The "client.views_update()" code exmaple in the document is for @app.action listener use cases. For the pattern, views.update API is the right way to manipulate modals. I've shared an example in the previous reply. Please refer to the code for more details.

devikaTV commented 7 months ago

Hi @seratch

Same issue with error (We had some trouble connecting), views are being updated after 5 sec

The whole thing works fine but with delay and that error displayed at the top of the modal

slack_sdk.errors.SlackApiError: The request to the Slack API failed. (url: https://www.slack.com/api/views.open, status: 200)
The server responded with: {'ok': False, 'error': 'expired_trigger_id'}
seratch commented 7 months ago

It seems that you're trying to open a new modal using views.open API. When you already opened a modal, views.open never works for the same user interaction. You can use either views.update or views.push API in the case.

If you're still confused with the modal APIs, I highly recommend using only ack(response_action="update") within @app.view listeners. This approach is very simple, plus you won't be confused with anything. Also, most use cases can be covered by the approach.

devikaTV commented 7 months ago

So I want a second view on view submission of a modal before the end processing of data

For that I used ack(response_action="update", view= updated_view) only instead of web client API

But now the whole modal is getting closed suddenly once I hit submit

devikaTV commented 7 months ago

I can see the second view if I am using any listeners inside first view and use web client API

But then the submit button will be there doing nothing (I believe we can't remove it in a modal with input type fields)

devikaTV commented 7 months ago

Hi @seratch Could you please help ?

seratch commented 7 months ago

Sorry, with given the last two comments, I am not sure what you're struggling with now.

Could you share a simplified code snippet demonstrating what you want to do (or the one that does not work as you expect)? As we are unable to help you out for the entire app development, you don't need to share your complete code. With simple code snippets, we may be able to help you on more specifics.

devikaTV commented 7 months ago

Sure Please check the below code snippet

@app.command("/editticket")
def open_ticket_modal(ack, body, client):
    ack()
    res=client.views_open(
        trigger_id=body["trigger_id"],
        view={
            "type": "modal",
            "callback_id": "submit_ticket",
            "title": {"type": "plain_text", "text": "Edit Ticket"},
            "blocks": [
            ]

        }
    )
    print(res)

@app.view("submit_ticket")
def handle_ticket_submission(ack,body,client):
    ack()
    values = body["view"]["state"]["values"]

    errors = {}
    # performs some checks on input entered in previous view and raise errors

    if len(errors) > 0: 
        ack(response_action="errors", errors=errors)    
    else:
        ack()
        #a github api call to get values to fill in the next view

        updated_view={
            "type": "modal",
            "callback_id": "submit_edit_ticket",
            "blocks": [

            ]
        }
        ack(response_action="update", view= updated_view2)

@app.view("submit_edit_ticket")
def handle_edit_submission_event(ack,body,client):
    ack()
    slack_user_id = body["user"]["id"] 
    values = body["view"]["state"]["values"]
    values[""][""]["value"] if "" in values else None
    # fetch values from each block in previous view

    errors = {}
    #checks for errors
    if len(errors) > 0:

        ack(response_action="errors", errors=errors)

    else:
        ack(response_action = "clear" )

        #api call for updations
        message = f"....."
        client.chat_postEphemeral (channel = Channel_name, text = message , user = slack_user_id)
seratch commented 7 months ago

Thanks for sharing it. Here is the complete example code. Your code had a few issues:

import os
import logging
import time
from slack_sdk import WebClient
from slack_bolt import App, Ack
from slack_bolt.adapter.socket_mode import SocketModeHandler

logging.basicConfig(level=logging.DEBUG)

app = App(token=os.environ.get("SLACK_BOT_TOKEN"))

@app.command("/editticket")
def open_ticket_modal(ack: Ack, body: dict, client: WebClient):
    ack()  # ack comamnd execution within 3 seconds
    # Open a modal dialog
    client.views_open(
        trigger_id=body["trigger_id"],
        view={
            "type": "modal",
            "callback_id": "submit_ticket",
            "title": {"type": "plain_text", "text": "Edit Ticket"},
            "submit": {"type": "plain_text", "text": "Submit"},
            "blocks": [
                {"type": "section", "text": {"type": "mrkdwn", "text": "First page"}},
            ],
        },
    )

@app.view("submit_ticket")
def handle_ticket_submission(ack: Ack, payload: dict, client: WebClient):
    # Don't call ack() here, ack with no arg closes this modal
    errors = {}
    if len(errors) > 0:
        ack(response_action="errors", errors=errors)
    else:
        ack(
            response_action="update",
            view={
                # type, callback_id, title, blocks are required
                "type": "modal",
                "callback_id": "submit_edit_ticket",
                "title": {"type": "plain_text", "text": "Edit Ticket"},
                # intentionally removed "submit" here to avoid the end-user submitting this modal before it's ready
                "blocks": [
                    {
                        "type": "section",
                        "text": {"type": "mrkdwn", "text": "Loading..."},
                    },
                ],
            },
        )

        # demonstrating how to asynchronously update the same modal
        time.sleep(3)
        client.views_update(
            view_id=payload["id"],
            view={
                "type": "modal",
                "callback_id": "submit_edit_ticket",
                "title": {"type": "plain_text", "text": "Edit Ticket"},
                "submit": {"type": "plain_text", "text": "Submit"},
                "blocks": [
                    {
                        "type": "section",
                        "text": {"type": "mrkdwn", "text": "Second page loaded!"},
                    },
                ],
            },
        )

@app.view("submit_edit_ticket")
def handle_edit_submission_event(ack, body, client):
    errors = {}
    if len(errors) > 0:
        ack(response_action="errors", errors=errors)
    else:
        # If you want to close this modal immediately,
        # either ack() or ack(response_action="clear") works
        ack(
            response_action="update",
            view={
                "type": "modal",
                "callback_id": "submit_edit_ticket",
                "title": {"type": "plain_text", "text": "Edit Ticket"},
                # intentionally removed "submit" as this is the completion page
                "blocks": [
                    {
                        "type": "section",
                        "text": {"type": "mrkdwn", "text": ":white_check_mark: Done!"},
                    },
                ],
            },
        )

if __name__ == "__main__":
    SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"]).start()

I believe this example should answer all the questions you had. Would you mind closing this issue? Whenever you have a new question, please feel free to open a new issue for it!

github-actions[bot] commented 6 months ago

👋 It looks like this issue has been open for 30 days with no activity. We'll mark this as stale for now, and wait 10 days for an update or for further comment before closing this issue out. If you think this issue needs to be prioritized, please comment to get the thread going again! Maintainers also review issues marked as stale on a regular basis and comment or adjust status if the issue needs to be reprioritized.

github-actions[bot] commented 5 months ago

As this issue has been inactive for more than one month, we will be closing it. Thank you to all the participants! If you would like to raise a related issue, please create a new issue which includes your specific details and references this issue number.