diff --git a/.babelrc.bak b/.babelrc.bak deleted file mode 100644 index e976af7a6..000000000 --- a/.babelrc.bak +++ /dev/null @@ -1,10 +0,0 @@ -{ - "presets": [ - "@babel/preset-env", - "@babel/preset-react" - ], - "plugins": [ - "@babel/plugin-proposal-object-rest-spread", - "@babel/plugin-proposal-class-properties" - ] -} diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index ce79844da..000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: bug -assignees: '' - ---- - -- I am an ELNer -- I am a developer -- I am a system/ELN admin - -# Expected Behavior - -Please describe the behavior you are expecting - -# Current Behavior - -What is the current behavior? - -# Failure Information - -Please help provide information about the failure if this is a bug. If it is not a bug, please remove the rest of this template. - -## Steps to Reproduce - -Please provide detailed steps for reproducing the issue. - -1. step 1 -2. step 2 -3. you get it... - -## Context - -Please provide any relevant information about your setup. This is important in case the issue is not reproducible except for under certain conditions. - - - OS: [e.g. iOS] - - Chrome Browser - - Firefox Browser - - ELN version, or branch, or commit - - -## Failure Logs / Screenshots - -Include any relevant log snippets or files here. diff --git a/.github/ISSUE_TEMPLATE/enhancement.md b/.github/ISSUE_TEMPLATE/enhancement.md deleted file mode 100644 index c279b3b30..000000000 --- a/.github/ISSUE_TEMPLATE/enhancement.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -name: enhancement Issue -about: About improving existing feature -title: '' -labels: '' -assignees: '' - ---- - -- I am an ELNer / developer / system/ELN admin - -# Description -Please provide a description of: -1. what enhancement you suggest -2. why you think it is needed (You might provide a use case here) - -# Image -Please provide an image of how you expect your suggestion to appear in the ELN. -Details of the location of the enhancement and any related buttons could be useful in this image. diff --git a/.github/ISSUE_TEMPLATE/feature.md b/.github/ISSUE_TEMPLATE/feature.md deleted file mode 100644 index 754e0d30f..000000000 --- a/.github/ISSUE_TEMPLATE/feature.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -name: Feature -about: Getting new or revamping old feature -title: '' -labels: 'feature' -assignees: '' - ---- -- I am an ELNer / developer / system/ELN admin - -# Description -Please provide a description of: -1. what feature you suggest -2. why you think it is needed (You might provide a use case here) - -# Image -Please provide an image of how you expect your suggestion to appear in the ELN. -Details of the location of the feautre and any related buttons could be useful in this image. diff --git a/.github/ISSUE_TEMPLATE/greater-code.md b/.github/ISSUE_TEMPLATE/greater-code.md deleted file mode 100644 index 23406f64e..000000000 --- a/.github/ISSUE_TEMPLATE/greater-code.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -name: Greater Code -about: About greater coding -title: '' -labels: 'greater code' -assignees: '' - ---- - - -- refactoring -- dependencies -- linting -- more testing - diff --git a/.github/ISSUE_TEMPLATE/installation-issue.md b/.github/ISSUE_TEMPLATE/installation-issue.md deleted file mode 100644 index 24a3ee41b..000000000 --- a/.github/ISSUE_TEMPLATE/installation-issue.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -name: Installation Issue -about: About getting things running -title: '' -labels: '' -assignees: '' - ---- - -I am setting up a -- production -- development environment - - -# Failure Information - -Please help provide information about the failure if this is a bug. If it is not a bug, please remove the rest of this template. - -## Context - -Please provide any relevant information about your setup. This is important in case the issue is not reproducible except for under certain conditions. - - -I am -- using Docker -- not using Docker - -- OS: -- ELN version, or branch, or commit - -## Steps to Reproduce - -1. step 1 -2. step 2 -3. you get it... - -## Failure Logs / Screenshots - -Include any relevant log snippets or files here. diff --git a/.github/PULL_REQUEST_TEMPLATE/generic.md b/.github/PULL_REQUEST_TEMPLATE/generic.md deleted file mode 100644 index 00c8d0573..000000000 --- a/.github/PULL_REQUEST_TEMPLATE/generic.md +++ /dev/null @@ -1,21 +0,0 @@ - - -- [ ] rather 1-story 1-commit than sub-atomic commits - -- [ ] commit title is meaningful => git history search - -- [ ] commit description is helpful => helps the reviewer to understand the changes - -- [ ] code is up-to-date with the latest developments of the target branch (rebased to it or whatever) => :fast_forward:-merge for linear history is favoured - -- [ ] added code is linted - -- [ ] tests are passing (at least locally): we still have some random test failure on CI. thinking of asking spec/examples.txt to be commited - -- [ ] in case the changes are visible to the end-user,  video or screenshots should be added to the PR => helps with user testing - -- [ ] testing coverage is improved. - -- [ ] CHANGELOG :  add a bullet point on top (optional: reference to github issue/PR ) - -- [ ] parallele PR for documentation  on docusaurus  if the feature/fix is tagged for a release diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md deleted file mode 100644 index a02ebfb7b..000000000 --- a/.github/pull_request_template.md +++ /dev/null @@ -1,21 +0,0 @@ - - -- [ ] rather 1-story 1-commit than sub-atomic commits - -- [ ] commit title is meaningful => git history search - -- [ ] commit description is helpful => helps the reviewer to understand the changes - -- [ ] code is up-to-date with the latest developments of the target branch (rebased to it or whatever) => :fast_forward:-merge for linear history is favoured - -- [ ] added code is linted - -- [ ] tests are passing (at least locally): we still have some random test failure on CI. thinking of asking spec/examples.txt to be commited - -- [ ] in case the changes are visible to the end-user,  video or screenshots should be added to the PR => helps with user testing - -- [ ] testing coverage improvement is improved. - -- [ ] CHANGELOG :  add a bullet point on top (optional: reference to github issue/PR ) - -- [ ] parallele PR for documentation  on docusaurus  if the feature/fix is tagged for a release diff --git a/.gitignore b/.gitignore index 875903d63..da6d6c141 100644 --- a/.gitignore +++ b/.gitignore @@ -124,6 +124,9 @@ !/public/json/pictograms.json +/public/jsmol/* +!/public/jsmol/.keep + /public/jsmol/* !/public/jsmol/.keep diff --git a/.gitlab/merge_request_templates/generic.md b/.gitlab/merge_request_templates/generic.md deleted file mode 100644 index 056bc73b5..000000000 --- a/.gitlab/merge_request_templates/generic.md +++ /dev/null @@ -1,12 +0,0 @@ - - -- [ ] rather 1-story 1-commit than sub-atomic commits -- [ ] commit title is meaningful => git history search -- [ ] commit description is helpful => helps the reviewer to understand the changes -- [ ] code is up-to-date with the latest developments of the target branch (rebased to it or whatever) => :fast_forward:-merge for linear history is favoured -- [ ] added code is linted -- [ ] tests are passing (at least locally): we still have some random test failure on CI. thinking of asking spec/examples.txt to be commited -- [ ] in case the changes are visible to the end-user,  video or screenshots should be added to the PR => helps with user testing -- [ ] testing coverage improvement is more than welcome. -- [ ] CHANGELOG :  add a bullet point on top (optional: reference to github issue/PR ) -- [ ] (parallele PR for documentation  on docusaurus  if the feature/fix is tagged for a release) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c63d6cac..b51cd8a33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ -# Chemotion_ELN Changelog + +# Chemotion_Repository Changelog ## [v1.8.0] > (2023-10-24) @@ -494,72 +495,32 @@ ## [v1.0.2] > 2021-10-19: https://github.com/ComPlat/chemotion_ELN/releases/tag/v1.0.2 -* Fixes - * DB migration: fix typo that could prevent updating from 0.9.1 - * structure editor: chemdrawjs-20 support - * gate transfer: attachment checksum bckwrd compatibility - - -## [v1.0.1] -> 2021-10-11: https://github.com/ComPlat/chemotion_ELN/releases/tag/v1.0.1 - -* Improvements - * LCSS display info only from ECHA source - * QuillEditor: added special characters menu for ResearchPlan - -* Fixes - * postinstall rewrite of some imports in citation.js to fix wbpk assets compilation - * Reaction SVG refresh after editing or adding samples - * Fix Cron Jobs for LCSS and Pubchem Info - * dev: fix reaction seeds - - -## [v1.0.0] -> 2021-09-22: https://github.com/ComPlat/chemotion_ELN/releases/tag/v1.0.0 - -* Improvements - * Resize private note field and remove save button [Private note rework #534](https://github.com/ComPlat/chemotion_ELN/issues/534) - * Add hover over information to generic elements' symbols [add hover over information to generic elements' symbols #524](https://github.com/ComPlat/chemotion_ELN/issues/524) - * Structure editor configuration [documentation](https://www.chemotion.net/chemotionsaurus/docs/eln/settings#structure-editor) - -* Fixes - * Adapt install_development.sh to Rails 5 environment [adapt install_development.sh to Rails 5 environment #530](https://github.com/ComPlat/chemotion_ELN/pull/530) - * Tab headers in navigation items are italic [Tab headers in navigation items are italic #500](https://github.com/ComPlat/chemotion_ELN/issues/500) - * Reaction svg shrinking or dedoubling - * Reaction svg size become smaller and the svg is overlapping - * Prevent hidden cell from being added to visible layout in tabslayout - * QC curation tab is not working - - -## [v1.0.0-beta] -> 2021-08-26 - -* Updates - * upd to rails from 4.2 to 5.2 - * now using yarn instead of npm, and webpack instead of browserify - -* New features and improvements - * Private notes for samples - * Generic elements/segments/datasets - - Element details tab layout: segment tab to show if data present [Element details tab layout: segment tab to show if data present #506](https://github.com/ComPlat/chemotion_ELN/issues/506) - - In user view, enable the sorting of lines of a table [generic elements, segments and analyses #480](https://github.com/ComPlat/chemotion_ELN/issues/480) - - Add samples to generic element in table #461 [Add samples to generic element in table #461](https://github.com/ComPlat/chemotion_ELN/issues/461) - - Generic element/segment units (Joule) [generic element/segment units (Joule) #457](https://github.com/ComPlat/chemotion_ELN/issues/457) - - Administrator can export/import the generic template [generic template upload and download #444](https://github.com/ComPlat/chemotion_ELN/issues/444) - - Revision control, track changes of the template and user inputs [generic revision feature #443](https://github.com/ComPlat/chemotion_ELN/issues/443) - - Drag and drop sample/molecule to the table [ELN Adminstration/generic elements: Create a table for drag and drop Sample/Molecule #437](https://github.com/ComPlat/chemotion_ELN/issues/437) - - new units for generic sections [new units for generic sections #436](https://github.com/ComPlat/chemotion_ELN/issues/436) - - new units for generic sections [new units for generic sections #434](https://github.com/ComPlat/chemotion_ELN/issues/434) - - Add new field type: Upload in generic element/segment #400 [Upload option in generic element/segment #400](https://github.com/ComPlat/chemotion_ELN/issues/400) - * Send welcome email for new users [Customized welcome email to new user #483](https://github.com/ComPlat/chemotion_ELN/issues/483) - * Instance customizable home page as MD file [Display customized welcome message at home page #470](https://github.com/ComPlat/chemotion_ELN/issues/470) - * Decoupled sample - - Remove name in scheme for decoupled sample [remove name in scheme for decoupled sample #465](https://github.com/ComPlat/chemotion_ELN/issues/465) - - Add "undefined structure" as default value for decoupled samples [Add "undefined structure" as default value for decoupled samples #463](https://github.com/ComPlat/chemotion_ELN/issues/463) - - Provide table function in generic element/segment [table function #414](https://github.com/ComPlat/chemotion_ELN/issues/414) - * Revamp of analytics inbox - * Report template management - * Device metadata +## [1.1.0] +> 2023-06-12 + +* Enhancements: + + * Integration of Molecule Archive to enhance data visibility and management capabilities. + * Support for assigning Collection DOIs, enabling persistent identification of collections. + * Shibboleth support for streamlined authentication and authorization processes. + * JSON-LD format support for enriched metadata representation. + * Open API for convenient downloading of metadata in JSON-LD format. + * Addition of group leader review functionality, facilitating improved collaboration and oversight. + * Support for publishing MOF reactions. + * Introduction of the Converter service, enabling format conversion for data. + * Introduction of the Ketcher backend service. + * Enriched metadata support for various types of published samples. + * Redesigned sample representation on the landing page for improved usability. + * Support for ORCID authentication, providing seamless user identification. + * Upgraded Chemspectra function. + * Styling improvements to enhance the user experience. + +* Bug Fixes: + + * Addressed an issue with the exporting function when data includes hyperlinks. + * Resolved a QC issue related to the usage of 'mass spectrometry' and 'IR'. + * Corrected the nmrium aasm_state to ensure accurate representation. + * Fixed an issue where the embargo job would get stuck if the mail server did not respond. * Fixes * rename chemotion.net to chemotion-repository.net in the Collection Bar [rename chemotion.net to chemotion-repository.net in the Collection Bar #515](https://github.com/ComPlat/chemotion_ELN/issues/515) diff --git a/CITATION.cff b/CITATION.cff deleted file mode 100644 index 0e948137c..000000000 --- a/CITATION.cff +++ /dev/null @@ -1,31 +0,0 @@ -cff-version: 1.2.0 -message: "Please cite this software as specified in the 'preferred-citation' section below." -title: "Chemotion Electronic Lab Notebook (ELN)" -authors: - - name: "Chemotion ELN contributors" -url: "https://github.com/ComPlat/chemotion_ELN" -preferred-citation: - type: article - authors: - - family-names: "Tremouilhac" - given-names: "Pierre" - - family-names: "Nguyen" - given-names: "An" - - family-names: "Huang" - given-names: "Yu-Chieh" - - family-names: "Kotov" - given-names: "Serhii" - - family-names: "Lütjohann" - given-names: "Dominic Sebastian" - - family-names: "Hübsch" - given-names: "Florian" - - family-names: "Jung" - given-names: "Nicole" - - family-names: "Bräse" - given-names: "Stefan" - doi: "10.1186/s13321-017-0240-0" - journal: "Journal of Cheminformatics" - title: "Chemotion ELN: an Open Source electronic lab notebook for chemists in academia" - issue: 1 - volume: 9 - year: 2017 diff --git a/Dockerfile.focal.gitlab-ci b/Dockerfile.focal.gitlab-ci deleted file mode 100644 index 8ed0f43fc..000000000 --- a/Dockerfile.focal.gitlab-ci +++ /dev/null @@ -1,81 +0,0 @@ -FROM ubuntu:focal - -ARG DEBIAN_FRONTEND=noninteractive -ARG VRUBY=2.7.7 -ARG VNODE=14.21.3 -ARG VNODENEXT=16.16.0 - -RUN set -xe && apt-get update -yqqq --fix-missing && apt-get upgrade -y -RUN apt update && apt-get install -yqq --fix-missing bash ca-certificates wget apt-transport-https git gpg\ - imagemagick libmagic-dev libmagickcore-dev libmagickwand-dev curl gnupg2 \ - build-essential nodejs sudo postgresql-client libappindicator1 swig \ - gconf-service libasound2 libgconf-2-4 cmake \ - libnspr4 libnss3 libpango1.0-0 libxss1 xdg-utils tzdata libpq-dev \ - gtk2-engines-pixbuf \ - libssl-dev libreadline-dev\ - unzip openssh-client \ - python-dev libsqlite3-dev libboost-all-dev p7zip-full \ - xfonts-cyrillic xfonts-100dpi xfonts-75dpi xfonts-base xfonts-scalable \ - fonts-crosextra-caladea fonts-crosextra-carlito \ - fonts-dejavu fonts-dejavu-core fonts-dejavu-extra fonts-liberation2 fonts-liberation \ - fonts-linuxlibertine fonts-noto-core fonts-noto-extra fonts-noto-ui-core \ - fonts-opensymbol fonts-sil-gentium fonts-sil-gentium-basic -RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \ - && echo "deb http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google.list \ - && apt-get update -yqqq && apt-get -y install google-chrome-stable \ - && CHROMEDRIVER_VERSION=`curl -sS chromedriver.storage.googleapis.com/LATEST_RELEASE` \ - && mkdir -p /opt/chromedriver-$CHROMEDRIVER_VERSION \ - && curl -sS -o /tmp/chromedriver_linux64.zip http://chromedriver.storage.googleapis.com/$CHROMEDRIVER_VERSION/chromedriver_linux64.zip \ - && unzip -qq /tmp/chromedriver_linux64.zip -d /opt/chromedriver-$CHROMEDRIVER_VERSION \ - && rm /tmp/chromedriver_linux64.zip \ - && chmod +x /opt/chromedriver-$CHROMEDRIVER_VERSION/chromedriver \ - && ln -fs /opt/chromedriver-$CHROMEDRIVER_VERSION/chromedriver /usr/local/bin/chromedriver -RUN apt-get clean \ - && rm -rf /var/lib/apt/lists/* -RUN useradd -ms /bin/bash gitlab-runner \ - && echo "gitlab-runner ALL=NOPASSWD: ALL" >> /etc/sudoers - -USER gitlab-runner -WORKDIR /home/gitlab-runner - -SHELL ["/bin/bash", "-c"] - -RUN git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.8.1 - -ENV ASDF_DIR=/home/gitlab-runner/.asdf -ENV PATH=/home/gitlab-runner/.asdf/shims:/home/gitlab-runner/.asdf/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin - -RUN asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs.git -RUN asdf install nodejs $VNODE -RUN asdf install nodejs $VNODENEXT -RUN asdf global nodejs $VNODE - -RUN asdf plugin add ruby https://github.com/asdf-vm/asdf-ruby.git -RUN asdf install ruby $VRUBY -RUN asdf global ruby $VRUBY - -RUN mkdir -p shared/node_modules -RUN ln -s shared/node_modules node_modules - - -COPY Gemfile /home/gitlab-runner/ -COPY Gemfile.lock /home/gitlab-runner/ -COPY package.json /home/gitlab-runner/ -COPY yarn.lock /home/gitlab-runner/ -COPY package_postinstall.sh /home/gitlab-runner/ - -RUN sudo chmod 666 Gemfile.lock -RUN sudo chmod 666 yarn.lock - -RUN /bin/bash -l -c "npm install -g yarn" -RUN /bin/bash -l -c "yarn install" - - -RUN /bin/bash -l -c "gem install bundler -v 2.1.4 && bundle install " -#RUN /bin/bash -l -c "chromedriver-update" -RUN sudo apt -yy remove lib*-dev -RUN sudo apt-get -y --autoremove --fix-missing install \ - libboost-serialization1.71.0 \ - libboost-iostreams1.71.0 \ - libboost-system1.71.0 - diff --git a/Gemfile.lock b/Gemfile.lock index 7195dd110..aec1e2235 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -503,7 +503,7 @@ GEM net-smtp (0.3.3) net-protocol net-ssh (6.1.0) - nio4r (2.5.9) + nio4r (2.7.0) nokogiri (1.15.4) mini_portile2 (~> 2.8.2) racc (~> 1.4) @@ -576,7 +576,7 @@ GEM pry-rails (0.3.9) pry (>= 0.10.4) public_suffix (5.0.0) - puma (5.6.7) + puma (5.6.8) nio4r (~> 2.0) pundit (2.2.0) activesupport (>= 3.0.0) @@ -992,4 +992,4 @@ DEPENDENCIES yaml_db BUNDLED WITH - 2.2.29 + 1.17.3 diff --git a/Gemfile.plugin b/Gemfile.plugin deleted file mode 100644 index 3d89e5213..000000000 --- a/Gemfile.plugin +++ /dev/null @@ -1 +0,0 @@ -# gem 'nmr_sim', git: 'https://github.com/ComPlat/nmr_sim', ref: 'e2f91776aafd8eb1fa9d88c8ec2291b02201f222', group: [:plugins,:development,:production] diff --git a/INSTALL.md b/INSTALL.md index 8c87ecf85..fb2e33412 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -3,6 +3,7 @@ see [docs on chemotion.net](https://chemotion.net/docs/eln/install_configure/) +* To enable email confirmation, uncomment ":confirmable" at line 5 of `app/models/user.rb`, stop the `docker-compose` by `docker-compose stop` and start `docker-compose`. # Basic Development Setup diff --git a/README.md b/README.md index e0d6bf087..42b907e64 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,10 @@ of the **[Karlsruhe Institute of Technology]**. ## User Documentation +This project has been funded by the **[DFG]**. + +[![DFG Logo]][DFG] + ## User Documentation see online **⸢ [Documentation] ⸥** diff --git a/app/api/chemotion/attachment_api.rb b/app/api/chemotion/attachment_api.rb index ca73c3128..db9323d3d 100644 --- a/app/api/chemotion/attachment_api.rb +++ b/app/api/chemotion/attachment_api.rb @@ -158,6 +158,13 @@ def validate_uuid_format(uuid) (ElementPolicy.new(current_user, element).read? && ElementPermissionProxy.new(current_user, element, user_ids).read_dataset?) end + + if !can_dwnld && @attachment.attachable_type == 'SegmentProps' + element = Segment.find(@attachment.attachable_id)&.element + can_dwnld = @attachment.created_for == current_user.id || + (ElementPolicy.new(current_user, element).read? && + ElementPermissionProxy.new(current_user, element, user_ids).read_dataset?) + end end error!('401 Unauthorized', 401) unless can_dwnld end @@ -569,6 +576,35 @@ def validate_uuid_format(uuid) end end + desc 'Regenerate edited spectra' + params do + requires :edited, type: Array[Integer] + optional :molfile, type: String + end + post 'regenerate_edited_spectrum' do + pm = to_rails_snake_case(params) + pm[:edited].each do |g_id| + att = Attachment.find(g_id) + next unless att + can_edit = writable?(att) + if can_edit + abs_path = att.abs_path + molfile = pm[:molfile] + result = Tempfile.create('molfile') do |t_molfile| + t_molfile.write(molfile) + t_molfile.rewind + Chemotion::Jcamp::RegenerateJcamp.spectrum( + abs_path, t_molfile.path + ) + end + + att.file_data = result + att.rewrite_file_data! + { status: true } + end + end + end + desc 'Save spectra to file' params do requires :attachment_id, type: Integer diff --git a/app/api/chemotion/gate_api.rb b/app/api/chemotion/gate_api.rb index 906697f1a..07eb980a6 100644 --- a/app/api/chemotion/gate_api.rb +++ b/app/api/chemotion/gate_api.rb @@ -159,6 +159,78 @@ def self.parsed?(value) end + desc <<~DESC + receive sample and reaction data from a remote eln and import them into a designated + collection according to JWT info. (authentication through JWT) + DESC + namespace :receiving_zip do + params do + requires :data, type: File + end + + before do + http_token = if request.headers['Authorization'].present? + request.headers['Authorization'].split(' ').last + end + error!('Unauthorized', 401) unless http_token + secret = Rails.application.secrets.secret_key_base + begin + @auth_token = HashWithIndifferentAccess.new( + JWT.decode(http_token, secret)[0] + ) + rescue JWT::VerificationError, JWT::DecodeError, JWT::ExpiredSignature => e + error!("#{e}", 401) + end + @user = Person.find_by(email: @auth_token[:iss]) + error!('Unauthorized', 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 + + @origin = @auth_token["origin"] + end + + post do + db_file = params[:data]&.fetch('tempfile', nil) + # file = params[:file] + tempfile = db_file + att = Attachment.new( + filename: params[:data][:filename], + key: File.basename(tempfile.path), + file_path: tempfile, + created_by: @user.id, + created_for: @user.id, + content_type: 'application/zip' + ) + begin + att.save! + ensure + tempfile.close + tempfile.unlink + end + + begin + import = Import::ImportCollections.new(att, @user.id, true, @collection.id, @origin) + import.extract + import.import! + rescue => e + Delayed::Worker.logger.error e + Message.create_msg_notification( + channel_subject: Channel::COLLECTION_ZIP_FAIL, + message_from: @user&.id, + data_args: { col_labels: '', operation: 'import' }, + autoDismiss: 5 + ) + @success = false + ensure + att&.destroy! + end + status(200) + end + end + + desc <<~DESC receive sample and reaction data from a remote eln and import them into a designated collection according to JWT info. (authentication through JWT) diff --git a/app/api/chemotion/generic_element_api.rb b/app/api/chemotion/generic_element_api.rb index 021461285..8f996e27a 100644 --- a/app/api/chemotion/generic_element_api.rb +++ b/app/api/chemotion/generic_element_api.rb @@ -189,11 +189,11 @@ class GenericElementAPI < Grape::API post do attach_ary = [] att_ary = create_uploads('Element', params[:att_id], params[:elfiles], params[:elInfo], current_user.id) if params[:elfiles].present? && params[:elInfo].present? - (attach_ary << att_ary).flatten! unless att_ary&.empty? + (attach_ary << att_ary).flatten! unless att_ary.blank? att_ary = create_uploads('Segment', params[:att_id], params[:sefiles], params[:seInfo], current_user.id) if params[:sefiles].present? && params[:seInfo].present? - (attach_ary << att_ary).flatten! unless att_ary&.empty? + (attach_ary << att_ary).flatten! unless att_ary.blank? att_ary = create_attachments(params[:attfiles], params[:delfiles], params[:att_type], params[:att_id], current_user.id) if params[:attfiles].present? || params[:delfiles].present? - (attach_ary << att_ary).flatten! unless att_ary&.empty? + (attach_ary << att_ary).flatten! unless att_ary.blank? TransferThumbnailToPublicJob.set(queue: "transfer_thumbnail_to_public_#{current_user.id}").perform_now(attach_ary) unless attach_ary.empty? TransferFileFromTmpJob.set(queue: "transfer_file_from_tmp_#{current_user.id}").perform_now(attach_ary) unless attach_ary.empty? true diff --git a/app/api/chemotion/public_api.rb b/app/api/chemotion/public_api.rb index 93d1ae817..22ce38320 100644 --- a/app/api/chemotion/public_api.rb +++ b/app/api/chemotion/public_api.rb @@ -8,26 +8,26 @@ module Chemotion # API for Public data class PublicAPI < Grape::API include Grape::Kaminari - helpers do - def send_notification(attachment, user, status, has_error = false) - data_args = { 'filename': attachment.filename, 'comment': 'the file has been updated' } - level = 'success' - if has_error - data_args['comment'] = ' an error has occurred, the file is not changed.' - level = 'error' - elsif status == 4 - data_args['comment'] = ' file has not changed.' - level = 'info' - elsif @status == 7 - data_args['comment'] = ' an error has occurred while force saving the document, please review your changes.' - level = 'error' - end - message = Message.create_msg_notification( - channel_subject: Channel::EDITOR_CALLBACK, message_from: user.id, - data_args: data_args, attach_id: attachment.id, research_plan_id: attachment.attachable_id, level: level - ) - end - end + # helpers do + # def send_notification(attachment, user, status, has_error = false) + # data_args = { 'filename': attachment.filename, 'comment': 'the file has been updated' } + # level = 'success' + # if has_error + # data_args['comment'] = ' an error has occurred, the file is not changed.' + # level = 'error' + # elsif status == 4 + # data_args['comment'] = ' file has not changed.' + # level = 'info' + # elsif @status == 7 + # data_args['comment'] = ' an error has occurred while force saving the document, please review your changes.' + # level = 'error' + # end + # message = Message.create_msg_notification( + # channel_subject: Channel::EDITOR_CALLBACK, message_from: user.id, + # data_args: data_args, attach_id: attachment.id, research_plan_id: attachment.attachable_id, level: level + # ) + # end + # end helpers CompoundHelpers helpers PublicHelpers @@ -1529,7 +1529,7 @@ def query_ontologies(name) pub_coll = Collection.public_collection if current_user coll = SyncCollectionsUser.find_by(user_id: current_user.id, collection_id: pub_coll.id) - { id: coll.id, is_sync_to_me: true } + { id: coll&.id, is_sync_to_me: true } else { id: nil } end @@ -1605,11 +1605,11 @@ def query_ontologies(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] + optional :adv_type, type: String, desc: 'advanced search type', values: %w[Authors Ontologies Embargo] optional :adv_val, type: Array[String], desc: 'advanced search value', regexp: /^(\d+|([[:alpha:]]+:\d+))$/ end paginate per_page: 10, offset: 0, max_per_page: 100 - get '/', each_serializer: MoleculeGuestListSerializer do + get '/' do public_collection_id = Collection.public_collection_id params[:adv_val] adv_search = ' ' @@ -1625,11 +1625,17 @@ def query_ontologies(name) INNER JOIN publication_ontologies pub on pub.element_id = samples.id and pub.element_type = 'Sample' and term_id in ('#{params[:adv_val].join("','")}') SQL + when 'Embargo' + param_sql = ActiveRecord::Base.send(:sanitize_sql_array, [' css.collection_id in (?)', params[:adv_val].map(&:to_i).join(',')]) + adv_search = <<~SQL + INNER JOIN collections_samples css on css.sample_id = samples.id and css.deleted_at ISNULL + and #{param_sql} + SQL end end sample_join = <<~SQL INNER JOIN ( - SELECT molecule_id, published_at max_published_at, sample_svg_file + 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 @@ -1641,38 +1647,41 @@ def query_ontologies(name) )) s where rownum = 1 ) s on s.molecule_id = molecules.id SQL - xvial_count = <<~SQL - inner join ( - select count(e.id) as xvial_count, m.id as molecule_id from molecules m - inner join samples s on s.molecule_id = m.id - inner join publications p on p.element_type='Sample' and p.element_id=s.id and p.deleted_at isnull - left outer join element_tags e on e.taggable_id = s.id and (e.taggable_data -> 'xvial' is not null and e.taggable_data -> 'xvial' ->> 'num' != '') - group by m.id - ) c on c.molecule_id = molecules.id + + embargo_sql = <<~SQL + molecules.*, sample_svg_file, sid, + (select count(*) from publication_ontologies po where po.element_type = 'Sample' and po.element_id = sid) as ana_cnt, + (select "collections".label from "collections" inner join collections_samples cs on collections.id = cs.collection_id + and cs.sample_id = sid where "collections"."deleted_at" is null and (ancestry in ( + select c.id::text from collections c where c.label = 'Published Elements')) order by position asc limit 1) as embargo, + (select id from publications where element_type = 'Sample' and element_id = sid and deleted_at is null) as pub_id, + (select taggable_data -> 'creators'->0->>'name' from publications where element_type = 'Sample' and element_id = sid and deleted_at is null) as author_name SQL + + list = paginate(Molecule.joins(sample_join).order("s.max_published_at desc").select(embargo_sql)) + + entities = Entities::MoleculePublicationListEntity.represent(list, serializable: true) + sids = entities.map { |e| e[:sid] } + com_config = Rails.configuration.compound_opendata - xvial_com = <<~SQL - inner join (select -1 as xvial_com, m.id molcule_id from molecules m) cod on cod.molcule_id = molecules.id + + xvial_count_sql = <<~SQL + inner join element_tags e on e.taggable_id = samples.id and (e.taggable_data -> 'xvial' is not null and e.taggable_data -> 'xvial' ->> 'num' != '') SQL - if com_config.present? - xvial_com = if com_config.allowed_uids.include?(current_user&.id) - <<~SQL - inner join ( - select count(a.x_id) as xvial_com, m.id molcule_id from molecules m left outer join com_xvial(true) a on a.x_inchikey = m.inchikey - group by m.id - ) cod on cod.molcule_id = molecules.id - SQL - else - <<~SQL - inner join (select -2 as xvial_com, m.id molcule_id from molecules m) cod on cod.molcule_id = molecules.id - SQL - end + x_cnt_ids = Sample.joins(xvial_count_sql).where(id: sids).distinct.pluck(:id) || [] + + xvial_com_sql = <<~SQL + inner join molecules m on m.id = samples.molecule_id + inner join com_xvial(true) a on a.x_inchikey = m.inchikey + SQL + x_com_ids = Sample.joins(xvial_com_sql).where(id: sids).distinct.pluck(:id) if com_config.present? && com_config.allowed_uids.include?(current_user&.id) + + + entities = entities.each do |obj| + obj[:xvial_count] = 1 if x_cnt_ids.include?(obj[:sid]) + obj[:xvial_com] = 1 if com_config.present? && com_config.allowed_uids.include?(current_user&.id) && (x_com_ids || []).include?(obj[:sid]) end - paginate(Molecule.joins(sample_join).joins(xvial_count).joins(xvial_com).order("s.max_published_at desc").select( - <<~SQL - molecules.*, sample_svg_file, xvial_count, xvial_com - SQL - )) + entities end end @@ -1683,13 +1692,12 @@ def query_ontologies(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] + optional :adv_type, type: String, desc: 'advanced search type', values: %w[Authors Ontologies Embargo] optional :adv_val, type: Array[String], desc: 'advanced search value', regexp: /^(\d+|([[:alpha:]]+:\d+))$/ 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 - get '/', each_serializer: ReactionGuestListSerializer do - + get '/' do if params[:adv_flag] === true && params[:adv_type].present? && params[:adv_val].present? case params[:adv_type] when 'Authors' @@ -1698,11 +1706,16 @@ def query_ontologies(name) and author_id in ('#{params[:adv_val].join("','")}') SQL when 'Ontologies' - str_term_id = params[:adv_val].split(',').map { |val| val}.to_s adv_search = <<~SQL INNER JOIN publication_ontologies pub on pub.element_id = reactions.id and pub.element_type = 'Reaction' and term_id in ('#{params[:adv_val].join("','")}') SQL + when 'Embargo' + param_sql = ActiveRecord::Base.send(:sanitize_sql_array, [' cr.collection_id in (?)', params[:adv_val].map(&:to_i).join(',')]) + adv_search = <<~SQL + INNER JOIN collections_reactions cr on cr.reaction_id = reactions.id and cr.deleted_at is null + and #{param_sql} + SQL else adv_search = ' ' end @@ -1710,11 +1723,43 @@ def query_ontologies(name) adv_search = ' ' end + com_config = Rails.configuration.compound_opendata + embargo_sql = <<~SQL + reactions.id, reactions.name, reactions.reaction_svg_file, publications.id as pub_id, publications.taggable_data, + (select count(*) from publication_ontologies po where po.element_type = 'Reaction' and po.element_id = reactions.id) as ana_cnt, + (select "collections".label from "collections" inner join collections_reactions cr on collections.id = cr.collection_id + and cr.reaction_id = reactions.id where "collections"."deleted_at" is null and (ancestry in ( + select c.id::text from collections c where c.label = 'Published Elements')) order by position asc limit 1) as embargo + SQL + if params[:scheme_only] - paginate(Collection.scheme_only_reactions_collection.reactions.joins(adv_search).joins(:publication).includes(:publication).references(:publication).order('publications.published_at desc').uniq) + list = paginate(Collection.scheme_only_reactions_collection.reactions.joins(adv_search).joins(:publication).select(embargo_sql).order('publications.published_at desc')) else - paginate(Collection.public_collection.reactions.joins(adv_search).joins(:publication).includes(:publication).references(:publication).order('publications.published_at desc').uniq) + list = paginate(Collection.public_collection.reactions.joins(adv_search).joins(:publication).select(embargo_sql).order('publications.published_at desc')) end + + entities = Entities::ReactionPublicationListEntity.represent(list, serializable: true) + + ids = entities.map { |e| e[:id] } + + xvial_count_sql = <<~SQL + inner join element_tags e on e.taggable_id = reactions_samples.sample_id and (e.taggable_data -> 'xvial' is not null and e.taggable_data -> 'xvial' ->> 'num' != '') + SQL + x_cnt_ids = ReactionsSample.joins(xvial_count_sql).where(type: 'ReactionsProductSample', reaction_id: ids).distinct.pluck(:reaction_id) || [] + + xvial_com_sql = <<~SQL + inner join samples s on reactions_samples.sample_id = s.id and s.deleted_at is null + inner join molecules m on m.id = s.molecule_id + inner join com_xvial(true) a on a.x_inchikey = m.inchikey + SQL + x_com_ids = ReactionsSample.joins(xvial_com_sql).where(type: 'ReactionsProductSample', reaction_id: ids).distinct.pluck(:reaction_id) if com_config.present? && com_config.allowed_uids.include?(current_user&.id) + + entities = entities.each do |obj| + obj[:xvial_count] = 1 if x_cnt_ids.include?(obj[:id]) + obj[:xvial_com] = 1 if com_config.present? && com_config.allowed_uids.include?(current_user&.id) && (x_com_ids || []).include?(obj[:id]) + end + + entities end end @@ -1805,163 +1850,90 @@ def query_ontologies(name) end end - resource :reaction do + resource :embargo do helpers RepositoryHelpers - desc "Return PUBLISHED serialized reaction" + desc "Return PUBLISHED serialized collection" params do - requires :id, type: Integer, desc: "Reaction id" + requires :id, type: Integer, desc: "collection id" end get do - r = CollectionsReaction.where(reaction_id: params[:id], collection_id: [Collection.public_collection_id, Collection.scheme_only_reactions_collection.id]) - return nil unless r.present? + pub = Publication.find_by(element_type: 'Collection', element_id: params[:id]) + { col: pub } + end + end - 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') || [] - reaction.products.each do |p| - literatures += get_literature(p.id,'Sample') - end + resource :col_list do + helpers RepositoryHelpers + get do + @embargo_collection = Collection.find(params[:collection_id]) + anasql = <<~SQL + publications.*, (select count(*) from publication_ontologies po where po.element_type = publications.element_type and po.element_id = publications.element_id) as ana_cnt + SQL + sample_list = Publication.where(ancestry: nil, element: @embargo_collection.samples).select(anasql).order(updated_at: :desc) + reaction_list = Publication.where(ancestry: nil, element: @embargo_collection.reactions).select(anasql).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' - pub = Publication.find_by(element_type: 'Reaction', element_id: params[:id]) - pub_info = (pub.review.present? && pub.review['info'].present? && pub.review['info']['comment']) || '' - infos = {} - ana_infos = {} - pd_infos = {} - 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 + 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, pub_id: e.id, svg: svg_file, type: element_type, title: title, published_at: e.published_at&.strftime('%d-%m-%Y'), + published_by: u&.name, submit_at: e.updated_at, state: e.state, scheme_only: scheme_only, ana_cnt: e.ana_cnt + ) end + { elements: elements, embargo_id: params[:collection_id], current_user: { id: current_user&.id, type: current_user&.type } } + end + end - schemeList = get_reaction_table(params[:id]) - entities = Entities::ReactionEntity.represent(reaction, serializable: true) - entities[:products].each do |p| - 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? + resource :col_element do + helpers RepositoryHelpers + params do + requires :collection_id, type: Integer, desc: "collection id" + requires :el_id, type: Integer, desc: "element id" + end + get do + @embargo_collection = Collection.find(params[:collection_id]) + if params[:el_type] == 'Reaction' + return get_pub_reaction(params[:el_id]) + elsif params[:el_type] == 'Sample' + sample = Sample.find(params[:el_id]) + return get_pub_molecule(sample.molecule_id) + end + end + end - xvial = pub_product_tag['xvial'] && pub_product_tag['xvial']['num'] - next unless xvial.present? + resource :reaction do + helpers RepositoryHelpers + desc "Return PUBLISHED serialized reaction" + params do + requires :id, type: Integer, desc: "Reaction id" + end + get do + r = CollectionsReaction.where(reaction_id: params[:id], collection_id: [Collection.public_collection_id, Collection.scheme_only_reactions_collection.id]) + return nil unless r.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[: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[: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 + return get_pub_reaction(params[:id]) end 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] + optional :adv_type, type: String, desc: "advanced search type", allow_blank: true, values: %w[Authors Ontologies Embargo] optional :adv_val, type: Array[String], desc: 'advanced search value', regexp: /^(\d+|([[:alpha:]]+:\d+))$/ end get do - molecule = Molecule.find(params[:id]) - xvial_com = build_xvial_com(molecule.inchikey, current_user&.id) - pub_id = Collection.public_collection_id - if params[:adv_flag].present? && params[:adv_flag] == true && params[:adv_type].present? && params[:adv_type] == 'Authors' && params[: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 ('#{params[: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| - containers = 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(users.name_abbreviation, 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? - 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 - 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 - tag.merge(analyses: containers, literatures: literatures, sample_svg_file: s.sample_svg_file, short_label: s.short_label, - sample_id: s.id, reaction_ids: reaction_ids, sid: sid, xvial: xvial, showed_name: s.showed_name, pub_id: pub.id, ana_infos: ana_infos, pub_info: pub_info) - - 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 - } + get_pub_molecule(params[:id], params[:adv_flag], params[:adv_type], params[:adv_val]) end end diff --git a/app/api/chemotion/repository_api.rb b/app/api/chemotion/repository_api.rb index 85226b4e5..267dae3fb 100644 --- a/app/api/chemotion/repository_api.rb +++ b/app/api/chemotion/repository_api.rb @@ -607,7 +607,7 @@ def add_submission_history(root) review_info: review_info } end - end + end resource :sample do helpers RepositoryHelpers diff --git a/app/api/chemotion/sync_collection_api.rb b/app/api/chemotion/sync_collection_api.rb index 8e27a0c38..7db435540 100644 --- a/app/api/chemotion/sync_collection_api.rb +++ b/app/api/chemotion/sync_collection_api.rb @@ -59,6 +59,24 @@ class SyncCollectionAPI < Grape::API end end + namespace :publication do + desc "Return the 'All' collection of the current user" + get do + current_user.sync_published_collection + end + end + + namespace :review do + desc "Return the 'All' collection of the current user" + get do + if User.reviewer_ids.include?(current_user.id) + current_user.sync_element_to_review_collection + else + current_user.sync_reviewing_collection + end + end + end + namespace :take_ownership do desc 'Take ownership of collection with specified sync_collections_user id' params do diff --git a/app/assets/javascripts/components/PublishCommon.js b/app/assets/javascripts/components/PublishCommon.js deleted file mode 100644 index 3dc367290..000000000 --- a/app/assets/javascripts/components/PublishCommon.js +++ /dev/null @@ -1,327 +0,0 @@ -import React from 'react'; -import { - Button, - Tooltip, - OverlayTrigger, -} from 'react-bootstrap'; -import PropTypes from 'prop-types'; -import Aviator from 'aviator'; -import Sample from './models/Sample'; -import { sampleShowOrNew, reactionShow } from './routesUtils'; -import Reaction from './models/Reaction'; -import { isNmrPass, isDatasetPass } from './utils/ElementUtils'; - -const labelStyle = { - display: 'inline-block', - marginLeft: '5px', - marginRight: '5px' -}; - -const handleClick = (e, id, clickType) => { - e.preventDefault(); - e.stopPropagation(); - const uri = Aviator.getCurrentURI(); - const uriArray = uri.split(/\//); - switch (clickType) { - case 'Reaction': - Aviator.navigate(`/${uriArray[1]}/${uriArray[2]}/reaction/${id}`, { silent: true }); - reactionShow({ params: { reactionID: id } }); - break; - - default: - Aviator.navigate(`/${uriArray[1]}/${uriArray[2]}/sample/${id}`, { silent: true }); - sampleShowOrNew({ params: { sampleID: id } }); - break; - } -}; -const validateYield = (reaction) => { - const result = []; - const products = reaction.products || []; - products.forEach((product) => { - const val = ((product.equivalent || 0) * 100).toFixed(0); - if (val === '0') result.push({ name: 'product-yield', value: false, message: `${product.molecule_iupac_name}: yield is 0` }); - }); - if (result.length !== 0 && result.length === products.length) return result; - return []; -}; - -const validateMolecule = (element) => { - const validates = []; - const sample = element; - const analyses = sample.analysisArray(); - analyses.forEach((al) => { - const status = al.extended_metadata.status || ''; - const kind = al.extended_metadata.kind || ''; - if (status !== 'Confirmed') { - validates.push({ name: `analysis [${al.name}]`, value: false, message: `[${sample.name || sample.short_label}] Analysis [${al.name}]: Status must be Confirmed.` }); - } - if (kind === '' || (kind.split('|').length < 2)) { - validates.push({ name: `analysis [${al.name}]`, value: false, message: `[${sample.name || sample.short_label}] Analysis [${al.name}]: Type is invalid.` }); - } - if (!isNmrPass(al, sample)) { - validates.push({ name: `analysis [${al.name}]`, value: false, message: `[${sample.name || sample.short_label}] Analysis [${al.name}]: Content is invalid, NMR check fails.` }); - } - if (!isDatasetPass(al)) { - validates.push({ name: `analysis [${al.name}]`, value: false, message: `[${sample.name || sample.short_label}] Analysis [${al.name}]: Dataset is incomplete. Please check that: 1. for NMR, Mass, or IR analyses, at least one dataset has been attached with an image and a jcamp files. 2. the instrument field is not empty.` }); - } - }); - return validates; -}; - -const PublishBtnReaction = ({ reaction, showModal }) => { - const tagData = (reaction.tag && reaction.tag.taggable_data) || {}; - // NB set publishedId to true to hide it - const publishedId = tagData.public_reaction || (tagData.publication && tagData.publication.queued_at); - const notPublishable = reaction.notPublishable; // false or [samples] - const isDisabled = reaction.changed || reaction.isNew || !!notPublishable; - const btnTip = (reaction.changed || reaction.isNew) ? 'Publication panel cannot be open on unsaved reaction.' : 'Open the reaction publication panel'; - const btnTipNotPub = notPublishable && `Product(s) ${notPublishable.map(s => s.short_label).join()} not publishable`; - return ( - (!publishedId && !tagData.publication) ? ( - {btnTipNotPub || btnTip}} - > - - - ) : - ); -}; - -PublishBtnReaction.propTypes = { - showModal: PropTypes.func.isRequired, - reaction: PropTypes.instanceOf(Reaction).isRequired, -}; - -const PublishBtn = ({ sample, showModal }) => { - const tagData = (sample.tag && sample.tag.taggable_data) || {}; - - const publishedId = tagData.public_sample; - const isPoly = sample._contains_residues; - - return (sample.can_publish && !sample.isEdited && !publishedId && !tagData.publication) ? ( - {isPoly ? 'Cannot publish polymer structure!' : 'Open the sample publication panel'}} - > - - - ) : ; -}; - -PublishBtn.propTypes = { - showModal: PropTypes.func.isRequired, - sample: PropTypes.instanceOf(Sample).isRequired, -}; - -const ReviewPublishBtn = ({ element, showComment, validation }) => { - const tagData = (element.tag && element.tag.taggable_data) || {}; - const publishedId = tagData.public_sample || tagData.public_reaction; - const isDecline = (tagData && tagData.decline === true) || false; - const canPublish = element.can_publish || (element.type === 'reaction' && !element.notPublishable && element.is_published === false) - - const isEdit = element.type === 'reaction' ? element.changed : element.isEdited; - const reviewBtn = (canPublish && !isEdit && !publishedId && tagData.publication) ? ( - Submit for Publication} - > - - - ) : (); - const commentBtn = ((canPublish && !publishedId && tagData.publication) || isDecline) ? ( - Reviewer's comment} - > - - - ) : () - return ( - - {reviewBtn} - {commentBtn} - - ) -}; - - -const PublishedTag = ({ element }) => { - const tag = (element && element.tag) || {}; - const tagData = (tag && tag.taggable_data) || {}; - const tagType = tag.taggable_type; - const isPending = (tagData && tagData.publish_pending && tagData.publish_pending === true) || false; - let tip = ''; - let publishedId; - switch (tagType) { - case 'Reaction': - publishedId = tagData.public_reaction; - if (isPending) { - tip = 'Reaction is being reviewed'; - } else { - tip = 'Reaction has been published'; - } - break; - - default: - publishedId = tagData.public_sample; - if (isPending) { - tip = 'Sample is being reviewed'; - } else { - tip = 'Sample has been published'; - } - break; - } - return ( - publishedId ? ( - {tip}} - > -