feat(workflows): M6 — dashboard run-history + DAG view
A per-app "Workflows" tab: list definitions, browse run history, and inspect a run's per-step progress with a static layered DAG. - API: the run-detail endpoint now returns each step's `depends_on` (loaded from the definition via a new `get_workflow_by_id` reader) so the dashboard can draw the graph edges — the run's step rows don't carry them. - dashboard `$lib/api`: a `workflows` namespace (list / runs / start / run) + the matching types. - `apps/[slug]/workflows/+page.svelte`: workflow table → drill into runs → drill into a run. The run view renders a step table plus an SVG DAG (nodes = steps colored by status, laid out by longest-path level; edges = depends_on, arrowed) and a "Start run" button. Nested/parked steps show their child run. - AppTabBar + the per-app layout gain the Workflows tab (admin-only). Tests: `npm run check` clean (0 errors); two Playwright smoke tests (navigation tabs — workflows loads cleanly + renders its empty state) pass against the full stack. With M6 the v1.2 Workflows track (M1 schema/validation → M2 durable orchestrator → M3 when/templating → M4 nested → M5 SDK/API/CLI → M6 dashboard) is COMPLETE. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -26,8 +26,8 @@ use uuid::Uuid;
|
||||
use crate::app_repo::AppRepository;
|
||||
use crate::authz::{require, AuthzDenied, AuthzError, AuthzRepo, Capability};
|
||||
use crate::workflow_repo::{
|
||||
get_run, get_workflow_by_name, list_run_steps, list_runs_for_workflow, list_workflows_for_app,
|
||||
start_run, NewWorkflowRun, WorkflowRun, WorkflowRunStep,
|
||||
get_run, get_workflow_by_id, get_workflow_by_name, list_run_steps, list_runs_for_workflow,
|
||||
list_workflows_for_app, start_run, NewWorkflowRun, WorkflowRun, WorkflowRunStep,
|
||||
};
|
||||
use sqlx::PgPool;
|
||||
|
||||
@@ -103,10 +103,12 @@ struct StepDto {
|
||||
output: Option<Value>,
|
||||
error: Option<String>,
|
||||
child_run_id: Option<Uuid>,
|
||||
/// DAG edges (from the definition) so the dashboard can draw the graph.
|
||||
depends_on: Vec<String>,
|
||||
}
|
||||
|
||||
impl From<WorkflowRunStep> for StepDto {
|
||||
fn from(s: WorkflowRunStep) -> Self {
|
||||
impl StepDto {
|
||||
fn from_step(s: WorkflowRunStep, depends_on: Vec<String>) -> Self {
|
||||
Self {
|
||||
name: s.step_name,
|
||||
status: s.status.as_str().to_string(),
|
||||
@@ -115,6 +117,7 @@ impl From<WorkflowRunStep> for StepDto {
|
||||
output: s.output,
|
||||
error: s.error,
|
||||
child_run_id: s.child_run_id.map(WorkflowRunId::into_inner),
|
||||
depends_on,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -219,8 +222,29 @@ async fn get_run_detail(
|
||||
let steps = list_run_steps(&s.pool, run.id)
|
||||
.await
|
||||
.map_err(|e| WorkflowsApiError::Backend(e.to_string()))?;
|
||||
// Load the definition for the per-step `depends_on` DAG edges (they live on
|
||||
// the definition, not the run's step rows).
|
||||
let deps: std::collections::HashMap<String, Vec<String>> =
|
||||
match get_workflow_by_id(&s.pool, app_id, run.workflow_id)
|
||||
.await
|
||||
.map_err(|e| WorkflowsApiError::Backend(e.to_string()))?
|
||||
{
|
||||
Some(w) => w
|
||||
.definition
|
||||
.steps
|
||||
.into_iter()
|
||||
.map(|st| (st.name, st.depends_on))
|
||||
.collect(),
|
||||
None => std::collections::HashMap::new(),
|
||||
};
|
||||
let run_dto: RunDto = run.into();
|
||||
let step_dtos: Vec<StepDto> = steps.into_iter().map(Into::into).collect();
|
||||
let step_dtos: Vec<StepDto> = steps
|
||||
.into_iter()
|
||||
.map(|st| {
|
||||
let d = deps.get(&st.step_name).cloned().unwrap_or_default();
|
||||
StepDto::from_step(st, d)
|
||||
})
|
||||
.collect();
|
||||
Ok(Json(json!({ "run": run_dto, "steps": step_dtos })))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user