mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-01-20 04:28:09 -05:00
Compare commits
5 Commits
dev
...
claude/add
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f063fd99b | ||
|
|
79c50dd79b | ||
|
|
635c057e82 | ||
|
|
e954cab6a2 | ||
|
|
af5e462caf |
@@ -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)}",
|
||||
)
|
||||
@@ -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"],
|
||||
|
||||
@@ -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} />,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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 />;
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user