81 lines · 3.2 KB
import { repoDb } from "../scoped-db";
import { loadPull } from "../pulls";
import type { RepoRow } from "~/lib/types";
import type { WorkflowRunRow } from "../ci/jobs";
import { notifyQuietly, quiet } from "./deliver";
import { repoWatchers } from "./recipients";

const FAILED = new Set(["failure", "timed_out", "startup_failure"]);

/**
 * Failure notices go to the actor, the PR author for PR runs and (by the "ci_watching" pref) watchers;
 * a success right after a failure on the same workflow and ref sends a "fixed" notice to the same people.
 */
async function notifyRunCompletedUnsafe(repo: RepoRow, runRow: WorkflowRunRow, conclusion: string): Promise<void> {
  const failed = FAILED.has(conclusion);
  if (!failed && conclusion !== "success") return;
  const db = repoDb(repo.id);
  const state = await db.first<{ conclusion: string; last_run_id: string | null; previous_conclusion: string | null }>(
    "SELECT conclusion, last_run_id, previous_conclusion FROM ci_notify_state WHERE repo_id = ? AND workflow_path = ? AND ref = ?",
    repo.id,
    runRow.workflow_path,
    runRow.ref,
  );
  const previous = state?.last_run_id === runRow.id ? state.previous_conclusion : (state?.conclusion ?? null);
  if (state?.last_run_id !== runRow.id) {
    await db.run(
      `INSERT INTO ci_notify_state (repo_id, workflow_path, ref, conclusion, last_run_id, previous_conclusion, updated_at)
       VALUES (?, ?, ?, ?, ?, ?, ?)
       ON CONFLICT (repo_id, workflow_path, ref) DO UPDATE SET conclusion = excluded.conclusion,
         last_run_id = excluded.last_run_id, previous_conclusion = excluded.previous_conclusion, updated_at = excluded.updated_at`,
      repo.id,
      runRow.workflow_path,
      runRow.ref,
      conclusion,
      runRow.id,
      previous,
      Date.now(),
    );
  }
  const fixed = conclusion === "success" && previous !== null && FAILED.has(previous);