Views and dashboards¶
The optional Orchestra Views submodule (orchestra_views) exposes the
engine's runtime entities to Views, so you can build dashboards over your
processes without writing code. Enable it and the Orchestra content entities
become Views base tables, complete with relationships, readable labels and a
tenant-aware filter; it also ships ready-made dashboards you can use as-is or
clone, grouped under a Views item in the Orchestra admin menu.
The views over work items live in a second submodule,
Orchestra Inbox Views (orchestra_inbox_views), which depends on both
orchestra_views and orchestra_inbox. They need each side:
the work item comes from the inbox, the Views integration from here. Neither
parent can ship them alone, so enable that module for the task lists.
What it exposes¶
Installing the module attaches a Views data handler to each Orchestra content entity, turning every base field into a field/filter/sort and every reference into a relationship:
| Base table | Entity | Key relationships |
|---|---|---|
orchestra_instance |
Process instance | parent token |
orchestra_token |
Token (a branch in flight) | instance, parent token |
orchestra_variable |
Process variable | instance, token |
orchestra_work_item |
User task (needs orchestra_inbox) | instance, token, assignee, completer |
orchestra_incident |
Incident (a dead-lettered branch) | instance, token, resolved by |
Workflows, tenants and the other configuration live as config entities, which Views cannot use as base tables; the dashboards reach them through the readable fields below instead.
Readable labels¶
The runtime stores machine names for speed: an instance keeps its workflow's machine name, a token keeps a node ID local to that workflow. Two extra fields resolve those to the human labels a dashboard wants:
- Workflow (on the instance): resolves the stored machine name to the workflow's label. The raw value is still available as Workflow (machine name).
- Node label (on the token): resolves the token's node ID against its instance's workflow definition.
- Current step (on the instance): where a running instance is waiting now,
its parked non-timer tokens mapped to their version-pinned node labels (joined
when a run is on several branches at once). Backed by the reusable
orchestra.current_step_resolverservice, so a page and a View report the same step. - State (on the instance): the engine lifecycle, rendered as its human label (Running, Completed, Canceled, Failed) rather than the raw machine value.
- Status (on the instance): the workflow-authored status the current node references, rendered as its vocabulary term label (for example "Under review" or "Approved"), empty when the workflow authors none. An option renders it as a styled tag carrying the author's CSS classes, so a theme can color it.
There is also a Workflow filter on the instance that offers the workflows by label as a select/checkbox list, so you can scope a dashboard to one or more workflows without typing machine names.
State versus Status¶
An instance carries two distinct values, each with a labeled field and a labeled filter:
- State is the engine lifecycle (running, completed, canceled, failed), set by the engine. Its filter offers the four states as a labeled select.
- Status is the workflow-authored value the current node references, a tenant-scoped status vocabulary term that speaks to the business. Its filter lists the vocabulary terms as a labeled select, limited to the current tenant by default (a setting toggles this to list every tenant's terms).
Both filter on the stored machine value, so they stay one query; only the field and filter labels are resolved for display.
Process variables as columns¶
A process carries named variables (see Concepts), and the instance offers a Process variable field that shows one of them as a column. Add the field, name the variable, and choose how to display it:
- Variable: the variable to show. A dotted path reads into a structured
value, the same syntax flow conditions use:
order.totalshows thetotalkey of the array held inorder. A plain name shows a scalar. - Display as: Text, Number, Date (a Unix timestamp or a date string, formatted with the chosen date format), or Yes/No for a boolean.
The field reads the instance-wide value, not a branch's token-local one, and the named variable is loaded once for the whole page, so a column over many instances stays one query. Add the field more than once to show several variables side by side.
Tenant scoping¶
Every Orchestra entity carries a tenant (see Multi-tenancy). The module adds a Current tenant filter to all five entities: enabled, a view shows only the rows of the tenant the request is acting in. The shipped dashboards all carry this filter, so on a multi-tenant site each operator sees only their own tenant's work; on a single-tenant site every row is the default tenant, so the filter simply narrows to everything.
The orchestra_tenant cache context
The filter resolves the active tenant through the same tagged resolvers the
engine uses, and declares the orchestra_tenant cache context so cached
output never leaks across tenants. That cache context ships in the base
module and is reusable by any tenant-scoped render (a block, a custom
dashboard).
The shipped dashboards¶
Enabling the module installs these page views, each scoped to the current tenant. They are ordinary views: clone or edit them freely.
| View | Path | Shows | Permission |
|---|---|---|---|
| Orchestra processes | /admin/orchestra/dashboard/processes |
running instances, by workflow label | Administer Orchestra |
| Orchestra tokens | /admin/orchestra/dashboard/tokens |
live tokens (not consumed/canceled), by node label | Administer Orchestra |
| Orchestra inbox | /admin/orchestra/dashboard/inbox |
open user tasks | Reassign Orchestra tasks |
| Orchestra completed processes | /admin/orchestra/dashboard/completed |
finished instances (completed, canceled, failed), with started/ended times | Administer Orchestra |
| Orchestra process trace | /admin/orchestra/dashboard/trace/{id} |
one instance's node-by-node history | Administer Orchestra |
| My tasks | /orchestra/my-tasks |
the acting user's own tasks, in two tabs | Process Orchestra tasks |
The inbox and My tasks views ship in orchestra_inbox_views, since they read the
work item; the rest ship in orchestra_views.
Every one of them is reachable from Views in the Orchestra admin menu, next
to Tenants and Settings. Each link is declared by the module that guarantees its
route exists, which is why the two task views come from
orchestra_inbox_views: a link declared by either parent alone would point at a
route that was never registered on a site running only that parent. The trace
view gets no link, since it takes an instance ID and has no argument-less
landing page; the dashboards link to it per row instead.
Process history and trace¶
A finished process keeps its whole execution: every node it passed through is a retained token (the engine consumes a token rather than deleting it, and spawns the next one on the outgoing flow), so the token table already holds the trail. Two views surface it:
- Completed processes lists finished instances with their started (
created) and ended (changed) times, the reporting counterpart to the running dashboard. - Process trace takes an instance ID in its path and lists all that instance's tokens (consumed ones included) in chronological order, showing the node, state, the flow each arrived on and its parent token (the branch it belongs to). The processes and completed dashboards carry a Trace link per row that opens it.
This is a per-node-occupancy trail: each token records when it arrived and its
final state, which reconstructs which nodes ran, in what order, on which branch
and ending how. It is not a full per-transition event log: a token keeps only
its last changed time, and variable changes are not retained. For a durable,
append-only record that survives instance deletion, enable the optional
Audit log submodule.
My workflow instances (requester-facing)¶
The dashboards above are operator surfaces gated by administrative permissions.
For the person who started a run, orchestra_views ships a My workflow
instances view at /orchestra/my-requests: it lists only the instances the
current user initiated (the Initiated by the current user filter over the
initiator), scoped to the acting
tenant, showing each request's reference, workflow, state, status and Current
step, with exposed State and Status filters.
It is gated by a dedicated View own Orchestra process instances permission, so a
requester needs no operator rights. orchestra_ui offers the same list as a
themed card page at /orchestra/my-instances under the same permission; the two
are interchangeable, so keep whichever fits the site and remove the other.
For them to be genuinely interchangeable the Views one renders the same way: its rows are the shared card row, its Reference is the run's correlation key falling back to the run's id (linked to the run, as on the card page), its Status is the authored status on the shared tag with the engine state beneath it as the lifecycle marker, and its Current step is hidden on a row that has none rather than left as an empty column.
It also carries an Action link, the row's call to action: the step of that run
which is waiting on the viewer, opened where its family says it is acted on. Both
this and the themed page ask one service, orchestra.pending_actions, so neither
can offer a step the other does not. A run waiting on somebody else, or on
nothing, shows no link, which is what makes the column safe in a list an operator
can also open.
Giving one step its own look¶
Every control offered for a step carries CSS classes naming that step, so a
design can put its own icon on one step and something else on another without
replacing a template. Two modifiers ride on a base class, both built from machine
names run through Html::getClass():
| Control | Classes |
|---|---|
| The action link (this view, the task lists, the themed card page) | orchestra-action, orchestra-action--{node}, orchestra-action--{workflow}-{node} |
| The operations widget (the task lists and the inbox page) | orchestra-operations, orchestra-operations--{node}, orchestra-operations--{workflow}-{node} |
{node} is the step's machine name inside its workflow definition, and
{workflow} the workflow's own. So node n_review of workflow
change_of_address yields orchestra-action--n-review and
orchestra-action--change-of-address-n-review.
Prefer the workflow-scoped modifier
A node id is local to its workflow, so unrelated workflows routinely reuse
the same one: a site with a handful of workflows will have n_start and
n_end in most of them, and a convention like n_review in several. A rule
written against orchestra-action--n-review therefore fires on every workflow
using that id. Style orchestra-action--{workflow}-{node} unless you genuinely
mean "this step wherever it appears".
All four surfaces emit the same hook, so a step styled once is styled everywhere it is offered.
An icon still needs a name
Styling the control down to a glyph removes the only thing that tells a
screen-reader user which step it opens, and a list of identical unnamed
controls is worse than no icon. Keep the action label in the markup and hide it
visually (core's visually-hidden), rather than replacing the text with the
icon or relying on title.
Current step and Action link are not the same string
The column resolves the run's parked tokens against the version its run
pinned; the link is labeled with the step's action label,
read live. They read alike by default, since both are the node's label, and
diverge exactly where that matters: a node carrying an action_label override,
or one renamed after the run started.
Linking to a run, without typing a path¶
Two shipped fields open one of a run's pages, and both build the link from the route rather than from a path written into the view:
| Field | Opens | Shown when |
|---|---|---|
| Reference (Link to the run on) | The run's read-only page | The viewer may read the run |
| Trace link | The fullest trace the site has: the instance page when orchestra_ui is installed, else the Process trace view here |
The viewer may open that page |
A path in a view's configuration cannot ask whether the viewer may follow it, and it rots the day a route moves: every site that installed the view keeps the old path until the view is re-imported. A field with a route name has neither problem, so prefer these over a custom-text field with a hand-typed path in your own views too.
The Trace link's preference matters on a site without orchestra_ui: this
module does not depend on it, so the Process trace view below is then the only
per-instance trail, and the column opens that. Install the UI and the same column
opens its instance page instead, which shows the same trail plus the run's
summary, variables, incidents and operations, and which a tenant-scoped operator
may open while the trace view is administrator-only.
My tasks (operator-facing)¶
orchestra_inbox_views ships a My tasks view with two page displays, sibling
tabs over the same rows:
- Tasks to process at
/orchestra/my-tasks: what the acting user may act on now, via the Actionable by the current user filter. - Task history at
/orchestra/my-tasks/history: what that user has already processed, via the Completed by the current user filter.
The Actionable by the current user filter reads through any standing delegation, so a stand-in's list includes the work they cover, pooled and already-claimed alike. The history filter does not: it is keyed on the completer, which is always whoever actually acted, so covering for somebody never rewrites their record or borrows it.
A third personal filter, Handled on the current user's behalf, reads the same rows from the delegator's side: what somebody did in your name while you were away. The shipped View carries no display for it, since a tab for an optional submodule's field would sit empty on every site that never delegates; adding it to a duplicate of the history display is a two-click job. The three are not interchangeable, because each asks about a different person:
| Filter | Answers | Keyed on |
|---|---|---|
| Actionable by the current user | May I act on this? | assignee + candidates, read through cover |
| Completed by the current user | Did I process this? | completer |
| Handled on the current user's behalf | Was this mine, done by somebody else? | on_behalf_of |
One completed delegated task legitimately answers the last two at once: it is in the stand-in's history because they acted, and in the delegator's on-behalf list because it was their work.
The three user-shaped columns on a work item (assignee, completer and on behalf of) are stored as plain user ids, so that the work-item table carries no hard dependency on the user entity type. Views therefore exposes them as numeric columns rather than as relationships to the user entity: each gets a filter-by-username, and the readable Held by field renders the display name (with "(for somebody)" appended when a delegation is what brought the work within reach).
On both tabs the Reference column links to the run behind the task, its read-only page: holding the task, or having completed it, is what entitles the viewer to read the run (see read access), so no operator permission is involved. The column is a field with a Link to the run checkbox, not a path typed into the view: the field builds the link from the route, offers it only to a viewer the page admits (plain text for anyone else, never a link to a 403), and carries the list it was clicked on as the return target, so the run's page leads back to that tab with its filters and pager.
They are two displays of one view rather than two views, so both tabs share one
row template and cannot drift apart. The module ships that template, and it needs
no theme work: each row renders as a card on the shared
orchestra_presentation/cards component, the same one the requester's list of
runs uses. To restyle just these tabs, override
views-view-fields--my-tasks.html.twig in your theme and both follow; to restyle
every personal list at once, override orchestra-card-row.html.twig instead.
That template prints one thing, the shared card row orchestra_card_row in
orchestra_presentation, which every shipped personal list renders. Which field
plays which part is said in the row's preprocess, so your own list matches the
shipped ones by building the same card:
#[Hook('preprocess_views_view_fields__my_list')]
public function preprocessMyListRow(array &$variables): void {
$variables['card'] = [
'#theme' => 'orchestra_card_row',
'#fields' => $variables['fields'],
'#title' => 'label',
'#tag' => 'task_state',
'#status' => 'status',
'#lifecycle' => 'state',
'#actions' => ['action_link', 'operations'],
];
}
with views-view-fields--my-list.html.twig being just {{ card }}. A theme can
do the same in its .theme file, for a view it did not build.
The title field is the card's heading, tag renders its own status tag beside
it, status and lifecycle pair into one column (the authored status, with the
engine state beneath it as the always-present marker), and actions sit at the
end of the row, the first of them styled as the card's call to action. Every
field not named becomes a labeled column, in the order the view lists them, so
cloning the view and adding a field lays that field out for you.
The card's own components¶
The row is built from three components orchestra_presentation owns, each a
theme hook a theme overrides once for every Orchestra surface (the personal
lists, a run's own page, the task inbox, and any view of your own):
| Theme hook | Variables | What it renders |
|---|---|---|
orchestra_status_tag |
label, classes |
The prominent pill: an authored status, a task's state, or an engine state where that is all there is to show. |
orchestra_lifecycle_marker |
label, classes |
The engine state as a small muted marker with a state-colored dot. |
orchestra_card_column |
label, value, classes |
A labeled column. value is one value or a list of parts, shown in the order given. |
The tone is always in classes, passed by the caller: a status takes its palette
from its vocabulary term, a task state and an engine state from their own
per-state convention, so a workflow introduces a tone by naming a class your
theme styles, with no code change. The tag and the marker are separate hooks and
a column shows whatever parts it is given, so a surface of your own can show an
authored status without an engine state, or the other way round.
Each row carries the task's label, its own state, who holds it, the business data from its process (through the instance relationship: the initiator, the correlation key, the workflow status as its term label, the engine state and the Current step) and its Operations. The history display swaps the operations for the outcome and the processed time.
These handlers make this work, all of them usable in your own views:
| Handler | Type | What it does |
|---|---|---|
| Actionable by the current user | filter | The visibility rule the inbox applies: assigned to the acting user, or unassigned and pooled either to everyone or to one of their audiences. |
| Completed by the current user | filter | The items the acting user completed, read from the completer recorded at completion. |
| Operations | field | Every action the task offers the viewer, as the inbox offers them: claim it, open it, each non-guarded outcome in one click, reassign it, return a claimed one to its pool. |
| Action link | field | Just the link opening the task where its family declares it is acted on, labeled with the task's action label. Operations already leads with this link, so a display carrying operations needs no separate action link. |
| State | field | The task's own state as its human label (Open, Claimed, In progress, ...), optionally on the shared status tag. Distinct from the engine state of its process. |
| Held by | field | The assignee's display name, who the task is offered to while it is still in a pool, or an explicit unassigned marker: the same wording the inbox uses. |
Three fields on the instance serve any personal list, not just these: Reference (the correlation key, falling back to the run's id, so the column always names the run), Status with its Render as a styled tag option (the tone comes from the status vocabulary term, so a workflow colors its own steps), and State with its Render as a lifecycle marker option (a muted label with a state-colored dot, since the engine state has no term behind it to take a palette from).
Operations and the action link both ask the task's family where it is acted on, and they ask it through the family's pending-action handler, which is the seam every task type declares that surface through. A family shipping its own handler, an interaction task for instance, is therefore opened from a view exactly as it is opened from the inbox page. Both also thread the listing's own URL on as the return target, so acting on a task from a view comes back to that view, on the page and in the sort order you left it.
The two filters are not interchangeable. The first answers may I act on this, the second did I process this. Neither says anything about state, so pair them with a state filter: the shipped displays exclude completed and canceled items from the first tab and keep only completed ones in the second.
An item completed with nobody identifiable acting, such as an external interaction resumed through a bearer link or an engine-driven timeout, records no completer and so appears in no personal history. That is deliberate: there is no user whose history it belongs in. Operations and the action link are likewise empty for a finished task and for a row the viewer may not act on, so both columns are safe in a list a manager can also open.
Carrying the operations column is what makes these lists usable instead of the
inbox at /orchestra/tasks rather than only alongside it: the same actions, built
by the same code, on a surface you can filter, sort and theme. Keep whichever
fits the site.
Building your own¶
Add a view on any of the five base tables and you have the fields, filters and relationships above to work with. A few patterns:
- Per-workflow board: add the Workflow filter (exposed) and group by it.
- Stuck work: on tokens, filter
state = parkedand sort by deadline. - Variables of a process: start from
orchestra_variable, add the instance relationship, expose an instance ID argument. - A team's queue: on work items, start from the shipped My tasks view and swap Actionable by the current user for a plain assignee or candidates filter.
- Open incidents across tenants: on incidents, filter
state = openand drop the Current tenant filter.
Bulk actions¶
To act on rows from a dashboard, enable the Orchestra VBO
(orchestra_vbo) submodule and add a Views bulk operations field to a view.
It ships Views Bulk Operations
actions over the same entities:
| Submodule | Entity | Actions | Permission |
|---|---|---|---|
orchestra_vbo |
Process instance | Cancel process, Delete process | Administer Orchestra |
orchestra_vbo |
Token | Resume (signal) token, Cancel token | Administer Orchestra |
orchestra_vbo_inbox |
Work item (task) | Claim (assign to me), Complete task, Reassign task | Process / Reassign Orchestra tasks |
The process and token actions (orchestra_vbo) are plain Drupal action plugins,
so they also work with core's bulk-actions field. The task-inbox actions
(orchestra_vbo_inbox) need orchestra_inbox; Complete task takes an optional
outcome to record on each task, and Reassign task a target user. Each action
is gated on the matching permission.