Requirement Specification Viewer - sebiboga/testlink-upgraded GitHub Wiki

Requirement Specification Viewer โ€” Modernized Screen

Type: popup viewer (not an ASIDE entry) โ€” reached from Search Requirement Specifications, requirement-spec links in requirement views and the TestLink search results. Refs #755.

URL: gui/templates/requirements/reqSpecView.html?id=<req_spec_id> (&tproject_id=<id> optional โ€” the API resolves the owning project itself) BFF API: api/reqspec/index.php, JSON I/O, session-based auth.

Requirement Specification Viewer

Replaces: legacy popup lib/requirements/reqSpecView.php + gui/templates/dashio/requirements/reqSpecView.tpl.


What it does

Read-only detail view of one requirement specification โ€” the Dashio rewrite of the legacy spec viewer. Nothing from the legacy screen is dropped; the declared/modify/freeze/revision actions that pointed at separate legacy screens are inlined (freeze, new revision) or kept as navigation (spec management, req viewer popup).

Section Content
Toolbar Refresh, Open in Spec Management (jumps to the modern reqSpecMgmt.html of the owning project), New Revision and Freeze all Requirements (visible only with mgt_modify_req on the owning project)
Overview identifier chip (doc_id) + spec title, current revision badge, type badge, declared total requirements, requirements in spec, revisions count, created by / on, last modified by / on
Scope full scope note (from the current revision, white-space: pre-wrap)
Custom fields design-time fields linked to requirement_spec, values of the current revision (date/datetime fields localized by the BFF)
Attachments file list with title, file name, size, date and a Download link (lib/attachments/attachmentdownload.php); managers (mgt_modify_req) additionally get an upload form (file + optional title) and a per-attachment Delete action
Requirements DataTable with identifier, title (opens the modern reqView.html?id= popup), version, type and status โ€” type/status labels localized server-side

The popup title shows the project name, the toolbar info line shows #<spec_id> ยท Revision r<N>, and the footer is the standard TestLink footer.

Actions (rights: mgt_modify_req)

  • Freeze all Requirements โ€” mirrors legacy reqSpecCommands::doFreeze(): collects the whole spec subtree (child specs included), takes the latest version of every requirement under it and sets is_open = 0 (via requirement_mgr::updateOpen). One audit_req_version_frozen audit event is logged per requirement version (legacy parity). The confirm dialog asks first; the info banner reports how many versions were actually frozen.
  • New Revision โ€” mirrors legacy doCreateRevision() โ†’ clone_revision(): clones the latest spec revision with the supplied log message and a new author, then reloads the view (revision badge r+1, revisions count +1). Custom field values are copied to the new revision (copy_cfields, legacy behavior).
  • Attachment upload (manager) โ€” mirrors legacy reqSpecEdit.php?doAction=fileUpload / attachments.inc.tpl: a multipart form (file + attachment title) posts to api/attachments/index.php?action=upload with table=req_specs and the spec id. The title defaults to the file name when left empty. Errors (size/type limits, e.g. TL_REPOSITORY_MAXFILESIZE = 1 MB) surface as a red toast, legacy file_upload_ko alert parity.
  • Attachment delete (manager) โ€” mirrors legacy jsCallDeleteFile + delAttachmentURL: a window.confirm dialog, then api/attachments/index.php?action=delete (ownership-hardened: the file must belong to this spec), then the view reloads.

Both attachment controls stay hidden for view-only users (the BFF reports rights.manage=false), exactly like legacy $attach_downloadOnly=true; the download link stays available to everyone.

