mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-08 03:00:28 -04:00
fix(frontend/library): graceful schedule deletion with auto-selection (#12278)
### Motivation 🎯 Fixes the issue where deleting a schedule shows an error screen instead of gracefully handling the deletion. Previously, when a user deleted a schedule, a race condition occurred where the query cache refetch completed before the URL state updated, causing the component to try rendering a schedule that no longer existed (resulting in a 404 error screen). ### Changes 🏗️ **1. Fixed deletion order to prevent error screen flash** - `useSelectedScheduleActions.ts` - Call `onDeleted()` callback **before** invalidating queries to clear selection first - `ScheduleActionsDropdown.tsx` - Same fix for sidebar dropdown deletion **2. Added smart auto-selection logic** - `useNewAgentLibraryView.ts`: - Added query to fetch current schedules list - Added `handleScheduleDeleted(deletedScheduleId)` function that: - Auto-selects the first remaining schedule if others exist - Clears selection to show empty state if no schedules remain **3. Wired up smart deletion handler throughout component tree** - `NewAgentLibraryView.tsx` - Passes `handleScheduleDeleted` to child components - `SelectedScheduleView.tsx` - Changed callback from `onClearSelectedRun` to `onScheduleDeleted` and passes schedule ID - `SidebarRunsList.tsx` - Added `onScheduleDeleted` prop and passes it through to list items ### Checklist 📋 **Test Plan:** - [] Create 2-3 test schedules for an agent - [] Delete a schedule from the detail view (trash icon in actions) when other schedules exist → Verify next schedule auto-selects without error - [] Delete a schedule from the sidebar dropdown (three-dot menu) when other schedules exist → Verify next schedule auto-selects without error - [] Delete all schedules until only one remains → Verify empty state shows gracefully without error - [] Verify "Schedule deleted" toast appears on successful deletion - [] Verify no error screen appears at any point during deletion flow
This commit is contained in:
@@ -44,6 +44,7 @@ export function NewAgentLibraryView() {
|
||||
handleSelectRun,
|
||||
handleCountsChange,
|
||||
handleClearSelectedRun,
|
||||
handleScheduleDeleted,
|
||||
onRunInitiated,
|
||||
onTriggerSetup,
|
||||
onScheduleCreated,
|
||||
@@ -196,6 +197,7 @@ export function NewAgentLibraryView() {
|
||||
selectedRunId={activeItem ?? undefined}
|
||||
onSelectRun={handleSelectRun}
|
||||
onClearSelectedRun={handleClearSelectedRun}
|
||||
onScheduleDeleted={handleScheduleDeleted}
|
||||
onTabChange={setActiveTab}
|
||||
onCountsChange={handleCountsChange}
|
||||
/>
|
||||
@@ -206,7 +208,7 @@ export function NewAgentLibraryView() {
|
||||
<SelectedScheduleView
|
||||
agent={agent}
|
||||
scheduleId={activeItem}
|
||||
onClearSelectedRun={handleClearSelectedRun}
|
||||
onScheduleDeleted={handleScheduleDeleted}
|
||||
banner={renderMarketplaceUpdateBanner()}
|
||||
/>
|
||||
) : activeTab === "templates" ? (
|
||||
|
||||
@@ -19,14 +19,14 @@ import { useSelectedScheduleView } from "./useSelectedScheduleView";
|
||||
interface Props {
|
||||
agent: LibraryAgent;
|
||||
scheduleId: string;
|
||||
onClearSelectedRun?: () => void;
|
||||
onScheduleDeleted?: (deletedScheduleId: string) => void;
|
||||
banner?: React.ReactNode;
|
||||
}
|
||||
|
||||
export function SelectedScheduleView({
|
||||
agent,
|
||||
scheduleId,
|
||||
onClearSelectedRun,
|
||||
onScheduleDeleted,
|
||||
banner,
|
||||
}: Props) {
|
||||
const { schedule, isLoading, error } = useSelectedScheduleView(
|
||||
@@ -89,7 +89,7 @@ export function SelectedScheduleView({
|
||||
<SelectedScheduleActions
|
||||
agent={agent}
|
||||
scheduleId={schedule.id}
|
||||
onDeleted={onClearSelectedRun}
|
||||
onDeleted={() => onScheduleDeleted?.(schedule.id)}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
@@ -168,7 +168,7 @@ export function SelectedScheduleView({
|
||||
<SelectedScheduleActions
|
||||
agent={agent}
|
||||
scheduleId={schedule.id}
|
||||
onDeleted={onClearSelectedRun}
|
||||
onDeleted={() => onScheduleDeleted?.(schedule.id)}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
@@ -28,13 +28,15 @@ export function useSelectedScheduleActions({
|
||||
mutation: {
|
||||
onSuccess: () => {
|
||||
toast({ title: "Schedule deleted" });
|
||||
setShowDeleteDialog(false);
|
||||
|
||||
onDeleted?.();
|
||||
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: getGetV1ListExecutionSchedulesForAGraphQueryOptions(
|
||||
agent.graph_id,
|
||||
).queryKey,
|
||||
});
|
||||
setShowDeleteDialog(false);
|
||||
onDeleted?.();
|
||||
},
|
||||
onError: (error: unknown) =>
|
||||
toast({
|
||||
|
||||
@@ -28,6 +28,7 @@ interface Props {
|
||||
tab?: "runs" | "scheduled" | "templates" | "triggers",
|
||||
) => void;
|
||||
onClearSelectedRun?: () => void;
|
||||
onScheduleDeleted?: (deletedScheduleId: string) => void;
|
||||
onTabChange?: (tab: "runs" | "scheduled" | "templates" | "triggers") => void;
|
||||
onCountsChange?: (info: {
|
||||
runsCount: number;
|
||||
@@ -43,6 +44,7 @@ export function SidebarRunsList({
|
||||
selectedRunId,
|
||||
onSelectRun,
|
||||
onClearSelectedRun,
|
||||
onScheduleDeleted,
|
||||
onTabChange,
|
||||
onCountsChange,
|
||||
}: Props) {
|
||||
@@ -183,6 +185,7 @@ export function SidebarRunsList({
|
||||
agent={agent}
|
||||
selected={selectedRunId === s.id}
|
||||
onClick={() => onSelectRun(s.id, "scheduled")}
|
||||
onDeleted={() => onScheduleDeleted?.(s.id)}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
|
||||
@@ -39,15 +39,15 @@ export function ScheduleActionsDropdown({ agent, schedule, onDeleted }: Props) {
|
||||
await deleteSchedule({ scheduleId: schedule.id });
|
||||
|
||||
toast({ title: "Schedule deleted" });
|
||||
setShowDeleteDialog(false);
|
||||
|
||||
onDeleted?.();
|
||||
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: getGetV1ListExecutionSchedulesForAGraphQueryOptions(
|
||||
agent.graph_id,
|
||||
).queryKey,
|
||||
});
|
||||
|
||||
setShowDeleteDialog(false);
|
||||
onDeleted?.();
|
||||
} catch (error: unknown) {
|
||||
toast({
|
||||
title: "Failed to delete schedule",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useGetV2GetLibraryAgent } from "@/app/api/__generated__/endpoints/library/library";
|
||||
import { useGetV2GetASpecificPreset } from "@/app/api/__generated__/endpoints/presets/presets";
|
||||
import { useGetV1ListExecutionSchedulesForAGraph } from "@/app/api/__generated__/endpoints/schedules/schedules";
|
||||
import { GraphExecutionJobInfo } from "@/app/api/__generated__/models/graphExecutionJobInfo";
|
||||
import { GraphExecutionMeta } from "@/app/api/__generated__/models/graphExecutionMeta";
|
||||
import { LibraryAgentPreset } from "@/app/api/__generated__/models/libraryAgentPreset";
|
||||
@@ -122,6 +123,37 @@ export function useNewAgentLibraryView() {
|
||||
});
|
||||
}
|
||||
|
||||
const { data: schedules } = useGetV1ListExecutionSchedulesForAGraph(
|
||||
agent?.graph_id || "",
|
||||
{
|
||||
query: {
|
||||
enabled: !!agent?.graph_id,
|
||||
select: okData,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
function handleScheduleDeleted(deletedScheduleId: string) {
|
||||
if (activeItem !== deletedScheduleId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!schedules) {
|
||||
handleClearSelectedRun();
|
||||
return;
|
||||
}
|
||||
|
||||
const remainingSchedules = schedules.filter(
|
||||
(s) => s.id !== deletedScheduleId,
|
||||
);
|
||||
|
||||
if (remainingSchedules.length > 0) {
|
||||
handleSelectRun(remainingSchedules[0].id, "scheduled");
|
||||
} else {
|
||||
handleClearSelectedRun();
|
||||
}
|
||||
}
|
||||
|
||||
function handleSetActiveTab(
|
||||
tab: "runs" | "scheduled" | "templates" | "triggers",
|
||||
) {
|
||||
@@ -205,6 +237,7 @@ export function useNewAgentLibraryView() {
|
||||
activeTab,
|
||||
setActiveTab: handleSetActiveTab,
|
||||
handleClearSelectedRun,
|
||||
handleScheduleDeleted,
|
||||
handleCountsChange,
|
||||
handleSelectRun,
|
||||
handleSelectSettings,
|
||||
|
||||
Reference in New Issue
Block a user