feat(runs): add runs pages, polling, autorefresh indicator, and scheduler fix
- add global runs page and per-scenario runs/run-detail pages - run detail page polls every 1s until pass/fail, cleans up on unmount - runs list pages poll every 10s with AutoRefreshIndicator (pulse on data load) - fix scheduler: set run to pass after empty step loop to prevent stuck in_progress - fix table header colors and link cell color for readability - fix play button to navigate to the new run after creation - fix package.json import paths in server for Docker build context
This commit is contained in:
+1
-1
@@ -8,7 +8,7 @@ import { AppModule } from "./app.module";
|
||||
import { HttpExceptionFilter } from "./filters/http-exception.filter";
|
||||
import { TraceInterceptor } from "./interceptors/trace.interceptor";
|
||||
import { LoggingInterceptor } from "./interceptors/logging.interceptor";
|
||||
import { name as pkgName, version as pkgVersion } from "../../package.json";
|
||||
import { name as pkgName, version as pkgVersion } from "../package.json";
|
||||
|
||||
async function bootstrap() {
|
||||
const logger = new TraceLogger("Bootstrap");
|
||||
|
||||
@@ -12,7 +12,7 @@ import { BrowserService } from "../browser/browser.service";
|
||||
import { CodeExecutorService } from "../code-executor/code-executor.service";
|
||||
import { ScenarioService } from "../scenario/scenario.service";
|
||||
|
||||
import pkg from "../../../package.json";
|
||||
import pkg from "../../package.json";
|
||||
|
||||
@Injectable()
|
||||
export class McpService {
|
||||
|
||||
@@ -114,6 +114,14 @@ export class ScenarioSchedulerService {
|
||||
order: { order: "ASC" },
|
||||
});
|
||||
}
|
||||
// Guard: if there were no steps (or all steps already resolved via passStepRun),
|
||||
// ensure the run is not left in in_progress.
|
||||
await this.runRepo
|
||||
.createQueryBuilder()
|
||||
.update()
|
||||
.set({ status: "pass" })
|
||||
.where("id = :id AND status = 'in_progress'", { id: runId })
|
||||
.execute();
|
||||
} catch (err) {
|
||||
this.logger.error(
|
||||
`Run #${runId}: unexpected error: ${(err as Error).message}`,
|
||||
|
||||
@@ -49,6 +49,13 @@ export class ScenarioController {
|
||||
return this.scenarioService.findAll(query);
|
||||
}
|
||||
|
||||
@Get("runs")
|
||||
@ApiOperation({ summary: "List runs across all scenarios (paginated, filterable by status)" })
|
||||
@ApiResponse({ status: 200 })
|
||||
findAllRuns(@Query() query: RunsQueryDto) {
|
||||
return this.scenarioService.findAllRuns(query);
|
||||
}
|
||||
|
||||
@Get(":id")
|
||||
@ApiOperation({ summary: "Get a scenario with its steps" })
|
||||
@ApiResponse({ status: 200 })
|
||||
|
||||
@@ -140,6 +140,25 @@ export class ScenarioService {
|
||||
return { data, total, page, limit };
|
||||
}
|
||||
|
||||
async findAllRuns(
|
||||
query: RunsQueryDto,
|
||||
): Promise<PaginatedResult<ScenarioRunEntity & { scenario: ScenarioEntity }>> {
|
||||
const page = query.page ?? 1;
|
||||
const limit = query.limit ?? 20;
|
||||
const where: Record<string, unknown> = {};
|
||||
if (query.status) where["status"] = query.status;
|
||||
const [data, total] = await this.runRepo.findAndCount({
|
||||
where,
|
||||
relations: ["stepRuns", "scenario"],
|
||||
order: { id: "DESC", stepRuns: { order: "ASC" } },
|
||||
skip: (page - 1) * limit,
|
||||
take: limit,
|
||||
});
|
||||
return { data, total, page, limit } as PaginatedResult<
|
||||
ScenarioRunEntity & { scenario: ScenarioEntity }
|
||||
>;
|
||||
}
|
||||
|
||||
async findRun(
|
||||
scenarioId: number,
|
||||
runId: number,
|
||||
|
||||
Reference in New Issue
Block a user