Manager attachment CRUD (Refs #1352)

reqSpecView attachments manager UI

Upload form + per-attachment Delete for mgt_modify_req users (above); a view-only user sees the download link only (below).

reqSpecView attachments view-only

Requirement Operations (Refs #1348)

Legacy reqSpecViewButtons.inc.tpl:125-138 renders a req_operations fieldset only when grants->req_mgmt == yes, and inside it three action links only when the spec has requirements. The modern viewer keeps the exact gate: a Requirement Operations group (#reqOpsGroup, gui/templates/requirements/reqSpecView.html:97-102) appears in the toolbar iff the BFF spec_view payload reports rights.manage == true (mgt_modify_req on the owning project) and spec.requirements_count > 0 (reqSpecView.html:492-496). It hosts three actions:

Requirement Operations toolbar group for a manager on a non-empty spec

Action Modern screen Legacy source
Create Test Cases reqCreateTestCases.html?spec_id=&tproject_id= (already-modernized screen) reqSpecEdit.php?doAction=createTestCases (reqSpecView.tpl:41-43)
Copy Requirements reqCopy.html?id=<spec>&tproject_id= reqSpecEdit.php?doAction=copyRequirements (reqSpecView.tpl:59-61)
Bulk Monitoring reqBulkMon.html?id=<spec>&tproject_id= reqSpecEdit.php?doAction=bulkReqMon (reqSpecView.tpl:69-70)

Copy Requirements screen (reqCopy.html)

Port of legacy reqSpecCommands::copyRequirements() โ†’ reqCopy.tpl. On load the BFF GET api/reqspec/index.php?action=copy_options&id=<spec> returns the spec's requirements (latest version each), the project's req-spec destination containers (tree subtree, createHierarchyMap('dotted', doc_id), non-rspec node types excluded) and project context:

Copy Requirements screen: target select + Copy Test Case Assignments + requirement checkboxes

The screen offers a choose target specification select (dotted doc_id labels, e.g. .SRS-RSV-002:Destination Specification RSV8), a Copy Test Case Assignments checkbox (on by default), a DataTable of the source requirements with per-row checkboxes (+ select-all) and Copy โ†’ confirm โ†’ POST action=copy_reqs โ†’ requirement_mgr::copy_to() per selected requirement (+ copy_also.testcase_assignment, audit_requirement_copy COPY audit event), then a localized "Copied requirements" summary. Copied requirements get the (1) doc-id suffix (legacy parity):

Copy Requirements result โ€” 3 copied requirements report

Bulk Monitoring screen (reqBulkMon.html)

Port of legacy reqSpecCommands::bulkReqMon() โ†’ reqBulkMon.tpl. The BFF GET action=bulk_mon_options returns the spec's requirements with the current user's per-requirement monitor flag (getMonitoredByUser scoped to the spec) and enable_start_btn/enable_stop_btn parity. The screen renders a DataTable with per-requirement On/Off monitor state and Toggle / Start / Stop Monitoring buttons, each posting POST action=bulk_mon_toggle (op = toogleMon/startMon/stopMon), mirroring doBulkReqMon():

Bulk Monitoring screen โ€” REQ-002 already On, buttons disabled until selection

Both copy/monitor BFF routes enforce the legacy rights: mgt_view_req to reach the payload, mgt_modify_req to write.

Create Requirement from the viewer (Refs #1347)

The viewer is no longer a read-only dead end for managers: the toolbar now leads with + Create Requirement (reqSpecView.html #createReqBtn), mirroring the legacy req_operations fieldset create_req button (gui/templates/dashio/requirements/include/reqSpecViewButtons.inc.tpl:104-107 + reqSpecView.tpl:25 req_edit_url = reqEdit.php?doAction=create&req_spec_id=).

reqSpecView toolbar with the + Create Requirement button for a manager

  • Gated only on req_mgmt (BFF spec_view rights.manage) โ€” a manager sees it even when the spec contains 0 requirements, exactly like legacy; viewers without the right never see it (display:none default + showHideActions()). It sits outside the reqOpsGroup, whose Create Test Cases / Copy / Bulk Monitoring links keep their own requirements_count > 0 gate (Refs #1348).
  • Clicking opens gui/templates/requirements/reqEdit.html?spec_id=<id>&tproject_id=<tid> in a new popup โ€” the modern Requirement Editor enters create mode whenever no id is given (the form URL branches to spec_id when REQ_ID <= 0, reqEdit.html:233; MODE defaults to create, reqEdit.html:142), pre-bound to the current spec (title, doc-id scheme, types, scope scaffold). This is the exact analogue of the legacy reqEdit.php?doAction=create&req_spec_id=<id> URL; the modern screen reads spec_id (reqEdit.html:579) instead of req_spec_id, and doAction is ignored.
  • After saving, click Refresh in the viewer โ€” the Requirements table then re-renders with the new requirement (confirmed in E2E: QAM-REQ-001 created from the button flow appears in the spec's table) and the Requirement Operations group activates once requirements_count > 0. (Legacy returned to the viewer automatically only because create_req was a same-window location redirect through reqEdit.php.)

Spec lifecycle toolbar (Refs #1345)

Legacy reqSpecViewButtons.inc.tpl:38-52 renders a Requirement Specification Operations fieldset with New Req Spec (btn_new_req_spec), Edit (btn_edit) and Delete (btn_delete) โ€” all gated on grants->req_mgmt == yes, with New Req Spec additionally gated on $tlCfg->req_cfg->child_requirements_mgmt != DISABLED (config.inc.php:1661). The modern viewer now renders the same three actions as a #specOpsGroup (label + ghost buttons) in the toolbar:

Spec lifecycle toolbar

Button Modern behaviour Legacy source
+ New Req Spec opens an inline Bootstrap modal (#specModal, openSpecChildCreate()) pre-bound to the current spec as parent โ€” doc id, title, type (sourced from the option list, current spec type preselected), declared total requirements, scope โ€” and POSTes action=create_spec with parent_id=<spec_id> to BFF api/reqspec/index.php; on success the toast "Requirement specification saved." shows and the viewer reloads onto the new child spec reqSpecEdit.php?doAction=createChild&parentID=<spec_id>
Edit opens the same modal prefilled from the current spec (openSpecEdit() โ€” doc id, title, type, total_req, scope of the latest revision) and POSTs action=update_spec reqSpecEdit.php?doAction=edit
Delete confirmDeleteSpec() โ†’ #delModal confirm dialog (spec title + "will be deleted together with ALL its requirements, revisions and coverage") โ†’ POST action=delete_spec; on success the toast "Requirement specification deleted." shows and the screen falls to the 404 state (the spec is gone, refresh resolves the parent tree) reqSpecEdit.php?doAction=doDelete&req_spec_id=

Delete confirmation

BFF (api/reqspec/index.php)

  • create_spec now accepts an optional parent_id (validated with needOwnedSpec) and passes parentId (child semantics) โ€” requirement_spec_mgr::create($tproject_id,$parentId,...) creates the node under the parent spec's tree node (deep link #1345); without parent_id the behaviour is unchanged (root-level spec under the test project).
  • options exposes childRequirementsManagement (boolishConfig, default ENABLED) so the client can apply the legacy New Req Spec gate.
  • spec_view now returns spec.child_requirements_mgmt (same flag) and a top-level specTypes map (id โ†’ label) when ?full=1&id= is used, so the modal's type dropdown renders even on deep links that never hit options.

When child_requirements_mgmt = DISABLED the group shrinks to Edit + Delete only โ€” verified live by flipping config.inc.php:1661.

Parity note (not a regression): the legacy engine itself never persists the "expected total requirements" number on create โ€” requirement_spec_mgr::create_revision() (lib/functions/requirement_spec_mgr.class.php) omits total_req from its INSERT (column defaults 0) and plain update_revision() (no new revision) only touches scope/modifier. The modern create_spec/update_spec reuse the same engine calls, so total_req behaviour is bit-for-bit legacy parity (and matches the sibling reqSpecMgmt.html screen).

Rights + testing

The whole group is hidden for viewers without mgt_modify_req (BFF spec_view rights.manage โ€” same gate as Freeze / New Revision / attachment upload). The BFF write routes enforce needManageRight server-side. Verified E2E on a fresh DB: child spec created via the modal appears under the parent (DB node parent check), edit persists title/scope/doc-id on the latest revision and the node, delete removes the subtree incl. revision nodes, deep link reqSpecView.html?id=<child> resolves the project, disabled-config gate hides only New Req Spec, Event Viewer clean.

Deleting / not-found & permissions

  • Nonexistent spec is probed with a direct SELECT testproject_id FROM req_specs WHERE id=N first; the API answers HTTP 404 and the screen shows "Failed to load requirement specification: Requirement specification not found" while hiding the body. This covers orphaned specs too (a req_specs row with no revision): since the #1714 fix, requirement_spec_mgr::get_by_id() itself returns null when get_last_child_info() finds no revision, so no E_WARNING / SQL-1064 pair is ever emitted and the screen degrades to the same clean 404.
  • Without mgt_view_req the API answers HTTP 403 and the screen shows "No permission"; the Freeze / New Revision buttons are hidden in that case.
  • Opening the screen with no tproject_id (e.g. from the search results deep link) works too: spec_view resolves the owning project itself and carries its own localized type/status labels.

i18n

New keys under the rsv.* family were added to all ten locale bundles (gui/templates/i18n/{de,en,es,fr,it,ja,pt,ro,ru,zh}.json); the requirement type/status and spec type labels come from the existing server-side label maps.

Deep links switched

  • gui/templates/requirements/searchReqSpec.html โ€” openReqSpec() now opens reqSpecView.html?id= (was reqSpecView.php?req_spec_id=).
  • gui/javascript/testlink_library.js โ€” openLinkedReqSpecWindow() (used by remaining legacy search screens) now targets the modern viewer URL.
  • The revision links (rev. N) still point to the legacy reqSpecViewRevision.php โ€” those are the next piece of #755.
โš ๏ธ **GitHub.com Fallback** โš ๏ธ