Skip to content

Prevent publication of duplicate theses with holds#1511

Merged
jazairi merged 1 commit into
mainfrom
etd-689
Jul 14, 2026
Merged

Prevent publication of duplicate theses with holds#1511
jazairi merged 1 commit into
mainfrom
etd-689

Conversation

@jazairi

@jazairi jazairi commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Why these changes are being introduced:

When an author changes degree periods, an
additional copy of the thesis is created during
registrar load. Any Holds on the original are not
transferred to the new copy of the thesis.

Relevant ticket(s):

How this addresses that need:

  • Adds a cross-thesis lookup for active or expired Holds.
  • Integrated said lookup in to the pre-publication checks (evaluate_status), such that publication
    review is blocked if a Thesis has a User with
    another Thesis that has an active or expired Hold.
  • During registrar data import, checks for pre-existing theses with active/expired holds for
    a given User and records the results in the report email.
  • Adds a field to the Thesis Metadata section of the thesis processing form that alerts processors
    to related theses with holds.

Side effects of this change:

  • Certain fixtures have been adjusted to test the new feature
  • Some unrelated style changes due to running rubocop

Developer

  • All new ENV is documented in README
  • All new ENV has been added to Heroku Pipeline, Staging and Prod
  • ANDI or Wave has been run in accordance to
    our guide and
    all issues introduced by these changes have been resolved or opened as new
    issues (link to those issues in the Pull Request details above)
  • Stakeholder approval has been confirmed (or is not needed)

Code Reviewer

  • The commit message is clear and follows our guidelines
    (not just this pull request message)
  • There are appropriate tests covering any new functionality
  • The documentation has been updated or is unnecessary
  • The changes have been verified
  • New dependencies are appropriate or there were no changes

Requires database migrations?

NO

Includes new or updated dependencies?

NO

@mitlib
mitlib temporarily deployed to thesis-submit-pr-1511 July 10, 2026 15:34 Inactive
@coveralls

coveralls commented Jul 10, 2026

Copy link
Copy Markdown

Coverage Status

coverage: 98.25% (+0.02%) from 98.232% — etd-689 into main

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds cross-thesis Hold detection to prevent publication of duplicate theses when an author has another thesis with an active or expired Hold, and surfaces that information in both the processing UI and registrar import reporting.

Changes:

  • Add Thesis#other_theses_with_holds / #no_other_theses_with_holds? and integrate the new check into evaluate_status to block publication review.
  • Display related theses-with-holds on the thesis processing form; include “multiple hold users” warnings in the registrar import summary email.
  • Update/extend test coverage and adjust fixtures; minor RuboCop/date handling cleanups.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
app/models/thesis.rb Adds cross-thesis lookup for active/expired holds and integrates it into pre-publication status checks.
app/controllers/thesis_controller.rb Loads related theses-with-holds for the processing form view.
app/views/thesis/process_theses.html.erb Renders a read-only “Related holds resolved?” field and links to related theses with holds.
app/jobs/registrar_import_job.rb Tracks newly created theses and reports users who already have theses with holds during registrar import.
app/mailers/report_mailer.rb Extends registrar import email method to accept “multiple hold users” cases and uses ENV.fetch.
app/views/report_mailer/registrar_import_email.html.erb Adds a warning section listing users and thesis links when holds exist on other theses.
test/models/thesis_test.rb Adds/updates tests for the new cross-thesis hold logic and status evaluation behavior.
test/controllers/thesis_controller_test.rb Adds integration assertions for the processing form UI behavior.
test/mailers/report_mailer_test.rb Adds assertions for registrar import email warning section rendering.
test/jobs/registrar_import_job_test.rb Adds a basic test for the new registrar-import hold aggregation helper.
test/models/marc_test.rb Adjusts MARC expectation to use fixture-driven author name.
test/models/hold_test.rb Switches date comparisons to Date.current for Rails-consistent time zone behavior.
test/integration/admin/admin_hold_test.rb Switches to Date.current for release date assertion.
test/fixtures/authors.yml Updates fixture associations to support the revised tests and expected author ordering.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread app/jobs/registrar_import_job.rb
Comment thread app/views/thesis/process_theses.html.erb Outdated
Comment thread app/views/report_mailer/registrar_import_email.html.erb Outdated
Comment thread app/models/thesis.rb Outdated
Comment thread test/jobs/registrar_import_job_test.rb Outdated
Comment thread test/controllers/thesis_controller_test.rb Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 5 comments.

Comment thread app/jobs/registrar_import_job.rb Outdated
Comment thread app/models/thesis.rb Outdated
Comment on lines +8 to +10
mail(from: "MIT Libraries <#{ENV.fetch('ETD_APP_EMAIL', nil)}>",
to: ENV.fetch('THESIS_ADMIN_EMAIL', nil),
cc: ENV.fetch('MAINTAINER_EMAIL', nil),

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This conflicts directly with rubocop guidance.

Comment on lines +18 to +20
mail(from: "MIT Libraries <#{ENV.fetch('ETD_APP_EMAIL', nil)}>",
to: ENV.fetch('THESIS_ADMIN_EMAIL', nil),
cc: ENV.fetch('MAINTAINER_EMAIL', nil),

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This conflicts directly with rubocop guidance.

Comment on lines +28 to +30
mail(from: "MIT Libraries <#{ENV.fetch('ETD_APP_EMAIL', nil)}>",
to: ENV.fetch('THESIS_ADMIN_EMAIL', nil),
cc: ENV.fetch('MAINTAINER_EMAIL', nil),

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This conflicts directly with rubocop guidance.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

test/models/thesis_test.rb:34

  • attach_file_with_purpose_to still accepts a purpose argument (and call sites pass values like 'signature_page'), but the helper always hard-codes 'thesis_pdf'. This makes the helper misleading and can hide test intent (or future regressions) when different purposes are expected.
  def attach_file_with_purpose_to(thesis, _purpose = 'thesis_pdf')
    file = Rails.root.join('test', 'fixtures', 'files', 'a_pdf.pdf')
    thesis.files.attach(io: File.open(file), filename: 'a_pdf.pdf')
    thesis.files.last.purpose = 'thesis_pdf'
    thesis.save

Comment thread app/controllers/thesis_controller.rb
Comment thread test/jobs/registrar_import_job_test.rb

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

test/models/thesis_test.rb:34

  • attach_file_with_purpose_to accepts a purpose argument, but the implementation ignores it and always sets the attachment purpose to 'thesis_pdf'. This makes call sites like attach_file_with_purpose_to(t, 'signature_page') misleading and reduces test correctness/clarity.
  def attach_file_with_purpose_to(thesis, _purpose = 'thesis_pdf')
    file = Rails.root.join('test', 'fixtures', 'files', 'a_pdf.pdf')
    thesis.files.attach(io: File.open(file), filename: 'a_pdf.pdf')
    thesis.files.last.purpose = 'thesis_pdf'
    thesis.save

Comment thread app/jobs/registrar_import_job.rb

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

test/models/thesis_test.rb:34

  • attach_file_with_purpose_to still hard-codes the attachment purpose to 'thesis_pdf', so callers that pass a different purpose (e.g. 'signature_page') aren’t actually testing that behavior. This makes the helper misleading and can hide bugs in purpose-dependent logic.
  def attach_file_with_purpose_to(thesis, _purpose = 'thesis_pdf')
    file = Rails.root.join('test', 'fixtures', 'files', 'a_pdf.pdf')
    thesis.files.attach(io: File.open(file), filename: 'a_pdf.pdf')
    thesis.files.last.purpose = 'thesis_pdf'
    thesis.save

Comment thread app/models/thesis.rb
Comment on lines +72 to +92
def collect_users_with_multiple_hold_theses(new_theses)
multiple_hold_users = []

new_theses.each do |thesis|
other_theses_with_holds = thesis.other_theses_with_holds.includes(:users).to_a
thesis.users.each do |user|
user_with_other_theses_with_holds = other_theses_with_holds.select do |other_thesis|
other_thesis.users.any? { |u| u.id == user.id }
end
next if user_with_other_theses_with_holds.empty?

multiple_hold_users << {
user: user,
new_thesis: thesis,
other_theses_with_holds: user_with_other_theses_with_holds
}
end
end

multiple_hold_users
end

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a tad overstated. We execute one DB query per new thesis to fetch related theses and eager-load users on those theses.

@jazairi
jazairi temporarily deployed to thesis-submit-pr-1511 July 10, 2026 20:42 Inactive
@JPrevost JPrevost self-assigned this Jul 14, 2026
Comment thread app/controllers/thesis_controller.rb
Comment thread app/models/thesis.rb
thesis.new_thesis? ? results[:new_theses] += 1 : results[:updated_theses] += 1
if thesis.new_thesis?
results[:new_theses] += 1
new_theses << thesis

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of keeping this larger list and then winnowing it down at the end to the (hopefully much smaller or empty) list I feel like we could update collect_users_with_multiple_hold_theses to act on a single thesis and call it here and refactor to only be tracking multiple_hold_users array. I think this is both more memory efficient and easier to follow unless we need the full list for something I'm not seeing yet

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless I misunderstand the flow, I think we need the full list to refer back to for later loop iterations.

Let's say we have a thesis with two coauthors. Author 1 is earlier in the CSV and has no other held theses; Author 2 is later and does have another held thesis. If the hold check only runs when the thesis is first created, we would miss the hold warning for Author 2. It's an unlikely use case, but one that we ought to consider.

I agree that we can refactor such that it doesn't store the thesis objects in memory while still running a hold check on every row, but that would likely add complexity. How concerned are we about efficiency?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I missed that important use case. Let's leave it as is and if we notice performance issues we can revisit. We probably won't :)

My concern noted the performance concern, but was really rooted in trying to make this easier to follow... but accuracy is more important than simplicity for this for sure.

Comment thread test/jobs/registrar_import_job_test.rb Outdated
assert_equal 'registrar', Thesis.last.versions.first.whodunnit
end

test 'collect_users_with_multiple_hold_theses method exists and works' do

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test doesn't show me that the method works, just that it exists. I think this test is really collect_users_with_multiple_hold_theses method returns not values when there are no holds or something like that?

Then we need another test to show it returning values when appropriate right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed about the naming. But it does show that it returns an empty array when appropriate. As for the case of returning values when appropriate, that's covered on line 88, unless I'm misunderstanding the request.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't the test on 88 only cover one case? Specifically something about co-authors? Is there a test here that returns values and is only involving one user? (There very well may be and I may be missing it or misunderstanding the tests!)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ohh yeah, good point! It makes total sense to test the simplest use case. I'll add something to that effect.

Comment thread test/jobs/registrar_import_job_test.rb Outdated
Comment thread test/models/thesis_test.rb
Comment thread test/models/thesis_test.rb Outdated
@jazairi
jazairi requested a review from JPrevost July 14, 2026 19:45
@jazairi
jazairi temporarily deployed to thesis-submit-pr-1511 July 14, 2026 19:46 Inactive
@jazairi

jazairi commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

@JPrevost Thanks for the review! The latest commit addresses most of the comments, but I had a couple of clarifying questions.

@jazairi
jazairi temporarily deployed to thesis-submit-pr-1511 July 14, 2026 20:38 Inactive
@jazairi
jazairi temporarily deployed to thesis-submit-pr-1511 July 14, 2026 20:39 Inactive
@jazairi
jazairi requested a review from JPrevost July 14, 2026 20:42
Why these changes are being introduced:

When an author changes degree periods, an
additional copy of the thesis is created during
registrar load. Any Holds on the original are not
transferred to the new copy of the thesis.

Relevant ticket(s):

- [ETD-689](https://mitlibraries.atlassian.net/browse/ETD-689)

How this addresses that need:

- Adds a cross-thesis lookup for active or expired
Holds.
- Integrated said lookup in to the pre-publication
checks (`evaluate_status`), such that publication
review is blocked if a Thesis has a User with
another Thesis that has an active or expired Hold.
- During registrar data import, checks for
pre-existing theses with active/expired holds for
a given User and records the results in the report
email.
- Adds a field to the Thesis Metadata section of
the thesis processing form that alerts processors
to related theses with holds.

Side effects of this change:

- Certain fixtures have been adjusted to test the new feature
- Some unrelated style changes due to running rubocop
@jazairi
jazairi temporarily deployed to thesis-submit-pr-1511 July 14, 2026 20:49 Inactive
@jazairi
jazairi merged commit b163202 into main Jul 14, 2026
1 check passed
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.

5 participants