Slack: update action rows for select interactions

This commit is contained in:
Colin
2026-02-16 13:19:03 -05:00
committed by Peter Steinberger
parent 1bfdd4e237
commit d1aa2323bd
2 changed files with 107 additions and 5 deletions

View File

@@ -165,7 +165,7 @@ describe("registerSlackInteractionEvents", () => {
expect(app.client.chat.update).toHaveBeenCalledTimes(1);
});
it("captures select values and skips chat.update for non-button actions", async () => {
it("captures select values and updates action rows for non-button actions", async () => {
enqueueSystemEventMock.mockReset();
const { ctx, app, getHandler } = createContext();
registerSlackInteractionEvents({ ctx: ctx as never });
@@ -205,7 +205,19 @@ describe("registerSlackInteractionEvents", () => {
expect(payload.actionType).toBe("static_select");
expect(payload.selectedValues).toEqual(["canary"]);
expect(payload.selectedLabels).toEqual(["Canary"]);
expect(app.client.chat.update).not.toHaveBeenCalled();
expect(app.client.chat.update).toHaveBeenCalledTimes(1);
expect(app.client.chat.update).toHaveBeenCalledWith(
expect.objectContaining({
channel: "C1",
ts: "111.222",
blocks: [
{
type: "context",
elements: [{ type: "mrkdwn", text: ":white_check_mark: *Canary* selected" }],
},
],
}),
);
});
it("falls back to container channel and message timestamps", async () => {
@@ -254,6 +266,62 @@ describe("registerSlackInteractionEvents", () => {
expect(app.client.chat.update).not.toHaveBeenCalled();
});
it("summarizes multi-select confirmations in updated message rows", async () => {
enqueueSystemEventMock.mockReset();
const { ctx, app, getHandler } = createContext();
registerSlackInteractionEvents({ ctx: ctx as never });
const handler = getHandler();
expect(handler).toBeTruthy();
const ack = vi.fn().mockResolvedValue(undefined);
await handler!({
ack,
body: {
user: { id: "U222" },
channel: { id: "C2" },
message: {
ts: "333.444",
text: "fallback",
blocks: [
{
type: "actions",
block_id: "multi_block",
elements: [{ type: "multi_static_select", action_id: "openclaw:multi" }],
},
],
},
},
action: {
type: "multi_static_select",
action_id: "openclaw:multi",
block_id: "multi_block",
selected_options: [
{ text: { type: "plain_text", text: "Alpha" }, value: "alpha" },
{ text: { type: "plain_text", text: "Beta" }, value: "beta" },
{ text: { type: "plain_text", text: "Gamma" }, value: "gamma" },
{ text: { type: "plain_text", text: "Delta" }, value: "delta" },
],
},
});
expect(ack).toHaveBeenCalled();
expect(app.client.chat.update).toHaveBeenCalledTimes(1);
expect(app.client.chat.update).toHaveBeenCalledWith(
expect.objectContaining({
channel: "C2",
ts: "333.444",
blocks: [
{
type: "context",
elements: [
{ type: "mrkdwn", text: ":white_check_mark: *Alpha, Beta, Gamma +1* selected" },
],
},
],
}),
);
});
it("captures expanded selection and temporal payload fields", async () => {
enqueueSystemEventMock.mockReset();
const { ctx, getHandler } = createContext();

View File

@@ -147,6 +147,36 @@ function isBulkActionsBlock(block: InteractionMessageBlock): boolean {
);
}
function formatInteractionSelectionLabel(params: {
actionId: string;
summary: Omit<InteractionSummary, "actionId" | "blockId">;
buttonText?: string;
}): string {
if (params.summary.actionType === "button" && params.buttonText?.trim()) {
return params.buttonText.trim();
}
if (params.summary.selectedLabels?.length) {
if (params.summary.selectedLabels.length <= 3) {
return params.summary.selectedLabels.join(", ");
}
return `${params.summary.selectedLabels.slice(0, 3).join(", ")} +${
params.summary.selectedLabels.length - 3
}`;
}
if (params.summary.selectedValues?.length) {
if (params.summary.selectedValues.length <= 3) {
return params.summary.selectedValues.join(", ");
}
return `${params.summary.selectedValues.slice(0, 3).join(", ")} +${
params.summary.selectedValues.length - 3
}`;
}
if (params.summary.value?.trim()) {
return params.summary.value.trim();
}
return params.actionId;
}
function summarizeViewState(values: unknown): ModalInputSummary[] {
if (!values || typeof values !== "object") {
return [];
@@ -274,17 +304,21 @@ export function registerSlackInteractionEvents(params: { ctx: SlackMonitorContex
return;
}
if (typedAction.type !== "button") {
if (!blockId) {
return;
}
const buttonText = typedAction.text?.text ?? actionId;
const selectedLabel = formatInteractionSelectionLabel({
actionId,
summary: actionSummary,
buttonText: typedAction.text?.text,
});
let updatedBlocks = originalBlocks.map((block) => {
const typedBlock = block as InteractionMessageBlock;
if (typedBlock.type === "actions" && typedBlock.block_id === blockId) {
return {
type: "context",
elements: [{ type: "mrkdwn", text: `:white_check_mark: *${buttonText}* selected` }],
elements: [{ type: "mrkdwn", text: `:white_check_mark: *${selectedLabel}* selected` }],
};
}
return block;