"use client"; import React, { useState, useCallback, useEffect } from "react"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Alert, AlertDescription } from "@/components/ui/alert"; import { Textarea } from "@/components/ui/textarea"; import { Label } from "@/components/ui/label"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { ScrollArea } from "@/components/ui/scroll-area"; import { TestTube, Play, CheckCircle, XCircle, Clock, AlertCircle, Copy, RefreshCw, Zap, Code, Network, Timer, } from "lucide-react"; import { toast } from "sonner"; // νƒ€μž… import import { ExternalCallTestPanelProps, ApiTestResult, ExternalCallContext, } from "@/types/external-call/ExternalCallTypes"; import { ExternalCallAPI } from "@/lib/api/externalCall"; /** * πŸ§ͺ API ν…ŒμŠ€νŠΈ μ „μš© μ»΄ν¬λ„ŒνŠΈ * * REST API 섀정을 μ‹€μ œλ‘œ ν…ŒμŠ€νŠΈν•˜κ³  κ²°κ³Όλ₯Ό ν‘œμ‹œ * ν…œν”Œλ¦Ώ λ³€μˆ˜ μΉ˜ν™˜, 응닡 뢄석, 였λ₯˜ 진단 λ“± 제곡 */ const ExternalCallTestPanel: React.FC = ({ settings, context, onTestResult, disabled = false, }) => { // μƒνƒœ 관리 const [isLoading, setIsLoading] = useState(false); const [testResult, setTestResult] = useState(null); const [activeTab, setActiveTab] = useState("request"); const [processedTemplate, setProcessedTemplate] = useState(""); const [testContext, setTestContext] = useState(() => ({ relationshipId: context?.relationshipId || "test-relationship", diagramId: context?.diagramId || "test-diagram", userId: context?.userId || "test-user", executionId: context?.executionId || `test-${Date.now()}`, sourceData: context?.sourceData || { id: 1, name: "ν…ŒμŠ€νŠΈ 데이터", value: 100, status: "active", }, targetData: context?.targetData, timestamp: context?.timestamp || new Date().toISOString(), metadata: context?.metadata, })); // ν…œν”Œλ¦Ώ λ³€μˆ˜ μΉ˜ν™˜ ν•¨μˆ˜ const processTemplate = useCallback((template: string, context: ExternalCallContext): string => { let processed = template; // 각 ν…œν”Œλ¦Ώ λ³€μˆ˜λ₯Ό μ‹€μ œ κ°’μœΌλ‘œ μΉ˜ν™˜ const replacements = { "{{sourceData}}": JSON.stringify(context.sourceData, null, 2), "{{targetData}}": context.targetData ? JSON.stringify(context.targetData, null, 2) : "null", "{{timestamp}}": context.timestamp, "{{relationshipId}}": context.relationshipId, "{{diagramId}}": context.diagramId, "{{userId}}": context.userId, "{{executionId}}": context.executionId, }; Object.entries(replacements).forEach(([variable, value]) => { processed = processed.replace(new RegExp(variable.replace(/[{}]/g, "\\$&"), "g"), value); }); return processed; }, []); // ν…œν”Œλ¦Ώ 처리 (μ„€μ •μ΄λ‚˜ μ»¨ν…μŠ€νŠΈ λ³€κ²½ μ‹œ) useEffect(() => { if (settings.bodyTemplate) { const processed = processTemplate(settings.bodyTemplate, testContext); setProcessedTemplate(processed); } }, [settings.bodyTemplate, testContext, processTemplate]); // API ν…ŒμŠ€νŠΈ μ‹€ν–‰ const handleRunTest = useCallback(async () => { if (!settings.apiUrl) { toast.error("API URL을 μž…λ ₯ν•΄μ£Όμ„Έμš”."); return; } setIsLoading(true); setTestResult(null); try { // ν…ŒμŠ€νŠΈ μš”μ²­ 데이터 ꡬ성 (λ°±μ—”λ“œ ν˜•μ‹μ— 맞좀) const testRequest = { settings: { callType: "rest-api" as const, apiType: "generic" as const, url: settings.apiUrl, method: settings.httpMethod, headers: settings.headers, body: processedTemplate, authentication: settings.authentication, // 인증 정보 μΆ”κ°€ timeout: settings.timeout, retryCount: settings.retryCount, }, templateData: testContext, }; // API 호좜 const response = await ExternalCallAPI.testExternalCall(testRequest); if (response.success && response.result) { // λ°±μ—”λ“œ 응닡을 ApiTestResult ν˜•νƒœλ‘œ λ³€ν™˜ const apiTestResult: ApiTestResult = { success: response.result.success, statusCode: response.result.statusCode, responseTime: response.result.executionTime || 0, response: response.result.response, error: response.result.error, timestamp: new Date().toISOString(), }; setTestResult(apiTestResult); onTestResult(apiTestResult); if (apiTestResult.success) { toast.success("API ν…ŒμŠ€νŠΈκ°€ μ„±κ³΅ν–ˆμŠ΅λ‹ˆλ‹€!"); setActiveTab("response"); } else { toast.error("API 호좜이 μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€."); setActiveTab("response"); } } else { const errorResult: ApiTestResult = { success: false, responseTime: 0, error: response.error || "μ•Œ 수 μ—†λŠ” 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€.", timestamp: new Date().toISOString(), }; setTestResult(errorResult); onTestResult(errorResult); toast.error(response.error || "ν…ŒμŠ€νŠΈ μ‹€ν–‰ 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€."); } } catch (error) { const errorResult: ApiTestResult = { success: false, responseTime: 0, error: error instanceof Error ? error.message : "λ„€νŠΈμ›Œν¬ 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€.", timestamp: new Date().toISOString(), }; setTestResult(errorResult); onTestResult(errorResult); toast.error("ν…ŒμŠ€νŠΈ μ‹€ν–‰ 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€."); } finally { setIsLoading(false); } }, [settings, processedTemplate, testContext, onTestResult]); // ν…ŒμŠ€νŠΈ 데이터 볡사 const handleCopyToClipboard = useCallback(async (text: string) => { try { await navigator.clipboard.writeText(text); toast.success("ν΄λ¦½λ³΄λ“œμ— λ³΅μ‚¬λ˜μ—ˆμŠ΅λ‹ˆλ‹€."); } catch (error) { toast.error("볡사에 μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€."); } }, []); // ν…ŒμŠ€νŠΈ μ»¨ν…μŠ€νŠΈ 리셋 const handleResetContext = useCallback(() => { setTestContext({ relationshipId: "test-relationship", diagramId: "test-diagram", userId: "test-user", executionId: `test-${Date.now()}`, sourceData: { id: 1, name: "ν…ŒμŠ€νŠΈ 데이터", value: 100, status: "active", }, timestamp: new Date().toISOString(), }); }, []); return (
{/* ν…ŒμŠ€νŠΈ μ‹€ν–‰ 헀더 */}
API ν…ŒμŠ€νŠΈ {testResult && ( {testResult.success ? "성곡" : "μ‹€νŒ¨"} )}
{/* ν…ŒμŠ€νŠΈ κ²°κ³Ό μš”μ•½ */} {testResult && (
{testResult.success ? ( ) : ( )} μƒνƒœ
{testResult.success ? "성곡" : "μ‹€νŒ¨"}
μƒνƒœ μ½”λ“œ
{testResult.statusCode || "N/A"}
응닡 μ‹œκ°„
{testResult.responseTime}ms
μ‹€ν–‰ μ‹œκ°„
{new Date(testResult.timestamp).toLocaleTimeString()}
)} {/* 상세 정보 νƒ­ */} μš”μ²­ 정보 응닡 정보 ν…ŒμŠ€νŠΈ 데이터 {/* μš”μ²­ 정보 νƒ­ */}
μš”μ²­ 정보
{/* URLκ³Ό λ©”μ„œλ“œ */}
{settings.httpMethod}
{settings.apiUrl}
{/* 헀더 */}
{JSON.stringify(settings.headers, null, 2)}
{/* μš”μ²­ λ°”λ”” (POST/PUT/PATCH인 경우) */} {["POST", "PUT", "PATCH"].includes(settings.httpMethod) && (
{processedTemplate}
)}
{/* 응닡 정보 νƒ­ */} {testResult ? (
응닡 정보
{testResult.success ? ( <> {/* μƒνƒœ μ½”λ“œ */} {testResult.statusCode && (
{testResult.statusCode}
)} {/* 응닡 μ‹œκ°„ */} {testResult.responseTime !== undefined && (
{testResult.responseTime}ms
)} {/* 응닡 데이터 */} {testResult.response && (
{testResult.response}
)} ) : (
였λ₯˜ λ°œμƒ
{testResult.error}
{testResult.statusCode &&
μƒνƒœ μ½”λ“œ: {testResult.statusCode}
}
)}
) : (

ν…ŒμŠ€νŠΈλ₯Ό μ‹€ν–‰ν•˜λ©΄ 응닡 정보가 여기에 ν‘œμ‹œλ©λ‹ˆλ‹€.

)}
{/* ν…ŒμŠ€νŠΈ 데이터 νƒ­ */}
ν…ŒμŠ€νŠΈ μ»¨ν…μŠ€νŠΈ
ν…œν”Œλ¦Ώ λ³€μˆ˜ μΉ˜ν™˜μ— μ‚¬μš©λ˜λŠ” ν…ŒμŠ€νŠΈ λ°μ΄ν„°μž…λ‹ˆλ‹€.
{JSON.stringify(testContext, null, 2)}
μ‹€μ œ μ‹€ν–‰ μ‹œμ—λŠ” κ΄€κ³„μ˜ μ‹€μ œ 데이터가 μ‚¬μš©λ©λ‹ˆλ‹€. 이 λ°μ΄ν„°λŠ” ν…ŒμŠ€νŠΈ λͺ©μ μœΌλ‘œλ§Œ μ‚¬μš©λ©λ‹ˆλ‹€.
); }; export default ExternalCallTestPanel;