Skip to content

Test Project Management Modernized

github-actions[bot] edited this page Sep 26, 2026 · 7 revisions

Test Project Management (projectView) — Modernized Screen

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.

Projects list


Table of Contents

  1. What the screen does
  2. REST API Reference
  3. Legacy parity notes
  4. i18n Keys
  5. Security
  6. Testing

1. What the screen does

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

2. REST API Reference

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)

3. Legacy parity notes

  • 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.20 doDelete()).
  • 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 / tlReqMgrSystem managers, 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 legacy projectEdit.tpl help-icon behavior and the separate projectEdit.html pattern. The button is hidden in create mode and when the mgt_view_events right 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 sends copy_from_tproject_id to the BFF, which validates the source (Source project not found 400 for unknown ids) and runs the legacy testproject::copy_as() clone (custom-field assignments, keywords, platforms, requirements per the optReq checkbox, 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, nl2br when the project editor is none). Newlines are preserved with white-space: pre-line, the cell is clamped to 2 lines with the full text in its title, 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/projects already returned description from tp.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-cubes icon (projectView.tpl:104) using the shared .api-id style, whose tooltip is [ID: <id>] — legacy renders "API " + $tlCfg->api->id_format and config.inc.php:637 sets $tlCfg->api->id_format = "[ID: %s ]". Same pattern and i18n format string as buildsView.html / platformsView.html.

Notes column and API tooltips

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

Before

4. i18n Keys

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.

5. Security

  • Server-side right check on every route (mgt_modify_product), session auth via the shared api/_guard.php bootstrap + 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.

6. Testing

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.

Create/Edit modal

Create modal with "Create from existing Test Project?" dropdown

Edit modal with Show event history

7. Requirement Feature quick toggle (Issue #988)

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>/requirements with {"enabled":0|1} in api/projects/index.php, calling the same manager methods — so a click does not re-run the duplicate name/prefix checks nor emit a spurious audit_testproject_saved the way a full PUT would. 404 unknown project, 400 bad payload, 403 without mgt_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's icon_cell.
  • i18n 4 new keys in all 10 locale bundles, wording taken from the legacy th_requirement_feature / active_click_to_change strings.

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).

Requirement Feature column

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

Clone this wiki locally