refactor: add session and environment relationships to scenario run

- add sessionId optional column to ScenarioRunEntity
- add ManyToOne relationship to EnvironmentEntity (with eager loading)
- add ManyToOne relationship to SessionEntity (optional)
- update ScenarioRun client types to include session and environment data
- add environment name and session link to RunDetailPage
- update UI to display save session, environment, and session info
This commit is contained in:
2026-04-14 19:29:48 +03:00
parent 80d9af42b5
commit 5ec9fd0bc3
6 changed files with 30 additions and 6 deletions
+3
View File
@@ -98,8 +98,11 @@ export interface ScenarioRun {
id: string; id: string;
scenarioId: string; scenarioId: string;
environmentId: string; environmentId: string;
sessionId?: string | null;
saveSession: boolean; saveSession: boolean;
scenario?: Pick<Scenario, 'id' | 'name'>; scenario?: Pick<Scenario, 'id' | 'name'>;
environment?: Pick<Environment, 'id' | 'name'>;
session?: { id: string; sessionName: string };
status: ScenarioRunStatus; status: ScenarioRunStatus;
stepRuns?: ScenarioRunStep[]; stepRuns?: ScenarioRunStep[];
createdAt: string; createdAt: string;
+9 -1
View File
@@ -1,5 +1,5 @@
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams, Link } from 'react-router-dom';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { runs } from '../../api'; import { runs } from '../../api';
import type { import type {
@@ -324,6 +324,14 @@ export function RunDetailPage() {
}, },
{ term: t('runs.field_created'), detail: <Timestamp value={run.createdAt} /> }, { term: t('runs.field_created'), detail: <Timestamp value={run.createdAt} /> },
{ term: t('runs.field_updated'), detail: <Timestamp value={run.updatedAt} /> }, { term: t('runs.field_updated'), detail: <Timestamp value={run.updatedAt} /> },
...(run.environment ? [{
term: 'Environment',
detail: <span style={{ color: 'var(--color-link)', cursor: 'pointer' }}>{run.environment.name}</span>,
}] : []),
...(run.session ? [{
term: 'Session',
detail: <Link to={`/sessions/${run.session.id}`} style={{ color: 'var(--color-link)' }}>{run.session.sessionName}</Link>,
}] : []),
]} ]}
/> />
</Card> </Card>
@@ -559,9 +559,9 @@ export function ScenarioDetailPage() {
type="checkbox" type="checkbox"
checked={saveSessionFlag} checked={saveSessionFlag}
onChange={(e) => setSaveSessionFlag(e.target.checked)} onChange={(e) => setSaveSessionFlag(e.target.checked)}
disabled={running} disabled
/> />
<span>Save session for explore mode</span> <span>Save session</span>
</label> </label>
</> </>
)} )}
+2 -2
View File
@@ -222,9 +222,9 @@ export function ScenariosPage() {
type="checkbox" type="checkbox"
checked={saveSessionFlag} checked={saveSessionFlag}
onChange={(e) => setSaveSessionFlag(e.target.checked)} onChange={(e) => setSaveSessionFlag(e.target.checked)}
disabled={running} disabled
/> />
<span>Save session for explore mode</span> <span>Save session</span>
</label> </label>
</> </>
)} )}
+1 -1
View File
@@ -703,7 +703,7 @@ export class McpService {
inputSchema: { inputSchema: {
id: z.uuid().describe("Scenario ID to run"), id: z.uuid().describe("Scenario ID to run"),
environmentId: z.uuid().describe("Environment ID to run the scenario in"), environmentId: z.uuid().describe("Environment ID to run the scenario in"),
saveSession: z.boolean().optional().describe("Save session for explore mode after run completes"), saveSession: z.boolean().optional().describe("Save session after run completes"),
}, },
}, },
async ({ id, environmentId, saveSession }) => { async ({ id, environmentId, saveSession }) => {
@@ -10,6 +10,8 @@ import {
} from "typeorm"; } from "typeorm";
import { ScenarioRunStepEntity } from "./scenario-run-step.entity"; import { ScenarioRunStepEntity } from "./scenario-run-step.entity";
import { ScenarioEntity } from "./scenario.entity"; import { ScenarioEntity } from "./scenario.entity";
import { EnvironmentEntity } from "../environment/environment.entity";
import { SessionEntity } from "../session/session.entity";
export type RunStatus = "pending" | "in_progress" | "pass" | "fail"; export type RunStatus = "pending" | "in_progress" | "pass" | "fail";
@@ -28,6 +30,17 @@ export class ScenarioRunEntity {
@JoinColumn({ name: "scenarioId" }) @JoinColumn({ name: "scenarioId" })
scenario: ScenarioEntity; scenario: ScenarioEntity;
@ManyToOne(() => EnvironmentEntity, { onDelete: "SET NULL", nullable: true })
@JoinColumn({ name: "environmentId" })
environment: EnvironmentEntity;
@Column("text", { nullable: true, default: null })
sessionId: string | null;
@ManyToOne(() => SessionEntity, { onDelete: "SET NULL", nullable: true })
@JoinColumn({ name: "sessionId" })
session: SessionEntity | null;
@Column({ type: "text", default: "pending" }) @Column({ type: "text", default: "pending" })
status: RunStatus; status: RunStatus;