Compare commits

...

5 Commits

Author SHA1 Message Date
claude[bot]
2f063fd99b refactor(frontend): Replace Lucide icons with Phosphor icons and use Text component
- Update admin layout.tsx to use Phosphor icons instead of Lucide:
  - Users, CurrencyDollar, UserFocus, FileText, Database, Faders
- Update test-data page.tsx to use Text component for all text elements
- Update GenerateTestDataButton.tsx warning and result text with Text component

Co-authored-by: Ubbe <0ubbe@users.noreply.github.com>
2026-01-17 09:33:37 +00:00
claude[bot]
79c50dd79b refactor(frontend): Replace legacy components with new design system
- Replace Button from __legacy__/ui/button with atoms/Button/Button
- Replace Dialog from __legacy__/ui/dialog with molecules/Dialog/Dialog
- Replace Select from __legacy__/ui/select with atoms/Select/Select
- Replace Label from __legacy__/ui/label with atoms/Text/Text
- Update component usage to match new API patterns

Co-authored-by: Nicholas Tindle <ntindle@users.noreply.github.com>
2026-01-17 04:52:43 +00:00
claude[bot]
635c057e82 fix(ci): Fix linting issues in test data generation feature
- Fix Prettier formatting in admin layout.tsx (break long import line)
- Add type: ignore comments for dynamic test script imports in pyright

Co-authored-by: Nicholas Tindle <ntindle@users.noreply.github.com>
2026-01-17 00:02:14 +00:00
Claude
e954cab6a2 refactor(admin): Use OpenAPI generated types and add production safeguard
- Add production environment check with Discord alert
- Block test data generation in production with security alert
- Use OpenAPI generated hooks and types instead of manual definitions
- Remove manual types from types.ts and client.ts
- Update component to use usePostAdminGenerateTestData mutation hook

Note: Run `npm run generate:api` after deploying backend to generate
the new types from the OpenAPI spec.
2026-01-16 19:06:56 +00:00
Claude
af5e462caf feat(admin): Add test data generation button to admin panel
Add a new admin page that allows administrators to generate test data
for the platform. This feature includes:

- Backend API endpoint at /api/admin/admin/generate-test-data
- Support for two script types: E2E (15 users) and Full (100+ users)
- Frontend admin page with button and dialog to trigger generation
- Navigation link in the admin sidebar

The test data generation runs the existing test data scripts
(e2e_test_data.py or test_data_creator.py) from the backend.
2026-01-16 18:52:04 +00:00
6 changed files with 536 additions and 8 deletions

View File

