refactor(workspace): restore yaml exports and align docker builds

- return scenario export payloads as yaml to match existing workflows

- harden step reordering flow for drag-and-drop and one-based ui labels

- switch server container to workspace-lockfile installs and root ignore rules
This commit is contained in:
2026-04-10 18:19:38 +03:00
parent 259dac806e
commit de0cbeef7c
18 changed files with 361 additions and 11385 deletions
+65 -6
View File
@@ -94,19 +94,39 @@ export class ScenarioService {
// ── Steps ─────────────────────────────────────────────────────────────────
private async normalizeStepOrder(scenarioId: string): Promise<void> {
const ordered = await this.stepRepo.find({
where: { scenarioId },
order: { order: "ASC", createdAt: "ASC", id: "ASC" },
});
for (let i = 0; i < ordered.length; i += 1) {
const step = ordered[i];
if (step.order !== i) {
step.order = i;
await this.stepRepo.save(step);
}
}
}
async createStep(
scenarioId: string,
dto: CreateScenarioStepDto,
): Promise<ScenarioStepEntity> {
await this.findOne(scenarioId);
return this.stepRepo.save(
const currentCount = await this.stepRepo.count({ where: { scenarioId } });
const created = await this.stepRepo.save(
this.stepRepo.create({
...dto,
order: currentCount,
scenarioId,
title: dto.title ?? null,
sessionName: dto.sessionName ?? null,
execCode: dto.execCode ?? null,
validateCode: dto.validateCode ?? null,
}),
);
await this.normalizeStepOrder(scenarioId);
return this.findStep(scenarioId, created.id);
}
async findStep(
@@ -127,13 +147,52 @@ export class ScenarioService {
dto: UpdateScenarioStepDto,
): Promise<ScenarioStepEntity> {
const step = await this.findStep(scenarioId, stepId);
Object.assign(step, dto);
return this.stepRepo.save(step);
const nextOrder = dto.order;
Object.assign(step, {
...dto,
order: step.order,
title: dto.title ?? step.title,
sessionName: dto.sessionName ?? step.sessionName,
execCode: dto.execCode ?? step.execCode,
validateCode: dto.validateCode ?? step.validateCode,
});
await this.stepRepo.save(step);
if (nextOrder !== undefined) {
const ordered = await this.stepRepo.find({
where: { scenarioId },
order: { order: "ASC", createdAt: "ASC", id: "ASC" },
});
const currentIndex = ordered.findIndex((s) => s.id === stepId);
if (currentIndex >= 0) {
const boundedTarget = Math.max(
0,
Math.min(nextOrder, ordered.length - 1),
);
if (currentIndex !== boundedTarget) {
const [moved] = ordered.splice(currentIndex, 1);
ordered.splice(boundedTarget, 0, moved);
}
for (let i = 0; i < ordered.length; i += 1) {
const item = ordered[i];
if (item.order !== i) {
item.order = i;
await this.stepRepo.save(item);
}
}
}
} else {
await this.normalizeStepOrder(scenarioId);
}
return this.findStep(scenarioId, stepId);
}
async removeStep(scenarioId: string, stepId: string): Promise<void> {
await this.findStep(scenarioId, stepId);
await this.stepRepo.delete(stepId);
await this.normalizeStepOrder(scenarioId);
}
// ── Scenario Credentials ──────────────────────────────────────────────────
@@ -333,7 +392,6 @@ export class ScenarioService {
id: scenario.id,
name: scenario.name,
steps: scenario.steps.map((s) => ({
order: s.order,
title: s.title,
execCode: s.execCode,
validateCode: s.validateCode,
@@ -362,16 +420,17 @@ export class ScenarioService {
);
}
if (dto.steps.length > 0) {
const steps = dto.steps.map((s) =>
const steps = dto.steps.map((s, index) =>
this.stepRepo.create({
scenarioId: scenario.id,
order: s.order,
order: index,
title: s.title ?? null,
execCode: s.execCode ?? null,
validateCode: s.validateCode ?? null,
}),
);
await this.stepRepo.save(steps);
await this.normalizeStepOrder(scenario.id);
}
return this.findOne(scenario.id);
}