slackapi / bolt-js

A framework to build Slack apps using JavaScript
https://tools.slack.dev/bolt-js/
MIT License
2.74k stars 393 forks source link

How to get channel selection input and update view afterwards? #1673

Closed automationgeekx closed 1 year ago

automationgeekx commented 1 year ago

Description

Describe your issue here.

What type of issue is this? (place an x in one of the [ ])

Requirements (place an x in each of the [ ])


Bug Report

Filling out the following details about bugs will help us solve your issue sooner.

Reproducible in:

package version:

node version: v12.16.1 OS version(s): Amazon linux 2

Steps to reproduce:

1. 2. 3.

Expected result:

When user selects a channel, I want to update the view. Right now, when I select a channel, no value is returned. This is branched off of the node-tasks-app on the bolt-js github. The code i pasted at the bottom is the new-task-modal.js file and new-task.js file from that same bolt-js repository. I am using it as a baseline to create a polling application for slack. I want to be able to retrieve the channel name and also push a different view when the channel is selected.

Actual result:

The following line of code below is used for when there is an error. Where it says "selectedChannel", I want it to post the channel name. When I give the modal a case where this line of code would run, it says "undefined" where selectedChannel is.

text: Sorry, but we couldn't set a reminder for ${selectedChannel}, as it's more than 120 days from now,

Attachments:

const { DateTime } = require('luxon');

const { User, Task } = require('../../models');
const { modals } = require('../../user-interface');
const { taskReminder } = require('../../user-interface/messages');
const { reloadAppHome } = require('../../utilities');

const newTaskModalCallback = async ({ ack, view, body, client }) => {
  const providedValues = view.state.values;

  const taskTitle = providedValues.taskTitle.taskTitle.value;

  const selectedDate = providedValues.taskDueDate.taskDueDate.selected_date;
  const selectedTime = providedValues.taskDueTime.taskDueTime.selected_time;

  const selectedUser =
    providedValues.taskAssignUser.taskAssignUser.selected_user;

    const selectedChannel =
    providedValues.taskAssignChannel.taskAssignChannel.selected_option;

  const task = Task.build({ title: taskTitle });

  if (selectedDate) {
    if (!selectedTime) {
      await ack({
        response_action: 'errors',
        errors: {
          taskDueTime: "Please set a time for the date you've chosen",
        },
      });
      return;
    }
    const taskDueDate = DateTime.fromISO(`${selectedDate}T${selectedTime}`);
    const diffInDays = taskDueDate.diffNow('days').toObject().days;
    // Task due date is in the past, so reject
    if (diffInDays < 0) {
      await ack({
        response_action: 'errors',
        errors: {
          taskDueDate: 'Please select a due date in the future',
          taskDueTime: 'Please select a time in the future',
        },
      });
      return;
    }
    task.dueDate = taskDueDate;
  }

  try {
    // Grab the creating user from the DB
    const queryResult = await User.findOrCreate({
      where: {
        slackUserID: body.user.id,
        slackWorkspaceID: body.team.id,
      },
    });
    const user = queryResult[0];

    // Grab the assignee user from the DB
    const querySelectedUser = await User.findOrCreate({
      where: {
        slackUserID: selectedUser,
        slackWorkspaceID: body.team.id, // TODO better compatibility with Slack Connect.
      },
    });
    const selectedUserObject = querySelectedUser[0];

    // Persist what we know about the task so far
    await task.save();
    await task.setCreator(user);
    await task.setCurrentAssignee(selectedUserObject);

    if (task.dueDate) {
      const dateObject = DateTime.fromJSDate(task.dueDate);
      // The `chat.scheduleMessage` endpoint only accepts messages in the next 120 days,
      // so if the date is further than that, don't set a reminder, and let the user know.
      const assignee = await task.getCurrentAssignee();
      if (dateObject.diffNow('days').toObject().days < 120) {
        await client.chat
          .scheduleMessage(
            taskReminder(
              dateObject.toSeconds(),
              assignee.slackUserID,
              task.title,
              dateObject.toRelativeCalendar(),
              task.id,
            ),
          )
          .then(async (response) => {
            task.scheduledMessageId = response.scheduled_message_id;
            await task.save();
          });
      } else {
        // TODO better error message and store it in /user-interface
        await client.chat.postMessage({
          text: `Sorry, but we couldn't set a reminder for ${selectedChannel}, as it's more than 120 days from now`,
          channel: assignee.slackUserID,
        });
      }
    }
    await task.save();
    await ack({
      response_action: 'update',
      view: modals.taskCreated(taskTitle),
    });
    if (selectedUser !== body.user.id) {
      await client.chat.postMessage({
        channel: selectedUser,
        text: `<@${body.user.id}> assigned you a new task:\n- *${taskTitle}*`,
      });
      await reloadAppHome(client, selectedUser, body.team.id);
    }

    await reloadAppHome(client, body.user.id, body.team.id);
  } catch (error) {
    await ack({
      response_action: 'update',
      view: modals.taskCreationError(taskTitle),
    });
    // eslint-disable-next-line no-console
    console.error(error);
  }
};

