Projects & Issues

Agile Boards and VQL

Track network and security delivery as agile work in Versus Desk — projects, issues, a data-driven workflow engine, Scrum and Kanban boards, sprints, and VQL, the Jira-compatible query language that powers every board, filter, and the REST search API.

Versus Desk includes a full agile issue tracker — projects, issues, boards, sprints, and roadmaps — modeled closely on Jira so a team that already works this way feels at home. It exists so that the delivery work around your automation (fleet upgrades, migrations, remediation, certificate lifecycle) can be planned and tracked in the same platform that executes it, and so a board transition can launch a real Versus workflow run.

Everything that lists or filters issues — the issue navigator, board columns, saved filters, quick filters, webhooks, and automation rules — runs through one query language, VQL (Versus Query Language). VQL is Jira-Query-Language-compatible, so queries you already know keep working, and it compiles to safe, parameterized database lookups rather than raw SQL.


The building blocks

Projects

A project is the container for issues and carries a short uppercase key (NET, SEC, CLD) that prefixes every issue in it. Projects are created from a template — scrum, kanban, or business — which seeds the right features (backlog, sprints, board, timeline, releases, components, reports), a board, default roles, and two starter quick filters. Each project is either company-managed or team-managed.

When an issue is created, Versus row-locks the project's counter and allocates the next number atomically, producing an immutable key like NET-142. The number is monotonic per project and never reused.

Issues

An issue is the atomic unit of work. Issue types carry a hierarchy level that defines the parent/child structure:

TypeHierarchyRole
Epic1Groups stories/tasks/bugs via epic_link
Story / Task / Bug0Standard, board-level work
Sub-task-1Child of a standard issue via parent

Beyond the basics (summary, description, priority, assignee, reporter), an issue carries agile fields: story points, sprint, labels, components, a fix version (release), time-tracking estimates, timeline start/due dates, and a rank for ordering. Issues can be linked to one another (blocks, is blocked by, duplicates, relates, clones, causes) and bridged to the ITIL side of Versus Desk — an agile Issue can link to a Change or Incident Ticket so delivery work and operational work stay connected.

Statuses and the workflow engine

Each issue's lifecycle is driven by a data-driven workflow, not hard-coded states. A workflow is a set of status nodes and transitions stored as rows, so it can be edited without code changes. Every status belongs to one of three categories — To Do, In Progress, or Done — which is what boards, reports, and resolution logic key off.

The built-in Software Simplified workflow ships as:

To Do ──Start Progress──▶ In Progress ──Ready for Review──▶ In Review ──Approve──▶ Done
  ▲                            ▲                                 │                    │
  └────────── Reopen ──────────┴───────────── Reject ────────────┘        Stop Progress

Transitions can carry conditions (who may run them), validators (checks that must pass before the move — a failure aborts the whole transition atomically and returns 400), and post-functions (side effects applied after the move). Moving an issue into a Done-category status sets its resolution and resolved_at; moving it back out clears them, matching Jira semantics. Every transition is written to an immutable transition log and field-history trail.


Boards

A board is a live view of a project's issues, grouped by workflow status into columns. Boards come in two flavors:

  • Kanban — a continuous flow of all matching issues, with an optional "done" cutoff (sub_filter_jql, e.g. resolved <= -2w) so completed cards age off the board.
  • Scrum — shows only the active sprint; the rest of the work is planned on the Backlog page.

A board is defined by a small amount of configuration:

SettingWhat it controls
filter_jqlThe VQL scope of the board (e.g. project = NET)
column_configNamed columns, each mapping to one or more workflow statuses, with an optional WIP max
swimlane_configHorizontal grouping: none, assignee, epic, or component
card_fieldsWhich fields render on each card
Quick filtersNamed VQL snippets AND-composed onto the board on click

Requesting a board's issues returns them grouped swimlane → column, along with assignee facets, quick filters, and per-column insights — issue count, average age, and how many cards have gone stale (no update in three or more days). Dragging a card to another column posts a transition to that column's status; if the workflow has no explicit edge, Versus synthesizes a direct move so a drag never dead-ends.

Ordering with LexoRank

Backlog and board order is maintained with a LexoRank-style scheme: each issue's rank is a base-36 string, and inserting an issue between two neighbors computes a midpoint string. Reordering is O(1) and never renumbers the cards around it. A rebalance pass can regenerate evenly spaced ranks if strings grow long.

To reorder, post the neighbors you're dropping between:

POST /api/agile/issues/{key}/rank/
{ "before": "NET-18", "after": "NET-42" }   // place after NET-18, before NET-42

Sprints

Sprints belong to a board and move through future → active → closed. A board may have only one active sprint at a time.

POST /api/agile/boards/{id}/sprints/          # create a future sprint
POST /api/agile/sprints/{id}/start/           # { "duration_days": 14, "goal": "…" }
POST /api/agile/sprints/{id}/issues/          # { "issue_keys": ["NET-1","NET-2"] }
POST /api/agile/sprints/{id}/complete/        # { "move_to": "backlog" | "<sprint_id>" }

Starting a sprint stamps its start and end dates (14 days by default). Completing it moves incomplete issues back to the backlog (or forward into another sprint) and records a report — committed vs. completed story points — which becomes a data point in the velocity chart. Closed-sprint reports feed three reports on the project: velocity (committed vs. completed across recent sprints), burndown (ideal vs. remaining points for the active sprint, derived from when each issue entered a Done status), and distribution (counts by category and type).


VQL — the query language

VQL is the single query language behind the issue navigator, board and quick filters, saved filters, webhook match rules, and automation-rule conditions. One grammar compiles to one safe Django ORM translation — user input only ever reaches parameterized lookups, never string-concatenated SQL.

Grammar

  • Boolean logic: AND, OR, NOT, and parentheses for grouping.
  • Comparison: =, !=, ~ (contains), !~ (does not contain), <, >, <=, >=.
  • Sets: IN (...), NOT IN (...).
  • Emptiness: IS EMPTY, IS NOT EMPTY (NULL accepted as a synonym).
  • Ordering: ORDER BY <field> [ASC|DESC], comma-separated.
  • Functions: currentUser(), now().
  • Relative dates: signed offsets in days, weeks, hours, or minutes — -1d, -2w, +3d.

Fields

FieldNotes
projectProject key
type / issuetypeIssue type name
status, statusCategoryStatus name, or category (todo/in_progress/done)
assignee, reporterUsername; use with currentUser()
priority1 (highest) … 5
resolutionUnresolved matches empty
summary, description, texttext searches summary and description
key, labels, component, sprint
epic / epicLink, parentBy issue key
storyPoints, rank
created, updated, resolved, dueDates; use relative offsets
cf[key]Any custom field, matched by its key

Examples

project = NET AND status != Done ORDER BY priority ASC, updated DESC
assignee = currentUser() AND sprint IN ("NET Sprint 3") AND resolution = Unresolved
type = Bug AND priority <= 2 AND created >= -7d AND labels IN (regression, panos)
epic = NET-1 AND cf[environment] = Production AND text ~ "BGP"

Custom fields are resolved without a per-field migration: cf[key] compiles to a lookup against the issue's stored field values. Because label and custom-field predicates can multiply rows, VQL de-duplicates those results automatically.

Searching over the API

The navigator and every list route through one search endpoint:

POST /api/agile/search/
{ "jql": "project = NET AND status != Done", "startAt": 0, "maxResults": 100 }
200 OK
{ "total": 42, "startAt": 0, "maxResults": 100, "issues": [ … ] }

Validate a query without running it (for live editor feedback) at POST /api/agile/search/validate/, which returns any syntax error, unknown field, or unknown order field with its character position. The plain list endpoint GET /api/agile/issues/?jql=… accepts VQL too.

Isolation is never the query's job. VQL only filters. Tenant and permission scoping are applied to the base result set by the server before VQL runs, so a query can never widen what a caller is allowed to see. See Platform Architecture for how tenant isolation is enforced in the data layer.


The orchestration seam — issues that run workflows

This is what ties the tracker to the rest of Versus. A workflow transition can carry a binding to a Versus automation workflow, in one of two modes:

  • launch — moving the issue starts a real workflow run fire-and-forget; on success the issue auto-advances to a configured status.
  • gate — the issue moves provisionally and is flagged run_gated until the launched run finishes; the run's outcome then drives the terminal move (advance_on_success) or a failure path (on_failure).

When the bound run reaches a terminal state, a run → issue callback reads its status and advances the issue, optionally posting the run's result as a comment. In the shipped demo, moving an issue from To Do to In Progress launches a safe multi-section deploy workflow and auto-advances the issue to In Review when the run completes — no device is touched.

Automation rules generalize this to events rather than manual transitions. A rule fires on issue_created, issue_transitioned, field_changed, commented, or scheduled, optionally filtered by a VQL expression, and launches a workflow. Rules are throttled and hash-chain audited — each firing extends a running hash so the history is tamper-evident.


Everything else the tracker carries

  • Backlog & timeline. The Backlog page plans future sprints against the ranked backlog; the Timeline/roadmap renders epics with nested child bars, sprint bands, and dependency links — and warns when a dependent issue is scheduled to start before its blocker finishes.
  • Portfolio plans. A Plan groups several projects onto one shared roadmap with a per-project delivery summary and a team capacity view that flags over-allocated assignees.
  • Time tracking. Log work against an issue (/worklog) and set original/remaining estimates (/estimate); worklogs roll up into time spent and remaining.
  • Releases & components. Releases act as fix versions (releasing one warns, but does not block, on unresolved issues); components carry a lead and default assignee.
  • Webhooks & notifications. Outbound webhooks fire on issue events (optionally VQL-filtered and signed with a shared secret) on a background thread, so a slow endpoint never blocks a request; in-app notifications reach the assignee and reporter.
  • Permissions. A project can attach a permission scheme whose grants narrow who may browse, create, edit, transition, or administer. With no scheme configured, the module is usable out of the box (default-allow), and superusers/staff always pass.
  • Jira import. POST /api/agile/import-jira/ ingests a Jira export JSON to bootstrap projects and issues.

How an operator uses it

  1. Create a project from the Scrum or Kanban template — you get a board, roles, and quick filters immediately.
  2. Fill the backlog with epics, stories, tasks, and bugs; rank them by dragging.
  3. Plan a sprint (Scrum) or work the flow directly (Kanban); drag cards across columns to transition them.
  4. Filter with VQL in the navigator or as board quick filters; save the ones you reuse.
  5. Bind delivery to automation — configure a transition or automation rule to launch a Versus workflow, so moving an issue actually performs the change.
  6. Review velocity, burndown, and the roadmap as the sprint runs.

Related