Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Shortcut modal does not close after submit #610

Closed
BSouzaDock opened this issue Mar 3, 2022 · 13 comments
Closed

Shortcut modal does not close after submit #610

BSouzaDock opened this issue Mar 3, 2022 · 13 comments
Assignees
Labels
need info question Further information is requested

Comments

@BSouzaDock
Copy link

Hi,

I developed a shortcut but after submitting by submit button, the modal is not closed.

After the submit, several validations are carried out and a message is sent to the user, but I want the modal to be closed and these processes to be in the background, as it consumes a lot of time and there is no need for the user to wait.

In the documentation it asks to be done this way:

# Handle a view_submission request
@app.view("view_1")
def handle_submission(ack, body, client, view, logger):
    # Assume there's an input block with `input_c` as the block_id and `dreamy_input`
    hopes_and_dreams = view["state"]["values"]["input_c"]["dreamy_input"]
    user = body["user"]["id"]
    # Validate the inputs
    errors = {}
    if hopes_and_dreams is not None and len(hopes_and_dreams) <= 5:
        errors["input_c"] = "The value must be longer than 5 characters"
    if len(errors) > 0:
        ack(response_action="errors", errors=errors)
        return
    # Acknowledge the view_submission request and close the modal
    ack()
    # Do whatever you want with the input data - here we're saving it to a DB
    # then sending the user a verification of their submission

    # Message to send user
    msg = ""
    try:
        # Save to DB
        msg = f"Your submission of {hopes_and_dreams} was successful"
    except Exception as e:
        # Handle error
        msg = "There was an error with your submission"

    # Message the user
    try:
        client.chat_postMessage(channel=user, text=msg)
    except e:
        logger.exception(f"Failed to post a message {e}")

I did it this way:

@app.view("aws_access_request")
    def handle_view_events(ack, body, client, view):
        # Assume there's an input block with `input_c` as the block_id and `dreamy_input`
        awsname = view["state"]["values"]["input_aws_select"]["aws_access_select"]["selected_option"]["value"]
        awsrole = view["state"]["values"]["input_aws_role_select"]["aws_access_role_select"]["selected_option"]["value"]
        managermail = view["state"]["values"]["input_aws_access_manager"]["aws_access_manager"]["value"]
        squad = view["state"]["values"]["input_aws_access_squad"]["aws_access_squad"]["value"]
        reason = view["state"]["values"]["input_aws_access_reason"]["aws_access_reason"]["value"]

        senderid = body["user"]["id"]
        senderobj = client.users_profile_get(user=senderid)
        sendermail = senderobj["profile"]["email"]
        sendermention = f"<@{senderid}>"
        sendername = senderobj["profile"]["display_name"]

        # Validate inputs
        errors = {}
        mincaracter = 25

        inputs = {...}

        if reason is not None and len(reason) <= mincaracter:
            errors["input_aws_access_reason"] = f"The value must be longer than {mincaracter} characters"
        elif awsname == "nothing":
            errors["input_aws_select"] = "Please select valid option"

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

        # Acknowledge the view_submission request and close the modal
        ack()

        checkinputname = Ot.check_inputs(inputs, INPUT_AWS_NAME)
        checkinputmanager = Ot.check_inputs(inputs, INPUT_MANAGER_MAIL)

        # Caso o usuário não tenha informado corretamente o Acesso/Gestor, não envia a solicitação
        if not checkinputname or not checkinputmanager:...
        else...

After ack() "Acknowledge the view_submission request and close the modal" It is not closing the modal and continues running the code below.

The problem is that the code below ack() takes about 10 seconds to run, so this error appears:
image

As I said, I wanted the form to close immediately after ack() and not wait for all the code to run to close.

@filmaj filmaj self-assigned this Mar 3, 2022
@filmaj filmaj added question Further information is requested need info labels Mar 3, 2022
@filmaj
Copy link
Contributor

filmaj commented Mar 3, 2022

Hi @BSouzaDock, sorry you're having this issue.

