Repository navigation
Test Project Management Modernized
Modernization of Test Project Management (lib/project/projectView.php +
lib/project/projectEdit.php) — GitHub issue
#640.
The legacy Smarty screen is replaced by a standalone Dashio page
(gui/templates/projectsView.html) backed by a plain-PHP REST BFF
(api/projects/index.php). The ASIDE Dashboard → Test Project Management
entry now points to the new HTML screen (switched in
getActions() in lib/functions/common.php).
Path: Dashboard → Test Project Management
URL: gui/templates/projectsView.html?tproject_id=<id>&tplan_id=<id>
BFF API: api/projects/index.php
Right: mgt_modify_product enforced server-side on every BFF route
(same as legacy checkRights()); create/edit/delete controls are also hidden
client-side when the right is missing.

| Feature | Legacy behavior | Modern implementation |
|---|---|---|
| List test projects ordered by name |
testproject::get_accessible_for_user() with issue/code tracker info |
GET /api/projects/ — joined rows incl. tracker names + enabled flags + feature flags from the serialized options blob |
| Create project |
doCreate: crossChecks (name syntax, dup name, dup prefix), feature checkboxes, tracker link |
POST /api/projects/ with same checks via tprojectMgr::checkName/get_by_name/get_by_prefix, create(doChecks:false) after explicit crossChecks |
| Create project from existing (copy) |
projectEdit.tpl:118-130 "Create from existing Test Project?" dropdown → doCreate runs copy_as(source,new,user,name,{copy_requirements=>optReq})
|
Create from existing Test Project? dropdown in the create modal (only on create, hidden on edit), populated from the projects list; BFF accepts copy_from_tproject_id and calls testproject::copy_as() after create — Ref #989 |
| Edit project in modal |
edit/doUpdate: same crossChecks excluding self, then update()+activate()
|
PUT /api/projects/{id} — partial updates supported (only sent fields change), manager-based update |
| Show event history from edit modal |
projectEdit.tpl help icon gated on mgt_view_events right → showEventHistoryFor(itemID,"testprojects")
|
Show event history button in the modal header (edit mode only), gated on mgt_view_events, opens eventviewer.html?objectId=<projectId>&objectType=testprojects — Ref #991 |
| Active/Inactive click-to-change | toggle icon per row → setActive/setInactive
|
row Deactivate/Activate button → PUT {isActive} partial update |
| Issue tracker integration |
tlIssueTracker::link/unlink + setIssueTrackerEnabled
|
identical manager calls (raw SQL removed) |
| Code tracker integration |
tlCodeTracker::link/unlink + setCodeTrackerEnabled
|
identical manager calls |
| Requirement mgmt system integration |
tlReqMgrSystem::link/unlink + setReqMgrIntegrationEnabled
|
same manager calls; third select section in the modal, options served by /api/trackers/ (reqMgrSystems) |
| Private-project lockout guard | non-public project auto-adds editor's global role as project-specific role | replicated via tlUser::hasRoleOnTestProject() + addUserRole() on create & update |
| Delete project |
doDelete = full cascade via tprojectMgr->delete() + confirm |
DELETE /api/projects/{id} real cascade; 409 JSON on refusal; confirm dialog explicitly warns all data is permanently deleted |
| Search by name | server-side LIKE filter (doAction=search) |
client-side instant filter over the loaded list (same result for the accessible set) |
| Audit trail | audit_testproject_created/saved/deleted |
identical events fired by the BFF |
All routes require an authenticated session; non-admin callers without
mgt_modify_product get 403 {"status":"error","message":"No permission"}.
| Method & path | Purpose | Failure modes |
|---|---|---|
GET /api/projects/ |
list all projects (id, name, prefix, description, isActive, isPublic, opt* flags, trackers) + grants.mgt_view_events
|
401 unauthenticated, 403 no right |
GET /api/projects/{id} |
one project | 404 not found |
POST /api/projects/ |
create | 400 empty name/prefix, name-syntax or duplicate name/prefix (legacy messages), create failure |
PUT /api/projects/{id} |
update (partial: only provided keys) | 404 not found, 400 validation/duplicates |
DELETE /api/projects/{id} |
full cascade delete | 404 not found, 409 cascade refused (localized legacy message) |
- Delete is a real cascade delete (test plans, cases, executions,
keywords, attachments...), exactly like
projectEdit.php doDelete()— an earlier draft of this screen only deactivated the project, which silently diverged from legacy semantics. Known legacy-inherited limitation: the cascade is non-transactional, so a mid-way failure can leave partial residue (same as 1.9.20doDelete()). - Duplicate-name and duplicate-prefix errors reuse the legacy language keys
(
error_product_name_duplicate,error_tcase_prefix_exists). - Tracker + requirement-mgmt linking goes through the
tlIssueTracker/tlCodeTracker/tlReqMgrSystemmanagers, so the link tables never drift from what the legacy screens write. - When a project becomes non-public, the BFF replicates legacy's lockout protection by adding the editing user's global role as a project-specific role if none exists.
- Partial updates preserve stored fields: color, feature flags and any key not present in the request body keep their stored values; feature-flag merges start from the stored options blob (corrupt blobs fall back to legacy create defaults instead of wiping flags).
-
Show event history (Ref #991): the edit modal shows a Show event
history button in the header when the user has
mgt_view_events. It opens the event viewer filtered to the project being edited (eventviewer.html?objectId=<projectId>&objectType=testprojects), matching the legacyprojectEdit.tplhelp-icon behavior and the separateprojectEdit.htmlpattern. The button is hidden in create mode and when themgt_view_eventsright is absent. -
Create from existing Test Project (Ref #989): the create modal shows a
Create from existing Test Project? dropdown (legacy
copy_from_tproject_id) listing every project with a "-- No --" default. Picking a source and saving sendscopy_from_tproject_idto the BFF, which validates the source (Source project not found400 for unknown ids) and runs the legacytestproject::copy_as()clone (custom-field assignments, keywords, platforms, requirements per theoptReqcheckbox, test specification, test plans, user roles). The dropdown is hidden in edit mode and the field is zeroed in edit PUTs so editing never triggers a copy. -
Notes column (Ref #987): the list carries a Notes column right after
Project Name, showing the project description that legacy rendered at
projectView.tpl:88,113-115($testproject.notes,nl2brwhen the project editor isnone). Newlines are preserved withwhite-space: pre-line, the cell is clamped to 2 lines with the full text in itstitle, an empty project shows-, and the value is HTML-escaped (legacy trusted the DB). Like the legacy{#NOT_SORTABLE#}header it is neither sortable nor per-column filterable. No BFF change was needed —api/projectsalready returneddescriptionfromtp.notes; the data was reaching the browser and being dropped by the row builder. -
REST API id tooltip (Ref #987): the name cell is prefixed with the legacy
fa-cubesicon (projectView.tpl:104) using the shared.api-idstyle, whose tooltip is[ID: <id>]— legacy renders"API " + $tlCfg->api->id_formatandconfig.inc.php:637sets$tlCfg->api->id_format = "[ID: %s ]". Same pattern and i18n format string asbuildsView.html/platformsView.html.

Before #987 — 7 columns, no notes, no cube icon:

All strings are client-side localized through TLi18n. Keys live under the
proj.* prefix plus header.projects*, present in ALL 10 locale bundles:
en, de, es, fr, it, ja, pt, ro, ru, zh. New in #640:
proj.toggleActive, proj.activate, proj.deactivate,
proj.reqMgrIntegration, proj.reqMgrSystem; corrected
proj.msg.confirmDelete / proj.msg.errorDelete to describe a real,
irreversible deletion. Ref #991 adds proj.eventHistory
("Show event history"). Ref #989 adds proj.copyFrom
("Create from existing Test Project?") and proj.noProject ("-- No --").
Ref #987 adds proj.notes ("Notes") for the list Notes column — a
separate key from proj.description ("Description"), which labels the edit
modal field; legacy's list header is th_notes = "Notes" — and proj.apiId
("[ID: {id}]", the format string of $tlCfg->api->id_format, mirroring
pl.apiId) for the API-id tooltip.
- Server-side right check on every route (
mgt_modify_product), session auth via the sharedapi/_guard.phpbootstrap + same-origin CSRF guard. - All user-controlled values (name, prefix, tracker names) are HTML-escaped in
the row renderer (
esc()) — verified by injecting<script>into a project name. - Duplicate checks prevent case/prefix collisions server-side, not just in the UI.
Regression suite Suite 640 in tmp/TLU_Test_Cases.md: 21/21 PASS base
run + 9 PASS re-verification of the code-review fixes (render, modal
validation, CRUD, toggles, tracker + req-mgmt linking, search, XSS,
cascade delete + cancel, lockout guard, partial-update preservation,
permission paths 401/403/404/500 mapping, i18n completeness, aside switch,
Event Viewer clean — AUDIT-only). Ref #989 adds Suite 989 (10/10 PASS):
create-modal dropdown listing every project, copy via UI + via BFF with
copy_from_tproject_id, clone verification (cfield_testprojects carried
over), invalid source 400, no-copy regression, edit-mode hiding, payload
copy_from_tproject_id:0 on plain create, i18n coverage, Event Viewer clean.



The modern screen restored the legacy "Requirement Feature" column that the
2.0.1 rewrite had dropped. Legacy (dashio/project/projectView.tpl:92,
:126-134) had a dedicated, deliberately non-sortable and non-filterable column
whose whole content was a click-to-change fa-toggle-on / fa-toggle-off icon
posting doAction=enableRequirements|disableRequirements;
lib/project/projectEdit.php:87-88 routed that to
testproject::enableRequirements() / disableRequirements()
(lib/functions/testproject.class.php:3935-3951), which flip only
requirementsEnabled and log no event.
Implemented as:
-
BFF
POST /api/projects/<id>/requirementswith{"enabled":0|1}inapi/projects/index.php, calling the same manager methods — so a click does not re-run the duplicate name/prefix checks nor emit a spuriousaudit_testproject_savedthe way a fullPUTwould.404unknown project,400bad payload,403withoutmgt_modify_product. -
Screen the column itself in
gui/templates/projectsView.html, between "Code Tracker" and "Status", with the legacy "Active (click to set inactive)" / "Inactive (click to set active)" tooltips,role="button"+tabindex="0"+aria-pressed, and Enter/Space activation. Not sortable, no filter input, like legacy'sicon_cell. -
i18n 4 new keys in all 10 locale bundles, wording taken from the legacy
th_requirement_feature/active_click_to_changestrings.
Verified by Suite 988 (14/14 PASS) — both toggle directions persist across a
reload, the other three option flags and all row fields are untouched, and the
events table gains zero rows across three toggles (legacy parity).

Details: Task-Issue-988-projectsView-Requirement-Feature-Toggle.md