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

Getting We had some trouble connecting. Try again? after updating a view. Using aws lambda. #2150

Closed
tusharwagh-mdsol opened this issue Jun 26, 2024 · 5 comments
Labels
auto-triage-stale question M-T: User needs support to use the project

Comments

@tusharwagh-mdsol
Copy link

tusharwagh-mdsol commented Jun 26, 2024

Below code will open a comment modal.

// Handle selection of a pending task in the static select
app.action(
  { type: "block_actions", action_id: /^pending_task_action_(\d+)$/ },
  async ({ ack, body, client }) => {
    await ack();

    const selectedOption = body.actions[0].selected_option.value;
    const selectedApplicationId = body.actions[0].block_id;
    const userEmail = body.user.name + "@mdsol.com";

    // Open a modal with a text input for reason
    await client.views.open({
      trigger_id: body.trigger_id,
      view: {
        type: "modal",
        callback_id: "reason_modal",
        private_metadata: JSON.stringify({
          selectedOption,
          selectedApplicationId,
          userEmail
        }),
        title: {
          type: "plain_text",
          text: ":wave: Please comment ",
        },
        blocks: [
          {
            type: "input",
            block_id: "reason_input",
            element: {
              type: "plain_text_input",
              action_id: "reason",
              multiline: true,
            },
            label: {
              type: "plain_text",
              text: "Please provide comment for your action:",
            },
          },
        ],
        submit: {
          type: "plain_text",
          text: "Submit",
        },
      },
    });
  }
);

And After comment we will update the existing view with

