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
75 changes: 39 additions & 36 deletions components/home-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { format } from "date-fns";
import { toZonedTime } from "date-fns-tz";
import { ScheduleForm } from "@/components/schedule/schedule-form";
import { getSchedules, deleteSchedule, type ScheduleResponse } from "@/lib/actions/schedules";
import { scheduleBucket } from "@/types/schedule";

// The database only changes as fast as the cron tick that writes it (every
// 1 minute), so polling faster than this buys nothing. Background tabs are
Expand Down Expand Up @@ -272,15 +273,19 @@ export function HomeContent() {
})();
}, [fetchSchedules]);

const bucketed: Record<ReturnType<typeof scheduleBucket>, ScheduleResponse[]> = {
scheduled: [],
running: [],
history: [],
};
for (const schedule of schedules) {
bucketed[scheduleBucket(schedule.status, schedule.runConclusion)].push(schedule);
}

// Anything not in a terminal state can still change on its own, so keep
// polling. A dashboard where every schedule has settled starts no timer and
// issues no requests.
const isActive = schedules.some(
(schedule) =>
schedule.status === "pending" ||
schedule.status === "processing" ||
(schedule.status === "triggered" && schedule.runConclusion === null),
);
const isActive = bucketed.scheduled.length > 0 || bucketed.running.length > 0;

useEffect(() => {
if (!isActive) return;
Expand Down Expand Up @@ -312,9 +317,6 @@ export function HomeContent() {
setEditingSchedule(null);
}

const pendingSchedules = schedules.filter((s) => s.status === "pending");
const completedSchedules = schedules.filter((s) => s.status !== "pending");

if (isLoading) {
return (
<div className="flex items-center justify-center py-12">
Expand Down Expand Up @@ -393,35 +395,36 @@ export function HomeContent() {
</div>
</div>

{pendingSchedules.length > 0 && (
<div className="flex flex-col gap-3">
<SectionHeader
label="Pending"
count={pendingSchedules.length}
isUpdating={isFetching}
/>
<div className="flex flex-col gap-2">
{pendingSchedules.map((schedule) => (
<ScheduleCard
key={schedule.id}
schedule={schedule}
onDelete={handleScheduleDeleted}
onEdit={handleEditSchedule}
/>
))}
{(
[
["Scheduled", bucketed.scheduled],
["Running", bucketed.running],
] as const
).map(([label, items]) =>
items.length === 0 ? null : (
<div key={label} className="flex flex-col gap-3">
<SectionHeader label={label} count={items.length} isUpdating={isFetching} />
<div className="flex flex-col gap-2">
{items.map((schedule) => (
<ScheduleCard
key={schedule.id}
schedule={schedule}
onDelete={handleScheduleDeleted}
onEdit={handleEditSchedule}
/>
))}
</div>
</div>
</div>
),
)}

{completedSchedules.length > 0 && (
<div className="flex flex-col gap-3">
<SectionHeader
label="History"
count={completedSchedules.length}
isUpdating={isFetching}
/>
<div className="flex flex-col gap-2">
{completedSchedules.map((schedule) => (
{bucketed.history.length > 0 && (
<details>
<summary className="cursor-pointer text-sm font-medium text-zinc-600 dark:text-zinc-400">
History ({bucketed.history.length})
</summary>
<div className="mt-3 flex flex-col gap-2">
{bucketed.history.map((schedule) => (
<ScheduleCard
key={schedule.id}
schedule={schedule}
Expand All @@ -430,7 +433,7 @@ export function HomeContent() {
/>
))}
</div>
</div>
</details>
)}
</div>
)}
Expand Down
13 changes: 13 additions & 0 deletions types/schedule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,19 @@
// triggered / failed: terminal, non-editable
export type ScheduleStatus = "pending" | "processing" | "triggered" | "failed";

// Which dashboard section a schedule belongs to. "triggered" splits across
// two: the GitHub run is still live until a conclusion comes back.
export function scheduleBucket(
status: ScheduleStatus,
runConclusion: string | null,
): "scheduled" | "running" | "history" {
if (status === "pending") return "scheduled";
if (status === "processing" || (status === "triggered" && runConclusion === null)) {
return "running";
}
return "history";
}

export interface ScheduleFormData {
repository: string | null;
workflow: string | null;
Expand Down