Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions lib/tasks/__tests__/enrichTasks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { describe, it, expect, vi, beforeEach } from "vitest";
import { enrichTasks } from "../enrichTasks";
import { fetchTriggerRuns } from "@/lib/trigger/fetchTriggerRuns";
import { retrieveTaskRun } from "@/lib/trigger/retrieveTaskRun";
import { retrieveScheduleTimezone } from "@/lib/trigger/retrieveScheduleTimezone";
import selectAccountEmails from "@/lib/supabase/account_emails/selectAccountEmails";

vi.mock("@/lib/trigger/fetchTriggerRuns", () => ({
Expand All @@ -12,6 +13,10 @@ vi.mock("@/lib/trigger/retrieveTaskRun", () => ({
retrieveTaskRun: vi.fn(),
}));

vi.mock("@/lib/trigger/retrieveScheduleTimezone", () => ({
retrieveScheduleTimezone: vi.fn(),
}));

vi.mock("@/lib/supabase/account_emails/selectAccountEmails", () => ({
default: vi.fn(),
}));
Expand Down Expand Up @@ -44,10 +49,12 @@ const mockRun = {
describe("enrichTasks", () => {
beforeEach(() => {
vi.clearAllMocks();
vi.mocked(retrieveScheduleTimezone).mockResolvedValue(undefined);
});

it("returns recent_runs, upcoming, and owner_email", async () => {
it("returns recent_runs, upcoming, owner_email, and the schedule's timezone", async () => {
vi.mocked(fetchTriggerRuns).mockResolvedValue([mockRun] as never);
vi.mocked(retrieveScheduleTimezone).mockResolvedValue("America/New_York");
vi.mocked(retrieveTaskRun).mockResolvedValue({
...mockRun,
payload: {
Expand All @@ -71,13 +78,15 @@ describe("enrichTasks", () => {
recent_runs: [mockRun],
upcoming: ["2026-03-27T09:00:00Z", "2026-04-03T09:00:00Z"],
owner_email: "owner@example.com",
timezone: "America/New_York",
},
]);
expect(fetchTriggerRuns).toHaveBeenCalledWith({ "filter[schedule]": "sched_abc" }, 5);
expect(retrieveScheduleTimezone).toHaveBeenCalledWith("sched_abc");
expect(selectAccountEmails).toHaveBeenCalledWith({ accountIds: ["account-456"] });
});

it("returns empty trigger fields and null owner_email when no schedule or email exists", async () => {
it("returns empty trigger fields, null owner_email, and null timezone when no schedule exists", async () => {
vi.mocked(selectAccountEmails).mockResolvedValue([]);

const result = await enrichTasks([{ ...mockTask, trigger_schedule_id: null }]);
Expand All @@ -89,12 +98,14 @@ describe("enrichTasks", () => {
recent_runs: [],
upcoming: [],
owner_email: null,
timezone: null,
},
]);
expect(fetchTriggerRuns).not.toHaveBeenCalled();
expect(retrieveScheduleTimezone).not.toHaveBeenCalled();
});

it("returns empty trigger enrichment when Trigger.dev fails", async () => {
it("returns empty enrichment (timezone null) when Trigger.dev fails", async () => {
vi.mocked(fetchTriggerRuns).mockRejectedValue(new Error("API error"));
vi.mocked(selectAccountEmails).mockResolvedValue([]);

Expand All @@ -106,12 +117,14 @@ describe("enrichTasks", () => {
recent_runs: [],
upcoming: [],
owner_email: null,
timezone: null,
},
]);
});

it("returns empty upcoming when no runs exist", async () => {
it("returns empty upcoming but still the timezone when no runs exist", async () => {
vi.mocked(fetchTriggerRuns).mockResolvedValue([] as never);
vi.mocked(retrieveScheduleTimezone).mockResolvedValue("UTC");
vi.mocked(selectAccountEmails).mockResolvedValue([]);

const result = await enrichTasks([mockTask]);
Expand All @@ -122,6 +135,7 @@ describe("enrichTasks", () => {
recent_runs: [],
upcoming: [],
owner_email: null,
timezone: "UTC",
},
]);
expect(retrieveTaskRun).not.toHaveBeenCalled();
Expand Down
20 changes: 16 additions & 4 deletions lib/tasks/enrichTasks.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import selectAccountEmails from "@/lib/supabase/account_emails/selectAccountEmails";
import { fetchTriggerRuns, type TriggerRun } from "@/lib/trigger/fetchTriggerRuns";
import { retrieveTaskRun } from "@/lib/trigger/retrieveTaskRun";
import { retrieveScheduleTimezone } from "@/lib/trigger/retrieveScheduleTimezone";
import type { Tables } from "@/types/database.types";

type ScheduledAction = Tables<"scheduled_actions">;
Expand All @@ -9,11 +10,14 @@ export type EnrichedTask = ScheduledAction & {
recent_runs: TriggerRun[];
upcoming: string[];
owner_email: string | null;
/** IANA timezone read from the Trigger.dev schedule (source of truth); null when unavailable. */
timezone: string | null;
};

interface TriggerInfo {
recent_runs: TriggerRun[];
upcoming: string[];
timezone: string | null;
}

type TriggerInfoEntry = readonly [string, TriggerInfo];
Expand All @@ -30,11 +34,16 @@ export async function enrichTasks(tasks: ScheduledAction[]): Promise<EnrichedTas
const scheduleId = task.trigger_schedule_id;

if (!scheduleId) {
return [task.id, { recent_runs: [], upcoming: [] }] as const;
return [task.id, { recent_runs: [], upcoming: [], timezone: null }] as const;
}

try {
const recentRuns = await fetchTriggerRuns({ "filter[schedule]": scheduleId }, 5);
// The schedule owns the timezone (chat#1881 3c) — read it back so the
// edit UI can prefill the current zone. Runs in parallel with the runs.
const [recentRuns, timezone] = await Promise.all([
fetchTriggerRuns({ "filter[schedule]": scheduleId }, 5),
retrieveScheduleTimezone(scheduleId),
]);

let upcoming: string[] = [];

Expand All @@ -53,10 +62,13 @@ export async function enrichTasks(tasks: ScheduledAction[]): Promise<EnrichedTas
}
}

return [task.id, { recent_runs: recentRuns, upcoming }] as const;
return [
task.id,
{ recent_runs: recentRuns, upcoming, timezone: timezone ?? null },
] as const;
} catch {
// Trigger.dev API failed — return task without trigger enrichment
return [task.id, { recent_runs: [], upcoming: [] }] as const;
return [task.id, { recent_runs: [], upcoming: [], timezone: null }] as const;
}
}),
);
Expand Down
Loading