diff --git a/app/Http/Controllers/DevTools/FlowTesterController.php b/app/Http/Controllers/DevTools/FlowTesterController.php index 7cc336d9..899e9c9a 100644 --- a/app/Http/Controllers/DevTools/FlowTesterController.php +++ b/app/Http/Controllers/DevTools/FlowTesterController.php @@ -5,6 +5,7 @@ use App\Http\Controllers\Controller; use App\Models\Admin\AdminApiFlow; use App\Models\Admin\AdminApiFlowRun; +use App\Services\FlowTester\FlowExecutor; use Illuminate\Http\Request; use Illuminate\View\View; @@ -216,14 +217,57 @@ public function run(int $id) 'executed_by' => auth()->id(), ]); - // TODO: 실제 플로우 실행 로직은 Service 클래스로 분리 - // 현재는 스캐폴딩만 제공 + try { + // FlowExecutor로 실제 실행 + $executor = new FlowExecutor(); + $result = $executor->execute($flow->flow_definition); - return response()->json([ - 'success' => true, - 'run_id' => $run->id, - 'message' => '플로우 실행이 시작되었습니다.', - ]); + // 실행 결과 저장 + $run->update([ + 'status' => $result['status'], + 'completed_at' => now(), + 'duration_ms' => $result['duration'], + 'completed_steps' => $result['completedSteps'], + 'failed_step' => $result['failedStep'], + 'execution_log' => $result['executionLog'], + 'error_message' => $result['errorMessage'], + ]); + + return response()->json([ + 'success' => $result['status'] === 'SUCCESS', + 'run_id' => $run->id, + 'status' => $result['status'], + 'message' => $this->getResultMessage($result), + 'result' => $result, + ]); + } catch (\Exception $e) { + // 예외 발생 시 실패 처리 + $run->update([ + 'status' => AdminApiFlowRun::STATUS_FAILED, + 'completed_at' => now(), + 'error_message' => $e->getMessage(), + ]); + + return response()->json([ + 'success' => false, + 'run_id' => $run->id, + 'status' => 'FAILED', + 'message' => '실행 오류: '.$e->getMessage(), + ], 500); + } + } + + /** + * 실행 결과 메시지 생성 + */ + private function getResultMessage(array $result): string + { + return match ($result['status']) { + 'SUCCESS' => "플로우 실행 완료! ({$result['completedSteps']}/{$result['totalSteps']} 스텝 성공)", + 'FAILED' => "플로우 실행 실패: ".($result['errorMessage'] ?? '알 수 없는 오류'), + 'PARTIAL' => "부분 성공: {$result['completedSteps']}/{$result['totalSteps']} 스텝 완료", + default => "실행 완료 (상태: {$result['status']})", + }; } /** diff --git a/resources/views/dev-tools/flow-tester/index.blade.php b/resources/views/dev-tools/flow-tester/index.blade.php index f39e11c7..d45f0e3b 100644 --- a/resources/views/dev-tools/flow-tester/index.blade.php +++ b/resources/views/dev-tools/flow-tester/index.blade.php @@ -196,6 +196,15 @@ class="p-2 text-red-600 hover:bg-red-50 rounded-lg transition" function runFlow(id) { if (!confirm('이 플로우를 실행하시겠습니까?')) return; + // 실행 버튼을 로딩 상태로 변경 + const btn = document.querySelector(`button[onclick="runFlow(${id})"]`); + const originalHtml = btn.innerHTML; + btn.disabled = true; + btn.innerHTML = ``; + fetch(`/dev-tools/flow-tester/${id}/run`, { method: 'POST', headers: { @@ -205,18 +214,114 @@ function runFlow(id) { }) .then(response => response.json()) .then(data => { - if (data.success) { - alert(data.message); - location.reload(); - } else { - alert('실행 실패: ' + (data.message || '알 수 없는 오류')); - } + // 버튼 복원 + btn.disabled = false; + btn.innerHTML = originalHtml; + + // 결과 모달 표시 + showResultModal(data); }) .catch(error => { + btn.disabled = false; + btn.innerHTML = originalHtml; alert('오류 발생: ' + error.message); }); } + function showResultModal(data) { + const isSuccess = data.status === 'SUCCESS'; + const isFailed = data.status === 'FAILED'; + const isPartial = data.status === 'PARTIAL'; + + const statusColor = isSuccess ? 'green' : (isFailed ? 'red' : 'yellow'); + const statusIcon = isSuccess + ? `` + : (isFailed + ? `` + : ``); + + const result = data.result || {}; + const duration = result.duration ? `${(result.duration / 1000).toFixed(2)}초` : '-'; + + // 실행 로그 요약 + let logSummary = ''; + if (result.executionLog && result.executionLog.length > 0) { + logSummary = result.executionLog.map((step, idx) => { + const stepIcon = step.success + ? '✓' + : '✗'; + return `