mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-06-15 23:34:00 +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";
|
} from "@/lib/types";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { fetchSettings, fetchWorkspaces } from "@/lib/api";
|
import { fetchSessionAutomations, fetchSettings, fetchWorkspaces } from "@/lib/api";
|
||||||
import {
|
import {
|
||||||
createRuntimeHost,
|
createRuntimeHost,
|
||||||
getHostApi,
|
getHostApi,
|
||||||
@ -548,7 +548,6 @@ function Shell({
|
|||||||
key: string;
|
key: string;
|
||||||
label: string;
|
label: string;
|
||||||
automations?: SessionAutomationJob[];
|
automations?: SessionAutomationJob[];
|
||||||
confirmAutomations?: boolean;
|
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
const [pendingRename, setPendingRename] = useState<{
|
const [pendingRename, setPendingRename] = useState<{
|
||||||
key: string;
|
key: string;
|
||||||
@ -1273,6 +1272,7 @@ function Shell({
|
|||||||
const onConfirmDelete = useCallback(async () => {
|
const onConfirmDelete = useCallback(async () => {
|
||||||
if (!pendingDelete) return;
|
if (!pendingDelete) return;
|
||||||
const key = pendingDelete.key;
|
const key = pendingDelete.key;
|
||||||
|
const hasAutomations = (pendingDelete.automations?.length ?? 0) > 0;
|
||||||
const deletingActive = activeKey === key;
|
const deletingActive = activeKey === key;
|
||||||
const currentIndex = sessions.findIndex((s) => s.key === key);
|
const currentIndex = sessions.findIndex((s) => s.key === key);
|
||||||
const fallbackKey = deletingActive
|
const fallbackKey = deletingActive
|
||||||
@ -1281,13 +1281,12 @@ function Shell({
|
|||||||
try {
|
try {
|
||||||
const result = await deleteChat(
|
const result = await deleteChat(
|
||||||
key,
|
key,
|
||||||
pendingDelete.confirmAutomations ? { deleteAutomations: true } : undefined,
|
hasAutomations ? { deleteAutomations: true } : undefined,
|
||||||
);
|
);
|
||||||
if (result.blocked_by_automations) {
|
if (result.blocked_by_automations) {
|
||||||
setPendingDelete({
|
setPendingDelete({
|
||||||
...pendingDelete,
|
...pendingDelete,
|
||||||
automations: result.automations ?? [],
|
automations: result.automations ?? [],
|
||||||
confirmAutomations: true,
|
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1304,6 +1303,16 @@ function Shell({
|
|||||||
}
|
}
|
||||||
}, [pendingDelete, deleteChat, activeKey, navigate, sessions]);
|
}, [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
|
const headerTitle = activeSession
|
||||||
? sidebarState.title_overrides[activeSession.key] ||
|
? sidebarState.title_overrides[activeSession.key] ||
|
||||||
activeSession.title ||
|
activeSession.title ||
|
||||||
@ -1340,8 +1349,7 @@ function Shell({
|
|||||||
loading,
|
loading,
|
||||||
onNewChat,
|
onNewChat,
|
||||||
onSelect: onSelectChat,
|
onSelect: onSelectChat,
|
||||||
onRequestDelete: (key: string, label: string) =>
|
onRequestDelete,
|
||||||
setPendingDelete({ key, label }),
|
|
||||||
onTogglePin,
|
onTogglePin,
|
||||||
onRequestRename,
|
onRequestRename,
|
||||||
onToggleArchive,
|
onToggleArchive,
|
||||||
@ -1566,7 +1574,7 @@ function Shell({
|
|||||||
<DeleteConfirm
|
<DeleteConfirm
|
||||||
open={!!pendingDelete}
|
open={!!pendingDelete}
|
||||||
title={pendingDelete?.label ?? ""}
|
title={pendingDelete?.label ?? ""}
|
||||||
automations={pendingDelete?.confirmAutomations ? pendingDelete.automations : undefined}
|
automations={pendingDelete?.automations}
|
||||||
onCancel={() => setPendingDelete(null)}
|
onCancel={() => setPendingDelete(null)}
|
||||||
onConfirm={onConfirmDelete}
|
onConfirm={onConfirmDelete}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -146,8 +146,9 @@ vi.mock("@/hooks/useSessions", async (importOriginal) => {
|
|||||||
refresh: refreshSpy,
|
refresh: refreshSpy,
|
||||||
createChat: createChatSpy,
|
createChat: createChatSpy,
|
||||||
forkChat: async () => "fork-chat",
|
forkChat: async () => "fork-chat",
|
||||||
deleteChat: async (key: string) => {
|
deleteChat: async (key: string, options?: { deleteAutomations?: boolean }) => {
|
||||||
await deleteChatSpy(key);
|
if (options === undefined) await deleteChatSpy(key);
|
||||||
|
else await deleteChatSpy(key, options);
|
||||||
setSessions((prev: ChatSummary[]) => prev.filter((s) => s.key !== key));
|
setSessions((prev: ChatSummary[]) => prev.filter((s) => s.key !== key));
|
||||||
return { deleted: true };
|
return { deleted: true };
|
||||||
},
|
},
|
||||||
@ -434,6 +435,73 @@ describe("App layout", () => {
|
|||||||
expect(document.body.style.pointerEvents).not.toBe("none");
|
expect(document.body.style.pointerEvents).not.toBe("none");
|
||||||
}, 15_000);
|
}, 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 () => {
|
it("keeps the mobile session action menu inside the sidebar sheet", async () => {
|
||||||
mockSessions = [
|
mockSessions = [
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user