mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-08 03:00:28 -04:00
fix(frontend/library): fix schedule display issues for recurring schedules (#11362)
Fixes two related bugs in the agent scheduling UI that caused confusion for users setting up recurring schedules: 1. **"on day nan of every month" display bug**: When scheduling an agent to repeat every N days (e.g., "every 2 days"), the schedule info panel incorrectly displayed "on day nan of every month" instead of the correct "Every N days at HH:MM" format. 2. **Confusing time picker for hourly intervals**: When setting up a schedule with "every N hours", the UI displayed a time picker labeled "at 9 o'clock" which was confusing because the time setting is ignored for hourly intervals. Users were unclear about what this setting meant or if it had any effect. ### Changes 🏗️ **Fixed `humanizeCronExpression` function** (`autogpt_platform/frontend/src/lib/cron-expression-utils.ts`): - Reordered cron expression parsing logic to handle day intervals (`*/N`) before monthly checks - Added `!dayOfMonth.startsWith("*/")` guard to monthly and yearly checks to prevent misinterpreting day intervals as monthly day lists - This ensures expressions like `0 9 */2 * *` (every 2 days at 9:00) are correctly displayed as "Every 2 days at 09:00" instead of "on day nan of every month" **Updated `CronScheduler` component** (`autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/AgentRunsView/components/ScheduleAgentModal/components/CronScheduler/CronScheduler.tsx`): - Hide `TimeAt` component for custom intervals with unit "hours" (time is ignored for hourly intervals) - Pass context-aware label to `TimeAt`: "Starting at" for custom day intervals, "At" for other frequencies - This clarifies that the time setting is the starting time for day intervals and removes confusion for hourly intervals **Enhanced `TimeAt` component** (`autogpt_platform/frontend/src/app/(platform)/library/agents/[id]/components/AgentRunsView/components/ScheduleAgentModal/components/CronScheduler/TimeAt.tsx`): - Added optional `label` prop (defaults to "At") to allow context-aware labeling - Component now displays "Starting at" when used with custom day intervals for better clarity ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: <!-- Put your test plan here: --> - [x] Schedule an agent with "Custom" frequency, "Every 2 days" interval - verify it displays as "Every 2 days at HH:MM" in the schedule info panel (not "on day nan of every month") - [x] Schedule an agent with "Monthly" frequency - verify it displays correctly (e.g., "On day 1, 15 of every month at HH:MM") <img width="845" height="388" alt="image" src="https://github.com/user-attachments/assets/02ed0b73-bf5e-48fd-a7b0-6f4d4687eb13" /> <img width="839" height="374" alt="image" src="https://github.com/user-attachments/assets/be62eee2-3fdd-4b20-aecf-669c3c6c6fb2" />
This commit is contained in:
@@ -259,9 +259,18 @@ export function CronScheduler({
|
||||
<YearlyPicker values={selectedMonths} onChange={setSelectedMonths} />
|
||||
)}
|
||||
|
||||
{frequency !== "hourly" && (
|
||||
<TimeAt value={selectedTime} onChange={setSelectedTime} />
|
||||
)}
|
||||
{frequency !== "hourly" &&
|
||||
!(frequency === "custom" && customInterval.unit === "hours") && (
|
||||
<TimeAt
|
||||
value={selectedTime}
|
||||
onChange={setSelectedTime}
|
||||
label={
|
||||
frequency === "custom" && customInterval.unit === "days"
|
||||
? "Starting at"
|
||||
: "At"
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,9 +6,11 @@ import { Select } from "@/components/atoms/Select/Select";
|
||||
export function TimeAt({
|
||||
value,
|
||||
onChange,
|
||||
label = "At",
|
||||
}: {
|
||||
value: string;
|
||||
onChange: (v: string) => void;
|
||||
label?: string;
|
||||
}) {
|
||||
const [hour12, setHour12] = useState<string>("9");
|
||||
const [minute, setMinute] = useState<string>("00");
|
||||
@@ -54,7 +56,7 @@ export function TimeAt({
|
||||
<div className="flex items-end gap-1">
|
||||
<div className="relative">
|
||||
<label className="mb-0 block text-sm font-medium text-zinc-700">
|
||||
At
|
||||
{label}
|
||||
</label>
|
||||
<div className="flex items-center gap-2">
|
||||
<Select
|
||||
|
||||
@@ -150,31 +150,6 @@ export function humanizeCronExpression(cronExpression: string): string {
|
||||
return `Every ${days} at ${formatTime(hour, minute)}`;
|
||||
}
|
||||
|
||||
// Handle monthly (e.g., 30 14 1,15 * *)
|
||||
if (
|
||||
dayOfMonth !== "*" &&
|
||||
month === "*" &&
|
||||
dayOfWeek === "*" &&
|
||||
!minute.includes("/") &&
|
||||
!hour.includes("/")
|
||||
) {
|
||||
const days = dayOfMonth.split(",").map(Number);
|
||||
const dayList = days.join(", ");
|
||||
return `On day ${dayList} of every month at ${formatTime(hour, minute)}`;
|
||||
}
|
||||
|
||||
// Handle yearly (e.g., 30 14 1 1,6,12 *)
|
||||
if (
|
||||
dayOfMonth !== "*" &&
|
||||
month !== "*" &&
|
||||
dayOfWeek === "*" &&
|
||||
!minute.includes("/") &&
|
||||
!hour.includes("/")
|
||||
) {
|
||||
const months = getMonthNames(month);
|
||||
return `Every year on the 1st day of ${months} at ${formatTime(hour, minute)}`;
|
||||
}
|
||||
|
||||
// Handle custom minute intervals with other fields as * (e.g., every N minutes)
|
||||
if (
|
||||
minute.includes("/") &&
|
||||
@@ -200,6 +175,7 @@ export function humanizeCronExpression(cronExpression: string): string {
|
||||
}
|
||||
|
||||
// Handle specific days with custom intervals (e.g., every N days)
|
||||
// This must come BEFORE the monthly check to avoid misinterpreting */N as monthly days
|
||||
if (
|
||||
dayOfMonth.startsWith("*/") &&
|
||||
month === "*" &&
|
||||
@@ -211,6 +187,35 @@ export function humanizeCronExpression(cronExpression: string): string {
|
||||
return `Every ${interval} days at ${formatTime(hour, minute)}`;
|
||||
}
|
||||
|
||||
// Handle monthly (e.g., 30 14 1,15 * *)
|
||||
// Check that dayOfMonth doesn't start with */ to avoid matching day intervals
|
||||
if (
|
||||
dayOfMonth !== "*" &&
|
||||
!dayOfMonth.startsWith("*/") &&
|
||||
month === "*" &&
|
||||
dayOfWeek === "*" &&
|
||||
!minute.includes("/") &&
|
||||
!hour.includes("/")
|
||||
) {
|
||||
const days = dayOfMonth.split(",").map(Number);
|
||||
const dayList = days.join(", ");
|
||||
return `On day ${dayList} of every month at ${formatTime(hour, minute)}`;
|
||||
}
|
||||
|
||||
// Handle yearly (e.g., 30 14 1 1,6,12 *)
|
||||
// Check that dayOfMonth doesn't start with */ to avoid matching day intervals
|
||||
if (
|
||||
dayOfMonth !== "*" &&
|
||||
!dayOfMonth.startsWith("*/") &&
|
||||
month !== "*" &&
|
||||
dayOfWeek === "*" &&
|
||||
!minute.includes("/") &&
|
||||
!hour.includes("/")
|
||||
) {
|
||||
const months = getMonthNames(month);
|
||||
return `Every year on the 1st day of ${months} at ${formatTime(hour, minute)}`;
|
||||
}
|
||||
|
||||
return `Cron Expression: ${cronExpression}`;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user