Priority Bar Chart Modernized - sebiboga/testlink-upgraded GitHub Wiki
Modernization of the Priority Bar Chart report
(lib/results/priorityBarChart.php) β GitHub issue
#1845; the three
defects found while building and reviewing it are
#1846,
#1847 and
#1848.
With every ASIDE entry already pointing at a modern screen, this report was
picked by the sweep as the smallest coherent legacy slice that still had no
modern twin β and it turned out to be an orphan that could not run at all
on 2.0.1. It is now a standalone Dashio page
(gui/templates/results/priorityBarChart.html) backed by a plain-PHP REST BFF
(api/prioritybarchart/index.php), reachable from the Results by Keyword
section of the modern Graphical Charts report.

Path: Results β Graphical Charts β Results by Keyword β Open
Priority Bar Chart
URL: gui/templates/results/priorityBarChart.html?tplan_id=N&tproject_id=P
BFF API: api/prioritybarchart/index.php (GET|HEAD ?action=init&tplan_id=N[&tproject_id=P])
Legacy URL: lib/results/priorityBarChart.php β now a launcher shim (302 / 405 / 400)
Auth: session (401 anonymous) + same-origin CSRF guard + the
testplan_metrics right on the owning test project.
| Legacy statement | Reality in 2.0.1 |
|---|---|
require_once("../../third_party/charts/charts.php") |
file gone β phpchart dropped with third_party/charts/
|
require_once("../functions/results.class.php") |
file gone β split into per-area classes |
results::getAggregateKeywordResults() |
method gone with the class above |
Any request β uncaught Error β HTTP 500 with an empty body. Measured
before the fix:
curl -s -o /dev/null -w '%{http_code}\n' -b <cookie> \
'http://localhost:8082/lib/results/priorityBarChart.php?tplan_id=1&tproject_id=1'
# 500
On top of that it was an IDOR: testlinkInitPage() validates the session
only, tplan_id came raw out of $_REQUEST and hasRight() was never called,
so any authenticated account could read the per-keyword result breakdown of
any test plan in the installation. tproject_id was read from the session and
never compared with the plan's real owner, so the project "assertion" was
decorative.
-
Context strip β test project, test plan, assigned versions, platforms,
keywords (
3 in projectvs2 plotted, so an unused keyword is visible). -
One stacked horizontal bar per keyword β passed (teal) / failed (red) /
blocked (dark) / not executed (grey) with the progress percentage on the
right,
1 din 2 executate (50%)style. - DataTable β the same numbers plus the raw result count and a bold Totals row.
-
Export CSV β
;separated, UTF-8 BOM, one line per keyword + aTotalsline, namedpriority-bar-chart-<plan>.csv. - Refresh and Close (pops the report, like every other modern report).
- State cards for bad request / session expiry / no right / plan not found / project mismatch / server error β each localized, each keeping the raw machine code visible so a support request is self-explanatory.
- Empty state β "no keyword of this test project is attached to a test case of this test plan" instead of an empty chart box.
tcversions has no testcase_id column in 2.0.1, the planβversion link is a
plain testplan_tcversions row (assign_tcversion_to_plan() no longer exists)
and builds hang off builds.testproject_id. The bar is built like this:
- universe = distinct
tcversion_idintestplan_tcversionsfor the plan; - each version is attributed the result of its latest execution row
(
MAX(executions.id)for plan + version) β a version passed then failed is counted once, as failed, and the four buckets always partition the keyword total exactly; - a version with no execution row falls into not executed;
- the raw number of execution rows per keyword is its own column, so work done in several builds stays visible;
- progress % = executed versions / versions in the keyword.
Measured on the fixture: login = 4 versions / 2 failed / 1 blocked / 1 not run
/ 4 results / 3 executed / 75 %; checkout = 2 versions / 1 passed / 1 not run
/ 50 %; totals 6 versions, 5 results, 4 executed, 66.7 %.
| Case | Answer |
|---|---|
?action=init&tplan_id=N |
200 {status:"ok", context, keywords[], totals}
|
no / non-numeric / array / negative tplan_id, unknown action |
400 invalid_request
|
| no session | 401 not_authenticated (or session_expired) |
no testplan_metrics on the owning project |
403 no_right + tLog audit row |
| unknown plan | 404 tplan_not_found
|
tproject_id β owning project |
404 project_mismatch
|
POST with same-origin Origin
|
405 method_not_allowed
|
POST without same-origin proof |
403 CSRF (shared bffEnforceSession helper) |
HEAD |
200 (safe verb) |
Every response carries Cache-Control: no-store + X-Content-Type-Options: nosniff,
and every parameter is a bound placeholder, never interpolated.
lib/results/priorityBarChart.php keeps testlinkInitPage() β so an anonymous
caller still lands on login.php?note=expired, the 1.9.20 contract β and then:
-
302 for a browser navigation β
priorityBarChart.html?tplan_id=β¦&tproject_id=β¦(tproject_iddefaults to the session project when absent), -
405
modern_endpoint_onlyfor an XHR caller (the legacy answer was a PNG β there is nothing left to serve), -
400
invalid_requestJSON whentplan_idis missing.
No ASIDE row, template or JS caller of the legacy file is left; the only two mentions left in the repo are explanatory comments.
-
$actions->priorityBarChartinlib/functions/common.php, inside the$tplan_id > 0guard, so the report is plan-scoped like every other plan report; - a visible Open Priority Bar Chart button on the Results by Keyword
section of
gui/templates/results/charts.htmlβ the report family that shares this aggregate:

