From a486906c1c74de2704537ba460e144e13357e193 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Fri, 9 Jun 2023 13:22:01 +0200 Subject: [PATCH 1/9] Add retraction notice <--> retracted paper relationship --- app/models/paper.rb | 14 +++ ...9104144_add_retraction_for_id_to_papers.rb | 5 ++ db/schema.rb | 5 +- spec/models/paper_spec.rb | 87 ++++++++++++------- 4 files changed, 80 insertions(+), 31 deletions(-) create mode 100644 db/migrate/20230609104144_add_retraction_for_id_to_papers.rb diff --git a/app/models/paper.rb b/app/models/paper.rb index 88a4dcf64..c34343132 100644 --- a/app/models/paper.rb +++ b/app/models/paper.rb @@ -19,6 +19,16 @@ class Paper < ApplicationRecord optional: true, foreign_key: "eic_id" + belongs_to :retracted_paper, + class_name: 'Paper', + optional: true, + foreign_key: "retraction_for_id" + + has_one :retraction_paper, + class_name: 'Paper', + foreign_key: "retraction_for_id", + inverse_of: :retracted_paper + has_many :invitations has_many :notes has_many :votes @@ -172,6 +182,10 @@ def published? accepted? || retracted? end + def is_a_retraction_notice? + retraction_for_id.present? + end + def invite_editor(editor_handle) return false unless editor = Editor.find_by_login(editor_handle) Notifications.editor_invite_email(self, editor).deliver_now diff --git a/db/migrate/20230609104144_add_retraction_for_id_to_papers.rb b/db/migrate/20230609104144_add_retraction_for_id_to_papers.rb new file mode 100644 index 000000000..af60e6b4b --- /dev/null +++ b/db/migrate/20230609104144_add_retraction_for_id_to_papers.rb @@ -0,0 +1,5 @@ +class AddRetractionForIdToPapers < ActiveRecord::Migration[7.0] + def change + add_reference :papers, :retraction_for, foreign_key: { to_table: :papers }, null: true + end +end diff --git a/db/schema.rb b/db/schema.rb index fc27cbd69..2df47d9d8 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2022_06_16_085520) do +ActiveRecord::Schema[7.0].define(version: 2023_06_09_104144) do # These are extensions that must be enabled in order to support this database enable_extension "hstore" enable_extension "plpgsql" @@ -110,10 +110,12 @@ t.string "git_branch" t.bigint "track_id" t.string "suggested_subject" + t.bigint "retraction_for_id" t.index ["editor_id"], name: "index_papers_on_editor_id" t.index ["eic_id"], name: "index_papers_on_eic_id" t.index ["labels"], name: "index_papers_on_labels", using: :gin t.index ["last_activity"], name: "index_papers_on_last_activity" + t.index ["retraction_for_id"], name: "index_papers_on_retraction_for_id" t.index ["reviewers"], name: "index_papers_on_reviewers", using: :gin t.index ["sha"], name: "index_papers_on_sha" t.index ["track_id"], name: "index_papers_on_track_id" @@ -178,4 +180,5 @@ t.index ["paper_id"], name: "index_votes_on_paper_id" end + add_foreign_key "papers", "papers", column: "retraction_for_id" end diff --git a/spec/models/paper_spec.rb b/spec/models/paper_spec.rb index 9caae45dd..b53972833 100644 --- a/spec/models/paper_spec.rb +++ b/spec/models/paper_spec.rb @@ -21,6 +21,14 @@ expect(association.macro).to eq(:belongs_to) end + it "retraction paper belongs to a retracted paper" do + association = Paper.reflect_on_association(:retracted_paper) + expect(association.macro).to eq(:belongs_to) + + association = Paper.reflect_on_association(:retraction_paper) + expect(association.macro).to eq(:has_one) + end + it "has many invitations" do association = Paper.reflect_on_association(:invitations) expect(association.macro).to eq(:has_many) @@ -73,43 +81,43 @@ end end - # Scopes - - it "should return recent" do - old_paper = create(:paper, created_at: 2.weeks.ago) - new_paper = create(:paper) + describe "Scopes" do + it "should return recent" do + old_paper = create(:paper, created_at: 2.weeks.ago) + new_paper = create(:paper) - expect(Paper.recent).to eq([new_paper]) - end + expect(Paper.recent).to eq([new_paper]) + end - it "should return only visible papers" do - hidden_paper = create(:paper, state: "submitted") - visible_paper_1 = create(:accepted_paper) - visible_paper_2 = create(:paper, state: "superceded") + it "should return only visible papers" do + hidden_paper = create(:paper, state: "submitted") + visible_paper_1 = create(:accepted_paper) + visible_paper_2 = create(:paper, state: "superceded") - expect(Paper.visible).to contain_exactly(visible_paper_1, visible_paper_2) - assert hidden_paper.invisible? - end + expect(Paper.visible).to contain_exactly(visible_paper_1, visible_paper_2) + assert hidden_paper.invisible? + end - it "should exclude withdrawn and rejected papers" do - rejected_paper = create(:paper, state: "rejected") - withdrawn_paper = create(:paper, state: "withdrawn") - paper = create(:accepted_paper) + it "should exclude withdrawn and rejected papers" do + rejected_paper = create(:paper, state: "rejected") + withdrawn_paper = create(:paper, state: "withdrawn") + paper = create(:accepted_paper) - expect(Paper.everything).to contain_exactly(paper) - expect(Paper.invisible).to contain_exactly(rejected_paper, withdrawn_paper) - end + expect(Paper.everything).to contain_exactly(paper) + expect(Paper.invisible).to contain_exactly(rejected_paper, withdrawn_paper) + end - it "should filter by track" do - track_A, track_B = create_list(:track, 2) - paper_A1, paper_A2 = create_list(:paper, 2, track: track_A) - paper_B1, paper_B2 = create_list(:paper, 2, track: track_B) - paper_C = create(:paper) + it "should filter by track" do + track_A, track_B = create_list(:track, 2) + paper_A1, paper_A2 = create_list(:paper, 2, track: track_A) + paper_B1, paper_B2 = create_list(:paper, 2, track: track_B) + paper_C = create(:paper) - track_A_papers = Paper.by_track(track_A.id) - expect(track_A_papers.size).to eq(2) - expect(track_A_papers.include?(paper_A1)).to be true - expect(track_A_papers.include?(paper_A2)).to be true + track_A_papers = Paper.by_track(track_A.id) + expect(track_A_papers.size).to eq(2) + expect(track_A_papers.include?(paper_A1)).to be true + expect(track_A_papers.include?(paper_A2)).to be true + end end # GitHub stuff @@ -201,6 +209,25 @@ end end + describe "#is_a_retraction_notice?" do + it "should return true if paper is a retraction notice for another paper" do + retracted_paper = create(:paper) + paper = create(:paper, retracted_paper: retracted_paper) + + expect(paper.retraction_for_id).to eq(retracted_paper.id) + expect(paper.retracted_paper).to eq(retracted_paper) + expect(paper.is_a_retraction_notice?).to be true + end + + it "should return false oherwise" do + paper = create(:paper) + + expect(paper.retraction_for_id).to be_nil + expect(paper.retracted_paper).to be_nil + expect(paper.is_a_retraction_notice?).to be false + end + end + context "when accepted" do it "should know how to generate a PDF URL for Google Scholar" do paper = create(:accepted_paper) From 57fa2eed51c4ba66e8fdd5ce394ceaa06ad9fa6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Mon, 12 Jun 2023 13:02:13 +0200 Subject: [PATCH 2/9] Refactor retracion info partial --- app/views/papers/_show_published.html.erb | 2 +- app/views/shared/_retraction_info.html.erb | 17 +++++++++++++++++ app/views/shared/_retraction_notice.html.erb | 3 --- spec/factories/papers_factory.rb | 6 +++--- 4 files changed, 21 insertions(+), 7 deletions(-) create mode 100644 app/views/shared/_retraction_info.html.erb delete mode 100644 app/views/shared/_retraction_notice.html.erb diff --git a/app/views/papers/_show_published.html.erb b/app/views/papers/_show_published.html.erb index c400bcff5..e68d57efc 100644 --- a/app/views/papers/_show_published.html.erb +++ b/app/views/papers/_show_published.html.erb @@ -1,5 +1,5 @@
- <%= render partial: "shared/retraction_notice" if @paper.retracted? %> + <%= render partial: "shared/retraction_info" %>
diff --git a/app/views/shared/_retraction_info.html.erb b/app/views/shared/_retraction_info.html.erb new file mode 100644 index 000000000..4363e73ca --- /dev/null +++ b/app/views/shared/_retraction_info.html.erb @@ -0,0 +1,17 @@ +<% if @paper.retracted? %> +
+ <% if @paper.retraction_notice.present? %> + <%= @paper.retraction_notice.html_safe %> + <% elsif @paper.retraction_paper.present? %> + This paper has been retracted, <%= link_to "read details here", @paper.retraction_paper.seo_url %> + <% else %> + This paper has been retracted + <% end %> +
+<% end %> + +<% if @paper.is_a_retraction_notice? %> +
+ This paper is a retraction notice for: <%= link_to @paper.retracted_paper.title, @paper.retracted_paper.seo_url %> +
+<% end %> diff --git a/app/views/shared/_retraction_notice.html.erb b/app/views/shared/_retraction_notice.html.erb deleted file mode 100644 index e0c3f1f37..000000000 --- a/app/views/shared/_retraction_notice.html.erb +++ /dev/null @@ -1,3 +0,0 @@ -
- <%= @paper.retraction_notice.html_safe %> -
diff --git a/spec/factories/papers_factory.rb b/spec/factories/papers_factory.rb index b9dc458b2..8eb03e49c 100644 --- a/spec/factories/papers_factory.rb +++ b/spec/factories/papers_factory.rb @@ -9,8 +9,8 @@ submission_kind { 'new' } track { create(:track) } - created_at { Time.now } - updated_at { Time.now } + created_at { Time.now } + updated_at { Time.now } factory :paper_with_sha do sha { '48d24b0158528e85ac7706aecd8cddc4' } @@ -35,7 +35,7 @@ end factory :resubmission_paper do - submission_kind { 'resubmission' } + submission_kind { 'resubmission' } end factory :rejected_paper do From e9f36171215ad8649cef4cb31814bf9b1e276da6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Mon, 12 Jun 2023 13:03:04 +0200 Subject: [PATCH 3/9] Draft retraction via API --- app/controllers/dispatch_controller.rb | 25 +++++++++++++++++++++++++ app/models/paper.rb | 11 ++++++++--- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/app/controllers/dispatch_controller.rb b/app/controllers/dispatch_controller.rb index 03a72980e..693cbeca4 100644 --- a/app/controllers/dispatch_controller.rb +++ b/app/controllers/dispatch_controller.rb @@ -145,4 +145,29 @@ def api_deposit head :forbidden end end + + def api_retract + if params[:secret] == ENV['BOT_SECRET'] + @paper = Paper.find_by_review_issue_id!(params[:id]) + + retraction_paper = Paper.new + retraction_paper.doi = @paper.doi + "RN" + retraction_paper.retraction_for_id = @paper.id + retraction_paper.title = "Retraction paper for #{@paper.title}" + retraction_paper.body = "Retraction paper for #{@paper.title}" + retraction_paper.repository_url = @paper.repository_url + retraction_paper.software_version = @paper.software_version + retraction_paper.track_id = @paper.track_id + retraction_paper.submission_kind = "new" + retraction_paper.state = "accepted" + + if retraction_paper.save! + @paper.update(retraction_notice: params[:retraction_notice]) if params[:retraction_notice].present? + @paper.retract! + end + else + head :forbidden + end + end + end diff --git a/app/models/paper.rb b/app/models/paper.rb index c34343132..0bc609c69 100644 --- a/app/models/paper.rb +++ b/app/models/paper.rb @@ -72,6 +72,10 @@ class Paper < ApplicationRecord event :withdraw do transitions to: :withdrawn end + + event :retract do + transitions to: :retracted + end end VISIBLE_STATES = [ @@ -138,14 +142,14 @@ class Paper < ApplicationRecord validates_presence_of :track_id, on: :create, message: "You must select a valid subject for the paper", if: Proc.new { JournalFeatures.tracks? } validates :kind, inclusion: { in: Rails.application.settings["paper_types"] }, allow_nil: true validates :submission_kind, inclusion: { in: SUBMISSION_KINDS, message: "You must select a submission type" }, allow_nil: false - validate :check_repository_address, on: :create + validate :check_repository_address, on: :create, unless: Proc.new {|paper| paper.is_a_retraction_notice?} def notify_editors - Notifications.submission_email(self).deliver_now + Notifications.submission_email(self).deliver_now unless self.is_a_retraction_notice? end def notify_author - Notifications.author_submission_email(self).deliver_now + Notifications.author_submission_email(self).deliver_now unless self.is_a_retraction_notice? end # Only index papers that are visible @@ -301,6 +305,7 @@ def archive_doi_url # A 5-figure integer used to produce the JOSS DOI def joss_id id = "%05d" % review_issue_id + id += "RN" if self.is_a_retraction_notice? "#{setting(:abbreviation).downcase}.#{id}" end From 8a76cc9e725449a1d2bf64efb77a60c12b1ddc05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Wed, 19 Jul 2023 13:38:28 +0200 Subject: [PATCH 4/9] Change DOI for retraction notices --- app/controllers/dispatch_controller.rb | 2 +- app/models/paper.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/dispatch_controller.rb b/app/controllers/dispatch_controller.rb index 693cbeca4..51019c050 100644 --- a/app/controllers/dispatch_controller.rb +++ b/app/controllers/dispatch_controller.rb @@ -151,7 +151,7 @@ def api_retract @paper = Paper.find_by_review_issue_id!(params[:id]) retraction_paper = Paper.new - retraction_paper.doi = @paper.doi + "RN" + retraction_paper.doi = @paper.doi + "R" retraction_paper.retraction_for_id = @paper.id retraction_paper.title = "Retraction paper for #{@paper.title}" retraction_paper.body = "Retraction paper for #{@paper.title}" diff --git a/app/models/paper.rb b/app/models/paper.rb index 0bc609c69..944ccda3f 100644 --- a/app/models/paper.rb +++ b/app/models/paper.rb @@ -305,7 +305,7 @@ def archive_doi_url # A 5-figure integer used to produce the JOSS DOI def joss_id id = "%05d" % review_issue_id - id += "RN" if self.is_a_retraction_notice? + id += "R" if self.is_a_retraction_notice? "#{setting(:abbreviation).downcase}.#{id}" end From a8e9150c4af3cac6c30d31602a181c758d3e2789 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Wed, 19 Jul 2023 13:38:45 +0200 Subject: [PATCH 5/9] Add authors and metadata to retraction papers --- app/controllers/dispatch_controller.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/controllers/dispatch_controller.rb b/app/controllers/dispatch_controller.rb index 51019c050..42169c15b 100644 --- a/app/controllers/dispatch_controller.rb +++ b/app/controllers/dispatch_controller.rb @@ -150,16 +150,24 @@ def api_retract if params[:secret] == ENV['BOT_SECRET'] @paper = Paper.find_by_review_issue_id!(params[:id]) + if params[:metadata] + metadata = JSON.parse(Base64.decode64(params[:metadata])) + else + metadata = nil + end + retraction_paper = Paper.new retraction_paper.doi = @paper.doi + "R" retraction_paper.retraction_for_id = @paper.id retraction_paper.title = "Retraction paper for #{@paper.title}" retraction_paper.body = "Retraction paper for #{@paper.title}" + retraction_paper.authors = "Editorial Board" retraction_paper.repository_url = @paper.repository_url retraction_paper.software_version = @paper.software_version retraction_paper.track_id = @paper.track_id retraction_paper.submission_kind = "new" retraction_paper.state = "accepted" + retraction_paper.metadata = metadata if retraction_paper.save! @paper.update(retraction_notice: params[:retraction_notice]) if params[:retraction_notice].present? From d690b2e5739b6187f3df970ecd1b003253da4824 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Fri, 11 Aug 2023 13:05:37 +0200 Subject: [PATCH 6/9] Show tags as array --- app/views/papers/show.json.jbuilder | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/papers/show.json.jbuilder b/app/views/papers/show.json.jbuilder index d216c1fb6..36128504f 100644 --- a/app/views/papers/show.json.jbuilder +++ b/app/views/papers/show.json.jbuilder @@ -18,7 +18,7 @@ if @paper.published? end json.reviewers @paper.metadata_reviewers json.languages @paper.language_tags.join(', ') - json.tags @paper.author_tags.join(', ') + json.tags @paper.author_tags json.paper_review @paper.review_url json.meta_review_issue_id @paper.meta_review_issue_id json.pdf_url @paper.seo_pdf_url From 5035e5860d4722a761939210fd1ef40974071caa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Fri, 11 Aug 2023 13:06:58 +0200 Subject: [PATCH 7/9] Show languages as Array --- app/views/papers/show.json.jbuilder | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/papers/show.json.jbuilder b/app/views/papers/show.json.jbuilder index 36128504f..23403bf31 100644 --- a/app/views/papers/show.json.jbuilder +++ b/app/views/papers/show.json.jbuilder @@ -17,7 +17,7 @@ if @paper.published? json.editor_orcid @paper.editor.orcid if @paper.editor.orcid end json.reviewers @paper.metadata_reviewers - json.languages @paper.language_tags.join(', ') + json.languages @paper.language_tags json.tags @paper.author_tags json.paper_review @paper.review_url json.meta_review_issue_id @paper.meta_review_issue_id From d92d4c9579c3fee6df293e73f9d1bb54d0b14e1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Fri, 11 Aug 2023 14:35:52 +0200 Subject: [PATCH 8/9] API retract --- app/controllers/dispatch_controller.rb | 40 +++++++++---- app/models/paper.rb | 9 ++- config/routes.rb | 7 ++- spec/controllers/dispatch_controller_spec.rb | 61 ++++++++++++++++++++ spec/factories/papers_factory.rb | 1 + 5 files changed, 100 insertions(+), 18 deletions(-) diff --git a/app/controllers/dispatch_controller.rb b/app/controllers/dispatch_controller.rb index 42169c15b..c11d49289 100644 --- a/app/controllers/dispatch_controller.rb +++ b/app/controllers/dispatch_controller.rb @@ -148,30 +148,46 @@ def api_deposit def api_retract if params[:secret] == ENV['BOT_SECRET'] - @paper = Paper.find_by_review_issue_id!(params[:id]) + paper = Paper.find_by_doi!(params[:doi]) + return head :unprocessable_entity if paper.retracted? if params[:metadata] metadata = JSON.parse(Base64.decode64(params[:metadata])) else - metadata = nil + metadata = {} end retraction_paper = Paper.new - retraction_paper.doi = @paper.doi + "R" - retraction_paper.retraction_for_id = @paper.id - retraction_paper.title = "Retraction paper for #{@paper.title}" - retraction_paper.body = "Retraction paper for #{@paper.title}" + retraction_paper.doi = metadata[:doi] || "#{paper.doi}R" + retraction_paper.retraction_for_id = paper.id + retraction_paper.title = metadata[:title] || "Retraction notice for: #{paper.title}" + retraction_paper.body = "Retraction notice for: #{paper.title}" retraction_paper.authors = "Editorial Board" - retraction_paper.repository_url = @paper.repository_url - retraction_paper.software_version = @paper.software_version - retraction_paper.track_id = @paper.track_id + retraction_paper.repository_url = paper.repository_url + retraction_paper.software_version = paper.software_version + retraction_paper.track_id = paper.track_id + retraction_paper.citation_string = params[:citation_string] retraction_paper.submission_kind = "new" retraction_paper.state = "accepted" retraction_paper.metadata = metadata + retraction_paper.accepted_at = Time.now + retraction_paper.review_issue_id = paper.review_issue_id + + if paper.track.nil? + submitting_author = Editor.includes(:user).board.select {|e| e.user.present? }.first.user + else + submitting_author = paper.track.aeics.select {|e| e.user.present? }.first.user + end + submitting_author = User.where(admin: true).first if submitting_author.nil? - if retraction_paper.save! - @paper.update(retraction_notice: params[:retraction_notice]) if params[:retraction_notice].present? - @paper.retract! + retraction_paper.submitting_author = submitting_author + + if retraction_paper.save! && retraction_paper.accept! + paper.update(retraction_notice: params[:retraction_notice]) if params[:retraction_notice].present? + paper.retract! + render json: retraction_paper.to_json, status: '201' + else + head :unprocessable_entity end else head :forbidden diff --git a/app/models/paper.rb b/app/models/paper.rb index 944ccda3f..0f7f74690 100644 --- a/app/models/paper.rb +++ b/app/models/paper.rb @@ -304,9 +304,12 @@ def archive_doi_url # A 5-figure integer used to produce the JOSS DOI def joss_id - id = "%05d" % review_issue_id - id += "R" if self.is_a_retraction_notice? - "#{setting(:abbreviation).downcase}.#{id}" + if self.is_a_retraction_notice? + return retracted_paper.joss_id + "R" + else + id = "%05d" % review_issue_id + return "#{setting(:abbreviation).downcase}.#{id}" + end end # This URL returns the 'DOI optimized' representation of a URL for a paper diff --git a/config/routes.rb b/config/routes.rb index 4be6d66cc..d96cd891e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -68,9 +68,9 @@ doi_prefix_name = Rails.application.settings[:abbreviation].downcase || "joss" - get '/papers/:doi/status.svg', to: "papers#status", format: "svg", constraints: { doi: /10.21105\/#{doi_prefix_name}\.\d{5}/} - get '/papers/:doi', to: "papers#show", constraints: { doi: /10.21105\/#{doi_prefix_name}\.\d{5}/} - get '/papers/:doi.:format', to: "papers#show", constraints: { doi: /10.21105\/#{doi_prefix_name}\.\d{5}/} + get '/papers/:doi/status.svg', to: "papers#status", format: "svg", constraints: { doi: /10.21105\/#{doi_prefix_name}\.\d{5}R?/} + get '/papers/:doi', to: "papers#show", constraints: { doi: /10.21105\/#{doi_prefix_name}\.\d{5}R?/} + get '/papers/:doi.:format', to: "papers#show", constraints: { doi: /10.21105\/#{doi_prefix_name}\.\d{5}R?/} get '/editor_profile', to: 'editors#profile', as: 'editor_profile' patch '/update_editor_profile', to: 'editors#update_profile', as: 'update_editor_profile' @@ -95,6 +95,7 @@ post '/papers/api_editor_invite', to: 'dispatch#api_editor_invite' post '/papers/api_start_review', to: 'dispatch#api_start_review' post '/papers/api_deposit', to: 'dispatch#api_deposit' + post '/papers/api_retract', to: 'dispatch#api_retract' post '/papers/api_assign_editor', to: 'dispatch#api_assign_editor' post '/papers/api_update_paper_info', to: 'dispatch#api_update_paper_info' post '/papers/api_assign_reviewers', to: 'dispatch#api_assign_reviewers' diff --git a/spec/controllers/dispatch_controller_spec.rb b/spec/controllers/dispatch_controller_spec.rb index ee00d1d60..81dd6f1a5 100644 --- a/spec/controllers/dispatch_controller_spec.rb +++ b/spec/controllers/dispatch_controller_spec.rb @@ -522,4 +522,65 @@ def headers(event, payload) expect(paper.accepted_at).to eql(initial_accepted_at) end end + + describe "POST #api_retract" do + + it "with no API key" do + post :api_retract + expect(response).to be_forbidden + end + + it "with the correct API key" do + user = create(:user) + paper = create(:accepted_paper, title: "Bad paper", review_issue_id: 1234, doi: "10.21105/test.00042") + track_editor = paper.track.aeics.first + track_editor.update(user: user) + + expect(paper.accepted_at).to be_present + expect(paper.state).to eql('accepted') + encoded_metadata = "eyJwYXBlciI6eyJ0aXRsZSI6IkZpZGdpdDogQW4gdW5nb2RseSB1bmlvbiBv\nZiBHaXRIdWIgYW5kIGZpZ3NoYXJlIiwidGFncyI6WyJleGFtcGxlIiwidGFn\ncyIsImZvciB0aGUgcGFwZXIiXSwibGFuZ3VhZ2VzIjpbIlB5dGhvbiIsIlJ1\nc3QiLCJQZXJsIl0sImF1dGhvcnMiOlt7ImdpdmVuX25hbWUiOiJBcmZvbiIs\nIm1pZGRsZV9uYW1lIjoiTS4iLCJsYXN0X25hbWUiOiJTbWl0aCIsIm9yY2lk\nIjoiMDAwMC0wMDAyLTM5NTctMjQ3NCIsImFmZmlsaWF0aW9uIjoiR2l0SHVi\nIEluYy4sIERpc25leSBJbmMuIn0seyJnaXZlbl9uYW1lIjoiSmFtZXMiLCJt\naWRkbGVfbmFtZSI6IlAuIiwibGFzdF9uYW1lIjoidmFuIERpc2hvZWNrIiwi\nb3JjaWQiOiIwMDAwLTAwMDItMzk1Ny0yNDc0IiwiYWZmaWxpYXRpb24iOiJE\naXNuZXkgSW5jLiJ9XSwiZG9pIjoiMTAuMjExMDUvam9zcy4wMDAxNyIsImFy\nY2hpdmVfZG9pIjoiaHR0cDovL2R4LmRvaS5vcmcvMTAuNTI4MS96ZW5vZG8u\nMTM3NTAiLCJyZXBvc2l0b3J5X2FkZHJlc3MiOiJodHRwczovL2dpdGh1Yi5j\nb20vYXBwbGljYXRpb25za2VsZXRvbi9Ta2VsZXRvbiIsImVkaXRvciI6ImFy\nZm9uIiwicmV2aWV3ZXJzIjpbIkBqaW0iLCJAYm9iIl19fQ==\n" + + post :api_retract, params: {secret: "testBOTsecret", + doi: "10.21105/test.00042", + citation_string: "Editorial Board, 2023, JOSS, Retraction etc.", + metadata: encoded_metadata + } + + expect(response).to be_successful + expect(paper.reload.state).to eql("retracted") + expect(paper.retraction_paper).to be_present + + retraction_notice = paper.retraction_paper + + expect(retraction_notice.state).to eql("accepted") + expect(retraction_notice.retracted_paper).to eql(paper) + expect(retraction_notice.submitting_author).to eql(user) + expect(retraction_notice.doi).to eql("10.21105/test.00042R") + expect(retraction_notice.track_id).to eql(paper.track_id) + expect(Base64.encode64(retraction_notice.metadata.to_json)).to eql(encoded_metadata) + expect(retraction_notice.review_issue_id).to eql(paper.review_issue_id) + expect(retraction_notice.citation_string).to eql("Editorial Board, 2023, JOSS, Retraction etc.") + expect(retraction_notice.title).to eql("Retraction notice for: Bad paper") + end + + it "should not retract papers twice" do + paper = create(:retracted_paper, title: "Bad paper", review_issue_id: 1234, doi: "10.21105/test.00042") + retraction_notice = paper.retraction_paper + expect(paper.accepted_at).to be_present + expect(paper.state).to eql('retracted') + encoded_metadata = "eyJwYXBlciI6eyJ0aXRsZSI6IkZpZGdpdDogQW4gdW5nb2RseSB1bmlvbiBv\nZiBHaXRIdWIgYW5kIGZpZ3NoYXJlIiwidGFncyI6WyJleGFtcGxlIiwidGFn\ncyIsImZvciB0aGUgcGFwZXIiXSwibGFuZ3VhZ2VzIjpbIlB5dGhvbiIsIlJ1\nc3QiLCJQZXJsIl0sImF1dGhvcnMiOlt7ImdpdmVuX25hbWUiOiJBcmZvbiIs\nIm1pZGRsZV9uYW1lIjoiTS4iLCJsYXN0X25hbWUiOiJTbWl0aCIsIm9yY2lk\nIjoiMDAwMC0wMDAyLTM5NTctMjQ3NCIsImFmZmlsaWF0aW9uIjoiR2l0SHVi\nIEluYy4sIERpc25leSBJbmMuIn0seyJnaXZlbl9uYW1lIjoiSmFtZXMiLCJt\naWRkbGVfbmFtZSI6IlAuIiwibGFzdF9uYW1lIjoidmFuIERpc2hvZWNrIiwi\nb3JjaWQiOiIwMDAwLTAwMDItMzk1Ny0yNDc0IiwiYWZmaWxpYXRpb24iOiJE\naXNuZXkgSW5jLiJ9XSwiZG9pIjoiMTAuMjExMDUvam9zcy4wMDAxNyIsImFy\nY2hpdmVfZG9pIjoiaHR0cDovL2R4LmRvaS5vcmcvMTAuNTI4MS96ZW5vZG8u\nMTM3NTAiLCJyZXBvc2l0b3J5X2FkZHJlc3MiOiJodHRwczovL2dpdGh1Yi5j\nb20vYXBwbGljYXRpb25za2VsZXRvbi9Ta2VsZXRvbiIsImVkaXRvciI6ImFy\nZm9uIiwicmV2aWV3ZXJzIjpbIkBqaW0iLCJAYm9iIl19fQ==\n" + total_papers = Paper.count + + post :api_retract, params: {secret: "testBOTsecret", + doi: "10.21105/test.00042", + citation_string: "Editorial Board, 2023, JOSS, Retraction etc.", + metadata: encoded_metadata + } + + expect(response.status).to eql(422) + expect(paper.reload.state).to eql("retracted") + expect(paper.reload.retraction_paper).to eql(retraction_notice) + expect(Paper.count).to eql(total_papers) + end + end end diff --git a/spec/factories/papers_factory.rb b/spec/factories/papers_factory.rb index 8eb03e49c..45f70ef9b 100644 --- a/spec/factories/papers_factory.rb +++ b/spec/factories/papers_factory.rb @@ -47,6 +47,7 @@ state { 'retracted' } accepted_at { Time.now } review_issue_id { 0 } + retraction_paper { create(:paper) } sequence(:doi) {|n| "10.21105/joss.0000#{n}" } end From ed22eb56705ba14a843bea8c0a07fbcd0b4a957e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Fri, 11 Aug 2023 15:49:55 +0200 Subject: [PATCH 9/9] Change show page for retracted/retraction papers --- app/views/papers/_show_published.html.erb | 54 +-------------- app/views/papers/_sidebar_published.html.erb | 73 ++++++++++++++++++++ spec/factories/papers_factory.rb | 3 +- spec/system/papers/show_published_spec.rb | 73 ++++++++++++++++++++ 4 files changed, 148 insertions(+), 55 deletions(-) create mode 100644 app/views/papers/_sidebar_published.html.erb create mode 100644 spec/system/papers/show_published_spec.rb diff --git a/app/views/papers/_show_published.html.erb b/app/views/papers/_show_published.html.erb index e68d57efc..fd89188f5 100644 --- a/app/views/papers/_show_published.html.erb +++ b/app/views/papers/_show_published.html.erb @@ -22,58 +22,6 @@
- -
-
- <%= link_to @paper.repository_url, class: 'btn paper-btn' do %> - <%= image_tag "gh-icon.svg" %> - Software repository - <% end %> - - <%= link_to @paper.review_url, class: 'btn paper-btn' do %> - <%= image_tag "icon_docs.svg" %> - Paper review - <% end %> - - <%= link_to @paper.pdf_url, class: 'btn paper-btn' do %> - <%= image_tag "dl-icon.svg" %> - Download paper - <% end %> - - <%= link_to @paper.archive_doi_url, class: 'btn paper-btn' do %> - <%= image_tag "hist-icon.svg" %> - Software archive - <% end %> -
- -
Review
-

Editor: <%= github_link @paper.metadata_editor %> (<%= link_to "all papers", papers_by_editor_path(@paper.metadata_editor) %>)
Reviewers: <%= pretty_reviewers(@paper.metadata_reviewers) %>

- -
Authors
-

<%= pretty_authors(@paper.metadata_authors) %>

- -
Citation
-

<%= @paper.citation_string %>

- -
<%= render partial: "bibtex", locals: { paper: @paper } %>
-
<%= link_to "Copy citation string".html_safe, "#", class: "clipboard-btn", "data-clipboard-action": "copy", "data-clipboard-target": "#citationstring" %> · <%= link_to "Copy BibTeX".html_safe, "#", class: "clipboard-btn", "data-clipboard-action": "copy", "data-clipboard-target": "#bibtex" %>  <%= octicon "paste", height: 16, class: "", "aria-label": "Copy" %>
- -
Tags
-

- <% @paper.author_tags.compact.each do |tag| %> - <%= link_to tag, papers_by_tag_path(tag: tag) %> - <% end %> -

-
Altmetrics
-
- -
Markdown badge
-

<%= image_tag @paper.status_badge_url %>   <%= octicon "paste", height: 16, class: "", "aria-label": "Copy" %>

- -
License
-

Authors of <%= Rails.application.settings['abbreviation'] %> papers retain copyright.

-

This work is licensed under a Creative Commons Attribution 4.0 International License.

-

Creative Commons License

-
+ <%= render partial: "sidebar_published" %>
diff --git a/app/views/papers/_sidebar_published.html.erb b/app/views/papers/_sidebar_published.html.erb new file mode 100644 index 000000000..900732a1d --- /dev/null +++ b/app/views/papers/_sidebar_published.html.erb @@ -0,0 +1,73 @@ +
+
+ <% if @paper.is_a_retraction_notice? %> + <%= link_to @paper.pdf_url, class: 'btn paper-btn' do %> + <%= image_tag "dl-icon.svg" %> + Download Retraction Notice + <% end %> + + <%= link_to @paper.retracted_paper.seo_url, class: 'btn paper-btn danger' do %> + <%= image_tag "icon_docs.svg" %> + Retracted Paper + <% end %> + <% else %> + + <% if @paper.retracted? && @paper.retraction_paper.present? %> + <%= link_to @paper.retraction_paper.seo_url, class: 'btn paper-btn' do %> + <%= image_tag "icon_docs.svg" %> + Retraction notice + <% end %> + <% end %> + <%= link_to @paper.repository_url, class: 'btn paper-btn' do %> + <%= image_tag "gh-icon.svg" %> + Software repository + <% end %> + + <%= link_to @paper.review_url, class: 'btn paper-btn' do %> + <%= image_tag "icon_docs.svg" %> + Paper review + <% end %> + + <%= link_to @paper.pdf_url, class: 'btn paper-btn' do %> + <%= image_tag "dl-icon.svg" %> + Download paper + <% end %> + + <%= link_to @paper.archive_doi_url, class: 'btn paper-btn' do %> + <%= image_tag "hist-icon.svg" %> + Software archive + <% end %> + <% end %> +
+ + <% unless @paper.is_a_retraction_notice? %> +
Review
+

Editor: <%= github_link @paper.metadata_editor %> (<%= link_to "all papers", papers_by_editor_path(@paper.metadata_editor) %>)
Reviewers: <%= pretty_reviewers(@paper.metadata_reviewers) %>

+ <% end %> + +
Authors
+

<%= pretty_authors(@paper.metadata_authors) %>

+ +
Citation
+

<%= @paper.citation_string %>

+ +
<%= render partial: "bibtex", locals: { paper: @paper } %>
+
<%= link_to "Copy citation string".html_safe, "#", class: "clipboard-btn", "data-clipboard-action": "copy", "data-clipboard-target": "#citationstring" %> · <%= link_to "Copy BibTeX".html_safe, "#", class: "clipboard-btn", "data-clipboard-action": "copy", "data-clipboard-target": "#bibtex" %>  <%= octicon "paste", height: 16, class: "", "aria-label": "Copy" %>
+ +
Tags
+

+ <% @paper.author_tags.compact.each do |tag| %> + <%= link_to tag, papers_by_tag_path(tag: tag) %> + <% end %> +

+
Altmetrics
+
+ +
Markdown badge
+

<%= image_tag @paper.status_badge_url %>   <%= octicon "paste", height: 16, class: "", "aria-label": "Copy" %>

+ +
License
+

Authors of <%= Rails.application.settings['abbreviation'] %> papers retain copyright.

+

This work is licensed under a Creative Commons Attribution 4.0 International License.

+

Creative Commons License

+
diff --git a/spec/factories/papers_factory.rb b/spec/factories/papers_factory.rb index 45f70ef9b..0abf896b9 100644 --- a/spec/factories/papers_factory.rb +++ b/spec/factories/papers_factory.rb @@ -47,8 +47,7 @@ state { 'retracted' } accepted_at { Time.now } review_issue_id { 0 } - retraction_paper { create(:paper) } - sequence(:doi) {|n| "10.21105/joss.0000#{n}" } + sequence(:doi) {|n| "10.21105/joss.4000#{n}" } end factory :submitted_paper_with_sha do diff --git a/spec/system/papers/show_published_spec.rb b/spec/system/papers/show_published_spec.rb new file mode 100644 index 000000000..d0d92f683 --- /dev/null +++ b/spec/system/papers/show_published_spec.rb @@ -0,0 +1,73 @@ +require "rails_helper" + +feature "Published paper's show page" do + before do + @accepted_paper = create(:accepted_paper, title: "Astronomy paper", doi: "10.21105/joss.00001", review_issue_id: 1) + @accepted_paper.metadata['paper']['title'] = "Astronomy paper" + @accepted_paper.metadata['paper']['authors'] = [{'given_name' => "Vera", 'last_name' => "Rubin"}] + @accepted_paper.metadata['paper']['tags'] = ["Galaxy rotation curves"] + @accepted_paper.save! + + @retracted_paper = create(:retracted_paper, title: "Bad paper", doi: "10.21105/joss.00002", review_issue_id: 2) + @retracted_paper.metadata['paper']['title'] = "Bad paper" + @retracted_paper.save! + + @retraction_notice = create(:accepted_paper, title: "Retraction notice for: Bad paper", doi: "10.21105/joss.00002R") + @retraction_notice.update(retracted_paper: @retracted_paper) + end + + scenario "Accepted paper" do + visit paper_path(@accepted_paper) + + expect(page).to have_content("Astronomy paper") + expect(page).to_not have_content("This paper has been retracted") + expect(page).to_not have_content("This paper is a retraction notice") + + expect(page).to have_link("Software repository") + expect(page).to have_link("Paper review") + expect(page).to have_link("Download paper") + expect(page).to have_link("Software archive") + + expect(page).to_not have_link("Retraction notice") + expect(page).to_not have_link("Retracted Paper") + expect(page).to_not have_link("Download Retraction Notice") + end + + scenario "Retracted paper" do + visit paper_path(@retracted_paper) + + expect(page).to have_content("Bad paper") + expect(page).to have_content("This paper has been retracted") + expect(page).to_not have_content("This paper is a retraction notice") + + expect(page).to have_link("Software repository") + expect(page).to have_link("Paper review") + expect(page).to have_link("Download paper") + expect(page).to have_link("Software archive") + + expect(page).to have_link("Retraction notice") + expect(page).to have_link("read details here") + + expect(page).to_not have_link("Retracted Paper") + expect(page).to_not have_link("Download Retraction Notice") + end + + scenario "Retraction notice" do + visit paper_path(@retraction_notice) + + expect(page).to have_content("Retraction notice for: Bad paper") + expect(page).to have_content("This paper is a retraction notice for: Bad paper") + expect(page).to_not have_content("This paper has been retracted") + + expect(page).to_not have_link("Software repository") + expect(page).to_not have_link("Paper review") + expect(page).to_not have_link("Download paper") + expect(page).to_not have_link("Software archive") + + expect(page).to_not have_link("Retraction notice") + expect(page).to_not have_link("read details here") + + expect(page).to have_link("Retracted Paper") + expect(page).to have_link("Download Retraction Notice") + end +end