Added views and view submission support to Slack integration
This commit is contained in:
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
"@trigger.dev/sdk": patch
|
||||||
|
"@trigger.dev/slack": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Added views and view submission support to Slack integration
|
||||||
@@ -46,22 +46,36 @@ function SlackInteraction({
|
|||||||
{trigger.name}
|
{trigger.name}
|
||||||
</Header2>
|
</Header2>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2 items-baseline">
|
{trigger.source.type === "block_action" && (
|
||||||
<Body size="extra-small" className={workflowNodeUppercaseClasses}>
|
<div className="flex gap-2 items-baseline">
|
||||||
Block
|
<Body size="extra-small" className={workflowNodeUppercaseClasses}>
|
||||||
</Body>
|
Block
|
||||||
<Header2 size="small" className="text-slate-300 mb-2">
|
</Body>
|
||||||
{trigger.source.blockId}
|
<Header2 size="small" className="text-slate-300 mb-2">
|
||||||
</Header2>
|
{trigger.source.blockId}
|
||||||
</div>
|
</Header2>
|
||||||
<div className="flex gap-2 items-baseline">
|
</div>
|
||||||
<Body size="extra-small" className={workflowNodeUppercaseClasses}>
|
)}
|
||||||
Action
|
{trigger.source.type === "block_action" && (
|
||||||
</Body>
|
<div className="flex gap-2 items-baseline">
|
||||||
<Header2 size="small" className="text-slate-300 mb-2">
|
<Body size="extra-small" className={workflowNodeUppercaseClasses}>
|
||||||
{trigger.source.actionIds.join(", ")}
|
Action
|
||||||
</Header2>
|
</Body>
|
||||||
</div>
|
<Header2 size="small" className="text-slate-300 mb-2">
|
||||||
|
{trigger.source.actionIds.join(", ")}
|
||||||
|
</Header2>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{trigger.source.type === "view_submission" && (
|
||||||
|
<div className="flex gap-2 items-baseline">
|
||||||
|
<Body size="extra-small" className={workflowNodeUppercaseClasses}>
|
||||||
|
Callback IDs
|
||||||
|
</Body>
|
||||||
|
<Header2 size="small" className="text-slate-300 mb-2">
|
||||||
|
{trigger.source.callbackIds.join(", ")}
|
||||||
|
</Header2>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -247,11 +247,17 @@ function triggerProperties(
|
|||||||
internalSource.source
|
internalSource.source
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const title =
|
||||||
|
slackSource.type === "block_action"
|
||||||
|
? `block_id = ${slackSource.blockId}`
|
||||||
|
: `callback_id = ${slackSource.callbackIds.join(", ")}`;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: workflow.type,
|
type: workflow.type,
|
||||||
typeTitle: "Slack interaction",
|
typeTitle: "Slack interaction",
|
||||||
title: `block_id = ${slackSource.blockId}`,
|
title: title,
|
||||||
properties:
|
properties:
|
||||||
|
slackSource.type === "block_action" &&
|
||||||
slackSource.actionIds.length > 0
|
slackSource.actionIds.length > 0
|
||||||
? [{ key: "Action ID", value: slackSource.actionIds.join(", ") }]
|
? [{ key: "Action ID", value: slackSource.actionIds.join(", ") }]
|
||||||
: undefined,
|
: undefined,
|
||||||
|
|||||||
@@ -15,10 +15,13 @@ export async function action({ request }: ActionArgs) {
|
|||||||
const service = new HandleSlackInteractivity();
|
const service = new HandleSlackInteractivity();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await service.call(parsedPayload);
|
return await service.call(parsedPayload);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
|
||||||
|
|
||||||
return { status: 200 };
|
return new Response(
|
||||||
|
error instanceof Error ? error.message : "Unknown error",
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
|
import { json } from "@remix-run/server-runtime";
|
||||||
import { schemas } from "@trigger.dev/slack/internal";
|
import { schemas } from "@trigger.dev/slack/internal";
|
||||||
import { ulid } from "ulid";
|
import { ulid } from "ulid";
|
||||||
|
import type { z } from "zod";
|
||||||
import { generateErrorMessage } from "zod-error";
|
import { generateErrorMessage } from "zod-error";
|
||||||
import type { PrismaClient } from "~/db.server";
|
import type { PrismaClient } from "~/db.server";
|
||||||
import { prisma } from "~/db.server";
|
import { prisma } from "~/db.server";
|
||||||
import { IngestEvent } from "../events/ingest.server";
|
import { IngestEvent } from "../events/ingest.server";
|
||||||
|
import type { OutputUnit } from "@cfworker/json-schema";
|
||||||
|
import { Validator } from "@cfworker/json-schema";
|
||||||
|
|
||||||
export class HandleSlackInteractivity {
|
export class HandleSlackInteractivity {
|
||||||
#prismaClient: PrismaClient;
|
#prismaClient: PrismaClient;
|
||||||
@@ -15,7 +19,7 @@ export class HandleSlackInteractivity {
|
|||||||
public async call(payload: unknown) {
|
public async call(payload: unknown) {
|
||||||
console.log("payload", JSON.stringify(payload, null, 2));
|
console.log("payload", JSON.stringify(payload, null, 2));
|
||||||
|
|
||||||
const parsedPayload = schemas.blockAction.safeParse(payload);
|
const parsedPayload = schemas.InteractivityPayloadSchema.safeParse(payload);
|
||||||
|
|
||||||
if (!parsedPayload.success) {
|
if (!parsedPayload.success) {
|
||||||
console.error(
|
console.error(
|
||||||
@@ -23,38 +27,26 @@ export class HandleSlackInteractivity {
|
|||||||
generateErrorMessage(parsedPayload.error.issues)
|
generateErrorMessage(parsedPayload.error.issues)
|
||||||
);
|
);
|
||||||
|
|
||||||
return;
|
return new Response(null, { status: 200 });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parsedPayload.data.type !== "block_actions") {
|
switch (parsedPayload.data.type) {
|
||||||
return;
|
case "block_actions":
|
||||||
|
return this.#handleBlockActionInteraction(parsedPayload.data);
|
||||||
|
case "view_submission":
|
||||||
|
return this.#handleViewSubmissionInteraction(parsedPayload.data);
|
||||||
|
case "view_closed":
|
||||||
|
return this.#handleViewClosedInteraction(parsedPayload.data);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!parsedPayload.data.message) {
|
async #handleBlockActionInteraction(
|
||||||
return;
|
payload: z.infer<typeof schemas.BlockActionInteractivityPayloadSchema>
|
||||||
}
|
) {
|
||||||
|
const apiKey = await this.#getApiKeyForBlockActionPayload(payload);
|
||||||
|
|
||||||
if (!parsedPayload.data.message.metadata) {
|
if (!apiKey) {
|
||||||
return;
|
return new Response(null, { status: 200 });
|
||||||
}
|
|
||||||
|
|
||||||
const { requestId } = parsedPayload.data.message.metadata.event_payload;
|
|
||||||
|
|
||||||
const integrationRequest =
|
|
||||||
await this.#prismaClient.integrationRequest.findUnique({
|
|
||||||
where: { id: requestId },
|
|
||||||
include: {
|
|
||||||
run: {
|
|
||||||
include: {
|
|
||||||
environment: true,
|
|
||||||
workflow: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!integrationRequest) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ingestService = new IngestEvent();
|
const ingestService = new IngestEvent();
|
||||||
@@ -64,10 +56,335 @@ export class HandleSlackInteractivity {
|
|||||||
type: "SLACK_INTERACTION",
|
type: "SLACK_INTERACTION",
|
||||||
name: "block.action",
|
name: "block.action",
|
||||||
service: "slack",
|
service: "slack",
|
||||||
payload: parsedPayload.data,
|
payload: payload,
|
||||||
apiKey: integrationRequest.run.environment.apiKey,
|
apiKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return new Response(null, { status: 200 });
|
||||||
|
}
|
||||||
|
|
||||||
|
async #getApiKeyForBlockActionPayload(
|
||||||
|
payload: z.infer<typeof schemas.BlockActionInteractivityPayloadSchema>
|
||||||
|
) {
|
||||||
|
if (payload.message) {
|
||||||
|
if (!payload.message.metadata) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { requestId } = payload.message.metadata.event_payload;
|
||||||
|
|
||||||
|
const integrationRequest =
|
||||||
|
await this.#prismaClient.integrationRequest.findUnique({
|
||||||
|
where: { id: requestId },
|
||||||
|
include: {
|
||||||
|
run: {
|
||||||
|
include: {
|
||||||
|
environment: true,
|
||||||
|
workflow: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!integrationRequest) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return integrationRequest.run.environment.apiKey;
|
||||||
|
} else if (payload.view) {
|
||||||
|
if (typeof payload.view.private_metadata !== "string") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const privateMetadataRaw = safeJsonParse(payload.view.private_metadata);
|
||||||
|
|
||||||
|
if (!privateMetadataRaw) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsedPrivateMetadata =
|
||||||
|
schemas.ViewPrivateMetadataSchema.safeParse(privateMetadataRaw);
|
||||||
|
|
||||||
|
if (!parsedPrivateMetadata.success) {
|
||||||
|
console.error(
|
||||||
|
"Invalid private metadata",
|
||||||
|
generateErrorMessage(parsedPrivateMetadata.error.issues)
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const runId = parsedPrivateMetadata.data.__trigger.runId;
|
||||||
|
|
||||||
|
const run = await this.#prismaClient.workflowRun.findUnique({
|
||||||
|
where: { id: runId },
|
||||||
|
include: {
|
||||||
|
environment: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!run) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return run.environment.apiKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async #handleViewSubmissionInteraction(
|
||||||
|
payload: z.infer<typeof schemas.ViewSubmissionInteractivityPayloadSchema>
|
||||||
|
) {
|
||||||
|
if (typeof payload.view.private_metadata !== "string") {
|
||||||
|
return new Response(null, { status: 200 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const privateMetadataRaw = safeJsonParse(payload.view.private_metadata);
|
||||||
|
|
||||||
|
if (!privateMetadataRaw) {
|
||||||
|
return new Response(null, { status: 200 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsedPrivateMetadata =
|
||||||
|
schemas.ViewPrivateMetadataSchema.safeParse(privateMetadataRaw);
|
||||||
|
|
||||||
|
if (!parsedPrivateMetadata.success) {
|
||||||
|
console.error(
|
||||||
|
"Invalid private metadata",
|
||||||
|
generateErrorMessage(parsedPrivateMetadata.error.issues)
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const runId = parsedPrivateMetadata.data.__trigger.runId;
|
||||||
|
|
||||||
|
const run = await this.#prismaClient.workflowRun.findUnique({
|
||||||
|
where: { id: runId },
|
||||||
|
include: {
|
||||||
|
environment: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!run) {
|
||||||
|
return new Response(null, { status: 200 });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
parsedPrivateMetadata.data.__trigger.validationSchema &&
|
||||||
|
payload.view.state
|
||||||
|
) {
|
||||||
|
const validator = new Validator(
|
||||||
|
parsedPrivateMetadata.data.__trigger.validationSchema,
|
||||||
|
"7",
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
const viewData = prepareValidationData(payload.view.state.values);
|
||||||
|
|
||||||
|
console.log("Validating view submission", {
|
||||||
|
viewData,
|
||||||
|
schema: JSON.stringify(
|
||||||
|
parsedPrivateMetadata.data.__trigger.validationSchema
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = validator.validate(viewData);
|
||||||
|
|
||||||
|
if (!result.valid) {
|
||||||
|
console.log("view submission validation errors", {
|
||||||
|
errors: result.errors,
|
||||||
|
});
|
||||||
|
|
||||||
|
const errors = prepareValidationErrors(result.errors);
|
||||||
|
|
||||||
|
console.log("prepared view submission validation errors", {
|
||||||
|
errors,
|
||||||
|
});
|
||||||
|
|
||||||
|
return json({
|
||||||
|
response_action: "errors",
|
||||||
|
errors,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ingestService = new IngestEvent();
|
||||||
|
|
||||||
|
await ingestService.call({
|
||||||
|
id: payload.view.hash,
|
||||||
|
type: "SLACK_INTERACTION",
|
||||||
|
name: "view.submission",
|
||||||
|
service: "slack",
|
||||||
|
payload: payload,
|
||||||
|
apiKey: run.environment.apiKey,
|
||||||
|
});
|
||||||
|
|
||||||
|
return parsedPrivateMetadata.data.__trigger.onSubmit === "clear"
|
||||||
|
? json({ response_action: "clear" })
|
||||||
|
: parsedPrivateMetadata.data.__trigger.onSubmit === "close"
|
||||||
|
? new Response(null, { status: 200 })
|
||||||
|
: json({ response_action: "none" });
|
||||||
|
}
|
||||||
|
|
||||||
|
async #handleViewClosedInteraction(
|
||||||
|
payload: z.infer<typeof schemas.ViewClosedInteractivityPayloadSchema>
|
||||||
|
) {
|
||||||
|
return new Response(null, { status: 200 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function prepareValidationData(
|
||||||
|
values: Record<string, Record<string, any>>
|
||||||
|
): Record<string, any> {
|
||||||
|
const data: Record<string, any> = {};
|
||||||
|
|
||||||
|
Object.keys(values).forEach((blockId) => {
|
||||||
|
const actionId = Object.keys(values[blockId])[0];
|
||||||
|
|
||||||
|
if (actionId) {
|
||||||
|
const actionData = values[blockId][actionId];
|
||||||
|
|
||||||
|
switch (actionData.type) {
|
||||||
|
case "plain_text_input":
|
||||||
|
data[blockId] = actionData.value;
|
||||||
|
break;
|
||||||
|
case "static_select":
|
||||||
|
data[blockId] = actionData.selected_option.value;
|
||||||
|
break;
|
||||||
|
case "external_select":
|
||||||
|
data[blockId] = actionData.selected_option.value;
|
||||||
|
break;
|
||||||
|
case "users_select":
|
||||||
|
data[blockId] = actionData.selected_user;
|
||||||
|
break;
|
||||||
|
case "conversations_select":
|
||||||
|
data[blockId] = actionData.selected_conversation;
|
||||||
|
break;
|
||||||
|
case "channels_select":
|
||||||
|
data[blockId] = actionData.selected_channel;
|
||||||
|
break;
|
||||||
|
case "overflow":
|
||||||
|
data[blockId] = actionData.selected_option.value;
|
||||||
|
break;
|
||||||
|
case "datepicker":
|
||||||
|
data[blockId] = actionData.selected_date;
|
||||||
|
break;
|
||||||
|
case "datetimepicker":
|
||||||
|
if (typeof actionData.selected_date_time !== "number") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
data[blockId] = new Date(
|
||||||
|
actionData.selected_date_time * 1000
|
||||||
|
).toISOString();
|
||||||
|
|
||||||
|
break;
|
||||||
|
case "timepicker":
|
||||||
|
data[blockId] = actionData.selected_time;
|
||||||
|
break;
|
||||||
|
case "radio_buttons":
|
||||||
|
data[blockId] = actionData.selected_option.value;
|
||||||
|
break;
|
||||||
|
case "checkboxes":
|
||||||
|
data[blockId] = actionData.selected_options.map(
|
||||||
|
(option: any) => option.value
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "multi_static_select":
|
||||||
|
data[blockId] = actionData.selected_options.map(
|
||||||
|
(option: any) => option.value
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "multi_external_select":
|
||||||
|
data[blockId] = actionData.selected_options.map(
|
||||||
|
(option: any) => option.value
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "multi_users_select":
|
||||||
|
data[blockId] = actionData.selected_users;
|
||||||
|
break;
|
||||||
|
case "multi_conversations_select":
|
||||||
|
data[blockId] = actionData.selected_conversations;
|
||||||
|
break;
|
||||||
|
case "multi_channels_select":
|
||||||
|
data[blockId] = actionData.selected_channels;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return removeUndefinedValues(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeUndefinedValues(obj: Record<string, any>) {
|
||||||
|
const newObj: Record<string, any> = {};
|
||||||
|
|
||||||
|
Object.keys(obj).forEach((key) => {
|
||||||
|
if (obj[key]) {
|
||||||
|
newObj[key] = obj[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return newObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
function prepareValidationErrors(
|
||||||
|
outputUnits: OutputUnit[]
|
||||||
|
): Record<string, string> {
|
||||||
|
const errors: Record<string, string> = {};
|
||||||
|
|
||||||
|
outputUnits.forEach((outputUnit) => {
|
||||||
|
if (outputUnit.keyword === "required") {
|
||||||
|
const blockId = parseBlockIdFromRequiredError(outputUnit.error);
|
||||||
|
|
||||||
|
if (blockId) {
|
||||||
|
errors[blockId] = `This field is required`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const blockId = getBlockIdForInstanceLocation(outputUnit.instanceLocation);
|
||||||
|
|
||||||
|
if (!blockId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
errors[blockId] = outputUnit.error;
|
||||||
|
});
|
||||||
|
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If instanceLocation is in the form of #/nameField then we should return nameField, but if it's in the form of #/nameField/0 or #/properties/nameField then we should return undefined
|
||||||
|
function getBlockIdForInstanceLocation(
|
||||||
|
instanceLocation: string
|
||||||
|
): string | undefined {
|
||||||
|
const parts = instanceLocation.split("/");
|
||||||
|
|
||||||
|
if (parts.length === 2) {
|
||||||
|
return parts[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// error will be the string like: Instance does not have required property "issueAtField"
|
||||||
|
// We want to return issueAtField
|
||||||
|
function parseBlockIdFromRequiredError(error: string): string | undefined {
|
||||||
|
const regex = /property "(.*)"/;
|
||||||
|
|
||||||
|
const match = error.match(regex);
|
||||||
|
|
||||||
|
if (match && match[1]) {
|
||||||
|
return match[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function safeJsonParse(json: string): unknown | undefined {
|
||||||
|
try {
|
||||||
|
return JSON.parse(json);
|
||||||
|
} catch (error) {
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.186.0",
|
"@aws-sdk/client-s3": "^3.186.0",
|
||||||
"@aws-sdk/s3-request-presigner": "^3.186.0",
|
"@aws-sdk/s3-request-presigner": "^3.186.0",
|
||||||
"@cfworker/json-schema": "^1.12.4",
|
"@cfworker/json-schema": "^1.12.5",
|
||||||
"@codemirror/autocomplete": "^6.3.1",
|
"@codemirror/autocomplete": "^6.3.1",
|
||||||
"@codemirror/commands": "^6.1.2",
|
"@codemirror/commands": "^6.1.2",
|
||||||
"@codemirror/lang-javascript": "^6.1.1",
|
"@codemirror/lang-javascript": "^6.1.1",
|
||||||
@@ -191,4 +191,4 @@
|
|||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.0.0"
|
"node": ">=16.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -354,7 +354,7 @@ export class TriggerServer {
|
|||||||
topic: Topics.triggers,
|
topic: Topics.triggers,
|
||||||
subscription: `websocketserver-${this.#workflowId}-${this.#apiKey}`,
|
subscription: `websocketserver-${this.#workflowId}-${this.#apiKey}`,
|
||||||
subscriptionType: "Shared",
|
subscriptionType: "Shared",
|
||||||
subscriptionInitialPosition: "Earliest",
|
subscriptionInitialPosition: "Latest",
|
||||||
},
|
},
|
||||||
handlers: {
|
handlers: {
|
||||||
TRIGGER_WORKFLOW: async (id, data, properties, messageAttributes) => {
|
TRIGGER_WORKFLOW: async (id, data, properties, messageAttributes) => {
|
||||||
|
|||||||
@@ -218,7 +218,7 @@ new Trigger({
|
|||||||
return slack.addReaction("React to message", {
|
return slack.addReaction("React to message", {
|
||||||
name: "cry",
|
name: "cry",
|
||||||
timestamp: event.message.ts,
|
timestamp: event.message.ts,
|
||||||
channelId: event.channel.id,
|
channelId: event.channel!.id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -229,7 +229,7 @@ new Trigger({
|
|||||||
return slack.addReaction("React to message", {
|
return slack.addReaction("React to message", {
|
||||||
name: "sos",
|
name: "sos",
|
||||||
timestamp: event.message.ts,
|
timestamp: event.message.ts,
|
||||||
channelId: event.channel.id,
|
channelId: event.channel!.id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -243,7 +243,7 @@ new Trigger({
|
|||||||
//only the user pressing the button will see this message
|
//only the user pressing the button will see this message
|
||||||
return slack.postMessageResponse(
|
return slack.postMessageResponse(
|
||||||
"Added a comment to the issue",
|
"Added a comment to the issue",
|
||||||
event.response_url,
|
event.response_url!,
|
||||||
{
|
{
|
||||||
text: `You rated your day ${action.selected_option?.value} stars`,
|
text: `You rated your day ${action.selected_option?.value} stars`,
|
||||||
replace_original: false,
|
replace_original: false,
|
||||||
@@ -277,7 +277,7 @@ new Trigger({
|
|||||||
await slack.addReaction("React to message", {
|
await slack.addReaction("React to message", {
|
||||||
name: "thumbsup",
|
name: "thumbsup",
|
||||||
timestamp: event.message.ts,
|
timestamp: event.message.ts,
|
||||||
channelId: event.channel.id,
|
channelId: event.channel!.id,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
}).listen();
|
}).listen();
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"private": true,
|
||||||
|
"name": "@examples/slack-modals",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "Open a modal window in Slack and do something with the response",
|
||||||
|
"dependencies": {
|
||||||
|
"@trigger.dev/slack": "workspace:*",
|
||||||
|
"@trigger.dev/sdk": "workspace:*",
|
||||||
|
"jsx-slack": "^5.3.0",
|
||||||
|
"zod": "^3.20.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@trigger.dev/tsconfig": "workspace:*",
|
||||||
|
"@types/node": "16",
|
||||||
|
"tsx": "^3.12.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"dev": "tsx src/index.tsx"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,860 @@
|
|||||||
|
import { customEvent, Trigger } from "@trigger.dev/sdk";
|
||||||
|
import * as slack from "@trigger.dev/slack";
|
||||||
|
import JSXSlack, {
|
||||||
|
Actions,
|
||||||
|
Blocks,
|
||||||
|
Button,
|
||||||
|
Checkbox,
|
||||||
|
CheckboxGroup,
|
||||||
|
DatePicker,
|
||||||
|
DateTimePicker,
|
||||||
|
Divider,
|
||||||
|
Input,
|
||||||
|
Modal,
|
||||||
|
RadioButton,
|
||||||
|
RadioButtonGroup,
|
||||||
|
Section,
|
||||||
|
Select,
|
||||||
|
Option,
|
||||||
|
Textarea,
|
||||||
|
TimePicker,
|
||||||
|
Context,
|
||||||
|
Image,
|
||||||
|
Field,
|
||||||
|
Header,
|
||||||
|
Overflow,
|
||||||
|
OverflowItem,
|
||||||
|
} from "jsx-slack";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
const IssueBlockID = "issue.action";
|
||||||
|
|
||||||
|
new Trigger({
|
||||||
|
id: "slack-modals",
|
||||||
|
name: "Initial Slack Modal Flow",
|
||||||
|
apiKey: "trigger_development_GJE9dEaqhqes",
|
||||||
|
endpoint: "ws://localhost:8889/ws",
|
||||||
|
logLevel: "debug",
|
||||||
|
on: customEvent({ name: "slack.modal.initiate", schema: z.any() }),
|
||||||
|
run: async (event, ctx) => {
|
||||||
|
await slack.postMessage("jsx-test", {
|
||||||
|
channelName: "test-integrations",
|
||||||
|
//text appears in Slack notifications on mobile/desktop
|
||||||
|
text: "New github issue",
|
||||||
|
//import and use JSXSlack to make creating rich messages much easier
|
||||||
|
blocks: JSXSlack(
|
||||||
|
<Blocks>
|
||||||
|
<Section>New GitHub Issue, would you like to reply?</Section>
|
||||||
|
<Actions blockId={IssueBlockID}>
|
||||||
|
<Button value="issue_1234" actionId="reply-to-issue">
|
||||||
|
Reply
|
||||||
|
</Button>
|
||||||
|
<Button value="issue_1234" actionId="close-issue">
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
</Actions>
|
||||||
|
</Blocks>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}).listen();
|
||||||
|
|
||||||
|
new Trigger({
|
||||||
|
id: "slack-modals-initiate-reply",
|
||||||
|
name: "Slack Modals Initiate Reply",
|
||||||
|
apiKey: "trigger_development_GJE9dEaqhqes",
|
||||||
|
endpoint: "ws://localhost:8889/ws",
|
||||||
|
logLevel: "debug",
|
||||||
|
on: slack.events.blockActionInteraction({
|
||||||
|
blockId: IssueBlockID,
|
||||||
|
actionId: ["reply-to-issue", "close-issue"],
|
||||||
|
}),
|
||||||
|
run: async (event, ctx) => {
|
||||||
|
//create promises from all the actions
|
||||||
|
const promises = event.actions.map(async (action) => {
|
||||||
|
switch (action.action_id) {
|
||||||
|
case "reply-to-issue": {
|
||||||
|
// Use the trigger_id to open a modal
|
||||||
|
await ctx.logger.info(`Replying to issue ${action.action_ts}`, {
|
||||||
|
action,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (event.trigger_id) {
|
||||||
|
const response = await slack.openView(
|
||||||
|
`Opening modal for ${action.action_ts}`,
|
||||||
|
event.trigger_id,
|
||||||
|
JSXSlack(
|
||||||
|
<Modal
|
||||||
|
title="My first modal"
|
||||||
|
close="Cancel"
|
||||||
|
callbackId="reply-to-issue-modal"
|
||||||
|
>
|
||||||
|
<Section>
|
||||||
|
<p>
|
||||||
|
<strong>It's my first modal!</strong> :sunglasses:
|
||||||
|
</p>
|
||||||
|
<p>jsx-slack also has supported Slack Modals.</p>
|
||||||
|
</Section>
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<Actions id="view-interaction">
|
||||||
|
<Button value="push" actionId="push">
|
||||||
|
Push View
|
||||||
|
</Button>
|
||||||
|
<Button value="update" actionId="update">
|
||||||
|
Update view
|
||||||
|
</Button>
|
||||||
|
</Actions>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
name="name"
|
||||||
|
label="Name"
|
||||||
|
maxLength={50}
|
||||||
|
id="nameField"
|
||||||
|
placeholder="Your name"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Textarea
|
||||||
|
name="message"
|
||||||
|
label="Message"
|
||||||
|
placeholder="Your message"
|
||||||
|
maxLength={500}
|
||||||
|
id="messageField"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<DatePicker
|
||||||
|
name="closeAt"
|
||||||
|
label="Close At"
|
||||||
|
id="closeAtField"
|
||||||
|
initialDate={new Date(Date.now() + 1000 * 60 * 60 * 24)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TimePicker
|
||||||
|
name="remindMeAtTime"
|
||||||
|
label="Remind me at"
|
||||||
|
id="remindMeAtTimeField"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<DateTimePicker
|
||||||
|
name="issueAt"
|
||||||
|
label="Issue At"
|
||||||
|
id="issueAtField"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Input type="hidden" name="postId" value="xxxx" />
|
||||||
|
<Input type="submit" value="Send" />
|
||||||
|
</Modal>
|
||||||
|
),
|
||||||
|
{
|
||||||
|
validationSchema: z.object({
|
||||||
|
nameField: z.string().min(3),
|
||||||
|
issueAtField: z.string().datetime(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await ctx.logger.info("Modal response", { response });
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "close-issue": {
|
||||||
|
// Use the trigger_id to open a modal
|
||||||
|
await ctx.logger.info(`Closing issue ${action.action_ts}`, {
|
||||||
|
action,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all(promises);
|
||||||
|
},
|
||||||
|
}).listen();
|
||||||
|
|
||||||
|
new Trigger({
|
||||||
|
id: "slack-modals-block-actions-in-view",
|
||||||
|
name: "Slack Modals Block Actions in View",
|
||||||
|
apiKey: "trigger_development_GJE9dEaqhqes",
|
||||||
|
endpoint: "ws://localhost:8889/ws",
|
||||||
|
logLevel: "debug",
|
||||||
|
on: slack.events.blockActionInteraction({
|
||||||
|
blockId: "view-interaction",
|
||||||
|
}),
|
||||||
|
run: async (event, ctx) => {
|
||||||
|
const action = event.actions[0];
|
||||||
|
|
||||||
|
await ctx.logger.info("View interaction", { action });
|
||||||
|
|
||||||
|
if (!event.trigger_id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We should be able to update the view, or push a new modal
|
||||||
|
|
||||||
|
if (action.action_id === "push") {
|
||||||
|
await slack.pushView(
|
||||||
|
"Pushing view",
|
||||||
|
event.trigger_id,
|
||||||
|
JSXSlack(
|
||||||
|
<Modal
|
||||||
|
title="My pushed modal"
|
||||||
|
close="Cancel"
|
||||||
|
callbackId="reply-to-issue-modal3"
|
||||||
|
>
|
||||||
|
<Section>
|
||||||
|
<p>
|
||||||
|
<strong>This is an pushed model!</strong>
|
||||||
|
</p>
|
||||||
|
</Section>
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<CheckboxGroup
|
||||||
|
id="anotherField"
|
||||||
|
name="anotherInput"
|
||||||
|
label="Should we actually close this view"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<Checkbox value="yes">Yes Please :hamburger:</Checkbox>
|
||||||
|
<Checkbox value="no">No keep it going :pizza:</Checkbox>
|
||||||
|
</CheckboxGroup>
|
||||||
|
|
||||||
|
<Input type="submit" value="Send" />
|
||||||
|
</Modal>
|
||||||
|
),
|
||||||
|
{
|
||||||
|
onSubmit: "close",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else if (event.view) {
|
||||||
|
await slack.updateView(
|
||||||
|
"Updating view",
|
||||||
|
event.view,
|
||||||
|
JSXSlack(
|
||||||
|
<Modal
|
||||||
|
title="My first modal"
|
||||||
|
close="Cancel"
|
||||||
|
callbackId="reply-to-issue-modal2"
|
||||||
|
>
|
||||||
|
<Section>
|
||||||
|
<p>
|
||||||
|
<strong>This is an updated model!</strong>
|
||||||
|
</p>
|
||||||
|
</Section>
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<CheckboxGroup
|
||||||
|
id="foodsField"
|
||||||
|
name="foods"
|
||||||
|
label="What do you want to eat for the party in this Friday?"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<Checkbox value="burger">Burger :hamburger:</Checkbox>
|
||||||
|
<Checkbox value="pizza">Pizza :pizza:</Checkbox>
|
||||||
|
<Checkbox value="taco">Tex-Mex taco :taco:</Checkbox>
|
||||||
|
<Checkbox value="sushi">Sushi :sushi:</Checkbox>
|
||||||
|
</CheckboxGroup>
|
||||||
|
|
||||||
|
<Input type="submit" value="Send" />
|
||||||
|
</Modal>
|
||||||
|
),
|
||||||
|
{
|
||||||
|
onSubmit: "clear",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}).listen();
|
||||||
|
|
||||||
|
new Trigger({
|
||||||
|
id: "slack-modals-handle-reply-update",
|
||||||
|
name: "Slack Modals Handle Reply Update",
|
||||||
|
apiKey: "trigger_development_GJE9dEaqhqes",
|
||||||
|
endpoint: "ws://localhost:8889/ws",
|
||||||
|
logLevel: "debug",
|
||||||
|
on: slack.events.viewSubmissionInteraction({
|
||||||
|
callbackId: "reply-to-issue-modal2",
|
||||||
|
}),
|
||||||
|
run: async (event, ctx) => {
|
||||||
|
await ctx.logger.info("Modal submission", { event });
|
||||||
|
|
||||||
|
return event;
|
||||||
|
},
|
||||||
|
}).listen();
|
||||||
|
|
||||||
|
new Trigger({
|
||||||
|
id: "send-slack-modal-catalog-message",
|
||||||
|
name: "Send Slack Modal Catalog Message",
|
||||||
|
apiKey: "trigger_development_GJE9dEaqhqes",
|
||||||
|
endpoint: "ws://localhost:8889/ws",
|
||||||
|
logLevel: "debug",
|
||||||
|
on: customEvent({
|
||||||
|
name: "slack.modal.catalog",
|
||||||
|
schema: z.any(),
|
||||||
|
}),
|
||||||
|
run: async (event, ctx) => {
|
||||||
|
await slack.postMessage("Send Modal Catalog Message", {
|
||||||
|
channelName: "test-integrations",
|
||||||
|
text: "Select a modal to open",
|
||||||
|
blocks: JSXSlack(
|
||||||
|
<Blocks>
|
||||||
|
<Section>Which modal would you like to test?</Section>
|
||||||
|
<Actions blockId="modal-catalog">
|
||||||
|
<Button value="poll" actionId="poll">
|
||||||
|
Poll
|
||||||
|
</Button>
|
||||||
|
<Button value="searchResults" actionId="searchResults">
|
||||||
|
Search Results
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button value="appMenu" actionId="appMenu">
|
||||||
|
App Menu (Settings)
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
value="notificationSettings"
|
||||||
|
actionId="notificationSettings"
|
||||||
|
>
|
||||||
|
Notification Settings
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button value="yourItinerary" actionId="yourItinerary">
|
||||||
|
Your Itinerary
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button value="ticketApp" actionId="ticketApp">
|
||||||
|
Ticket App
|
||||||
|
</Button>
|
||||||
|
</Actions>
|
||||||
|
</Blocks>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}).listen();
|
||||||
|
|
||||||
|
new Trigger({
|
||||||
|
id: "slack-modal-catalog-block-interaction",
|
||||||
|
name: "Slack Modal Catalog Handle Block Interaction",
|
||||||
|
apiKey: "trigger_development_GJE9dEaqhqes",
|
||||||
|
endpoint: "ws://localhost:8889/ws",
|
||||||
|
logLevel: "debug",
|
||||||
|
on: slack.events.blockActionInteraction({
|
||||||
|
blockId: "modal-catalog",
|
||||||
|
}),
|
||||||
|
run: async (event, ctx) => {
|
||||||
|
if (!event.trigger_id) {
|
||||||
|
await ctx.logger.error("No trigger_id", { event });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const action = event.actions[0];
|
||||||
|
|
||||||
|
switch (action.action_id) {
|
||||||
|
case "poll": {
|
||||||
|
await slack.openView("Opening view", event.trigger_id, PollModal, {
|
||||||
|
onSubmit: "close",
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "searchResults": {
|
||||||
|
await slack.openView(
|
||||||
|
"Opening view",
|
||||||
|
event.trigger_id,
|
||||||
|
SearchResultsModal,
|
||||||
|
{
|
||||||
|
onSubmit: "close",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "appMenu": {
|
||||||
|
await slack.openView("Opening view", event.trigger_id, AppMenuModal, {
|
||||||
|
onSubmit: "close",
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "notificationSettings": {
|
||||||
|
await slack.openView(
|
||||||
|
"Opening view",
|
||||||
|
event.trigger_id,
|
||||||
|
NotificationSettingsModal,
|
||||||
|
{
|
||||||
|
onSubmit: "close",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "yourItinerary": {
|
||||||
|
await slack.openView(
|
||||||
|
"Opening view",
|
||||||
|
event.trigger_id,
|
||||||
|
YourItineraryModal,
|
||||||
|
{
|
||||||
|
onSubmit: "close",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "ticketApp": {
|
||||||
|
await slack.openView("Opening view", event.trigger_id, TicketAppModal, {
|
||||||
|
onSubmit: "close",
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}).listen();
|
||||||
|
|
||||||
|
new Trigger({
|
||||||
|
id: "slack-modals-handle-catalog-submission",
|
||||||
|
name: "Slack Modals Handle Catalog Submission",
|
||||||
|
apiKey: "trigger_development_GJE9dEaqhqes",
|
||||||
|
endpoint: "ws://localhost:8889/ws",
|
||||||
|
logLevel: "debug",
|
||||||
|
on: slack.events.viewSubmissionInteraction({
|
||||||
|
callbackId: "modal-catalog-submission",
|
||||||
|
}),
|
||||||
|
run: async (event, ctx) => {
|
||||||
|
await ctx.logger.info("Modal submission", { event });
|
||||||
|
|
||||||
|
return event;
|
||||||
|
},
|
||||||
|
}).listen();
|
||||||
|
|
||||||
|
const PollModal = JSXSlack(
|
||||||
|
<Modal
|
||||||
|
title="Workplace check-in"
|
||||||
|
close="Cancel"
|
||||||
|
callbackId="modal-catalog-submission"
|
||||||
|
>
|
||||||
|
<Section>
|
||||||
|
<p>:wave: Hey David!</p>
|
||||||
|
<p>
|
||||||
|
We'd love to hear from you how we can make this place the best place
|
||||||
|
you’ve ever worked.
|
||||||
|
</p>
|
||||||
|
</Section>
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<RadioButtonGroup label="You enjoy working here at Pistachio & Co" required>
|
||||||
|
<RadioButton value="1">Strongly agree</RadioButton>
|
||||||
|
<RadioButton value="2">Agree</RadioButton>
|
||||||
|
<RadioButton value="3">Neither agree nor disagree</RadioButton>
|
||||||
|
<RadioButton value="4">Disagree</RadioButton>
|
||||||
|
<RadioButton value="5">Strongly disagree</RadioButton>
|
||||||
|
</RadioButtonGroup>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
label="What do you want for our team weekly lunch?"
|
||||||
|
placeholder="Select your favorites"
|
||||||
|
multiple
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<Option value="value-0">:pizza: Pizza</Option>
|
||||||
|
<Option value="value-1">:fried_shrimp: Thai food</Option>
|
||||||
|
<Option value="value-2">:desert_island: Hawaiian</Option>
|
||||||
|
<Option value="value-3">:meat_on_bone: Texas BBQ</Option>
|
||||||
|
<Option value="value-4">:hamburger: Burger</Option>
|
||||||
|
<Option value="value-5">:taco: Tacos</Option>
|
||||||
|
<Option value="value-6">:green_salad: Salad</Option>
|
||||||
|
<Option value="value-7">:stew: Indian</Option>
|
||||||
|
</Select>
|
||||||
|
|
||||||
|
<Textarea
|
||||||
|
label="What can we do to improve your experience working here?"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<Textarea label="Anything else you want to tell us?" />
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
|
||||||
|
const SearchResultsModal = JSXSlack(
|
||||||
|
<Modal
|
||||||
|
title="Your accommodation"
|
||||||
|
close="Cancel"
|
||||||
|
callbackId="modal-catalog-submission"
|
||||||
|
>
|
||||||
|
<Section>
|
||||||
|
Please choose an option where you'd like to stay from Oct 21 - Oct 23 (2
|
||||||
|
nights).
|
||||||
|
</Section>
|
||||||
|
<Divider />
|
||||||
|
<Section>
|
||||||
|
<b>Airstream Suite</b>
|
||||||
|
<br />
|
||||||
|
<b>Share with another person</b>. Private walk-in bathroom. TV. Heating.
|
||||||
|
Kitchen with microwave, basic cooking utensils, wine glasses and
|
||||||
|
silverware.
|
||||||
|
<Image
|
||||||
|
src="https://api.slack.com/img/blocks/bkb_template_images/Streamline-Beach.png"
|
||||||
|
alt="Airstream Suite"
|
||||||
|
/>
|
||||||
|
</Section>
|
||||||
|
<Context>
|
||||||
|
1x Queen Bed
|
||||||
|
<span>|</span>
|
||||||
|
$220 / night
|
||||||
|
</Context>
|
||||||
|
<Actions>
|
||||||
|
<Button value="click_me_123">Choose</Button>
|
||||||
|
<Button value="click_me_123">View Details</Button>
|
||||||
|
</Actions>
|
||||||
|
<Divider />
|
||||||
|
<Section>
|
||||||
|
<b>Redwood Suite</b>
|
||||||
|
<br />
|
||||||
|
<b>Share with 2 other person</b>. Studio home. Modern bathroom. TV.
|
||||||
|
Heating. Full kitchen. Patio with lounge chairs and campfire style fire
|
||||||
|
pit and grill.
|
||||||
|
<Image
|
||||||
|
src="https://api.slack.com/img/blocks/bkb_template_images/redwoodcabin.png"
|
||||||
|
alt="Redwood Suite"
|
||||||
|
/>
|
||||||
|
</Section>
|
||||||
|
<Context>
|
||||||
|
1x King Bed
|
||||||
|
<span>|</span>
|
||||||
|
$350 / night
|
||||||
|
</Context>
|
||||||
|
<Actions>
|
||||||
|
<Button value="click_me_123" style="primary">
|
||||||
|
✓ Your Choice
|
||||||
|
</Button>
|
||||||
|
<Button value="click_me_123">View Details</Button>
|
||||||
|
</Actions>
|
||||||
|
<Divider />
|
||||||
|
<Section>
|
||||||
|
<b>Luxury Tent</b>
|
||||||
|
<br />
|
||||||
|
<b>One person only</b>. Shared modern bathrooms and showers in lounge
|
||||||
|
building. Temperature control with heated blankets. Lights and electrical
|
||||||
|
outlets.
|
||||||
|
<Image
|
||||||
|
src="https://api.slack.com/img/blocks/bkb_template_images/tent.png"
|
||||||
|
alt="Redwood Suite"
|
||||||
|
/>
|
||||||
|
</Section>
|
||||||
|
<Context>
|
||||||
|
1x Queen Bed
|
||||||
|
<span>|</span>
|
||||||
|
$260 / night
|
||||||
|
</Context>
|
||||||
|
<Actions>
|
||||||
|
<Button value="click_me_123">Choose</Button>
|
||||||
|
<Button value="click_me_123">View Details</Button>
|
||||||
|
</Actions>
|
||||||
|
<Divider />
|
||||||
|
<Input type="submit" value="Submit" />
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
|
||||||
|
const AppMenuModal = JSXSlack(
|
||||||
|
<Modal title="App menu" close="Cancel">
|
||||||
|
<Section>
|
||||||
|
<b>
|
||||||
|
Hi <a href="fakelink.toUser.com">@David</a>!
|
||||||
|
</b>{" "}
|
||||||
|
Here's how I can help you:
|
||||||
|
</Section>
|
||||||
|
<Divider />
|
||||||
|
<Section>
|
||||||
|
:calendar: <b>Create event</b>
|
||||||
|
<br />
|
||||||
|
Create a new event
|
||||||
|
<Button value="click_me_123" style="primary">
|
||||||
|
Create event
|
||||||
|
</Button>
|
||||||
|
</Section>
|
||||||
|
<Section>
|
||||||
|
:clipboard: <b>List of events</b>
|
||||||
|
<br />
|
||||||
|
Choose from different event lists
|
||||||
|
<Select placeholder="Choose list">
|
||||||
|
<Option value="value-0">My events</Option>
|
||||||
|
<Option value="value-1">All events</Option>
|
||||||
|
<Option value="value-2">Event invites</Option>
|
||||||
|
</Select>
|
||||||
|
</Section>
|
||||||
|
<Section>
|
||||||
|
:gear: <b>Settings</b>
|
||||||
|
<br />
|
||||||
|
Manage your notifications and team settings
|
||||||
|
<Select placeholder="Edit settings">
|
||||||
|
<Option value="value-0">Notifications</Option>
|
||||||
|
<Option value="value-1">Team settings</Option>
|
||||||
|
</Select>
|
||||||
|
</Section>
|
||||||
|
<Actions>
|
||||||
|
<Button value="click_me_123">Send feedback</Button>
|
||||||
|
<Button value="click_me_123">FAQs</Button>
|
||||||
|
</Actions>
|
||||||
|
<Input type="submit" value="Submit" />
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
|
||||||
|
const NotificationSettingsModal = JSXSlack(
|
||||||
|
<Modal title="Notification settings" close="Cancel">
|
||||||
|
<Section>
|
||||||
|
<p>
|
||||||
|
<b>
|
||||||
|
<a href="fakelink.toUrl.com">PR Strategy 2019</a> posts into{" "}
|
||||||
|
<a href="fakelink.toChannel.com">#public-relations</a>
|
||||||
|
</b>
|
||||||
|
</p>
|
||||||
|
<p>Select which notifications to send:</p>
|
||||||
|
</Section>
|
||||||
|
<Actions>
|
||||||
|
<CheckboxGroup>
|
||||||
|
<Checkbox value="tasks">
|
||||||
|
New tasks
|
||||||
|
<small>When new tasks are added to project</small>
|
||||||
|
</Checkbox>
|
||||||
|
<Checkbox value="comments">
|
||||||
|
New comments
|
||||||
|
<small>When new comments are added</small>
|
||||||
|
</Checkbox>
|
||||||
|
<Checkbox value="updates">
|
||||||
|
Project updates
|
||||||
|
<small>When project is updated</small>
|
||||||
|
</Checkbox>
|
||||||
|
</CheckboxGroup>
|
||||||
|
</Actions>
|
||||||
|
<Input type="submit" value="Submit" />
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
|
||||||
|
const YourItineraryModal = JSXSlack(
|
||||||
|
<Modal title="Your itinerary" close="Cancel">
|
||||||
|
<Header>:tada: You're all set! This is your booking summary.</Header>
|
||||||
|
<Divider />
|
||||||
|
<Section>
|
||||||
|
<Field>
|
||||||
|
<b>Attendee</b>
|
||||||
|
<br />
|
||||||
|
Katie Chen
|
||||||
|
</Field>
|
||||||
|
<Field>
|
||||||
|
<b>Date</b>
|
||||||
|
<br />
|
||||||
|
Oct 22-23
|
||||||
|
</Field>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Context>:house: Accommodation</Context>
|
||||||
|
<Divider />
|
||||||
|
<Section>
|
||||||
|
<b>Redwood Suite</b>
|
||||||
|
<br />
|
||||||
|
<b>Share with 2 other person</b>. Studio home. Modern bathroom. TV.
|
||||||
|
Heating. Full kitchen. Patio with lounge chairs and campfire style fire
|
||||||
|
pit and grill.
|
||||||
|
<Image
|
||||||
|
src="https://api.slack.com/img/blocks/bkb_template_images/redwood-suite.png"
|
||||||
|
alt="Redwood Suite"
|
||||||
|
/>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Context>:fork_and_knife: Food & Dietary restrictions</Context>
|
||||||
|
<Divider />
|
||||||
|
<Section>
|
||||||
|
<b>All-rounder</b>
|
||||||
|
<br />
|
||||||
|
You eat most meats, seafood, dairy and vegetables.
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Context>:woman-running: Activities</Context>
|
||||||
|
<Divider />
|
||||||
|
<Section>
|
||||||
|
<b>Winery tour and tasting</b>
|
||||||
|
<Field>Wednesday, Oct 22 2019, 2pm-5pm</Field>
|
||||||
|
<Field>Hosted by Sandra Mullens</Field>
|
||||||
|
</Section>
|
||||||
|
<Section>
|
||||||
|
<b>Sunrise hike to Mount Amazing</b>
|
||||||
|
<Field>Thursday, Oct 23 2019, 5:30am</Field>
|
||||||
|
<Field>Hosted by Jordan Smith</Field>
|
||||||
|
</Section>
|
||||||
|
<Section>
|
||||||
|
<b>Design systems brainstorm</b>
|
||||||
|
<Field>Thursday, Oct 23 2019, 11a</Field>
|
||||||
|
<Field>Hosted by Mary Lee</Field>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Input type="submit" value="Submit" />
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
|
||||||
|
const TicketAppModal = JSXSlack(
|
||||||
|
<Modal title="Ticket app" close="Cancel">
|
||||||
|
<Section>
|
||||||
|
Pick a ticket list from the dropdown
|
||||||
|
<Select placeholder="Select an item">
|
||||||
|
<Option value="all_tickets">All Tickets</Option>
|
||||||
|
<Option value="assigned_to_me" selected>
|
||||||
|
Assigned To Me
|
||||||
|
</Option>
|
||||||
|
<Option value="issued_by_me">Issued By Me</Option>
|
||||||
|
</Select>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
<Context>
|
||||||
|
<Image
|
||||||
|
src="https://api.slack.com/img/blocks/bkb_template_images/highpriority.png"
|
||||||
|
alt="High Priority"
|
||||||
|
/>
|
||||||
|
<b>High Priority</b>
|
||||||
|
</Context>
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<Section>
|
||||||
|
<b>
|
||||||
|
<a href="fakelink.com">WEB-1098 Adjust borders on homepage graphic</a>
|
||||||
|
</b>
|
||||||
|
<Overflow>
|
||||||
|
<OverflowItem value="done">
|
||||||
|
:white_check_mark: Mark as done
|
||||||
|
</OverflowItem>
|
||||||
|
<OverflowItem value="edit">:pencil: Edit</OverflowItem>
|
||||||
|
<OverflowItem value="delete">:x: Delete</OverflowItem>
|
||||||
|
</Overflow>
|
||||||
|
</Section>
|
||||||
|
<Context>
|
||||||
|
Awaiting Release
|
||||||
|
<Image
|
||||||
|
src="https://api.slack.com/img/blocks/bkb_template_images/task-icon.png"
|
||||||
|
alt="Task Icon"
|
||||||
|
/>{" "}
|
||||||
|
Task
|
||||||
|
<Image
|
||||||
|
src="https://api.slack.com/img/blocks/bkb_template_images/profile_1.png"
|
||||||
|
alt="Michael Scott"
|
||||||
|
/>{" "}
|
||||||
|
<a href="fakelink.toUser.com">Michael Scott</a>
|
||||||
|
</Context>
|
||||||
|
|
||||||
|
<Section>
|
||||||
|
<b>
|
||||||
|
<a href="fakelink.com">
|
||||||
|
MOB-2011 Deep-link from web search results to product page
|
||||||
|
</a>
|
||||||
|
</b>
|
||||||
|
<Overflow>
|
||||||
|
<OverflowItem value="done">
|
||||||
|
:white_check_mark: Mark as done
|
||||||
|
</OverflowItem>
|
||||||
|
<OverflowItem value="edit">:pencil: Edit</OverflowItem>
|
||||||
|
<OverflowItem value="delete">:x: Delete</OverflowItem>
|
||||||
|
</Overflow>
|
||||||
|
</Section>
|
||||||
|
<Context>
|
||||||
|
Open
|
||||||
|
<Image
|
||||||
|
src="https://api.slack.com/img/blocks/bkb_template_images/newfeature.png"
|
||||||
|
alt="New Feature Icon"
|
||||||
|
/>{" "}
|
||||||
|
New Feature
|
||||||
|
<Image
|
||||||
|
src="https://api.slack.com/img/blocks/bkb_template_images/profile_2.png"
|
||||||
|
alt="Pam Beasely"
|
||||||
|
/>{" "}
|
||||||
|
<a href="fakelink.toUser.com">Pam Beasely</a>
|
||||||
|
</Context>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
<Context>
|
||||||
|
<Image
|
||||||
|
src="https://api.slack.com/img/blocks/bkb_template_images/mediumpriority.png"
|
||||||
|
alt="palm tree"
|
||||||
|
/>
|
||||||
|
<b>Medium Priority</b>
|
||||||
|
</Context>
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<Section>
|
||||||
|
<b>
|
||||||
|
<a href="fakelink.com">WEB-1098 Adjust borders on homepage graphic</a>
|
||||||
|
</b>
|
||||||
|
<Overflow>
|
||||||
|
<OverflowItem value="done">
|
||||||
|
:white_check_mark: Mark as done
|
||||||
|
</OverflowItem>
|
||||||
|
<OverflowItem value="edit">:pencil: Edit</OverflowItem>
|
||||||
|
<OverflowItem value="delete">:x: Delete</OverflowItem>
|
||||||
|
</Overflow>
|
||||||
|
</Section>
|
||||||
|
<Context>
|
||||||
|
Awaiting Release
|
||||||
|
<Image
|
||||||
|
src="https://api.slack.com/img/blocks/bkb_template_images/task-icon.png"
|
||||||
|
alt="Task Icon"
|
||||||
|
/>{" "}
|
||||||
|
Task
|
||||||
|
<Image
|
||||||
|
src="https://api.slack.com/img/blocks/bkb_template_images/profile_1.png"
|
||||||
|
alt="Michael Scott"
|
||||||
|
/>{" "}
|
||||||
|
<a href="fakelink.toUser.com">Michael Scott</a>
|
||||||
|
</Context>
|
||||||
|
|
||||||
|
<Section>
|
||||||
|
<b>
|
||||||
|
<a href="fakelink.com">
|
||||||
|
MOB-2011 Deep-link from web search results to product page
|
||||||
|
</a>
|
||||||
|
</b>
|
||||||
|
<Overflow>
|
||||||
|
<OverflowItem value="done">
|
||||||
|
:white_check_mark: Mark as done
|
||||||
|
</OverflowItem>
|
||||||
|
<OverflowItem value="edit">:pencil: Edit</OverflowItem>
|
||||||
|
<OverflowItem value="delete">:x: Delete</OverflowItem>
|
||||||
|
</Overflow>
|
||||||
|
</Section>
|
||||||
|
<Context>
|
||||||
|
Open
|
||||||
|
<Image
|
||||||
|
src="https://api.slack.com/img/blocks/bkb_template_images/newfeature.png"
|
||||||
|
alt="New Feature Icon"
|
||||||
|
/>{" "}
|
||||||
|
New Feature
|
||||||
|
<Image
|
||||||
|
src="https://api.slack.com/img/blocks/bkb_template_images/profile_2.png"
|
||||||
|
alt="Pam Beasely"
|
||||||
|
/>{" "}
|
||||||
|
<a href="fakelink.toUser.com">Pam Beasely</a>
|
||||||
|
</Context>
|
||||||
|
|
||||||
|
<Section>
|
||||||
|
<b>
|
||||||
|
<a href="fakelink.com">WEB-1098 Adjust borders on homepage graphic</a>
|
||||||
|
</b>
|
||||||
|
<Overflow>
|
||||||
|
<OverflowItem value="done">
|
||||||
|
:white_check_mark: Mark as done
|
||||||
|
</OverflowItem>
|
||||||
|
<OverflowItem value="edit">:pencil: Edit</OverflowItem>
|
||||||
|
<OverflowItem value="delete">:x: Delete</OverflowItem>
|
||||||
|
</Overflow>
|
||||||
|
</Section>
|
||||||
|
<Context>
|
||||||
|
Awaiting Release
|
||||||
|
<Image
|
||||||
|
src="https://api.slack.com/img/blocks/bkb_template_images/task-icon.png"
|
||||||
|
alt="Task Icon"
|
||||||
|
/>{" "}
|
||||||
|
Task
|
||||||
|
<Image
|
||||||
|
src="https://api.slack.com/img/blocks/bkb_template_images/profile_1.png"
|
||||||
|
alt="Michael Scott"
|
||||||
|
/>{" "}
|
||||||
|
<a href="fakelink.toUser.com">Michael Scott</a>
|
||||||
|
</Context>
|
||||||
|
|
||||||
|
<Input type="submit" value="Submit" />
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"extends": "@trigger.dev/tsconfig/examples.json",
|
||||||
|
"include": ["src/**/*.ts", "src/**/*.tsx"],
|
||||||
|
"exclude": ["node_modules", "**/*.test.*"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"jsx": "react-jsx", // or "react-jsxdev" for development
|
||||||
|
"jsxImportSource": "jsx-slack"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,6 +31,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"zod": "^3.20.2"
|
"zod": "^3.20.2",
|
||||||
|
"zod-to-json-schema": "^3.20.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -22,14 +22,18 @@ export const mrkdwnOptionSchema = z.object({
|
|||||||
text: mrkdwnElementSchema,
|
text: mrkdwnElementSchema,
|
||||||
value: z.string().optional(),
|
value: z.string().optional(),
|
||||||
url: z.string().optional(),
|
url: z.string().optional(),
|
||||||
description: plainTextElementSchema.optional(),
|
description: z
|
||||||
|
.discriminatedUnion("type", [mrkdwnElementSchema, plainTextElementSchema])
|
||||||
|
.optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const plainTextOptionSchema = z.object({
|
export const plainTextOptionSchema = z.object({
|
||||||
text: plainTextElementSchema,
|
text: plainTextElementSchema,
|
||||||
value: z.string().optional(),
|
value: z.string().optional(),
|
||||||
url: z.string().optional(),
|
url: z.string().optional(),
|
||||||
description: plainTextElementSchema.optional(),
|
description: z
|
||||||
|
.discriminatedUnion("type", [mrkdwnElementSchema, plainTextElementSchema])
|
||||||
|
.optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const optionSchema = z.union([
|
export const optionSchema = z.union([
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import type { TriggerEvent } from "@trigger.dev/sdk";
|
import type { TriggerEvent } from "@trigger.dev/sdk";
|
||||||
import { blockAction } from "./interactivity";
|
import {
|
||||||
|
BlockActionInteractivityPayloadSchema,
|
||||||
|
ViewSubmissionInteractivityPayloadSchema,
|
||||||
|
} from "./interactivity";
|
||||||
|
|
||||||
export function blockActionInteraction(params: {
|
export function blockActionInteraction(params: {
|
||||||
blockId: string;
|
blockId: string;
|
||||||
actionId?: string | string[];
|
actionId?: string | string[];
|
||||||
}): TriggerEvent<typeof blockAction> {
|
}): TriggerEvent<typeof BlockActionInteractivityPayloadSchema> {
|
||||||
const actionIds =
|
const actionIds =
|
||||||
typeof params.actionId === "undefined"
|
typeof params.actionId === "undefined"
|
||||||
? []
|
? []
|
||||||
@@ -28,10 +31,44 @@ export function blockActionInteraction(params: {
|
|||||||
event: ["block.action"],
|
event: ["block.action"],
|
||||||
},
|
},
|
||||||
source: {
|
source: {
|
||||||
|
type: "block_action",
|
||||||
blockId: params.blockId,
|
blockId: params.blockId,
|
||||||
actionIds,
|
actionIds,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
schema: blockAction,
|
schema: BlockActionInteractivityPayloadSchema,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function viewSubmissionInteraction(params: {
|
||||||
|
callbackId?: string | string[];
|
||||||
|
}): TriggerEvent<typeof ViewSubmissionInteractivityPayloadSchema> {
|
||||||
|
const callbackIds =
|
||||||
|
typeof params.callbackId === "undefined"
|
||||||
|
? []
|
||||||
|
: Array.isArray(params.callbackId)
|
||||||
|
? params.callbackId
|
||||||
|
: [params.callbackId];
|
||||||
|
|
||||||
|
return {
|
||||||
|
metadata: {
|
||||||
|
type: "SLACK_INTERACTION",
|
||||||
|
service: "slack",
|
||||||
|
name: "view.submission",
|
||||||
|
filter: {
|
||||||
|
service: ["slack"],
|
||||||
|
payload: {
|
||||||
|
view: {
|
||||||
|
callback_id: callbackIds,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
event: ["view.submission"],
|
||||||
|
},
|
||||||
|
source: {
|
||||||
|
type: "view_submission",
|
||||||
|
callbackIds,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
schema: ViewSubmissionInteractivityPayloadSchema,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { getTriggerRun } from "@trigger.dev/sdk";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import * as events from "./events";
|
import * as events from "./events";
|
||||||
import * as schemas from "./schemas";
|
import * as schemas from "./schemas";
|
||||||
|
import zodToJsonSchema from "zod-to-json-schema";
|
||||||
|
|
||||||
export { events };
|
export { events };
|
||||||
|
|
||||||
@@ -95,3 +96,136 @@ export async function addReaction(
|
|||||||
|
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type OpenViewResponse = z.infer<
|
||||||
|
typeof schemas.OpenViewSuccessResponseSchema
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type OpenViewOptions = z.infer<typeof schemas.ModalSchema>;
|
||||||
|
|
||||||
|
export type OpenViewInteractionOptions = {
|
||||||
|
onSubmit?: "clear" | "close" | "none";
|
||||||
|
validationSchema?: z.ZodObject<any, any>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function openView(
|
||||||
|
key: string,
|
||||||
|
triggerId: string,
|
||||||
|
view: OpenViewOptions,
|
||||||
|
options?: OpenViewInteractionOptions
|
||||||
|
): Promise<OpenViewResponse> {
|
||||||
|
const run = getTriggerRun();
|
||||||
|
|
||||||
|
if (!run) {
|
||||||
|
throw new Error("Cannot call openView outside of a trigger run");
|
||||||
|
}
|
||||||
|
|
||||||
|
view.private_metadata = decoratePrivateMetadata(
|
||||||
|
run.id,
|
||||||
|
view.private_metadata,
|
||||||
|
options
|
||||||
|
);
|
||||||
|
|
||||||
|
const output = await run.performRequest(key, {
|
||||||
|
service: "slack",
|
||||||
|
endpoint: "views.open",
|
||||||
|
params: { trigger_id: triggerId, view },
|
||||||
|
response: {
|
||||||
|
schema: schemas.OpenViewSuccessResponseSchema,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cannot be called when the trigger is a viewSubmissionInteraction event, only a blockActionInteraction event
|
||||||
|
export async function updateView(
|
||||||
|
key: string,
|
||||||
|
view: { id: string; hash: string; external_id?: string },
|
||||||
|
updatedView: OpenViewOptions,
|
||||||
|
options?: OpenViewInteractionOptions
|
||||||
|
): Promise<OpenViewResponse> {
|
||||||
|
const run = getTriggerRun();
|
||||||
|
|
||||||
|
if (!run) {
|
||||||
|
throw new Error("Cannot call updateView outside of a trigger run");
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedView.private_metadata = decoratePrivateMetadata(
|
||||||
|
run.id,
|
||||||
|
updatedView.private_metadata,
|
||||||
|
options
|
||||||
|
);
|
||||||
|
|
||||||
|
const output = await run.performRequest(key, {
|
||||||
|
service: "slack",
|
||||||
|
endpoint: "views.update",
|
||||||
|
params: {
|
||||||
|
hash: view.hash,
|
||||||
|
view_id: view.id,
|
||||||
|
external_id: view.id ? undefined : view.external_id,
|
||||||
|
view: updatedView,
|
||||||
|
},
|
||||||
|
response: {
|
||||||
|
schema: schemas.OpenViewSuccessResponseSchema,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cannot be called when the trigger is a viewSubmissionInteraction event, only a blockActionInteraction event
|
||||||
|
export async function pushView(
|
||||||
|
key: string,
|
||||||
|
triggerId: string,
|
||||||
|
view: OpenViewOptions,
|
||||||
|
options?: OpenViewInteractionOptions
|
||||||
|
): Promise<OpenViewResponse> {
|
||||||
|
const run = getTriggerRun();
|
||||||
|
|
||||||
|
if (!run) {
|
||||||
|
throw new Error("Cannot call pushView outside of a trigger run");
|
||||||
|
}
|
||||||
|
|
||||||
|
view.private_metadata = decoratePrivateMetadata(
|
||||||
|
run.id,
|
||||||
|
view.private_metadata,
|
||||||
|
options
|
||||||
|
);
|
||||||
|
|
||||||
|
const output = await run.performRequest(key, {
|
||||||
|
service: "slack",
|
||||||
|
endpoint: "views.push",
|
||||||
|
params: { trigger_id: triggerId, view },
|
||||||
|
response: {
|
||||||
|
schema: schemas.OpenViewSuccessResponseSchema,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
function decoratePrivateMetadata(
|
||||||
|
runId: string,
|
||||||
|
existingMetadata?: string,
|
||||||
|
options?: OpenViewInteractionOptions
|
||||||
|
): string {
|
||||||
|
if (!existingMetadata) {
|
||||||
|
existingMetadata = "{}";
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSubmit = options?.onSubmit;
|
||||||
|
const validationSchema = options?.validationSchema;
|
||||||
|
|
||||||
|
const privateMetadata = JSON.parse(existingMetadata);
|
||||||
|
|
||||||
|
privateMetadata.__trigger = {
|
||||||
|
runId: runId,
|
||||||
|
onSubmit: typeof onSubmit === "undefined" ? "none" : onSubmit,
|
||||||
|
validationSchema: validationSchema
|
||||||
|
? zodToJsonSchema(validationSchema.passthrough(), { errorMessages: true })
|
||||||
|
: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
return JSON.stringify(privateMetadata);
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,10 +11,6 @@ const textSchema = z.discriminatedUnion("type", [
|
|||||||
plainTextElementSchema,
|
plainTextElementSchema,
|
||||||
mrkdwnElementSchema,
|
mrkdwnElementSchema,
|
||||||
]);
|
]);
|
||||||
const blockActionType = z.union([
|
|
||||||
z.literal("block_actions"),
|
|
||||||
z.literal("interactive_message"),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const sourceType = z.literal("message");
|
const sourceType = z.literal("message");
|
||||||
|
|
||||||
@@ -135,13 +131,23 @@ const userSchema = z.object({
|
|||||||
team_id: z.string(),
|
team_id: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const containerSchema = z.object({
|
const viewContainerSchema = z.object({
|
||||||
type: sourceType,
|
type: z.literal("view"),
|
||||||
|
view_id: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const messageContainerSchema = z.object({
|
||||||
|
type: z.literal("message"),
|
||||||
message_ts: z.string(),
|
message_ts: z.string(),
|
||||||
channel_id: z.string(),
|
channel_id: z.string(),
|
||||||
is_ephemeral: z.boolean(),
|
is_ephemeral: z.boolean(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const containerSchema = z.discriminatedUnion("type", [
|
||||||
|
viewContainerSchema,
|
||||||
|
messageContainerSchema,
|
||||||
|
]);
|
||||||
|
|
||||||
const teamSchema = z.object({ id: z.string(), domain: z.string() });
|
const teamSchema = z.object({ id: z.string(), domain: z.string() });
|
||||||
const channelSchema = z.object({ id: z.string(), name: z.string() });
|
const channelSchema = z.object({ id: z.string(), name: z.string() });
|
||||||
|
|
||||||
@@ -154,7 +160,7 @@ const viewActionDataSchema = z.object({
|
|||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
hash: z.string(),
|
hash: z.string(),
|
||||||
previous_view_id: z.string().optional(),
|
previous_view_id: z.string().nullable().optional(),
|
||||||
root_view_id: z.string().optional(),
|
root_view_id: z.string().optional(),
|
||||||
app_id: z.string().optional(),
|
app_id: z.string().optional(),
|
||||||
app_installed_team_id: z.string().optional(),
|
app_installed_team_id: z.string().optional(),
|
||||||
@@ -182,34 +188,94 @@ const messageActionSchema = z.object({
|
|||||||
.optional(),
|
.optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const blockAction: Zod.ZodObject<{
|
export const BlockActionInteractivityPayloadSchema: Zod.ZodObject<{
|
||||||
type: typeof blockActionType;
|
type: z.ZodLiteral<"block_actions">;
|
||||||
|
team: typeof teamSchema;
|
||||||
user: typeof userSchema;
|
user: typeof userSchema;
|
||||||
api_app_id: Zod.ZodString;
|
api_app_id: Zod.ZodString;
|
||||||
container: typeof containerSchema;
|
container: z.ZodOptional<typeof containerSchema>;
|
||||||
trigger_id: z.ZodOptional<Zod.ZodString>;
|
trigger_id: z.ZodOptional<Zod.ZodString>;
|
||||||
team: typeof teamSchema;
|
channel: z.ZodOptional<typeof channelSchema>;
|
||||||
enterprise: Zod.ZodAny;
|
|
||||||
is_enterprise_install: Zod.ZodBoolean;
|
|
||||||
channel: typeof channelSchema;
|
|
||||||
view: z.ZodOptional<typeof viewActionSchema>;
|
view: z.ZodOptional<typeof viewActionSchema>;
|
||||||
message: z.ZodOptional<typeof messageActionSchema>;
|
message: z.ZodOptional<typeof messageActionSchema>;
|
||||||
state: z.ZodOptional<typeof stateSchema>;
|
state: z.ZodOptional<typeof stateSchema>;
|
||||||
response_url: Zod.ZodString;
|
response_url: z.ZodOptional<Zod.ZodString>;
|
||||||
actions: Zod.ZodArray<typeof actionSchema>;
|
actions: Zod.ZodArray<typeof actionSchema>;
|
||||||
}> = z.object({
|
}> = z.object({
|
||||||
type: blockActionType,
|
type: z.literal("block_actions"),
|
||||||
|
team: teamSchema,
|
||||||
user: userSchema,
|
user: userSchema,
|
||||||
api_app_id: z.string(),
|
api_app_id: z.string(),
|
||||||
container: containerSchema,
|
container: containerSchema.optional(),
|
||||||
trigger_id: z.string().optional(),
|
trigger_id: z.string().optional(),
|
||||||
team: teamSchema,
|
channel: channelSchema.optional(),
|
||||||
enterprise: z.any(),
|
|
||||||
is_enterprise_install: z.boolean(),
|
|
||||||
channel: channelSchema,
|
|
||||||
view: viewActionSchema.optional(),
|
view: viewActionSchema.optional(),
|
||||||
message: messageActionSchema.optional(),
|
message: messageActionSchema.optional(),
|
||||||
state: stateSchema.optional(),
|
state: stateSchema.optional(),
|
||||||
response_url: z.string(),
|
response_url: z.string().optional(),
|
||||||
actions: z.array(actionSchema),
|
actions: z.array(actionSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const ResponseUrlObjectSchema = z.object({
|
||||||
|
response_url: z.string(),
|
||||||
|
block_id: z.string(),
|
||||||
|
action_id: z.string(),
|
||||||
|
channel_id: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ViewSubmissionInteractivityPayloadSchema: Zod.ZodObject<{
|
||||||
|
type: z.ZodLiteral<"view_submission">;
|
||||||
|
team: typeof teamSchema;
|
||||||
|
user: typeof userSchema;
|
||||||
|
view: typeof viewActionSchema;
|
||||||
|
response_urls: Zod.ZodArray<typeof ResponseUrlObjectSchema>;
|
||||||
|
api_app_id: Zod.ZodString;
|
||||||
|
trigger_id: z.ZodOptional<Zod.ZodString>;
|
||||||
|
token: z.ZodOptional<Zod.ZodString>;
|
||||||
|
}> = z.object({
|
||||||
|
type: z.literal("view_submission"),
|
||||||
|
team: teamSchema,
|
||||||
|
user: userSchema,
|
||||||
|
view: viewActionSchema,
|
||||||
|
response_urls: z.array(ResponseUrlObjectSchema),
|
||||||
|
api_app_id: z.string(),
|
||||||
|
trigger_id: z.string().optional(),
|
||||||
|
token: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ViewClosedInteractivityPayloadSchema: Zod.ZodObject<{
|
||||||
|
type: z.ZodLiteral<"view_closed">;
|
||||||
|
team: typeof teamSchema;
|
||||||
|
user: typeof userSchema;
|
||||||
|
view: typeof viewActionSchema;
|
||||||
|
is_cleared: Zod.ZodBoolean;
|
||||||
|
api_app_id: Zod.ZodString;
|
||||||
|
}> = z.object({
|
||||||
|
type: z.literal("view_closed"),
|
||||||
|
team: teamSchema,
|
||||||
|
user: userSchema,
|
||||||
|
view: viewActionSchema,
|
||||||
|
is_cleared: z.boolean(),
|
||||||
|
api_app_id: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const InteractivityPayloadSchema: Zod.ZodDiscriminatedUnion<
|
||||||
|
"type",
|
||||||
|
[
|
||||||
|
typeof BlockActionInteractivityPayloadSchema,
|
||||||
|
typeof ViewSubmissionInteractivityPayloadSchema,
|
||||||
|
typeof ViewClosedInteractivityPayloadSchema
|
||||||
|
]
|
||||||
|
> = z.discriminatedUnion("type", [
|
||||||
|
BlockActionInteractivityPayloadSchema,
|
||||||
|
ViewSubmissionInteractivityPayloadSchema,
|
||||||
|
ViewClosedInteractivityPayloadSchema,
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const ViewPrivateMetadataSchema = z.object({
|
||||||
|
__trigger: z.object({
|
||||||
|
runId: z.string(),
|
||||||
|
onSubmit: z.enum(["clear", "close", "none"]),
|
||||||
|
validationSchema: z.any().optional(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|||||||
@@ -17,10 +17,13 @@ import {
|
|||||||
JoinConversationBodySchema,
|
JoinConversationBodySchema,
|
||||||
JoinConversationResponseSchema,
|
JoinConversationResponseSchema,
|
||||||
ListConversationsResponseSchema,
|
ListConversationsResponseSchema,
|
||||||
|
OpenViewBodySchema,
|
||||||
|
OpenViewResponseSchema,
|
||||||
PostMessageBodySchema,
|
PostMessageBodySchema,
|
||||||
PostMessageOptionsSchema,
|
PostMessageOptionsSchema,
|
||||||
PostMessageResponseOptionsSchema,
|
PostMessageResponseOptionsSchema,
|
||||||
PostMessageResponseSchema,
|
PostMessageResponseSchema,
|
||||||
|
UpdateViewBodySchema,
|
||||||
} from "../schemas";
|
} from "../schemas";
|
||||||
|
|
||||||
const log = debug("trigger:integrations:slack");
|
const log = debug("trigger:integrations:slack");
|
||||||
@@ -66,6 +69,33 @@ export class SlackRequestIntegration implements RequestIntegration {
|
|||||||
path: "/reactions.add",
|
path: "/reactions.add",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
#openViewEndpoint = new HttpEndpoint<
|
||||||
|
typeof OpenViewResponseSchema,
|
||||||
|
typeof OpenViewBodySchema
|
||||||
|
>({
|
||||||
|
response: OpenViewResponseSchema,
|
||||||
|
method: "POST",
|
||||||
|
path: "/views.open",
|
||||||
|
});
|
||||||
|
|
||||||
|
#pushViewEndpoint = new HttpEndpoint<
|
||||||
|
typeof OpenViewResponseSchema,
|
||||||
|
typeof OpenViewBodySchema
|
||||||
|
>({
|
||||||
|
response: OpenViewResponseSchema,
|
||||||
|
method: "POST",
|
||||||
|
path: "/views.push",
|
||||||
|
});
|
||||||
|
|
||||||
|
#updateViewEndpoint = new HttpEndpoint<
|
||||||
|
typeof OpenViewResponseSchema,
|
||||||
|
typeof UpdateViewBodySchema
|
||||||
|
>({
|
||||||
|
response: OpenViewResponseSchema,
|
||||||
|
method: "POST",
|
||||||
|
path: "/views.update",
|
||||||
|
});
|
||||||
|
|
||||||
constructor(private readonly baseUrl: string = "https://slack.com/api") {}
|
constructor(private readonly baseUrl: string = "https://slack.com/api") {}
|
||||||
|
|
||||||
perform(options: PerformRequestOptions): Promise<PerformedRequestResponse> {
|
perform(options: PerformRequestOptions): Promise<PerformedRequestResponse> {
|
||||||
@@ -94,6 +124,30 @@ export class SlackRequestIntegration implements RequestIntegration {
|
|||||||
options.metadata
|
options.metadata
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
case "views.open": {
|
||||||
|
return this.#openView(
|
||||||
|
options.accessInfo,
|
||||||
|
options.params,
|
||||||
|
options.cache,
|
||||||
|
options.metadata
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case "views.update": {
|
||||||
|
return this.#updateView(
|
||||||
|
options.accessInfo,
|
||||||
|
options.params,
|
||||||
|
options.cache,
|
||||||
|
options.metadata
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case "views.push": {
|
||||||
|
return this.#pushView(
|
||||||
|
options.accessInfo,
|
||||||
|
options.params,
|
||||||
|
options.cache,
|
||||||
|
options.metadata
|
||||||
|
);
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
throw new Error(`Unknown endpoint: ${options.endpoint}`);
|
throw new Error(`Unknown endpoint: ${options.endpoint}`);
|
||||||
}
|
}
|
||||||
@@ -132,6 +186,24 @@ export class SlackRequestIntegration implements RequestIntegration {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
case "views.open": {
|
||||||
|
return {
|
||||||
|
title: `Open view`,
|
||||||
|
properties: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case "views.update": {
|
||||||
|
return {
|
||||||
|
title: `Update view`,
|
||||||
|
properties: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case "views.push": {
|
||||||
|
return {
|
||||||
|
title: `Push view`,
|
||||||
|
properties: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
throw new Error(`Unknown endpoint: ${endpoint}`);
|
throw new Error(`Unknown endpoint: ${endpoint}`);
|
||||||
@@ -335,7 +407,178 @@ export class SlackRequestIntegration implements RequestIntegration {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
log("chat.postMessage performedRequest %O", performedRequest);
|
log("reactions.add performedRequest %O", performedRequest);
|
||||||
|
|
||||||
|
return performedRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
async #openView(
|
||||||
|
accessInfo: AccessInfo,
|
||||||
|
params: any,
|
||||||
|
cache?: CacheService,
|
||||||
|
metadata?: Record<string, string>
|
||||||
|
): Promise<PerformedRequestResponse> {
|
||||||
|
const parsedParams = OpenViewBodySchema.parse(params);
|
||||||
|
|
||||||
|
log("views.open %O", parsedParams);
|
||||||
|
|
||||||
|
const accessToken = getAccessToken(accessInfo);
|
||||||
|
|
||||||
|
const service = new HttpService({
|
||||||
|
accessToken,
|
||||||
|
baseUrl: this.baseUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await service.performRequest(
|
||||||
|
this.#openViewEndpoint,
|
||||||
|
parsedParams
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.success) {
|
||||||
|
log("views.open failed %O", response);
|
||||||
|
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
isRetryable: this.#isRetryable(response.statusCode),
|
||||||
|
response: {
|
||||||
|
output: response.error,
|
||||||
|
context: {
|
||||||
|
statusCode: response.statusCode,
|
||||||
|
headers: response.headers,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const ok = response.data.ok;
|
||||||
|
|
||||||
|
const performedRequest = {
|
||||||
|
ok,
|
||||||
|
isRetryable: this.#isRetryable(response.statusCode),
|
||||||
|
response: {
|
||||||
|
output: response.data,
|
||||||
|
context: {
|
||||||
|
statusCode: response.statusCode,
|
||||||
|
headers: response.headers,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
log("views.open performedRequest %O", performedRequest);
|
||||||
|
|
||||||
|
return performedRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
async #updateView(
|
||||||
|
accessInfo: AccessInfo,
|
||||||
|
params: any,
|
||||||
|
cache?: CacheService,
|
||||||
|
metadata?: Record<string, string>
|
||||||
|
): Promise<PerformedRequestResponse> {
|
||||||
|
const parsedParams = UpdateViewBodySchema.parse(params);
|
||||||
|
|
||||||
|
log("views.update %O", parsedParams);
|
||||||
|
|
||||||
|
const accessToken = getAccessToken(accessInfo);
|
||||||
|
|
||||||
|
const service = new HttpService({
|
||||||
|
accessToken,
|
||||||
|
baseUrl: this.baseUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await service.performRequest(
|
||||||
|
this.#updateViewEndpoint,
|
||||||
|
parsedParams
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.success) {
|
||||||
|
log("views.update failed %O", response);
|
||||||
|
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
isRetryable: this.#isRetryable(response.statusCode),
|
||||||
|
response: {
|
||||||
|
output: response.error,
|
||||||
|
context: {
|
||||||
|
statusCode: response.statusCode,
|
||||||
|
headers: response.headers,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const ok = response.data.ok;
|
||||||
|
|
||||||
|
const performedRequest = {
|
||||||
|
ok,
|
||||||
|
isRetryable: this.#isRetryable(response.statusCode),
|
||||||
|
response: {
|
||||||
|
output: response.data,
|
||||||
|
context: {
|
||||||
|
statusCode: response.statusCode,
|
||||||
|
headers: response.headers,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
log("views.update performedRequest %O", performedRequest);
|
||||||
|
|
||||||
|
return performedRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
async #pushView(
|
||||||
|
accessInfo: AccessInfo,
|
||||||
|
params: any,
|
||||||
|
cache?: CacheService,
|
||||||
|
metadata?: Record<string, string>
|
||||||
|
): Promise<PerformedRequestResponse> {
|
||||||
|
const parsedParams = OpenViewBodySchema.parse(params);
|
||||||
|
|
||||||
|
log("views.push %O", parsedParams);
|
||||||
|
|
||||||
|
const accessToken = getAccessToken(accessInfo);
|
||||||
|
|
||||||
|
const service = new HttpService({
|
||||||
|
accessToken,
|
||||||
|
baseUrl: this.baseUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await service.performRequest(
|
||||||
|
this.#pushViewEndpoint,
|
||||||
|
parsedParams
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.success) {
|
||||||
|
log("views.push failed %O", response);
|
||||||
|
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
isRetryable: this.#isRetryable(response.statusCode),
|
||||||
|
response: {
|
||||||
|
output: response.error,
|
||||||
|
context: {
|
||||||
|
statusCode: response.statusCode,
|
||||||
|
headers: response.headers,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const ok = response.data.ok;
|
||||||
|
|
||||||
|
const performedRequest = {
|
||||||
|
ok,
|
||||||
|
isRetryable: this.#isRetryable(response.statusCode),
|
||||||
|
response: {
|
||||||
|
output: response.data,
|
||||||
|
context: {
|
||||||
|
statusCode: response.statusCode,
|
||||||
|
headers: response.headers,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
log("views.push performedRequest %O", performedRequest);
|
||||||
|
|
||||||
return performedRequest;
|
return performedRequest;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { knownBlockSchema } from "./blocks";
|
import { knownBlockSchema, plainTextElementSchema } from "./blocks";
|
||||||
import { blockAction } from "./interactivity";
|
|
||||||
|
|
||||||
export { blockAction };
|
export * from "./interactivity";
|
||||||
|
|
||||||
export const PostMessageSuccessResponseSchema = z.object({
|
export const PostMessageSuccessResponseSchema = z.object({
|
||||||
ok: z.literal(true),
|
ok: z.literal(true),
|
||||||
@@ -117,3 +116,45 @@ export const AddReactionResponseSchema = z.discriminatedUnion("ok", [
|
|||||||
AddReactionSuccessResponseSchema,
|
AddReactionSuccessResponseSchema,
|
||||||
ErrorResponseSchema,
|
ErrorResponseSchema,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
export const ModalSchema = z.object({
|
||||||
|
type: z.literal("modal"),
|
||||||
|
title: plainTextElementSchema,
|
||||||
|
blocks: z.array(knownBlockSchema),
|
||||||
|
private_metadata: z.string().optional(),
|
||||||
|
callback_id: z.string().optional(),
|
||||||
|
close: plainTextElementSchema.optional(),
|
||||||
|
submit: plainTextElementSchema.optional(),
|
||||||
|
clear_on_close: z.boolean().optional(),
|
||||||
|
notify_on_close: z.boolean().optional(),
|
||||||
|
external_id: z.string().optional(),
|
||||||
|
submit_disabled: z.boolean().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const OpenViewBodySchema = z.object({
|
||||||
|
trigger_id: z.string(),
|
||||||
|
view: ModalSchema,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const OpenViewSuccessResponseSchema = z.object({
|
||||||
|
ok: z.literal(true),
|
||||||
|
view: z.object({
|
||||||
|
id: z.string(),
|
||||||
|
team_id: z.string(),
|
||||||
|
type: z.string(),
|
||||||
|
private_metadata: z.string(),
|
||||||
|
callback_id: z.string(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const OpenViewResponseSchema = z.discriminatedUnion("ok", [
|
||||||
|
OpenViewSuccessResponseSchema,
|
||||||
|
ErrorResponseSchema,
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const UpdateViewBodySchema = z.object({
|
||||||
|
view_id: z.string().optional(),
|
||||||
|
hash: z.string().optional(),
|
||||||
|
external_id: z.string().optional(),
|
||||||
|
view: ModalSchema,
|
||||||
|
});
|
||||||
|
|||||||
@@ -90,11 +90,22 @@ export const ManualWebhookSourceSchema = z.object({
|
|||||||
event: z.string(),
|
event: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const SlackInteractionSourceSchema = z.object({
|
export const SlackBlockInteractionSourceSchema = z.object({
|
||||||
|
type: z.literal("block_action"),
|
||||||
blockId: z.string(),
|
blockId: z.string(),
|
||||||
actionIds: z.array(z.string()),
|
actionIds: z.array(z.string()),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const SlackViewSubmissionInteractionSourceSchema = z.object({
|
||||||
|
type: z.literal("view_submission"),
|
||||||
|
callbackIds: z.array(z.string()),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const SlackInteractionSourceSchema = z.discriminatedUnion("type", [
|
||||||
|
SlackBlockInteractionSourceSchema,
|
||||||
|
SlackViewSubmissionInteractionSourceSchema,
|
||||||
|
]);
|
||||||
|
|
||||||
export type SlackInteractionSource = z.infer<
|
export type SlackInteractionSource = z.infer<
|
||||||
typeof SlackInteractionSourceSchema
|
typeof SlackInteractionSourceSchema
|
||||||
>;
|
>;
|
||||||
|
|||||||
@@ -7,9 +7,9 @@
|
|||||||
"types": "./src/index.ts",
|
"types": "./src/index.ts",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@trigger.dev/tsconfig": "workspace:*",
|
"@trigger.dev/tsconfig": "workspace:*",
|
||||||
"pulsar-client": "1.7.0",
|
|
||||||
"@types/node": "16",
|
"@types/node": "16",
|
||||||
"@types/node-fetch": "2.6",
|
"@types/node-fetch": "2.6",
|
||||||
|
"pulsar-client": "1.7.0",
|
||||||
"typescript": "^4.9.4",
|
"typescript": "^4.9.4",
|
||||||
"undici": "^5.14.0"
|
"undici": "^5.14.0"
|
||||||
},
|
},
|
||||||
@@ -18,7 +18,8 @@
|
|||||||
"@trigger.dev/common-schemas": "workspace:*",
|
"@trigger.dev/common-schemas": "workspace:*",
|
||||||
"node-fetch": "2.6",
|
"node-fetch": "2.6",
|
||||||
"ulid": "^2.3.0",
|
"ulid": "^2.3.0",
|
||||||
"zod": "^3.20.2"
|
"zod": "^3.20.2",
|
||||||
|
"zod-error": "^1.1.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.0.0 <17.0.0"
|
"node": ">=16.0.0 <17.0.0"
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import type {
|
|||||||
import { Logger } from "../logger";
|
import { Logger } from "../logger";
|
||||||
import { MessageCatalogSchema } from "./messageCatalogSchema";
|
import { MessageCatalogSchema } from "./messageCatalogSchema";
|
||||||
import { ulid } from "ulid";
|
import { ulid } from "ulid";
|
||||||
|
import { generateErrorMessage } from "zod-error";
|
||||||
|
|
||||||
import { z, ZodError } from "zod";
|
import { z, ZodError } from "zod";
|
||||||
import { ZodPubSubStatus } from "./types";
|
import { ZodPubSubStatus } from "./types";
|
||||||
@@ -171,7 +172,8 @@ export class ZodPublisher<PublisherSchema extends MessageCatalogSchema> {
|
|||||||
this.#logger.error(
|
this.#logger.error(
|
||||||
"[ZodPublisher] Could not publish invalid message data or properties",
|
"[ZodPublisher] Could not publish invalid message data or properties",
|
||||||
data,
|
data,
|
||||||
properties
|
properties,
|
||||||
|
generateErrorMessage(e.issues)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
this.#logger.error("[ZodPublisher] Error handling message", e);
|
this.#logger.error("[ZodPublisher] Error handling message", e);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
MessageData,
|
MessageData,
|
||||||
MessageDataSchema,
|
MessageDataSchema,
|
||||||
} from "./messageCatalogSchema";
|
} from "./messageCatalogSchema";
|
||||||
|
import { generateErrorMessage } from "zod-error";
|
||||||
|
|
||||||
import { z, ZodError } from "zod";
|
import { z, ZodError } from "zod";
|
||||||
import { ZodPubSubStatus } from "./types";
|
import { ZodPubSubStatus } from "./types";
|
||||||
@@ -153,7 +154,7 @@ export class ZodSubscriber<SubscriberSchema extends MessageCatalogSchema> {
|
|||||||
"[ZodSubscriber] Received invalid message data or properties",
|
"[ZodSubscriber] Received invalid message data or properties",
|
||||||
messageData,
|
messageData,
|
||||||
properties,
|
properties,
|
||||||
e.format()
|
generateErrorMessage(e.issues)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
this.#logger.error("[ZodSubscriber] Error handling message", e);
|
this.#logger.error("[ZodSubscriber] Error handling message", e);
|
||||||
|
|||||||
@@ -471,6 +471,7 @@ export class TriggerClient<TSchema extends z.ZodTypeAny> {
|
|||||||
fetch: fetchFunction,
|
fetch: fetchFunction,
|
||||||
workflowId: data.meta.workflowId,
|
workflowId: data.meta.workflowId,
|
||||||
appOrigin: data.meta.appOrigin,
|
appOrigin: data.meta.appOrigin,
|
||||||
|
id: data.id,
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
this.#logger.debug("Running trigger...");
|
this.#logger.debug("Running trigger...");
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ type TriggerRunLocalStorage = {
|
|||||||
fetch: TriggerFetch;
|
fetch: TriggerFetch;
|
||||||
workflowId: string;
|
workflowId: string;
|
||||||
appOrigin: string;
|
appOrigin: string;
|
||||||
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const triggerRunLocalStorage =
|
export const triggerRunLocalStorage =
|
||||||
|
|||||||
Generated
+24
-2
@@ -39,7 +39,7 @@ importers:
|
|||||||
specifiers:
|
specifiers:
|
||||||
'@aws-sdk/client-s3': ^3.186.0
|
'@aws-sdk/client-s3': ^3.186.0
|
||||||
'@aws-sdk/s3-request-presigner': ^3.186.0
|
'@aws-sdk/s3-request-presigner': ^3.186.0
|
||||||
'@cfworker/json-schema': ^1.12.4
|
'@cfworker/json-schema': ^1.12.5
|
||||||
'@codemirror/autocomplete': ^6.3.1
|
'@codemirror/autocomplete': ^6.3.1
|
||||||
'@codemirror/commands': ^6.1.2
|
'@codemirror/commands': ^6.1.2
|
||||||
'@codemirror/lang-javascript': ^6.1.1
|
'@codemirror/lang-javascript': ^6.1.1
|
||||||
@@ -548,6 +548,25 @@ importers:
|
|||||||
'@types/node': 16.18.11
|
'@types/node': 16.18.11
|
||||||
tsx: 3.12.2
|
tsx: 3.12.2
|
||||||
|
|
||||||
|
examples/slack-modals:
|
||||||
|
specifiers:
|
||||||
|
'@trigger.dev/sdk': workspace:*
|
||||||
|
'@trigger.dev/slack': workspace:*
|
||||||
|
'@trigger.dev/tsconfig': workspace:*
|
||||||
|
'@types/node': '16'
|
||||||
|
jsx-slack: ^5.3.0
|
||||||
|
tsx: ^3.12.0
|
||||||
|
zod: ^3.20.2
|
||||||
|
dependencies:
|
||||||
|
'@trigger.dev/sdk': link:../../packages/trigger-sdk
|
||||||
|
'@trigger.dev/slack': link:../../integrations/slack
|
||||||
|
jsx-slack: 5.3.0
|
||||||
|
zod: 3.20.2
|
||||||
|
devDependencies:
|
||||||
|
'@trigger.dev/tsconfig': link:../../config-packages/tsconfig
|
||||||
|
'@types/node': 16.18.11
|
||||||
|
tsx: 3.12.2
|
||||||
|
|
||||||
examples/smoke-test:
|
examples/smoke-test:
|
||||||
specifiers:
|
specifiers:
|
||||||
'@trigger.dev/sdk': workspace:*
|
'@trigger.dev/sdk': workspace:*
|
||||||
@@ -680,9 +699,11 @@ importers:
|
|||||||
rimraf: ^3.0.2
|
rimraf: ^3.0.2
|
||||||
tsup: ^6.5.0
|
tsup: ^6.5.0
|
||||||
zod: ^3.20.2
|
zod: ^3.20.2
|
||||||
|
zod-to-json-schema: ^3.20.2
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.3.4
|
debug: 4.3.4
|
||||||
zod: 3.20.2
|
zod: 3.20.2
|
||||||
|
zod-to-json-schema: 3.20.2_zod@3.20.2
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@trigger.dev/integration-sdk': link:../../packages/integration-sdk
|
'@trigger.dev/integration-sdk': link:../../packages/integration-sdk
|
||||||
'@trigger.dev/sdk': link:../../packages/trigger-sdk
|
'@trigger.dev/sdk': link:../../packages/trigger-sdk
|
||||||
@@ -880,11 +901,13 @@ importers:
|
|||||||
ulid: ^2.3.0
|
ulid: ^2.3.0
|
||||||
undici: ^5.14.0
|
undici: ^5.14.0
|
||||||
zod: ^3.20.2
|
zod: ^3.20.2
|
||||||
|
zod-error: ^1.1.0
|
||||||
dependencies:
|
dependencies:
|
||||||
'@trigger.dev/common-schemas': link:../common-schemas
|
'@trigger.dev/common-schemas': link:../common-schemas
|
||||||
node-fetch: 2.6.7
|
node-fetch: 2.6.7
|
||||||
ulid: 2.3.0
|
ulid: 2.3.0
|
||||||
zod: 3.20.2
|
zod: 3.20.2
|
||||||
|
zod-error: 1.1.0
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@trigger.dev/tsconfig': link:../../config-packages/tsconfig
|
'@trigger.dev/tsconfig': link:../../config-packages/tsconfig
|
||||||
'@types/node': 16.18.11
|
'@types/node': 16.18.11
|
||||||
@@ -16909,7 +16932,6 @@ packages:
|
|||||||
zod: ^3.20.0
|
zod: ^3.20.0
|
||||||
dependencies:
|
dependencies:
|
||||||
zod: 3.20.2
|
zod: 3.20.2
|
||||||
dev: true
|
|
||||||
|
|
||||||
/zod/3.20.2:
|
/zod/3.20.2:
|
||||||
resolution: {integrity: sha512-1MzNQdAvO+54H+EaK5YpyEy0T+Ejo/7YLHS93G3RnYWh5gaotGHwGeN/ZO687qEDU2y4CdStQYXVHIgrUl5UVQ==}
|
resolution: {integrity: sha512-1MzNQdAvO+54H+EaK5YpyEy0T+Ejo/7YLHS93G3RnYWh5gaotGHwGeN/ZO687qEDU2y4CdStQYXVHIgrUl5UVQ==}
|
||||||
|
|||||||
Reference in New Issue
Block a user