diff --git a/.github/workflows/eslint.yml b/.github/workflows/eslint.yml index ac13cdc40a..1910be2d94 100644 --- a/.github/workflows/eslint.yml +++ b/.github/workflows/eslint.yml @@ -15,6 +15,7 @@ jobs: - uses: actions/setup-node@v2 with: cache: 'yarn' + node-version: 16 # Run yarn install for JS dependencies - name: 'Yarn Install' diff --git a/.github/workflows/mysql.yml b/.github/workflows/mysql.yml index 71d4202bd2..e561c8e8f6 100644 --- a/.github/workflows/mysql.yml +++ b/.github/workflows/mysql.yml @@ -14,18 +14,20 @@ jobs: steps: # Checkout the repo - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 # Install Ruby and run bundler - uses: ruby/setup-ruby@v1 with: ruby-version: '3.0' bundler-cache: true - + # Install Node - - uses: actions/setup-node@v2 + - uses: actions/setup-node@v3 with: + node-version: '16.6.0' cache: 'yarn' + node-version: 16 # Copy all of the example configs over - name: 'Setup the application' diff --git a/.github/workflows/postgres.yml b/.github/workflows/postgres.yml index 6a829ed760..1f2ca93ce2 100644 --- a/.github/workflows/postgres.yml +++ b/.github/workflows/postgres.yml @@ -30,18 +30,23 @@ jobs: steps: # Checkout the repo - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 # Install Ruby and run bundler - uses: ruby/setup-ruby@v1 with: ruby-version: '3.0' bundler-cache: true - + + ## - run: echo 'NODE_OPTIONS="--openssl-legacy-provider"' >> $GITHUB_ENV + ## /home/runner/runners/2.301.1/externals/node12/bin/node: --openssl-legacy-provider is not allowed in NODE_OPTIONS + # Install Node - - uses: actions/setup-node@v2 + - uses: actions/setup-node@v3 with: + node-version: '16.6.0' cache: 'yarn' + node-version: 16 # Install the Postgres developer packages - name: 'Install Postgresql Packages' diff --git a/.rubocop.yml b/.rubocop.yml index 97624066f7..b902aabe81 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -23,6 +23,11 @@ # # Try to place any new Cops under their relevant section and in alphabetical order +require: +# - rubocop-rails +# - rubocop-rspec + - rubocop-performance + AllCops: # Show the name of the cops being voilated in the feedback DisplayCopNames: true @@ -104,6 +109,12 @@ Lint/UnexpectedBlockArity: # new in 1.5 Enabled: true Lint/UnmodifiedReduceAccumulator: # new in 1.1 Enabled: true +Lint/Debugger: # new in 1.45.0 + Description: 'Check for debugger calls.' + Enabled: true + Exclude: + - 'lib/tasks/**/*' + # ----------- # - METRICS - diff --git a/CHANGELOG.md b/CHANGELOG.md index dfaf89f3a3..b28bcbab00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ **Note that the Webpacker gem has been removed in favor of jsbundling-rails.** This was done in preparation for the future migration to Rails 7. See [issue #3185](https://github.com/DMPRoadmap/roadmap/issues/3185) for more details on this change. If, after migrating to this version, you see 'Sprockets' related errors in your application you will need to rebuild you asset library. To do this run `bin/rails assets:clobber && bin/rails assets:precompile` from the project directory. +All gem and JS dependencies were also updated via `bundle update && yarn upgrade` + ### Upgrade to Ruby 3 - Upgrade to Ruby version 3.0.5 [#3225](https://github.com/DMPRoadmap/roadmap/issues/3225) @@ -16,6 +18,15 @@ - Froze `lib/deprecators/*.rb` constants that were Strings - Updated places that were incorrectly using keyword args. See [this article](https://makandracards.com/makandra/496481-changes-to-positional-and-keyword-args-in-ruby-3-0) for an overview +#### Upgraded TinyMCE to v6 + +- Upgraded TinyMCE to v6 (v5 EOL is April 20 2023) +- Adjusted JS code to conform to new TinyMCE version +- Adjusted views to work with the new version +- Updated variables.scss file to fix issue with button text/background color contrast +- Updated blocks/_tables.scss to fix issue with dropdown menu overlap against table +- updated config/initializers/assets.rb to copy over the tinymce skins and bootstrap glyphicons to the public directory so that they are accessible by TinyMCE and Bootstrap code + #### Removed webpacker gem As Webpacker is no longer maintained by the Rails community, we have replaced it by `jsbundling-rails` and `cssbundling-rails` for the Javascript & CSS compilation. @@ -43,6 +54,11 @@ With the removal of the webpacker gem, the DartSass package has been installed t - Sass variables are no longer declared globally and have to be included in files where they are used. For more detailed explanation, please refer to this video : https://www.youtube.com/watch?v=CR-a8upNjJ0 +### Introduction of RackAttack +[Rack Attack](https://github.com/rack/rack-attack) is middleware that can be used to help protect the application from malicious activity. You can establish white/black lists for specific IP addresses and also define rate limits. + +- Using Rack-attack address vulnerabilities pointed out in password reset and login: there was no request rate limit.[#3214](https://github.com/DMPRoadmap/roadmap/issues/3214) + ### Cleanup of Capybara configuration - Cleaned up Gemfile by: - removing gems that were already commented out @@ -51,16 +67,31 @@ For more detailed explanation, please refer to this video : https://www.youtube. - Cleaned up `spec/rails_helper.rb` and `spec/spec_helper.rb` - Simplified the `spec/support/capybara.rb` helper to work with the latest version of Capybara and use its built in headless Chrome driver +### Rubocop updates +- Installed rubocop-performance gem and made suggested changes +- Added lib tasks as exclusive from debugger rubocop check after rubocop upgrading to >= v1.45 [#3291](https://github.com/DMPRoadmap/roadmap/issues/3291) + +### GitHub actions updates +- Added node version specification (v16) to eslint, PostgreSQL and MySQL github action to eliminate `digital routine enveloped` error [#319](https://github.com/portagenetwork/roadmap/issues/319) + +### Enhancements +- Added enum to the funding status attribute of plan model to make the dropdown of 'funding status' being translatable +- Allow users to download both single phase and in PDF, TEXT and DOCX format. CSV file can only download single phase instead of all phases. + +### Bug Fixes + ## v4.0.2 ### Added - - Added CHANGELOG.md and Danger Github Action [#3257](https://github.com/DMPRoadmap/roadmap/issues/3257) - Added validation with custom error message in research_output.rb to ensure a user does not enter a very large value as 'Anticipated file size'. [#3161](https://github.com/DMPRoadmap/roadmap/issues/3161) - Added popover for org profile page and added explanation for public plan -### Fixed + - Added rack-attack version 6.6.1 gem. https://rubygems.org/gems/rack-attack/versions/6.6.1 +### Fixed +- Fixed an issue that was preventing uses from leaving the research output byte_size field blank +- Patched issue that was causing template visibility to default to organizationally visible after saving - Froze mail gem version [#3254](https://github.com/DMPRoadmap/roadmap/issues/3254) - Updated the CSV export so that it now includes research outputs - Updated sans-serif font used in PDF downloads to Roboto since Google API no longer offers Helvetica diff --git a/CHANGELOG_DMPOPIDoR.md b/CHANGELOG_DMPOPIDoR.md index bd7bb9ea23..d812a59100 100644 --- a/CHANGELOG_DMPOPIDoR.md +++ b/CHANGELOG_DMPOPIDoR.md @@ -2,6 +2,16 @@ **Attention** Cette liste de changements concerne les déploiements sur nos serveurs de test en interne. +## 12/05/2023 +- Mise à jour du CAPTCHA vers Recaptcha V3 : + - La validation est transparente basée sur un score calculé par Google + - Si la validation échoue, le site propose le test "Je ne suis pas un robot" + +## 28/04/2023 +- Correction du problème de pagination de la liste des contributeurs dans l'onglet Contributeurs (issue gitbucket 482) & retrait du champ de recherche +- Correction du problème d'affichage des boutons +- Correction du problème d'affichage du message indiquant qu'un élément est déjà présent dans le plan, lors de la sauvegarde dans une popup. + ## 05/04/2023 - Amélioration des fenetres de confirmation pour le partage d'un plan public, l'import ANR, l'envoi d'une notification et la suppression d'un sous fragment dans une liste de sous fragment. (Installation de la librarie Sweetalert2) - Correction d'un problème d'affichage des logos Twitter et Github dans le pied de page diff --git a/Gemfile b/Gemfile index f0f379d78d..921994bb99 100644 --- a/Gemfile +++ b/Gemfile @@ -108,9 +108,8 @@ gem 'jwt' # OO authorization for Rails (https://github.com/elabs/pundit) gem 'pundit' -# Protect your Rails and Rack apps from bad clients. Rack::Attack lets you easily decide when -# to allow, block and throttle based on properties of the request. -gem 'rack-attack' +# Gem for throttling malicious attacks +gem 'rack-attack', '~> 6.6', '>= 6.6.1' # ========== # # UI / VIEWS # @@ -290,6 +289,9 @@ group :ci, :development do # RuboCop rules for detecting and autocorrecting undecorated strings for i18n # (gettext and rails-i18n) gem 'rubocop-i18n' + + # Performance checks by Rubocop + gem 'rubocop-performance', require: false end group :development do diff --git a/Gemfile.lock b/Gemfile.lock index a5e8b942fa..fbfcbb8215 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -58,7 +58,7 @@ GEM activesupport (= 6.1.7.3) activerecord-import (1.4.1) activerecord (>= 4.2) - activerecord_json_validator (2.1.3) + activerecord_json_validator (2.1.5) activerecord (>= 4.2.0, < 8) json_schemer (~> 0.2.18) activestorage (6.1.7.3) @@ -86,16 +86,16 @@ GEM autoprefixer-rails (10.4.13.0) execjs (~> 2) bcrypt (3.1.18) - better_errors (2.9.1) - coderay (>= 1.0.0) + better_errors (2.10.0) erubi (>= 1.0.0) rack (>= 0.9.0) + rouge (>= 1.0.0) bindex (0.8.1) binding_of_caller (1.0.0) debug_inspector (>= 0.0.1) bootsnap (1.16.0) msgpack (~> 1.2) - brakeman (5.4.1) + brakeman (6.0.0) builder (3.2.4) bullet (7.0.7) activesupport (>= 3.0.0) @@ -106,7 +106,7 @@ GEM bundler (>= 1.2.0, < 3) thor (~> 1.0) byebug (11.1.3) - capybara (3.39.0) + capybara (3.39.1) addressable matrix mini_mime (>= 0.1.3) @@ -159,7 +159,7 @@ GEM railties (>= 4.1.0) responders warden (~> 1.2.3) - devise_invitable (2.0.7) + devise_invitable (2.0.8) actionmailer (>= 5.0) devise (>= 4.6) diff-lcs (1.5.0) @@ -184,12 +184,12 @@ GEM factory_bot_rails (6.2.0) factory_bot (~> 6.2.0) railties (>= 5.0.0) - faker (3.1.1) + faker (3.2.0) i18n (>= 1.8.11, < 2) - faraday (2.7.4) + faraday (2.7.5) faraday-net_http (>= 2.0, < 3.1) ruby2_keywords (>= 0.0.4) - faraday-http-cache (2.4.1) + faraday-http-cache (2.5.0) faraday (>= 0.8) faraday-net_http (3.0.2) feedjira (3.2.2) @@ -197,7 +197,7 @@ GEM sax-machine (>= 1.0) ffi (1.15.5) flag_shih_tzu (0.3.23) - fog-aws (3.18.0) + fog-aws (3.19.0) fog-core (~> 2.1) fog-json (~> 1.1) fog-xml (~> 0.1) @@ -247,7 +247,7 @@ GEM httparty (0.21.0) mini_mime (>= 1.0.0) multi_xml (>= 0.5.2) - i18n (1.12.0) + i18n (1.13.0) concurrent-ruby (~> 1.0) jbuilder (2.11.5) actionview (>= 5.0.0) @@ -262,7 +262,7 @@ GEM hana (~> 1.3) regexp_parser (~> 2.0) uri_template (~> 0.7) - jsonpath (1.1.2) + jsonpath (1.1.3) multi_json jwt (2.7.0) kaminari (1.2.2) @@ -287,9 +287,9 @@ GEM rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) locale (2.1.3) - loofah (2.20.0) + loofah (2.21.3) crass (~> 1.0.2) - nokogiri (>= 1.5.9) + nokogiri (>= 1.12.0) lumberjack (1.2.8) mail (2.8.1) mini_mime (>= 0.1.1) @@ -309,7 +309,7 @@ GEM minitest (5.18.0) mocha (2.0.2) ruby2_keywords (>= 0.0.5) - msgpack (1.7.0) + msgpack (1.7.1) multi_json (1.15.0) multi_xml (0.6.0) mysql2 (0.5.5) @@ -326,9 +326,9 @@ GEM net-protocol nio4r (2.5.9) no_proxy_fix (0.1.2) - nokogiri (1.14.3-arm64-darwin) + nokogiri (1.15.2-arm64-darwin) racc (~> 1.4) - nokogiri (1.14.3-x86_64-linux) + nokogiri (1.15.2-x86_64-linux) racc (~> 1.4) notiffany (0.1.3) nenv (~> 0.1) @@ -361,10 +361,10 @@ GEM open4 (1.3.4) options (2.3.2) orm_adapter (0.5.0) - parallel (1.22.1) - parser (3.2.2.0) + parallel (1.23.0) + parser (3.2.2.1) ast (~> 2.4.1) - pg (1.4.6) + pg (1.5.3) prime (0.1.2) forwardable singleton @@ -375,14 +375,14 @@ GEM coderay (~> 1.1) method_source (~> 1.0) public_suffix (5.0.1) - puma (6.2.1) + puma (6.2.2) nio4r (~> 2.0) pundit (2.2.0) activesupport (>= 3.0.0) - pundit-matchers (1.8.4) + pundit-matchers (2.3.0) rspec-rails (>= 3.0.0) racc (1.6.2) - rack (2.2.6.4) + rack (2.2.7) rack-attack (6.6.1) rack (>= 1.0, < 3) rack-mini-profiler (3.1.0) @@ -427,48 +427,48 @@ GEM rb-inotify (0.10.1) ffi (~> 1.0) rchardet (1.8.0) - recaptcha (5.13.0) - json - regexp_parser (2.7.0) + recaptcha (5.14.0) + regexp_parser (2.8.0) responders (3.1.0) actionpack (>= 5.2) railties (>= 5.2) rexml (3.2.5) rollbar (3.4.0) + rouge (4.1.1) rspec-collection_matchers (1.2.0) rspec-expectations (>= 2.99.0.beta1) - rspec-core (3.12.1) + rspec-core (3.12.2) rspec-support (~> 3.12.0) - rspec-expectations (3.12.2) + rspec-expectations (3.12.3) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.12.0) rspec-mocks (3.12.5) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.12.0) - rspec-rails (6.0.1) + rspec-rails (6.0.2) actionpack (>= 6.1) activesupport (>= 6.1) railties (>= 6.1) - rspec-core (~> 3.11) - rspec-expectations (~> 3.11) - rspec-mocks (~> 3.11) - rspec-support (~> 3.11) + rspec-core (~> 3.12) + rspec-expectations (~> 3.12) + rspec-mocks (~> 3.12) + rspec-support (~> 3.12) rspec-support (3.12.0) - rswag (2.8.0) - rswag-api (= 2.8.0) - rswag-specs (= 2.8.0) - rswag-ui (= 2.8.0) - rswag-api (2.8.0) + rswag (2.9.0) + rswag-api (= 2.9.0) + rswag-specs (= 2.9.0) + rswag-ui (= 2.9.0) + rswag-api (2.9.0) railties (>= 3.1, < 7.1) - rswag-specs (2.8.0) + rswag-specs (2.9.0) activesupport (>= 3.1, < 7.1) json-schema (>= 2.2, < 4.0) railties (>= 3.1, < 7.1) rspec-core (>= 2.14) - rswag-ui (2.8.0) + rswag-ui (2.9.0) actionpack (>= 3.1, < 7.1) railties (>= 3.1, < 7.1) - rubocop (1.50.0) + rubocop (1.51.0) json (~> 2.3) parallel (~> 1.10) parser (>= 3.2.0.0) @@ -478,10 +478,13 @@ GEM rubocop-ast (>= 1.28.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.28.0) + rubocop-ast (1.28.1) parser (>= 3.2.1.0) rubocop-i18n (3.0.0) rubocop (~> 1.0) + rubocop-performance (1.18.0) + rubocop (>= 1.7.0, < 2.0) + rubocop-ast (>= 0.4.0) ruby-progressbar (1.13.0) ruby2_keywords (0.0.5) ruby_dig (0.0.2) @@ -490,7 +493,7 @@ GEM addressable (>= 2.3.5) faraday (>= 0.17.3, < 3) sax-machine (1.3.2) - selenium-webdriver (4.8.6) + selenium-webdriver (4.9.1) rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) @@ -521,10 +524,10 @@ GEM terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) text (1.3.1) - thor (1.2.1) + thor (1.2.2) timeout (0.3.2) tomparse (0.4.2) - translation (1.35) + translation (1.36) gettext (~> 3.2, >= 3.2.5, <= 3.4.3) turbo-rails (1.4.0) actionpack (>= 6.0.0) @@ -560,11 +563,11 @@ GEM wkhtmltopdf-binary (0.12.6.6) xpath (3.2.0) nokogiri (~> 1.8) - yard (0.9.33) + yard (0.9.34) yard-tomdoc (0.7.1) tomparse (>= 0.4.0) yard - zeitwerk (2.6.7) + zeitwerk (2.6.8) PLATFORMS arm64-darwin-21 @@ -623,7 +626,7 @@ DEPENDENCIES puma pundit pundit-matchers - rack-attack + rack-attack (~> 6.6, >= 6.6.1) rack-mini-profiler rails (~> 6.1) rails-controller-testing @@ -634,6 +637,7 @@ DEPENDENCIES rswag rubocop rubocop-i18n + rubocop-performance shoulda spring spring-commands-rspec @@ -650,7 +654,7 @@ DEPENDENCIES yard-tomdoc RUBY VERSION - ruby 3.0.5p211 + ruby 3.0.4p208 BUNDLED WITH 2.4.8 diff --git a/README.md b/README.md index 4481f8519b..a99a2df6e6 100644 --- a/README.md +++ b/README.md @@ -1,60 +1,46 @@ -# DMP OPIDoR - -[![Actions Status](https://github.com/DMPRoadmap/roadmap/workflows/Brakeman/badge.svg)](https://github.com/DMPRoadmap/roadmap/actions) -[![Actions Status](https://github.com/DMPRoadmap/roadmap/workflows/Rubocop/badge.svg)](https://github.com/DMPRoadmap/roadmap/actions) -[![Actions Status](https://github.com/DMPRoadmap/roadmap/workflows/ESLint/badge.svg)](https://github.com/DMPRoadmap/roadmap/actions) -[![Actions Status](https://github.com/DMPRoadmap/roadmap/workflows/Tests%20-%20PostgreSQL/badge.svg)](https://github.com/DMPRoadmap/roadmap/actions) -[![Actions Status](https://github.com/DMPRoadmap/roadmap/workflows/Tests%20-%20MySQL/badge.svg)](https://github.com/DMPRoadmap/roadmap/actions) - -DMP Roadmap is a Data Management Planning tool. Management and development of DMP Roadmap is jointly provided by the Digital Curation Centre (DCC), http://www.dcc.ac.uk/, and the University of California Curation Center (UC3), http://www.cdlib.org/services/uc3/. - -### Requis - -- Ruby 2.4.x -- Rails 4.2.x -- NodeJS LTS -- PostgreSQL 9.x - -Click here for the latest [releases](https://github.com/DMPRoadmap/roadmap/releases/). - -#### Pre-requisites -Roadmap is a Ruby on Rails application and you will need to have: -* Ruby = 2.7.6 -* Rails = 6.1 -* MySQL >= 5.0 OR PostgreSQL - -Further detail on how to install Ruby on Rails applications are available from the Ruby on Rails site: http://rubyonrails.org. - -L'application se lance par défaut en mode développement. - -## Développement - -### Structure du dépôt Git - -#### Installation -See the [Installation Guide](https://github.com/DMPRoadmap/roadmap/wiki/Installation) on the Wiki. - -#### Troubleshooting -See the [Troubleshooting Guide](https://github.com/DMPRoadmap/roadmap/wiki/Troubleshooting) on the Wiki. - -#### Support -Issues should be reported here on [Github Issues](https://github.com/DMPRoadmap/roadmap/issues) -Please be advised though that we can only provide limited support for your local installations. -Any security patches and bugfixes will be applied to the most recent version, and we will endeavour to support migrations to the current release. - -#### Contributing -If you would like to contribute to the project. Please follow these steps to submit a contribution: -* Comment on the Github issue (or create one if one does not exist) and let us know that you're working on it. -* Fork the project (if you have not already) or rebase your fork so that it is up to date with the current repository's '_**development**_' branch -* Create a new branch in your fork. This will ensure that you are able to work at your own pace and continue to pull in any updates made to this project. -* Make your changes in the new branch -* When you have finished your work, make sure that your version of the '_**development**_' branch is still up to date with this project. Then merge your new branch into your '_**development**_' branch. -* Then create a new Pull Request (PR) from your branch to this project's '_**development**_' branch in GitHub -* The project team will then review your PR and communicate with you to convey any additional changes that would ensure that your work adheres to our guidelines. - -See the [Contribution Guide](https://github.com/DMPRoadmap/roadmap/blob/development/CONTRIBUTING.md) on the Wiki for more details. - -#### License -The DMP Roadmap project uses the MIT License. - -foobar +# DMP OPIDoR + +[![Actions Status](https://github.com/DMPRoadmap/roadmap/workflows/Brakeman/badge.svg)](https://github.com/DMPRoadmap/roadmap/actions) +[![Actions Status](https://github.com/DMPRoadmap/roadmap/workflows/Rubocop/badge.svg)](https://github.com/DMPRoadmap/roadmap/actions) +[![Actions Status](https://github.com/DMPRoadmap/roadmap/workflows/ESLint/badge.svg)](https://github.com/DMPRoadmap/roadmap/actions) +[![Actions Status](https://github.com/DMPRoadmap/roadmap/workflows/Tests%20-%20PostgreSQL/badge.svg)](https://github.com/DMPRoadmap/roadmap/actions) +[![Actions Status](https://github.com/DMPRoadmap/roadmap/workflows/Tests%20-%20MySQL/badge.svg)](https://github.com/DMPRoadmap/roadmap/actions) + +DMP Roadmap is a Data Management Planning tool. Management and development of DMP Roadmap is jointly provided by the Digital Curation Centre (DCC), http://www.dcc.ac.uk/, and the University of California Curation Center (UC3), http://www.cdlib.org/services/uc3/. + +### Requis + +- Ruby 2.4.x +- Rails 4.2.x +- NodeJS LTS +- PostgreSQL 9.x + +Click here for the latest [releases](https://github.com/DMPRoadmap/roadmap/releases/). + +#### Installation +See the [Installation Guide](https://github.com/DMPRoadmap/roadmap/wiki/Installation) on the Wiki. + +#### Troubleshooting +See the [Troubleshooting Guide](https://github.com/DMPRoadmap/roadmap/wiki/Troubleshooting) on the Wiki. + +#### Support +Issues should be reported here on [Github Issues](https://github.com/DMPRoadmap/roadmap/issues) +Please be advised though that we can only provide limited support for your local installations. +Any security patches and bugfixes will be applied to the most recent version, and we will endeavour to support migrations to the current release. + +#### Contributing +If you would like to contribute to the project. Please follow these steps to submit a contribution: +* Comment on the Github issue (or create one if one does not exist) and let us know that you're working on it. +* Fork the project (if you have not already) or rebase your fork so that it is up to date with the current repository's '_**development**_' branch +* Create a new branch in your fork. This will ensure that you are able to work at your own pace and continue to pull in any updates made to this project. +* Make your changes in the new branch +* When you have finished your work, make sure that your version of the '_**development**_' branch is still up to date with this project. Then merge your new branch into your '_**development**_' branch. +* Then create a new Pull Request (PR) from your branch to this project's '_**development**_' branch in GitHub +* The project team will then review your PR and communicate with you to convey any additional changes that would ensure that your work adheres to our guidelines. + +See the [Contribution Guide](https://github.com/DMPRoadmap/roadmap/blob/development/CONTRIBUTING.md) on the Wiki for more details. + +#### License +The DMP Roadmap project uses the MIT License. + +foobar diff --git a/app/assets/stylesheets/blocks/_buttons.scss b/app/assets/stylesheets/blocks/_buttons.scss index f137fc9cd0..ffc1d5ee69 100644 --- a/app/assets/stylesheets/blocks/_buttons.scss +++ b/app/assets/stylesheets/blocks/_buttons.scss @@ -15,6 +15,7 @@ .btn-primary.active:hover { background-color: $color-primary-background; border-color: $color-border-default; + color: $color-primary-text; outline: none; margin-top: 5px; margin-bottom: 10px; @@ -30,6 +31,7 @@ .btn-default.active:hover { background-color: $color-primary-background; border-color: $color-border-default; + color: $color-primary-text; outline: none; margin-top: 5px; margin-bottom: 10px; diff --git a/app/assets/stylesheets/blocks/_tables.scss b/app/assets/stylesheets/blocks/_tables.scss index 7661e39e81..32e3a2038c 100644 --- a/app/assets/stylesheets/blocks/_tables.scss +++ b/app/assets/stylesheets/blocks/_tables.scss @@ -48,7 +48,7 @@ th.download-column { @media (min-width: 768px) { .table-responsive { - overflow: visible; + overflow: visible !important; } } diff --git a/app/assets/stylesheets/dmpopidor.scss b/app/assets/stylesheets/dmpopidor.scss index 5a7120ac12..fc4f6af1cb 100644 --- a/app/assets/stylesheets/dmpopidor.scss +++ b/app/assets/stylesheets/dmpopidor.scss @@ -493,6 +493,7 @@ header .main-nav { border-color: $rust; background-color: $rust; color: $white; + margin-bottom: 10px; } .btn-default, @@ -505,6 +506,7 @@ header .main-nav { border-color: $rust; background-color: $rust; color: $white; + margin-bottom: 10px; } .btn-secondary, @@ -992,6 +994,7 @@ header .main-nav { border-color: $blue; background-color: $blue; color: $white; + margin-bottom: 10px; } .btn-default, @@ -1004,6 +1007,7 @@ header .main-nav { border-color: $blue; background-color: $blue; color: $white; + margin-bottom: 10px; } .combobox-clear-button, @@ -1119,6 +1123,7 @@ header .main-nav { background-color: $yellow; color: $black; text-decoration: none; + margin-bottom: 10px; } .btn-default, @@ -1132,6 +1137,7 @@ header .main-nav { background-color: $yellow; color: $black; text-decoration: none; + margin-bottom: 10px; } .combobox-clear-button, @@ -1723,6 +1729,10 @@ header .main-nav { background-color: $light-grey; } + .paginable-search { + display: none; + } + } .spinner { diff --git a/app/controllers/answers_controller.rb b/app/controllers/answers_controller.rb index 9d1abc106f..03c73da29a 100644 --- a/app/controllers/answers_controller.rb +++ b/app/controllers/answers_controller.rb @@ -200,8 +200,8 @@ def permitted_params # -------------------------------- def check_answered(section, q_array, all_answers) - n_qs = section.questions.select { |question| q_array.include?(question.id) }.length - n_ans = all_answers.select { |ans| q_array.include?(ans.question.id) and ans.answered? }.length + n_qs = section.questions.count { |question| q_array.include?(question.id) } + n_ans = all_answers.count { |ans| q_array.include?(ans.question.id) and ans.answered? } [n_qs, n_ans] end end diff --git a/app/controllers/api/v0/plans_controller.rb b/app/controllers/api/v0/plans_controller.rb index 0ea43ba707..4e74ab2db9 100644 --- a/app/controllers/api/v0/plans_controller.rb +++ b/app/controllers/api/v0/plans_controller.rb @@ -88,7 +88,7 @@ def index if params['updated_after'].present? || params['updated_before'].present? @plans = @plans.where(updated_at: dates_to_range(params, 'updated_after', 'updated_before')) end - if params['remove_tests'].present? && params['remove_tests'].downcase == 'true' + if params['remove_tests'].present? && params['remove_tests'].casecmp('true').zero? @plans = @plans.where.not(visibility: Plan.visibilities[:is_test]) end # filter on funder (dmptemplate_id) diff --git a/app/controllers/api/v0/statistics_controller.rb b/app/controllers/api/v0/statistics_controller.rb index 2af50dfcb2..dffd03c67c 100644 --- a/app/controllers/api/v0/statistics_controller.rb +++ b/app/controllers/api/v0/statistics_controller.rb @@ -219,7 +219,7 @@ def plans raise Pundit::NotAuthorizedError unless Api::V0::StatisticsPolicy.new(@user, :statistics).plans? @org_plans = @user.org.plans - if params['remove_tests'].present? && params['remove_tests'].downcase == 'true' + if params['remove_tests'].present? && params['remove_tests'].casecmp('true').zero? @org_plans = @org_plans.where.not(visibility: Plan.visibilities[:is_test]) end if params['start_date'].present? || params['end_date'].present? diff --git a/app/controllers/api/v1/plans_controller.rb b/app/controllers/api/v1/plans_controller.rb index b1480437bd..3f83f5da93 100644 --- a/app/controllers/api/v1/plans_controller.rb +++ b/app/controllers/api/v1/plans_controller.rb @@ -108,7 +108,7 @@ def plan_exists?(json:) # Get the Plan's owner def determine_owner(client:, plan:) - contact = plan.contributors.select(&:data_curation?).first + contact = plan.contributors.find(&:data_curation?) # Use the contact if it was sent in and has an affiliation defined return contact if contact.present? && contact.org.present? diff --git a/app/controllers/concerns/conditional_user_mailer.rb b/app/controllers/concerns/conditional_user_mailer.rb index c83338347d..e7eddc58fe 100644 --- a/app/controllers/concerns/conditional_user_mailer.rb +++ b/app/controllers/concerns/conditional_user_mailer.rb @@ -12,14 +12,14 @@ module ConditionalUserMailer # # Returns Boolean def deliver_if(key:, recipients: [], &block) - return false unless block_given? + return false unless block Array(recipients).each do |recipient| email_hash = recipient.get_preferences('email').with_indifferent_access # Violation of rubocop's DoubleNegation check # preference_value = !!email_hash.dig(*key.to_s.split(".")) preference_value = email_hash.dig(*key.to_s.split('.')) - block.call(recipient) if preference_value + yield(recipient) if preference_value end true diff --git a/app/controllers/contact_us/contacts_controller.rb b/app/controllers/contact_us/contacts_controller.rb index ca95d2fa3d..14e22d7d7e 100644 --- a/app/controllers/contact_us/contacts_controller.rb +++ b/app/controllers/contact_us/contacts_controller.rb @@ -9,8 +9,9 @@ def create @contact = ContactUs::Contact.new(params[:contact_us_contact]) if !user_signed_in? && Rails.configuration.x.recaptcha.enabled && - !(verify_recaptcha(model: @contact) && @contact.save) + !(verify_recaptcha(action: 'contact_us', model: @contact) && @contact.save) flash[:alert] = _('Captcha verification failed, please retry.') + @show_checkbox_recaptcha = true render_new_page and return end if @contact.save diff --git a/app/controllers/dmpopidor/paginable/contributors_controller.rb b/app/controllers/dmpopidor/paginable/contributors_controller.rb index 6f0db7ca42..88ffa56ce3 100644 --- a/app/controllers/dmpopidor/paginable/contributors_controller.rb +++ b/app/controllers/dmpopidor/paginable/contributors_controller.rb @@ -7,12 +7,14 @@ module ContributorsController # GET /paginable/plans/:plan_id/contributors # GET /paginable/plans/:plan_id/contributors/index/:page def index - @plan = Plan.find_by(id: params[:plan_id]) + @plan = ::Plan.find_by(id: params[:plan_id]) dmp_fragment = @plan.json_fragment - authorize @plan + authorize @plan, :show? paginable_renderise( partial: 'index', - scope: dmp_fragment.persons, + scope: dmp_fragment.persons.order( + Arel.sql("data->>'lastName', data->>'firstName'") + ), format: :json ) end diff --git a/app/controllers/org_admin/plans_controller.rb b/app/controllers/org_admin/plans_controller.rb index 89b9900498..5308b2ea55 100644 --- a/app/controllers/org_admin/plans_controller.rb +++ b/app/controllers/org_admin/plans_controller.rb @@ -66,7 +66,7 @@ def download_plans raise Pundit::NotAuthorizedError unless current_user.present? && current_user.can_org_admin? org = current_user.org - file_name = org.name.gsub(/ /, '_') + file_name = org.name.tr(' ', '_') .gsub(/[.;,]/, '') header_cols = [ _('Project title').to_s, diff --git a/app/controllers/org_admin/templates_controller.rb b/app/controllers/org_admin/templates_controller.rb index df9b282f70..41dce34daa 100644 --- a/app/controllers/org_admin/templates_controller.rb +++ b/app/controllers/org_admin/templates_controller.rb @@ -2,6 +2,7 @@ module OrgAdmin # Controller that handles templates + # rubocop:disable Metrics/ClassLength class TemplatesController < ApplicationController include Paginable include Versionable @@ -15,7 +16,7 @@ class TemplatesController < ApplicationController def index authorize Template templates = Template.latest_version.where(customization_of: nil) - published = templates.select { |t| t.published? || t.draft? }.length + published = templates.count { |t| t.published? || t.draft? } @orgs = Org.includes(:identifiers).managed @title = _('All Templates') @@ -40,7 +41,7 @@ def organisational authorize Template templates = Template.latest_version_per_org(current_user.org.id) .where(customization_of: nil, org_id: current_user.org.id) - published = templates.select { |t| t.published? || t.draft? }.length + published = templates.count { |t| t.published? || t.draft? } @orgs = current_user.can_super_admin? ? Org.includes(:identifiers).all : nil @title = if current_user.can_super_admin? @@ -78,7 +79,7 @@ def customisable customizations = customizations.select do |t| funder_template_families.include?(t.customization_of) end - published = customizations.select { |t| t.published? || t.draft? }.length + published = customizations.count { |t| t.published? || t.draft? } @orgs = current_user.can_super_admin? ? Org.includes(:identifiers).all : [] @title = _('Customizable Templates') @@ -174,6 +175,10 @@ def new # -------------------------------- # End DMP OPIDoR Customization # -------------------------------- + # If the Org is a funder set the visibility to Public otherwise set to Organizational + # for Orgs that are both, the admin will see controls on the page to let them choose. + # The default is already 'organisationally_visible' so change it if this is a funder + @template.visibility = Template.visibilities[:publicly_visible] if current_org.funder? end # POST /org_admin/templates @@ -345,7 +350,7 @@ def template_export @formatting = Settings::Template::DEFAULT_SETTINGS[:formatting] begin - safe_title = @template.title.gsub(/[^a-zA-Z\d\s]/, '').gsub(/ /, '_') + safe_title = @template.title.gsub(/[^a-zA-Z\d\s]/, '').tr(' ', '_') file_name = "#{safe_title}_v#{@template.version}" respond_to do |format| format.docx do @@ -435,4 +440,5 @@ def get_referrer(template, referrer) end end end + # rubocop:enable Metrics/ClassLength end diff --git a/app/controllers/plan_exports_controller.rb b/app/controllers/plan_exports_controller.rb index fa7bbd0748..3e051b5b42 100644 --- a/app/controllers/plan_exports_controller.rb +++ b/app/controllers/plan_exports_controller.rb @@ -43,12 +43,18 @@ def show @hash = @plan.as_pdf(current_user, @show_coversheet) @formatting = export_params[:formatting] || @plan.settings(:export).formatting - @selected_phase = if params.key?(:phase_id) - @plan.phases.find(params[:phase_id]) - else - @plan.phases.order('phases.updated_at DESC') + if params.key?(:phase_id) && params[:phase_id].length.positive? + # order phases by phase number asc + @hash[:phases] = @hash[:phases].sort_by { |phase| phase[:number] } + if params[:phase_id] == 'All' + @hash[:all_phases] = true + else + @selected_phase = @plan.phases.find(params[:phase_id]) + end + else + @selected_phase = @plan.phases.order('phases.updated_at DESC') .detect { |p| p.visibility_allowed?(@plan) } - end + end # Added contributors to coverage of plans. # Users will see both roles and contributor names if the role is filled @@ -116,7 +122,7 @@ def show_pdf date: l(@plan.updated_at.to_date, format: :readable)), font_size: 8, spacing: (Integer(@formatting[:margin][:bottom]) / 2) - 4, - right: '[page] of [topage]', + right: _('[page] of [topage]'), encoding: 'utf8' } end @@ -140,7 +146,7 @@ def file_name # Sanitize bad characters and replace spaces with underscores ret = @plan.title ret = ret.strip.gsub(/\s+/, '_') - ret = ret.gsub(/"/, '') + ret = ret.delete('"') ret = ActiveStorage::Filename.new(ret).sanitized # limit the filename length to 100 chars. Windows systems have a MAX_PATH allowance # of 255 characters, so this should provide enough of the title to allow the user diff --git a/app/controllers/plans_controller.rb b/app/controllers/plans_controller.rb index 76c5b839ba..304604b5e6 100644 --- a/app/controllers/plans_controller.rb +++ b/app/controllers/plans_controller.rb @@ -165,7 +165,16 @@ def create # rubocop:enable Layout/LineLength else # We used the specified org's or funder's template - msg += " #{_('This plan is based on the')} #{@plan.template.org.name}: '#{@plan.template.title}' template." + # -------------------------------- + # Start DMP OPIDoR Customization + # CHANGES : Change message + # -------------------------------- + msg += _('This plan is based on the "%{template_title}" template provided by %{org_name}.') % { + template_title: @plan.template.title, org_name: @plan.template.org.name + } + # -------------------------------- + # End DMP OPIDoR Customization + # -------------------------------- end @plan.add_user!(current_user.id, :creator) @@ -319,7 +328,7 @@ def edit .find(params[:id]) authorize plan phase_id = params[:phase_id].to_i - phase = plan.template.phases.select { |p| p.id == phase_id }.first + phase = plan.template.phases.find { |p| p.id == phase_id } raise ActiveRecord::RecordNotFound if phase.nil? guidance_groups = GuidanceGroup.where(published: true, id: plan.guidance_group_ids) @@ -484,6 +493,7 @@ def download # -------------------------------- @phase_options = @plan.phases.order(:number).pluck(:title, :id) + @phase_options.insert(0, ['All phases', 'All']) if @phase_options.length > 1 @export_settings = @plan.settings(:export) render 'download' end diff --git a/app/controllers/public_pages_controller.rb b/app/controllers/public_pages_controller.rb index 2a49be2c54..7e5e1dd2f1 100644 --- a/app/controllers/public_pages_controller.rb +++ b/app/controllers/public_pages_controller.rb @@ -64,7 +64,7 @@ def template_export @formatting = Settings::Template::DEFAULT_SETTINGS[:formatting] begin - file_name = @template.title.gsub(/[^a-zA-Z\d\s]/, '').gsub(/ /, '_') + file_name = @template.title.gsub(/[^a-zA-Z\d\s]/, '').tr(' ', '_') file_name = "#{file_name}_v#{@template.version}" respond_to do |format| format.docx do diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index 17dc020039..cafd3ae5e5 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -109,7 +109,7 @@ def create # Determine if reCAPTCHA is enabled and if so verify it use_recaptcha = Rails.configuration.x.recaptcha.enabled || false - if (!use_recaptcha || verify_recaptcha(model: resource)) && resource.save + if (!use_recaptcha || verify_recaptcha(action: 'create_account', model: resource)) && resource.save # rubocop:disable Metrics/BlockNesting if resource.active_for_authentication? set_flash_message :notice, :signed_up if is_navigational_format? @@ -135,6 +135,7 @@ def create end # rubocop:enable Metrics/BlockNesting else + @show_checkbox_recaptcha = true clean_up_passwords resource redirect_to after_sign_up_error_path_for(resource), alert: _("Unable to create your account.#{errors_for_display(resource)}") diff --git a/app/helpers/conditions_helper.rb b/app/helpers/conditions_helper.rb index 8b6a956c14..9973efb975 100644 --- a/app/helpers/conditions_helper.rb +++ b/app/helpers/conditions_helper.rb @@ -89,11 +89,9 @@ def num_section_questions(plan, section, phase = nil) phase = plan.template .phases - .select { |ph| ph.number == phase[:number] } - .first + .find { |ph| ph.number == phase[:number] } section = phase.sections - .select { |s| s.phase_id == phase.id && s.title == section[:title] } - .first + .find { |s| s.phase_id == phase.id && s.title == section[:title] } end count = 0 plan_remove_list = remove_list(plan) diff --git a/app/javascript/src/answers/edit.js b/app/javascript/src/answers/edit.js index 5f25712828..f88a58b1a1 100644 --- a/app/javascript/src/answers/edit.js +++ b/app/javascript/src/answers/edit.js @@ -3,7 +3,7 @@ import { isNumber, isString, } from '../utils/isType'; -import { Tinymce } from '../utils/tinymce.js.erb'; +import { Tinymce } from '../utils/tinymce'; import { formLoadingCallback } from '../utils/dynamicFormHelper'; // import debounce from '../utils/debounce'; import { updateSectionProgress, getQuestionDiv } from '../utils/sectionUpdate'; diff --git a/app/javascript/src/guidances/newEdit.js b/app/javascript/src/guidances/newEdit.js index e5da657c8b..f018eca0b8 100644 --- a/app/javascript/src/guidances/newEdit.js +++ b/app/javascript/src/guidances/newEdit.js @@ -1,4 +1,4 @@ -import { Tinymce } from '../utils/tinymce.js.erb'; +import { Tinymce } from '../utils/tinymce.js'; $(() => { Tinymce.init({ selector: '#guidance_text' }); diff --git a/app/javascript/src/notes/index.js b/app/javascript/src/notes/index.js index ecdaa549db..c2133f54fa 100644 --- a/app/javascript/src/notes/index.js +++ b/app/javascript/src/notes/index.js @@ -1,4 +1,4 @@ -import { Tinymce } from '../utils/tinymce.js.erb'; +import { Tinymce } from '../utils/tinymce.js'; import { isObject, isString } from '../utils/isType'; import TimeagoFactory from '../utils/timeagoFactory.js.erb'; @@ -162,7 +162,9 @@ $(() => { }; const initOrReload = (researchOutputId = null) => { if (researchOutputId) { - Tinymce.init({ selector: '.note' }); + $('.note').each((_idx, el) => { + Tinymce.init({ selector: `#${$(el).attr('id')}` }); + }); } eventHandlers({ attachment: 'on' }); TimeagoFactory.render($('time.timeago')); diff --git a/app/javascript/src/orgAdmin/phases/newEdit.js b/app/javascript/src/orgAdmin/phases/newEdit.js index 7760ee44f9..e32a49abd0 100644 --- a/app/javascript/src/orgAdmin/phases/newEdit.js +++ b/app/javascript/src/orgAdmin/phases/newEdit.js @@ -1,5 +1,5 @@ // import 'bootstrap-sass/assets/javascripts/bootstrap/collapse'; -import { Tinymce } from '../../utils/tinymce.js.erb'; +import { Tinymce } from '../../utils/tinymce.js'; import { isObject, isString } from '../../utils/isType'; import getConstant from '../../utils/constants'; import { addAsterisks } from '../../utils/requiredField'; @@ -9,7 +9,7 @@ import initQuestionOption from '../questionOptions/index'; import updateConditions from '../conditions/updateConditions'; $(() => { - Tinymce.init({ selector: '.phase' }); + Tinymce.init({ selector: '#phase_description' }); const parentSelector = '.section-group'; const initQuestion = (context) => { @@ -64,36 +64,29 @@ $(() => { // For some reason the toolbar options are retained after the call to Tinymce.init() on // the views/notifications/edit.js file. Tried 'Object.assign' instead of '$.extend' but it // made no difference + const prefix = 'collapseSection' + let sectionId = selector; + if (sectionId.startsWith(prefix)) { + sectionId = `sc_${sectionId.replace(prefix, '')}_section_description` + } + Tinymce.init({ - selector: `${selector} .section`, - init_instance_callback(editor) { + selector: `#${sectionId}`, + init_instance_callback: (editor) => { // When the text editor changes to blank, set the corresponding destroy // field to true (if present). - editor.on('Change', () => { - const $texteditor = $(editor.targetElm); + editor.on('Change', (ed) => { + const $texteditor = $(ed.getContentAreaContainer()); const $fieldset = $texteditor.parents('fieldset'); const $hiddenField = $fieldset.find('input[type=hidden][id$="_destroy"]'); - $hiddenField.val(editor.getContent() === ''); + $hiddenField.val(ed.getContent() === ''); }); }, }); - const questionForm = $(selector).find('.question_form'); + + const questionForm = $(`#${selector}`).find('.question_form'); if (questionForm.length > 0) { - // Load Tinymce when the 'show' form has a question form. - // ONLY applicable for template customizations - Tinymce.init({ - selector: `${selector} .question_form .question`, - init_instance_callback(editor) { - // When the text editor changes to blank, set the corresponding destroy - // field to true (if present). - editor.on('Change', () => { - const $texteditor = $(editor.targetElm); - const $fieldset = $texteditor.parents('fieldset'); - const $hiddenField = $fieldset.find('input[type=hidden][id$="_destroy"]'); - $hiddenField.val(editor.getContent() === ''); - }); - }, - }); + initQuestion(selector); } } }; @@ -111,8 +104,9 @@ $(() => { // Display the section's html panelBody.attr('data-loaded', 'true'); panelBody.html(e.detail[0].html); + // Wire up the section - initSection(`#${panel.attr('id')}`); + initSection(`${panel.attr('id')}`); } }); @@ -160,8 +154,9 @@ $(() => { // Handle the section that has focus on initial page load const currentSection = $('.section-group .in'); if (currentSection.length > 0) { - initSection(`#${currentSection.attr('id')}`); + initSection(`${currentSection.attr('id')}`); } // Handle the new section - initSection('#new_section_new_section'); + // initSection('#new_section_section_description'); + Tinymce.init({ selector: '#new_section_section_description' }); }); diff --git a/app/javascript/src/orgAdmin/templates/edit.js b/app/javascript/src/orgAdmin/templates/edit.js index 6566fba161..c5cdf68794 100644 --- a/app/javascript/src/orgAdmin/templates/edit.js +++ b/app/javascript/src/orgAdmin/templates/edit.js @@ -1,4 +1,4 @@ -import { Tinymce } from '../../utils/tinymce.js.erb'; +import { Tinymce } from '../../utils/tinymce.js'; import { eachLinks } from '../../utils/links'; import { isObject, isString } from '../../utils/isType'; import { renderNotice, renderAlert } from '../../utils/notificationHelper'; @@ -6,7 +6,7 @@ import { scrollTo } from '../../utils/scrollTo'; $(() => { Tinymce.init({ - selector: '.template', + selector: '#template_description', init_instance_callback(editor) { // When the text editor changes to blank, set the corresponding destroy // field to true (if present). diff --git a/app/javascript/src/orgAdmin/templates/new.js b/app/javascript/src/orgAdmin/templates/new.js index 3e843df0bd..a0e20c6606 100644 --- a/app/javascript/src/orgAdmin/templates/new.js +++ b/app/javascript/src/orgAdmin/templates/new.js @@ -1,9 +1,9 @@ -import { Tinymce } from '../../utils/tinymce.js.erb'; +import { Tinymce } from '../../utils/tinymce.js'; import { eachLinks } from '../../utils/links'; $(() => { Tinymce.init({ - selector: '.template', + selector: '#template_description', init_instance_callback(editor) { // When the text editor changes to blank, set the corresponding destroy // field to true (if present). diff --git a/app/javascript/src/orgs/adminEdit.js b/app/javascript/src/orgs/adminEdit.js index 92d32dfa04..a440eda1de 100644 --- a/app/javascript/src/orgs/adminEdit.js +++ b/app/javascript/src/orgs/adminEdit.js @@ -1,7 +1,7 @@ // TODO: we need to be able to swap in the appropriate locale here import 'number-to-text/converters/en-us'; import { isObject } from '../utils/isType'; -import { Tinymce } from '../utils/tinymce.js.erb'; +import { Tinymce } from '../utils/tinymce.js'; import { eachLinks } from '../utils/links'; import { initAutocomplete, scrubOrgSelectionParamsOnSubmit } from '../utils/autoComplete'; diff --git a/app/javascript/src/plans/download.js b/app/javascript/src/plans/download.js index 70499337a0..71e39b2b4e 100644 --- a/app/javascript/src/plans/download.js +++ b/app/javascript/src/plans/download.js @@ -16,7 +16,7 @@ $(() => { $('#pdf-formatting').hide(); } - if ($(e.currentTarget).val() === 'json') { + if (frmt === 'json') { $('#research-output-export-mode, #export-options').hide(); $('#json-formatting').show(); $('#download-settings').hide(); @@ -25,7 +25,17 @@ $(() => { $('#json-formatting').hide(); $('#download-settings').show(); } - }); + + if (frmt === 'csv') { + $('#phase_id').find('option[value="All"').hide(); + $('#phase_id option:eq(1)').attr('selected', 'selected'); + $('#phase_id').val($('#phase_id option:eq(1)').val()); + } else if (frmt === 'pdf' || frmt === 'html' || frmt === 'docx' || frmt === 'text') { + $('#phase_id').find('option[value="All"').show(); + $('#phase_id').val($('#phase_id option:first').val()); + $('#phase_id option:first').attr('selected', 'selected'); + } + }).trigger('change'); $('#select-all-phases').on('click', (e) => { if (e.target.checked) { diff --git a/app/javascript/src/plans/editDetails.js b/app/javascript/src/plans/editDetails.js index bee7d1b050..56a331d9a2 100644 --- a/app/javascript/src/plans/editDetails.js +++ b/app/javascript/src/plans/editDetails.js @@ -1,4 +1,4 @@ -import { Tinymce } from '../utils/tinymce.js.erb'; +import { Tinymce } from '../utils/tinymce'; import { Select2 } from '../utils/select2'; import getConstant from '../utils/constants'; import * as notifier from '../utils/notificationHelper'; @@ -22,13 +22,9 @@ $(() => { $('#plan_visibility').val($(e.target).is(':checked') ? 'is_test' : 'privately_visible'); }); - const showHideDataContact = (el) => { - if ((el).is(':checked')) { - $('div.data-contact').fadeOut(); - } else { - $('div.data-contact').fadeIn(); - } - }; + if (form.length > 0) { + Tinymce.init({ selector: 'textarea#plan_description' }); + Tinymce.init({ selector: 'textarea#plan_ethical_issues_description' }); $('#show_data_contact').click((e) => { showHideDataContact($(e.currentTarget)); diff --git a/app/javascript/src/researchOutputs/form.js b/app/javascript/src/researchOutputs/form.js index b454b9eb3d..c3df1eb317 100644 --- a/app/javascript/src/researchOutputs/form.js +++ b/app/javascript/src/researchOutputs/form.js @@ -1,6 +1,6 @@ import getConstant from '../utils/constants'; import { isUndefined, isObject } from '../utils/isType'; -import { Tinymce } from '../utils/tinymce.js.erb'; +import { Tinymce } from '../utils/tinymce.js'; $(() => { const form = $('.research_output_form'); diff --git a/app/javascript/src/superAdmin/notifications/edit.js b/app/javascript/src/superAdmin/notifications/edit.js index 03cac9a60b..3587eee0d7 100644 --- a/app/javascript/src/superAdmin/notifications/edit.js +++ b/app/javascript/src/superAdmin/notifications/edit.js @@ -1,11 +1,11 @@ -import { Tinymce } from '../../utils/tinymce.js.erb'; +import { Tinymce } from '../../utils/tinymce.js'; // add the info on selecting the check from notification suitable import { paginableSelector } from '../../utils/paginable'; import * as notifier from '../../utils/notificationHelper'; $(() => { - Tinymce.init({ selector: '.notification-text', forced_root_block: '' }); + Tinymce.init({ selector: '#notification_body', forced_root_block: '' }); $(paginableSelector).on('click, change', '.enable_notification input[type="checkbox"]', (e) => { const form = $(e.target).closest('form'); diff --git a/app/javascript/src/superAdmin/staticPages/edit.js b/app/javascript/src/superAdmin/staticPages/edit.js index 758ed5bf6e..d9332df441 100644 --- a/app/javascript/src/superAdmin/staticPages/edit.js +++ b/app/javascript/src/superAdmin/staticPages/edit.js @@ -1,14 +1,12 @@ -import { Tinymce } from '../../utils/tinymce.js.erb'; +import { Tinymce } from '../../utils/tinymce'; import 'tinymce/plugins/code'; -import 'tinymce/plugins/textcolor'; -import 'tinymce/plugins/colorpicker'; $(() => { Tinymce.init({ selector: '.content-editor', forced_root_block: '', toolbar: 'bold italic underline | formatselect fontsizeselect forecolor backcolor | bullist numlist | link | code', - plugins: 'table autoresize link paste advlist lists code textcolor colorpicker', + plugins: 'table autoresize link paste advlist lists code', menubar: 'toolbar', }); }); diff --git a/app/javascript/src/superAdmin/themes/newEdit.js b/app/javascript/src/superAdmin/themes/newEdit.js index d838e77006..81b51bf55f 100644 --- a/app/javascript/src/superAdmin/themes/newEdit.js +++ b/app/javascript/src/superAdmin/themes/newEdit.js @@ -1,4 +1,4 @@ -import { Tinymce } from '../../utils/tinymce.js.erb'; +import { Tinymce } from '../../utils/tinymce.js'; $(() => { Tinymce.init({ selector: '#theme_description' }); diff --git a/app/javascript/src/utils/conditionalFields.js b/app/javascript/src/utils/conditionalFields.js index df4a2af42f..d545a1a099 100644 --- a/app/javascript/src/utils/conditionalFields.js +++ b/app/javascript/src/utils/conditionalFields.js @@ -6,7 +6,7 @@ // For example see: app/views/plans/_edit_details.html.erb // app/javascript/src/plans/editDetails.js // -import { Tinymce } from './tinymce.js.erb'; +import { Tinymce } from './tinymce.js'; // Expecting `context` to be the field that triggers the hide/show of the corresponding fields export default function toggleConditionalFields(context, showThem) { diff --git a/app/javascript/src/utils/dynamicFormHelper.js b/app/javascript/src/utils/dynamicFormHelper.js index 31e6f79035..8c9517c0e9 100644 --- a/app/javascript/src/utils/dynamicFormHelper.js +++ b/app/javascript/src/utils/dynamicFormHelper.js @@ -1,4 +1,4 @@ -import { Tinymce } from './tinymce.js.erb'; +import { Tinymce } from './tinymce'; import { Select2 } from './select2'; import { AutoNumericHelper } from './autoNumericHelper'; diff --git a/app/javascript/src/utils/tinymce.js.erb b/app/javascript/src/utils/tinymce.js similarity index 61% rename from app/javascript/src/utils/tinymce.js.erb rename to app/javascript/src/utils/tinymce.js index c263e4bd02..37f7056d68 100644 --- a/app/javascript/src/utils/tinymce.js.erb +++ b/app/javascript/src/utils/tinymce.js @@ -1,21 +1,24 @@ // Import TinyMCE import tinymce from 'tinymce/tinymce'; -// Import TinyMCE theme -import 'tinymce/themes/silver/theme'; + +// TinyMCE DOM helpers +import 'tinymce/models/dom/'; + +// TinyMCE toolbar icons import 'tinymce/icons/default'; -// Plugins + +// TinyMCE theme +import 'tinymce/themes/silver'; + +// TinyMCE Plugins import 'tinymce/plugins/table'; import 'tinymce/plugins/lists'; import 'tinymce/plugins/autoresize'; import 'tinymce/plugins/link'; -import 'tinymce/plugins/paste'; import 'tinymce/plugins/advlist'; // Other dependencies -import { isObject, isString } from './isType'; - -// Pull in the rails helper functions -<% helpers = ActionController::Base.helpers %> +import { isObject, isString, isUndefined } from './isType'; // // Configuration extracted from // // https://www.tinymce.com/docs/advanced/usage-with-module-loaders/ @@ -24,8 +27,7 @@ export const defaultOptions = { statusbar: true, menubar: false, toolbar: 'bold italic underline | fontsizeselect forecolor | bullist numlist | link | table', - plugins: 'table autoresize link paste advlist lists', - contextmenu: false, + plugins: 'table autoresize link advlist lists', browser_spellcheck: true, advlist_bullet_styles: 'circle,disc,square', // Only disc bullets display on htmltoword target_list: false, @@ -36,12 +38,14 @@ export const defaultOptions = { autoresize_bottom_margin: 10, branding: false, extended_valid_elements: 'iframe[tooltip] , a[href|target=_blank]', - paste_auto_cleanup_on_paste: true, - paste_remove_styles: true, - paste_convert_middot_lists: true, + paste_as_text: true, + paste_block_drop: true, + paste_merge_formats: true, + paste_tab_spaces: 4, + smart_paste: true, + paste_data_images: true, paste_remove_styles_if_webkit: true, - paste_remove_spans: true, - paste_strip_class_attributes: 'all', + paste_webkit_styles: 'none', table_default_attributes: { border: 1, }, @@ -50,17 +54,16 @@ export const defaultOptions = { skin_url: '/tinymce/skins/oxide', content_css: ['/tinymce/tinymce.css'], }; + /* - This function is invoked anytime a new editor is initialised (e.g. Tinymce.init()) - and shrinks a tinymce editor to the minimum height specified at autoresize_min_height - editor's settings. Since there are cases that tinymce editor is loaded in the DOM - but has display:none style, the iframe associated gets the height of the screen's device - and using this function there is no need to wait until the tinymce gains focus to be autoresized. -*/ -const resizeEditors = (editors) => { - editors.forEach((editor) => { - $(editor.iframeElement).height(editor.settings.autoresize_min_height); - }); + This function determines whether or not the editor is a TinyMCE editor + */ +const isTinymceEditor = (editor) => { + if (isObject(editor)) { + return editor.hasOwnProperty('id') && typeof editor.getContainer === 'function'; + } else { + return false; + } }; /* @@ -69,16 +72,14 @@ const resizeEditors = (editors) => { behind the scenes) to the Tinymce iframe so that screen readers read the correct label when the tinymce iframe receives focus. */ -const attachLabelToIframe = (tinymceContext, hiddenFieldSelector) => { - const iframe = $(tinymceContext).siblings('.mce-container').find('iframe'); - const hiddenField = $(hiddenFieldSelector); +const attachLabelToIframe = (editor) => { + if (isTinymceEditor(editor)) { + const iframe = editor.getContainer().querySelector('iframe'); + const lbl = document.querySelector(`label[for="${editor.id}"]`); - if (isObject(iframe) && isObject(hiddenField)) { - const id = hiddenField.attr('id'); - const lbl = iframe.closest('form').find(`label[for="${id}"]`); - if (isObject(lbl)) { - // Connect the label to the iframe - lbl.attr('for', iframe.attr('id')); + // If the iframe and label could be found, then set the label's 'for' attribute to the id of the iframe + if (isObject(iframe) && isObject(lbl)) { + lbl.setAttribute('for', iframe.getAttribute('id')); } } }; @@ -90,17 +91,23 @@ export const Tinymce = { @param options - An object with tinyMCE properties */ init(options = {}) { - if (isObject(options)) { - tinymce.init($.extend({}, defaultOptions, options)).then(resizeEditors); - } else { - tinymce.init(defaultOptions).then(resizeEditors); - } + // If any options were specified, merge them with the default options. + const opts = { + ...defaultOptions, + ...options, + }; - // Connect the label to the Tinymce iframe - $(options.selector).each((idx, el) => { - attachLabelToIframe(el, options.selector); + tinymce.init(opts).then((editors) => { + if (editors.length > 0) { + for (const editor of editors) { + // auto-resize the editor and connect the form label to the TinyMCE iframe + editor.execCommand('mceAutoResize'); + attachLabelToIframe(editor, editor.id); + } + } }); }, + /* Finds any tinyMCE editor whose target element/textarea has the className passed @param className - A string representing the class name of the tinyMCE editor @@ -109,12 +116,11 @@ export const Tinymce = { */ findEditorsByClassName(className) { if (isString(className)) { - return tinymce.editors.reduce((acc, e) => { - if ($(e.getElement()).hasClass(className)) { - return acc.concat([e]); - } - return acc; - }, []); + const elements = Array.from(document.getElementsByClassName(className)); + // Fetch the textarea elements and then return the TinyMCE editors associated with the element ids + return elements.map((el) => { + return Tinymce.findEditorById(el.getAttribute('id')); + }); } return []; }, @@ -126,7 +132,7 @@ export const Tinymce = { */ findEditorById(id) { if (isString(id)) { - return tinymce.editors.find(el => el.id === id); + return tinymce.get(id); } return undefined; }, @@ -139,7 +145,14 @@ export const Tinymce = { */ destroyEditorsByClassName(className) { const editors = this.findEditorsByClassName(className); - editors.forEach(ed => ed.destroy(false)); + if (editors.length > 0) { + /* editors.forEach(ed => ed.destroy(false)); */ + for (const editor of editors) { + if (isTinymceEditor(editor)) { + editor.destroy(false); + } + } + } }, /* Destroy an editor instance whose target element/textarea has HTML id passed. This method @@ -148,7 +161,7 @@ export const Tinymce = { */ destroyEditorById(id) { const editor = this.findEditorById(id); - if (editor) { + if (isTinymceEditor(editor)) { editor.destroy(false); } }, diff --git a/app/models/concerns/exportable_plan.rb b/app/models/concerns/exportable_plan.rb index da67a8106b..3bd4981fd9 100644 --- a/app/models/concerns/exportable_plan.rb +++ b/app/models/concerns/exportable_plan.rb @@ -244,7 +244,7 @@ def prepare_research_outputs_for_csv(csv, _headings, hash) csv << [_('Research Outputs: ')] # Convert the hash keys to column headers - csv << hash[:research_outputs].first.keys.map { |key| key.to_s.capitalize.gsub('_', ' ') } + csv << hash[:research_outputs].first.keys.map { |key| key.to_s.capitalize.tr('_', ' ') } hash[:research_outputs].each do |research_output| csv << research_output.values end diff --git a/app/models/concerns/identifiable.rb b/app/models/concerns/identifiable.rb index e1f0e01eef..217d13b32b 100644 --- a/app/models/concerns/identifiable.rb +++ b/app/models/concerns/identifiable.rb @@ -54,7 +54,7 @@ def self.from_identifiers(array:) # gets the identifier for the scheme def identifier_for_scheme(scheme:) scheme = IdentifierScheme.by_name(scheme.downcase).first if scheme.is_a?(String) - identifiers.select { |id| id.identifier_scheme == scheme }.last + identifiers.reverse.find { |id| id.identifier_scheme == scheme } end # Combines the existing identifiers with the new ones diff --git a/app/models/exported_plan.rb b/app/models/exported_plan.rb index b8e8313805..65890d3286 100644 --- a/app/models/exported_plan.rb +++ b/app/models/exported_plan.rb @@ -67,7 +67,7 @@ def project_description end def owner - plan.roles.to_a.select(&:creator?).first.user + plan.roles.to_a.find(&:creator?)&.user end def funder diff --git a/app/models/identifier.rb b/app/models/identifier.rb index 8d8a6221ca..9b7236407a 100644 --- a/app/models/identifier.rb +++ b/app/models/identifier.rb @@ -96,7 +96,7 @@ def identifier_format return 'ark' if value.include?('ark:') doi_regex = %r{(doi:)?[0-9]{2}\.[0-9]+/.} - return 'doi' if value =~ doi_regex + return 'doi' if value.match?(doi_regex) return 'url' if value.starts_with?('http') diff --git a/app/models/plan.rb b/app/models/plan.rb index 361e64f667..9ce1a9450a 100644 --- a/app/models/plan.rb +++ b/app/models/plan.rb @@ -62,6 +62,8 @@ class Plan < ApplicationRecord # = Constants = # ============= + DMP_ID_TYPES = %w[ark doi].freeze + # Returns visibility message given a Symbol type visibility passed, otherwise # nil VISIBILITY_MESSAGE = { @@ -79,6 +81,12 @@ class Plan < ApplicationRecord privately_visible: _('private') }.freeze + FUNDING_STATUS = { + planned: _('Planned'), + funded: _('Funded'), + denied: _('Denied') + }.freeze + # ============== # = Attributes = # ============== @@ -438,7 +446,7 @@ def settings(key) # rubocop:disable Metrics/AbcSize, Style/OptionalBooleanParameter def answer(qid, create_if_missing = true) answer = answers.select { |a| a.question_id == qid } - .max { |a, b| a.created_at <=> b.created_at } + .max_by(&:created_at) if answer.nil? && create_if_missing question = Question.find(qid) answer = Answer.new @@ -621,7 +629,7 @@ def latest_update # Returns nil def owner r = roles.select { |rr| rr.active && rr.administrator } - .min { |a, b| a.created_at <=> b.created_at } + .min_by(&:created_at) r&.user end @@ -691,7 +699,7 @@ def authors # # Returns Integer def num_answered_questions(phase = nil) - return answers.select(&:answered?).length unless phase.present? + return answers.count(&:answered?) unless phase.present? answered = answers.select do |answer| answer.answered? && phase.questions.include?(answer.question) @@ -759,7 +767,7 @@ def deactivate! # Returns the plan's identifier (either a DOI/ARK) def landing_page - identifiers.select { |i| %w[doi ark].include?(i.identifier_format) }.first + identifiers.find { |i| DMP_ID_TYPES.include?(i.identifier_format) } end # Since the Grant is not a normal AR association, override the getter and setter diff --git a/app/models/question.rb b/app/models/question.rb index 03da719e99..0147f3260f 100644 --- a/app/models/question.rb +++ b/app/models/question.rb @@ -172,7 +172,7 @@ def example_answers(org_ids) org_ids = Array(org_ids) annotations.select { |a| org_ids.include?(a.org_id) } .select(&:example_answer?) - .sort { |a, b| a.created_at <=> b.created_at } + .sort_by(&:created_at) end alias get_example_answers example_answers @@ -188,8 +188,7 @@ def example_answers(org_ids) # Returns Annotation def guidance_annotation(org_id) annotations.select { |a| a.org_id == org_id } - .select(&:guidance?) - .first + .find(&:guidance?) end alias get_guidance_annotation guidance_annotation diff --git a/app/presenters/api/v1/contributor_presenter.rb b/app/presenters/api/v1/contributor_presenter.rb index 4ed0c1ca68..185e98de56 100644 --- a/app/presenters/api/v1/contributor_presenter.rb +++ b/app/presenters/api/v1/contributor_presenter.rb @@ -8,13 +8,13 @@ class << self # Convert the specified role into a CRediT Taxonomy URL def role_as_uri(role:) return nil unless role.present? - return 'other' if role.to_s.downcase == 'other' + return 'other' if role.to_s.casecmp('other').zero? - "#{Contributor::ONTOLOGY_BASE_URL}/#{role.to_s.downcase.gsub('_', '-')}" + "#{Contributor::ONTOLOGY_BASE_URL}/#{role.to_s.downcase.tr('_', '-')}" end def contributor_id(identifiers:) - identifiers.select { |id| id.identifier_scheme.name == 'orcid' }.first + identifiers.find { |id| id.identifier_scheme.name == 'orcid' } end end end diff --git a/app/presenters/api/v1/org_presenter.rb b/app/presenters/api/v1/org_presenter.rb index 5ada0166b1..8e14483881 100644 --- a/app/presenters/api/v1/org_presenter.rb +++ b/app/presenters/api/v1/org_presenter.rb @@ -6,10 +6,10 @@ module V1 class OrgPresenter class << self def affiliation_id(identifiers:) - ident = identifiers.select { |id| id.identifier_scheme&.name == 'ror' }.first + ident = identifiers.find { |id| id.identifier_scheme&.name == 'ror' } return ident if ident.present? - identifiers.select { |id| id.identifier_scheme&.name == 'fundref' }.first + identifiers.find { |id| id.identifier_scheme&.name == 'fundref' } end end end diff --git a/app/presenters/api/v1/plan_presenter.rb b/app/presenters/api/v1/plan_presenter.rb index 291f3bb930..e871e2fa32 100644 --- a/app/presenters/api/v1/plan_presenter.rb +++ b/app/presenters/api/v1/plan_presenter.rb @@ -27,7 +27,7 @@ def initialize(plan:) # Extract the ARK or DOI for the DMP OR use its URL if none exists def identifier doi = @plan.identifiers.select do |id| - %w[ark doi].include?(id.identifier_format) + ::Plan::DMP_ID_TYPES.include?(id.identifier_format) end return doi.first if doi.first.present? diff --git a/app/presenters/api/v1/research_output_presenter.rb b/app/presenters/api/v1/research_output_presenter.rb index 28b7325312..885586045e 100644 --- a/app/presenters/api/v1/research_output_presenter.rb +++ b/app/presenters/api/v1/research_output_presenter.rb @@ -62,7 +62,7 @@ def fetch_q_and_a(themes:) ret = themes.map do |theme| qs = @plan.questions.select { |q| q.themes.collect(&:title).include?(theme) } descr = qs.map do |q| - a = @plan.answers.select { |ans| ans.question_id = q.id }.first + a = @plan.answers.find { |ans| ans.question_id = q.id } next unless a.present? && !a.blank? "Question: #{q.text}
Answer: #{a.text}" diff --git a/app/presenters/identifier_presenter.rb b/app/presenters/identifier_presenter.rb index 77e66d6bbe..00a86f9cc2 100644 --- a/app/presenters/identifier_presenter.rb +++ b/app/presenters/identifier_presenter.rb @@ -19,7 +19,7 @@ def id_for_scheme(scheme:) end def scheme_by_name(name:) - schemes.select { |scheme| scheme.name.downcase == name.downcase } + schemes.select { |scheme| scheme.name.casecmp(name).zero? } end private @@ -39,7 +39,7 @@ def load_schemes # a curated list of Orgs that can use institutional login if @identifiable.is_a?(Org) && !Rails.configuration.x.shibboleth.use_filtered_discovery_service - schemes = schemes.reject { |scheme| scheme.name.downcase == 'shibboleth' } + schemes = schemes.reject { |scheme| scheme.name.casecmp('shibboleth').zero? } end schemes end diff --git a/app/presenters/org_selection_presenter.rb b/app/presenters/org_selection_presenter.rb index 2d8820eecc..2f0b2ffc82 100644 --- a/app/presenters/org_selection_presenter.rb +++ b/app/presenters/org_selection_presenter.rb @@ -33,7 +33,7 @@ def select_list def crosswalk_entry_from_org_id(value:) return {}.to_json unless value.present? && value.to_s =~ /[0-9]+/ - entry = @crosswalk.select { |item| item[:id].to_s == value.to_s }.first + entry = @crosswalk.find { |item| item[:id].to_s == value.to_s } entry.present? ? entry.to_json : {}.to_json rescue StandardError {}.to_json diff --git a/app/presenters/research_output_presenter.rb b/app/presenters/research_output_presenter.rb index 3a77f1be80..4187b5d870 100644 --- a/app/presenters/research_output_presenter.rb +++ b/app/presenters/research_output_presenter.rb @@ -37,7 +37,7 @@ def selectable_metadata_standards(category:) # Returns the available licenses for a select tag def complete_licenses License.selectable - .sort { |a, b| a.identifier <=> b.identifier } + .sort_by(&:identifier) .map { |license| [license.identifier, license.id] } end @@ -71,7 +71,7 @@ def self.selectable_subjects '12-Social and Behavioural Sciences', '42-Thermal Engineering/Process Engineering' ].map do |subject| - [subject.split('-').last, subject.gsub('-', ' ')] + [subject.split('-').last, subject.tr('-', ' ')] end end @@ -111,7 +111,7 @@ def display_type # Return the user entered text for the type if they selected 'other' return @research_output.output_type_description if @research_output.other? - @research_output.output_type.gsub('_', ' ').capitalize + @research_output.output_type.tr('_', ' ').capitalize end # Returns the display name(s) of the repository(ies) diff --git a/app/presenters/super_admin/orgs/merge_presenter.rb b/app/presenters/super_admin/orgs/merge_presenter.rb index 7adad112c3..6966baa5de 100644 --- a/app/presenters/super_admin/orgs/merge_presenter.rb +++ b/app/presenters/super_admin/orgs/merge_presenter.rb @@ -22,7 +22,7 @@ def initialize(from_org:, to_org:) @from_org_entries = prepare_org(org: @from_org) @to_org_entries = prepare_org(org: @to_org) @mergeable_entries = prepare_mergeables - @categories = @from_org_entries.keys.sort { |a, b| a <=> b } + @categories = @from_org_entries.keys.sort # Specific Org columns @from_org_attributes = org_attributes(org: @from_org) @@ -39,19 +39,19 @@ def prepare_org(org:) return {} unless org.present? && org.is_a?(Org) { - annotations: org.annotations.sort { |a, b| a.text <=> b.text }, - departments: org.departments.sort { |a, b| a.name <=> b.name }, - funded_plans: org.funded_plans.sort { |a, b| a.title <=> b.title }, + annotations: org.annotations.sort_by(&:text), + departments: org.departments.sort_by(&:name), + funded_plans: org.funded_plans.sort_by(&:title), guidances: org.guidance_groups.collect(&:guidances).flatten, - identifiers: org.identifiers.sort { |a, b| a.value <=> b.value }, + identifiers: org.identifiers.sort_by(&:value), # TODO: Org.plans is overridden and does not clearly identify Orgs that 'own' # the plan (i.e. the one the user selected as the 'Research Org') # Loading them directly here until issue #2724 is resolved - plans: Plan.where(org: org).sort { |a, b| a.title <=> b.title }, - templates: org.templates.sort { |a, b| a.title <=> b.title }, - token_permission_types: org.token_permission_types.sort { |a, b| a.to_s <=> b.to_s }, + plans: Plan.where(org: org).sort_by(&:title), + templates: org.templates.sort_by(&:title), + token_permission_types: org.token_permission_types.sort_by(&:to_s), tracker: [org.tracker].compact, - users: org.users.sort { |a, b| a.email <=> b.email } + users: org.users.sort_by(&:email) } end # rubocop:enable Metrics/AbcSize diff --git a/app/services/api/v1/deserialization/org.rb b/app/services/api/v1/deserialization/org.rb index 65773951b6..3bbb252ab2 100644 --- a/app/services/api/v1/deserialization/org.rb +++ b/app/services/api/v1/deserialization/org.rb @@ -64,7 +64,8 @@ def find_by_name(json: {}) # Grab the closest match - only caring about results that 'contain' # the name with preference to those that start with the name - result = results.select { |r| %i[0 1].include?(r[:weight]) }.first + match_weights = %i[0 1] + result = results.find { |r| match_weights.include?(r[:weight]) } # If no good result was found just use the specified name result ||= { name: name } diff --git a/app/services/api/v1/persistence_service.rb b/app/services/api/v1/persistence_service.rb index 68c83f79da..55fe37d4b6 100644 --- a/app/services/api/v1/persistence_service.rb +++ b/app/services/api/v1/persistence_service.rb @@ -96,7 +96,7 @@ def deduplicate_contributors(contributors:) next unless contributor.is_a?(Contributor) # See if we've already processed this contributor - existing = out.select { |c| c == contributor }.first + existing = out.find { |c| c == contributor } out << contributor unless existing.present? next unless existing.present? diff --git a/app/services/org/monthly_usage_service.rb b/app/services/org/monthly_usage_service.rb index 6d6b460181..c663c77843 100644 --- a/app/services/org/monthly_usage_service.rb +++ b/app/services/org/monthly_usage_service.rb @@ -34,7 +34,7 @@ def reducer_body(acc, rec, key_target) else args = { month: month } args[key_target] = count - acc[month] = build_model(args) + acc[month] = build_model(**args) end acc diff --git a/app/validators/email_validator.rb b/app/validators/email_validator.rb index 6a3709a13b..bf7242740a 100644 --- a/app/validators/email_validator.rb +++ b/app/validators/email_validator.rb @@ -3,7 +3,7 @@ # Validation for email format class EmailValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) - return if value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i + return if value&.match?(/\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i) record.errors.add(attribute, options.fetch(:message, 'is not a valid email address')) end diff --git a/app/validators/url_validator.rb b/app/validators/url_validator.rb index 3f344b6ce7..2ba9ebb561 100644 --- a/app/validators/url_validator.rb +++ b/app/validators/url_validator.rb @@ -4,7 +4,7 @@ class UrlValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) reg = %r{https?://[-a-zA-Z0-9@:%_+.~#?&/=]{2,256}\.[a-z]{2,4}\b(/[-a-zA-Z0-9@:%_+.~#?&/=]*)?} - return unless value =~ reg + return unless value&.match?(reg) record.errors.add(attribute, options.fetch(:message, 'is not a valid URL')) end diff --git a/app/views/api/v0/plans/index.json.jbuilder b/app/views/api/v0/plans/index.json.jbuilder index 516c9ef98d..056f828cdb 100644 --- a/app/views/api/v0/plans/index.json.jbuilder +++ b/app/views/api/v0/plans/index.json.jbuilder @@ -57,7 +57,7 @@ json.array! @plans.each do |plan| json.themes question.themes.each do |theme| json.theme theme.title end - answer = plan.answers.select { |a| a.question_id == question.id }.first + answer = plan.answers.find { |a| a.question_id == question.id } if answer.present? json.answered true json.answer do diff --git a/app/views/api/v1/datasets/_show.json.jbuilder b/app/views/api/v1/datasets/_show.json.jbuilder index 2aab9e3a7b..5b5b4e0863 100644 --- a/app/views/api/v1/datasets/_show.json.jbuilder +++ b/app/views/api/v1/datasets/_show.json.jbuilder @@ -46,7 +46,7 @@ if output.is_a?(ResearchOutput) end json.metadata output.metadata_standards do |metadata_standard| - website = metadata_standard.locations.select { |loc| loc["type"] == "website" }.first + website = metadata_standard.locations.find { |loc| loc["type"] == "website" } website = { url: "" } unless website.present? descr_array = [metadata_standard.title, metadata_standard.description, website["url"]] diff --git a/app/views/branded/contact_us/contacts/_new_left.html.erb b/app/views/branded/contact_us/contacts/_new_left.html.erb index 1620aed144..034fadd7b9 100644 --- a/app/views/branded/contact_us/contacts/_new_left.html.erb +++ b/app/views/branded/contact_us/contacts/_new_left.html.erb @@ -36,9 +36,13 @@ <% if !user_signed_in? && Rails.configuration.x.recaptcha.enabled then %>
- <%= label_tag(nil, _('Security check')) %> - <%= recaptcha_tags %> + <% if @show_checkbox_recaptcha %> + <%= label_tag(nil, _('Security check')) %> + <%= recaptcha_tags %> + <% else %> + <%= recaptcha_v3(action: 'contact_us') %> + <% end %>
<% end %> <%= f.button(_('Send'), class: "btn btn-default", type: "submit") %> -<% end %> \ No newline at end of file +<% end %> diff --git a/app/views/branded/contributors/index.html.erb b/app/views/branded/contributors/index.html.erb index a32f0c6bad..cea03c59b5 100644 --- a/app/views/branded/contributors/index.html.erb +++ b/app/views/branded/contributors/index.html.erb @@ -18,27 +18,16 @@

