Organizing Cases: Projects & Sections

Once you have more than a handful of YAMLs on the agent, the flat Tests list becomes hard to navigate. Two orthogonal axes keep it tidy:

  • Project — a first-class container (TestRail-like), stored in a registry. One case belongs to at most one project. Used by the Project filter.

  • Section — free-text metadata copied from the TestRail section (e.g. TR-181, TR-181 Mesh). The Tests list groups on it.

Both are pure organisation — they never change how a case converts or runs.

At a glance
  • Projects live in a registry (GET/POST /api/projects). Cases reference a project by name.

  • Project filter dropdown narrows the list to one project (or Unassigned).

  • Section sorts/groups the list; a case keeps its TestRail section.

  • Bulk assign — tick rows → assign to a project in one call.

  • Backfill org fields (project / refs / priority / type) from TestRail without re-running the converter — POST /api/testrail/backfill-fields.

Projects Registry

A project is a row in the projects SQLite table (scripts/case_db.py), sharing the same DB file as the auth layer. Columns: name (primary key), description, testrail_project_id, created_by, created_at.

Endpoint Role Purpose

GET /api/projects

any authed

List projects, each with a live case_count (YAMLs whose project matches).

POST /api/projects

admin / editor

Create a project (name required; duplicate name → 400).

PATCH /api/projects/{name}

admin / editor

Rename and/or set description. A rename rewrites the project field of every affected YAML.

DELETE /api/projects/{name}

admin / editor

Delete the project (see Unassigned below).

POST /api/tests/assign-project

admin / editor

Bulk-assign a list of files to one project.

The "Unassigned" bucket

There is no literal Unassigned row in the registry. A case whose project is empty ("") is unassigned, and the UI surfaces those under a virtual Unassigned option in the Project filter.

Deleting a project does not delete its cases. The DELETE /api/projects/{name} handler first walks every YAML, and for each case that belonged to the deleted project it clears the project field (orphans it to Unassigned), then removes the registry row. No test is ever lost by a project delete.
Importing from TestRail auto-registers the project (insert-if-absent via ensure_project, created_by="testrail-import"), so you rarely create one by hand. The + New project action (POST /api/projects) is for projects you want to exist before any case lands in them.

Section Grouping & the Project Filter

The Tests tab list is sorted by section first, then by case ID, so cases sharing a section sit together. The section value comes straight from TestRail (test_case.section) and is editable in the Editor.

Above the list, a Project dropdown narrows what you see:

  • All projects — no project filter (default).

  • Unassigned — only cases with an empty project.

  • <name> (N) — only that project’s cases; N is the live count.

The filter is independent of the Portal filter and the search box — they compose (project AND portal AND text match). The same dropdown appears on the Editor tab.

Case Metadata Fields

Beyond section, each case carries a small set of organisational fields. project and section live at the top level of test_case; the rest live under test_case.metadata. They surface in the list as pills/links and in the Editor as form inputs.

Field Stored as Rendering

Project

test_case.project

Drives the Project filter and case_count. Editor exposes a dropdown of registered projects.

Section

test_case.section

Free text; groups the list.

Reference

metadata.references

A Jira issue key (e.g. DEV-1234). Rendered as a clickable link when the jira_base_url setting is set (<jira_base_url><key>), otherwise as plain text. Multiple comma/space-separated refs each become their own link.

Priority

metadata.priority

A coloured pill — Critical (red), High (amber), Medium (blue), Low (green).

Type

metadata.type

A grey pill (e.g. Functional, Regression, Smoke & Sanity).

Author

metadata.created_by / metadata.edited_by

Who imported vs. who last saved an edit. Set automatically; not editable by hand.

The jira_base_url is read from Settings and exposed (read-only, non-secret) at GET /api/ui-config. Leave it blank and references render as plain text instead of links.

Populating the Fields

There are three ways org fields get filled, from most to least automatic.

1. TestRail import (automatic)

Importing a case resolves and registers its Project from the case’s TestRail project/suite, and maps Priority / Type / Reference from the TestRail priority_id / type_id / refs into metadata. Bare-number refs are key-ified by the converter using a Jira prefix (e.g. 11278DEV-11278).

2. Manual edit (Editor)

The Editor form has Project (dropdown), Section, Priority, Type, and Reference inputs. Save persists them into the YAML and stamps edited_by.

3. Bulk assign (multi-select)

On either the Tests or Editor list, tick several rows and choose a project to assign them all in one shot:

curl -X POST http://ai.friendly-tech.com:8083/api/tests/assign-project \
  -H 'Content-Type: application/json' \
  -d '{"files":["C7677.yaml","C7681.yaml"],"project":"TR-181 Mesh"}'

Path components in files are rejected (basename-only, no traversal). The response reports {"assigned": N}.

Backfilling Org Fields (no re-convert)

After you have already imported a corpus, you often want to enrich the existing YAMLs with Project / Reference / Priority / Type pulled fresh from TestRail — without re-running Stage 2 LLM enrichment (which would re-cost and risk jitter) and without bumping the converter cache.

POST /api/testrail/backfill-fields does exactly that. It matches local YAMLs to TestRail cases by numeric ID, computes the org-field diff, and patches only those fields.

Field Meaning

project_id / suite_id

Required — the TestRail project + suite to read.

section_id

Optional — narrow to one section.

dry_run

Defaults to true — returns the would-be changes without writing. Set false to apply.

ref_prefix

Prefixes bare-number refs with a Jira key (e.g. DEV-) so 11278 becomes DEV-11278. Tokens that are already keys (DEV-5) are left untouched.

# Preview (no write) — see matched / changed counts + per-case diffs
curl -X POST http://ai.friendly-tech.com:8083/api/testrail/backfill-fields \
  -H 'Content-Type: application/json' \
  -d '{"project_id":1,"suite_id":3,"ref_prefix":"DEV-"}'

# Apply
curl -X POST http://ai.friendly-tech.com:8083/api/testrail/backfill-fields \
  -H 'Content-Type: application/json' \
  -d '{"project_id":1,"suite_id":3,"ref_prefix":"DEV-","dry_run":false}'

The response carries matched (local YAMLs found in the suite), changed (cases whose org fields actually differ), and a details list of per-file diffs. Always run a dry_run first and read the diff before applying.

Backfill is the right tool after a TestRail re-org (a case moved project, got a priority, gained a Jira ref). Use reimport only when the converted output (tree / parameters / steps) needs to change.

See Also