// Handle submission of the reason modal
app.view('reason_modal', async ({ ack, body, view, client }) => {

  await ack()

  const viewId = view.id;
   // Open a quick loading modal
   await client.views.update({
    view_id: viewId,
    "response_action": "update",
    view: {
      type: "modal",
      title: {
        type: "plain_text",
        text: ":man-biking:Processing..",
      },
      blocks: [
        {
          type: "section",
          text: {
            type: "plain_text",
            text: ":hourglass_flowing_sand: Please wait while we process your request...",
          },
        },
      ],
    },
  });

**

But I am getting an error as We had some trouble connecting. Try again? after submitting the comment.

**

image

@seratch seratch transferred this issue from slackapi/python-slack-sdk Jun 26, 2024
@seratch seratch added question M-T: User needs support to use the project and removed untriaged labels Jun 26, 2024
@seratch
Copy link
Member

seratch commented Jun 26, 2024

Hi @tusharwagh-mdsol, thanks for asking the question!

When you want to update the submitted modal in app.view listeners, you can use await ack({ response_action: "update", view: view}) instead of views.update API call. Please refer to https://slack.dev/bolt-js/concepts#view-submissions for more details.

@tusharwagh-mdsol
Copy link
Author

tusharwagh-mdsol commented Jun 26, 2024

Hi @seratch
Thanks for the quick response.
So basically i am using aws lambda for hosting my bolt application using JS.

In above code when we submit the comments i want to display processing modal and after that the modal should get updated with another message as this task is completed but here task completion will take some time to complete because we have some api calls as well and also after task completion I have to refresh the homeview as well to remove the completed task.

Can you please suggest best way to do this ?

below is the full code for the same.

// Handle submission of the reason modal
app.view('reason_modal', async ({ ack, body, view, client }) => {
  
  await ack({ response_action: "update", view: view})

  const viewId = view.id;
   // Open a quick loading modal
   await client.views.update({
    view_id: viewId,
    "response_action": "update",
    view: {
      type: "modal",
      title: {
        type: "plain_text",
        text: ":man-biking:Processing..",
      },
      blocks: [
        {
          type: "section",
          text: {
            type: "plain_text",
            text: ":hourglass_flowing_sand: Please wait while we process your request...",
          },
        },
      ],
    },
  });

  const { selectedOption, selectedApplicationId, userEmail } = JSON.parse(view.private_metadata);
  const reason = view.state.values.reason_input.reason.value;
  const approveruserId = body.user.id;

  const pendingTasksUrl = `${process.env.slack_local_api}/pending-tasks?user_email=${userEmail}`;

  try {
    const response = await axios.get(pendingTasksUrl, {
      headers: {
        "Content-Type": "application/json",
        "x-api-key": process.env.X_API_KEY,
      },
    });

    const tasks = response.data.tasks;
    const task = tasks.find(task => task.application_req_ref_id == selectedApplicationId);

    if (!task) {
      throw new Error("Task not found");
    }

    const payload = {
      _id: task._id,
      recordedAction: capitalizeFirstCharacter(selectedOption),
      tasktoken: task.tasktoken,
      application_req_ref_id: selectedApplicationId,
      requestingApplication: task.requestingApplication,
      actioned_at: new Date().toISOString().slice(0, 10),
      taskName: task.taskName,
      description: task.description,
      userEmail: userEmail,
      reason: reason,
      approveruserId: approveruserId,
      approvalScheme: task.approvalScheme
    };

    const approvalUrl = `${process.env.slack_local_api}/slack-approval`;

    // Ensure the post request is awaited
    await axios.post(approvalUrl, payload, {
      headers: {
        "Content-Type": "application/json",
        "x-api-key": process.env.X_API_KEY,
      },
    });

    // Determine the final title and message based on the selected option
    let titleTxt = "";
    let blockMsg = "";
    switch (selectedOption) {
      case "Approve":
        titleTxt = "Approved";
        blockMsg = "Requested action is completed :white_check_mark:";
        break;
      case "Reject":
        titleTxt = "Rejected";
        blockMsg = "Requested action is rejected :x:";
        break;
      case "Ignore":
        titleTxt = "Ignored";
        blockMsg = "Requested action is ignored :no_entry_sign:";
        break;
      default:
        titleTxt = "Unknown Action";
        blockMsg = "An unknown action was selected.";
    }

    // Update the modal with the final content
    await client.views.update({
      view_id: viewId,
      "response_action": "update",
      view: {
        type: "modal",
        callback_id: "modal-1",
        title: {
          type: "plain_text",
          text: titleTxt,
        },
        blocks: [
          {
            type: "section",
            block_id: "section-1",
            text: {
              type: "mrkdwn",
              text: blockMsg,
            },
          },
        ],
      },
    });
    

    try{
    // Optionally refresh the pending tasks view
    const refreshResponse = await axios.get(pendingTasksUrl, {
      headers: {
        "Content-Type": "application/json",
        "x-api-key": process.env.X_API_KEY,
      },
    });

    const refreshedTasks = refreshResponse.data.tasks;

    // Always include the Pending and Completed Tasks buttons
    const baseViewBlocks = [
      {
        type: "section",
        text: {
          type: "mrkdwn",
          text: ":wave: *Welcome to the Approval Application!*\n Please choose between the options to check tasks..",
        },
      },
      {
        type: "actions",
        elements: [
          {
            type: "button",
            text: {
              type: "plain_text",
              text: "Pending Tasks",
              emoji: true,
            },
            action_id: "pending_button",
            style: "danger",
          },
          {
            type: "button",
            text: {
              type: "plain_text",
              text: "Completed Tasks",
              emoji: true,
            },
            action_id: "completed_button",
            style: "primary",
          },
        ],
      },
    ];

    if (refreshedTasks.length === 0) {
      baseViewBlocks.push(
        {
          type: "section",
          text: {
            type: "mrkdwn",
            text: "*You have no pending tasks at the moment.*",
          },
        },
        {
          type: "section",
          text: {
            type: "mrkdwn",
            text: "*Please check back later or contact support if you believe this is an error.*",
          },
        }
      );

      await client.views.publish({
        user_id: body.user.id,
        "response_action": "clear",
        view: {
          type: "home",
          blocks: baseViewBlocks,
        },
      });

      return;
    }

    const uniqueCategories = [...new Set(refreshedTasks.map(task => task.requestingApplication))];
    const defaultCategory = uniqueCategories.includes(task.requestingApplication) ? task.requestingApplication : uniqueCategories[0];
    const tasksForDefaultCategory = getTasksForCategory(defaultCategory, refreshedTasks);

    const categoryButtons = uniqueCategories.map(category => {
      const tasksForCategory = getTasksForCategory(category, refreshedTasks);
      const pendingTasksCount = tasksForCategory.length;
      return {
        type: "button",
        text: {
          type: "plain_text",
          text: `${category} (${pendingTasksCount})`,
        },
        action_id: `pending_category_${category.replace(/\s/g, "_")}_button`,
        style: selectedCategory === category ? "primary" : "danger",
      };
    });

    const tasksBlocks = tasksForDefaultCategory.map((task, index) => [
      {
        type: "section",
        block_id: `${task.application_req_ref_id}`,
        text: {
          type: "mrkdwn",
          text: `:clipboard: *Task ${index + 1}:* <${task.req_app_redirect_url}| ${task.application_req_ref_id}> ${task.taskName}`,
        },
        accessory: {
          type: "static_select",
          action_id: `pending_task_action_${index + 1}`,
          placeholder: {
            type: "plain_text",
            text: "Select an action",
          },
          options: getOptionsForTaskActions(task.actions),
        },
      },
      {
        type: "divider",
      },
    ]).flat();

    const view = {
      type: "home",
      blocks: [
        ...baseViewBlocks,
        {
          type: "section",
          text: {
            type: "mrkdwn",
            text: "*Please choose between below tasks category*",
          },
        },
        {
          type: "actions",
          elements: categoryButtons,
        },
        {
          type: "divider",
        },
        {
          type: "section",
          text: {
            type: "mrkdwn",
            text: `Selected Category Tasks (${defaultCategory}):`,
          },
        },
        ...tasksBlocks,
      ],
    };

    await client.views.publish({
      user_id: body.user.id,
      "response_action": "clear",
      view,
    });
  }catch (error) {

    // Always include the Pending and Completed Tasks buttons
    const baseViewBlocks = [
      {
        type: "section",
        text: {
          type: "mrkdwn",
          text: ":wave: *Welcome to the Approval Application!*\n Please choose between the options to check tasks..",
        },
      },
      {
        type: "actions",
        elements: [
          {
            type: "button",
            text: {
              type: "plain_text",
              text: "Pending Tasks",
              emoji: true,
            },
            action_id: "pending_button",
            style: "danger",
          },
          {
            type: "button",
            text: {
              type: "plain_text",
              text: "Completed Tasks",
              emoji: true,
            },
            action_id: "completed_button",
            style: "primary",
          },
        ],
      },
    ];
    
      baseViewBlocks.push(
        {
          type: "section",
          text: {
            type: "mrkdwn",
            text: "*You have no pending tasks at the moment.*",
          },
        },
        {
          type: "section",
          text: {
            type: "mrkdwn",
            text: "*Please check back later or contact support if you believe this is an error.*",
          },
        }
      );

      await client.views.publish({
        user_id: body.user.id,
        "response_action": "clear",
        view: {
          type: "home",
          blocks: baseViewBlocks,
        },
      });

      return;
    

  }
  } catch (error) {
  
    let errorMessage = "Something went wrong while processing your request.";
    if (error.response && error.response.status === 404) {
      errorMessage = "You have caught up with all tasks! :white_check_mark:";
    }

    await client.views.update({
      view_id: viewId,
      "response_action": "update",
      view: {
        type: "modal",
        title: {
          type: "plain_text",
          text: "Congratulations!",
        },
        blocks: [
          {
            type: "section",
            text: {
              type: "mrkdwn",
              text: errorMessage,
            },
          },
        ],
      },
    });
  }
});

@seratch
Copy link
Member

seratch commented Jun 26, 2024

bolt-python's lazy listeners could be the best solution for your use case: https://slack.dev/bolt-python/concepts#lazy-listeners However, unfortunately the same functionality does not exist in bolt-js due to its design restrictions. Also, we don't have plans to enhance bolt-js for the need.

If we want to write your app using bolt-js + AWS Lambda, the only suggestion I can give is to have a queue for delegating the time-consuming task to a different Lambda function. You can store the event payload and view_id etc. into the queue message. With that, the async task worker function can update the end-user once the task completes.

I hope this helps. Is everyting clear now? If yes, would you mind closing this?

Copy link

👋 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.

Copy link

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.

@github-actions github-actions bot closed this as not planned Won't fix, can't repro, duplicate, stale Aug 12, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
auto-triage-stale question M-T: User needs support to use the project
Projects
None yet
Development

No branches or pull requests

2 participants