Configuring the Pipeline View
Overview
The pipeline view on a branch or pull request shows a fixed set of canonical nodes — the CI stages you care about most — grouped into categories. Out of the box these are:
Build: Native, Docker
Tests: Client, Server, E2E
Quality: Client, Server
Every configured node is always visible. A node whose CI job has not started yet (or does not run for a given change) is rendered as pending (a dashed, non-spinning icon) rather than being hidden. This is the key difference from the previous behaviour, where a stage only appeared once a matching workflow run existed — so the most interesting stages (notably Build) were frequently missing.
Nodes are declared in configuration and mapped by name to the actual GitHub Actions jobs, so the catalog can be adapted to any CI layout without code changes.
How it works
Helios ingests GitHub Actions
workflow_jobevents and stores each job (name, status, conclusion, timestamps) linked to itsworkflow_run.For a branch/PR, Helios resolves the latest run(s) on the head commit and loads their jobs.
Each configured node matches the jobs whose name starts with any of its
job-name-matchers(case-insensitive), optionally restricted to a workflow viaworkflow-name-matcher.The matched jobs are aggregated into a single node status (see Status aggregation). A node with no matching job is reported as
PENDING.
Note
The catalog is global (one shared configuration, not per repository). For a repository whose CI uses different job names, unmatched nodes simply stay pending. Tune the matchers to the CI you actually monitor.
Configuration
Nodes are defined under helios.pipeline in
server/application-server/src/main/resources/application.yml (or any Spring configuration
source — an application-*.yml overlay or environment variables). The shape is a list of
categories, each with an ordered list of nodes:
helios:
pipeline:
categories:
- name: "Build" # category title shown as a panel header
nodes:
- key: "build-native" # stable, unique identifier for the node
label: "Native" # text shown next to the status icon
job-name-matchers: # any prefix match (case-insensitive) selects the job
- "Build / Build .war artifact"
- key: "build-docker"
label: "Docker"
job-name-matchers:
- "Build / Build and Push Docker Image"
- name: "Tests"
nodes:
- key: "test-client"
label: "Client"
job-name-matchers: ["Test / Client Tests"]
- key: "test-server"
label: "Server"
job-name-matchers: ["Test / Server Tests"]
- key: "test-e2e"
label: "E2E"
job-name-matchers: ["E2E /"]
- name: "Quality"
nodes:
- key: "quality-client"
label: "Client"
job-name-matchers: ["Quality / Client Code Style"]
- key: "quality-server"
label: "Server"
job-name-matchers:
- "Quality / Server Code Style"
- "Quality / Server Code Quality"
Field reference
categories[].nameCategory title, rendered as the panel header. Categories appear in declaration order.
categories[].nodes[].keyStable, unique identifier for the node (used by the client for tracking). Keep it stable across edits.
categories[].nodes[].labelHuman-readable text shown next to the status icon.
categories[].nodes[].job-name-matchersA list of name prefixes. A GitHub Actions job matches the node when its name starts with any of these (case-insensitive). Prefix matching means
"Build / Build and Push Docker Image"also matches the matrix/variant job"Build / Build and Push Docker Image (PR, amd64)". Multiple matchers on one node are OR-ed, and all matching jobs are aggregated together.categories[].nodes[].workflow-name-matcher(optional)When set, a job only matches if its workflow name contains this substring (case-insensitive). Use it to disambiguate identically-named jobs that live in different workflows — e.g. constrain a node to the main
CIorchestrator run.
Finding the right job names
The matchers must correspond to the job names GitHub emits, which are what the pipeline
receives on workflow_job events.
For a normal job, the name is the job’s
name:(or its job id ifname:is omitted).For a reusable workflow call (
jobs.<id>.uses: ./.github/workflows/…), GitHub prefixes the reusable workflow’s internal job names with the caller job name, joined by `` /. So a caller job named ``Buildthat calls a reusable workflow whose internal job is namedBuild .war artifactproduces the job nameBuild / Build .war artifact.Matrix legs append the matrix values in parentheses, e.g.
Build / Build and Push Docker Image (PR, amd64)— which is why matching is prefix-based.
To discover the exact names for a repository, open a recent run in the GitHub Actions UI and read
the job names in the left-hand job list, or inspect the .github/workflows definitions.
The defaults above target Artemis’ single CI orchestrator run
(ls1intum/Artemis .github/workflows/ci.yml), whose Build / Test / Quality /
E2E jobs call reusable workflows and therefore surface as <Stage> / <Job>.
Status aggregation
For each node, the matched jobs are reduced to one human-legible state, chosen so the earliest actionable signal is never hidden and a busy branch never looks idle:
Running (
IN_PROGRESS, spinner) — at least one matching job has started while others are not yet done.Failed (
FAILURE, red) — fail-fast: as soon as any matching job fails, times out, or has a startup failure, the node is red, even while slower legs keep running. The node then links to the failing job (not an arbitrary passing one).Passed / Skipped / Cancelled — once all matching jobs are terminal, the conclusion is the worst-wins result across them:
CANCELLED if any job was cancelled;
else SKIPPED if every job was skipped (e.g. gated out by change-detection on this change);
else SUCCESS if any job succeeded;
else NEUTRAL.
When a node has no matching job yet, its state is inferred from the CI run it belongs to, rather than a permanent “not running yet” — so a node is honest about what is actually happening:
Running (
IN_PROGRESS, spinner) — the CI run is executing but this stage’s job hasn’t been ingested yet (job events routinely lag the run on a busy CI). The node mirrors the run so an active pipeline reads as running rather than idle; the exact per-stage state backfills as the job’s events arrive.Queued (
QUEUED, muted clock) — the run is queued and nothing has started yet. Scheduled, not idle, and visually distinct from the running spinner.Waiting for approval (
ACTION_REQUIRED, amber pause) — the run is gated on a maintainer’s approval (e.g. a first-time contributor). Actionable, not a silent blank.No result (
NEUTRAL) — the run finished but this job never appeared. We cannot tell an intentional skip from a webhook event we simply never ingested, so we make the weaker, honest claim rather than asserting the job was skipped.Not running yet (
PENDING, dashed) — only when no CI run matches the node at all.
The client renders a distinct icon per state: dashed circle (not running yet), muted clock (queued), amber pause (waiting for approval), yellow spinner (running), red ✗ (failed), green ✓ (passed), grey ⊝ (skipped), grey ⃠ (cancelled), grey ? (no result / neutral).
Freshness and the previous commit
The pipeline reflects the branch/PR head commit, resolved purely from ingested webhook runs (no GitHub API calls). Two behaviours keep it trustworthy on fast-moving branches:
The header shows which commit the states are for and whether it is the head. The commit’s short SHA (a link to the commit on GitHub), its subject line, and how long ago it was authored are shown so the developer can recognise it at a glance. If the newest commit has no CI run yet (just pushed, gated, or a missed push webhook), the most recent commit that did run is shown instead, clearly flagged “newest commit not built yet”.
While the displayed commit is still running, a footer summarises the last result — the most recent commit in history that reached a definitive pass or fail (e.g. “Last result ✓ Passed · abc1234”, the SHA linking to that CI run) — for at-a-glance confidence. Inconclusive commits are walked past rather than shown: a cancelled run on PR CI almost always just means it was superseded by a newer push (Artemis’
ci.ymlsetscancel-in-progresson pull requests), so it carries no confidence signal; the same applies to skipped or still-running commits. If no definitive result exists in recent history, no footer is shown. It disappears once the displayed commit is terminal (its own row then tells the whole story).
Note
Freshness is only as current as the branch head Helios has ingested. The head is updated by push webhooks and the periodic repository sync, so a missed push webhook can briefly leave the view a commit behind until the next sync heals it — the displayed commit SHA is always shown so this stays visible rather than silent. Tightening this window (a targeted head refresh on the reconciliation cadence) is a natural follow-up.
Merge gate
An optional helios.pipeline.gate node maps to the CI’s single required-checks job — the one job
that reflects “can this PR merge” (for Artemis, All required CI Passed). It is aggregated with
the same state rules as any node but rendered as a badge next to the pipeline header rather than
inside a category, so merge-readiness is visible at a glance. Omit the key to hide the badge.
helios:
pipeline:
gate:
key: "ci-gate"
label: "All required CI passed"
job-name-matchers: ["All required CI Passed"]
workflow-name-matcher: "CI"
Adding a node
To add, for example, a Documentation quality node that maps to a Build Documentation job:
helios:
pipeline:
categories:
# … existing categories …
- name: "Docs"
nodes:
- key: "docs-build"
label: "Build"
job-name-matchers: ["Build Documentation"]
Add the category/node under
helios.pipeline.categories.Ensure the
job-name-matchersmatch the emitted job name(s) (see above).Redeploy the application server so the new configuration is loaded (see Troubleshooting Helios). No database migration or client change is required — the client renders whatever the
/api/pipelineendpoint returns.
Applying changes
helios.pipeline is ordinary Spring configuration and is read at startup. After editing the
YAML (or the corresponding environment overlay), restart/redeploy the application server for the
change to take effect. Because the client consumes the canonical structure from the server, no
client rebuild is needed to add, remove, or relabel nodes.