Slack: enrich modal input payload normalization

This commit is contained in:
Colin
2026-02-16 13:24:05 -05:00
committed by Peter Steinberger
parent d1aa2323bd
commit 7c5529a153
2 changed files with 128 additions and 1 deletions

View File

@@ -519,6 +519,51 @@ describe("registerSlackInteractionEvents", () => {
selected_date_time: 1_771_632_300,
},
},
radio_block: {
radio_select: {
type: "radio_buttons",
selected_option: {
text: { type: "plain_text", text: "Blue" },
value: "blue",
},
},
},
checks_block: {
checks_select: {
type: "checkboxes",
selected_options: [
{ text: { type: "plain_text", text: "A" }, value: "a" },
{ text: { type: "plain_text", text: "B" }, value: "b" },
],
},
},
number_block: {
number_input: {
type: "number_input",
value: "42.5",
},
},
email_block: {
email_input: {
type: "email_text_input",
value: "team@openclaw.ai",
},
},
url_block: {
url_input: {
type: "url_text_input",
value: "https://docs.openclaw.ai",
},
},
richtext_block: {
richtext_input: {
type: "rich_text_input",
rich_text_value: {
type: "rich_text",
elements: [{ type: "rich_text_section", elements: [] }],
},
},
},
},
},
},
@@ -531,11 +576,16 @@ describe("registerSlackInteractionEvents", () => {
const payload = JSON.parse(eventText.replace("Slack interaction: ", "")) as {
inputs: Array<{
actionId: string;
inputKind?: string;
selectedValues?: string[];
selectedLabels?: string[];
selectedDate?: string;
selectedTime?: string;
selectedDateTime?: number;
inputNumber?: number;
inputEmail?: string;
inputUrl?: string;
richTextValue?: unknown;
}>;
};
expect(payload.inputs).toEqual(
@@ -551,6 +601,39 @@ describe("registerSlackInteractionEvents", () => {
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 }),
expect.objectContaining({
actionId: "radio_select",
selectedValues: ["blue"],
selectedLabels: ["Blue"],
}),
expect.objectContaining({
actionId: "checks_select",
selectedValues: ["a", "b"],
selectedLabels: ["A", "B"],
}),
expect.objectContaining({
actionId: "number_input",
inputKind: "number",
inputNumber: 42.5,
}),
expect.objectContaining({
actionId: "email_input",
inputKind: "email",
inputEmail: "team@openclaw.ai",
}),
expect.objectContaining({
actionId: "url_input",
inputKind: "url",
inputUrl: "https://docs.openclaw.ai/",
}),
expect.objectContaining({
actionId: "richtext_input",
inputKind: "rich_text",
richTextValue: {
type: "rich_text",
elements: [{ type: "rich_text_section", elements: [] }],
},
}),
]),
);
});

View File

@@ -23,6 +23,7 @@ type InteractionSummary = {
actionId: string;
blockId?: string;
actionType?: string;
inputKind?: "text" | "number" | "email" | "url" | "rich_text";
value?: string;
selectedValues?: string[];
selectedLabels?: string[];
@@ -30,6 +31,10 @@ type InteractionSummary = {
selectedTime?: string;
selectedDateTime?: number;
inputValue?: string;
inputNumber?: number;
inputEmail?: string;
inputUrl?: string;
richTextValue?: unknown;
userId?: string;
teamId?: string;
triggerId?: string;
@@ -43,6 +48,7 @@ type ModalInputSummary = {
blockId: string;
actionId: string;
actionType?: string;
inputKind?: "text" | "number" | "email" | "url" | "rich_text";
value?: string;
selectedValues?: string[];
selectedLabels?: string[];
@@ -50,6 +56,10 @@ type ModalInputSummary = {
selectedTime?: string;
selectedDateTime?: number;
inputValue?: string;
inputNumber?: number;
inputEmail?: string;
inputUrl?: string;
richTextValue?: unknown;
};
function readOptionValues(options: unknown): string[] | undefined {
@@ -108,6 +118,7 @@ function summarizeAction(
selected_time?: string;
selected_date_time?: number;
value?: string;
rich_text_value?: unknown;
};
const actionType = typed.type;
const selectedValues = uniqueNonEmptyStrings([
@@ -124,9 +135,38 @@ function summarizeAction(
...(typed.selected_option?.text?.text ? [typed.selected_option.text.text] : []),
...(readOptionLabels(typed.selected_options) ?? []),
]);
const inputValue = typeof typed.value === "string" ? typed.value : undefined;
const inputNumber =
actionType === "number_input" && inputValue != null ? Number.parseFloat(inputValue) : undefined;
const parsedNumber = Number.isFinite(inputNumber) ? inputNumber : undefined;
const inputEmail =
actionType === "email_text_input" && inputValue?.includes("@") ? inputValue : undefined;
let inputUrl: string | undefined;
if (actionType === "url_text_input" && inputValue) {
try {
// Normalize to a canonical URL string so downstream handlers do not need to reparse.
inputUrl = new URL(inputValue).toString();
} catch {
inputUrl = undefined;
}
}
const richTextValue = actionType === "rich_text_input" ? typed.rich_text_value : undefined;
const inputKind =
actionType === "number_input"
? "number"
: actionType === "email_text_input"
? "email"
: actionType === "url_text_input"
? "url"
: actionType === "rich_text_input"
? "rich_text"
: inputValue != null
? "text"
: undefined;
return {
actionType,
inputKind,
value: typed.value,
selectedValues: selectedValues.length > 0 ? selectedValues : undefined,
selectedLabels: selectedLabels.length > 0 ? selectedLabels : undefined,
@@ -134,7 +174,11 @@ function summarizeAction(
selectedTime: typed.selected_time,
selectedDateTime:
typeof typed.selected_date_time === "number" ? typed.selected_date_time : undefined,
inputValue: typed.value,
inputValue,
inputNumber: parsedNumber,
inputEmail,
inputUrl,
richTextValue,
};
}