module.exports = { newTaskModalCallback };

const { Modal, Blocks, Elements } = require('slack-block-builder');

module.exports = (prefilledTitle, currentUser) => {
  const textInput = (taskTitle) => {
    if (taskTitle) {
      return Elements.TextInput({
        placeholder: 'Enter Survey Title',
        actionId: 'taskTitle',
        initialValue: taskTitle,
      });
    }
    return Elements.TextInput({
      placeholder: 'Enter Survey Title',
      actionId: 'taskTitle',
    });
  };

  const textInp = (addNewQuestion) => {
    if (addNewQuestion) {
      return Elements.TextInput({
        placeholder: 'Enter Survey Title',
        actionId: 'addNewQuestion',
        initialValue: addNewQuestion,
      });
    }
    return Elements.TextInput({
      placeholder: 'Enter Survey Title',
      actionId: 'addNewQuestion',
    });
  };

  return Modal({ title: 'Create new survey', submit: 'Create', callbackId: 'new-task-modal' })
    .blocks(
      Blocks.Input({ label: 'New Survey', blockId: 'taskTitle' }).element(
        textInput(prefilledTitle),
      ),
      Blocks.Input({ label: 'Assign a Survey Creator', blockId: 'taskAssignUser' }).element(
        Elements.UserSelect({
          actionId: 'taskAssignUser',
        }).initialUser(currentUser),
      ),
      Blocks.Input({ label: 'Assign a channel', blockId: 'taskAssignChannel' }).element(
        Elements.ChannelSelect({
          actionId: 'taskAssignChannel',
        }),
      ),
      Blocks.Input({ label: 'Due date', blockId: 'taskDueDate', optional: true }).element(
        Elements.DatePicker({
          actionId: 'taskDueDate',
        }),
      ),
      Blocks.Input({ label: 'Time Due', blockId: 'taskDueTime', optional: true }).element(
        Elements.TimePicker({
          actionId: 'taskDueTime',
        }),
      ),
      Blocks.Input({ label: 'What type of question would you like to add?', blockId: 'questionSelect'}).element(
        Elements.TextInput({
          actionId: 'questionSelect',
        })
      ),
    ).buildToJSON();
};
filmaj commented 1 year ago

Hello @bg339,

How is the newTaskModalCallback invoked?

automationgeekx commented 1 year ago

From the repository i forked on bolt-js, this piece of code is from testing.

  it('schedules a message to remind the user of an upcoming task if the due date is < 120 days in the future', async () => {
    await testView(
      modalViewPayloadSelectedDateAndTime,
      newTaskModalCallback,
      global.chatScheduleMessageMockFunc,
      {
        channel: modalViewPayloadSelectedDateAndTime.user.id,
        post_at: expect.any(Number), // TODO: possibly beef up this test to check for a valid time
        text: expect.stringContaining(
          modalViewPayloadSelectedDateAndTime.view.state.values.taskTitle
            .taskTitle.value,
        ),
      },
    );
  });

  it('posts a message to the user if the due date is > 120 days in the future', async () => {
    await testView(
      modalViewPayloadDueDateTooFarInFuture,
      newTaskModalCallback,
      global.chatPostMessageMockFunc,
      {
        text: expect.stringContaining('more than 120 days from'),
        channel: modalViewPayloadSelectedDateAndTime.user.id,
      },
    );
  });

Aside from that, it is not called within any other file

automationgeekx commented 1 year ago

This would be an example on how its called

seratch commented 1 year ago

Hi @bg339, your two other questions are related to this one, right? If yes, my reply at https://github.com/slackapi/bolt-js/issues/1674#issuecomment-1343755780 should be the answer for your question. Does my answer make sense to you? If yes, would you mind closing all other (possibly duplicated) issues?

github-actions[bot] commented 1 year 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 1 year 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.