2_1_2
JCR Stats 2.1.2
Release date: 2026-06-25
A usability and robustness release for whole-site analysis: computations no
longer abort on un-representable nodes, long runs can be cancelled, branches can
be excluded, and every run is saved so it can be reopened and compared later.
Also a broad hardening pass across security, accessibility (WCAG 2.2 AAA), and
reliability. Drop-in upgrade from 2.1.x.
Highlights
- 🌳 Whole-site computations no longer crash on nodes with names that aren't
valid JCR paths (e.g. external mounts). - ⏹️ Cancel a running computation for real — it stops on the server.
- 🚫 Exclude paths from a computation, persisted in an OSGi config file.
- 🗂️ Saved executions — every run is auto-saved as JSON; reopen, compare, or
delete past runs. - ♿ WCAG 2.2 Level AAA accessibility pass on the admin UI.
Added
Saved executions (server-side JSON history)
Every completed computation is automatically saved as a JSON snapshot in the JCR
(/sites/systemsite/files/jcr-stats/snapshots/), so a past run's flamegraph can
be reopened later — by anyone with the jcrStatsAdmin permission, and across
restarts. A Saved executions panel lists the history (most recent first) with
its date and size, and per-row View (reload into the viewer), Compare
(diff against the current result), and Delete actions. Snapshots are capped
at the 50 most recent (oldest pruned on write). New GraphQL:
jcrStats.snapshots, jcrStats.saveSnapshot, jcrStats.deleteSnapshot.
Path exclusions
Exclude a path — and its whole subtree — from the computation. Click a frame in
the flamegraph and choose Exclude this path; excluded paths are listed with a
Remove control. Exclusions are persisted as an OSGi configuration file
(${karaf.etc}/org.jahia.community.jcrstats.cfg, property
jcrStats.excludedPaths) via a ManagedService, so they survive restarts and
can be hand-edited. New GraphQL: jcrStats.exclusions,
jcrStats.addExclusion(path), jcrStats.removeExclusion(path). Exclusions take
effect on the next computation.
Server-side cancellation
A new jcrStats.cancel mutation and a cancelled flag on jcrStats.status. The
traversal polls a cooperative cancellation flag at the start of every node, so a
running job stops between nodes (never mid-JCR-operation). The Cancel button
now actually stops the server-side job.
Changed
- "Load data" joins the history — loading a JSON file now also stores it as a
saved execution (so loaded data is persisted alongside computed runs), and the
standalone Compare with… button was removed; comparison is driven entirely
from the Saved executions list. - Saved-executions UX — rows show date and size, with per-row Delete and a
capped, scrollable list; Compare is gated until a current result is loaded.
Fixed
- Whole-site traversal no longer aborts on un-listable branches. Descending
into an external data-source mount whose child name isn't a valid JCR path
(e.g. acloud-dumpsnode named with an ISO-8601 timestamp, where:is the
namespace-prefix separator) previously raised
RepositoryException: Invalid path … ':' not valid name characterand aborted
the whole computation. Direct listing now falls back to an escaped
ISCHILDNODEquery, recovering the valid children; only the single
un-representable node is omitted. TheMAX_VISITED_NODESsafety cap is
unchanged. - The "Cancel" button now stops the computation instead of only stopping the
client-side polling. - Snapshot list reflects deletes immediately — listing now iterates the
folder's children directly instead of an index-backed query that lagged behind
deletions. - Review hardening: snapshot/flamegraph writes use a dedicated system session;
cancellation is classified by exception type; the flamegraph write aborts on a
failed header/footer;jsonEscapehandles lone surrogates;jcr:datareads
guard multi-valued properties; assorted frontend race fixes (no stale-run result
applied to a new run; load-as-snapshot failures surfaced).
Accessibility (WCAG 2.2 Level AAA)
Focus is no longer relocated on async completion (only on user-initiated view
changes); reduced-motion fully stops the progress-bar animation; info/success use
role="status" and errors role="alert"; list/icon buttons carry descriptive
accessible names; the flamegraph uses role="img" with the keyboard-accessible
tree-table as its alternative; the progressbar exposes aria-valuemin/max;
delete uses an accessible inline two-step confirm; borders/links meet AAA
contrast; headings reflow at 400% zoom.
Security
saveSnapshotvalidates the uploaded envelope structurally (parsed JSON
withformat = jcr-stats-flamegraphand atree), not by substring match.deleteSnapshotonly deletes within the snapshots folder.- Excluded-path validation hardened (absolute, length-capped, segment-scoped
./..rejection, no ReDoS-prone regex). - All GraphQL operations remain gated by the
jcrStatsAdminpermission; JCR
traversal uses a privileged system session by design.
Quality
- Backend: 88 unit tests. Frontend: 108 Jest tests. End-to-end: 33 Cypress tests
(admin UI, API, permissions) — all green. SonarQube quality gate passing;
ESLint clean.
Upgrade notes
- Drop-in from 2.1.x — no API or data migration required. The admin route is
jcrStats(/jahia/administration/jcrStats). - Assign the
jcr-stats-administratorrole (or thejcrStatsAdminpermission)
to users who should access the tool. - Saved snapshots and generated flamegraph reports accumulate in the JCR under
/sites/systemsite/files/jcr-stats/(snapshots are capped at 50; HTML reports
are not — prune if needed).
Known limitations
- The computation is a single, server-wide job — concurrent admins share one run.
- The subtree is held in memory, bounded by the
MAX_VISITED_NODES = 5,000,000
safety cap. - The read-only
size/nodeCount/treeGraphQL queries run synchronously on the
request thread (use the asynccompute/status/resultflow for large paths).
Compatibility
- Requires Jahia 8.2.1.0 or later.