All files / src/lib/envelope checkin-builder.ts

96.15% Statements 25/26
95% Branches 19/20
100% Functions 2/2
96.15% Lines 25/26

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134                                                                                                112x               112x 61x 208x 48x           13x     51x       51x 45x   51x 40x   51x     51x 37x   51x 37x     51x                                                     66x           66x 12x   66x 8x   66x 2x     66x    
/**
 * Constructs cron monitor check-in payloads for `sentry monitor run`.
 *
 * Mirrors the behaviour of the legacy Rust sentry-cli `monitors run` command:
 * an `in_progress` check-in is sent when the wrapped command starts, then an
 * `ok`/`error` check-in (with duration) is sent on completion. The optional
 * `monitor_config` (built from `--schedule` and dependent flags) upserts the
 * monitor and is only attached to the opening `in_progress` check-in.
 *
 * The payloads are `SerializedCheckIn` objects (the snake_case wire format),
 * ready to be wrapped via `createCheckInEnvelope` and serialized for posting
 * to the ingest endpoint.
 */
 
import type { SerializedCheckIn } from "@sentry/core";
import { ValidationError } from "../errors.js";
 
/**
 * CLI flags accepted by `sentry monitor run` that affect the monitor config.
 *
 * `schedule` is a crontab string (matching the legacy CLI, which only supports
 * crontab schedules — not intervals). The remaining flags require `schedule`
 * to be set and are forwarded to the monitor's upsert config.
 */
export type CheckInConfigFlags = {
  schedule?: string;
  "check-in-margin"?: number;
  "max-runtime"?: number;
  timezone?: string;
  "failure-issue-threshold"?: number;
  "recovery-threshold"?: number;
};
 
/** Non-undefined `monitor_config` shape of {@link SerializedCheckIn}. */
type MonitorConfig = NonNullable<SerializedCheckIn["monitor_config"]>;
 
/**
 * Build a monitor upsert config from `--schedule` and its dependent flags.
 *
 * Returns `undefined` when `--schedule` is not provided (no upsert requested).
 * Throws {@link ValidationError} when a dependent flag (`--check-in-margin`,
 * `--max-runtime`, `--timezone`, `--failure-issue-threshold`,
 * `--recovery-threshold`) is set without `--schedule`, matching the legacy
 * CLI's `requires("schedule")` constraint.
 */
export function buildMonitorConfig(
  flags: CheckInConfigFlags
): MonitorConfig | undefined {
  const dependentFlags: [keyof CheckInConfigFlags, string][] = [
    ["check-in-margin", "--check-in-margin"],
    ["max-runtime", "--max-runtime"],
    ["timezone", "--timezone"],
    ["failure-issue-threshold", "--failure-issue-threshold"],
    ["recovery-threshold", "--recovery-threshold"],
  ];
 
  if (!flags.schedule) {
    for (const [key, flagName] of dependentFlags) {
      if (flags[key] !== undefined) {
        throw new ValidationError(
          `${flagName} requires --schedule to be set.`,
          "schedule"
        );
      }
    }
    return;
  }
 
  const config: MonitorConfig = {
    schedule: { type: "crontab", value: flags.schedule },
  };
 
  if (flags["check-in-margin"] !== undefined) {
    config.checkin_margin = flags["check-in-margin"];
  }
  if (flags["max-runtime"] !== undefined) {
    config.max_runtime = flags["max-runtime"];
  }
  Iif (flags.timezone !== undefined) {
    config.timezone = flags.timezone;
  }
  if (flags["failure-issue-threshold"] !== undefined) {
    config.failure_issue_threshold = flags["failure-issue-threshold"];
  }
  if (flags["recovery-threshold"] !== undefined) {
    config.recovery_threshold = flags["recovery-threshold"];
  }
 
  return config;
}
 
/** Options for {@link buildCheckIn}. */
export type BuildCheckInOptions = {
  /** Shared check-in ID linking the open and close check-ins. */
  checkInId: string;
  /** The monitor's distinct slug. */
  monitorSlug: string;
  /** Check-in status. */
  status: SerializedCheckIn["status"];
  /** Environment name (e.g. "production"). */
  environment?: string;
  /** Duration in seconds — only meaningful for `ok`/`error` (close) check-ins. */
  duration?: number;
  /** Monitor upsert config — only attached to the opening `in_progress` check-in. */
  monitorConfig?: MonitorConfig;
};
 
/**
 * Assemble a {@link SerializedCheckIn} payload.
 *
 * The caller generates a single `checkInId` (via `uuid4()`) and passes it to
 * both the opening and closing check-ins so Sentry links them. `duration` is
 * only set for close check-ins; `monitorConfig` only for the open one.
 */
export function buildCheckIn(opts: BuildCheckInOptions): SerializedCheckIn {
  const checkIn: SerializedCheckIn = {
    check_in_id: opts.checkInId,
    monitor_slug: opts.monitorSlug,
    status: opts.status,
  };
 
  if (opts.environment !== undefined) {
    checkIn.environment = opts.environment;
  }
  if (opts.duration !== undefined) {
    checkIn.duration = opts.duration;
  }
  if (opts.monitorConfig !== undefined) {
    checkIn.monitor_config = opts.monitorConfig;
  }
 
  return checkIn;
}