mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-07 21:31:25 +00:00
Slack: add rich text previews for modal inputs
This commit is contained in:
@@ -707,7 +707,15 @@ describe("registerSlackInteractionEvents", () => {
|
|||||||
type: "rich_text_input",
|
type: "rich_text_input",
|
||||||
rich_text_value: {
|
rich_text_value: {
|
||||||
type: "rich_text",
|
type: "rich_text",
|
||||||
elements: [{ type: "rich_text_section", elements: [] }],
|
elements: [
|
||||||
|
{
|
||||||
|
type: "rich_text_section",
|
||||||
|
elements: [
|
||||||
|
{ type: "text", text: "Ship this now" },
|
||||||
|
{ type: "text", text: "with canary metrics" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -736,6 +744,7 @@ describe("registerSlackInteractionEvents", () => {
|
|||||||
inputEmail?: string;
|
inputEmail?: string;
|
||||||
inputUrl?: string;
|
inputUrl?: string;
|
||||||
richTextValue?: unknown;
|
richTextValue?: unknown;
|
||||||
|
richTextPreview?: string;
|
||||||
}>;
|
}>;
|
||||||
};
|
};
|
||||||
expect(payload.inputs).toEqual(
|
expect(payload.inputs).toEqual(
|
||||||
@@ -791,15 +800,72 @@ describe("registerSlackInteractionEvents", () => {
|
|||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
actionId: "richtext_input",
|
actionId: "richtext_input",
|
||||||
inputKind: "rich_text",
|
inputKind: "rich_text",
|
||||||
|
richTextPreview: "Ship this now with canary metrics",
|
||||||
richTextValue: {
|
richTextValue: {
|
||||||
type: "rich_text",
|
type: "rich_text",
|
||||||
elements: [{ type: "rich_text_section", elements: [] }],
|
elements: [
|
||||||
|
{
|
||||||
|
type: "rich_text_section",
|
||||||
|
elements: [
|
||||||
|
{ type: "text", text: "Ship this now" },
|
||||||
|
{ type: "text", text: "with canary metrics" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("truncates rich text preview to keep payload summaries compact", async () => {
|
||||||
|
enqueueSystemEventMock.mockReset();
|
||||||
|
const { ctx, getViewHandler } = createContext();
|
||||||
|
registerSlackInteractionEvents({ ctx: ctx as never });
|
||||||
|
const viewHandler = getViewHandler();
|
||||||
|
expect(viewHandler).toBeTruthy();
|
||||||
|
|
||||||
|
const longText = "deploy ".repeat(40).trim();
|
||||||
|
const ack = vi.fn().mockResolvedValue(undefined);
|
||||||
|
await viewHandler!({
|
||||||
|
ack,
|
||||||
|
body: {
|
||||||
|
user: { id: "U555" },
|
||||||
|
view: {
|
||||||
|
id: "V555",
|
||||||
|
callback_id: "openclaw:long_richtext",
|
||||||
|
state: {
|
||||||
|
values: {
|
||||||
|
richtext_block: {
|
||||||
|
richtext_input: {
|
||||||
|
type: "rich_text_input",
|
||||||
|
rich_text_value: {
|
||||||
|
type: "rich_text",
|
||||||
|
elements: [
|
||||||
|
{
|
||||||
|
type: "rich_text_section",
|
||||||
|
elements: [{ type: "text", text: longText }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(ack).toHaveBeenCalled();
|
||||||
|
const [eventText] = enqueueSystemEventMock.mock.calls[0] as [string];
|
||||||
|
const payload = JSON.parse(eventText.replace("Slack interaction: ", "")) as {
|
||||||
|
inputs: Array<{ actionId: string; richTextPreview?: string }>;
|
||||||
|
};
|
||||||
|
const richInput = payload.inputs.find((input) => input.actionId === "richtext_input");
|
||||||
|
expect(richInput?.richTextPreview).toBeTruthy();
|
||||||
|
expect((richInput?.richTextPreview ?? "").length).toBeLessThanOrEqual(120);
|
||||||
|
});
|
||||||
|
|
||||||
it("captures modal close events and enqueues view closed event", async () => {
|
it("captures modal close events and enqueues view closed event", async () => {
|
||||||
enqueueSystemEventMock.mockReset();
|
enqueueSystemEventMock.mockReset();
|
||||||
const { ctx, getViewClosedHandler, resolveSessionKey } = createContext();
|
const { ctx, getViewClosedHandler, resolveSessionKey } = createContext();
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ type InteractionSummary = {
|
|||||||
inputEmail?: string;
|
inputEmail?: string;
|
||||||
inputUrl?: string;
|
inputUrl?: string;
|
||||||
richTextValue?: unknown;
|
richTextValue?: unknown;
|
||||||
|
richTextPreview?: string;
|
||||||
userId?: string;
|
userId?: string;
|
||||||
teamId?: string;
|
teamId?: string;
|
||||||
triggerId?: string;
|
triggerId?: string;
|
||||||
@@ -66,6 +67,7 @@ type ModalInputSummary = {
|
|||||||
inputEmail?: string;
|
inputEmail?: string;
|
||||||
inputUrl?: string;
|
inputUrl?: string;
|
||||||
richTextValue?: unknown;
|
richTextValue?: unknown;
|
||||||
|
richTextPreview?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
function readOptionValues(options: unknown): string[] | undefined {
|
function readOptionValues(options: unknown): string[] | undefined {
|
||||||
@@ -107,6 +109,35 @@ function uniqueNonEmptyStrings(values: string[]): string[] {
|
|||||||
return unique;
|
return unique;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function collectRichTextFragments(value: unknown, out: string[]): void {
|
||||||
|
if (!value || typeof value !== "object") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const typed = value as { text?: unknown; elements?: unknown };
|
||||||
|
if (typeof typed.text === "string" && typed.text.trim().length > 0) {
|
||||||
|
out.push(typed.text.trim());
|
||||||
|
}
|
||||||
|
if (Array.isArray(typed.elements)) {
|
||||||
|
for (const child of typed.elements) {
|
||||||
|
collectRichTextFragments(child, out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function summarizeRichTextPreview(value: unknown): string | undefined {
|
||||||
|
const fragments: string[] = [];
|
||||||
|
collectRichTextFragments(value, fragments);
|
||||||
|
if (fragments.length === 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const joined = fragments.join(" ").replace(/\s+/g, " ").trim();
|
||||||
|
if (!joined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const max = 120;
|
||||||
|
return joined.length <= max ? joined : `${joined.slice(0, max - 1)}…`;
|
||||||
|
}
|
||||||
|
|
||||||
function summarizeAction(
|
function summarizeAction(
|
||||||
action: Record<string, unknown>,
|
action: Record<string, unknown>,
|
||||||
): Omit<InteractionSummary, "actionId" | "blockId"> {
|
): Omit<InteractionSummary, "actionId" | "blockId"> {
|
||||||
@@ -166,6 +197,7 @@ function summarizeAction(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const richTextValue = actionType === "rich_text_input" ? typed.rich_text_value : undefined;
|
const richTextValue = actionType === "rich_text_input" ? typed.rich_text_value : undefined;
|
||||||
|
const richTextPreview = summarizeRichTextPreview(richTextValue);
|
||||||
const inputKind =
|
const inputKind =
|
||||||
actionType === "number_input"
|
actionType === "number_input"
|
||||||
? "number"
|
? "number"
|
||||||
@@ -197,6 +229,7 @@ function summarizeAction(
|
|||||||
inputEmail,
|
inputEmail,
|
||||||
inputUrl,
|
inputUrl,
|
||||||
richTextValue,
|
richTextValue,
|
||||||
|
richTextPreview,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,6 +275,9 @@ function formatInteractionSelectionLabel(params: {
|
|||||||
if (typeof params.summary.selectedDateTime === "number") {
|
if (typeof params.summary.selectedDateTime === "number") {
|
||||||
return new Date(params.summary.selectedDateTime * 1000).toISOString();
|
return new Date(params.summary.selectedDateTime * 1000).toISOString();
|
||||||
}
|
}
|
||||||
|
if (params.summary.richTextPreview) {
|
||||||
|
return params.summary.richTextPreview;
|
||||||
|
}
|
||||||
if (params.summary.value?.trim()) {
|
if (params.summary.value?.trim()) {
|
||||||
return params.summary.value.trim();
|
return params.summary.value.trim();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user