diff --git a/Gemfile b/Gemfile index 50c257eb..60705c82 100644 --- a/Gemfile +++ b/Gemfile @@ -70,7 +70,8 @@ gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] # Custom gems: gem 'serrano', '~> 1.0' gem 'devise' -gem 'bootstrap', '~> 4.3' +gem 'bootstrap', '~> 4.6' +#gem 'dartsass-sprockets' gem 'jquery-rails' gem 'will_paginate' gem 'will_paginate-bootstrap4' diff --git a/Gemfile.lock b/Gemfile.lock index 598f333b..cca22657 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -61,7 +61,7 @@ GEM minitest (>= 5.1) tzinfo (~> 2.0) zeitwerk (~> 2.3) - addressable (2.8.5) + addressable (2.8.6) public_suffix (>= 2.0.2, < 6.0) afm (0.2.2) autoprefixer-rails (10.4.16.0) @@ -155,10 +155,10 @@ GEM mini_mime (1.1.5) minitest (5.20.0) multi_json (1.15.0) - net-ftp (0.3.0) + net-ftp (0.3.3) net-protocol time - net-imap (0.4.7) + net-imap (0.4.8) date net-protocol net-pop (0.1.2) @@ -329,7 +329,7 @@ PLATFORMS x86_64-linux DEPENDENCIES - bootstrap (~> 4.3) + bootstrap (~> 4.6) byebug capybara (~> 2.13) coffee-rails (~> 5.0) diff --git a/app/assets/stylesheets/custom.scss b/app/assets/stylesheets/custom.scss index 13bdc0c5..dd4959a3 100644 --- a/app/assets/stylesheets/custom.scss +++ b/app/assets/stylesheets/custom.scss @@ -29,6 +29,10 @@ } } +input[type=checkbox] { + accent-color: theme-color-level(primary, 0); +} + #dashboard-actions, .btn { .fa, .fas, .far, .fal, .fad, .fab { min-width: 1.5em; @@ -203,7 +207,7 @@ nav.navbar-dark { } } } - dd { + dd:not(.qc-body) { padding-left: 4rem; margin-left: 0.2rem; } @@ -215,3 +219,11 @@ nav.navbar-dark { } } +.workflow-panel { + .workflow-arrow { + position: absolute; + left: -1.1rem; + top: 0.25rem; + } +} + diff --git a/app/controllers/checks_controller.rb b/app/controllers/checks_controller.rb index e3072a3b..8992320b 100644 --- a/app/controllers/checks_controller.rb +++ b/app/controllers/checks_controller.rb @@ -1,6 +1,7 @@ class ChecksController < ApplicationController before_action(:authenticate_curator!) + # POST /checks/123 # POST /checks/123.json def update unless Name.exists?(params[:name_id].to_i) @@ -27,7 +28,12 @@ def update end if success - render(json: @check.to_json, status: :ok) + respond_to do |format| + format.html { redirect_to(@check.name) } + format.json do + render(json: @check.to_json, status: :ok) + end + end else render( json: @check.try(:errors) || { error: 'something went wrong' }, diff --git a/app/controllers/names_controller.rb b/app/controllers/names_controller.rb index b126fd71..ff5bf925 100644 --- a/app/controllers/names_controller.rb +++ b/app/controllers/names_controller.rb @@ -8,6 +8,7 @@ class NamesController < ApplicationController edit_rank edit_notes edit_etymology edit_links edit_type autofill_etymology link_parent link_parent_commit return validate endorse claim unclaim new_correspondence + observe unobserve ] ) before_action( @@ -28,6 +29,7 @@ class NamesController < ApplicationController return validate endorse ] ) + before_action(:authenticate_user!, only: %i[observe unobserve]) # GET /autocomplete_names.json?q=Maco # GET /autocomplete_names.json?q=Allo&rank=genus @@ -65,7 +67,7 @@ def index(opts = {}) Name.valid_status end - @names = + @names ||= case @sort when 'date' if opts[:status] == 15 @@ -81,7 +83,7 @@ def index(opts = {}) @sort = 'alphabetically' Name.order(name: :asc) end - @names = @names.where(status: opts[:status]) + @names = @names.where(status: opts[:status]) if opts[:status] @names = @names.where(opts[:where]) if opts[:where] @names = @names.paginate(page: params[:page], per_page: 30) @@ -104,9 +106,22 @@ def user_names if params[:user] && current_user.admin? user = User.find_by(username: params[:user]) end - @title = "Names by #{user.username}" + @title = "Names by #{user.username}" @status = 'user' - index(where: { created_by: user }, status: Name.status_hash.keys) + index(where: { created_by: user }) + render(:index) + end + + # GET /observing-names + def observing_names + user = current_user + if params[:user] && current_user.admin? + user = User.find_by(username: params[:user]) + end + @title = 'Names with active alerts' + @status = 'user' + @names = user.observing_names.reverse + index render(:index) end @@ -203,6 +218,7 @@ def create respond_to do |format| if @name.save + @name.add_observer(current_user) format.html { redirect_to @name, notice: 'Name was successfully created' } format.json { render :show, status: :created, location: @name } else @@ -346,6 +362,7 @@ def link_parent_commit end if ok && @name.update(par) + par[:parent].add_observer(current_user) flash[:notice] = 'Parent successfully updated' redirect_to(@name) else @@ -355,103 +372,41 @@ def link_parent_commit # POST /names/1/return def return - par = { status: 5 } - if !@name.after_submission? - flash[:alert] = 'Name status is incompatible with return' - elsif @name.update(par) - # Email notification - AdminMailer.with( - user: @name.created_by, - name: @name, - action: 'return' - ).name_status_email.deliver_later - flash[:notice] = 'Name returned to author' - else - flash[:alert] = 'An unexpected error occurred' - end - redirect_to(@name) + change_status(:return, 'Name returned to author', current_user) end # POST /names/1/validate def validate - if params[:code] == 'icnp' || params[:code] == 'icn' - par = { - status: params[:code] == 'icnp' ? 20 : 25, - validated_by: current_user, validated_at: Time.now - } - if @name.validated? - flash[:alert] = 'Name status is incompatible with validation' - elsif @name.update(par) - # Email notification - AdminMailer.with( - user: @name.created_by, - name: @name, - action: 'validate' - ).name_status_email.deliver_later - flash[:notice] = 'Name successfully validated' - else - flash[:alert] = 'An unexpected error occurred' - end - else - flash[:alert] = - 'Invalid procedure for nomenclatural code ' + params[:code] - end - redirect_to(@name) + change_status( + :validate, 'Name successfully validated', current_user, params[:code] + ) end # POST /names/1/endorse def endorse - par = { status: 12, endorsed_by: current_user, endorsed_at: Time.now } - if @name.after_endorsement? - flash[:alert] = 'Name status is incompatible with endorsement' - elsif @name.update(par) - # Email notification - AdminMailer.with( - user: @name.created_by, - name: @name, - action: 'endorse' - ).name_status_email.deliver_later - flash[:notice] = 'Name successfully endorsed' - else - flash[:alert] = 'An unexpected error occurred' - end - redirect_to(@name) + change_status(:endorse, 'Name successfully endorsed', current_user) end # POST /names/1/claim def claim - if !@name.can_claim?(current_user) - flash[:alert] = 'You cannot claim this name' - elsif @name.claim(current_user) - flash[:notice] = 'Name successfully claimed' - else - flash[:alert] = 'An unexpected error occurred' - end - redirect_to(@name) + change_status(:claim, 'Name successfully claimed', current_user) end # POST /names/1/unclaim def unclaim - par = { status: 0 } - if !@name.can_unclaim?(current_user) - flash[:alert] = 'You cannot unclaim this name' - elsif @name.unclaim(current_user) - flash[:notice] = 'Name successfully returned to the public pool' - else - flash[:alert] = 'An unexpected error occurred' - end - redirect_to(@name) + change_status(:unclaim, 'Name successfully claimed', current_user) end # POST /names/1/new_correspondence def new_correspondence @name_correspondence = NameCorrespondence.new( - params.require(:name_correspondence).permit(:message) + params.require(:name_correspondence).permit(:message, :notify) ) unless @name_correspondence.message.empty? @name_correspondence.user = current_user @name_correspondence.name = @name if @name_correspondence.save + @name.add_observer(current_user) flash[:notice] = 'Correspondence recorded' else flash[:alert] = 'An unexpected error occurred with the correspondence' @@ -460,6 +415,26 @@ def new_correspondence redirect_to(@tutorial || @name) end + # GET /names/1/observe + def observe + @name.add_observer(current_user) + if params[:from] && RedirectSafely.safe?(params[:from]) + redirect_to(params[:from]) + else + redirect_back(fallback_location: @name) + end + end + + # GET /names/1/unobserve + def unobserve + @name.observers.delete(current_user) + if params[:from] && RedirectSafely.safe?(params[:from]) + redirect_to(params[:from]) + else + redirect_back(fallback_location: @name) + end + end + private # Use callbacks to share common setup or constraints between actions @@ -510,4 +485,18 @@ def etymology_pars Name.etymology_fields.map { |j| :"etymology_#{i}_#{j}" } end.flatten end + + def change_status(fun, success_msg, *extra_opts) + if @name.send(fun, *extra_opts) + flash[:notice] = success_msg + else + flash[:alert] = @name.status_alert + end + redirect_to(@name) + rescue ActiveRecord::RecordInvalid => inv + flash['alert'] = + 'An unexpected error occurred while updating the name: ' + + inv.record.errors.map { |e| "#{e.attribute} #{e.message}" }.to_sentence + redirect_to(inv.record) + end end diff --git a/app/controllers/registers_controller.rb b/app/controllers/registers_controller.rb index 611a367e..7796fbe0 100644 --- a/app/controllers/registers_controller.rb +++ b/app/controllers/registers_controller.rb @@ -6,6 +6,7 @@ class RegistersController < ApplicationController submit return return_commit endorse notification notify validate publish new_correspondence internal_notes nomenclature_review genomics_review + observe unobserve ] ) before_action(:set_name, only: %i[new create]) @@ -31,6 +32,7 @@ class RegistersController < ApplicationController :authenticate_can_edit!, only: %i[edit update destroy submit notification notify new_correspondence] ) + before_action(:authenticate_user!, only: %i[observe unobserve]) # GET /registers or /registers.json def index(status = :validated) @@ -51,6 +53,16 @@ def index(status = :validated) else current_user.registers end + when :alerts + authenticate_curator! && return + if params[:user] + authenticate_curator! && return + user = User.find_by(username: params[:user]) + @extra_title = "by #{user.display_name}" + user.observing_registers + else + current_user.observing_registers + end when :draft authenticate_curator! && return Register.where(validated: false, notified: false, submitted: false) @@ -100,6 +112,7 @@ def create if @register.can_edit?(current_user) && @register.save && (!@name || @name.add_to_register(@register, current_user)) && (!@tutorial || @tutorial.add_to_register(@register, current_user)) + @register.add_observer(current_user) flash[:notice] = 'Register was successfully created' if @tutorial flash[:notice] += '. Remember to submit register list for evaluation' @@ -138,19 +151,9 @@ def destroy # POST /registers/r:abcd/submit def submit - ActiveRecord::Base.transaction do - par = { status: 10, submitted_at: Time.now, submitted_by: current_user } - @register.names.each do |name| - if name.after_submission? - flash[:alert] = 'Some names in the list have already been submitted' - end - name.update!(par) - end - @register.update!(submitted: true, submitted_at: Time.now) - flash[:notice] = 'Register list successfully submitted for review' - end - - redirect_to @register + change_status( + :submit, 'Register list successfully submitted for review', current_user + ) end # GET /registers/r:abcd/return @@ -162,41 +165,17 @@ def return # POST /registers/r:abcd/return def return_commit - ActiveRecord::Base.transaction do - par = { status: 5 } - @register.names.each { |name| name.update!(par) unless name.validated? } - @register.update!( - submitted: false, notified: false, notes: params[:register][:notes] - ) - end - - # Notify submitter - AdminMailer.with( - user: @register.user, - register: @register, - action: 'return' - ).register_status_email.deliver_later - - redirect_to(@register) + change_status( + :return, 'Register list returned to authors', + current_user, params[:register][:notes] + ) end # POST /registers/r:abcd/endorse def endorse - ActiveRecord::Base.transaction do - par = { status: 12, endorsed_by: current_user, endorsed_at: Time.now } - @register.names.each do |name| - name.update!(par) unless name.after_endorsement? - end - end - - # Notify submitter - AdminMailer.with( - user: @register.user, - register: @register, - action: 'endorse' - ).register_status_email.deliver_later - - redirect_to(@register) + change_status( + :endorse, 'Register list has been endorsed', current_user + ) end # GET /registers/r:abc/notify @@ -207,44 +186,14 @@ def notification # POST /registers/r:abc/notify def notify - par = register_notify_params.merge(notified: true, notified_at: Time.now) - @register.title = par[:title] - @register.abstract = par[:abstract] - - all_ok = false - publication = Publication.by_doi(params[:doi]) - par[:publication] = publication - if publication.new_record? - @register.errors.add(:doi, publication.errors[:doi].join('; ')) - elsif !par[:publication_pdf] && !@register.publication_pdf.attached? - @register.errors.add(:publication_pdf, 'cannot be empty') - else - par[:publication] = publication - ActiveRecord::Base.transaction do - @register.names.each do |name| - unless name.after_endorsement? - flash[:warning] = 'Some names in the list have not been endorsed ' \ - 'yet and will require expert review, which could delay validation' - name.status = 10 - name.submitted_at = Time.now - name.submitted_by = current_user - end - - unless name.publications.include? publication - name.publications << publication - end - name.proposed_by ||= publication - name.save! - end - all_ok = @register.update(par) - end - end - - if all_ok - HeavyMethodJob.perform_later(:automated_validation, @register) + # Note that +notify+ handles errors differently, and is incompatible with + # the standard +change_status+ call used in all other status changes + if @register.notify(current_user, register_notify_params, params[:doi]) flash[:notice] = 'The list has been successfully submitted for validation' redirect_to(@register) else + @register.title = par[:title] + @register.abstract = par[:abstract] flash[:alert] = 'Please review the errors below' notification render(:notification) @@ -253,29 +202,14 @@ def notify # POST /registers/r:abc/validate def validate - success = true - - @register.validate!(current_user) - flash['notice'] = 'Successfully validated the register list' - - # Notify submitter - AdminMailer.with( - user: @register.user, - register: @register, - action: 'validate' - ).register_status_email.deliver_later - - redirect_to(@register) - rescue ActiveRecord::RecordInvalid => inv - flash['alert'] = - 'An unexpected error occurred while validating the list: ' + - inv.record.errors.map { |e| "#{e.attribute} #{e.message}" }.to_sentence - redirect_to(inv.record) + change_status( + :validate, 'Successfully validated the register list', current_user + ) end # POST /registers/r:abc/publish def publish - # TODO See Register#post_validation + # TODO See Register::Status#post_validation end # GET /registers/r:abc/table @@ -318,18 +252,39 @@ def cite # POST /registers/r:abc/new_correspondence def new_correspondence @register_correspondence = RegisterCorrespondence.new( - params.require(:register_correspondence).permit(:message) + params.require(:register_correspondence).permit(:message, :notify) ) unless @register_correspondence.message.empty? @register_correspondence.user = current_user @register_correspondence.register = @register if @register_correspondence.save + @register.add_observer(current_user) flash[:notice] = 'Correspondence recorded' else flash[:alert] = 'An unexpected error occurred with the correspondence' end end - redirect_to @register + redirect_to(@register) + end + + # GET /register/1/observe + def observe + @register.add_observer(current_user) + if params[:from] && RedirectSafely.safe?(params[:from]) + redirect_to(params[:from]) + else + redirect_back(fallback_location: @register) + end + end + + # GET /register/1/unobserve + def unobserve + @register.observers.delete(current_user) + if params[:from] && RedirectSafely.safe?(params[:from]) + redirect_to(params[:from]) + else + redirect_back(fallback_location: @register) + end end # POST /registers/r:abc/internal_notes @@ -408,4 +363,18 @@ def authenticate_can_edit! def ensure_valid! @register&.validated? end + + def change_status(fun, success_msg, *extra_opts) + if @register.send(fun, *extra_opts) + flash[:notice] = success_msg + else + flash[:alert] = @register.status_alert + end + redirect_to(@register) + rescue ActiveRecord::RecordInvalid => inv + flash['alert'] = + 'An unexpected error occurred while updating the list: ' + + inv.record.errors.map { |e| "#{e.attribute} #{e.message}" }.to_sentence + redirect_to(inv.record) + end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index fe0f910b..87680502 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -14,6 +14,12 @@ def download_link(file, name = nil) end end + def time_ago_with_date(date) + content_tag(:u, class: 'hover-help', title: date.to_s) do + time_ago_in_words(date) + ' ago' + end + end + def pager(object) will_paginate( object, @@ -131,7 +137,7 @@ def adaptable_value(entry, name) def modal(title, opts = {}) @modals ||= [] - id = opts[:id] || "modal-#{@modals.size}" + id = opts[:id] || "modal-#{SecureRandom.uuid}" @modals << content_tag( :div, id: id, class: 'modal fade', tabindex: '-1', role: 'dialog' @@ -165,6 +171,7 @@ def modal_button(id, opts = {}) opts[:data][:toggle] = 'modal' opts[:data][:target] = "##{id}" opts[:tag] ||= :span + opts.merge!(type: '', class: '', tag: :a, href: '#') if opts[:as_anchor] content_tag(opts.delete(:tag), opts) { yield } end @@ -187,24 +194,30 @@ def download_buttons(list) end end - def download_button(url, icon, text) - link_to(url, class: 'btn btn-light btn-sm text-muted') do + def download_button(url, icon, text, opts = {}) + opts[:color] ||= 'light' + opts[:class] ||= '' + opts[:class] += " btn btn-#{opts[:color]} btn-sm" + opts[:class] += ' text-muted' if opts[:color] == 'light' + link_to(url, opts) do fa_icon(icon) + text end end + def display_obj(obj) + preferred_fields = %i[name_html name display_name accession citation] + field = preferred_fields.find { |i| obj.respond_to? i } + if field + obj.send(field) + elsif obj.respond_to? :id + '%s %i' % [obj.class, obj.id] + else + obj.to_s + end + end + def display_link(obj) - field = - %i[name_html name accession citation].find { |i| obj.respond_to? i } - display = - if field - obj.send(field) - elsif obj.respond_to? :id - obj.class.to_s + ' ' + obj.id - else - obj.to_s - end - link_to(display, obj) + link_to(display_obj(obj), obj) end end diff --git a/app/mailers/admin_mailer.rb b/app/mailers/admin_mailer.rb index 3c8f3dd1..32b2f0c5 100644 --- a/app/mailers/admin_mailer.rb +++ b/app/mailers/admin_mailer.rb @@ -6,7 +6,7 @@ class AdminMailer < ApplicationMailer ## # Email notifying of a user status change def user_status_email - return unless @user.opt_regular_email + return unless @user.opt_regular_email? @params = params @action = @@ -27,21 +27,39 @@ def user_status_email ## # Email notifying of a name status change def name_status_email - return unless @user.opt_regular_email + return unless @user.opt_regular_email? - @name = params[:name] + @name = params[:name] || params[:object] mail(subject: 'New name status in SeqCode Registry') end ## # Email notifying of a register list status change def register_status_email - return unless @user.opt_regular_email + return unless @user.opt_regular_email? - @register = params[:register] + @register = params[:register] || params[:object] mail(subject: 'New register list status in SeqCode Registry') end + ## + # Email notifying observers of a new correspondence + def correspondence_email + return unless @user.opt_message_email? + + @object = params[:object] + mail(subject: 'New correspondence message in SeqCode Registry') + end + + ## + # Email notifying observers of a new status change + def observer_status_email + return unless @user.opt_regular_email? + + @object = params[:object] + mail(subject: 'New status update in SeqCode Registry') + end + ## # Periodic reminder for contributors sent by +ReminderMail.register_reminder+ def register_reminder_email diff --git a/app/models/has_observers.rb b/app/models/has_observers.rb new file mode 100644 index 00000000..a467c1c1 --- /dev/null +++ b/app/models/has_observers.rb @@ -0,0 +1,71 @@ +module HasObservers + + # ============ --- EMAIL NOTIFICATIONS --- ============ + + def notify_user(action, user) + am = AdminMailer.with( + user: user, + object: self, + action: action.to_s + ) + + case action.to_sym + when :correspondence + # For observers + am.correspondence_email.deliver_later + when :status + # For observers + am.observer_status_email.deliver_later + else + if is_a? Name + # All other name status changes + am.name_status_email.deliver_later + elsif is_a? Register + # All other register status changes + am.register_status_email.deliver_later + else + raise 'Unknown class for observer notifications' + end + end + end + + ## + # Notify observers of a change via email + # + # action: One of +:status+ or +:correspondence+ + # options: + # - exclude_creator: Exclude creator from the list of observers notified + # - exclude_users: Array of users to exclude from notified observers + def notify_observers(action, exclude_creator: false, exclude_users: []) + observers.each do |user| + next if exclude_users.include?(user) + next if exclude_creator && user == created_by + + notify_user(action, user) + end + end + + def notify_status_change(action, user) + notify_user(action, created_by) + notify_observers( + :status, exclude_creator: true, exclude_users: [user] + ) + true + end + + # ============ --- HELPER FUNCTIONS FOR STATUS UPDATES --- ============ + + def assert_status_with_alert(test, action) + return true if test + + @status_alert = 'Status is incompatible with ' + action + false + end + + def update_status_with_alert(par) + return true if update(par) + + @status_alert = 'An unexpected error occurred' + false + end +end diff --git a/app/models/name.rb b/app/models/name.rb index 62336170..87886f02 100644 --- a/app/models/name.rb +++ b/app/models/name.rb @@ -20,6 +20,8 @@ class Name < ApplicationRecord :child_placements, class_name: 'Name', foreign_key: 'parent_id', dependent: :destroy ) + has_many(:observe_names, dependent: :destroy) + has_many(:observers, through: :observe_names, source: :user) belongs_to( :proposed_by, optional: true, @@ -82,6 +84,8 @@ class Name < ApplicationRecord } ) + include HasObservers + include Name::Status include Name::QualityChecks include Name::Etymology include Name::Citations @@ -232,7 +236,20 @@ def type_material_name(type) end end + # ============ --- STATUS --- ============ + + def status_hash + self.class.status_hash[status] + end + + Name.status_hash.each do |k, v| + define_method("#{v[:symbol]}?") do + status == k + end + end + # ============ --- NOMENCLATURE --- ============ + def sanitize(str) ActionView::Base.full_sanitizer.sanitize(str) end @@ -326,6 +343,10 @@ def formal_txt sanitize(formal_html.gsub(/̶[01];/, "'")) end + def display(html: true) + html ? name_html : name + end + def rank_suffix self.class.rank_suffixes[inferred_rank.to_s.to_sym] end @@ -371,66 +392,6 @@ def citations ].flatten.compact.uniq end - # ============ --- STATUS --- ============ - - def status_hash - self.class.status_hash[status] - end - - def status_name - status_hash[:name] - end - - def status_help - status_hash[:help].gsub(/\n/, ' ') - end - - def status_symbol - status_hash[:symbol] - end - - def validated? - status_hash[:valid] - end - - def public? - status_hash[:public] - end - - def after_claim? - status >= 5 - end - - def after_register? - register.present? || after_submission? - end - - def after_submission? - status >= 10 - end - - def after_endorsement? - status >= 12 - end - - def after_notification? - validated? || register.try(:notified?) - end - - def after_validation? - valid? - end - - def after_register_publication? - register.try(:published?) - end - - Name.status_hash.each do |k, v| - define_method("#{v[:symbol]}?") do - status == k - end - end - # ============ --- OUTLINKS --- ============ def ncbi_search_url @@ -528,41 +489,11 @@ def claimed?(user) draft? && user?(user) end - def claim(user) - raise('User cannot claim name') unless can_claim?(user) - par = { created_by: user, created_at: Time.now } - par[:status] = 5 if auto? - return false unless update(par) - - # Email notification - AdminMailer.with( - user: user, - name: self, - action: 'claim' - ).name_status_email.deliver_later - - true - end - def can_unclaim?(user) curator_or_owner = user.try(:curator?) || self.user?(user) curator_or_owner && draft? end - def unclaim(user) - raise('User cannot unclaim name') unless can_unclaim?(user) - return false unless update(status: 0) - - # Email notification - AdminMailer.with( - user: created_by, - name: self, - action: 'unclaim' - ).name_status_email.deliver_later - - true - end - def correspondence_by?(user) return false unless user @@ -581,6 +512,30 @@ def curators @curators ||= (check_users + reviewers).uniq end + def corresponding_users + correspondences.map(&:user).uniq + end + + def associated_users + ( + [created_by, validated_by, submitted_by, endorsed_by] + + corresponding_users + ).compact.uniq + end + + def observing?(user) + observe_names.where(user: user).present? + end + + ## + # Attempts to add an observer while silently ignoring it if the user + # already observes the name + def add_observer(user) + self.observers << user + rescue ActiveRecord::RecordNotUnique + true + end + # ============ --- TAXONOMY --- ============ def top_rank? diff --git a/app/models/name/external_resources.rb b/app/models/name/external_resources.rb index 52bf1f41..a199603d 100644 --- a/app/models/name/external_resources.rb +++ b/app/models/name/external_resources.rb @@ -13,6 +13,8 @@ def queued_for_external_resources ## # Queue name for +NameExternalResourcesJob+ def queue_for_external_resources + return if Rails.configuration.bypass_external_apis + unless queued_for_external_resources NameExternalResourcesJob.perform_later(self) update_column(:queued_external, DateTime.now) @@ -23,6 +25,8 @@ def queue_for_external_resources # Generate a request to the external +uri+, and return the reponse body # if successful or +nil+ otherwise (fails silently) def external_request(uri) + return if Rails.configuration.bypass_external_apis + require 'uri' require 'net/http' diff --git a/app/models/name/quality_checks.rb b/app/models/name/quality_checks.rb index 79951a13..6a2dfca0 100644 --- a/app/models/name/quality_checks.rb +++ b/app/models/name/quality_checks.rb @@ -637,7 +637,11 @@ def fail end def check - name.check(type) if checklist + name.check(type) + end + + def bypassed? + !checklist && check&.pass? end def to_hash @@ -662,12 +666,17 @@ def initialize(name) @name = name @set_h = {} @checks_h = {} + @bypassed_h = {} end def add(type, opts = {}) qc = QcWarning.new(type, opts.merge(name: name)) @checks_h[qc.type] = qc if qc.checklist - @set_h[qc.type] = qc if !qc.checklist || (qc.check && !qc.check.pass?) + if (!qc.checklist && !qc.check) || (qc.check && qc.check.fail?) + @set_h[qc.type] = qc + elsif qc.bypassed? + @bypassed_h[qc.type] = qc + end end def set @@ -682,6 +691,10 @@ def checked_checks @checks_h.values.select(&:check) end + def bypassed + @bypassed_h.values + end + def resort! new_set_h = @set_h new_checks_h = @checks_h diff --git a/app/models/name/status.rb b/app/models/name/status.rb new file mode 100644 index 00000000..c47a4edb --- /dev/null +++ b/app/models/name/status.rb @@ -0,0 +1,133 @@ +module Name::Status + # ============ --- GENERIC STATUS --- ============ + + attr_accessor :status_alert + + def status_name + status_hash[:name] + end + + def status_help + status_hash[:help].gsub(/\n/, ' ') + end + + def status_symbol + status_hash[:symbol] + end + + def validated? + status_hash[:valid] + end + + def public? + status_hash[:public] + end + + def after_claim? + status >= 5 + end + + def after_register? + register.present? || after_submission? + end + + def after_submission? + status >= 10 + end + + def after_endorsement? + status >= 12 + end + + def after_notification? + validated? || register.try(:notified?) + end + + def after_validation? + valid? + end + + def after_register_publication? + register.try(:published?) + end + + # ============ --- CHANGE STATUS --- ============ + + ## + # Return the name back to the authors + # + # user: The user returning the name (the current user) + def return(user) + assert_status_with_alert(after_submission?, 'return') or return false + update_status_with_alert(status: 5) or return false + notify_status_change(:return, user) + end + + ## + # Validate the name + # + # user: The user validating the name (the current user) + # code: Once of 'icnp' or 'icn'. Validation under SeqCode can only + # be done through a register list + def validate(user, code) + if !%w[icnp icn].include?(code) + @status_alert = 'Invalid procedure for nomenclatural code ' + code + return false + end + + assert_status_with_alert(!validated?, 'validation') or return false + update_status_with_alert( + status: code == 'icnp' ? 20 : 25, + validated_by: user, validated_at: Time.now + ) or return false + notify_status_change(:validate, user) + end + + ## + # Endorse the name for future publication + # + # user: The user endorsing the name (the current user) + def endorse(user) + assert_status_with_alert(!after_endorsement?, 'endorsement') or return false + update_status_with_alert( + status: 12, endorsed_by: user, endorsed_at: Time.now + ) or return false + notify_status_change(:endorse, user) + end + + ## + # Claim the name + # + # user: The user claiming the name (usually the current user) + def claim(user) + if !can_claim?(user) + @status_alert = 'User cannot claim name' + return false + end + + par = { created_by: user, created_at: Time.now } + par[:status] = 5 if auto? + update_status_with_alert(par) or return false + + add_observer(user) + notify_user(:claim, user) + notify_observers(:status, exclude_users: [user]) + true + end + + ## + # Unclaim the name + # + # user: The user marking the name as unclaimed (the current user) + def unclaim(user) + if !can_unclaim?(user) + @status_alert = 'User cannot unclaim name' + return false + end + + update_status_with_alert(status: 0) or return false + notify_user(:unclaim, user) + notify_observers(:status, exclude_users: [user]) + true + end +end diff --git a/app/models/name_correspondence.rb b/app/models/name_correspondence.rb index 2f010b7f..1d097212 100644 --- a/app/models/name_correspondence.rb +++ b/app/models/name_correspondence.rb @@ -2,4 +2,11 @@ class NameCorrespondence < ApplicationRecord belongs_to(:name) belongs_to(:user) has_rich_text(:message) + attr_accessor :notify + after_create(:notify_observers) + + def notify_observers + return unless notify == '1' + name.notify_observers(:correspondence, exclude_users: [user]) + end end diff --git a/app/models/observe_name.rb b/app/models/observe_name.rb new file mode 100644 index 00000000..04b61695 --- /dev/null +++ b/app/models/observe_name.rb @@ -0,0 +1,4 @@ +class ObserveName < ApplicationRecord + belongs_to :user + belongs_to :name +end diff --git a/app/models/observe_register.rb b/app/models/observe_register.rb new file mode 100644 index 00000000..5669cd8b --- /dev/null +++ b/app/models/observe_register.rb @@ -0,0 +1,4 @@ +class ObserveRegister < ApplicationRecord + belongs_to :user + belongs_to :register +end diff --git a/app/models/register.rb b/app/models/register.rb index 8fa695e3..c0872d3b 100644 --- a/app/models/register.rb +++ b/app/models/register.rb @@ -15,6 +15,9 @@ class Register < ApplicationRecord has_many(:checks, through: :names) has_many(:check_users, -> { distinct }, through: :checks, source: :user) alias :correspondences :register_correspondences + alias :created_by :user + has_many(:observe_registers, dependent: :destroy) + has_many(:observers, through: :observe_registers, source: :user) has_rich_text(:notes) has_rich_text(:abstract) has_rich_text(:submitter_authorship_explanation) @@ -27,6 +30,9 @@ class Register < ApplicationRecord validates(:title, presence: true, if: :validated?) validate(:title_different_from_effective_publication) + include HasObservers + include Register::Status + attr_accessor :modal_form_id def to_param @@ -70,25 +76,6 @@ def acc_url(protocol = false) "#{'https://' if protocol}seqco.de/#{accession}" end - def status_name - validated? ? 'validated' : - notified? ? 'notified' : - endorsed? ? 'endorsed' : - submitted? ? 'submitted' : 'draft' - end - - def before_notification? - !validated? && !notified? - end - - def endorsed? - submitted? && all_endorsed? - end - - def draft? - status_name == 'draft' - end - def names_by_rank names.sort do |a, b| Name.ranks.index(a.rank) <=> Name.ranks.index(b.rank) @@ -132,6 +119,10 @@ def can_view_publication?(user) user.curator? || user.id == user_id end + def display + 'Register List %s' % accession + end + def proposing_publications @proposing_publications ||= Publication.where(id: names.pluck(:proposed_by)) @@ -223,83 +214,6 @@ def citations ([publication] + sorted_names.map(&:citations).flatten).compact.uniq end - ## - # Automated checks to prepare for validation, adding relevant notes - # to the list - def automated_validation - # Trivial cases (not-yet-notified or already validated) - return false unless notified? - return true if validated? - - # Minimum requirements - success = true - unless publication && publication_pdf.attached? - add_note('Missing publication or PDF files') - success = false - end - - # Check that all names have been endorsed - unless names.all?(&:after_endorsement?) - add_note('Some names have not been endorsed yet') - success = false - end - - # Check if the list has a PDF that includes the accession - has_acc = false - bnames = Hash[names.map { |n| [n.base_name, false] }] - cnames = Hash[names.map { |n| [n.base_name, n.corrigendum_from] }] - [publication_pdf, supplementary_pdf].each do |as| - break if has_acc && bnames.values.all? - next unless as.attached? - - as.open do |file| - render = PDF::Reader.new(file.path) - render.pages.each do |page| - txt = page.text - has_acc = true if txt.index(accession) - bnames.each_key do |bn| - if txt.index(bn) || (cnames[bn] && txt.index(cnames[bn])) - bnames[bn] = true - end - end - break if has_acc && bnames.values.all? - end - end - end - - if has_acc - add_note('The effective publication includes the SeqCode accession') - else - add_note( - 'The effective publication does not include the accession ' \ - '(SeqCode, Rule 26, Note 2)' - ) - end - - if bnames.values.all? - add_note('The effective publication mentions all names in the list') - elsif bnames.values.any? - if bnames.values.count(&:!) > 5 - add_note( - "The effective publication mentions" \ - " #{bnames.values.count(&:itself)} out of" \ - " #{bnames.count} names in the list" - ) - else - add_note( - "The effective publication mentions some names in the list," \ - " but not: #{bnames.select { |_, v| !v }.keys.join(', ')}" - ) - end - else - add_note( - 'The effective publication does not mention any names in the list' - ) - end - - save - end - def add_note(note, title = 'Auto-check') self.notes.body = <<~TXT #{notes.body} @@ -308,29 +222,6 @@ def add_note(note, title = 'Auto-check') TXT end - def validate!(user) - ActiveRecord::Base.transaction do - par = { validated_by: user, validated_at: Time.now } - names.each { |name| name.update!(par.merge(status: 15)) } - update!(par.merge(notes: nil, validated: true)) - end - - HeavyMethodJob.perform_later(:post_validation, @register) - true - end - - ## - # Production tasks to be executed once a list is validated - def post_validation - # TODO Produce and attach the certificate in PDF - # TODO Distribute the certificate to mirrors - # TODO Notify submitter - end - - def all_endorsed? - names.all?(&:after_endorsement?) - end - def reviewer_ids @reviewer_ids ||= names.pluck(:validated_by, :endorsed_by, :nomenclature_reviewer) @@ -345,6 +236,30 @@ def curators @curators ||= (check_users + reviewers).uniq end + def observing?(user) + observe_registers.where(user: user).present? + end + + ## + # Attempts to add an observer while silently ignoring it if the user + # already observes the register + def add_observer(user) + self.observers << user + rescue ActiveRecord::RecordNotUnique + true + end + + def corresponding_users + correspondences.map(&:user).uniq + end + + def associated_users + ( + [user, validated_by, published_by] + + corresponding_users + ).compact.uniq + end + private def assign_accession diff --git a/app/models/register/status.rb b/app/models/register/status.rb new file mode 100644 index 00000000..4e147c81 --- /dev/null +++ b/app/models/register/status.rb @@ -0,0 +1,263 @@ +module Register::Status + # ============ --- GENERIC STATUS --- ============ + + attr_accessor :status_alert + + def status_name + validated? ? 'validated' : + notified? ? 'notified' : + endorsed? ? 'endorsed' : + submitted? ? 'submitted' : 'draft' + end + + def status_help_hash + { + draft: + 'This is a draft register list, currently in preparation', + submitted: + 'This register list is currently being evaluated by expert curators ' \ + 'or awaiting effective publication', + endorsed: + 'All the names in this register list have been endorsed by expert ' \ + 'curators, and the SeqCode is awaiting notification of publication', + notified: + 'The SeqCode has been notified of effective publication and the ' \ + 'request is currently being evaluated by expert curators', + validated: + 'All names in this register list have been validly published ' \ + 'with a registered effective publication' + } + end + + def status_help + status_help_hash[status_name.to_sym] + end + + def in_curation? + notified? || endorsed? || submitted? + end + + def before_notification? + !validated? && !notified? + end + + def endorsed? + submitted? && all_endorsed? + end + + def draft? + status_name == 'draft' + end + + def public? + can_view? nil + end + + def all_endorsed? + names.all?(&:after_endorsement?) + end + + # ============ --- CHANGE STATUS --- ============ + + ## + # Submit the list for evaluation + # + # user: The user submitting the list (the current user) + def submit(user) + assert_status_with_alert(draft?, 'submit') or return false + ActiveRecord::Base.transaction do + par = { status: 10, submitted_at: Time.now, submitted_by: user } + names.each do |name| + name.update!(par) + end + update_status_with_alert( + submitted: true, submitted_at: Time.now + ) or return false + end + notify_status_change(:submit, user) + end + + ## + # Return the register (and all associated names) to the authors + # + # user: The user returning the liat (the current user, a curator) + # notes: Notes to add to the list, overwritting any old notes + def return(user, notes) + assert_status_with_alert(in_curation?, 'return') or return false + ActiveRecord::Base.transaction do + names.each { |name| name.update!(status: 5) unless name.validated? } + update_status_with_alert( + submitted: false, notified: false, notes: notes + ) or return false + end + notify_status_change(:return, user) + end + + ## + # Endorse the register (and all associated names) + # + # user: The user endorsing the list (the current user, a curator) + def endorse(user) + ActiveRecord::Base.transaction do + par = { status: 12, endorsed_by: user, endorsed_at: Time.now } + names.each { |name| name.update!(par) unless name.after_endorsement? } + end + notify_status_change(:endorse, user) + end + + ## + # Notify the Registry of publication + # + # user: The user notifying the Registry (the current user) + # params: The notification parameters + # doi: DOI of the effective publication + def notify(user, params, doi) + publication = Publication.by_doi(doi) + params[:publication] = publication + + if publication.new_record? + errors.add(:doi, publication.errors[:doi].join('; ')) + return false + end + + if !params[:publication_pdf] && !publication_pdf.attached? + errors.add(:publication_pdf, 'cannot be empty') + return false + end + + ActiveRecord::Base.transaction do + names.each do |name| + unless name.after_endorsement? + name.status = 10 + name.submitted_at = Time.now + name.submitted_by = user + end + unless name.publications.include? publication + name.publications << publication + end + name.proposed_by = publication + name.save! + end + update_status_with_alert( + params.merge(notified: true, notified_at: Time.now) + ) or return false + end + + HeavyMethodJob.perform_later(:automated_validation, self) + notify_status_change(:notify, user) + end + + ## + # Validate the register list and all associated names + # + # user: The user validating the list (the current user, a curator) + def validate(user) + ActiveRecord::Base.transaction do + par = { validated_by: user, validated_at: Time.now } + names.each { |name| name.update!(par.merge(status: 15)) } + update!(par.merge(notes: nil, validated: true)) + end + + HeavyMethodJob.perform_later(:post_validation, @register) + notify_status_change(:validate, user) + end + + # ============ --- TASKS ASSOCIATED TO STATUS CHANGE --- ============ + + ## + # Production tasks to be executed once a list is validated + def post_validation + # TODO Produce and attach the certificate in PDF + # TODO Distribute the certificate to mirrors + # TODO Notify submitter + end + + ## + # Automated checks to prepare for validation, adding relevant notes + # to the list + def automated_validation + # Trivial cases (not-yet-notified or already validated) + return false unless notified? + return true if validated? + + # Minimum requirements + success = true + unless publication && publication_pdf.attached? + add_note('Missing publication or PDF files') + success = false + end + + # Check that all names have been endorsed + unless names.all?(&:after_endorsement?) + add_note('Some names have not been endorsed yet') + success = false + end + + success = check_pdf_files && success + save && success + end + + ## + # Check if the PDF file(s) include accession and all list names, and report + # results as register list notes + # + # Returns boolean, with true indicating all checks passed and false otherwise + # + # IMPORTANT: Notes are soft-registered, remember to +save+ to make them + # persistent + def check_pdf_files + has_acc = false + bnames = Hash[names.map { |n| [n.base_name, false] }] + cnames = Hash[names.map { |n| [n.base_name, n.corrigendum_from] }] + [publication_pdf, supplementary_pdf].each do |as| + break if has_acc && bnames.values.all? + next unless as.attached? + + as.open do |file| + render = PDF::Reader.new(file.path) + render.pages.each do |page| + txt = page.text + has_acc = true if txt.index(accession) + bnames.each_key do |bn| + if txt.index(bn) || (cnames[bn] && txt.index(cnames[bn])) + bnames[bn] = true + end + end + break if has_acc && bnames.values.all? + end + end + end + + if has_acc + add_note('The effective publication includes the SeqCode accession') + else + add_note( + 'The effective publication does not include the accession ' \ + '(SeqCode, Rule 26, Note 2)' + ) + end + + if bnames.values.all? + add_note('The effective publication mentions all names in the list') + elsif bnames.values.any? + if bnames.values.count(&:!) > 5 + add_note( + "The effective publication mentions" \ + " #{bnames.values.count(&:itself)} out of" \ + " #{bnames.count} names in the list" + ) + else + add_note( + "The effective publication mentions some names in the list," \ + " but not: #{bnames.select { |_, v| !v }.keys.join(', ')}" + ) + end + else + add_note( + 'The effective publication does not mention any names in the list' + ) + end + + has_acc && bnames.values.all? + end +end diff --git a/app/models/register_correspondence.rb b/app/models/register_correspondence.rb index 6a5c663c..fcaad450 100644 --- a/app/models/register_correspondence.rb +++ b/app/models/register_correspondence.rb @@ -2,4 +2,11 @@ class RegisterCorrespondence < ApplicationRecord belongs_to(:register) belongs_to(:user) has_rich_text(:message) + attr_accessor :notify + after_create(:notify_observers) + + def notify_observers + return unless notify == '1' + register.notify_observers(:correspondence, exclude_users: [user]) + end end diff --git a/app/models/user.rb b/app/models/user.rb index 2ea49069..100a8836 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -19,6 +19,10 @@ class User < ApplicationRecord has_many(:tutorials, dependent: :nullify) has_many(:checks, dependent: :nullify) has_many(:checked_names, -> { distinct }, through: :checks, source: :name) + has_many(:observe_names, dependent: :destroy) + has_many(:observing_names, through: :observe_names, source: :name) + has_many(:observe_registers, dependent: :destroy) + has_many(:observing_registers, through: :observe_registers, source: :register) validates( :username, diff --git a/app/views/admin_mailer/correspondence_email.html.erb b/app/views/admin_mailer/correspondence_email.html.erb new file mode 100644 index 00000000..fb57becd --- /dev/null +++ b/app/views/admin_mailer/correspondence_email.html.erb @@ -0,0 +1,22 @@ +
There is a new message in correspondence of +<%= link_to(@object.display, + polymorphic_url(@object, anchor: 'correspondence')) %>.
++ You have active notifications for + <%= link_to(@object.display, + polymorphic_url(@object, anchor: 'correspondence')) %> + and somebody has posted a message in correspondence with curators. +
++ If you are actively interested in this entry, open the link above and see the + messages in "Correspondence with curators". Otherwise, + <%= link_to('click here to stop notifications for this entry', + [@object, :unobserve]) %>. +
+ diff --git a/app/views/admin_mailer/correspondence_email.text.erb b/app/views/admin_mailer/correspondence_email.text.erb new file mode 100644 index 00000000..7ac971bf --- /dev/null +++ b/app/views/admin_mailer/correspondence_email.text.erb @@ -0,0 +1,20 @@ +There is a new message in correspondence of <%= @object.display %> + +What does this mean? +==================== + +You have active notifications for <%= @object.display %> and somebody +has posted a message in correspondence with curators. + +To view the entry in the Registry, visit: +<%= polymorphic_url(@object, anchor: 'correspondence') %> + +What to do next? +================ + +If you are actively interested in this entry, open the link above and see the +messages in "Correspondence with curators". + +Otherwise, open the following link to stop notifications for this entry: +<%= polymorphic_url([@object, :unobserve]) %> + diff --git a/app/views/admin_mailer/name_status_email.html.erb b/app/views/admin_mailer/name_status_email.html.erb index c1f2aa9b..84fc253e 100644 --- a/app/views/admin_mailer/name_status_email.html.erb +++ b/app/views/admin_mailer/name_status_email.html.erb @@ -1,7 +1,8 @@ -<% case @action %> +<% case @action.to_s %> <% when 'endorse' %>The name <%= link_to(@name.name_html, @name) %> has been endorsed.
Our team of curators have marked this name and the associated metadata @@ -10,6 +11,7 @@ notified of publication.
If you are currently writing the manuscript that describes this taxon, @@ -26,12 +28,14 @@ <% when 'return' %>
The name <%= link_to(@name.name_html, @name) %> has been returned.
Our team of curators have found some issues with the name and have returned it to you for further inspection.
Review the correspondance with the curators to determine the following @@ -45,11 +49,13 @@ <% when 'validate' %>
The name <%= link_to(@name.name_html, @name) %> has been validated.
The name is now considered valid under the SeqCode.
You can cite the name without quotation marks now. The formal recommended @@ -64,12 +70,14 @@ <% when 'claim' %>
You have claimed the name <%= link_to(@name.name_html, @name) %>.
The data and metadata associated to this name are no longer publicly available, and you are now able to edit all the associated metadata.
You can now complete all the validation requirements and submit the @@ -79,11 +87,13 @@ <% when 'unclaim' %>
You have unclaimed the name <%= @name.name_html %>.
You are no longer authorized to modify this name's data and metadata.
If you were aware and agree with this, you don't need to take any further diff --git a/app/views/admin_mailer/name_status_email.text.erb b/app/views/admin_mailer/name_status_email.text.erb index 3fa6717e..e1f0c189 100644 --- a/app/views/admin_mailer/name_status_email.text.erb +++ b/app/views/admin_mailer/name_status_email.text.erb @@ -1,4 +1,4 @@ -<% case @action %> +<% case @action.to_s %> <% when 'endorse' %> The name <%= @name.name %> has been endorsed. diff --git a/app/views/admin_mailer/observer_status_email.html.erb b/app/views/admin_mailer/observer_status_email.html.erb new file mode 100644 index 00000000..565b2ea5 --- /dev/null +++ b/app/views/admin_mailer/observer_status_email.html.erb @@ -0,0 +1,20 @@ +
The entry <%= link_to(@object.display, + polymorphic_url(@object, anchor: 'correspondence')) %> has changed status. +
++ You have active notifications for <%= link_to(@object.display, @object) %> + and this entry has changed status. +
++ If you are actively interested in this entry, open the link above and see the + new status. Otherwise, + <%= link_to('click here to stop notifications for this entry', + [@object, :unobserve]) %>. +
+ diff --git a/app/views/admin_mailer/observer_status_email.text.erb b/app/views/admin_mailer/observer_status_email.text.erb new file mode 100644 index 00000000..1a7bbf25 --- /dev/null +++ b/app/views/admin_mailer/observer_status_email.text.erb @@ -0,0 +1,20 @@ +The entry <%= @object.display %> has changed status. + +What does this mean? +==================== + +You have active notifications for <%= @object.display %> and this entry +has changed status. + +To view the entry in the Registry, visit: +<%= polymorphic_url(@object) %> + +What to do next? +================ + +If you are actively interested in this entry, open the link above and see the +new status. + +Otherwise, open the following link to stop notifications for this entry: +<%= polymorphic_url([@object, :unobserve]) %> + diff --git a/app/views/admin_mailer/register_status_email.html.erb b/app/views/admin_mailer/register_status_email.html.erb index b1419936..6be39ac5 100644 --- a/app/views/admin_mailer/register_status_email.html.erb +++ b/app/views/admin_mailer/register_status_email.html.erb @@ -1,5 +1,38 @@ <% name_s = @register.names.count == 1 ? 'name' : 'names' %> -<% case @action %> +<% case @action.to_s %> +<% when 'submit' %> ++ You have successfully submitted the register list + <%= link_to(@register.acc_url, @register.acc_url(true)) %>. +
++ Our team of curators have received your submission and will process it as + soon as possible. +
+No actions are needed from your side at the moment.
+<% when 'notify' %> ++ You have successfully notified the SeqCode Registry of effective publication + for the register list + <%= link_to(@register.acc_url, @register.acc_url(true)) %>. +
++ Our team of curators have received your notification and will process it as + soon as possible. +
+No actions are needed from your side at the moment.
<% when 'endorse' %>The register list @@ -7,6 +40,7 @@ has been endorsed.
Our team of curators have marked this list and all the included names @@ -15,6 +49,7 @@ been published and the Registry is notified of publication.
If you are currently writing the manuscript that describes these taxa, @@ -33,6 +68,7 @@ has been validated.
All the names in this list are now considered valid under the SeqCode. @@ -40,6 +76,7 @@ <%= @register.priority_date %>.
You can now cite the registration list using the assigned DOI: @@ -61,12 +98,14 @@ has been returned to you.
Our team of curators have identified at least one blocking issue with your list, and have returned it to you for inspection.
Please visit the <%= link_to('register list page', @register) %>, and diff --git a/app/views/admin_mailer/register_status_email.text.erb b/app/views/admin_mailer/register_status_email.text.erb index 6f8901ef..0dc22659 100644 --- a/app/views/admin_mailer/register_status_email.text.erb +++ b/app/views/admin_mailer/register_status_email.text.erb @@ -1,5 +1,29 @@ <% name_s = @register.names.count == 1 ? 'name' : 'names' %> -<% case @action %> +<% case @action.to_s %> +<% when 'submit' %> + You have successfully submitted the register list + <%= @register.acc_url %> + + What does this mean? + ==================== + Our team of curators have received your submission and will process it as + soon as possible. + + What to do next? + ================ + No actions are needed from your side at the moment. +<% when 'notify' %> + You have successfully notified the SeqCode Registry of effective publication + for the register list <%= @register.acc_url %> + + What does this mean? + ==================== + Our team of curators have received your notification and will process it as + soon as possible. + + What to do next? + ================ + No actions are needed from your side at the moment. <% when 'endorse' %> The register list <%= @register.acc_url %> has been endorsed. diff --git a/app/views/admin_mailer/user_status_email.html.erb b/app/views/admin_mailer/user_status_email.html.erb index ea02d705..54edccc7 100644 --- a/app/views/admin_mailer/user_status_email.html.erb +++ b/app/views/admin_mailer/user_status_email.html.erb @@ -1,13 +1,14 @@ -<% case @action[1] %> +<% case @action[1].to_s %> <% when 'endorse' %>
Your application as a <%= @action[0] %> of the SeqCode Registry has been endorsed.
- <% case @action[0] %> + <% case @action[0].to_s %> <% when 'curator' %> You are now authorized to access and edit any pre-validation names, including changing their status (e.g., endorsing or returning to @@ -27,9 +28,10 @@ been denied.
- <% case @action[0] %> + <% case @action[0].to_s %> <% when 'curator' %> You are not authorized to curate other submitter's records in the Registry, but you can still submit your own names as a contributor. diff --git a/app/views/admin_mailer/user_status_email.text.erb b/app/views/admin_mailer/user_status_email.text.erb index ae25a137..b967e1d7 100644 --- a/app/views/admin_mailer/user_status_email.text.erb +++ b/app/views/admin_mailer/user_status_email.text.erb @@ -1,11 +1,11 @@ -<% case @action[1] %> +<% case @action[1].to_s %> <% when 'endorse' %> Your application as a <%= @action[0] %> of the SeqCode Registry has been endorsed. What does this mean? ==================== - <% case @action[0] %> + <% case @action[0].to_s %> <% when 'curator' %> You are now authorized to access and edit any pre-validation names, including changing their status (e.g., endorsing or returning to submitters). We @@ -23,7 +23,7 @@ What does this mean? ==================== - <% case @action[0] %> + <% case @action[0].to_s %> <% when 'curator' %> You are not authorized to curate other submitter's records in the Registry, but you can still submit your own names as a contributor. diff --git a/app/views/checks/_check.html.erb b/app/views/checks/_check.html.erb new file mode 100644 index 00000000..1e742cdb --- /dev/null +++ b/app/views/checks/_check.html.erb @@ -0,0 +1,60 @@ +
Currently waiting confirmation for: <%= resource.unconfirmed_email %>
<% end %> +