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:
Bently
2026-03-11 09:01:55 +00:00
committed by GitHub
parent 34a2f9a0a2
commit 0633475915
6 changed files with 50 additions and 10 deletions

View File

@@ -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" ? (

View File

@@ -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}

View File

@@ -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({

View File

@@ -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>
))

View File

@@ -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",

View File

@@ -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,