Unverified 提交 c599f4e5 authored 作者: Mohamed Aziz Mejri's avatar Mohamed Aziz Mejri 提交者: GitHub

Adding clear logs button (#2118)

<!-- This is an auto-generated description by cubic. --> ## Summary by cubic Added a Clear Logs button to the System Messages console to remove all logs for the selected app. Renamed the filters button to “Clear Filters” and updated e2e tests. - **New Features** - Console adds onClearLogs handler that calls IpcClient.clearLogs(selectedAppId), then clears appConsoleEntriesAtom; shows a toast on error. - E2E test verifies “Clear Logs” removes all entries; existing test updated to expect “Clear Filters” label. <sup>Written for commit cc7cafc267e13e05cdb6cc45b3122ac572fcb048. Summary will update on new commits.</sup> <!-- End of auto-generated description by cubic. -->
上级 e99cf759
...@@ -235,7 +235,7 @@ testSkipIfWindows("clear filters button works", async ({ po }) => { ...@@ -235,7 +235,7 @@ testSkipIfWindows("clear filters button works", async ({ po }) => {
await levelFilter.selectOption("error"); await levelFilter.selectOption("error");
// Check that clear button appears // Check that clear button appears
const clearButton = po.page.getByRole("button", { name: "Clear" }); const clearButton = po.page.getByRole("button", { name: "Clear Filters" });
await expect(clearButton).toBeVisible(); await expect(clearButton).toBeVisible();
// Click clear button // Click clear button
...@@ -245,3 +245,50 @@ testSkipIfWindows("clear filters button works", async ({ po }) => { ...@@ -245,3 +245,50 @@ testSkipIfWindows("clear filters button works", async ({ po }) => {
const filterValue = await levelFilter.inputValue(); const filterValue = await levelFilter.inputValue();
expect(filterValue).toBe("all"); expect(filterValue).toBe("all");
}); });
testSkipIfWindows("clear logs button clears all logs", async ({ po }) => {
await po.setUp();
// Create an app with console logs
await po.sendPrompt("tc=console-logs");
await po.approveProposal();
// Wait for app to run
const picker = po.page.getByTestId("preview-pick-element-button");
await expect(picker).toBeEnabled({ timeout: Timeout.EXTRA_LONG });
// Wait for iframe to load
const iframe = po.getPreviewIframeElement();
await expect(
iframe.contentFrame().getByText("Console Logs Test App"),
).toBeVisible({ timeout: Timeout.MEDIUM });
// Open the system messages console
const consoleHeader = po.page.locator('text="System Messages"').first();
await consoleHeader.click();
// Wait for logs to appear
await expect(async () => {
const allLogs = po.page.getByTestId("console-entry");
const count = await allLogs.count();
expect(count).toBeGreaterThan(0);
await expect(allLogs.first()).toBeVisible();
}).toPass({ timeout: Timeout.MEDIUM });
// Verify we have multiple logs before clearing
const logsBeforeClear = po.page.getByTestId("console-entry");
const countBeforeClear = await logsBeforeClear.count();
expect(countBeforeClear).toBeGreaterThan(0);
// Click the Clear Logs button
const clearLogsButton = po.page.getByTestId("clear-logs-button");
await expect(clearLogsButton).toBeVisible();
await clearLogsButton.click();
// Verify all logs are cleared
await expect(async () => {
const logsAfterClear = po.page.getByTestId("console-entry");
const countAfterClear = await logsAfterClear.count();
expect(countAfterClear).toBe(0);
}).toPass({ timeout: Timeout.MEDIUM });
});
import { appConsoleEntriesAtom } from "@/atoms/appAtoms"; import { appConsoleEntriesAtom, selectedAppIdAtom } from "@/atoms/appAtoms";
import type { ConsoleEntry } from "@/ipc/ipc_types"; import type { ConsoleEntry } from "@/ipc/ipc_types";
import { useAtomValue } from "jotai"; import { useAtomValue, useSetAtom } from "jotai";
import { IpcClient } from "@/ipc/ipc_client";
import { useEffect, useRef, useState, useMemo, useCallback, memo } from "react"; import { useEffect, useRef, useState, useMemo, useCallback, memo } from "react";
import { Virtuoso, VirtuosoHandle } from "react-virtuoso"; import { Virtuoso, VirtuosoHandle } from "react-virtuoso";
import { ConsoleEntryComponent } from "./ConsoleEntry"; import { ConsoleEntryComponent } from "./ConsoleEntry";
import { ConsoleFilters } from "./ConsoleFilters"; import { ConsoleFilters } from "./ConsoleFilters";
import { useSettings } from "@/hooks/useSettings"; import { useSettings } from "@/hooks/useSettings";
import { showError } from "@/lib/toast";
// Placeholder component shown during fast scrolling // Placeholder component shown during fast scrolling
const ScrollSeekPlaceholder = () => { const ScrollSeekPlaceholder = () => {
...@@ -64,6 +66,8 @@ ConsoleItem.displayName = "ConsoleItem"; ...@@ -64,6 +66,8 @@ ConsoleItem.displayName = "ConsoleItem";
// Console component // Console component
export const Console = () => { export const Console = () => {
const consoleEntries = useAtomValue(appConsoleEntriesAtom); const consoleEntries = useAtomValue(appConsoleEntriesAtom);
const setConsoleEntries = useSetAtom(appConsoleEntriesAtom);
const selectedAppId = useAtomValue(selectedAppIdAtom);
const { settings } = useSettings(); const { settings } = useSettings();
const virtuosoRef = useRef<VirtuosoHandle>(null); const virtuosoRef = useRef<VirtuosoHandle>(null);
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
...@@ -104,6 +108,21 @@ export const Console = () => { ...@@ -104,6 +108,21 @@ export const Console = () => {
setSourceFilter(""); setSourceFilter("");
}; };
const handleClearLogs = useCallback(async () => {
if (selectedAppId) {
try {
// Clear logs from backend store
await IpcClient.getInstance().clearLogs(selectedAppId);
// Clear logs from UI
setConsoleEntries([]);
} catch (error) {
showError(
error instanceof Error ? error.message : "Failed to clear logs",
);
}
}
}, [selectedAppId, setConsoleEntries]);
useEffect(() => { useEffect(() => {
const container = containerRef.current?.parentElement; const container = containerRef.current?.parentElement;
if (!container) return; if (!container) return;
...@@ -219,6 +238,7 @@ export const Console = () => { ...@@ -219,6 +238,7 @@ export const Console = () => {
onTypeFilterChange={setTypeFilter} onTypeFilterChange={setTypeFilter}
onSourceFilterChange={setSourceFilter} onSourceFilterChange={setSourceFilter}
onClearFilters={handleClearFilters} onClearFilters={handleClearFilters}
onClearLogs={handleClearLogs}
uniqueSources={uniqueSources} uniqueSources={uniqueSources}
totalLogs={filteredEntries.length} totalLogs={filteredEntries.length}
showFilters={showFilters} showFilters={showFilters}
......
import { Filter, X } from "lucide-react"; import { Filter, X, Trash2 } from "lucide-react";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
interface ConsoleFiltersProps { interface ConsoleFiltersProps {
levelFilter: "all" | "info" | "warn" | "error"; levelFilter: "all" | "info" | "warn" | "error";
...@@ -22,6 +28,7 @@ interface ConsoleFiltersProps { ...@@ -22,6 +28,7 @@ interface ConsoleFiltersProps {
) => void; ) => void;
onSourceFilterChange: (value: string) => void; onSourceFilterChange: (value: string) => void;
onClearFilters: () => void; onClearFilters: () => void;
onClearLogs: () => void;
uniqueSources: string[]; uniqueSources: string[];
totalLogs: number; totalLogs: number;
showFilters: boolean; showFilters: boolean;
...@@ -35,6 +42,7 @@ export const ConsoleFilters = ({ ...@@ -35,6 +42,7 @@ export const ConsoleFilters = ({
onTypeFilterChange, onTypeFilterChange,
onSourceFilterChange, onSourceFilterChange,
onClearFilters, onClearFilters,
onClearLogs,
uniqueSources, uniqueSources,
totalLogs, totalLogs,
showFilters, showFilters,
...@@ -111,10 +119,26 @@ export const ConsoleFilters = ({ ...@@ -111,10 +119,26 @@ export const ConsoleFilters = ({
className="text-xs px-2 py-1 flex items-center gap-1 border border-border rounded bg-transparent hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors" className="text-xs px-2 py-1 flex items-center gap-1 border border-border rounded bg-transparent hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
> >
<X size={12} /> <X size={12} />
Clear Clear Filters
</button> </button>
)} )}
{/* Clear logs button */}
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<button
onClick={onClearLogs}
className="p-1 border border-border rounded bg-transparent hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
data-testid="clear-logs-button"
>
<Trash2 size={14} />
</button>
</TooltipTrigger>
<TooltipContent>Clear logs</TooltipContent>
</Tooltip>
</TooltipProvider>
<div className="ml-auto text-xs text-gray-500">{totalLogs} logs</div> <div className="ml-auto text-xs text-gray-500">{totalLogs} logs</div>
</div> </div>
); );
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论