Bugzilla – Attachment 194752 Details for
Bug 36698
Display 'diff' nicely in action logs
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 36698: Render diff column as a human-readable table in the log viewer
c141bc1.patch (text/plain), 6.53 KB, created by
Martin Renvoize (ashimema)
on 2026-03-06 15:59:31 UTC
(
hide
)
Description:
Bug 36698: Render diff column as a human-readable table in the log viewer
Filename:
MIME Type:
Creator:
Martin Renvoize (ashimema)
Created:
2026-03-06 15:59:31 UTC
Size:
6.53 KB
patch
obsolete
>From c141bc1a29299d6a3d8dc4b2f0f56df3a7955e65 Mon Sep 17 00:00:00 2001 >From: Martin Renvoize <martin.renvoize@openfifth.co.uk> >Date: Fri, 6 Mar 2026 15:54:46 +0000 >Subject: [PATCH] Bug 36698: Render diff column as a human-readable table in > the log viewer > >Previously the diff column in the log viewer displayed raw Struct::Diff >JSON (e.g. {"D":{"superlibrarian":{"N":1,"O":0}}}), which was not >useful to end users. > >This follow-up adds a JavaScript renderer (renderStructDiff) to >viewlog.js that parses the Struct::Diff JSON and presents it as a >compact Field / Before / After table, reusing the existing <del>/<ins> >colour styling already present for the jsdiff compare feature. Nested >diffs (e.g. granular subpermissions) are flattened using dot-separated >key names. Non-Struct::Diff content falls back gracefully to a <pre> >block so that entries from other modules are not affected. > >The change benefits all modules that currently populate the diff column: >HOLDS (MODIFY/SUSPEND/RESUME/CANCEL/FILL), CATALOGUING (MODIFY), >APIKEYS (MODIFY/REVOKE/ACTIVATE), and MEMBERS (MODIFY for permissions). >--- > .../prog/en/modules/tools/viewlog.tt | 29 +++++- > koha-tmpl/intranet-tmpl/prog/js/viewlog.js | 99 +++++++++++++++++++ > 2 files changed, 127 insertions(+), 1 deletion(-) > >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/viewlog.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/viewlog.tt >index 42634a034e8..c399f5bc31b 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/viewlog.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/viewlog.tt >@@ -60,6 +60,33 @@ > overflow: scroll; > padding: 10px; > } >+ /* Struct::Diff renderer */ >+ table.struct-diff { >+ border-collapse: collapse; >+ font-size: 0.85em; >+ width: 100%; >+ } >+ table.struct-diff th, >+ table.struct-diff td { >+ border: 1px solid #ccc; >+ padding: 2px 6px; >+ vertical-align: top; >+ } >+ table.struct-diff th { >+ background-color: #f5f5f5; >+ white-space: nowrap; >+ } >+ table.struct-diff td.diff-key { >+ font-family: monospace; >+ white-space: nowrap; >+ } >+ table.struct-diff del, >+ table.struct-diff ins { >+ font-family: monospace; >+ text-decoration: none; >+ padding: 1px 3px; >+ display: inline-block; >+ } > </style> > [% END %] > </head> >@@ -410,7 +437,7 @@ > [% END %] > </td> > <td>[% PROCESS translate_log_interface log_interface=loopro.interface %]</td> >- <td>[% loopro.diff | html %]</td> >+ <td class="diff-col">[% loopro.diff | html %]</td> > </tr> > [% END %] > </tbody> >diff --git a/koha-tmpl/intranet-tmpl/prog/js/viewlog.js b/koha-tmpl/intranet-tmpl/prog/js/viewlog.js >index 417dcaf18b7..6d9e84319b8 100644 >--- a/koha-tmpl/intranet-tmpl/prog/js/viewlog.js >+++ b/koha-tmpl/intranet-tmpl/prog/js/viewlog.js >@@ -1,3 +1,95 @@ >+/** >+ * Recursively collect changed entries from a Struct::Diff node. >+ * Returns an array of { key, before, after } objects. >+ * Nested diffs are flattened with dot-separated keys. >+ */ >+function collectDiffEntries(node, prefix) { >+ var entries = []; >+ Object.keys(node).forEach(function (key) { >+ var change = node[key]; >+ var fullKey = prefix ? prefix + "." + key : key; >+ if ("O" in change || "N" in change) { >+ entries.push({ key: fullKey, before: change.O, after: change.N }); >+ } else if ("A" in change) { >+ entries.push({ key: fullKey, before: undefined, after: change.A }); >+ } else if ("R" in change) { >+ entries.push({ key: fullKey, before: change.R, after: undefined }); >+ } else if ("D" in change) { >+ entries = entries.concat(collectDiffEntries(change.D, fullKey)); >+ } >+ }); >+ return entries; >+} >+ >+/** >+ * Render Struct::Diff JSON as a human-readable before/after table. >+ * Falls back to a <pre> block for non-Struct::Diff JSON or plain text. >+ */ >+function renderStructDiff(raw) { >+ if (!raw) return ""; >+ var diff; >+ try { >+ diff = JSON.parse(raw); >+ } catch (e) { >+ return $("<pre>").text(raw)[0].outerHTML; >+ } >+ if (!diff || !diff.D || typeof diff.D !== "object") { >+ return $("<pre>").text(raw)[0].outerHTML; >+ } >+ >+ var entries = collectDiffEntries(diff.D, ""); >+ if (!entries.length) return ""; >+ >+ function fmt(v) { >+ if (v === undefined) return ""; >+ var s = typeof v === "object" ? JSON.stringify(v) : String(v); >+ return $("<span>").text(s).html(); >+ } >+ >+ var rows = entries >+ .map(function (e) { >+ var before = >+ e.before !== undefined >+ ? "<del>" + fmt(e.before) + "</del>" >+ : ""; >+ var after = >+ e.after !== undefined ? "<ins>" + fmt(e.after) + "</ins>" : ""; >+ return ( >+ "<tr>" + >+ '<td class="diff-key">' + >+ $("<span>").text(e.key).html() + >+ "</td>" + >+ '<td class="diff-before">' + >+ before + >+ "</td>" + >+ '<td class="diff-after">' + >+ after + >+ "</td>" + >+ "</tr>" >+ ); >+ }) >+ .join(""); >+ >+ return ( >+ '<table class="struct-diff">' + >+ "<thead><tr>" + >+ "<th>" + >+ __("Field") + >+ "</th>" + >+ "<th>" + >+ __("Before") + >+ "</th>" + >+ "<th>" + >+ __("After") + >+ "</th>" + >+ "</tr></thead>" + >+ "<tbody>" + >+ rows + >+ "</tbody>" + >+ "</table>" >+ ); >+} >+ > function tickAll(section) { > $("input[type='checkbox'][name='" + section + "']").prop("checked", true); > $("#" + section.slice(0, -1) + "ALL").prop("checked", true); >@@ -151,6 +243,13 @@ $(document).ready(function () { > e.preventDefault(); > $(".compare:checked").prop("checked", false).change(); > }); >+ >+ $("td.diff-col").each(function () { >+ var raw = $(this).text().trim(); >+ if (raw) { >+ $(this).html(renderStructDiff(raw)); >+ } >+ }); > patron_autocomplete($("#user"), { > "on-select-callback": function (event, ui) { > $("#user").val(ui.item.patron_id); >-- >2.53.0 >
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 36698
:
194566
|
194745
|
194752
|
194772
|
194773
|
194873
|
194874
|
194878
|
194900
|
194901