Skip to content

feat: scheduled canvas PDF export - #9770

Open
nishantmonu51 wants to merge 6 commits into
mainfrom
nishant/scheduled-canvas-pdf-export
Open

feat: scheduled canvas PDF export#9770
nishantmonu51 wants to merge 6 commits into
mainfrom
nishant/scheduled-canvas-pdf-export

Conversation

@nishantmonu51

@nishantmonu51 nishantmonu51 commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

Adds scheduled PDF exports for canvas dashboards, reusing the scheduled-reports machinery end-to-end. Recipients get the usual link-only report email; clicking "Download PDF file" opens the report export page, which renders the canvas in the browser (authenticated by the report's magic token) and generates the PDF with the existing client-side pipeline.

  • A canvas PDF report is a Report with export.format: pdf, a canvas annotation, and no data/query (nothing executes server-side at send time). New EXPORT_FORMAT_PDF enum value; parser and admin server validate the combination.
  • ReportReconciler.ResolveTransitiveAccess now reuses the canvas transitive-access logic (extracted as canvasTransitiveConditionResources) so magic-token recipients can query the canvas's components and metrics views.
  • Canvas filter/time state at scheduling time is captured as URL search params in the web_open_state annotation and replayed on the export and open pages; PDF options are persisted as pdf_include_filters / pdf_all_tabs annotations (new ReportOptions fields).
  • UI: "Schedule email delivery…" in the Share popover's PDF tab, a canvas variant of ScheduledReportDialog (schedule, recipients, PDF options; no query fields), a canvas-PDF branch on the report export page (CanvasPdfReportExport), and canvas support on the report open/metadata pages.
  • Tests: parser validation cases, a reconciler transitive-access test (TestReportCanvasResolveTransitiveAccess), and unit tests for the report form utils.

Adds scheduled PDF exports for canvas dashboards, reusing the Report
resource end-to-end. A canvas PDF report has export format 'pdf', a
'canvas' annotation, and no query; the reconciler emails the usual
link-only report email, and the export page renders the canvas in the
recipient's browser (via magic token) and generates the PDF with the
existing client-side pipeline. Canvas filter/time state captured at
scheduling time is stored in web_open_state and replayed on the export
and open pages.
@nishantmonu51
nishantmonu51 requested a review from pjain1 July 29, 2026 15:56
Shows the canvas filter bar (time range, comparison, and filter pills) in the
canvas variant of the scheduled report dialog, matching the filters section in
explore scheduled reports. The bar is backed by the live canvas store, which
edits the page URL: the dialog snapshots the URL when it opens (replaying the
report's stored state in edit mode), captures the edited state into
web_open_state on submit, and restores the original URL when it closes so the
dashboard behind the dialog is left unchanged.
@nishantmonu51 nishantmonu51 changed the title feat: scheduled canvas PDF export via email link feat: scheduled canvas PDF export Jul 30, 2026
@nishantmonu51

Copy link
Copy Markdown
Collaborator Author

@codex: review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e0673f3588

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread admin/server/reports.go
Comment thread runtime/reconcilers/report.go
Comment thread runtime/reconcilers/report.go
Comment thread proto/rill/admin/v1/api.proto Outdated
Comment thread runtime/reconcilers/canvas.go Outdated
Comment thread runtime/reconcilers/report.go Outdated
@pjain1

pjain1 commented Jul 30, 2026

Copy link
Copy Markdown
Member

Manual end-to-end verification on a cloud stack (email delivery, tokened download in incognito, recipient mode) is still pending.

I believe this is done?

@nishantmonu51

Copy link
Copy Markdown
Collaborator Author

Manual end-to-end verification on a cloud stack (email delivery, tokened download in incognito, recipient mode) is still pending.

I believe this is done?

Yes, I tested it manually.

- Bake the canvas's filters into the report's security rules: the dialog
  captures per-metrics-view filter expressions (metrics_view_filters in
  ReportOptions), the admin server stores them in a report annotation, and
  the report reconciler converts them to row-filter rules so magic-token
  recipients cannot query data beyond the report's filters.
- Remove the pdf_include_filters/pdf_all_tabs proto fields and annotations;
  the PDF options are now stored as extra params in web_open_state, which
  is only read by the UI.
- GenerateReportYAML: emit canvas/explore annotations and canvas state so
  generated canvas PDF report YAML passes parser validation.
- Report/canvas reconcilers: use the controller directly instead of
  re-fetching it; propagate canvas lookup errors other than not-found.
- Rename the share popover button to "Create scheduled export..." since
  scheduled exports deliver to Slack as well as email.
Comment thread web-common/src/features/scheduled-reports/ScheduledReportDialog.svelte Outdated
…ion time

- The canvas filter bar in the scheduled report dialog is now read-only
  (report forms must not change the underlying dashboard view); editing
  returns once unified filters support in-memory editing.
- Anchor relative time ranges at the report's execution time on the export
  page: TimeManager gains an executionTimeStore that TimeState passes
  through deriveInterval to MetricsViewTimeRanges' execution_time, which
  replaces now/latest/watermark server-side. This keeps the rendered data
  consistent with the scheduled run for every range format, across all of
  the canvas's metrics views.

@AdityaHegde AdityaHegde left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets add an info icon stating the filters section is readonly.

Image

Comment thread .superset/config.json Outdated
Comment thread web-common/src/features/scheduled-reports/ScheduledReportDialog.svelte Outdated
@pjain1

pjain1 commented Jul 31, 2026

Copy link
Copy Markdown
Member

AI review -

P0 — fix before merge

  1. Filter lock fails open. ScheduledReportDialog.svelte:309-311 — when getCanvasStoreUnguarded returns undefined, metricsViewFilters is undefined and the backend drops the annotation entirely. On edit, fall back to props.reportSpec.annotations.metrics_view_filters, or block submit until the store exists. Reachable via filters: {enable: false} on the canvas, or submitting before ResolveCanvas lands.
  2. Alerts time-zone regression. AlertDialogDeliveryTab.svelte:53 still passes {exploreName} to ScheduleForm, which now takes availableTimeZones. Alert schedules only offer Local + UTC. One-line fix.

P1 — should fix in this PR

  1. Strip pdf_include_filters / pdf_all_tabs from the two places web_open_state becomes a user-facing URL: CanvasPdfReportExport.svelte:43-44 (PDF footer link) and open/+page.ts:19-22 (report open redirect).
  2. Export page hangs when the canvas never resolves. CanvasPdfReportExport.svelte:149 — runExport never fires because CanvasLoadingState gates the slot on ready. Surface reconcileErrorMessage or add a wall-clock guard.
  3. Set executionTimeStore before the provider mounts rather than in use:startExportOnMount (CanvasPdfReportExport.svelte:80-88), so the canvas never fetches a now-anchored interval first.
  4. Add pdf to the export.format enum in runtime/parser/schema/project.schema.yaml:2954-2957.
  5. Remove .superset/config.json — unrelated to this feature, and missing a trailing newline.

P2 — small correctness/consistency

  1. Gate the metrics_view_filters write on opts.Canvas != "" in yamlForManagedReport (reports.go:606-610) to match yamlForCommittedReport and the canvas-gated read.
  2. Add a size cap on the serialized filters, mirroring magicAuthTokenFilterMaxSize = 1024 (magic_tokens.go:25).
  3. Replace
    with pointer-events: none on the read-only filter bar (BaseScheduledReportForm.svelte:166) so it stays in the accessibility tree.

…et config

Canvas reports have no query and their queryArgsJson is an empty string
(the proto default), which nullish coalescing does not catch, so editing
threw on JSON.parse(""). Skip the parse for canvas reports and use || so
empty strings fall through to "{}" in getDashboardNameFromReport too.

Also removes .superset/config.json (local tooling config, committed by
mistake).
- Filter capture no longer fails open: editing falls back to the report's
  stored metrics_view_filters annotation when the canvas store is
  unavailable, and creating refuses to submit with a clear error.
- Fix alerts time-zone regression: AlertDialogDeliveryTab now passes
  availableTimeZones to ScheduleForm (it previously passed the removed
  exploreName prop, reducing options to Local + UTC).
- Strip the internal pdf_include_filters/pdf_all_tabs params from
  user-facing URLs (PDF footer link and report open redirect).
- Export page no longer hangs when the canvas never resolves: a
  wall-clock guard surfaces an error after 60s.
- Anchor at execution time from the start: CanvasProvider accepts
  executionTime and sets it on the store at resolution, before any
  component fetches a now-anchored interval.
- Add pdf to the export.format enum in the project JSON schema.
- Gate the metrics_view_filters annotation write on canvas being set and
  cap serialized filter size at 1024 bytes (mirrors magic auth tokens).
- Keep the read-only filter bar in the accessibility tree: force
  pointer-events off on descendants instead of using inert.
@nishantmonu51

Copy link
Copy Markdown
Collaborator Author

All ten items addressed in dff754f:

P0

  1. Filter capture no longer fails open: edit mode falls back to the report's stored metrics_view_filters annotation when the canvas store is unavailable, and create mode refuses to submit with a "dashboard is still loading" error instead of silently dropping the filters.
  2. Fixed the alerts regression: AlertDialogDeliveryTab now computes availableTimeZones from the explore spec and passes it to ScheduleForm.

P1
3. pdf_include_filters/pdf_all_tabs are stripped from both user-facing URLs (PDF footer link and the report open redirect).
4. The export page no longer hangs on an unresolvable canvas: a 60s wall-clock guard surfaces an error with a retry button.
5. CanvasProvider now accepts executionTime and sets it on the store at resolution time, so the canvas never fetches a now-anchored interval first.
6. Added pdf to the export.format enum in project.schema.yaml.
7. .superset/config.json was already removed in ef39ca4 (it was committed by mistake).

P2
8. The metrics_view_filters write in yamlForManagedReport is now gated on opts.Canvas != "", matching the committed-YAML path.
9. Added reportFilterMaxSize = 1024 mirroring magicAuthTokenFilterMaxSize, applied cumulatively across the serialized expressions.
10. Replaced inert with forced pointer-events: none on the filter bar's descendants (needed because the bar re-enables pointer events internally), keeping it in the accessibility tree.

@AdityaHegde

Copy link
Copy Markdown
Collaborator

Filters are not loaded during edit. Can you also test and ensure editing does not clear the filters after fixing the loading issue?

Screenshot 2026-07-31 at 2 49 24 PM

@pjain1

pjain1 commented Jul 31, 2026

Copy link
Copy Markdown
Member

@AdityaHegde AI flagging this, is this valid

New: the execution-time anchor leaks into the live dashboard

web-common/src/features/canvas/CanvasInitialization.svelte:150-154, 185-189

Both call sites are guarded on if (executionTime), so the store is only ever set, never reset. canvasRegistry is module-level and is only evicted by removeCanvasStore (called solely from CanvasEditor.svelte:35) or an allowUnvalidatedSpec mismatch, and dispose() does not touch TimeManager.executionTimeStore. So the anchor survives the export page's teardown.

Reachable for the report creator: export page (sets the anchor) → "Go to report page" → the dashboard link in ReportMetadata → the canvas renders with existingStore, executionTime is undefined, the guard doesn't fire, and every relative time range stays anchored at the report's scheduled run. Silently stale data on the live dashboard.

Setting it unconditionally fixes it, since undefined is the correct reset value:

existingStore?.canvasEntity.timeManager.executionTimeStore.set(executionTime);
// ...and the same at the newStore call site

What breaks: relative time ranges on the canvas dashboard resolve against the report's execution_time instead of now/latest. The dashboard shows data as of the scheduled run rather than current data. Comparison ranges inherit the same anchor, and dimension-value lookups in the filter bar are scoped to the stale window too. Absolute ranges are unaffected.

Trigger path: visit /-/reports/{report}/export, then navigate in the same tab to that canvas. In practice: the report owner clicks "Go to report page" (that CTA is hidden for token recipients, CanvasPdfReportExport.svelte:145), then the dashboard link in ReportMetadata. Any logged-in member who opens an export link and browses on from it hits it.

@AdityaHegde

Copy link
Copy Markdown
Collaborator

@pjain1 Yes that is a legit issue. @nishantmonu51 worth fixing, since clicking dashboards and going to canvas will also trigger this. This and #9770 (comment) are both P0 IMO

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants