1 | import { WebClient } from "@slack/web-api"; |
2 | import dayjs from "dayjs"; |
3 |
|
4 | type Slack = { |
5 | token: string; |
6 | }; |
7 |
|
8 | function formatError(error: any) { |
9 | if (error.stack && error.name && error.message) { |
10 | const formatted = `*${error.name}: ${error.message}*\`\`\`\n${error.stack}\n\`\`\``; |
11 | if (formatted.length > 2900) { |
12 | return `\`\`\`\n${JSON.stringify(error, null, 2).substring(0, 2900)}...\n(truncated)\n\`\`\``; |
13 | } else { |
14 | return formatted; |
15 | } |
16 | } else { |
17 | const stringified = JSON.stringify(error, null, 2); |
18 | if (stringified.length > 2900) { |
19 | return `\`\`\`\n${stringified.substring(0, 2900)}...\n(truncated)\n\`\`\``; |
20 | } else { |
21 | return `\`\`\`\n${stringified}\n\`\`\``; |
22 | } |
23 | } |
24 | } |
25 |
|
26 | function getTriggerInfo(baseUrl: string, workspace_id: string, trigger_path?: string) { |
27 | if (trigger_path) { |
28 | const triggerType = trigger_path.split("/")[0]; |
29 | const prettyTriggerType = triggerType.charAt(0).toUpperCase() + triggerType.replace("_", " ").slice(1); |
30 | const triggerTypePath = triggerType === "http_trigger" ? "routes" : triggerType + "s"; |
31 | const triggerPath = trigger_path.replace(`${triggerType}/`, ""); |
32 | const url = `${baseUrl}/${triggerTypePath}?workspace=${encodeURIComponent(workspace_id)}\#${encodeURIComponent( |
33 | triggerPath |
34 | )}`; |
35 | return { |
36 | prettyTriggerType, |
37 | triggerPath, |
38 | url, |
39 | }; |
40 | } |
41 | } |
42 |
|
43 | export async function main( |
44 | workspace_id: string, |
45 | job_id: string, |
46 | path: string, |
47 | is_flow: boolean, |
48 | error: object, |
49 | started_at: string, |
50 | slack: Slack, |
51 | channel: string, |
52 | schedule_path?: string, |
53 | trigger_path?: string, |
54 | failed_times?: number |
55 | ) { |
56 | const baseUrl = process.env["WM_BASE_URL"]; |
57 | const scheduleUrl = |
58 | baseUrl + |
59 | "/runs?schedule_path=" + |
60 | encodeURIComponent(schedule_path) + |
61 | "&workspace=" + |
62 | encodeURIComponent(workspace_id); |
63 | const scriptOrFlowRunUrl = |
64 | baseUrl + (is_flow ? "/flows/get/" : "/scripts/get/") + path + "?workspace=" + encodeURIComponent(workspace_id); |
65 | const web = new WebClient(slack.token); |
66 | const jobRunUrl = baseUrl + "/run/" + job_id + "?workspace=" + encodeURIComponent(workspace_id); |
67 | const triggerInfo = getTriggerInfo(baseUrl, workspace_id, trigger_path); |
68 |
|
69 | let mdText = ""; |
70 | if (schedule_path) { |
71 | mdText = `*Schedule <${scheduleUrl}|${schedule_path}> failed*${ |
72 | failed_times > 1 ? " " + failed_times + " times in a row" : "" |
73 | }:\n- Run <${jobRunUrl}|${job_id}> of ${is_flow ? "flow" : "script"}: <${scriptOrFlowRunUrl}|${path}>`; |
74 | } else if (triggerInfo) { |
75 | mdText = `*${triggerInfo.prettyTriggerType} <${triggerInfo.url}|${ |
76 | triggerInfo.triggerPath |
77 | }> failed*\n- Run <${jobRunUrl}|${job_id}> of ${is_flow ? "flow" : "script"}: <${scriptOrFlowRunUrl}|${path}>`; |
78 | } else { |
79 | mdText = `*Run <${jobRunUrl}|${job_id}> of ${is_flow ? "flow" : "script"} <${scriptOrFlowRunUrl}|${path}> failed*`; |
80 | } |
81 |
|
82 | const rawText = mdText.replaceAll(/[<>$*]/g, ""); |
83 |
|
84 | const errorText = `${(failed_times ?? 1) > 1 ? "Last failed" : "Failed"} job started at: ${dayjs(started_at).format( |
85 | "DD.MM.YYYY HH:mm (Z)" |
86 | )}\n${formatError(error)}`; |
87 |
|
88 | await web.chat.postMessage({ |
89 | channel, |
90 | text: rawText, |
91 | blocks: [ |
92 | { |
93 | type: "section", |
94 | text: { |
95 | type: "mrkdwn", |
96 | text: mdText, |
97 | }, |
98 | }, |
99 | ], |
100 | attachments: [ |
101 | { |
102 | color: "#ff0000", |
103 | blocks: [ |
104 | { |
105 | type: "section", |
106 | text: { |
107 | type: "mrkdwn", |
108 | text: errorText, |
109 | }, |
110 | }, |
111 | ], |
112 | fallback: errorText, |
113 | }, |
114 | ], |
115 | }); |
116 | } |
117 |
|