Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions src/components/DatasetPicker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import type { DatasetSummary } from "@aws-sdk/client-bedrock-agentcore-control";
import { useNavigate } from "react-router";
import type { ScreenProps } from "../handlers/types";
import { coreOptsFromCtx } from "../handlers/utils";
import { formatTimestamp } from "./formatTimestamp";
import { PaginatedTablePicker } from "./PaginatedTablePicker";
import type { DataTableColumn } from "./ui/data-table";

interface DatasetRow extends Record<string, unknown> {
datasetId: string;
datasetName: string;
status: string;
schemaType: string;
exampleCount: string;
updatedAt: string;
}

export const datasetColumns = [
{ key: "datasetName", header: "name", flex: true },
{ key: "status", header: "status", width: 14 },
{ key: "schemaType", header: "schema", width: 12 },
{ key: "exampleCount", header: "examples", width: 8 },
{
key: "updatedAt",
header: "updated UTC",
width: 16,
render: formatTimestamp,
},
] satisfies DataTableColumn<DatasetRow>[];

function displaySchemaType(schemaType: DatasetSummary["schemaType"]): string {
if (schemaType === "AGENTCORE_EVALUATION_PREDEFINED_V1") return "predefined";
if (schemaType === "AGENTCORE_EVALUATION_SIMULATED_V1") return "simulated";
return schemaType ?? "-";
}

function toRow(dataset: DatasetSummary): DatasetRow {
const id = dataset.datasetId ?? "";
return {
datasetId: id,
datasetName: dataset.datasetName ?? id,
status: dataset.status ?? "-",
schemaType: displaySchemaType(dataset.schemaType),
exampleCount: dataset.exampleCount?.toString() ?? "-",
updatedAt: dataset.updatedAt?.toISOString() ?? "-",
};
}

export interface DatasetPickerProps extends ScreenProps {
breadcrumb: string[];
description?: string;
onSelect: (datasetId: string) => void;
onEscape?: () => void;
}

export function DatasetPicker({
ctx,
core,
breadcrumb,
description,
onSelect,
onEscape,
}: DatasetPickerProps) {
const opts = coreOptsFromCtx(ctx);
const navigate = useNavigate();
const goBack = onEscape ?? (() => navigate("/" + breadcrumb.slice(0, -1).join("/")));

return (
<PaginatedTablePicker
breadcrumb={breadcrumb}
description={description}
queryKey={["datasets", opts.region]}
loadPage={async (token, pageSize) => {
const response = await core.eval.listDatasets(token, pageSize, opts);
return {
items: response.datasets ?? [],
nextToken: response.nextToken,
};
}}
toRow={toRow}
columns={datasetColumns}
getValue={(row) => row.datasetId}
onSelect={onSelect}
onBack={goBack}
loadingMessage="Loading datasets…"
errorMessage={(error) => `Error: ${error.message}`}
emptyMessage="No datasets found in this Region."
emptyPageMessage="No datasets on this page."
/>
);
}
20 changes: 20 additions & 0 deletions src/components/Root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ import {
import { BatchEvaluationScreen } from "../handlers/eval/batch-evaluation/screen.tsx";
import { BatchEvaluationListScreen } from "../handlers/eval/batch-evaluation/list/screen.tsx";
import { BatchEvaluationGetJsonScreen } from "../handlers/eval/batch-evaluation/get/screen.tsx";
import { DatasetScreen } from "../handlers/eval/dataset/screen.tsx";
import { DatasetListScreen } from "../handlers/eval/dataset/list/screen.tsx";
import { DatasetGetScreen, DatasetGetJsonScreen } from "../handlers/eval/dataset/get/screen.tsx";
import { MemoryEventScreen } from "../handlers/memory/event/screen.tsx";
import { MemoryEventGetScreen } from "../handlers/memory/event/get/screen.tsx";
import { MemoryEventListScreen } from "../handlers/memory/event/list/screen.tsx";
Expand Down Expand Up @@ -458,6 +461,23 @@ export function Root({ path, ctx, core, queryClient }: RootProps) {
path="agentcore/eval/online-eval/get/:configId/json"
element={<OnlineEvalGetJsonScreen ctx={ctx} core={core} />}
/>
<Route path="agentcore/eval/dataset" element={<DatasetScreen ctx={ctx} core={core} />} />
<Route
path="agentcore/eval/dataset/list"
element={<DatasetListScreen ctx={ctx} core={core} />}
/>
<Route
path="agentcore/eval/dataset/get"
element={<Navigate to="/agentcore/eval/dataset/list" replace />}
/>
<Route
path="agentcore/eval/dataset/get/:datasetId"
element={<DatasetGetScreen ctx={ctx} core={core} />}
/>
<Route
path="agentcore/eval/dataset/get/:datasetId/json"
element={<DatasetGetJsonScreen ctx={ctx} core={core} />}
/>
<Route
path="agentcore/eval/batch-evaluation"
element={<BatchEvaluationScreen ctx={ctx} core={core} />}
Expand Down
195 changes: 195 additions & 0 deletions src/handlers/eval/dataset/dataset.screen.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
import { afterEach, describe, expect, test } from "bun:test";
import type { DatasetSummary, GetDatasetResponse } from "@aws-sdk/client-bedrock-agentcore-control";
import {
cleanupScreens,
renderScreen,
TestCoreClient,
waitFor,
waitForText,
} from "../../../testing";

afterEach(cleanupScreens);

const evalEndpointUrl = "https://eval.test";

function datasetSummary(overrides: Partial<DatasetSummary> = {}): DatasetSummary {
return {
datasetArn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:dataset/dataset-1",
datasetId: "dataset-1",
datasetName: "orders-regression",
status: "ACTIVE",
draftStatus: "MODIFIED",
schemaType: "AGENTCORE_EVALUATION_PREDEFINED_V1",
exampleCount: 2,
createdAt: new Date("2026-08-01T01:02:03.000Z"),
updatedAt: new Date("2026-08-02T12:34:56.000Z"),
...overrides,
};
}

function getDatasetResponse(overrides: Partial<GetDatasetResponse> = {}): GetDatasetResponse {
return {
datasetArn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:dataset/dataset-1",
datasetId: "dataset-1",
datasetVersion: "DRAFT",
datasetName: "orders-regression",
description: "Regression tests for the order-support agent",
status: "ACTIVE",
draftStatus: "MODIFIED",
schemaType: "AGENTCORE_EVALUATION_PREDEFINED_V1",
exampleCount: 2,
createdAt: new Date("2026-08-01T01:02:03.000Z"),
updatedAt: new Date("2026-08-02T12:34:56.000Z"),
tags: { team: "agentcore" },
...overrides,
};
}

function coreWithDatasets(datasets: DatasetSummary[]): TestCoreClient {
const core = new TestCoreClient();
core.eval.setListDatasetsResponse({ datasets });
return core;
}

describe("dataset menu", () => {
test("offers only the read-only commands", async () => {
const screen = renderScreen("/agentcore/eval/dataset");

await waitForText(screen.lastFrame, "get a dataset's metadata");
const frame = screen.lastFrame()!;
expect(frame).toContain("list");
expect(frame).not.toContain("create");
expect(frame).not.toContain("update");
expect(frame).not.toContain("publish");
expect(frame).not.toContain("delete");
});
});

describe("dataset picker", () => {
test("renders name, status, schema, example count, and update time", async () => {
const core = coreWithDatasets([
datasetSummary({
datasetName: "staging-regression",
status: "UPDATE_FAILED",
schemaType: "AGENTCORE_EVALUATION_SIMULATED_V1",
exampleCount: 17,
updatedAt: new Date("2026-08-03T02:03:04.000Z"),
}),
]);
const screen = renderScreen("/agentcore/eval/dataset/list", { core });

await waitForText(screen.lastFrame, "staging-regression");
const frame = screen.lastFrame()!;
expect(frame).toContain("UPDATE_FAILED");
expect(frame).toContain("simulated");
expect(frame).toMatch(/17\s+2026-08-03 02:03/);
});

test("calls listDatasets with exact Core options", async () => {
const core = coreWithDatasets([datasetSummary()]);
renderScreen("/agentcore/eval/dataset/list", { core, endpointUrl: evalEndpointUrl });

await waitFor(() => core.eval.calls.some((call) => call.method === "listDatasets"));
expect(core.eval.calls.filter((call) => call.method === "listDatasets")).toEqual([
{
method: "listDatasets",
args: [
undefined,
expect.any(Number),
{ region: "us-east-1", endpointUrl: evalEndpointUrl },
],
},
]);
});

test("bare dataset get redirects to the picker", async () => {
const core = coreWithDatasets([
datasetSummary({ datasetId: "redirected-dataset", datasetName: "redirected-dataset" }),
]);
const screen = renderScreen("/agentcore/eval/dataset/get", { core });

await waitForText(screen.lastFrame, "redirected-dataset");
expect(core.eval.calls[0]?.method).toBe("listDatasets");
});

test("selection opens the matching dataset detail", async () => {
const core = coreWithDatasets([datasetSummary({ datasetId: "dataset-1" })]);
core.eval.setGetDatasetResponse(getDatasetResponse({ datasetId: "dataset-1" }));
const screen = renderScreen("/agentcore/eval/dataset/list", { core });

await waitForText(screen.lastFrame, "orders-regression");
await screen.press("return");
await waitForText(screen.lastFrame, "agentcore → eval → dataset → get → dataset-1");
await waitFor(() =>
core.eval.calls.some((call) => call.method === "getDataset" && call.args[0] === "dataset-1"),
);
});

test("shows the empty state", async () => {
const screen = renderScreen("/agentcore/eval/dataset/list");
await waitForText(screen.lastFrame, "No datasets found in this Region.");
});
});

describe("dataset detail", () => {
test("renders DRAFT metadata without downloading examples", async () => {
const core = new TestCoreClient();
core.eval.setGetDatasetResponse(getDatasetResponse());
const screen = renderScreen("/agentcore/eval/dataset/get/dataset-1", {
core,
endpointUrl: evalEndpointUrl,
});

await waitForText(screen.lastFrame, "show the full JSON metadata");
const frame = screen.lastFrame()!;
expect(frame).toContain("orders-regression");
expect(frame).toMatch(/version\s+DRAFT/);
expect(frame).toMatch(/draftStatus\s+MODIFIED/);
expect(frame).toMatch(/examples\s+2/);
expect(core.eval.calls).toEqual([
{
method: "getDataset",
args: ["dataset-1", undefined, { region: "us-east-1", endpointUrl: evalEndpointUrl }],
},
]);
});

test("shows failure details when present", async () => {
const core = new TestCoreClient();
core.eval.setGetDatasetResponse(
getDatasetResponse({
status: "UPDATE_FAILED",
failureReason: "The source could not be read",
}),
);
const screen = renderScreen("/agentcore/eval/dataset/get/dataset-1", { core });

await waitForText(screen.lastFrame, "The source could not be read");
expect(screen.lastFrame()).toContain("UPDATE_FAILED");
});

test("opens the complete dataset JSON", async () => {
const core = new TestCoreClient();
core.eval.setGetDatasetResponse(getDatasetResponse());
const screen = renderScreen("/agentcore/eval/dataset/get/dataset-1", { core });

await waitForText(screen.lastFrame, "show the full JSON metadata");
await screen.press("return");
await waitForText(screen.lastFrame, "agentcore → eval → dataset → get → dataset-1 → json");
expect(screen.lastFrame()).toContain('"team"');
});

test("retries a failed detail query", async () => {
const core = new TestCoreClient();
core.eval.setError(new Error("dataset unavailable"));
const screen = renderScreen("/agentcore/eval/dataset/get/dataset-1", { core });

await waitForText(screen.lastFrame, "dataset unavailable");
expect(screen.lastFrame()).toContain("[r] retry");

core.eval.setError(undefined);
core.eval.setGetDatasetResponse(getDatasetResponse());
await screen.write("r");
await waitForText(screen.lastFrame, "show the full JSON metadata");
});
});
19 changes: 19 additions & 0 deletions src/handlers/eval/dataset/dataset.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,25 @@ describe("eval dataset command hierarchy", () => {
expect(stdout()).toContain("Usage: agentcore eval dataset");
expect(core.eval.calls).toHaveLength(0);
});

test.each([["get"], ["list"]] as const)(
"opens the TUI for a bare `eval dataset %s` leaf",
async (command) => {
const { route } = testDatasetCommand();

await expect(route(["eval", "dataset", command])).rejects.toThrow(
"interactive mode requires a TTY on stdin and stdout",
);
},
);

test("runs normal validation for a bare CLI-only dataset command", async () => {
const { route } = testDatasetCommand();

await expect(route(["eval", "dataset", "update"])).rejects.toThrow(
"required option '--id <id>' not specified",
);
});
});

describe("dataset create", () => {
Expand Down
Loading
Loading