Files
webdev-pipeline/lib/campaign-scheduling.ts

104 lines
2.6 KiB
TypeScript

import { CAMPAIGN_RECURRENCES, CAMPAIGN_STATUSES } from "./campaign-form";
export type CampaignRecurrence = (typeof CAMPAIGN_RECURRENCES)[number];
export type CampaignStatus = (typeof CAMPAIGN_STATUSES)[number];
export type CampaignFormRecurrenceInput = {
recurrence: CampaignRecurrence | (string & {});
status: CampaignStatus;
lastRunAt?: number | null;
now?: number;
};
export type CampaignRunInfo = {
campaignStatus: CampaignStatus;
agentRuns?: Array<{
status: string;
updatedAt?: number;
}>;
};
export function isAllowedCampaignRecurrence(value: string): boolean {
return CAMPAIGN_RECURRENCES.includes(value as CampaignRecurrence);
}
function addDaysUTC(base: number, days: number): number {
const date = new Date(base);
return Date.UTC(
date.getUTCFullYear(),
date.getUTCMonth(),
date.getUTCDate() + days,
date.getUTCHours(),
date.getUTCMinutes(),
date.getUTCSeconds(),
date.getUTCMilliseconds(),
);
}
function addMonthsUTC(base: number, months: number): number {
const date = new Date(base);
return Date.UTC(
date.getUTCFullYear(),
date.getUTCMonth() + months,
date.getUTCDate(),
date.getUTCHours(),
date.getUTCMinutes(),
date.getUTCSeconds(),
date.getUTCMilliseconds(),
);
}
export function calculateNextRunAt(input: CampaignFormRecurrenceInput): number | null {
if (input.status !== "active") {
return null;
}
if (!isAllowedCampaignRecurrence(input.recurrence)) {
return null;
}
if (input.recurrence === "manual") {
return null;
}
const anchor = input.lastRunAt ?? input.now ?? Date.now();
if (input.recurrence === "daily") {
return addDaysUTC(anchor, 1);
}
if (input.recurrence === "weekly") {
return addDaysUTC(anchor, 7);
}
return addMonthsUTC(anchor, 1);
}
export function getCampaignCurrentRunStatus(input: CampaignRunInfo): string {
const agentRuns = input.agentRuns ?? [];
if (agentRuns.length > 0) {
const ordered = [...agentRuns].sort((a, b) => {
const aUpdatedAt = typeof a.updatedAt === "number" ? a.updatedAt : 0;
const bUpdatedAt = typeof b.updatedAt === "number" ? b.updatedAt : 0;
return bUpdatedAt - aUpdatedAt;
});
const latestStatus = ordered.at(0)?.status;
if (latestStatus === "running") {
return "running";
}
if (latestStatus === "pending") {
return "pending";
}
if (
latestStatus === "succeeded" ||
latestStatus === "failed" ||
latestStatus === "canceled"
) {
return latestStatus;
}
}
return input.campaignStatus === "paused" ? "paused" : "idle";
}