Merge pull request #781 from ln-12/clear_history_and_notification_panel
feat: add clear buttons for request history and server notifications
This commit is contained in:
commit
7792125a02
6 changed files with 105 additions and 3 deletions
|
|
@ -200,6 +200,7 @@ const App = () => {
|
||||||
serverCapabilities,
|
serverCapabilities,
|
||||||
mcpClient,
|
mcpClient,
|
||||||
requestHistory,
|
requestHistory,
|
||||||
|
clearRequestHistory,
|
||||||
makeRequest,
|
makeRequest,
|
||||||
sendNotification,
|
sendNotification,
|
||||||
handleCompletion,
|
handleCompletion,
|
||||||
|
|
@ -736,6 +737,10 @@ const App = () => {
|
||||||
await sendNotification({ method: "notifications/roots/list_changed" });
|
await sendNotification({ method: "notifications/roots/list_changed" });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleClearNotifications = () => {
|
||||||
|
setNotifications([]);
|
||||||
|
};
|
||||||
|
|
||||||
const sendLogLevelRequest = async (level: LoggingLevel) => {
|
const sendLogLevelRequest = async (level: LoggingLevel) => {
|
||||||
await sendMCPRequest(
|
await sendMCPRequest(
|
||||||
{
|
{
|
||||||
|
|
@ -1102,6 +1107,8 @@ const App = () => {
|
||||||
<HistoryAndNotifications
|
<HistoryAndNotifications
|
||||||
requestHistory={requestHistory}
|
requestHistory={requestHistory}
|
||||||
serverNotifications={notifications}
|
serverNotifications={notifications}
|
||||||
|
onClearHistory={clearRequestHistory}
|
||||||
|
onClearNotifications={handleClearNotifications}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@ jest.mock("../lib/hooks/useConnection", () => ({
|
||||||
serverCapabilities: null,
|
serverCapabilities: null,
|
||||||
mcpClient: null,
|
mcpClient: null,
|
||||||
requestHistory: [],
|
requestHistory: [],
|
||||||
|
clearRequestHistory: jest.fn(),
|
||||||
makeRequest: jest.fn(),
|
makeRequest: jest.fn(),
|
||||||
sendNotification: jest.fn(),
|
sendNotification: jest.fn(),
|
||||||
handleCompletion: jest.fn(),
|
handleCompletion: jest.fn(),
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ const disconnectedConnectionState = {
|
||||||
serverCapabilities: null,
|
serverCapabilities: null,
|
||||||
mcpClient: null,
|
mcpClient: null,
|
||||||
requestHistory: [],
|
requestHistory: [],
|
||||||
|
clearRequestHistory: jest.fn(),
|
||||||
makeRequest: jest.fn(),
|
makeRequest: jest.fn(),
|
||||||
sendNotification: jest.fn(),
|
sendNotification: jest.fn(),
|
||||||
handleCompletion: jest.fn(),
|
handleCompletion: jest.fn(),
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,18 @@
|
||||||
import { ServerNotification } from "@modelcontextprotocol/sdk/types.js";
|
import { ServerNotification } from "@modelcontextprotocol/sdk/types.js";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import JsonView from "./JsonView";
|
import JsonView from "./JsonView";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
||||||
const HistoryAndNotifications = ({
|
const HistoryAndNotifications = ({
|
||||||
requestHistory,
|
requestHistory,
|
||||||
serverNotifications,
|
serverNotifications,
|
||||||
|
onClearHistory,
|
||||||
|
onClearNotifications,
|
||||||
}: {
|
}: {
|
||||||
requestHistory: Array<{ request: string; response?: string }>;
|
requestHistory: Array<{ request: string; response?: string }>;
|
||||||
serverNotifications: ServerNotification[];
|
serverNotifications: ServerNotification[];
|
||||||
|
onClearHistory?: () => void;
|
||||||
|
onClearNotifications?: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const [expandedRequests, setExpandedRequests] = useState<{
|
const [expandedRequests, setExpandedRequests] = useState<{
|
||||||
[key: number]: boolean;
|
[key: number]: boolean;
|
||||||
|
|
@ -27,7 +32,17 @@ const HistoryAndNotifications = ({
|
||||||
return (
|
return (
|
||||||
<div className="bg-card overflow-hidden flex h-full">
|
<div className="bg-card overflow-hidden flex h-full">
|
||||||
<div className="flex-1 overflow-y-auto p-4 border-r">
|
<div className="flex-1 overflow-y-auto p-4 border-r">
|
||||||
<h2 className="text-lg font-semibold mb-4">History</h2>
|
<div className="flex items-center justify-between mb-4">
|
||||||
|
<h2 className="text-lg font-semibold">History</h2>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={onClearHistory}
|
||||||
|
disabled={requestHistory.length === 0}
|
||||||
|
>
|
||||||
|
Clear
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
{requestHistory.length === 0 ? (
|
{requestHistory.length === 0 ? (
|
||||||
<p className="text-sm text-gray-500 dark:text-gray-400 italic">
|
<p className="text-sm text-gray-500 dark:text-gray-400 italic">
|
||||||
No history yet
|
No history yet
|
||||||
|
|
@ -93,7 +108,17 @@ const HistoryAndNotifications = ({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 overflow-y-auto p-4">
|
<div className="flex-1 overflow-y-auto p-4">
|
||||||
<h2 className="text-lg font-semibold mb-4">Server Notifications</h2>
|
<div className="flex items-center justify-between mb-4">
|
||||||
|
<h2 className="text-lg font-semibold">Server Notifications</h2>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={onClearNotifications}
|
||||||
|
disabled={serverNotifications.length === 0}
|
||||||
|
>
|
||||||
|
Clear
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
{serverNotifications.length === 0 ? (
|
{serverNotifications.length === 0 ? (
|
||||||
<p className="text-sm text-gray-500 dark:text-gray-400 italic">
|
<p className="text-sm text-gray-500 dark:text-gray-400 italic">
|
||||||
No notifications yet
|
No notifications yet
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { render, screen, fireEvent } from "@testing-library/react";
|
import { render, screen, fireEvent, within } from "@testing-library/react";
|
||||||
|
import { useState } from "react";
|
||||||
import { describe, it, expect, jest } from "@jest/globals";
|
import { describe, it, expect, jest } from "@jest/globals";
|
||||||
import HistoryAndNotifications from "../HistoryAndNotifications";
|
import HistoryAndNotifications from "../HistoryAndNotifications";
|
||||||
import { ServerNotification } from "@modelcontextprotocol/sdk/types.js";
|
import { ServerNotification } from "@modelcontextprotocol/sdk/types.js";
|
||||||
|
|
@ -223,4 +224,66 @@ describe("HistoryAndNotifications", () => {
|
||||||
expect(screen.getByText("No history yet")).toBeTruthy();
|
expect(screen.getByText("No history yet")).toBeTruthy();
|
||||||
expect(screen.getByText("No notifications yet")).toBeTruthy();
|
expect(screen.getByText("No notifications yet")).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("clears request history when Clear is clicked", () => {
|
||||||
|
const Wrapper = () => {
|
||||||
|
const [history, setHistory] = useState(mockRequestHistory);
|
||||||
|
return (
|
||||||
|
<HistoryAndNotifications
|
||||||
|
requestHistory={history}
|
||||||
|
serverNotifications={[]}
|
||||||
|
onClearHistory={() => setHistory([])}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
render(<Wrapper />);
|
||||||
|
|
||||||
|
// Verify items are present initially
|
||||||
|
expect(screen.getByText("2. test/method2")).toBeTruthy();
|
||||||
|
expect(screen.getByText("1. test/method1")).toBeTruthy();
|
||||||
|
|
||||||
|
// Click Clear in History header (scoped by the History heading's container)
|
||||||
|
const historyHeader = screen.getByText("History");
|
||||||
|
const historyHeaderContainer = historyHeader.parentElement as HTMLElement;
|
||||||
|
const historyClearButton = within(historyHeaderContainer).getByRole(
|
||||||
|
"button",
|
||||||
|
{ name: "Clear" },
|
||||||
|
);
|
||||||
|
fireEvent.click(historyClearButton);
|
||||||
|
|
||||||
|
// History should now be empty
|
||||||
|
expect(screen.getByText("No history yet")).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("clears server notifications when Clear is clicked", () => {
|
||||||
|
const Wrapper = () => {
|
||||||
|
const [notifications, setNotifications] =
|
||||||
|
useState<ServerNotification[]>(mockNotifications);
|
||||||
|
return (
|
||||||
|
<HistoryAndNotifications
|
||||||
|
requestHistory={[]}
|
||||||
|
serverNotifications={notifications}
|
||||||
|
onClearNotifications={() => setNotifications([])}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
render(<Wrapper />);
|
||||||
|
|
||||||
|
// Verify items are present initially
|
||||||
|
expect(screen.getByText("2. notifications/progress")).toBeTruthy();
|
||||||
|
expect(screen.getByText("1. notifications/message")).toBeTruthy();
|
||||||
|
|
||||||
|
// Click Clear in Server Notifications header (scoped by its heading's container)
|
||||||
|
const notifHeader = screen.getByText("Server Notifications");
|
||||||
|
const notifHeaderContainer = notifHeader.parentElement as HTMLElement;
|
||||||
|
const notifClearButton = within(notifHeaderContainer).getByRole("button", {
|
||||||
|
name: "Clear",
|
||||||
|
});
|
||||||
|
fireEvent.click(notifClearButton);
|
||||||
|
|
||||||
|
// Notifications should now be empty
|
||||||
|
expect(screen.getByText("No notifications yet")).toBeTruthy();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -653,11 +653,16 @@ export function useConnection({
|
||||||
setServerCapabilities(null);
|
setServerCapabilities(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const clearRequestHistory = () => {
|
||||||
|
setRequestHistory([]);
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
connectionStatus,
|
connectionStatus,
|
||||||
serverCapabilities,
|
serverCapabilities,
|
||||||
mcpClient,
|
mcpClient,
|
||||||
requestHistory,
|
requestHistory,
|
||||||
|
clearRequestHistory,
|
||||||
makeRequest,
|
makeRequest,
|
||||||
sendNotification,
|
sendNotification,
|
||||||
handleCompletion,
|
handleCompletion,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue