-
Notifications
You must be signed in to change notification settings - Fork 25
HOLD UNTIL REQUESTED: Add "Download all" button that zips a callout's attached documents #1832
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -150,6 +150,7 @@ def handouts | |
| return redirect_to registration_ticket_path(@event_registration.slug) unless builtin_published?("handouts") | ||
| callout = @event.registration_ticket_callouts.find_by(builtin_key: "handouts") | ||
| @handout_cards = resource_cards_for(callout, icon: "fa-solid fa-file-pdf", return_to: "handouts") | ||
| @download_all_url = download_all_url_for(callout) | ||
| end | ||
|
|
||
| # Registrant-facing page for a single Resource, shown in the shared callout | ||
|
|
@@ -178,6 +179,17 @@ def faq | |
| @faq_content = callout&.description.presence || BuiltinCallouts.faq_html | ||
| end | ||
|
|
||
| # Streams every attached document on a callout as a single zip (the "Download | ||
| # all" button). Payment is excluded β it manages its own documents β and a | ||
| # callout with fewer than two files 404s, since the button only shows past one. | ||
| def download_all | ||
| callout = @event.registration_ticket_callouts.find_by(builtin_key: params[:builtin_key]) | ||
| resources = callout && callout.builtin_key != "payment" ? downloadable_resources(callout) : [] | ||
| raise ActiveRecord::RecordNotFound if resources.size < 2 | ||
| send_data build_documents_zip(resources), | ||
| filename: documents_zip_filename(callout), type: "application/zip" | ||
| end | ||
|
|
||
| private | ||
|
|
||
| # Whether the event's built-in callout for this key is materialized and | ||
|
|
@@ -228,6 +240,7 @@ def set_builtin_content | |
| @builtin_intro = callout&.description.presence | ||
| callout = nil if action_name == "payment" | ||
| @builtin_resource_cards = resource_cards_for(callout, icon: "fa-solid fa-file-lines", return_to: action_name) | ||
| @download_all_url = download_all_url_for(callout) | ||
| end | ||
|
|
||
| # This registrant's cards for a callout's linked resources (nil callout β none). | ||
|
|
@@ -239,6 +252,57 @@ def resource_cards_for(callout, icon:, return_to:) | |
| callout.decorate.resource_cards(registrant_slug: @event_registration.slug, return_to:, icon:) | ||
| end | ||
|
|
||
| # The zip endpoint for a callout's attached documents, or nil when the button | ||
| # shouldn't show. Backs the "Download all" button the shared callout chrome | ||
| # renders whenever a page has more than one downloadable resource (handouts and | ||
| # any built-in callout with attachments β Payment passes a nil callout here, so | ||
| # its own invoice/receipt/W-9 documents are excluded). Hidden in the sample | ||
| # preview, whose sentinel slug wouldn't resolve at the zip endpoint. | ||
| def download_all_url_for(callout) | ||
| return if callout.nil? || sample_preview? | ||
| return unless downloadable_resources(callout).many? | ||
| registration_download_all_path(@event_registration.slug, callout.builtin_key) | ||
| end | ||
|
|
||
| # A callout's linked resources that carry an attached file, in display order. | ||
| def downloadable_resources(callout) | ||
| callout.registration_ticket_callout_resources.ordered | ||
| .includes(resource: { downloadable_asset: :file_attachment }) | ||
| .filter_map(&:resource) | ||
| .select { |resource| resource.downloadable_asset&.file&.attached? } | ||
| end | ||
|
|
||
| # Builds an in-memory zip of the resources' files, named by resource title and | ||
| # de-duped so two documents sharing a title don't collide inside the archive. | ||
| def build_documents_zip(resources) | ||
| used = Set.new | ||
| Zip::OutputStream.write_buffer do |zip| | ||
| resources.each do |resource| | ||
| file = resource.downloadable_asset.file | ||
| zip.put_next_entry(unique_entry_name(resource, file, used)) | ||
| zip.write(file.download) | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π€ From Claude: Builds the whole zip in memory (each |
||
| end | ||
| end.string | ||
| end | ||
|
|
||
| def unique_entry_name(resource, file, used) | ||
| base = resource.title.to_s.strip.gsub(%r{[/\\]}, "-").presence || "document" | ||
| ext = File.extname(file.filename.to_s) | ||
| name = "#{base}#{ext}" | ||
| index = 1 | ||
| while used.include?(name.downcase) | ||
| index += 1 | ||
| name = "#{base} (#{index})#{ext}" | ||
| end | ||
| used << name.downcase | ||
| name | ||
| end | ||
|
|
||
| def documents_zip_filename(callout) | ||
| base = callout.title.presence || callout.builtin_key.humanize | ||
| "#{base.parameterize}-documents.zip" | ||
| end | ||
|
|
||
| # The join row for @resource on the callout the registrant arrived from | ||
| # (via return_to / callout_id), so the resource page can show that callout's | ||
| # editable page content under the title. | ||
|
|
||
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| <%# "Download all" header action for a callout page: a plain link to the callout's | ||
| documents.zip endpoint (see CalloutsController#download_all), which bundles every | ||
| attached document into one archive. url is the zip path. %> | ||
| <%= link_to url, class: "inline-flex items-center gap-2 rounded-lg bg-white/15 px-3 py-1.5 text-sm font-medium text-white ring-1 ring-inset ring-white/30 hover:bg-white/25 transition-colors" do %> | ||
| <i class="fa-solid fa-download"></i> | ||
| <span class="hidden sm:inline">Download all</span> | ||
| <% end %> |
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
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
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π€ From Claude: Guards the endpoint independently of the button: payment is excluded and anything with fewer than two files 404s, so a hand-crafted URL can't pull a zip the UI would never offer.