- - - - - - - - - - - <%= paginable_renderise partial: "/paginable/contributors/index", - controller: "paginable/contributors", - action: "index", - scope: @contributors, - locals: { plan: @plan } %> - - -
<%= _("Name") %><%= _("Affiliation") %><%= _("Attributed roles (Associated research outputs)") %> - <%= _("Actions") %> -
- + <% if @contributors.any? %> + <%= paginable_renderise partial: "/paginable/contributors/index", + controller: "paginable/contributors", + action: "index", + remote: true, + scope: @contributors, + locals: { plan: @plan } %> + <% else %> +

<%= _("No contributors have been defined.") %>

+ <% end %>
<% if @plan.editable_by?(current_user.id) && @plan.template.structured? %> @@ -65,4 +54,4 @@ <% end %> <%= render partial: "plans/navigation", locals: { plan: @plan } %> - \ No newline at end of file + diff --git a/app/views/branded/paginable/contributors/_index.html.erb b/app/views/branded/paginable/contributors/_index.html.erb index 505d458fbd..2e11623589 100644 --- a/app/views/branded/paginable/contributors/_index.html.erb +++ b/app/views/branded/paginable/contributors/_index.html.erb @@ -1,68 +1,82 @@ <%# locals: @plan, scope %> <% template_locale = LocaleService.to_gettext(locale: @plan.template.locale) %> -<% scope.each do |contributor| %> - <% person_id = contributor.data["personId"]%> - - - <%= contributor.to_s %> - <% if person_id.present? %> - - <%= uri?(person_id) ? link_to(person_id, person_id, target: "_blank") : person_id %> - <% end %> - - <%= contributor.data["affiliationName"] %> - - - - - <% if @plan.editable_by?(current_user.id) %> - <%= link_to new_edit_linked_madmp_fragments_url( - :fragment_id => contributor.id, - :parent_id => contributor.dmp_id, - :schema_id => contributor.madmp_schema_id, - :template_locale => template_locale, - :source => "list-modal", - :query_id => "contributor" - ), { - :remote => true, - :target => "_self", - 'data-toggle' => 'modal', - 'data-target' => '#modal-window' - } do %> - - <% end %> - <% if scope.length > 1 %> - - <% end %> - <% else %> - <%= link_to show_linked_madmp_fragments_url( - :fragment_id => contributor.id, - :template_locale => template_locale, - ), { - :remote => true, - 'data-toggle' => 'modal', - 'data-target' => '#modal-window' - } do %> - - <% end %> - <% end %> - - -<% end %> + + + + + + + + + + + <% scope.each do |contributor| %> + <% person_id = contributor.data["personId"]%> + + + + + + + <% end %> + +
<%= _("Name") %><%= _("Affiliation") %><%= _("Attributed roles (Associated research outputs)") %> + <%= _("Actions") %> +
+ <%= contributor.to_s %> + <% if person_id.present? %> + - <%= uri?(person_id) ? link_to(person_id, person_id, target: "_blank") : person_id %> + <% end %> + <%= contributor.data["affiliationName"] %> +
    + <% contributor.roles.each do |role| %> +
  • <%= role %>
  • + <% end %> +
