feat(scenarios): link scenarios to a default environment

- add optional ManyToOne relation from scenario to environment entity
- expose environmentId in create/update DTOs, service, and MCP tools
- pre-select linked environment in run modals on detail and list pages
- add environment selector to create/edit scenario forms
- show linked environment as a navigable link on scenario detail page
This commit is contained in:
2026-04-15 00:05:55 +03:00
parent e6aacc899d
commit a779ab4667
12 changed files with 109 additions and 19 deletions
+13 -2
View File
@@ -472,13 +472,18 @@ export class McpService {
.string()
.optional()
.describe("Optional markdown description"),
environmentId: z
.uuid()
.optional()
.describe("Optional linked environment ID"),
},
},
async ({ name, description }) => {
async ({ name, description, environmentId }) => {
try {
const scenario = await this.scenarioService.create({
name,
description,
environmentId,
});
return {
content: [
@@ -505,13 +510,19 @@ export class McpService {
.string()
.optional()
.describe("New markdown description"),
environmentId: z
.uuid()
.nullable()
.optional()
.describe("Linked environment ID (null to unlink)"),
},
},
async ({ id, name, description }) => {
async ({ id, name, description, environmentId }) => {
try {
const scenario = await this.scenarioService.update(id, {
name,
description,
environmentId,
});
return {
content: [
@@ -1,5 +1,5 @@
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
import { IsNotEmpty, IsOptional, IsString } from "class-validator";
import { IsNotEmpty, IsOptional, IsString, IsUUID } from "class-validator";
export class CreateScenarioDto {
@ApiProperty({ example: "Login and verify cabinet" })
@@ -11,4 +11,9 @@ export class CreateScenarioDto {
@IsOptional()
@IsString()
description?: string;
@ApiPropertyOptional({ description: "Optional linked environment ID" })
@IsOptional()
@IsUUID()
environmentId?: string;
}
@@ -1,5 +1,5 @@
import { ApiPropertyOptional } from "@nestjs/swagger";
import { IsNotEmpty, IsOptional, IsString } from "class-validator";
import { IsNotEmpty, IsOptional, IsString, IsUUID } from "class-validator";
export class UpdateScenarioDto {
@ApiPropertyOptional({ example: "Updated scenario name" })
@@ -12,4 +12,9 @@ export class UpdateScenarioDto {
@IsOptional()
@IsString()
description?: string;
@ApiPropertyOptional({ description: "Linked environment ID (null to unlink)" })
@IsOptional()
@IsUUID()
environmentId?: string | null;
}
+10
View File
@@ -2,10 +2,13 @@ import {
Column,
CreateDateColumn,
Entity,
JoinColumn,
ManyToOne,
OneToMany,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from "typeorm";
import { EnvironmentEntity } from "../environment/environment.entity";
import { ScenarioCredentialEntity } from "./scenario-credential.entity";
import { ScenarioStepEntity } from "./scenario-step.entity";
@@ -20,6 +23,13 @@ export class ScenarioEntity {
@Column("text", { nullable: true })
description: string | null;
@Column({ nullable: true, type: "text" })
environmentId: string | null;
@ManyToOne(() => EnvironmentEntity, { nullable: true, onDelete: "SET NULL", eager: false })
@JoinColumn({ name: "environmentId" })
environment: EnvironmentEntity | null;
@OneToMany(() => ScenarioStepEntity, (step) => step.scenario, {
cascade: true,
eager: false,
+1
View File
@@ -77,6 +77,7 @@ export class ScenarioService {
"steps",
"scenarioCredentials",
"scenarioCredentials.credential",
"environment",
],
order: { steps: { order: "ASC" } },
});