Add scheduled reports and a pluggable distribution SPI - #95
Merged
Conversation
Reports can now run unattended on a cron schedule and deliver their results through a pluggable distribution SPI, for both scheduled and manual runs. Scheduling - Per-report cron schedule stored on the definition, reconciled to a Sling scheduled job (persistent, cluster-safe) on save/delete. - Cron expressions are validated before persistence (Quartz 6/7-field form); sub-minute schedules are rejected. - UI/API schedules run as their author (runAs/scheduledBy forced server-side to the requesting user, enabled by a self-scoped impersonation grant) so scheduling can never escalate privileges. - Reports deployed in code are auto-discovered from the immutable /apps/groovyconsole-reports-definitions drop-zone (startup scan plus a resource-change listener) and run as the dedicated executor service user, which reads /content. Jobs are keyed by definition path. Distribution - ReportDistributor/ReportDistributorRegistry SPI mirroring the exporter SPI; distributors render through any registered export format. - Email distributor (AEM mail service) with an optional recipient-domain allowlist; filesystem distributor sandboxed to a configured root and disabled by default. - Targets stored on the definition, applied automatically on scheduled completion and on demand via "Distribute now"; failures are recorded on the execution without failing the run. Includes editor UI for schedules and distribution targets, README documentation, unit tests and integration tests.
The console.api package and the reports api/model packages are still on the unreleased 20.0.0 line (baseline 19.1.0), so additive changes don't need a package-version bump. Restore them to 20.0.0 / 1.0.0 and describe the /apps definitions drop-zone by what it is.
The schedule editor now offers a Frequency picker (every day / weekday / week / month / custom) with hour, minute, weekday and day-of-month inputs, a plain-language summary and the resulting cron expression. The raw cron field remains under "Custom", with the same client-side validation the server applies. Existing expressions are parsed back into the matching preset. Each distribution field now has a visible label and help text (Destination, Export format, Directory, File name, Recipients, Subject) instead of relying on placeholders, and each target is shown as its own card.
royteeuwen
added a commit
that referenced
this pull request
Jul 20, 2026
…ge to main Records the refreshed 19.x history as merged (so future 20.x->19.x fix syncs have a clean base) while keeping main's tree verbatim: Groovy 5, modern UI and 20.x package exports are all preserved. -s ours = none of 19.x's downgrades land on main; #95 reaches main via its own PR.
Add ReportDistributor.isAvailable() and list only available distributors in the distributors endpoint, so the editor never offers a destination that would fail. The filesystem distributor is available only when enabled; the email distributor only when a mail service is bound.
The page servlet appends a per-deploy version token (the bundle's last-modified time) to the stable-named entry assets, and the reports Vite build now content-hashes shared chunks. The entry is refetched after a deploy and pulls in the hashed chunks, so a redeploy shows up without clearing the browser cache, while assets stay cached within a deployment.
…duled-reports # Conflicts: # extensions/reports/bundle/pom.xml # extensions/reports/bundle/src/main/groovy/be/orbinson/aem/groovy/console/reports/servlets/ReportsServlet.groovy # extensions/reports/ui.frontend/src/api/reports-api.ts # extensions/reports/ui.frontend/src/api/reports-types.ts # extensions/reports/ui.frontend/src/components/reports/gcr-report-editor.ts # it.tests/src/test/java/be/orbinson/aem/groovy/console/it/GroovyConsoleReportsIT.java
Align testDistributorsListed with the availability gating: on the plain Sling test instance the filesystem distributor is disabled and email has no mail service, so the endpoint lists neither. Also remove a PathType import left unused after merging the query-audit browse changes.
… test The per-deploy ?v token on the module entry made the browser load reports.js twice (once as reports.js?v=…, once as the bare specifier the bundle's own imports use), so sp-theme was defined twice and the second define threw — aborting the dynamic import chain and leaving the Monaco code editor unregistered on any report whose editor also lazy-loads the path browser (e.g. a PATH parameter). Revert the ?v token and the chunk hashing back to the known-good stable-name build. Add a Playwright test that opens a report's edit view and asserts the Monaco editor actually renders, closing the gap that let this through (the existing reports specs only cover the list and run views).
Content-hash the reports entry, chunks and assets, emit a Vite manifest, and have the shared page servlet link the hashed entry (js + css) read from that manifest. A changed file gets a fresh URL and is refetched; an unchanged file keeps its URL and stays cached — busting without a query token, which previously double-loaded the entry module and broke the code editor. The console-panel entry keeps a stable name (the extension provider references it by fixed path), and the servlet falls back to the stable <name>.js/.css names when no manifest is present (e.g. migration).
The migration service serialises runs and rejects a new run with 409 while another is in progress or queued. Other migration tests in the suite can still be draining an async run when this test fires, so its single POST intermittently got a 409 (surfacing as "no response from migration run"). Retry the run until it is accepted, and mark the audited script ".always." so the accepted run re-executes and audits it even if a background run executed it first (which would otherwise skip it and drop the queryAudit).
Extend the manifest-based cache-busting to all console SPAs, so every content-hashed asset is linked by its hashed URL (cached while unchanged, refetched on change) with a graceful fallback to stable names: - add SpaManifest, a shared resolver (entry js/css by chunk name, and stable-module-URL -> hashed) used across the page servlet and console; - core modern console: hash the entry + chunks + assets and resolve the entry js, entry css and Monaco chunk css via ModernConsoleConfig, which the HTL page now links (also resolves the extension panel module URLs); - reports: also hash the console-panel entry (was stable); - migration: hash entries/chunks/assets + emit a manifest, so the shared servlet busts the migration page and the console busts its panel. Tests: it.tests assert hashed names for the reports page, reports panel, core entry, migration page and migration panel; the Playwright specs load every page (incl. the reports edit-view Monaco guard) so a manifest/ hashing regression that breaks a page is caught.
- The split Run button applied left-only rounding to the Run segment unconditionally, so without the (extension-registered) chevron the button looked square on the right. Default to a full pill; square the right edge only when the split chevron is present. - The reports/migration ui.apps nav filters replaced the shared /apps/cq/core/content/nav tree on install, wiping the core console's "AEM Groovy Console" Tools entry (and each other's). Use mode="merge" on all three nav filters so each adds its entry without deleting siblings.
Scheduler fixed-parameter values now render the same rich typed widgets as the run form (SELECT/DYNAMIC dropdowns, PATH/TAG browser, DATE picker, BOOLEAN checkbox, and a multifield for 'multiple' parameters) via a shared gcr-parameter-fields component, replacing the plain text inputs. The schedule parameterValues model now carries arrays end-to-end (ReportSchedule Map<String, Object>, servlet + service preserve List values, TS Record<string, ReportParameterValue>). Nav overlay filters switched to a two-filter pattern (merge shared nav folders, own only the leaf entry) so each package creates its Tools entry without wiping siblings.
…g is ready The migration scripts base under /conf is provisioned asynchronously and can still be absent when the console endpoint first reports ready, so the one-shot deploy in setUp intermittently 404s. Wrap it in an awaitility retry.
Two new flags on the reports configuration service (both default on): schedulingEnabled and distributionEnabled. Backend enforcement: - scheduling: DefaultReportScheduleService skips registration and unschedules all report jobs when disabled; ReportScheduledJobConsumer skips any job that still fires (authoritative runtime guard). - distribution: applyDistributions is the single sink for both scheduled and manual runs, so one guard there blocks every distribution; the manual ReportDistributeServlet returns 503 with a clear message. Frontend adapts: a new GET /bin/groovyconsole/reports/config endpoint exposes the flags; the editor hides the Schedule / Distribution sections and the run view hides 'Distribute now' when the matching feature is off (defaulting to enabled if the endpoint is unavailable).
|
royteeuwen
added a commit
that referenced
this pull request
Jul 21, 2026
…ge to main Refreshed after PR #95 (scheduled reports) merged to main. -s ours keeps main's tree verbatim (Groovy 5, modern UI, 20.x packages); records the 19.x history as merged so future 20.x->19.x fix syncs stay clean.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Summary
Reports can now run unattended on a cron schedule and deliver their results through a pluggable distribution SPI, available for both scheduled and manual runs.
Scheduling
runAs/scheduledByare forced server-side to the requesting user (body values ignored), enabled by a self-scoped impersonation grant. A user can only ever schedule a report to run as themselves, so scheduling can never be used to gain access./apps/groovyconsole-reports-definitionsdrop-zone (startup scan + resource-change listener) and run as the dedicatedaem-groovy-console-reports-executorservice user (reads/content). Because/appscan't be written at runtime, this is the trust boundary for the executor identity./confand/appsdefinitions never collide.Distribution
ReportDistributor/ReportDistributorRegistrySPI mirroring the exporter SPI; distributors render through any registered export format (CSV, XLSX, …).distributionErrors) but never fails the run.Security notes
/conf/groovyconsole/reports(unchanged); the console-permission check is defense-in-depth on top.runAsfails closed (impersonation is only ever self-granted); manual runs always use the caller's own resolver.Verification
mvn clean installgreen across all modules (aemanalyser on).Includes editor UI for schedules and distribution targets, and README documentation.