+
+ <% if @plan.editable_by?(current_user.id) %> + <%= link_to new_edit_linked_madmp_fragments_url( + :fragment_id => contributor.id, + :parent_id => contributor.dmp_id, + :schema_id => contributor.madmp_schema_id, + :template_locale => template_locale, + :source => "list-modal", + :query_id => "contributor" + ), { + :remote => true, + :target => "_self", + 'data-toggle' => 'modal', + 'data-target' => '#modal-window' + } do %> + + <% end %> + <% if scope.length > 1 %> + + <% end %> + <% else %> + <%= link_to show_linked_madmp_fragments_url( + :fragment_id => contributor.id, + :template_locale => template_locale, + ), { + :remote => true, + 'data-toggle' => 'modal', + 'data-target' => '#modal-window' + } do %> + + <% end %> + <% end %> +
diff --git a/app/views/branded/plans/_share_form.html.erb b/app/views/branded/plans/_share_form.html.erb index 704d9ed5f4..ccde5df928 100644 --- a/app/views/branded/plans/_share_form.html.erb +++ b/app/views/branded/plans/_share_form.html.erb @@ -146,7 +146,7 @@ <%= f.radio_button :visibility, :publicly_visible, class: 'set-plan-public', data: { - confirm_message: _('You have chosen to make your plan public. Before confirming, please ensure that your plan is complete. This DMP can be used as an example for other, please fill the different sections. Do you confirm?') + confirm_message: _('You have chosen to make your plan public. Before confirming, please ensure that your plan is complete. This DMP can be used as an example for other, please fill the different sections. Do you confirm and then update your choice?') } %> <%= _('Public: anyone can view') %> diff --git a/app/views/branded/shared/_create_account_form.html.erb b/app/views/branded/shared/_create_account_form.html.erb index 8fcc5a80e2..882aaf5e24 100644 --- a/app/views/branded/shared/_create_account_form.html.erb +++ b/app/views/branded/shared/_create_account_form.html.erb @@ -43,8 +43,12 @@
<% if Rails.configuration.x.recaptcha.enabled %>
- <%= label_tag(nil, _('Security check')) %> - <%= recaptcha_tags %> + <% if @show_checkbox_recaptcha %> + <%= label_tag(nil, _('Security check')) %> + <%= recaptcha_tags %> + <% else %> + <%= recaptcha_v3(action: 'create_account') %> + <% end %>
<% end %> diff --git a/app/views/org_admin/conditions/_webhook_form.html.erb b/app/views/org_admin/conditions/_webhook_form.html.erb index 6613139bb6..8230e61b63 100644 --- a/app/views/org_admin/conditions/_webhook_form.html.erb +++ b/app/views/org_admin/conditions/_webhook_form.html.erb @@ -9,33 +9,33 @@