Requirement Spec Revision Compare - sebiboga/testlink-upgraded GitHub Wiki
Modernization of Requirement Spec Revision Compare
(lib/requirements/reqSpecCompareRevisions.php) β issue #837. Closes the HIGH-priority reqSpecViewRevisions (revision compare) item of #755.
The legacy Smarty diff screen is replaced by a standalone Dashio page
(gui/templates/requirements/reqSpecCompare.html) backed by the requirement-spec
BFF (api/reqspec/index.php, new spec_revision_compare action). It is a
non-ASIDE screen reached from the modernized Requirement Specification
Viewer (reqSpecView.html) and Spec Revision Viewer (reqSpecViewRevision.html)
via the new "Compare revisions" button.
URL: gui/templates/requirements/reqSpecCompare.html?spec_id=<spec_node_id>&tproject_id=<id>
BFF API: api/reqspec/index.php?action=spec_revision_compare
Rights: mgt_view_req (view) enforced server-side (same gate as spec_revision_view).


| Feature | Legacy behavior | Modern implementation |
|---|---|---|
| Revision picker | table of revisions (revision, left/right radios, log message, timestamp) | DataTable of revisions (ordered DESC) with two radio columns (left / right), log message, "last change" timestamp + editor; most recent two pre-selected |
| Diff method | radio htmlCompare (DaisyDiff) vs htmlCodeCompare (text diff/inline + context + show-all) |
same two radio options; context input + "Show all" shown only in code-compare mode |
| Attributes diff |
getAttrDiff() table (doc_id, name, type) with changed highlighting |
same β attribute table with changed rows highlighted |
| Scope diff |
diff/HTMLDiffer on the scope of each revision |
same β HTML ins/del or text diff table; "No changes" when equal |
| Custom fields diff |
getCFDiff() table of linked CF values on each revision |
same β CF table, show_custom_fields_without_value honored |
| Validation | two distinct revisions required, context must be numeric β₯ 0 | same client-side validation before the compare request |
| Context default (gap #1360) | Context input prefilled with diffEngine default 5 so code-compare runs without typing (lib/requirements/reqSpecCompareRevisions.php:241-245; tpl line 251 renders value={$gui->context}) |
same β HTML code-comparison mode shows Context prefilled with 5; the BFF list response returns context (mirror of sibling reqCompare.html:153-154 / api/reqcompare/index.php:161); user can still edit it or tick "Show all" |
| Revision ordering (gap #1362) | newest-first (DESC by revision number) | same β DataTable disabled default client-side ascending sort; the Revision cell carries an integer data-order sort key and the DataTable is initialised with order: [[0,"desc"]], so rows render newest-first AND sort numerically for 10+ revisions ("Revision 10" > "Revision 2") |
| Cancel/Back (gap #1359) |
two Cancel buttons (cancel_top tpl line 209 + cancel_bottom line 258, both btn_cancel β history.back()) so the user can leave the compare page without the browser Back |
same β a btn-ghost Cancel button (.fa-arrow-left + common.cancel) in the toolbar and a second one in the bottom footer; both call goBack() = history.back(), with a same-origin fallback to reqSpecView.html?id=<spec>&tproject_id=<tid> when the page was opened directly (history.length <= 1) |
| Revision-popup link (gap #1358) | the Last change cell is clickable (blue rgb(0,85,153), hand cursor) and opens the spec-revision viewer popup for that row (reqSpecCompareRevisions.tpl:234 β openReqSpecRevisionWindow(item_id) β lib/requirements/reqSpecViewRevision.php sized from ReqPopupWidth/ReqPopupHeight cookies, window name ReqSpecRevisionView) |
same β the timestamp cell is a clickable link (cursor:pointer;color:rgb(0,85,153) + tooltip rsvc.openRevision) calling openReqSpecRevision(item_id), which opens the modern reqSpecViewRevision.html?revision_id=<id> viewer popup, cookie-sized (defaults 800x600), window name ReqSpecRevisionView
|
| Log-message hover tooltip (gap #1357) | every revision's Log message cell (id="tooltip-<item_id>", tpl:231-233) opens an Ext.ToolTip (tip4log, tpl:32-48 β width 500, dismissDelay 0, trackMouse) that autoLoads the FULL untruncated log from lib/ajax/getreqspeclog.php?item_id=; the cell itself shows the log truncated to req_spec_cfg->log_message_len (200, controller reqSpecCompareRevisions.php:262-271); an empty log makes getreqspeclog.php return the empty_log_message placeholder |
same β the BFF list response returns log_message_len (from req_spec_cfg), the cell renders the log truncated to that length + ... exactly like legacy, and a mouse-tracked #logTooltip div (legacy-affordance: width 500 / dismissDelay 0 / trackMouse, event-delegated on #revTable so it survives DataTables rebuilds) shows the FULL untruncated log with the getreqspeclog.php <p>βnewline normalization; empty logs render the translated common.emptyLogMessage placeholder instead of the old '-' fallback |





All routes are session-authenticated and JSON; CSRF Origin header required.
| Method | Route | Query | Returns |
|---|---|---|---|
| GET | ?action=spec_revision_compare |
spec_id, tproject_id
|
{status, tproject_id, tproject_name, spec_id, spec_doc_id, context, log_message_len, revisions:[{item_id, revision, log_message, timestamp, last_editor}]} |
| GET | ?action=spec_revision_compare |
spec_id, tproject_id, left, right, method=html|text, context, context_show_all
|
{status, left, right, method, attributes:[{label,lvalue,rvalue,changed}], scope:{type,left,right,count,diff}, custom_fields:[{label,lvalue,rvalue,changed}]} |
- Missing/invalid session β HTTP 401.
- Unknown spec β 404
Requirement specification not found. - Test project mismatch β 400.
- No
mgt_view_reqright on the owning test project β 403. -
left/rightmissing or equal β 400Select two different revisions to compare. - One/both revisions not found β 404.
- The revision list comes from
requirement_spec_mgr->get_history()(same as legacy), returningitem_id/revision/log_message/timestamp/last_editor. - The diff reads the full
req_specs_revisionsrow (doc_id/name/type/scope) and linked design custom fields for each revision (1.x design-node sink), same as the modernizedspec_revision_viewaction. - Two diff engines are ported 1:1:
HTMLDiffer::htmlDiff(HTML comparison) anddiff::doDiff+inline(HTML code comparison with a context window). - The
scopeis compared for both engines; attributes and custom fields use the legacychangedflag for row highlighting. -
Context default (gap #1360): the BFF returns the
diffEngine->contextdefault (config.inc.php:1774, = 5) in the revision-list response, and also uses it server-side as fallback when a text-compare request omitscontext(previously the fallback wasnull). The screen prefills the Context input from that field (r.context || 5), identical to legacy and to the sibling requirement-version compare screen. -
Cancel/Back (gap #1359): legacy
reqSpecCompareRevisions.tplexposescancel_top(line 209) andcancel_bottom(line 258) β twobtn_cancelbuttons callinghistory.back(). The modern screen restores the same affordance in the toolbar and in the bottom footer (labelcommon.cancel, no new i18n keys);goBack()keeps exact legacy semantics on a flowing history and adds a same-origin deep-link fallback toreqSpecView.html(pattern mirrorsreqEdit.html:58+reqEdit.html:219). -
Revision-popup link (gap #1358): legacy
reqSpecCompareRevisions.tpl:234renders the Last change cell as a link callingopenReqSpecRevisionWindow({$rspec.item_id})(gui/javascript/testlink_library.js:1743), which openslib/requirements/reqSpecViewRevision.php?showReqSpecTitle=1&item_id=Nin a popup sized from theReqPopupWidth/ReqPopupHeightcookies (defaults 800x600), window nameReqSpecRevisionView. The modern screen restores the same affordance: the timestamp cell is clickable (cursor:pointer, legacy bluergb(0,85,153), tooltiprsvc.openRevision) and calls the newopenReqSpecRevision(item_id)helper, which opens the modern revision viewergui/templates/requirements/reqSpecViewRevision.html?revision_id=<id>with the same cookie sizing/window name.item_idis the samereq_specs_revisions.idref the BFF already returns per row, so no BFF change was needed. -
Log-message hover tooltip (gap #1357): legacy cells were truncated
server-side to
req_spec_cfg->log_message_len(200) with...and the full message was loaded on hover throughlib/ajax/getreqspeclog.php(nl2br, strips surrounding<p>, falls back toempty_log_message). The modern screen keeps the FULLlog_messagein the BFF payload (no extra fetch round-trip) and mirrors the rest 1:1: the BFF list response now exposeslog_message_len(config_get('req_spec_cfg')->log_message_len), the cell truncates to it with..., a mouse-tracked#logTooltip(Ext.ToolTip affordance: width 500, dismissDelay 0, trackMouse) shows the full log normalizing<p>β newline, and blank logs render the translatedcommon.emptyLogMessageplaceholder.
All labels are client-side via TLi18n; keys under the rsvc. namespace
(rsvc.title, rsvc.selectRevisions, rsvc.selectTwo, rsvc.sameSelected,
rsvc.noSpec, rsvc.noRevisions, rsvc.loadError, rsvc.diffMethod,
rsvc.htmlCompare, rsvc.codeCompare, rsvc.context, rsvc.showAll,
rsvc.revision, rsvc.compare, rsvc.logMessage, rsvc.timestamp,
rsvc.left, rsvc.right, rsvc.compareSelected, rsvc.invalidContext,
rsvc.computing, rsvc.diffDetails, rsvc.diffBetween, rsvc.attributes,
rsvc.attribute, rsvc.scope, rsvc.noChanges, rsvc.changes,
rsvc.customFields, rsvc.customField, rsvc.openRevision). The link labels use
rsv.compareRevisions / rsvr.compareRevisions. Present in all 10 bundles
(en ro de es fr it ja pt ru zh). The log-tooltip placeholder adds the shared
common.emptyLogMessage key ("Log message is empty", legacy empty_log_message)
in all 10 bundles.
- Server-side rights check on every route:
mgt_view_reqrequired even to list revisions; the spec's owning test project is resolved from thereq_specsrow. - CSRF guarded via Origin header check (all BFF routes).
- Output is HTML-escaped client-side; DB reads use parameterized queries / int-casts on every id.
See Suite 837 β Requirement Spec Revision Compare in tmp/TLU_Test_Cases.md
(13/13 PASS): revision list, HTML diff, code-compare diff, same-revision
validation, empty-context validation, missing-spec error, BFF 404/400 guards,
link switches from both the current-spec viewer and the revision viewer, locale
switch, console cleanliness, and Event Viewer cleanliness.
See also Suite 1360 β Context default prefill (8/8 PASS): the code-compare
Context input is prefilled with 5 from the BFF context field on load, compare
runs without typing, "Show all" still disables the field, HTML-compare (Daisy)
path unaffected, explicit context override honored, BFF fallback default,
syntax gates, and Event Viewer/console cleanliness.
See also Suite 1359 β Cancel/Back buttons (10/10 PASS): toolbar + footer
Cancel buttons present, both return to the calling screen via history.back(),
direct-open fallback navigates to the spec viewer, compare/diff regression
passes, common.cancel reused from all 10 locale bundles, Event Viewer and
console clean. Screenshots: issue-1359-gap-before.png,
issue-1359-cancel-buttons.png.
See also Suite 1358 β Revision-popup link on the Last-change cell (10/10 PASS):
each row's timestamp cell is clickable (blue + pointer cursor) and opens
reqSpecViewRevision.html?revision_id=<item_id> for that exact revision
(cookie-sized popup, window name ReqSpecRevisionView), popup content matches the
selected revision, compare/diff regression passes, rsvc.openRevision present in
all 10 locale bundles, Event Viewer + console + PHP log clean.


See also Suite 1357 β Log-message hover tooltip (10/10 PASS): the Log message
cell truncates to req_spec_cfg->log_message_len (200) + ... like legacy,
hovering shows the FULL untruncated log in a mouse-tracked tooltip (survives
DataTables refresh), empty logs render the translated common.emptyLogMessage
placeholder instead of '-', the BFF list response exposes log_message_len,
compare/diff regression passes, i18n gate 10/10 bundles, Event Viewer + console
clean. Screenshot: images/issue-1357-tooltip.png.