Use workflow ID in simulation runs

This commit is contained in:
akhisud3195 2025-02-17 13:43:49 +05:30
parent c318064567
commit 2efe076226
4 changed files with 148 additions and 67 deletions

View file

@ -125,7 +125,8 @@ export async function getRun(projectId: string, runId: string): Promise<WithStri
export async function createRun(
projectId: string,
scenarioIds: string[]
scenarioIds: string[],
workflowId: string
): Promise<WithStringId<z.infer<typeof SimulationRun>>> {
await projectAuthCheck(projectId);
@ -133,6 +134,7 @@ export async function createRun(
projectId,
status: 'pending' as const,
scenarioIds,
workflowId,
startedAt: new Date().toISOString(),
};

View file

@ -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(),

View file

@ -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<z.infer<typeof Scenario>>;
type SimulationRunType = WithStringId<z.infer<typeof SimulationRun>>;
@ -75,6 +76,7 @@ export default function SimulationApp() {
const [runResults, setRunResults] = useState<SimulationResultType[]>([]);
const [isLoadingRuns, setIsLoadingRuns] = useState(true);
const [allRunResults, setAllRunResults] = useState<Record<string, SimulationResultType[]>>({});
const [workflowVersions, setWorkflowVersions] = useState<Record<string, WithStringId<z.infer<typeof Workflow>>>>({});
// 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<string, WithStringId<z.infer<typeof Workflow>>> = {};
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 (
<div className="flex h-screen">
{/* Left sidebar */}
@ -382,6 +443,7 @@ export default function SimulationApp() {
run={run}
results={allRunResults[run._id] || []}
scenarios={scenarios}
workflow={workflowVersions[run.workflowId]}
/>
))}
</div>

View file

@ -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<z.infer<typeof Scenario>>;
type SimulationRunType = WithStringId<z.infer<typeof SimulationRun>>;
@ -15,18 +15,32 @@ interface SimulationResultCardProps {
run: SimulationRunType;
results: SimulationResultType[];
scenarios: ScenarioType[];
workflow?: WithStringId<z.infer<typeof Workflow>>;
}
export const SimulationResultCard = ({ run, results, scenarios }: SimulationResultCardProps) => {
export const SimulationResultCard = ({ run, results, scenarios, workflow }: SimulationResultCardProps) => {
const [isExpanded, setIsExpanded] = useState(false);
const [expandedScenarios, setExpandedScenarios] = useState<Set<string>>(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)}
</div>
</div>
<span className={`${statusLabelClass} ${
run.status === 'completed' ? 'bg-green-100 text-green-800' :
run.status === 'failed' ? 'bg-red-100 text-red-800' :
'bg-yellow-100 text-yellow-800'
}`}>
<span className={getStatusClass(run.status)}>
{run.status}
</span>
</div>
{isExpanded && (
<div className="p-4 border-t">
{/* Simplified timing information */}
<div className="mb-6 text-sm text-gray-500 space-y-1">
<div className="flex items-center">
<span className="w-24 text-gray-600">Completed:</span>
<span>{run.completedAt ? formatDateTime(run.completedAt) : 'Not completed'}</span>
{/* Workflow and timing information in a grid */}
<div className="grid grid-cols-3 gap-4 mb-6">
{workflow && (
<div className="bg-gray-50 p-4 rounded-lg">
<div className="text-sm font-medium text-gray-600 mb-1">Workflow Version</div>
<div className="font-medium">{workflow.name}</div>
</div>
)}
<div className="bg-gray-50 p-4 rounded-lg">
<div className="text-sm font-medium text-gray-600 mb-1">Completed</div>
<div className="text-sm">
{run.completedAt ? formatDateTime(run.completedAt) : 'Not completed'}
</div>
</div>
<div className="flex items-center">
<span className="w-24 text-gray-600">Duration:</span>
<span>{getDuration()}</span>
<div className="bg-gray-50 p-4 rounded-lg">
<div className="text-sm font-medium text-gray-600 mb-1">Duration</div>
<div className="text-sm">{getDuration()}</div>
</div>
</div>
@ -156,9 +174,7 @@ export const SimulationResultCard = ({ run, results, scenarios }: SimulationResu
<span className="font-medium text-gray-900">{scenario.name}</span>
</div>
{result && (
<span className={`${statusLabelClass} ${
result.result === 'pass' ? 'bg-green-200 text-green-900' : 'bg-red-200 text-red-900'
}`}>
<span className={getStatusClass(result.result)}>
{result.result}
</span>
)}