Files
elink-test-master/TestMasterUI/src/components/LoadTestResult.jsx
T
2025-01-21 13:19:21 +09:00

170 lines
5.7 KiB
React

import React, { useState, useEffect, useRef, useCallback } from 'react';
import { Client } from '@stomp/stompjs';
import SockJS from 'sockjs-client';
import { Card, CardHeader, CardContent } from '@/components/ui/card';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Button } from '@/components/ui/button';
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs';
import { cn } from '@/lib/utils';
import { useMonaco } from '@monaco-editor/react';
const LoadTestResult = ({ contextPath = '/', testId }) => {
const [results, setResults] = useState([]);
const [selectedResult, setSelectedResult] = useState(null);
const [activeTab, setActiveTab] = useState('body');
const stompClientRef = useRef(null);
const monaco = useMonaco();
const editorRef = useRef(null);
useEffect(() => {
// Initialize Monaco editor
if (monaco && !editorRef.current) {
editorRef.current = monaco.editor.create(document.getElementById('test-output'), {
value: '',
language: 'json',
automaticLayout: true,
readOnly: true,
minimap: { enabled: false },
theme: 'vs-dark'
});
}
return () => {
if (editorRef.current) {
editorRef.current.dispose();
editorRef.current = null;
}
};
}, [monaco]);
useEffect(() => {
if (!testId) return;
// Initialize WebSocket connection
const socket = new SockJS(`${contextPath}load-test-result`);
const client = new Client({
webSocketFactory: () => socket,
onConnect: () => {
client.subscribe(`/topic/${testId}`, (message) => {
const result = JSON.parse(message.body);
setResults(prev => [...prev, result]);
});
},
onStompError: (error) => {
console.error('STOMP error:', error);
}
});
stompClientRef.current = client;
client.activate();
return () => {
if (client.connected) {
client.deactivate();
}
};
}, [testId, contextPath]);
const reset = useCallback(() => {
setResults([]);
setSelectedResult(null);
if (editorRef.current) {
editorRef.current.setValue('');
}
}, []);
const formatResponse = useCallback((response) => {
if (!response) return '';
if (response.headers) {
const sortedHeaders = Object.entries(response.headers)
.sort(([a], [b]) => a.localeCompare(b))
.map(([key, value]) => `${key}: ${value}`)
.join('\n');
return sortedHeaders;
}
try {
return JSON.stringify(JSON.parse(response.body || '{}'), null, 2);
} catch {
return response.body || '';
}
}, []);
const handleResultSelect = useCallback((result) => {
setSelectedResult(result);
if (editorRef.current) {
const content = result.type === 'TCP' ?
`[TCP Response]\n${result.response.body}` :
`[HTTP Response]\nStatus: ${result.response.status}\n\n[Headers]\n${formatResponse(result.response.headers)}\n\n[Body]\n${result.response.body}`;
editorRef.current.setValue(content);
}
}, [formatResponse]);
return (
<div className="flex h-full gap-4">
{/* Results List */}
<Card className="w-64">
<CardHeader>Test Results</CardHeader>
<CardContent>
<ScrollArea className="h-[600px]">
<div className="space-y-2">
{results.map((result, index) => (
<div
key={index}
onClick={() => handleResultSelect(result)}
className={cn(
"p-2 rounded cursor-pointer hover:bg-accent",
selectedResult === result && "bg-accent"
)}
>
<div className="font-medium truncate">{result.request.name}</div>
<div className="text-sm text-muted-foreground">
Status: {result.response.status || 'N/A'}
</div>
</div>
))}
</div>
</ScrollArea>
</CardContent>
</Card>
{/* Result Details */}
<Card className="flex-1">
<CardHeader className="flex flex-row items-center justify-between">
<div>Result Details</div>
<Button variant="outline" onClick={reset}>
Clear
</Button>
</CardHeader>
<CardContent>
<Tabs value={activeTab} onValueChange={setActiveTab}>
<TabsList>
<TabsTrigger value="body">Body</TabsTrigger>
<TabsTrigger value="console">Console</TabsTrigger>
</TabsList>
<TabsContent value="body" className="h-[600px]">
<div id="test-output" className="h-full w-full border rounded-md"></div>
</TabsContent>
<TabsContent value="console" className="h-[600px]">
<div id="test-console" className="h-full w-full border rounded-md bg-zinc-950 text-zinc-50 p-4 font-mono overflow-auto">
{results.map((result, index) => (
<div key={index} className="whitespace-pre-wrap mb-2">
{`[${new Date(result.timestamp).toISOString()}] ${result.request.name}\n`}
{`Status: ${result.response.status}\n`}
{`Time: ${result.response.time}ms\n`}
{`Size: ${result.response.size} bytes\n`}
{'='.repeat(50)}
</div>
))}
</div>
</TabsContent>
</Tabs>
</CardContent>
</Card>
</div>
);
};
export default LoadTestResult;