Skip to content

refactor: consolidate ontology download routes into a shared helper - #249

Open
alexskr wants to merge 2 commits into
developfrom
refactor/ontology-download-endpoints
Open

refactor: consolidate ontology download routes into a shared helper#249
alexskr wants to merge 2 commits into
developfrom
refactor/ontology-download-endpoints

Conversation

@alexskr

@alexskr alexskr commented Jul 28, 2026

Copy link
Copy Markdown
Member

Summary

The three download routes — ontology GET /:acronym/download, submission GET /:acronym/submissions/:id/download, and GET /:acronym/submissions/:id/download_diff — each reimplemented format resolution, file streaming, and access/restriction checks inline. That copy-paste had drifted into several latent bugs. This introduces Sinatra::Helpers::DownloadHelper (enforce_download_access + send_submission_download) and reduces each route to "resolve the submission, then delegate," fixing the bugs structurally in one place.

What this fixes

  • CSV download 500 (NameError). submissions/:id/download?download_format=csv referenced an undefined latest_submission (copy-paste from the ontology route) and returned 500 for the latest submission — the one case CSV is meant to work. Now uses submission.
  • Nil-path 500s → 404. The ontology /download and /download_diff routes called File.readable?(nil) (→ TypeError → 500) when a file path was unset. One guard now returns a clean 404.
  • Unified access control (strictest). Admins bypass restrict_download, and the restricted?/accessible? private-ontology ACL check now applies to all three routes (previously only the ontology route). ⚠️ Behavior change — see below.
  • diff is now just a format, so download_diff reuses the shared streamer.
  • Clearer CSV error for a non-latest submission (whose CSV the archiver deletes) instead of the misleading "Invalid download format: csv".

Performance fix

New Relic showed a submissions/:id/download taking 79.6s, 64.0s of it a single Redis GET (80%). Cause: the global Rack::Cache (Redis entitystore) was heuristically caching large send_file ontology bodies — buffering the file into Redis on write and reading it back as one multi-second Redis GET on hit, defeating send_file streaming and bloating the HTTP Redis. Download responses are now marked Cache-Control: no-store, so Rack::Cache skips them.

Behavior change to review

Unifying access control means, for the submission and diff routes:

  1. Admins gain the restrict_download bypass they lacked.
  2. The restricted?/accessible? ACL check now applies — a non-admin outside a private ontology's ACL who could download those routes before may now get 403. Both existing ACL tests already expect this (blocked→403, allowed/admin→200).

Tests

  • Adds test_download_ontology_submission_csv — asserts latest-submission CSV → 200 (regression for the NameError) and non-latest CSV → 400.
  • ⚠️ Not yet run against live backends (4store/Solr/Redis) — needs a CI/local run before merge.

Out of scope (follow-ups)

  • Wiring AgroPortal's GET /submissions/:id/diff structured-JSON diff endpoint (parse_diff_report) — model support already exists in linked_data.
  • Deciding whether to stop deleting .csv on archive (contrast: the diff file is retained).
  • Longer-term: serve downloads via web-server sendfile (nginx X-Accel-Redirect) so Ruby/Redis never touch the bytes.

🤖 Generated with Claude Code

The three download routes (ontology `/download`, submission `/download`,
and `/download_diff`) each reimplemented format resolution, file streaming,
and access/restriction checks inline. The copy-paste had drifted into bugs.
Introduce `Sinatra::Helpers::DownloadHelper` with `enforce_download_access`
and `send_submission_download`, and reduce each route to "resolve the
submission, then delegate."

This fixes several latent issues in one place:

- Fix a NameError (undefined `latest_submission`) that made
  `submissions/:id/download?download_format=csv` return 500 for the latest
  submission — the one case CSV is supposed to work. The reference now
  correctly uses `submission`.
- Replace nil-path 500s (`File.readable?(nil)` -> TypeError) on the ontology
  `/download` and `/download_diff` routes with a clean 404.
- Unify access control to the strictest policy across all three routes:
  admins bypass `restrict_download`, and the `restricted?`/`accessible?`
  private-ontology ACL check now applies everywhere (previously only on the
  ontology route).
- `diff` becomes just another download format, so `download_diff` reuses the
  shared streamer.
- Clearer error when CSV is requested for a non-latest submission (whose CSV
  the archiver deletes) instead of "Invalid download format: csv".

Also mark download responses `Cache-Control: no-store`. The global
Rack::Cache (Redis entitystore) was heuristically caching large `send_file`
ontology bodies: buffering the file into Redis on write and reading it back
as a single multi-second Redis GET on hit (observed 64s of a 79s request in
New Relic), defeating send_file streaming and bloating the HTTP Redis.

Add a regression test exercising CSV download of both the latest submission
(200) and a non-latest submission (400).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@codecov-commenter

codecov-commenter commented Jul 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 86.04651% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.17%. Comparing base (e1ee452) to head (7ba6174).
⚠️ Report is 19 commits behind head on develop.

Files with missing lines Patch % Lines
controllers/ontology_submissions_controller.rb 33.33% 4 Missing ⚠️
helpers/download_helper.rb 93.93% 2 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop     #249      +/-   ##
===========================================
+ Coverage    78.30%   79.17%   +0.87%     
===========================================
  Files           67       69       +2     
  Lines         3748     3876     +128     
===========================================
+ Hits          2935     3069     +134     
+ Misses         813      807       -6     
Flag Coverage Δ
ag 79.17% <86.04%> (+0.87%) ⬆️
fs 79.17% <86.04%> (+0.87%) ⬆️
gd 79.17% <86.04%> (+0.87%) ⬆️
unittests 79.17% <86.04%> (+0.87%) ⬆️
vo 79.17% <86.04%> (+0.87%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Guards against silently reintroducing the Rack::Cache regression where large
send_file ontology bodies were buffered into the Redis entitystore (confirmed
in production: entitystore entries up to ~535 MB).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@alexskr
alexskr requested a review from mdorf July 29, 2026 00:02
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.

2 participants