diff --git a/config/locales/partners/en/graduate/graduate.en.yml b/config/locales/partners/en/graduate/graduate.en.yml
index 7547f616a..d549344af 100644
--- a/config/locales/partners/en/graduate/graduate.en.yml
+++ b/config/locales/partners/en/graduate/graduate.en.yml
@@ -428,6 +428,10 @@ en:
login_html: 'Login using your Penn State access account to view the paper.'
approval_help:
message: "If you know you need to approve a student's work, but do not see it in your queue or the link is broken, your ability to vote has expired. To resolve this issue, please try clearing your cache and then trying the link again or try the link on a different browser. If that doesn't work, please call OTD at 814-865-5448 or email us at gradthesis@psu.edu so that we may proxy for you."
+ default_format_review_note:
+ "The documents below contain your format review corrections, information on supporting materials, and the graduation checklist. Please refer to the Thesis and Dissertation Handbook when making corrections. \n \n The next step is to submit your final dissertation. The final dissertation must be reviewed and approved by all committee members and all changes made before it is uploaded to the eTD site for electronic approval. Formatting changes requested by the Office of Theses and Dissertations will be the only changes permitted after the final is uploaded."
+ default_final_submission_note:
+ "Your final submission requires further revisions. To see these revisions, open the PDF below in Adobe Acrobat. Please note this DOES NOT require you to get committee approvals again. Rather, make the requested revisions and then upload the corrected file to the ETDA. The Office of Theses and Dissertations will review your submission again until it is error-free. Please resubmit by the deadline for your intended graduation semester requirements. Not doing so puts you at risk of being removed from the graduation list. \n \n Thank you and do reach out if you have any questions!"
fee_message:
master_thesis:
message: "Before proceeding to your final submission, you must pay the $10 thesis fee. The fee can be paid at the Payment Section of the Graduate School Thesis and Dissertation Information webpage."
diff --git a/config/routes.rb b/config/routes.rb
index 7a75f6fb3..00d7e2029 100755
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -83,6 +83,7 @@
get '/files/format_reviews/:id', to: 'files#download_format_review', as: :format_review_file
get '/files/final_submissions/:id', to: 'files#download_final_submission', as: :final_submission_file
+ get '/files/admin_feedbacks/:id', to: 'files#download_admin_feedback', as: :admin_feedback_file
root to: 'submissions#redirect_to_default_dashboard'
end
@@ -113,6 +114,7 @@
get '/files/format_reviews/:id', to: 'files#download_format_review', as: :format_review_file
get '/files/final_submissions/:id', to: 'files#download_final_submission', as: :final_submission_file
+ get '/files/admin_feedbacks/:id', to: 'files#download_admin_feedback', as: :admin_feedback_file
root to: 'submissions#index'
get '/tips', to: 'authors#technical_tips', as: :technical_tips
diff --git a/db/migrate/20240613195728_create_admin_feedback_files.rb b/db/migrate/20240613195728_create_admin_feedback_files.rb
new file mode 100644
index 000000000..4cd71a916
--- /dev/null
+++ b/db/migrate/20240613195728_create_admin_feedback_files.rb
@@ -0,0 +1,11 @@
+class CreateAdminFeedbackFiles < ActiveRecord::Migration[6.1]
+ def change
+ create_table :admin_feedback_files do |t|
+ t.bigint :submission_id
+ t.text :asset
+ t.string :feedback_type
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 5c9869e4c..63eee0ac2 100755
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,15 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2024_06_03_141826) do
+ActiveRecord::Schema.define(version: 2024_07_02_191344) do
+
+ create_table "admin_feedback_files", charset: "utf8mb4", force: :cascade do |t|
+ t.bigint "submission_id"
+ t.text "asset"
+ t.string "feedback_type"
+ t.datetime "created_at", precision: 6, null: false
+ t.datetime "updated_at", precision: 6, null: false
+ end
create_table "admins", charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t|
t.string "access_id", default: "", null: false
diff --git a/spec/factories/admin_feedback_files.rb b/spec/factories/admin_feedback_files.rb
new file mode 100644
index 000000000..be304a379
--- /dev/null
+++ b/spec/factories/admin_feedback_files.rb
@@ -0,0 +1,8 @@
+# # frozen_string_literal: true
+
+FactoryBot.define do
+ factory :admin_feedback_file, class: 'AdminFeedbackFile' do |_f|
+ submission
+ asset { File.open(fixture('admin_feedback_01.pdf')) }
+ end
+end
diff --git a/spec/fixtures/admin_feedback_01.pdf b/spec/fixtures/admin_feedback_01.pdf
new file mode 100644
index 000000000..fed677586
Binary files /dev/null and b/spec/fixtures/admin_feedback_01.pdf differ
diff --git a/spec/integration/admin/submissions/edit_submission_spec.rb b/spec/integration/admin/submissions/edit_submission_spec.rb
index 1ce9cf69e..e4e43e3d0 100644
--- a/spec/integration/admin/submissions/edit_submission_spec.rb
+++ b/spec/integration/admin/submissions/edit_submission_spec.rb
@@ -74,8 +74,13 @@
end
within('#format-review-files') do
click_link "Additional File"
- all('input[type="file"]').first.set(fixture('format_review_file_01.pdf'))
- all('input[type="file"]').last.set(fixture('format_review_file_02.pdf'))
+ end
+ within('#format-review-file-fields') do
+ all('input[type="file"]')[0].set(fixture('format_review_file_01.pdf'))
+ all('input[type="file"]')[1].set(fixture('format_review_file_02.pdf'))
+ end
+ within('#admin-feedback-files') do
+ all('input[type="file"]')[0].set(fixture('admin_feedback_01.pdf'))
end
find('#submission_federal_funding_true').click
@@ -104,10 +109,14 @@
expect(page).to have_link "format_review_file_02.pdf"
end
+ within('#admin-feedback-files') do
+ expect(page).to have_link "admin_feedback_01.pdf"
+ end
+
expect(page.find_field("Format Review Notes to Student").value).to eq "New review notes"
expect(page.find_field("Admin notes").value).to eq "Some admin notes"
- within('#format-review-files') do
+ within('#format-review-file-fields') do
delete_link = find_all('a#file_delete_link').first
delete_link.click
end
@@ -116,6 +125,14 @@
visit admin_edit_submission_path(submission)
expect(page).to have_link "format_review_file_02.pdf"
expect(page).not_to have_link "format_review_file_01.pdf"
+
+ within('#admin-feedback-files') do
+ delete_link = find_all('a#file_delete_link').first
+ delete_link.click
+ end
+ click_button 'Update Metadata'
+ visit admin_edit_submission_path(submission)
+ expect(page).not_to have_link "admin_feedback_01.pdf"
end
it 'Allows admin to upload and delete final submission files' do
@@ -124,18 +141,25 @@
within('#final-submission-information') do
click_link "Additional File"
all('input[type="file"]').first.set(fixture('final_submission_file_01.pdf'))
+ click_link "Add File"
+ all('input[type="file"]').last.set(fixture('admin_feedback_01.pdf'))
end
click_button 'Update Metadata'
visit admin_edit_submission_path(final_submission)
expect(page).to have_link('final_submission_file_01.pdf')
+ expect(page).to have_link('admin_feedback_01.pdf')
within('#final-submission-information') do
- delete_link = find_all('a#file_delete_link').first
+ delete_links = find_all('a#file_delete_link')
+ delete_link = delete_links.first
+ delete_link2 = delete_links.last
delete_link.click
+ delete_link2.click
end
expect(page).to have_content("Marked for deletion [undo]")
click_button 'Update Metadata'
visit admin_edit_submission_path(final_submission)
expect(page).not_to have_link('final_submission_file_01.pdf')
+ expect(page).not_to have_link('admin_feedback_01.pdf')
end
it 'Allows admin to upload multiple final submission files' do
diff --git a/spec/integration/author/submission_review_pages_spec.rb b/spec/integration/author/submission_review_pages_spec.rb
index 758c2e775..a8a5f9e29 100644
--- a/spec/integration/author/submission_review_pages_spec.rb
+++ b/spec/integration/author/submission_review_pages_spec.rb
@@ -3,6 +3,9 @@
let!(:submission1) { FactoryBot.create :submission, :waiting_for_publication_release, author: current_author }
let!(:submission2) { FactoryBot.create :submission, :waiting_for_committee_review, author: current_author }
+ let!(:submission3) { FactoryBot.create :submission, :collecting_format_review_files_rejected, author: current_author }
+ let!(:submission3_1) { FactoryBot.create :submission, :collecting_format_review_files_rejected, author: current_author }
+ let!(:submission4) { FactoryBot.create :submission, :collecting_final_submission_files_rejected, author: current_author }
let!(:approval_configuration1) { FactoryBot.create :approval_configuration, degree_type: submission1.degree_type }
let!(:approval_configuration2) { FactoryBot.create :approval_configuration, degree_type: submission2.degree_type }
let(:invention_disclosures) { FactoryBot.create(:invention_disclosure, submission) }
@@ -14,6 +17,8 @@
let(:final_submission_file) do
FactoryBot.create :final_submission_file, submission: submission1
end
+ let(:admin_feedback_format) { FactoryBot.create :admin_feedback_file, submission: submission1, feedback_type: 'format-review' }
+ let(:admin_feedback_final) { FactoryBot.create :admin_feedback_file, submission: submission1, feedback_type: 'final-submission' }
let(:long_note) do
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Amicitiam autem adhibendam esse censent, quia sit ex eo genere, quae prosunt. An vero, inquit, quisquam potest probare, quod perceptfum, quod. Hinc ceteri particulas arripere conati suam quisque videro voluit afferre sententiam. Illum mallem levares, quo optimum atque humanissimum virum, Cn. Ne amores quidem sanctos a sapiente alienos esse arbitrantur. Duo Reges: constructio interrete. Id enim volumus, id contendimus, ut officii fructus sit ipsum officium. Hoc ille tuus non vult omnibusque ex rebus voluptatem quasi mercedem exigit.
Haec para/doca illi, nos admirabilia dicamus. Nobis aliter videtur, recte secusne, postea; Verum tamen cum de rebus grandioribus dicas, ipsae res verba rapiunt; Non quaeritur autem quid naturae tuae consentaneum sit, sed quid disciplinae. Eorum enim est haec querela, qui sibi cari sunt seseque diligunt. Paulum, cum regem Persem captum adduceret, eodem flumine invectio? Quantum Aristoxeni ingenium consumptum videmus in musicis? Fortemne possumus dicere eundem illum Torquatum? At iste non dolendi status non vocatur voluptas. Atque hoc loco similitudines eas, quibus illi uti solent, dissimillimas proferebas. Quare attende, quaeso. Et nemo nimium beatus est; Quid enim est a Chrysippo praetermissum in Stoicis? Lorem ipsum dolor sit amet, consectetur adipiscing elit. Amicitiam autem adhibendam esse censent, quia sit ex eo genere, quae prosunt. An vero, inquit, quisquam potest probare, quod perceptfum, quod. Hinc ceteri particulas arripere conati suam quisque videro voluit afferre sententiam. Illum mallem levares, quo optimum atque humanissimum virum, Cn. Ne amores quidem sanctos a sapiente alienos esse arbitrantur. Duo Reges: constructio interrete. Id enim volumus, id contendimus, ut officii fructus sit ipsum officium. Hoc ille tuus non vult omnibusque ex rebus voluptatem quasi mercedem exigit. Et nemo nimium beatus est; Quid enim est a Chrysippo praetermissum in Stoicis?'
@@ -30,6 +35,15 @@
submission1.save
submission2.committee_members << committee_member3
submission2.committee_members << committee_member4
+ submission3.admin_feedback_files << admin_feedback_format
+ submission3.format_review_notes = "not your best work"
+ submission3.save
+ submission3_1.format_review_notes = "no files, just notes"
+ submission3_1.save
+ submission4.admin_feedback_files << admin_feedback_final
+ submission4.status = "collecting final submission files rejected"
+ submission4.final_submission_notes = "not your best work"
+ submission4.save
oidc_authorize_author
visit author_submissions_path
end
@@ -63,8 +77,40 @@
end
end
+ context 'author can review admin feedback' do
+ it 'when format review is rejected' do
+ allow(admin_feedback_format).to receive(:current_location).and_return('spec/fixtures/admin_feedback_01.pdf')
+ visit "/author/submissions/#{submission3.id}/format_review/edit"
+ expect(page).to have_content('Format Review notes from the administrator')
+ expect(page).to have_content("not your best work")
+ expect(page).to have_link('admin_feedback_01.pdf')
+ # Makes sure that the file opens in a new tab
+ num_windows = page.driver.browser.window_handles.count
+ page.find_link('admin_feedback_01.pdf').click
+ expect(page.driver.browser.window_handles.count).to eql(num_windows + 1)
+ end
+
+ it 'even if no feedback file is attached' do
+ visit "/author/submissions/#{submission3_1.id}/format_review/edit"
+ expect(page).to have_content('Format Review notes from the administrator')
+ expect(page).to have_content("no files, just notes")
+ end
+
+ it 'when final submission is rejected' do
+ allow(admin_feedback_final).to receive(:current_location).and_return('spec/fixtures/admin_feedback_01.pdf')
+ visit "/author/submissions/#{submission4.id}/final_submission/edit"
+ expect(page).to have_content('Final Submission notes from the administrator')
+ expect(page).to have_content("not your best work")
+ expect(page).to have_link('admin_feedback_01.pdf')
+ # Makes sure that the file opens in a new tab
+ num_windows = page.driver.browser.window_handles.count
+ page.find_link('admin_feedback_01.pdf').click
+ expect(page.driver.browser.window_handles.count).to eql(num_windows + 1)
+ end
+ end
+
context 'author can review format review information' do
- it 'displays format review information for the submission in a new browser tab' do
+ it 'in a new browser tab' do
allow(format_review_file).to receive(:current_location).and_return('spec/fixtures/format_review_file_01.pdf')
visit "/author/submissions/#{submission1.id}/format_review"
expect(page).to have_content('Format Review Files')
@@ -84,8 +130,8 @@
end
end
- context 'author can review final submission information in a new browser tab' do
- it 'displays final submission information' do
+ context 'author can review final submission information ' do
+ it 'in a new browser tab' do
allow(final_submission_file).to receive(:current_location).and_return('spec/fixtures/final_submission_file_01.pdf')
visit "/author/submissions/#{submission1.id}/final_submission"
expect(page).to have_content('Final Submission Files')
diff --git a/spec/models/admin_feedback_file_spec.rb b/spec/models/admin_feedback_file_spec.rb
new file mode 100644
index 000000000..beadba353
--- /dev/null
+++ b/spec/models/admin_feedback_file_spec.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+require 'model_spec_helper'
+
+RSpec.describe AdminFeedbackFile, type: :model do
+ it { is_expected.to have_db_column(:id).of_type(:integer).with_options(null: false) }
+ it { is_expected.to have_db_column(:submission_id).of_type(:integer) }
+ it { is_expected.to have_db_column(:asset).of_type(:text) }
+ it { is_expected.to have_db_column(:created_at).of_type(:datetime) }
+ it { is_expected.to have_db_column(:updated_at).of_type(:datetime) }
+ it { is_expected.to have_db_column(:feedback_type).of_type(:string) }
+
+ it { is_expected.to validate_presence_of :asset }
+ it { is_expected.to validate_presence_of :submission_id }
+
+ it { is_expected.to belong_to :submission }
+
+ it 'returns class name with dashes' do
+ final_submission_file = described_class.new
+ expect(final_submission_file.class_name).to eql('admin-feedback-file')
+ end
+
+ it 'current_location returns full path and filename' do
+ submission = FactoryBot.create :submission, :waiting_for_publication_release
+ admin_feedback_file = described_class.new(submission_id: submission.id)
+ admin_feedback_file.id = 1234
+ allow_any_instance_of(described_class).to receive(:asset_identifier).and_return('stubbed_filename.pdf')
+ expect(admin_feedback_file.current_location).to eq("#{WORKFLOW_BASE_PATH}admin_feedback_files/#{EtdaFilePaths.new.detailed_file_path(admin_feedback_file.id)}stubbed_filename.pdf")
+ end
+
+ it 'full_file_path returns the full file path w/o filename' do
+ submission = FactoryBot.create :submission, :waiting_for_publication_release
+ admin_feedback_file = described_class.new(submission_id: submission.id)
+ admin_feedback_file.id = 1234
+ expect(admin_feedback_file.full_file_path).to eq("#{WORKFLOW_BASE_PATH}admin_feedback_files/#{EtdaFilePaths.new.detailed_file_path(admin_feedback_file.id)}")
+ end
+
+ describe 'feedback type validation' do
+ it 'validates the inclusion of feedback type in AdminFeedbackFile.feedback_types array' do
+ submission = FactoryBot.create :submission, :waiting_for_publication_release
+ admin_feedback_file = described_class.new(submission_id: submission.id)
+ expect(admin_feedback_file).to validate_inclusion_of(:feedback_type).in_array(described_class.feedback_types)
+ end
+ end
+end
diff --git a/spec/models/author_ability_spec.rb b/spec/models/author_ability_spec.rb
index 48c25c9b5..de70a3423 100644
--- a/spec/models/author_ability_spec.rb
+++ b/spec/models/author_ability_spec.rb
@@ -23,5 +23,10 @@
expect(author_ability.can? :edit, different_person).to be_falsey
expect(author_ability.can? :udpate, different_person).to be_falsey
end
+
+ it "does not allow author to create or delete an admin feedback file" do
+ expect(author_ability.can? :create, AdminFeedbackFile).to be_falsey
+ expect(author_ability.can? :destroy, AdminFeedbackFile).to be_falsey
+ end
end
end
diff --git a/spec/models/submission_spec.rb b/spec/models/submission_spec.rb
index cf507e871..f52ddcf6b 100755
--- a/spec/models/submission_spec.rb
+++ b/spec/models/submission_spec.rb
@@ -82,6 +82,7 @@
it { is_expected.to have_many :committee_members }
it { is_expected.to have_many :format_review_files }
it { is_expected.to have_many :final_submission_files }
+ it { is_expected.to have_many :admin_feedback_files }
it { is_expected.to have_many :keywords }
it { is_expected.to have_many :invention_disclosures }
@@ -886,4 +887,32 @@
end
end
end
+
+ describe "#final_submission_feedback_files?" do
+ it 'returns true if at least one admin feedback file with type final-submission' do
+ sub1 = described_class.new
+ sub1.admin_feedback_files.build(feedback_type: 'final-submission')
+ expect(sub1).to be_final_submission_feedback_files
+ end
+
+ it 'returns false if no admin feedback file has the type of final-submission' do
+ sub2 = described_class.new
+ sub2.admin_feedback_files.build(feedback_type: 'format-review')
+ expect(sub2).not_to be_final_submission_feedback_files
+ end
+ end
+
+ describe "#format_review_feedback_files?" do
+ it 'returns true if at least one admin feedback file with type format-review' do
+ sub3 = described_class.new
+ sub3.admin_feedback_files.build(feedback_type: 'format-review')
+ expect(sub3).to be_format_review_feedback_files
+ end
+
+ it 'returns false if no admin feedback file has the type of format-review' do
+ sub4 = described_class.new
+ sub4.admin_feedback_files.build(feedback_type: 'final-submission')
+ expect(sub4).not_to be_format_review_feedback_files
+ end
+ end
end