Test Plan with Custom Fields Report Modernized - sebiboga/testlink-upgraded GitHub Wiki
Test Plan with Custom Fields Report ā Modernized Screen
Modernization of Test Plan with Custom Fields (lib/results/testPlanWithCF.php)
ā GitHub issue #739.
The legacy Smarty screen is replaced by a standalone Dashio page
(gui/templates/results/tplanWithCF.html) backed by a plain-PHP REST BFF
action inside api/reports/index.php (action=tplan_with_cf). The ASIDE
Reports ā Test Plan with Custom Fields entry now points to the new HTML
screen when a test plan is selected.
Path: Reports ā Test Plan with Custom Fields
URL: gui/templates/results/tplanWithCF.html?tproject_id=<id>&tplan_id=<id>
BFF API: api/reports/index.php?action=tplan_with_cf&tproject_id=<id>&tplan_id=<id>
Right: testplan_metrics enforced server-side (same as legacy).
Table of Contents
1. What the screen does
| Feature | Legacy behavior | Modern implementation |
|---|---|---|
| Page load | testPlanWithCF.php loads CF definitions via cfield_mgr::get_linked_cfields_at_testplan_design() |
BFF action fetches same CF definitions, returns them as cf_columns array |
| Empty state | Warning message "There are no test cases on this test plan, with custom fields ENABLED ON Test Plan Design" | Same warning message shown in info panel, no DataTable rendered |
| Results table | ExtTable with dynamic columns for each CF, grouped by Test Suite | DataTables with sort, search, pagination; grouped by Test Suite path (RowGroup extension), CF columns; extra Test Suite data column hidden |
| Column sorting | Ext grid: EVERY column (Test Suite, Test Case and each custom-field column) is sortable: true; default sort is Test Case ASC (setSortByColumnName('test_case'), sortDirection='ASC') |
Same ā all CF columns are orderable: true in the DataTables config (issue #865); clicking any CF column header sorts the rows by that CF value AāZ / ZāA; default order remains Test Case ASC |
| Group toolbar | ExtTable toolbar: expand/collapse groups button + show all columns button, storeTableState=true |
Dashio toolbar in section header: Expand all groups, Collapse all groups, Show all columns buttons + match-count badge |
| Group collapse | ExtTable expand/collapse per group; hidden rows stay in result set (filterable) | Group headers include chevron + "(N items)"; click a header to collapse/expand that group; hidden rows remain in the DataTables result set |
| CF values | Rendered from CF design values per testplan, filtered rows with all-empty CFs | Same data shape; cf_columns drives column headers, rows with all-empty CF values filtered out |
| Edit link | openTCEditWindow() popup to test case editor |
Edit icon opens tcEdit.html?tcase_id=&tproject_id= (the test case editor) in new window |
| Footer info | Static "Generated by TestLink on ..." text below table | Italic info text + "Generated on YYYY-MM-DD HH:MM:SS" + elapsed time in footer |
| External IDs | Correct single glue-char (e.g. "TPWCF-1") | Same ā buildExternalIdString($prefix, ...) (fix #860 corrected double-glue) |
| Filtering | Ext GridFilters plugin: per-column string filter box under each header (Test Suite, Test Case, each CF column) |
Per-column footer filter inputs (one under each visible column: Test Case + every custom-field column), wired to DataTables dt.column(i).search() ā same per-column behavior (#864) |
| Reset Filters | Ext toolbar "Reset Filters" button (filters.clearFilters()) clears all active column filters at once |
"Reset Filters" toolbar button clears every column search + footer inputs + redraws; hidden until any column filter is active (#864) |
| Locale | Server-side Smarty lang |
Client-side TLi18n module; 18 locale switcher |
2. REST API Reference
All routes require an authenticated session; callers without testplan_metrics get 403.
GET api/reports/index.php?action=tplan_with_cf
Returns test cases with Test Plan Design custom field values.
Query parameters:
| Parameter | Required | Description |
|---|---|---|
tproject_id |
Yes | Test project ID |
tplan_id |
Yes | Test plan ID |
Response:
{
"status": "ok",
"hasData": true,
"tproject_id": 1,
"tplan_id": 10,
"tproject_name": "TP CF Test",
"tplan_name": "Test Plan CF",
"cf_columns": [
{ "id": "CF_TP_Design", "label": "CF TP Design", "name": "CF_TP_Design" }
],
"rows": [
{
"tcase_id": 3,
"tc_external_id": 1,
"external_id": "TPWCF-1",
"tcase_name": "Test Case CF 1",
"suite_path": "Test Suite CF",
"cfields": { "CF_TP_Design": "Option A" }
}
],
"warning_msg": "",
"elapsed_time": 0.01,
"generated_on": "2026-09-04 16:39:37"
}
Error response (no data):
{
"status": "ok",
"hasData": false,
"warning_msg": "There are no test cases on this test plan, with custom fields ENABLED ON Test Plan Design",
"elapsed_time": 0,
"generated_on": "2026-09-04 16:39:37"
}
3. Legacy parity notes
- CF definitions fetched via same
cfield_mgr::get_linked_cfields_at_testplan_design()path - CF values joined via
testplan_tcversions.id(link_id) ā same as legacy getPathLayered()used for Test Suite path display- Rows with all-empty CF values are filtered out (legacy parity)
- Edit link opens
tcEdit.html?tcase_id=&tproject_id=(direct editor, same as legacy #848 fix) - External IDs use single glue-char via
buildExternalIdString($prefix, ...)(#860 fix) - Footer shows italic info text + "Generated on" timestamp + elapsed time (#858 #859 fixes)
- Per-column string filters under each visible column header (Test Case + every custom-field column) using DataTables
column().search()ā mirrors the legacy ExtGridFiltersfilter:{type:'string'}(Refs #864); the hidden Test Suite (group-by) column keeps a hidden footer input like the legacy group-by filter - "Reset Filters" toolbar button clears every column search + all footer inputs at once ā mirrors the legacy
filters.clearFilters()toolbar action (Refs #864); the button is shown only while at least one column filter is active - Every custom-field column is
orderable: trueā clicking its header sorts all rows by that CF value (ascending/descending), exactly like the legacy Ext grid where every column hadsortable: truewith afilter:{type:'string'}and the CF column model rendered{header:"Owner", dataIndex:'id_cf_Owner', filter:{type:'string'}, renderer: columnWrap, sortable: true}(Refs #865). The Test-Suite hidden column keeps the suite ascendant + Test Case ascendant default order (order: [0,'asc'],[1,'asc'](/sebiboga/testlink-upgraded/wiki/0,'asc'],[1,'asc')), matching legacysetSortByColumnName('test_case')+sortDirection='ASC' - Multi-column sort ā a "Multi sort" toolbar sits between the section header and the table. Any visible column header (Test Case + every custom-field column) can be dragged onto it: each drop creates a sort chip (default direction DESC, mirroring legacy
createItem), and dropping several headers builds a combined multi-key sort applied viadt.order([...]).draw(). Chips can be re-ordered by dragging (drop on a later chip = insert-after, on an earlier chip = insert-before), toggled ASC/DESC by click, removed byĆor shift+click (legacybutton.destroy()), and all cleared by the Clear sorts button (reverts to[0,'asc'],[1,'asc'](/sebiboga/testlink-upgraded/wiki/0,'asc'],[1,'asc')). Duplicate drops are rejected with a toast, mirroring legacycanDrop. This is the client-side port of the legacyExt.ux.ToolbarReorderer+Ext.ux.ToolbarDroppablemulti-sort feature (gui/templates/dashio/include/inc_ext_table.tpl:209-239,lib/results/testPlanWithCF.php:160-166) (Refs #866).
4. i18n Keys
All strings use TLi18n keys; no hardcoded text.
| Key | English value |
|---|---|
tpwcf.header |
Test Plan with Custom Fields |
tpwcf.forPlan |
for test plan |
tpwcf.testProject |
Test Project |
tpwcf.testCases |
test cases |
tpwcf.matchCount |
Match count |
tpwcf.editTC |
Edit Test Case |
tpwcf.elapsed |
Elapsed |
tpwcf.noData |
No test cases with custom field values found. |
tpwcf.infoText |
This Report shows all test cases with any custom field set while adding test cases to test plan. |
tpwcf.generatedOn |
Generated on |
tpwcf.expandAll |
Expand all groups |
tpwcf.collapseAll |
Collapse all groups |
tpwcf.showAllColumns |
Show all columns |
tpwcf.groupItem |
({count} item) |
tpwcf.groupItems |
({count} items) |
tpwcf.resetFilters |
Reset Filters |
tpwcf.filtersCleared |
Filters cleared |
tpwcf.columnFilterPlaceholder |
Filter |
tpwcf.multiSortTitle |
Multi sort |
tpwcf.multiSortHint |
Drag column headers here to sort by multiple columns; click a chip to toggle direction, drag chips to reorder, shift+click or X to remove. |
tpwcf.multiSortClear |
Clear sorts |
tpwcf.multiSortToggle |
Click to toggle ascending/descending; shift+click or X to remove |
tpwcf.multiSortRemove |
Remove sort |
tpwcf.multiSortDup |
Column is already in the sort list |
tpwcf.multiSortCleared |
Sort cleared |
tpwcf.sortDesc |
descending |
common.sort |
Sort |
Keys present in all 10 locale bundles (en, ro, de, fr, es, it, ja, pt, ru, zh).
5. Security
- Session-based authentication enforced at BFF entry point
testplan_metricsright checked server-side before any DB querytproject_idandtplan_idvalidated against user's allowed projects- All output escaped via
esc()function in JavaScript rendering
6. Testing
Test suite #847 in tmp/TLU_Test_Cases.md ā 8 test cases covering:
- Group headers rendered per Test Suite path with "(N items)" count (RowGroup 1.4.1 via CDN)
- Per-group header click collapses/expands the group (chevron toggles)
- Expand all groups toolbar button re-shows all rows
- Collapse all groups toolbar button hides all data rows
- Show all columns toolbar button keeps all columns visible
- i18n keys present in all 10 locale bundles
- No JS console errors on data load / toolbar interactions
- DataTable ready state + ordering by suite then test case preserved
Test suite TPWCF-2 in tmp/TLU_Test_Cases.md ā 8 test cases covering per-column filters + Reset Filters (#864):
- Footer renders one filter input per visible column (Test Case + each custom-field column)
- Filtering the Owner column narrows rows to that owner only
- Filtering the Test Case column narrows rows to the matching test case
- Filtering a custom-field column (Tier) narrows rows by that CF value
- "Reset Filters" button appears only when a column filter is active
- "Reset Filters" clears every column search, empties the footer inputs, restores all rows
- No JS console errors during filtering / reset interactions
- No new Error/Warning in Event Viewer
Test suite #865 in tmp/TLU_Test_Cases.md ā 6 test cases covering sortable CF columns (#865):
- Custom-field columns report
orderable: truein the DataTables config (nosorting_disabledclass on their headers) - Clicking a CF column header sorts asc (AāZ) by the CF value
- Clicking again sorts desc (ZāA) by the CF value
- Default order on load stays Test Case ASC (
order: [0,'asc'],[1,'asc'](/sebiboga/testlink-upgraded/wiki/0,'asc'],[1,'asc')) - DataTable
order()reflects the CF column index + direction after each click - No new Error/Warning in Event Viewer after sort interactions
Test suite #866 in tmp/TLU_Test_Cases.md ā 13 test cases covering multi-column sort (Refs #866):
- Multi-sort toolbar renders above the table with label + hint, no initial chips
- Dragging the Owner header onto the bar creates an
Owner āchip (default DESC), order[2,'desc'](/sebiboga/testlink-upgraded/wiki/2,'desc') - Dragging a second header (Tier) builds the combined sort
[2,'desc'],[3,'desc'](/sebiboga/testlink-upgraded/wiki/2,'desc'],[3,'desc') - Clicking a chip toggles ASC/DESC
- Dropping a duplicate column is rejected with a toast (legacy
canDrop) Ćand shift+click both remove chips (legacybutton.destroy())- Dragging chips reorders their precedence (insert-before/after) and re-applies the sort
- "Clear sorts" resets to the default
[0,'asc'],[1,'asc'](/sebiboga/testlink-upgraded/wiki/0,'asc'],[1,'asc')order - RowGroup headers stay consistent with the combined sort
- Per-column footer filters keep working while a multi-sort is active
- No JS console errors; no new Error/Warning in Event Viewer
Test suite TPWCF-1 in tmp/TLU_Test_Cases.md ā 12 test cases covering:
- External IDs show single glue-char (#860)
- Info text displayed in footer (#858)
- Generated timestamp shown in footer (#859)
- Elapsed time shown in footer
- Edit icon opens tcEdit.html (#848)
- No-data state shows info text + timestamp
- BFF returns generated_on field
- BFF returns single glue-char IDs
- i18n keys present in all 10 bundles
- No new Error/Warning in Event Viewer
- BFF early-return paths include generated_on
- Syntax check passes
Previous test suite #18 ā 9 test cases covering:
- Screen load without PHP warnings
- Data display with CF columns
- Edit link opens test case editor
- Locale switching translates labels
- Empty state ā no CF values
- Error state ā invalid plan
- Filter/search works
- Aside menu links to modernized screen
- i18n keys present in all locales
Screenshots
Per-column filters + Reset Filters (Refs #864)
Owner column filtered to "Alice" ā the per-column footer filter is active and the Reset Filters toolbar button is shown.

Sortable custom-field columns (Refs #865)
Rows sorted by the Owner custom-field column (descending) ā the header shows a
sort arrow and all rows are re-ordered by their CF value (Zed corp ā Mid corp ā
Alpha corp), matching the legacy Ext grid where every CF column was sortable: true.

Multi-column sort: drag column headers to the toolbar (Refs #866)
The "Multi sort" bar with no sort keys selected ā the drop target for column headers.

The Owner and Tier column headers have been dragged onto the bar: two sort
chips (Owner ā, Tier ā) are active and the grid is re-sorted by Owner then Tier
(both descending), with the RowGroup headers reflecting the new order ā the legacy
Ext.ux.ToolbarDroppable multi-sort behavior.
