diff --git a/Gemfile b/Gemfile
index 1883d68ce..b83703353 100644
--- a/Gemfile
+++ b/Gemfile
@@ -9,7 +9,7 @@ gem 'google_drive'
gem 'groupdate'
gem 'honeybadger', '~> 5.2.0'
gem 'html-pipeline', '~> 2.14.3'
-gem 'commonmarker', '~> 0.23.8'
+gem 'commonmarker', '~> 0.23.10'
gem 'net-sftp', '~> 4.0'
gem 'octicons_helper'
gem 'omniauth-orcid', '~> 2.1.1'
@@ -18,7 +18,7 @@ gem 'octokit', '~> 6.0'
gem 'pdf-reader', '~> 2.11.0'
gem 'pg', '~> 1.4.6'
gem 'pagy'
-gem 'rails', '7.0.6'
+gem 'rails', '7.0.7'
gem "importmap-rails"
gem "turbo-rails"
gem "stimulus-rails"
diff --git a/Gemfile.lock b/Gemfile.lock
index 2f2259f4c..dc4f83a20 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -4,47 +4,47 @@ GEM
Ascii85 (1.1.0)
aasm (5.5.0)
concurrent-ruby (~> 1.0)
- actioncable (7.0.6)
- actionpack (= 7.0.6)
- activesupport (= 7.0.6)
+ actioncable (7.0.7)
+ actionpack (= 7.0.7)
+ activesupport (= 7.0.7)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
- actionmailbox (7.0.6)
- actionpack (= 7.0.6)
- activejob (= 7.0.6)
- activerecord (= 7.0.6)
- activestorage (= 7.0.6)
- activesupport (= 7.0.6)
+ actionmailbox (7.0.7)
+ actionpack (= 7.0.7)
+ activejob (= 7.0.7)
+ activerecord (= 7.0.7)
+ activestorage (= 7.0.7)
+ activesupport (= 7.0.7)
mail (>= 2.7.1)
net-imap
net-pop
net-smtp
- actionmailer (7.0.6)
- actionpack (= 7.0.6)
- actionview (= 7.0.6)
- activejob (= 7.0.6)
- activesupport (= 7.0.6)
+ actionmailer (7.0.7)
+ actionpack (= 7.0.7)
+ actionview (= 7.0.7)
+ activejob (= 7.0.7)
+ activesupport (= 7.0.7)
mail (~> 2.5, >= 2.5.4)
net-imap
net-pop
net-smtp
rails-dom-testing (~> 2.0)
- actionpack (7.0.6)
- actionview (= 7.0.6)
- activesupport (= 7.0.6)
+ actionpack (7.0.7)
+ actionview (= 7.0.7)
+ activesupport (= 7.0.7)
rack (~> 2.0, >= 2.2.4)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0)
- actiontext (7.0.6)
- actionpack (= 7.0.6)
- activerecord (= 7.0.6)
- activestorage (= 7.0.6)
- activesupport (= 7.0.6)
+ actiontext (7.0.7)
+ actionpack (= 7.0.7)
+ activerecord (= 7.0.7)
+ activestorage (= 7.0.7)
+ activesupport (= 7.0.7)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
- actionview (7.0.6)
- activesupport (= 7.0.6)
+ actionview (7.0.7)
+ activesupport (= 7.0.7)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
@@ -52,22 +52,22 @@ GEM
active_link_to (1.0.5)
actionpack
addressable
- activejob (7.0.6)
- activesupport (= 7.0.6)
+ activejob (7.0.7)
+ activesupport (= 7.0.7)
globalid (>= 0.3.6)
- activemodel (7.0.6)
- activesupport (= 7.0.6)
- activerecord (7.0.6)
- activemodel (= 7.0.6)
- activesupport (= 7.0.6)
- activestorage (7.0.6)
- actionpack (= 7.0.6)
- activejob (= 7.0.6)
- activerecord (= 7.0.6)
- activesupport (= 7.0.6)
+ activemodel (7.0.7)
+ activesupport (= 7.0.7)
+ activerecord (7.0.7)
+ activemodel (= 7.0.7)
+ activesupport (= 7.0.7)
+ activestorage (7.0.7)
+ actionpack (= 7.0.7)
+ activejob (= 7.0.7)
+ activerecord (= 7.0.7)
+ activesupport (= 7.0.7)
marcel (~> 1.0)
mini_mime (>= 1.1.0)
- activesupport (7.0.6)
+ activesupport (7.0.7)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
@@ -91,7 +91,7 @@ GEM
xpath (~> 3.2)
chartkick (5.0.2)
coderay (1.1.3)
- commonmarker (0.23.9)
+ commonmarker (0.23.10)
concurrent-ruby (1.2.2)
connection_pool (2.4.1)
crack (0.4.5)
@@ -204,16 +204,16 @@ GEM
matrix (0.4.2)
memoist (0.16.2)
method_source (1.0.0)
- mini_mime (1.1.2)
+ mini_mime (1.1.5)
mini_portile2 (2.8.4)
mini_racer (0.8.0)
libv8-node (~> 18.16.0.0)
- minitest (5.18.1)
+ minitest (5.19.0)
msgpack (1.7.2)
multi_json (1.15.0)
multi_xml (0.6.0)
multipart-post (2.3.0)
- net-imap (0.3.6)
+ net-imap (0.3.7)
date
net-protocol
net-pop (0.1.2)
@@ -227,12 +227,12 @@ GEM
net-ssh (7.1.0)
newrelic_rpm (9.3.1)
nio4r (2.5.9)
- nokogiri (1.15.3)
+ nokogiri (1.15.4)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
- nokogiri (1.15.3-x86_64-darwin)
+ nokogiri (1.15.4-x86_64-darwin)
racc (~> 1.4)
- nokogiri (1.15.3-x86_64-linux)
+ nokogiri (1.15.4-x86_64-linux)
racc (~> 1.4)
oauth2 (2.0.9)
faraday (>= 0.17.3, < 3.0)
@@ -282,39 +282,39 @@ GEM
puma (6.3.0)
nio4r (~> 2.0)
racc (1.7.1)
- rack (2.2.7)
+ rack (2.2.8)
rack-protection (3.0.6)
rack
rack-test (2.1.0)
rack (>= 1.3)
- rails (7.0.6)
- actioncable (= 7.0.6)
- actionmailbox (= 7.0.6)
- actionmailer (= 7.0.6)
- actionpack (= 7.0.6)
- actiontext (= 7.0.6)
- actionview (= 7.0.6)
- activejob (= 7.0.6)
- activemodel (= 7.0.6)
- activerecord (= 7.0.6)
- activestorage (= 7.0.6)
- activesupport (= 7.0.6)
+ rails (7.0.7)
+ actioncable (= 7.0.7)
+ actionmailbox (= 7.0.7)
+ actionmailer (= 7.0.7)
+ actionpack (= 7.0.7)
+ actiontext (= 7.0.7)
+ actionview (= 7.0.7)
+ activejob (= 7.0.7)
+ activemodel (= 7.0.7)
+ activerecord (= 7.0.7)
+ activestorage (= 7.0.7)
+ activesupport (= 7.0.7)
bundler (>= 1.15.0)
- railties (= 7.0.6)
+ railties (= 7.0.7)
rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1)
actionview (>= 5.0.1.rc1)
activesupport (>= 5.0.1.rc1)
- rails-dom-testing (2.1.1)
+ rails-dom-testing (2.2.0)
activesupport (>= 5.0.0)
minitest
nokogiri (>= 1.6)
rails-html-sanitizer (1.6.0)
loofah (~> 2.21)
nokogiri (~> 1.14)
- railties (7.0.6)
- actionpack (= 7.0.6)
- activesupport (= 7.0.6)
+ railties (7.0.7)
+ actionpack (= 7.0.7)
+ activesupport (= 7.0.7)
method_source
rake (>= 12.2)
thor (~> 1.0)
@@ -423,12 +423,12 @@ GEM
hashdiff (>= 0.4.0, < 2.0.0)
webrick (1.8.1)
websocket (1.2.9)
- websocket-driver (0.7.5)
+ websocket-driver (0.7.6)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
xpath (3.2.0)
nokogiri (~> 1.8)
- zeitwerk (2.6.8)
+ zeitwerk (2.6.11)
PLATFORMS
ruby
@@ -441,7 +441,7 @@ DEPENDENCIES
bootsnap
capybara (~> 3.38)
chartkick
- commonmarker (~> 0.23.8)
+ commonmarker (~> 0.23.10)
dotenv (~> 2.8.1)
elasticsearch (< 7.14)
factory_bot_rails (~> 6.2.0)
@@ -464,7 +464,7 @@ DEPENDENCIES
pg (~> 1.4.6)
pry-byebug
puma
- rails (= 7.0.6)
+ rails (= 7.0.7)
rails-controller-testing (~> 1.0.5)
redis (~> 5.0)
responders
diff --git a/app/controllers/dispatch_controller.rb b/app/controllers/dispatch_controller.rb
index 03a72980e..c11d49289 100644
--- a/app/controllers/dispatch_controller.rb
+++ b/app/controllers/dispatch_controller.rb
@@ -145,4 +145,53 @@ def api_deposit
head :forbidden
end
end
+
+ def api_retract
+ if params[:secret] == ENV['BOT_SECRET']
+ 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 = {}
+ end
+
+ retraction_paper = Paper.new
+ 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.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?
+
+ 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
+ end
+ end
+
end
diff --git a/app/models/paper.rb b/app/models/paper.rb
index 7366c8e52..5dd0a1f78 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
@@ -62,6 +72,10 @@ class Paper < ApplicationRecord
event :withdraw do
transitions to: :withdrawn
end
+
+ event :retract do
+ transitions to: :retracted
+ end
end
VISIBLE_STATES = [
@@ -128,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
@@ -172,6 +186,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
@@ -296,8 +314,12 @@ def archive_doi_url
# A 5-figure integer used to produce the JOSS DOI
def joss_id
- id = "%05d" % review_issue_id
- "#{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/app/views/papers/_show_published.html.erb b/app/views/papers/_show_published.html.erb
index c400bcff5..fd89188f5 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" %>
@@ -22,58 +22,6 @@
-
-
+ <%= 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 @@
+
diff --git a/app/views/papers/show.json.jbuilder b/app/views/papers/show.json.jbuilder
index d216c1fb6..23403bf31 100644
--- a/app/views/papers/show.json.jbuilder
+++ b/app/views/papers/show.json.jbuilder
@@ -17,8 +17,8 @@ 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.tags @paper.author_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
json.pdf_url @paper.seo_pdf_url
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/config/routes.rb b/config/routes.rb
index 6eb1156b9..44da0e53d 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -67,9 +67,9 @@
get '/papers/:id/status.svg', to: "papers#status", format: "svg", as: 'status_badge'
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'
@@ -94,6 +94,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/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/docs/submitting.md b/docs/submitting.md
index 8a3f7209f..8d8954e87 100644
--- a/docs/submitting.md
+++ b/docs/submitting.md
@@ -108,7 +108,9 @@ As this short list shows, JOSS papers are only expected to contain a limited set
## Example paper and bibliography
-This example `paper.md` is adapted from _Gala: A Python package for galactic dynamics_ by Adrian M. Price-Whelan [http://doi.org/10.21105/joss.00388](http://doi.org/10.21105/joss.00388):
+This example `paper.md` is adapted from _Gala: A Python package for galactic dynamics_ by Adrian M. Price-Whelan [http://doi.org/10.21105/joss.00388](http://doi.org/10.21105/joss.00388).
+
+For a complete description of available options to describe author names [see here](https://github.com/openjournals/inara/blob/main/docs/names.md).
```
---
@@ -130,6 +132,10 @@ authors:
- name: Author with no affiliation
corresponding: true # (This is how to denote the corresponding author)
affiliation: 3
+ - given-names: Ludwig
+ dropping-particle: van
+ surname: Beethoven
+ affiliation: 3
affiliations:
- name: Lyman Spitzer, Jr. Fellow, Princeton University, USA
index: 1
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 3cc7d895a..fb2fee098 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
@@ -47,7 +47,7 @@
state { 'retracted' }
accepted_at { Time.now }
review_issue_id { 0 }
- sequence(:doi) {|n| "10.21105/jose.0000#{n}" }
+ sequence(:doi) {|n| "10.21105/jose.4000#{n}" }
end
factory :submitted_paper_with_sha do
diff --git a/spec/models/paper_spec.rb b/spec/models/paper_spec.rb
index 47137d7e3..906df57f7 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)
diff --git a/spec/system/papers/show_published_spec.rb b/spec/system/papers/show_published_spec.rb
new file mode 100644
index 000000000..2dd31d20f
--- /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/jose.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/jose.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/jose.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