Update Linked Test Case Versions - sebiboga/testlink-upgraded GitHub Wiki
Update Linked Test Case Versions (planUpdateTC) — Wiki
The modernized Update Linked Test Case Versions screen replaces the legacy
lib/plan/planUpdateTC.php. It re-links the test case versions attached to a
test plan to newer (or any user-chosen) active versions, remapping
executions and custom-field execution values exactly like TestLink 1.9.20
(doUpdate() / doBulkUpdateToLatest() → testplan::get_linked_and_newest_tcversions()).
Path: Test Plan > Update Linked Test Case Versions (requires an active test plan)
URL: gui/templates/plans/planUpdateTC.html?tproject_id=<id>&tplan_id=<id>
BFF API: api/plans/index.php (REST: GET /update-linked;
POST /update-linked/selected, /update-linked/latest)
Prerequisite: menu visibility needs testplan_update_linked_testcase_versions;
every mutation requires testplan_planning (same as legacy rightsAnd check).

Table of Contents
- Screen Layout
- Row States & Legacy Parity
- Three-Table Remap
- Rights Model
- REST API Reference
- i18n Keys
- Testing
1. Screen Layout
- Teal Dashio header: title, subtitle, locale switcher.
- Toolbar: test project name, test plan picker, linked-count chip, reload, Update ALL to latest version, locale switcher.
- Left panel — suite list with updatable counts as badges; clicking a suite switches the table.
- Right panel — per-suite table with columns:
- tick checkbox + Target Version dropdown (
--default; every active sibling version offered, newest tagged). - Test Case (external id + name) with links to Test Specification
(
tcView.html) and Execution History (execHistory.html). - Linked Version, Newest Active Version, Status badge (Latest / Updatable / linked version inactive), Executed counter.
- tick checkbox + Target Version dropdown (
- Footer: selected count + hint line.
- Confirm modals for both bulk actions list each old → new pair before applying.
2. Row States & Legacy Parity
- Rows flagged Latest still expose checkbox + target select whenever another
active sibling exists — legacy
tideUpForGUI()allowed re-linking an up-to-date case to any other active version (downgrades included). Only rows whose linked version is inactive or has no active siblings are read-only. - The green "already newest" banner shows when nothing is updatable.
- Since #624 a row counts as Latest only under the exact legacy rule of
testplan::get_linked_and_newest_tcversions(): among ACTIVE siblings take those with node id > linked id, newest = max-id candidate, and require its VERSION number > linked version number (node-id order ≠ version order after imports). The Linked version inactive badge outranks the green Latest badge.
3. Three-Table Remap
Both mutations re-link using the LIVE link row read from the database and remap,
in one transaction-style sequence, exactly like legacy doUpdate():
testplan_tcversions— link moved to target tcversion id;executions— execution history follows the new version;cfield_execution_values— custom field values follow too. The plan↔project binding is validated server-side on every request (400 when the suppliedtproject_iddoes not match the plan's parent), preventing cross-project manipulation.

4. Rights Model
| Right | Effect |
|---|---|
testplan_update_linked_testcase_versions |
ASIDE menu entry visible |
testplan_planning |
required by all three BFF routes (403 otherwise) |
No-rights users get a persistent localized error state in the screen and a 403 JSON from the API.
5. REST API Reference
| Method & route | Purpose |
|---|---|
GET /api/plans/index.php/update-linked?tproject_id=&tplan_id= |
Context payload: project/plan info, suites with updatable counts, per-item linked/newest/target versions + executed qty + state (ok / empty / pick). Since #624: tplan_id=0 with a valid tproject_id no longer 400s — it returns the picker payload (tplans, tplan:null, empty suites/items) so the screen boots into plan-selection mode |
POST /update-linked/selected {updates:{tcId:tcversionId}} |
Re-links each listed case to the chosen active version (3-table remap); returns updated count |
POST /update-linked/latest |
Bulk move of every non-latest link to its newest active version (legacy get_linked_and_newest_tcversions()) |
Errors are JSON with error_code: NO_TPLAN, NO_TPROJECT,
NO_RIGHTS (403), plus per-pair validation failures.
6. i18n Keys
All UI strings use the client-side TLi18n module with uptc.* keys present
in all ten locale bundles (en, ro, de, es, fr, it, ja, pt, ru, zh),
including uptc.noPermission, uptc.description, uptc.feedbackUpdated
({n} placeholder). Since #624 apiFail() maps server error_codes to
localized toasts: NO_TPLAN→uptc.errNoTplan,
NO_TPROJECT/BAD_TPLAN→uptc.errNoTproject, NO_SELECTION→uptc.noSelection;
unknown codes fall back to the raw server message.
6a. Issue #624 Fix — is_latest semantics & boot dead-end
Root cause chain
updTcItems()derivedis_latestfrom node-id ordering only (max(active sibling id) <= linked id) — legacy requires the additional version-number condition (testplan.class.php:1150,1158). A link on version 5 whose v3 sibling had a higher node id was offered a "downgrade as update"; bulk preview disagreed with the bulk action.- Badge priority rendered green Latest before checking
linked_active, masking inactive-linked rows. - GET with
tplan_id=0hard-failed 400NO_TPLAN, leaving an empty picker (dead end) plus a raw English toast (apiFail()had no error_code map). - Minor: footer printed bare
"0 ";getDBTables()called inside the per-item loop of POST/latest.
Fix approach — replicate the legacy candidate rule in updTcItems()
(active sibling with id>linked AND version>linked ⇒ updatable; newest = max-id
such candidate), keep all active non-linked siblings as manual targets
(tideUpForGUI() parity); add context-only GET mode returning state:'pick'
with the plan list; reorder badge checks (inactive first); map error_codes to
TLi18n keys added to all 10 bundles; localize the 0-row footer; hoist
getDBTables().
Before / after evidence:

7. Testing
Full suite (15 cases) in tmp/TLU_Test_Cases.md — Suite 619, 15/15 PASS:
rendering, suite switching, update-selected with non-newest target, executions
remap proof, bulk-to-latest, no-selection guard, cancel path, empty plan,
permission paths (BFF/screen/menu) and cross-project IDOR guard.
Regression Suite 624 (11 cases R1–R11, all PASS) covers the #624 fixes:
id-order vs version-order edge (v5-linked row stays Latest), inactive-badge
priority, preview==bulk-action agreement, pick-mode boot, localized error
toasts and footer.