46 pbc.* keys + footers.priorityBarChart + charts.openPriorityBarChart in
all 10 client bundles (en ro de fr es it pt ja ru zh), validated with
python3 -m json.tool. Verified in the browser by switching the locale: header,
subtitle, legend, the progress text, all eight table headers and the footer
translate and no raw dotted key ever reaches the DOM.
| # | Defect | Symptom | Fix |
|---|---|---|---|
| #1846 |
fetchFirstRowSingleColumn() called with 1 argument instead of 2 in the first BFF commit |
every request answered HTTP 500 with an empty body |
get_recordset() + (int) $rs[0]['n']
|
| #1847 |
bar() composed its label key from the CSS class β pbc.seriesNot_run, a key that exists in no bundle |
the grey segment's tooltip showed the raw dotted key (TLi18n.t() has no fallback β the #1840 class); invisible in the a11y snapshot |
the four series are declared in one SERIES map |
| #1848 | the unknown-failure branch fed the server message to TLi18n.t(), leaving pbc.serverErrorBody dead |
a 500 printed an English server string inside a localized card | localized sentence + escaped raw detail + http_<status> fallback, {status} interpolated |
The mandatory code review added 6 findings on top: the
order-of-operations comment contradicted the code and the tproject_id
assertion ran before the rights check (an IDOR probe β now the api/tcsummary
order), platform_id = 0 counted as a real platform, a redundant
GROUP BY/COUNT(*) in the latest-result query, a duplicated version map, and
unquoted CSV cells. All fixed.
Suite 1845 in tmp/TLU_Test_Cases.md β 71/71 PASS
(python3 tmp/suite_1845.py, exit 0) plus 9 browser cases:
- auth: admin 200, anonymous 401, a real role-3 login 403 (also with a
forged
Origin), the refused right audited in the Event Viewer; - the aggregate semantics in both directions (latest-wins, exact partition, progress %, raw result count);
- project isolation proven with a second project + plan β each plan reads its own
keywords, a foreign
tproject_idis refused; - the full 400/401/403/404/405 matrix,
HEAD,no-store/nosniff; - all four legacy-shim branches;
- wiring, and all 10 bundles.
Event Viewer: no new Warning/Error row β the only log_level=1 entries are
the deliberate no_right audit rows, same convention as
api/namecheck/checkDuplicateName.php.
Fixture: tmp/fixtures_1845.php β project PBC1 + plan PBC Plan (keywords
checkout/login + one unused), plan PBC Empty Plan with no assigned version,
project PBC2 + plan PBD Plan holding foreign-keyword, user pbcnorights
(role 3).
Docs mirror: docs/Priority-Bar-Chart.md.