Task Issue 1040 TcView Relations - sebiboga/testlink-upgraded GitHub Wiki
TestLink 1.9.20 renders, inside every version panel of the Test Case Viewer, the relations
block from gui/templates/dashio/testcases/include/relations.inc.tpl, included once per
version by gui/templates/dashio/testcases/tcView_viewer.tpl:590-596:
- header "Relations with other test cases (N)" (count = rows after filtering),
- a New relation: row — type combo (
is parent of/is child of/blocks/depends on/is related to/Also Automates/Is Automated By/is executed together with, each with a_source/_destinationvariant) plus an external-id box (PREFIX-ID) and an Add button, - a table
# / Type | Test Case | Set by | (icon)with one row per relation, a link to the related case (EXTID: name [Version N]), the author (tooltip Created {ts} by {who}), and either a trash icon (delete) or a warning icon carrying the reason the relation cannot be deleted, - an
Ext.Msg.confirmmodal — Really delete relation #%i? — and an info line rendering the legacyadd_relation_feedback_msgsentences.
The modern viewer had a stub for it that could never render anything:
-
api/testcases/index.phpjoined a table that does not exist in TestLink:LEFT JOIN relation_type RT ON TR.relation_type = RT.id
testcase_relations.relation_typeis a baresmallint; its meaning lives in the config arraytestcase_cfg->relations->type_labels(config.inc.php:1331), exactly like legacytestcase::getRelationLabels(). The tablerelation_typehas never existed —ERROR 1146 (42S02): Table 'testlink.relation_type' doesn't exist. -
The same query filtered on the test case id while
testcase_relations.source_id/destination_idhold tcversion ids, so even a fixed JOIN would have matched nothing. -
The DB call was wrapped in a silent
catch, sorelationswas always[]. -
renderRelations()intcView.htmlwas called once for the whole screen (not per version) and returned early when the list was empty, so the section could never render even a valid empty-but-editable state. -
There were no write endpoints at all — add and delete existed nowhere in 2.0.1.
| Helper | Legacy twin |
|---|---|
tcRelationLabels() |
testcase::getRelationLabels() — reads testcase_cfg->relations->type_labels and lang_get()s it, returns null when relations are disabled |
tcRelationTypeDomain() |
testcase::getRelationTypeDomainForHTMLSelect() — <code>_source / <code>_destination, the _destination variant omitted when both labels are identical, 3_source (related_to) preselected |
tcVersionRelations() |
testcase::getTCVersionRelations() — one query per tcversion, both sides (source → related and related → source), side-aware label, id / <relation_type_localized> composite key, cannot_delete_reason for the warning icon |
New response keys on action=view:
Row shape (cannot_delete_reason is empty when the user simply may not edit — legacy
$cannotDelMsg = ''):
{
"id": 14, "related_id": 4, "relation_type": 2, "relation_type_label": "blocks",
"external_id": "TR1-3", "link_status": 3,
"related_tcase_name": "TCR Case R Third",
"related_tcversion_id": 9, "related_version_number": 1,
"author": "admin", "author_ts": "2026-01-06 10:00:00",
"can_delete": false, "cannot_delete_reason": "can_not_delete_a_frozen_relation"
}Two new POST actions:
| Action | Legacy twin | Notes |
|---|---|---|
add_relation |
testcaseCommands::doAddRelation() |
tcversion_id, relation_type (3_source…), relation_destination_tcase; resolves the typed id with getInternalID() (same project, `_fcp_id: int |
delete_relation |
testcaseCommands::doDeleteRelation() |
requires mgt_modify_tc, verifies the relation belongs to one of the case's tcversions, blocks frozen relations, then testcase::deleteRelationByID()
|
Both actions inherit the endpoint's existing CSRF/same-origin and rights gates.
- The obsolete global card is gone;
renderRelations(v)is called fromrenderVersion(v), reproducing the legacy per-version inclusion. - Section visibility reproduces the legacy
$canWork:editEnabled && (is_latest || !onlyOnLatest). The add row additionally requires!v.is_frozen(legacyif is_frozen == 0). -
REL_I18Nmaps the BFF's snake_case legacy codes (relation_added,testcase_doesnot_exists,can_not_delete_a_frozen_relation, …) to the camelCase bundle keys. Unknown codes fall through toTLi18n.t()so a missing translation is visible instead of silent. -
platApiPost()'s.fail()handler was fixed: it forwarded only{status, message}and discardedfeedback_code/feedback_arg, so every 4xx showed the generic "Error adding new relation." instead of the legacy sentence.
26 new tcview.rel* / tcview.newRelation / tcview.deleteRel* / tcview.tcaseRelation*
keys in de, en, es, fr, it, ja, pt, ro, ru, zh (the 26 legacy label/meta keys from
gui/templates/dashio/testcases/include/relations.inc.tpl).
-
relation_typeis not a foreign key. 5 configured values (config.inc.php:1331-1339):1parent/child,2blocks/depends,3related,4automates/is automated,5executed together. Legacy drops any row whose type is not in that array — kept (fixture relation13withrelation_type = 9proves it). -
Relations are per-version.
source_id/destination_idare tcversion ids, so the same test case shows different rows on different versions. The modern screen only ever asked the API for one version, sorelationsByVersionis keyed by tcversion id andrelationscarries the requested one. -
Side-aware labels. When we are the destination the label is the other half of the
pair (
is child ofinstead ofis parent of) — legacygetTCVersionRelations()readstype_labels[tc_id]['destination']. -
Non-latest versions show nothing while
$tlCfg->testcase_cfg->addTCVRelationsOnlyOnLatestTCVersionisTRUE(config.inc.php:1391) — legacy wraps the whole section in$canWork. Reproduced, not "fixed". -
fetchFirstRow()returnsfalse, notnull, when nothing matches; the legacy-styleif ($x == null)guard silently letfalsethrough. -
Legacy duplicate detection is broken and stays that way.
testcase::addRelation()callsrelationExits()with the unmapped arguments (lib/functions/testcase.class.php:8206-8211), so a duplicate is never detected and the row is inserted twice. Do not "fix" this without a separate issue.

Full suite in tmp/TLU_Test_Cases.md — Suite 1040, 34 PASS / 0 FAIL (A3 records the
pre-fix baseline). Covers: the broken query, payload before/after, per-version rendering,
combo contents, side-aware labels, unconfigured-type drop, frozen / non-latest versions,
add (UI + duplicate + empty + bogus type + frozen source + frozen destination), delete
(UI + modal text + frozen + foreign relation), rights, CSRF, editOnExec=1 read-only,
console, Event Viewer, PHP log, all 10 i18n bundles, and the untouched blocks on the screen.
| File | Purpose |
|---|---|
api/testcases/index.php |
tcRelationLabels(), tcRelationTypeDomain(), tcVersionRelations(), relationsConfig / relationsByVersion payload, add_relation, delete_relation
|
gui/templates/testcases/tcView.html |
per-version relations section, add form, table, delete modal, warning icons, platApiPost() error-body forwarding |
gui/templates/i18n/*.json |
26 new keys × 10 locales |
tmp/fixtures_1040.sql |
idempotent fixture: 3 cases / 4 versions / 7 relations covering every legacy branch |
issue-1040-tcview-relations.png |
screenshot of the restored section |