DSM-176: CSV Exports API - #77
Conversation
…t information for the csv file; use HeadObject to retrieve archive_status and restore_status
…b for preparing Csv and port existing logic; modify the received data format so files and folders are split by bucket, allowing to export object data across different s3 buckets
…figured in aws.yml; add endpoints for GET all csv exports and GET single export
…e S3 URI; install kaminari and paginate csv exports list; create export directory if missing
…ath for failed records, update csv path after writing to file, include timestamp and export id; add csv download endpoint
…export detail info
…intelligent tiering; make sure that selections are not empty
…ject URI / folder prefix if available
…r controller to buckets controller; move duplicate methods into a new BucketValidation concern
…), add tests for PrepareCsvExportJob; use Rails logger
…6 months, run it via cron
goldsmithb
left a comment
There was a problem hiding this comment.
This looks great! I left a few questions and suggestions, but overall it looks ready to go.
| end | ||
|
|
||
| render json: { folders: folders, objects: objects } | ||
| render_camelized_json({ folders: folders, objects: objects }) |
There was a problem hiding this comment.
I keep reading this as 'render_caramelized_json' in my head and thinking "sounds delicious" 😆
| csv_exports_directory = AWS_CONFIG[:s3_browser][:csv_exports_directory] | ||
|
|
||
| if csv_exports_directory.blank? | ||
| logger.error('CSV export directory is not configured in aws.yml') |
There was a problem hiding this comment.
Slight typo:
| logger.error('CSV export directory is not configured in aws.yml') | |
| Rails.logger.error('CSV export directory is not configured in aws.yml') |
|
|
||
| # A request must include at least one selection and every selection must target | ||
| # at least one file OR directory | ||
| def validate_selections!(selections) |
There was a problem hiding this comment.
Should we add a rule to reject selections that include an entire bucket? Those requests would have the form:
{
"selections": [
{
"bucket": "some-bucket",
"directories": ["/"],
"files": []
}
]
}
There was a problem hiding this comment.
We already do it in the normalize_prefixes method
| def csv_export_detail_json(csv_export) | ||
| { | ||
| id: csv_export.id, | ||
| export_paths: JSON.parse(csv_export.export_paths), # do we want to limit the number of paths returned here? |
There was a problem hiding this comment.
I don't think it's necessary; a copy of the original export request body would be nice to have on an export's details page.
There was a problem hiding this comment.
My comment here is a bit confusing because it was meant to be added to csv_export_summary_json used in the index action. We definitely want to display all export paths on the detail page.
The list view doesn't need to show all exported paths, and since the selection can include thousands of files, it makes sense to avoid sending unnecessary data. Similarly, we don't need to send export_errors when fetching the export list, since those can also contain many entries. I think it makes sense to keep summary (used in index) and detail (used in show) objects separate in this case and avoid overfetching.
| } | ||
| end | ||
|
|
||
| def csv_export_summary_json(csv_export) |
There was a problem hiding this comment.
perhaps we can combine this method and csv_export_detail_json to render responses for both index and show actions, and show/hide fields on the front end as needed.
| status: csv_export.status, | ||
| export_paths: JSON.parse(csv_export.export_paths), | ||
| export_errors: csv_export.export_errors, | ||
| path_to_csv_file: csv_export.path_to_csv_file, |
There was a problem hiding this comment.
I know we put it in the API spec, but I'm not sure why a user would need this information. Is it worth rethinking this? In either case I think we can consolidate this with the other csv export json method.
There was a problem hiding this comment.
I can remove this field if we just want to render a download button. For the consolidation part, see #77 (comment)
| end | ||
|
|
||
| def set_csv_export | ||
| @csv_export = CsvExport.find(params[:id]) |
There was a problem hiding this comment.
We tend to use strong params for all controller actions so that interfaces are super explicit. It doesn't add any security benefits in this case, but worth considering
| AWS_CONFIG[:aws_access_key_id], | ||
| AWS_CONFIG[:aws_secret_access_key] | ||
| AWS_CONFIG[:aws_secret_access_key], | ||
| AWS_CONFIG[:aws_session_token] |
There was a problem hiding this comment.
What is the session token for?
There was a problem hiding this comment.
I sometimes use it locally when testing but I removed it to avoid confusion
…g params when passing export id
…e list view, add tests
Ticket DSM-176
Overview
This PR adds a CSV Exports feature to the S3 Browser API. A user selects files and/or folders across one or more buckets and the app generates a downloadable CSV describing every matching object (S3 URI, name, modification date, storage tier, size and restore status). Generation runs in the background using Resque so large selections don't block the request. Finished exports can be listed, viewed and downloaded through the API. Exports are retained for 6 months and then cleaned up automatically via a daily cron job.
API Endpoints
All endpoints live under
/api/csv_exports, return JSON and require an authenticated user.POST /api/csv_exports- create an exportAccepts a
selectionspayload (one entry per bucket), each entry listingfiles(individual object keys) anddirectories(folder prefixes). The action:/directory)CsvExportrecord (statuspending) owned by the current user, enqueuesPrepareCsvExportJoband responds202 Acceptedwith the new record's id.The S3 lookups and file writing happen asynchronously in the job (see below), not during the request.
Permissions: any authenticated user may create an export.
GET /api/csv_exports- list exportsReturns the current user's exports (summary + pagination metadata), newest first.
Permissions: non-admins see only their own exports; admins see everyone's.
GET /api/csv_exports/:id- export detailReturns a single export's status, selections and any per-object errors.
Permissions: non-admins may view only their own exports, admins may view any.
GET /api/csv_exports/:id/download- download the CSVReturns the generated CSV file (
text/csv). Returns404if the file isn't available yet (still processing or the export failed).Permissions: same as detail/show - non-admins can download only their own exports, admins can download any.
CSV Generation (
PrepareCsvExportJob)A Resque job (in the
CSV_EXPORTqueue) builds the file:head_objectcall is made only forINTELLIGENT_TIERINGobjects, which are the only ones that can report archive/restore status.completed_with_errors. Only an error that prevents producing any file at all results infailure.Data Model & Permissions
CsvExportmodel /csv_exportstable:path_to_csv_file,userreference,status(enum:pending,processing,success,failure,completed_with_errors),export_errors,export_paths, timestamps. Destroying a record also removes its CSV file from disk (after_destroy).is_adminboolean tousers(defaultfalse). Admins can manage all exports, while regular users can create exports and read only their own.Automatic Cleanup
Atc::CsvExports::ExpiredPurgerdeletesCsvExportrecords (and their CSV files) older than 6 monthsExpiredPurgeris called in theatc:csv_exports:delete_expiredrake task and scheduled viawheneverto run daily.