diff --git a/source/api/api.ts b/source/api/api.ts index fcd03243..5986a810 100644 --- a/source/api/api.ts +++ b/source/api/api.ts @@ -150,6 +150,10 @@ export const sendMessageToConnectionsWithAccessToInstallation = (iID: number, me }) } +/** + * This is a lazy loaded version of the above, the callback func will only get called + * if there is a connected websocket. + */ export const sendAsyncMessageToConnectionsWithAccessToInstallation = ( iID: number, callback: (spark: any) => Promise diff --git a/source/api/graphql/mutations/index.ts b/source/api/graphql/mutations/index.ts index d7bd38a2..c33e6b7e 100644 --- a/source/api/graphql/mutations/index.ts +++ b/source/api/graphql/mutations/index.ts @@ -2,6 +2,7 @@ import { getDB } from "../../../db/getDB" import { MongoDB } from "../../../db/mongo" import { GitHubInstallation } from "../../../db" +import { sendLogsToSlackForInstallation } from "../../../infrastructure/installationSlackMessaging" import logger from "../../../logger" import { getRecordedWebhook, @@ -191,25 +192,43 @@ export const mutations = { // TODO: Store the time in some kind of per-installation analytics document // Wait 2 seconds for the container to finish - setTimeout(() => { + setTimeout(async () => { + let dangerfileLog: MSGDangerfileLog | undefined + // Get Hyper logs - // Send another message - sendAsyncMessageToConnectionsWithAccessToInstallation(installation.iID, async spark => { - // TODO: Cache the hyper call, because the logs will disappear after the first - // connected client gets access to them. + const getLogs = async () => { let logs = null try { logs = await getHyperLogs(opts.hyperCallID) } catch (error) { logger.error(`Requesting the hyper logs for ${installation.iID} with callID ${opts.hyperCallID} - ${error}`) + return } const logMessage: MSGDangerfileLog = { event: opts.name, action: "log", filenames: opts.dangerfiles, - log: logs, + log: logs as string, + } + return logMessage + } + + // If you have a connected slack webhook, then always grab the logs + // and store the value somewhere where the websocket to admin connections + // can also read. + if (installation.installationSlackUpdateWebhookURL) { + dangerfileLog = await getLogs() + sendLogsToSlackForInstallation("Received logs from Peril", dangerfileLog!, installation) + } + + // Callback inside is lazy loaded and only called if there are people + // in the dashboard + sendAsyncMessageToConnectionsWithAccessToInstallation(installation.iID, async spark => { + // If the slack trigger above didn't grab the logs, then re-grab them. + if (!dangerfileLog) { + dangerfileLog = await getLogs() } - spark.write(logMessage) + spark.write(dangerfileLog) }) }, 2000) diff --git a/source/infrastructure/installationSlackMessaging.ts b/source/infrastructure/installationSlackMessaging.ts index 147d130c..4d69041a 100644 --- a/source/infrastructure/installationSlackMessaging.ts +++ b/source/infrastructure/installationSlackMessaging.ts @@ -1,4 +1,6 @@ import { IncomingWebhook } from "@slack/client" +import { sentence } from "danger/distribution/runner/DangerUtils" +import { MSGDangerfileLog } from "../api/api" import { GitHubInstallation } from "../db" import { getDB } from "../db/getDB" import { MongoDB } from "../db/mongo" @@ -14,8 +16,7 @@ export const replaceAllKeysInString = (obj: any, message: string) => { let mutableMessage = message const keys = Object.keys(obj) keys.forEach(key => { - const re = new RegExp(obj[key], "g") - mutableMessage = mutableMessage.replace(re, `[${key}]`) + mutableMessage = mutableMessage.split(obj[key]).join(`[${key}]`) }) return mutableMessage @@ -53,3 +54,33 @@ export const sendSlackMessageToInstallation = async (message: string, installati } } } + +export const sendLogsToSlackForInstallation = async ( + message: string, + logs: MSGDangerfileLog, + installation: GitHubInstallation +) => { + if (installation.installationSlackUpdateWebhookURL) { + let filteredLogs = replaceAllKeysInString(globals, logs.log) + filteredLogs = replaceAllKeysInString(installation.envVars, filteredLogs) + + // Doesn't matter if it fails, so long as it's logged. Shouldn't take down the server. + try { + const webhook = new IncomingWebhook(installation.installationSlackUpdateWebhookURL) + + await webhook.send({ + unfurl_links: false, + username: `Peril for ${installation.login}`, + text: message, + attachments: [ + { + title: `${logs.event}.${logs.action} - ${sentence(logs.filenames)}`, + text: `\`\`\`\n${filteredLogs}\n\`\`\``, + }, + ], + }) + } catch (error) { + logger.error(`Sending a slack logs failed for ${installation.login}`) + } + } +} diff --git a/source/peril.ts b/source/peril.ts index 570cfdc8..d6f7414b 100644 --- a/source/peril.ts +++ b/source/peril.ts @@ -36,7 +36,7 @@ export const peril = () => { // Error logging process.on("unhandledRejection", (reason: string, _: any) => { - logger.error("Error: ", reason) + logger.error("UnhandledRejection Error: ", reason) throw reason })