0
Workspace or Schedule error handler Failure
One script reply has been approved by the moderators Verified

Error handler script sending an error message to a Slack channel. It can be used for both a global workspace error handler and specific schedules error handlers

Created by hugo697 636 days ago Viewed 103519 times
0
Submitted by hugo697 Bun
Verified 322 days ago
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, // The UUID of the job that errored
46
  path: string, // The path of the script or flow that errored
47
  is_flow: boolean, // Whether the runnable is a flow
48
  error: object, // The error details
49
  started_at: string, // The start datetime of the latest job that failed
50
  slack: Slack,
51
  channel: string,
52
  schedule_path?: string, // The path of the schedule
53
  trigger_path?: string, // The path of the trigger that errored in the format <trigger_type>/<trigger_path>
54
  failed_times?: number // Minimum number of times the schedule failed before calling the error handler
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

Other submissions