mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-14 11:30:41 +00:00
Slack: expand advanced modal controls payloads and confirms
This commit is contained in:
@@ -504,6 +504,9 @@ describe("registerSlackInteractionEvents", () => {
|
||||
const payload = JSON.parse(eventText.replace("Slack interaction: ", "")) as {
|
||||
actionType: string;
|
||||
selectedValues?: string[];
|
||||
selectedUsers?: string[];
|
||||
selectedChannels?: string[];
|
||||
selectedConversations?: string[];
|
||||
selectedLabels?: string[];
|
||||
selectedDate?: string;
|
||||
selectedTime?: string;
|
||||
@@ -520,6 +523,9 @@ describe("registerSlackInteractionEvents", () => {
|
||||
"G777",
|
||||
"G888",
|
||||
]);
|
||||
expect(payload.selectedUsers).toEqual(["U777", "U888"]);
|
||||
expect(payload.selectedChannels).toEqual(["C777", "C888"]);
|
||||
expect(payload.selectedConversations).toEqual(["G777", "G888"]);
|
||||
expect(payload.selectedLabels).toEqual(["Alpha", "Beta"]);
|
||||
expect(payload.selectedDate).toBe("2026-02-16");
|
||||
expect(payload.selectedTime).toBe("14:30");
|
||||
@@ -719,6 +725,9 @@ describe("registerSlackInteractionEvents", () => {
|
||||
actionId: string;
|
||||
inputKind?: string;
|
||||
selectedValues?: string[];
|
||||
selectedUsers?: string[];
|
||||
selectedChannels?: string[];
|
||||
selectedConversations?: string[];
|
||||
selectedLabels?: string[];
|
||||
selectedDate?: string;
|
||||
selectedTime?: string;
|
||||
@@ -736,9 +745,21 @@ describe("registerSlackInteractionEvents", () => {
|
||||
selectedValues: ["prod"],
|
||||
selectedLabels: ["Production"],
|
||||
}),
|
||||
expect.objectContaining({ actionId: "assignee_select", selectedValues: ["U900"] }),
|
||||
expect.objectContaining({ actionId: "channel_select", selectedValues: ["C900"] }),
|
||||
expect.objectContaining({ actionId: "convo_select", selectedValues: ["G900"] }),
|
||||
expect.objectContaining({
|
||||
actionId: "assignee_select",
|
||||
selectedValues: ["U900"],
|
||||
selectedUsers: ["U900"],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
actionId: "channel_select",
|
||||
selectedValues: ["C900"],
|
||||
selectedChannels: ["C900"],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
actionId: "convo_select",
|
||||
selectedValues: ["G900"],
|
||||
selectedConversations: ["G900"],
|
||||
}),
|
||||
expect.objectContaining({ actionId: "date_select", selectedDate: "2026-02-16" }),
|
||||
expect.objectContaining({ actionId: "time_select", selectedTime: "12:45" }),
|
||||
expect.objectContaining({ actionId: "datetime_select", selectedDateTime: 1_771_632_300 }),
|
||||
|
||||
@@ -26,6 +26,9 @@ type InteractionSummary = {
|
||||
inputKind?: "text" | "number" | "email" | "url" | "rich_text";
|
||||
value?: string;
|
||||
selectedValues?: string[];
|
||||
selectedUsers?: string[];
|
||||
selectedChannels?: string[];
|
||||
selectedConversations?: string[];
|
||||
selectedLabels?: string[];
|
||||
selectedDate?: string;
|
||||
selectedTime?: string;
|
||||
@@ -51,6 +54,9 @@ type ModalInputSummary = {
|
||||
inputKind?: "text" | "number" | "email" | "url" | "rich_text";
|
||||
value?: string;
|
||||
selectedValues?: string[];
|
||||
selectedUsers?: string[];
|
||||
selectedChannels?: string[];
|
||||
selectedConversations?: string[];
|
||||
selectedLabels?: string[];
|
||||
selectedDate?: string;
|
||||
selectedTime?: string;
|
||||
@@ -121,15 +127,24 @@ function summarizeAction(
|
||||
rich_text_value?: unknown;
|
||||
};
|
||||
const actionType = typed.type;
|
||||
const selectedUsers = uniqueNonEmptyStrings([
|
||||
...(typed.selected_user ? [typed.selected_user] : []),
|
||||
...(Array.isArray(typed.selected_users) ? typed.selected_users : []),
|
||||
]);
|
||||
const selectedChannels = uniqueNonEmptyStrings([
|
||||
...(typed.selected_channel ? [typed.selected_channel] : []),
|
||||
...(Array.isArray(typed.selected_channels) ? typed.selected_channels : []),
|
||||
]);
|
||||
const selectedConversations = uniqueNonEmptyStrings([
|
||||
...(typed.selected_conversation ? [typed.selected_conversation] : []),
|
||||
...(Array.isArray(typed.selected_conversations) ? typed.selected_conversations : []),
|
||||
]);
|
||||
const selectedValues = uniqueNonEmptyStrings([
|
||||
...(typed.selected_option?.value ? [typed.selected_option.value] : []),
|
||||
...(readOptionValues(typed.selected_options) ?? []),
|
||||
...(typed.selected_user ? [typed.selected_user] : []),
|
||||
...(Array.isArray(typed.selected_users) ? typed.selected_users : []),
|
||||
...(typed.selected_channel ? [typed.selected_channel] : []),
|
||||
...(Array.isArray(typed.selected_channels) ? typed.selected_channels : []),
|
||||
...(typed.selected_conversation ? [typed.selected_conversation] : []),
|
||||
...(Array.isArray(typed.selected_conversations) ? typed.selected_conversations : []),
|
||||
...selectedUsers,
|
||||
...selectedChannels,
|
||||
...selectedConversations,
|
||||
]);
|
||||
const selectedLabels = uniqueNonEmptyStrings([
|
||||
...(typed.selected_option?.text?.text ? [typed.selected_option.text.text] : []),
|
||||
@@ -169,6 +184,9 @@ function summarizeAction(
|
||||
inputKind,
|
||||
value: typed.value,
|
||||
selectedValues: selectedValues.length > 0 ? selectedValues : undefined,
|
||||
selectedUsers: selectedUsers.length > 0 ? selectedUsers : undefined,
|
||||
selectedChannels: selectedChannels.length > 0 ? selectedChannels : undefined,
|
||||
selectedConversations: selectedConversations.length > 0 ? selectedConversations : undefined,
|
||||
selectedLabels: selectedLabels.length > 0 ? selectedLabels : undefined,
|
||||
selectedDate: typed.selected_date,
|
||||
selectedTime: typed.selected_time,
|
||||
|
||||
@@ -169,7 +169,7 @@ function encodeValue(parts: { command: string; arg: string; value: string; userI
|
||||
|
||||
function findFirstActionsBlock(payload: { blocks?: Array<{ type: string }> }) {
|
||||
return payload.blocks?.find((block) => block.type === "actions") as
|
||||
| { type: string; elements?: Array<{ type?: string; action_id?: string }> }
|
||||
| { type: string; elements?: Array<{ type?: string; action_id?: string; confirm?: unknown }> }
|
||||
| undefined;
|
||||
}
|
||||
|
||||
@@ -293,6 +293,7 @@ describe("Slack native command argument menus", () => {
|
||||
const actions = findFirstActionsBlock(payload);
|
||||
const elementType = actions?.elements?.[0]?.type;
|
||||
expect(elementType).toBe("button");
|
||||
expect(actions?.elements?.[0]?.confirm).toBeTruthy();
|
||||
});
|
||||
|
||||
it("shows a static_select menu when choices exceed button row size", async () => {
|
||||
@@ -321,6 +322,7 @@ describe("Slack native command argument menus", () => {
|
||||
const element = actions?.elements?.[0];
|
||||
expect(element?.type).toBe("static_select");
|
||||
expect(element?.action_id).toBe("openclaw_cmdarg");
|
||||
expect(element?.confirm).toBeTruthy();
|
||||
});
|
||||
|
||||
it("falls back to buttons when static_select value limit would be exceeded", async () => {
|
||||
@@ -345,6 +347,7 @@ describe("Slack native command argument menus", () => {
|
||||
const actions = findFirstActionsBlock(payload);
|
||||
const firstElement = actions?.elements?.[0];
|
||||
expect(firstElement?.type).toBe("button");
|
||||
expect(firstElement?.confirm).toBeTruthy();
|
||||
});
|
||||
|
||||
it("shows an overflow menu when choices fit compact range", async () => {
|
||||
@@ -370,6 +373,7 @@ describe("Slack native command argument menus", () => {
|
||||
const element = actions?.elements?.[0];
|
||||
expect(element?.type).toBe("overflow");
|
||||
expect(element?.action_id).toBe("openclaw_cmdarg");
|
||||
expect(element?.confirm).toBeTruthy();
|
||||
});
|
||||
|
||||
it("dispatches the command when a menu button is clicked", async () => {
|
||||
|
||||
@@ -47,6 +47,18 @@ function truncatePlainText(value: string, max: number): string {
|
||||
return `${trimmed.slice(0, max - 1)}…`;
|
||||
}
|
||||
|
||||
function buildSlackArgMenuConfirm(params: { command: string; arg: string }) {
|
||||
return {
|
||||
title: { type: "plain_text", text: "Confirm selection" },
|
||||
text: {
|
||||
type: "mrkdwn",
|
||||
text: `Run */${params.command}* with *${params.arg}* set to this value?`,
|
||||
},
|
||||
confirm: { type: "plain_text", text: "Run command" },
|
||||
deny: { type: "plain_text", text: "Cancel" },
|
||||
};
|
||||
}
|
||||
|
||||
type CommandsRegistry = typeof import("../../auto-reply/commands-registry.js");
|
||||
let commandsRegistry: CommandsRegistry | undefined;
|
||||
async function getCommandsRegistry(): Promise<CommandsRegistry> {
|
||||
@@ -141,6 +153,7 @@ function buildSlackCommandArgMenuBlocks(params: {
|
||||
{
|
||||
type: "overflow",
|
||||
action_id: SLACK_COMMAND_ARG_ACTION_ID,
|
||||
confirm: buildSlackArgMenuConfirm({ command: params.command, arg: params.arg }),
|
||||
options: encodedChoices.map((choice) => ({
|
||||
text: { type: "plain_text", text: choice.label.slice(0, 75) },
|
||||
value: choice.value,
|
||||
@@ -157,6 +170,7 @@ function buildSlackCommandArgMenuBlocks(params: {
|
||||
action_id: SLACK_COMMAND_ARG_ACTION_ID,
|
||||
text: { type: "plain_text", text: choice.label },
|
||||
value: choice.value,
|
||||
confirm: buildSlackArgMenuConfirm({ command: params.command, arg: params.arg }),
|
||||
})),
|
||||
}))
|
||||
: chunkItems(encodedChoices, SLACK_COMMAND_ARG_SELECT_OPTIONS_MAX).map((choices, index) => ({
|
||||
@@ -165,6 +179,7 @@ function buildSlackCommandArgMenuBlocks(params: {
|
||||
{
|
||||
type: "static_select",
|
||||
action_id: SLACK_COMMAND_ARG_ACTION_ID,
|
||||
confirm: buildSlackArgMenuConfirm({ command: params.command, arg: params.arg }),
|
||||
placeholder: {
|
||||
type: "plain_text",
|
||||
text: index === 0 ? `Choose ${params.arg}` : `Choose ${params.arg} (${index + 1})`,
|
||||
|
||||
Reference in New Issue
Block a user