What kind of logs do you see from your app's side? I'm not exactly sure what the error you are seeing implies, or where it is happening. Is this showing up in your modal that you are trying to close?

The only thing I can see is that your ack is below a few actions, including an API call to get a user profile, so perhaps the ack is too delayed? Can you do the error checking + ack before the API call to users_profile_get? But to be honest, this is a shot in the dark from my perspective. The best way for me to help you would be to get more logs from your app, especially if you can set the log level to debug.

@BSouzaDock
Copy link
Author

@filmaj

If I take the code below the ack() it works and closes the modal.

The error validation part works normally. When I put this code snippet below ack(), after clicking on submit it takes about 3 seconds and this message from the print I sent appears.

In the aws logs it does not generate any kind of error despite this image message, which I found strange.

@filmaj
Copy link
Contributor

filmaj commented Mar 3, 2022

Oh is this app running on AWS? You may want to look at the Lazy Listeners section of the Bolt for Python docs. You may also want to check the AWS example apps for Bolt we have in the examples/ folder.

@BSouzaDock
Copy link
Author

@filmaj

I believe the problem is the same. I didn't find any example of lazy with view submition coming from a shortcut . Do you have an example to show?

I found from action and workflowstep

@filmaj
Copy link
Contributor

filmaj commented Mar 3, 2022

I would suggest you try to combine the lazy_aws_lambda.py example with your own use case.

@BSouzaDock
Copy link
Author

BSouzaDock commented Mar 3, 2022

@filmaj

I had already managed to use lazy with workflowstep like this:

        def execute_step(step: dict, complete: Complete, fail: Fail)

        @step_builder.execute(lazy=[execute_step])
        def execute():
            pass

I tried this way but it didn't work("Syntax error in module 'init': invalid syntax"):

    def handle_view_events(ack, body, client, view):...

    @app.view("aws_access_request")(lazy=[handle_view_events])
    def handle():
        pass

Can you tell me if this view method using decorator accepts lazy?

@filmaj
Copy link
Contributor

filmaj commented Mar 3, 2022

The view method can indeed accept a lazy named function argument, but you also must provide an ack named function argument. lazy runs the long-running tasks you want to execute, and your named ack method must call ack inside within 3 seconds. The other important thing to note is that this approach is not available using the decorator approach. It needs to be a direct invocation of app.view, just like the example I pointed you to does with app.command.

@BSouzaDock
Copy link
Author

BSouzaDock commented Mar 3, 2022

@filmaj ,

Many thanks for the reply. I managed to do it this way:

    def handle_errors(ack, view):...

    def handle_submit(body, client, view):...

    app.view("aws_access_request")(ack=handle_errors, lazy=[handle_submit])

The problem is that I have to validate 2 fields in the form. In this way, regardless of whether the fields are filled in correctly or not, the lazy function is executed. The user receives a message as if the form has been successfully executed, while still validating errors:
image

@filmaj
Copy link
Contributor

filmaj commented Mar 3, 2022

@BSouzaDock can you share your full code? How you construct your App, your full handle_errors and handle_submit methods, etc.?

@BSouzaDock
Copy link
Author

BSouzaDock commented Mar 3, 2022

@filmaj

I saw this problem being mentioned in another thread: Is it not the same case I'm experiencing?

This is the full shortcut code:

from applications.okta.okta_utils import OktaTools
from applications.slack.features.workflow.aws_access_request.utils import get_workflow_block_fail, \
    send_block_fail_message, get_workflow_block_success, send_request_aws_message
from applications.slack.main.slack_main import SlackTools
from util.constants import *

Ot = OktaTools()
St = SlackTools()


