-
Notifications
You must be signed in to change notification settings - Fork 4
Add JSON metadata export feature #1528
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
Open
jazairi
wants to merge
6
commits into
main
Choose a base branch
from
etd-695
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
7a9faf9
Add JSON metadata export feature
jazairi ff7c8c3
Apply Copilot code review suggestions
jazairi 0fc146c
Address code review feedback
jazairi d13b1d8
Rename instances of json_file to catalog_file for clarity
jazairi e6bf2ce
Clean up metadata rake tasks
jazairi 43ad3a4
Confirm correct format of titles in catalog exporter test
jazairi 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
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 |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| # Generates a JSON metadata file from a collection of theses to add to the Libraries catalog. | ||
| # | ||
| # Produces a tempfile containing a JSON object with a 'theses' array, where each thesis is | ||
| # exported via CatalogExporter. | ||
| # | ||
| # Example: | ||
| # batch = CatalogBatch.new(theses_array, 'export.json') | ||
| # catalog_file = batch.build | ||
| # File.write('export.json', File.read(catalog_file.path)) | ||
| # catalog_file.close! # Clean up tempfile | ||
| class CatalogBatch | ||
| def initialize(theses, filename) | ||
| @theses = theses | ||
| @filename = filename | ||
| end | ||
|
|
||
| # Builds and returns a Tempfile containing the JSON metadata export. The file is ready to read | ||
| # (file pointer rewound after writing). Caller is responsible for closing the file. | ||
| def build | ||
| catalog_file = Tempfile.new(@filename) | ||
| write_catalog_file(catalog_file) | ||
| catalog_file | ||
| end | ||
|
|
||
| private | ||
|
|
||
| def write_catalog_file(catalog_file) | ||
| theses_data = @theses.map do |thesis| | ||
| CatalogExporter.new(thesis).to_hash | ||
| end | ||
|
|
||
| json_output = { theses: theses_data } | ||
|
|
||
| catalog_file.write(JSON.pretty_generate(json_output)) | ||
| catalog_file.rewind | ||
| end | ||
| 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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| # Exports a single thesis as a hash for JSON serialization. | ||
| # | ||
| # Transforms a thesis record into a flat-ish structure with nested arrays for repeating fields | ||
| # (authors, advisors, degrees, departments). | ||
| # | ||
| # Example: | ||
| # exporter = CatalogExporter.new(thesis) | ||
| # hash = exporter.to_hash | ||
| # # => { title: "...", abstract: "...", authors: [{name: "..."}, ...], ... } | ||
| class CatalogExporter | ||
| def initialize(thesis) | ||
| @thesis = thesis | ||
| end | ||
|
|
||
| # Returns a hash representation of the thesis with all fields required by the metadata team. | ||
| # Includes: title, abstract, graduation_year, dspace_url, advisors, authors, degrees, and | ||
|
Member
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. Non-blocking: This docstring list of returned fields feels like it could be easy to fall out of sync with reality. The method is so clean I'd leave this out and stick with just the first line for docs. |
||
| # departments. Array fields are normalized to hashes with relevant metadata. | ||
| def to_hash | ||
| { | ||
| abstract:, | ||
| advisors:, | ||
| authors:, | ||
| degrees:, | ||
| departments:, | ||
| dspace_url:, | ||
| graduation_year:, | ||
| title: | ||
| } | ||
| end | ||
|
|
||
| private | ||
|
|
||
| def abstract | ||
| @thesis.abstract | ||
| end | ||
|
|
||
| def advisors | ||
| @thesis.advisors.map do |advisor| | ||
| { name: advisor.name } | ||
| end | ||
| end | ||
|
|
||
| def authors | ||
| @thesis.authors.map do |author| | ||
| { name: author.user.preferred_name } | ||
| end | ||
| end | ||
|
|
||
| def degrees | ||
| @thesis.degrees.map do |degree| | ||
| { abbreviation: degree.abbreviation } | ||
| end | ||
| end | ||
|
|
||
| def departments | ||
| @thesis.departments.map do |department| | ||
| { name: department.name_dspace } | ||
| end | ||
| end | ||
|
|
||
| def dspace_url | ||
| "https://dspace.mit.edu/handle/#{@thesis.dspace_handle}" | ||
| end | ||
|
|
||
| def graduation_year | ||
| @thesis.graduation_year | ||
| end | ||
|
|
||
| def title | ||
| @thesis.title.squish | ||
| end | ||
| 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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,8 @@ | ||
| <p>Hello,</p> | ||
|
|
||
| <p>Attached is a metadata export of <%= @theses.count %> theses generated on | ||
| <%= Date.current.strftime('%A, %B %d, %Y') %> at <%= Time.now.strftime('%r %Z') %>. | ||
| <%= Date.current.strftime('%A, %B %d, %Y') %> at <%= Time.now.strftime('%r %Z') %>.</p> | ||
|
|
||
| <p>This export includes both MARC (in zip) and JSON.</p> | ||
|
|
||
| <p>Please contact the ETD team at <%= ENV['THESIS_ADMIN_EMAIL'] %> with any questions.</p> |
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,49 @@ | ||
| namespace :metadata do | ||
| desc 'Generate a catalog export of a single published thesis for debugging' | ||
| task :catalog_export_thesis, [:thesis_id] => :environment do |_t, args| | ||
| if args.thesis_id.blank? | ||
| puts 'No thesis ID provided.' | ||
| next | ||
| end | ||
|
|
||
| thesis = Thesis.find(args.thesis_id) | ||
|
|
||
| if thesis.publication_status == 'Published' | ||
| catalog_exporter = CatalogExporter.new(thesis) | ||
| json_data = catalog_exporter.to_hash | ||
|
|
||
| puts "Catalog Export for Thesis #{args.thesis_id}:" | ||
| puts JSON.pretty_generate(json_data) | ||
| else | ||
| puts "Thesis status of #{thesis.publication_status} cannot be exported. Only published theses can be exported." | ||
| end | ||
| end | ||
|
|
||
| # This task is recommended for local development only. On Heroku (or other ephemeral filesystems), | ||
| # files saved to disk will be deleted when the dyno restarts, making them inaccessible. | ||
| desc 'Generate a catalog export batch for a specific term (e.g., "2024-June") and save to temp file' | ||
| task :catalog_export_batch, %i[term output_file] => :environment do |_t, args| | ||
| if args.term.blank? | ||
| puts 'Usage: rake metadata:catalog_export_batch["2024-June","output.json"]' | ||
| puts 'Term format: YYYY-Month (e.g., 2024-June, 2024-September)' | ||
| next | ||
| end | ||
|
|
||
| year, month_name = args.term.split('-') | ||
| query_date = Date.parse("1 #{month_name} #{year}") | ||
|
|
||
| output_file = args.output_file || Rails.root.join("tmp/catalog_export_#{args.term}_#{DateTime.now.utc.strftime('%H_%M')}.json").to_s | ||
|
|
||
| theses = Thesis.published.where(grad_date: query_date.all_month) | ||
|
|
||
| if theses.any? | ||
| catalog_batch = CatalogBatch.new(theses, File.basename(output_file)) | ||
| catalog_file = catalog_batch.build | ||
| FileUtils.cp(catalog_file.path, output_file) | ||
| catalog_file.close! | ||
| puts "Exported #{theses.count} theses to: #{output_file}" | ||
| else | ||
| puts "No published theses found for #{args.term}" | ||
| end | ||
| end | ||
| 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
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,73 @@ | ||
| require 'test_helper' | ||
|
|
||
| class CatalogBatchTest < ActiveSupport::TestCase | ||
| test 'builds a valid JSON file' do | ||
| theses = [theses(:published)] | ||
| batch = CatalogBatch.new(theses, 'test.json') | ||
| catalog_file = batch.build | ||
|
|
||
| json_content = File.read(catalog_file.path) | ||
| json_data = JSON.parse(json_content) | ||
|
|
||
| assert_not_nil(json_data) | ||
| catalog_file.close | ||
| end | ||
|
|
||
| test 'wraps theses in a wrapper object with theses key' do | ||
| theses = [theses(:published)] | ||
| batch = CatalogBatch.new(theses, 'test.json') | ||
| catalog_file = batch.build | ||
|
|
||
| json_content = File.read(catalog_file.path) | ||
| json_data = JSON.parse(json_content) | ||
|
|
||
| assert(json_data.key?('theses')) | ||
| assert(json_data['theses'].is_a?(Array)) | ||
| catalog_file.close | ||
| end | ||
|
|
||
| test 'includes all theses in the batch' do | ||
| theses = [theses(:published), theses(:one)] | ||
| batch = CatalogBatch.new(theses, 'test.json') | ||
| catalog_file = batch.build | ||
|
|
||
| json_content = File.read(catalog_file.path) | ||
| json_data = JSON.parse(json_content) | ||
|
|
||
| assert_equal(2, json_data['theses'].count) | ||
| catalog_file.close | ||
| end | ||
|
|
||
| test 'includes all required fields' do | ||
| theses = [theses(:published)] | ||
| batch = CatalogBatch.new(theses, 'test.json') | ||
| catalog_file = batch.build | ||
|
|
||
| json_content = File.read(catalog_file.path) | ||
| json_data = JSON.parse(json_content) | ||
|
|
||
| thesis_data = json_data['theses'].first | ||
|
|
||
| assert(thesis_data.key?('abstract')) | ||
| assert(thesis_data.key?('advisors')) | ||
| assert(thesis_data.key?('authors')) | ||
| assert(thesis_data.key?('degrees')) | ||
| assert(thesis_data.key?('departments')) | ||
| assert(thesis_data.key?('dspace_url')) | ||
| assert(thesis_data.key?('graduation_year')) | ||
| assert(thesis_data.key?('title')) | ||
|
|
||
| catalog_file.close | ||
| end | ||
|
|
||
| test 'empty theses array produces valid JSON' do | ||
| batch = CatalogBatch.new([], 'test.json') | ||
| catalog_file = batch.build | ||
|
|
||
| json_content = File.read(catalog_file.path) | ||
| json_data = JSON.parse(json_content) | ||
|
|
||
| assert_equal(0, json_data['theses'].count) | ||
| catalog_file.close | ||
| end | ||
| end |
Oops, something went wrong.
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.
Thanks for adding this. Very clear and made me think about how this all works.