@@ -0,0 +1,170 @@
import logging
from enum import Enum
from autogpt_libs.auth import get_user_id, requires_admin_user
from fastapi import APIRouter, Security
from pydantic import BaseModel
from backend.util.metrics import DiscordChannel, discord_send_alert
from backend.util.settings import AppEnvironment, Settings
logger = logging.getLogger(__name__)
settings = Settings()
class TestDataScriptType(str, Enum):
"""Available test data generation scripts."""
FULL = "full" # test_data_creator.py - creates 100+ users, comprehensive data
E2E = "e2e" # e2e_test_data.py - creates 15 users with API functions
class GenerateTestDataRequest(BaseModel):
"""Request model for test data generation."""
script_type: TestDataScriptType = TestDataScriptType.E2E
class GenerateTestDataResponse(BaseModel):
"""Response model for test data generation."""
success: bool
message: str
details: dict | None = None
router = APIRouter(
prefix="/admin",
tags=["admin", "test-data"],
dependencies=[Security(requires_admin_user)],
)
@router.post(
"/generate-test-data",
response_model=GenerateTestDataResponse,
summary="Generate Test Data",
)
async def generate_test_data(
request: GenerateTestDataRequest,
admin_user_id: str = Security(get_user_id),
) -> GenerateTestDataResponse:
"""
Generate test data for the platform.
This endpoint runs the test data generation scripts to populate the database
with sample users, agents, graphs, executions, store listings, and more.
Available script types:
- `e2e`: Creates 15 test users with graphs, library agents, presets, and store submissions.
Uses API functions for better compatibility. (Recommended)
- `full`: Creates 100+ users with comprehensive test data using direct Prisma calls.
Generates more data but may take longer.
**Warning**: This will add significant data to your database. Use with caution.
**Note**: This endpoint is disabled in production environments.
"""
# Block execution in production environment
if settings.config.app_env == AppEnvironment.PRODUCTION:
alert_message = (
f"🚨 **SECURITY ALERT**: Test data generation attempted in PRODUCTION!\n"
f"Admin User ID: `{admin_user_id}`\n"
f"Script Type: `{request.script_type}`\n"
f"Action: Request was blocked."
)
logger.warning(
f"Test data generation blocked in production. Admin: {admin_user_id}"
)
# Send Discord alert
try:
await discord_send_alert(alert_message, DiscordChannel.PLATFORM)
except Exception as e:
logger.error(f"Failed to send Discord alert: {e}")
return GenerateTestDataResponse(
success=False,
message="Test data generation is disabled in production environments.",
)
logger.info(
f"Admin user {admin_user_id} is generating test data with script type: {request.script_type}"
)
try:
if request.script_type == TestDataScriptType.E2E:
from backend.data.db import prisma
# Import and run the E2E test data creator
# We need to import within the function to avoid circular imports
import sys
from pathlib import Path
# Add the test directory to the path
test_dir = Path(__file__).parent.parent.parent.parent.parent / "test"
sys.path.insert(0, str(test_dir))
try:
from e2e_test_data import TestDataCreator # type: ignore[import-not-found]
# Connect to database if not already connected
if not prisma.is_connected():
await prisma.connect()
creator = TestDataCreator()
await creator.create_all_test_data()
return GenerateTestDataResponse(
success=True,
message="E2E test data generated successfully",
details={
"users_created": len(creator.users),
"graphs_created": len(creator.agent_graphs),
"library_agents_created": len(creator.library_agents),
"store_submissions_created": len(creator.store_submissions),
"presets_created": len(creator.presets),
"api_keys_created": len(creator.api_keys),
},
)
finally:
# Remove the test directory from the path
if str(test_dir) in sys.path:
sys.path.remove(str(test_dir))
elif request.script_type == TestDataScriptType.FULL:
# Import and run the full test data creator
import sys
from pathlib import Path
test_dir = Path(__file__).parent.parent.parent.parent.parent / "test"
sys.path.insert(0, str(test_dir))
try:
from test_data_creator import main as create_full_test_data # type: ignore[import-not-found]
await create_full_test_data()
return GenerateTestDataResponse(
success=True,
message="Full test data generated successfully",
details={
"script": "test_data_creator.py",
"note": "Created 100+ users with comprehensive test data",
},
)
finally:
if str(test_dir) in sys.path:
sys.path.remove(str(test_dir))
else:
return GenerateTestDataResponse(
success=False,
message=f"Unknown script type: {request.script_type}",
)
except Exception as e:
logger.exception(f"Error generating test data: {e}")
return GenerateTestDataResponse(
success=False,
message=f"Failed to generate test data: {str(e)}",
)

View File

