234 lines
5.3 KiB
TypeScript
234 lines
5.3 KiB
TypeScript
export interface SlackMessage {
|
|
text?: string;
|
|
blocks?: SlackBlock[];
|
|
channel?: string;
|
|
}
|
|
|
|
interface SlackBlock {
|
|
type: string;
|
|
text?: { type: string; text: string };
|
|
elements?: unknown[];
|
|
}
|
|
|
|
/**
|
|
* Send a message to a Slack webhook URL.
|
|
*/
|
|
export async function sendSlackMessage(
|
|
webhookUrl: string,
|
|
message: SlackMessage
|
|
): Promise<void> {
|
|
const res = await fetch(webhookUrl, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(message),
|
|
});
|
|
|
|
if (!res.ok) {
|
|
const body = await res.text();
|
|
console.error("[Slack] Webhook failed:", res.status, body);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Send a plain text Slack notification.
|
|
*/
|
|
export async function sendSlackText(
|
|
webhookUrl: string,
|
|
text: string
|
|
): Promise<void> {
|
|
return sendSlackMessage(webhookUrl, { text });
|
|
}
|
|
|
|
// ── Pre-built notification templates ─────────────────────────────────────────
|
|
|
|
export async function slackNotifyVersionUploaded(
|
|
webhookUrl: string,
|
|
params: {
|
|
shotCode: string;
|
|
versionLabel: string;
|
|
artistName: string;
|
|
projectName: string;
|
|
reviewUrl: string;
|
|
}
|
|
) {
|
|
await sendSlackMessage(webhookUrl, {
|
|
blocks: [
|
|
{
|
|
type: "section",
|
|
text: {
|
|
type: "mrkdwn",
|
|
text: `🎬 *New version uploaded*\n*${params.projectName}* · ${params.shotCode} ${params.versionLabel} by ${params.artistName}`,
|
|
},
|
|
},
|
|
{
|
|
type: "actions",
|
|
elements: [
|
|
{
|
|
type: "button",
|
|
text: { type: "plain_text", text: "Review Now" },
|
|
url: params.reviewUrl,
|
|
style: "primary",
|
|
},
|
|
],
|
|
},
|
|
],
|
|
});
|
|
}
|
|
|
|
export async function slackNotifyApproval(
|
|
webhookUrl: string,
|
|
params: {
|
|
shotCode: string;
|
|
versionLabel: string;
|
|
status: "APPROVED" | "REJECTED" | "NEEDS_CHANGES";
|
|
reviewerName: string;
|
|
projectName: string;
|
|
reviewUrl: string;
|
|
}
|
|
) {
|
|
const icons = {
|
|
APPROVED: "✅",
|
|
REJECTED: "❌",
|
|
NEEDS_CHANGES: "⚠️",
|
|
};
|
|
|
|
const labels = {
|
|
APPROVED: "approved",
|
|
REJECTED: "rejected",
|
|
NEEDS_CHANGES: "needs changes",
|
|
};
|
|
|
|
const icon = icons[params.status];
|
|
const label = labels[params.status];
|
|
|
|
await sendSlackMessage(webhookUrl, {
|
|
blocks: [
|
|
{
|
|
type: "section",
|
|
text: {
|
|
type: "mrkdwn",
|
|
text: `${icon} *${params.shotCode} ${params.versionLabel} ${label}*\nBy ${params.reviewerName} · ${params.projectName}`,
|
|
},
|
|
},
|
|
{
|
|
type: "actions",
|
|
elements: [
|
|
{
|
|
type: "button",
|
|
text: { type: "plain_text", text: "View Version" },
|
|
url: params.reviewUrl,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
});
|
|
}
|
|
|
|
export async function slackNotifyTaskReadyForReview(
|
|
webhookUrl: string,
|
|
params: {
|
|
taskTitle: string;
|
|
contextCode: string | null;
|
|
artistName: string;
|
|
projectName: string;
|
|
taskUrl: string;
|
|
}
|
|
) {
|
|
const label = params.contextCode ? `${params.contextCode} — ` : "";
|
|
await sendSlackMessage(webhookUrl, {
|
|
blocks: [
|
|
{
|
|
type: "section",
|
|
text: {
|
|
type: "mrkdwn",
|
|
text: `👁 *Task ready for review*\n*${params.projectName}* · ${label}${params.taskTitle}\nSubmitted by ${params.artistName}`,
|
|
},
|
|
},
|
|
{
|
|
type: "actions",
|
|
elements: [
|
|
{
|
|
type: "button",
|
|
text: { type: "plain_text", text: "Review Task" },
|
|
url: params.taskUrl,
|
|
style: "primary",
|
|
},
|
|
],
|
|
},
|
|
],
|
|
});
|
|
}
|
|
|
|
export async function slackNotifyTaskAssigned(
|
|
webhookUrl: string,
|
|
params: {
|
|
taskTitle: string;
|
|
contextCode: string | null;
|
|
artistName: string;
|
|
assignedByName: string;
|
|
projectName: string;
|
|
taskUrl: string;
|
|
}
|
|
) {
|
|
const label = params.contextCode ? `${params.contextCode} — ` : "";
|
|
await sendSlackMessage(webhookUrl, {
|
|
blocks: [
|
|
{
|
|
type: "section",
|
|
text: {
|
|
type: "mrkdwn",
|
|
text: `📋 *Task assigned*\n*${params.projectName}* · ${label}${params.taskTitle}\nAssigned to ${params.artistName} by ${params.assignedByName}`,
|
|
},
|
|
},
|
|
{
|
|
type: "actions",
|
|
elements: [
|
|
{
|
|
type: "button",
|
|
text: { type: "plain_text", text: "View Task" },
|
|
url: params.taskUrl,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
});
|
|
}
|
|
|
|
export async function slackNotifyNewFeedback(
|
|
webhookUrl: string,
|
|
params: {
|
|
shotCode: string;
|
|
frameNumber: number;
|
|
authorName: string;
|
|
commentText: string;
|
|
reviewUrl: string;
|
|
}
|
|
) {
|
|
const truncated =
|
|
params.commentText.length > 120
|
|
? params.commentText.slice(0, 117) + "..."
|
|
: params.commentText;
|
|
|
|
await sendSlackMessage(webhookUrl, {
|
|
blocks: [
|
|
{
|
|
type: "section",
|
|
text: {
|
|
type: "mrkdwn",
|
|
text: `💬 *New feedback* on ${params.shotCode} frame ${params.frameNumber}\n*${params.authorName}:* ${truncated}`,
|
|
},
|
|
},
|
|
{
|
|
type: "actions",
|
|
elements: [
|
|
{
|
|
type: "button",
|
|
text: { type: "plain_text", text: "Jump to Frame" },
|
|
url: params.reviewUrl,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
});
|
|
}
|