diff --git a/.env.production.example b/.env.production.example
index d1dc30f6a..eb4db7265 100644
--- a/.env.production.example
+++ b/.env.production.example
@@ -68,6 +68,3 @@ SENTRY_BACKEND_SAMPLE_RATE=0.5
SENTRY_FRONTEND_DSN=https://sentryserver/OTHER-ID
SENTRY_FRONTEND_SAMPLE_RATE=1.0
-
-## For REPO
-MATOMO_URL=https://matomo.tld
diff --git a/Gemfile b/Gemfile
index 33db436a3..8ea2233f7 100644
--- a/Gemfile
+++ b/Gemfile
@@ -60,7 +60,7 @@ gem 'kaminari'
gem 'kaminari-grape'
gem 'ketcherails', git: 'https://github.com/complat/ketcher-rails.git', branch: 'upgrade-to-rails-6'
-gem 'labimotion', '1.3.2'
+gem 'labimotion', '1.4.0.1'
gem 'mimemagic', '0.3.10'
gem 'mime-types'
diff --git a/Gemfile.lock b/Gemfile.lock
index 387d94be4..8f92aaa64 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -447,7 +447,7 @@ GEM
rexml
kramdown-parser-gfm (1.1.0)
kramdown (~> 2.0)
- labimotion (1.3.2)
+ labimotion (1.4.0.1)
rails (~> 6.1.7)
latex-decode (0.4.0)
launchy (2.5.0)
@@ -918,7 +918,7 @@ DEPENDENCIES
kaminari
kaminari-grape
ketcherails!
- labimotion (= 1.3.2)
+ labimotion (= 1.4.0.1)
launchy
listen
memory_profiler
diff --git a/app/api/chemotion/element_api.rb b/app/api/chemotion/element_api.rb
index aee921953..7b8d247ca 100644
--- a/app/api/chemotion/element_api.rb
+++ b/app/api/chemotion/element_api.rb
@@ -83,12 +83,12 @@ class ElementAPI < Grape::API
elements = @collection.send(element + 's').by_ui_state(params[element])
elements.each do |el|
- pub = el.publication
+ pub = el.publication if el.respond_to?(:publication)
next if pub.nil?
pub.update_state(Publication::STATE_DECLINED)
pub.process_element(Publication::STATE_DECLINED)
- pub.inform_users(Publication::STATE_DECLINED, current_user.id)
+ pub.process_new_state_job(Publication::STATE_DECLINED, current_user.id)
end
deleted[element] = elements.destroy_all.map(&:id)
diff --git a/app/api/chemotion/public_api.rb b/app/api/chemotion/public_api.rb
index a6ae313df..f5e304308 100644
--- a/app/api/chemotion/public_api.rb
+++ b/app/api/chemotion/public_api.rb
@@ -31,13 +31,11 @@ class PublicAPI < Grape::API
end
end
-
desc 'Public initialization'
- params do
- end
get 'initialize' do
{
- molecule_viewer: Matrice.molecule_viewer
+ molecule_viewer: Matrice.molecule_viewer,
+ u: Rails.configuration.u || {},
}
end
@@ -377,8 +375,9 @@ def query_embargo(name)
optional :pages, type: Integer, desc: 'pages'
optional :per_page, type: Integer, desc: 'per page'
optional :adv_flag, type: Boolean, desc: 'advanced search?'
- optional :adv_type, type: String, desc: 'advanced search type', values: %w[Authors Ontologies Embargo]
+ optional :adv_type, type: String, desc: 'advanced search type', values: %w[Authors Ontologies Embargo Label]
optional :adv_val, type: Array[String], desc: 'advanced search value', regexp: /^(\d+|([[:alpha:]]+:\d+))$/
+ optional :label_val, type: Integer, desc: 'label_val'
optional :req_xvial, type: Boolean, default: false, desc: 'xvial is required or not'
end
paginate per_page: 10, offset: 0, max_per_page: 100
@@ -407,13 +406,18 @@ def query_embargo(name)
SQL
end
end
+ if params[:adv_type] == 'Label' && params[:label_val].present?
+ label_search = <<~SQL
+ and pub.taggable_data->'user_labels' @> '#{params[:label_val]}'
+ SQL
+ end
sample_join = <<~SQL
INNER JOIN (
SELECT molecule_id, published_at max_published_at, sample_svg_file, id as sid
FROM (
SELECT samples.*, pub.published_at, rank() OVER (PARTITION BY molecule_id order by pub.published_at desc) as rownum
FROM samples, publications pub
- WHERE pub.element_type='Sample' and pub.element_id=samples.id and pub.deleted_at ISNULL
+ WHERE pub.element_type='Sample' and pub.element_id=samples.id and pub.deleted_at ISNULL #{label_search}
and samples.id IN (
SELECT samples.id FROM samples
INNER JOIN collections_samples cs on cs.collection_id = #{public_collection_id} and cs.sample_id = samples.id and cs.deleted_at ISNULL
@@ -465,8 +469,9 @@ def query_embargo(name)
optional :pages, type: Integer, desc: 'pages'
optional :per_page, type: Integer, desc: 'per page'
optional :adv_flag, type: Boolean, desc: 'is it advanced search?'
- optional :adv_type, type: String, desc: 'advanced search type', values: %w[Authors Ontologies Embargo]
+ optional :adv_type, type: String, desc: 'advanced search type', values: %w[Authors Ontologies Embargo Label]
optional :adv_val, type: Array[String], desc: 'advanced search value', regexp: /^(\d+|([[:alpha:]]+:\d+))$/
+ optional :label_val, type: Integer, desc: 'label_val'
optional :scheme_only, type: Boolean, desc: 'is it a scheme-only reaction?', default: false
end
paginate per_page: 10, offset: 0, max_per_page: 100
@@ -518,6 +523,9 @@ def query_embargo(name)
else
col_scope = Collection.public_collection.reactions.joins(adv_search).joins(:publication).select(embargo_sql).order('publications.published_at desc')
end
+ if params[:adv_type] == 'Label' && params[:label_val].present?
+ col_scope = col_scope.where("publications.taggable_data->'user_labels' @> '?'", params[:label_val])
+ end
reset_pagination_page(col_scope)
list = paginate(col_scope)
entities = Entities::ReactionPublicationListEntity.represent(list, serializable: true)
@@ -574,8 +582,8 @@ def query_embargo(name)
r_pub = Publication.where(element_type: 'Reaction', state: 'completed').order(:published_at).last
reaction = r_pub.element
- { last_published: { sample: { id: sample.id, sample_svg_file: sample.sample_svg_file, molecule: sample.molecule, tag: s_pub.taggable_data, contributor: User.find(s_pub.published_by).name },
- reaction: { id: reaction.id, reaction_svg_file: reaction.reaction_svg_file, tag: r_pub.taggable_data, contributor: User.find(r_pub.published_by).name } } }
+ { last_published: { sample: { id: sample.id, sample_svg_file: sample.sample_svg_file, molecule: sample.molecule, tag: s_pub.taggable_data, contributor: User.with_deleted.find(s_pub.published_by).name },
+ reaction: { id: reaction.id, reaction_svg_file: reaction.reaction_svg_file, tag: r_pub.taggable_data, contributor: User.with_deleted.find(r_pub.published_by).name } } }
end
end
@@ -638,7 +646,6 @@ def query_embargo(name)
end
resource :embargo do
- helpers RepositoryHelpers
desc "Return PUBLISHED serialized collection"
params do
requires :id, type: Integer, desc: "collection id"
@@ -652,15 +659,18 @@ def query_embargo(name)
resource :col_list do
- helpers RepositoryHelpers
after_validation do
@embargo_collection = Collection.find(params[:collection_id])
@pub = @embargo_collection.publication
error!('401 Unauthorized', 401) if @pub.nil?
if @pub.state != 'completed'
- error!('401 Unauthorized', 401) unless current_user.present? && (User.reviewer_ids.include?(current_user.id) || @pub.published_by == current_user.id || current_user.type == 'Anonymous')
+ is_reviewer = User.reviewer_ids.include?(current_user&.id)
+ is_submitter = (@pub.published_by == current_user&.id || @pub.review&.dig('submitters')&.include?(current_user&.id)) && SyncCollectionsUser.find_by(user_id: current_user.id, collection_id: @embargo_collection.id).present?
+ is_anonymous = current_user&.type == 'Anonymous' && SyncCollectionsUser.find_by(user_id: current_user.id, collection_id: @embargo_collection.id).present?
+ error!('401 Unauthorized', 401) unless current_user.present? && (is_reviewer || is_submitter || is_anonymous)
end
+ @is_reviewer = User.reviewer_ids.include?(current_user&.id)
end
get do
anasql = <<~SQL
@@ -672,7 +682,7 @@ def query_embargo(name)
elements = []
list.each do |e|
element_type = e.element&.class&.name
- u = User.find(e.published_by) unless e.published_by.nil?
+ u = User.with_deleted.find(e.published_by) unless e.published_by.nil?
svg_file = e.element.sample_svg_file if element_type == 'Sample'
title = e.element.short_label if element_type == 'Sample'
@@ -685,13 +695,12 @@ def query_embargo(name)
published_by: u&.name, submit_at: e.created_at, state: e.state, scheme_only: scheme_only, ana_cnt: e.ana_cnt
)
end
- is_reviewer = User.reviewer_ids.include?(current_user&.id)
- { elements: elements, embargo: @pub, embargo_id: params[:collection_id], current_user: { id: current_user&.id, type: current_user&.type, is_reviewer: is_reviewer } }
+
+ { elements: elements, embargo: @pub, embargo_id: params[:collection_id], current_user: { id: current_user&.id, type: current_user&.type, is_reviewer: @is_reviewer } }
end
end
resource :col_element do
- helpers RepositoryHelpers
params do
requires :collection_id, type: Integer, desc: "collection id"
requires :el_id, type: Integer, desc: "element id"
@@ -717,7 +726,6 @@ def query_embargo(name)
end
resource :reaction do
- helpers RepositoryHelpers
desc "Return PUBLISHED serialized reaction"
params do
requires :id, type: Integer, desc: "Reaction id"
@@ -738,16 +746,16 @@ def query_embargo(name)
end
resource :molecule do
- helpers RepositoryHelpers
desc 'Return serialized molecule with list of PUBLISHED dataset'
params do
requires :id, type: Integer, desc: 'Molecule id'
optional :adv_flag, type: Boolean, desc: 'advanced search flag'
- optional :adv_type, type: String, desc: 'advanced search type', allow_blank: true, values: %w[Authors Ontologies Embargo]
+ optional :adv_type, type: String, desc: 'advanced search type', allow_blank: true, values: %w[Authors Ontologies Embargo Label]
optional :adv_val, type: Array[String], desc: 'advanced search value', regexp: /^(\d+|([[:alpha:]]+:\d+))$/
+ optional :label_val, type: Integer, desc: 'label_val'
end
get do
- get_pub_molecule(params[:id], params[:adv_flag], params[:adv_type], params[:adv_val])
+ get_pub_molecule(params[:id], params[:adv_flag], params[:adv_type], params[:adv_val], params[:label_val])
end
end
@@ -895,11 +903,33 @@ def query_embargo(name)
error!('404 Is not published yet', 404) unless @publication&.state&.include?('completed')
end
get do
- Base64.encode64(@attachment.read_thumbnail) if @attachment.thumb
+ if @attachment.thumb
+ thumbnail = @attachment.read_thumbnail
+ thumbnail ? Base64.encode64(thumbnail) : nil
+ else
+ nil
+ end
end
end
end
+ resource :export_metadata do
+ desc 'Get dataset metadata of publication'
+ params do
+ requires :id, type: Integer, desc: "Dataset Id"
+ end
+ before do
+ @dataset_id = params[:id]
+ @container = Container.find_by(id: @dataset_id)
+ element = @container.root.containable
+ @publication = Publication.find_by(element: element, state: 'completed') if element.present?
+ error!('404 Publication not found', 404) unless @publication.present?
+ end
+ get do
+ prepare_and_export_dataset(@container.id)
+ end
+ end
+
resource :metadata do
desc "batch download metadata"
params do
@@ -916,11 +946,11 @@ def query_embargo(name)
result = declared(params, include_missing: false)
list = []
limit = params[:limit] - params[:offset] > 1000 ? params[:offset] + 1000 : params[:limit]
- scope = Publication.where(element_type: params[:type], state: 'completed')
+ scope = Publication.includes(:doi).where(element_type: params[:type], state: 'completed')
scope = scope.where('published_at >= ?', params[:date_from]) if params[:date_from].present?
scope = scope.where('published_at <= ?', params[:date_to]) if params[:date_to].present?
publications = scope.order(:published_at).offset(params[:offset]).limit(limit)
- publications.map do |publication|
+ publications.each do |publication|
inchikey = publication&.doi&.suffix
list.push("#{service_url}#{api_url}#{inchikey}") if inchikey.present?
end
@@ -929,23 +959,6 @@ def query_embargo(name)
result
end
- resource :export do
- desc 'Get dataset metadata of publication'
- params do
- requires :id, type: Integer, desc: "Dataset Id"
- end
- before do
- @dataset_id = params[:id]
- @container = Container.find_by(id: @dataset_id)
- element = @container.root.containable
- @publication = Publication.find_by(element: element, state: 'completed') if element.present?
- error!('404 Publication not found', 404) unless @publication.present?
- end
- get do
- prepare_and_export_dataset(@container.id)
- end
- end
-
desc "metadata of publication"
params do
optional :id, type: Integer, desc: "Id"
@@ -1001,15 +1014,15 @@ def query_embargo(name)
end
end
- resource :service do
- desc 'convert molfile to 3d'
+ resource :represent do
+ desc 'represent molfile structure'
params do
- requires :molfile, type: String, desc: 'Molecule molfile'
+ requires :mol, type: String, desc: 'Molecule molfile'
end
- post :convert do
- convert_to_3d(params[:molfile])
+ post :structure do
+ represent_structure(params[:mol])
rescue StandardError => e
- return { msg: { level: 'error', message: e } }
+ return { molfile: params[:mol], msg: { level: 'error', message: e } }
end
end
end
diff --git a/app/api/chemotion/reaction_api.rb b/app/api/chemotion/reaction_api.rb
index b20f88ff2..4bdc7b848 100644
--- a/app/api/chemotion/reaction_api.rb
+++ b/app/api/chemotion/reaction_api.rb
@@ -9,6 +9,7 @@ class ReactionAPI < Grape::API
helpers ParamsHelpers
helpers LiteratureHelpers
helpers ProfileHelpers
+ helpers UserLabelHelpers
resource :reactions do
desc 'Return serialized reactions'
@@ -17,6 +18,7 @@ class ReactionAPI < Grape::API
optional :sync_collection_id, type: Integer, desc: 'SyncCollectionsUser id'
optional :from_date, type: Integer, desc: 'created_date from in ms'
optional :to_date, type: Integer, desc: 'created_date to in ms'
+ optional :user_label, type: Integer, desc: 'user label'
optional :filter_created_at, type: Boolean, desc: 'filter by created at or updated at'
optional :sort_column, type: String, desc: 'sort by created_at, updated_at, rinchi_short_key, or rxno',
values: %w[created_at updated_at rinchi_short_key rxno],
@@ -52,17 +54,19 @@ class ReactionAPI < Grape::API
from = params[:from_date]
to = params[:to_date]
+ user_label = params[:user_label]
by_created_at = params[:filter_created_at] || false
sort_column = params[:sort_column].presence || 'created_at'
sort_direction = params[:sort_direction].presence ||
(%w[created_at updated_at].include?(sort_column) ? 'DESC' : 'ASC')
- scope = scope.includes_for_list_display.order("#{sort_column} #{sort_direction}")
+ scope = scope.includes_for_list_display.order("reactions.#{sort_column} #{sort_direction}")
scope = scope.created_time_from(Time.at(from)) if from && by_created_at
scope = scope.created_time_to(Time.at(to) + 1.day) if to && by_created_at
scope = scope.updated_time_from(Time.at(from)) if from && !by_created_at
scope = scope.updated_time_to(Time.at(to) + 1.day) if to && !by_created_at
+ scope = scope.by_user_label(user_label) if user_label
reset_pagination_page(scope)
@@ -158,11 +162,11 @@ class ReactionAPI < Grape::API
requires :materials, type: Hash
optional :literatures, type: Hash
-
requires :container, type: Hash
optional :duration, type: String
optional :rxno, type: String
optional :segments, type: Array
+ optional :user_labels, type: Array
optional :variations, type: [Hash]
end
route_param :id do
@@ -175,6 +179,8 @@ class ReactionAPI < Grape::API
put do
reaction = @reaction
attributes = declared(params, include_missing: false)
+ update_element_labels(reaction, attributes[:user_labels], current_user.id)
+ attributes.delete(:user_labels)
materials = attributes.delete(:materials)
attributes.delete(:literatures)
attributes.delete(:id)
@@ -224,6 +230,7 @@ class ReactionAPI < Grape::API
optional :origin, type: Hash
optional :reaction_svg_file, type: String
optional :segments, type: Array
+ optional :user_labels, type: Array
requires :materials, type: Hash
optional :literatures, type: Hash
requires :container, type: Hash
@@ -241,6 +248,7 @@ class ReactionAPI < Grape::API
container_info = params[:container]
attributes.delete(:container)
attributes.delete(:segments)
+ attributes.delete(:user_labels)
collection = current_user.collections.where(id: collection_id).take
attributes[:created_by] = current_user.id
@@ -275,6 +283,7 @@ class ReactionAPI < Grape::API
end
reaction.container = update_datamodel(container_info)
reaction.save!
+ update_element_labels(reaction, params[:user_labels], current_user.id)
reaction.save_segments(segments: params[:segments], current_user_id: current_user.id)
CollectionsReaction.create(reaction: reaction, collection: collection) if collection.present?
diff --git a/app/api/chemotion/repository_api.rb b/app/api/chemotion/repository_api.rb
index 5b460f35b..1d3c24933 100644
--- a/app/api/chemotion/repository_api.rb
+++ b/app/api/chemotion/repository_api.rb
@@ -5,479 +5,77 @@ module Chemotion
# Repository API
class RepositoryAPI < Grape::API
include Grape::Kaminari
- helpers ContainerHelpers
- helpers ParamsHelpers
- helpers CollectionHelpers
- helpers SampleHelpers
- helpers SubmissionHelpers
- helpers EmbargoHelpers
+ helpers RepoParamsHelpers
+ helpers RepositoryHelpers
namespace :repository do
- helpers do
- def duplicate_analyses(new_element, analyses_arr, ik = nil)
- unless new_element.container
- Container.create_root_container(containable: new_element)
- new_element.reload
- end
- analyses = Container.analyses_container(new_element.container.id).first
- parent_publication = new_element.publication
- analyses_arr&.each do |ana|
- new_ana = analyses.children.create(
- name: ana.name,
- container_type: ana.container_type,
- description: ana.description
- )
- new_ana.extended_metadata = ana.extended_metadata
- new_ana.save!
-
- # move reserved doi
- if (d = ana.doi)
- d.update(doiable: new_ana)
- else
- d = Doi.create_for_analysis!(new_ana, ik)
- end
- Publication.create!(
- state: Publication::STATE_PENDING,
- element: new_ana,
- original_element: ana,
- published_by: current_user.id,
- doi: d,
- parent: new_element.publication,
- taggable_data: @publication_tag.merge(
- author_ids: @author_ids
- )
- )
- # duplicate datasets and copy attachments
- ana.children.where(container_type: 'dataset').each do |ds|
- new_dataset = new_ana.children.create(container_type: 'dataset')
-
- new_dataset.name = ds.name
- new_dataset.extended_metadata = ds.extended_metadata
- new_dataset.save!
- clone_attachs = ds.attachments
- Usecases::Attachments::Copy.execute!(clone_attachs, new_dataset, current_user.id) if clone_attachs.present?
- end
- end
- end
-
- def reviewer_collections
- c = current_user.pending_collection
- User.reviewer_ids.each do |rev_id|
- SyncCollectionsUser.find_or_create_by(
- collection_id: c.id,
- user_id: rev_id,
- shared_by_id: c.user_id,
- permission_level: 3,
- sample_detail_level: 10,
- reaction_detail_level: 10,
- label: 'REVIEWING'
- )
- end
- end
-
- # Create(clone) publication sample/analyses with dois
- def duplicate_sample(sample = @sample, analyses = @analyses, parent_publication_id = nil)
- new_sample = sample.dup
- new_sample.reprocess_svg if new_sample.sample_svg_file.blank?
- new_sample.collections << current_user.pending_collection
- new_sample.collections << Collection.element_to_review_collection
- new_sample.collections << @embargo_collection unless @embargo_collection.nil?
- new_sample.save!
- new_sample.copy_segments(segments: sample.segments, current_user_id: current_user.id) if sample.segments
- duplicate_residues(new_sample, sample) if sample.residues
- duplicate_elemental_compositions(new_sample, sample) if sample.elemental_compositions
- duplicate_user_labels(new_sample, sample) ## if sample.tag.taggable_data['user_labels']
- unless @literals.nil?
- lits = @literals&.select { |lit| lit['element_type'] == 'Sample' && lit['element_id'] == sample.id }
- duplicate_literals(new_sample, lits)
- end
- duplicate_analyses(new_sample, analyses, new_sample.molecule.inchikey)
- has_analysis = new_sample.analyses.present?
- if (has_analysis = new_sample.analyses.present?)
- if (d = sample.doi)
- d.update!(doiable: new_sample)
- else
- d = Doi.create_for_element!(new_sample)
- end
- pub = Publication.create!(
- state: Publication::STATE_PENDING,
- element: new_sample,
- original_element: sample,
- published_by: current_user.id,
- doi: d,
- parent_id: parent_publication_id,
- taggable_data: @publication_tag.merge(
- author_ids: @author_ids,
- original_analysis_ids: analyses.pluck(:id),
- analysis_ids: new_sample.analyses.pluck(:id)
- )
- )
- end
- new_sample.analyses.each do |ana|
- Publication.find_by(element: ana).update(parent: pub)
- end
- new_sample
- end
-
- def concat_author_ids(coauthors = params[:coauthors])
- coauthor_ids = coauthors.map do |coa|
- val = coa.strip
- next val.to_i if val =~ /^\d+$/
-
- User.where(type: %w(Person Collaborator)).where.not(confirmed_at: nil).find_by(email: val)&.id if val =~ /^\S+@\S+$/
- end.compact
- [current_user.id] + coauthor_ids
- end
-
- def duplicate_reaction(reaction, analysis_set)
- new_reaction = reaction.dup
- if analysis_set && analysis_set.length > 0
- analysis_set_ids = analysis_set.map(&:id)
- reaction_analysis_set = reaction.analyses.where(id: analysis_set_ids)
- end
- princhi_string, princhi_long_key, princhi_short_key, princhi_web_key = reaction.products_rinchis
-
- new_reaction.collections << current_user.pending_collection
- new_reaction.collections << Collection.element_to_review_collection
- new_reaction.collections << @embargo_collection unless @embargo_collection.nil?
-
- # composer = SVG::ReactionComposer.new(paths, temperature: temperature_display_with_unit,
- # solvents: solvents_in_svg,
- # show_yield: true)
- # new_reaction.reaction_svg_file = composer.compose_reaction_svg_and_save(prefix: Time.now)
- dir = File.join(Rails.root, 'public', 'images', 'reactions')
- rsf = reaction.reaction_svg_file
- path = File.join(dir, rsf)
- new_rsf = "#{Time.now.to_i}-#{rsf}"
- dest = File.join(dir, new_rsf)
-
- new_reaction.save!
- new_reaction.copy_segments(segments: reaction.segments, current_user_id: current_user.id)
- unless @literals.nil?
- lits = @literals&.select { |lit| lit['element_type'] == 'Reaction' && lit['element_id'] == reaction.id }
- duplicate_literals(new_reaction, lits)
- end
- if File.exists? path
- FileUtils.cp(path, dest)
- new_reaction.update_columns(reaction_svg_file: new_rsf)
- end
- # new_reaction.save!
- et = new_reaction.tag
- data = et.taggable_data || {}
- # data[:products_rinchi] = {
- # rinchi_string: princhi_string,
- # rinchi_long_key: princhi_long_key,
- # rinchi_short_key: princhi_short_key,
- # rinchi_web_key: princhi_web_key
- # }
- et.update!(taggable_data: data)
-
- if (d = reaction.doi)
- d.update!(doiable: new_reaction)
- else
- # NB: the reaction has still no sample, so it cannot get a proper rinchi needed for the doi
- # => use the one from original reaction
- d = Doi.create_for_element!(new_reaction, 'reaction/' + reaction.products_short_rinchikey_trimmed)
- end
-
- pub = Publication.create!(
- state: Publication::STATE_PENDING,
- element: new_reaction,
- original_element: reaction,
- published_by: current_user.id,
- doi: d,
- taggable_data: @publication_tag.merge(
- author_ids: @author_ids,
- original_analysis_ids: analysis_set_ids,
- products_rinchi: {
- rinchi_string: princhi_string,
- rinchi_long_key: princhi_long_key,
- rinchi_short_key: princhi_short_key,
- rinchi_web_key: princhi_web_key
- }
- )
- )
-
- duplicate_analyses(new_reaction, reaction_analysis_set, 'reaction/' + reaction.products_short_rinchikey_trimmed)
- reaction.reactions_samples.each do |rs|
- new_rs = rs.dup
- sample = current_user.samples.find_by(id: rs.sample_id)
- if @scheme_only == true
- sample.target_amount_value = 0.0
- sample.real_amount_value = nil
- end
- sample_analysis_set = sample.analyses.where(id: analysis_set_ids)
- new_sample = duplicate_sample(sample, sample_analysis_set, pub.id)
- sample.tag_as_published(new_sample, sample_analysis_set)
- new_rs.sample_id = new_sample
- new_rs.reaction_id = new_reaction.id
- new_rs.sample_id = new_sample.id
- new_rs.reaction_id = new_reaction.id
- new_rs.save!
- end
-
- new_reaction.update_svg_file!
- new_reaction.reload
- new_reaction.save!
- new_reaction.reload
- end
-
- def create_publication_tag(contributor, author_ids, license)
- authors = User.where(type: %w[Person Collaborator], id: author_ids)
- .includes(:affiliations)
- .order(Arel.sql("position(users.id::text in '#{author_ids}')"))
- affiliations = authors.map(&:current_affiliations)
- affiliations_output = {}
- affiliations.flatten.each do |aff|
- affiliations_output[aff.id] = aff.output_full
- end
- {
- published_by: author_ids[0],
- author_ids: author_ids,
- creators: authors.map do |author|
- {
- 'givenName' => author.first_name,
- 'familyName' => author.last_name,
- 'name' => author.name,
- 'ORCID' => author.orcid,
- 'affiliationIds' => author.current_affiliations.map(&:id),
- 'id' => author.id
- }
- end,
- contributors: {
- 'givenName' => contributor.first_name,
- 'familyName' => contributor.last_name,
- 'name' => contributor.name,
- 'ORCID' => contributor.orcid,
- 'affiliations' => contributor.current_affiliations.map(&:output_full),
- 'id' => contributor.id
- },
- affiliations: affiliations_output,
- affiliation_ids: affiliations.map { |as| as.map(&:id) },
- queued_at: DateTime.now,
- license: license,
- scheme_only: @scheme_only
- }
- end
-
- def prepare_reaction_data
- reviewer_collections
- new_reaction = duplicate_reaction(@reaction, @analysis_set)
- reaction_analysis_set = @reaction.analyses.where(id: @analysis_set_ids)
- @reaction.tag_as_published(new_reaction, reaction_analysis_set)
- new_reaction.create_publication_tag(current_user, @author_ids, @license)
- new_reaction.samples.each do |new_sample|
- new_sample.create_publication_tag(current_user, @author_ids, @license)
- end
- pub = Publication.where(element: new_reaction).first
- add_submission_history(pub)
- pub
- end
-
- def duplicate_user_labels(newSample, originalSample)
- user_labels = originalSample.tag&.taggable_data.dig('user_labels')
- return if user_labels.nil?
-
- tag = newSample.tag
- taggable_data = tag.taggable_data || {}
- taggable_data['user_labels'] = user_labels
- tag.update!(taggable_data: taggable_data)
- end
-
- def duplicate_elemental_compositions(newSample, originalSample)
- originalSample&.elemental_compositions&.each do |ec|
- newComposition = ElementalComposition.find_or_create_by(sample_id: newSample.id, composition_type: ec.composition_type)
- newComposition.update_columns(data: ec.data, loading: ec.loading)
- end
- end
-
- def duplicate_residues(newSample, originalSample)
- originalSample&.residues&.each do |res|
- newRes = Residue.find_or_create_by(sample_id: newSample.id, residue_type: res.residue_type)
- newRes.update_columns(custom_info: res.custom_info)
- end
- end
-
- def duplicate_literals(element, literals)
- literals&.each do |lit|
- attributes = {
- literature_id: lit.literature_id,
- element_id: element.id,
- element_type: lit.element_type,
- category: 'detail',
- user_id: lit.user_id,
- litype: lit.litype
- }
- Literal.create(attributes)
- end
- end
-
- def prepare_sample_data
- reviewer_collections
- new_sample = duplicate_sample(@sample, @analyses)
- @sample.tag_as_published(new_sample, @analyses)
- new_sample.create_publication_tag(current_user, @author_ids, @license)
- @sample.untag_reserved_suffix
- pub = Publication.where(element: new_sample).first
- add_submission_history(pub)
- pub
- end
-
- def add_submission_history(root)
- init_node = {
- state: 'submission',
- action: 'submission',
- timestamp: Time.now.strftime('%d-%m-%Y %H:%M:%S'),
- username: current_user.name,
- user_id: current_user.id,
- type: 'submit'
- }
- review = root.review || {}
- history = review['history'] || []
- history << init_node
-
- current_node = {
- action: 'reviewing',
- type: 'reviewed',
- state: 'pending'
- }
- history << current_node
- review['history'] = history
- review['reviewers'] = @group_reviewers if @group_reviewers.present?
- root.update!(review: review)
- end
+ before do
+ error!('404 user not found', 404) unless current_user
end
- desc 'Get review list'
- params do
- optional :type, type: String, desc: 'Type'
- optional :state, type: String, desc: 'State'
- optional :search_type, type: String, desc: 'search type', values: %w[All Name Embargo Submitter]
- optional :search_value, type: String, desc: 'search value'
- optional :page, type: Integer, desc: 'page'
- optional :pages, type: Integer, desc: 'pages'
- optional :per_page, type: Integer, desc: 'per page'
+ after_validation do
+ @is_reviewer = User.reviewer_ids.include?(current_user.id)
+ @is_embargo_viewer = User.embargo_viewer_ids.include?(current_user.id)
end
- paginate per_page: 10, offset: 0, max_per_page: 100
- get 'list' do
- type = params[:type].blank? || params[:type] == 'All' ? %w[Sample Reaction] : params[:type].chop!
- state = params[:state].empty? || params[:state] == 'All' ? [Publication::STATE_PENDING, Publication::STATE_REVIEWED, Publication::STATE_ACCEPTED] : params[:state]
- pub_scope = if User.reviewer_ids.include?(current_user.id)
- Publication.where(state: state, ancestry: nil, element_type: type)
- else
- Publication.where(state: state, ancestry: nil, element_type: type).where("published_by = ? OR (review -> 'reviewers')::jsonb @> '?'", current_user.id, current_user.id)
- end
- unless params[:search_value].blank? || params[:search_value] == 'All'
- case params[:search_type]
- when 'Submitter'
- pub_scope = pub_scope.where(published_by: params[:search_value])
- when 'Embargo'
- embargo_search = <<~SQL
- (element_type = 'Reaction' and element_id in (select reaction_id from collections_reactions cr where cr.deleted_at is null and cr.collection_id = ?))
- or
- (element_type = 'Sample' and element_id in (select sample_id from collections_samples cs where cs.deleted_at is null and cs.collection_id = ?))
- SQL
- embargo_search = ActiveRecord::Base.send(:sanitize_sql_array, [embargo_search, params[:search_value], params[:search_value]])
- pub_scope = pub_scope.where(embargo_search)
- when 'Name'
- r_name_sql = " r.short_label like '%#{ActiveRecord::Base.send(:sanitize_sql_like, params[:search_value])}%' "
- s_name_sql = " s.short_label like '%#{ActiveRecord::Base.send(:sanitize_sql_like, params[:search_value])}%' "
- name_search = <<~SQL
- (element_type = 'Reaction' and element_id in (select id from reactions r where #{r_name_sql}))
- or
- (element_type = 'Sample' and element_id in (select id from samples s where #{s_name_sql}))
- SQL
- pub_scope = pub_scope.where(name_search)
- end
- end
-
- list = pub_scope.order('publications.updated_at desc')
- elements = []
- paginate(list).each do |e|
- element_type = e.element&.class&.name
- next if element_type.nil?
-
- u = User.find(e.published_by) unless e.published_by.nil?
- svg_file = e.element.reaction_svg_file if element_type == 'Reaction'
- title = e.element.short_label if element_type == 'Reaction'
- svg_file = e.element.sample_svg_file if element_type == 'Sample'
- title = e.element.short_label if element_type == 'Sample'
- review_info = repo_review_info(e, current_user&.id, true)
- checklist = e.review && e.review['checklist'] if User.reviewer_ids.include?(current_user&.id) || review_info[:groupleader] == true
- scheme_only = element_type == 'Reaction' && e.taggable_data && e.taggable_data['scheme_only']
-
-
- elements.push(
- id: e.element_id, svg: svg_file, type: element_type, title: title, checklist: checklist || {}, review_info: review_info, isReviewer: User.reviewer_ids.include?(current_user&.id) || false,
- published_by: u&.name, submitter_id: u&.id, submit_at: e.created_at, state: e.state, embargo: find_embargo_collection(e).label, scheme_only: scheme_only
- )
+ namespace :review_list do
+ helpers ReviewHelpers
+ desc 'Get review list'
+ params do
+ use :get_review_list_params
+ end
+ before do
+ end
+ paginate per_page: 10, offset: 0, max_per_page: 100
+ get do
+ get_review_list(params, current_user, @is_reviewer)
end
- { elements: elements }
end
- desc 'Get embargo list'
- helpers RepositoryHelpers
- post 'embargo_list' do
+ ## Get embargo selection list for submission
+ namespace :embargo_list do
+ helpers EmbargoHelpers
+ desc 'Get embargo list'
params do
optional :is_submit, type: Boolean, default: false, desc: 'Publication submission'
end
- is_reviewer = User.reviewer_ids.include?(current_user.id)
- is_embargo_viewer = User.embargo_viewer_ids.include?(current_user.id)
- is_submitter = false
- if (is_reviewer || is_embargo_viewer) && params[:is_submit] == false
- es = Publication.where(element_type: 'Collection', state: 'pending').order(Arel.sql("taggable_data->>'label' ASC"))
- else
- is_submitter = current_user.type == 'Anonymous' ? false : true
- cols = if current_user.type == 'Anonymous'
- Collection.where(id: current_user.sync_in_collections_users.pluck(:collection_id)).where.not(label: 'chemotion')
- else
- Collection.where(ancestry: current_user.publication_embargo_collection.id)
- end
- es = Publication.where(element_type: 'Collection', element_id: cols.pluck(:id)).order(Arel.sql("taggable_data->>'label' ASC")) unless cols&.empty?
+ before do
+ end
+ get do
+ embargo_select_list(params[:is_submit], current_user, @is_reviewer, @is_embargo_viewer)
end
- # es = build_publication_element_state(es) unless es.empty?
-
- { repository: es, current_user: { id: current_user.id, type: current_user.type, is_reviewer: is_reviewer, is_submitter: is_submitter } }
end
namespace :assign_embargo do
+ helpers EmbargoHelpers
desc 'assign to an embargo bundle'
params do
- requires :new_embargo, type: Integer, desc: 'Collection id'
- requires :element, type: Hash, desc: 'Element'
+ use :assign_embargo_params
end
after_validation do
declared_params = declared(params, include_missing: false)
- @p_element = declared_params[:element]
- @p_embargo = declared_params[:new_embargo]
- pub = Publication.find_by(element_type: @p_element['type'].classify, element_id: @p_element['id'])
+ @element = declared_params[:element]
+ @embargo_id = declared_params[:new_embargo]
+ pub = Publication.find_by(element_type: @element['type'], element_id: @element['id'])
error!('404 Publication not found', 404) unless pub
error!("404 Publication state must be #{Publication::STATE_REVIEWED}", 404) unless pub.state == Publication::STATE_REVIEWED
error!('401 Unauthorized', 401) unless pub.published_by == current_user.id
- if @p_embargo.to_i.positive?
- e_col = Collection.find(@p_embargo.to_i)
+ if @embargo_id.to_i.positive?
+ e_col = Collection.find(@embargo_id.to_i)
error!('404 This embargo has been released.', 404) unless e_col.ancestry.to_i == current_user.publication_embargo_collection.id
end
end
post do
- embargo_collection = fetch_embargo_collection(@p_embargo.to_i, current_user) if @p_embargo.to_i >= 0
- case @p_element['type'].classify
- when 'Sample'
- CollectionsSample
- when 'Reaction'
- CollectionsReaction
- end.create_in_collection(@p_element['id'], [embargo_collection.id])
- { element: @p_element,
- new_embargo: embargo_collection,
- is_new_embargo: @p_embargo.to_i.zero?,
- message: "#{@p_element['type']} [#{@p_element['title']}] has been moved to Embargo Bundle [#{embargo_collection.label}]" }
+ embargo = Repo::EmbargoHandler.find_or_create_embargo(@embargo_id.to_i, current_user) if @embargo_id.to_i >= 0
+ assign_embargo(@element, embargo, current_user, @embargo_id.to_i.zero?)
rescue StandardError => e
{ error: e.message }
end
end
+ ## TO BE CHECKED
resource :compound do
+ # helpers RepositoryHelpers
desc 'compound'
params do
requires :id, type: Integer, desc: 'Element id'
@@ -494,28 +92,22 @@ def add_submission_history(root)
after_validation do
@pub = ElementTag.find_by(taggable_type: 'Sample', taggable_id: params[:id])
error!('404 No data found', 404) unless @pub
+
element_policy = ElementPolicy.new(current_user, @pub.taggable)
error!('401 Unauthorized', 401) unless element_policy.read? || User.reviewer_ids.include?(current_user.id)
end
post do
- data = @pub.taggable_data || {}
- xvial = data['xvial'] || {}
- xvial['num'] = params[:data]
- xvial['comp_num'] = params[:xcomp]
- xvial['username'] = current_user.name
- xvial['userid'] = current_user.id
- xvial['timestamp'] = Time.now.strftime('%d-%m-%Y %H:%M:%S')
- data['xvial'] = xvial
- @pub.update!(taggable_data: data)
+ update_compound(@pub, params, current_user)
end
end
end
resource :comment do
- desc 'User comment'
+ # helpers RepositoryHelpers
+ desc 'Publication comment (public)'
params do
requires :id, type: Integer, desc: 'Element id'
- optional :type, type: String, values: %w[Reaction Sample Container Collection]
+ optional :type, type: String, values: %w[Reaction Sample Container Collection] ### TO BE CHECKED (Collection)
requires :pageId, type: Integer, desc: 'Page Element id'
optional :pageType, type: String, values: %w[reactions molecules]
optional :comment, type: String
@@ -523,135 +115,66 @@ def add_submission_history(root)
after_validation do
@pub = Publication.find_by(element_type: params[:type], element_id: params[:id])
error!('404 No data found', 404) unless @pub
- element_policy = ElementPolicy.new(current_user, @pub.element)
+
+ element_check = params[:type] == 'Container' ? @pub.root.element : @pub.element
+ element_policy = ElementPolicy.new(current_user, element_check)
error!('401 Unauthorized', 401) unless element_policy.read? || User.reviewer_ids.include?(current_user.id)
end
post 'user_comment' do
PublicationMailer.mail_user_comment(current_user, params[:id], params[:type], params[:pageId], params[:pageType], params[:comment]).deliver_now
end
post 'reviewer' do
- pub = Publication.find_by(element_type: params[:type], element_id: params[:id])
- review = pub.review || {}
- review_info = review['info'] || {}
- review_info['comment'] = params[:comment]
- review_info['timestamp'] = Time.now.strftime('%d-%m-%Y %H:%M:%S')
- review_info['username'] = current_user.name
- review_info['userid'] = current_user.id
-
- review['info'] = review_info
- pub.update!(review: review)
+ update_public_comment(params, current_user)
end
end
resource :reaction do
- helpers RepositoryHelpers
- desc 'Return PUBLISHED serialized reaction'
+ helpers ReviewHelpers
+ desc 'Return Reviewing serialized reaction'
params do
requires :id, type: Integer, desc: 'Reaction id'
- optional :is_public, type: Boolean, default: true
end
after_validation do
- element = Reaction.find_by(id: params[:id])
- error!('404 No data found', 404) unless element
+ @reaction = Reaction.find_by(id: params[:id])
+ error!('404 No data found', 404) unless @reaction
- element_policy = ElementPolicy.new(current_user, element)
+ element_policy = ElementPolicy.new(current_user, @reaction)
error!('401 Unauthorized', 401) unless element_policy.read? || User.reviewer_ids.include?(current_user.id)
- pub = Publication.find_by(element_type: 'Reaction', element_id: params[:id])
- error!('404 No data found', 404) if pub.nil?
+ @publication = @reaction.publication
+ error!('404 No data found', 404) if @publication.nil?
- error!('401 Unauthorized', 401) if (params[:is_public] == false && pub.state == 'completed')
+ error!('401 The submission has been published', 401) if @publication.state == 'completed'
end
get do
- reaction = Reaction.where(id: params[:id])
- .select(
- <<~SQL
- reactions.id, reactions.name, reactions.description, reactions.reaction_svg_file, reactions.short_label,
- reactions.status, reactions.tlc_description, reactions.tlc_solvents, reactions.rf_value,
- reactions.temperature, reactions.timestamp_start,reactions.timestamp_stop,reactions.observation,
- reactions.rinchi_string, reactions.rinchi_long_key, reactions.rinchi_short_key,reactions.rinchi_web_key,
- (select json_extract_path(taggable_data::json, 'publication') from publications where element_type = 'Reaction' and element_id = reactions.id) as publication,
- reactions.duration
- SQL
- ).includes(container: :attachments).last
- literatures = get_literature(params[:id], 'Reaction', params[:is_public] ? 'public' : 'detail') || []
- reaction.products.each do |p|
- literatures += get_literature(p.id, 'Sample', params[:is_public] ? 'public' : 'detail')
- end
- schemeList = get_reaction_table(params[:id])
- publication = Publication.find_by(element_id: params[:id], element_type: 'Reaction')
- review_info = repo_review_info(publication, current_user&.id, false)
- publication.review&.slice!('history') unless User.reviewer_ids.include?(current_user.id) || review_info[:groupleader] == true
- published_user = User.find(publication.published_by) unless publication.nil?
- entities = Entities::RepoReactionEntity.represent(reaction, serializable: true)
- entities[:literatures] = literatures unless entities.nil? || literatures.blank?
- entities[:schemes] = schemeList unless entities.nil? || schemeList.blank?
- entities[:segments] = Labimotion::SegmentEntity.represent(reaction.segments)
- embargo = find_embargo_collection(publication)
- entities[:embargo] = embargo&.label
- entities[:embargoId] = embargo&.id
- {
- reaction: entities,
- selectEmbargo: Publication.find_by(element_type: 'Collection', element_id: embargo&.id),
- pub_name: published_user&.name || '',
- review_info: review_info
- }
+ fetch_reviewing_reaction(@reaction, @publication, current_user)
end
end
resource :sample do
- helpers RepositoryHelpers
+ helpers ReviewHelpers
desc 'Return Review serialized Sample'
params do
requires :id, type: Integer, desc: 'Sample id'
- optional :is_public, type: Boolean, default: true
end
after_validation do
- element = Sample.find_by(id: params[:id])
- error!('401 No data found', 401) unless element
+ @sample = Sample.find_by(id: params[:id])
+ error!('401 No data found', 401) unless @sample
- element_policy = ElementPolicy.new(current_user, element)
+ element_policy = ElementPolicy.new(current_user, @sample)
error!('401 Unauthorized', 401) unless element_policy.read? || User.reviewer_ids.include?(current_user.id)
- pub = Publication.find_by(element_type: 'Sample', element_id: params[:id])
- error!('401 No data found', 401) if pub.nil?
- error!('401 Unauthorized', 401) if (params[:is_public] == false && pub.state == 'completed')
+ @publication = @sample.publication
+ error!('401 No data found', 401) if @publication.nil?
+ error!('401 The submission has been published', 401) if @publication.state == 'completed'
end
get do
- sample = Sample.where(id: params[:id]).includes(:molecule, :tag).last
- review_sample = { **sample.serializable_hash.deep_symbolize_keys }
- review_sample[:segments] = sample.segments.present? ? Labimotion::SegmentEntity.represent(sample.segments) : []
- molecule = Molecule.find(sample.molecule_id) unless sample.nil?
- containers = Entities::ContainerEntity.represent(sample.container)
- publication = Publication.find_by(element_id: params[:id], element_type: 'Sample')
- review_info = repo_review_info(publication, current_user&.id, false)
- # preapproved = publication.review.dig('checklist', 'glr', 'status') == true
- # is_leader = publication.review.dig('reviewers')&.include?(current_user&.id)
- publication.review&.slice!('history') unless User.reviewer_ids.include?(current_user.id) || review_info[:groupleader] == true
- published_user = User.find(publication.published_by) unless publication.nil?
- literatures = get_literature(params[:id], 'Sample', params[:is_public] ? 'public' : 'detail')
- # embargo = PublicationCollections.where("(elobj ->> 'element_type')::text = 'Sample' and (elobj ->> 'element_id')::integer = #{sample.id}")&.first&.label
- embargo = find_embargo_collection(publication)
- review_sample[:embargo] = embargo&.label
- review_sample[:embargoId] = embargo&.id
- label_ids = sample.tag.taggable_data['user_labels'] || [] unless sample.tag.taggable_data.nil?
- user_labels = UserLabel.public_labels(label_ids) unless label_ids.nil?
- {
- molecule: MoleculeGuestSerializer.new(molecule).serializable_hash.deep_symbolize_keys,
- sample: review_sample,
- labels: user_labels,
- publication: publication,
- literatures: literatures,
- analyses: containers,
- selectEmbargo: Publication.find_by(element_type: 'Collection', element_id: embargo&.id),
- doi: Entities::DoiEntity.represent(sample.doi, serializable: true),
- pub_name: published_user&.name,
- review_info: review_info
- }
+ fetch_reviewing_sample(@sample, @publication, current_user)
end
end
resource :metadata do
+ # helpers RepositoryHelpers
desc 'metadata of publication'
params do
requires :id, type: Integer, desc: 'Id'
@@ -663,77 +186,20 @@ def add_submission_history(root)
element_id: params['id']
).root
error!('404 Publication not found', 404) unless @root_publication
- error!('401 Unauthorized', 401) unless User.reviewer_ids.include?(current_user.id) || @root_publication.published_by == current_user.id || @root_publication.review['reviewers'].include?(current_user.id)
+ error!('401 Unauthorized', 401) unless User.reviewer_ids.include?(current_user.id) || @root_publication.published_by == current_user.id || (@root_publication.review['reviewers'] && @root_publication.review['reviewers'].include?(current_user.id))
end
post :preview do
- mt = []
- root_publication = @root_publication
- publications = [root_publication] + root_publication.descendants
- publications.each do |pub|
- mt.push(element_type: pub.element_type, metadata_xml: pub.datacite_metadata_xml)
- end
- { metadata: mt }
+ metadata_preview(@root_publication, current_user)
end
post :preview_zip do
env['api.format'] = :binary
content_type('application/zip, application/octet-stream')
- root_publication = @root_publication
- publications = [root_publication] + root_publication.descendants
- filename = URI.escape("metadata_#{root_publication.element_type}_#{root_publication.element_id}-#{Time.new.strftime('%Y%m%d%H%M%S')}.zip")
- header('Content-Disposition', "attachment; filename=\"#{filename}\"")
- zip = Zip::OutputStream.write_buffer do |zip|
- publications.each do |pub|
- el_type = pub.element_type == 'Container' ? 'analysis' : pub.element_type.downcase
- zip.put_next_entry URI.escape("metadata_#{el_type}_#{pub.element_id}.xml")
- zip.write pub.datacite_metadata_xml
- end
- end
- zip.rewind
- zip.read
+ metadata_preview_zip(@root_publication, current_user)
end
end
- namespace :review_search_options do
- helpers do
- def query_submitter(element_type, state)
- if User.reviewer_ids.include?(current_user.id)
- state_sql = state == 'All' || state.empty? ? " state in ('pending', 'reviewed', 'accepted')" : ActiveRecord::Base.send(:sanitize_sql_array, [' state=? ', state])
- type_sql = element_type == 'All' || element_type.empty? ? " element_type in ('Sample', 'Reaction')" : ActiveRecord::Base.send(:sanitize_sql_array, [' element_type=? ', element_type.chop])
- search_scope = User.where(type: 'Person').where(
- <<~SQL
- users.id in (
- select published_by from publications pub where ancestry is null and deleted_at is null
- and #{state_sql} and #{type_sql})
- SQL
- )
- .order('first_name ASC')
- else
- search_scope = User.where(id: current_user.id)
- end
- result = search_scope.select(
- <<~SQL
- id as key, first_name, last_name, first_name || chr(32) || last_name as name, first_name || chr(32) || last_name || chr(32) || '(' || name_abbreviation || ')' as label
- SQL
- )
- end
- def query_embargo
- search_scope = if User.reviewer_ids.include?(current_user.id)
- Collection.where(
- <<~SQL
- ancestry::integer in (select id from collections cx where label = 'Embargoed Publications')
- SQL
- )
- else
- Collection.where(ancestry: current_user.publication_embargo_collection.id)
- end
- result = search_scope.select(
- <<~SQL
- id as key, label as name, label as label
- SQL
- )
- .order('label ASC')
- end
- end
+ namespace :review_search_options do
+ helpers ReviewHelpers
desc 'Find matched review values'
params do
requires :type, type: String, allow_blank: false, desc: 'Type', values: %w[All Submitter Embargo]
@@ -741,247 +207,35 @@ def query_embargo
optional :state, type: String, desc: 'Type', values: %w[All reviewed accepted pending]
end
get do
- result = case params[:type]
- when 'Submitter'
- query_submitter(params[:element_type], params[:state])
- when 'Embargo'
- query_embargo
- else
- []
- end
- { result: result }
- end
- end
-
- namespace :reviewing do
- helpers do
- def approve_comments(root, comment, _checklist, _reviewComments, _action, _his = true)
- review = root.review || {}
- review_history = review['history'] || []
- current = review_history.last
- current['username'] = current_user.name
- current['userid'] = current_user.id
- current['action'] = 'pre-approved'
- current['comment'] = comment unless comment.nil?
- current['timestamp'] = Time.now.strftime('%d-%m-%Y %H:%M:%S')
- review_history[review_history.length - 1] = current
- next_node = { action: 'reviewing', type: 'reviewed', state: 'pending' }
- review_history << next_node
- review['history'] = review_history
- revst = review['checklist'] || {}
- revst['glr'] = { status: true, user: current_user.name, updated_at: Time.now.strftime('%d-%m-%Y %H:%M:%S') }
- review['checklist'] = revst
-
- root.update!(review: review)
- end
-
- def save_comments(root, comment, checklist, reviewComments, action, his = true)
- review = root.review || {}
- review_history = review['history'] || []
- current = review_history.last || {}
- current['state'] = %w[accepted declined].include?(action) ? action : root.state
- current['action'] = action unless action.nil?
- current['username'] = current_user.name
- current['userid'] = current_user.id
- current['comment'] = comment unless comment.nil?
- current['type'] = root.state == Publication::STATE_PENDING ? 'reviewed' : 'submit'
- current['timestamp'] = Time.now.strftime('%d-%m-%Y %H:%M:%S')
-
- if review_history.length == 0
- review_history[0] = current
- else
- review_history[review_history.length - 1] = current
- end
- if his ## add next_node
- next_node = { action: 'revising', type: 'submit', state: 'reviewed' } if root.state == Publication::STATE_PENDING
- next_node = { action: 'reviewing', type: 'reviewed', state: 'pending' } if root.state == Publication::STATE_REVIEWED
- review_history << next_node
- review['history'] = review_history
- else
-
- # is_leader = review.dig('reviewers')&.include?(current_user&.id)
- if root.state == Publication::STATE_PENDING && (action.nil? || action == Publication::STATE_REVIEWED)
- next_node = { action: 'reviewing', type: 'reviewed', state: 'pending' }
- review_history << next_node
- review['history'] = review_history
- end
- end
- if checklist&.length&.positive?
- revst = review['checklist'] || {}
- checklist.each do |k, v|
- revst[k] = v['status'] == true ? { status: v['status'], user: current_user.name, updated_at: Time.now.strftime('%d-%m-%Y %H:%M:%S') } : { status: false } unless revst[k] && revst[k]['status'] == v['status']
- end
- review['checklist'] = revst
- end
- review['reviewComments'] = reviewComments if reviewComments.present?
- root.update!(review: review)
- end
-
- # TODO: mv to model
- def save_comment(root, comment)
- review = root.review || {}
- review_history = review['history'] || []
- current = review_history.last
- comments = current['comments'] || {}
- comment[comment.keys[0]]['timestamp'] = Time.now.strftime('%d-%m-%Y %H:%M:%S') unless comment.keys.empty?
- comment[comment.keys[0]]['username'] = current_user.name
- comment[comment.keys[0]]['userid'] = current_user.id
-
- current['comments'] = comments.deep_merge(comment || {})
- review['history'] = review_history
- root.update!(review: review)
- end
- end
-
- desc 'process reviewed publication'
- params do
- requires :id, type: Integer, desc: 'Id'
- requires :type, type: String, desc: 'Type', values: %w[sample reaction collection]
- optional :comments, type: Hash
- optional :comment, type: String
- optional :checklist, type: Hash
- end
-
- after_validation do
- @root_publication = Publication.find_by(
- element_type: params['type'].classify,
- element_id: params['id']
- ).root
- error!('401 Unauthorized', 401) unless (User.reviewer_ids.include?(current_user.id) && @root_publication.state == Publication::STATE_PENDING) || (@root_publication.review.dig('reviewers')&.include?(current_user&.id)) || (@root_publication.published_by == current_user.id && @root_publication.state == Publication::STATE_REVIEWED)
-
- embargo = find_embargo_collection(@root_publication) unless params['type'] == 'collection'
- @embargo_pub = embargo.publication if embargo.present?
- end
-
- post :comments do
- save_comments(@root_publication, params[:comment], params[:checklist], params[:reviewComments], nil, false)
- element = Entities::ReactionEntity.represent(@root_publication.element) if params[:type] == 'reaction'
- element = Entities::SampleEntity.represent(@root_publication.element) if params[:type] == 'sample'
- element = Entities::CollectionEntity.represent(@root_publication.element) if params[:type] == 'collection'
-
- review_info = repo_review_info(@root_publication, current_user&.id, false)
- his = @root_publication.review&.slice('history') unless User.reviewer_ids.include?(current_user.id) || @root_publication.review.dig('reviewers')&.include?(current_user.id)
- { "#{params[:type]}": element, review: his || @root_publication.review, review_info: review_info }
- end
-
- post :comment do
- save_comment(@root_publication, params[:comments]) unless params[:comments].nil?
- his = @root_publication.review&.slice('history') unless User.reviewer_ids.include?(current_user.id)
- { review: his || @root_publication.review }
- end
-
- post :reviewed do
- save_comments(@root_publication, params[:comment], params[:checklist], params[:reviewComments], 'review')
- # element_submit(@root_publication)
- @root_publication.update_state(Publication::STATE_REVIEWED)
- @root_publication.process_element(Publication::STATE_REVIEWED)
- @root_publication.inform_users(Publication::STATE_REVIEWED)
- # @root_publication.element
- @embargo_pub&.refresh_embargo_metadata
- element = Entities::ReactionEntity.represent(@root_publication.element) if params[:type] == 'reaction'
- element = Entities::SampleEntity.represent(@root_publication.element) if params[:type] == 'sample'
- review_info = repo_review_info(@root_publication, current_user&.id, false)
- { "#{params[:type]}": element, review: @root_publication.review, review_info: review_info }
- end
-
- post :submit do
- save_comments(@root_publication, params[:comment], params[:checklist], params[:reviewComments], 'revision')
- element_submit(@root_publication)
- @root_publication.update_state(Publication::STATE_PENDING)
- @root_publication.process_element(Publication::STATE_PENDING)
- @root_publication.inform_users(Publication::STATE_PENDING)
- @embargo_pub&.refresh_embargo_metadata
- element = Entities::ReactionEntity.represent(@root_publication.element) if params[:type] == 'reaction'
- element = Entities::SampleEntity.represent(@root_publication.element) if params[:type] == 'sample'
- his = @root_publication.review&.slice('history') unless User.reviewer_ids.include?(current_user.id) || @root_publication.review.dig('reviewers')&.include?(current_user.id)
- review_info = repo_review_info(@root_publication, current_user&.id, false)
- { "#{params[:type]}": element, review: his || @root_publication.review, review_info: review_info }
- end
-
- post :approved do
- approve_comments(@root_publication, params[:comment], params[:checklist], params[:reviewComments], 'approved', false)
- element = Entities::ReactionEntity.represent(@root_publication.element) if params[:type] == 'reaction'
- element = Entities::SampleEntity.represent(@root_publication.element) if params[:type] == 'sample'
- his = @root_publication.review&.slice('history') unless User.reviewer_ids.include?(current_user.id) || @root_publication.review.dig('reviewers')&.include?(current_user.id)
- review_info = repo_review_info(@root_publication, current_user&.id, false)
- { "#{params[:type]}": element, review: his || @root_publication.review, review_info: review_info }
- end
-
- post :accepted do
- save_comments(@root_publication, params[:comment], params[:checklist], params[:reviewComments], 'accepted', false)
- element_submit(@root_publication)
- public_literature(@root_publication)
- # element_accepted(@root_publication)
- @root_publication.update_state(Publication::STATE_ACCEPTED)
- @root_publication.process_element(Publication::STATE_ACCEPTED)
- @root_publication.inform_users(Publication::STATE_ACCEPTED)
- @embargo_pub&.refresh_embargo_metadata
-
- element = Entities::ReactionEntity.represent(@root_publication.element) if params[:type] == 'reaction'
- element = Entities::SampleEntity.represent(@root_publication.element) if params[:type] == 'sample'
- review_info = repo_review_info(@root_publication, current_user&.id, false)
- { "#{params[:type]}": element, review: @root_publication.review, message: ENV['PUBLISH_MODE'] ? "publication on: #{ENV['PUBLISH_MODE']}" : 'publication off', review_info: review_info }
- end
- post :declined do
- save_comments(@root_publication, params[:comment], params[:checklist], params[:reviewComments], 'declined', false)
- @root_publication.update_state('declined')
- @root_publication.process_element('declined')
-
- ## TO BE HANDLED - remove from embargo collection
- @root_publication.inform_users(Publication::STATE_DECLINED, current_user.id)
- element = Entities::ReactionEntity.represent(@root_publication.element) if params[:type] == 'reaction'
- element = Entities::SampleEntity.represent(@root_publication.element) if params[:type] == 'sample'
- his = @root_publication.review&.slice('history') unless User.reviewer_ids.include?(current_user.id)
- { "#{params[:type]}": element, review: his || @root_publication.review }
+ review_advanced_search(params, current_user)
end
end
+ # desc: submit sample data for publication
namespace :publishSample do
+ helpers SubmissionHelpers
desc 'Publish Samples with chosen Dataset'
params do
- requires :sampleId, type: Integer, desc: 'Sample Id'
- requires :analysesIds, type: Array[Integer], desc: 'Selected analyses ids'
- optional :coauthors, type: Array[String], default: [], desc: 'Co-author (User)'
- optional :reviewers, type: Array[String], default: [], desc: 'reviewers (User)'
- optional :refs, type: Array[Integer], desc: 'Selected references'
- optional :embargo, type: Integer, desc: 'Embargo collection'
- requires :license, type: String, desc: 'Creative Common License'
- requires :addMe, type: Boolean, desc: 'add me as author'
+ use :publish_sample_params
end
-
after_validation do
- @sample = current_user.samples.find_by(id: params[:sampleId])
+ @sample = current_user.samples.find_by(id: params[:id])
+ error!('You do not have permission to publish this sample', 403) unless @sample
+
@analyses = @sample&.analyses&.where(id: params[:analysesIds])
- @literals = Literal.where(id: params[:refs]) unless params[:refs].nil? || params[:refs].empty?
ols_validation(@analyses)
+ error!('No analysis data available for publication', 404) if @analyses.empty?
+
@author_ids = if params[:addMe]
[current_user.id] + coauthor_validation(params[:coauthors])
else
coauthor_validation(params[:coauthors])
end
- error!('401 Unauthorized', 401) unless @sample
- error!('404 analyses not found', 404) if @analyses.empty?
- @group_reviewers = coauthor_validation(params[:reviewers])
end
-
post do
- @license = params[:license]
- @publication_tag = create_publication_tag(current_user, @author_ids, @license)
- @embargo_collection = fetch_embargo_collection(params[:embargo], current_user) if params[:embargo].present? && params[:embargo] >= 0
- pub = prepare_sample_data
- pub.process_element
- update_tag_doi(pub.element)
- if col_pub = @embargo_collection&.publication
- col_pub.refresh_embargo_metadata
- end
- pub.inform_users
-
- @sample.reload
- detail_levels = ElementDetailLevelCalculator.new(user: current_user, element: @sample).detail_levels
- {
- sample: Entities::SampleEntity.represent(@sample, detail_levels: detail_levels),
- message: ENV['PUBLISH_MODE'] ? "publication on: #{ENV['PUBLISH_MODE']}" : 'publication off'
- }
+ declared_params = declared(params, include_missing: false)
+ SubmittingJob.send(perform_method, declared_params, 'Sample', @author_ids, current_user.id)
+ send_message_and_tag(@sample, current_user)
end
put :dois do
@@ -996,211 +250,167 @@ def save_comment(root, comment)
# desc: submit reaction data for publication
namespace :publishReaction do
+ helpers SubmissionHelpers
desc 'Publish Reaction with chosen Dataset'
params do
- requires :reactionId, type: Integer, desc: 'Reaction Id'
- requires :analysesIds, type: Array[Integer], desc: 'Selected analyses ids'
- optional :coauthors, type: Array[String], default: [], desc: 'Co-author (User)'
- optional :reviewers, type: Array[String], default: [], desc: 'reviewers (User)'
- optional :refs, type: Array[Integer], desc: 'Selected references'
- optional :embargo, type: Integer, desc: 'Embargo collection'
- requires :license, type: String, desc: 'Creative Common License'
- requires :addMe, type: Boolean, desc: 'add me as author'
+ use :publish_reaction_params
end
-
after_validation do
- @scheme_only = false
- @reaction = current_user.reactions.find_by(id: params[:reactionId])
- error!('404 found no reaction to publish', 404) unless @reaction
+ @reaction = current_user.reactions.find_by(id: params[:id])
+ error!('You do not have permission to publish this reaction', 404) unless @reaction
+
@analysis_set = @reaction.analyses.where(id: params[:analysesIds]) | Container.where(id: (@reaction.samples.map(&:analyses).flatten.map(&:id) & params[:analysesIds]))
ols_validation(@analysis_set)
+ error!('No analysis data available for publication', 404) unless @analysis_set.present?
+
@author_ids = if params[:addMe]
[current_user.id] + coauthor_validation(params[:coauthors])
else
coauthor_validation(params[:coauthors])
end
- error!('404 found no analysis to publish', 404) unless @analysis_set.present?
-
- @group_reviewers = coauthor_validation(params[:reviewers])
- # error!('Reaction Publication not authorized', 401)
- @analysis_set_ids = @analysis_set.map(&:id)
- @literals = Literal.where(id: params[:refs]) unless params[:refs].nil? || params[:refs].empty?
end
-
post do
- @license = params[:license]
- @publication_tag = create_publication_tag(current_user, @author_ids, @license)
- @embargo_collection = fetch_embargo_collection(params[:embargo], current_user) if params[:embargo].present? && params[:embargo] >= 0
- pub = prepare_reaction_data
- pub.process_element
- update_tag_doi(pub.element)
- if col_pub = @embargo_collection&.publication
- col_pub.refresh_embargo_metadata
- end
- pub.inform_users
-
- @reaction.reload
- {
- reaction: Entities::ReactionEntity.represent(@reaction, serializable: true),
- message: ENV['PUBLISH_MODE'] ? "publication on: #{ENV['PUBLISH_MODE']}" : 'publication off'
- }
+ declared_params = declared(params, include_missing: false)
+ declared_params[:scheme_only] = false
+ SubmittingJob.send(perform_method, declared_params, 'Reaction', @author_ids, current_user.id)
+ send_message_and_tag(@reaction, current_user)
end
put :dois do
- reaction_products = @reaction.products.select { |s| s.analyses.select { |a| a.id.in? @analysis_set_ids }.count > 0 }
- @reaction.reserve_suffix
- reaction_products.each do |p|
- d = p.reserve_suffix
- et = p.tag
- et.update!(
- taggable_data: (et.taggable_data || {}).merge(reserved_doi: d.full_doi)
- )
- end
- @reaction.reserve_suffix_analyses(@analysis_set)
- @reaction.reload
- @reaction.tag_reserved_suffix(@analysis_set)
- @reaction.reload
- {
- reaction: Entities::ReactionEntity.represent(@reaction, serializable: true),
- message: ENV['PUBLISH_MODE'] ? "publication on: #{ENV['PUBLISH_MODE']}" : 'publication off'
- }
+ analysis_set_ids = @analysis_set&.map(&:id)
+ reserve_reaction_dois(@reaction, @analysis_set, analysis_set_ids)
end
end
- namespace :save_repo_authors do
- desc 'Save REPO authors'
+ # desc: submit reaction data (scheme only) for publication
+ namespace :publishReactionScheme do
+ helpers SubmissionHelpers
+ desc 'Publish Reaction Scheme only'
params do
- requires :elementId, type: Integer, desc: 'Element Id'
- requires :elementType, type: String, desc: 'Element Type'
- requires :taggData, type: Hash do
- requires :creators, type: Array[Hash]
- requires :affiliations, type: Hash
- requires :contributors, type: Hash
- end
+ use :publish_reaction_scheme_params
end
-
after_validation do
- unless User.reviewer_ids.include?(current_user.id)
- @pub = Publication.find_by(element_id: params[:elementId], element_type: params[:elementType], published_by: current_user.id)
- error!('404 No publication found', 404) unless @pub
+ @reaction = current_user.reactions.find_by(id: params[:id])
+ error!('You do not have permission to publish this reaction', 404) unless @reaction
+
+ @author_ids = if params[:addMe]
+ [current_user.id] + coauthor_validation(params[:coauthors])
+ else
+ coauthor_validation(params[:coauthors])
end
end
-
post do
- pub = Publication.find_by(element_id: params[:elementId], element_type: params[:elementType])
declared_params = declared(params, include_missing: false)
-
- et = ElementTag.find_or_create_by(taggable_id: declared_params[:elementId], taggable_type: declared_params[:elementType])
- tagg_data = declared_params[:taggData] || {}
-
- tagg_data['author_ids'] = tagg_data['creators']&.map { |cr| cr['id'] }
- tagg_data['affiliation_ids'] = tagg_data['creators']&.map { |cr| cr['affiliationIds'] }.flatten.uniq
- tagg_data['affiliations'] = tagg_data['affiliations']&.select { |k, _| tagg_data['affiliation_ids'].include?(k.to_i) }
-
- pub_taggable_data = pub.taggable_data || {}
- pub_taggable_data = pub_taggable_data.deep_merge(tagg_data || {})
- pub.update(taggable_data: pub_taggable_data)
-
- et_taggable_data = et.taggable_data || {}
- pub_tag = et_taggable_data['publication'] || {}
- pub_tag = pub_tag.deep_merge(tagg_data || {})
- et_taggable_data['publication'] = pub_tag
- et.update(taggable_data: et_taggable_data)
+ declared_params[:scheme_only] = true
+ SubmittingJob.send(perform_method, declared_params, 'Reaction', @author_ids, current_user.id)
+ send_message_and_tag(@reaction, current_user)
end
end
+ namespace :reviewing do
+ helpers ReviewHelpers
+ helpers RepoCommentHelpers
- # desc: submit reaction data (scheme only) for publication
- namespace :publishReactionScheme do
- desc 'Publish Reaction Scheme only'
+ desc 'process reviewed publication'
params do
- requires :reactionId, type: Integer, desc: 'Reaction Id'
- requires :temperature, type: Hash, desc: 'Temperature'
- requires :duration, type: Hash, desc: 'Duration'
- requires :products, type: Array, desc: 'Products'
- optional :coauthors, type: Array[String], default: [], desc: 'Co-author (User)'
- optional :embargo, type: Integer, desc: 'Embargo collection'
- requires :license, type: String, desc: 'Creative Common License'
- requires :addMe, type: Boolean, desc: 'add me as author'
- requires :schemeDesc, type: Boolean, desc: 'publish scheme'
+ use :reviewing_params
end
- after_validation do
- @reaction = current_user.reactions.find_by(id: params[:reactionId])
- @scheme_only = true
- error!('404 found no reaction to publish', 404) unless @reaction
- schemeYield = params[:products]&.map { |v| v.slice(:id, :_equivalent) }
- @reaction.reactions_samples.select { |rs| rs.type == 'ReactionsProductSample' }.map do |p|
- py = schemeYield.select { |o| o['id'] == p.sample_id }
- p.equivalent = py[0]['_equivalent'] if py && !py.empty?
- p.scheme_yield = py[0]['_equivalent'] if py && !py.empty?
+ helpers do
+ def process_review(action)
+ Repo::ReviewProcess.new(params, current_user.id, action).process
end
- @reaction.reactions_samples.select{ |rs| rs.type != 'ReactionsProductSample' }.map do |p|
- p.equivalent = 0
+ def extract_action
+ env['api.endpoint'].routes.first.pattern.origin[/[^\/]+$/]
end
- @reaction.name = ''
- @reaction.purification = '{}'
- @reaction.dangerous_products = '{}'
- @reaction.description = { 'ops' => [{ 'insert' => '' }] } unless params[:schemeDesc]
- @reaction.observation = { 'ops' => [{ 'insert' => '' }] }
- @reaction.tlc_solvents = ''
- @reaction.tlc_description = ''
- @reaction.rf_value = 0
- @reaction.rxno = nil
- @reaction.role = ''
- @reaction.temperature = params[:temperature]
- @reaction.duration = "#{params[:duration][:dispValue]} #{params[:duration][:dispUnit]}" unless params[:duration].nil?
- @author_ids = if params[:addMe]
- [current_user.id] + coauthor_validation(params[:coauthors])
- else
- coauthor_validation(params[:coauthors])
- end
end
- post do
- @license = params[:license]
- @publication_tag = create_publication_tag(current_user, @author_ids, @license)
- @embargo_collection = fetch_embargo_collection(params[:embargo], current_user) if params[:embargo].present? && params[:embargo] >= 0
- pub = prepare_reaction_data
- pub.process_element
- pub.inform_users
+ before do
+ @root_publication = Publication.find_by(element_type: params['type'].classify,element_id: params['id']).root
+ reviewer_auth = User.reviewer_ids.include?(current_user.id) && @root_publication.state == Publication::STATE_PENDING
+ grouplead_auth = @root_publication.review.dig('reviewers')&.include?(current_user&.id) && @root_publication.state == Publication::STATE_PENDING
+ submitter_auth = (@root_publication.published_by == current_user.id || @root_publication.review.dig('submitters')&.include?(current_user&.id)) && @root_publication.state == Publication::STATE_REVIEWED
+ error!('Unauthorized. The operation cannot proceed.', 401) unless reviewer_auth || grouplead_auth || submitter_auth
+ end
+
+ post :comment do
+ process_review(extract_action)
+ end
+
+ post :comments do
+ process_review(extract_action)
+ end
+
+ post :reviewed do
+ process_review(extract_action)
+ end
+
+ post :submit do
+ process_review(extract_action)
+ end
+
+ post :approved do
+ process_review(extract_action)
+ end
- @reaction.reload
- {
- reaction: Entities::ReactionEntity.represent(@reaction, serializable: true),
- message: ENV['PUBLISH_MODE'] ? "publication on: #{ENV['PUBLISH_MODE']}" : 'publication off'
- }
+ post :accepted do
+ process_review(extract_action)
+ end
+
+ post :declined do
+ process_review(extract_action)
end
end
- namespace :embargo do
- helpers do
- def handle_embargo_collections(col)
- col.update_columns(ancestry: current_user.published_collection.id)
- sync_emb_col = col.sync_collections_users.where(user_id: current_user.id)&.first
- sync_published_col = SyncCollectionsUser.joins("INNER JOIN collections ON collections.id = sync_collections_users.collection_id ")
- .where("collections.label='Published Elements'")
- .where("sync_collections_users.user_id = #{current_user.id}").first
- sync_emb_col.update_columns(fake_ancestry: sync_published_col.id)
- end
+ namespace :save_repo_authors do
+ helpers ReviewHelpers
+ desc 'Save REPO authors'
+ params do
+ use :save_repo_authors_params
+ end
- def remove_anonymous(col)
- anonymous_ids = col.sync_collections_users.joins("INNER JOIN users on sync_collections_users.user_id = users.id")
- .where("users.type='Anonymous'").pluck(:user_id)
- anonymous_ids.each do |anonymous_id|
- anonymous = Anonymous.find(anonymous_id)
- anonymous.sync_in_collections_users.destroy_all
- anonymous.collections.each { |c| c.really_destroy! }
- anonymous.really_destroy!
+ after_validation do
+ if User.reviewer_ids.include?(current_user.id)
+ @pub = Publication.find_by(element_id: params[:elementId], element_type: params[:elementType])
+ else
+ @pub = Publication.find_by(element_id: params[:elementId], element_type: params[:elementType], published_by: current_user.id)
+ end
+ if @pub.nil?
+ pub_tmp = Publication.find_by(element_id: params[:elementId], element_type: params[:elementType])
+ if pub_tmp&.review&.dig('submitters')&.include?(current_user.id)
+ @pub = pub_tmp
end
end
+ error!('404 No publication found', 404) unless @pub
+ end
+
+ post do
+ save_repo_authors(params, @pub, current_user)
+ end
+ end
- def remove_embargo_collection(col)
- col&.publication.really_destroy!
- col.sync_collections_users.destroy_all
- col.really_destroy!
+ namespace :save_repo_labels do
+ helpers UserLabelHelpers
+ desc 'Save REPO user labels'
+ params do
+ use :save_repo_labels_params
+ end
+ after_validation do
+ if @is_reviewer
+ @publication = Publication.find_by(element_id: params[:elementId], element_type: params[:elementType])
+ else
+ @publication = Publication.find_by(element_id: params[:elementId], element_type: params[:elementType], published_by: current_user.id)
end
+ error!('404 No publication found', 404) unless @publication
end
+ post do
+ element = @publication.element
+ update_element_labels(@publication.element, params[:user_labels], current_user.id)
+ end
+ end
+
+ namespace :embargo do
+ helpers EmbargoHelpers
desc 'Generate account with chosen Embargo'
params do
requires :collection_id, type: Integer, desc: 'Embargo Collection Id'
@@ -1208,131 +418,28 @@ def remove_embargo_collection(col)
after_validation do
@embargo_collection = Collection.find_by(id: params[:collection_id])
- error!('404 collection not found', 404) unless @embargo_collection
+ error!('Collection not found', 404) unless @embargo_collection
+
unless User.reviewer_ids.include?(current_user.id)
@sync_emb_col = @embargo_collection.sync_collections_users.where(user_id: current_user.id)&.first
- error!('404 found no collection', 404) unless @sync_emb_col
+ error!('Collection not found!', 404) unless @sync_emb_col
end
end
get :list do
- sample_list = Publication.where(ancestry: nil, element: @embargo_collection.samples).order(updated_at: :desc)
- reaction_list = Publication.where(ancestry: nil, element: @embargo_collection.reactions).order(updated_at: :desc)
- list = sample_list + reaction_list
- elements = []
- list.each do |e|
- element_type = e.element&.class&.name
- u = User.find(e.published_by) unless e.published_by.nil?
- svg_file = e.element.sample_svg_file if element_type == 'Sample'
- title = e.element.short_label if element_type == 'Sample'
-
- svg_file = e.element.reaction_svg_file if element_type == 'Reaction'
- title = e.element.short_label if element_type == 'Reaction'
-
- scheme_only = element_type == 'Reaction' && e.taggable_data && e.taggable_data['scheme_only']
- elements.push(
- id: e.element_id, svg: svg_file, type: element_type, title: title,
- published_by: u&.name, submit_at: e.created_at, state: e.state, scheme_only: scheme_only
- )
- end
- { elements: elements, embargo_id: params[:collection_id], current_user: { id: current_user.id, type: current_user.type } }
+ embargo_list(@embargo_collection, current_user)
end
post :account do
- begin
- # create Anonymous user
- name_abbreviation = "e#{SecureRandom.random_number(9999)}"
- email = "#{@embargo_collection.id}.#{name_abbreviation}@chemotion.net"
- pwd = Devise.friendly_token.first(8)
- first_name = 'External'
- last_name = 'Chemotion'
- type = 'Anonymous'
-
- params = { email: email, password: pwd, first_name: first_name, last_name: last_name, type: type, name_abbreviation: name_abbreviation, confirmed_at: Time.now }
- new_obj = User.create!(params)
- new_obj.profile.update!({data: {}})
- # sync collection with Anonymous user
- chemotion_user = User.chemotion_user
- root_label = 'with %s' %chemotion_user.name_abbreviation
- rc = Collection.find_or_create_by(user: new_obj, shared_by_id: chemotion_user.id, is_locked: true, is_shared: true, label: root_label)
-
- # Chemotion Collection
- SyncCollectionsUser.find_or_create_by(user: new_obj, shared_by_id: chemotion_user.id, collection_id: Collection.public_collection_id,
- permission_level: 0, sample_detail_level: 10, reaction_detail_level: 10, fake_ancestry: rc.id.to_s)
-
-
- SyncCollectionsUser.find_or_create_by(user: new_obj, shared_by_id: chemotion_user.id, collection_id: @embargo_collection.id,
- permission_level: 0, sample_detail_level: 10, reaction_detail_level: 10, fake_ancestry: rc.id.to_s)
-
- # send mail
- if ENV['PUBLISH_MODE'] == 'production'
- PublicationMailer.mail_external_review(current_user, @embargo_collection.label, email, pwd).deliver_now
- end
-
- { message: 'A temporary account has been created' }
- rescue StandardError => e
- { error: e.message }
- end
+ create_anonymous_user(@embargo_collection, current_user)
end
post :release do
- begin
- col_pub = @embargo_collection.publication
- if col_pub.nil? || col_pub.published_by != current_user.id
- { error: "only the owner of embargo #{@embargo_collection.label} can perform the release."}
- else
- col_pub.update(accepted_at: Time.now.utc)
- col_pub.refresh_embargo_metadata
- pub_samples = Publication.where(ancestry: nil, element: @embargo_collection.samples).order(updated_at: :desc)
- pub_reactions = Publication.where(ancestry: nil, element: @embargo_collection.reactions).order(updated_at: :desc)
- pub_list = pub_samples + pub_reactions
-
- check_state = pub_list.select { |pub| pub.state != Publication::STATE_ACCEPTED }
- if check_state.present?
- { error: "Embargo #{@embargo_collection.label} release failed, because not all elements have been 'accepted'."}
- else
- scheme_only_list = pub_list.select { |pub| pub.taggable_data['scheme_only'] == true }
- if pub_list.flatten.length == scheme_only_list.flatten.length
- col_pub.update(state: 'scheme_only')
- else
- col_pub.update(state: 'accepted')
- end
-
- pub_list.each { |pub| element_submit(pub) }
- remove_anonymous(@embargo_collection)
- handle_embargo_collections(@embargo_collection)
- case ENV['PUBLISH_MODE']
- when 'production'
- if Rails.env.production?
- ChemotionEmbargoPubchemJob.set(queue: "publishing_embargo_#{@embargo_collection.id}").perform_later(@embargo_collection.id)
- end
- when 'staging'
- ChemotionEmbargoPubchemJob.perform_now(@embargo_collection.id)
- else 'development'
- end
-
-
- { message: "Embargo #{@embargo_collection.label} has been released" }
- end
- end
- rescue StandardError => e
- { error: e.message }
- end
+ Repo::EmbargoHandler.release_embargo(@embargo_collection.id, current_user.id)
end
post :delete do
- begin
- element_cnt = @embargo_collection.samples.count + @embargo_collection.reactions.count
- if element_cnt.positive?
- { error: "Delete Embargo #{@embargo_collection.label} deletion failed: the collection is not empty. Please refresh your page."}
- else
- remove_anonymous(@embargo_collection)
- remove_embargo_collection(@embargo_collection)
- { message: "Embargo #{@embargo_collection.label} has been deleted" }
- end
- rescue StandardError => e
- { error: e.message }
- end
+ Repo::EmbargoHandler.delete(@embargo_collection.id, current_user.id)
end
post :refresh do
@@ -1343,30 +450,7 @@ def remove_embargo_collection(col)
post :move do
begin
- # @new_embargo = params[:new_embargo]
- @element = params[:element]
- @new_embargo_collection = fetch_embargo_collection(params[:new_embargo]&.to_i, current_user) if params[:new_embargo].present? && params[:new_embargo]&.to_i >= 0
- case @element['type']
- when 'Sample'
- CollectionsSample
- when 'Reaction'
- CollectionsReaction
- end.remove_in_collection(@element['id'], [@embargo_collection.id])
-
- case @element['type']
- when 'Sample'
- CollectionsSample
- when 'Reaction'
- CollectionsReaction
- end.create_in_collection(@element['id'], [@new_embargo_collection.id])
-
- @embargo_collection&.publication&.refresh_embargo_metadata
- @new_embargo_collection&.publication&.refresh_embargo_metadata
-
- { col_id: @embargo_collection.id,
- new_embargo: @new_embargo_collection.publication,
- is_new_embargo: params[:new_embargo]&.to_i == 0,
- message: "#{@element['type']} [#{@element['title']}] has been moved from Embargo Bundle [#{@embargo_collection.label}] to Embargo Bundle [#{@new_embargo_collection.label}]" }
+ move_embargo(@embargo_collection, params, current_user)
rescue StandardError => e
{ error: e.message }
end
diff --git a/app/api/chemotion/sample_api.rb b/app/api/chemotion/sample_api.rb
index 2ab411638..8477c8d90 100644
--- a/app/api/chemotion/sample_api.rb
+++ b/app/api/chemotion/sample_api.rb
@@ -173,6 +173,7 @@ class SampleAPI < Grape::API
optional :molecule_sort, type: Integer, desc: 'Sort by parameter'
optional :from_date, type: Integer, desc: 'created_date from in ms'
optional :to_date, type: Integer, desc: 'created_date to in ms'
+ optional :user_label, type: Integer, desc: 'user label'
optional :filter_created_at, type: Boolean, desc: 'filter by created at or updated at'
optional :product_only, type: Boolean, desc: 'query only reaction products'
end
@@ -220,13 +221,14 @@ class SampleAPI < Grape::API
end
from = params[:from_date]
to = params[:to_date]
+ user_label = params[:user_label]
by_created_at = params[:filter_created_at] || false
sample_scope = sample_scope.created_time_from(Time.zone.at(from)) if from && by_created_at
sample_scope = sample_scope.created_time_to(Time.zone.at(to) + 1.day) if to && by_created_at
sample_scope = sample_scope.updated_time_from(Time.zone.at(from)) if from && !by_created_at
sample_scope = sample_scope.updated_time_to(Time.zone.at(to) + 1.day) if to && !by_created_at
-
+ sample_scope = sample_scope.by_user_label(user_label) if user_label
sample_list = []
if params[:molecule_sort] == 1
@@ -247,7 +249,7 @@ class SampleAPI < Grape::API
end
else
reset_pagination_page(sample_scope)
- sample_scope = sample_scope.order('updated_at DESC')
+ sample_scope = sample_scope.order('samples.updated_at DESC')
paginate(sample_scope).each do |sample|
detail_levels = ElementDetailLevelCalculator.new(user: current_user, element: sample).detail_levels
sample_list.push(
@@ -440,6 +442,7 @@ class SampleAPI < Grape::API
optional :melting_point_lowerbound, type: Float, desc: 'lower bound of sample melting point'
optional :residues, type: Array
optional :segments, type: Array
+ optional :user_labels, type: Array
optional :elemental_compositions, type: Array
optional :xref, type: Hash
optional :stereo, type: Hash do
@@ -520,6 +523,7 @@ class SampleAPI < Grape::API
)
end
attributes.delete(:segments)
+ attributes.delete(:user_labels)
sample = Sample.new(attributes)
@@ -545,6 +549,7 @@ class SampleAPI < Grape::API
sample.container = update_datamodel(params[:container])
sample.save!
+ update_element_labels(sample, params[:user_labels], current_user.id)
sample.save_segments(segments: params[:segments], current_user_id: current_user.id)
diff --git a/app/api/chemotion/suggestion_api.rb b/app/api/chemotion/suggestion_api.rb
index 0d95a5ebc..97f0fa855 100644
--- a/app/api/chemotion/suggestion_api.rb
+++ b/app/api/chemotion/suggestion_api.rb
@@ -175,10 +175,10 @@ def search_possibilities_by_type_user_and_collection(type)
requirements: requirements,
}
when 'cell_lines'
- dl_cl.positive? ? search_for_celllines : []
+ dl_cl&.positive? ? search_for_celllines : []
else
chemotion_id = suggest_pid(qry)
- element_short_label = (dl_e.positive? && search_by_element_short_label.call(Labimotion::Element, qry)) || []
+ element_short_label = (dl_e&.positive? && search_by_element_short_label.call(Labimotion::Element, qry)) || []
sample_name = (dl_s.positive? && search_by_field.call(Sample, :name, qry)) || []
sample_short_label = (dl_s.positive? && search_by_field.call(Sample, :short_label, qry)) || []
sample_external_label = (dl_s > -1 && search_by_field.call(Sample, :external_label, qry)) || []
@@ -200,7 +200,7 @@ def search_possibilities_by_type_user_and_collection(type)
screen_name = (dl_sc > -1 && search_by_field.call(Screen, :name, qry)) || []
conditions = (dl_sc > -1 && search_by_field.call(Screen, :conditions, qry)) || []
requirements = (dl_sc > -1 && search_by_field.call(Screen, :requirements, qry)) || []
- cell_line_infos = dl_cl.positive? ? search_for_celllines : []
+ cell_line_infos = dl_cl&.positive? ? search_for_celllines : {}
{
element_short_label: element_short_label,
diff --git a/app/api/chemotion/ui_api.rb b/app/api/chemotion/ui_api.rb
index f4b15ffa3..7883d33b8 100644
--- a/app/api/chemotion/ui_api.rb
+++ b/app/api/chemotion/ui_api.rb
@@ -41,6 +41,7 @@ def load_x_config
has_radar: radar_config.present?,
molecule_viewer: Matrice.molecule_viewer,
collector_address: collector_address.presence,
+ u: Rails.configuration.u || {},
x: load_x_config,
}
end
diff --git a/app/api/chemotion/user_api.rb b/app/api/chemotion/user_api.rb
index 46bd7c96c..ff899df8c 100644
--- a/app/api/chemotion/user_api.rb
+++ b/app/api/chemotion/user_api.rb
@@ -26,8 +26,7 @@ class UserAPI < Grape::API
desc 'list user labels'
get 'list_labels' do
- labels = UserLabel.where('user_id = ? or access_level >= 1', current_user.id)
- .order('access_level desc, position, title')
+ labels = UserLabel.my_labels(current_user, false)
present labels || [], with: Entities::UserLabelEntity, root: 'labels'
end
diff --git a/app/api/entities/reaction_entity.rb b/app/api/entities/reaction_entity.rb
index 262395fca..d4d021020 100644
--- a/app/api/entities/reaction_entity.rb
+++ b/app/api/entities/reaction_entity.rb
@@ -101,7 +101,7 @@ def type
end
def comment_count
- object.comments.count
+ 0 # object.comments.count
end
def variations
diff --git a/app/api/entities/research_plan_entity.rb b/app/api/entities/research_plan_entity.rb
index 6934c9078..539685b3d 100644
--- a/app/api/entities/research_plan_entity.rb
+++ b/app/api/entities/research_plan_entity.rb
@@ -53,7 +53,7 @@ def wellplates
end
def comment_count
- object.comments.count
+ 0 # object.comments.count
end
end
end
diff --git a/app/api/entities/sample_entity.rb b/app/api/entities/sample_entity.rb
index 352662dcd..027dece4a 100644
--- a/app/api/entities/sample_entity.rb
+++ b/app/api/entities/sample_entity.rb
@@ -82,14 +82,6 @@ class SampleEntity < ApplicationEntity
expose_timestamps
expose :is_repo_public
- expose :labels
-
- def labels
- return [] if object.tag&.taggable_data&.nil?
-
- label_ids = object.tag.taggable_data['user_labels'] || []
- UserLabel.public_labels(label_ids) || []
- end
# expose :molecule, using: Entities::MoleculeEntity
# expose :container, using: Entities::ContainerEntity
@@ -156,7 +148,7 @@ def type
end
def comment_count
- object.comments.count
+ 0 # object.comments.count
end
end
end
diff --git a/app/api/entities/screen_entity.rb b/app/api/entities/screen_entity.rb
index 6ac20babf..f52588d2a 100644
--- a/app/api/entities/screen_entity.rb
+++ b/app/api/entities/screen_entity.rb
@@ -60,7 +60,7 @@ def wellplates
end
def comment_count
- object.comments.count
+ 0 # object.comments.count
end
end
end
diff --git a/app/api/entities/wellplate_entity.rb b/app/api/entities/wellplate_entity.rb
index 9d8fe55df..e00745be8 100644
--- a/app/api/entities/wellplate_entity.rb
+++ b/app/api/entities/wellplate_entity.rb
@@ -55,7 +55,7 @@ def type
end
def comment_count
- object.comments.count
+ 0 # object.comments.count
end
end
end
diff --git a/app/api/helpers/embargo_helpers.rb b/app/api/helpers/embargo_helpers.rb
index 55c11ec4f..ce3e8bef5 100644
--- a/app/api/helpers/embargo_helpers.rb
+++ b/app/api/helpers/embargo_helpers.rb
@@ -4,39 +4,137 @@
module EmbargoHelpers
extend Grape::API::Helpers
- def fetch_embargo_collection(cid, current_user)
- if (cid == 0)
- chemotion_user = User.chemotion_user
- new_col_label = current_user.initials + '_' + Time.now.strftime('%Y-%m-%d')
- col_check = Collection.where([' label like ? ', new_col_label + '%'])
- new_col_label = new_col_label << '_' << (col_check&.length + 1)&.to_s if col_check&.length.positive?
- new_embargo_col = Collection.create!(user: chemotion_user, label: new_col_label, ancestry: current_user.publication_embargo_collection.id)
- SyncCollectionsUser.find_or_create_by(user: current_user, shared_by_id: chemotion_user.id, collection_id: new_embargo_col.id,
- permission_level: 0, sample_detail_level: 10, reaction_detail_level: 10,
- fake_ancestry: current_user.publication_embargo_collection.sync_collections_users.first.id.to_s)
- #embargo = Embargo.create!(name: new_embargo_col.label, collection_id: new_embargo_col.id, created_by: current_user.id)
- d = Doi.create_for_element!(new_embargo_col)
- Publication.create!(
- state: Publication::STATE_PENDING,
- element: new_embargo_col,
- published_by: current_user.id,
- doi: d,
- taggable_data: { label: new_embargo_col.label, col_doi: d.full_doi }
- )
- new_embargo_col
+
+ def ext_embargo_list(user_id)
+ Publication.where(element_type: 'Collection')
+ .where.not(state: 'completed')
+ .where("review -> 'submitters' @> ?", user_id.to_s)
+ .pluck(:element_id)
+ end
+
+ ## Get embargo selection list for submission
+ def embargo_select_list(is_submit, current_user, is_reviewer, is_embargo_viewer)
+ is_submitter = false
+ if (is_reviewer || is_embargo_viewer) && is_submit == false
+ es = Publication.where(element_type: 'Collection', state: 'pending').order(Arel.sql("taggable_data->>'label' ASC"))
else
- Collection.find(cid)
+ is_submitter = current_user.type == 'Anonymous' ? false : true
+ cols = if current_user.type == 'Anonymous'
+ Collection.where(id: current_user.sync_in_collections_users.pluck(:collection_id)).where.not(label: 'chemotion')
+ else
+ ext_col_ids = ext_embargo_list(current_user.id) || []
+ Collection.where(ancestry: current_user.publication_embargo_collection.id).or(Collection.where(id: ext_col_ids))
+ end
+ es = Publication.where(element_type: 'Collection', element_id: cols.pluck(:id)).order(Arel.sql("taggable_data->>'label' ASC")) unless cols&.empty?
end
+ { repository: es, current_user: { id: current_user.id, type: current_user.type, is_reviewer: is_reviewer, is_submitter: is_submitter } }
+ rescue StandardError => e
+ Publication.repo_log_exception(e, { is_submit: is_submit, user_id: current_user&.id, is_reviewer: is_reviewer, is_embargo_viewer: is_embargo_viewer })
+ { error: e.message }
+ end
+
+ def assign_embargo(element, embargo, current_user, is_new = false)
+ case element['type']
+ when 'Sample'
+ CollectionsSample
+ when 'Reaction'
+ CollectionsReaction
+ end.create_in_collection(element['id'], [embargo.id])
+ { element: element, new_embargo: embargo,
+ is_new_embargo: is_new,
+ message: "#{element['type']} [#{element['title']}] has been assigned to Embargo Bundle [#{embargo.label}]" }
+ rescue StandardError => e
+ Publication.repo_log_exception(e, { element: element&.id, user_id: current_user&.id, is_new: is_new })
+ { error: e.message }
end
+ def embargo_list(embargo_collection, current_user)
+ sample_list = Publication.where(ancestry: nil, element: embargo_collection.samples).order(updated_at: :desc)
+ reaction_list = Publication.where(ancestry: nil, element: embargo_collection.reactions).order(updated_at: :desc)
+ list = sample_list + reaction_list
+ elements = []
+ list.each do |e|
+ element_type = e.element&.class&.name
+ u = User.with_deleted.find(e.published_by) unless e.published_by.nil?
+ svg_file = e.element.sample_svg_file if element_type == 'Sample'
+ title = e.element.short_label if element_type == 'Sample'
+ svg_file = e.element.reaction_svg_file if element_type == 'Reaction'
+ title = e.element.short_label if element_type == 'Reaction'
- def find_embargo_collection(root_publication)
- has_embargo_col = root_publication.element&.collections&.select { |c| c['ancestry'].to_i == User.find(root_publication.published_by).publication_embargo_collection.id }
- has_embargo_col && has_embargo_col.length > 0 ? has_embargo_col.first : OpenStruct.new(label: '')
+ scheme_only = element_type == 'Reaction' && e.taggable_data && e.taggable_data['scheme_only']
+ elements.push(
+ id: e.element_id, svg: svg_file, type: element_type, title: title,
+ published_by: u&.name, submit_at: e.created_at, state: e.state, scheme_only: scheme_only
+ )
+ end
+ { elements: elements, embargo_id: params[:collection_id], current_user: { id: current_user.id, type: current_user.type } }
+ rescue StandardError => e
+ Publication.repo_log_exception(e, { embargo_collection: embargo_collection&.id, user_id: current_user&.id })
+ { error: e.message }
end
- def create_embargo()
+ def create_anonymous_user(embargo_collection, current_user)
+ name_abbreviation = "e#{SecureRandom.random_number(9999)}"
+ email = "#{embargo_collection.id}.#{name_abbreviation}@chemotion.net"
+ pwd = Devise.friendly_token.first(8)
+ first_name = 'External'
+ last_name = 'Chemotion'
+ type = 'Anonymous'
+
+ params = { email: email, password: pwd, first_name: first_name, last_name: last_name, type: type, name_abbreviation: name_abbreviation, confirmed_at: Time.now }
+ new_obj = User.create!(params)
+ new_obj.profile.update!({data: {}})
+ # sync collection with Anonymous user
+ chemotion_user = User.chemotion_user
+ root_label = 'with %s' %chemotion_user.name_abbreviation
+ rc = Collection.find_or_create_by(user: new_obj, shared_by_id: chemotion_user.id, is_locked: true, is_shared: true, label: root_label)
+
+ # Chemotion Collection
+ SyncCollectionsUser.find_or_create_by(user: new_obj, shared_by_id: chemotion_user.id, collection_id: Collection.public_collection_id,
+ permission_level: 0, sample_detail_level: 10, reaction_detail_level: 10, fake_ancestry: rc.id.to_s)
+
+
+ SyncCollectionsUser.find_or_create_by(user: new_obj, shared_by_id: chemotion_user.id, collection_id: embargo_collection.id,
+ permission_level: 0, sample_detail_level: 10, reaction_detail_level: 10, fake_ancestry: rc.id.to_s)
+
+ # send mail
+ if ENV['PUBLISH_MODE'] == 'production'
+ PublicationMailer.mail_external_review(current_user, embargo_collection.label, email, pwd).deliver_now
+ end
+
+ { message: 'A temporary account has been created' }
+ rescue StandardError => e
+ Publication.repo_log_exception(e, { embargo_collection: embargo_collection&.id, user_id: current_user&.id })
+ { error: e.message }
+ end
+
+ def move_embargo(embargo_collection, params, current_user)
+ element = params[:element]
+ new_embargo_collection = Repo::EmbargoHandler.find_or_create_embargo(params[:new_embargo]&.to_i, current_user) if params[:new_embargo].present? && params[:new_embargo]&.to_i >= 0
+ case element['type']
+ when 'Sample'
+ CollectionsSample
+ when 'Reaction'
+ CollectionsReaction
+ end.remove_in_collection(element['id'], [embargo_collection.id])
+
+ case element['type']
+ when 'Sample'
+ CollectionsSample
+ when 'Reaction'
+ CollectionsReaction
+ end.create_in_collection(element['id'], [new_embargo_collection.id])
+
+ embargo_collection&.publication&.refresh_embargo_metadata
+ new_embargo_collection&.publication&.refresh_embargo_metadata
+ { col_id: embargo_collection.id,
+ new_embargo: new_embargo_collection.publication,
+ is_new_embargo: params[:new_embargo]&.to_i == 0,
+ message: "#{element['type']} [#{element['title']}] has been moved from Embargo Bundle [#{embargo_collection.label}] to Embargo Bundle [#{new_embargo_collection.label}]" }
+ rescue StandardError => e
+ Publication.repo_log_exception(e, { embargo_collection: embargo_collection&.id, params: params, user_id: current_user&.id })
+ { error: e.message }
end
end
diff --git a/app/api/helpers/gate_helpers.rb b/app/api/helpers/gate_helpers.rb
index 21d58193c..2647e1139 100644
--- a/app/api/helpers/gate_helpers.rb
+++ b/app/api/helpers/gate_helpers.rb
@@ -3,7 +3,7 @@ module GateHelpers
def prepare_for_receiving(request)
http_token = (request.headers['Authorization'].split(' ').last if request.headers['Authorization'].present?) # rubocop: disable Style/RedundantArgument
- error!('Unauthorized', 401) unless http_token
+ error!('Unauthorized, token not found', 401) unless http_token
secret = Rails.application.secrets.secret_key_base
begin
@auth_token = ActiveSupport::HashWithIndifferentAccess.new(
@@ -14,11 +14,11 @@ def prepare_for_receiving(request)
error!("#{e}", 401)
end
@user = Person.find_by(email: @auth_token[:iss])
- error!('Unauthorized', 401) unless @user
+ error!('Unauthorized, user not found', 401) unless @user
@collection = Collection.find_by(
id: @auth_token[:collection], user_id: @user.id, is_shared: false,
)
- error!('Unauthorized access to collection', 401) unless @collection
+ error!('Unauthorized, can not access to collection', 401) unless @collection
@origin = @auth_token["origin"]
[@user, @collection, @origin]
rescue StandardError => e
@@ -32,7 +32,7 @@ def save_chunk(user_id, col_id, params)
tempfile = params[:chunk]&.fetch('tempfile', nil)
if tempfile
File.open(filename, 'ab') { |file| file.write(tempfile.read) }
- end
+ end
filename
rescue StandardError => e
log_exception('save_chunk', e, user_id)
@@ -42,9 +42,9 @@ def save_chunk(user_id, col_id, params)
tempfile&.unlink
end
-
+
def log_exception(func_name, exception, user_id = nil)
- transfer_logger.error("[#{DateTime.now}] [#{func_name}] user: [#{user_id}] \n Exception: #{exception.message}")
+ transfer_logger.error("[#{DateTime.now}] [#{func_name}] user: [#{user_id}] \n Exception: #{exception.message}")
transfer_logger.error(exception.backtrace.join("\n"))
end
diff --git a/app/api/helpers/public_helpers.rb b/app/api/helpers/public_helpers.rb
index d19226fbb..f561f225d 100644
--- a/app/api/helpers/public_helpers.rb
+++ b/app/api/helpers/public_helpers.rb
@@ -5,6 +5,167 @@ module PublicHelpers
extend Grape::API::Helpers
include ApplicationHelper
+ def get_pub_reaction(id)
+ reaction = Reaction.where('id = ?', id)
+ .select(
+ <<~SQL
+ reactions.id, reactions.name, reactions.description, reactions.reaction_svg_file, reactions.short_label,
+ reactions.status, reactions.tlc_description, reactions.tlc_solvents, reactions.rf_value,
+ reactions.temperature, reactions.timestamp_start,reactions.timestamp_stop,reactions.observation,
+ reactions.rinchi_string, reactions.rinchi_long_key, reactions.rinchi_short_key,reactions.rinchi_web_key,
+ (select label from publication_collections where (elobj ->> 'element_type')::text = 'Reaction' and (elobj ->> 'element_id')::integer = reactions.id) as embargo,
+ (select json_extract_path(taggable_data::json, 'publication') from publications where element_type = 'Reaction' and element_id = reactions.id) as publication,
+ reactions.duration
+ SQL
+ )
+ .includes(
+ container: :attachments
+ ).last
+ literatures = Repo::FetchHandler.literatures_by_cat(reaction.id,'Reaction') || []
+ reaction.products.each do |p|
+ literatures += Repo::FetchHandler.literatures_by_cat(p.id,'Sample')
+ end
+
+ pub = Publication.find_by(element_type: 'Reaction', element_id: reaction.id)
+ pub_info = (pub.review.present? && pub.review['info'].present? && pub.review['info']['comment']) || ''
+ infos = {}
+ ana_infos = {}
+ pd_infos = {}
+
+ pub.state != Publication::STATE_COMPLETED && pub.descendants.each do |pp|
+ review = pp.review || {}
+ info = review['info'] || {}
+ next if info.empty?
+ if pp.element_type == 'Sample'
+ pd_infos[pp.element_id] = info['comment']
+ else
+ ana_infos[pp.element_id] = info['comment']
+ end
+ end
+
+ schemeList = Repo::FetchHandler.get_reaction_table(reaction.id)
+ entities = Entities::RepoReactionEntity.represent(reaction, serializable: true)
+ entities[:products].each do |p|
+ label_ids = p[:tag]['taggable_data']['user_labels'] || [] unless p[:tag]['taggable_data'].nil?
+ p[:labels] = UserLabel.public_labels(label_ids, current_user, pub.state == Publication::STATE_COMPLETED) unless label_ids.nil?
+ pub_product = p
+ p[:xvialCom] = build_xvial_com(p[:molecule][:inchikey], current_user&.id)
+ pub_product_tag = pub_product[:tag]['taggable_data']
+ next if pub_product_tag.nil?
+
+ xvial = pub_product_tag['xvial'] && pub_product_tag['xvial']['num']
+ next unless xvial.present?
+
+ unless current_user.present? && User.reviewer_ids.include?(current_user.id)
+ pub_product_tag['xvial']['num'] = 'x'
+ end
+ p[:xvialCom][:hasSample] = true
+ end
+ label_ids = (pub.taggable_data && pub.taggable_data['user_labels']) || []
+ labels = UserLabel.public_labels(label_ids, current_user, pub.state == Publication::STATE_COMPLETED) unless label_ids.nil?
+
+ entities[:publication]['review']['history'] = []
+ entities[:publication]['review'] = nil if pub.state === Publication::STATE_COMPLETED
+ entities[:literatures] = literatures unless entities.nil? || literatures.nil? || literatures.length == 0
+ entities[:schemes] = schemeList unless entities.nil? || schemeList.nil? || schemeList.length == 0
+ entities[:isLogin] = current_user.present?
+ entities[:embargo] = reaction.embargo
+ entities[:labels] = labels
+ entities[:infos] = { pub_info: pub_info, pd_infos: pd_infos, ana_infos: ana_infos }
+ entities[:isReviewer] = current_user.present? && User.reviewer_ids.include?(current_user.id) ? true : false
+ entities[:elementType] = 'reaction'
+ entities[:segments] = Labimotion::SegmentEntity.represent(reaction.segments)
+ entities
+ end
+
+ def get_pub_molecule(id, adv_flag=nil, adv_type=nil, adv_val=nil, label_val=nil)
+ molecule = Molecule.find(id)
+ xvial_com = build_xvial_com(molecule.inchikey, current_user&.id)
+ pub_id = Collection.public_collection_id
+ if adv_flag.present? && adv_flag == true && adv_type.present? && adv_type == 'Authors' && adv_val.present?
+ adv = <<~SQL
+ INNER JOIN publication_authors rs on rs.element_id = samples.id and rs.element_type = 'Sample' and rs.state = 'completed'
+ and rs.author_id in ('#{adv_val.join("','")}')
+ SQL
+ else
+ adv = ''
+ end
+
+ pub_samples = Collection.public_collection.samples
+ .includes(:molecule,:tag).where("samples.molecule_id = ?", molecule.id)
+ .where(
+ <<~SQL
+ samples.id in (
+ SELECT samples.id FROM samples
+ INNER JOIN collections_samples cs on cs.collection_id = #{pub_id} and cs.sample_id = samples.id and cs.deleted_at ISNULL
+ INNER JOIN publications pub on pub.element_type='Sample' and pub.element_id=samples.id and pub.deleted_at ISNULL
+ #{adv}
+ )
+ SQL
+ )
+ .select(
+ <<~SQL
+ samples.*, (select published_at from publications where element_type='Sample' and element_id=samples.id and deleted_at is null) as published_at
+ SQL
+ )
+ .order('published_at desc')
+ published_samples = pub_samples.map do |s|
+ container = Entities::ContainerEntity.represent(s.container)
+ tag = s.tag.taggable_data['publication']
+ #u = User.find(s.tag.taggable_data['publication']['published_by'].to_i)
+ #time = DateTime.parse(s.tag.taggable_data['publication']['published_at'])
+ #published_time = time.strftime("%A, %B #{time.day.ordinalize} %Y %H:%M")
+ #aff = u.affiliations.first
+ next unless tag
+ literatures = Literature.by_element_attributes_and_cat(s.id, 'Sample', 'public')
+ .joins("inner join users on literals.user_id = users.id")
+ .select(
+ <<~SQL
+ literatures.*,
+ json_object_agg(literals.id, literals.litype) as litype,
+ json_object_agg(literals.id, users.first_name || chr(32) || users.last_name) as ref_added_by
+ SQL
+ ).group('literatures.id').as_json
+ reaction_ids = ReactionsProductSample.where(sample_id: s.id).pluck(:reaction_id)
+ pub = Publication.find_by(element_type: 'Sample', element_id: s.id)
+ sid = pub.taggable_data["sid"] unless pub.nil? || pub.taggable_data.nil?
+ label_ids = s.tag.taggable_data['user_labels'] || [] unless s.tag.taggable_data.nil?
+ user_labels = UserLabel.public_labels(label_ids, current_user, pub.state == Publication::STATE_COMPLETED) unless label_ids.nil?
+ xvial = s.tag.taggable_data['xvial'] && s.tag.taggable_data['xvial']['num'] unless s.tag.taggable_data.nil?
+ if xvial.present?
+ unless current_user.present? && User.reviewer_ids.include?(current_user.id)
+ xvial = 'x'
+ end
+ end
+ comp_num = s.tag.taggable_data['xvial'] && s.tag.taggable_data['xvial']['comp_num'] unless s.tag.taggable_data.nil?
+ pub_info = (pub.review.present? && pub.review['info'].present? && pub.review['info']['comment']) || ''
+ ana_infos = {}
+ pub.descendants.each do |pp|
+ review = pp.review || {}
+ info = review['info'] || {}
+ next if info.empty?
+ ana_infos[pp.element_id] = info['comment']
+ end
+ embargo = PublicationCollections.where("(elobj ->> 'element_type')::text = 'Sample' and (elobj ->> 'element_id')::integer = #{s.id}")&.first&.label
+ segments = Labimotion::SegmentEntity.represent(s.segments)
+ tag.merge(container: container, literatures: literatures, sample_svg_file: s.sample_svg_file, short_label: s.short_label, melting_point: s.melting_point, boiling_point: s.boiling_point,
+ sample_id: s.id, reaction_ids: reaction_ids, sid: sid, xvial: xvial, comp_num: comp_num, embargo: embargo, labels: user_labels,
+ showed_name: s.showed_name, pub_id: pub.id, ana_infos: ana_infos, pub_info: pub_info, segments: segments, published_at: pub.published_at,
+ molecular_mass: s.molecular_mass, sum_formula: s.sum_formula, decoupled: s.decoupled)
+ end
+ x = published_samples.select { |s| s[:xvial].present? }
+ xvial_com[:hasSample] = x.length.positive?
+ published_samples = published_samples.flatten.compact
+ {
+ molecule: MoleculeGuestSerializer.new(molecule).serializable_hash.deep_symbolize_keys,
+ published_samples: published_samples,
+ isLogin: current_user.nil? ? false : true,
+ isReviewer: (current_user.present? && User.reviewer_ids.include?(current_user.id)) ? true : false,
+ xvialCom: xvial_com,
+ elementType: 'molecule'
+ }
+ end
+
def send_notification(attachment, user, status, has_error = false)
data_args = { 'filename': attachment.filename, 'comment': 'the file has been updated' }
level = 'success'
@@ -32,14 +193,18 @@ def de_encode_json(json, key = '', viv = '', encode = true)
end
end
- def convert_to_3d(molfile)
+ def represent_structure(molfile)
molecule_viewer = Matrice.molecule_viewer
- if molecule_viewer.blank? || molecule_viewer[:chembox_endpoint].blank?
+ if molecule_viewer.blank? || molecule_viewer[:chembox].blank?
{ molfile: molfile }
else
options = { timeout: 10, body: { mol: molfile }.to_json, headers: { 'Content-Type' => 'application/json' } }
- response = HTTParty.post(molecule_viewer[:chembox_endpoint], options)
- response.code == 200 ? { molfile: response.parsed_response } : { molfile: molfile }
+ response = HTTParty.post("#{molecule_viewer[:chembox]}/core/rdkit/v1/structure", options)
+ if response.code == 200
+ { molfile: (response.parsed_response && response.parsed_response['molfile']) || molfile }
+ else
+ { molfile: molfile }
+ end
end
end
diff --git a/app/api/helpers/repo_comment_helpers.rb b/app/api/helpers/repo_comment_helpers.rb
new file mode 100644
index 000000000..38a172670
--- /dev/null
+++ b/app/api/helpers/repo_comment_helpers.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+# A helper for reviewing/submission
+module RepoCommentHelpers
+ extend Grape::API::Helpers
+ def approve_comments(root, comment, _checklist, _reviewComments, _action, _his = true)
+ review = root.review || {}
+ review_history = review['history'] || []
+ current = review_history.last
+ current['username'] = current_user.name
+ current['userid'] = current_user.id
+ current['action'] = 'pre-approved'
+ current['comment'] = comment unless comment.nil?
+ current['timestamp'] = Time.now.strftime('%d-%m-%Y %H:%M:%S')
+ review_history[review_history.length - 1] = current
+ next_node = { action: 'reviewing', type: 'reviewed', state: 'pending' }
+ review_history << next_node
+ review['history'] = review_history
+ revst = review['checklist'] || {}
+ revst['glr'] = { status: true, user: current_user.name, updated_at: Time.now.strftime('%d-%m-%Y %H:%M:%S') }
+ review['checklist'] = revst
+
+ root.update!(review: review)
+ end
+
+ # def save_comments(root, comment, checklist, reviewComments, action, his = true)
+ # review = root.review || {}
+ # review_history = review['history'] || []
+ # current = review_history.last || {}
+ # current['state'] = %w[accepted declined].include?(action) ? action : root.state
+ # current['action'] = action unless action.nil?
+ # current['username'] = current_user.name
+ # current['userid'] = current_user.id
+ # current['comment'] = comment unless comment.nil?
+ # current['type'] = root.state == Publication::STATE_PENDING ? 'reviewed' : 'submit'
+ # current['timestamp'] = Time.now.strftime('%d-%m-%Y %H:%M:%S')
+
+ # if review_history.length == 0
+ # review_history[0] = current
+ # else
+ # review_history[review_history.length - 1] = current
+ # end
+ # if his ## add next_node
+ # next_node = { action: 'revising', type: 'submit', state: 'reviewed' } if root.state == Publication::STATE_PENDING
+ # next_node = { action: 'reviewing', type: 'reviewed', state: 'pending' } if root.state == Publication::STATE_REVIEWED
+ # review_history << next_node
+ # review['history'] = review_history
+ # else
+
+ # # is_leader = review.dig('reviewers')&.include?(current_user&.id)
+ # if root.state == Publication::STATE_PENDING && (action.nil? || action == Publication::STATE_REVIEWED)
+ # next_node = { action: 'reviewing', type: 'reviewed', state: 'pending' }
+ # review_history << next_node
+ # review['history'] = review_history
+ # end
+ # end
+ # if checklist&.length&.positive?
+ # revst = review['checklist'] || {}
+ # checklist.each do |k, v|
+ # revst[k] = v['status'] == true ? { status: v['status'], user: current_user.name, updated_at: Time.now.strftime('%d-%m-%Y %H:%M:%S') } : { status: false } unless revst[k] && revst[k]['status'] == v['status']
+ # end
+ # review['checklist'] = revst
+ # end
+ # review['reviewComments'] = reviewComments if reviewComments.present?
+ # root.update!(review: review)
+ # end
+
+ # TODO: mv to model
+ def save_comment(root, comment)
+ review = root.review || {}
+ review_history = review['history'] || []
+ current = review_history.last
+ comments = current['comments'] || {}
+ comment[comment.keys[0]]['timestamp'] = Time.now.strftime('%d-%m-%Y %H:%M:%S') unless comment.keys.empty?
+ comment[comment.keys[0]]['username'] = current_user.name
+ comment[comment.keys[0]]['userid'] = current_user.id
+
+ current['comments'] = comments.deep_merge(comment || {})
+ review['history'] = review_history
+ root.update!(review: review)
+ end
+end
\ No newline at end of file
diff --git a/app/api/helpers/repo_params_helpers.rb b/app/api/helpers/repo_params_helpers.rb
new file mode 100644
index 000000000..18250997a
--- /dev/null
+++ b/app/api/helpers/repo_params_helpers.rb
@@ -0,0 +1,88 @@
+module RepoParamsHelpers
+ extend Grape::API::Helpers
+
+ params :get_review_list_params do
+ requires :type, type: String, desc: 'Search Type', values: %w[All Samples Reactions]
+ requires :state, type: String, desc: 'Publication State', values: %w[All pending reviewed accepted]
+ optional :label, type: Integer, desc: 'User label'
+ optional :search_type, type: String, desc: 'search type', values: %w[All Name Embargo Submitter]
+ optional :search_value, type: String, desc: 'search value'
+ optional :page, type: Integer, desc: 'page'
+ optional :pages, type: Integer, desc: 'pages'
+ optional :per_page, type: Integer, desc: 'per page'
+ end
+
+ params :publish_sample_params do
+ requires :id, type: Integer, desc: 'Sample Id'
+ requires :analysesIds, type: Array[Integer], desc: 'Selected analyses ids'
+ optional :coauthors, type: Array[Integer], default: [], desc: 'Co-author (User)'
+ optional :reviewers, type: Array[Integer], default: [], desc: 'reviewers (User)'
+ optional :refs, type: Array[Integer], desc: 'Selected references'
+ optional :embargo, type: Integer, desc: 'Embargo collection'
+ requires :license, type: String, desc: 'Creative Common License'
+ requires :addMe, type: Boolean, desc: 'add me as author'
+ end
+
+ params :publish_reaction_params do
+ requires :id, type: Integer, desc: 'Reaction Id'
+ requires :analysesIds, type: Array[Integer], desc: 'Selected analyses ids'
+ optional :coauthors, type: Array[Integer], default: [], desc: 'Co-author (User)'
+ optional :reviewers, type: Array[Integer], default: [], desc: 'reviewers (User)'
+ optional :refs, type: Array[Integer], desc: 'Selected references'
+ optional :embargo, type: Integer, desc: 'Embargo collection'
+ requires :license, type: String, desc: 'Creative Common License'
+ requires :addMe, type: Boolean, desc: 'add me as author'
+ end
+
+ params :publish_reaction_scheme_params do
+ requires :id, type: Integer, desc: 'Reaction Id'
+ requires :temperature, type: Hash, desc: 'Temperature'
+ requires :duration, type: Hash, desc: 'Duration'
+ requires :products, type: Array, desc: 'Products'
+ optional :coauthors, type: Array[String], default: [], desc: 'Co-author (User)'
+ optional :embargo, type: Integer, desc: 'Embargo collection'
+ optional :reviewers, type: Array[Integer], default: [], desc: 'reviewers (User)'
+ requires :license, type: String, desc: 'Creative Common License'
+ requires :addMe, type: Boolean, desc: 'add me as author'
+ requires :schemeDesc, type: Boolean, desc: 'publish scheme'
+ end
+
+ params :save_repo_authors_params do
+ requires :elementId, type: Integer, desc: 'Element Id'
+ requires :elementType, type: String, desc: 'Element Type'
+ optional :leaders, type: Array, default: nil, desc: 'Leaders'
+ optional :taggData, type: Hash do
+ optional :creators, type: Array[Hash]
+ optional :affiliations, type: Hash
+ optional :contributors, type: Hash
+ end
+ end
+
+ params :assign_embargo_params do
+ requires :new_embargo, type: Integer, desc: 'Collection id'
+ requires :element, type: Hash, desc: 'Element' do
+ requires :id, type: Integer, desc: 'Element id'
+ requires :type, type: String, desc: 'Element type', values: %w[Sample Reaction]
+ requires :title, type: String, desc: 'Element title'
+ end
+ end
+
+ params :save_repo_labels_params do
+ requires :elementId, type: Integer, desc: 'Element Id'
+ requires :elementType, type: String, desc: 'Element Type'
+ optional :user_labels, type: Array[Integer]
+ end
+
+ params :reviewing_params do
+ requires :id, type: Integer, desc: 'Element Id'
+ requires :type, type: String, desc: 'Type', values: %w[sample reaction collection]
+ optional :comments, type: Hash
+ optional :comment, type: String
+ optional :checklist, type: Hash
+ optional :analysesIds, type: Array[Integer]
+ optional :coauthors, type: Array[Integer]
+ optional :reviewers, type: Array[Integer]
+ optional :reviewComments, type: String
+ end
+
+end
\ No newline at end of file
diff --git a/app/api/helpers/repository_helpers.rb b/app/api/helpers/repository_helpers.rb
index d84d8f5f3..414b70e66 100644
--- a/app/api/helpers/repository_helpers.rb
+++ b/app/api/helpers/repository_helpers.rb
@@ -1,299 +1,71 @@
module RepositoryHelpers
extend Grape::API::Helpers
- def get_pub_reaction(id)
- reaction = Reaction.where('id = ?', id)
- .select(
- <<~SQL
- reactions.id, reactions.name, reactions.description, reactions.reaction_svg_file, reactions.short_label,
- reactions.status, reactions.tlc_description, reactions.tlc_solvents, reactions.rf_value,
- reactions.temperature, reactions.timestamp_start,reactions.timestamp_stop,reactions.observation,
- reactions.rinchi_string, reactions.rinchi_long_key, reactions.rinchi_short_key,reactions.rinchi_web_key,
- (select label from publication_collections where (elobj ->> 'element_type')::text = 'Reaction' and (elobj ->> 'element_id')::integer = reactions.id) as embargo,
- (select json_extract_path(taggable_data::json, 'publication') from publications where element_type = 'Reaction' and element_id = reactions.id) as publication,
- reactions.duration
- SQL
- )
- .includes(
- container: :attachments
- ).last
- literatures = get_literature(reaction.id,'Reaction') || []
- reaction.products.each do |p|
- literatures += get_literature(p.id,'Sample')
- end
-
- pub = Publication.find_by(element_type: 'Reaction', element_id: reaction.id)
- pub_info = (pub.review.present? && pub.review['info'].present? && pub.review['info']['comment']) || ''
- infos = {}
- ana_infos = {}
- pd_infos = {}
-
- pub.state != Publication::STATE_COMPLETED && pub.descendants.each do |pp|
- review = pp.review || {}
- info = review['info'] || {}
- next if info.empty?
- if pp.element_type == 'Sample'
- pd_infos[pp.element_id] = info['comment']
- else
- ana_infos[pp.element_id] = info['comment']
- end
- end
-
- schemeList = get_reaction_table(reaction.id)
- entities = Entities::RepoReactionEntity.represent(reaction, serializable: true)
- entities[:products].each do |p|
- label_ids = p[:tag]['taggable_data']['user_labels'] || [] unless p[:tag]['taggable_data'].nil?
- p[:labels] = UserLabel.public_labels(label_ids) unless label_ids.nil?
- pub_product = p
- p[:xvialCom] = build_xvial_com(p[:molecule][:inchikey], current_user&.id)
- pub_product_tag = pub_product[:tag]['taggable_data']
- next if pub_product_tag.nil?
-
- xvial = pub_product_tag['xvial'] && pub_product_tag['xvial']['num']
- next unless xvial.present?
-
- unless current_user.present? && User.reviewer_ids.include?(current_user.id)
- pub_product_tag['xvial']['num'] = 'x'
- end
- p[:xvialCom][:hasSample] = true
- end
- entities[:publication]['review']['history'] = []
- entities[:publication]['review'] = nil if pub.state === Publication::STATE_COMPLETED
- entities[:literatures] = literatures unless entities.nil? || literatures.nil? || literatures.length == 0
- entities[:schemes] = schemeList unless entities.nil? || schemeList.nil? || schemeList.length == 0
- entities[:isLogin] = current_user.present?
- entities[:embargo] = reaction.embargo
- entities[:infos] = { pub_info: pub_info, pd_infos: pd_infos, ana_infos: ana_infos }
- entities[:isReviewer] = current_user.present? && User.reviewer_ids.include?(current_user.id) ? true : false
- entities[:elementType] = 'reaction'
- entities[:segments] = Labimotion::SegmentEntity.represent(reaction.segments)
- entities
+ def update_public_comment(params, current_user)
+ pub = Publication.find_by(element_type: params[:type], element_id: params[:id])
+ review = pub.review || {}
+ review_info = review['info'] || {}
+ review_info['comment'] = params[:comment]
+ review_info['timestamp'] = Time.now.strftime('%d-%m-%Y %H:%M:%S')
+ review_info['username'] = current_user.name
+ review_info['userid'] = current_user.id
+
+ review['info'] = review_info
+ pub.update!(review: review)
+ rescue StandardError => e
+ Publication.repo_log_exception(e, { params: params, user_id: current_user&.id })
+ raise
end
- def get_pub_molecule(id, adv_flag=nil, adv_type=nil, adv_val=nil)
- molecule = Molecule.find(id)
- xvial_com = build_xvial_com(molecule.inchikey, current_user&.id)
- pub_id = Collection.public_collection_id
- if adv_flag.present? && adv_flag == true && adv_type.present? && adv_type == 'Authors' && adv_val.present?
- adv = <<~SQL
- INNER JOIN publication_authors rs on rs.element_id = samples.id and rs.element_type = 'Sample' and rs.state = 'completed'
- and rs.author_id in ('#{adv_val.join("','")}')
- SQL
- else
- adv = ''
- end
-
- pub_samples = Collection.public_collection.samples
- .includes(:molecule,:tag).where("samples.molecule_id = ?", molecule.id)
- .where(
- <<~SQL
- samples.id in (
- SELECT samples.id FROM samples
- INNER JOIN collections_samples cs on cs.collection_id = #{pub_id} and cs.sample_id = samples.id and cs.deleted_at ISNULL
- INNER JOIN publications pub on pub.element_type='Sample' and pub.element_id=samples.id and pub.deleted_at ISNULL
- #{adv}
- )
- SQL
- )
- .select(
- <<~SQL
- samples.*, (select published_at from publications where element_type='Sample' and element_id=samples.id and deleted_at is null) as published_at
- SQL
- )
- .order('published_at desc')
- published_samples = pub_samples.map do |s|
- container = Entities::ContainerEntity.represent(s.container)
- tag = s.tag.taggable_data['publication']
- #u = User.find(s.tag.taggable_data['publication']['published_by'].to_i)
- #time = DateTime.parse(s.tag.taggable_data['publication']['published_at'])
- #published_time = time.strftime("%A, %B #{time.day.ordinalize} %Y %H:%M")
- #aff = u.affiliations.first
- next unless tag
- literatures = Literature.by_element_attributes_and_cat(s.id, 'Sample', 'public')
- .joins("inner join users on literals.user_id = users.id")
- .select(
- <<~SQL
- literatures.*,
- json_object_agg(literals.id, literals.litype) as litype,
- json_object_agg(literals.id, users.first_name || chr(32) || users.last_name) as ref_added_by
- SQL
- ).group('literatures.id').as_json
- reaction_ids = ReactionsProductSample.where(sample_id: s.id).pluck(:reaction_id)
- pub = Publication.find_by(element_type: 'Sample', element_id: s.id)
- sid = pub.taggable_data["sid"] unless pub.nil? || pub.taggable_data.nil?
- label_ids = s.tag.taggable_data['user_labels'] || [] unless s.tag.taggable_data.nil?
- user_labels = UserLabel.public_labels(label_ids) unless label_ids.nil?
- xvial = s.tag.taggable_data['xvial'] && s.tag.taggable_data['xvial']['num'] unless s.tag.taggable_data.nil?
- if xvial.present?
- unless current_user.present? && User.reviewer_ids.include?(current_user.id)
- xvial = 'x'
- end
- end
- comp_num = s.tag.taggable_data['xvial'] && s.tag.taggable_data['xvial']['comp_num'] unless s.tag.taggable_data.nil?
- pub_info = (pub.review.present? && pub.review['info'].present? && pub.review['info']['comment']) || ''
- ana_infos = {}
- pub.descendants.each do |pp|
- review = pp.review || {}
- info = review['info'] || {}
- next if info.empty?
- ana_infos[pp.element_id] = info['comment']
- end
- embargo = PublicationCollections.where("(elobj ->> 'element_type')::text = 'Sample' and (elobj ->> 'element_id')::integer = #{s.id}")&.first&.label
- segments = Labimotion::SegmentEntity.represent(s.segments)
- tag.merge(container: container, literatures: literatures, sample_svg_file: s.sample_svg_file, short_label: s.short_label, melting_point: s.melting_point, boiling_point: s.boiling_point,
- sample_id: s.id, reaction_ids: reaction_ids, sid: sid, xvial: xvial, comp_num: comp_num, embargo: embargo, labels: user_labels,
- showed_name: s.showed_name, pub_id: pub.id, ana_infos: ana_infos, pub_info: pub_info, segments: segments, published_at: pub.published_at)
- end
- x = published_samples.select { |s| s[:xvial].present? }
- xvial_com[:hasSample] = x.length.positive?
- published_samples = published_samples.flatten.compact
- {
- molecule: MoleculeGuestSerializer.new(molecule).serializable_hash.deep_symbolize_keys,
- published_samples: published_samples,
- isLogin: current_user.nil? ? false : true,
- isReviewer: (current_user.present? && User.reviewer_ids.include?(current_user.id)) ? true : false,
- xvialCom: xvial_com,
- elementType: 'molecule'
- }
+ def update_compound(pub, params, current_user)
+ data = pub.taggable_data || {}
+ xvial = data['xvial'] || {}
+ xvial['num'] = params[:data]
+ xvial['comp_num'] = params[:xcomp]
+ xvial['username'] = current_user.name
+ xvial['userid'] = current_user.id
+ xvial['timestamp'] = Time.now.strftime('%d-%m-%Y %H:%M:%S')
+ data['xvial'] = xvial
+ pub.update!(taggable_data: data)
+ rescue StandardError => e
+ Publication.repo_log_exception(e, { pub: pub&.id, user_id: current_user&.id, params: params })
+ raise
end
- def check_repo_review_permission(element)
- return true if User.reviewer_ids&.include? current_user.id
- pub = Publication.find_by(element_id: element.id, element_type: element.class.name)
- return false if pub.nil?
- return true if pub && pub.published_by == current_user.id && ( pub.state == Publication::STATE_REVIEWED || pub.state == Publication::STATE_PENDING)
- return false
- end
+ def metadata_preview(root_publication, current_user)
+ mt = []
+ root_publication = root_publication
+ publications = [root_publication] + root_publication.descendants
+ publications.each do |pub|
+ next if pub.element.nil?
- def repo_review_info(pub, user_id, lst)
- {
- submitter: pub&.published_by == user_id || false,
- reviewer: User.reviewer_ids&.include?(user_id) || false,
- groupleader: pub&.review&.dig('reviewers')&.include?(user_id),
- leaders: User.where(id: pub&.review&.dig('reviewers'))&.map{ |u| u.name },
- preapproved: pub&.review&.dig('checklist', 'glr', 'status') == true,
- review_level: repo_review_level(pub&.element_id, pub&.element_type)
- }
- end
-
- def repo_review_level(id, type)
- return 3 if User.reviewer_ids&.include? current_user.id
- pub = Publication.find_by(element_id: id, element_type: type.classify)
- return 0 if pub.nil?
- return 2 if pub.published_by === current_user.id
- sync_cols = pub.element.sync_collections_users.where(user_id: current_user.id)
- return 1 if (sync_cols&.length > 0)
- return 0
- end
-
- def get_literature(id, type, cat='public')
- literatures = Literature.by_element_attributes_and_cat(id, type.classify, cat)
- .joins("inner join users on literals.user_id = users.id")
- .select(
- <<~SQL
- literatures.* , literals.element_type, literals.element_id,
- json_object_agg(literals.id, literals.litype) as litype,
- json_object_agg(literals.id, users.first_name || chr(32) || users.last_name) as ref_added_by
- SQL
- ).group('literatures.id, literals.element_type, literals.element_id').as_json
- literatures
- end
-
- def get_reaction_table(id)
- schemeAll = ReactionsSample.where('reaction_id = ? and type != ?', id, 'ReactionsPurificationSolventSample')
- .joins(:sample)
- .joins("inner join molecules on samples.molecule_id = molecules.id")
- .select(
- <<~SQL
- reactions_samples.id,
- (select name from molecule_names mn where mn.id = samples.molecule_name_id) as molecule_iupac_name,
- molecules.iupac_name, molecules.sum_formular,
- molecules.molecular_weight, samples.name, samples.short_label,
- samples.real_amount_value, samples.real_amount_unit,
- samples.target_amount_value, samples.target_amount_unit,
- samples.purity, samples.density, samples.external_label,
- samples.molarity_value, samples.molarity_unit,
- reactions_samples.equivalent,reactions_samples.scheme_yield,
- reactions_samples."position" as rs_position,
- case when reactions_samples."type" = 'ReactionsStartingMaterialSample' then 'starting_materials'
- when reactions_samples."type" = 'ReactionsReactantSample' then 'reactants'
- when reactions_samples."type" = 'ReactionsProductSample' then 'products'
- when reactions_samples."type" = 'ReactionsSolventSample' then 'solvents'
- when reactions_samples."type" = 'ReactionsPurificationSolventSample' then 'purification_solvents'
- else reactions_samples."type"
- end mat_group,
- case when reactions_samples."type" = 'ReactionsStartingMaterialSample' then 1
- when reactions_samples."type" = 'ReactionsReactantSample' then 2
- when reactions_samples."type" = 'ReactionsProductSample' then 3
- when reactions_samples."type" = 'ReactionsSolventSample' then 4
- when reactions_samples."type" = 'ReactionsPurificationSolventSample' then 5
- else 6
- end type_seq
- SQL
- ).order('reactions_samples.position ASC').as_json
-
- schemeSorted = schemeAll.sort_by {|o| o['type_seq']}
- solvents_sum = schemeAll.select{ |d| d['mat_group'] === 'solvents'}.sum { |r|
- value = (r['real_amount_value'].nil? || r['real_amount_value'].zero?) ? r['target_amount_value'].to_f : r['real_amount_value'].to_f
- unit = (r['real_amount_value'].nil? || r['real_amount_value'].zero?) ? r['target_amount_unit'] : r['real_amount_unit']
-
- has_molarity = !r['molarity_value'].nil? && r['molarity_value'] > 0.0 && (r['density'] === 0.0) || false
- has_density = !r['density'].nil? && r['density'] > 0.0 && (r['molarity_value'] === 0.0) || false
-
- molarity = r['molarity_value'] && r['molarity_value'].to_f || 1.0
- density = r['density'] && r['density'].to_f || 1.0
- purity = r['purity'] && r['purity'].to_f || 1.0
- molecular_weight = r['molecular_weight'] && r['molecular_weight'].to_f || 1.0
-
- r['amount_g'] = unit === 'g'? value : unit === 'mg'? value.to_f / 1000.0 : unit === 'mol' ? (value / purity) * molecular_weight : unit === 'l' && !has_molarity && !has_density ? 0 : has_molarity ? value * molarity * molecular_weight : value * density * 1000
- r['amount_l'] = unit === 'l'? value : !has_molarity && !has_density ? 0 : has_molarity ? (r['amount_g'].to_f * purity) / (molarity * molecular_weight) : has_density ? r['amount_g'].to_f / (density * 1000) : 0
- r['amount_l'].nil? ? 0 : r['amount_l'].to_f
- }
-
- schemeList = []
- schemeList = schemeSorted.map do |r|
- scheme = {}
- value = (r['real_amount_value'].nil? || r['real_amount_value'].zero?) ? r['target_amount_value'].to_f : r['real_amount_value'].to_f
- unit = (r['real_amount_value'].nil? || r['real_amount_value'].zero?) ? r['target_amount_unit'] : r['real_amount_unit']
- has_molarity = !r['molarity_value'].nil? && r['molarity_value'] > 0.0 && (r['density'] === 0.0) || false
- has_density = !r['density'].nil? && r['density'] > 0.0 && (r['molarity_value'] === 0.0) || false
-
- molarity = r['molarity_value'] && r['molarity_value'].to_f || 1.0
- density = r['density'] && r['density'].to_f || 1.0
- purity = r['purity'] && r['purity'].to_f || 1.0
- molecular_weight = r['molecular_weight'] && r['molecular_weight'].to_f || 1.0
- r['amount_g'] = unit === 'g'? value : unit === 'mg'? value.to_f / 1000.0 : unit === 'mol' ? (value / purity) * molecular_weight : unit === 'l' && !has_molarity && !has_density ? 0 : has_molarity ? value * molarity * molecular_weight : value * density * 1000
- r['amount_l'] = unit === 'l'? value : !has_molarity && !has_density ? 0 : has_molarity ? (r['amount_g'].to_f * purity) / (molarity * molecular_weight) : has_density ? r['amount_g'].to_f / (density * 1000) : 0
-
- if r['mat_group'] === 'solvents'
- r['equivalent'] = r['amount_l'] / solvents_sum
- else
- r['amount_mol'] = unit === 'mol'? value : has_molarity ? r['amount_l'] * molarity : r['amount_g'].to_f * purity / molecular_weight
- r['dmv'] = !has_molarity && !has_density ? '- / -' : has_density ? + density.to_s + ' / - ' : ' - / ' + molarity.to_s + r['molarity_unit']
- end
-
- r.delete('real_amount_value');
- r.delete('real_amount_unit');
- r.delete('target_amount_value');
- r.delete('target_amount_unit');
- r.delete('molarity_value');
- r.delete('molarity_unit');
- r.delete('purity');
- r.delete('molecular_weight');
- r.delete('rs_position');
- r.delete('density');
- r
+ mt.push(element_type: pub.element_type, metadata_xml: pub.datacite_metadata_xml)
end
- schemeList
+ rescue StandardError => e
+ Publication.repo_log_exception(e, { root_publication: root_publication&.id, user_id: current_user&.id })
+ { metadata: mt }
end
- def build_publication_element_state(publications)
- publications.map do |c|
- c.taggable_data['element_dois']&.map do |obj|
- obj['state'] = Publication.find_by(id: obj['id'])&.state unless obj['state'].present?
- obj
+ def metadata_preview_zip(root_publication, current_user)
+ publications = [root_publication] + root_publication.descendants
+ filename = URI.escape("metadata_#{root_publication.element_type}_#{root_publication.element_id}-#{Time.new.strftime('%Y%m%d%H%M%S')}.zip")
+ header('Content-Disposition', "attachment; filename=\"#{filename}\"")
+ zip = Zip::OutputStream.write_buffer do |zip|
+ publications.each do |pub|
+ next if pub.element.nil?
+
+ el_type = pub.element_type == 'Container' ? 'analysis' : pub.element_type.downcase
+ zip.put_next_entry URI.escape("metadata_#{el_type}_#{pub.element_id}.xml")
+ zip.write pub.datacite_metadata_xml
end
- c
end
+ zip.rewind
+ zip.read
+ rescue StandardError => e
+ Publication.repo_log_exception(e, { root_publication: root_publication&.id, user_id: current_user&.id })
+ raise
end
-end
+
+ private
+
+end
\ No newline at end of file
diff --git a/app/api/helpers/review_helpers.rb b/app/api/helpers/review_helpers.rb
new file mode 100644
index 000000000..09a390348
--- /dev/null
+++ b/app/api/helpers/review_helpers.rb
@@ -0,0 +1,281 @@
+# frozen_string_literal: true
+
+# A helper for reviewing publications
+# It includes the following methods:
+# 1. get_review_list: Get the list of publications to review
+# 2. fetch_reviewing_reaction: Fetch the details of a reaction for reviewing
+# 3. fetch_reviewing_sample: Fetch the details of a sample for reviewing
+
+module ReviewHelpers
+ extend Grape::API::Helpers
+
+ def get_review_list(params, current_user, is_reviewer = false)
+ type = params[:type].blank? || params[:type] == 'All' ? %w[Sample Reaction] : params[:type].chop!
+ state = params[:state].empty? || params[:state] == 'All' ? [Publication::STATE_PENDING, Publication::STATE_REVIEWED, Publication::STATE_ACCEPTED] : params[:state]
+ pub_scope = Publication.where(state: state, ancestry: nil, element_type: type)
+ pub_scope = pub_scope.where("published_by = ? OR (review -> 'reviewers')::jsonb @> '?' OR (review -> 'submitters')::jsonb @> '?'", current_user.id, current_user.id, current_user.id) unless is_reviewer
+ unless params[:search_value].blank? || params[:search_value] == 'All'
+ case params[:search_type]
+ when 'Submitter'
+ pub_scope = pub_scope.where(published_by: params[:search_value])
+ when 'Embargo'
+ embargo_search = <<~SQL
+ (element_type = 'Reaction' and element_id in (select reaction_id from collections_reactions cr where cr.deleted_at is null and cr.collection_id = ?))
+ or
+ (element_type = 'Sample' and element_id in (select sample_id from collections_samples cs where cs.deleted_at is null and cs.collection_id = ?))
+ SQL
+ embargo_search = ActiveRecord::Base.send(:sanitize_sql_array, [embargo_search, params[:search_value], params[:search_value]])
+ pub_scope = pub_scope.where(embargo_search)
+ when 'Name'
+ r_name_sql = " r.short_label like '%#{ActiveRecord::Base.send(:sanitize_sql_like, params[:search_value])}%' "
+ s_name_sql = " s.short_label like '%#{ActiveRecord::Base.send(:sanitize_sql_like, params[:search_value])}%' "
+ name_search = <<~SQL
+ (element_type = 'Reaction' and element_id in (select id from reactions r where #{r_name_sql}))
+ or
+ (element_type = 'Sample' and element_id in (select id from samples s where #{s_name_sql}))
+ SQL
+ pub_scope = pub_scope.where(name_search)
+ end
+ end
+ pub_scope = pub_scope.where("taggable_data->'user_labels' @> '?'", params[:label]) if params[:label].present?
+ list = pub_scope.order('publications.updated_at desc')
+ elements = []
+ paginate(list).each do |e|
+ element_type = e.element&.class&.name
+ next if element_type.nil?
+
+ u = User.with_deleted.find(e.published_by) unless e.published_by.nil?
+ svg_file = e.element.reaction_svg_file if element_type == 'Reaction'
+ title = e.element.short_label if element_type == 'Reaction'
+
+ svg_file = e.element.sample_svg_file if element_type == 'Sample'
+ title = e.element.short_label if element_type == 'Sample'
+ review_info = Repo::FetchHandler.repo_review_info(e, current_user&.id)
+ checklist = e.review && e.review['checklist'] if is_reviewer || review_info[:groupleader] == true
+ scheme_only = element_type == 'Reaction' && e.taggable_data && e.taggable_data['scheme_only']
+
+ label_ids = (e.taggable_data && e.taggable_data['user_labels']) || []
+ labels = UserLabel.public_labels(label_ids, current_user, e.state == Publication::STATE_COMPLETED) unless label_ids.nil?
+ elements.push(
+ id: e.element_id, svg: svg_file, type: element_type, title: title, checklist: checklist || {}, review_info: review_info, isReviewer: is_reviewer,
+ published_by: u&.name, submitter_id: u&.id, submit_at: e.created_at, state: e.state, embargo: Repo::FetchHandler.find_embargo_collection(e).label, scheme_only: scheme_only, labels: labels
+ )
+ end
+ { elements: elements }
+ rescue StandardError => e
+ Publication.repo_log_exception(e, { params: params, user_id: current_user&.id, is_reviewer: is_reviewer })
+ { error: e.message }
+ end
+
+ def fetch_reviewing_reaction(reaction, publication, current_user)
+ reaction = Reaction.where(id: params[:id])
+ .select(
+ <<~SQL
+ reactions.id, reactions.name, reactions.description, reactions.reaction_svg_file, reactions.short_label,
+ reactions.status, reactions.tlc_description, reactions.tlc_solvents, reactions.rf_value,
+ reactions.temperature, reactions.timestamp_start,reactions.timestamp_stop,reactions.observation,
+ reactions.rinchi_string, reactions.rinchi_long_key, reactions.rinchi_short_key,reactions.rinchi_web_key,
+ (select json_extract_path(taggable_data::json, 'publication') from publications where element_type = 'Reaction' and element_id = reactions.id) as publication,
+ reactions.duration
+ SQL
+ ).includes(container: :attachments).last
+ literatures = Repo::FetchHandler.literatures_by_cat(reaction.id, 'Reaction', 'detail') || []
+ reaction.products.each do |p|
+ literatures += Repo::FetchHandler.literatures_by_cat(p.id, 'Sample', 'detail')
+ end
+ schemeList = Repo::FetchHandler.get_reaction_table(reaction.id)
+ review_info = Repo::FetchHandler.repo_review_info(publication, current_user&.id)
+ publication.review&.slice!('history') unless User.reviewer_ids.include?(current_user.id) || review_info[:groupleader] == true
+ published_user = User.with_deleted.find(publication.published_by) unless publication.nil?
+ entities = Entities::RepoReactionEntity.represent(reaction, serializable: true)
+ entities[:literatures] = literatures unless entities.nil? || literatures.blank?
+ entities[:schemes] = schemeList unless entities.nil? || schemeList.blank?
+ entities[:segments] = Labimotion::SegmentEntity.represent(reaction.segments)
+ embargo = Repo::FetchHandler.find_embargo_collection(publication)
+ entities[:embargo] = embargo&.label
+ entities[:embargoId] = embargo&.id
+ label_ids = publication.taggable_data['user_labels'] || [] unless publication.taggable_data.nil?
+ user_labels = UserLabel.public_labels(label_ids, current_user, publication.state == Publication::STATE_COMPLETED) unless label_ids.nil?
+ entities[:labels] = user_labels
+ {
+ reaction: entities,
+ selectEmbargo: Publication.find_by(element_type: 'Collection', element_id: embargo&.id),
+ pub_name: published_user&.name || '',
+ review_info: review_info
+ }
+ rescue StandardError => e
+ Publication.repo_log_exception(e, { reaction: reaction&.id, publication: publication&.id, current_user: current_user&.id })
+ { error: e.message }
+ end
+
+ def fetch_reviewing_sample(sample, publication, current_user)
+ review_sample = { **sample.serializable_hash.deep_symbolize_keys }
+ review_sample[:segments] = sample.segments.present? ? Labimotion::SegmentEntity.represent(sample.segments) : []
+ containers = Entities::ContainerEntity.represent(sample.container)
+ publication = Publication.find_by(element_id: params[:id], element_type: 'Sample')
+ review_info = review_info = Repo::FetchHandler.repo_review_info(publication, current_user&.id)
+ # preapproved = publication.review.dig('checklist', 'glr', 'status') == true
+ # is_leader = publication.review.dig('reviewers')&.include?(current_user&.id)
+ publication.review&.slice!('history') unless User.reviewer_ids.include?(current_user.id) || review_info[:groupleader] == true
+ published_user = User.with_deleted.find(publication.published_by) unless publication.nil?
+ literatures = Repo::FetchHandler.literatures_by_cat(params[:id], 'Sample', 'detail')
+ # embargo = PublicationCollections.where("(elobj ->> 'element_type')::text = 'Sample' and (elobj ->> 'element_id')::integer = #{sample.id}")&.first&.label
+ embargo = Repo::FetchHandler.find_embargo_collection(publication)
+ review_sample[:embargo] = embargo&.label
+ review_sample[:embargoId] = embargo&.id
+ review_sample[:user_labels] = publication.taggable_data['user_labels'] || [] unless publication.taggable_data.nil?
+ review_sample[:showed_name] = sample.showed_name
+ label_ids = publication.taggable_data['user_labels'] || [] unless publication.taggable_data.nil?
+ user_labels = UserLabel.public_labels(label_ids, current_user, publication.state == Publication::STATE_COMPLETED) unless label_ids.nil?
+ {
+ molecule: MoleculeGuestSerializer.new(sample.molecule).serializable_hash.deep_symbolize_keys,
+ sample: review_sample,
+ labels: user_labels,
+ publication: publication,
+ literatures: literatures,
+ analyses: containers,
+ selectEmbargo: Publication.find_by(element_type: 'Collection', element_id: embargo&.id),
+ doi: Entities::DoiEntity.represent(sample.doi, serializable: true),
+ pub_name: published_user&.name,
+ review_info: review_info
+ }
+ rescue StandardError => e
+ Publication.repo_log_exception(e, { sample: sample&.id, publication: publication&.id, current_user: current_user&.id })
+ { error: e.message }
+ end
+
+ def save_repo_authors(declared_params, pub, current_user)
+ et = ElementTag.find_or_create_by(taggable_id: declared_params[:elementId], taggable_type: declared_params[:elementType])
+ tagg_data = declared_params[:taggData] || {}
+ leaders = declared_params[:leaders]
+
+ if tagg_data.present?
+ tagg_data['author_ids'] = tagg_data['creators']&.map { |cr| cr['id'] }
+ tagg_data['affiliation_ids'] = [tagg_data['creators']&.map { |cr| cr['affiliationIds'] }.flatten.uniq]
+ tagg_data['affiliations'] = tagg_data['affiliations']&.select { |k, _| tagg_data['affiliation_ids'].include?(k.to_i) }
+
+ pub_taggable_data = pub.taggable_data || {}
+ pub_taggable_data = pub_taggable_data.deep_merge(tagg_data || {})
+ pub.update(taggable_data: pub_taggable_data)
+
+ et_taggable_data = et.taggable_data || {}
+ pub_tag = et_taggable_data['publication'] || {}
+ pub_tag = pub_tag.deep_merge(tagg_data || {})
+ et_taggable_data['publication'] = pub_tag
+ et.update(taggable_data: et_taggable_data)
+ end
+ if !leaders.nil?
+ review = pub.review || {}
+ orig_leaders = review['reviewers'] || []
+ curr_leaders = leaders.map { |l| l['id'] }
+
+ del_leaders = orig_leaders - curr_leaders
+ new_leaders = curr_leaders - orig_leaders
+ pub.update(review: review.deep_merge({ 'reviewers' => curr_leaders }))
+ reassign_leaders(pub, current_user, del_leaders, new_leaders) if del_leaders.present? || new_leaders.present?
+ end
+ pub
+ rescue StandardError => e
+ Publication.repo_log_exception(e, { declared_params: declared_params, pub: pub&.id, current_user: current_user&.id })
+ { error: e.message }
+ end
+
+ def reassign_leaders(pub, current_user, del_leaders, new_leaders)
+ pub_user = User.with_deleted.find(pub.published_by)
+ element = pub.element
+ return false unless pub_user && element
+
+ if new_leaders.present?
+ new_leader_list = User.where(id: new_leaders, type: 'Person')
+ new_leader_list&.each do |user|
+ col = user.find_or_create_grouplead_collection
+ case pub.element_type
+ when 'Sample'
+ CollectionsSample
+ when 'Reaction'
+ CollectionsReaction
+ end.create_in_collection([element.id], [col.id])
+ end
+ end
+
+ if del_leaders.present?
+ del_leader_list = User.where(id: del_leaders, type: 'Person')
+ del_leader_list&.each do |user|
+ col = user.find_or_create_grouplead_collection
+ case pub.element_type
+ when 'Sample'
+ CollectionsSample
+ when 'Reaction'
+ CollectionsReaction
+ end.remove_in_collection([element.id], [col.id])
+ end
+ end
+ rescue StandardError => e
+ Publication.repo_log_exception(e, { pub: pub&.id, current_user: current_user&.id, del_leaders: del_leaders, new_leaders: new_leaders })
+ raise e
+ end
+
+ def review_advanced_search(params, current_user)
+ result = case params[:type]
+ when 'Submitter'
+ query_submitter(params[:element_type], params[:state], current_user)
+ when 'Embargo'
+ query_embargo(current_user)
+ else
+ []
+ end
+ { result: result }
+ rescue StandardError => e
+ Publication.repo_log_exception(e, { params: params, current_user: current_user&.id })
+ { error: e.message }
+ end
+
+
+ private
+
+ def query_submitter(element_type, state, current_user)
+ if User.reviewer_ids.include?(current_user.id)
+ state_sql = state == 'All' || state.empty? ? " state in ('pending', 'reviewed', 'accepted')" : ActiveRecord::Base.send(:sanitize_sql_array, [' state=? ', state])
+ type_sql = element_type == 'All' || element_type.empty? ? " element_type in ('Sample', 'Reaction')" : ActiveRecord::Base.send(:sanitize_sql_array, [' element_type=? ', element_type.chop])
+ search_scope = User.where(type: 'Person').where(
+ <<~SQL
+ users.id in (
+ select published_by from publications pub where ancestry is null and deleted_at is null
+ and #{state_sql} and #{type_sql})
+ SQL
+ )
+ .order('first_name ASC')
+ else
+ search_scope = User.where(id: current_user.id)
+ end
+ result = search_scope.select(
+ <<~SQL
+ id as key, first_name, last_name, first_name || chr(32) || last_name as name, first_name || chr(32) || last_name || chr(32) || '(' || name_abbreviation || ')' as label
+ SQL
+ )
+ rescue StandardError => e
+ Publication.repo_log_exception(e, { element_type: element_type, state: state, current_user: current_user&.id })
+ { error: e.message }
+ end
+
+ def query_embargo(current_user)
+ search_scope = if User.reviewer_ids.include?(current_user.id)
+ Collection.where(
+ <<~SQL
+ ancestry::integer in (select id from collections cx where label = 'Embargoed Publications')
+ SQL
+ )
+ else
+ Collection.where(ancestry: current_user.publication_embargo_collection.id)
+ end
+ result = search_scope.select(
+ <<~SQL
+ id as key, label as name, label as label
+ SQL
+ ).order('label ASC')
+ rescue StandardError => e
+ Publication.repo_log_exception(e, { current_user: current_user&.id })
+ { error: e.message }
+ end
+
+end
diff --git a/app/api/helpers/submission_helpers.rb b/app/api/helpers/submission_helpers.rb
index 22eb8c80b..6d7c342ba 100644
--- a/app/api/helpers/submission_helpers.rb
+++ b/app/api/helpers/submission_helpers.rb
@@ -4,132 +4,95 @@
module SubmissionHelpers
extend Grape::API::Helpers
+ def tag_as_submitted(element)
+ return unless element.is_a?(Sample) || element.is_a?(Reaction)
+
+ et = element.tag
+ return if et.taggable_data.nil?
+
+ et.update!(taggable_data: (et.taggable_data || {}).merge(publish_pending: true))
+ rescue StandardError => e
+ Publication.repo_log_exception(e, { element: element&.id })
+ nil
+ end
+
+ def reserve_reaction_dois(reaction, analysis_set, analysis_set_ids)
+ reaction_products = reaction.products.select { |s| s.analyses.select { |a| a.id.in? analysis_set_ids }.count > 0 }
+ reaction.reserve_suffix
+ reaction_products.each do |p|
+ d = p.reserve_suffix
+ et = p.tag
+ et.update!(
+ taggable_data: (et.taggable_data || {}).merge(reserved_doi: d.full_doi)
+ )
+ end
+ reaction.reserve_suffix_analyses(analysis_set)
+ reaction.reload
+ reaction.tag_reserved_suffix(analysis_set)
+ reaction.reload
+ {
+ reaction: Entities::ReactionEntity.represent(reaction, serializable: true),
+ message: ENV['PUBLISH_MODE'] ? "publication on: #{ENV['PUBLISH_MODE']}" : 'publication off'
+ }
+ rescue StandardError => e
+ Publication.repo_log_exception(e, { reaction: reaction&.id, analysis_set: analysis_set, analysis_set_ids: analysis_set_ids })
+ nil
+ end
+
def ols_validation(analyses)
analyses.each do |ana|
error!('analyses check fail', 404) if (ana.extended_metadata['kind'].match /^\w{3,4}\:\d{6,7}\s\|\s\w+/).nil?
end
+ rescue StandardError => e
+ Publication.repo_log_exception(e, { analyses: analyses })
+ nil
end
def coauthor_validation(coauthors)
coauthor_ids = []
coauthors&.each do |coa|
- val = coa.strip
+ val = coa
usr = User.where(type: %w[Person Collaborator]).where.not(confirmed_at: nil).where('id = ? or email = ?', val.to_i, val.to_s).first
error!('invalid co-author: ' + val.to_s, 404) if usr.nil?
coauthor_ids << usr.id
end
coauthor_ids
+ rescue StandardError => e
+ Publication.repo_log_exception(e, { coauthors: coauthors })
+ nil
end
-
- def update_tag_doi(element)
- unless element.nil? || element&.doi.nil? || element&.tag.nil?
- mds = Datacite::Mds.new
- et = element.tag
- tag_data = (et.taggable_data && et.taggable_data['publication']) || {}
- tag_data['doi'] = "#{mds.doi_prefix}/#{element&.doi.suffix}"
- et.update!(
- taggable_data: (et.taggable_data || {}).merge(publication: tag_data)
- )
- if element&.class&.name == 'Reaction'
- element&.publication.children.each do |child|
- next unless child&.element&.class&.name == 'Sample'
-
- update_tag_doi(child.element)
- end
- end
- end
+ def perform_method
+ method = ENV['PUBLISH_MODE'] == 'production' ? :perform_later : :perform_now
+ method
end
- def accept_new_sample(root, sample)
- pub_s = Publication.create!(
- state: Publication::STATE_PENDING,
- element: sample,
- doi: sample&.doi,
- published_by: root.published_by,
- parent: root,
- taggable_data: root.taggable_data
+ def send_message_and_tag(element, user)
+ tag_as_submitted(element)
+ Message.create_msg_notification(
+ channel_id: Channel.find_by(subject: Channel::SUBMITTING)&.id,
+ message_from: user.id,
+ autoDismiss: 5,
+ message_content: { 'data': "Your submission for #{element.class.name} [#{element.short_label}] is currently being processed. You will recieve a notification upon completion." },
)
- sample.analyses.each do |sa|
- accept_new_analysis(pub_s, sa)
- end
+ element.reload
+ rescue StandardError => e
+ Publication.repo_log_exception(e, { element: element&.id, user: user&.id })
end
- def accept_new_analysis(root, analysis, nil_analysis = true)
- if nil_analysis
- ap = Publication.create!(
- state: Publication::STATE_PENDING,
- element: analysis,
- doi: analysis.doi,
- published_by: root.published_by,
- parent: root,
- taggable_data: root.taggable_data
- )
- atag = ap.taggable_data
- aids = atag&.delete('analysis_ids')
- aoids = atag&.delete('original_analysis_ids')
- ap.save! if aids || aoids
- end
- analysis.children.where(container_type: 'dataset').each do |ds|
- ds.attachments.each do |att|
- if MimeMagic.by_path(att.filename)&.type&.start_with?('image')
- file_path = File.join('public/images/publications/', att.id.to_s, '/', att.filename)
- public_path = File.join('public/images/publications/', att.id.to_s)
- FileUtils.mkdir_p(public_path)
- File.write(file_path, att.read_file.force_encoding("utf-8"))
- end
- end
- end
- end
+ # def concat_author_ids(coauthors = params[:coauthors])
+ # coauthor_ids = coauthors.map do |coa|
+ # val = coa.strip
+ # next val.to_i if val =~ /^\d+$/
+
+ # User.where(type: %w(Person Collaborator)).where.not(confirmed_at: nil).find_by(email: val)&.id if val =~ /^\S+@\S+$/
+ # end.compact
+ # [current_user.id] + coauthor_ids
+ # end
+
+ private
+
- def public_literature(root_publication)
- publications = [root_publication] + root_publication.descendants
- publications.each do |pub|
- next unless pub.element_type == 'Reaction' || pub.element_type == 'Sample'
- literals = Literal.where(element_type: pub.element_type, element_id: pub.element_id)
- literals&.each { |l| l.update_columns(category: 'public') } unless literals.nil?
- end
- end
- def element_submit(root)
- root.descendants.each { |np| np.destroy! if np.element.nil? }
- root.element.reserve_suffix
- root.element.reserve_suffix_analyses(root.element.analyses) if root.element.analyses&.length > 0
- root.element.analyses&.each do |a|
- accept_new_analysis(root, a, Publication.find_by(element: a).nil?)
- end
- case root.element_type
- when 'Sample'
- analyses_ids = root.element.analyses.pluck(:id)
- root.update!(taggable_data: root.taggable_data.merge(analysis_ids: analyses_ids))
- root.element.analyses.each do |sa|
- accept_new_analysis(root, sa, Publication.find_by(element: sa).nil?)
- end
-
- when 'Reaction'
- root.element.products.each do |pd|
- Publication.find_by(element_type: 'Sample', element_id: pd.id)&.destroy! if pd.analyses&.length == 0
- next if pd.analyses&.length == 0
- pd.reserve_suffix
- pd.reserve_suffix_analyses(pd.analyses)
- pd.reload
- prod_pub = Publication.find_by(element: pd)
- if prod_pub.nil?
- accept_new_sample(root, pd)
- else
- pd.analyses.each do |rpa|
- accept_new_analysis(prod_pub, rpa, Publication.find_by(element: rpa).nil?)
- end
- end
- end
- end
- root.reload
- root.update_columns(doi_id: root.element.doi.id) unless root.doi_id == root.element.doi.id
- root.descendants.each { |pub_a|
- next if pub_a.element.nil?
- pub_a.update_columns(doi_id: pub_a.element.doi.id) unless pub_a.doi_id == pub_a.element&.doi&.id
- }
- update_tag_doi(root.element)
- end
end
diff --git a/app/api/helpers/user_label_helpers.rb b/app/api/helpers/user_label_helpers.rb
index 9345122dd..5d08aaab2 100644
--- a/app/api/helpers/user_label_helpers.rb
+++ b/app/api/helpers/user_label_helpers.rb
@@ -5,7 +5,16 @@ def update_element_labels(element, user_labels, current_user_id)
tag = ElementTag.find_by(taggable: element)
data = tag.taggable_data || {}
private_labels = UserLabel.where(id: data['user_labels'], access_level: [0, 1]).where.not(user_id: current_user_id).pluck(:id)
- data['user_labels'] = ((user_labels || []) + private_labels)&.uniq
+ if !User.reviewer_ids.include?(current_user_id)
+ review_labels = UserLabel.where(id: data['user_labels'], access_level: 3).pluck(:id)
+ end
+ data['user_labels'] = ((user_labels || []) + private_labels + (review_labels || []))&.uniq
tag.save!
+
+ ## For Chemotion Repository
+ if element.respond_to?(:publication) && pub = element.publication
+ pub.update_user_labels(data['user_labels'], current_user_id) if pub.present?
+ end
end
+
end
\ No newline at end of file
diff --git a/app/assets/stylesheets/components/select.scss b/app/assets/stylesheets/components/select.scss
index 812ec9214..e3081f1ee 100644
--- a/app/assets/stylesheets/components/select.scss
+++ b/app/assets/stylesheets/components/select.scss
@@ -1,6 +1,6 @@
.header-group-select {
.Select-control {
- width: 200px;
+ width: 120px;
height: 27px;
cursor: pointer;
box-shadow: inset 0 2px 2px #e9e9e9;
diff --git a/app/assets/stylesheets/repo-extension.scss b/app/assets/stylesheets/repo-extension.scss
new file mode 100644
index 000000000..fb82e2e5f
--- /dev/null
+++ b/app/assets/stylesheets/repo-extension.scss
@@ -0,0 +1,15 @@
+.ext-icon {
+ width: auto;
+ height: 4vh;
+ max-height: 4vh;
+ padding-right: 8px;
+}
+
+.ext-icon-list {
+ margin-right: 10px;
+ float: right;
+ img {
+ height: 2.2vh !important;
+ max-height: 2.2vh !important;
+ }
+}
diff --git a/app/assets/stylesheets/repo_home.scss b/app/assets/stylesheets/repo_home.scss
index 5535e33a9..dffce9d99 100644
--- a/app/assets/stylesheets/repo_home.scss
+++ b/app/assets/stylesheets/repo_home.scss
@@ -132,6 +132,7 @@ $bs-successcolor: #5cb85c;
.repo-registed-compound-desc {
color: $bs-primarycolor;
font-style: italic;
+ font-size: small;
i {
color: black;
cursor: pointer;
@@ -705,6 +706,11 @@ $bs-successcolor: #5cb85c;
}
}
+@mixin preview-btn {
+ position: absolute;
+ top: 80%;
+}
+
.repo-analysis-header {
display: inline-block;
width: 100%;
@@ -723,10 +729,16 @@ $bs-successcolor: #5cb85c;
.preview-table:hover {
border: 1px solid $statisticcolor;
}
- .spectra {
- position: absolute;
- top: 80%;
- left: 76%;
+ .btn0 {
+ display: none;
+ }
+ .btn1 {
+ @include preview-btn;
+ right: 0px;
+ }
+ .btn2 {
+ @include preview-btn;
+ right: 30px;
}
}
.abstract {
@@ -1105,16 +1117,16 @@ $bs-successcolor: #5cb85c;
a {
padding: 15px 15px 10px 0px !important;
}
-}
-
-.white-nav-item:hover {
- text-decoration: underline;
- text-decoration-color: #2e6da4;
- text-decoration-style: solid;
- text-decoration-thickness: 4px;
- text-underline-offset: 20%;
- color: #2e6da4;
- font-weight: bolder;
+ &:hover a,
+ &:hover b {
+ text-decoration: underline !important;
+ text-decoration-color: #2e6da4 !important;
+ text-decoration-style: solid !important;
+ text-decoration-thickness: 4px !important;
+ text-underline-offset: 20% !important;
+ color: #2e6da4 !important;
+ font-weight: bolder !important;
+ }
}
.hub-menu {
@@ -1150,3 +1162,12 @@ $bs-successcolor: #5cb85c;
justify-content: center;
margin-top: 8px;
}
+
+.repo-nfdi-award {
+ display: inline-flex;;
+ align-items: center;
+ justify-content: center;
+ > img {
+ width: 80%;
+ }
+}
diff --git a/app/assets/stylesheets/structure_viewer.scss b/app/assets/stylesheets/structure_viewer.scss
index 17f431f69..9f5cca2aa 100644
--- a/app/assets/stylesheets/structure_viewer.scss
+++ b/app/assets/stylesheets/structure_viewer.scss
@@ -35,3 +35,9 @@
border: 0;
}
}
+
+.structure-editor-container {
+ position: absolute;
+ top: 6px;
+ right: 46px;
+}
diff --git a/app/controllers/pages_controller.rb b/app/controllers/pages_controller.rb
index efea6ebec..98ecd3363 100644
--- a/app/controllers/pages_controller.rb
+++ b/app/controllers/pages_controller.rb
@@ -19,8 +19,6 @@ def mydb; end
def editor; end
- def jsmol; end
-
def sfn_cb
code = params[:code]
sf_verifer = request.env.dig('action_dispatch.request.unsigned_session_cookie', 'omniauth.pkce.verifier')
diff --git a/app/jobs/chemotion_embargo_pubchem_job.rb b/app/jobs/chemotion_embargo_pubchem_job.rb
index adc05be8d..0054c98c9 100644
--- a/app/jobs/chemotion_embargo_pubchem_job.rb
+++ b/app/jobs/chemotion_embargo_pubchem_job.rb
@@ -146,7 +146,15 @@ def send_pubchem
def send_email
if ENV['PUBLISH_MODE'] == 'production'
- PublicationMailer.mail_publish_embargo_release(@embargo_collection.id).deliver_now
+ begin
+ PublicationMailer.mail_publish_embargo_release(@embargo_collection.id).deliver_now
+ rescue StandardError => e
+ Delayed::Worker.logger.error <<~TXT
+ --------- ChemotionEmbargoPubchemJob send_email error --------------
+ Error Message: #{e.message}
+ --------------------------------------------------------------------
+ TXT
+ end
end
Message.create_msg_notification(
channel_subject: Channel::PUBLICATION_REVIEW,
diff --git a/app/jobs/chemotion_repo_reviewing_job.rb b/app/jobs/chemotion_repo_reviewing_job.rb
index ec9423cd6..fa4cac43d 100644
--- a/app/jobs/chemotion_repo_reviewing_job.rb
+++ b/app/jobs/chemotion_repo_reviewing_job.rb
@@ -28,7 +28,7 @@ def notify_users
channel_subject: Channel::PUBLICATION_REVIEW,
message_from: submitter
}
- sgl = publication.review.dig('reviewers').nil? ? [submitter] : publication.review.dig('reviewers') + [submitter]
+ sgl = publication.review&.dig('reviewers').nil? ? [submitter] : publication.review&.dig('reviewers') + [submitter]
case publication.state
when Publication::STATE_PENDING
diff --git a/app/jobs/submitting_job.rb b/app/jobs/submitting_job.rb
new file mode 100644
index 000000000..6f70501f9
--- /dev/null
+++ b/app/jobs/submitting_job.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+class SubmittingJob < ApplicationJob
+ include ActiveJob::Status
+ queue_as :submitting
+
+ def max_attempts
+ 1
+ end
+
+ def perform(params, type, author_ids, current_user_id)
+ if params[:scheme_only]
+ scheme_yield = params[:products]&.map { |v| v.slice(:id, :_equivalent) } || []
+ scheme_params = {
+ scheme_yield: scheme_yield,
+ temperature: params[:temperature],
+ duration: params[:duration],
+ schemeDesc: params[:schemeDesc]
+ }
+ else
+ scheme_params = {}
+ end
+
+ Repo::Submission.new(
+ type: type,
+ id: params[:id],
+ author_ids: author_ids,
+ group_leaders: params[:reviewers],
+ analyses_ids: params[:analysesIds],
+ refs: params[:refs],
+ license: params[:license],
+ embargo: params[:embargo],
+ scheme_only: params[:scheme_only] || false,
+ scheme_params: scheme_params,
+ user_id: current_user_id
+ ).submitting
+ end
+end
\ No newline at end of file
diff --git a/app/models/channel.rb b/app/models/channel.rb
index 147c024bf..e92dc23f5 100644
--- a/app/models/channel.rb
+++ b/app/models/channel.rb
@@ -43,6 +43,7 @@ class Channel < ApplicationRecord
# REPOSITORY ONLY
PUBLICATION_REVIEW = 'Publication Review'
PUBLICATION_PUBLISHED = 'Publication Published'
+ SUBMITTING = 'Publication Submission'
class << self
def build_message(**args)
diff --git a/app/models/concerns/collectable.rb b/app/models/concerns/collectable.rb
index 1789c502a..3936fa0e3 100644
--- a/app/models/concerns/collectable.rb
+++ b/app/models/concerns/collectable.rb
@@ -6,7 +6,7 @@ module Collectable
scope :for_user_n_groups, ->(user_ids) { joins(:collections).where('collections.user_id IN (?)', user_ids).references(:collections) }
scope :by_collection_id, ->(id) { joins(:collections).where('collections.id = ?', id) }
scope :search_by, ->(search_by_method, arg) { public_send("search_by_#{search_by_method}", arg) }
-
+ scope :by_user_label, ->(id) { joins(:tag).where("element_tags.taggable_data->'user_labels' @> '?'", id) }
# TODO: Filters are not working properly
# the following scopes are not working as I would expect
# in the ui the selection is a date but in the api we are getting a timestamp,
diff --git a/app/models/concerns/element_codes.rb b/app/models/concerns/element_codes.rb
index 3ceda5f43..9a33a4e12 100644
--- a/app/models/concerns/element_codes.rb
+++ b/app/models/concerns/element_codes.rb
@@ -13,6 +13,9 @@ def code_logs
return [] if source_class == 'container' && containable_type == 'Labimotion::Element'
CodeLog.where(source: source_class).where(source_id: id).order(created_at: 'DESC')
+ rescue StandardError => e
+ Rails.logger.error e.message
+ []
end
def code_log() code_logs.first end
diff --git a/app/models/concerns/embargo_col.rb b/app/models/concerns/embargo_col.rb
index bde82f7ee..995387daa 100644
--- a/app/models/concerns/embargo_col.rb
+++ b/app/models/concerns/embargo_col.rb
@@ -3,8 +3,9 @@ module EmbargoCol
def refresh_embargo_metadata
return if element_type != 'Collection' || element.nil?
- ps = Publication.where(element_type: 'Sample', ancestry: nil, element_id: element.samples&.pluck(:id))
- pr = Publication.where(element_type: 'Reaction', ancestry: nil, element_id: element.reactions&.pluck(:id))
+ # Fetch publications for samples and reactions with preloaded doi
+ ps = Publication.where(element_type: 'Sample', ancestry: nil, element_id: element.samples&.pluck(:id)).includes(:doi)
+ pr = Publication.where(element_type: 'Reaction', ancestry: nil, element_id: element.reactions&.pluck(:id)).includes(:doi)
creators = []
author_ids = []
@@ -14,16 +15,15 @@ def refresh_embargo_metadata
dois = []
(ps + pr)&.each do |pu|
- if pu.taggable_data['scheme_only'] == true
- else
- eids.push(pu.id)
- dois.push({ id: pu.id, element_type: pu.element_type, element_id: pu.element_id, doi: pu.doi.full_doi, state: pu.state }) if pu.doi.present?
- ctag = pu.taggable_data || {}
- creators << ctag["creators"]
- author_ids << ctag["author_ids"]
- affiliation_ids << ctag["affiliation_ids"]
- contributors = ctag["contributors"]
- end
+ next if pu.taggable_data['scheme_only'] == true
+
+ eids.push(pu.id)
+ dois.push({ id: pu.id, element_type: pu.element_type, element_id: pu.element_id, doi: pu.doi.full_doi, state: pu.state }) if pu.doi.present?
+ ctag = pu.taggable_data || {}
+ creators << ctag["creators"]
+ author_ids << ctag["author_ids"]
+ affiliation_ids << ctag["affiliation_ids"]
+ contributors = ctag["contributors"]
end
et = ElementTag.find_or_create_by(taggable_id: element_id, taggable_type: element_type)
diff --git a/app/models/concerns/metadata_jsonld.rb b/app/models/concerns/metadata_jsonld.rb
index dab3bb694..027db2669 100644
--- a/app/models/concerns/metadata_jsonld.rb
+++ b/app/models/concerns/metadata_jsonld.rb
@@ -12,21 +12,21 @@ def json_ld(ext = nil)
if element_type == 'Sample'
json_ld_sample_root
elsif element_type == 'Reaction'
- json_ld_reaction(ext)
+ json_ld_reaction(self, ext)
elsif element_type == 'Container'
json_ld_container
end
end
- def json_ld_sample_root(pub = self)
- json = json_ld_study
- json['about'] = [json_ld_sample]
+ def json_ld_sample_root(pub = self, is_root = true)
+ json = json_ld_study(pub, is_root)
+ json['about'] = [json_ld_sample(pub, nil, is_root)]
json
end
- def json_ld_study(pub = self)
+ def json_ld_study(pub = self, is_root = true)
json = {}
- json['@context'] = 'https://schema.org'
+ json['@context'] = 'https://schema.org' if is_root == true
json['@type'] = 'Study'
json['@id'] = "https://doi.org/#{doi.full_doi}"
json['dct:conformsTo'] = {
@@ -39,13 +39,13 @@ def json_ld_study(pub = self)
json['author'] = json_ld_authors(pub.taggable_data)
json['contributor'] = json_ld_contributor(pub.taggable_data["contributors"])
json['citation'] = json_ld_citations(pub.element.literatures, pub.element.id)
- json['includedInDataCatalog'] = json_ld_data_catalog(pub)
+ json['includedInDataCatalog'] = json_ld_data_catalog(pub) if is_root == true
json
end
def json_ld_data_catalog(pub = self)
json = {}
- json['@context'] = 'https://schema.org'
+ ## json['@context'] = 'https://schema.org'
json['@type'] = 'DataCatalog'
json['@id'] = 'https://www.chemotion-repository.net'
json['description'] = 'Repository for samples, reactions and related research data.'
@@ -137,12 +137,12 @@ def conforms_to
}
end
- def json_ld_sample(pub = self, ext = nil)
+ def json_ld_sample(pub = self, ext = nil, is_root = true)
# metadata_xml
json = {}
- json['@context'] = 'https://schema.org'
+ json['@context'] = 'https://schema.org' if is_root == true
json['@type'] = 'ChemicalSubstance'
- json['@id'] = "https://doi.org/#{pub.doi.full_doi}"
+ json['@id'] = pub.doi.full_doi
json['identifier'] = "CRS-#{pub.id}"
json['url'] = "https://www.chemotion-repository.net/inchikey/#{pub.doi.suffix}"
json['name'] = pub.element.molecule_name&.name
@@ -152,7 +152,8 @@ def json_ld_sample(pub = self, ext = nil)
json['description'] = json_ld_description(pub.element.description)
#json['author'] = json_ld_authors(pub.taggable_data)
json['hasBioChemEntityPart'] = json_ld_moelcule_entity(pub.element)
- json['subjectOf'] = json_ld_subjectOf(pub)
+ json['subjectOf'] = json_ld_subjectOf(pub) if is_root == true
+ json['isPartOf'] = is_part_of(pub) if pub.parent.present?
#json_object = JSON.parse(json)
#JSON.pretty_generate(json_object)
@@ -166,27 +167,27 @@ def json_ld_sample(pub = self, ext = nil)
# formatted_json
end
- def json_ld_reaction(ext = nil)
+ def json_ld_reaction(pub= self, ext = nil, is_root = true)
json = {}
- json['@context'] = 'https://schema.org'
+ json['@context'] = 'https://schema.org' if is_root == true
json['@type'] = 'Study'
- json['@id'] = "https://doi.org/#{doi.full_doi}"
- json['identifier'] = "CRR-#{id}"
- json['url'] = "https://www.chemotion-repository.net/inchikey/#{doi.suffix}"
+ json['@id'] = pub.doi.full_doi
+ json['identifier'] = "CRR-#{pub.id}"
+ json['url'] = "https://www.chemotion-repository.net/inchikey/#{pub.doi.suffix}"
json['additionalType'] = 'Reaction'
- json['name'] = element.rinchi_short_key
- json['creator'] = json_ld_authors(taggable_data)
+ json['name'] = pub.element.rinchi_short_key
+ json['creator'] = json_ld_authors(pub.taggable_data)
json['author'] = json['creator']
- json['description'] = json_ld_description(element.description)
- json['license'] = rights_data[:rightsURI]
- json['datePublished'] = published_at&.strftime('%Y-%m-%d')
- json['dateCreated'] = created_at&.strftime('%Y-%m-%d')
+ json['description'] = json_ld_description(pub.element.description)
+ json['license'] = pub.rights_data[:rightsURI]
+ json['datePublished'] = pub.published_at&.strftime('%Y-%m-%d')
+ json['dateCreated'] = pub.created_at&.strftime('%Y-%m-%d')
json['publisher'] = json_ld_publisher
json['provider'] = json_ld_publisher
json['keywords'] = 'chemical reaction: structures conditions'
- json['citation'] = json_ld_citations(element.literatures, element.id)
- json['subjectOf'] = json_ld_reaction_has_part(ext)
+ json['citation'] = json_ld_citations(pub.element.literatures, pub.element.id)
+ json['subjectOf'] = json_ld_reaction_has_part(pub, ext, is_root) if is_root == true
if ext == 'LLM'
json = Metadata::Jsonldllm.reaction_ext(json, element)
@@ -210,17 +211,17 @@ def json_ld_lab_protocol
json
end
- def json_ld_reaction_has_part(ext = nil)
+ def json_ld_reaction_has_part(root, ext = nil, is_root = true)
json = []
- children&.each do |pub|
+ root.children&.each do |pub|
json.push(json_ld_sample(pub, ext)) if pub.element_type == 'Sample'
json.push(json_ld_analysis(pub, false)) if pub.element_type == 'Container'
end
- if ext == 'LLM' && element&.samples.present?
- element&.samples&.each do |sample|
+ if ext == 'LLM' && root.element&.samples.present?
+ root.element&.samples&.each do |sample|
next if sample.publication.present? || !sample.collections&.pluck(:id).include?(Collection.public_collection&.id)
- json.push(Metadata::Jsonldllm.all_samples(element, sample))
+ json.push(Metadata::Jsonldllm.all_samples(root.element, sample))
end
end
@@ -236,13 +237,15 @@ def json_ld_description(desc)
#xml_data = Nokogiri::XML(metadata_xml)
#desc = xml_data.search('description')&.text&.strip
#desc
+ rescue StandardError => e
+ Rails.logger.error ["API call - json_ld_description:", e.message, *e.backtrace].join($INPUT_RECORD_SEPARATOR)
+ ''
end
def json_ld_container
json_ld_analysis(self, true)
end
-
def json_ld_subjectOf(pub = self)
arr = []
# arr.push(json_ld_creative_work(pub))
@@ -254,23 +257,103 @@ def json_ld_subjectOf(pub = self)
def json_ld_analysis(pub = self, root = true)
json = {}
- json['@context'] = 'https://schema.org'
+ json['@context'] = 'https://schema.org' if root == true
json['@type'] = 'Dataset'
- json['@id'] = "https://doi.org/#{pub.doi.full_doi}"
+ json['@id'] = pub.doi.full_doi
json['identifier'] = "CRD-#{pub.id}"
json['url'] = "https://www.chemotion-repository.net/inchikey/#{pub.doi.suffix}"
+ json['dct:conformsTo'] = {
+ "@id": 'https://schema.org/Dataset',
+ "@type": 'CreativeWork'
+ }
json['publisher'] = json_ld_publisher
json['license'] = pub.rights_data[:rightsURI]
- json['name'] = pub.element.extended_metadata['kind'] || '' if pub&.element&.extended_metadata.present?
+ json['name'] = (pub.element.extended_metadata['kind'] || '').split(' | ')&.last if pub&.element&.extended_metadata.present?
measureInfo = json_ld_measurement_technique(pub) if pub&.element&.extended_metadata.present? && ENV['OLS_SERVICE'].present?
+ variable_measured = json_ld_variable_measured(pub)
json['measurementTechnique'] = measureInfo if measureInfo.present?
+ json['variableMeasured'] = variable_measured if variable_measured.present?
json['creator'] = json_ld_authors(pub.taggable_data)
json['author'] = json['creator']
json['description'] = json_ld_analysis_description(pub)
- json['includedInDataCatalog'] = json_ld_data_catalog(pub) if root == true
+ if root == true
+ json['includedInDataCatalog'] = json_ld_data_catalog(pub)
+ json['isPartOf'] = is_part_of(pub)
+ end
+ json
+ end
+
+ def is_part_of(pub = self)
+
+ json = {}
+ if pub.parent.present?
+ json = json_ld_reaction(pub.parent, nil, false) if pub.parent.element_type == 'Reaction'
+ json = json_ld_sample_root(pub.parent, false) if pub.parent.element_type == 'Sample'
+ end
json
end
+ def get_val(field)
+ return '' if field.blank?
+ case field['type']
+ when Labimotion::FieldType::SYSTEM_DEFINED
+ unit = Labimotion::Units::FIELDS.find { |o| o[:field] == field['option_layers'] }&.fetch(:units, []).find { |u| u[:key] == field['value_system'] }&.fetch(:label, '')
+ "#{field['value']} #{unit}"
+ when Labimotion::FieldType::TEXT, Labimotion::FieldType::INTEGER, Labimotion::FieldType::SELECT, Labimotion::FieldType::INTEGER
+ field['value']
+ else
+ ''
+ end
+ end
+
+ def get_ols_short_form(field, klass, key)
+ short_form = field.fetch('ontology', {}).fetch('short_form', '')
+ return short_form if short_form.present?
+
+ klass_prop = klass['properties_release']
+ klass_layer = klass_prop['layers'][key]
+ klass_field = klass_layer && klass_layer['fields']&.find { |f| f['field'] == field['field'] }
+ return klass_field && klass_field['ontology']&.fetch('short_form', '')
+ end
+
+ def json_ld_variable_measured(pub = self)
+ arr = []
+ analysis = pub.element
+ containers = Container.where(parent_id: analysis.id, container_type: 'dataset')
+ containers.each do |container|
+ ds = container.dataset
+ ols_id = ds&.dataset_klass&.ols_term_id
+ next if ds.nil? || ols_id.nil?
+ # mcon = Rails.configuration.try(:m)&.dataset&.find { |ss| ss[:ols_term] == ols_id }
+ # con_layers = mcon[:layers].pluck(:identifier) if mcon.present?
+ # next if mcon.nil? || con_layers.nil?
+
+ klass = Labimotion::DatasetKlass.find_by(ols_term_id: ols_id)
+
+ ds&.properties.fetch('layers', nil)&.keys.each do |key|
+ # next unless con_layers&.include?(key)
+ # mcon_fields = mcon[:layers].find { |ss| ss[:identifier] == key }&.fetch(:fields, [])&.map { |field| field[:identifier] }
+ # next if mcon_fields.nil?
+
+ ds&.properties['layers'][key].fetch('fields', []).each do |field|
+ # next unless mcon_fields&.include?(field['field'])
+ # short_form = field.fetch('ontology', {}).fetch('short_form', '')
+ short_form = get_ols_short_form(field, klass, key)
+ val = get_val(field)
+ next if field['value'].blank? || short_form.blank? || val.blank?
+
+ json = {}
+ json['@type'] = 'PropertyValue'
+ json['name'] = field["label"]
+ json['propertyID'] = short_form if short_form.present?
+ json['value'] = val
+ arr.push(json) unless json.empty?
+ end
+ end
+ end
+ arr
+ end
+
def json_ld_measurement_technique(pub = self)
json = {}
term_id = pub.element.extended_metadata['kind']&.split('|')&.first&.strip
@@ -296,11 +379,12 @@ def json_ld_analysis_description(pub)
kind = 'dataset for ' + (element.extended_metadata['kind'] || '')&.split('|').pop + '\n'
desc = element.extended_metadata['description'] || '' + '\n'
content = element.extended_metadata['content'].nil? ? '' : REXML::Text.new(Nokogiri::HTML( Chemotion::QuillToHtml.new.convert(element.extended_metadata['content'])).text, false, nil, false).to_s
-
kind + desc + content
+ rescue StandardError => e
+ Rails.logger.error ["API call - json_ld_analysis_description:", e.message, *e.backtrace].join($INPUT_RECORD_SEPARATOR)
+ kind + desc
end
-
def json_ld_citations(literatures, id)
json = []
literatures.each do |lit|
diff --git a/app/models/concerns/publishing.rb b/app/models/concerns/publishing.rb
index 8a2c6285d..7ee4d5e45 100644
--- a/app/models/concerns/publishing.rb
+++ b/app/models/concerns/publishing.rb
@@ -68,7 +68,6 @@ def reserve_suffix_analyses(as = Container.none)
### for all
def full_doi
return nil unless (d = Doi.find_by(doiable: self))
-
d.full_doi
end
diff --git a/app/models/publication.rb b/app/models/publication.rb
index 834649369..975c63cb5 100644
--- a/app/models/publication.rb
+++ b/app/models/publication.rb
@@ -76,7 +76,7 @@ def puttextcontent(content, remotefile, &block)
STATE_RETRACTED = 'retracted'
def embargoed?(root_publication = root)
- cid = User.find(root_publication.published_by).publication_embargo_collection.id
+ cid = User.with_deleted.find(root_publication.published_by).publication_embargo_collection.id
embargo_col = root_publication.element.collections.select { |c| c['ancestry'].to_i == cid }
embargo_col.present? ? true : false
end
@@ -103,6 +103,7 @@ def process_element(new_state = state)
when Publication::STATE_ACCEPTED
move_to_accepted_collection
group_review_collection
+ publish_user_labels
when Publication::STATE_DECLINED
declined_reverse_original_element
declined_move_collections
@@ -112,7 +113,7 @@ def process_element(new_state = state)
# WARNING: for STATE_ACCEPTED the method does more than just notify users (see ChemotionRepoPublishingJob)
# TODO: separate publishing responsability
- def inform_users(new_state = state, current_user_id = 0)
+ def process_new_state_job(new_state = state, current_user_id = 0)
method = if ENV['PUBLISH_MODE'] == 'production' && Rails.env.production?
:perform_later
elsif ENV['PUBLISH_MODE'] == 'staging'
@@ -130,9 +131,32 @@ def inform_users(new_state = state, current_user_id = 0)
klass.set(queue: "#{queue_name} #{id}").send(method, id, new_state, current_user_id)
end
+ def publish_user_labels
+ tag = element&.tag
+ return if tag.nil?
+
+ data = tag.taggable_data || {}
+ return if data['user_labels'].blank?
+
+ public_labels = UserLabel.where(id: data['user_labels'], access_level: 2).pluck(:id)
+ data['user_labels'] = public_labels
+ tag.save!
+
+ pub_data = taggable_data || {}
+ pub_data['user_labels'] = public_labels
+ update_columns(taggable_data: pub_data)
+ end
+
+ def update_user_labels(user_labels, current_user_id)
+ data = taggable_data || {}
+ private_labels = UserLabel.where(id: data['user_labels'], access_level: [0, 1]).where.not(user_id: current_user_id).pluck(:id)
+ data['user_labels'] = ((user_labels || []) + private_labels)&.uniq
+ update_columns(taggable_data: data)
+ end
+
# remove publication element from editable collections
def move_to_accepted_collection
- pub_user = User.find(published_by)
+ pub_user = User.with_deleted.find(published_by)
return false unless pub_user && element
return true unless embargoed?
@@ -157,7 +181,7 @@ def move_to_accepted_collection
# move publication element from reviewer editable collection to submitter editable collection
# move publication element from submitter readable collection to reviewer readable collection
def move_to_review_collection
- pub_user = User.find(published_by)
+ pub_user = User.with_deleted.find(published_by)
return false unless pub_user && element
case element_type
@@ -173,7 +197,7 @@ def move_to_review_collection
end
def move_to_pending_collection
- pub_user = User.find(published_by)
+ pub_user = User.with_deleted.find(published_by)
return false unless pub_user && element
case element_type
@@ -203,11 +227,11 @@ def declined_reverse_original_element
end
def group_review_collection
- pub_user = User.find(published_by)
+ pub_user = User.with_deleted.find(published_by)
return false unless pub_user && element
group_reviewers = review && review['reviewers']
- reviewers = User.where(id: group_reviewers) if group_reviewers.present?
+ reviewers = User.where(id: group_reviewers, type: 'Person') if group_reviewers.present?
return false if reviewers&.empty?
reviewers&.each do |user|
@@ -237,7 +261,7 @@ def declined_reverse_original_reaction_elements
end
def declined_move_collections
- all_col_id = User.find(published_by).all_collection&.id
+ all_col_id = User.with_deleted.find(published_by).all_collection&.id
return unless element && all_col_id
col_ids = element&.collections&.pluck(:id)
@@ -285,6 +309,11 @@ def publication_logger
@@publication_logger ||= Logger.new(File.join(Rails.root, 'log', 'publication.log'))
end
+
+ def self.repository_logger
+ @@repository_logger ||= Logger.new(Rails.root.join('log/repository.log'))
+ end
+
def doi_bag
d = doi
case element_type
@@ -716,6 +745,21 @@ def log_invalid_transition(to_state)
logger("CANNOT TRANSITION from #{state} to #{to_state}")
end
+ def self.repo_log_exception(exception, options = {})
+ Publication.repository_logger.error(self.class.name);
+ Publication.repository_logger.error("options [#{options}] \n ")
+ Publication.repository_logger.error("exception: #{exception.message} \n")
+ Publication.repository_logger.error(exception.backtrace.join("\n"))
+
+ # send message to admin
+ Message.create_msg_notification(
+ channel_id: Channel.find_by(subject: Channel::SUBMITTING)&.id,
+ message_from: User.find_by(name_abbreviation: 'CHI')&.id,
+ autoDismiss: 5,
+ message_content: { 'data': "Repository Error Log: #{exception.message}" },
+ )
+ end
+
def logger(message_arr)
message = [message_arr].flatten.join("\n")
publication_logger.info(
diff --git a/app/models/user_label.rb b/app/models/user_label.rb
index 7d4841f92..2954035f0 100644
--- a/app/models/user_label.rb
+++ b/app/models/user_label.rb
@@ -17,10 +17,36 @@
class UserLabel < ApplicationRecord
acts_as_paranoid
- def self.public_labels(label_ids)
+ def self.public_labels(label_ids, current_user, is_public)
return [] if label_ids.blank?
- labels = UserLabel.where(id: label_ids, access_level: [1,2])
- .order('access_level desc, position, title')
- labels&.map{|l| {title: l.title, description: l.description, color: l.color} } ||[]
+
+ if is_public
+ labels = UserLabel.where(id: label_ids, access_level: 2).order('access_level desc, position, title')
+ else
+ if User.reviewer_ids.include?(current_user.id)
+ labels = UserLabel.where('id in (?) and ((user_id = ? AND access_level = 0) OR access_level IN (?))', label_ids, current_user.id, [1, 2, 3])
+ .order('access_level desc, position, title')
+ else
+ labels = UserLabel.where('id in (?) and ((user_id = ? AND access_level = 0) OR access_level IN (?))', label_ids, current_user.id, [1, 2])
+ .order('access_level desc, position, title')
+ end
+ end
+
+ labels&.map{|l| {id: l.id, title: l.title, description: l.description, color: l.color, access_level: l.access_level} } ||[]
+ end
+
+ def self.my_labels(current_user, is_public)
+ if is_public
+ labels = UserLabel.where('access_level IN (?)', current_user.id, [2])
+ .order('access_level desc, position, title')
+ else
+ if User.reviewer_ids.include?(current_user.id)
+ labels = UserLabel.where('(user_id = ? AND access_level in (0, 1)) OR access_level IN (2, 3)', current_user.id)
+ .order('access_level desc, position, title')
+ else
+ labels = UserLabel.where('(user_id = ? AND access_level in (0, 1)) OR access_level = 2', current_user.id)
+ .order('access_level desc, position, title')
+ end
+ end
end
end
diff --git a/app/packs/src/apps/home/Home.js b/app/packs/src/apps/home/Home.js
index 36a586e7b..516337524 100644
--- a/app/packs/src/apps/home/Home.js
+++ b/app/packs/src/apps/home/Home.js
@@ -2,12 +2,9 @@ import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Grid, Row } from 'react-bootstrap';
import Aviator from 'aviator';
-
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
-
import initPublicRoutes from 'src/libHome/homeRoutes';
-
import Navigation from 'src/libHome/Navigation';
import Notifications from 'src/components/Notifications';
import RepoEmbargo from 'src/repoHome/RepoEmbargo';
@@ -35,19 +32,18 @@ import LoadingModal from 'src/components/common/LoadingModal';
import PublicActions from 'src/stores/alt/repo/actions/PublicActions';
import RepoGenericHub from 'src/repoHome/RepoGenericHub';
-import embedMatomo from 'src/components/chemrepo/matomo';
+import SysInfo from 'src/components/chemrepo/SysInfo';
class Home extends Component {
constructor(props) {
super();
this.state = {
- guestPage: null
+ guestPage: null,
};
this.onChange = this.onChange.bind(this);
}
componentDidMount() {
- embedMatomo();
PublicStore.listen(this.onChange);
RStore.listen(this.onChange);
PublicActions.initialize();
@@ -69,7 +65,8 @@ class Home extends Component {
}
renderGuestPage() {
- switch (this.state.guestPage) {
+ const { guestPage, listType } = this.state;
+ switch (guestPage) {
case 'genericHub':
return ;
case 'moleculeArchive':
@@ -91,7 +88,7 @@ class Home extends Component {
case 'contact':
return ;
case 'publications':
- return ;
+ return ;
case 'review':
return ;
case 'collection':
@@ -115,7 +112,8 @@ class Home extends Component {
}
renderNavFooter() {
- switch (this.state.guestPage) {
+ const { guestPage } = this.state;
+ switch (guestPage) {
case 'publications':
case 'review':
case 'embargo':
@@ -134,15 +132,14 @@ class Home extends Component {
render() {
return (
+
-
- {this.renderGuestPage()}
-
+ {this.renderGuestPage()}
{this.renderNavFooter()}
diff --git a/app/packs/src/apps/home/index.js b/app/packs/src/apps/home/index.js
deleted file mode 100644
index da53cab94..000000000
--- a/app/packs/src/apps/home/index.js
+++ /dev/null
@@ -1,8 +0,0 @@
-import React from 'react';
-import ReactDOM from 'react-dom';
-import Home from 'src/apps/home/Home';
-
-document.addEventListener('DOMContentLoaded', () => {
- const domElement = document.getElementById('Home');
- if (domElement) { ReactDOM.render(
, domElement); }
-});
diff --git a/app/packs/src/apps/mydb/elements/details/ElementDetails.js b/app/packs/src/apps/mydb/elements/details/ElementDetails.js
index fc9385906..719da02ad 100644
--- a/app/packs/src/apps/mydb/elements/details/ElementDetails.js
+++ b/app/packs/src/apps/mydb/elements/details/ElementDetails.js
@@ -174,10 +174,17 @@ export default class ElementDetails extends Component {
content(_el) {
const el = _el;
- el.sealed = getPublicationId(el) ? true : false;
+ const isPending = el?.tag?.taggable_data?.publish_pending;
+
+ el.sealed = isPending || !!getPublicationId(el);
if (el && el.klassType === 'GenericEl' && el.type != null) {
- return
;
+ return (
+
+ );
}
switch (el.type) {
diff --git a/app/packs/src/apps/mydb/elements/details/literature/LiteratureCommon.js b/app/packs/src/apps/mydb/elements/details/literature/LiteratureCommon.js
index 99e9d8f47..88fac8dc0 100644
--- a/app/packs/src/apps/mydb/elements/details/literature/LiteratureCommon.js
+++ b/app/packs/src/apps/mydb/elements/details/literature/LiteratureCommon.js
@@ -138,13 +138,18 @@ const literatureContent = (literature, onlyText) => {
indexData = indexData.substr(0, indexData.lastIndexOf(','));
litBibtex = litBibtex.replace(indexData, indexData.replace(/[^a-zA-Z0-9\-_]/g, ''));
}
- const citation = new Cite(litBibtex);
+ let citation;
+ try {
+ citation = new Cite(litBibtex);
+ } catch (error) {
+ console.error('An error occurred:', error);
+ }
if (onlyText) {
- content = citation.format('bibliography', { format: 'text', template: 'apa' });
+ content = citation?.format('bibliography', { format: 'text', template: 'apa' });
} else {
content = (
- {citation.format('bibliography', { format: 'text', template: 'apa' })}
+ {citation?.format('bibliography', { format: 'text', template: 'apa' })}
);
}
diff --git a/app/packs/src/apps/mydb/elements/details/reactions/ReactionDetails.js b/app/packs/src/apps/mydb/elements/details/reactions/ReactionDetails.js
index 39c6a5c6a..40fdf338f 100644
--- a/app/packs/src/apps/mydb/elements/details/reactions/ReactionDetails.js
+++ b/app/packs/src/apps/mydb/elements/details/reactions/ReactionDetails.js
@@ -49,6 +49,7 @@ import CommentActions from 'src/stores/alt/actions/CommentActions';
import CommentModal from 'src/components/common/CommentModal';
import { commentActivation } from 'src/utilities/CommentHelper';
import { formatTimeStampsOfElement } from 'src/utilities/timezoneHelper';
+import { ShowUserLabels } from 'src/components/UserLabels';
import {
PublishedTag,
@@ -526,6 +527,7 @@ export default class ReactionDetails extends Component {
{colLabel}
{rsPlanLabel}
+
{ schemeOnly ? scheme only : '' }
diff --git a/app/packs/src/apps/mydb/elements/details/reactions/propertiesTab/ReactionDetailsProperties.js b/app/packs/src/apps/mydb/elements/details/reactions/propertiesTab/ReactionDetailsProperties.js
index fce129f6f..db9b04bb0 100644
--- a/app/packs/src/apps/mydb/elements/details/reactions/propertiesTab/ReactionDetailsProperties.js
+++ b/app/packs/src/apps/mydb/elements/details/reactions/propertiesTab/ReactionDetailsProperties.js
@@ -14,6 +14,7 @@ import { solventsTL } from 'src/utilities/reactionPredefined';
import OlsTreeSelect from 'src/components/OlsComponent';
import { permitOn } from 'src/components/common/uis';
import HelpInfo from 'src/components/common/HelpInfo';
+import { EditUserLabels } from 'src/components/UserLabels';
export default class ReactionDetailsProperties extends Component {
constructor(props) {
@@ -166,6 +167,11 @@ export default class ReactionDetailsProperties extends Component {
+
+
+
+
+
diff --git a/app/packs/src/apps/mydb/elements/details/reactions/schemeTab/Material.js b/app/packs/src/apps/mydb/elements/details/reactions/schemeTab/Material.js
index 8216b7a20..417321204 100644
--- a/app/packs/src/apps/mydb/elements/details/reactions/schemeTab/Material.js
+++ b/app/packs/src/apps/mydb/elements/details/reactions/schemeTab/Material.js
@@ -403,7 +403,7 @@ class Material extends Component {
createParagraph(m) {
const { materialGroup } = this.props;
- let molName = m.molecule_name_hash.label;
+ let molName = m.molecule_name_hash?.label;
if (!molName) { molName = m.molecule.iupac_name; }
if (!molName) { molName = m.molecule.sum_formular; }
diff --git a/app/packs/src/apps/mydb/elements/details/samples/SampleDetails.js b/app/packs/src/apps/mydb/elements/details/samples/SampleDetails.js
index b71806b15..b4c48594a 100644
--- a/app/packs/src/apps/mydb/elements/details/samples/SampleDetails.js
+++ b/app/packs/src/apps/mydb/elements/details/samples/SampleDetails.js
@@ -519,27 +519,6 @@ export default class SampleDetails extends React.Component {
this.forceUpdate();
}
- svgOrLoading(sample) {
- let svgPath = '';
- if (this.state.loadingMolecule) {
- svgPath = '/images/wild_card/loading-bubbles.svg';
- } else {
- svgPath = sample.svgPath;
- }
- let className = svgPath ? 'svg-container' : 'svg-container-empty'
- return (
- sample.can_update
- ?
-
-
-
- :
-
-
- );
- }
-
handleChemIdentSectionToggle() {
this.setState({
showChemicalIdentifiers: !this.state.showChemicalIdentifiers
@@ -981,7 +960,7 @@ export default class SampleDetails extends React.Component {
decoupleMolecule={this.decoupleMolecule}
/>
-
+
{this.elementalPropertiesItem(sample)}
{this.chemicalIdentifiersItem(sample)}
@@ -1556,32 +1535,45 @@ export default class SampleDetails extends React.Component {
svgOrLoading(sample) {
let svgPath = '';
+ const { svgPath: sampleSvgPath } = sample;
if (this.state.loadingMolecule) {
svgPath = '/images/wild_card/loading-bubbles.svg';
} else {
- svgPath = sample.svgPath;
+ svgPath = sampleSvgPath;
}
const className = svgPath ? 'svg-container' : 'svg-container-empty';
- return (
- sample.can_update
- ? (
-
-
-
-
- )
- : (
-
-
-
- )
+ return sample.can_update ? (
+ <>
+
+
+
+
+
+ >
+ ) : (
+
+
+
+
);
}
@@ -1870,12 +1862,6 @@ export default class SampleDetails extends React.Component {
tabTitles={tabTitlesMap}
onTabPositionChanged={this.onTabPositionChanged}
/>
-
{this.state.sfn ?
: null}
{tabContents}
diff --git a/app/packs/src/apps/mydb/elements/details/samples/analysesTab/SampleDetailsContainers.js b/app/packs/src/apps/mydb/elements/details/samples/analysesTab/SampleDetailsContainers.js
index 968bd88af..317745c14 100644
--- a/app/packs/src/apps/mydb/elements/details/samples/analysesTab/SampleDetailsContainers.js
+++ b/app/packs/src/apps/mydb/elements/details/samples/analysesTab/SampleDetailsContainers.js
@@ -45,9 +45,6 @@ export default class SampleDetailsContainers extends Component {
TextTemplateActions.fetchTextTemplates('sample');
}
- // componentWillReceiveProps(nextProps) {
- // }
-
componentWillUnmount() {
UIStore.unlisten(this.onUIStoreChange);
}
diff --git a/app/packs/src/apps/mydb/elements/details/screens/ScreenDetails.js b/app/packs/src/apps/mydb/elements/details/screens/ScreenDetails.js
index 2e99d2eb2..02c84db68 100644
--- a/app/packs/src/apps/mydb/elements/details/screens/ScreenDetails.js
+++ b/app/packs/src/apps/mydb/elements/details/screens/ScreenDetails.js
@@ -62,7 +62,8 @@ export default class ScreenDetails extends Component {
}
}
- componentWillReceiveProps(nextProps) {
+ // eslint-disable-next-line camelcase
+ UNSAFE_componentWillReceiveProps(nextProps) {
const { screen } = nextProps;
this.setState({ screen });
}
@@ -71,12 +72,6 @@ export default class ScreenDetails extends Component {
UIStore.unlisten(this.onUIStoreChange);
}
- // eslint-disable-next-line camelcase
- UNSAFE_componentWillReceiveProps(nextProps) {
- const { screen } = nextProps;
- this.setState({ screen });
- }
-
onUIStoreChange(state) {
if (state.screen.activeTab !== this.state.activeTab) {
this.setState({
diff --git a/app/packs/src/apps/mydb/elements/details/screens/researchPlansTab/EmbeddedResearchPlanDetails.js b/app/packs/src/apps/mydb/elements/details/screens/researchPlansTab/EmbeddedResearchPlanDetails.js
index b8d04c8dd..686415122 100644
--- a/app/packs/src/apps/mydb/elements/details/screens/researchPlansTab/EmbeddedResearchPlanDetails.js
+++ b/app/packs/src/apps/mydb/elements/details/screens/researchPlansTab/EmbeddedResearchPlanDetails.js
@@ -56,7 +56,7 @@ export default class EmbeddedResearchPlanDetails extends Component {
this.handleBodyAdd = this.handleBodyAdd.bind(this);
}
- componentWillReceiveProps(nextProps) {
+ UNSAFE_componentWillReceiveProps(nextProps) {
let { researchPlan, expanded } = nextProps;
if (!(researchPlan instanceof ResearchPlan)) {
const rResearchPlan = new ResearchPlan(researchPlan);
diff --git a/app/packs/src/apps/mydb/elements/list/ElementsTable.js b/app/packs/src/apps/mydb/elements/list/ElementsTable.js
index 231c709ae..dd2e589c8 100644
--- a/app/packs/src/apps/mydb/elements/list/ElementsTable.js
+++ b/app/packs/src/apps/mydb/elements/list/ElementsTable.js
@@ -16,7 +16,7 @@ import ElementStore from 'src/stores/alt/stores/ElementStore';
import ElementAllCheckbox from 'src/apps/mydb/elements/list/ElementAllCheckbox';
import ElementsTableEntries from 'src/apps/mydb/elements/list/ElementsTableEntries';
import ElementsTableSampleEntries from 'src/apps/mydb/elements/list/ElementsTableSampleEntries';
-
+import { SearchUserLabels } from 'src/components/UserLabels';
import UserStore from 'src/stores/alt/stores/UserStore';
import ElementsTableGroupedEntries from 'src/apps/mydb/elements/list/ElementsTableGroupedEntries';
import Select from 'react-select';
@@ -50,6 +50,7 @@ export default class ElementsTable extends React.Component {
this.changeDateFilter = this.changeDateFilter.bind(this);
this.toggleProductOnly = this.toggleProductOnly.bind(this);
+ this.setUserLabel = this.setUserLabel.bind(this);
this.setFromDate = this.setFromDate.bind(this);
this.setToDate = this.setToDate.bind(this);
this.timer = null;
@@ -108,7 +109,7 @@ export default class ElementsTable extends React.Component {
}
const { checkedIds, uncheckedIds, checkedAll } = state[type];
const {
- filterCreatedAt, fromDate, toDate, number_of_results, currentSearchByID, productOnly
+ filterCreatedAt, fromDate, toDate, userLabel, number_of_results, currentSearchByID, productOnly
} = state;
// check if element details of any type are open at the moment
@@ -120,7 +121,7 @@ export default class ElementsTable extends React.Component {
const { currentStateProductOnly, searchResult } = this.state;
const stateChange = (
checkedIds || uncheckedIds || checkedAll || currentId || filterCreatedAt
- || fromDate || toDate || productOnly !== currentStateProductOnly
+ || fromDate || toDate || userLabel || productOnly !== currentStateProductOnly
|| isSearchResult !== searchResult
);
const moleculeSort = isSearchResult ? true : ElementStore.getState().moleculeSort;
@@ -135,7 +136,8 @@ export default class ElementsTable extends React.Component {
currentId,
number_of_results,
fromDate,
- toDate
+ toDate,
+ userLabel,
},
productOnly,
searchResult: isSearchResult,
@@ -164,6 +166,12 @@ export default class ElementsTable extends React.Component {
if (elementsDidChange || currentElementDidChange) { this.setState(nextState); }
}
+
+ setUserLabel(label) {
+ const { userLabel } = this.state;
+ if (userLabel !== label) UIActions.setUserLabel(label);
+ }
+
setFromDate(date) {
const { fromDate } = this.state;
if (fromDate !== date) UIActions.setFromDate(date);
@@ -573,15 +581,25 @@ export default class ElementsTable extends React.Component {
renderHeader = () => {
const { filterCreatedAt, ui } = this.state;
const { type, showReport, genericEl } = this.props;
- const { fromDate, toDate } = ui;
+ const { fromDate, toDate, userLabel } = ui;
+ let searchLabel = ;
let typeSpecificHeader = ;
if (type === 'sample') {
typeSpecificHeader = this.renderSamplesHeader();
+ searchLabel = (
+
+ );
} else if (type === 'reaction') {
typeSpecificHeader = this.renderReactionsHeader();
+ searchLabel = (
+
+ );
} else if (genericEl) {
typeSpecificHeader = this.renderGenericElementsHeader();
+ searchLabel = (
+
+ );
}
const filterTitle = filterCreatedAt === true
@@ -610,6 +628,7 @@ export default class ElementsTable extends React.Component {
flexWrap: 'wrap'
}}
>
+ {searchLabel}
{reactionVariations(element)}
{sampleMoleculeName}
diff --git a/app/packs/src/apps/mydb/elements/list/ElementsTableGroupedEntries.js b/app/packs/src/apps/mydb/elements/list/ElementsTableGroupedEntries.js
index 7d2b8a607..5dc79cfd3 100644
--- a/app/packs/src/apps/mydb/elements/list/ElementsTableGroupedEntries.js
+++ b/app/packs/src/apps/mydb/elements/list/ElementsTableGroupedEntries.js
@@ -13,6 +13,7 @@ import KeyboardStore from 'src/stores/alt/stores/KeyboardStore';
import { DragDropItemTypes } from 'src/utilities/DndConst';
import { elementShowOrNew } from 'src/utilities/routesUtils';
+import { ShowUserLabels } from 'src/components/UserLabels';
import SvgWithPopover from 'src/components/common/SvgWithPopover';
import { reactionStatus, reactionRole } from 'src/apps/mydb/elements/list/ElementsTableEntries';
@@ -349,6 +350,7 @@ export default class ElementsTableGroupedEntries extends Component {
{reactionStatus(element)}
{reactionRole(element)}
+
@@ -390,6 +392,7 @@ export default class ElementsTableGroupedEntries extends Component {
{element.title()}
+
diff --git a/app/packs/src/components/Quill2Viewer.js b/app/packs/src/components/Quill2Viewer.js
new file mode 100644
index 000000000..2c694a1b2
--- /dev/null
+++ b/app/packs/src/components/Quill2Viewer.js
@@ -0,0 +1,52 @@
+import React, { useEffect, useRef } from 'react';
+import PropTypes from 'prop-types';
+import Quill from 'quill2';
+import { isEqual } from 'lodash';
+import { keepSupSub } from 'src/utilities/quillFormat';
+
+function Quill2Viewer({ value, preview }) {
+ const quillViewerRef = useRef(null);
+ const viewerRef = useRef(null);
+
+ useEffect(() => {
+ if (!viewerRef.current) {
+ const defaultOptions = {
+ theme: 'bubble',
+ readOnly: true,
+ modules: {
+ toolbar: null,
+ },
+ };
+ viewerRef.current = new Quill(quillViewerRef.current, defaultOptions);
+ const oriValue = value;
+ const initialValue = preview ? keepSupSub(oriValue) : oriValue;
+ viewerRef.current.setContents(initialValue);
+ }
+ }, []);
+
+ useEffect(() => {
+ if (viewerRef.current && !isEqual(viewerRef.current.getContents(), value)) {
+ viewerRef.current.setContents(value);
+ }
+ }, [value]);
+
+ return preview ? (
+
+ ) : (
+
+ );
+}
+
+Quill2Viewer.propTypes = {
+ value: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
+ preview: PropTypes.bool,
+};
+
+Quill2Viewer.defaultProps = {
+ value: [],
+ preview: false,
+};
+
+export default Quill2Viewer;
diff --git a/app/packs/src/components/UserLabels.js b/app/packs/src/components/UserLabels.js
index 473f26c31..496815eef 100644
--- a/app/packs/src/components/UserLabels.js
+++ b/app/packs/src/components/UserLabels.js
@@ -3,8 +3,18 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import {
- Modal, Checkbox, Table, Col, Badge, Panel, ButtonGroup, Button,
- Form, FormGroup, FormControl, ControlLabel, InputGroup,
+ Modal,
+ Table,
+ Col,
+ Badge,
+ Panel,
+ ButtonGroup,
+ Button,
+ Form,
+ FormGroup,
+ FormControl,
+ ControlLabel,
+ InputGroup,
} from 'react-bootstrap';
import { CirclePicker } from 'react-color';
import Select from 'react-select';
@@ -12,9 +22,6 @@ import UsersFetcher from 'src/fetchers/UsersFetcher';
import NotificationActions from 'src/stores/alt/actions/NotificationActions';
import UserActions from 'src/stores/alt/actions/UserActions';
import UserStore from 'src/stores/alt/stores/UserStore';
-import MatrixCheck from 'src/components/common/MatrixCheck';
-
-const UL_FUNC_NAME = 'userLabel';
class UserLabelModal extends Component {
constructor(props) {
@@ -23,7 +30,7 @@ class UserLabelModal extends Component {
labels: [],
label: {},
showDetails: false,
- defaultColor: '#428BCA'
+ defaultColor: '#428BCA',
};
this.onChange = this.onChange.bind(this);
this.handelNewLabel = this.handelNewLabel.bind(this);
@@ -113,31 +120,33 @@ class UserLabelModal extends Component {
onChange(state) {
const { currentUser, labels } = state;
- const list = (labels || []).filter(
- (r) => r.access_level === 2 || r.user_id === (currentUser && currentUser.id)
- ) || [];
+ const list =
+ (labels || []).filter(
+ r =>
+ r.access_level === 2 || r.user_id === (currentUser && currentUser.id)
+ ) || [];
this.setState({
- labels: list
+ labels: list,
});
}
handelNewLabel() {
this.setState({
label: {},
- showDetails: true
+ showDetails: true,
});
}
renderUserLabels() {
const { labels } = this.state;
if (labels == null || labels.length === 0) {
- return (
);
+ return
;
}
- return (labels || []).map((g) => {
+ return (labels || []).map(g => {
const badgeStyle = {
- backgroundColor: g.color || this.state.defaultColor
+ backgroundColor: g.color || this.state.defaultColor,
};
let accessLabel = '';
switch (g.access_level) {
@@ -150,11 +159,12 @@ class UserLabelModal extends Component {
case 2:
accessLabel = 'Global';
break;
+ case 3:
+ accessLabel = 'Review';
+ break;
default:
accessLabel = '';
}
- const currentUser = UserStore.getState()?.currentUser;
- const isReviewer = (currentUser?.is_reviewer) || false;
return (
@@ -228,6 +238,7 @@ class UserLabelModal extends Component {
const isReviewer = (currentUser?.is_reviewer) || false;
if (isReviewer) {
+ accessList.unshift({ label: 'Review - Reviewer only', value: 3 });
accessList.unshift({ label: 'Global - Open to everyone', value: 2 });
}
@@ -239,6 +250,7 @@ class UserLabelModal extends Component {
{ this.titleInput = m; }}
defaultValue={label.title || ''}
/>
@@ -267,6 +280,7 @@ class UserLabelModal extends Component {
{ this.descInput = m; }}
defaultValue={label.description || ''}
/>
@@ -327,7 +341,7 @@ class EditUserLabels extends React.Component {
super(props);
this.state = {
currentUser: (UserStore.getState() && UserStore.getState().currentUser) || {},
- labels: (UserStore.getState() && UserStore.getState().labels) || [],
+ labels: this.props.labels || (UserStore.getState() && UserStore.getState().labels) || [],
selectedLabels: null
};
this.onChange = this.onChange.bind(this);
@@ -343,13 +357,15 @@ class EditUserLabels extends React.Component {
}
handleSelectChange(val) {
+ const { element } = this.props;
if (val) {
const ids = val.map((v) => v.value);
if (ids != null) {
- this.props.element.setUserLabels(ids);
+ element.setUserLabels(ids);
+ this.props.fnCb(element);
}
this.setState({ selectedLabels: val });
- }
+ }
}
onChange(state) {
@@ -364,10 +380,6 @@ class EditUserLabels extends React.Component {
let { selectedLabels } = this.state;
const { currentUser, labels } = this.state;
- if (!MatrixCheck(currentUser && currentUser.matrix, UL_FUNC_NAME)) {
- return ( );
- }
-
const { element } = this.props;
const curLableIds = element.tag && element.tag.taggable_data
? element.tag.taggable_data.user_labels
@@ -384,10 +396,7 @@ class EditUserLabels extends React.Component {
{ll.title}
@@ -399,19 +408,134 @@ class EditUserLabels extends React.Component {
selectedLabels = defaultLabels;
}
- const labelOptions = (this.state.labels || [])
- .filter((r) => r.access_level === 2 || r.user_id === currentUser.id)
+ const labelOptions =
+ (this.state.labels || [])
+ .filter(r => r.access_level === 2 || r.user_id === currentUser.id)
+ .map(ll => ({
+ value: ll.id,
+ label: (
+
+ {ll.title}
+
+ ),
+ })) || [];
+
+ return (
+
+
+ My Labels
+ this.handleSelectChange(e)}
+ />
+
+
+ );
+ }
+}
+
+class ReviewUserLabels extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ currentUser: (UserStore.getState() && UserStore.getState().currentUser) || {},
+ labels: this.props.labels || (UserStore.getState() && UserStore.getState().labels) || [],
+ selectedLabels: this.props.element.user_labels || [],
+ };
+ this.onChange = this.onChange.bind(this);
+ this.handleSelectChange = this.handleSelectChange.bind(this);
+ }
+
+ componentDidMount() {
+ UserStore.listen(this.onChange);
+ }
+
+ componentWillUnmount() {
+ UserStore.unlisten(this.onChange);
+ }
+
+ handleSelectChange(val) {
+ const { element } = this.props;
+ if (val) {
+ const ids = val.map(v => v.value);
+ if (ids != null) {
+ // element.setUserLabels(ids);
+ this.props.fnCb(element, ids);
+ }
+ this.setState({ selectedLabels: val });
+ }
+ }
+
+ onChange(state) {
+ const { currentUser, labels } = state;
+ this.setState({
+ currentUser,
+ labels
+ });
+ }
+
+ render() {
+ let { selectedLabels } = this.state;
+ const { currentUser, labels } = this.state;
+ const { element } = this.props;
+ const curLableIds = element.user_labels || [];
+
+ // const reviewLabel = labels.filter(l => l.access_level === 3);
+ const reviewLabel = labels;
+
+ const defaultLabels = (labels || [])
+ .filter(
+ (r) => (curLableIds || []).includes(r.id)
+ && (r.access_level > 0 || r.user_id === currentUser.id)
+ )
.map((ll) => ({
value: ll.id,
label: (
- {ll.title}
+
+ {ll.title}
+
),
- })) || [];
+ }));
+
+ if (selectedLabels == null) {
+ selectedLabels = defaultLabels;
+ }
+
+ const labelOptions =
+ (reviewLabel || [])
+ .filter(r => r.access_level > 0 || r.user_id === currentUser.id)
+ .map(ll => ({
+ value: ll.id,
+ label: (
+
+ {ll.title}
+
+ ),
+ })) || [];
return (
- My Labels
);
+ if (element.labels != null) {
+ showLabels = element.labels;
+ } else {
+ const curLableIds =
+ element.tag && element.tag.taggable_data
+ ? element.tag.taggable_data.user_labels
+ : [];
+
+ showLabels = (labels || []).filter(
+ r =>
+ (curLableIds || []).includes(r.id) &&
+ (r.access_level > 0 || r.user_id === currentUser.id)
+ );
}
- const elementLabels = (labels || []).filter((r) => (
- (curLableIds || []).includes(r.id) && (r.access_level > 0 || r.user_id === currentUser.id)
- )).map((ll) => (
+
+ const elementLabels = (showLabels || []).map((ll) => (
{ll.title}
@@ -492,16 +624,118 @@ class ShowUserLabels extends React.Component {
}
}
+class SearchUserLabels extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ currentUser:
+ (UserStore.getState() && UserStore.getState().currentUser) || {},
+ labels: (UserStore.getState() && UserStore.getState().labels) || [],
+ };
+ this.onChange = this.onChange.bind(this);
+ this.handleSelectChange = this.handleSelectChange.bind(this);
+ }
+
+ componentDidMount() {
+ UserStore.listen(this.onChange);
+ }
+
+ componentWillUnmount() {
+ UserStore.unlisten(this.onChange);
+ }
+
+ handleSelectChange(val) {
+ this.props.fnCb(val || null);
+ }
+
+ onChange(state) {
+ const { currentUser, labels } = state;
+ this.setState({
+ currentUser,
+ labels
+ });
+ }
+
+ render() {
+ const { currentUser, labels } = this.state;
+ const { className, userLabel, isPublish } = this.props;
+
+ let labelList = [];
+ if (isPublish === true) {
+ labelList = (labels || []).filter(r => r.access_level === 2);
+ } else {
+ labelList = (labels || []);
+ }
+
+ const labelOptions =
+ labelList.map(ll => ({
+ value: ll.id,
+ label: (
+
+ {ll.title}
+
+ ),
+ })) || [];
+
+ return (
+
+ this.handleSelectChange(e)}
+ />
+
+ );
+ }
+}
+
UserLabelModal.propTypes = {
showLabelModal: PropTypes.bool.isRequired,
onHide: PropTypes.func.isRequired
};
EditUserLabels.propTypes = {
- element: PropTypes.object.isRequired
+ element: PropTypes.object.isRequired,
+ fnCb: PropTypes.func.isRequired,
+};
+
+ReviewUserLabels.propTypes = {
+ element: PropTypes.object.isRequired,
+ fnCb: PropTypes.func.isRequired,
};
ShowUserLabels.propTypes = {
element: PropTypes.object.isRequired
};
-export { UserLabelModal, EditUserLabels, ShowUserLabels };
+
+SearchUserLabels.propTypes = {
+ fnCb: PropTypes.func.isRequired,
+ // eslint-disable-next-line react/require-default-props
+ userLabel: PropTypes.number,
+ className: PropTypes.string,
+ isPublish: PropTypes.bool,
+};
+
+SearchUserLabels.defaultProps = {
+ className: 'header-group-select',
+ isPublish: false,
+};
+
+export {
+ UserLabelModal,
+ EditUserLabels,
+ ReviewUserLabels,
+ ShowUserLabels,
+ SearchUserLabels,
+};
diff --git a/app/packs/src/components/chemrepo/ExactMass.js b/app/packs/src/components/chemrepo/ExactMass.js
new file mode 100644
index 000000000..3ddf3b881
--- /dev/null
+++ b/app/packs/src/components/chemrepo/ExactMass.js
@@ -0,0 +1,27 @@
+/* eslint-disable react/destructuring-assignment */
+import React from 'react';
+import RepoConst from 'src/components/chemrepo/common/RepoConst';
+
+function FormatEM(em) {
+ if (em) {
+ return (
+
+ {em.toFixed(6)} g⋅mol-1
+
+ );
+ }
+ return null;
+}
+
+function ExactMass(sample, molecule) {
+ const { decoupled = false, molecular_mass: molecularMass } = sample ?? {};
+ const { inchikey = '', exact_molecular_weight: exactMolecularWeight } =
+ molecule ?? {};
+ const mass =
+ decoupled && inchikey !== RepoConst.INCHIKEY_DUMMY
+ ? molecularMass
+ : exactMolecularWeight;
+ return FormatEM(mass);
+}
+
+export default ExactMass;
diff --git a/app/packs/src/components/chemrepo/ExtIcon.js b/app/packs/src/components/chemrepo/ExtIcon.js
new file mode 100644
index 000000000..8bac9e37c
--- /dev/null
+++ b/app/packs/src/components/chemrepo/ExtIcon.js
@@ -0,0 +1,40 @@
+import React from 'react';
+import PublicStore from 'src/stores/alt/repo/stores/PublicStore';
+import UIStore from 'src/stores/alt/stores/UIStore';
+
+function getCollectionIcon(_collectionLabel) {
+ const uiExtension = UIStore.getState().u || PublicStore.getState().u;
+ const { collectionIcons } = uiExtension;
+ if (!_collectionLabel || !collectionIcons) return null;
+ return collectionIcons.find(c => c.label === _collectionLabel)?.icons[0];
+}
+
+function ExtIcon(_collectionLabel) {
+ const icon = getCollectionIcon(_collectionLabel);
+ if (!icon || !icon.filename) return null;
+ const { filename, title } = icon;
+
+ return (
+
+ );
+}
+
+function ExtInfo(_collectionLabel) {
+ const icon = getCollectionIcon(_collectionLabel);
+ if (!icon || !icon.info) return null;
+ const { info } = icon;
+
+ return (
+
+ Additional Information:
+ {info}
+
+ );
+}
+
+export { ExtIcon, ExtInfo };
diff --git a/app/packs/src/components/chemrepo/PublicAnchor.js b/app/packs/src/components/chemrepo/PublicAnchor.js
index d5d231e6c..58e513069 100644
--- a/app/packs/src/components/chemrepo/PublicAnchor.js
+++ b/app/packs/src/components/chemrepo/PublicAnchor.js
@@ -1,15 +1,15 @@
import React from 'react';
import PropTypes from 'prop-types';
-const PublicAnchor = (props) => {
+function PublicAnchor(props) {
const { doi, isPublished } = props;
if (!isPublished || typeof doi !== 'string') return null;
const anchorId = doi?.split('/').pop() || '';
- return ;
-};
+ return ;
+}
PublicAnchor.propTypes = {
- doi: PropTypes.string.isRequired,
+ doi: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired,
isPublished: PropTypes.bool.isRequired,
};
diff --git a/app/packs/src/components/chemrepo/PublicLabels.js b/app/packs/src/components/chemrepo/PublicLabels.js
index 3839d6967..44145c4cf 100644
--- a/app/packs/src/components/chemrepo/PublicLabels.js
+++ b/app/packs/src/components/chemrepo/PublicLabels.js
@@ -2,31 +2,32 @@ import React from 'react';
import { Badge, OverlayTrigger, Tooltip } from 'react-bootstrap';
import uuid from 'uuid';
-const PublicLabels = (_labels) => {
+function PublicLabels(_labels) {
+ // eslint-disable-next-line react/destructuring-assignment
if (!_labels || !Array.isArray(_labels) || _labels.length < 1) return null;
const renderTooltip = (description, title) => (
-
- {description || title}
-
+ {description || title}
);
+ // eslint-disable-next-line react/destructuring-assignment
const output = _labels.map(_label => (
-
+
{_label.title}
-
+
));
- return (
-
- {output}
-
- );
-};
+ return {output}
;
+}
export default PublicLabels;
diff --git a/app/packs/src/components/chemrepo/PublicReactionTlc.js b/app/packs/src/components/chemrepo/PublicReactionTlc.js
index da3575324..606d5af78 100644
--- a/app/packs/src/components/chemrepo/PublicReactionTlc.js
+++ b/app/packs/src/components/chemrepo/PublicReactionTlc.js
@@ -15,7 +15,6 @@ const PublicReactionTlc = ({
} = reaction;
tlcDescription = tlcDescription || '';
tlcSolvents = tlcSolvents || '';
- rfValue = rfValue && rfValue !== '0' ? rfValue : '';
if (isPublished && !tlcDescription && !tlcSolvents && !rfValue) return null;
return (
diff --git a/app/packs/src/components/chemrepo/PublicSample.js b/app/packs/src/components/chemrepo/PublicSample.js
index 49d22a750..2305a10ff 100644
--- a/app/packs/src/components/chemrepo/PublicSample.js
+++ b/app/packs/src/components/chemrepo/PublicSample.js
@@ -3,6 +3,8 @@ import { Button } from 'react-bootstrap';
import { Citation, literatureContent, RefByUserInfo } from 'src/apps/mydb/elements/details/literature/LiteratureCommon';
import { ChemotionId, CommentBtn, Doi } from 'src/repoHome/RepoCommon';
import { formatPhysicalProps } from 'src/components/chemrepo/publication-utils';
+import RepoConst from 'src/components/chemrepo/common/RepoConst';
+import DecoupleInfo from 'src/repoHome/DecoupleInfo';
const PublicSample = (_props) => {
const {
@@ -18,9 +20,9 @@ const PublicSample = (_props) => {
)) : [];
let sampleTypeDescription = 'Consists of molecule with defined structure';
- if (sample.decoupled && element.molecule.inchikey === 'DUMMY') {
+ if (sample.decoupled && element.molecule.inchikey === RepoConst.INCHIKEY_DUMMY) {
sampleTypeDescription = 'Includes only undefined structural components';
- } else if (sample.decoupled && element.molecule.inchikey !== 'DUMMY') {
+ } else if (sample.decoupled && element.molecule.inchikey !== RepoConst.INCHIKEY_DUMMY) {
sampleTypeDescription = 'Includes a fragment with defined structure';
}
@@ -65,6 +67,8 @@ const PublicSample = (_props) => {
return (
Sample type: {sampleTypeDescription}
+
+
{embargo}
diff --git a/app/packs/src/components/chemrepo/PublishCommon.js b/app/packs/src/components/chemrepo/PublishCommon.js
index 7a7e46300..61424b95e 100644
--- a/app/packs/src/components/chemrepo/PublishCommon.js
+++ b/app/packs/src/components/chemrepo/PublishCommon.js
@@ -19,6 +19,8 @@ const labelStyle = {
const handleClick = (e, id, clickType) => {
e.preventDefault();
e.stopPropagation();
+ if (typeof id === 'undefined' || id === null) return;
+
const uri = Aviator.getCurrentURI();
const uriArray = uri.split(/\//);
switch (clickType) {
@@ -216,23 +218,27 @@ const PublishedTag = ({ element, fnUnseal }) => {
? `${tagType} is being reviewed`
: `${tagType} has been published`;
const publishedId = getPublicationId(element);
- return publishedId ? (
-
- {tip}}
- >
- handleClick(event, publishedId, tagType)}
+
+ const pubIdIcon =
+ isPending || publishedId ? (
+
+ {tip}}
>
-
-
-
- {fnUnseal ? : null}
-
- ) : null;
+
handleClick(event, publishedId, tagType)}
+ >
+
+
+
+ {fnUnseal ?
: null}
+
+ ) : null;
+
+ return pubIdIcon;
};
PublishedTag.propTypes = {
diff --git a/app/packs/src/components/chemrepo/PublishReactionContainers.js b/app/packs/src/components/chemrepo/PublishReactionContainers.js
deleted file mode 100644
index a145711f0..000000000
--- a/app/packs/src/components/chemrepo/PublishReactionContainers.js
+++ /dev/null
@@ -1,150 +0,0 @@
-import React, { Component } from 'react';
-import PropTypes from 'prop-types';
-import {
- PanelGroup,
- Panel,
- Button,
-} from 'react-bootstrap';
-import ContainerComponent from 'src/libHome/ContainerComponent';
-import PrintCodeButton from 'src/components/common/PrintCodeButton';
-import Reaction from 'src/models/Reaction';
-
-export default class PublishReactionContainers extends Component {
- constructor(props) {
- super();
- const { reaction } = props;
- this.state = {
- reaction,
- activeContainer: 0
- };
-
- this.handleAccordionOpen = this.handleAccordionOpen.bind(this);
- }
-
- componentWillReceiveProps(nextProps) {
- this.setState({
- reaction: nextProps.reaction,
- });
- }
-
- handleAccordionOpen(key) {
- this.setState({ activeContainer: key });
- }
-
- render() {
- const {reaction, activeContainer} = this.state;
- const {readOnly} = this.props;
-
- let containerHeader = (container) => {
- const kind = container.extended_metadata['kind'] && container.extended_metadata['kind'] != '';
- const titleKind = kind ? (' - Type: ' + container.extended_metadata['kind']) : '';
-
- const status = container.extended_metadata['status'] && container.extended_metadata['status'] != '';
- const titleStatus = status ? (' - Status: ' + container.extended_metadata['status']) : '';
-
- return (
-
- {container.name}
- {titleKind}
- {titleStatus}
-
this.handleOnClickRemove(container)}
- >
-
-
-
-
- )
- };
-
- let containerHeaderDeleted = (container) => {
- const kind = container.extended_metadata['kind'] && container.extended_metadata['kind'] != '';
- const titleKind = kind ? (' - Type: ' + container.extended_metadata['kind']) : '';
-
- const status = container.extended_metadata['status'] && container.extended_metadata['status'] != '';
- const titleStatus = status ? (' - Status: ' + container.extended_metadata['status']) : '';
-
- return (
-
-
- {container.name}
- {titleKind}
- {titleStatus}
-
- this.handleUndo(container)}>
-
-
-
- )
- };
-
- if (reaction.container != null && reaction.container.children) {
- const analyses_container = reaction.container.children.filter(element => (
- ~element.container_type.indexOf('analyses')
- ));
-
- if (analyses_container.length === 1 && analyses_container[0].children.length > 0) {
- return (
-
-
- {analyses_container[0].children.map((container, key) => {
- if (container.is_deleted) {
- return (
-
- {containerHeaderDeleted(container)}
-
- );
- }
-
- return (
-
- {containerHeader(container)}
-
-
-
-
- );
- })}
-
-
- );
- }
-
- return (
-
- There are currently no Analyses.
-
- );
- }
-
- return (
-
- There are currently no Analyses.
-
- );
- }
-}
-
-PublishReactionContainers.propTypes = {
- readOnly: PropTypes.bool.isRequired,
- reaction: PropTypes.instanceOf(Reaction).isRequired,
-};
diff --git a/app/packs/src/components/chemrepo/PublishReactionModal.js b/app/packs/src/components/chemrepo/PublishReactionModal.js
index 14da027b6..44fa8abc6 100644
--- a/app/packs/src/components/chemrepo/PublishReactionModal.js
+++ b/app/packs/src/components/chemrepo/PublishReactionModal.js
@@ -462,7 +462,7 @@ export default class PublishReactionModal extends Component {
const aff = u && u.current_affiliations && u.current_affiliations.map(af => (
-{af.department}, {af.organization}, {af.country}
));
- return (
{orcid}{a.label} {aff}
);
+ return (
{orcid}{a.label} {aff}
);
});
return (
@@ -709,7 +709,7 @@ export default class PublishReactionModal extends Component {
if (show) {
const analysesView = [];
- const analysesReaction = head(filter(reaction.container.children, o => o.container_type === 'analyses')).children;
+ const analysesReaction = head(filter(reaction?.container?.children, o => o.container_type === 'analyses')).children;
selectedAnalysesCount = (analysesReaction || []).filter(a =>
(a.extended_metadata && (a.extended_metadata.publish && (a.extended_metadata.publish === true || a.extended_metadata.publish === 'true')) && a.extended_metadata.kind)).length;
diff --git a/app/packs/src/components/chemrepo/PublishSampleModal.js b/app/packs/src/components/chemrepo/PublishSampleModal.js
index 32e539ca2..4d25c24ab 100644
--- a/app/packs/src/components/chemrepo/PublishSampleModal.js
+++ b/app/packs/src/components/chemrepo/PublishSampleModal.js
@@ -53,9 +53,8 @@ export default class PublishSampleModal extends Component {
this.handleReserveDois = this.handleReserveDois.bind(this);
this.handleSelectUser = this.handleSelectUser.bind(this);
this.handleSelectReviewer = this.handleSelectReviewer.bind(this);
- this.promptTextCreator = this.promptTextCreator.bind(this);
- this.loadUserByName = this.loadUserByName.bind(this);
this.loadReferences = this.loadReferences.bind(this);
+ this.loadMyCollaborations = this.loadMyCollaborations.bind(this);
this.loadBundles = this.loadBundles.bind(this);
this.selectReferences = this.selectReferences.bind(this);
this.handleRefCheck = this.handleRefCheck.bind(this);
@@ -66,17 +65,17 @@ export default class PublishSampleModal extends Component {
componentDidMount() {
UserStore.listen(this.onUserChange);
- this.loadBundles();
- }
-
- componentWillReceiveProps(nextProps) {
this.loadReferences();
+ this.loadBundles();
this.loadMyCollaborations();
- this.setState({
- sample: nextProps.sample,
- });
}
+ // UNSAFE_componentWillReceiveProps(nextProps) {
+ // this.setState({
+ // sample: nextProps.sample,
+ // });
+ // }
+
componentWillUnmount() {
UserStore.unlisten(this.onUserChange);
}
@@ -115,7 +114,8 @@ export default class PublishSampleModal extends Component {
loadReferences() {
let { selectedRefs } = this.state;
- LiteraturesFetcher.fetchElementReferences(this.state.sample).then((literatures) => {
+ const { sample } = this.state;
+ LiteraturesFetcher.fetchElementReferences(sample).then(literatures => {
const sortedIds = groupByCitation(literatures);
selectedRefs = selectedRefs.filter(item => sortedIds.includes(item));
this.setState({ selectedRefs, literatures, sortedIds });
@@ -130,29 +130,6 @@ export default class PublishSampleModal extends Component {
if (val) { this.setState({ selectedReviewers: val }); }
}
- loadUserByName(input) {
- if (!input) {
- return Promise.resolve({ options: [] });
- }
-
- return UsersFetcher.fetchUsersByName(input)
- .then((res) => {
- const usersEntries = res.users.filter(u => u.id !== this.state.currentUser.id)
- .map(u => ({
- value: u.id,
- name: u.name,
- label: `${u.name} (${u.abb})`
- }));
- return { options: usersEntries };
- }).catch((errorMessage) => {
- console.log(errorMessage);
- });
- }
-
- promptTextCreator(label) {
- return ("Share with \"" + label + "\"");
- }
-
handleSampleChanged(sample) {
this.setState(prevState => ({ ...prevState, sample }));
}
@@ -496,13 +473,23 @@ export default class PublishSampleModal extends Component {
});
return (
-
onHide()}>
+ onHide()}
+ >
-
- Publish Sample
-
+ Publish Sample
-
+
{awareEmbargo}
-
+
Select Analyses ({selectedAnalysesCount})
@@ -553,26 +538,29 @@ export default class PublishSampleModal extends Component {
Select References ({selectedRefs.length})
-
- {this.selectReferences()}
-
+ {this.selectReferences()}
- Additional Reviewers ({selectedReviewers.length})
+
+ {' '}
+ Additional Reviewers ({selectedReviewers.length})
+
-
- {this.addReviewers()}
-
+ {this.addReviewers()}
{showPreview ? null : null}
onHide()}>Close
-
+
Publish Sample
diff --git a/app/packs/src/components/chemrepo/SVGViewPan.js b/app/packs/src/components/chemrepo/SVGViewPan.js
new file mode 100644
index 000000000..f389b3f1c
--- /dev/null
+++ b/app/packs/src/components/chemrepo/SVGViewPan.js
@@ -0,0 +1,25 @@
+/* eslint-disable react/forbid-prop-types */
+import React from 'react';
+import PropTypes from 'prop-types';
+import SVG from 'react-inlinesvg';
+
+const SvgPath = (svg, type) => {
+ if (svg && svg !== '***') {
+ return type === 'Reaction'
+ ? `/images/reactions/${svg}`
+ : `/images/samples/${svg}`;
+ }
+ return 'images/wild_card/no_image_180.svg';
+};
+
+function SVGView({ svg, type, className }) {
+ return ;
+}
+
+SVGView.propTypes = {
+ className: PropTypes.string.isRequired,
+ svg: PropTypes.string.isRequired,
+ type: PropTypes.string.isRequired,
+};
+
+export default SVGView;
diff --git a/app/packs/src/components/chemrepo/SvgViewPan.js b/app/packs/src/components/chemrepo/SvgViewPan.js
deleted file mode 100644
index 12b998234..000000000
--- a/app/packs/src/components/chemrepo/SvgViewPan.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import React from 'react';
-import SvgFileZoomPan from 'react-svg-file-zoom-pan-latest';
-
-const SvgViewPan = (path, extra = null) => (
-
- {extra}
-
-
-);
-
-export default SvgViewPan;
diff --git a/app/packs/src/components/chemrepo/SysInfo.js b/app/packs/src/components/chemrepo/SysInfo.js
new file mode 100644
index 000000000..4f45d7223
--- /dev/null
+++ b/app/packs/src/components/chemrepo/SysInfo.js
@@ -0,0 +1,110 @@
+import React, { useState, useEffect } from 'react';
+import { Button } from 'react-bootstrap';
+import ContactEmail from 'src/components/chemrepo/core/ContactEmail';
+
+const sessSysInfoClosed = 'infoBarClosed';
+const infoLink = (href, text) => {
+ return (
+
+ {text}
+
+ );
+};
+
+function SysInfo() {
+ const [show, setShow] = useState(true);
+
+ useEffect(() => {
+ const closed = sessionStorage.getItem(sessSysInfoClosed);
+ if (closed === 'true') {
+ setShow(false);
+ }
+ }, []);
+
+ const handleClose = () => {
+ setShow(false);
+ sessionStorage.setItem(sessSysInfoClosed, 'true');
+ };
+
+ return (
+ show && (
+
+
+
+ {' '}
+ )))
+
+
+
+
+ New to the Repository? Check the{' '}
+ {infoLink(
+ 'https://chemotion.net/docs/repo/settings_preparation',
+ 'Settings and Preparation'
+ )}{' '}
+ guide.{' '}
+
+
+ Starting your research? Review our{' '}
+ {infoLink(
+ 'https://www.chemotion.net/docs/repo/workflow/new',
+ 'How to provide data'
+ )}{' '}
+ instructions.{' '}
+
+
+ Learn more in{' '}
+ {infoLink('https://www.chemotion.net/docs/repo', 'How-To')}{' '}
+ section, and feel free to reach out via{' '}
+
+ {' or '}
+
+ window.open(
+ 'https://github.com/ComPlat/chemotion_REPO',
+ '_blank'
+ )
+ }
+ >
+
+
+
+
+
+
+ Close
+
+
+
+
+
+ )
+ );
+}
+
+export default SysInfo;
diff --git a/app/packs/src/components/chemrepo/common/EmbargoCommentsModal.js b/app/packs/src/components/chemrepo/common/EmbargoCommentsModal.js
index b000a9bc9..8792689cb 100644
--- a/app/packs/src/components/chemrepo/common/EmbargoCommentsModal.js
+++ b/app/packs/src/components/chemrepo/common/EmbargoCommentsModal.js
@@ -91,7 +91,8 @@ export default class EmbargoCommentsModal extends React.Component {
EmbargoCommentsModal.propTypes = {
showModal: PropTypes.bool.isRequired,
- selectEmbargo: PropTypes.object.isRequired,
+ // eslint-disable-next-line react/require-default-props
+ selectEmbargo: PropTypes.object,
onSaveFn: PropTypes.func.isRequired,
onCloseFn: PropTypes.func.isRequired,
};
diff --git a/app/packs/src/components/chemrepo/common/RepoConst.js b/app/packs/src/components/chemrepo/common/RepoConst.js
new file mode 100644
index 000000000..d397b0514
--- /dev/null
+++ b/app/packs/src/components/chemrepo/common/RepoConst.js
@@ -0,0 +1,11 @@
+export default Object.freeze({
+ INCHIKEY_DUMMY: 'DUMMY',
+ P_STATE: {
+ ACCEPTED: 'accepted',
+ PENDING: 'pending',
+ REVIEWED: 'reviewed',
+ },
+ U_TYPE: {
+ ANONYMOUS: 'Anonymous',
+ },
+});
diff --git a/app/packs/src/components/chemrepo/common/RepoNmrium.js b/app/packs/src/components/chemrepo/common/RepoNmrium.js
new file mode 100644
index 000000000..15f522a79
--- /dev/null
+++ b/app/packs/src/components/chemrepo/common/RepoNmrium.js
@@ -0,0 +1,67 @@
+/* eslint-disable react/require-default-props */
+/* eslint-disable react/forbid-prop-types */
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Tooltip, Button, OverlayTrigger } from 'react-bootstrap';
+import PublicActions from 'src/stores/alt/repo/actions/PublicActions';
+import SpectraActions from 'src/stores/alt/actions/SpectraActions';
+import NMRiumDisplayer from 'src/components/nmriumWrapper/NMRiumDisplayer';
+
+function RepoNmriumBtn(props) {
+ const { element, spc, isPublic } = props;
+ const toggleNMRDisplayerModal = e => {
+ e.stopPropagation();
+ SpectraActions.ToggleModalNMRDisplayer();
+ if (isPublic) {
+ PublicActions.loadSpectraForNMRDisplayer.defer(spc);
+ } else {
+ SpectraActions.LoadSpectraForNMRDisplayer.defer(spc);
+ }
+ };
+
+ return (
+
+
+ Click to view data in NMRium
+
+ }
+ >
+ {
+ if (event) {
+ event.stopPropagation();
+ }
+ }}
+ onClick={toggleNMRDisplayerModal}
+ >
+
+
+
+ {}}
+ handleSubmit={() => {}}
+ readOnly
+ />
+
+ );
+}
+
+RepoNmriumBtn.propTypes = {
+ element: PropTypes.object,
+ spc: PropTypes.array,
+ isPublic: PropTypes.bool,
+};
+RepoNmriumBtn.defaultProps = { isPublic: false };
+
+export default RepoNmriumBtn;
diff --git a/app/packs/src/components/chemrepo/common/RepoPreviewImage.js b/app/packs/src/components/chemrepo/common/RepoPreviewImage.js
index 47aa93a45..f8ff749c2 100644
--- a/app/packs/src/components/chemrepo/common/RepoPreviewImage.js
+++ b/app/packs/src/components/chemrepo/common/RepoPreviewImage.js
@@ -1,11 +1,22 @@
-/* eslint-disable no-unused-vars */
/* eslint-disable react/require-default-props */
-/* eslint-disable react/jsx-props-no-spreading */
/* eslint-disable react/forbid-prop-types */
import React from 'react';
import PropTypes from 'prop-types';
import ImageModal from 'src/components/common/ImageModal';
import RepoSpectraBtn from 'src/components/chemrepo/common/RepoSpectra';
+import RepoNmriumBtn from 'src/components/chemrepo/common/RepoNmrium';
+import spc from 'src/components/chemrepo/spc-utils';
+
+function getClassName(nmrium, spectra) {
+ const sClass = spectra ? 'btn1' : 'btn0';
+ let nClass = 'btn0';
+ if (nmrium && spectra) {
+ nClass = 'btn2';
+ } else if (nmrium) {
+ nClass = 'btn1';
+ }
+ return { nmrium: nClass, spectra: sClass };
+}
function RepoPreviewImage(props) {
const { element, analysis, isLogin, isPublic, previewImg, title } = props;
@@ -22,15 +33,28 @@ function RepoPreviewImage(props) {
hasPop = false;
imageStyle = { style: { cursor: 'default', display: 'none' } };
}
+ const spcs = spc(element, analysis);
+ const btnClass = getClassName(spcs.nmrium.hasData, spcs.spectra.hasData);
return (
-
-
+
+ {spcs.nmrium.hasData ? (
+
+ ) : null}
+
+
+ {spcs.spectra.hasData ? (
+
+ ) : null}
Add affiliation for this Publication;
-const removeAffTooltip = Remove this affiliation from this Publication ;
+const addAffTooltip = (
+ Add affiliation for this Publication
+);
+const removeAffTooltip = (
+
+ Remove this affiliation from this Publication
+
+);
const lineAff = (creator, aid, affs, onDeleteAff) => {
const removeBtn = (
-
+
{
);
return (
- {affs[aid]}
- { removeBtn }
+
+ {affs[aid]}
+
+
+ {removeBtn}
+
);
};
@@ -69,13 +88,9 @@ const secAff = (fields, g, countries, organizations, departments, onAddAff, onDe
placeholder="e.g. Germany"
/>
-
-
- onAddAff(g)}
- >
+
+
+ onAddAff(g)}>
@@ -96,14 +111,15 @@ const affbody = (taggData, creator, fields, countries, organizations, department
{moreAff}
);
-
-}
+};
export default class RepoReviewAuthorsModal extends React.Component {
constructor(props) {
super(props);
this.state = {
+ // eslint-disable-next-line react/destructuring-assignment
taggData: null,
+ leaders: null,
modalShow: false,
selectedAuthors: null,
collaborations: [],
@@ -114,53 +130,121 @@ export default class RepoReviewAuthorsModal extends React.Component {
};
this.handleSelectAuthors = this.handleSelectAuthors.bind(this);
- this.loadCollaborations = this.loadCollaborations.bind(this);
this.handleInputChange = this.handleInputChange.bind(this);
this.onAddAff = this.onAddAff.bind(this);
this.onDeleteAff = this.onDeleteAff.bind(this);
this.onAddNewAuthor = this.onAddNewAuthor.bind(this);
+ this.onAddNewReviewer = this.onAddNewReviewer.bind(this);
this.onSave = this.onSave.bind(this);
this.loadOrcid = this.loadOrcid.bind(this);
this.handleDeleteAuthor = this.handleDeleteAuthor.bind(this);
+ this.handleDeleteLeader = this.handleDeleteLeader.bind(this);
this.handleClose = this.handleClose.bind(this);
}
componentDidMount() {
- this.loadCollaborations();
- PublicFetcher.affiliations('countries').then((result) => {
- const affOption = result.affiliations.map(a => ({ label: a, value: a }))
- .filter(a => a.value && a.value.length > 1);
- this.setState({ countries: affOption });
- });
- PublicFetcher.affiliations('organizations').then((result) => {
- const affOption = result.affiliations.map(a => ({ label: a, value: a }))
- .filter(a => a.value && a.value.length > 1);
- this.setState({ organizations: affOption });
- });
- PublicFetcher.affiliations('departments').then((result) => {
- const affOption = result.affiliations.map(a => ({ label: a, value: a }))
- .filter(a => a.value && a.value.length > 1);
- this.setState({ departments: affOption });
- });
+ Promise.all([
+ CollaboratorFetcher.fetchMyCollaborations(),
+ PublicFetcher.affiliations('countries'),
+ PublicFetcher.affiliations('organizations'),
+ PublicFetcher.affiliations('departments'),
+ ]).then(
+ ([
+ collaborationsResult,
+ countriesResult,
+ organizationsResult,
+ departmentsResult,
+ ]) => {
+ const collaborations = collaborationsResult?.authors || [];
+
+ const formatAffiliations = result =>
+ result?.affiliations
+ ?.map(a => ({ label: a, value: a }))
+ .filter(a => a.value && a.value.length > 1);
+ const countries = formatAffiliations(countriesResult);
+ const organizations = formatAffiliations(organizationsResult);
+ const departments = formatAffiliations(departmentsResult);
+ this.setState({
+ collaborations,
+ countries,
+ organizations,
+ departments,
+ });
+ }
+ );
}
componentWillUnmount() {
this.setState({
- taggData: null
+ taggData: null,
+ leaders: null,
+ });
+ }
+
+ handleInputChange(type, ev) {
+ let { fields } = this.state;
+ switch (type) {
+ case 'country':
+ fields.country = ev && ev.value;
+ break;
+ case 'organization':
+ fields.organization = ev && ev.value;
+ break;
+ case 'department':
+ fields.department = ev && ev.value;
+ break;
+ default:
+ if (typeof fields === 'undefined') {
+ fields = {};
+ }
+ fields[type] = ev && ev.value;
+ }
+ this.setState({ fields });
+ }
+
+ handleSelectAuthors(val) {
+ if (val) {
+ this.setState({ selectedAuthors: val });
+ }
+ }
+
+ handleDeleteLeader(leader) {
+ const leaders = this.state.leaders || this.props.leaders;
+ const newLeaders = filter(leaders, o => o.id !== leader.id);
+ this.setState({ leaders: newLeaders });
+ }
+
+ handleDeleteAuthor(author) {
+ const taggData = this.state.taggData || this.props.taggData;
+ const { creators, author_ids } = taggData;
+ taggData.creators = filter(creators, o => o.id !== author.id);
+ taggData.author_ids = filter(author_ids, o => o !== author.id);
+ this.setState({
+ taggData,
});
}
+ handleClose() {
+ this.setState({ taggData: null, leaders: null, modalShow: false });
+ }
+
onAddNewAuthor() {
const { selectedAuthors, collaborations } = this.state;
const taggData = this.state.taggData || this.props.taggData;
const { affiliations, creators, affiliation_ids, author_ids } = taggData;
- const coidx = findIndex(collaborations, o => o.id === selectedAuthors.value);
+ const coidx = findIndex(
+ collaborations,
+ o => o.id === selectedAuthors.value
+ );
const selCol = collaborations[coidx];
- const affIds = selCol.current_affiliations.map(ca => (ca.id));
+ const affIds = selCol.current_affiliations.map(ca => ca.id);
- selCol.current_affiliations.map((ca) => {
- affiliations[ca.id] = [ca.department, ca.organization, ca.country].join(', ');
+ // eslint-disable-next-line array-callback-return
+ selCol.current_affiliations.map(ca => {
+ affiliations[ca.id] = [ca.department, ca.organization, ca.country].join(
+ ', '
+ );
});
const newAuthor = {
@@ -169,21 +253,40 @@ export default class RepoReviewAuthorsModal extends React.Component {
givenName: selCol.first_name,
name: selCol.name,
ORCID: selCol.orcid,
- affiliationIds: affIds
+ affiliationIds: affIds,
};
creators.push(newAuthor);
- //collaborations.splice(coidx, 1);
-
taggData.creators = creators;
- taggData.affiliation_ids = uniq(flattenDeep(affiliation_ids.concat(affIds)));
+ taggData.affiliation_ids = uniq(
+ flattenDeep(affiliation_ids.concat(affIds))
+ );
author_ids.push(newAuthor.id);
taggData.author_ids = author_ids;
taggData.affiliations = affiliations;
this.setState({ taggData, selectedAuthors: null });
}
+ onAddNewReviewer() {
+ const { selectedAuthors, collaborations } = this.state;
+ const leaders = this.state.leaders || this.props.leaders;
+
+ const coidx = findIndex(
+ collaborations,
+ o => o.id === selectedAuthors.value
+ );
+ const selCol = collaborations[coidx];
+
+ const newLeader = {
+ id: selCol.id,
+ name: selCol.name,
+ };
+
+ leaders.push(newLeader);
+ this.setState({ leaders, selectedAuthors: null });
+ }
+
onAddAff(g) {
const { fields } = this.state;
const taggData = this.state.taggData || this.props.taggData;
@@ -193,31 +296,35 @@ export default class RepoReviewAuthorsModal extends React.Component {
const { affiliations, creators, affiliation_ids } = taggData;
const params = {
- department, organization, country
+ department,
+ organization,
+ country,
};
- UsersFetcher.findAndCreateAff(params)
- .then((result) => {
- if (result.error) {
- alert(result.error);
- } else {
- affiliations[result.id] = result.aff_output;
- g.affiliationIds.push(result.id);
- const idx = findIndex(creators, o => o.id === g.id);
- // authors.splice(idx, 1, result.user);
- fields[`${g.id}@line_department`] = '';
- fields[`${g.id}@line_organization`] = '';
- fields[`${g.id}@line_country`] = '';
- taggData.affiliations = affiliations;
- affiliation_ids.push(result.id);
- taggData.affiliation_ids = uniq(flattenDeep(affiliation_ids));
- taggData.creators[idx] = g;
-
- this.setState({ taggData, fields });
- }
- });
+
+ UsersFetcher.findAndCreateAff(params).then(result => {
+ if (result.error) {
+ alert(result.error);
+ } else {
+ affiliations[result.id] = result.aff_output;
+ g.affiliationIds.push(result.id);
+ const idx = findIndex(creators, o => o.id === g.id);
+ // authors.splice(idx, 1, result.user);
+ fields[`${g.id}@line_department`] = '';
+ fields[`${g.id}@line_organization`] = '';
+ fields[`${g.id}@line_country`] = '';
+ taggData.affiliations = affiliations;
+ // eslint-disable-next-line camelcase
+ affiliation_ids.push(result.id);
+ taggData.affiliation_ids = uniq(flattenDeep(affiliation_ids));
+ taggData.creators[idx] = g;
+
+ this.setState({ taggData, fields });
+ }
+ });
}
onDeleteAff(g, aid) {
+ // eslint-disable-next-line react/destructuring-assignment
const taggData = this.state.taggData || this.props.taggData;
const { creators } = taggData;
@@ -229,35 +336,60 @@ export default class RepoReviewAuthorsModal extends React.Component {
}
onSave() {
- const { taggData } = this.state;
+ const { taggData, leaders, collaborations } = this.state;
const { element } = this.props;
+ const elementId = element.element_id || element.id;
+ const elementType = element.element_type || element.elementType;
- if (taggData == null) {
+ if (taggData == null && leaders == null) {
alert('no changes!');
return true;
}
- const { creators } = taggData;
- const authorCount = (creators || []).length;
-
- if (authorCount > 0 && !this.refBehalfAsAuthor.checked) {
- alert(`Please confirm you are contributing on behalf of the author${authorCount > 0 ? 's' : ''}.'`);
- return true;
+ if (taggData != null) {
+ const { creators } = taggData;
+ const authorCount = (creators || []).length;
+
+ if (authorCount > 0 && !this.refBehalfAsAuthor.checked) {
+ alert(
+ `Please confirm you are contributing on behalf of the author${
+ authorCount > 0 ? 's' : ''
+ }.'`
+ );
+ return true;
+ }
}
- RepositoryFetcher.saveRepoAuthors({ taggData, elementId: element.element_id || element.id, elementType: element.element_type || element.elementType })
- .then((result) => {
- if (result.error) {
- alert(result.error);
- } else {
- if (element.elementType === 'Reaction') {
- ReviewActions.displayReviewReaction(element.id);
- } else if (element.elementType === 'Sample') {
- ReviewActions.displayReviewSample(element.id);
- }
- this.setState({ taggData: null, modalShow: false });
+ if (leaders != null) {
+ const filterAcc = collaborations.filter(
+ ({ id, type }) =>
+ leaders.some(leader => leader.id === id) && type === 'Collaborator'
+ );
+ if (filterAcc.length > 0) {
+ alert(
+ 'The selected collaborator does not have an account and cannot be a group lead reviewer.'
+ );
+ return true;
+ }
+ }
+ RepositoryFetcher.saveRepoAuthors({
+ taggData,
+ leaders,
+ elementId,
+ elementType,
+ }).then(result => {
+ if (result.error) {
+ // eslint-disable-next-line no-alert
+ alert(result.error);
+ } else {
+ if (elementType === 'Reaction') {
+ ReviewActions.displayReviewReaction(elementId);
+ } else if (elementType === 'Sample') {
+ ReviewActions.displayReviewSample(elementId);
}
- });
+ this.setState({ taggData: null, modalShow: false });
+ }
+ });
return true;
}
@@ -267,7 +399,7 @@ export default class RepoReviewAuthorsModal extends React.Component {
let ids = [];
ids.push(contributors.id);
ids = ids.concat(author_ids);
- CollaboratorFetcher.loadOrcidByUserId({ ids }).then((result) => {
+ CollaboratorFetcher.loadOrcidByUserId({ ids }).then(result => {
const orcids = result.orcids || [];
const cx = findIndex(orcids, o => o.id === contributors.id);
if (cx > -1) {
@@ -286,107 +418,72 @@ export default class RepoReviewAuthorsModal extends React.Component {
});
}
- handleInputChange(type, ev) {
- let { fields } = this.state;
- switch (type) {
- case 'country':
- fields.country = ev && ev.value;
- break;
- case 'organization':
- fields.organization = ev && ev.value;
- break;
- case 'department':
- fields.department = ev && ev.value;
- break;
- default:
- if (typeof fields === 'undefined') {
- fields = {};
- }
- fields[type] = ev && ev.value;
- }
- this.setState({ fields });
- }
-
-
- handleSelectAuthors(val) {
- if (val) { this.setState({ selectedAuthors: val }); }
- }
-
- loadCollaborations() {
- CollaboratorFetcher.fetchMyCollaborations().then(result => {
- if (result) {
- this.setState({
- collaborations: result?.authors,
- });
- }
- });
- }
-
- handleDeleteAuthor(author) {
- const taggData = this.state.taggData || this.props.taggData;
- const { creators, author_ids } = taggData;
- taggData.creators = filter(creators, o => o.id !== author.id);
- taggData.author_ids = filter(author_ids, o => o !== author.id);
- this.setState({
- taggData
- });
- }
-
contributor() {
const taggData = this.state.taggData || this.props.taggData;
const contributors = taggData?.contributors || {};
- const orcid = contributors.ORCID == null ? '' : ;
- const aff = contributors.affiliations && Object.keys(contributors.affiliations).map(k => (
- -{contributors.affiliations[k]}
- ));
- return (
Contributor: {orcid}{contributors.name} {aff} );
-
- }
-
-
- handleClose() {
- this.setState({ taggData: null, modalShow: false })
+ const orcid =
+ contributors.ORCID == null ? (
+ ''
+ ) : (
+
+ );
+ const aff =
+ contributors.affiliations &&
+ Object.keys(contributors.affiliations).map(k => (
+ -{contributors.affiliations[k]}
+ ));
+ return (
+
+
+ Contributor:
+
+ {orcid}
+ {contributors.name} {aff}{' '}
+
+ );
}
selectUsers() {
const { selectedAuthors, collaborations } = this.state;
+ const { isEmbargo } = this.props;
+ // eslint-disable-next-line react/destructuring-assignment
const taggData = this.state.taggData || this.props.taggData || {};
const creators = taggData?.creators || [];
- const author_ids = taggData?.author_ids || [];
-
- const affiliations = taggData.affiliations || [];
- let defaultSelectedAuthors = [];
-
- const filterCol = (collaborations || []).filter(({ id }) => !(author_ids || []).includes(id));
-
- const options = filterCol.map(c => (
- { label: c.name, value: c.id }
- ));
-
- if (selectedAuthors == null) {
- defaultSelectedAuthors = creators.map(au => (
- { label: au.name, value: au.id }
- ));
- } else {
- defaultSelectedAuthors = selectedAuthors;
- }
+ const authorIds = taggData?.author_ids || [];
- const authorInfo = creators && creators.map((author) => {
- const orcid = author.ORCID == null ? '' : ;
+ const filterCol = collaborations?.filter(
+ ({ id }) => !(authorIds || []).includes(id)
+ );
- const affIds = author.affiliationIds || [];
- const aff = affIds.map(id => (
- -{affiliations[id]}
- ));
- return ({orcid}{author.name} {aff}
);
- });
+ const options = filterCol.map(c => ({
+ name: c.name,
+ label: c.name,
+ value: c.id,
+ }));
const authorCount = (creators || []).length;
+ const btnReviewer = isEmbargo ? (
+
+ ) : (
+ this.onAddNewReviewer()}
+ disabled={!selectedAuthors}
+ >
+
+ Add to Reviewer List
+
+ );
+
return (
-
-
{ this.refBehalfAsAuthor = ref; }}>
+
+ {
+ this.refBehalfAsAuthor = ref;
+ }}
+ >
I am contributing on behalf of the author{authorCount > 0 ? 's' : ''}
@@ -395,8 +492,8 @@ export default class RepoReviewAuthorsModal extends React.Component {
searchable
placeholder="Select authors from my collaboration"
backspaceRemoves
- value={defaultSelectedAuthors}
- defaultValue={defaultSelectedAuthors}
+ value={selectedAuthors}
+ defaultValue={selectedAuthors}
valueKey="value"
labelKey="label"
matchProp="name"
@@ -404,28 +501,66 @@ export default class RepoReviewAuthorsModal extends React.Component {
onChange={this.handleSelectAuthors}
/>
- this.onAddNewAuthor()}>
+ this.onAddNewAuthor()}
+ disabled={!selectedAuthors}
+ >
Add to Author List
+ {btnReviewer}
);
}
- render() {
- const { modalShow, countries, organizations, departments, fields, title } = this.state;
- const { element, disabled } = this.props;
- const taggData = this.state.taggData || this.props.taggData;
- const creators = taggData?.creators || [];
- if (this.props.schemeOnly === true) {
+ renderLeaders() {
+ const { disabled, isEmbargo } = this.props;
+ const leaders = this.state.leaders || this.props.leaders;
+ if (isEmbargo) {
return '';
}
- let tbody = '';
+ const tbodyLeaders = leaders?.map(leader => (
+
+
+
+ this.handleDeleteLeader(leader)}
+ />
+
+
+ {leader.name}
+
+
+ ));
- tbody = creators.map(creator => (
+ return (
+
+
Additional Reviewer List
+
+
+
+ Action
+ Name
+
+
+ {tbodyLeaders || ''}
+
+
+ );
+ }
+
+ renderAuthors() {
+ const { countries, organizations, departments, fields } = this.state;
+ // eslint-disable-next-line react/destructuring-assignment
+ const taggData = this.state.taggData || this.props.taggData;
+ const creators = taggData?.creators || [];
+
+ const tbodyAuthors = creators.map(creator => (
@@ -436,23 +571,117 @@ export default class RepoReviewAuthorsModal extends React.Component {
{creator.name}
- {creator.ORCID}
- {affbody(taggData, creator, fields, countries, organizations, departments, this.onAddAff, this.onDeleteAff, this.handleInputChange)}
+
+ {creator.ORCID}
+
+
+ {affbody(
+ taggData,
+ creator,
+ fields,
+ countries,
+ organizations,
+ departments,
+ this.onAddAff,
+ this.onDeleteAff,
+ this.handleInputChange
+ )}
-
));
- let btn = ( this.setState({ modalShow: true })}> Publication Authors );
- if (element?.element_type === 'Collection' || disabled === true) {
- btn = ( this.setState({ modalShow: true })}> {title} );
+ return (
+
+
Author List
+
+
+
+ Action
+ Name
+ ORCID iD
+
+
+
+
+
+ Affiliation
+
+
+
+
+
+
+
+
+
+
+ {tbodyAuthors || ''}
+
+
+ );
+ }
+
+ renderButton() {
+ const { disabled, isEmbargo } = this.props;
+ let btn = (
+ this.setState({ modalShow: true })}
+ >
+
+ Authors & Reviewers
+
+ );
+ if (isEmbargo || disabled === true) {
+ btn = (
+ this.setState({ modalShow: true })}
+ >
+
+
+ );
+ }
+ return (
+ Add/Remove Authors & Reviewers
+ }
+ >
+ {btn}
+
+ );
+ }
+
+ renderButtons() {
+ return (
+
+ this.handleClose()}>
+ Close
+
+ this.loadOrcid()}>
+ get ORCID iD
+
+ this.onSave()}>
+ Save
+
+
+ );
+ }
+
+ render() {
+ const { modalShow } = this.state;
+ const { schemeOnly } = this.props;
+ if (schemeOnly === true) {
+ return '';
}
return (
- Add Authors}>{btn}
+ {this.renderButton()}
-
-
-
-
- Action
- Name
- ORCID iD
-
-
-
-
- Affiliation
-
-
-
-
-
-
-
- { tbody }
-
-
-
-
- this.handleClose()}> Close
- this.loadOrcid()}
- > get ORCID iD
-
- this.onSave()}
- > Save
-
-
+ {this.renderAuthors()}
+ {this.renderLeaders()}
+ {this.renderButtons()}
@@ -508,18 +704,18 @@ export default class RepoReviewAuthorsModal extends React.Component {
}
RepoReviewAuthorsModal.propTypes = {
- element: PropTypes.object,
- review_info: PropTypes.object,
+ element: PropTypes.object.isRequired,
+ leaders: PropTypes.array,
disabled: PropTypes.bool,
+ isEmbargo: PropTypes.bool,
schemeOnly: PropTypes.bool,
- title: PropTypes.string,
- taggData: PropTypes.object.isRequired,
+ taggData: PropTypes.object,
};
RepoReviewAuthorsModal.defaultProps = {
- review_info: {},
+ taggData: {},
+ leaders: [],
schemeOnly: false,
- title: 'Add Authors',
+ isEmbargo: false,
disabled: false,
- taggData: {}
};
diff --git a/app/packs/src/components/chemrepo/common/RepoSpectra.js b/app/packs/src/components/chemrepo/common/RepoSpectra.js
index 0345eb6d6..da931d5f5 100644
--- a/app/packs/src/components/chemrepo/common/RepoSpectra.js
+++ b/app/packs/src/components/chemrepo/common/RepoSpectra.js
@@ -1,19 +1,15 @@
-/* eslint-disable react/require-default-props */
/* eslint-disable react/forbid-prop-types */
+/* eslint-disable react/require-default-props */
import React from 'react';
import PropTypes from 'prop-types';
import { Tooltip, Button, OverlayTrigger } from 'react-bootstrap';
-import { BuildSpcInfos } from 'src/utilities/SpectraHelper';
import PublicActions from 'src/stores/alt/repo/actions/PublicActions';
import SpectraActions from 'src/stores/alt/actions/SpectraActions';
import ViewSpectra from 'src/apps/mydb/elements/details/ViewSpectra';
import NotificationActions from 'src/stores/alt/actions/NotificationActions';
function RepoSpectraBtn(props) {
- const { element, analysis, isLogin, isPublic } = props;
- if (element == null || analysis == null) return null;
- const spcInfos = BuildSpcInfos(element, analysis);
- if (spcInfos.length < 1) return null;
+ const { element, spc, isLogin, isPublic } = props;
const toggleSpectraModal = e => {
e.stopPropagation();
if (!isLogin) {
@@ -26,9 +22,9 @@ function RepoSpectraBtn(props) {
} else {
SpectraActions.ToggleModal();
if (isPublic) {
- PublicActions.loadSpectra.defer(spcInfos);
+ PublicActions.loadSpectra.defer(spc);
} else {
- SpectraActions.LoadSpectra.defer(spcInfos);
+ SpectraActions.LoadSpectra.defer(spc);
}
}
};
@@ -48,7 +44,7 @@ function RepoSpectraBtn(props) {
}
}}
onClick={toggleSpectraModal}
- disabled={!(spcInfos.length > 0)}
+ disabled={!(spc.length > 0)}
>
@@ -65,7 +61,7 @@ function RepoSpectraBtn(props) {
RepoSpectraBtn.propTypes = {
element: PropTypes.object,
- analysis: PropTypes.object,
+ spc: PropTypes.array,
isLogin: PropTypes.bool,
isPublic: PropTypes.bool,
};
diff --git a/app/packs/src/components/chemrepo/common/RepoUserLabelModal.js b/app/packs/src/components/chemrepo/common/RepoUserLabelModal.js
new file mode 100644
index 000000000..c78d73d3d
--- /dev/null
+++ b/app/packs/src/components/chemrepo/common/RepoUserLabelModal.js
@@ -0,0 +1,94 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import {
+ Modal,
+ Button,
+ ButtonToolbar,
+ OverlayTrigger,
+ Tooltip,
+ Label,
+} from 'react-bootstrap';
+import { ReviewUserLabels } from 'src/components/UserLabels';
+import ReviewActions from 'src/stores/alt/repo/actions/ReviewActions';
+
+export default class RepoUserLabelModal extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ modalShow: false,
+ selectedIds: this.props.element.user_labels || [],
+ };
+ this.handleSelectLabels = this.handleSelectLabels.bind(this);
+ this.handleSaveLabels = this.handleSaveLabels.bind(this);
+ }
+
+ handleSelectLabels(e, ids) {
+ this.setState({ selectedIds: ids });
+ }
+
+ handleSaveLabels(e) {
+ const { selectedIds } = this.state;
+ ReviewActions.saveReviewLabel(e, selectedIds);
+ this.setState({ modalShow: false });
+ }
+
+ render() {
+ const { modalShow, selectedIds } = this.state;
+ const { element } = this.props;
+
+ return (
+
+
add/remove user labels}
+ >
+ this.setState({ modalShow: true })}
+ style={{ marginLeft: '5px' }}
+ >
+ Labels
+
+
+
this.setState({ modalShow: false })}
+ dialogClassName="news-preview-dialog"
+ >
+
+
+
+
+
+
+
+
+ this.setState({ modalShow: false })}
+ >
+ Close
+
+ this.handleSaveLabels(element)}
+ >
+ Save
+
+
+
+
+
+ );
+ }
+}
+
+RepoUserLabelModal.propTypes = {
+ element: PropTypes.shape({
+ id: PropTypes.number,
+ elementType: PropTypes.string,
+ }).isRequired,
+};
diff --git a/app/packs/src/components/chemrepo/common/StateLabel.js b/app/packs/src/components/chemrepo/common/StateLabel.js
new file mode 100644
index 000000000..9a9830bce
--- /dev/null
+++ b/app/packs/src/components/chemrepo/common/StateLabel.js
@@ -0,0 +1,16 @@
+import React from 'react';
+import { Label } from 'react-bootstrap';
+import RepoConst from 'src/components/chemrepo/common/RepoConst';
+
+function StateLabel(state) {
+ const stateStyle =
+ {
+ [RepoConst.P_STATE.REVIEWED]: 'info',
+ [RepoConst.P_STATE.PENDING]: 'warning',
+ [RepoConst.P_STATE.ACCEPTED]: 'success',
+ }[state] || 'default';
+
+ return {state} ;
+}
+
+export default StateLabel;
diff --git a/app/packs/src/components/chemrepo/core/ContactEmail.js b/app/packs/src/components/chemrepo/core/ContactEmail.js
index 7737cbbf4..be49d1d55 100644
--- a/app/packs/src/components/chemrepo/core/ContactEmail.js
+++ b/app/packs/src/components/chemrepo/core/ContactEmail.js
@@ -3,13 +3,13 @@ import React from 'react';
import { Button } from 'react-bootstrap';
function ContactEmail(props) {
- const { label, email } = props;
+ const { label, email, size } = props;
const handleSendEmail = () => {
window.location.href = `mailto:${email}`;
};
return (
-
+
{` ${label}`}
@@ -19,11 +19,13 @@ function ContactEmail(props) {
ContactEmail.propTypes = {
label: PropTypes.string,
email: PropTypes.string,
+ size: PropTypes.string,
};
ContactEmail.defaultProps = {
label: 'Contact Us',
email: 'chemotion-repository@lists.kit.edu',
+ size: 'small',
};
export default ContactEmail;
diff --git a/app/packs/src/components/chemrepo/matomo.js b/app/packs/src/components/chemrepo/matomo.js
deleted file mode 100644
index 5b9312231..000000000
--- a/app/packs/src/components/chemrepo/matomo.js
+++ /dev/null
@@ -1,17 +0,0 @@
-/* eslint-disable no-underscore-dangle */
-const embedMatomo = () => {
- if (process.env.MATOMO_URL) {
- if (!window._mtm) {
- window._mtm = [];
- }
- const mtm = window._mtm;
- mtm.push({ 'mtm.startTime': new Date().getTime(), event: 'mtm.Start' });
- const g = document.createElement('script');
- const s = document.getElementsByTagName('script')[0];
- g.async = true;
- g.src = process.env.MATOMO_URL;
- s.parentNode.insertBefore(g, s);
- }
-};
-
-export default embedMatomo;
diff --git a/app/packs/src/components/chemrepo/reaction/ContainerComponent.js b/app/packs/src/components/chemrepo/reaction/ContainerComponent.js
new file mode 100644
index 000000000..9cc792794
--- /dev/null
+++ b/app/packs/src/components/chemrepo/reaction/ContainerComponent.js
@@ -0,0 +1,27 @@
+/* eslint-disable react/forbid-prop-types */
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Col } from 'react-bootstrap';
+import ContainerDatasets from 'src/components/container/ContainerDatasets';
+
+function ContainerComponent({ container }) {
+ return (
+
+
Datasets
+
+ {}}
+ />
+
+
+ );
+}
+
+ContainerComponent.propTypes = {
+ container: PropTypes.object.isRequired,
+};
+
+export default ContainerComponent;
diff --git a/app/packs/src/components/chemrepo/spc-utils.js b/app/packs/src/components/chemrepo/spc-utils.js
new file mode 100644
index 000000000..39e61c6b9
--- /dev/null
+++ b/app/packs/src/components/chemrepo/spc-utils.js
@@ -0,0 +1,39 @@
+import {
+ BuildSpcInfos,
+ BuildSpcInfosForNMRDisplayer,
+} from 'src/utilities/SpectraHelper';
+
+const spcChemSpectra = (element, analysis) => {
+ if (element == null || analysis == null) return [];
+ const spcInfos = BuildSpcInfos(element, analysis);
+ if (spcInfos.length < 1) return [];
+ return spcInfos;
+};
+
+const spcNmrium = (element, analysis) => {
+ if (element == null || analysis == null) return [];
+ const container = analysis;
+ const spcInfosForNMRDisplayer = BuildSpcInfosForNMRDisplayer(
+ element,
+ container
+ );
+ if (spcInfosForNMRDisplayer.length < 1) return [];
+ const arrNMRiumSpecs = spcInfosForNMRDisplayer.filter(spc =>
+ spc.label.includes('.nmrium')
+ );
+ if (!arrNMRiumSpecs || arrNMRiumSpecs.length === 0) {
+ return [];
+ }
+ return arrNMRiumSpecs;
+};
+
+const spc = (element, analysis) => {
+ const sInfos = spcChemSpectra(element, analysis);
+ const nInfos = spcNmrium(element, analysis);
+ return {
+ nmrium: { hasData: nInfos.length > 0, data: nInfos },
+ spectra: { hasData: sInfos.length > 0, data: sInfos },
+ };
+};
+
+export default spc;
diff --git a/app/packs/src/components/comments/HeaderCommentSection.js b/app/packs/src/components/comments/HeaderCommentSection.js
index de00c7519..37c5d8c83 100644
--- a/app/packs/src/components/comments/HeaderCommentSection.js
+++ b/app/packs/src/components/comments/HeaderCommentSection.js
@@ -65,8 +65,6 @@ class HeaderCommentSection extends Component {
}}
>
-
- Comments
{
let content = '';
@@ -8,9 +9,9 @@ const Formula = ({ formula, customText }) => {
const keys = formula.split(/([A-Za-z]{1}[a-z]{0,2})(\+?)(-?)(\d*)/);
content = compact(keys).map((item, i) => {
const key = `${item}-${i}`;
- if ((/\d+/).test(item)) {
+ if (/\d+/.test(item)) {
return {item} ;
- } else if ((/[+-]/).test(item)) {
+ } else if (/[+-]/.test(item)) {
return {item} ;
}
return item;
@@ -35,4 +36,13 @@ Formula.defaultProps = {
customText: '',
};
+const ExactFormula = ({ sample, molecule }) => {
+ const { decoupled = false, sum_formula: sFormula } = sample ?? {};
+ const { inchikey = '', sum_formular: mFormula } = molecule ?? {};
+ const formula =
+ decoupled && inchikey !== RepoConst.INCHIKEY_DUMMY ? sFormula : mFormula;
+ return ;
+};
+
export default Formula;
+export { ExactFormula };
diff --git a/app/packs/src/components/common/NewsPreviewModal.js b/app/packs/src/components/common/NewsPreviewModal.js
index 815f0fe45..2de391112 100644
--- a/app/packs/src/components/common/NewsPreviewModal.js
+++ b/app/packs/src/components/common/NewsPreviewModal.js
@@ -1,6 +1,6 @@
import React from 'react';
import { Panel, Modal } from 'react-bootstrap';
-import QuillViewer from 'src/components/QuillViewer';
+import Quill2Viewer from 'src/components/Quill2Viewer';
const NewsPreviewModal = ({ showModal, article, onClick }) => {
return (
@@ -20,7 +20,7 @@ const NewsPreviewModal = ({ showModal, article, onClick }) => {
-
+
Preview
diff --git a/app/packs/src/components/contextActions/NoticeButton.js b/app/packs/src/components/contextActions/NoticeButton.js
index cd44765f4..0f1aded07 100644
--- a/app/packs/src/components/contextActions/NoticeButton.js
+++ b/app/packs/src/components/contextActions/NoticeButton.js
@@ -79,6 +79,9 @@ const handleNotification = (nots, act, needCallback = true) => {
case 'Repository_Published':
// CollectionActions.fetchSyncInCollectionRoots();
break;
+ case 'Submission':
+ CollectionActions.fetchSyncInCollectionRoots();
+ break;
case 'InboxActions.fetchInbox':
InboxActions.fetchInbox({ currentPage, itemsPerPage });
break;
diff --git a/app/packs/src/components/generic/GenericContainer.js b/app/packs/src/components/generic/GenericContainer.js
index 0821e3eb7..c2ae42996 100644
--- a/app/packs/src/components/generic/GenericContainer.js
+++ b/app/packs/src/components/generic/GenericContainer.js
@@ -5,7 +5,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { Panel, Button } from 'react-bootstrap';
import ContainerComponent from 'src/components/container/ContainerComponent';
-import QuillViewer from 'src/components/QuillViewer';
+import Quill2Viewer from 'src/components/Quill2Viewer';
import ImageModal from 'src/components/common/ImageModal';
import { instrumentText } from 'src/utilities/ElementUtils';
import { previewContainerImage } from 'src/utilities/imageHelper';
@@ -108,11 +108,11 @@ const newHeader = (props) => {
Type: {kind}
Status: {status} {insText}
-
diff --git a/app/packs/src/components/generic/GenericElDetails.js b/app/packs/src/components/generic/GenericElDetails.js
index 8a5727347..e1ee7ffcb 100644
--- a/app/packs/src/components/generic/GenericElDetails.js
+++ b/app/packs/src/components/generic/GenericElDetails.js
@@ -40,6 +40,7 @@ import ElementDetailSortTab from 'src/apps/mydb/elements/details/ElementDetailSo
import UserStore from 'src/stores/alt/stores/UserStore';
import UserActions from 'src/stores/alt/actions/UserActions';
import CollectionActions from 'src/stores/alt/actions/CollectionActions';
+import { EditUserLabels, ShowUserLabels } from 'src/components/UserLabels';
const onNaviClick = (type, id) => {
const { currentCollection, isSync } = UIStore.getState();
@@ -354,6 +355,10 @@ export default class GenericElDetails extends Component {
{this.elementalToolbar(genericEl)}
{layersLayout}
+
);
}
@@ -436,6 +441,7 @@ export default class GenericElDetails extends Component {
{genericEl.short_label}
+
{copyBtn}
);
- let userLabel = ;
- if (MatrixCheck(this.state.currentUser.matrix, 'userLabel')) {
- userLabel = My Labels ;
- }
-
return (
@@ -691,7 +686,7 @@ export default class UserAuth extends Component {
My Affiliations
My Groups & Devices
- {userLabel}
+ My Labels
{
this.state.currentUser ?
My Collaboration : null
diff --git a/app/packs/src/components/navigation/search/SearchFilter.js b/app/packs/src/components/navigation/search/SearchFilter.js
index 2944f71bb..e99969ea4 100644
--- a/app/packs/src/components/navigation/search/SearchFilter.js
+++ b/app/packs/src/components/navigation/search/SearchFilter.js
@@ -210,11 +210,11 @@ export default class SearchFilter extends React.Component {
this.listAdvOptions = [
{
value: 'Authors',
- label: 'by authors'
+ label: 'by authors',
},
{
value: 'Contributors',
- label: 'by contributors'
+ label: 'by contributors',
}
];
return (
diff --git a/app/packs/src/components/nmriumWrapper/NMRiumDisplayer.js b/app/packs/src/components/nmriumWrapper/NMRiumDisplayer.js
index 6d5d1829a..9002b8566 100644
--- a/app/packs/src/components/nmriumWrapper/NMRiumDisplayer.js
+++ b/app/packs/src/components/nmriumWrapper/NMRiumDisplayer.js
@@ -98,7 +98,7 @@ export default class NMRiumDisplayer extends React.Component {
const spectra = data?.spectra || [];
return spectra.some((spc) => spc.info?.dimension === 2);
};
-
+
if (event.origin === nmriumOrigin && event.data) {
const eventData = event.data;
const eventDataType = eventData.type;
@@ -430,11 +430,15 @@ export default class NMRiumDisplayer extends React.Component {
renderModalTitle() {
const { nmriumData } = this.state;
- const { sample } = this.props;
+ const { sample, readOnly: forecReadOnly = false } = this.props; // forecReadOnly for REPO
let readOnly = false;
if (sample.hasOwnProperty('can_update')) {
readOnly = !(sample.can_update);
}
+ // forecReadOnly for REPO
+ if (forecReadOnly) {
+ readOnly = true;
+ }
let hasSpectra = false;
if (nmriumData) {
const { version } = nmriumData;
@@ -462,7 +466,7 @@ export default class NMRiumDisplayer extends React.Component {
{
- hasSpectra && !readOnly ?
+ hasSpectra && !readOnly ?
(
- Click to see structure in Viewer}>
- this.handleModalOpen(e)}>
- {' '}Viewer
+
+ Click to see structure in Viewer
+
+ }
+ >
+ this.handleModalOpen(e)}
+ >
+ View in 3D
- {
- show ?
- this.handleModalOpen(e)}
- isPublic={isPublic}
- show={show}
- viewType={viewType}
- /> : null
- }
+ {show ? (
+ this.handleModalOpen(e)}
+ isPublic={isPublic}
+ show={show}
+ viewType={viewType}
+ />
+ ) : null}
>
);
}
@@ -53,4 +70,7 @@ MolViewerBtn.propTypes = {
fileContent: PropTypes.string.isRequired,
isPublic: PropTypes.bool.isRequired,
viewType: PropTypes.string.isRequired,
+ className: PropTypes.string,
};
+
+MolViewerBtn.defaultProps = { className: 'button-right' };
diff --git a/app/packs/src/components/viewer/MolViewerListBtn.js b/app/packs/src/components/viewer/MolViewerListBtn.js
index fc1e50112..142a5f11e 100644
--- a/app/packs/src/components/viewer/MolViewerListBtn.js
+++ b/app/packs/src/components/viewer/MolViewerListBtn.js
@@ -17,65 +17,94 @@ export default class MolViewerListBtn extends Component {
}
handleModalOpen(e) {
- if (e) { e.stopPropagation(); }
- this.setState({ openModal: !this.state.openModal });
+ if (e) {
+ e.stopPropagation();
+ }
+ const { openModal } = this.state;
+ this.setState({ openModal: !openModal });
}
renderBtn(disabled) {
- const btnStyle = disabled ? 'default' : 'info';
- const tipDesc = disabled ? ' (Nothing to view)' : '';
- const onClick = disabled ? e => e.stopPropagation() : e => this.handleModalOpen(e);
+ const { disabled: propsDisabled } = this.props;
+ const btnStyle = disabled ? 'warning' : 'info';
+ const tipDesc = disabled ? ' (No supported format)' : '';
+ const onClick = disabled
+ ? e => e.stopPropagation()
+ : e => this.handleModalOpen(e);
return (
Click to view structure file in Viewer{tipDesc}}
+ overlay={
+
+ Click to see structure in Viewer{tipDesc}
+
+ }
>
-
- {' '}Viewer
+
+ View in 3D
);
}
render() {
- const {
- container, el, isPublic
- } = this.props;
+ const { container, el, isPublic } = this.props;
const { openModal } = this.state;
- const config = UIStore.getState().moleculeViewer || PublicStore.getState().moleculeViewer;
+ const config =
+ UIStore.getState().moleculeViewer ||
+ PublicStore.getState().moleculeViewer;
if (!el) return null;
if (isPublic && !config?.featureEnabled) return null;
- if (container?.children?.length < 1) { return this.renderBtn(true); }
+ if (container?.children?.length < 1) {
+ return this.renderBtn(true);
+ }
- let datasetContainer = ArrayUtils.sortArrByIndex(filter(container.children, o => o.container_type === 'dataset' && o.attachments.length > 0));
- if (datasetContainer?.length < 1) { return this.renderBtn(true); }
+ let datasetContainer = ArrayUtils.sortArrByIndex(
+ filter(
+ container.children,
+ o => o.container_type === 'dataset' && o.attachments.length > 0
+ )
+ );
+ if (datasetContainer?.length < 1) {
+ return this.renderBtn(true);
+ }
- datasetContainer = datasetContainer.map((dc) => {
- const ds = Object.assign({}, dc);
+ datasetContainer = datasetContainer.map(dc => {
+ const ds = { ...dc };
const { attachments } = ds;
- ds.attachments = attachments.filter(attachment => ['cif', 'mmcif', 'mol', 'sdf', 'pdb', 'mol2'].includes(attachment.filename?.match(/\.([^.]+)$/)?.[1]?.toLowerCase()));
+ ds.attachments = attachments.filter(attachment =>
+ ['cif', 'mmcif', 'mol', 'sdf', 'pdb', 'mol2'].includes(
+ attachment.filename?.match(/\.([^.]+)$/)?.[1]?.toLowerCase()
+ )
+ );
if (ds.attachments.length > 0) return ds;
return null;
});
datasetContainer = datasetContainer.filter(dc => dc !== null);
- if (datasetContainer?.length < 1) { return this.renderBtn(true); }
+ if (datasetContainer?.length < 1) {
+ return this.renderBtn(true);
+ }
return (
<>
{this.renderBtn(false)}
- {
- openModal ?
- this.handleModalOpen(e)}
- show={openModal}
- title={el.short_label}
- datasetContainer={datasetContainer}
- isPublic={isPublic}
- /> : null
- }
+ {openModal ? (
+ this.handleModalOpen(e)}
+ show={openModal}
+ title={el.short_label}
+ datasetContainer={datasetContainer}
+ isPublic={isPublic}
+ />
+ ) : null}
>
);
}
diff --git a/app/packs/src/components/viewer/MolViewerListModal.js b/app/packs/src/components/viewer/MolViewerListModal.js
index d1f795d32..01fb4ffe3 100644
--- a/app/packs/src/components/viewer/MolViewerListModal.js
+++ b/app/packs/src/components/viewer/MolViewerListModal.js
@@ -1,21 +1,54 @@
/* eslint-disable react/forbid-prop-types */
-import React, { useState } from 'react';
+import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
-import { Alert, Col, Modal, PanelGroup, Panel, Nav, NavItem } from 'react-bootstrap';
+import { Col, Modal, PanelGroup, Panel, Nav, NavItem } from 'react-bootstrap';
import { MolViewer } from 'react-molviewer';
+import PublicStore from 'src/stores/alt/repo/stores/PublicStore';
+import UIStore from 'src/stores/alt/stores/UIStore';
import LoadingActions from 'src/stores/alt/actions/LoadingActions';
+import MolViewerSet from 'src/components/viewer/MolViewerSet';
-const MolViewerListModal = (props) => {
- const {
- datasetContainer, handleModalOpen, isPublic, show
- } = props;
+function MolViewerListModal(props) {
+ const config =
+ UIStore.getState().moleculeViewer || PublicStore.getState().moleculeViewer;
+ if (!config?.featureEnabled) return ;
+ const { datasetContainer, handleModalOpen, isPublic, show } = props;
+ const [molContent, setMolContent] = useState(null);
const [activeKey, setActiveKey] = useState(1);
const [selected, setSelected] = useState(() => {
const ds = datasetContainer[0];
const file = (ds?.attachments?.length > 0 && ds?.attachments[0]) || {};
return { ...file, dsName: ds.name };
});
+ const [modalBody, setModalBody] = useState(null);
+
+ useEffect(() => {
+ if (selected?.id) {
+ const url = isPublic
+ ? `${window.location.origin}/api/v1/public/download/attachment?id=${selected?.id}`
+ : `${window.location.origin}/api/v1/attachments/${selected?.id}`;
+ setMolContent(url);
+ }
+ }, [selected?.id, isPublic]);
+
+ useEffect(() => {
+ if (selected?.id && molContent) {
+ const viewer = (
+ LoadingActions.start()}
+ fnCb={() => LoadingActions.stop()}
+ />
+ );
+ setModalBody(
+
+ {viewer}
+
+ );
+ }
+ }, [molContent]);
const handleFile = (e, attachment, ds) => {
e.stopPropagation();
@@ -34,71 +67,74 @@ const MolViewerListModal = (props) => {
accordion
id="accordion-controlled-example"
defaultActiveKey={defaultActiveKey}
- style={{ width: '100%', height: 'calc(100vh - 200px)', overflow: 'auto' }}
+ style={{
+ width: '100%',
+ height: 'calc(100vh - 200px)',
+ overflow: 'auto',
+ }}
>
- {
- datasetContainer.map((ds) => {
- const { attachments } = ds;
- return (
- handleSelect(e, ds.id)}>
-
- {`Dataset: ${ds.name}`}
-
-
-
- {
- attachments.map(attachment => (
- handleFile(e, attachment, ds)}
- >
- {attachment.filename}
-
- ))
- }
-
-
-
- );
- })
- }
+ {datasetContainer.map(ds => {
+ const { attachments } = ds;
+ return (
+ handleSelect(e, ds.id)}
+ >
+
+ {`Dataset: ${ds.name}`}
+
+
+
+ {attachments.map(attachment => (
+ handleFile(e, attachment, ds)}
+ >
+
+
+ {attachment.filename}
+
+ ))}
+
+
+
+ );
+ })}
);
};
if (show) {
- let modalBody = This service is offline. Please contact your system administrator. ;
- if (selected?.id) {
- const viewer = ( LoadingActions.start()}
- fnCb={() => LoadingActions.stop()}
- />);
- modalBody = {viewer}
;
- }
return (
-
+
e.stopPropagation()} closeButton>
- Dataset: {selected.dsName} / File: {selected?.filename}
+ {`Dataset: ${selected.dsName} / File: ${selected?.filename}`}
+ {MolViewerSet.INFO}
e.stopPropagation()}>
{list()}
- {modalBody}
+
+ {modalBody}
+
);
}
return ;
-};
+}
MolViewerListModal.propTypes = {
datasetContainer: PropTypes.array.isRequired,
diff --git a/app/packs/src/components/viewer/MolViewerModal.js b/app/packs/src/components/viewer/MolViewerModal.js
index c48b99198..a4b9d1500 100644
--- a/app/packs/src/components/viewer/MolViewerModal.js
+++ b/app/packs/src/components/viewer/MolViewerModal.js
@@ -1,87 +1,64 @@
/* eslint-disable react/forbid-prop-types */
-import React, { useEffect, useState } from 'react';
+import React, { useState } from 'react';
import PropTypes from 'prop-types';
-import { Modal, ProgressBar } from 'react-bootstrap';
+import { Modal } from 'react-bootstrap';
import { MolViewer } from 'react-molviewer';
+import PublicStore from 'src/stores/alt/repo/stores/PublicStore';
+import UIStore from 'src/stores/alt/stores/UIStore';
import LoadingActions from 'src/stores/alt/actions/LoadingActions';
-import PublicFetcher from 'src/repo/fetchers/PublicFetcher';
+import MolViewerSet from 'src/components/viewer/MolViewerSet';
-const MolViewerModal = (props) => {
- const {
- fileContent, title, handleModalOpen, viewType, show
- } = props;
+function MolViewerModal(props) {
+ const { fileContent, handleModalOpen, viewType, show, isPublic } = props;
+ const [newContent] = useState(fileContent);
+ const config =
+ UIStore.getState().moleculeViewer || PublicStore.getState().moleculeViewer;
+ if (!config?.featureEnabled || !fileContent) return ;
- const [newContent, setNewContent] = useState(fileContent);
- const [progress, setProgress] = useState(0);
-
- const updateNewContent = (data) => {
- setNewContent(new Blob([data], { type: 'text/plain' }));
- };
-
- const convertMolfile = () => {
- const intervalId = setInterval(() => {
- setProgress((preProgress) => {
- if (preProgress >= 90) {
- clearInterval(intervalId);
- return 100;
- }
- return preProgress + 10;
- });
- }, 1000);
- const params = {
- data: { mol: fileContent },
- };
- PublicFetcher.convertMolfile(params).then((result) => {
- updateNewContent(result);
- }).catch(() => {
- updateNewContent(fileContent);
- }).finally(() => {
- setProgress(100);
- clearInterval(intervalId);
- });
- };
-
- useEffect(() => {
- convertMolfile();
- setProgress(0);
- }, []);
+ const src = isPublic
+ ? '/api/v1/public/represent/structure'
+ : '/api/v1/converter/structure';
if (show) {
- const viewer = ( LoadingActions.start()}
- fnCb={() => LoadingActions.stop()}
- />);
+ const viewer = (
+ LoadingActions.start()}
+ fnCb={() => LoadingActions.stop()}
+ src={src}
+ />
+ );
return (
-
+
-
- {title}
+
+ {MolViewerSet.INFO}
- {
- progress >= 100 ?
- {viewer}
- :
-
- }
+
+ {viewer}
+
);
}
return ;
-};
+}
MolViewerModal.propTypes = {
- fileContent: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired,
+ fileContent: PropTypes.oneOfType([PropTypes.string, PropTypes.object])
+ .isRequired,
handleModalOpen: PropTypes.func.isRequired,
show: PropTypes.bool.isRequired,
- title: PropTypes.string,
viewType: PropTypes.string.isRequired,
+ isPublic: PropTypes.bool.isRequired, // for REPO
};
-MolViewerModal.defaultProps = { title: 'Structure Viewer' };
-
export default MolViewerModal;
diff --git a/app/packs/src/components/viewer/MolViewerSet.js b/app/packs/src/components/viewer/MolViewerSet.js
index 6140bd2c6..ab0eac14c 100644
--- a/app/packs/src/components/viewer/MolViewerSet.js
+++ b/app/packs/src/components/viewer/MolViewerSet.js
@@ -1,5 +1,22 @@
+import React from 'react';
+
export default Object.freeze({
PK: 'moleculeViewer',
JSMOL: 'jsmol',
NGL: 'ngl',
+ INFO: (
+ <>
+
+ Zoom In / Out:
+ Use mouse wheel or Shift + Left-click and drag Vertically
+ Rotate:
+ Click and hold the left mouse button, then drag to rotate
+
+
+ More Functions:
+ Right-click on the molecule view to open the JSmol menu and access more
+ functions, such as saving as PNG file.
+
+ >
+ ),
});
diff --git a/app/packs/src/fetchers/BaseFetcher.js b/app/packs/src/fetchers/BaseFetcher.js
index 12abd6058..3e85fb173 100644
--- a/app/packs/src/fetchers/BaseFetcher.js
+++ b/app/packs/src/fetchers/BaseFetcher.js
@@ -54,10 +54,11 @@ export default class BaseFetcher {
? '&filter_created_at=true' : '&filter_created_at=false';
const fromDate = queryParams.fromDate ? `&from_date=${queryParams.fromDate.unix()}` : '';
const toDate = queryParams.toDate ? `&to_date=${queryParams.toDate.unix()}` : '';
+ const userLabel = queryParams.userLabel ? `&user_label=${queryParams.userLabel}` : '';
const productOnly = queryParams.productOnly === true ? '&product_only=true' : '&product_only=false';
const api = `/api/v1/${type}.json?${isSync ? 'sync_' : ''}`
+ `collection_id=${id}&page=${page}&per_page=${perPage}`
- + `${fromDate}${toDate}${filterCreatedAt}${productOnly}`;
+ + `${fromDate}${toDate}${userLabel}${filterCreatedAt}${productOnly}`;
let addQuery = '';
let userState;
let group;
diff --git a/app/packs/src/fetchers/ReactionsFetcher.js b/app/packs/src/fetchers/ReactionsFetcher.js
index ea820f8fa..e2484407d 100644
--- a/app/packs/src/fetchers/ReactionsFetcher.js
+++ b/app/packs/src/fetchers/ReactionsFetcher.js
@@ -17,6 +17,7 @@ export default class ReactionsFetcher {
credentials: 'same-origin'
}).then((response) => response.json())
.then((json) => {
+ const userLabels = json?.reaction?.tag?.taggable_data?.user_labels || null;
if (json.hasOwnProperty('reaction')) {
const reaction = new Reaction(json.reaction);
if (json.literatures && json.literatures.length > 0) {
@@ -29,10 +30,12 @@ export default class ReactionsFetcher {
}
reaction.updateMaxAmountOfProducts();
reaction.publication = json.publication || {};
+ if (userLabels != null) reaction.user_labels = userLabels;
return new Reaction(defaultAnalysisPublish(reaction));
}
const rReaction = new Reaction(json.reaction);
rReaction.publication = json.publication || {};
+ if (userLabels != null) rReaction.setUserLabels(userLabels);
if (json.error) {
rReaction.id = `${id}:error:Reaction ${id} is not accessible!`;
}
diff --git a/app/packs/src/libHome/ContainerComponent.js b/app/packs/src/libHome/ContainerComponent.js
deleted file mode 100644
index 872af0b26..000000000
--- a/app/packs/src/libHome/ContainerComponent.js
+++ /dev/null
@@ -1,98 +0,0 @@
-import React, { Component } from 'react';
-import { Col, FormControl, FormGroup, ControlLabel } from 'react-bootstrap';
-import Select from 'react-select'
-import ContainerDatasets from 'src/components/container/ContainerDatasets';
-import QuillViewer from 'src/components/QuillViewer';
-
-import { sampleAnalysesContentSymbol } from 'src/utilities/quillToolbarSymbol';
-import { confirmOptions } from 'src/components/staticDropdownOptions/options';
-
-export default class ContainerComponent extends Component {
- constructor(props) {
- super();
- const {container} = props;
- this.state = {
- container
- };
- }
-
- componentWillReceiveProps(nextProps) {
- this.setState({
- container: nextProps.container
- });
- }
-
-
-
- render() {
- const {container} = this.state;
-
- let quill = (
-
- )
-
-
- return (
-
- {/*
-
Name
-
-
-
-
- Type
-
-
-
-
-
- Status
-
-
- */}
- {/*
-
- Content
- {quill}
-
-
- Description
-
-
- */}
-
-
Datasets
-
this.props.onChange(container)}
- />
-
-
- );
- }
-}
diff --git a/app/packs/src/libHome/Navigation.js b/app/packs/src/libHome/Navigation.js
index 258949b1f..423f686e6 100644
--- a/app/packs/src/libHome/Navigation.js
+++ b/app/packs/src/libHome/Navigation.js
@@ -1,5 +1,13 @@
import React from 'react';
-import { Button, Nav, Navbar, NavItem, OverlayTrigger, Tooltip } from 'react-bootstrap';
+import {
+ Button,
+ Nav,
+ Navbar,
+ NavItem,
+ OverlayTrigger,
+ Tooltip,
+} from 'react-bootstrap';
+import Aviator from 'aviator';
import UserAuth from 'src/components/navigation/UserAuth';
import UserStore from 'src/stores/alt/stores/UserStore';
import UIStore from 'src/stores/alt/stores/UIStore';
@@ -8,6 +16,19 @@ import NavNewSession from 'src/components/navigation/NavNewSession';
import NavHead from 'src/repoHome/NavHead';
import DocumentHelper from 'src/utilities/DocumentHelper';
+const aviItem = (currentUser, key, url, text) => {
+ if (!currentUser) return null;
+ return (
+ Aviator.navigate(url)}
+ className="white-nav-item"
+ >
+ {text}
+
+ );
+};
+
export default class Navigation extends React.Component {
constructor(props) {
super(props);
@@ -19,7 +40,7 @@ export default class Navigation extends React.Component {
component: '',
action: null,
listSharedCollections: false,
- }
+ },
};
this.onChange = this.onChange.bind(this);
this.onUIChange = this.onUIChange.bind(this);
@@ -30,6 +51,7 @@ export default class Navigation extends React.Component {
UIStore.listen(this.onUIChange);
UserStore.listen(this.onChange);
UserActions.fetchCurrentUser();
+ UserActions.fetchUserLabels();
UserActions.fetchOmniauthProviders();
}
@@ -81,61 +103,69 @@ export default class Navigation extends React.Component {
render() {
const { modalProps, currentUser, omniauthProviders } = this.state;
- let userBar = ( );
+ let userBar = ;
if (currentUser) {
- userBar = ( );
+ userBar = ;
} else {
- userBar = ( );
+ userBar = (
+
+ );
}
// const logo =
return (
- Aviator.navigate('/home')} >
- Chemotion-Repository
+ Aviator.navigate('/home')}
+ >
+ Chemotion-Repository
+
{userBar}
- {currentUser ?
+ {currentUser ? (
My DB
- : null }
- Aviator.navigate('/home/publications')} className="white-nav-item" >
- Data publications
-
- Aviator.navigate('/home/moleculeArchive')} className="white-nav-item" >
- Molecule Archive
+ ) : null}
+ {aviItem(true, 2, '/home/publications', 'Data Publications')}
+ {aviItem(true, 7, '/home/moleculeArchive', 'Molecule Archive')}
+ {aviItem(currentUser, 3, '/home/review', 'Review')}
+ {aviItem(currentUser, 6, '/home/embargo', 'Embargoed Publications')}
+ {aviItem(true, 9, '/home/newsroom', 'News')}
+
+ How-To
- { currentUser ?
- Aviator.navigate('/home/review')} className="white-nav-item" >
- Review
- : null
- }
- {currentUser ?
- Aviator.navigate('/home/embargo')} className="white-nav-item" >
- Embargoed Publications
- : null
- }
- {
- true ?
- Aviator.navigate('/home/newsroom')} className="white-nav-item">
- News
- : null
- }
- {
- true ?
-
- How-To
- : null
- }
- Aviator.navigate('/home/genericHub')} className="repo-generic-hub-btn">
- LabIMotion Template Hub}>
- {' '}LabIMotion
+ Aviator.navigate('/home/genericHub')}
+ className="repo-generic-hub-btn"
+ >
+
+ LabIMotion Template Hub
+
+ }
+ >
+
+ LabIMotion
+
diff --git a/app/packs/src/models/GenericEl.js b/app/packs/src/models/GenericEl.js
index 04e2a235b..b6c2aaf5e 100644
--- a/app/packs/src/models/GenericEl.js
+++ b/app/packs/src/models/GenericEl.js
@@ -39,6 +39,7 @@ export default class GenericEl extends Element {
container: this.container,
attachments: this.attachments,
files: this.files,
+ user_labels: this.user_labels || [],
segments: this.segments.map(s => s.serialize()),
});
}
@@ -218,6 +219,14 @@ export default class GenericEl extends Element {
return `${this.short_label} ${this.name}`;
}
+ userLabels() {
+ return this.user_labels;
+ }
+
+ setUserLabels(userLabels) {
+ this.user_labels = userLabels;
+ }
+
get isPendingToSave() {
return !isEmpty(this) && (this.isNew || this.changed);
}
diff --git a/app/packs/src/models/Reaction.js b/app/packs/src/models/Reaction.js
index d365e1f0c..b11c986c7 100644
--- a/app/packs/src/models/Reaction.js
+++ b/app/packs/src/models/Reaction.js
@@ -118,6 +118,7 @@ export default class Reaction extends Element {
reactants: [],
rf_value: 0.00,
role: '',
+ user_labels: [],
solvent: '',
solvents: [],
status: '',
@@ -193,6 +194,7 @@ export default class Reaction extends Element {
tlc_description: this.tlc_description,
reaction_svg_file: this.reaction_svg_file,
role: this.role,
+ user_labels: this.user_labels || [],
rf_value: this.rf_value,
rxno: this.rxno,
short_label: this.short_label,
@@ -596,6 +598,14 @@ export default class Reaction extends Element {
));
}
+ userLabels() {
+ return this.user_labels;
+ }
+
+ setUserLabels(userLabels) {
+ this.user_labels = userLabels;
+ }
+
// We will process all reaction policy here
// If oldGroup = null -> drag new Sample into Reaction
// Else -> moving between Material Group
diff --git a/app/packs/src/repo/fetchers/EmbargoFetcher.js b/app/packs/src/repo/fetchers/EmbargoFetcher.js
index f74ffe9ae..a026f490b 100644
--- a/app/packs/src/repo/fetchers/EmbargoFetcher.js
+++ b/app/packs/src/repo/fetchers/EmbargoFetcher.js
@@ -83,7 +83,15 @@ export default class EmbargoFetcher {
method: 'POST',
headers: { Accept: 'application/json', 'Content-Type': 'application/json' },
body: JSON.stringify({ collection_id: id })
- }).then(response => response.json())
+ })
+ .then(response => response.json())
+ .then(json => {
+ if (json.error) {
+ console.log('json', json);
+ alert('Error releasing embargo. Please contact system administrator (chemotion-repository@lists.kit.edu).');
+ }
+ return json;
+ })
.catch((errorMessage) => { console.log(errorMessage); });
}
@@ -113,15 +121,8 @@ export default class EmbargoFetcher {
}
static fetchEmbargoCollections(isSubmit = false) {
- const api = '/api/v1/repository/embargo_list.json';
- return fetch(api, {
- method: 'post',
- credentials: 'same-origin',
- headers: { Accept: 'application/json', 'Content-Type': 'application/json' },
- body: JSON.stringify({
- is_submit: isSubmit
- })
- })
+ const api = `/api/v1/repository/embargo_list.json?is_submit=${isSubmit}`;
+ return fetch(api, { credentials: 'same-origin'})
.then(response => response.json())
.catch((errorMessage) => { console.log(errorMessage); });
}
diff --git a/app/packs/src/repo/fetchers/PublicFetcher.js b/app/packs/src/repo/fetchers/PublicFetcher.js
index 8eec9ee04..5ac5df875 100644
--- a/app/packs/src/repo/fetchers/PublicFetcher.js
+++ b/app/packs/src/repo/fetchers/PublicFetcher.js
@@ -21,7 +21,16 @@ export default class PublicFetcher {
const perPage = params.perPage || 10;
const advFlag = params.advFlag || false;
const paramAdvType = params.advType ? `&adv_type=${params.advType}` : '';
- const paramAdvValue = params.advValue ? params.advValue.map(x => `&adv_val[]=${x.value}`).join('') : '';
+
+ let paramAdvValue = '';
+ if (typeof params.advValue === 'number') {
+ paramAdvValue = `&label_val=${params.advValue}`;
+ } else if (Array.isArray(params.advValue)) {
+ paramAdvValue = params.advValue.map(x => `&adv_val[]=${x.value}`).join('');
+ } else {
+ paramAdvValue = '';
+ }
+// const paramAdvValue = params.advValue ? params.advValue.map(x => `&adv_val[]=${x.value}`).join('') : '';
const listType = params.listType || RepoNavListTypes.SAMPLE;
const api = `/api/v1/public/molecules.json?page=${page}&per_page=${perPage}&adv_flag=${advFlag}${paramAdvType}${paramAdvValue}&req_xvial=${listType === RepoNavListTypes.MOLECULE_ARCHIVE}`;
return fetch(api, { credentials: 'same-origin' })
@@ -39,7 +48,16 @@ export default class PublicFetcher {
const perPage = params.perPage || 10;
const advFlag = params.advFlag || false;
const paramAdvType = params.advType ? `&adv_type=${params.advType}` : '';
- const paramAdvValue = params.advValue ? params.advValue.map(x => `&adv_val[]=${x.value}`).join('') : '';
+
+ let paramAdvValue = '';
+ if (typeof params.advValue === 'number') {
+ paramAdvValue = `&label_val=${params.advValue}`;
+ } else if (Array.isArray(params.advValue)) {
+ paramAdvValue = params.advValue.map(x => `&adv_val[]=${x.value}`).join('');
+ } else {
+ paramAdvValue = '';
+ }
+
const schemeOnly = params.schemeOnly || false;
const api = `/api/v1/public/reactions.json?page=${page}&per_page=${perPage}&adv_flag=${advFlag}${paramAdvType}${paramAdvValue}&scheme_only=${schemeOnly}`;
@@ -172,7 +190,17 @@ export default class PublicFetcher {
static fetchMolecule(id, advFlag = false, advType = '', advValues = null) {
const paramAdvType = (advType && advType !== '') ? `&adv_type=${advType}` : '';
- const paramAdvValue = advValues ? advValues.map(x => `&adv_val[]=${x.value}`).join('') : '';
+
+ let paramAdvValue = '';
+ if (typeof advValues === 'number') {
+ paramAdvValue = `&label_val=${advValues}`;
+ } else if (Array.isArray(advValues)) {
+ paramAdvValue = advValues.map(x => `&adv_val[]=${x.value}`).join('');
+ } else {
+ paramAdvValue = '';
+ }
+
+ // const paramAdvValue = advValues ? advValues.map(x => `&adv_val[]=${x.value}`).join('') : '';
const api = `/api/v1/public/molecule.json?id=${id}&adv_flag=${advFlag}${paramAdvType}${paramAdvValue}`;
return fetch(api, { credentials: 'same-origin' })
.then(response => response.json())
@@ -290,7 +318,7 @@ export default class PublicFetcher {
static downloadDataset(id) {
let fileName = 'dataset.xlsx';
- const api = `/api/v1/public/metadata/export?id=${id}`;
+ const api = `/api/v1/public/export_metadata?id=${id}`;
return fetch(api, { credentials: 'same-origin' })
.then(response => {
const disposition = response.headers.get('Content-Disposition');
diff --git a/app/packs/src/repo/fetchers/RepositoryFetcher.js b/app/packs/src/repo/fetchers/RepositoryFetcher.js
index dc54d62f9..e5407e5c1 100644
--- a/app/packs/src/repo/fetchers/RepositoryFetcher.js
+++ b/app/packs/src/repo/fetchers/RepositoryFetcher.js
@@ -4,39 +4,59 @@ import Reaction from 'src/models/Reaction';
import NotificationActions from 'src/stores/alt/actions/NotificationActions';
const AnalysisIdstoPublish = element => (
- element.analysisArray()
- .filter(a => (a.extended_metadata.publish && (a.extended_metadata.publish === true || a.extended_metadata.publish === 'true')))
+ element
+ .analysisArray()
+ .filter(
+ a =>
+ a.extended_metadata.publish &&
+ (a.extended_metadata.publish === true ||
+ a.extended_metadata.publish === 'true')
+ )
.map(x => x.id)
);
export default class RepositoryFetcher {
-
static reviewPublish(element) {
const { id, type } = element;
return fetch('/api/v1/repository/reviewing/submit', {
credentials: 'same-origin',
method: 'POST',
headers: {
- 'Accept': 'application/json',
- 'Content-Type': 'application/json'
+ Accept: 'application/json',
+ 'Content-Type': 'application/json',
},
- body: JSON.stringify({ id, type })
- }).then(response => response.json())
- .then((json) => {
+ body: JSON.stringify({ id, type }),
+ })
+ .then(response => response.json())
+ .then(json => {
+ if (json.error) {
+ const notification = {
+ title: 'Failed to Submit for Review',
+ message: `Error: ${json.error}`,
+ level: 'error',
+ dismissible: 'button',
+ autoDismiss: 6,
+ position: 'tr',
+ uid: 'publish_error',
+ };
+ NotificationActions.add(notification);
+ return null;
+ }
if (typeof json.reaction !== 'undefined') {
json.reaction.can_publish = false;
json.reaction.can_update = false;
return new Reaction(json.reaction);
- } else if (type === 'sample') {
+ }
+ if (type === 'sample') {
json.sample.can_publish = false;
json.sample.can_update = false;
return new Sample(json.sample);
}
return null;
- })
- .catch((errorMessage) => {
- console.log(errorMessage);
- });
+ })
+ .catch(errorMessage => {
+ console.log(errorMessage);
+ });
}
static publishSample(params, option = null) {
@@ -46,57 +66,63 @@ export default class RepositoryFetcher {
credentials: 'same-origin',
method: option ? 'PUT' : 'POST',
headers: {
- 'Accept': 'application/json',
- 'Content-Type': 'application/json'
+ Accept: 'application/json',
+ 'Content-Type': 'application/json',
},
body: JSON.stringify({
- sampleId: sample.id,
+ id: sample.id,
analysesIds,
coauthors,
reviewers,
refs,
embargo,
license,
- addMe
+ addMe,
+ }),
+ })
+ .then(response => {
+ return response.json();
})
- }).then((response) => {
- return response.json()
- }).then((json) => {
- if (json.error) {
- const notification = {
- title: 'Publish sample fail',
- message: `Error: ${json.error}`,
- level: 'error',
- dismissible: 'button',
- autoDismiss: 6,
- position: 'tr',
- uid: 'publish_sample_error'};
- NotificationActions.add(notification);
- return null;
- }
- if (option=='dois') {
- json.sample.can_publish = true;
- json.sample.can_update = true;
- }
- return new Sample(json.sample);
- }).catch((errorMessage) => {
- console.log(errorMessage);
- });
+ .then(json => {
+ if (json.error) {
+ const notification = {
+ title: 'Failed to Publish Sample',
+ message: `Error: ${json.error}`,
+ level: 'error',
+ dismissible: 'button',
+ autoDismiss: 6,
+ position: 'tr',
+ uid: 'publish_sample_error',
+ };
+ NotificationActions.add(notification);
+ return null;
+ }
+ return { element: sample, closeView: true };
+ })
+ .catch(errorMessage => {
+ console.log(errorMessage);
+ });
}
static publishReactionScheme(params) {
const {
- reaction, coauthors, reviewers, embargo, license, addMe, schemeDesc
+ reaction,
+ coauthors,
+ reviewers,
+ embargo,
+ license,
+ addMe,
+ schemeDesc,
} = params;
return fetch('/api/v1/repository/publishReactionScheme', {
credentials: 'same-origin',
method: 'POST',
headers: {
Accept: 'application/json',
- 'Content-Type': 'application/json'
+ 'Content-Type': 'application/json',
},
body: JSON.stringify({
- reactionId: reaction.id,
+ id: reaction.id,
temperature: reaction.temperature,
duration: reaction.durationDisplay,
products: reaction.products,
@@ -105,112 +131,176 @@ export default class RepositoryFetcher {
reviewers,
embargo,
license,
- addMe
- })
+ addMe,
+ }),
})
- .then(response => (response.json()))
- .then((json) => {
+ .then(response => {
+ if (response.headers.get('content-type')?.includes('application/json')) {
+ return response.json();
+ } else {
+ throw new Error(response.statusText);
+ }
+ })
+ .then(json => {
if (json.error) {
const notification = {
- title: 'Publish reaction scheme fail',
+ title: 'Failed to Publish Reaction Scheme',
message: `Error: ${json.error}`,
level: 'error',
dismissible: 'button',
autoDismiss: 6,
position: 'tr',
- uid: 'publish_reaction_error'
+ uid: 'publish_reaction_error',
};
NotificationActions.add(notification);
return null;
}
- return new Reaction(json.reaction);
- }).catch((errorMessage) => {
- console.log(errorMessage);
+ return reaction;
+ })
+ .catch(errorMessage => {
+ console.log('errorMessage', errorMessage);
+ const notification = {
+ title: 'Failed to Publish Scheme Only Reaction',
+ message: `Error: ${errorMessage}`,
+ level: 'error',
+ dismissible: 'button',
+ autoDismiss: 6,
+ position: 'tr',
+ uid: 'publish_reaction_error',
+ };
+ NotificationActions.add(notification);
+ return null;
});
}
static publishReaction(params, option = null) {
- const { reaction, coauthors, reviewers, refs, embargo, license, isFullyPublish, addMe } = params;
+ const {
+ reaction,
+ coauthors,
+ reviewers,
+ refs,
+ embargo,
+ license,
+ isFullyPublish,
+ addMe,
+ } = params;
if (!isFullyPublish) return this.publishReactionScheme(params);
- const analysesIds = reaction.samples.reduce((acc, s) => acc.concat(AnalysisIdstoPublish(s)),
+ const analysesIds = reaction.samples.reduce(
+ (acc, s) => acc.concat(AnalysisIdstoPublish(s)),
AnalysisIdstoPublish(reaction)
- )
+ );
return fetch(`/api/v1/repository/publishReaction/${option ? 'dois' : ''}`, {
credentials: 'same-origin',
method: option ? 'PUT' : 'POST',
headers: {
- 'Accept': 'application/json',
- 'Content-Type': 'application/json'
+ Accept: 'application/json',
+ 'Content-Type': 'application/json',
},
body: JSON.stringify({
- reactionId: reaction.id,
+ id: reaction.id,
analysesIds,
coauthors,
reviewers,
refs,
embargo,
license,
- addMe
+ addMe,
})
- }).then((response) => {
- return response.json()
- }).then((json) => {
- if (json.error) {
+ })
+ .then(response => {
+ if (response.headers.get('content-type')?.includes('application/json')) {
+ return response.json();
+ } else {
+ throw new Error(response.statusText);
+ }
+ })
+ .then(json => {
+ if (json.error) {
+ const notification = {
+ title: 'Failed to Publish Reaction',
+ message: `Error: ${json.error}`,
+ level: 'error',
+ dismissible: 'button',
+ autoDismiss: 6,
+ position: 'tr',
+ uid: 'publish_reaction_error',
+ };
+ NotificationActions.add(notification);
+ return null;
+ }
+ if (option === 'dois') {
+ json.reaction.can_publish = true;
+ json.reaction.can_update = true;
+ }
+ return reaction;
+ })
+ .catch(errorMessage => {
+ console.log('errorMessage', errorMessage);
const notification = {
- title: 'Publish reaction fail',
- message: `Error: ${json.error}`,
+ title: 'Failed to Publish Reaction',
+ message: `Error: ${errorMessage}`,
level: 'error',
dismissible: 'button',
autoDismiss: 6,
position: 'tr',
- uid: 'publish_reaction_error'
+ uid: 'publish_reaction_error',
};
NotificationActions.add(notification);
return null;
- }
- if (option=='dois') {
- json.reaction.can_publish = true;
- json.reaction.can_update = true;
- }
- return new Reaction(json.reaction);
- }).catch((errorMessage) => {
- console.log(errorMessage);
- });
+ });
}
-
-
- static fetchReviewElements(type, state, searchType, searchValue, page, perPage) {
- const paramSearchType = (searchType && searchType !== '') ? `&search_type=${searchType}` : '';
- const paramSearchValue = (searchValue && searchValue !== '') ? `&search_value=${searchValue}` : '';
- const api = `/api/v1/repository/list.json?type=${type}&state=${state}${paramSearchType}${paramSearchValue}&page=${page}&per_page=${perPage}`;
+ static fetchReviewElements(
+ type,
+ state,
+ label,
+ searchType,
+ searchValue,
+ page,
+ perPage
+ ) {
+ const strApi = '/api/v1/repository/review_list.json?';
+ const paramSearchType =
+ searchType && searchType !== '' ? `&search_type=${searchType}` : '';
+ const paramSearchValue =
+ searchValue && searchValue !== '' ? `&search_value=${searchValue}` : '';
+ const searchLabel = label === null ? '' : `&label=${label}`;
+ const paramPage = `&page=${page}&per_page=${perPage}`;
+ const api = `${strApi}type=${type}&state=${state}${searchLabel}${paramSearchType}${paramSearchValue}${paramPage}`;
return fetch(api, { credentials: 'same-origin' })
- .then(response => response.json().then(json => ({
- elements: json.elements,
- page: parseInt(response.headers.get('X-Page'), 10),
- pages: parseInt(response.headers.get('X-Total-Pages'), 10),
- perPage: parseInt(response.headers.get('X-Per-Page'), 10),
- selectType: type,
- selectState: state,
- searchType,
- searchValue
- })))
- .catch((errorMessage) => { console.log(errorMessage); });
+ .then(response =>
+ response.json().then(json => ({
+ elements: json.elements,
+ page: parseInt(response.headers.get('X-Page'), 10),
+ pages: parseInt(response.headers.get('X-Total-Pages'), 10),
+ perPage: parseInt(response.headers.get('X-Per-Page'), 10),
+ selectType: type,
+ selectState: state,
+ searchType,
+ searchValue,
+ }))
+ )
+ .catch(errorMessage => {
+ console.log(errorMessage);
+ });
}
-
- static fetchReaction(id, isPublic) {
- const api = `/api/v1/repository/reaction.json?id=${id}&is_public=${isPublic}`;
+ static fetchReaction(id) {
+ const api = `/api/v1/repository/reaction.json?id=${id}`;
return fetch(api, { credentials: 'same-origin' })
.then(response => response.json())
- .catch((errorMessage) => { console.log(errorMessage); });
+ .catch(errorMessage => {
+ console.log(errorMessage);
+ });
}
- static fetchSample(id, isPublic) {
- const api = `/api/v1/repository/sample.json?id=${id}&is_public=${isPublic}`
+ static fetchSample(id) {
+ const api = `/api/v1/repository/sample.json?id=${id}`;
return fetch(api, { credentials: 'same-origin' })
.then(response => response.json())
- .catch((errorMessage) => { console.log(errorMessage); });
+ .catch(errorMessage => {
+ console.log(errorMessage);
+ });
}
static saveComments(id, type, comments) {
@@ -371,6 +461,20 @@ export default class RepositoryFetcher {
});
}
+ static saveReviewLabel(params = {}) {
+ return fetch('/api/v1/repository/save_repo_labels', {
+ credentials: 'same-origin',
+ method: 'POST',
+ headers: {
+ Accept: 'application/json',
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify(params)
+ }).then(response => response.json())
+ .then(json => json)
+ .catch((errorMessage) => { console.log(errorMessage); });
+ }
+
static compound(id, data, action = 'request') {
const api = `/api/v1/repository/compound/${action}`;
return fetch(api, {
diff --git a/app/packs/src/repoHome/DecoupleInfo.js b/app/packs/src/repoHome/DecoupleInfo.js
new file mode 100644
index 000000000..0d9deb3a9
--- /dev/null
+++ b/app/packs/src/repoHome/DecoupleInfo.js
@@ -0,0 +1,36 @@
+/* eslint-disable react/forbid-prop-types */
+import React from 'react';
+import PropTypes from 'prop-types';
+import RepoConst from 'src/components/chemrepo/common/RepoConst';
+import ExactMass from 'src/components/chemrepo/ExactMass';
+import { ExactFormula } from 'src/components/common/Formula';
+
+function DecoupleInfo({ sample, molecule }) {
+ const { inchikey = '' } = molecule ?? {};
+ if (!sample.decoupled || inchikey === RepoConst.INCHIKEY_DUMMY) return null;
+ const em = ExactMass(sample, molecule);
+ return (
+ <>
+
+ Formula:
+
+
+ {em && (
+
+ Exact Mass: {em}
+
+ )}
+ >
+ );
+}
+
+DecoupleInfo.propTypes = {
+ sample: PropTypes.object.isRequired,
+ molecule: PropTypes.object,
+};
+
+DecoupleInfo.defaultProps = {
+ molecule: null,
+};
+
+export default DecoupleInfo;
diff --git a/app/packs/src/repoHome/RepoCollection.js b/app/packs/src/repoHome/RepoCollection.js
index 3ba45b3a0..98fc1c855 100644
--- a/app/packs/src/repoHome/RepoCollection.js
+++ b/app/packs/src/repoHome/RepoCollection.js
@@ -1,22 +1,12 @@
import React, { Component } from 'react';
import { Table, Col, Row, Navbar, ButtonGroup, Button, ButtonToolbar, OverlayTrigger, Tooltip } from 'react-bootstrap';
-import SVG from 'react-inlinesvg';
import uuid from 'uuid';
import RepoCollectionDetails from 'src/repoHome/RepoCollectionDetails';
import PublicStore from 'src/stores/alt/repo/stores/PublicStore';
import { MetadataModal, InfoModal } from 'src/repoHome/RepoEmbargoModal';
import EmbargoFetcher from 'src/repo/fetchers/EmbargoFetcher';
import { getFormattedISODate } from 'src/components/chemrepo/date-utils';
-
-const SvgPath = (svg, type) => {
- if (svg && svg !== '***') {
- if (type === 'Reaction') {
- return `/images/reactions/${svg}`;
- }
- return `/images/samples/${svg}`;
- }
- return 'images/wild_card/no_image_180.svg';
-};
+import SVGView from 'src/components/chemrepo/SVGViewPan';
const infoTag = (el, la) => {
let authorInfo = '';
@@ -59,7 +49,7 @@ const Elist = (cid, la, el, selectEmbargo = null, user = null, element = null, f
-
+
{infoTag(el, la)}
diff --git a/app/packs/src/repoHome/RepoCommon.js b/app/packs/src/repoHome/RepoCommon.js
index a76826a57..0f7c5a2fa 100644
--- a/app/packs/src/repoHome/RepoCommon.js
+++ b/app/packs/src/repoHome/RepoCommon.js
@@ -28,24 +28,25 @@ import Select from 'react-select';
import uuid from 'uuid';
import SvgFileZoomPan from 'react-svg-file-zoom-pan-latest';
import { RepoCommentBtn } from 'repo-review-ui';
-import ContainerComponent from 'src/libHome/ContainerComponent';
-import Formula from 'src/components/common/Formula';
+import ContainerComponent from 'src/components/chemrepo/reaction/ContainerComponent';
+import ExactMass from 'src/components/chemrepo/ExactMass';
+import Formula, { ExactFormula } from 'src/components/common/Formula';
import HelpInfo from 'src/components/common/HelpInfo';
import PubchemLabels from 'src/components/pubchem/PubchemLabels';
-import QuillViewer from 'src/components/QuillViewer';
+import Quill2Viewer from 'src/components/Quill2Viewer';
import { ChemotionTag } from 'src/components/chemrepo/PublishCommon'; // TODO: Paggy
import Sample from 'src/models/Sample';
import Reaction from 'src/models/Reaction';
import PrintCodeButton from 'src/components/common/PrintCodeButton';
import { stopBubble } from 'src/utilities/DomHelper';
import RepoContainerDatasets from 'src/repoHome/RepoContainerDatasets';
-import ImageModal from 'src/components/common/ImageModal';
import Utils from 'src/utilities/Functions';
import { hNmrCheckMsg, cNmrCheckMsg, instrumentText } from 'src/utilities/ElementUtils';
import { contentToText } from 'src/utilities/quillFormat';
import { chmoConversions } from 'src/components/OlsComponent';
import DropdownButtonSelection from 'src/components/common/DropdownButtonSelection';
import InputButtonField from 'src/components/common/InputButtonField';
+import RepoConst from 'src/components/chemrepo/common/RepoConst';
import RepoReactionSchemeInfo from 'src/repoHome/RepoReactionSchemeInfo';
import PublicActions from 'src/stores/alt/repo/actions/PublicActions';
import RepoUserComment from 'src/components/chemrepo/common/RepoUserComment';
@@ -64,8 +65,10 @@ import LdData from 'src/components/chemrepo/LdData';
import PublicLabels from 'src/components/chemrepo/PublicLabels';
import PublicReactionTlc from 'src/components/chemrepo/PublicReactionTlc';
import PublicReactionProperties from 'src/components/chemrepo/PublicReactionProperties';
+import StateLabel from 'src/components/chemrepo/common/StateLabel';
+import SVGView from 'src/components/chemrepo/SVGViewPan';
-const hideInfo = _molecule => ((_molecule?.inchikey === 'DUMMY') ? { display: 'none' } : {});
+const hideInfo = _molecule => ((_molecule?.inchikey === RepoConst.INCHIKEY_DUMMY) ? { display: 'none' } : {});
const CollectionDesc = (props) => {
let { label } = props;
@@ -120,11 +123,6 @@ ChemotionId.propTypes = {
const SchemeWord = () =>
(scheme) ;
-
-const NewsroomTemplate = {
- title: '', content: {}, article: []
-};
-
const HomeFeature = props => (
{props.title}
@@ -470,26 +468,6 @@ PublishTypeAs.defaultProps = {
selected: 'full',
};
-
-const ElStateLabel = (state) => {
- let stateStyle;
- switch (state) {
- case 'reviewed':
- stateStyle = 'info';
- break;
- case 'pending':
- stateStyle = 'warning';
- break;
- case 'accepted':
- stateStyle = 'success';
- break;
- default:
- stateStyle = 'default';
- }
- return
{state} ;
-};
-
-
const MoveEmbargoedBundle = (element, onMoveClick) => {
return (
Move to another embargoed bundle}>
@@ -498,17 +476,7 @@ const MoveEmbargoedBundle = (element, onMoveClick) => {
);
};
-const SvgPath = (svg, type) => {
- if (svg && svg !== '***') {
- if (type === 'Reaction') {
- return `/images/reactions/${svg}`;
- }
- return `/images/samples/${svg}`;
- }
- return 'images/wild_card/no_image_180.svg';
-};
-
-const ElAspect = (e, onClick, user = null, owner, currentElement = null, onMoveClick) => {
+const ElAspect = (e, onClick, user = null, isOwner, currentElement = null, onMoveClick) => {
if (!e) {
return '';
}
@@ -530,10 +498,10 @@ const ElAspect = (e, onClick, user = null, owner, currentElement = null, onMoveC
{schemeOnly ? : ''} {e.title}
By {e.published_by} at
- {getFormattedISODateTime(e.submit_at)} {user !== null && user.type === 'Anonymous' ? '' : ElStateLabel(e.state)}
- {user !== null && user.id != owner ? '' : MoveEmbargoedBundle(e, onMoveClick)}
+ {getFormattedISODateTime(e.submit_at)} {user?.type === RepoConst.U_TYPE.ANONYMOUS ? '' : StateLabel(e.state)}
+ {user !== null && !isOwner ? '' : MoveEmbargoedBundle(e, onMoveClick)}
-
+
@@ -627,7 +595,9 @@ const ChecklistPanel = ({
if (isReviewer === true || review_info?.groupleader == true) {
- const leaders = review_info?.leaders?.length > 0 ? `additional reviewer(s): ${review_info?.leaders?.join(', ')}` : '';
+
+ const leader_names = review_info?.leaders?.length > 0 ? review_info.leaders.map(u => u.name) : [];
+ const leaders = leader_names.length > 0 ? `additional reviewer(s): ${leader_names.join(', ')}` : '';
const isGL = review_info?.leaders?.length > 0 ? (group leader review}>{dglr} ) : '';
return (
@@ -884,7 +854,7 @@ class ClipboardCopyBtn extends Component {
}
}
-const MoleculeInfo = ({ molecule, sample_svg_file = '', hasXvial = false }) => {
+const MoleculeInfo = ({ molecule, sample_svg_file = '', hasXvial = false, children }) => {
let svgPath = `/images/molecules/${molecule.molecule_svg_file}`;
if (sample_svg_file && sample_svg_file != '') {
svgPath = `/images/samples/${sample_svg_file}`;
@@ -910,6 +880,11 @@ const MoleculeInfo = ({ molecule, sample_svg_file = '', hasXvial = false }) => {
{resizableSvg(svgPath,
)}
+
+ {children}
+
+ This information is based on the molecular structure shown on the left side. For a decoupled sample, please refer to its individual details.
+
{nameOrFormula}
@@ -934,6 +909,7 @@ const MoleculeInfo = ({ molecule, sample_svg_file = '', hasXvial = false }) => {
+
);
@@ -952,8 +928,8 @@ const RenderAnalysisHeader = (props) => {
doiLink = (element.doi && element.doi.full_doi) || '';
}
const nameOrFormula = molecule.iupac_name && molecule.iupac_name !== ''
- ?
IUPAC Name: {molecule.iupac_name} ( )
- :
Formula: ;
+ ?
IUPAC Name: {molecule.iupac_name} ( )
+ :
Formula: ;
const iupacUserDefined = element.showed_name == (molecule.iupac_name)
?
@@ -992,7 +968,7 @@ const RenderAnalysisHeader = (props) => {
Canonical SMILES:
InChI:
InChIKey:
-
Exact Mass: {SampleExactMW(molecule.exact_molecular_weight)}
+
Exact Mass: {ExactMass(element, molecule)}
Sample DOI:
{
@@ -1513,9 +1489,9 @@ const ReactionInfo = ({ reaction, toggleScheme, showScheme, isPublic = true,
const additionlength = (additionInfo && additionInfo.ops && additionInfo.ops.length > 0 && additionInfo.ops[0].insert) ? additionInfo.ops[0].insert.trim().length : 0;
const descQV = contentlength > 0 ?
- (Description: ) : null;
+ (Description: ) : null;
const addQV = additionlength > 0 ?
- (Additional information for publication and purification details: ) : null;
+ (Additional information for publication and purification details: ) : null;
const bodyAttrs = {
@@ -1674,7 +1650,7 @@ class RenderPublishAnalysesPanel extends Component {
copy to clipboard}>
{ navigator.clipboard.writeText(contentToText(content)); }}>
-
+
@@ -1755,10 +1731,10 @@ class RenderPublishAnalyses extends Component {
-
@@ -1980,11 +1956,11 @@ class PublishAnalysesTag extends Component {
:
Status: {status} {statusMsg}
}
-
@@ -2221,7 +2197,6 @@ export {
DownloadJsonBtn,
EditorTips,
ElementIcon,
- ElStateLabel,
ElAspect,
EmbargoCom,
IconToMyDB,
@@ -2230,7 +2205,6 @@ export {
isDatasetPass,
HomeFeature,
MoleculeInfo,
- NewsroomTemplate,
PublishAnalysesTag,
PublishTypeAs,
ReactionSchemeOnlyInfo,
@@ -2244,7 +2218,6 @@ export {
SchemeWord,
SidToPubChem,
OrcidIcon,
- SvgPath,
ToggleIndicator,
CollectionDesc
};
diff --git a/app/packs/src/repoHome/RepoEmbargo.js b/app/packs/src/repoHome/RepoEmbargo.js
index 71d468817..21353d28d 100644
--- a/app/packs/src/repoHome/RepoEmbargo.js
+++ b/app/packs/src/repoHome/RepoEmbargo.js
@@ -209,22 +209,29 @@ export default class RepoEmbargo extends Component {
handleEmbargoRelease() {
const { selectEmbargo, current_user } = this.state;
+ const isSubmitter =
+ current_user?.id === selectEmbargo?.published_by ||
+ (selectEmbargo?.review?.submitters || []).includes(current_user?.id);
if (selectEmbargo === null) {
alert('Please select an embargo first!');
- } else if (current_user.id !== selectEmbargo.published_by) {
+ } else if (!isSubmitter) {
alert('only the submitter can perform the release!');
} else {
EmbargoActions.releaseEmbargo(selectEmbargo.element_id);
- alert(`The embargo on [${selectEmbargo.taggable_data && selectEmbargo.taggable_data.label}] has been released!`);
+ alert(`The submission for the release of the embargo [${selectEmbargo.taggable_data && selectEmbargo.taggable_data.label}] has been completed!`);
}
}
handleEmbargoDelete(shouldPerform) {
if (shouldPerform) {
const { selectEmbargo, current_user } = this.state;
+ const isSubmitter =
+ current_user?.id === selectEmbargo?.published_by ||
+ (selectEmbargo?.review?.submitters || []).includes(current_user?.id);
+
if (selectEmbargo === null) {
alert('Please select an embargo first!');
- } else if (current_user?.is_reviewer === false && current_user.id !== selectEmbargo.published_by) {
+ } else if (!isSubmitter) {
alert('only the submitter can delete the release!');
} else {
EmbargoActions.deleteEmbargo(selectEmbargo.element_id);
@@ -242,7 +249,8 @@ export default class RepoEmbargo extends Component {
rendeActionBtn(bundles) {
const { selectEmbargo, elements, current_user } = this.state;
const acceptedEl = ((typeof (elements) !== 'undefined' && elements) || []).filter(e => e.state === 'accepted');
- const actionButtons = (!selectEmbargo || !current_user || (current_user?.is_reviewer == false && current_user.id !== selectEmbargo.published_by)) ? :
+ const is_submitter = current_user?.id === selectEmbargo?.published_by || (selectEmbargo?.review?.submitters || []).includes(current_user?.id);
+ const actionButtons = (!selectEmbargo || !current_user || (current_user?.is_reviewer == false && !is_submitter)) ? :
(
@@ -294,8 +302,11 @@ export default class RepoEmbargo extends Component {
const options = [];
const hasComment = selectEmbargo?.review?.history?.length > 0;
bundles?.forEach((col) => {
- const tag = col.taggable_data || {};
- options.push({ value: col.element_id, name: tag.label, label: tag.label });
+ if (current_user.is_reviewer || current_user.is_submitter || col.published_by === current_user.id ||
+ (col.review?.submitters || []).includes(current_user.id) || current_user.type === 'anonymous') {
+ const tag = col.taggable_data || {};
+ options.push({ value: col.element_id, name: tag.label, label: tag.label });
+ }
});
const filterDropdown = (
@@ -343,6 +354,7 @@ export default class RepoEmbargo extends Component {
)}
0) {
return (
@@ -442,7 +457,7 @@ export default class RepoEmbargo extends Component {
- {((typeof (elements) !== 'undefined' && elements) || []).map(r => ElAspect(r, EmbargoActions.displayReviewEmbargo, current_user, owner, currentElement, this.handleMoveShow)) }
+ {((typeof (elements) !== 'undefined' && elements) || []).map(r => ElAspect(r, EmbargoActions.displayReviewEmbargo, current_user, isOwner, currentElement, this.handleMoveShow)) }
@@ -457,7 +472,7 @@ export default class RepoEmbargo extends Component {
- { this.renderMoveModal() }
+ {this.renderMoveModal()}
{
@@ -31,7 +33,7 @@ const renderAuthors = ({ creators, affiliationMap }) => {
if (!creators) return null;
return (
- {creators.map((creator, i) => (
+ {creators.map(creator => (
{creator.name}
@@ -51,7 +53,7 @@ const renderAuthors = ({ creators, affiliationMap }) => {
const renderOverview = ({ states, state }) => {
return (
- {ElStateLabel(state)}
+ {StateLabel(state)}
{
});
const viewDetailBtn =
- currentUser.is_reviewer || currentUser.is_submitter ? (
+ currentUser.is_reviewer ||
+ currentUser.is_submitter ||
+ (rec?.review?.submitters || []).includes(currentUser?.id) ||
+ currentUser?.type === RepoConst.U_TYPE.ANONYMOUS ? (
EmbargoActions.getEmbargoElements(recId)}
@@ -115,15 +120,16 @@ const renderRecord = (rec, index, currentUser) => {
) : null;
- const viewComment = comment ? (
-
- {comment}
-
- ) : null;
+ const viewComment =
+ comment && (currentUser.is_reviewer || currentUser.is_submitter) ? (
+
+ {comment}
+
+ ) : null;
return (
diff --git a/app/packs/src/repoHome/RepoHome.js b/app/packs/src/repoHome/RepoHome.js
index d168d9cf4..40da55c5c 100644
--- a/app/packs/src/repoHome/RepoHome.js
+++ b/app/packs/src/repoHome/RepoHome.js
@@ -152,6 +152,18 @@ class RepoHome extends Component {
+
+
+
diff --git a/app/packs/src/repoHome/RepoHowToReader.js b/app/packs/src/repoHome/RepoHowToReader.js
index 53a49d92d..f94686d26 100644
--- a/app/packs/src/repoHome/RepoHowToReader.js
+++ b/app/packs/src/repoHome/RepoHowToReader.js
@@ -2,7 +2,7 @@ import React, { Component } from 'react';
import { Panel, Row, Col } from 'react-bootstrap';
import uuid from 'uuid';
import PublicStore from 'src/stores/alt/repo/stores/PublicStore';
-import QuillViewer from 'src/components/QuillViewer';
+import Quill2Viewer from 'src/components/Quill2Viewer';
import { DateFormatYMDLong } from 'src/repoHome/RepoCommon';
export default class RepoHowToReader extends Component {
@@ -99,7 +99,7 @@ export default class RepoHowToReader extends Component {
howto.article ?
howto.article.map((s) => {
if (s.art === 'txt') {
- return
;
+ return
;
}
if (s.art === 'img') {
return (
diff --git a/app/packs/src/repoHome/RepoMoleculeArchive.js b/app/packs/src/repoHome/RepoMoleculeArchive.js
index c854c6850..ac2f6d77b 100644
--- a/app/packs/src/repoHome/RepoMoleculeArchive.js
+++ b/app/packs/src/repoHome/RepoMoleculeArchive.js
@@ -42,6 +42,7 @@ const RepoMoleculeArchive = (props) => {
const {
molecule, currentElement, isPubElement, advFlag, advType, advValue
} = props;
+ if (!molecule.xvial_count) return null;
const listClass = (currentElement && currentElement.molecule && currentElement.molecule.id === molecule.id) ? 'list_focus_on' : 'list_focus_off';
const svgPathSample = molecule.sample_svg_file
? `/images/samples/${molecule.sample_svg_file}`
diff --git a/app/packs/src/repoHome/RepoMoleculeList.js b/app/packs/src/repoHome/RepoMoleculeList.js
index 7ee49548b..e7bac6a7d 100644
--- a/app/packs/src/repoHome/RepoMoleculeList.js
+++ b/app/packs/src/repoHome/RepoMoleculeList.js
@@ -9,70 +9,122 @@ import Formula from 'src/components/common/Formula';
import PubchemLabels from 'src/components/pubchem/PubchemLabels';
import { xvialTag, svgTag } from 'src/repoHome/RepoPubCommon';
import { getFormattedISODate } from 'src/components/chemrepo/date-utils';
+import { ExtIcon } from 'src/components/chemrepo/ExtIcon';
-const pubchemTag = (molecule) => {
- if (molecule && molecule.tag &&
- molecule.tag.taggable_data && molecule.tag.taggable_data.pubchem_cid) {
+const pubchemTag = molecule => {
+ if (molecule?.tag?.taggable_data?.pubchem_cid) {
return {
- pubchem_tag: { pubchem_cid: molecule.tag.taggable_data.pubchem_cid }
+ pubchem_tag: { pubchem_cid: molecule.tag.taggable_data.pubchem_cid },
};
}
return false;
};
-const infoTag = (molecule) => {
+const infoTag = molecule => {
const pubData = (molecule && molecule.pub_id) || '';
return (
- Chemotion-Repository unique ID}>
+
+ Chemotion-Repository unique ID
+
+ }
+ >
-
ID
{`CRS-${pubData}`}
+
ID
+
{`CRS-${pubData}`}
- an embargo bundle contains publications which have been published at the same time}>
+
+ an embargo bundle contains publications which have been published at
+ the same time
+
+ }
+ >
-
Embargo
{molecule.embargo}
+
Embargo
+
{molecule.embargo}
-
Author
{molecule.author_name}
+
Author
+
{molecule.author_name}
-
Published on
{getFormattedISODate(molecule.published_at)}
+
Published on
+
+ {getFormattedISODate(molecule.published_at)}
+
-
Analyses
{molecule.ana_cnt || 0}
+
Analyses
+
{molecule.ana_cnt || 0}
- When the X-Vial icon available, a physical sample of this molecule was registered to the Molecule Archive of the Compound Platform and can be requested from there}>
+
+ When the X-Vial icon available, a physical sample of this molecule
+ was registered to the Molecule Archive of the Compound Platform and
+ can be requested from there
+
+ }
+ >
-
X-Vial
{xvialTag(molecule)}
+
X-Vial
+
{xvialTag(molecule)}
);
};
-const RepoMoleculeList = (props) => {
- const {
- molecule, currentElement, isPubElement, advFlag, advType, advValue
- } = props;
- const listClass = (currentElement && currentElement.molecule && currentElement.molecule.id === molecule.id) ? 'list_focus_on' : 'list_focus_off';
+function RepoMoleculeList(props) {
+ const { molecule, currentElement, isPubElement, advFlag, advType, advValue } =
+ props;
+ const listClass =
+ currentElement?.molecule?.id === molecule.id
+ ? 'list_focus_on'
+ : 'list_focus_off';
const svgPathSample = molecule.sample_svg_file
? `/images/samples/${molecule.sample_svg_file}`
: `/images/molecules/${molecule.molecule_svg_file}`;
const pubchemInfo = pubchemTag(molecule);
return (
-
{ LoadingActions.start(); PublicActions.displayMolecule(molecule.id, '', advFlag, advType, advValue); }}>
+
{
+ LoadingActions.start();
+ PublicActions.displayMolecule(
+ molecule.id,
+ '',
+ advFlag,
+ advType,
+ advValue
+ );
+ }}
+ >
-
- {svgTag(svgPathSample, 'molecule', true)}
-
+ {svgTag(svgPathSample, 'molecule', true)}
-
+
+
+ {ExtIcon(molecule.embargo)}
+
{molecule.iupac_name}
- {pubchemInfo ? : null }
+
+ {pubchemInfo ? (
+
+ ) : null}
+
@@ -80,17 +132,17 @@ const RepoMoleculeList = (props) => {
);
-};
+}
RepoMoleculeList.propTypes = {
molecule: PropTypes.object.isRequired,
currentElement: PropTypes.object,
isPubElement: PropTypes.bool,
+ advFlag: PropTypes.bool.isRequired,
+ advType: PropTypes.string.isRequired,
+ advValue: PropTypes.array.isRequired,
};
-RepoMoleculeList.defaultProps = {
- isPubElement: false,
- currentElement: null
-};
+RepoMoleculeList.defaultProps = { isPubElement: false, currentElement: null };
export default RepoMoleculeList;
diff --git a/app/packs/src/repoHome/RepoNewsEditor.js b/app/packs/src/repoHome/RepoNewsEditor.js
index cb9c3331e..f7fc76d89 100644
--- a/app/packs/src/repoHome/RepoNewsEditor.js
+++ b/app/packs/src/repoHome/RepoNewsEditor.js
@@ -1,5 +1,16 @@
import React, { Component } from 'react';
-import { Alert, Jumbotron, Row, Col, Form, FormGroup, FormControl, ControlLabel, Button, InputGroup } from 'react-bootstrap';
+import {
+ Alert,
+ Button,
+ Col,
+ ControlLabel,
+ Form,
+ FormGroup,
+ FormControl,
+ InputGroup,
+ Jumbotron,
+ Row,
+} from 'react-bootstrap';
import Dropzone from 'react-dropzone';
import moment from 'moment';
import ArticleFetcher from 'src/repo/fetchers/ArticleFetcher';
@@ -7,11 +18,17 @@ import NewsPreviewModal from 'src/components/common/NewsPreviewModal';
import PublicStore from 'src/stores/alt/repo/stores/PublicStore';
import { ConfirmModal } from 'src/components/common/ConfirmModal';
import Attachment from 'src/models/Attachment';
-import { EditorTips, DateFormatDMYTime, NewsroomTemplate } from 'src/repoHome/RepoCommon';
+import { EditorTips, DateFormatDMYTime } from 'src/repoHome/RepoCommon';
import { contentToText } from 'src/utilities/quillFormat';
import { EditorBtn, EditorBaseBtn } from 'src/libHome/RepoHowTo/EditorBtn';
import EditorStelle from 'src/libHome/RepoHowTo/EditorStelle';
+const NewsroomTemplate = {
+ title: '',
+ content: {},
+ article: [],
+};
+
const extractIntro = (article) => {
const result = article.filter(a => a.art === 'txt');
if (result.length < 1) {
diff --git a/app/packs/src/repoHome/RepoNewsReader.js b/app/packs/src/repoHome/RepoNewsReader.js
index 16ef2764d..6ac6d0e25 100644
--- a/app/packs/src/repoHome/RepoNewsReader.js
+++ b/app/packs/src/repoHome/RepoNewsReader.js
@@ -2,7 +2,7 @@ import React, { Component } from 'react';
import { Panel, Row, Col } from 'react-bootstrap';
import uuid from 'uuid';
import PublicStore from 'src/stores/alt/repo/stores/PublicStore';
-import QuillViewer from 'src/components/QuillViewer';
+import Quill2Viewer from 'src/components/Quill2Viewer';
import { DateFormatYMDLong } from 'src/repoHome/RepoCommon';
export default class RepoNewsReader extends Component {
@@ -96,7 +96,7 @@ export default class RepoNewsReader extends Component {
news.article ?
news.article.map((s) => {
if (s.art === 'txt') {
- return
;
+ return
;
}
if (s.art === 'img') {
return (
diff --git a/app/packs/src/repoHome/RepoPubl.js b/app/packs/src/repoHome/RepoPubl.js
index 906313fab..07b405cad 100644
--- a/app/packs/src/repoHome/RepoPubl.js
+++ b/app/packs/src/repoHome/RepoPubl.js
@@ -22,6 +22,7 @@ import RepoMoleculeList from 'src/repoHome/RepoMoleculeList';
import RepoMoleculeArchive from 'src/repoHome/RepoMoleculeArchive';
import RepoNavListTypes from 'src/repoHome/RepoNavListTypes';
import capitalizeFirstLetter from 'src/components/chemrepo/format-utils';
+import { SearchUserLabels } from 'src/components/UserLabels';
const renderMoleculeArchive =
(molecule, currentElement, isPubElement, advFlag, advType, advValue) => (
@@ -478,14 +479,20 @@ export default class RepoPubl extends Component {
}
handleSelectAdvValue(val) {
- if (val && val.length > 0) {
+ if (this.state.advType === 'Label') {
this.setState({ advValue: val }, () => {
PublicActions.setSearchParams({ advValue: val });
});
- } else {
- this.setState({ advValue: [], searchOptions: [] }, () => {
- PublicActions.setSearchParams({ advValue: [], searchOptions: [] });
- });
+ } else {
+ if (val && val.length > 0) {
+ this.setState({ advValue: val }, () => {
+ PublicActions.setSearchParams({ advValue: val });
+ });
+ } else {
+ this.setState({ advValue: [], searchOptions: [] }, () => {
+ PublicActions.setSearchParams({ advValue: [], searchOptions: [] });
+ });
+ }
}
}
@@ -509,8 +516,54 @@ export default class RepoPubl extends Component {
this.listOptions = [
{ value: 'Authors', label: 'by authors' },
{ value: 'Ontologies', label: 'by analysis type' },
- { value: 'Embargo', label: 'by Embargo Bundle#' }
+ { value: 'Embargo', label: 'by Embargo Bundle#' },
+ { value: 'Label', label: 'by label' }
];
+ // const userLabel = [];
+ const customClass = '.btn-unified';
+ let valSelect = '';
+ if (advType === 'Label') {
+ valSelect = (
+
+ );
+ } else {
+ valSelect = (
+
({
+ ...base,
+ height: '36px',
+ minHeight: '36px',
+ minWidth: '200px',
+ borderRadius: 'unset',
+ border: '1px solid #ccc',
+ }),
+ multiValue: styles => ({
+ ...styles,
+ backgroundColor: '#00b8d91a',
+ opacity: '0.8',
+ }),
+ multiValueLabel: styles => ({
+ ...styles,
+ color: '#0052CC',
+ }),
+ }}
+ />
+ );
+ }
return (
@@ -528,37 +581,7 @@ export default class RepoPubl extends Component {
className="o-author"
/>
-
-
({
- ...base,
- height: '36px',
- minHeight: '36px',
- minWidth: '200px',
- borderRadius: 'unset',
- border: '1px solid #ccc',
- }),
- multiValue: styles => ({
- ...styles,
- backgroundColor: '#00b8d91a',
- opacity: '0.8',
- }),
- multiValueLabel: styles => ({
- ...styles,
- color: '#0052CC',
- }),
- }}
- />
-
+
{valSelect}
Advanced Search}>
diff --git a/app/packs/src/repoHome/RepoReactionDetails.js b/app/packs/src/repoHome/RepoReactionDetails.js
index 22374045d..cf362952e 100644
--- a/app/packs/src/repoHome/RepoReactionDetails.js
+++ b/app/packs/src/repoHome/RepoReactionDetails.js
@@ -1,3 +1,4 @@
+/* eslint-disable react/forbid-prop-types */
import React, { Component } from 'react';
import { Panel, Row, Col, Button, Jumbotron } from 'react-bootstrap';
import PropTypes from 'prop-types';
@@ -139,7 +140,17 @@ export default class RepoReactionDetails extends Component {
reactionInfo(reaction) {
const { showScheme, showRinchi, showProp, showTlc } = this.state;
- const { canComment, review_info, review, isPublished } = this.props;
+ const {
+ canComment: propsCanComment,
+ review_info,
+ review,
+ isPublished,
+ } = this.props;
+ const { currentUser } = UserStore.getState();
+ const canComment =
+ currentUser?.type === RepoConst.U_TYPE.ANONYMOUS
+ ? false
+ : propsCanComment;
const svgPath = `/images/reactions/${reaction.reaction_svg_file}`;
const content = reaction.description;
@@ -150,9 +161,9 @@ export default class RepoReactionDetails extends Component {
? content.ops[0].insert.trim()
: '';
let descQV = (
-
+
Description:
-
+
);
if (descContent === '') {
@@ -168,9 +179,9 @@ export default class RepoReactionDetails extends Component {
? additionInfo.ops[0].insert.trim()
: '';
let addQV = (
-
+
Additional information for publication and purification details:
-
+
);
if (addinfoContent === '') {
@@ -487,7 +498,7 @@ export default class RepoReactionDetails extends Component {
const {
reaction,
isPublished,
- canComment,
+ canComment: propsCanComment,
review_info,
showComment,
review,
@@ -497,6 +508,11 @@ export default class RepoReactionDetails extends Component {
if (typeof reaction === 'undefined' || !reaction) {
return
;
}
+ const { currentUser } = UserStore.getState();
+ const canComment =
+ currentUser?.type === RepoConst.U_TYPE.ANONYMOUS
+ ? false
+ : propsCanComment;
const taggData =
(reaction &&
@@ -601,6 +617,7 @@ export default class RepoReactionDetails extends Component {
);
}
+ const userLabels = (reaction.labels || []).map(label => label.id);
return (
@@ -608,7 +625,7 @@ export default class RepoReactionDetails extends Component {
{canComment ? (
1
}
/>
-
-
-
+
+ {PublicLabels(reaction.labels)}
+
+
+
+
{
const content = props.reaction.description;
const contentlength = (content && content.ops && content.ops.length > 0
&& content.ops[0].insert) ? content.ops[0].insert.trim().length : 0;
const descQV = contentlength > 0 ?
- (Description: ) : null;
+ (Description: ) : null;
return (
diff --git a/app/packs/src/repoHome/RepoReview.js b/app/packs/src/repoHome/RepoReview.js
index 30f23de0a..a5192df6c 100644
--- a/app/packs/src/repoHome/RepoReview.js
+++ b/app/packs/src/repoHome/RepoReview.js
@@ -12,7 +12,12 @@ import UserStore from 'src/stores/alt/stores/UserStore';
import RepositoryFetcher from 'src/repo/fetchers/RepositoryFetcher';
import LoadingActions from 'src/stores/alt/actions/LoadingActions';
import { getFormattedISODateTime } from 'src/components/chemrepo/date-utils';
-import { SvgPath, ElStateLabel, SchemeWord, ChecklistPanel } from 'src/repoHome/RepoCommon';
+import StateLabel from 'src/components/chemrepo/common/StateLabel';
+import SVGView from 'src/components/chemrepo/SVGViewPan';
+import { SchemeWord, ChecklistPanel } from 'src/repoHome/RepoCommon';
+import { ShowUserLabels, SearchUserLabels } from 'src/components/UserLabels';
+
+
// import RepoReviewModal from '../components/common/RepoReviewModal';
const renderElement = (e, currentElement, embargoBtn) => {
@@ -30,10 +35,13 @@ const renderElement = (e, currentElement, embargoBtn) => {
{schemeOnly ? : ''} {e.title}
By {e.published_by} at
- {getFormattedISODateTime(e.submit_at)} {ElStateLabel(e.state)} {ElStateLabel(e.embargo)}
+ {getFormattedISODateTime(e.submit_at)} {StateLabel(e.state)} {StateLabel(e.embargo)}
{embargoBtn}
+
+
+
-
+
@@ -52,10 +60,13 @@ const renderElement = (e, currentElement, embargoBtn) => {
{e.title}
By {e.published_by} at
- {getFormattedISODateTime(e.submit_at)} {ElStateLabel(e.state)} {ElStateLabel(e.embargo)}
+ {getFormattedISODateTime(e.submit_at)} {StateLabel(e.state)} {StateLabel(e.embargo)}
{embargoBtn}
+
+
+
-
+
@@ -99,6 +110,7 @@ export default class RepoReview extends Component {
this.handleSubmitReview = this.handleSubmitReview.bind(this);
this.handleReviewUpdate = this.handleReviewUpdate.bind(this);
this.handleCommentUpdate = this.handleCommentUpdate.bind(this);
+ this.setUserLabel = this.setUserLabel.bind(this);
}
componentDidMount() {
@@ -141,22 +153,31 @@ export default class RepoReview extends Component {
ReviewActions.updateComment(elementId, elementType, cinfo);
}
+ setUserLabel(label) {
+ const { userLabel } = this.state;
+ this.setState({ userLabel: label });
+ if (userLabel !== label) ReviewActions.setUserLabel(label);
+
+ this.handleElementSelection('label', label);
+ }
+
+
onPerPageChange(e) {
const {
- page, selectType, selectState, searchType, searchValue
+ page, selectType, selectState, selectLabel, searchType, searchValue
} = this.state;
const perPage = e.target.value;
this.setState({ perPage });
- ReviewActions.getElements(selectType, selectState, searchType, searchValue, page, perPage);
+ ReviewActions.getElements(selectType, selectState, selectLabel, searchType, searchValue, page, perPage);
}
onPaginationSelect(eventKey) {
const {
- pages, perPage, selectType, selectState, searchType, searchValue
+ pages, perPage, selectType, selectState, selectLabel, searchType, searchValue
} = this.state;
if (eventKey > 0 && eventKey <= pages) {
ReviewActions.getElements(
- selectType, selectState, searchType, searchValue,
+ selectType, selectState, selectLabel, searchType, searchValue,
eventKey, perPage
);
}
@@ -231,18 +252,18 @@ export default class RepoReview extends Component {
}
handleSelectType(val) {
- const { selectType, selectState, perPage } = this.state;
+ const { selectType, selectState, perPage, selectLabel } = this.state;
if (val && (val === 'Submitter' || val === 'Embargo')) {
RepositoryFetcher.fetchReviewSearchOptions(val, selectType, selectState).then((res) => {
const options = res && res.result && res.result
.map(u => ({ value: u.key, name: u.name, label: u.label }));
this.setState({ listTypeOptions: options });
- ReviewActions.getElements(selectType, selectState, val, '', 1, perPage);
+ ReviewActions.getElements(selectType, selectState, selectLabel, val, '', 1, perPage);
}).catch((errorMessage) => {
console.log(errorMessage);
});
} else {
- ReviewActions.getElements(selectType, selectState, val, '', 1, perPage);
+ ReviewActions.getElements(selectType, selectState, selectLabel, val, '', 1, perPage);
}
}
@@ -255,32 +276,35 @@ export default class RepoReview extends Component {
handleSelectAdvValue(val) {
const {
- perPage, selectType, selectState, searchType
+ perPage, selectType, selectState, selectLabel, searchType
} = this.state;
if (val) {
this.setState({ page: 1, searchValue: val });
- ReviewActions.getElements(selectType, selectState, searchType, val, 1, perPage);
+ ReviewActions.getElements(selectType, selectState, selectLabel, searchType, val, 1, perPage);
}
}
handleElementSelection(t, event) {
- const { perPage, searchType, searchValue } = this.state;
+ const { perPage, searchType, selectLabel, searchValue } = this.state;
if (t === 'type') {
this.setState({ selectType: event });
- ReviewActions.getElements(event, this.state.selectState, searchType, searchValue, 1, perPage);
+ ReviewActions.getElements(event, this.state.selectState, selectLabel, searchType, searchValue, 1, perPage);
} else if (t === 'state') {
this.setState({ selectState: event });
- ReviewActions.getElements(this.state.selectType, event, searchType, searchValue, 1, perPage);
+ ReviewActions.getElements(this.state.selectType, event, selectLabel, searchType, searchValue, 1, perPage);
+ } else if (t === 'label') {
+ // this.setState({ selectState: event });
+ ReviewActions.getElements(this.state.selectType, this.state.selectState, event, searchType, searchValue, 1, perPage);
}
}
handleKeyDown(event) {
const {
- perPage, selectType, selectState, searchType, searchValue
+ perPage, selectType, selectState, selectLabel, searchType, searchValue
} = this.state;
switch (event.keyCode) {
case 13: // Enter
- ReviewActions.getElements(selectType, selectState, searchType, searchValue, 1, perPage);
+ ReviewActions.getElements(selectType, selectState, selectLabel, searchType, searchValue, 1, perPage);
event.preventDefault();
break;
default:
@@ -315,7 +339,7 @@ export default class RepoReview extends Component {
}
renderSearch() {
- const { searchType, searchValue, listTypeOptions } = this.state;
+ const { searchType, searchValue, listTypeOptions, userLabel } = this.state;
const customClass = '.btn-unified';
const optSearchType = ['All', 'Samples', 'Reactions'];
@@ -344,6 +368,7 @@ export default class RepoReview extends Component {
>
{this.renderMenuItems('state', optSearchState)}
+
);
diff --git a/app/packs/src/repoHome/RepoReviewButtonBar.js b/app/packs/src/repoHome/RepoReviewButtonBar.js
index 0400cd668..72cf61a94 100644
--- a/app/packs/src/repoHome/RepoReviewButtonBar.js
+++ b/app/packs/src/repoHome/RepoReviewButtonBar.js
@@ -3,11 +3,12 @@ import {
Button,
ButtonToolbar,
OverlayTrigger,
- Tooltip
+ Tooltip,
} from 'react-bootstrap';
import PropTypes from 'prop-types';
import RepoMetadataModal from 'src/components/chemrepo/common/RepoMetadataModal';
import RepoReviewAuthorsModal from 'src/components/chemrepo/common/RepoReviewAuthorsModal';
+import RepoUserLabelModal from 'src/components/chemrepo/common/RepoUserLabelModal';
const showButton = (btn, func, pubState, review_info) => {
let title = btn;
@@ -87,9 +88,22 @@ const showCommentButton = (btn, func, currComment) => {
);
};
-const RepoReviewButtonBar = props =>
- (
-
+function RepoReviewButtonBar(props) {
+ let authorModel = '';
+
+ if (props?.review_info?.groupleader !== true) {
+ authorModel = (
+
+ );
+ }
+
+ return (
{
props.showComment === true && props.buttons.filter(b => b === 'Comments').map(b =>
showCommentButton(b, props.buttonFunc, (props.currComment)))
@@ -102,14 +116,16 @@ const RepoReviewButtonBar = props =>
elementId={props.element.id}
elementType={props.element.elementType.toLowerCase()}
/>
-
-
- );
+ {authorModel}
+
+ )
+};
RepoReviewButtonBar.propTypes = {
element: PropTypes.shape({
id: PropTypes.number,
- elementType: PropTypes.string
+ elementType: PropTypes.string,
+ user_labels: PropTypes.arrayOf(PropTypes.number)
}).isRequired,
buttons: PropTypes.arrayOf(PropTypes.string),
buttonFunc: PropTypes.func,
@@ -128,7 +144,7 @@ RepoReviewButtonBar.defaultProps = {
showComment: true,
schemeOnly: false,
currComment: {},
- taggData: {}
+ taggData: {},
};
export default RepoReviewButtonBar;
diff --git a/app/packs/src/repoHome/RepoSampleDetails.js b/app/packs/src/repoHome/RepoSampleDetails.js
index fe0754c6b..b820b2507 100644
--- a/app/packs/src/repoHome/RepoSampleDetails.js
+++ b/app/packs/src/repoHome/RepoSampleDetails.js
@@ -1,14 +1,13 @@
import React, { Component } from 'react';
import { Panel } from 'react-bootstrap';
import PropTypes from 'prop-types';
-import {
- ClosePanel,
- MoleculeInfo,
-} from 'src/repoHome/RepoCommon';
+import { ClosePanel, MoleculeInfo } from 'src/repoHome/RepoCommon';
+import UserStore from 'src/stores/alt/stores/UserStore';
import LoadingActions from 'src/stores/alt/actions/LoadingActions';
import ReviewActions from 'src/stores/alt/repo/actions/ReviewActions';
import RepoReviewButtonBar from 'src/repoHome/RepoReviewButtonBar';
import RepoSample from 'src/repoHome/RepoSample';
+import RepoConst from 'src/components/chemrepo/common/RepoConst';
export default class RepoSampleDetails extends Component {
constructor(props) {
@@ -44,12 +43,17 @@ export default class RepoSampleDetails extends Component {
const {
element,
isPublished,
- canComment,
+ canComment: propsCanComment,
review_info,
showComment,
review,
canClose,
} = this.props;
+ const { currentUser } = UserStore.getState();
+ const canComment =
+ currentUser?.type === RepoConst.U_TYPE.ANONYMOUS
+ ? false
+ : propsCanComment;
let { buttons } = this.props;
@@ -93,6 +97,12 @@ export default class RepoSampleDetails extends Component {
doi_reg_at: s.doi_reg_at
};
}
+
+ if (typeof s === 'undefined' || !s) {
+ console.log('Sample is undefined');
+ return null;
+ }
+
const el = {
id: s.id || s.sample_id,
decoupled: s.decoupled,
@@ -120,6 +130,8 @@ export default class RepoSampleDetails extends Component {
boiling_point: s.boiling_point || '',
melting_point: s.melting_point || '',
labels: (isPublished ? s.labels : labels) || [],
+ molecular_mass: s.molecular_mass || '',
+ sum_formula: s.sum_formula || '',
};
return (
@@ -142,17 +154,18 @@ export default class RepoSampleDetails extends Component {
{
canComment ?
: ''
+ /> : null
}
- {canClose ? : ''}
-
+
+ {canClose ? : null}
+
{details}
diff --git a/app/packs/src/stores/alt/actions/UIActions.js b/app/packs/src/stores/alt/actions/UIActions.js
index 8acdf2b46..91bc65178 100644
--- a/app/packs/src/stores/alt/actions/UIActions.js
+++ b/app/packs/src/stores/alt/actions/UIActions.js
@@ -131,6 +131,10 @@ class UIActions {
return filterCreatedAt;
}
+ setUserLabel(label) {
+ return label;
+ }
+
setFromDate(date) {
return date;
}
diff --git a/app/packs/src/stores/alt/repo/actions/PublicActions.js b/app/packs/src/stores/alt/repo/actions/PublicActions.js
index e5e05c16d..bdce23fd3 100644
--- a/app/packs/src/stores/alt/repo/actions/PublicActions.js
+++ b/app/packs/src/stores/alt/repo/actions/PublicActions.js
@@ -298,8 +298,8 @@ class PublicActions {
}
}
- getElements(type='All', state='pending', searchType='All', searchValue='', page=1, perPage=10) {
- return (dispatch) => { RepositoryFetcher.fetchReviewElements(type, state, searchType, searchValue, page, perPage)
+ getElements(type='All', state='pending', label, searchType='All', searchValue='', page=1, perPage=10) {
+ return (dispatch) => { RepositoryFetcher.fetchReviewElements(type, state, label, searchType, searchValue, page, perPage)
.then((result) => {
dispatch(result)
}).catch((errorMessage) => {
@@ -351,6 +351,22 @@ class PublicActions {
setSearchParams(params) {
return params;
}
+
+ loadSpectraForNMRDisplayer(spcInfos) {
+ const idxs = spcInfos && spcInfos.map(si => si.idx);
+ if (idxs.length === 0) {
+ return null;
+ }
+
+ return (dispatch) => {
+ PublicFetcher.fetchFiles(idxs)
+ .then((fetchedFiles) => {
+ dispatch({ fetchedFiles, spcInfos });
+ }).catch((errorMessage) => {
+ console.log(errorMessage); // eslint-disable-line
+ });
+ };
+ }
}
export default alt.createActions(PublicActions);
diff --git a/app/packs/src/stores/alt/repo/actions/RepositoryActions.js b/app/packs/src/stores/alt/repo/actions/RepositoryActions.js
index ccdef7edc..52e30875e 100644
--- a/app/packs/src/stores/alt/repo/actions/RepositoryActions.js
+++ b/app/packs/src/stores/alt/repo/actions/RepositoryActions.js
@@ -1,13 +1,16 @@
/* eslint-disable class-methods-use-this */
import alt from 'src/stores/alt/alt';
import RepositoryFetcher from 'src/repo/fetchers/RepositoryFetcher';
+import LoadingActions from 'src/stores/alt/actions/LoadingActions';
class RepositoryActions {
publishSample(params, closeView = false) {
return (dispatch) => { RepositoryFetcher.publishSample(params)
.then((result) => {
if (result != null) {
- dispatch({ element: result, closeView })
+ dispatch(result)
+ } else {
+ LoadingActions.stop();
}
}).catch((errorMessage) => {
console.log(errorMessage);
@@ -20,6 +23,7 @@ class RepositoryActions {
dispatch({ element: result })
}).catch((errorMessage) => {
console.log(errorMessage);
+ dispatch({ element: element })
});};
}
@@ -40,6 +44,8 @@ class RepositoryActions {
.then((result) => {
if (result != null) {
dispatch({ element: result, closeView })
+ } else {
+ LoadingActions.stop();
}
}).catch((errorMessage) => {
console.log(errorMessage);
diff --git a/app/packs/src/stores/alt/repo/actions/ReviewActions.js b/app/packs/src/stores/alt/repo/actions/ReviewActions.js
index 55c25a619..29cf40b96 100644
--- a/app/packs/src/stores/alt/repo/actions/ReviewActions.js
+++ b/app/packs/src/stores/alt/repo/actions/ReviewActions.js
@@ -14,7 +14,7 @@ class ReviewActions {
}
fetchSample(id) {
- return (dispatch) => { RepositoryFetcher.fetchSample(id, false)
+ return (dispatch) => { RepositoryFetcher.fetchSample(id)
.then((result) => {
dispatch(result)
}).catch((errorMessage) => {
@@ -25,7 +25,7 @@ class ReviewActions {
}
displayReviewReaction(id) {
- return (dispatch) => { RepositoryFetcher.fetchReaction(id, false)
+ return (dispatch) => { RepositoryFetcher.fetchReaction(id)
.then((result) => {
dispatch({id, element: result})
}).catch((errorMessage) => {
@@ -35,7 +35,7 @@ class ReviewActions {
}
displayReviewSample(id) {
- return (dispatch) => { RepositoryFetcher.fetchSample(id, false)
+ return (dispatch) => { RepositoryFetcher.fetchSample(id)
.then((result) => {
dispatch({id, element: result})
}).catch((errorMessage) => {
@@ -54,8 +54,8 @@ class ReviewActions {
}
- getElements(type='All', state='pending', searchType='All', searchValue='', page=1, perPage=10) {
- return (dispatch) => { RepositoryFetcher.fetchReviewElements(type, state, searchType, searchValue, page, perPage)
+ getElements(type='All', state='pending', label=null, searchType='All', searchValue='', page=1, perPage=10) {
+ return (dispatch) => { RepositoryFetcher.fetchReviewElements(type, state, label, searchType, searchValue, page, perPage)
.then((result) => {
dispatch(result)
}).catch((errorMessage) => {
@@ -64,7 +64,6 @@ class ReviewActions {
}
}
-
updateComment(id, type, comments) {
return (dispatch) => {
RepositoryFetcher.updateComment(id, type, comments)
@@ -75,6 +74,26 @@ class ReviewActions {
});
};
}
+
+ saveReviewLabel(element, ids) {
+ return dispatch => {
+ RepositoryFetcher.saveReviewLabel({
+ elementId: element.id,
+ elementType: element.elementType,
+ user_labels: ids,
+ })
+ .then(() => {
+ dispatch(element);
+ })
+ .catch(errorMessage => {
+ console.log(errorMessage);
+ });
+ };
+ }
+
+ setUserLabel(label) {
+ return label;
+ }
}
export default alt.createActions(ReviewActions);
diff --git a/app/packs/src/stores/alt/repo/stores/EmbargoStore.js b/app/packs/src/stores/alt/repo/stores/EmbargoStore.js
index 67cf041b8..427563477 100644
--- a/app/packs/src/stores/alt/repo/stores/EmbargoStore.js
+++ b/app/packs/src/stores/alt/repo/stores/EmbargoStore.js
@@ -68,7 +68,7 @@ class EmbargoStore {
EmbargoActions.getEmbargoBundle();
// refresh element list
PublicActions.getElements(
- this.selectType, this.selectState, this.searchType,
+ this.selectType, this.selectState, null, this.searchType,
this.searchValue, this.page, this.perPage
);
}
diff --git a/app/packs/src/stores/alt/repo/stores/PublicStore.js b/app/packs/src/stores/alt/repo/stores/PublicStore.js
index 0265e4a41..e39d6d88d 100644
--- a/app/packs/src/stores/alt/repo/stores/PublicStore.js
+++ b/app/packs/src/stores/alt/repo/stores/PublicStore.js
@@ -29,6 +29,7 @@ class PublicStore {
this.showReviewModal = false;
this.showCommendModal = false;
this.reviewData = {};
+ this.u = {};
this.bindListeners({
handleInitialize: PublicActions.initialize,
diff --git a/app/packs/src/stores/alt/repo/stores/ReviewStore.js b/app/packs/src/stores/alt/repo/stores/ReviewStore.js
index 6cae15339..29fcc2f60 100644
--- a/app/packs/src/stores/alt/repo/stores/ReviewStore.js
+++ b/app/packs/src/stores/alt/repo/stores/ReviewStore.js
@@ -1,8 +1,10 @@
import Aviator from 'aviator';
import alt from 'src/stores/alt/alt';
+import UserStore from 'src/stores/alt/stores/UserStore';
import PublicActions from 'src/stores/alt/repo/actions/PublicActions';
import ReviewActions from 'src/stores/alt/repo/actions/ReviewActions';
import EmbargoActions from 'src/stores/alt/repo/actions/EmbargoActions';
+import RepoConst from 'src/components/chemrepo/common/RepoConst';
class ReviewStore {
constructor() {
@@ -15,6 +17,7 @@ class ReviewStore {
//this.bundles = [];
this.selectType;
this.selectState;
+ this.selectLabel;
this.searchType;
this.searchValue;
this.currentElement = null;
@@ -34,6 +37,8 @@ class ReviewStore {
handleClose: PublicActions.close,
handleRefreshEmbargoBundles: EmbargoActions.getEmbargoBundle,
handleEmbargoAssign: EmbargoActions.assignEmbargo,
+ handleSetUserLabel: ReviewActions.setUserLabel,
+ handleSaveReviewLabel: ReviewActions.saveReviewLabel,
});
}
@@ -52,7 +57,7 @@ class ReviewStore {
EmbargoActions.getEmbargoBundle();
// refresh element list
ReviewActions.getElements(
- this.selectType, this.selectState, this.searchType,
+ this.selectType, this.selectState, this.selectLabel, this.searchType,
this.searchValue, this.page, this.perPage
);
}
@@ -109,53 +114,104 @@ class ReviewStore {
this.setState({ review: result.review, showReviewModal: false, showCommentModal: false });
}
+ handleSaveReviewLabel(element) {
+ if (element.elementType === 'Reaction') {
+ ReviewActions.displayReviewReaction(element.id);
+ } else if (element.elementType === 'Sample') {
+ ReviewActions.displayReviewSample(element.id);
+ }
+ ReviewActions.getElements(
+ this.selectType || 'All',
+ this.selectState || 'pending',
+ this.selectLabel,
+ this.searchType || 'All',
+ this.searchValue || '',
+ this.page,
+ this.perPage
+ );
+ }
+
handelReviewPublish(results) {
// const { history, checklist, reviewComments } = results.review;
- this.setState({ review: results.review, showReviewModal: false, showCommentModal: false, review_info: results.review_info });
- ReviewActions.getElements(this.selectType || 'All', this.selectState || 'pending', this.searchType || 'All', this.searchValue || '', this.page, this.perPage);
+ this.setState({
+ review: results.review,
+ showReviewModal: false,
+ showCommentModal: false,
+ review_info: results.review_info,
+ });
+ ReviewActions.getElements(
+ this.selectType || 'All',
+ this.selectState || 'pending',
+ this.selectLabel,
+ this.searchType || 'All',
+ this.searchValue || '',
+ this.page,
+ this.perPage
+ );
}
handleDisplayReviewReaction(result) {
- const publication = (result.element && result.element.reaction && result.element.reaction.publication) || {};
- if (result.element?.review_info == null || result.element?.review_info?.review_level === 0) {
+ const publication = result.element?.reaction?.publication || {};
+ if (
+ result.element?.review_info == null ||
+ result.element?.review_info?.review_level === 0
+ ) {
//Aviator.navigate('/home');
} else {
- this.setState({
- guestPage: 'review',
- elementType: 'reaction',
- queryId: result?.id || 0,
- reaction: result?.element?.reaction || {},
- currentElement: result?.element || {},
- showReviewModal: false,
- showCommentModal: false,
- review: publication?.review || {},
- review_info: result?.element?.review_info || {},
- });
- Aviator.navigate(`/review/review_reaction/${result.id}`, { silent: true });
+ const currentUser =
+ (UserStore.getState() && UserStore.getState().currentUser) || {};
+ if (currentUser?.type === RepoConst.U_TYPE.ANONYMOUS) {
+ EmbargoActions.displayReviewEmbargo('reaction', result?.id);
+ } else {
+ this.setState({
+ guestPage: 'review',
+ elementType: 'reaction',
+ queryId: result?.id || 0,
+ reaction: result?.element?.reaction || {},
+ currentElement: result?.element || {},
+ showReviewModal: false,
+ showCommentModal: false,
+ review: publication?.review || {},
+ review_info: result?.element?.review_info || {},
+ });
+ Aviator.navigate(`/review/review_reaction/${result.id}`, {
+ silent: true,
+ });
+ }
}
}
handleDisplayReviewSample(result) {
const publication = result?.element?.publication || {};
-
- if (result.element?.review_info == null || result.element?.review_info?.review_level === 0) {
+ if (
+ result.element?.review_info == null ||
+ result.element?.review_info?.review_level === 0
+ ) {
//Aviator.navigate('/home');
} else {
- this.setState({
- guestPage: 'review',
- elementType: 'sample',
- queryId: result.id || 0,
- currentElement: result?.element || {},
- showReviewModal: false,
- showCommentModal: false,
- review: publication?.review || {},
- review_info: result?.element?.review_info || {},
- });
- Aviator.navigate(`/review/review_sample/${result.id}`, { silent: true });
+ const currentUser =
+ (UserStore.getState() && UserStore.getState().currentUser) || {};
+ if (currentUser?.type === RepoConst.U_TYPE.ANONYMOUS) {
+ EmbargoActions.displayReviewEmbargo('sample', result?.id);
+ } else {
+ this.setState({
+ guestPage: 'review',
+ elementType: 'sample',
+ queryId: result.id || 0,
+ currentElement: result?.element || {},
+ showReviewModal: false,
+ showCommentModal: false,
+ review: publication?.review || {},
+ review_info: result?.element?.review_info || {},
+ });
+ Aviator.navigate(`/review/review_sample/${result.id}`, { silent: true });
+ }
}
}
-
+ handleSetUserLabel(label) {
+ this.setState({ selectLabel: label });
+ }
};
diff --git a/app/packs/src/stores/alt/stores/ElementStore.js b/app/packs/src/stores/alt/stores/ElementStore.js
index e2d504498..fa19c9a98 100644
--- a/app/packs/src/stores/alt/stores/ElementStore.js
+++ b/app/packs/src/stores/alt/stores/ElementStore.js
@@ -607,7 +607,7 @@ class ElementStore {
const { profile } = UserStore.getState();
if (profile && profile.data && profile.data.layout) {
const { layout } = profile.data;
-
+
if (layout.sample && layout.sample > 0) { this.handleRefreshElements('sample'); }
if (layout.reaction && layout.reaction > 0) { this.handleRefreshElements('reaction'); }
if (layout.wellplate && layout.wellplate > 0) { this.handleRefreshElements('wellplate'); }
@@ -1112,8 +1112,8 @@ class ElementStore {
this.handleRefreshElementsForSearchById(type, uiState, currentSearchByID);
} else {
const per_page = uiState.number_of_results;
- const { fromDate, toDate, productOnly } = uiState;
- const params = { page, per_page, fromDate, toDate, productOnly, name: type };
+ const { fromDate, toDate, userLabel, productOnly } = uiState;
+ const params = { page, per_page, fromDate, userLabel, toDate, productOnly, name: type };
const fnName = type.split('_').map(x => x[0].toUpperCase() + x.slice(1)).join("") + 's';
let fn = `fetch${fnName}ByCollectionId`;
const allowedActions = [
@@ -1133,8 +1133,8 @@ class ElementStore {
}
MessagesFetcher.fetchSpectraMessages(0).then((result) => {
- result.messages.sort((a, b) => (a.id - b.id));
- const messages = result.messages;
+ result?.messages.sort((a, b) => (a.id - b.id));
+ const messages = result?.messages;
if (messages && messages.length > 0) {
const lastMsg = messages[0]
this.setState({ spectraMsg: lastMsg })
@@ -1144,18 +1144,19 @@ class ElementStore {
handleRefreshElementsForSearchById(type, uiState, currentSearchByID) {
currentSearchByID.page_size = uiState.number_of_results;
- const { filterCreatedAt, fromDate, toDate, productOnly } = uiState;
+ const { filterCreatedAt, fromDate, toDate, userLabel, productOnly } = uiState;
const { moleculeSort } = this.state;
const { page } = uiState[type];
let filterParams = {};
const elnElements = ['sample', 'reaction', 'screen', 'wellplate', 'research_plan'];
let modelName = !elnElements.includes(type) ? 'element' : type;
- if (fromDate || toDate || productOnly) {
+ if (fromDate || toDate || userLabel || productOnly) {
filterParams = {
filter_created_at: filterCreatedAt,
from_date: fromDate,
to_date: toDate,
+ user_label: userLabel,
product_only: productOnly,
}
}
diff --git a/app/packs/src/stores/alt/stores/SpectraStore.js b/app/packs/src/stores/alt/stores/SpectraStore.js
index 5399d4a6c..9f8e81dbd 100644
--- a/app/packs/src/stores/alt/stores/SpectraStore.js
+++ b/app/packs/src/stores/alt/stores/SpectraStore.js
@@ -38,7 +38,10 @@ class SpectraStore {
handleAddOthers: SpectraActions.AddOthers,
handleRegenerateEdited: SpectraActions.RegenerateEdited,
handleToggleModalNMRDisplayer: SpectraActions.ToggleModalNMRDisplayer,
- handleLoadSpectraForNMRDisplayer: SpectraActions.LoadSpectraForNMRDisplayer,
+ handleLoadSpectraForNMRDisplayer: [
+ SpectraActions.LoadSpectraForNMRDisplayer,
+ PublicActions.loadSpectraForNMRDisplayer,
+ ],
});
}
diff --git a/app/packs/src/stores/alt/stores/UIStore.js b/app/packs/src/stores/alt/stores/UIStore.js
index d23f9b949..a6794de65 100644
--- a/app/packs/src/stores/alt/stores/UIStore.js
+++ b/app/packs/src/stores/alt/stores/UIStore.js
@@ -65,6 +65,7 @@ class UIStore {
filterCreatedAt: true,
fromDate: null,
toDate: null,
+ userLabel: null,
productOnly: false,
number_of_results: 15,
currentCollection: null,
@@ -114,6 +115,7 @@ class UIStore {
handleShowModalChange: UIActions.updateModalProps,
handleHideModal: UIActions.hideModal,
handleSetFilterCreatedAt: UIActions.setFilterCreatedAt,
+ handleSetUserLabel: UIActions.setUserLabel,
handleSetFromDate: UIActions.setFromDate,
handleSetToDate: UIActions.setToDate,
handleSetProductOnly: UIActions.setProductOnly,
@@ -301,7 +303,7 @@ class UIStore {
handleSelectCollection(collection, hasChanged = false) {
const state = this.state;
const isSync = collection && collection.is_sync_to_me ? true : false;
- const { filterCreatedAt, fromDate, toDate, productOnly } = state;
+ const { filterCreatedAt, fromDate, toDate, userLabel, productOnly } = state;
if (!hasChanged) {
hasChanged = !state.currentCollection;
@@ -321,7 +323,7 @@ class UIStore {
this.state.isSync = isSync;
this.state.currentCollection = collection;
const per_page = state.number_of_results;
- const params = { per_page, filterCreatedAt, fromDate, toDate, productOnly };
+ const params = { per_page, filterCreatedAt, fromDate, toDate, userLabel, productOnly };
const { profile } = UserStore.getState();
if (profile && profile.data && profile.data.layout) {
@@ -387,7 +389,7 @@ class UIStore {
const state = this.state;
const isSync = state.isSync;
const searchResult = { ...state.currentSearchByID };
- const { filterCreatedAt, fromDate, toDate, productOnly } = state;
+ const { filterCreatedAt, fromDate, toDate, userLabel, productOnly } = state;
const { moleculeSort } = ElementStore.getState();
const per_page = state.number_of_results;
@@ -398,11 +400,12 @@ class UIStore {
const elnElements = ['sample', 'reaction', 'screen', 'wellplate', 'research_plan'];
let modelName = !elnElements.includes(key.slice(0, -1)) ? 'element' : key.slice(0, -1);
- if (fromDate || toDate || productOnly) {
+ if (fromDate || toDate || productOnly || userLabel) {
filterParams = {
filter_created_at: filterCreatedAt,
from_date: fromDate,
to_date: toDate,
+ user_label: userLabel,
product_only: productOnly,
}
}
@@ -491,6 +494,11 @@ class UIStore {
}
}
+ handleSetUserLabel(label) {
+ this.state.userLabel = label;
+ this.handleSelectCollection(this.state.currentCollection, true);
+ }
+
handleSetFromDate(fromDate) {
this.state.fromDate = fromDate;
this.handleSelectCollection(this.state.currentCollection, true);
diff --git a/app/packs/src/utilities/ElementUtils.js b/app/packs/src/utilities/ElementUtils.js
index 127bbd8aa..d99d62a61 100644
--- a/app/packs/src/utilities/ElementUtils.js
+++ b/app/packs/src/utilities/ElementUtils.js
@@ -182,7 +182,7 @@ const isEmwInMargin = (diff) => {
};
const emwInStr = (emw, msStr) => {
- const peaks = msStr.split(/,|:|;|=/).map((s) => {
+ const peaks = msStr.split(/,|:|;|\+|=/).map((s) => {
const t = s.replace('found', '')
.replace(/[\(\[\{](.*?)[\)\]\}]/g, '')
.replace(/\s/g, '');
diff --git a/app/packs/src/utilities/routesUtils.js b/app/packs/src/utilities/routesUtils.js
index 098cc9568..3690fa80f 100644
--- a/app/packs/src/utilities/routesUtils.js
+++ b/app/packs/src/utilities/routesUtils.js
@@ -11,6 +11,7 @@ import { elementNames } from 'src/apps/generic/Utils';
const collectionShow = (e) => {
UIActions.showElements.defer();
UserActions.fetchCurrentUser();
+ UserActions.fetchUserLabels();
const { profile } = UserStore.getState();
if (!profile) {
UserActions.fetchProfile();
@@ -60,6 +61,7 @@ const collectionShowCollectionManagement = () => {
const scollectionShow = (e) => {
UIActions.showElements();
UserActions.fetchCurrentUser();
+ UserActions.fetchUserLabels();
const { profile } = UserStore.getState();
if (!profile) {
UserActions.fetchProfile();
@@ -123,7 +125,7 @@ const sampleShowOrNew = (e) => {
// UIActions.selectTab(1);
};
-const cellLineShowOrNew = (e) => {
+const cellLineShowOrNew = (e) => {
if(e.params.new_cellLine||(e.params.new_cellLine===undefined&&e.params.cell_lineID==="new")){
ElementActions.generateEmptyCellLine(e.params.collectionID,e.params.cell_line_template);
}else{
@@ -258,7 +260,7 @@ const genericElShowOrNew = (e, type) => {
} else if (genericElID === 'copy') {
//
} else {
-
+
ElementActions.fetchGenericElById(genericElID, itype);
}
};
diff --git a/app/uploaders/attachment_uploader.rb b/app/uploaders/attachment_uploader.rb
index b3c0b8098..e9737abb0 100644
--- a/app/uploaders/attachment_uploader.rb
+++ b/app/uploaders/attachment_uploader.rb
@@ -43,7 +43,7 @@ def generate_location(io, context = {}) # rubocop:disable Metrics/AbcSize,Metric
Attacher.derivatives do |original|
file_extension = ".#{record.attachment.mime_type.split('/').last}" unless record.attachment.mime_type.nil?
- #
+ #
file_extension = '.svg' if file_extension == '.svg+xml'
file_extension = '.jpg' if file_extension == '.jpeg'
@@ -81,7 +81,21 @@ def self.create_derivatives(file_extension, file_path, original, attachment_id,
file_path.to_s, original, attachment_id, result, record
)
end
-
+ result
+ rescue StandardError => e
+ Rails.logger.error <<~TXT
+ --------- #{self.class.name} create_derivatives ------------
+ file_extension: #{file_extension}
+ file_path: #{file_path&.to_s}
+ original: #{original&.path}
+ attachment_id: #{attachment_id}
+ filename: #{record&.filename}
+ attachable_id: #{record&.attachable_id}
+
+ Error Message: #{e.message}
+ Error: #{e.backtrace.join("\n")}
+ --------------------------------------------------------------------
+ TXT
result
end
diff --git a/app/usecases/attachments/annotation/annotation_loader.rb b/app/usecases/attachments/annotation/annotation_loader.rb
index 7444b8af0..5f24b6ba9 100644
--- a/app/usecases/attachments/annotation/annotation_loader.rb
+++ b/app/usecases/attachments/annotation/annotation_loader.rb
@@ -21,7 +21,8 @@ def get_annotation_of_attachment(attachment_id)
--------- #{self.class.name} get_annotation_of_attachment ------------
attachment_id: #{attachment_id}
- Error Message: #{e.backtrace.join("\n")}
+ Error Message: #{e.message}
+ Error: #{e.backtrace.join("\n")}
--------------------------------------------------------------------
TXT
end
diff --git a/app/usecases/attachments/annotation/annotation_updater.rb b/app/usecases/attachments/annotation/annotation_updater.rb
index a18beef06..145104d3d 100644
--- a/app/usecases/attachments/annotation/annotation_updater.rb
+++ b/app/usecases/attachments/annotation/annotation_updater.rb
@@ -60,7 +60,8 @@ def update_thumbnail(attachment, svg_string)
AttachmentID: #{attachment&.id}
svg_string: #{svg_string}
- Error Message: #{e.backtrace.join("\n")}
+ Error Message: #{e.message}
+ Error: #{e.backtrace.join("\n")}
--------------------------------------------------------------------
TXT
end
diff --git a/app/usecases/attachments/copy.rb b/app/usecases/attachments/copy.rb
index 45b56d53a..255e873a1 100644
--- a/app/usecases/attachments/copy.rb
+++ b/app/usecases/attachments/copy.rb
@@ -22,6 +22,7 @@ def self.execute!(attachments, element, current_user_id)
attachable_id: element.id,
attachable_type: element.class.name,
aasm_state: original_attach.aasm_state,
+ con_state: original_attach.con_state,
created_by: current_user_id,
created_for: current_user_id,
filename: original_attach.filename,
@@ -56,7 +57,8 @@ def self.update_annotation(original_attach_id, copy_attach_id)
original_attach_id: #{original_attach_id}
copy_attach_id: #{copy_attach_id}
- Error Message: #{e.backtrace.join("\n")}
+ Error Message: #{e.message}
+ Error: #{e.backtrace.join("\n")}
--------------------------------------------------------------------
TXT
end
diff --git a/app/usecases/attachments/derivative_builder_factory.rb b/app/usecases/attachments/derivative_builder_factory.rb
index c74086fe4..5d0bd2856 100644
--- a/app/usecases/attachments/derivative_builder_factory.rb
+++ b/app/usecases/attachments/derivative_builder_factory.rb
@@ -21,6 +21,13 @@ def create_derivative_builders(data_type_in)
builders.append(creator.constantize.new) if @supported_formats_map[creator].include? data_type
end
+ builders
+ rescue StandardError => e
+ Rails.logger.error <<~TXT
+ --------- #{self.class.name} create_derivative_builders ------------
+ data_type_in: #{data_type_in}
+ --------------------------------------------------------------------
+ TXT
builders
end
diff --git a/config/initializers/ui_extensions.rb b/config/initializers/ui_extensions.rb
new file mode 100644
index 000000000..9ccffc21e
--- /dev/null
+++ b/config/initializers/ui_extensions.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+begin
+ ui_extensions_config = Rails.application.config_for(:ui_extensions)
+ Rails.application.configure do
+ config.u = ui_extensions_config
+ end
+rescue StandardError => e
+ Rails.logger.error "Could not load ui configuration. Error: #{e.message}"
+ Rails.application.configure do
+ config.u = {}.freeze
+ end
+end
diff --git a/config/initializers/variable_measured.rb b/config/initializers/variable_measured.rb
new file mode 100644
index 000000000..6e3917ef3
--- /dev/null
+++ b/config/initializers/variable_measured.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+begin
+ variable_measured_config = Rails.application.config_for(:variable_measured)
+ Rails.application.configure do
+ config.m = variable_measured_config
+ end
+rescue StandardError => e
+ Rails.logger.error "Could not load variable measured. Error: #{e.message}"
+ Rails.application.configure do
+ config.m = {}.freeze
+ end
+end
diff --git a/config/routes.rb b/config/routes.rb
index 655dface2..648b42c59 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -88,7 +88,6 @@
get 'welcome', to: 'pages#home'
get 'home', to: 'pages#home'
# get 'home', to: 'pages#home'
- get 'jsmol', to: 'pages#jsmol'
get 'directive', to: 'pages#directive'
get 'welcome', to: 'pages#home'
get 'about', to: 'pages#about'
diff --git a/config/ui_extensions.yml.example b/config/ui_extensions.yml.example
new file mode 100644
index 000000000..281a97fbb
--- /dev/null
+++ b/config/ui_extensions.yml.example
@@ -0,0 +1,18 @@
+development:
+ collection_icons:
+ - label: COLLECTION_LABEL_1
+ icons:
+ - filename: image1.png
+ title: image1_title
+ info: additional information
+ - filename: image2.png
+ title: image2_title
+ info: additional information
+ - label: COLLECTION_LABEL_2
+ icons:
+ - filename: image3.png
+ title: image3_title
+ info: additional information
+ - filename: image4.png
+ title: image4_title
+ info: additional information
diff --git a/config/variable_measured.yml.example b/config/variable_measured.yml.example
new file mode 100644
index 000000000..3bc4c6edb
--- /dev/null
+++ b/config/variable_measured.yml.example
@@ -0,0 +1,27 @@
+development:
+ dataset:
+ - ols_term: CHMO:0000593
+ desc: 1H nuclear magnetic resonance spectroscopy (1H NMR)
+ layers:
+ - identifier: set
+ fields:
+ - identifier: PULPROG
+ - identifier: temperature
+ - identifier: ph
+ - identifier: ns
+ - identifier: nucone
+ - identifier: general
+ fields:
+ - identifier: title
+ - identifier: creator
+ - ols_term: CHMO:0000595
+ desc: 13C nuclear magnetic resonance spectroscopy (13C NMR)
+ layers:
+ - identifier: layerAAA
+ fields:
+ - identifier: bbbb
+ - identifier: cccc
+ - identifier: layerBBB
+ fields:
+ - identifier: bbbb
+ - identifier: cccc
diff --git a/config/webpack/custom.js b/config/webpack/custom.js
index 72ab8c7ec..f3a05f728 100644
--- a/config/webpack/custom.js
+++ b/config/webpack/custom.js
@@ -16,7 +16,6 @@ module.exports = {
plugins: [
new webpack.DefinePlugin({
'process.version': JSON.stringify(process.version),
- 'process.env.MATOMO_URL': JSON.stringify(process.env.MATOMO_URL),
'process.env.SENTRY_FRONTEND_DSN': JSON.stringify(
process.env.SENTRY_FRONTEND_DSN
),
diff --git a/db/migrate/20200827144816_matrice_user_label.rb b/db/migrate/20200827144816_matrice_user_label.rb
deleted file mode 100644
index e49cf50c9..000000000
--- a/db/migrate/20200827144816_matrice_user_label.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-class MatriceUserLabel < ActiveRecord::Migration[4.2]
- def change
- Matrice.create(
- name: 'userLabel',
- enabled: false,
- label: 'userLabel',
- include_ids: [],
- exclude_ids: []
- )
- end
-end
diff --git a/db/migrate/20230630140647_fill_new_plain_text_description_fields.rb b/db/migrate/20230630140647_fill_new_plain_text_description_fields.rb
index 36e7d81d6..02190e138 100644
--- a/db/migrate/20230630140647_fill_new_plain_text_description_fields.rb
+++ b/db/migrate/20230630140647_fill_new_plain_text_description_fields.rb
@@ -12,7 +12,7 @@ def up
# force gc of node processes
ObjectSpace.garbage_collect
rescue Exception => e
- byebug
+ # byebug
end
end
Screen.where.not(description: nil).find_each do |screen|
diff --git a/db/migrate/20230814121455_fill_new_plain_text_content_field_at_containers.rb b/db/migrate/20230814121455_fill_new_plain_text_content_field_at_containers.rb
index a21dbfdb0..4a63a602b 100644
--- a/db/migrate/20230814121455_fill_new_plain_text_content_field_at_containers.rb
+++ b/db/migrate/20230814121455_fill_new_plain_text_content_field_at_containers.rb
@@ -24,12 +24,12 @@ def up
container.update_columns(plain_text_content: content)
rescue Exception => e
- byebug
+ ## byebug
end
end
rescue Exception => e
- byebug
+ ## byebug
end
def down
diff --git a/db/migrate/20240530000001_remove_user_labels_from_matrix.rb b/db/migrate/20240530000001_remove_user_labels_from_matrix.rb
new file mode 100644
index 000000000..0c5ce1d25
--- /dev/null
+++ b/db/migrate/20240530000001_remove_user_labels_from_matrix.rb
@@ -0,0 +1,7 @@
+class RemoveUserLabelsFromMatrix < ActiveRecord::Migration[6.1]
+ def change
+ Matrice.find_by(name: 'userLabel')&.destroy!
+ rescue StandardError => e
+ Rails.logger.error "Error changing channel msg: #{e.message}"
+ end
+end
diff --git a/db/migrate/20240531000003_user_labels_publication.rb b/db/migrate/20240531000003_user_labels_publication.rb
new file mode 100644
index 000000000..bcfea7508
--- /dev/null
+++ b/db/migrate/20240531000003_user_labels_publication.rb
@@ -0,0 +1,9 @@
+class UserLabelsPublication < ActiveRecord::Migration[6.1]
+ def change
+ Publication.where(element_type: 'Sample').each do |pub|
+ pub.publish_user_labels
+ end
+ rescue StandardError => e
+ Rails.logger.error "Error changing channel msg: #{e.message}"
+ end
+end
diff --git a/db/migrate/20240607000000_add_submission_notification.rb b/db/migrate/20240607000000_add_submission_notification.rb
new file mode 100644
index 000000000..b346cccb0
--- /dev/null
+++ b/db/migrate/20240607000000_add_submission_notification.rb
@@ -0,0 +1,14 @@
+class AddSubmissionNotification < ActiveRecord::Migration[6.1]
+ def change
+ channel = Channel.find_by(subject: Channel::SUBMITTING)
+ return unless channel.nil?
+ attributes = {
+ subject: Channel::SUBMITTING,
+ channel_type: 8,
+ msg_template: '{"data": "The submission has been submitted!",
+ "action":"Submission"
+ }'
+ }
+ Channel.create(attributes)
+ end
+end
diff --git a/lib/chemotion/open_babel_service.rb b/lib/chemotion/open_babel_service.rb
index 5096c804e..1d9d456f0 100644
--- a/lib/chemotion/open_babel_service.rb
+++ b/lib/chemotion/open_babel_service.rb
@@ -412,6 +412,8 @@ def self.molfile_clear_hydrogens molfile, options={}
end
def self.svg_from_molfile molfile, options={}
+ return nil if molfile.blank?
+
c = OpenBabel::OBConversion.new
c.set_in_format 'mol'
c.set_out_format 'svg'
@@ -428,6 +430,9 @@ def self.svg_from_molfile molfile, options={}
#m.do_transformations c.get_options(OpenBabel::OBConversion::GENOPTIONS), c
c.write_string(m, false)
+ rescue StandardError => e
+ Rails.logger.error e
+ nil
end
# Return an array of 32
diff --git a/lib/chemotion/orcid_service.rb b/lib/chemotion/orcid_service.rb
index a05d7d16c..68521dfd5 100644
--- a/lib/chemotion/orcid_service.rb
+++ b/lib/chemotion/orcid_service.rb
@@ -10,10 +10,14 @@ def self.record_person(orcid)
record = Nokogiri::XML(record).to_xml
record = Hash.from_xml(record).as_json
+ emails = record.dig('person', 'emails', 'email')
+ email = email['email'] if emails.is_a?(Hash)
+ email = email = emails.first['email'] if emails.is_a?(Array)
+
person = OpenStruct.new(
given_names: record.dig('person', 'name', 'given_names'),
family_name: record.dig('person', 'name', 'family_name'),
- email: record.dig('person', 'emails', 'email', 'email')
+ email: email
)
result = OpenStruct.new(person: person)
result
diff --git a/lib/import/import_collections.rb b/lib/import/import_collections.rb
index e811467b3..b07252fe4 100644
--- a/lib/import/import_collections.rb
+++ b/lib/import/import_collections.rb
@@ -147,10 +147,8 @@ def import_collections
# collection = Collection.find(@col_id)
collection = Collection.find_or_create_by(user_id: @current_user_id, label: 'Imported Data', is_locked: true, position: 3)
- @uuid = nil
-
@data.fetch('Collection', {}).each do |uuid, fields|
- @uuid = uuid
+ update_instances!(uuid, collection)
end
# @data.fetch('Collection', {}).each do |uuid, fields|
@@ -171,16 +169,13 @@ def import_collections
# add collection to @instances map
# update_instances!(uuid, collection)
# end
- update_instances!(@uuid, collection)
end
def gate_collection
collection = Collection.find(@col_id)
- @uuid = nil
@data.fetch('Collection', {}).each do |uuid, _fields|
- @uuid = uuid
+ update_instances!(uuid, collection)
end
- update_instances!(@uuid, collection)
end
def fetch_bound(value)
@@ -656,7 +651,8 @@ def import_literals
literal = Literal.create!(
fields.slice(
'element_type',
- 'category'
+ 'category',
+ 'litype',
).merge(
user_id: @current_user_id,
element: element,
diff --git a/lib/repo/embargo_handler.rb b/lib/repo/embargo_handler.rb
new file mode 100644
index 000000000..6eed0b102
--- /dev/null
+++ b/lib/repo/embargo_handler.rb
@@ -0,0 +1,144 @@
+module Repo
+ class EmbargoHandler
+ def self.find_or_create_embargo(cid, current_user)
+ if (cid == 0)
+ chemotion_user = User.chemotion_user
+ new_col_label = current_user.initials + '_' + Time.now.strftime('%Y-%m-%d')
+ col_check = Collection.where([' label like ? ', new_col_label + '%'])
+ new_col_label = new_col_label << '_' << (col_check&.length + 1)&.to_s if col_check&.length.positive?
+ new_embargo_col = Collection.create!(user: chemotion_user, label: new_col_label, ancestry: current_user.publication_embargo_collection.id)
+ SyncCollectionsUser.find_or_create_by(user: current_user, shared_by_id: chemotion_user.id, collection_id: new_embargo_col.id,
+ permission_level: 0, sample_detail_level: 10, reaction_detail_level: 10,
+ fake_ancestry: current_user.publication_embargo_collection.sync_collections_users.first.id.to_s)
+ #embargo = Embargo.create!(name: new_embargo_col.label, collection_id: new_embargo_col.id, created_by: current_user.id)
+ d = Doi.create_for_element!(new_embargo_col)
+
+ Publication.create!(
+ state: Publication::STATE_PENDING,
+ element: new_embargo_col,
+ published_by: current_user.id,
+ doi: d,
+ taggable_data: { label: new_embargo_col.label, col_doi: d.full_doi }
+ )
+ new_embargo_col
+ else
+ Collection.find(cid)
+ end
+ rescue StandardError => e
+ Repo::EmbargoHandler.logger.error ["[find_or_create_embargo] cid: #{cid}, current_user: #{current_user&.id}", e.message, *e.backtrace].join($INPUT_RECORD_SEPARATOR)
+ end
+
+ def self.release_embargo(embargo_collection_id, user_id)
+ embargo_collection = Collection.find(embargo_collection_id)
+ return { error: 'Embargo collection not found' } if embargo_collection.nil?
+
+ current_user = User.find(user_id)
+ return { error: 'User not found' } if current_user.nil?
+
+ col_pub = embargo_collection.publication
+ is_submitter = col_pub&.review&.dig('submitters')&.include?(current_user.id) || col_pub&.published_by == current_user.id
+ return { error: "only the owner of embargo #{embargo_collection.label} can perform the release."} if col_pub.nil? || !is_submitter
+
+ col_pub.update(accepted_at: Time.now.utc)
+ col_pub.refresh_embargo_metadata
+ pub_samples = Publication.where(ancestry: nil, element: embargo_collection.samples).order(updated_at: :desc)
+ pub_reactions = Publication.where(ancestry: nil, element: embargo_collection.reactions).order(updated_at: :desc)
+ pub_list = pub_samples + pub_reactions
+
+ check_state = pub_list.select { |pub| pub.state != Publication::STATE_ACCEPTED }
+ return { error: "Embargo #{embargo_collection.label} release failed, because not all elements have been 'accepted'."} if check_state.present?
+
+ scheme_only_list = pub_list.select { |pub| pub.taggable_data['scheme_only'] == true }
+ if pub_list.flatten.length == scheme_only_list.flatten.length
+ col_pub.update(state: 'scheme_only')
+ else
+ col_pub.update(state: 'accepted')
+ end
+
+ pub_list.each do |pub|
+ params = { id: pub.element_id, type: pub.element_type }
+ Repo::ReviewProcess.new(params, current_user.id, 'release').element_submit(pub)
+ end
+ Repo::EmbargoHandler.remove_anonymous(embargo_collection)
+ Repo::EmbargoHandler.handle_embargo_collections(embargo_collection, current_user)
+ case ENV['PUBLISH_MODE']
+ when 'production'
+ if Rails.env.production?
+ ChemotionEmbargoPubchemJob.set(queue: "publishing_embargo_#{embargo_collection.id}").perform_later(embargo_collection.id)
+ end
+ when 'staging'
+ ChemotionEmbargoPubchemJob.perform_now(embargo_collection.id)
+ else 'development'
+ end
+ { message: "Embargo #{embargo_collection.label} has been released" }
+ rescue StandardError => e
+ Repo::EmbargoHandler.logger.error ["[release_embargo] embargo_collection_id: #{embargo_collection_id}, user_id: #{user_id}", e.message, *e.backtrace].join($INPUT_RECORD_SEPARATOR)
+ Message.create_msg_notification(
+ channel_id: Channel.find_by(subject: Channel::SUBMITTING)&.id,
+ message_from: User.find_by(name_abbreviation: 'CHI')&.id,
+ autoDismiss: 5,
+ message_content: { 'data': "release_embargo exception, User: [#{user_id}], embargo_collection_id: [#{embargo_collection_id}], got error, #{e.message}" },
+ )
+ { error: e.message }
+ end
+
+ def self.delete(embargo_collection_id, user_id)
+ embargo_collection = Collection.find(embargo_collection_id)
+ return { error: 'Embargo collection not found' } if embargo_collection.nil?
+
+ current_user = User.find(user_id)
+ return { error: 'User not found' } if current_user.nil?
+
+ element_cnt = embargo_collection.samples.count + embargo_collection.reactions.count
+ if element_cnt.positive?
+ { error: "Delete Embargo #{embargo_collection.label} deletion failed: the collection is not empty. Please refresh your page."}
+ else
+ Repo::EmbargoHandler.remove_anonymous(embargo_collection)
+ Repo::EmbargoHandler.remove_embargo_collection(embargo_collection)
+ { message: "Embargo #{embargo_collection.label} has been deleted" }
+ end
+ rescue StandardError => e
+ Repo::EmbargoHandler.logger.error ["[delete embargo] embargo_collection_id: #{embargo_collection_id}, user_id: #{user_id}", e.message, *e.backtrace].join($INPUT_RECORD_SEPARATOR)
+ { error: e.message }
+ end
+
+ ### private methods
+
+ def self.remove_anonymous(col)
+ anonymous_ids = col.sync_collections_users.joins("INNER JOIN users on sync_collections_users.user_id = users.id")
+ .where("users.type='Anonymous'").pluck(:user_id)
+ anonymous_ids.each do |anonymous_id|
+ anonymous = Anonymous.find(anonymous_id)
+ anonymous.sync_in_collections_users.destroy_all
+ anonymous.collections.each { |c| c.really_destroy! }
+ anonymous.really_destroy!
+ end
+ rescue StandardError => e
+ Repo::EmbargoHandler.logger.error ["[remove_anonymous] col_id: #{col&.id}", e.message, *e.backtrace].join($INPUT_RECORD_SEPARATOR)
+ end
+
+ def self.remove_embargo_collection(col)
+ col&.publication.really_destroy!
+ col.sync_collections_users.destroy_all
+ col.really_destroy!
+ end
+
+ def self.handle_embargo_collections(col, current_user)
+ col.update_columns(ancestry: current_user.published_collection.id)
+ sync_emb_col = col.sync_collections_users.where(user_id: current_user.id)&.first
+ sync_published_col = SyncCollectionsUser.joins("INNER JOIN collections ON collections.id = sync_collections_users.collection_id ")
+ .where("collections.label='Published Elements'")
+ .where("sync_collections_users.user_id = #{current_user.id}").first
+ sync_emb_col.update_columns(fake_ancestry: sync_published_col.id)
+ rescue StandardError => e
+ Repo::EmbargoHandler.logger.error ["[handle_embargo_collections] col_id: #{col&.id}", e.message, *e.backtrace].join($INPUT_RECORD_SEPARATOR)
+ raise e
+ end
+
+ def self.logger
+ @@embargo_logger ||= Logger.new(Rails.root.join('log/embargo.log')) # rubocop:disable Style/ClassVars
+ end
+
+ end
+end
+
diff --git a/lib/repo/fetch_handler.rb b/lib/repo/fetch_handler.rb
new file mode 100644
index 000000000..5c152ccb5
--- /dev/null
+++ b/lib/repo/fetch_handler.rb
@@ -0,0 +1,152 @@
+# frozen_string_literal: true
+
+# A helper for fetching data from the repository
+# It includes the following methods:
+# 1. find_embargo_collection: Find the embargo collection of a publication
+# 2. repo_review_info: Get the review information of a publication
+# 3. repo_review_level: Get the review level of a publication
+# 4. literatures_by_cat: Fetch literatures by category
+# 5. get_reaction_table: Fetch the reaction table
+
+module Repo
+ class FetchHandler
+
+ def self.find_embargo_collection(root_publication)
+ has_embargo_col = root_publication.element&.collections&.select { |c| c['ancestry'].to_i == User.with_deleted.find(root_publication.published_by).publication_embargo_collection.id }
+ has_embargo_col && has_embargo_col.length > 0 ? has_embargo_col.first : OpenStruct.new(label: '')
+ rescue StandardError => e
+ Rails.logger.error(e.message)
+ Rails.logger.error(e.backtrace.join("\n"))
+ raise e
+ end
+
+ def self.repo_review_info(root_publication, user_id)
+ {
+ submitter: root_publication&.published_by == user_id || root_publication&.review&.dig('submitters')&.include?(user_id) || false,
+ reviewer: User.reviewer_ids&.include?(user_id) || false,
+ groupleader: root_publication&.review&.dig('reviewers')&.include?(user_id),
+ leaders: User.where(id: root_publication&.review&.dig('reviewers'))&.map{ |u| { name: u.name, id: u.id, type: u.type } },
+ preapproved: root_publication&.review&.dig('checklist', 'glr', 'status') == true,
+ review_level: Repo::FetchHandler.repo_review_level(root_publication, user_id)
+ }
+ rescue StandardError => e
+ Rails.logger.error(e.message)
+ Rails.logger.error(e.backtrace.join("\n"))
+ raise e
+ end
+
+ def self.repo_review_level(root_publication, user_id)
+ return 3 if User.reviewer_ids&.include? user_id
+ return 0 if root_publication.nil?
+ return 2 if root_publication.published_by === user_id || root_publication&.review&.dig('submitters')&.include?(user_id)
+ sync_cols = root_publication.element.sync_collections_users.where(user_id: user_id)
+ return 1 if (sync_cols&.length > 0)
+ return 0
+ rescue StandardError => e
+ Rails.logger.error(e.message)
+ Rails.logger.error(e.backtrace.join("\n"))
+ raise e
+ end
+
+ def self.literatures_by_cat(id, type, cat='public')
+ literatures = Literature.by_element_attributes_and_cat(id, type.classify, cat)
+ .joins("inner join users on literals.user_id = users.id")
+ .select(
+ <<~SQL
+ literatures.* , literals.element_type, literals.element_id,
+ json_object_agg(literals.id, literals.litype) as litype,
+ json_object_agg(literals.id, users.first_name || chr(32) || users.last_name) as ref_added_by
+ SQL
+ ).group('literatures.id, literals.element_type, literals.element_id').as_json
+ literatures
+ end
+
+ def self.get_reaction_table(id)
+ schemeAll = ReactionsSample.where('reaction_id = ? and type != ?', id, 'ReactionsPurificationSolventSample')
+ .joins(:sample)
+ .joins("inner join molecules on samples.molecule_id = molecules.id")
+ .select(
+ <<~SQL
+ reactions_samples.id,
+ (select name from molecule_names mn where mn.id = samples.molecule_name_id) as molecule_iupac_name,
+ molecules.iupac_name, molecules.sum_formular,
+ molecules.molecular_weight, samples.name, samples.short_label,
+ samples.real_amount_value, samples.real_amount_unit,
+ samples.target_amount_value, samples.target_amount_unit,
+ samples.purity, samples.density, samples.external_label,
+ samples.molarity_value, samples.molarity_unit,
+ reactions_samples.equivalent,reactions_samples.scheme_yield,
+ reactions_samples."position" as rs_position,
+ case when reactions_samples."type" = 'ReactionsStartingMaterialSample' then 'starting_materials'
+ when reactions_samples."type" = 'ReactionsReactantSample' then 'reactants'
+ when reactions_samples."type" = 'ReactionsProductSample' then 'products'
+ when reactions_samples."type" = 'ReactionsSolventSample' then 'solvents'
+ when reactions_samples."type" = 'ReactionsPurificationSolventSample' then 'purification_solvents'
+ else reactions_samples."type"
+ end mat_group,
+ case when reactions_samples."type" = 'ReactionsStartingMaterialSample' then 1
+ when reactions_samples."type" = 'ReactionsReactantSample' then 2
+ when reactions_samples."type" = 'ReactionsProductSample' then 3
+ when reactions_samples."type" = 'ReactionsSolventSample' then 4
+ when reactions_samples."type" = 'ReactionsPurificationSolventSample' then 5
+ else 6
+ end type_seq
+ SQL
+ ).order('reactions_samples.position ASC').as_json
+
+ schemeSorted = schemeAll.sort_by {|o| o['type_seq']}
+ solvents_sum = schemeAll.select{ |d| d['mat_group'] === 'solvents'}.sum { |r|
+ value = (r['real_amount_value'].nil? || r['real_amount_value'].zero?) ? r['target_amount_value'].to_f : r['real_amount_value'].to_f
+ unit = (r['real_amount_value'].nil? || r['real_amount_value'].zero?) ? r['target_amount_unit'] : r['real_amount_unit']
+
+ has_molarity = !r['molarity_value'].nil? && r['molarity_value'] > 0.0 && (r['density'] === 0.0) || false
+ has_density = !r['density'].nil? && r['density'] > 0.0 && (r['molarity_value'] === 0.0) || false
+
+ molarity = r['molarity_value'] && r['molarity_value'].to_f || 1.0
+ density = r['density'] && r['density'].to_f || 1.0
+ purity = r['purity'] && r['purity'].to_f || 1.0
+ molecular_weight = r['molecular_weight'] && r['molecular_weight'].to_f || 1.0
+
+ r['amount_g'] = unit === 'g'? value : unit === 'mg'? value.to_f / 1000.0 : unit === 'mol' ? (value / purity) * molecular_weight : unit === 'l' && !has_molarity && !has_density ? 0 : has_molarity ? value * molarity * molecular_weight : value * density * 1000
+ r['amount_l'] = unit === 'l'? value : !has_molarity && !has_density ? 0 : has_molarity ? (r['amount_g'].to_f * purity) / (molarity * molecular_weight) : has_density ? r['amount_g'].to_f / (density * 1000) : 0
+ r['amount_l'].nil? ? 0 : r['amount_l'].to_f
+ }
+
+ schemeList = []
+ schemeList = schemeSorted.map do |r|
+ scheme = {}
+ value = (r['real_amount_value'].nil? || r['real_amount_value'].zero?) ? r['target_amount_value'].to_f : r['real_amount_value'].to_f
+ unit = (r['real_amount_value'].nil? || r['real_amount_value'].zero?) ? r['target_amount_unit'] : r['real_amount_unit']
+ has_molarity = !r['molarity_value'].nil? && r['molarity_value'] > 0.0 && (r['density'] === 0.0) || false
+ has_density = !r['density'].nil? && r['density'] > 0.0 && (r['molarity_value'] === 0.0) || false
+
+ molarity = r['molarity_value'] && r['molarity_value'].to_f || 1.0
+ density = r['density'] && r['density'].to_f || 1.0
+ purity = r['purity'] && r['purity'].to_f || 1.0
+ molecular_weight = r['molecular_weight'] && r['molecular_weight'].to_f || 1.0
+ r['amount_g'] = unit === 'g'? value : unit === 'mg'? value.to_f / 1000.0 : unit === 'mol' ? (value / purity) * molecular_weight : unit === 'l' && !has_molarity && !has_density ? 0 : has_molarity ? value * molarity * molecular_weight : value * density * 1000
+ r['amount_l'] = unit === 'l'? value : !has_molarity && !has_density ? 0 : has_molarity ? (r['amount_g'].to_f * purity) / (molarity * molecular_weight) : has_density ? r['amount_g'].to_f / (density * 1000) : 0
+
+ if r['mat_group'] === 'solvents'
+ r['equivalent'] = r['amount_l'] / solvents_sum
+ else
+ r['amount_mol'] = unit === 'mol'? value : has_molarity ? r['amount_l'] * molarity : r['amount_g'].to_f * purity / molecular_weight
+ r['dmv'] = !has_molarity && !has_density ? '- / -' : has_density ? + density.to_s + ' / - ' : ' - / ' + molarity.to_s + r['molarity_unit']
+ end
+
+ r.delete('real_amount_value');
+ r.delete('real_amount_unit');
+ r.delete('target_amount_value');
+ r.delete('target_amount_unit');
+ r.delete('molarity_value');
+ r.delete('molarity_unit');
+ r.delete('purity');
+ r.delete('molecular_weight');
+ r.delete('rs_position');
+ r.delete('density');
+ r
+ end
+ schemeList
+ end
+ end
+end
diff --git a/lib/repo/review_process.rb b/lib/repo/review_process.rb
new file mode 100644
index 000000000..e34df6b49
--- /dev/null
+++ b/lib/repo/review_process.rb
@@ -0,0 +1,391 @@
+module Repo
+ class ReviewProcess
+ def initialize(args, user_id, action = nil)
+ @user_id = user_id ## required
+ @type = args[:type] ## required
+ @id = args[:id] ## required
+ @action = action
+ @comment = args[:comment]
+ @comments = args[:comments]
+ @checklist = args[:checklist]
+ @reviewComments = args[:reviewComments]
+ @step = 0
+ init_data
+ rescue StandardError => e
+ log_exception(e, method: __method__)
+ raise e
+ end
+
+ def process
+ return unless ['reaction', 'sample', 'collection'].include?(@type)
+ return unless ['comment', 'comments', 'reviewed', 'submit', 'approved', 'accepted', 'declined'].include?(@action)
+
+ logger(next_step, 'save_comments')
+ save_comments(action_name, @action != 'comments') unless @action == 'comment' || @action == 'approved'
+ logger(next_step, "process_#{@action} started")
+ @publication = send("process_#{@action}")
+ logger(next_step, "process_#{@action} completed")
+ refresh_embargo
+ @publication
+ rescue StandardError => e
+ log_exception(e, { step: @step, method: __method__ })
+ raise e
+ end
+
+ def element_submit(root)
+ logger(next_step, "element_submit(#{root.id})")
+ root.descendants.each { |np| np.destroy! if np.element.nil? }
+ root.element.reserve_suffix
+ root.element.reserve_suffix_analyses(root.element.analyses) if root.element.analyses&.length > 0
+ root.element.analyses&.each do |a|
+ accept_new_analysis(root, a, Publication.find_by(element: a).nil?)
+ end
+ case root.element_type
+ when 'Sample'
+ analyses_ids = root.element.analyses.pluck(:id)
+ root.update!(taggable_data: root.taggable_data.merge(analysis_ids: analyses_ids))
+ root.element.analyses.each do |sa|
+ accept_new_analysis(root, sa, Publication.find_by(element: sa).nil?)
+ end
+
+ when 'Reaction'
+ root.element.products.each do |pd|
+ Publication.find_by(element_type: 'Sample', element_id: pd.id)&.destroy! if pd.analyses&.length == 0
+ next if pd.analyses&.length == 0
+ pd.reserve_suffix
+ pd.reserve_suffix_analyses(pd.analyses)
+ pd.reload
+ prod_pub = Publication.find_by(element: pd)
+ if prod_pub.nil?
+ accept_new_sample(root, pd)
+ else
+ pd.analyses.each do |rpa|
+ accept_new_analysis(prod_pub, rpa, Publication.find_by(element: rpa).nil?)
+ end
+ end
+ end
+ end
+ root.reload
+ root.update_columns(doi_id: root.element.doi.id) unless root.doi_id == root.element.doi.id
+ root.descendants.each { |pub_a|
+ next if pub_a.element.nil?
+ pub_a.update_columns(doi_id: pub_a.element.doi.id) unless pub_a.doi_id == pub_a.element&.doi&.id
+ }
+ begin
+ logger(next_step, "update_tag_doi(#{root.element.id})")
+ Repo::SubmissionApis.update_tag_doi(root.element)
+ rescue StandardError => e
+ log_exception(e, method: __method__)
+ end
+ rescue StandardError => e
+ log_exception(e, { step: @step, method: __method__ })
+ raise e
+ end
+
+ private
+
+ def refresh_embargo
+ return if @type == 'collection'
+
+ embargo = Repo::FetchHandler.find_embargo_collection(@root_publication)
+ embargo_pub = embargo.publication if embargo.present?
+ embargo_pub&.refresh_embargo_metadata
+ end
+
+ def process_review_info
+ logger(next_step, "process_review_info")
+ element_class = Object.const_get("Entities::#{@type.capitalize}Entity")
+ element = element_class.represent(@root_publication.element)
+ review_info = Repo::FetchHandler.repo_review_info(@root_publication, @user_id)
+ his = @root_publication.review&.slice('history') unless User.reviewer_ids.include?(@user_id) || @root_publication.review.dig('reviewers')&.include?(@user_id)
+ { "#{@type}": element, review: his || @root_publication.review, review_info: review_info }
+ rescue StandardError => e
+ log_exception(e, { step: @step, method: __method__ })
+ raise e
+ end
+
+ ### TODO: add description for each method
+ def process_comment
+ save_comment(@comments)
+ his = @root_publication.review&.slice('history') unless User.reviewer_ids.include?(@user_id)
+ { review: his || @root_publication.review }
+ rescue StandardError => e
+ log_exception(e, { step: @step, method: __method__ })
+ raise e
+ end
+
+ def process_comments
+ process_review_info
+ rescue StandardError => e
+ log_exception(e, { step: @step, method: __method__ })
+ raise e
+ end
+
+ def process_reviewed
+ element_submit(@root_publication)
+ pub_update_state(Publication::STATE_REVIEWED)
+ process_review_info
+ rescue StandardError => e
+ log_exception(e, { step: @step, method: __method__ })
+ raise e
+ end
+
+ def process_submit
+ element_submit(@root_publication)
+ pub_update_state(Publication::STATE_PENDING)
+ process_review_info
+ rescue StandardError => e
+ log_exception(e, { step: @step, method: __method__ })
+ raise e
+ end
+
+ def process_approved ## group leader only
+ approve_comments
+ process_review_info
+ rescue StandardError => e
+ log_exception(e, { step: @step, method: __method__ })
+ raise e
+ end
+
+ def process_accepted
+ element_submit(@root_publication)
+ public_literature
+ pub_update_state(Publication::STATE_ACCEPTED)
+ process_review_info
+ rescue StandardError => e
+ log_exception(e, { step: @step, method: __method__ })
+ raise e
+ end
+
+ def process_declined
+ pub_update_state(Publication::STATE_DECLINED)
+ ## TO BE HANDLED - remove from embargo collection
+ process_review_info
+ rescue StandardError => e
+ log_exception(e, { step: @step, method: __method__ })
+ raise e
+ end
+
+ def pub_update_state(state)
+ logger(next_step, "pub_update_state(#{state})")
+
+ @root_publication.update_state(state)
+ @root_publication.process_new_state_job(state)
+ # @embargo_pub&.refresh_embargo_metadata
+ ##################### CHI to check
+ rescue StandardError => e
+ log_exception(e, { step: @step, method: __method__ })
+ raise e
+ end
+
+ def init_data
+ logger(@step, 'init_data', show_params, :info)
+ @current_user = User.find_by(id: @user_id)
+ # @embargo_collection = EmbargoHandler.find_or_create_embargo(@embargo_id, @current_user) if @embargo_id.present? && @embargo_id >= 0
+ @root_publication = Publication.find_by(element_type: @type.classify, element_id: @id).root
+ rescue StandardError => e
+ log_exception(e, method: __method__)
+ raise e
+ end
+
+ def show_params
+ { action: @action, root_pub_id: @root_publication&.id, user_id: @user_id, type: @type, element_id: @id, comment: @comment, comments: @comments, checklist: @checklist, reviewComments: @reviewComments}
+ end
+
+ ### Save detail comment to the current review history
+ def save_comment(comment)
+ return if comment.nil? || comment.keys&.empty?
+
+ review = @root_publication.review || {}
+ review_history = review['history'] || []
+ current = review_history.last
+ comments = current['comments'] || {}
+ comment[comment.keys[0]]['timestamp'] = Time.now.strftime('%d-%m-%Y %H:%M:%S') unless comment.keys.empty?
+ comment[comment.keys[0]]['username'] = @current_user.name
+ comment[comment.keys[0]]['userid'] = @current_user.id
+
+ current['comments'] = comments.deep_merge(comment || {})
+ review['history'] = review_history
+ @root_publication.update!(review: review)
+ rescue StandardError => e
+ log_exception(e, { step: @step, method: __method__ })
+ raise e
+ end
+
+ def save_comments(action, his = true)
+ review = @root_publication.review || {}
+ review_history = review['history'] || []
+ current = review_history.last || {}
+ current['state'] = %w[accepted declined].include?(action) ? action : @root_publication.state
+ current['action'] = action unless action.nil?
+ current['username'] = @current_user.name
+ current['userid'] = @current_user.id
+ current['comment'] = @comment unless @comment.nil?
+ current['type'] = @root_publication.state == Publication::STATE_PENDING ? 'reviewed' : 'submit'
+ current['timestamp'] = Time.now.strftime('%d-%m-%Y %H:%M:%S')
+
+ if review_history.length == 0
+ review_history[0] = current
+ else
+ review_history[review_history.length - 1] = current
+ end
+ if his ## add next_node
+ next_node = { action: 'revising', type: 'submit', state: 'reviewed' } if @root_publication.state == Publication::STATE_PENDING
+ next_node = { action: 'reviewing', type: 'reviewed', state: 'pending' } if @root_publication.state == Publication::STATE_REVIEWED
+ review_history << next_node
+ review['history'] = review_history
+ else
+
+ # is_leader = review.dig('reviewers')&.include?(current_user&.id)
+ if @root_publication.state == Publication::STATE_PENDING && (action.nil? || action == Publication::STATE_REVIEWED)
+ next_node = { action: 'reviewing', type: 'reviewed', state: 'pending' }
+ review_history << next_node
+ review['history'] = review_history
+ end
+ end
+ if @checklist&.length&.positive?
+ revst = review['checklist'] || {}
+ @checklist.each do |k, v|
+ revst[k] = v['status'] == true ? { status: v['status'], user: @current_user.name, updated_at: Time.now.strftime('%d-%m-%Y %H:%M:%S') } : { status: false } unless revst[k] && revst[k]['status'] == v['status']
+ end
+ review['checklist'] = revst
+ end
+ review['reviewComments'] = @reviewComments if @reviewComments.present?
+ @root_publication.update!(review: review)
+ rescue StandardError => e
+ log_exception(e, { step: @step, method: __method__ })
+ raise e
+ end
+
+ def approve_comments
+ review = @root_publication.review || {}
+ review_history = review['history'] || []
+ current = review_history.last
+ current['username'] = @current_user.name
+ current['userid'] = @current_user.id
+ current['action'] = 'pre-approved'
+ current['comment'] = @comment unless @comment.nil?
+ current['timestamp'] = Time.now.strftime('%d-%m-%Y %H:%M:%S')
+ review_history[review_history.length - 1] = current
+ next_node = { action: 'reviewing', type: 'reviewed', state: 'pending' }
+ review_history << next_node
+ review['history'] = review_history
+ revst = review['checklist'] || {}
+ revst['glr'] = { status: true, user: @current_user.name, updated_at: Time.now.strftime('%d-%m-%Y %H:%M:%S') }
+ review['checklist'] = revst
+ @root_publication.update!(review: review)
+ end
+
+ def accept_new_sample(root, sample)
+ pub_s = Publication.create!(
+ state: Publication::STATE_PENDING,
+ element: sample,
+ doi: sample&.doi,
+ published_by: root.published_by,
+ parent: root,
+ taggable_data: root.taggable_data
+ )
+ sample.analyses.each do |sa|
+ accept_new_analysis(pub_s, sa)
+ end
+ rescue StandardError => e
+ log_exception(e, { step: @step, method: __method__ })
+ raise e
+ end
+
+ def accept_new_analysis(root, analysis, nil_analysis = true)
+ if nil_analysis
+ ap = Publication.create!(
+ state: Publication::STATE_PENDING,
+ element: analysis,
+ doi: analysis.doi,
+ published_by: root.published_by,
+ parent: root,
+ taggable_data: root.taggable_data
+ )
+ atag = ap.taggable_data
+ aids = atag&.delete('analysis_ids')
+ aoids = atag&.delete('original_analysis_ids')
+ ap.save! if aids || aoids
+ end
+ begin
+ analysis.children.where(container_type: 'dataset').each do |ds|
+ ds.attachments.each do |att|
+ if MimeMagic.by_path(att.filename)&.type&.start_with?('image')
+ file_path = File.join('public/images/publications/', att.id.to_s, '/', att.filename)
+ public_path = File.join('public/images/publications/', att.id.to_s)
+ FileUtils.mkdir_p(public_path)
+ File.write(file_path, att.read_file.force_encoding("utf-8"))
+ end
+ end
+ end
+ rescue StandardError => e
+ log_exception(e, { step: @step, method: __method__ })
+ Attachment.logger.error <<~TXT
+ --------- #{self.class.name} accept_new_analysis ------------
+ root.id: #{root.id}
+ analysis: #{analysis.id}
+ nil_analysis: #{nil_analysis}
+
+ Error Message: #{e.message}
+ Error: #{e.backtrace.join("\n")}
+ --------------------------------------------------------------------
+ TXT
+ end
+ rescue StandardError => e
+ log_exception(e, { step: @step, method: __method__ })
+ raise e
+ end
+
+
+ def public_literature
+ publications = [@root_publication] + @root_publication.descendants
+ publications.each do |pub|
+ next unless pub.element_type == 'Reaction' || pub.element_type == 'Sample'
+ literals = Literal.where(element_type: pub.element_type, element_id: pub.element_id)
+ literals&.each { |l| l.update_columns(category: 'public') } unless literals.nil?
+ end
+ rescue StandardError => e
+ log_exception(e, { step: @step, method: __method__ })
+ raise e
+ end
+
+ def action_name
+ return nil if @action == 'comments'
+ return 'review' if @action == 'reviewed'
+ return 'revision' if @action == 'submit'
+ return 'approved' if @action == 'approved'
+ return 'accepted' if @action == 'accepted'
+ return 'declined' if @action == 'declined'
+ end
+
+ def next_step
+ @step += 1
+ end
+
+ def logger(step, msg, options = {}, log_level = :debug)
+ review_logger.send(log_level, "step: [#{step}], message: [#{msg}]\n ")
+ review_logger.send(log_level, "options [#{options}]\n ") if options.present?
+ end
+
+ def log_exception(exception, options = {})
+ review_logger.error(self.class.name);
+ review_logger.error("options [#{options}] \n ")
+ review_logger.error(show_params);
+ review_logger.error("exception: #{exception.message}")
+ review_logger.error(exception.backtrace.join("\n"))
+
+ # send email to admin
+ Message.create_msg_notification(
+ channel_id: Channel.find_by(subject: Channel::SUBMITTING)&.id,
+ message_from: User.find_by(name_abbreviation: 'CHI')&.id,
+ autoDismiss: 5,
+ message_content: { 'data': "Exception, User: [#{@user_id}], the original submission [#{@type}: #{@id}], got error, #{exception.message}" },
+ )
+ end
+
+ def review_logger
+ @@review_logger ||= Logger.new(Rails.root.join('log/reviewing.log'))
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/repo/submission.rb b/lib/repo/submission.rb
new file mode 100644
index 000000000..3216b8365
--- /dev/null
+++ b/lib/repo/submission.rb
@@ -0,0 +1,532 @@
+module Repo
+ class Submission
+ def initialize(args)
+ @user_id = args[:user_id]
+ @type = args[:type]
+ @id = args[:id]
+ @author_ids = args[:author_ids]
+ @group_reviewers = args[:group_leaders]
+ @analyses_ids = args[:analyses_ids]
+ @literal_ids = args[:refs]
+ @license = args[:license]
+ @embargo_id = args[:embargo]
+ @scheme_only = args[:scheme_only] || false
+ @scheme_params = args[:scheme_params] || []
+ @step = 0
+ init_data
+ rescue StandardError => e
+ log_exception(e, method: __method__)
+ raise e
+ end
+
+ def next_step
+ @step += 1
+ end
+
+ def submitting
+ logger(next_step, __method__)
+ return unless @type == 'Sample' || @type == 'Reaction'
+
+ @element = @type.constantize.find_by(id: @id)
+
+ scheme_only_handling if @type == 'Reaction' && @scheme_only == true
+ @analyses = @element&.analyses&.where(id: @analyses_ids)
+ @analysis_set = @analyses | Container.where(id: @element.samples.flat_map { |s| s.analyses.ids } & @analyses_ids) if @type == 'Reaction' && @analyses_ids.present?
+ @analysis_set_ids = @analysis_set&.map(&:id) if @type == 'Reaction' && @analysis_set && @analysis_set.length > 0
+
+ logger(next_step, 'create_publication_tag')
+ @publication_tag = create_publication_tag
+
+ logger(next_step, 'reviewer_collections')
+ reviewer_collections
+
+ logger(next_step, "prepare_#{@type.downcase}_data")
+ @publication = send("prepare_#{@type.downcase}_data")
+
+ logger(next_step, 'process_element')
+ @publication.process_element
+
+ logger(next_step, 'update_tag_doi')
+
+ begin
+ Repo::SubmissionApis.update_tag_doi(@publication.element)
+ rescue StandardError => e
+ log_exception(e, method: __method__)
+ end
+
+ if col_pub = @embargo_collection&.publication
+ logger(next_step, 'refresh_embargo_metadata')
+ col_pub.refresh_embargo_metadata
+ end
+ logger(next_step, 'process_new_state_job')
+ @publication.process_new_state_job
+
+ send_message_to_user
+
+ logger(next_step, "#{__method__} completed, submmited element [#{@new_root&.id&.to_s}]\n ", {}, :info)
+
+ rescue StandardError => e
+ log_exception(e, { step: @step, method: __method__ })
+ raise e
+ end
+
+
+ private
+
+ def show_params
+ { user_id: @user_id, type: @type, id: @id, author_ids: @author_ids, group_leaders: @group_reviewers, analyses_ids: @analyses_ids, refs: @literal_ids, license: @license, embargo: @embargo_id }
+ end
+
+ def init_data
+ logger(@step, 'init_data', show_params, :info)
+ @current_user = User.find_by(id: @user_id)
+ @literals = Literal.where(id: @literal_ids) if @literal_ids.present?
+ @embargo_collection = EmbargoHandler.find_or_create_embargo(@embargo_id, @current_user) if @embargo_id.present? && @embargo_id >= 0
+ rescue StandardError => e
+ log_exception(e, method: __method__)
+ raise e
+ end
+
+ def create_publication_tag
+ authors = User.where(type: %w[Person Collaborator], id: @author_ids)
+ .includes(:affiliations)
+ .order(Arel.sql("position(users.id::text in '#{@author_ids}')"))
+ affiliations = authors.map(&:current_affiliations)
+ affiliations_output = {}
+ affiliations.flatten.each do |aff|
+ affiliations_output[aff.id] = aff.output_full
+ end
+ {
+ published_by: @author_ids[0],
+ author_ids: @author_ids,
+ creators: authors.map do |author|
+ {
+ 'givenName' => author.first_name,
+ 'familyName' => author.last_name,
+ 'name' => author.name,
+ 'ORCID' => author.orcid,
+ 'affiliationIds' => author.current_affiliations.map(&:id),
+ 'id' => author.id
+ }
+ end,
+ contributors: {
+ 'givenName' => @current_user.first_name,
+ 'familyName' => @current_user.last_name,
+ 'name' => @current_user.name,
+ 'ORCID' => @current_user.orcid,
+ 'affiliations' => @current_user.current_affiliations.map(&:output_full),
+ 'id' => @current_user.id
+ },
+ affiliations: affiliations_output,
+ affiliation_ids: affiliations.map { |as| as.map(&:id) },
+ queued_at: DateTime.now,
+ license: @license,
+ scheme_only: @scheme_only
+ }
+ rescue StandardError => e
+ log_exception(e, method: __method__)
+ raise e
+ end
+
+
+ def prepare_reaction_data
+ @reaction_analysis_set = @element.analyses.where(id: @analysis_set_ids)
+ @new_root = duplicate_reaction
+ @element.tag_as_published(@new_root, @reaction_analysis_set)
+ @new_root.create_publication_tag(@current_user, @author_ids, @license)
+ @new_root.samples.each do |new_sample|
+ new_sample.create_publication_tag(@current_user, @author_ids, @license)
+ end
+ @new_publication = Publication.find_by(element: @new_root)
+ add_submission_history(@new_publication)
+ @new_publication
+ rescue StandardError => e
+ log_exception(e, method: __method__)
+ raise e
+ end
+
+ def prepare_sample_data
+ @new_root = duplicate_sample(@element, @analyses)
+ @element.tag_as_published(@new_root, @analyses)
+ @new_root.create_publication_tag(@current_user, @author_ids, @license)
+ @element.untag_reserved_suffix
+ @new_publication = Publication.find_by(element: @new_root)
+ add_submission_history(@new_publication)
+ @new_publication
+ rescue StandardError => e
+ log_exception(e, method: __method__)
+ raise e
+ end
+
+
+ def add_submission_history(root)
+ init_node = {
+ state: 'submission',
+ action: 'submission',
+ timestamp: Time.now.strftime('%d-%m-%Y %H:%M:%S'),
+ username: @current_user.name,
+ user_id: @current_user.id,
+ type: 'submit'
+ }
+ review = root.review || {}
+ history = review['history'] || []
+ history << init_node
+
+ current_node = {
+ action: 'reviewing',
+ type: 'reviewed',
+ state: 'pending'
+ }
+ history << current_node
+ review['history'] = history
+ review['reviewers'] = @group_reviewers if @group_reviewers.present?
+ root.update!(review: review)
+ rescue StandardError => e
+ log_exception(e, method: __method__)
+ raise e
+ end
+
+ def reviewer_collections
+ c = @current_user.pending_collection
+ User.reviewer_ids.each do |rev_id|
+ SyncCollectionsUser.find_or_create_by(
+ collection_id: c.id,
+ user_id: rev_id,
+ shared_by_id: c.user_id,
+ permission_level: 3,
+ sample_detail_level: 10,
+ reaction_detail_level: 10,
+ label: 'REVIEWING'
+ )
+ end
+ rescue StandardError => e
+ log_exception(e, method: __method__)
+ raise e
+ end
+
+ def duplicate_reaction
+ new_reaction = @element.dup
+ princhi_string, princhi_long_key, princhi_short_key, princhi_web_key = @element.products_rinchis
+
+ new_reaction.collections << @current_user.pending_collection
+ new_reaction.collections << Collection.element_to_review_collection
+ new_reaction.collections << @embargo_collection unless @embargo_collection.nil?
+
+ dir = File.join(Rails.root, 'public', 'images', 'reactions')
+ rsf = @element.reaction_svg_file
+ path = File.join(dir, rsf)
+ new_rsf = "#{Time.now.to_i}-#{rsf}"
+ dest = File.join(dir, new_rsf)
+
+ new_reaction.save!
+ new_reaction.copy_segments(segments: @element.segments, current_user_id: @user_id)
+ unless @literals.nil?
+ lits = @literals&.select { |lit| lit['element_type'] == 'Reaction' && lit['element_id'] == @element.id }
+ duplicate_literals(new_reaction, lits)
+ end
+ if File.exists? path
+ FileUtils.cp(path, dest)
+ new_reaction.update_columns(reaction_svg_file: new_rsf)
+ end
+ # new_reaction.save!
+ et = new_reaction.tag
+ data = et.taggable_data || {}
+ # data[:products_rinchi] = {
+ # rinchi_string: princhi_string,
+ # rinchi_long_key: princhi_long_key,
+ # rinchi_short_key: princhi_short_key,
+ # rinchi_web_key: princhi_web_key
+ # }
+ et.update!(taggable_data: data)
+
+ if (d = @element.doi)
+ d.update!(doiable: new_reaction)
+ else
+ # NB: the reaction has still no sample, so it cannot get a proper rinchi needed for the doi
+ # => use the one from original reaction
+ d = Doi.create_for_element!(new_reaction, 'reaction/' + @element.products_short_rinchikey_trimmed)
+ end
+
+ pub = Publication.create!(
+ state: Publication::STATE_PENDING,
+ element: new_reaction,
+ original_element: @element,
+ published_by: @user_id,
+ doi: d,
+ taggable_data: @publication_tag.merge(
+ author_ids: @author_ids,
+ original_analysis_ids: @analysis_set_ids,
+ products_rinchi: {
+ rinchi_string: princhi_string,
+ rinchi_long_key: princhi_long_key,
+ rinchi_short_key: princhi_short_key,
+ rinchi_web_key: princhi_web_key
+ }
+ )
+ )
+
+ duplicate_analyses(new_reaction, @reaction_analysis_set, 'reaction/' + @element.products_short_rinchikey_trimmed)
+ @element.reactions_samples.each do |rs|
+ new_rs = rs.dup
+ sample = @current_user.samples.find_by(id: rs.sample_id)
+ if @scheme_only == true
+ sample.target_amount_value = 0.0
+ sample.real_amount_value = nil
+ end
+ sample_analysis_set = sample.analyses.where(id: @analysis_set_ids)
+ new_sample = duplicate_sample(sample, sample_analysis_set, pub.id)
+ sample.tag_as_published(new_sample, sample_analysis_set)
+ new_rs.sample_id = new_sample
+ new_rs.reaction_id = new_reaction.id
+ new_rs.sample_id = new_sample.id
+ new_rs.reaction_id = new_reaction.id
+ new_rs.save!
+ end
+
+ new_reaction.update_svg_file!
+ new_reaction.reload
+ new_reaction.save!
+ new_reaction.reload
+ rescue StandardError => e
+ log_exception(e, method: __method__)
+ raise e
+ end
+
+ def duplicate_sample(sample = @sample, analyses = @analyses, parent_publication_id = nil)
+ new_sample = sample.dup
+ new_sample.reprocess_svg if new_sample.sample_svg_file.blank?
+ new_sample.collections << @current_user.pending_collection
+ new_sample.collections << Collection.element_to_review_collection
+ new_sample.collections << @embargo_collection unless @embargo_collection.nil?
+ new_sample.save!
+ new_sample.copy_segments(segments: sample.segments, current_user_id: @current_user.id) if sample.segments
+ duplicate_residues(new_sample, sample) if sample.residues
+ duplicate_elemental_compositions(new_sample, sample) if sample.elemental_compositions
+ duplicate_user_labels(new_sample, sample, @current_user&.id)
+ unless @literals.nil?
+ lits = @literals&.select { |lit| lit['element_type'] == 'Sample' && lit['element_id'] == sample.id }
+ duplicate_literals(new_sample, lits)
+ end
+ duplicate_analyses(new_sample, analyses, new_sample.molecule.inchikey)
+ has_analysis = new_sample.analyses.present?
+ if (has_analysis = new_sample.analyses.present?)
+ if (d = sample.doi)
+ d.update!(doiable: new_sample)
+ else
+ d = Doi.create_for_element!(new_sample)
+ end
+ pub = Publication.create!(
+ state: Publication::STATE_PENDING,
+ element: new_sample,
+ original_element: sample,
+ published_by: @current_user.id,
+ doi: d,
+ parent_id: parent_publication_id,
+ taggable_data: @publication_tag.merge(
+ author_ids: @author_ids,
+ user_labels: sample.tag.taggable_data['user_labels'],
+ original_analysis_ids: analyses.pluck(:id),
+ analysis_ids: new_sample.analyses.pluck(:id)
+ )
+ )
+ end
+ new_sample.analyses.each do |ana|
+ Publication.find_by(element: ana).update(parent: pub)
+ end
+ new_sample
+ rescue StandardError => e
+ log_exception(e, method: __method__)
+ raise e
+ end
+
+ def duplicate_residues(newSample, originalSample)
+ originalSample&.residues&.each do |res|
+ newRes = Residue.find_or_create_by(sample_id: newSample.id, residue_type: res.residue_type)
+ newRes.update_columns(custom_info: res.custom_info)
+ end
+ rescue StandardError => e
+ log_exception(e, method: __method__)
+ raise e
+ end
+
+ def duplicate_elemental_compositions(newSample, originalSample)
+ originalSample&.elemental_compositions&.each do |ec|
+ newComposition = ElementalComposition.find_or_create_by(sample_id: newSample.id, composition_type: ec.composition_type)
+ newComposition.update_columns(data: ec.data, loading: ec.loading)
+ end
+ rescue StandardError => e
+ log_exception(e, method: __method__)
+ raise e
+ end
+
+ def duplicate_user_labels(new_element, original_element, current_user_id)
+ user_labels = original_element.tag&.taggable_data&.dig('user_labels')
+ return if user_labels.nil?
+
+ tag = new_element.tag
+ taggable_data = tag.taggable_data || {}
+ taggable_data['user_labels'] = user_labels
+ tag.update!(taggable_data: taggable_data)
+
+ if new_element.respond_to?(:publication) && pub = new_element.publication
+ pub.update_user_labels(data['user_labels'], current_user_id) if pub.present?
+ end
+ rescue StandardError => e
+ log_exception(e, method: __method__)
+ raise e
+ end
+
+ def duplicate_literals(element, literals)
+ literals&.each do |lit|
+ attributes = {
+ literature_id: lit.literature_id,
+ element_id: element.id,
+ element_type: lit.element_type,
+ category: 'detail',
+ user_id: lit.user_id,
+ litype: lit.litype
+ }
+ Literal.create(attributes)
+ end
+ rescue StandardError => e
+ log_exception(e, method: __method__)
+ raise e
+ end
+
+ def duplicate_analyses(new_element, analyses_arr, ik = nil)
+ unless new_element.container
+ Container.create_root_container(containable: new_element)
+ new_element.reload
+ end
+ analyses = Container.analyses_container(new_element.container.id).first
+ parent_publication = new_element.publication
+ analyses_arr&.each do |ana|
+ new_ana = analyses.children.create(
+ name: ana.name,
+ container_type: ana.container_type,
+ description: ana.description
+ )
+ new_ana.extended_metadata = ana.extended_metadata
+ new_ana.save!
+
+ # move reserved doi
+ if (d = ana.doi)
+ d.update(doiable: new_ana)
+ else
+ d = Doi.create_for_analysis!(new_ana, ik)
+ end
+ Publication.create!(
+ state: Publication::STATE_PENDING,
+ element: new_ana,
+ original_element: ana,
+ published_by: @current_user.id,
+ doi: d,
+ parent: new_element.publication,
+ taggable_data: @publication_tag.merge(
+ author_ids: @author_ids
+ )
+ )
+ # duplicate datasets and copy attachments
+ ana.children.where(container_type: 'dataset').each do |ds|
+ new_dataset = new_ana.children.create(container_type: 'dataset')
+ new_dataset.name = ds.name
+ new_dataset.extended_metadata = ds.extended_metadata
+ new_dataset.save!
+ new_dataset.copy_dataset(ds)
+ clone_attachs = ds.attachments
+ Usecases::Attachments::Copy.execute!(clone_attachs, new_dataset, @current_user.id) if clone_attachs.present?
+ end
+ end
+ rescue StandardError => e
+ log_exception(e, method: __method__)
+ raise e
+ end
+
+ # def update_tag_doi(element)
+ # unless element.nil? || element&.doi.nil? || element&.tag.nil?
+ # mds = Datacite::Mds.new
+ # et = element.tag
+ # tag_data = (et.taggable_data && et.taggable_data['publication']) || {}
+ # tag_data['doi'] = "#{mds.doi_prefix}/#{element&.doi.suffix}"
+ # et.update!(
+ # taggable_data: (et.taggable_data || {}).merge(publication: tag_data)
+ # )
+ # if element&.class&.name == 'Reaction'
+ # element&.publication.children.each do |child|
+ # next unless child&.element&.class&.name == 'Sample'
+
+ # update_tag_doi(child.element)
+ # end
+ # end
+ # end
+ # rescue StandardError => e
+ # log_exception(e, method: __method__)
+ # raise e
+ # end
+
+ def scheme_only_handling
+ return unless @type == 'Reaction' && @scheme_only == true
+
+ @element.reactions_samples.select { |rs| rs.type == 'ReactionsProductSample' }.map do |p|
+ py = @scheme_params[:scheme_yield].select { |o| o['id'] == p.sample_id }
+ p.equivalent = py[0]['_equivalent'] if py && !py.empty?
+ p.scheme_yield = py[0]['_equivalent'] if py && !py.empty?
+ end
+
+ @element.reactions_samples.select{ |rs| rs.type != 'ReactionsProductSample' }.map do |p|
+ p.equivalent = 0
+ end
+ @element.name = ''
+ @element.purification = '{}'
+ @element.dangerous_products = '{}'
+ @element.description = { 'ops' => [{ 'insert' => '' }] } unless @scheme_params[:schemeDesc]
+ @element.observation = { 'ops' => [{ 'insert' => '' }] }
+ @element.tlc_solvents = ''
+ @element.tlc_description = ''
+ @element.rf_value = 0
+ @element.rxno = nil
+ @element.role = ''
+ @element.temperature = @scheme_params[:temperature]
+ @element.duration = "#{@scheme_params[:duration][:dispValue]} #{@scheme_params[:duration][:dispUnit]}" unless @scheme_params[:duration].nil?
+ end
+
+ def send_message_to_user
+ is_scheme_ony = @scheme_only == true ? 'scheme-only ' : ''
+ Message.create_msg_notification(
+ channel_id: Channel.find_by(subject: Channel::SUBMITTING)&.id,
+ message_from: @user_id,
+ autoDismiss: 5,
+ message_content: { 'data': "The submission [#{is_scheme_ony}#{@type}: #{@new_root.short_label}] has been generated from the original submission [#{@type}: #{@element.short_label}]" },
+ )
+ rescue StandardError => e
+ log_exception(e, method: __method__)
+ end
+
+ def logger(step, msg, options = {}, log_level = :debug)
+ submit_logger.send(log_level, "step: [#{step}], message: [#{msg}]\n ")
+ submit_logger.send(log_level, "options [#{options}]\n ") if options.present?
+ end
+
+ def log_exception(exception, options = {})
+ submit_logger.error(self.class.name);
+ submit_logger.error("options [#{options}] \n ")
+ submit_logger.error(show_params);
+ submit_logger.error("exception: #{exception.message}")
+ submit_logger.error(exception.backtrace.join("\n"))
+
+ # send message to admin
+ Message.create_msg_notification(
+ channel_id: Channel.find_by(subject: Channel::SUBMITTING)&.id,
+ message_from: User.find_by(name_abbreviation: 'CHI')&.id,
+ autoDismiss: 5,
+ message_content: { 'data': "Exception, User: [#{@user_id}], the original submission [#{@type}: #{@id}], new submission [#{@new_root&.id}] #{@new_root&.short_label}] got error, #{exception.message}" },
+ )
+
+ end
+
+ def submit_logger
+ @@submit_logger ||= Logger.new(Rails.root.join('log/submission.log'))
+ end
+
+ end
+end
\ No newline at end of file
diff --git a/lib/repo/submission_apis.rb b/lib/repo/submission_apis.rb
new file mode 100644
index 000000000..53abfb68b
--- /dev/null
+++ b/lib/repo/submission_apis.rb
@@ -0,0 +1,26 @@
+module Repo
+ class SubmissionApis
+ def self.update_tag_doi(element)
+ unless element.nil? || element&.doi.nil? || element&.tag.nil?
+ mds = Datacite::Mds.new
+ et = element.tag
+ tag_data = (et.taggable_data && et.taggable_data['publication']) || {}
+ tag_data['doi'] = "#{mds.doi_prefix}/#{element&.doi.suffix}"
+ et.update!(
+ taggable_data: (et.taggable_data || {}).merge(publication: tag_data)
+ )
+ if element&.class&.name == 'Reaction'
+ element&.publication.children.each do |child|
+ next unless child&.element&.class&.name == 'Sample'
+
+ Repo::SubmissionApis.update_tag_doi(child.element)
+ end
+ end
+ end
+ rescue StandardError => e
+ Rails.logger.error(e)
+ raise e
+ end
+
+ end
+end
\ No newline at end of file
diff --git a/package.json b/package.json
index 6ddeb7238..0169c910e 100644
--- a/package.json
+++ b/package.json
@@ -28,7 +28,7 @@
"aviator": "v0.6.1",
"base-64": "^0.1.0",
"chem-generic-ui": "^1.4.0-rc1",
- "chem-generic-ui-viewer": "^1.3.0",
+ "chem-generic-ui-viewer": "^1.3.3",
"chemotion-converter-client": "0.5.0",
"citation-js": "0.6.8",
"classnames": "^2.2.5",
@@ -66,6 +66,7 @@
"quill-delta": "3.4.3",
"quill-delta-to-html": "0.8.2",
"quill-delta-to-plaintext": "^1.0.0",
+ "quill2": "npm:quill@^2.0.2",
"raw-loader": "^4.0.2",
"react": "^17.0.2",
"react-async-script-loader": "0.3.0",
@@ -92,7 +93,7 @@
"react-json-editor-ajrm": "^2.5.10",
"react-markdown": "^6.0.2",
"react-modal-resizable-draggable": "^0.1.6",
- "react-molviewer": "^1.0.0",
+ "react-molviewer": "^1.1.3",
"react-notification-system": "^0.2.7",
"react-papaparse": "^3.17.2",
"react-pdf": "^7.7.3",
@@ -113,7 +114,7 @@
"redux": "^4.1.0",
"redux-immutable": "^4.0.0",
"redux-thunk": "^2.0.0",
- "repo-review-ui": "^0.0.14",
+ "repo-review-ui": "^0.0.17",
"sha256": "^0.2.0",
"spark-md5": "^3.0.1",
"svgedit": "^7.3.0",
diff --git a/yarn.lock b/yarn.lock
index 698f2b64f..3c01f118b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1869,6 +1869,11 @@
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.0.tgz#88da2b70d6ca18aaa6ed3687832e11f39e80624b"
integrity sha512-HNii132xfomg5QVZw0HwXXpN22s7VBHQBv9CeOu9tfJnhsWQNd2lmTNi8CSrnw5B+5YOmzu1UoPAyxaXsJ6RgQ==
+"@fortawesome/fontawesome-common-types@6.6.0":
+ version "6.6.0"
+ resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.6.0.tgz#31ab07ca6a06358c5de4d295d4711b675006163f"
+ integrity sha512-xyX0X9mc0kyz9plIyryrRbl7ngsA9jz77mCZJsUkLl+ZKs0KWObgaEBoSgQiYWAsSmjz/yjl0F++Got0Mdp4Rw==
+
"@fortawesome/fontawesome-svg-core@^6.1.2":
version "6.4.0"
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.4.0.tgz#3727552eff9179506e9203d72feb5b1063c11a21"
@@ -1876,6 +1881,13 @@
dependencies:
"@fortawesome/fontawesome-common-types" "6.4.0"
+"@fortawesome/fontawesome-svg-core@^6.5.2":
+ version "6.6.0"
+ resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.6.0.tgz#2a24c32ef92136e98eae2ff334a27145188295ff"
+ integrity sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg==
+ dependencies:
+ "@fortawesome/fontawesome-common-types" "6.6.0"
+
"@fortawesome/free-regular-svg-icons@^6.1.2":
version "6.4.0"
resolved "https://registry.yarnpkg.com/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.4.0.tgz#cacc53bd8d832d46feead412d9ea9ce80a55e13a"
@@ -1883,6 +1895,13 @@
dependencies:
"@fortawesome/fontawesome-common-types" "6.4.0"
+"@fortawesome/free-regular-svg-icons@^6.5.2":
+ version "6.6.0"
+ resolved "https://registry.yarnpkg.com/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.6.0.tgz#fc49a947ac8dfd20403c9ea5f37f0919425bdf04"
+ integrity sha512-Yv9hDzL4aI73BEwSEh20clrY8q/uLxawaQ98lekBx6t9dQKDHcDzzV1p2YtBGTtolYtNqcWdniOnhzB+JPnQEQ==
+ dependencies:
+ "@fortawesome/fontawesome-common-types" "6.6.0"
+
"@fortawesome/free-solid-svg-icons@^6.1.2":
version "6.4.0"
resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.4.0.tgz#48c0e790847fa56299e2f26b82b39663b8ad7119"
@@ -1890,6 +1909,13 @@
dependencies:
"@fortawesome/fontawesome-common-types" "6.4.0"
+"@fortawesome/free-solid-svg-icons@^6.5.2":
+ version "6.6.0"
+ resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.6.0.tgz#061751ca43be4c4d814f0adbda8f006164ec9f3b"
+ integrity sha512-IYv/2skhEDFc2WGUcqvFJkeK39Q+HyPf5GHUrT/l2pKbtgEIv1al1TKd6qStR5OIwQdN1GZP54ci3y4mroJWjA==
+ dependencies:
+ "@fortawesome/fontawesome-common-types" "6.6.0"
+
"@fortawesome/react-fontawesome@^0.2.0":
version "0.2.0"
resolved "https://registry.yarnpkg.com/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.0.tgz#d90dd8a9211830b4e3c08e94b63a0ba7291ddcf4"
@@ -1897,6 +1923,13 @@
dependencies:
prop-types "^15.8.1"
+"@fortawesome/react-fontawesome@^0.2.1":
+ version "0.2.2"
+ resolved "https://registry.yarnpkg.com/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.2.tgz#68b058f9132b46c8599875f6a636dad231af78d4"
+ integrity sha512-EnkrprPNqI6SXJl//m29hpaNzOp1bruISWaOiRtkMi/xSvHJlzc2j2JAYS7egxt/EbjSNV/k6Xy0AQI6vB2+1g==
+ dependencies:
+ prop-types "^15.8.1"
+
"@gar/promisify@^1.0.1":
version "1.1.2"
resolved "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.2.tgz"
@@ -2742,11 +2775,6 @@
resolved "https://registry.npmjs.org/@redux-saga/types/-/types-1.1.0.tgz"
integrity sha512-afmTuJrylUU/0OtqzaRkbyYFFNgCF73Bvel/sw90pvGrWIZ+vyoIJqA6eMSoA6+nb443kTmulmBtC9NerXboNg==
-"@remix-run/router@1.13.0":
- version "1.13.0"
- resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.13.0.tgz#7e29c4ee85176d9c08cb0f4456bff74d092c5065"
- integrity sha512-5dMOnVnefRsl4uRnAdoWjtVTdh8e6aZqgM4puy9nmEADH72ck+uXwzpJLEKE9Q6F8ZljNewLgmTfkxUrBdv4WA==
-
"@restart/hooks@^0.4.7":
version "0.4.9"
resolved "https://registry.yarnpkg.com/@restart/hooks/-/hooks-0.4.9.tgz#ad858fb39d99e252cccce19416adc18fc3f18fcb"
@@ -3809,11 +3837,6 @@ adler-32@~1.3.0:
dependencies:
printj "~1.2.2"
-ag-grid-community@^27.3.0:
- version "27.3.0"
- resolved "https://registry.yarnpkg.com/ag-grid-community/-/ag-grid-community-27.3.0.tgz#b1e94a58026aaf2f0cd7920e35833325b5e762c7"
- integrity sha512-R5oZMXEHXnOLrmhn91J8lR0bv6IAnRcU6maO+wKLMJxffRWaAYFAuw1jt7bdmcKCv8c65F6LEBx4ykSOALa9vA==
-
ag-grid-community@^28.2.1:
version "28.2.1"
resolved "https://registry.yarnpkg.com/ag-grid-community/-/ag-grid-community-28.2.1.tgz#50778cb2254ee79497222781909d8364007dd91e"
@@ -3824,13 +3847,6 @@ ag-grid-community@^31.0.0, ag-grid-community@~31.0.3:
resolved "https://registry.yarnpkg.com/ag-grid-community/-/ag-grid-community-31.0.3.tgz#80870881a3be03aa5df890b4a70409ef5d781e7f"
integrity sha512-k81YmLaOQOab9BavYD+Pw2smZSl6TXOJqj9hRuf70XQl3EknOHCGcra7joJxZRJLMKE/HdR+u33TNyX4TCuWfg==
-ag-grid-react@^27.3.0:
- version "27.3.0"
- resolved "https://registry.yarnpkg.com/ag-grid-react/-/ag-grid-react-27.3.0.tgz#fe06647653f8b0b349b8e613aab8ea2e07915562"
- integrity sha512-2bs9YfJ/shvBZQLLjny4NFvht+ic6VtpTPO0r3bHHOhlL3Fjx2rGvS6AHSwfvu+kJacHCta30PjaEbX8T3UDyw==
- dependencies:
- prop-types "^15.8.1"
-
ag-grid-react@^28.2.1:
version "28.2.1"
resolved "https://registry.yarnpkg.com/ag-grid-react/-/ag-grid-react-28.2.1.tgz#c38585b8f165a5cf9343eab6994c06855f5b2caf"
@@ -4483,24 +4499,6 @@ bluebird@^3.7.2:
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
-body-parser@1.20.1:
- version "1.20.1"
- resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668"
- integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==
- dependencies:
- bytes "3.1.2"
- content-type "~1.0.4"
- debug "2.6.9"
- depd "2.0.0"
- destroy "1.2.0"
- http-errors "2.0.0"
- iconv-lite "0.4.24"
- on-finished "2.4.1"
- qs "6.11.0"
- raw-body "2.5.1"
- type-is "~1.6.18"
- unpipe "1.0.0"
-
body-parser@1.20.2:
version "1.20.2"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd"
@@ -4519,24 +4517,6 @@ body-parser@1.20.2:
type-is "~1.6.18"
unpipe "1.0.0"
-body-parser@1.20.1:
- version "1.20.1"
- resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668"
- integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==
- dependencies:
- bytes "3.1.2"
- content-type "~1.0.4"
- debug "2.6.9"
- depd "2.0.0"
- destroy "1.2.0"
- http-errors "2.0.0"
- iconv-lite "0.4.24"
- on-finished "2.4.1"
- qs "6.11.0"
- raw-body "2.5.1"
- type-is "~1.6.18"
- unpipe "1.0.0"
-
bonjour-service@^1.0.11:
version "1.0.11"
resolved "https://registry.yarnpkg.com/bonjour-service/-/bonjour-service-1.0.11.tgz#5418e5c1ac91c89a406f853a942e7892829c0d89"
@@ -4885,22 +4865,22 @@ cheerio@^1.0.0-rc.3:
parse5-htmlparser2-tree-adapter "^6.0.1"
tslib "^2.2.0"
-chem-generic-ui-viewer@^1.3.0:
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/chem-generic-ui-viewer/-/chem-generic-ui-viewer-1.3.0.tgz#27cac2f0b6c58435b0980535c1c403b6783ce973"
- integrity sha512-voVAFGZXNZX/AGSpLqVluCrKdZvDK0q71NCzoxHQoD6p54bK5Jpt+X7Vzl5cn7ldBqb+m3lO8/JGA+3mYGO4LQ==
+chem-generic-ui-viewer@^1.3.3:
+ version "1.3.3"
+ resolved "https://registry.yarnpkg.com/chem-generic-ui-viewer/-/chem-generic-ui-viewer-1.3.3.tgz#dc54e26622fb004a85d58139f2c24ec6e68be1a3"
+ integrity sha512-l3o60Lf+oDJGcEyC9W6cX8pJdnmMqpUeF4wsE9jM9kc7Kpqs7DLcEGei+dPsG/6+mQQKPqdZkNWWKZDJK0/A5g==
dependencies:
- "@fortawesome/fontawesome-svg-core" "^6.1.2"
- "@fortawesome/free-regular-svg-icons" "^6.1.2"
- "@fortawesome/free-solid-svg-icons" "^6.1.2"
- "@fortawesome/react-fontawesome" "^0.2.0"
+ "@fortawesome/fontawesome-svg-core" "^6.5.2"
+ "@fortawesome/free-regular-svg-icons" "^6.5.2"
+ "@fortawesome/free-solid-svg-icons" "^6.5.2"
+ "@fortawesome/react-fontawesome" "^0.2.1"
chem-units "^1.1.0"
+ generic-ui-core "^1.4.6"
lodash "^4.17.21"
numeral "^2.0.6"
prop-types "^15.8.1"
react "^17.0.2"
react-dom "^17.0.2"
- react-select "5.2.2"
uuid "^8.3.2"
chem-generic-ui@^1.4.0-rc1:
@@ -4936,6 +4916,11 @@ chem-units@^1.1.0:
resolved "https://registry.yarnpkg.com/chem-units/-/chem-units-1.1.0.tgz#65d93db9686a50bb59430e34732f497aa2804be8"
integrity sha512-A3jY/B2aEb5s9bmg2lk77QYUl4xCTTCeeKQPbKfwvcD7DseuL40UF6o5Veusfkip8NOcdQ3X6m2hNLMdJKXHbg==
+chem-units@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/chem-units/-/chem-units-1.2.0.tgz#370b89ba225a84112ca1d201adb3343d9cddb9fd"
+ integrity sha512-Yzz6Re1ZSvEwZc0aINCj2yVvsZrdPms+RcF4EPdHLDdJK8ElHG73pFJM+3cIW1sLn9cddDSnqST7Qnltcy+n/w==
+
chemotion-converter-client@0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/chemotion-converter-client/-/chemotion-converter-client-0.5.0.tgz#a5c99b1f7bb215f86a7ebd2d78d5c7c6c92eb1b2"
@@ -5401,21 +5386,11 @@ cookie-signature@1.0.6:
resolved "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz"
integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw=
-cookie@0.5.0:
- version "0.5.0"
- resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b"
- integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==
-
cookie@0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051"
integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==
-cookie@0.5.0:
- version "0.5.0"
- resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b"
- integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==
-
cookie@^0.3.1:
version "0.3.1"
resolved "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz"
@@ -7255,6 +7230,11 @@ eventemitter3@^4.0.0:
resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz"
integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
+eventemitter3@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4"
+ integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==
+
eventlistener@0.0.1:
version "0.0.1"
resolved "https://registry.npmjs.org/eventlistener/-/eventlistener-0.0.1.tgz"
@@ -7380,80 +7360,6 @@ express@^4.17.3:
utils-merge "1.0.1"
vary "~1.1.2"
-express@^4.18.2:
- version "4.18.2"
- resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59"
- integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==
- dependencies:
- accepts "~1.3.8"
- array-flatten "1.1.1"
- body-parser "1.20.1"
- content-disposition "0.5.4"
- content-type "~1.0.4"
- cookie "0.5.0"
- cookie-signature "1.0.6"
- debug "2.6.9"
- depd "2.0.0"
- encodeurl "~1.0.2"
- escape-html "~1.0.3"
- etag "~1.8.1"
- finalhandler "1.2.0"
- fresh "0.5.2"
- http-errors "2.0.0"
- merge-descriptors "1.0.1"
- methods "~1.1.2"
- on-finished "2.4.1"
- parseurl "~1.3.3"
- path-to-regexp "0.1.7"
- proxy-addr "~2.0.7"
- qs "6.11.0"
- range-parser "~1.2.1"
- safe-buffer "5.2.1"
- send "0.18.0"
- serve-static "1.15.0"
- setprototypeof "1.2.0"
- statuses "2.0.1"
- type-is "~1.6.18"
- utils-merge "1.0.1"
- vary "~1.1.2"
-
-express@^4.18.2:
- version "4.18.2"
- resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59"
- integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==
- dependencies:
- accepts "~1.3.8"
- array-flatten "1.1.1"
- body-parser "1.20.1"
- content-disposition "0.5.4"
- content-type "~1.0.4"
- cookie "0.5.0"
- cookie-signature "1.0.6"
- debug "2.6.9"
- depd "2.0.0"
- encodeurl "~1.0.2"
- escape-html "~1.0.3"
- etag "~1.8.1"
- finalhandler "1.2.0"
- fresh "0.5.2"
- http-errors "2.0.0"
- merge-descriptors "1.0.1"
- methods "~1.1.2"
- on-finished "2.4.1"
- parseurl "~1.3.3"
- path-to-regexp "0.1.7"
- proxy-addr "~2.0.7"
- qs "6.11.0"
- range-parser "~1.2.1"
- safe-buffer "5.2.1"
- send "0.18.0"
- serve-static "1.15.0"
- setprototypeof "1.2.0"
- statuses "2.0.1"
- type-is "~1.6.18"
- utils-merge "1.0.1"
- vary "~1.1.2"
-
extend-shallow@^2.0.1:
version "2.0.1"
resolved "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz"
@@ -7537,7 +7443,7 @@ fast-diff@1.1.2:
resolved "https://registry.npmjs.org/fast-diff/-/fast-diff-1.1.2.tgz"
integrity sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig==
-fast-diff@^1.1.2:
+fast-diff@^1.1.2, fast-diff@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0"
integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==
@@ -7915,6 +7821,14 @@ generic-ui-core@^1.3.0:
chem-units "^1.1.0"
lodash "^4.17.21"
+generic-ui-core@^1.4.6:
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/generic-ui-core/-/generic-ui-core-1.5.0.tgz#2ed2d11901f90c1df2b946de1e82e56101e27728"
+ integrity sha512-MkoNSd2KompcjgfoSa+sGH2OAs2vowMJv13Pfas6nzJVDwSCmRTYFxMIKFjzJIhXd70QwAR4DcJ5T2UbwUyjmg==
+ dependencies:
+ chem-units "^1.2.0"
+ lodash "^4.17.21"
+
gensync@^1.0.0-beta.2:
version "1.0.0-beta.2"
resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz"
@@ -8383,17 +8297,6 @@ http-errors@2.0.0:
statuses "2.0.1"
toidentifier "1.0.1"
-http-errors@2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3"
- integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==
- dependencies:
- depd "2.0.0"
- inherits "2.0.4"
- setprototypeof "1.2.0"
- statuses "2.0.1"
- toidentifier "1.0.1"
-
http-errors@~1.6.2:
version "1.6.3"
resolved "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz"
@@ -9848,6 +9751,11 @@ lodash.camelcase@^4.3.0:
resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==
+lodash.clonedeep@^4.5.0:
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
+ integrity sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==
+
lodash.debounce@^4.0.0, lodash.debounce@^4.0.8:
version "4.0.8"
resolved "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz"
@@ -11229,6 +11137,11 @@ parchment@^1.1.2, parchment@^1.1.4:
resolved "https://registry.npmjs.org/parchment/-/parchment-1.1.4.tgz"
integrity sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg==
+parchment@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/parchment/-/parchment-3.0.0.tgz#2e3a4ada454e1206ae76ea7afcb50e9fb517e7d6"
+ integrity sha512-HUrJFQ/StvgmXRcQ1ftY6VEZUq3jA2t9ncFN4F84J/vN0/FPpQF+8FKXb3l6fLces6q0uOHj6NJn+2xvZnxO6A==
+
parent-module@^1.0.0:
version "1.0.1"
resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz"
@@ -11380,11 +11293,6 @@ pend@~1.2.0:
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA=
-pend@~1.2.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
- integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA=
-
performance-now@^0.2.0:
version "0.2.0"
resolved "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz"
@@ -11770,6 +11678,25 @@ quill-delta@^3.6.2:
extend "^3.0.2"
fast-diff "1.1.2"
+quill-delta@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/quill-delta/-/quill-delta-5.1.0.tgz#1c4bc08f7c8e5cc4bdc88a15a1a70c1cc72d2b48"
+ integrity sha512-X74oCeRI4/p0ucjb5Ma8adTXd9Scumz367kkMK5V/IatcX6A0vlgLgKbzXWy5nZmCGeNJm2oQX0d2Eqj+ZIlCA==
+ dependencies:
+ fast-diff "^1.3.0"
+ lodash.clonedeep "^4.5.0"
+ lodash.isequal "^4.5.0"
+
+"quill2@npm:quill@^2.0.2":
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/quill/-/quill-2.0.2.tgz#5b26bc10a74e9f7fdcfdb5156b3133a3ebf0a814"
+ integrity sha512-QfazNrhMakEdRG57IoYFwffUIr04LWJxbS/ZkidRFXYCQt63c1gK6Z7IHUXMx/Vh25WgPBU42oBaNzQ0K1R/xw==
+ dependencies:
+ eventemitter3 "^5.0.1"
+ lodash-es "^4.17.21"
+ parchment "^3.0.0"
+ quill-delta "^5.1.0"
+
quill@^1.3.7:
version "1.3.7"
resolved "https://registry.npmjs.org/quill/-/quill-1.3.7.tgz"
@@ -11819,16 +11746,6 @@ range-parser@^1.2.1, range-parser@~1.2.1:
resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz"
integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
-raw-body@2.5.1:
- version "2.5.1"
- resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857"
- integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==
- dependencies:
- bytes "3.1.2"
- http-errors "2.0.0"
- iconv-lite "0.4.24"
- unpipe "1.0.0"
-
raw-body@2.5.2:
version "2.5.2"
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a"
@@ -11839,16 +11756,6 @@ raw-body@2.5.2:
iconv-lite "0.4.24"
unpipe "1.0.0"
-raw-body@2.5.1:
- version "2.5.1"
- resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857"
- integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==
- dependencies:
- bytes "3.1.2"
- http-errors "2.0.0"
- iconv-lite "0.4.24"
- unpipe "1.0.0"
-
raw-loader@^4.0.2:
version "4.0.2"
resolved "https://registry.npmjs.org/raw-loader/-/raw-loader-4.0.2.tgz"
@@ -12674,18 +12581,13 @@ react-modal-resizable-draggable@^0.1.6:
react-motion "^0.5.2"
react-transition-group "^4.4.1"
-react-molviewer@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/react-molviewer/-/react-molviewer-1.0.0.tgz#08e1ae5919849541cb4c5e61d3886c9fefd6eccc"
- integrity sha512-sHWjtTm//nOf5nUtGJz4JL7heyYg1Cbwf8ittbpTCHruysQb6FTAfCNpPKp//6czg7KJK134QkZNal33MZQ9Dg==
+react-molviewer@^1.1.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/react-molviewer/-/react-molviewer-1.1.3.tgz#80183af3904d0ab6560762f2980d27c069fade59"
+ integrity sha512-muxVRJTEr+y2iOuztrQW3UtZh7ge5zhwskuo2h+w6GjEg1A4qva6NvOVRr95RiNP/PJtxgnO7NOowzUx9wzjJQ==
dependencies:
- express "^4.18.2"
- numeral "^2.0.6"
prop-types "^15.8.1"
react "^17.0.2"
- react-dom "^17.0.2"
- react-router-dom "^6.14.1"
- uuid "^9.0.0"
react-motion@^0.5.2:
version "0.5.2"
@@ -12839,21 +12741,6 @@ react-resizable-box@^2.0.4:
resolved "https://registry.yarnpkg.com/react-resizable-box/-/react-resizable-box-2.1.0.tgz#8bba081b5adbe2af0be4768c4f1de6a84a423aad"
integrity sha512-0U3kQfVyKnxCPdu/QWC4n/W8DvJmy+EGdq2lBSq2KvEnzhC1E0d/whZCz7PQ/Lq20RdPbHvn5/A2BtQgwcyCFg==
-react-router-dom@^6.14.1:
- version "6.20.0"
- resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.20.0.tgz#7b9527a1e29c7fb90736a5f89d54ca01f40e264b"
- integrity sha512-CbcKjEyiSVpA6UtCHOIYLUYn/UJfwzp55va4yEfpk7JBN3GPqWfHrdLkAvNCcpXr8QoihcDMuk0dzWZxtlB/mQ==
- dependencies:
- "@remix-run/router" "1.13.0"
- react-router "6.20.0"
-
-react-router@6.20.0:
- version "6.20.0"
- resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.20.0.tgz#4275a3567ecc55f7703073158048db10096bb539"
- integrity sha512-pVvzsSsgUxxtuNfTHC4IxjATs10UaAtvLGVSA1tbUE4GDaOSU1Esu2xF5nWLz7KPiMuW8BJWuPFdlGYJ7/rW0w==
- dependencies:
- "@remix-run/router" "1.13.0"
-
"react-select3@npm:react-select@^3.1.1":
version "3.2.0"
resolved "https://registry.npmjs.org/react-select/-/react-select-3.2.0.tgz"
@@ -13396,10 +13283,10 @@ replace-in-file@^6.3.5:
glob "^7.2.0"
yargs "^17.2.1"
-repo-review-ui@^0.0.14:
- version "0.0.14"
- resolved "https://registry.yarnpkg.com/repo-review-ui/-/repo-review-ui-0.0.14.tgz#88c432d632e5d4bc92db5f23a0c2c4df61c5de9d"
- integrity sha512-lt6NtIiJYgdwFYjOGuQ0ITcxXdc6FqmlKGRc/+MYlIGDfsuACaYko7rJ5GuA4oNTv4PtcjTa4JpFiUewLd0yPg==
+repo-review-ui@^0.0.17:
+ version "0.0.17"
+ resolved "https://registry.yarnpkg.com/repo-review-ui/-/repo-review-ui-0.0.17.tgz#c79f6ba6910f762cc779641c4fda020f8c82b538"
+ integrity sha512-kinVu0DbJ9q1aymsWWTze58arzCNVK7S+f2minxBHxnClLmYyDQp59zTIRnAr8zM1nCZpQ+9kz77PJF6wYWZxw==
dependencies:
"@fortawesome/fontawesome-svg-core" "^6.1.2"
"@fortawesome/free-regular-svg-icons" "^6.1.2"
@@ -13754,25 +13641,6 @@ send@0.18.0:
range-parser "~1.2.1"
statuses "2.0.1"
-send@0.18.0:
- version "0.18.0"
- resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be"
- integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==
- dependencies:
- debug "2.6.9"
- depd "2.0.0"
- destroy "1.2.0"
- encodeurl "~1.0.2"
- escape-html "~1.0.3"
- etag "~1.8.1"
- fresh "0.5.2"
- http-errors "2.0.0"
- mime "1.6.0"
- ms "2.1.3"
- on-finished "2.4.1"
- range-parser "~1.2.1"
- statuses "2.0.1"
-
serialize-javascript@6.0.0, serialize-javascript@^6.0.0:
version "6.0.0"
resolved "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz"
@@ -13803,16 +13671,6 @@ serve-static@1.15.0:
parseurl "~1.3.3"
send "0.18.0"
-serve-static@1.15.0:
- version "1.15.0"
- resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540"
- integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==
- dependencies:
- encodeurl "~1.0.2"
- escape-html "~1.0.3"
- parseurl "~1.3.3"
- send "0.18.0"
-
set-blocking@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz"