slackapi/bolt-js

[ERROR] bolt-app no more than 100 items allowed [json-pointer:/view/blocks/0/element/options]

Closed this issue · 13 comments

(Describe your issue and goal here)

const result = await client.views.open({
  trigger_id: body.trigger_id,
  view: {
    type: 'modal',
    callback_id: 'select_company_modal',
    title: {
      type: 'plain_text',
      text: 'Select Company'
    },
    blocks: [
      {
        type: 'input',
        block_id: 'company_dropdown',
        element: {
          type: 'static_select',
          action_id: 'company_select',
          placeholder: {
            type: 'plain_text',
            text: 'Select a company'
          },
          options: customerOptions
        },
        label: {
          type: 'plain_text',
          text: 'Company'
        }
      }
    ],
    submit: {
      type: 'plain_text',
      text: 'Get Details',
    },
  }
});

I have the above code snippet to open a modal and the customerOptions array has around 650 items. Is it not possible to show more than 100 items in the dropdown?

Reproducible in:

The Slack SDK version

"slack/bolt": "^3.17.1",
"slack-block-builder": "^2.8.0",

Node.js runtime version

v16.20.2

OS info

ProductName: macOS
ProductVersion: 14.3.1
BuildVersion: 23D60
Darwin Kernel Version 23.3.0: Wed Dec 20 21:30:59 PST 2023; root:xnu-10002.81.5~7/RELEASE_ARM64_T6030

Steps to reproduce:

(Share the commands to run, source code, and project settings)

Expected result:

Display all the 600+ items in the dropdown

Actual result:

Throwing the following error:

[ERROR]  bolt-app failed to match all allowed schemas [json-pointer:/view]
[ERROR]  bolt-app no more than 100 items allowed [json-pointer:/view/blocks/0/element/options]
Error: An API error occurred: invalid_arguments
    at platformErrorFromResult (/Users/pritishsamal/Documents/EscalationBot/node_modules/@slack/web-api/dist/errors.js:62:33)
    at WebClient.apiCall (/Users/pritishsamal/Documents/EscalationBot/node_modules/@slack/web-api/dist/WebClient.js:181:56)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)
    at async CustomerDetailsController (/Users/pritishsamal/Documents/EscalationBot/src/slashCommands/customerDetails.js:33:28)
    at async Array.<anonymous> (/Users/pritishsamal/Documents/EscalationBot/node_modules/@slack/bolt/dist/middleware/builtin.js:211:9)
    at async Array.onlyCommands (/Users/pritishsamal/Documents/EscalationBot/node_modules/@slack/bolt/dist/middleware/builtin.js:45:5) {
  code: 'slack_webapi_platform_error',
  data: {
    ok: false,
    error: 'invalid_arguments',
    response_metadata: { messages: [Array], scopes: [Array] }
  }
}

Requirements

For general questions/issues about Slack API platform or its server-side, could you submit questions at https://my.slack.com/help/requests/new instead. 🙇

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

Hi @CIPHERTron, thanks for asking the question!

I have the above code snippet to open a modal and the customerOptions array has around 650 items. Is it not possible to show more than 100 items in the dropdown?

Unfortunately, it's not possible to display more than 100 items in a static_select dropdown menu. However, for large data sets like yours, you may consider using the external_select menu: https://slack.dev/bolt-js/concepts#options

I hope you find this information helpful!

Hi @seratch , thank you so much for the quick reply. Let me explore the external_select menu

Hi again @seratch, now I am using external_select like shown below:

if (customerName === 'list') {
        try {          
            const result = await client.views.open({
              trigger_id: body.trigger_id,
              view: {
                type: 'modal',
                callback_id: 'select_company_modal',
                title: {
                  type: 'plain_text',
                  text: 'Customer 360° Insights',
                  emoji: true
                },
                submit: {
                  type: 'plain_text',
                  text: 'Get Details',
                },
                close: {
                  type: "plain_text",
                  text: "Cancel Search",
                  emoji: true
                },
                blocks: [
                  {
                    type: "input",
                    element: {
                      type: "external_select",
                      action_id: "company_select",
                      placeholder: {
                        type: "plain_text",
                        text: "Start typing a customer name...",
                        emoji: true,
                      },
                    },
                    label: {
                      type: "plain_text",
                      text: "Search for a customer",
                      emoji: true,
                    },
                  }
                ],
                
              }
            });

            // Acknowledge the command request
            await ack();
        }

and passing it options like this:

app.options({ action_id: /company_select/ }, async ({ ack }) => {
  const customerOptions = customers.map(customer => {
    return {
      "text": {
        "type": 'plain_text',
        "text": customer
      },
      "value": customer
    }
  })

  await ack({
    "options": customerOptions
  })
});

However, i'm not able to see the options getting listed in the dropdown select. it's always showing "Nothing could be found"
Screenshot 2024-04-17 at 12 52 44 PM

Could you pls help me out here? I'm not exactly sure what I'm doing wrong

Please double-check if you've configured Features > Interactivity & Shortcuts > Select Menus > Options Load URL on the https://api.slack.com/apps/{your app id} site.

Also, having min_query_length:1 will make the user experience more intuitive. For more details, please refer to this page: https://api.slack.com/reference/block-kit/block-elements#external_select

As of now, I'm using hardcoded options but then also, I'm unable to load the options in slack. I'm getting the same message: Nothing could be found

app.options({ action_id: /company_select/ }, async ({ ack }) => {
  console.log("Set Company select options invoked")
  try {
    // Call an API or perform any asynchronous operation to fetch options
    const options = [
      {
        text: {
          type: 'plain_text',
          text: 'One'
        },
        value: '111'
      },
      {
        text: {
          type: 'plain_text',
          text: 'Two'
        },
        value: '222'
      },
      {
        text: {
          type: 'plain_text',
          text: 'Three'
        },
        value: '333'
      }
    ];

    // Acknowledge the options request and return the options
    await ack({
      options: options
    });
  } catch (error) {
    console.error(error);
  }
});

I've confirmed your example (I rewrote it slightly for TypeScript compatibility) works for me without any issues. Please double-check if you've configured the Features > Interactivity & Shortcuts > Select Menus > Options Load URL correctly, plus your app.options listener passes a valid response data to ack() method call.

import { App, BlockAction, Option } from "@slack/bolt";

export function registerListeners(app: App) {
  app.event("message", async ({ say }) => {
    await say({
      text: "hi!",
      blocks: [
        {
          type: "actions",
          elements: [
            {
              type: "button",
              action_id: "test",
              text: { type: "plain_text", text: "Click Me" },
              value: "click_me_123",
            },
          ],
        },
      ],
    });
  });
  app.action<BlockAction>("test", async ({ body, client, ack }) => {
    await ack();
    await client.views.open({
      trigger_id: body.trigger_id,
      view: {
        type: "modal",
        callback_id: "select_company_modal",
        title: {
          type: "plain_text",
          text: "Select Company",
        },
        blocks: [
          {
            type: "input",
            element: {
              type: "external_select",
              action_id: "company_select",
              placeholder: {
                type: "plain_text",
                text: "Start typing a customer name...",
                emoji: true,
              },
            },
            label: {
              type: "plain_text",
              text: "Search for a customer",
              emoji: true,
            },
          },
        ],
        submit: {
          type: "plain_text",
          text: "Get Details",
        },
      },
    });
  });
  app.options<"block_suggestion">(
    { action_id: /company_select/ },
    async ({ ack }) => {
      const options: Option[] = [
        {
          text: {
            type: "plain_text",
            text: "One",
          },
          value: "111",
        },
        {
          text: {
            type: "plain_text",
            text: "Two",
          },
          value: "222",
        },
        {
          text: {
            type: "plain_text",
            text: "Three",
          },
          value: "333",
        },
      ];
      await ack({ options });
    }
  );
}

Hey @seratch , apologies for bugging you multiple times. The external_select method didn't work for me so I reverted back to the static_select way of showing the list items and instead of using options, I used option_groups. I'm doing it as shown below and I'm able to see all the options as expected. I just wanted to know how do we check which option the user has selected? Would really appreciate If you could help me with this. Thanks much!

const customerName = body.text.trim();
    const urlEncodesCustomername = encodeURIComponent(customerName)
    const customersHash = categorizeCustomersByAlphabet(customers)

    const optionGroupsArray = [] // store option groups alphabetically

    const optionGroupKeys = Object.keys(customersHash)
    optionGroupKeys.forEach((key) => {
      const hashkeyValue = customersHash[key]
      let options = [];
      if(hashkeyValue && hashkeyValue.length > 0) {
        options = hashkeyValue.map(customer => {
          return {
            text: {
              type: 'plain_text',
              text: customer
            },
            value: encodeURIComponent(customer)
          }
        })
      }

      let group = {
          "label": {
              "type": "plain_text",
              "text": key
          },
          "options": options
      };
      optionGroupsArray.push(group)
    })


    if (customerName === 'list') {
        try {          
            const result = await client.views.open({
              trigger_id: body.trigger_id,
              view: {
                type: 'modal',
                callback_id: 'select_company_modal',
                title: {
                  type: 'plain_text',
                  text: 'Customer 360° Insights'
                },
                blocks: [
                  {
                    type: 'input',
                    block_id: 'company_dropdown',
                    element: {
                      type: 'static_select',
                      action_id: 'company_select',
                      placeholder: {
                        type: 'plain_text',
                        text: 'Start typing to view suggestions...'
                      },
                      option_groups: optionGroupsArray
                    },
                    label: {
                      type: 'plain_text',
                      text: 'Select a customer from the dropdown'
                    }
                  }
                ],
                submit: {
                      type: 'plain_text',
                      text: 'Get Details',
                  },
              }
            });

            console.log(result)
        } catch (error) {
          console.error(error);
        }
    }

You can receive the selected item under body.view.state.values.{block_id}.{action_id} data structure in app.view("your_callback_id_here", listener) listener.

Glad to hear that you've achieved your goal with static select! If everything is clear now, would you mind closing this?

Got it. Thanks!

Yeah sure, I can go ahead and close it.

@seratch I did as you have mentioned above but when I click on the "Get Details" or Submit button, the @app.view("your_callback_id_here") controller doesn't get triggered in the first place. Am I doing something wrong?

app.view('select_company_modal', async ({ ack, body, view, client }) => {
  await ack();
  
  console.log("body", body)
  console.log("view", view)
});
Screenshot 2024-04-18 at 12 39 10 PM

Have you properly configured "Request URL" under the "Interactivity & Shortcuts" section on api.slack.com/apps/{your app id} site?

Oh okay, that might be the problem. The "Request URL" under the "Interactivity & Shortcuts" section was pointing to the prod GCP cluster external url and slash command request url was pointing to my local ngrok. Let me change that and try again

Yes, that was the issue. It's fixed and I'm able to see the selected option. Really appreciate your help and quick response @seratch.

Closing this issue!