mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-06-15 07:14:08 +00:00
fix: show cron bindings before deleting sessions
This commit is contained in:
parent
8335554894
commit
2248527971
@ -43,7 +43,7 @@ import type {
|
||||
} from "@/lib/types";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { fetchSettings, fetchWorkspaces } from "@/lib/api";
|
||||
import { fetchSessionAutomations, fetchSettings, fetchWorkspaces } from "@/lib/api";
|
||||
import {
|
||||
createRuntimeHost,
|
||||
getHostApi,
|
||||
@ -548,7 +548,6 @@ function Shell({
|
||||
key: string;
|
||||
label: string;
|
||||
automations?: SessionAutomationJob[];
|
||||
confirmAutomations?: boolean;
|
||||
} | null>(null);
|
||||
const [pendingRename, setPendingRename] = useState<{
|
||||
key: string;
|
||||
@ -1273,6 +1272,7 @@ function Shell({
|
||||
const onConfirmDelete = useCallback(async () => {
|
||||
if (!pendingDelete) return;
|
||||
const key = pendingDelete.key;
|
||||
const hasAutomations = (pendingDelete.automations?.length ?? 0) > 0;
|
||||
const deletingActive = activeKey === key;
|
||||
const currentIndex = sessions.findIndex((s) => s.key === key);
|
||||
const fallbackKey = deletingActive
|
||||
@ -1281,13 +1281,12 @@ function Shell({
|
||||
try {
|
||||
const result = await deleteChat(
|
||||
key,
|
||||
pendingDelete.confirmAutomations ? { deleteAutomations: true } : undefined,
|
||||
hasAutomations ? { deleteAutomations: true } : undefined,
|
||||
);
|
||||
if (result.blocked_by_automations) {
|
||||
setPendingDelete({
|
||||
...pendingDelete,
|
||||
automations: result.automations ?? [],
|
||||
confirmAutomations: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
@ -1304,6 +1303,16 @@ function Shell({
|
||||
}
|
||||
}, [pendingDelete, deleteChat, activeKey, navigate, sessions]);
|
||||
|
||||
const onRequestDelete = useCallback(async (key: string, label: string) => {
|
||||
let automations: SessionAutomationJob[] = [];
|
||||
try {
|
||||
automations = (await fetchSessionAutomations(token, key)).jobs;
|
||||
} catch {
|
||||
// Delete remains protected by the backend block; prefetch only improves the first prompt.
|
||||
}
|
||||
setPendingDelete({ key, label, automations });
|
||||
}, [token]);
|
||||
|
||||
const headerTitle = activeSession
|
||||
? sidebarState.title_overrides[activeSession.key] ||
|
||||
activeSession.title ||
|
||||
@ -1340,8 +1349,7 @@ function Shell({
|
||||
loading,
|
||||
onNewChat,
|
||||
onSelect: onSelectChat,
|
||||
onRequestDelete: (key: string, label: string) =>
|
||||
setPendingDelete({ key, label }),
|
||||
onRequestDelete,
|
||||
onTogglePin,
|
||||
onRequestRename,
|
||||
onToggleArchive,
|
||||
@ -1566,7 +1574,7 @@ function Shell({
|
||||
<DeleteConfirm
|
||||
open={!!pendingDelete}
|
||||
title={pendingDelete?.label ?? ""}
|
||||
automations={pendingDelete?.confirmAutomations ? pendingDelete.automations : undefined}
|
||||
automations={pendingDelete?.automations}
|
||||
onCancel={() => setPendingDelete(null)}
|
||||
onConfirm={onConfirmDelete}
|
||||
/>
|
||||
|
||||
@ -146,8 +146,9 @@ vi.mock("@/hooks/useSessions", async (importOriginal) => {
|
||||
refresh: refreshSpy,
|
||||
createChat: createChatSpy,
|
||||
forkChat: async () => "fork-chat",
|
||||
deleteChat: async (key: string) => {
|
||||
await deleteChatSpy(key);
|
||||
deleteChat: async (key: string, options?: { deleteAutomations?: boolean }) => {
|
||||
if (options === undefined) await deleteChatSpy(key);
|
||||
else await deleteChatSpy(key, options);
|
||||
setSessions((prev: ChatSummary[]) => prev.filter((s) => s.key !== key));
|
||||
return { deleted: true };
|
||||
},
|
||||
@ -434,6 +435,73 @@ describe("App layout", () => {
|
||||
expect(document.body.style.pointerEvents).not.toBe("none");
|
||||
}, 15_000);
|
||||
|
||||
it("shows bound automations in the first delete confirmation", async () => {
|
||||
mockSessions = [
|
||||
{
|
||||
key: "websocket:chat-a",
|
||||
channel: "websocket",
|
||||
chatId: "chat-a",
|
||||
createdAt: "2026-04-16T10:00:00Z",
|
||||
updatedAt: "2026-04-16T10:00:00Z",
|
||||
preview: "First chat",
|
||||
},
|
||||
{
|
||||
key: "websocket:chat-b",
|
||||
channel: "websocket",
|
||||
chatId: "chat-b",
|
||||
createdAt: "2026-04-16T11:00:00Z",
|
||||
updatedAt: "2026-04-16T11:00:00Z",
|
||||
preview: "Second chat",
|
||||
},
|
||||
];
|
||||
mockFetchRoutes({
|
||||
"/api/sessions/websocket%3Achat-a/automations": {
|
||||
jobs: [
|
||||
{
|
||||
id: "job-1",
|
||||
name: "Daily repo check",
|
||||
enabled: true,
|
||||
schedule: { kind: "every", every_ms: 86_400_000 },
|
||||
payload: { message: "Check the repo" },
|
||||
state: { next_run_at_ms: Date.UTC(2026, 3, 17, 10, 0, 0) },
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
render(<App />);
|
||||
|
||||
await waitFor(() => expect(connectSpy).toHaveBeenCalled());
|
||||
const sidebar = screen.getByRole("navigation", { name: "Sidebar navigation" });
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
within(sidebar).getByRole("button", { name: /^First chat$/ }),
|
||||
).toBeInTheDocument(),
|
||||
);
|
||||
|
||||
fireEvent.pointerDown(screen.getByLabelText("Chat actions for First chat"), {
|
||||
button: 0,
|
||||
});
|
||||
fireEvent.click(await screen.findByRole("menuitem", { name: "Delete" }));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.getByText("Daily repo check")).toBeInTheDocument(),
|
||||
);
|
||||
expect(
|
||||
screen.getByText("This chat has scheduled automations. Deleting it will also delete them."),
|
||||
).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Delete chat and automations" }));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(deleteChatSpy).toHaveBeenCalledWith("websocket:chat-a", {
|
||||
deleteAutomations: true,
|
||||
}),
|
||||
);
|
||||
expect(deleteChatSpy).toHaveBeenCalledTimes(1);
|
||||
expect(screen.queryByText("Daily repo check")).not.toBeInTheDocument();
|
||||
}, 15_000);
|
||||
|
||||
it("keeps the mobile session action menu inside the sidebar sheet", async () => {
|
||||
mockSessions = [
|
||||
{
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user