@@ -19,6 +19,7 @@ from prisma.errors import PrismaError
import backend.api.features.admin.credit_admin_routes
import backend.api.features.admin.execution_analytics_routes
import backend.api.features.admin.store_admin_routes
import backend.api.features.admin.test_data_routes
import backend.api.features.builder
import backend.api.features.builder.routes
import backend.api.features.chat.routes as chat_routes
@@ -293,6 +294,11 @@ app.include_router(
tags=["v2", "admin"],
prefix="/api/executions",
)
app.include_router(
backend.api.features.admin.test_data_routes.router,
tags=["v2", "admin"],
prefix="/api/admin",
)
app.include_router(
backend.api.features.executions.review.routes.router,
tags=["v2", "executions", "review"],

View File

@@ -1,7 +1,12 @@
import { Sidebar } from "@/components/__legacy__/Sidebar";
import { Users, DollarSign, UserSearch, FileText } from "lucide-react";
import { IconSliders } from "@/components/__legacy__/ui/icons";
import {
Users,
CurrencyDollar,
UserFocus,
FileText,
Database,
Faders,
} from "@phosphor-icons/react";
const sidebarLinkGroups = [
{
@@ -9,27 +14,32 @@ const sidebarLinkGroups = [
{
text: "Marketplace Management",
href: "/admin/marketplace",
icon: <Users className="h-6 w-6" />,
icon: <Users size={24} />,
},
{
text: "User Spending",
href: "/admin/spending",
icon: <DollarSign className="h-6 w-6" />,
icon: <CurrencyDollar size={24} />,
},
{
text: "User Impersonation",
href: "/admin/impersonation",
icon: <UserSearch className="h-6 w-6" />,
icon: <UserFocus size={24} />,
},
{
text: "Execution Analytics",
href: "/admin/execution-analytics",
icon: <FileText className="h-6 w-6" />,
icon: <FileText size={24} />,
},
{
text: "Admin User Management",
href: "/admin/settings",
icon: <IconSliders className="h-6 w-6" />,
icon: <Faders size={24} />,
},
{
text: "Test Data",
href: "/admin/test-data",
icon: <Database size={24} />,
},
],
},

View File

@@ -0,0 +1,179 @@
"use client";
import { useState } from "react";
import { Button } from "@/components/atoms/Button/Button";
import { Dialog } from "@/components/molecules/Dialog/Dialog";
import { Select, SelectOption } from "@/components/atoms/Select/Select";
import { Text } from "@/components/atoms/Text/Text";
import { useToast } from "@/components/molecules/Toast/use-toast";
// Generated types and hooks from OpenAPI spec
// Run `npm run generate:api` to regenerate after backend changes
import { usePostAdminGenerateTestData } from "@/app/api/__generated__/endpoints/admin/admin";
import type { GenerateTestDataResponse } from "@/app/api/__generated__/models/generateTestDataResponse";
import type { TestDataScriptType } from "@/app/api/__generated__/models/testDataScriptType";
const scriptTypeOptions: SelectOption[] = [
{
value: "e2e",
label:
"E2E Test Data - 15 users with graphs, agents, and store submissions",
},
{
value: "full",
label: "Full Test Data - 100+ users with comprehensive data (takes longer)",
},
];
export function GenerateTestDataButton() {
const { toast } = useToast();
const [isDialogOpen, setIsDialogOpen] = useState(false);
const [scriptType, setScriptType] = useState<TestDataScriptType>("e2e");
const [result, setResult] = useState<GenerateTestDataResponse | null>(null);
const generateMutation = usePostAdminGenerateTestData({
mutation: {
onSuccess: (response) => {
const data = response.data;
setResult(data);
if (data.success) {
toast({
title: "Success",
description: data.message,
});
} else {
toast({
title: "Error",
description: data.message,
variant: "destructive",
});
}
},
onError: (error) => {
console.error("Error generating test data:", error);
const errorMessage =
error instanceof Error ? error.message : "Unknown error occurred";
setResult({
success: false,
message: `Failed to generate test data: ${errorMessage}`,
});
toast({
title: "Error",
description: "Failed to generate test data. Please try again.",
variant: "destructive",
});
},
},
});
const handleGenerate = () => {
setResult(null);
generateMutation.mutate({
data: {
script_type: scriptType,
},
});
};
const handleDialogClose = () => {
setIsDialogOpen(false);
};
return (
<>
<Button
size="large"
variant="primary"
onClick={() => {
setIsDialogOpen(true);
setResult(null);
}}
>
Generate Test Data
</Button>
<Dialog
title="Generate Test Data"
controlled={{
isOpen: isDialogOpen,
set: (open) => {
if (!open) handleDialogClose();
},
}}
styling={{ maxWidth: "32rem" }}
>
<Dialog.Content>
<Text variant="body" className="pb-4 text-neutral-600">
This will populate the database with sample test data including
users, agents, graphs, store listings, and more.
</Text>
<div className="grid gap-4 py-4">
<Select
label="Script Type"
id="scriptType"
value={scriptType}
onValueChange={(value) =>
setScriptType(value as TestDataScriptType)
}
disabled={generateMutation.isPending}
options={scriptTypeOptions}
/>
<div className="rounded-md bg-yellow-50 p-3 text-yellow-800">
<Text variant="small" as="span">
<Text variant="small-medium" as="span">
Warning:
</Text>{" "}
This will add significant data to your database. This endpoint
is disabled in production environments.
</Text>
</div>
{result && (
<div
className={`rounded-md p-3 ${
result.success
? "bg-green-50 text-green-800"
: "bg-red-50 text-red-800"
}`}
>
<Text variant="small-medium">{result.message}</Text>
{result.details && (
<ul className="mt-2 list-inside list-disc">
{Object.entries(result.details).map(([key, value]) => (
<li key={key}>
<Text variant="small" as="span">
{key.replace(/_/g, " ")}: {String(value)}
</Text>
</li>
))}
</ul>
)}
</div>
)}
</div>
<Dialog.Footer>
<Button
variant="outline"
onClick={handleDialogClose}
disabled={generateMutation.isPending}
>
Cancel
</Button>
<Button
variant="primary"
onClick={handleGenerate}
disabled={generateMutation.isPending}
loading={generateMutation.isPending}
>
{generateMutation.isPending
? "Generating..."
: "Generate Test Data"}
</Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog>
</>
);
}

View File

@@ -0,0 +1,162 @@
import { withRoleAccess } from "@/lib/withRoleAccess";
import { GenerateTestDataButton } from "./components/GenerateTestDataButton";
import { Text } from "@/components/atoms/Text/Text";
function TestDataDashboard() {
return (
<div className="mx-auto p-6">
<div className="flex flex-col gap-6">
<div className="flex items-center justify-between">
<div>
<Text variant="h1" className="text-3xl">
Test Data Generation
</Text>
<Text variant="body" className="text-gray-500">
Generate sample data for testing and development
</Text>
</div>
</div>
<div className="rounded-lg border bg-white p-6 shadow-sm">
<Text variant="h2" className="mb-4 text-xl">
Generate Test Data
</Text>
<Text variant="body" className="mb-6 text-gray-600">
Use this tool to populate the database with sample test data. This
is useful for development and testing purposes.
</Text>
<div className="mb-6">
<Text variant="body-medium" className="mb-2">
Available Script Types:
</Text>
<ul className="list-inside list-disc space-y-2 text-gray-600">
<li>
<Text variant="body" as="span">
<Text variant="body-medium" as="span">
E2E Test Data:
</Text>{" "}
Creates 15 test users with graphs, library agents, presets,
store submissions, and API keys. Uses API functions for better
compatibility.
</Text>
</li>
<li>
<Text variant="body" as="span">
<Text variant="body-medium" as="span">
Full Test Data:
</Text>{" "}
Creates 100+ users with comprehensive test data including
agent blocks, nodes, executions, analytics, and more. Takes
longer to complete.
</Text>
</li>
</ul>
</div>
<GenerateTestDataButton />
</div>
<div className="rounded-lg border bg-gray-50 p-6">
<Text variant="body-medium" className="mb-2 text-gray-700">
What data is created?
</Text>
<div className="grid gap-4 text-sm text-gray-600 md:grid-cols-2">
<div>
<Text variant="body-medium">E2E Script:</Text>
<ul className="mt-1 list-inside list-disc">
<li>
<Text variant="small" as="span">
15 test users
</Text>
</li>
<li>
<Text variant="small" as="span">
15 graphs per user
</Text>
</li>
<li>
<Text variant="small" as="span">
Library agents
</Text>
</li>
<li>
<Text variant="small" as="span">
Agent presets
</Text>
</li>
<li>
<Text variant="small" as="span">
Store submissions
</Text>
</li>
<li>
<Text variant="small" as="span">
API keys
</Text>
</li>
<li>
<Text variant="small" as="span">
Creator profiles
</Text>
</li>
</ul>
</div>
<div>
<Text variant="body-medium">Full Script:</Text>
<ul className="mt-1 list-inside list-disc">
<li>
<Text variant="small" as="span">
100 users
</Text>
</li>
<li>
<Text variant="small" as="span">
100 agent blocks
</Text>
</li>
<li>
<Text variant="small" as="span">
Multiple graphs per user
</Text>
</li>
<li>
<Text variant="small" as="span">
Agent nodes and links
</Text>
</li>
<li>
<Text variant="small" as="span">
Graph executions
</Text>
</li>
<li>
<Text variant="small" as="span">
Store listings and reviews
</Text>
</li>
<li>
<Text variant="small" as="span">
Analytics data
</Text>
</li>
<li>
<Text variant="small" as="span">
Credit transactions
</Text>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
);
}
export default async function TestDataDashboardPage() {
"use server";
const withAdminAccess = await withRoleAccess(["admin"]);
const ProtectedTestDataDashboard = await withAdminAccess(TestDataDashboard);
return <ProtectedTestDataDashboard />;
}

View File

@@ -1134,6 +1134,7 @@ export type AddUserCreditsResponse = {
new_balance: number;
transaction_key: string;
};
const _stringFormatToDataTypeMap: Partial<Record<string, DataType>> = {
date: DataType.DATE,
time: DataType.TIME,