Features-ui: Add download audit modals, ZIP bundle generation, and PDF proxy endpoints#14
Features-ui: Add download audit modals, ZIP bundle generation, and PDF proxy endpoints#14nando-bingani wants to merge 74 commits intov2-prodfrom
Conversation
…set in catalog.j2 and a few css customs.
…set in catalog.j2 and a few css customs.
odp/ui/base/macros/catalog.j2
Outdated
| onclick="toggleSelectAll(this)"> | ||
| <label class="form-check-label" for="select_all">Select All</label> | ||
| </div> | ||
| {# <div class="form-check">#} |
There was a problem hiding this comment.
Don't leave commented out code. Remove it if it's not needed
odp/ui/base/macros/catalog.j2
Outdated
| {% macro result_list(records,app_name=None) %} | ||
|
|
||
| <form id="recordForm"> | ||
| {% if app_name == 'MIMS' %} |
There was a problem hiding this comment.
- Maybe move the code in this if into it's own macro and call that macro here.
- I think it would be better if this file didn't know about things like MIMS. The ides is for these to be re-usable chunks of code. So maybe pass in a variable called something like: 'show_bulk_download_options'. You can also set that to false by default which means you will only have to pass in true when you're calling it from MIMS and you don't have to change anything else from other areas.
odp/ui/base/macros/catalog.j2
Outdated
| onclick="toggleSelectAll(this)"> | ||
| <label class="form-check-label" for="select_all">Select All</label> | ||
| </div> | ||
| {# <div class="form-check">#} |
There was a problem hiding this comment.
If this code is not needed remove it rather than commenting it out
odp/ui/base/macros/catalog.j2
Outdated
| {% macro result_list(records,app_name=None) %} | ||
|
|
||
| <form id="recordForm"> | ||
| {% if app_name == 'MIMS' %} |
There was a problem hiding this comment.
- Maybe move the code that lies in this if to it's own macro. This will help declutter the current macro.
- This macro probably shouldn't know about app names like MIMS and such. It might be better to pass in a variable like 'show_download_options' that defaults to false.
odp/ui/base/macros/catalog.j2
Outdated
| </span> | ||
| </button> | ||
|
|
||
| {# <button#} |
odp/ui/base/macros/catalog.j2
Outdated
| <button class="btn btn-link p-0 no-outline" | ||
| type="button" | ||
| onclick="openSingleDownloadModal('{{ download_url }}', '{{ record.doi }}', '{{ record.id }}')"> | ||
| <img src="{{ url_for('static', filename='images/download-icon.png') }}" |
There was a problem hiding this comment.
Please have a look at the formatting of this file. Try keep the indenting right so that it's more readable.
odp/ui/base/macros/catalog.j2
Outdated
| {%- if not loop.last %}, {% endif %} | ||
| </span> | ||
| {% set download_urls = [] %} | ||
| {% for record in records %} |
There was a problem hiding this comment.
Lets look into this flow.
odp/ui/base/macros/catalog.j2
Outdated
|
|
||
| </form> | ||
|
|
||
| <div class="modal fade" id="download-popup" tabindex="-1"> |
There was a problem hiding this comment.
A lot of this looks like a repetition of the above?
odp/ui/base/macros/catalog.j2
Outdated
| (No DOI) | ||
| {% endif %} | ||
| </span> | ||
| {% macro result_list(records,app_name=None) %} |
There was a problem hiding this comment.
Maybe change the variable to something like "show_download_options" so that this file doesn't have to know about MIMS and such.
odp/ui/base/macros/catalog.j2
Outdated
| onclick="toggleSelectAll(this)"> | ||
| <label class="form-check-label" for="select_all">Select All</label> | ||
| </div> | ||
| {# <div class="form-check">#} |
There was a problem hiding this comment.
Commented out code should be removed if it's not being used
odp/ui/base/macros/catalog.j2
Outdated
| {% macro result_list(records,app_name=None) %} | ||
|
|
||
| <form id="recordForm"> | ||
| {% if app_name == 'MIMS' %} |
There was a problem hiding this comment.
It would maybe make this more readable if the code within this 'if' was moved to it's own macro and the macro was called here
odp/ui/base/macros/catalog.j2
Outdated
| </span> | ||
| </button> | ||
|
|
||
| {# <button#} |
odp/ui/base/macros/catalog.j2
Outdated
| {%- if not loop.last %}, {% endif %} | ||
| </span> | ||
| {% set download_urls = [] %} | ||
| {% for record in records %} |
There was a problem hiding this comment.
I'm not sure this should be stored in the DOM. Lets discuss
odp/ui/base/macros/catalog.j2
Outdated
|
|
||
| </form> | ||
|
|
||
| <div class="modal fade" id="download-popup" tabindex="-1"> |
There was a problem hiding this comment.
This looks very similar to the modal above
| {% elif prop == 'Keywords' %} | ||
| {{ record.keywords | sort | join(', ') }} | ||
| {% elif prop == 'Keywords' %} | ||
| {% for keyword in record.keywords | sort %} |
There was a problem hiding this comment.
Will this maybe be better in a server side function that gets the keywords data in the right state before it's delivered to the template
odp/ui/base/macros/catalog.j2
Outdated
| <input class="form-check-input" type="checkbox" id="accept-terms-of-use-popup"> | ||
| <label class="form-check-label" for="accept-terms-of-use-popup"> | ||
| <small class="text-muted"> | ||
| <strong>Data Usage Acknowledgment:</strong> These data are made available with the express understanding that any such use will properly acknowledge the originator(s) and publisher and cite the accession numbers and/or associated Digital Object Identifiers. Anyone wishing to use these data should properly cite and attribute the data providers listed as authors in the metadata provided with each dataset. It is expected that all the conditions of the data license will be strictly honoured. Use of any material herein should be properly cited using the dataset's persistent identifiers, such as accession numbers and DOIs. |
There was a problem hiding this comment.
This has been repeated 3 times
odp/ui/base/macros/submissions.j2
Outdated
There was a problem hiding this comment.
I'm not sure this should be here?
| } | ||
| } | ||
|
|
||
| function clearDownloadCache() { |
There was a problem hiding this comment.
This isn't being used apparently
| window.location.href = redirectUrl; | ||
| } | ||
|
|
||
| function selectedRecordListLink(event) { |
There was a problem hiding this comment.
Also not in use apparently
| updateButtonStates(); | ||
| } | ||
|
|
||
| function toggleUnSelectAll() { |
| if (cb?.type === 'checkbox') cb.checked = true; | ||
| } | ||
|
|
||
| function downloadSelectedRecords(event, buttonEl, record_id) { |
There was a problem hiding this comment.
Small issue, but pay attention to variable naming conventions. snake_case in python and camelCase in js. Then the actual name as well.
| const disclaimerCheckbox = document.getElementById('disclaimer-acknowledgment'); | ||
|
|
||
| // Validate all required fields | ||
| if (!nameInput.value.trim()) { |
There was a problem hiding this comment.
This approach of validating and handling the requests in JS works, but it's not traditionally how the systems handles this sort of thing. We would generally have a form within the modal with a submit target. The validation would be handled by wtforms and the submission logic would be handled in a view
|
|
||
| </div> | ||
| {{ pagination }} | ||
| {# <div class="d-flex justify-content-end p-3">#} |
odp/ui/base/views/catalog.py
Outdated
|
|
||
|
|
||
| @bp.route('/format/metadata.pdf', methods=['POST']) | ||
| def format_metadata_pdf(): |
There was a problem hiding this comment.
This is an unusual code flow. You are taking the metadata from the front-end, sending it to the back-end for pdf generation and then sending it back? Should it not just send a list of record id's back which will build the pdf by getting the metadata from the db and then sending it back, or something along those lines?
odp/ui/base/views/catalog.py
Outdated
| # The cli.post() method tries to parse JSON, but PDF is binary | ||
| # So we use requests directly to get the binary content | ||
| api_url = config.ODP.API_URL | ||
| response = requests.post( |
There was a problem hiding this comment.
We use either the api or cli objects so that communication between the client and API is authorised.
| event.preventDefault(); | ||
| const targetInput = document.getElementById('record-subset-link'); | ||
| if (!targetInput) { | ||
| console.error("Missing target input 'record-subset-link'"); |
There was a problem hiding this comment.
please look out for console logs
odp/ui/base/views/catalog.py
Outdated
| from flask import Blueprint, abort, current_app, make_response, redirect, render_template, request, url_for | ||
| from flask import Blueprint, abort, current_app, make_response, redirect, render_template, request, url_for, Response, \ | ||
| jsonify, send_file | ||
| from io import BytesIO |
There was a problem hiding this comment.
Format inputs. Check shortcuts. Mine is Ctrl+Alt+o
odp/ui/base/macros/catalog.j2
Outdated
| </div> | ||
| {% endfor %} | ||
|
|
||
| <div class="modal fade" id="download-audit-modal" tabindex="-1" aria-labelledby="downloadModalLabel" aria-hidden="true"> |
There was a problem hiding this comment.
Please check out the formatting, Especially with HTML. Use the shortcuts to help. Mine is Ctrl+Alt+l
odp/ui/base/views/catalog.py
Outdated
| page = 1 # request.args.getlist('page')[0] | ||
|
|
||
| size = 5 # request.args.getlist('size')[0] | ||
| catalog_record_list = cli.get(f'/catalog/{catalog_id}/subset?{record_ids_query}&page={page}&size={size}') |
There was a problem hiding this comment.
the convention is to pass the page and size in as parameters rather than on the url string. Have a look at index()
dylanpivo
left a comment
There was a problem hiding this comment.
If we can sit and talk about the flow that would be great
88a9851 to
1096888
Compare
- Replaced client-side JSZip with server-side ZIP generation - Simplified _performZipDownload() to use new /catalog/generate-zip-bundle endpoint - Removed manual audit logging from frontend (server handles it automatically) - Reduced frontend code: 73 lines of JSZip → 40 lines of clean server call - Added UI proxy endpoint for CORS handling (UI port 8080 → API port 2020) - Fixed modal field ID mismatch by passing userData as parameter Benefits: - fewer HTTP requests (2N+1 → 1) - Lower browser resource usage - atomic audit logging - No CORS errors Author: nando-bingani <n.bingani@saeon.nrf.ac.za>
1096888 to
1e6d42e
Compare
- Clean up: Removed commented-out code and unused JavaScript functions in `catalog.j2`, `catalog_subset.html`, and `catalog.js`.
- Refactor (Jinja):
- Replaced 'app_name' check with 'show_bulk_download_options' boolean in `result_list` macro.
- Extracted download audit logic into its own reusable macro `download_audit_modal`.
- Improved HTML indentation and formatting across template files.
- Refactor (Python/Views):
- Updated `subset_record_list` to pass 'page' and 'size' as parameters to `cli.get` rather than string formatting the URL.
- Standardized facet formatting and removed internal-only facets ('Keyword', 'SDG Variables') in a centralized helper.
- Fixed imports and removed redundant Response/jsonify definitions.
- JavaScript:
- Renamed variables to use camelCase (e.g., `recordId`) to match JS conventions.
- Removed production console logs.
- Download Flow:
- Migrated download validation to use a formal WTForm (`DownloadAuditForm`) and server-side ZIP generation via an API proxy.
Implement client-side download audit system with user metadata collection, ZIP bundle generation, and PDF proxy endpoints for CORS-free architecture.
Key Features:
/catalog/download-auditproxyNew Features:
#download-popup) - Single file downloads from detail page#download-audit-modal) - Multi-record ZIP downloads/catalog/format/metadata.pdfendpoint/catalog/download-auditendpoint forwards to ODP APIFiles Modified:
odp/ui/base/views/catalog.py- Proxy endpoints (PDF + audit)odp/ui/base/static/scripts/catalog.js- Download handlers, ZIP generation, form cachingodp/ui/base/templates/catalog_index.html- Search results with checkboxesodp/ui/base/templates/catalog_record.html- Detail page download buttonodp/ui/base/macros/catalog.j2- Download modals and buttonsBug Fixes:
Production Ready:
Browser Compatibility: