From 2efe076226c56ccd0549185911bdaf74e0e55fe1 Mon Sep 17 00:00:00 2001 From: akhisud3195 Date: Mon, 17 Feb 2025 13:43:49 +0530 Subject: [PATCH] Use workflow ID in simulation runs --- .../rowboat/app/actions/simulation_actions.ts | 4 +- apps/rowboat/app/lib/types/testing_types.ts | 1 + .../projects/[projectId]/simulation/app.tsx | 156 ++++++++++++------ .../simulation/components/RunComponents.tsx | 54 +++--- 4 files changed, 148 insertions(+), 67 deletions(-) diff --git a/apps/rowboat/app/actions/simulation_actions.ts b/apps/rowboat/app/actions/simulation_actions.ts index 2a3be600..63e8cea1 100644 --- a/apps/rowboat/app/actions/simulation_actions.ts +++ b/apps/rowboat/app/actions/simulation_actions.ts @@ -125,7 +125,8 @@ export async function getRun(projectId: string, runId: string): Promise>> { await projectAuthCheck(projectId); @@ -133,6 +134,7 @@ export async function createRun( projectId, status: 'pending' as const, scenarioIds, + workflowId, startedAt: new Date().toISOString(), }; diff --git a/apps/rowboat/app/lib/types/testing_types.ts b/apps/rowboat/app/lib/types/testing_types.ts index 64c31ba2..7b44b78f 100644 --- a/apps/rowboat/app/lib/types/testing_types.ts +++ b/apps/rowboat/app/lib/types/testing_types.ts @@ -49,6 +49,7 @@ export const SimulationRun = z.object({ projectId: z.string(), status: z.enum(['pending', 'running', 'completed', 'cancelled', 'failed']), scenarioIds: z.array(z.string()), + workflowId: z.string(), startedAt: z.string(), completedAt: z.string().optional(), aggregateResults: SimulationAggregateResult.optional(), diff --git a/apps/rowboat/app/projects/[projectId]/simulation/app.tsx b/apps/rowboat/app/projects/[projectId]/simulation/app.tsx index 4c7894ff..865a1fe7 100644 --- a/apps/rowboat/app/projects/[projectId]/simulation/app.tsx +++ b/apps/rowboat/app/projects/[projectId]/simulation/app.tsx @@ -1,7 +1,7 @@ 'use client'; import { useState, useEffect, useCallback } from 'react'; -import { PlusIcon, PencilIcon, XMarkIcon, DocumentDuplicateIcon, EllipsisVerticalIcon, TrashIcon, ChevronRightIcon, PlayIcon, ChevronDownIcon } from '@heroicons/react/24/outline'; +import { PlusIcon, PencilIcon, XMarkIcon, EllipsisVerticalIcon, TrashIcon, ChevronRightIcon, PlayIcon, ChevronDownIcon } from '@heroicons/react/24/outline'; import { useParams, useRouter } from 'next/navigation'; import { getScenarios, @@ -15,13 +15,14 @@ import { createRunResult, updateRunStatus, createAggregateResult, - getAggregateResult, } from '../../../actions/simulation_actions'; import { type WithStringId } from '../../../lib/types/types'; import { Scenario, SimulationRun, SimulationResult } from "../../../lib/types/testing_types"; +import { Workflow } from "../../../lib/types/workflow_types"; import { z } from 'zod'; import { SimulationResultCard, ScenarioResultCard } from './components/RunComponents'; import { ScenarioViewer } from './components/ScenarioComponents'; +import { fetchWorkflow } from '../../../actions/workflow_actions'; type ScenarioType = WithStringId>; type SimulationRunType = WithStringId>; @@ -75,6 +76,7 @@ export default function SimulationApp() { const [runResults, setRunResults] = useState([]); const [isLoadingRuns, setIsLoadingRuns] = useState(true); const [allRunResults, setAllRunResults] = useState>({}); + const [workflowVersions, setWorkflowVersions] = useState>>>({}); // Load scenarios on mount useEffect(() => { @@ -207,58 +209,82 @@ export default function SimulationApp() { setSimulationReport(null); try { - const newRun = await createRun( - projectId as string, - scenarios.map(s => s._id) - ); - setActiveRun(newRun); + // Get workflowId from localStorage + const workflowId = localStorage.getItem(`lastWorkflowId_${projectId}`); + if (!workflowId) { + throw new Error('No workflow selected. Please select a workflow first.'); + } - const shouldMock = process.env.NEXT_PUBLIC_MOCK_SIMULATION_RESULTS === 'true'; - - if (shouldMock) { - console.log('Using mock simulation...'); - - await updateRunStatus(projectId as string, newRun._id, 'running'); - - // Run all scenarios and collect results - const mockResults = await Promise.all( - scenarios.map(scenario => - dummySimulator(scenario, newRun._id, projectId as string) - ) + // First verify the workflow exists before creating the run + try { + await fetchWorkflow(projectId as string, workflowId); + } catch (error) { + // If workflow doesn't exist, clear localStorage and throw error + localStorage.removeItem(`lastWorkflowId_${projectId}`); + throw new Error('Selected workflow no longer exists. Please select a new workflow.'); + } + + const newRun = await createRun( + projectId as string, + scenarios.map(s => s._id), + workflowId ); + setActiveRun(newRun); - // Calculate and store aggregate results before marking as complete - const total = scenarios.length; - const pass = mockResults.filter(r => r.result === 'pass').length; - const fail = mockResults.filter(r => r.result === 'fail').length; + // Fetch and store workflow version + const workflow = await fetchWorkflow(projectId as string, workflowId); + setWorkflowVersions(prev => ({ + ...prev, + [workflowId]: workflow + })); - await createAggregateResult( - projectId as string, - newRun._id, - total, - pass, - fail - ); - - await updateRunStatus( - projectId as string, - newRun._id, - 'completed', - new Date().toISOString() - ); - - const results = await getRunResults(projectId as string, newRun._id); - setRunResults(results); + const shouldMock = process.env.NEXT_PUBLIC_MOCK_SIMULATION_RESULTS === 'true'; - const updatedRun = await getRun(projectId as string, newRun._id); - setActiveRun(updatedRun); - } - - await fetchRuns(); + if (shouldMock) { + console.log('Using mock simulation...'); + + await updateRunStatus(projectId as string, newRun._id, 'running'); + + // Run all scenarios and collect results + const mockResults = await Promise.all( + scenarios.map(scenario => + dummySimulator(scenario, newRun._id, projectId as string) + ) + ); + + // Calculate and store aggregate results before marking as complete + const total = scenarios.length; + const pass = mockResults.filter(r => r.result === 'pass').length; + const fail = mockResults.filter(r => r.result === 'fail').length; + + await createAggregateResult( + projectId as string, + newRun._id, + total, + pass, + fail + ); + + await updateRunStatus( + projectId as string, + newRun._id, + 'completed', + new Date().toISOString() + ); + + const results = await getRunResults(projectId as string, newRun._id); + setRunResults(results); + + const updatedRun = await getRun(projectId as string, newRun._id); + setActiveRun(updatedRun); + } + + await fetchRuns(); } catch (error) { - console.error('Error starting scenarios:', error); + console.error('Error starting scenarios:', error); + // Maybe show an error toast here } finally { - setIsRunning(false); + setIsRunning(false); } }; @@ -270,6 +296,41 @@ export default function SimulationApp() { setMenuOpenScenarioId(null); }; + // Add useEffect to fetch workflow versions for existing runs + useEffect(() => { + if (!projectId || !runs.length) return; + + const fetchWorkflowVersions = async () => { + const workflowIds = Array.from(new Set(runs.map(run => run.workflowId))); + const versions: Record>> = {}; + + for (const workflowId of workflowIds) { + try { + const workflow = await fetchWorkflow(projectId as string, workflowId); + versions[workflowId] = workflow; + } catch (error) { + console.error(`Error fetching workflow ${workflowId}:`, error); + // Add a placeholder for deleted workflows + versions[workflowId] = { + _id: workflowId, + name: "Deleted Workflow", + projectId: projectId as string, + agents: [], + prompts: [], + tools: [], + startAgent: "", + createdAt: "", + lastUpdatedAt: "", + }; + } + } + + setWorkflowVersions(versions); + }; + + fetchWorkflowVersions(); + }, [projectId, runs]); + return (
{/* Left sidebar */} @@ -382,6 +443,7 @@ export default function SimulationApp() { run={run} results={allRunResults[run._id] || []} scenarios={scenarios} + workflow={workflowVersions[run.workflowId]} /> ))}
diff --git a/apps/rowboat/app/projects/[projectId]/simulation/components/RunComponents.tsx b/apps/rowboat/app/projects/[projectId]/simulation/components/RunComponents.tsx index 7c0c4ccc..e2376fa9 100644 --- a/apps/rowboat/app/projects/[projectId]/simulation/components/RunComponents.tsx +++ b/apps/rowboat/app/projects/[projectId]/simulation/components/RunComponents.tsx @@ -5,7 +5,7 @@ import { ChevronDownIcon, ChevronRightIcon } from '@heroicons/react/24/outline'; import { WithStringId } from '../../../../lib/types/types'; import { Scenario, SimulationRun, SimulationResult, SimulationAggregateResult } from "../../../../lib/types/testing_types"; import { z } from 'zod'; -import { getAggregateResult } from '../../../../actions/simulation_actions'; +import { Workflow } from "../../../../lib/types/workflow_types"; type ScenarioType = WithStringId>; type SimulationRunType = WithStringId>; @@ -15,18 +15,32 @@ interface SimulationResultCardProps { run: SimulationRunType; results: SimulationResultType[]; scenarios: ScenarioType[]; + workflow?: WithStringId>; } -export const SimulationResultCard = ({ run, results, scenarios }: SimulationResultCardProps) => { +export const SimulationResultCard = ({ run, results, scenarios, workflow }: SimulationResultCardProps) => { const [isExpanded, setIsExpanded] = useState(false); const [expandedScenarios, setExpandedScenarios] = useState>(new Set()); - // Replace the manual calculations with aggregate results from the run object const totalScenarios = run.aggregateResults?.total ?? run.scenarioIds.length; const passedScenarios = run.aggregateResults?.pass ?? 0; const failedScenarios = run.aggregateResults?.fail ?? 0; const statusLabelClass = "px-3 py-1 rounded text-xs min-w-[60px] text-center uppercase font-semibold"; + const getStatusClass = (status: string) => { + switch (status) { + case 'completed': + case 'pass': + return `${statusLabelClass} bg-green-100 text-green-800`; + case 'failed': + case 'fail': + return `${statusLabelClass} bg-red-100 text-red-800`; + case 'running': + case 'pending': + default: + return `${statusLabelClass} bg-yellow-100 text-yellow-800`; + } + }; const formatMainTitle = (date: string) => { return `Run from ${new Date(date).toLocaleString('en-US', { @@ -89,26 +103,30 @@ export const SimulationResultCard = ({ run, results, scenarios }: SimulationResu {formatMainTitle(run.startedAt)} - + {run.status} {isExpanded && (
- {/* Simplified timing information */} -
-
- Completed: - {run.completedAt ? formatDateTime(run.completedAt) : 'Not completed'} + {/* Workflow and timing information in a grid */} +
+ {workflow && ( +
+
Workflow Version
+
{workflow.name}
+
+ )} +
+
Completed
+
+ {run.completedAt ? formatDateTime(run.completedAt) : 'Not completed'} +
-
- Duration: - {getDuration()} +
+
Duration
+
{getDuration()}
@@ -156,9 +174,7 @@ export const SimulationResultCard = ({ run, results, scenarios }: SimulationResu {scenario.name}
{result && ( - + {result.result} )}