def get_shortcuts(app):
    formdesc = "Peencha os campos abaixo para solicitar seu acesso. \n\nA solicitação será enviada para aprovação do " \
               "gestor e você receberá uma notificação no privado após a resposta."
    allawsoptions = [
        {
            "text": {
                "type": "plain_text",
                "text": "AWS Acquirer - HML"
            },
            "value": "AWS Acquirer - HML"
        },
        {
            "text": {
                "type": "plain_text",
                "text": "AWS Acquirer - PRD"
            },
            "value": "AWS Acquirer - PRD"
        },
        {
            "text": {
                "type": "plain_text",
                "text": "AWS C6 - DEV"
            },
            "value": "AWS C6 - DEV"
        },
        {
            "text": {
                "type": "plain_text",
                "text": "AWS C6 - HML"
            },
            "value": "AWS C6 - HML"
        },
        {
            "text": {
                "type": "plain_text",
                "text": "AWS C6 - PRD"
            },
            "value": "AWS C6 - PRD"
        },
        {
            "text": {
                "type": "plain_text",
                "text": "AWS Dablam - Banking"
            },
            "value": "AWS Dablam - Banking"
        },
        {
            "text": {
                "type": "plain_text",
                "text": "AWS Datalake HML - Banking"
            },
            "value": "AWS Datalake HML - Banking"
        },
        {
            "text": {
                "type": "plain_text",
                "text": "AWS Datalake HML - Core"
            },
            "value": "AWS Datalake HML - Core"
        },
        {
            "text": {
                "type": "plain_text",
                "text": "AWS Datalake PRD - Banking"
            },
            "value": "AWS Datalake PRD - Banking"
        },
        {
            "text": {
                "type": "plain_text",
                "text": "AWS Dev - Banking"
            },
            "value": "AWS Dev - Banking"
        },
        {
            "text": {
                "type": "plain_text",
                "text": "AWS Dev - Core"
            },
            "value": "AWS Dev - Core"
        },
        {
            "text": {
                "type": "plain_text",
                "text": "AWS DevOpsTools - Banking"
            },
            "value": "AWS DevOpsTools - Banking"
        },
        {
            "text": {
                "type": "plain_text",
                "text": "AWS DevopsTools - Core"
            },
            "value": "AWS DevopsTools - Core"
        },
        {
            "text": {
                "type": "plain_text",
                "text": "AWS DevopsTools - DEV"
            },
            "value": "AWS DevopsTools - DEV"
        },
        {
            "text": {
                "type": "plain_text",
                "text": "AWS DevopsTools - PRD"
            },
            "value": "AWS DevopsTools - PRD"
        },
        {
            "text": {
                "type": "plain_text",
                "text": "AWS Dock CloudTribe - DEV"
            },
            "value": "AWS Dock CloudTribe - DEV"
        },
        {
            "text": {
                "type": "plain_text",
                "text": "AWS Dock Tech Advocacy - DEV"
            },
            "value": "AWS Dock Tech Advocacy - DEV"
        },
        {
            "text": {
                "type": "plain_text",
                "text": "AWS DR - Banking"
            },
            "value": "AWS DR - Banking"
        },
        {
            "text": {
                "type": "plain_text",
                "text": "AWS DR - Core"
            },
            "value": "AWS DR - Core"
        },
        {
            "text": {
                "type": "plain_text",
                "text": "AWS HML - Banking"
            },
            "value": "AWS HML - Banking"
        },
        {
            "text": {
                "type": "plain_text",
                "text": "AWS HML - Core"
            },
            "value": "AWS HML - Core"
        },
        {
            "text": {
                "type": "plain_text",
                "text": "AWS Internal - Architecture"
            },
            "value": "AWS Internal - Architecture"
        },
        {
            "text": {
                "type": "plain_text",
                "text": "AWS Julius - HML"
            },
            "value": "AWS Julius - HML"
        },
        {
            "text": {
                "type": "plain_text",
                "text": "AWS Julius - PRD"
            },
            "value": "AWS Julius - PRD"
        },
        {
            "text": {
                "type": "plain_text",
                "text": "AWS Merci"
            },
            "value": "AWS Merci"
        },
        {
            "text": {
                "type": "plain_text",
                "text": "AWS Merci - HML"
            },
            "value": "AWS Merci - HML"
        },
        {
            "text": {
                "type": "plain_text",
                "text": "AWS Network - Banking"
            },
            "value": "AWS Network - Banking"
        },
        {
            "text": {
                "type": "plain_text",
                "text": "AWS Network - Core"
            },
            "value": "AWS Network - Core"
        },
        {
            "text": {
                "type": "plain_text",
                "text": "AWS PRD - Banking"
            },
            "value": "AWS PRD - Banking"
        },
        {
            "text": {
                "type": "plain_text",
                "text": "AWS PRD - Core"
            },
            "value": "AWS PRD - Core"
        },
        {
            "text": {
                "type": "plain_text",
                "text": "AWS QA - Banking"
            },
            "value": "AWS QA - Banking"
        },
        {
            "text": {
                "type": "plain_text",
                "text": "AWS QA - Core"
            },
            "value": "AWS QA - Core"
        },
        {
            "text": {
                "type": "plain_text",
                "text": "AWS Root - Banking"
            },
            "value": "AWS Root - Banking"
        },
        {
            "text": {
                "type": "plain_text",
                "text": "AWS Security - Banking"
            },
            "value": "AWS Security - Banking"
        },
        {
            "text": {
                "type": "plain_text",
                "text": "AWS Security - Dev"
            },
            "value": "AWS Security - Dev"
        },
        {
            "text": {
                "type": "plain_text",
                "text": "AWS Shared - PRD"
            },
            "value": "AWS Shared - PRD"
        }
    ]
    nothingoption = [{
        "text": {
            "type": "plain_text",
            "text": "Nothing found  :sob:"
        },
        "value": "nothing"
    }]
    roleoptions = [
        {
            "text": {
                "type": "plain_text",
                "text": "Developers",
                "emoji": True
            },
            "value": "Developers"
        },
        {
            "text": {
                "type": "plain_text",
                "text": "Qa",
                "emoji": True
            },
            "value": "Qa"
        },
        {
            "text": {
                "type": "plain_text",
                "text": "Cloud",
                "emoji": True
            },
            "value": "Cloud"
        },
        {
            "text": {
                "type": "plain_text",
                "text": "DBA",
                "emoji": True
            },
            "value": "DBA"
        },
        {
            "text": {
                "type": "plain_text",
                "text": "Data",
                "emoji": True
            },
            "value": "Data"
        },
        {
            "text": {
                "type": "plain_text",
                "text": "Architect",
                "emoji": True
            },
            "value": "Architect"
        },
        {
            "text": {
                "type": "plain_text",
                "text": "Telecom",
                "emoji": True
            },
            "value": "Telecom"
        },
        {
            "text": {
                "type": "plain_text",
                "text": "SRE",
                "emoji": True
            },
            "value": "SRE"
        },
        {
            "text": {
                "type": "plain_text",
                "text": "PSE",
                "emoji": True
            },
            "value": "PSE"
        },
        {
            "text": {
                "type": "plain_text",
                "text": "Csops",
                "emoji": True
            },
            "value": "Csops"
        },
        {
            "text": {
                "type": "plain_text",
                "text": "StarOps",
                "emoji": True
            },
            "value": "StarOps"
        },
        {
            "text": {
                "type": "plain_text",
                "text": "Account Manager",
                "emoji": True
            },
            "value": "Account Manager"
        },
        {
            "text": {
                "type": "plain_text",
                "text": "Techleads",
                "emoji": True
            },
            "value": "Techleads"
        },
        {
            "text": {
                "type": "plain_text",
                "text": "CloudSecurity",
                "emoji": True
            },
            "value": "CloudSecurity"
        }
    ]

    @app.shortcut("aws_access_request")
    def handle_shortcuts(ack, body, client):
        # Acknowledge the command request
        ack()
        # Call views_open with the built-in client
        client.views_open(
            # Pass a valid trigger_id within 3 seconds of receiving it
            trigger_id=body["trigger_id"],
            # View payload
            view={
                "type": "modal",
                # View identifier
                "callback_id": "aws_access_request",
                "title": {"type": "plain_text", "text": "AWS Access Request"},
                "submit": {"type": "plain_text", "text": "Submit"},
                "blocks": [
                    {
                        "type": "section",
                        "text": {
                            "type": "mrkdwn",
                            "text": formdesc
                        },
                        "accessory": {
                            "type": "image",
                            "image_url": "https://a0.awsstatic.com/libra-css/images/logos/aws_logo_smile_1200x630.png",
                            "alt_text": "AWS Access Request"
                        }
                    },
                    {
                        "type": "input",
                        "block_id": "input_aws_select",
                        "element": {
                            "action_id": "aws_access_select",
                            "type": "external_select",
                            "placeholder": {
                                "type": "plain_text",
                                "text": "Select or enter the expected value..."
                            },
                            "min_query_length": 0
                        },
                        "label": {
                            "type": "plain_text",
                            "text": "AWS que deseja acessar:",
                            "emoji": True
                        }
                    },
                    {
                        "type": "input",
                        "block_id": "input_aws_role_select",
                        "element": {
                            "action_id": "aws_access_role_select",
                            "type": "static_select",
                            "placeholder": {
                                "type": "plain_text",
                                "text": "Select an item..."
                            },
                            "options": roleoptions
                        },
                        "label": {
                            "type": "plain_text",
                            "text": "Role:",
                            "emoji": True
                        }
                    },
                    {
                        "type": "input",
                        "block_id": "input_aws_access_squad",
                        "element": {
                            "type": "plain_text_input",
                            "action_id": "aws_access_squad"
                        },
                        "label": {
                            "type": "plain_text",
                            "text": "Squad/Area de atuação:",
                            "emoji": True
                        }
                    },
                    {
                        "type": "input",
                        "block_id": "input_aws_access_reason",
                        "element": {
                            "type": "plain_text_input",
                            "multiline": True,
                            "action_id": "aws_access_reason"
                        },
                        "label": {
                            "type": "plain_text",
                            "text": "Motivo do acesso na conta AWS:",
                            "emoji": True
                        }
                    },
                    {
                        "type": "input",
                        "block_id": "input_aws_access_manager",
                        "element": {
                            "type": "plain_text_input",
                            "action_id": "aws_access_manager"
                        },
                        "label": {
                            "type": "plain_text",
                            "text": "E-mail do seu gestor imediato:",
                            "emoji": True
                        }
                    }
                ]
            }
        )

    @app.options("aws_access_select")
    def handle_some_options(ack, body):
        keyword = body.get("value")

        if len(keyword) > 0:
            options = [o for o in allawsoptions if
                       str(o["text"]["text"]).upper().find(keyword.upper()) >= 0]

            if len(options) == 0:
                options = nothingoption

            ack(options=options)
        elif keyword is None or len(keyword) == 0:
            ack(options=allawsoptions)

    def handle_errors(ack, view):
        # Assume there's an input block with `input_c` as the block_id and `dreamy_input`
        awsname = view["state"]["values"]["input_aws_select"]["aws_access_select"]["selected_option"]["value"]
        reason = view["state"]["values"]["input_aws_access_reason"]["aws_access_reason"]["value"]

        # Validate inputs
        errors = {}
        mincaracter = 25

        if reason is not None and len(reason) <= mincaracter:
            errors["input_aws_access_reason"] = f"The value must be longer than {mincaracter} characters"
        elif awsname == "nothing":
            errors["input_aws_select"] = "Please select valid option"

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

        # Acknowledge the view_submission request and close the modal
        ack()

    def handle_submit(body, client, view):
        awsname = view["state"]["values"]["input_aws_select"]["aws_access_select"]["selected_option"]["value"]
        awsrole = view["state"]["values"]["input_aws_role_select"]["aws_access_role_select"]["selected_option"]["value"]
        managermail = view["state"]["values"]["input_aws_access_manager"]["aws_access_manager"]["value"]
        squad = view["state"]["values"]["input_aws_access_squad"]["aws_access_squad"]["value"]
        reason = view["state"]["values"]["input_aws_access_reason"]["aws_access_reason"]["value"]
        senderid = body["user"]["id"]
        senderobj = client.users_profile_get(user=senderid)
        sendermail = senderobj["profile"]["email"]
        sendermention = f"<@{senderid}>"
        sendername = senderobj["profile"]["display_name"]

        inputs = {
            "awsName": {
                "value": awsname
            },
            "awsRole": {
                "value": awsrole
            },
            "managerMail": {
                "value": managermail
            }
        }

        checkinputname = Ot.check_inputs(inputs, INPUT_AWS_NAME)
        checkinputmanager = Ot.check_inputs(inputs, INPUT_MANAGER_MAIL)

        # Caso o usuário não tenha informado corretamente o Acesso/Gestor, não envia a solicitação
        if not checkinputname or not checkinputmanager:
            if not checkinputname:
                # errmsg = {
                #     "message": f"Acesso {awsname} não encontrado! Olhar no Pin fixado do canal o nome de cada AWS."}
                blocklayout = FAIL_MSG_AWS_NAME
            else:
                # errmsg = {"message": f"Gestor {managermail} não encontrado no ambiente!"}
                blocklayout = FAIL_MSG_MANAGER_MAIL

            # fail(error=errmsg)
            blockfail = get_workflow_block_fail(sendermention, managermail, awsname, awsrole, blocklayout)
            send_block_fail_message(blockfail, senderid)
        else:
            try:
                manager = client.users_lookupByEmail(email=managermail)
                managerid = manager["user"]["id"]

                blocksuccess = get_workflow_block_success(sendermention, awsname)

                send_request_aws_message(sendername, sendermail, sendermention,
                                         awsname, awsrole, squad, reason, managermail, managerid)
            except Exception as e:
                if "users_not_found" in str(e):
                    # error = {"message": f"Gestor {managermail} não encontrado no ambiente!"}
                    # fail(error=error)
                    blocklayout = FAIL_MSG_MANAGER_MAIL
                    blockfail = get_workflow_block_fail(sendermention, managermail, awsname, awsrole, blocklayout)

                    send_block_fail_message(blockfail, senderid)
                else:
                    # error = {"message": "Just testing step failure! {}".format(e)}
                    # fail(error=error)
                    St.webhook_directly(WEBHOOK_LOGS_URI_FAIL, e, FAIL)
            else:
                client.chat_postMessage(text="Envio com sucesso de solicitação de acesso AWS",
                                        blocks=blocksuccess,
                                        channel=senderid)

    app.view("aws_access_request")(ack=handle_errors, lazy=[handle_submit])

@filmaj
Copy link
Contributor

filmaj commented Mar 3, 2022

I saw this problem being mentioned in another #482: Is it not the same case I'm experiencing?

Oh nice find; yes it seems like it is not possible at this time. Looks like @seratch recommends to duplicate the error validation in the lazy functions. Perhaps we could improve the behaviour to enable this pattern of only running lazy functions if ack succeeds / returns a non-error response; this makes sense to me, but there are many use cases to consider!

@seratch
Copy link
Member

seratch commented Mar 3, 2022

Regarding the limitation of lazy listeners mentioned here, as I responded in that thread, having the same validation on the lazy function side is the recommended way.

@BSouzaDock By the way, are you running your app on AWS Lambda? If not, you don't need to use lazy listeners if the feature does not fit. You can merge handle_errors and handle_submit into one (handle_errors and then handle_submit) and it should work for you.

@BSouzaDock
Copy link
Author

@seratch @filmaj,

Thanks a lot for your help. I'm using an AWS Lambda with API Gateway.
I did a one-time validation and called on both methods. It worked perfectly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
need info question Further information is requested
Projects
None yet
Development

No branches or pull requests

3 participants