diff --git a/.github/workflows/eslint.yml b/.github/workflows/eslint.yml index f2bd4b3464..b05c9807c9 100644 --- a/.github/workflows/eslint.yml +++ b/.github/workflows/eslint.yml @@ -8,9 +8,19 @@ jobs: runs-on: ubuntu-latest steps: + # Checkout the repo - uses: actions/checkout@v2 - # Will run ES Lint checks on javascript files + # Install Node + - uses: actions/setup-node@v2 + with: + cache: 'yarn' + + # Run yarn install for JS dependencies + - name: 'Yarn Install' + run: yarn install + + # Run the ES Lint checks on javascript files # https://github.com/marketplace/actions/run-eslint - name: 'ES Lint checks' uses: stefanoeb/eslint-action@1.0.0 diff --git a/.github/workflows/mysql.yml b/.github/workflows/mysql.yml index 740bd53d1f..6c6a4395fb 100644 --- a/.github/workflows/mysql.yml +++ b/.github/workflows/mysql.yml @@ -6,6 +6,7 @@ jobs: mysql: runs-on: ubuntu-latest + # Define environment variables for MySQL and Rails env: DB_ADAPTER: mysql2 MYSQL_PWD: root @@ -14,94 +15,59 @@ jobs: steps: # Checkout the repo - uses: actions/checkout@v2 - with: - fetch-depth: 1 - - - name: 'Install MySQL Packages' - run: | - sudo apt-get update - sudo apt-get install -y mysql-client libmysqlclient-dev - - name: 'Determine Ruby and Bundler Versions from Gemfile.lock' - run: | - echo "RUBY_VERSION=`cat ./Gemfile.lock | grep -A 1 'RUBY VERSION' | grep 'ruby' | grep -oE '[0-9]\.[0-9]'`" >> $GITHUB_ENV - echo "BUNDLER_VERSION=`cat ./Gemfile.lock | grep -A 1 'BUNDLED WITH' | grep -oE '[0-9]\.[0-9]'`" >> $GITHUB_ENV + # Install Ruby and run bundler + - uses: ruby/setup-ruby@v1 + with: + ruby-version: 2.6.3 + bundler-cache: true - - name: 'Install Ruby' - uses: actions/setup-ruby@v1 + # Install Node + - uses: actions/setup-node@v2 with: - ruby-version: ${{ env.RUBY_VERSION }} + cache: 'yarn' # Copy all of the example configs over - name: 'Setup Default Configuration' run: | - # Make copies of all the example config files cp config/database.yml.sample config/database.yml cp config/initializers/contact_us.rb.example config/initializers/contact_us.rb cp config/initializers/wicked_pdf.rb.example config/initializers/wicked_pdf.rb - # Try to retrieve the gems from the cache - - name: 'Cache Gems' - uses: actions/cache@v2.1.5 - with: - path: vendor/bundle - key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }} - restore-keys: | - ${{ runner.os }}-gem- - - - name: 'Bundle Install' - run: | - gem install bundler -v ${{ env.BUNDLER_VERSION }} - bundle config path vendor/bundle - bundle install --jobs 4 --retry 3 --without pgsql rollbar aws - + # Stub out the Rails credentials file so that we can start the Rails app - name: 'Setup Credentials' - run: | - # generate a default credential file and key - EDITOR='echo "$(cat config/credentials.yml.example)" >' bundle exec rails credentials:edit - - # Try to retrieve the yarn JS dependencies from the cache - - name: 'Cache Yarn Packages' - uses: actions/cache@v2.1.5 - with: - path: node_modules/ - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-build-${{ env.cache-name }}- - ${{ runner.os }}-build- - ${{ runner.os }}-yarn- - ${{ runner.os }}- + run: EDITOR='echo "$(cat config/credentials.yml.example)" >' bundle exec rails credentials:edit + # Set the path to the wkhtmltopdf executable - name: 'Determine wkhtmltopdf location' run: echo "WICKED_PDF_PATH=`bundle exec which wkhtmltopdf`" >> $GITHUB_ENV + # Run yarn install for JS dependencies - name: 'Yarn Install' - run: | - yarn install + run: yarn install + # Start the DB server and initialize the DB - name: 'Start MySQL' - run: sudo systemctl start mysql - - - name: 'Setup Test DB' - run: bin/rails db:setup RAILS_ENV=test - - - name: 'Migrate DB' - run: bin/rails db:migrate RAILS_ENV=test - - - name: 'Compile Assets' run: | - bin/rails webpacker:compile - bin/rails assets:precompile + sudo systemctl start mysql + bin/rails db:setup RAILS_ENV=test + bin/rails db:migrate RAILS_ENV=test + + # Prebuild the CSS, JS and image assets + - name: 'Precompile all of the Assets' + run: bin/rails assets:precompile + # Run the JS tests - name: 'Run Karma Tests' run: yarn test + # Run the unit and functional tests - name: 'Run Rspec Unit and Functional Tests' run: | bin/bundle exec rspec spec/models/ spec/policies/ spec/services/ spec/helpers/ bin/bundle exec rspec spec/controllers/ spec/presenters/ spec/requests/ spec/views bin/bundle exec rspec spec/mixins/ - # Only run Integration tests if the PR or Push is to master or development branches + # Run the time consuming integration tests (using Chrome headless browser) - name: 'Run Rspec Integration Tests' run: bin/bundle exec rspec spec/features/ diff --git a/.github/workflows/postgres.yml b/.github/workflows/postgres.yml index 5681a97364..504983a12a 100644 --- a/.github/workflows/postgres.yml +++ b/.github/workflows/postgres.yml @@ -23,6 +23,7 @@ jobs: --health-timeout 5s --health-retries 5 + # Define environment variables for Postgres and Rails env: RAILS_ENV: test DATABASE_URL: postgres://postgres:@localhost:5432/roadmap_test @@ -30,92 +31,67 @@ jobs: steps: # Checkout the repo - uses: actions/checkout@v2 + + # Install Ruby and run bundler + - uses: ruby/setup-ruby@v1 + with: + ruby-version: 2.6.3 + bundler-cache: true + + # Install Node + - uses: actions/setup-node@v2 with: - fetch-depth: 1 + cache: 'yarn' + # Install the Postgres developer packages - name: 'Install Postgresql Packages' run: | sudo apt-get update sudo apt-get install libpq-dev - - name: 'Determine Ruby and Bundler Versions from Gemfile.lock' - run: | - echo "RUBY_VERSION=`cat ./Gemfile.lock | grep -A 1 'RUBY VERSION' | grep 'ruby' | grep -oE '[0-9]\.[0-9]'`" >> $GITHUB_ENV - echo "BUNDLER_VERSION=`cat ./Gemfile.lock | grep -A 1 'BUNDLED WITH' | grep -oE '[0-9]\.[0-9]'`" >> $GITHUB_ENV - - # Install Ruby - using the version found in the Gemfile.lock - - name: 'Install Ruby' - uses: actions/setup-ruby@v1 - with: - ruby-version: ${{ env.RUBY_VERSION }} - # Copy all of the example configs over - name: 'Setup Default Configuration' run: | - # Make copies of all the example config files cp config/database.yml.sample config/database.yml cp config/initializers/contact_us.rb.example config/initializers/contact_us.rb cp config/initializers/wicked_pdf.rb.example config/initializers/wicked_pdf.rb - # Try to retrieve the gems from the cache - - name: 'Cache Gems' - uses: actions/cache@v2.1.5 - with: - path: vendor/bundle - key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }} - restore-keys: | - ${{ runner.os }}-gem- - - - name: 'Bundle Install' - run: | - gem install bundler -v ${{ env.BUNDLER_VERSION }} - bundle config path vendor/bundle - bundle install --jobs 4 --retry 3 --without mysql rollbar aws - + # Stub out the Rails credentials file so that we can start the Rails app - name: 'Setup Credentials' run: | # generate a default credential file and key EDITOR='echo "$(cat config/credentials.yml.example)" >' bundle exec rails credentials:edit - # Try to retrieve the yarn JS dependencies from the cache - - name: 'Cache Yarn Packages' - uses: actions/cache@v2.1.5 - with: - path: node_modules/ - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-build-${{ env.cache-name }}- - ${{ runner.os }}-build- - ${{ runner.os }}-yarn- - ${{ runner.os }}- - + # Set the path to the wkhtmltopdf executable - name: 'Determine wkhtmltopdf location' run: echo "WICKED_PDF_PATH=`bundle exec which wkhtmltopdf`" >> $GITHUB_ENV + # Run yarn install for JS dependencies - name: 'Yarn Install' run: | yarn install + # Initialize the DB - name: 'Setup Test DB' - run: bin/rails db:setup RAILS_ENV=test - - - name: 'Migrate DB' - run: bin/rails db:migrate RAILS_ENV=test + run: | + bin/rails db:setup RAILS_ENV=test + bin/rails db:migrate RAILS_ENV=test + # Prebuild the CSS, JS and image assets - name: 'Compile Assets' - run: | - bin/rails webpacker:compile - bin/rails assets:precompile + run: bin/rails assets:precompile + # Run the JS tests - name: 'Run Karma Tests' run: yarn test + # Run the unit and functional tests - name: 'Run Rspec Unit and Functional Tests' run: | bin/rspec spec/models/ spec/policies/ spec/services/ spec/helpers/ bin/rspec spec/controllers/ spec/presenters/ spec/requests/ spec/views bin/rspec spec/mixins/ - # Integration Tests are only run if PR or Push is to master or development branches + # Run the time consuming integration tests (using Chrome headless browser) - name: 'Run Integration Tests' run: bin/rspec spec/features/ diff --git a/.github/workflows/rubocop.yml b/.github/workflows/rubocop.yml index 085bbb0183..0fd54b1cc4 100644 --- a/.github/workflows/rubocop.yml +++ b/.github/workflows/rubocop.yml @@ -7,24 +7,15 @@ jobs: runs-on: ubuntu-latest steps: + # Checkout the repo - uses: actions/checkout@v2 - - name: 'Determine Ruby and Bundler Versions from Gemfile.lock' - run: | - echo "RUBY_VERSION=`cat ./Gemfile.lock | grep -A 1 'RUBY VERSION' | grep 'ruby' | grep -oE '[0-9]\.[0-9]'`" >> $GITHUB_ENV - echo "BUNDLER_VERSION=`cat ./Gemfile.lock | grep -A 1 'BUNDLED WITH' | grep -oE '[0-9]\.[0-9]'`" >> $GITHUB_ENV - - # Install Ruby - using the version found in the Gemfile.lock - - name: 'Install Ruby' - uses: actions/setup-ruby@v1 + # Install Ruby and run bundler + - uses: ruby/setup-ruby@v1 with: - ruby-version: ${{ env.RUBY_VERSION }} - - - name: 'Bundle Install' - run: | - gem install bundler -v ${{ env.BUNDLER_VERSION }} - bundle config path vendor/bundle - bundle install --jobs 4 --retry 3 --without pgsql rollbar aws + ruby-version: 2.6.3 + bundler-cache: true + # Run the Rubocop linter checks - name: 'Run Rubocop' run: bin/rubocop diff --git a/.rubocop.yml b/.rubocop.yml index af6bbac1ba..5a9465c8f2 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,179 +1,186 @@ +# ---------------- +# - INSTRUCTIONS - +# ---------------- +# The DMPRoadmap codebase tries to follow the latest Ruby/Rails style guidelines as defined +# by the community via the Rubocop gem. +# +# Before submitting a PR, please run `bin/rubocop` from the project root. +# Note that you can specify individual files or folders e.g.: `bin/rubocop app/mailers` +# Note you can let Rubocop auto-correct many issues with the `-a` flag +# +# New versions of Rubocop typically include new Cops (Cops are inidivual Rubocop rules). +# If you see a message like the following when you run `bin/rubocop`: +# +# "The following cops were added to RuboCop, but are not configured. Please set Enabled +# to either `true` or `false` in your `.rubocop.yml` file." +# +# You should copy and paste the specified Cops into this file. You can review what the +# Cop will do by Googling the name of the rule e.g.: "rubocop Layout/SpaceBeforeBrackets" +# +# After you review the rule, you can either Enable it or Disable it in this file. The +# Rubocop documentation for the Cop may also give you additional options that can be +# configured. +# +# Try to place any new Cops under their relevant section and in alphabetical order + AllCops: - # Cache the results for faster processing - UseCache: true # Show the name of the cops being voilated in the feedback DisplayCopNames: true DisplayStyleGuide: true + + # Rubocop will skip checking the following directories Exclude: - 'bin/**/*' - 'db/**/*' - 'vendor/**/*' - 'node_modules/**/*' - - 'test/**/*' - - 'lib/tasks/*' + - 'scripts/**/*' -# Force no empty lines at the start or end of a block's body. Ignore specs, since this -# improves readability within the RSpec blocks. -Layout/EmptyLinesAroundBlockBody: - Exclude: - - 'spec/**/*' + # Automatically add any new Cops to this file and enable them + NewCops: enable -# Force a single blank line around a class's body. Adding this whitespace makes code -# a bit easier to read. -Layout/EmptyLinesAroundClassBody: - Enabled: true - EnforcedStyle: empty_lines - -# Force a single blank line around a module's body. Adding this whitespace makes code -# a bit easier to read. -Layout/EmptyLinesAroundModuleBody: - Enabled: true - EnforcedStyle: empty_lines + # Cache the results for faster processing + UseCache: true -# Ignore this cop. The Rubocop default is sensible, but the rubocop-rails gem modifies -# this to position end keywords awkwardly. -Layout/EndAlignment: +# ----------- +# - GEMSPEC - +# ----------- +Gemspec/DateAssignment: # new in 1.10 Enabled: true - EnforcedStyleAlignWith: keyword - -# The difference between `rails` and `normal` is that the `rails` style -# prescribes that in classes and modules the `protected` and `private` -# modifier keywords shall be indented the same as public methods and that -# protected and private members shall be indented one step more than the -# modifiers. Other than that, both styles mean that entities on the same -# logical depth shall have the same indentation. -Layout/IndentationConsistency: - Description: 'Keep indentation straight.' - StyleGuide: '#spaces-indentation' - Enabled: true - EnforcedStyle: normal -Layout/IndentationWidth: - Description: 'Use 2 spaces for indentation.' - StyleGuide: '#spaces-indentation' +# ---------- +# - LAYOUT - +# ---------- +Layout/LineEndStringConcatenationIndentation: # new in 1.18 Enabled: true - -# Restrict the length of each line of code to 90 characters. Enforcing this is important -# as many developers are working on smaller screens, or split screens. Having to scroll -# to read a full line of code makes code harder to read and more frustrating to work with. -Layout/LineLength: - # I've found that 90 is a suitable limit. Many developers balk at the 80 character - # default. - Max: 100 - -Layout/EmptyLinesAroundAttributeAccessor: +Layout/SpaceBeforeBrackets: # new in 1.7 Enabled: true -Layout/SpaceAroundMethodCallOperator: +# -------- +# - LINT - +# -------- +Lint/AmbiguousAssignment: # new in 1.7 Enabled: true - -# Enforce this in the main code but ignore it in specs since the Rspec core methods -# are defined as potentially ambiguous blocks Lint/AmbiguousBlockAssociation: Exclude: - 'spec/**/*' - -Lint/DeprecatedOpenSSLConstant: +Lint/AmbiguousOperatorPrecedence: # new in 1.21 Enabled: true - -Lint/MixedRegexpCaptureTypes: +Lint/AmbiguousRange: # new in 1.19 Enabled: true - -Lint/RaiseException: +Lint/DeprecatedConstants: # new in 1.8 Enabled: true - -Lint/StructNewOverride: +Lint/DuplicateBranch: # new in 1.3 + Enabled: true +Lint/DuplicateRegexpCharacterClassElement: # new in 1.1 + Enabled: true +Lint/EmptyBlock: # new in 1.1 + Enabled: true +Lint/EmptyClass: # new in 1.3 + Enabled: true +Lint/EmptyInPattern: # new in 1.16 + Enabled: true +Lint/IncompatibleIoSelectWithFiberScheduler: # new in 1.21 + Enabled: true +Lint/LambdaWithoutLiteralBlock: # new in 1.8 + Enabled: true +Lint/NoReturnInBeginEndBlocks: # new in 1.2 + Enabled: true +Lint/NumberedParameterAssignment: # new in 1.9 + Enabled: true +Lint/OrAssignmentToConstant: # new in 1.9 + Enabled: true +Lint/RedundantDirGlobSort: # new in 1.8 + Enabled: true +Lint/RequireRelativeSelfPath: # new in 1.22 + Enabled: true +Lint/SymbolConversion: # new in 1.9 + Enabled: true +Lint/ToEnumArguments: # new in 1.1 + Enabled: true +Lint/TripleQuotes: # new in 1.9 + Enabled: true +Lint/UnexpectedBlockArity: # new in 1.5 + Enabled: true +Lint/UnmodifiedReduceAccumulator: # new in 1.1 Enabled: true -# Bumping the default AbcSize so we don't need to refactor everything -Metrics/AbcSize: - Max: 25 - -# Restrict the number of lines of code that may be within a block of code. This should -# force developers to break their code into smaller discrete methods or objects. -Metrics/BlockLength: - # Exclude specs, since those are defined as large blocks of code - Exclude: - - 'spec/**/*' - -# Bumping the default ClassLength so we don't need to refactor everything +# ----------- +# - METRICS - +# ----------- +# briley Oct. 4th 2021 +# Default is 100 lines. Most of our controllers, models, etc. violate this +# Cop, so setting it to 300 since we do not have time to refactor everything Metrics/ClassLength: Max: 300 - -# Bumping the default CyclomaticComplexity so we don't need to refactor everything -Metrics/CyclomaticComplexity: - Max: 25 - -# Bumping the default MethodLength so we don't need to refactor everything +# briley Oct. 4th 2021 +# Default is 10 lines which feels very restrictive but would also require us to do +# too much refactoring at this point. Metrics/MethodLength: - Max: 25 - -# Bumping the default PerceivedComplexity so we don't need to refactor everything -Metrics/PerceivedComplexity: - Max: 25 + Max: 20 -# This cop enforces the use of boolean and/or "&&" and "||" over "and" "or". -# Sometimes using "and"/"or" is preferrable, when these are used as control flow. -# -# For example: -# -# render text: "Hello world" and return -# -Style/AndOr: - Enabled: false - -# This cop enforces how modules and classes are nested within another module or class. -# In Rails code (e.g. models and controllers) nesting with a colon is preferrable (e.g. -# User::Session). -Style/ClassAndModuleChildren: +# mnicholson Oct. 6th 2021 +# Default lenght for block is 25 lines, which it would be very restrictive for +# the Rspec views methods. So I'll just exclude some files. +Metrics/BlockLength: Exclude: - - 'app/**/*' + - 'lib/tasks/*.rake' + - 'lib/tasks/utils/*.rake' + - 'spec/**/*' -# This cop enforces each class to have documentation at the top. That's not always -# practical or necessary in Rails apps (e.g. the purpose of helpers is self evident). -Style/Documentation: - Enabled: false + IgnoredMethods: ['describe', 'context', 'task', 'namespace'] -# Enforce empty methods to be written across two lines, like any normal method would be. -# This allows for easy modification of the method in future. -Style/EmptyMethod: +# ------------ +# - SECURITY - +# ------------ +Security/IoMethods: # new in 1.22 Enabled: true - EnforcedStyle: expanded -# Leave the string formatting style as `"some text %{value}" % { value: "text" }` -# since we're uncertain what effect `format` and `sprintf` may have on the Fastgetext -# markup `_("text")` -Style/FormatString: - EnforcedStyle: percent - -# Prefer the use of `"some %{token} text"` instead of `some % text` or -# `some %token text` since it would invalidate many of our translation strings -Style/FormatStringToken: - EnforcedStyle: template - -# Enforce double quotes. Don't allow single quotes. This is preferred since double -# quotes are more useful (they support escaping characters, and interpolation). -Style/StringLiterals: +# --------- +# - STYLE - +# --------- +Style/ArgumentsForwarding: # new in 1.1 Enabled: true - EnforcedStyle: double_quotes - -Style/ExponentialNotation: +Style/CollectionCompact: # new in 1.2 Enabled: true - -Style/HashEachMethods: +Style/DocumentDynamicEvalDefinition: # new in 1.1 Enabled: true - -Style/HashTransformKeys: +Style/EndlessMethod: # new in 1.8 Enabled: true - -Style/HashTransformValues: +Style/HashConversion: # new in 1.10 Enabled: true - -Style/RedundantRegexpCharacterClass: +Style/HashExcept: # new in 1.7 Enabled: true - -Style/RedundantRegexpEscape: +Style/IfWithBooleanLiteralBranches: # new in 1.9 Enabled: true - -Style/SlicingWithRange: +Style/InPatternThen: # new in 1.16 + Enabled: true +Style/MultilineInPatternThen: # new in 1.16 + Enabled: true +Style/NegatedIfElseCondition: # new in 1.2 + Enabled: true +Style/NilLambda: # new in 1.3 + Enabled: true +Style/NumberedParameters: # new in 1.22 + Enabled: true +Style/NumberedParametersLimit: # new in 1.22 + Enabled: true +Style/OpenStructUse: + Enabled: false # used heavily in API so needs a lot of work to refactor +Style/QuotedSymbols: # new in 1.16 + Enabled: true +Style/RedundantArgument: # new in 1.4 + Enabled: true +Style/RedundantSelfAssignmentBranch: # new in 1.19 + Enabled: true +Style/SelectByRegexp: # new in 1.22 + Enabled: true +Style/StringChars: # new in 1.12 + Enabled: true +Style/StringLiterals: + Enabled: true + Exclude: + - 'app/views/**/*' + - 'config/**/*' +Style/SwapValues: # new in 1.1 Enabled: true diff --git a/Gemfile b/Gemfile index 63820fd17c..ce079fdbaa 100644 --- a/Gemfile +++ b/Gemfile @@ -1,39 +1,39 @@ # frozen_string_literal: true -source "https://rubygems.org" +source 'https://rubygems.org' -ruby ">= 2.6.3" +ruby '>= 2.6.3' # ===========# # CORE RAILS # # ===========# # Full-stack web application framework. (http://rubyonrails.org) -gem "rails", "~> 5.2" +gem 'rails', '~> 5.2' # TODO: Remove this once Rails addresses the issue with its dependency on mimemagic. Mimemagic had # an MIT license but was using some incompatible GPL license code. # Versions of mimemagic that were yanked: https://rubygems.org/gems/mimemagic/versions # Analysis of the issue: https://www.theregister.com/2021/03/25/ruby_rails_code/ -gem "mimemagic", "~> 0.3.7" +gem 'mimemagic', '~> 0.3.7' # Use sqlite3 as the database for Active Record # gem 'sqlite3', '~> 1.4' # Use Puma as the app server -gem "puma", group: :puma, require: false +gem 'puma', group: :puma, require: false # Use SCSS for stylesheets -gem "sass-rails" +gem 'sass-rails' # Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker -gem "webpacker" +gem 'webpacker' # Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks -gem "turbolinks" +gem 'turbolinks' # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder -gem "jbuilder" +gem 'jbuilder' # Use Redis adapter to run Action Cable in production # gem "redis", "~> 4.0" @@ -44,7 +44,7 @@ gem "jbuilder" # gem "image_processing", "~> 1.2" # Reduces boot times through caching; required in config/boot.rb -gem "bootsnap", require: false +gem 'bootsnap', require: false # GEMS ADDED TO HELP HANDLE RAILS MIGRATION FROM 3.x to 4.2 # THESE GEMS HELP SUPPORT DEPRACATED FUNCTIONALITY AND WILL LOSE SUPPORT IN @@ -60,7 +60,7 @@ gem "bootsnap", require: false # Rollbar-gem is the SDK for Ruby apps and includes support for apps using # Rails, Sinatra, Rack, plain Ruby, and other frameworks. -gem "rollbar", group: :rollbar, require: false +gem 'rollbar', group: :rollbar, require: false # ======== # # DATABASE # @@ -68,14 +68,14 @@ gem "rollbar", group: :rollbar, require: false # A simple, fast Mysql library for Ruby, binding to libmysql # (http://github.com/brianmario/mysql2) -gem "mysql2", group: :mysql, require: false +gem 'mysql2', group: :mysql, require: false # Pg is the Ruby interface to the {PostgreSQL # RDBMS}[http://www.postgresql.org/](https://bitbucket.org/ged/ruby-pg) -gem "pg", group: :pgsql, require: false +gem 'pg', group: :pgsql, require: false # Bit fields for ActiveRecord (https://github.com/pboling/flag_shih_tzu) -gem "flag_shih_tzu" # , "~> 0.3.23" +gem 'flag_shih_tzu' # , "~> 0.3.23" # ======== # # SECURITY # @@ -83,35 +83,35 @@ gem "flag_shih_tzu" # , "~> 0.3.23" # Flexible authentication solution for Rails with Warden # (https://github.com/plataformatec/devise) -gem "devise" +gem 'devise' # An invitation strategy for Devise (https://github.com/scambra/devise_invitable) -gem "devise_invitable" +gem 'devise_invitable' # A generalized Rack framework for multiple-provider authentication. # (https://github.com/omniauth/omniauth) -gem "omniauth" +gem 'omniauth' # OmniAuth Shibboleth strategies for OmniAuth 1.x -gem "omniauth-shibboleth" +gem 'omniauth-shibboleth' # ORCID OAuth 2.0 Strategy for OmniAuth 1.0 # (https://github.com/datacite/omniauth-orcid) -gem "omniauth-orcid" +gem 'omniauth-orcid' # This gem provides a mitigation against CVE-2015-9284 (Cross-Site Request # Forgery on the request phase when using OmniAuth gem with a Ruby on Rails # application) by implementing a CSRF token verifier that directly uses # ActionController::RequestForgeryProtection code from Rails. # https://nvd.nist.gov/vuln/detail/CVE-2015-9284 -gem "omniauth-rails_csrf_protection" +gem 'omniauth-rails_csrf_protection' # A ruby implementation of the RFC 7519 OAuth JSON Web Token (JWT) standard. -gem "jwt" +gem 'jwt' # Gems for repository integration # OO authorization for Rails (https://github.com/elabs/pundit) -gem "pundit" +gem 'pundit' # ========== # # UI / VIEWS # @@ -120,22 +120,22 @@ gem "pundit" # Ruby gem to handle settings for ActiveRecord instances by storing them as # serialized Hash in a separate database table. Namespaces and defaults # included. (https://github.com/ledermann/rails-settings) -gem "ledermann-rails-settings" +gem 'ledermann-rails-settings' # Gem providing simple Contact Us functionality with a Rails 3+ Engine. # (https://github.com/jdutil/contact_us) -gem "contact_us" # COULD BE EASILY REPLACED WITH OUR OWN CODE +gem 'contact_us' # COULD BE EASILY REPLACED WITH OUR OWN CODE # Helpers for the reCAPTCHA API (http://github.com/ambethia/recaptcha) -gem "recaptcha" +gem 'recaptcha' # Ideal gem for handling attachments in Rails, Sinatra and Rack applications. # (http://github.com/markevans/dragonfly) -gem "dragonfly" +gem 'dragonfly' group :aws do # Amazon AWS S3 data store for use with the Dragonfly gem. - gem "dragonfly-s3_data_store" + gem 'dragonfly-s3_data_store' end # ========== # @@ -144,21 +144,21 @@ end # A pagination engine plugin for Rails 4+ and other modern frameworks # (https://github.com/kaminari/kaminari) -gem "kaminari" +gem 'kaminari' # Paginate in your headers, not in your response body. This follows the # proposed RFC-8288 standard for Web linking. -gem "api-pagination" +gem 'api-pagination' # =========== # # STYLESHEETS # # =========== # # Integrate SassC-Ruby into Rails. (https://github.com/sass/sassc-rails) -gem "sassc-rails" +gem 'sassc-rails' # Font-Awesome SASS (https://github.com/FortAwesome/font-awesome-sass) -gem "font-awesome-sass", "~> 5.13.0" +gem 'font-awesome-sass', '~> 5.13.0' # Use webpack to manage app-like JavaScript modules in Rails # (https://github.com/rails/webpacker) @@ -166,7 +166,7 @@ gem "font-awesome-sass", "~> 5.13.0" # Parse CSS and add vendor prefixes to CSS rules using values from the Can # I Use website. (https://github.com/ai/autoprefixer-rails) -gem "autoprefixer-rails" +gem 'autoprefixer-rails' # Minimal embedded v8 for Ruby (https://github.com/discourse/mini_racer) # gem "mini_racer" @@ -176,27 +176,27 @@ gem "autoprefixer-rails" # ========= # # Provides binaries for WKHTMLTOPDF project in an easily accessible package. -gem "wkhtmltopdf-binary" +gem 'wkhtmltopdf-binary' # PDF generator (from HTML) gem for Ruby on Rails # (https://github.com/mileszs/wicked_pdf) -gem "wicked_pdf" +gem 'wicked_pdf' # This simple gem allows you to create MS Word docx documents from simple # html documents. This makes it easy to create dynamic reports and forms # that can be downloaded by your users as simple MS Word docx files. # (http://github.com/karnov/htmltoword) -gem "htmltoword" +gem 'htmltoword' # Filename sanitization for Ruby. This is useful when you generate filenames for # downloads from user input -gem "zaru" +gem 'zaru' # ==================== # # INTERNATIONALIZATION # # ==================== # -gem "translation" +gem 'translation' # ========= # # UTILITIES # @@ -205,13 +205,13 @@ gem "translation" # Run any code in parallel Processes(> use all CPUs) or Threads(> speedup # blocking operations). Best suited for map-reduce or e.g. parallel downloads/uploads. # TODO: Replace use of this with ActiveJob where possible -gem "parallel" +gem 'parallel' # Makes http fun again! Wrapper to simplify the native Net::HTTP libraries -gem "httparty" +gem 'httparty' # Autoload dotenv in Rails. (https://github.com/bkeepers/dotenv) -gem "dotenv-rails" +gem 'dotenv-rails' # ================================= # # ENVIRONMENT SPECIFIC DEPENDENCIES # @@ -219,139 +219,159 @@ gem "dotenv-rails" group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console - gem "byebug", platforms: %i[mri mingw x64_mingw] + gem 'byebug', platforms: %i[mri mingw x64_mingw] end group :test do # RSpec for Rails (https://github.com/rspec/rspec-rails) - gem "rspec-rails" + gem 'rspec-rails' # factory_bot_rails provides integration between factory_bot and rails 3 # or newer (http://github.com/thoughtbot/factory_bot_rails) - gem "factory_bot_rails" + gem 'factory_bot_rails' # Easily generate fake data (https://github.com/stympy/faker) - gem "faker" + gem 'faker' # the instafailing RSpec progress bar formatter # (https://github.com/thekompanee/fuubar) - gem "fuubar" + gem 'fuubar' # Guard keeps an eye on your file modifications (http://guardgem.org) - gem "guard" + gem 'guard' # Guard gem for RSpec (https://github.com/guard/guard-rspec) - gem "guard-rspec" + gem 'guard-rspec' # Library for stubbing HTTP requests in Ruby. # (http://github.com/bblimke/webmock) - gem "webmock" + gem 'webmock' # Code coverage for Ruby 1.9+ with a powerful configuration library and # automatic merging of coverage across test suites # (http://github.com/colszowka/simplecov) - gem "simplecov", require: false + # gem 'simplecov', require: false # Strategies for cleaning databases. Can be used to ensure a clean state # for testing. (http://github.com/DatabaseCleaner/database_cleaner) - gem "database_cleaner", require: false + gem 'database_cleaner', require: false # Making tests easy on the fingers and eyes # (https://github.com/thoughtbot/shoulda) - gem "shoulda", require: false + gem 'shoulda', require: false # Mocking and stubbing library (http://gofreerange.com/mocha/docs) - gem "mocha", require: false + gem 'mocha', require: false # Adds support for Capybara system testing and selenium driver - gem "capybara" - gem "selenium-webdriver" + gem 'capybara' + gem 'selenium-webdriver' # Easy installation and use of web drivers to run system tests with browsers - gem "webdrivers" + gem 'webdrivers' # Automatically create snapshots when Cucumber steps fail with Capybara # and Rails (http://github.com/mattheworiordan/capybara-screenshot) - gem "capybara-screenshot" + gem 'capybara-screenshot' # Browser integration tests are expensive. We can mock external requests # in our tests, but once a browser is involved, we lose control. - gem "capybara-webmock" + gem 'capybara-webmock' # RSpec::CollectionMatchers lets you express expected outcomes on # collections of an object in an example. - gem "rspec-collection_matchers" + gem 'rspec-collection_matchers' # A set of RSpec matchers for testing Pundit authorisation policies. - gem "pundit-matchers" + gem 'pundit-matchers' # This gem brings back assigns to your controller tests as well as assert_template # to both controller and integration tests. - gem "rails-controller-testing" + gem 'rails-controller-testing' end group :ci, :development do # Security vulnerability scanner for Ruby on Rails. # (http://brakemanscanner.org) - gem "brakeman" - - # Automatic Ruby code style checking tool. - # (https://github.com/rubocop-hq/rubocop) - # Rubocop style checks for DMP Roadmap projects. - # (https://github.com/DMPRoadmap/rubocop-DMP_Roadmap) - gem "rubocop-dmp_roadmap" + gem 'brakeman' # Helper gem to require bundler-audit # (http://github.com/stewartmckee/bundle-audit) - gem "bundle-audit" + gem 'bundle-audit' + + # RuboCop is a Ruby code style checking and code formatting tool. It aims to enforce + # the community-driven Ruby Style Guide. + gem 'rubocop' + + # RuboCop rules for detecting and autocorrecting undecorated strings for i18n + # (gettext and rails-i18n) + gem 'rubocop-i18n' + + # A collection of RuboCop cops to check for performance optimizations in Ruby code. + gem 'rubocop-performance' + + # Automatic Rails code style checking tool. A RuboCop extension focused on enforcing + # Rails best practices and coding conventions. + gem 'rubocop-rails' + + # A RuboCop plugin for Rake tasks + gem 'rubocop-rake' + + # Code style checking for RSpec files. A plugin for the RuboCop code style enforcing + # & linting tool. + gem 'rubocop-rspec' + + # Thread-safety checks via static analysis. A plugin for the RuboCop code style + # enforcing & linting tool. + gem 'rubocop-thread_safety' end group :development do # Access an interactive console on exception pages or by calling 'console' anywhere in the code. - gem "listen" - gem "web-console" + gem 'listen' + gem 'web-console' # Spring speeds up development by keeping your application running in the background. # Read more: https://github.com/rails/spring - gem "spring" - gem "spring-watcher-listen" + gem 'spring' + gem 'spring-watcher-listen' # Simple Progress Bar for output to a terminal # (http://github.com/paul/progress_bar) - gem "progress_bar", require: false + gem 'progress_bar', require: false # A collection of text algorithms (http://github.com/threedaymonk/text) - gem "text", require: false + gem 'text', require: false # Better error page for Rails and other Rack apps # (https://github.com/charliesome/better_errors) - gem "better_errors" + gem 'better_errors' # Retrieve the binding of a method's caller. Can also retrieve bindings # even further up the stack. (http://github.com/banister/binding_of_caller) - gem "binding_of_caller" + gem 'binding_of_caller' # rspec command for spring # (https://github.com/jonleighton/spring-commands-rspec) - gem "spring-commands-rspec" + gem 'spring-commands-rspec' # Profiles loading speed for rack applications. (http://miniprofiler.com) - gem "rack-mini-profiler" + gem 'rack-mini-profiler' # Annotates Rails Models, routes, fixtures, and others based on the # database schema. (http://github.com/ctran/annotate_models) - gem "annotate" + gem 'annotate' # Add comments to your Gemfile with each dependency's description. # (https://github.com/ivantsepp/annotate_gem) - gem "annotate_gem" + gem 'annotate_gem' # help to kill N+1 queries and unused eager loading. # (https://github.com/flyerhzm/bullet) - gem "bullet" + gem 'bullet' # Documentation tool for consistent and usable documentation in Ruby. # (http://yardoc.org) - gem "yard" + gem 'yard' # TomDoc for YARD (http://rubyworks.github.com/yard-tomdoc) - gem "yard-tomdoc" + gem 'yard-tomdoc' end diff --git a/Gemfile.lock b/Gemfile.lock index b9d51ac32d..81eca25817 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -52,7 +52,7 @@ GEM api-pagination (4.8.2) arel (9.0.0) ast (2.4.2) - autoprefixer-rails (10.3.3.0) + autoprefixer-rails (10.4.2.0) execjs (~> 2) bcrypt (3.1.16) better_errors (2.9.1) @@ -62,11 +62,11 @@ GEM bindex (0.8.1) binding_of_caller (1.0.0) debug_inspector (>= 0.0.1) - bootsnap (1.9.1) - msgpack (~> 1.0) - brakeman (5.1.1) + bootsnap (1.10.2) + msgpack (~> 1.2) + brakeman (5.2.1) builder (3.2.4) - bullet (6.1.5) + bullet (7.0.1) activesupport (>= 3.0.0) uniform_notifier (~> 1.11) bundle-audit (0.1.0) @@ -75,15 +75,16 @@ GEM bundler (>= 1.2.0, < 3) thor (~> 1.0) byebug (11.1.3) - capybara (3.35.3) + capybara (3.36.0) addressable + matrix mini_mime (>= 0.1.3) nokogiri (~> 1.8) rack (>= 1.6.0) rack-test (>= 0.6.3) regexp_parser (>= 1.5, < 3.0) xpath (~> 3.2) - capybara-screenshot (1.0.25) + capybara-screenshot (1.0.26) capybara (>= 1.0, < 4) launchy capybara-webmock (0.6.0) @@ -108,17 +109,16 @@ GEM database_cleaner-core (~> 2.0.0) database_cleaner-core (2.0.1) debug_inspector (1.1.0) - devise (4.8.0) + devise (4.8.1) bcrypt (~> 3.0) orm_adapter (~> 0.1) railties (>= 4.1.0) responders warden (~> 1.2.3) - devise_invitable (2.0.5) + devise_invitable (2.0.6) actionmailer (>= 5.0) devise (>= 4.6) - diff-lcs (1.4.4) - docile (1.4.0) + diff-lcs (1.5.0) dotenv (2.7.6) dotenv-rails (2.7.6) dotenv (= 2.7.6) @@ -131,7 +131,7 @@ GEM dragonfly (~> 1.0) fog-aws erubi (1.10.0) - excon (0.86.0) + excon (0.90.0) execjs (2.8.1) factory_bot (6.2.0) activesupport (>= 5.0.0) @@ -140,26 +140,30 @@ GEM railties (>= 5.0.0) faker (2.19.0) i18n (>= 1.6, < 2) - faraday (1.8.0) + faraday (1.9.3) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) faraday-excon (~> 1.1) - faraday-httpclient (~> 1.0.1) + faraday-httpclient (~> 1.0) + faraday-multipart (~> 1.0) faraday-net_http (~> 1.0) - faraday-net_http_persistent (~> 1.1) + faraday-net_http_persistent (~> 1.0) faraday-patron (~> 1.0) faraday-rack (~> 1.0) - multipart-post (>= 1.2, < 3) + faraday-retry (~> 1.0) ruby2_keywords (>= 0.0.4) faraday-em_http (1.0.0) faraday-em_synchrony (1.0.0) faraday-excon (1.1.0) faraday-httpclient (1.0.1) + faraday-multipart (1.0.3) + multipart-post (>= 1.2, < 3) faraday-net_http (1.0.1) faraday-net_http_persistent (1.2.0) faraday-patron (1.0.0) faraday-rack (1.0.0) - ffi (1.15.4) + faraday-retry (1.0.3) + ffi (1.15.5) flag_shih_tzu (0.3.23) fog-aws (3.12.0) fog-core (~> 2.1) @@ -180,13 +184,15 @@ GEM font-awesome-sass (5.13.1) sassc (>= 1.11) formatador (0.3.0) + forwardable (1.3.2) fuubar (2.5.1) rspec-core (~> 3.0) ruby-progressbar (~> 1.4) - gettext (3.3.7) + gettext (3.4.2) locale (>= 2.0.5) + prime text (>= 1.3.0) - globalid (0.5.2) + globalid (1.0.0) activesupport (>= 5.0) guard (2.18.0) formatador (>= 0.2.4) @@ -203,7 +209,7 @@ GEM guard-compat (~> 1.1) rspec (>= 2.99.0, < 4.0) hashdiff (1.0.1) - hashie (4.1.0) + hashie (5.0.0) highline (2.0.3) htmltoword (1.1.1) actionpack @@ -212,60 +218,64 @@ GEM httparty (0.20.0) mime-types (~> 3.0) multi_xml (>= 0.5.2) - i18n (1.8.10) + i18n (1.9.1) concurrent-ruby (~> 1.0) ipaddress (0.8.3) - jbuilder (2.11.2) + jbuilder (2.11.5) + actionview (>= 5.0.0) activesupport (>= 5.0.0) - json (2.5.1) - jwt (2.2.3) - kaminari (1.2.1) + json (2.6.1) + jwt (2.3.0) + kaminari (1.2.2) activesupport (>= 4.1.0) - kaminari-actionview (= 1.2.1) - kaminari-activerecord (= 1.2.1) - kaminari-core (= 1.2.1) - kaminari-actionview (1.2.1) + kaminari-actionview (= 1.2.2) + kaminari-activerecord (= 1.2.2) + kaminari-core (= 1.2.2) + kaminari-actionview (1.2.2) actionview - kaminari-core (= 1.2.1) - kaminari-activerecord (1.2.1) + kaminari-core (= 1.2.2) + kaminari-activerecord (1.2.2) activerecord - kaminari-core (= 1.2.1) - kaminari-core (1.2.1) + kaminari-core (= 1.2.2) + kaminari-core (1.2.2) launchy (2.5.0) addressable (~> 2.7) ledermann-rails-settings (2.5.0) activerecord (>= 4.2) - listen (3.7.0) + listen (3.7.1) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) locale (2.1.3) - loofah (2.12.0) + loofah (2.13.0) crass (~> 1.0.2) nokogiri (>= 1.5.9) lumberjack (1.2.8) mail (2.7.1) mini_mime (>= 0.1.1) marcel (1.0.2) + matrix (0.4.2) method_source (1.0.0) - mime-types (3.3.1) + mime-types (3.4.1) mime-types-data (~> 3.2015) - mime-types-data (3.2021.0901) + mime-types-data (3.2022.0105) mimemagic (0.3.10) nokogiri (~> 1) rake - mini_mime (1.1.1) - mini_portile2 (2.6.1) - minitest (5.14.4) + mini_mime (1.1.2) + mini_portile2 (2.7.1) + minitest (5.15.0) mocha (1.13.0) - msgpack (1.4.2) + msgpack (1.4.4) multi_json (1.15.0) multi_xml (0.6.0) multipart-post (2.1.1) mysql2 (0.5.3) nenv (0.3.0) nio4r (2.5.8) - nokogiri (1.12.5) - mini_portile2 (~> 2.6.1) + nokogiri (1.13.1) + mini_portile2 (~> 2.7.0) + racc (~> 1.4) + nokogiri (1.13.1-x86_64-linux) racc (~> 1.4) notiffany (0.1.3) nenv (~> 0.1) @@ -280,7 +290,7 @@ GEM hashie (>= 3.4.6) rack (>= 1.6.2, < 3) rack-protection - omniauth-oauth2 (1.7.1) + omniauth-oauth2 (1.7.2) oauth2 (~> 1.4) omniauth (>= 1.9, < 3) omniauth-orcid (2.1.1) @@ -294,9 +304,12 @@ GEM options (2.3.2) orm_adapter (0.5.0) parallel (1.21.0) - parser (3.0.2.0) + parser (3.1.0.0) ast (~> 2.4.1) - pg (1.2.3) + pg (1.3.0) + prime (0.1.2) + forwardable + singleton progress_bar (1.3.3) highline (>= 1.6, < 3) options (~> 2.3.0) @@ -304,19 +317,19 @@ GEM coderay (~> 1.1) method_source (~> 1.0) public_suffix (4.0.6) - puma (5.5.0) + puma (5.6.1) nio4r (~> 2.0) pundit (2.1.1) activesupport (>= 3.0.0) pundit-matchers (1.7.0) rspec-rails (>= 3.0.0) - racc (1.5.2) + racc (1.6.0) rack (2.2.3) rack-mini-profiler (2.3.3) rack (>= 1.2.0) rack-protection (2.1.0) rack - rack-proxy (0.7.0) + rack-proxy (0.7.2) rack rack-test (1.1.0) rack (>= 1.0, < 3) @@ -348,34 +361,34 @@ GEM method_source rake (>= 0.8.7) thor (>= 0.19.0, < 2.0) - rainbow (3.0.0) + rainbow (3.1.1) rake (13.0.6) rb-fsevent (0.11.0) rb-inotify (0.10.1) ffi (~> 1.0) recaptcha (5.8.1) json - regexp_parser (2.1.1) + regexp_parser (2.2.0) responders (3.0.1) actionpack (>= 5.0) railties (>= 5.0) rexml (3.2.5) - rollbar (3.2.0) + rollbar (3.3.0) rspec (3.10.0) rspec-core (~> 3.10.0) rspec-expectations (~> 3.10.0) rspec-mocks (~> 3.10.0) rspec-collection_matchers (1.2.0) rspec-expectations (>= 2.99.0.beta1) - rspec-core (3.10.1) + rspec-core (3.10.2) rspec-support (~> 3.10.0) - rspec-expectations (3.10.1) + rspec-expectations (3.10.2) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.10.0) - rspec-mocks (3.10.2) + rspec-mocks (3.10.3) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.10.0) - rspec-rails (5.0.2) + rspec-rails (5.1.0) actionpack (>= 5.2) activesupport (>= 5.2) railties (>= 5.2) @@ -383,43 +396,33 @@ GEM rspec-expectations (~> 3.10) rspec-mocks (~> 3.10) rspec-support (~> 3.10) - rspec-support (3.10.2) - rubocop (1.22.0) + rspec-support (3.10.3) + rubocop (1.25.0) parallel (~> 1.10) - parser (>= 3.0.0.0) + parser (>= 3.1.0.0) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) rexml - rubocop-ast (>= 1.12.0, < 2.0) + rubocop-ast (>= 1.15.1, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 3.0) - rubocop-ast (1.12.0) + rubocop-ast (1.15.1) parser (>= 3.0.1.1) - rubocop-dmp_roadmap (1.1.2) - rubocop (>= 0.58.2) - rubocop-rails_config (>= 0.2.2) - rubocop-rspec (>= 1.27.0) - rubocop-minitest (0.15.1) - rubocop (>= 0.90, < 2.0) - rubocop-packaging (0.5.1) - rubocop (>= 0.89, < 2.0) - rubocop-performance (1.11.5) + rubocop-i18n (3.0.0) + rubocop (~> 1.0) + rubocop-performance (1.13.2) rubocop (>= 1.7.0, < 2.0) rubocop-ast (>= 0.4.0) - rubocop-rails (2.12.2) + rubocop-rails (2.13.2) activesupport (>= 4.2.0) rack (>= 1.1) rubocop (>= 1.7.0, < 2.0) - rubocop-rails_config (1.7.3) - railties (>= 5.0) - rubocop (>= 1.19) - rubocop-ast (>= 1.0.1) - rubocop-minitest (~> 0.15) - rubocop-packaging (~> 0.5) - rubocop-performance (~> 1.11) - rubocop-rails (~> 2.0) - rubocop-rspec (2.5.0) + rubocop-rake (0.6.0) + rubocop (~> 1.0) + rubocop-rspec (2.8.0) rubocop (~> 1.19) + rubocop-thread_safety (0.4.4) + rubocop (>= 0.53.0) ruby-progressbar (1.11.0) ruby2_keywords (0.0.5) ruby_dig (0.0.2) @@ -445,12 +448,7 @@ GEM shoulda-context (2.0.0) shoulda-matchers (4.5.1) activesupport (>= 4.2.0) - simplecov (0.21.2) - docile (~> 1.1) - simplecov-html (~> 0.11) - simplecov_json_formatter (~> 0.1) - simplecov-html (0.12.3) - simplecov_json_formatter (0.1.3) + singleton (0.1.1) spring (2.1.1) spring-commands-rspec (1.0.4) spring (>= 0.9.1) @@ -460,17 +458,17 @@ GEM sprockets (4.0.2) concurrent-ruby (~> 1.0) rack (> 1, < 3) - sprockets-rails (3.2.2) - actionpack (>= 4.0) - activesupport (>= 4.0) + sprockets-rails (3.4.2) + actionpack (>= 5.2) + activesupport (>= 5.2) sprockets (>= 3.0.0) text (1.3.1) - thor (1.1.0) + thor (1.2.1) thread_safe (0.3.6) tilt (2.0.10) tomparse (0.4.2) - translation (1.26) - gettext (~> 3.2, >= 3.2.5, <= 3.3.7) + translation (1.28) + gettext (~> 3.2, >= 3.2.5, <= 3.4.2) turbolinks (5.2.1) turbolinks-source (~> 5.2) turbolinks-source (5.2.0) @@ -485,10 +483,10 @@ GEM activemodel (>= 5.0) bindex (>= 0.4.0) railties (>= 5.0) - webdrivers (4.6.1) + webdrivers (4.7.0) nokogiri (~> 1.6) rubyzip (>= 1.3.0) - selenium-webdriver (>= 3.0, < 4.0) + selenium-webdriver (> 3.141, < 5.0) webmock (3.14.0) addressable (>= 2.8.0) crack (>= 0.3.2) @@ -507,7 +505,8 @@ GEM wkhtmltopdf-binary (0.12.6.5) xpath (3.2.0) nokogiri (~> 1.8) - yard (0.9.26) + yard (0.9.27) + webrick (~> 1.7.0) yard-tomdoc (0.7.1) tomparse (>= 0.4.0) yard @@ -515,6 +514,7 @@ GEM PLATFORMS ruby + x86_64-linux DEPENDENCIES annotate @@ -572,12 +572,17 @@ DEPENDENCIES rollbar rspec-collection_matchers rspec-rails - rubocop-dmp_roadmap + rubocop + rubocop-i18n + rubocop-performance + rubocop-rails + rubocop-rake + rubocop-rspec + rubocop-thread_safety sass-rails sassc-rails selenium-webdriver shoulda - simplecov spring spring-commands-rspec spring-watcher-listen @@ -598,4 +603,4 @@ RUBY VERSION ruby 2.6.3p62 BUNDLED WITH - 2.1.4 + 2.2.33 diff --git a/Rakefile b/Rakefile index 700a8a33cf..42f9525d64 100755 --- a/Rakefile +++ b/Rakefile @@ -10,7 +10,7 @@ # task default: :test -require_relative "config/application" +require_relative 'config/application' DMPRoadmap::Application.load_tasks diff --git a/app/channels/application_cable/channel.rb b/app/channels/application_cable/channel.rb index fa6ee697da..9aec230539 100644 --- a/app/channels/application_cable/channel.rb +++ b/app/channels/application_cable/channel.rb @@ -1,9 +1,6 @@ # frozen_string_literal: true module ApplicationCable - class Channel < ActionCable::Channel::Base - end - end diff --git a/app/channels/application_cable/connection.rb b/app/channels/application_cable/connection.rb index b08f85080a..8d6c2a1bf4 100644 --- a/app/channels/application_cable/connection.rb +++ b/app/channels/application_cable/connection.rb @@ -1,9 +1,6 @@ # frozen_string_literal: true module ApplicationCable - class Connection < ActionCable::Connection::Base - end - end diff --git a/app/controllers/answers_controller.rb b/app/controllers/answers_controller.rb index 48980290bf..9e089bd5d4 100644 --- a/app/controllers/answers_controller.rb +++ b/app/controllers/answers_controller.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true +# Controller that handles Answers to DMP questions class AnswersController < ApplicationController - respond_to :html include ConditionsHelper @@ -12,6 +12,7 @@ class AnswersController < ApplicationController # `remote: true` in the
tag and just send back the ERB. # Consider using ActionCable for the progress bar(s) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity def create_or_update p_params = permitted_params @@ -21,21 +22,17 @@ def create_or_update unless p.question_exists?(p_params[:question_id]) # rubocop:disable Layout/LineLength render(status: :not_found, json: { - msg: _("There is no question with id %{question_id} associated to plan id %{plan_id} for which to create or update an answer") % { - question_id: p_params[:question_id], - plan_id: p_params[:plan_id] - } + msg: format(_('There is no question with id %s associated to plan id %s for which to create or update an answer'), question_id: p_params[:question_id], plan_id: p_params[:plan_id]) }) # rubocop:enable Layout/LineLength return end rescue ActiveRecord::RecordNotFound + # rubocop:disable Layout/LineLength render(status: :not_found, json: { - msg: _("There is no plan with id %{id} for which to create or update an answer") % { - id: p_params[:plan_id] - } + msg: format(_('There is no plan with id %s for which to create or update an answer'), id: p_params[:plan_id]) }) - + # rubocop:enable Layout/LineLength return end q = Question.find(p_params[:question_id]) @@ -127,19 +124,19 @@ def create_or_update send_webhooks(current_user, @answer) render json: { - "qn_data": qn_data, - "section_data": section_data, - "question" => { - "id" => @question.id, - "answer_lock_version" => @answer.lock_version, - "locking" => if @stale_answer - render_to_string(partial: "answers/locking", locals: { + qn_data: qn_data, + section_data: section_data, + 'question' => { + 'id' => @question.id, + 'answer_lock_version' => @answer.lock_version, + 'locking' => if @stale_answer + render_to_string(partial: 'answers/locking', locals: { question: @question, answer: @stale_answer, user: @answer.user }, formats: [:html]) end, - "form" => render_to_string(partial: "answers/new_edit", locals: { + 'form' => render_to_string(partial: 'answers/new_edit', locals: { template: template, question: @question, answer: @answer, @@ -147,13 +144,13 @@ def create_or_update locking: false, base_template_org: template.base_org }, formats: [:html]), - "answer_status" => render_to_string(partial: "answers/status", locals: { + 'answer_status' => render_to_string(partial: 'answers/status', locals: { answer: @answer }, formats: [:html]) }, - "plan" => { - "id" => @plan.id, - "progress" => render_to_string(partial: "plans/progress", locals: { + 'plan' => { + 'id' => @plan.id, + 'progress' => render_to_string(partial: 'plans/progress', locals: { plan: @plan, current_phase: @section.phase }, formats: [:html]) @@ -164,10 +161,11 @@ def create_or_update # rubocop:enable Style/GuardClause end # rubocop:enable Metrics/AbcSize, Metrics/MethodLength - # rubocop:enable + # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity private + # rubocop:disable Metrics/AbcSize def permitted_params permitted = params.require(:answer) .permit(:id, :text, :plan_id, :user_id, :question_id, @@ -184,11 +182,11 @@ def permitted_params permitted[:question_option_ids] = [] if params[:answer][:question_option_ids].nil? permitted end + # rubocop:enable Metrics/AbcSize 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, n_ans] end - end diff --git a/app/controllers/api/v0/base_controller.rb b/app/controllers/api/v0/base_controller.rb index f90f9c7320..c955adfdab 100644 --- a/app/controllers/api/v0/base_controller.rb +++ b/app/controllers/api/v0/base_controller.rb @@ -1,127 +1,130 @@ # frozen_string_literal: true -class Api::V0::BaseController < ApplicationController - - protect_from_forgery with: :null_session - before_action :define_resource, only: %i[destroy show update] - respond_to :json - - # POST /api/{plural_resource_name} - def create - define_resource(resource_class.new(resource_params)) +module Api + module V0 + # Generic controller for API V0 + class BaseController < ApplicationController + protect_from_forgery with: :null_session + before_action :define_resource, only: %i[destroy show update] + respond_to :json + + # POST /api/{plural_resource_name} + def create + define_resource(resource_class.new(resource_params)) + + if retrieve_resource.save + render :show, status: :created + else + render json: retrieve_resource.errors, status: :unprocessable_entity + end + end - if retrieve_resource.save - render :show, status: :created - else - render json: retrieve_resource.errors, status: :unprocessable_entity - end - end + # DELETE /api/{plural_resource_name}/1 + def destroy + retrieve_resource.destroy + head :no_content + end - # DELETE /api/{plural_resource_name}/1 - def destroy - retrieve_resource.destroy - head :no_content - end + # GET /api/{plural_resource_name} + def index + plural_resource_name = "@#{resource_name.pluralize}" + resources = resource_class.where(query_params) + .page(page_params[:page]) + .per(page_params[:page_size]) - # GET /api/{plural_resource_name} - def index - plural_resource_name = "@#{resource_name.pluralize}" - resources = resource_class.where(query_params) - .page(page_params[:page]) - .per(page_params[:page_size]) + instance_variable_set(plural_resource_name, resources) + respond_with instance_variable_get(plural_resource_name) + end - instance_variable_set(plural_resource_name, resources) - respond_with instance_variable_get(plural_resource_name) - end + # GET /api/{plural_resource_name}/1 + def show + respond_with retrieve_resource + end - # GET /api/{plural_resource_name}/1 - def show - respond_with retrieve_resource - end + # PATCH/PUT /api/{plural_resource_name}/1 + def update + if retrieve_resource.update(resource_params) + render :show + else + render json: retrieve_resource.errors, status: :unprocessable_entity + end + end - # PATCH/PUT /api/{plural_resource_name}/1 - def update - if retrieve_resource.update(resource_params) - render :show - else - render json: retrieve_resource.errors, status: :unprocessable_entity - end - end + private - private + # The resource from the created instance variable + # + # Returns Object + def retrieve_resource + instance_variable_get("@#{resource_name}") + end - # The resource from the created instance variable - # - # Returns Object - def retrieve_resource - instance_variable_get("@#{resource_name}") - end + # The allowed parameters for searching. Override this method in each API + # controller to permit additional parameters to search on + # + # Returns Hash + def query_params + {} + end - # The allowed parameters for searching. Override this method in each API - # controller to permit additional parameters to search on - # - # Returns Hash - def query_params - {} - end + # The allowed parameters for pagination + # + # Returns Hash + def page_params + params.permit(:page, :page_size) + end - # The allowed parameters for pagination - # - # Returns Hash - def page_params - params.permit(:page, :page_size) - end + # The resource class based on the controller + # + # Returns Object + def resource_class + @resource_class ||= resource_name.classify.constantize + end - # The resource class based on the controller - # - # Returns Object - def resource_class - @resource_class ||= resource_name.classify.constantize - end + # The singular name for the resource class based on the controller + # + # Returns String + def resource_name + @resource_name ||= controller_name.singularize + end - # The singular name for the resource class based on the controller - # - # Returns String - def resource_name - @resource_name ||= controller_name.singularize - end + # Only allow a trusted parameter "white list" through. + # If a single resource is loaded for #create or #update, + # then the controller for the resource must implement + # the method "#{resource_name}_params" to limit permitted + # parameters for the individual model. + def resource_params + @resource_params ||= send("#{resource_name}_params") + end - # Only allow a trusted parameter "white list" through. - # If a single resource is loaded for #create or #update, - # then the controller for the resource must implement - # the method "#{resource_name}_params" to limit permitted - # parameters for the individual model. - def resource_params - @resource_params ||= send("#{resource_name}_params") - end + # Use callbacks to share common setup or constraints between actions. + def define_resource(resource = nil) + resource ||= resource_class.find(params[:id]) + instance_variable_set("@#{resource_name}", resource) + end - # Use callbacks to share common setup or constraints between actions. - def define_resource(resource = nil) - resource ||= resource_class.find(params[:id]) - instance_variable_set("@#{resource_name}", resource) - end + def authenticate + authenticate_token || render_bad_credentials + end - def authenticate - authenticate_token || render_bad_credentials - end + def authenticate_token + authenticate_with_http_token do |token, _options| + # reject the empty string as it is our base empty token + if token == '' + false + else + @token = token + @user = User.find_by(api_token: token) + # if no user found, return false, otherwise true + !@user.nil? && @user.can_use_api? + end + end + end - def authenticate_token - authenticate_with_http_token do |token, _options| - # reject the empty string as it is our base empty token - if token != "" - @token = token - @user = User.find_by(api_token: token) - # if no user found, return false, otherwise true - !@user.nil? && @user.can_use_api? - else - false + def render_bad_credentials + headers['WWW-Authenticate'] = 'Token realm=""' + render json: _('Bad Credentials'), status: 401 end end end - - def render_bad_credentials - headers["WWW-Authenticate"] = "Token realm=\"\"" - render json: _("Bad Credentials"), status: 401 - end - end diff --git a/app/controllers/api/v0/departments_controller.rb b/app/controllers/api/v0/departments_controller.rb index 5bd31d6f18..83e483cce7 100644 --- a/app/controllers/api/v0/departments_controller.rb +++ b/app/controllers/api/v0/departments_controller.rb @@ -1,80 +1,79 @@ # frozen_string_literal: true -class Api::V0::DepartmentsController < Api::V0::BaseController - - before_action :authenticate - - ## - # Create a new department based on the information passed in JSON to the API - def create - raise Pundit::NotAuthorizedError unless Api::V0::DepartmentsPolicy.new(@user, nil).index? +module Api + module V0 + # Handles CRUD operations for Departments in API V0 + class DepartmentsController < Api::V0::BaseController + before_action :authenticate + + ## + # Create a new department based on the information passed in JSON to the API + def create + raise Pundit::NotAuthorizedError unless Api::V0::DepartmentsPolicy.new(@user, nil).index? + + @department = Department.new(org: @user.org, + code: params[:code], + name: params[:name]) + if @department.save + redirect_to api_v0_departments_path + else + # the department did not save + headers['WWW-Authenticate'] = 'Token realm=""' + render json: _('Departments code and name must be unique'), status: 400 + end + end - @department = Department.new(org: @user.org, - code: params[:code], - name: params[:name]) - if @department.save - redirect_to api_v0_departments_path - else - # the department did not save - headers["WWW-Authenticate"] = "Token realm=\"\"" - render json: _("Departments code and name must be unique"), status: 400 - end - end + ## + # Lists the departments for the API user's organisation + def index + raise Pundit::NotAuthorizedError unless Api::V0::DepartmentsPolicy.new(@user, nil).index? - ## - # Lists the departments for the API user's organisation - def index - raise Pundit::NotAuthorizedError unless Api::V0::DepartmentsPolicy.new(@user, nil).index? + @departments = @user.org.departments + end - @departments = @user.org.departments - end + ## + # List the users for each department on the organisation + def users + raise Pundit::NotAuthorizedError unless Api::V0::DepartmentsPolicy.new(@user, nil).users? - ## - # List the users for each department on the organisation - def users - raise Pundit::NotAuthorizedError unless Api::V0::DepartmentsPolicy.new(@user, nil).users? + @users = @user.org.users.includes(:department) + end - @users = @user.org.users.includes(:department) - end + ## + # Assign the list of users to the passed department id + def assign_users + @department = Department.find(params[:id]) - ## - # Assign the list of users to the passed department id - def assign_users - @department = Department.find(params[:id]) + raise Pundit::NotAuthorizedError unless Api::V0::DepartmentsPolicy.new(@user, @department).assign_users? - unless Api::V0::DepartmentsPolicy.new(@user, @department).assign_users? - raise Pundit::NotAuthorizedError - end + assign_users_to(@department.id) + redirect_to users_api_v0_departments_path + end - assign_users_to(@department.id) - redirect_to users_api_v0_departments_path - end + ## + # Remove departments from the list of users + def unassign_users + raise Pudndit::NotAuthorizedError unless Api::V0::DepartmentsPolicy.new(@user, @department).assign_users? - ## - # Remove departments from the list of users - def unassign_users - unless Api::V0::DepartmentsPolicy.new(@user, @department).assign_users? - raise Pudndit::NotAuthorizedError - end + assign_users_to(nil) + redirect_to users_api_v0_departments_path + end - assign_users_to(nil) - redirect_to users_api_v0_departments_path - end + private - private + def assign_users_to(department_id) + params[:users].each do |email| + reassign = User.find_by(email: email) + # Currently the validation is that the user's org matches the API user's + # Not sure if this is possible to capture in pundit + unless @user.present? && @user.org == reassign&.org + raise Pundit::NotAuthorizedError, _("user #{email} was not found on your organisation") + end - def assign_users_to(department_id) - params[:users].each do |email| - reassign = User.find_by(email: email) - # Currently the validation is that the user's org matches the API user's - # Not sure if this is possible to capture in pundit - unless @user.present? && @user.org == reassign&.org - raise Pundit::NotAuthorizedError, _("user #{email} was not found on your organisation") + reassign.department_id = department_id + reassign.save! + end end - - reassign.department_id = department_id - reassign.save! end end - end diff --git a/app/controllers/api/v0/guidance_groups_controller.rb b/app/controllers/api/v0/guidance_groups_controller.rb index 17beae33d4..7c828f1f69 100644 --- a/app/controllers/api/v0/guidance_groups_controller.rb +++ b/app/controllers/api/v0/guidance_groups_controller.rb @@ -1,26 +1,27 @@ # frozen_string_literal: true -class Api::V0::GuidanceGroupsController < Api::V0::BaseController +module Api + module V0 + # Handles GuidanceGroup queries for API V0 + class GuidanceGroupsController < Api::V0::BaseController + before_action :authenticate - before_action :authenticate + def index + raise Pundit::NotAuthorizedError unless Api::V0::GuidanceGroupPolicy.new(@user, :guidance_group).index? - def index - unless Api::V0::GuidanceGroupPolicy.new(@user, :guidance_group).index? - raise Pundit::NotAuthorizedError - end - - @all_viewable_groups = GuidanceGroup.all_viewable(@user) - respond_with @all_viewable_groups - end + @all_viewable_groups = GuidanceGroup.all_viewable(@user) + respond_with @all_viewable_groups + end - def pundit_user - @user - end + def pundit_user + @user + end - private + private - def query_params - params.permit(:id) + def query_params + params.permit(:id) + end + end end - end diff --git a/app/controllers/api/v0/plans_controller.rb b/app/controllers/api/v0/plans_controller.rb index 5e30069bda..8fa848bffd 100644 --- a/app/controllers/api/v0/plans_controller.rb +++ b/app/controllers/api/v0/plans_controller.rb @@ -1,123 +1,123 @@ # frozen_string_literal: true -class Api::V0::PlansController < Api::V0::BaseController - - include Paginable - - before_action :authenticate - - ## - # Creates a new plan based on the information passed in JSON to the API - # rubocop:disable Metrics/AbcSize, Metrics/MethodLength - def create - @template = Template.live(params[:template_id]) - raise Pundit::NotAuthorizedError unless Api::V0::PlansPolicy.new(@user, @template).create? - - plan_user = User.find_by(email: params[:plan][:email]) - # ensure user exists - if plan_user.blank? - User.invite!({ email: params[:plan][:email] }, @user) - plan_user = User.find_by(email: params[:plan][:email]) - plan_user.org = @user.org - plan_user.save - end - # ensure user's organisation is the same as api user's - unless plan_user.org == @user.org - raise Pundit::NotAuthorizedError, _("user must be in your organisation") - end - - # initialize the plan - @plan = Plan.new - - # Attach the user as the PI and Data Contact - @plan.contributors << Contributor.new( - name: [plan_user.firstname, plan_user.surname].join(" "), - email: plan_user.email, - investigation: true, - data_curation: true - ) - - # set funder name to template's org, or original template's org - @plan.funder_id = if @template.customization_of.nil? - @template.org.id - else - Template.where( - family_id: @template.customization_of - ).first.org.id - end - @plan.template = @template - @plan.title = params[:plan][:title] - if @plan.save - @plan.assign_creator(plan_user) - respond_with @plan - else - # the plan did not save - headers["WWW-Authenticate"] = "Token realm=\"\"" - render json: _("Bad Parameters"), status: 400 +module Api + module V0 + # Primary controller for API V0 that handles CRUD operations for Plans + class PlansController < Api::V0::BaseController + include Paginable + + before_action :authenticate + + ## + # Creates a new plan based on the information passed in JSON to the API + # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + def create + @template = Template.live(params[:template_id]) + raise Pundit::NotAuthorizedError unless Api::V0::PlansPolicy.new(@user, @template).create? + + plan_user = User.find_by(email: params[:plan][:email]) + # ensure user exists + if plan_user.blank? + User.invite!({ email: params[:plan][:email] }, @user) + plan_user = User.find_by(email: params[:plan][:email]) + plan_user.org = @user.org + plan_user.save + end + # ensure user's organisation is the same as api user's + raise Pundit::NotAuthorizedError, _('user must be in your organisation') unless plan_user.org == @user.org + + # initialize the plan + @plan = Plan.new + + # Attach the user as the PI and Data Contact + @plan.contributors << Contributor.new( + name: [plan_user.firstname, plan_user.surname].join(' '), + email: plan_user.email, + investigation: true, + data_curation: true + ) + + # set funder name to template's org, or original template's org + @plan.funder_id = if @template.customization_of.nil? + @template.org.id + else + Template.where( + family_id: @template.customization_of + ).first.org.id + end + @plan.template = @template + @plan.title = params[:plan][:title] + if @plan.save + @plan.assign_creator(plan_user) + respond_with @plan + else + # the plan did not save + headers['WWW-Authenticate'] = 'Token realm=""' + render json: _('Bad Parameters'), status: 400 + end + end + # rubocop:enable Metrics/AbcSize, Metrics/MethodLength + + # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity + def index + raise Pundit::NotAuthorizedError unless Api::V0::PlansPolicy.new(@user, nil).index? + + if params[:per_page].present? + max_pages = Rails.configuration.x.application.api_max_page_size + params[:per_page] = max_pages if params[:per_page].to_i > max_pages + end + + # Get all the Org Admin plans + org_admin_plans = @user.org.org_admin_plans + @plans = org_admin_plans.includes([{ roles: :user }, { answers: :question_options }, + template: [{ phases: { + sections: { questions: %i[question_format themes] } + } }, :org]]) + + # Filter on list of users + user_ids = extract_param_list(params, 'user') + @plans = @plans.where(roles: { user_id: user_ids, access: Role.bit_values(:editor) }) if user_ids.present? + # filter on dates + if params['created_after'].present? || params['created_before'].present? + @plans = @plans.where(created_at: dates_to_range(params, 'created_after', 'created_before')) + end + 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' + @plans = @plans.where.not(visibility: Plan.visibilities[:is_test]) + end + # filter on funder (dmptemplate_id) + template_ids = extract_param_list(params, 'template') + @plans = @plans.where(templates: { family_id: template_ids }) if template_ids.present? + # filter on id(s) + plan_ids = extract_param_list(params, 'plan') + @plans = @plans.where(id: plan_ids) if plan_ids.present? + # apply pagination after filtering + @args = { per_page: params[:per_page], page: params[:page] } + @plans = refine_query(@plans) + respond_with @plans + end + # rubocop:enable Metrics/AbcSize, Metrics/MethodLength + # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity + + private + + def extract_param_list(params, attribute) + list = params.fetch("#{attribute}[]", []) + val = params.fetch(attribute, []) + list << val if val.present? + list + end + + # takes in the params hash and converts to a date-range + def dates_to_range(hash, start, stop) + today = Date.today + start_date = Date.parse(hash.fetch(start, today.prev_month.to_date.to_s)) + end_date = Date.parse(hash.fetch(stop, today.to_date.to_s)) + 1.day + start_date..end_date + end end end - # rubocop:enable Metrics/AbcSize, Metrics/MethodLength - - # rubocop:disable Metrics/AbcSize, Metrics/MethodLength - def index - raise Pundit::NotAuthorizedError unless Api::V0::PlansPolicy.new(@user, nil).index? - - if params[:per_page].present? - max_pages = Rails.configuration.x.application.api_max_page_size - params[:per_page] = max_pages if params[:per_page].to_i > max_pages - end - - # Get all the Org Admin plans - org_admin_plans = @user.org.org_admin_plans - @plans = org_admin_plans.includes([{ roles: :user }, { answers: :question_options }, - template: [{ phases: { - sections: { questions: %i[question_format themes] } - } }, :org]]) - - # Filter on list of users - user_ids = extract_param_list(params, "user") - if user_ids.present? - @plans = @plans.where(roles: { user_id: user_ids, access: Role.bit_values(:editor) }) - end - # filter on dates - if params["created_after"].present? || params["created_before"].present? - @plans = @plans.where(created_at: dates_to_range(params, "created_after", "created_before")) - end - 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" - @plans = @plans.where.not(visibility: Plan.visibilities[:is_test]) - end - # filter on funder (dmptemplate_id) - template_ids = extract_param_list(params, "template") - @plans = @plans.where(templates: { family_id: template_ids }) if template_ids.present? - # filter on id(s) - plan_ids = extract_param_list(params, "plan") - @plans = @plans.where(id: plan_ids) if plan_ids.present? - # apply pagination after filtering - @args = { per_page: params[:per_page], page: params[:page] } - @plans = refine_query(@plans) - respond_with @plans - end - # rubocop:enable Metrics/AbcSize, Metrics/MethodLength - # rubocop:enable - - private - - def extract_param_list(params, attribute) - list = params.fetch(attribute + "[]", []) - val = params.fetch(attribute, []) - list << val if val.present? - list - end - - # takes in the params hash and converts to a date-range - def dates_to_range(hash, start, stop) - today = Date.today - start_date = Date.parse(hash.fetch(start, today.prev_month.to_date.to_s)) - end_date = Date.parse(hash.fetch(stop, today.to_date.to_s)) + 1.day - start_date..end_date - end - end diff --git a/app/controllers/api/v0/statistics_controller.rb b/app/controllers/api/v0/statistics_controller.rb index b74f1a241d..2af50dfcb2 100644 --- a/app/controllers/api/v0/statistics_controller.rb +++ b/app/controllers/api/v0/statistics_controller.rb @@ -1,244 +1,243 @@ # frozen_string_literal: true -class Api::V0::StatisticsController < Api::V0::BaseController - - before_action :authenticate - - # GET /api/v0/statistics/users_joined?start_date=&end_date=&org_id= - # - # Returns the number of users joined for the user's org. - # If start_date is passed, only counts those with created_at is >= than start_date - # If end_date is passed, only counts those with created_at is <= than end_date are - # If org_id is passed and user has super_admin privileges that counter is performed - # against org_id param instead of user's org - - # rubocop:disable Metrics/AbcSize, Metrics/MethodLength - def users_joined - unless Api::V0::StatisticsPolicy.new(@user, :statistics).users_joined? - raise Pundit::NotAuthorizedError - end - - scoped = if @user.can_super_admin? && params[:org_id].present? - User.unscoped.where(org_id: params[:org_id]) - else - User.unscoped.where(org_id: @user.org_id) - end - - if params[:range_dates].present? - r = {} - params[:range_dates].each_pair do |k, v| - r[k] = scoped.where(created_at: dates_to_range(v)).count - end - - # Reverse hash r, so dates in ascending order - r = Hash[r.to_a.reverse] - - respond_to do |format| - format.json { render(json: r.to_json) } - format.csv do - send_data(CSV.generate do |csv| - csv << [_("Month"), _("No. Users joined")] - total = 0 - r.each_pair do |k, v| - csv << [k, v] - total += v +module Api + module V0 + # Provides statistical info for API V0 + class StatisticsController < Api::V0::BaseController + before_action :authenticate + + # GET /api/v0/statistics/users_joined?start_date=&end_date=&org_id= + # + # Returns the number of users joined for the user's org. + # If start_date is passed, only counts those with created_at is >= than start_date + # If end_date is passed, only counts those with created_at is <= than end_date are + # If org_id is passed and user has super_admin privileges that counter is performed + # against org_id param instead of user's org + + # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity + def users_joined + raise Pundit::NotAuthorizedError unless Api::V0::StatisticsPolicy.new(@user, :statistics).users_joined? + + scoped = if @user.can_super_admin? && params[:org_id].present? + User.unscoped.where(org_id: params[:org_id]) + else + User.unscoped.where(org_id: @user.org_id) + end + + if params[:range_dates].present? + r = {} + params[:range_dates].each_pair do |k, v| + r[k] = scoped.where(created_at: dates_to_range(v)).count + end + + # Reverse hash r, so dates in ascending order + r = r.to_a.reverse.to_h + + respond_to do |format| + format.json { render(json: r.to_json) } + format.csv do + send_data(CSV.generate do |csv| + csv << [_('Month'), _('No. Users joined')] + total = 0 + r.each_pair do |k, v| + csv << [k, v] + total += v + end + csv << [_('Total'), total] + end, filename: "#{_('users_joined')}.csv") end - csv << [_("Total"), total] - end, filename: "#{_('users_joined')}.csv") + end + else + if params['start_date'].present? || params['end_date'].present? + scoped = scoped.where(created_at: dates_to_range(params)) + end + @users_count = scoped.count + respond_with @users_count end end - else - if params["start_date"].present? || params["end_date"].present? - scoped = scoped.where(created_at: dates_to_range(params)) - end - @users_count = scoped.count - respond_with @users_count - end - end - # rubocop:enable Metrics/AbcSize, Metrics/MethodLength - # rubocop:enable - - # GET - # Returns the number of completed plans within the user's org for the data - # start_date and end_date specified - # rubocop:disable Metrics/AbcSize, Metrics/MethodLength - def completed_plans - unless Api::V0::StatisticsPolicy.new(@user, :statistics).completed_plans? - raise Pundit::NotAuthorizedError - end - - scoped = if @user.can_super_admin? && params[:org_id].present? - Org.find(params[:org_id]).plans.where(complete: true) - else - @user.org.plans.where(complete: true) - end - - if params[:range_dates].present? - r = {} - params[:range_dates].each_pair do |k, v| - r[k] = scoped.where(created_at: dates_to_range(v)).count - end - - # Reverse hash r, so dates in ascending order - r = Hash[r.to_a.reverse] - - respond_to do |format| - format.json { render(json: r.to_json) } - format.csv do - send_data(CSV.generate do |csv| - csv << [_("Month"), _("No. Completed Plans")] - total = 0 - r.each_pair do |k, v| - csv << [k, v] - total += v + # rubocop:enable Metrics/AbcSize, Metrics/MethodLength + # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity + + # GET + # Returns the number of completed plans within the user's org for the data + # start_date and end_date specified + # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity + def completed_plans + raise Pundit::NotAuthorizedError unless Api::V0::StatisticsPolicy.new(@user, :statistics).completed_plans? + + scoped = if @user.can_super_admin? && params[:org_id].present? + Org.find(params[:org_id]).plans.where(complete: true) + else + @user.org.plans.where(complete: true) + end + + if params[:range_dates].present? + r = {} + params[:range_dates].each_pair do |k, v| + r[k] = scoped.where(created_at: dates_to_range(v)).count + end + + # Reverse hash r, so dates in ascending order + r = r.to_a.reverse.to_h + + respond_to do |format| + format.json { render(json: r.to_json) } + format.csv do + send_data(CSV.generate do |csv| + csv << [_('Month'), _('No. Completed Plans')] + total = 0 + r.each_pair do |k, v| + csv << [k, v] + total += v + end + csv << [_('Total'), total] + end, filename: "#{_('completed_plans')}.csv") end - csv << [_("Total"), total] - end, filename: "#{_('completed_plans')}.csv") + end + else + if params['start_date'].present? || params['end_date'].present? + scoped = scoped.where(created_at: dates_to_range(params)) + end + render(json: { completed_plans: scoped.count }) end end - else - if params["start_date"].present? || params["end_date"].present? - scoped = scoped.where(created_at: dates_to_range(params)) - end - render(json: { completed_plans: scoped.count }) - end - end - # rubocop:enable Metrics/AbcSize, Metrics/MethodLength - # rubocop:enable - - # /api/v0/statistics/created_plans - # Returns the number of created plans within the user's org for the data - # start_date and end_date specified - # rubocop:disable Metrics/AbcSize, Metrics/MethodLength - def created_plans - raise Pundit::NotAuthorizedError unless Api::V0::StatisticsPolicy.new(@user, :statistics).plans? - - scoped = if @user.can_super_admin? && params[:org_id].present? - Org.find(params[:org_id]).plans - else - @user.org.plans - end - - if params[:range_dates].present? - r = {} - params[:range_dates].each_pair do |k, v| - r[k] = scoped.where(created_at: dates_to_range(v)).count - end - - # Reverse hash r, so dates in ascending order - r = Hash[r.to_a.reverse] - - respond_to do |format| - format.json { render(json: r.to_json) } - format.csv do - send_data(CSV.generate do |csv| - csv << [_("Month"), _("No. Plans")] - total = 0 - r.each_pair do |k, v| - csv << [k, v] - total += v + # rubocop:enable Metrics/AbcSize, Metrics/MethodLength + # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity + + # /api/v0/statistics/created_plans + # Returns the number of created plans within the user's org for the data + # start_date and end_date specified + # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity + def created_plans + raise Pundit::NotAuthorizedError unless Api::V0::StatisticsPolicy.new(@user, :statistics).plans? + + scoped = if @user.can_super_admin? && params[:org_id].present? + Org.find(params[:org_id]).plans + else + @user.org.plans + end + + if params[:range_dates].present? + r = {} + params[:range_dates].each_pair do |k, v| + r[k] = scoped.where(created_at: dates_to_range(v)).count + end + + # Reverse hash r, so dates in ascending order + r = r.to_a.reverse.to_h + + respond_to do |format| + format.json { render(json: r.to_json) } + format.csv do + send_data(CSV.generate do |csv| + csv << [_('Month'), _('No. Plans')] + total = 0 + r.each_pair do |k, v| + csv << [k, v] + total += v + end + csv << [_('Total'), total] + end, filename: "#{_('plans')}.csv") end - csv << [_("Total"), total] - end, filename: "#{_('plans')}.csv") + end + else + if params['start_date'].present? || params['end_date'].present? + scoped = scoped.where(created_at: dates_to_range(params)) + end + render(json: { completed_plans: scoped.count }) end end - else - if params["start_date"].present? || params["end_date"].present? - scoped = scoped.where(created_at: dates_to_range(params)) + # rubocop:enable Metrics/AbcSize, Metrics/MethodLength + # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity + + ## + # Displays the number of DMPs using templates owned/create by the caller's Org + # between the optional specified dates + # rubocop:disable Metrics/AbcSize + def using_template + org_templates = @user.org.templates.where(customization_of: nil) + raise Pundit::NotAuthorizedError unless Api::V0::StatisticsPolicy.new(@user, + org_templates.first).using_template? + + @templates = {} + org_templates.each do |template| + if @templates[template.title].blank? + @templates[template.title] = {} + @templates[template.title][:title] = template.title + @templates[template.title][:id] = template.family_id + @templates[template.title][:uses] = 0 + end + scoped = template.plans + if params['start_date'].present? || params['end_date'].present? + scoped = scoped.where(created_at: dates_to_range(params)) + end + @templates[template.title][:uses] += scoped.length + end + respond_with @templates end - render(json: { completed_plans: scoped.count }) - end - end - # rubocop:enable Metrics/AbcSize, Metrics/MethodLength - # rubocop:enable - - ## - # Displays the number of DMPs using templates owned/create by the caller's Org - # between the optional specified dates - # rubocop:disable Metrics/AbcSize - def using_template - org_templates = @user.org.templates.where(customization_of: nil) - unless Api::V0::StatisticsPolicy.new(@user, org_templates.first).using_template? - raise Pundit::NotAuthorizedError - end - - @templates = {} - org_templates.each do |template| - if @templates[template.title].blank? - @templates[template.title] = {} - @templates[template.title][:title] = template.title - @templates[template.title][:id] = template.family_id - @templates[template.title][:uses] = 0 + # rubocop:enable Metrics/AbcSize + + ## + # GET + # Renders a list of templates with their titles, ids, and uses between the optional + # specified dates the uses are restricted to DMPs created by users of the same + # organisation as the user who ititiated the call. + # rubocop:disable Metrics/AbcSize + def plans_by_template + raise Pundit::NotAuthorizedError unless Api::V0::StatisticsPolicy.new(@user, :statistics).plans_by_template? + + @templates = {} + scoped = @user.org.plans + if params['start_date'].present? || params['end_date'].present? + scoped = scoped.where(created_at: dates_to_range(params)) + end + scoped.each do |plan| + # if hash exists + if @templates[plan.template.title].blank? + @templates[plan.template.title] = {} + @templates[plan.template.title][:title] = plan.template.title + @templates[plan.template.title][:id] = plan.template.family_id + @templates[plan.template.title][:uses] = 1 + else + @templates[plan.template.title][:uses] += 1 + end + end + respond_with @templates end - scoped = template.plans - if params["start_date"].present? || params["end_date"].present? - scoped = scoped.where(created_at: dates_to_range(params)) + # rubocop:enable Metrics/AbcSize + + # GET + # + # Renders a list of DMPs metadata, provided the DMPs were created between the + # optional specified dates DMPs must be owned by a user who's organisation is the + # same as the user who generates the call. + # rubocop:disable Metrics/AbcSize + 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' + @org_plans = @org_plans.where.not(visibility: Plan.visibilities[:is_test]) + end + if params['start_date'].present? || params['end_date'].present? + @org_plans = @org_plans.where(created_at: dates_to_range(params)) + end + respond_with @org_plans end - @templates[template.title][:uses] += scoped.length - end - respond_with @templates - end - # rubocop:enable Metrics/AbcSize - - ## - # GET - # Renders a list of templates with their titles, ids, and uses between the optional - # specified dates the uses are restricted to DMPs created by users of the same - # organisation as the user who ititiated the call. - # rubocop:disable Metrics/AbcSize - def plans_by_template - unless Api::V0::StatisticsPolicy.new(@user, :statistics).plans_by_template? - raise Pundit::NotAuthorizedError - end + # rubocop:enable Metrics/AbcSize - @templates = {} - scoped = @user.org.plans - if params["start_date"].present? || params["end_date"].present? - scoped = scoped.where(created_at: dates_to_range(params)) - end - scoped.each do |plan| - # if hash exists - if @templates[plan.template.title].blank? - @templates[plan.template.title] = {} - @templates[plan.template.title][:title] = plan.template.title - @templates[plan.template.title][:id] = plan.template.family_id - @templates[plan.template.title][:uses] = 1 - else - @templates[plan.template.title][:uses] += 1 + private + + # Convert start/end dates in hash to a range of Dates + def dates_to_range(hash) + today = Date.today + start_date = Date.parse(hash.fetch('start_date', today.prev_month.to_date.to_s)) + end_date = Date.parse(hash.fetch('end_date', today.to_date.to_s)) + 1.day + start_date..end_date end end - respond_with @templates - end - # rubocop:enable Metrics/AbcSize - - # GET - # - # Renders a list of DMPs metadata, provided the DMPs were created between the - # optional specified dates DMPs must be owned by a user who's organisation is the - # same as the user who generates the call. - # rubocop:disable Metrics/AbcSize - 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" - @org_plans = @org_plans.where.not(visibility: Plan.visibilities[:is_test]) - end - if params["start_date"].present? || params["end_date"].present? - @org_plans = @org_plans.where(created_at: dates_to_range(params)) - end - respond_with @org_plans end - # rubocop:enable Metrics/AbcSize - - private - - # Convert start/end dates in hash to a range of Dates - def dates_to_range(hash) - today = Date.today - start_date = Date.parse(hash.fetch("start_date", today.prev_month.to_date.to_s)) - end_date = Date.parse(hash.fetch("end_date", today.to_date.to_s)) + 1.day - start_date..end_date - end - end diff --git a/app/controllers/api/v0/templates_controller.rb b/app/controllers/api/v0/templates_controller.rb index b4f8e1e243..f1156593f5 100644 --- a/app/controllers/api/v0/templates_controller.rb +++ b/app/controllers/api/v0/templates_controller.rb @@ -1,58 +1,59 @@ # frozen_string_literal: true -class Api::V0::TemplatesController < Api::V0::BaseController - - before_action :authenticate - - # GET - # - # Renders a list of templates ordered by organisation - # rubocop:disable Metrics/AbcSize, Metrics/MethodLength - def index - # check if the user has permissions to use the templates API - unless Api::V0::TemplatePolicy.new(@user, :guidance_group).index? - raise Pundit::NotAuthorizedError - end - - @org_templates = {} - - published_templates = Template.includes(:org) - .unarchived - .where(customization_of: nil, published: true) - .order(:org_id, :version) - - customized_templates = Template.includes(:org) - .unarchived - .where(org_id: @user.org_id, published: true) - .where.not(customization_of: nil) - - published_templates.order(:org_id, :version).each do |temp| - if @org_templates[temp.org].present? - if @org_templates[temp.org][:own][temp.family_id].nil? - @org_templates[temp.org][:own][temp.family_id] = temp +module Api + module V0 + # Handles queries for templates for API V0 + class TemplatesController < Api::V0::BaseController + before_action :authenticate + + # GET + # + # Renders a list of templates ordered by organisation + # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity + def index + # check if the user has permissions to use the templates API + raise Pundit::NotAuthorizedError unless Api::V0::TemplatePolicy.new(@user, :guidance_group).index? + + @org_templates = {} + + published_templates = Template.includes(:org) + .unarchived + .where(customization_of: nil, published: true) + .order(:org_id, :version) + + customized_templates = Template.includes(:org) + .unarchived + .where(org_id: @user.org_id, published: true) + .where.not(customization_of: nil) + + published_templates.order(:org_id, :version).each do |temp| + if @org_templates[temp.org].present? + @org_templates[temp.org][:own][temp.family_id] = temp if @org_templates[temp.org][:own][temp.family_id].nil? + else + @org_templates[temp.org] = {} + @org_templates[temp.org][:own] = {} + @org_templates[temp.org][:cust] = {} + @org_templates[temp.org][:own][temp.family_id] = temp + end end - else - @org_templates[temp.org] = {} - @org_templates[temp.org][:own] = {} - @org_templates[temp.org][:cust] = {} - @org_templates[temp.org][:own][temp.family_id] = temp - end - end - customized_templates.each do |temp| - if @org_templates[temp.org].present? - if @org_templates[temp.org][:cust][temp.family_id].nil? - @org_templates[temp.org][:cust][temp.family_id] = temp + customized_templates.each do |temp| + if @org_templates[temp.org].present? + if @org_templates[temp.org][:cust][temp.family_id].nil? + @org_templates[temp.org][:cust][temp.family_id] = + temp + end + else + @org_templates[temp.org] = {} + @org_templates[temp.org][:own] = {} + @org_templates[temp.org][:cust] = {} + @org_templates[temp.org][:cust][temp.family_id] = temp + end end - else - @org_templates[temp.org] = {} - @org_templates[temp.org][:own] = {} - @org_templates[temp.org][:cust] = {} - @org_templates[temp.org][:cust][temp.family_id] = temp + respond_with @org_templates end + # rubocop:enable Metrics/AbcSize, Metrics/MethodLength + # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity end - respond_with @org_templates end - # rubocop:enable Metrics/AbcSize, Metrics/MethodLength - # rubocop:enable - end diff --git a/app/controllers/api/v1/authentication_controller.rb b/app/controllers/api/v1/authentication_controller.rb index 752bfbb117..c74dd39c5d 100644 --- a/app/controllers/api/v1/authentication_controller.rb +++ b/app/controllers/api/v1/authentication_controller.rb @@ -1,9 +1,7 @@ # frozen_string_literal: true module Api - module V1 - # Accepts 2 types of authentication: # # Client Credentials: @@ -24,12 +22,12 @@ module V1 # code: "[users.api_token]" # } class AuthenticationController < BaseApiController - respond_to :json skip_before_action :authorize_request, only: %i[authenticate] # POST /api/v1/authenticate + # rubocop:disable Metrics/AbcSize def authenticate body = request.body.read json = JSON.parse(body) @@ -38,20 +36,17 @@ def authenticate if @token.present? @expiration = auth_svc.expiration - @token_type = "Bearer" - render "/api/v1/token", status: :ok + @token_type = 'Bearer' + render '/api/v1/token', status: :ok else render_error errors: auth_svc.errors, status: :unauthorized end rescue JSON::ParserError => e Rails.logger.error "API V1 - authenticate: #{e.message}" Rails.logger.error request.body.read - render_error errors: _("Missing or invalid JSON"), status: :bad_request + render_error errors: _('Missing or invalid JSON'), status: :bad_request end - # rubocop:enable - + # rubocop:enable Metrics/AbcSize end - end - end diff --git a/app/controllers/api/v1/base_api_controller.rb b/app/controllers/api/v1/base_api_controller.rb index 77da394d82..e25a6740a7 100644 --- a/app/controllers/api/v1/base_api_controller.rb +++ b/app/controllers/api/v1/base_api_controller.rb @@ -1,12 +1,9 @@ # frozen_string_literal: true module Api - module V1 - # Base API Controller class BaseApiController < ApplicationController - # Skipping the standard Rails authenticity tokens passed in UI skip_before_action :verify_authenticity_token @@ -26,14 +23,14 @@ class BaseApiController < ApplicationController # GET /api/v1/heartbeat def heartbeat - render "/api/v1/heartbeat", status: :ok + render '/api/v1/heartbeat', status: :ok end protected def render_error(errors:, status:) @payload = { errors: [errors] } - render "/api/v1/error", status: status + render '/api/v1/error', status: status end private @@ -63,12 +60,13 @@ def base_response_content # Retrieve the requested pagination params or use defaults # only allow 100 per page as the max def pagination_params - @page = params.fetch("page", 1).to_i - @per_page = params.fetch("per_page", 20).to_i + @page = params.fetch('page', 1).to_i + @per_page = params.fetch('per_page', 20).to_i @per_page = 100 if @per_page > 100 end # Parse the body of the incoming request + # rubocop:disable Metrics/AbcSize def parse_request return false unless request.present? && request.body.present? @@ -78,10 +76,11 @@ def parse_request rescue JSON::ParserError => e Rails.logger.error "JSON Parser: #{e.message}" Rails.logger.error request.body - render_error(errors: _("Invalid JSON format"), status: :bad_request) + render_error(errors: _('Invalid JSON format'), status: :bad_request) false end end + # rubocop:enable Metrics/AbcSize # ========================== @@ -189,9 +188,6 @@ def host_permitted_params storage_type availability geo_location certified_with pid_system] + [host_ids: identifier_permitted_params] end - end - end - end diff --git a/app/controllers/api/v1/plans_controller.rb b/app/controllers/api/v1/plans_controller.rb index 649f987d97..b1480437bd 100644 --- a/app/controllers/api/v1/plans_controller.rb +++ b/app/controllers/api/v1/plans_controller.rb @@ -1,11 +1,9 @@ # frozen_string_literal: true module Api - module V1 - + # Handles CRUD operations for plans in API V1 class PlansController < BaseApiController - respond_to :json # GET /api/v1/plans/:id @@ -15,14 +13,15 @@ def show if plans.present? && plans.any? @items = paginate_response(results: plans) - render "/api/v1/plans/index", status: :ok + render '/api/v1/plans/index', status: :ok else - render_error(errors: [_("Plan not found")], status: :not_found) + render_error(errors: [_('Plan not found')], status: :not_found) end end # POST /api/v1/plans # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity def create dmp = @json.with_indifferent_access.fetch(:items, []).first.fetch(:dmp, {}) @@ -34,8 +33,8 @@ def create # Convert the JSON into a Plan and it's associations plan = Api::V1::Deserialization::Plan.deserialize(json: dmp) if plan.present? - save_err = _("Unable to create your DMP") - exists_err = _("Plan already exists. Send an update instead.") + save_err = _('Unable to create your DMP') + exists_err = _('Plan already exists. Send an update instead.') no_org_err = _("Could not determine ownership of the DMP. Please add an :affiliation to the :contact") @@ -55,11 +54,8 @@ def create # If we cannot save for some reason then return an error plan = Api::V1::PersistenceService.safe_save(plan: plan) - # rubocop:disable Layout/LineLength render_error(errors: save_err, status: :internal_server_error) and return if plan.new_record? - # rubocop:enable Layout/LineLength - # If the plan was generated by an ApiClient then associate them plan.update(api_client_id: client.id) if client.is_a?(ApiClient) @@ -69,14 +65,15 @@ def create # Kaminari Pagination requires an ActiveRecord result set :/ @items = paginate_response(results: Plan.where(id: plan.id)) - render "/api/v1/plans/index", status: :created + render '/api/v1/plans/index', status: :created else - render_error(errors: [_("Invalid JSON!")], status: :bad_request) + render_error(errors: [_('Invalid JSON!')], status: :bad_request) end rescue JSON::ParserError - render_error(errors: [_("Invalid JSON")], status: :bad_request) + render_error(errors: [_('Invalid JSON')], status: :bad_request) end # rubocop:enable Metrics/AbcSize, Metrics/MethodLength + # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity # GET /api/v1/plans def index @@ -88,9 +85,9 @@ def index if plans.present? && plans.any? @items = paginate_response(results: plans) @minimal = true - render "api/v1/plans/index", status: :ok + render 'api/v1/plans/index', status: :ok else - render_error(errors: [_("No Plans found")], status: :not_found) + render_error(errors: [_('No Plans found')], status: :not_found) end end @@ -134,11 +131,12 @@ def lookup_user(contributor:) user end + # rubocop:disable Metrics/AbcSize def invite_contributor(contributor:) return nil unless contributor.present? # If the user was not found, invite them and attach any know identifiers - names = contributor.name&.split || [""] + names = contributor.name&.split || [''] firstname = names.length > 1 ? names.first : nil surname = names.length > 1 ? names.last : names.first user = User.invite!({ email: contributor.email, @@ -153,9 +151,7 @@ def invite_contributor(contributor:) end user end - + # rubocop:enable Metrics/AbcSize end - end - end diff --git a/app/controllers/api/v1/templates_controller.rb b/app/controllers/api/v1/templates_controller.rb index 57383c825c..729b3edbbd 100644 --- a/app/controllers/api/v1/templates_controller.rb +++ b/app/controllers/api/v1/templates_controller.rb @@ -1,14 +1,13 @@ # frozen_string_literal: true module Api - module V1 - + # Provides a list of templates for API V1 class TemplatesController < BaseApiController - respond_to :json # GET /api/v1/templates + # rubocop:disable Metrics/AbcSize def index # If this is a User and not an ApiClient include the Org's # templates and customizations as well as the public ones @@ -35,12 +34,10 @@ def index templates = templates.order(:title) @items = paginate_response(results: templates) - render "/api/v1/templates/index", status: :ok + render '/api/v1/templates/index', status: :ok end # rubocop:enable - end - + # rubocop:enable Metrics/AbcSize end - end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 53dad81fb4..0253b5cef6 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true +# Base controller logic class ApplicationController < ActionController::Base - protect_from_forgery with: :exception before_action :configure_permitted_parameters, if: :devise_controller? @@ -22,8 +22,6 @@ class ApplicationController < ActionController::Base # When we are in production reroute Record Not Found errors to the branded 404 page rescue_from ActiveRecord::RecordNotFound, with: :render_not_found - rescue_from StandardError, with: :handle_server_error - rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized private @@ -35,11 +33,11 @@ def current_org def user_not_authorized if user_signed_in? # redirect_to plans_url, alert: _("You are not authorized to perform this action.") - msg = _("You are not authorized to perform this action.") + msg = _('You are not authorized to perform this action.') render_respond_to_format_with_error_message(msg, plans_url, 403, nil) else # redirect_to root_url, alert: _("You need to sign in or sign up before continuing.") - msg = _("You need to sign in or sign up before continuing.") + msg = _('You need to sign in or sign up before continuing.') render_respond_to_format_with_error_message(msg, root_url, 401, nil) end end @@ -56,15 +54,16 @@ def current_locale def store_location # store last url - this is needed for post-login redirect to whatever the user last # visited. - unless ["/users/sign_in", - "/users/sign_up", - "/users/password", - "/users/invitation/accept"].any? { |ur| request.fullpath.include?(ur) } \ - or request.xhr? # don't store ajax calls + unless ['/users/sign_in', + '/users/sign_up', + '/users/password', + '/users/invitation/accept'].any? { |ur| request.fullpath.include?(ur) } \ + || request.xhr? # don't store ajax calls session[:previous_url] = request.fullpath end end + # rubocop:disable Metrics/AbcSize def after_sign_in_path_for(_resource) referer_path = URI(request.referer).path unless request.referer.nil? if from_external_domain? || referer_path.eql?(new_user_session_path) || @@ -75,6 +74,7 @@ def after_sign_in_path_for(_resource) request.referer end end + # rubocop:enable Metrics/AbcSize def after_sign_up_path_for(_resource) referer_path = URI(request.referer).path unless request.referer.nil? @@ -104,45 +104,42 @@ def authenticate_admin! end end - def failure_message(obj, action = "save") - _("Unable to %{action} the %{object}.%{errors}") % { - object: obj_name_for_display(obj), - action: action || "save", - errors: errors_for_display(obj) - } + def failure_message(obj, action = 'save') + format(_('Unable to %s the %s.%s'), + object: obj_name_for_display(obj), + action: action || 'save', errors: errors_for_display(obj)) end - def success_message(obj, action = "saved") - _("Successfully %{action} the %{object}.") % { - object: obj_name_for_display(obj), - action: action || "save" - } + def success_message(obj, action = 'saved') + format(_('Successfully %s the %s.'), object: obj_name_for_display(obj), action: action || 'save') end def errors_for_display(obj) - return "" unless obj.present? && obj.errors.any? + return '' unless obj.present? && obj.errors.any? msgs = obj.errors.full_messages.uniq.collect { |msg| "
  • #{msg}
  • " } - "
      #{msgs.join('')}
    " + "
      #{msgs.join}
    " end + # rubocop:disable Metrics/AbcSize def obj_name_for_display(obj) display_name = { - ApiClient: _("API client"), - ExportedPlan: _("plan"), - GuidanceGroup: _("guidance group"), - Note: _("comment"), - Org: _("organisation"), - Perm: _("permission"), - Pref: _("preferences"), - User: obj == current_user ? _("profile") : _("user"), - QuestionOption: _("question option") + ApiClient: _('API client'), + ExportedPlan: _('plan'), + GuidanceGroup: _('guidance group'), + Note: _('comment'), + Org: _('organisation'), + Perm: _('permission'), + Pref: _('preferences'), + User: obj == current_user ? _('profile') : _('user'), + QuestionOption: _('question option') } if obj.respond_to?(:customization_of) && obj.send(:customization_of).present? - display_name[:Template] = "customization" + display_name[:Template] = 'customization' end - display_name[obj.class.name.to_sym] || obj.class.name.downcase || "record" + display_name[obj.class.name.to_sym] || obj.class.name.downcase || 'record' end + # rubocop:enable Metrics/AbcSize # Override rails default render action to look for a branded version of a # template instead of using the default one. If no override exists, the @@ -152,7 +149,7 @@ def obj_name_for_display(obj) # replacing. For example: # app/views/branded/layouts/_header.html.erb -> app/views/layouts/_header.html.erb def prepend_view_paths - prepend_view_path Rails.root.join("app", "views", "branded") + prepend_view_path Rails.root.join('app', 'views', 'branded') end ## @@ -181,15 +178,10 @@ def configure_permitted_parameters end def render_not_found(exception) - msg = _("Record Not Found") + ": #{exception.message}" + msg = _('Record Not Found') + ": #{exception.message}" render_respond_to_format_with_error_message(msg, root_url, 404, exception) end - def handle_server_error(exception) - msg = exception.message.to_s if exception.present? - render_respond_to_format_with_error_message(msg, root_url, 500, exception) - end - def render_respond_to_format_with_error_message(msg, url_or_path, http_status, exception) Rails.logger.error msg Rails.logger.error exception&.backtrace if exception.present? @@ -200,9 +192,8 @@ def render_respond_to_format_with_error_message(msg, url_or_path, http_status, e # Render the JSON error message (using API V1) format.json do @payload = { errors: [msg] } - render "/api/v1/error", status: http_status + render '/api/v1/error', status: http_status end end end - end diff --git a/app/controllers/concerns/allowed_question_formats.rb b/app/controllers/concerns/allowed_question_formats.rb index 0dc68f3af7..3e8d4bd995 100644 --- a/app/controllers/concerns/allowed_question_formats.rb +++ b/app/controllers/concerns/allowed_question_formats.rb @@ -1,12 +1,12 @@ # frozen_string_literal: true +# Controller that gets Questions types that allow multiple selections +# TODO: this could likely just live on the model! module AllowedQuestionFormats - private # The QuestionFormat "Multi select box" is no longer being used for new templates def allowed_question_formats - QuestionFormat.where.not(title: "Multi select box").order(:title) + QuestionFormat.where.not(title: 'Multi select box').order(:title) end - end diff --git a/app/controllers/concerns/conditional_user_mailer.rb b/app/controllers/concerns/conditional_user_mailer.rb index c9f5ce42e1..c83338347d 100644 --- a/app/controllers/concerns/conditional_user_mailer.rb +++ b/app/controllers/concerns/conditional_user_mailer.rb @@ -1,7 +1,8 @@ # frozen_string_literal: true +# Determines whether or not the user has enabled/disabled the email notification +# before sending it out module ConditionalUserMailer - # Executes a given block passed if the recipient user has the preference # email key enabled # @@ -10,18 +11,17 @@ module ConditionalUserMailer # prefences.email (see dmproadmap.rb initializer) # # Returns Boolean - def deliver_if(recipients: [], key:, &block) + def deliver_if(key:, recipients: [], &block) return false unless block_given? Array(recipients).each do |recipient| - email_hash = recipient.get_preferences("email").with_indifferent_access + 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(".")) + preference_value = email_hash.dig(*key.to_s.split('.')) block.call(recipient) if preference_value end true end - end diff --git a/app/controllers/concerns/org_selectable.rb b/app/controllers/concerns/org_selectable.rb index 5aac93cc48..84f3d270a4 100644 --- a/app/controllers/concerns/org_selectable.rb +++ b/app/controllers/concerns/org_selectable.rb @@ -54,7 +54,6 @@ # # See the comments on OrgsController#search for more info on how the typeaheads work module OrgSelectable - extend ActiveSupport::Concern # rubocop:disable Metrics/BlockLength @@ -65,7 +64,9 @@ module OrgSelectable # Converts the incoming params_into an Org by either locating it # via its id, identifier and/or name, or initializing a new one - def org_from_params(params_in:, allow_create: true) + # the default allow_create is based off restrict_orgs + def org_from_params(params_in:, + allow_create: !Rails.configuration.x.application.restrict_orgs) # params_in = params_in.with_indifferent_access return nil unless params_in[:org_id].present? && params_in[:org_id].is_a?(String) @@ -125,11 +126,10 @@ def create_org(org:, params_in:) end def prep_org_partial - name = Rails.configuration.x.application.restrict_orgs ? "local_only" : "combined" + name = Rails.configuration.x.application.restrict_orgs ? 'local_only' : 'combined' @org_partial = "shared/org_selectors/#{name}" @all_orgs = Org.includes(identifiers: [:identifier_scheme]).all end end # rubocop:enable Metrics/BlockLength - end diff --git a/app/controllers/concerns/paginable.rb b/app/controllers/concerns/paginable.rb index aea376ed12..0a81985b49 100644 --- a/app/controllers/concerns/paginable.rb +++ b/app/controllers/concerns/paginable.rb @@ -1,14 +1,14 @@ # frozen_string_literal: true +# Provides support for pagination/searching/sorting of table data # rubocop:disable Metrics/ModuleLength module Paginable - extend ActiveSupport::Concern - require "sort_direction" + require 'sort_direction' ## # Regex to validate sort_field param is safe - SORT_COLUMN_FORMAT = /[\w_]+\.[\w_]/.freeze + SORT_COLUMN_FORMAT = /[\w_]+\.[\w_]+$/.freeze PAGINATION_QUERY_PARAMS = %i[page sort_field sort_direction search controller action].freeze @@ -37,15 +37,16 @@ module Paginable # one approach to just include everything in the double splat `**options` param # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/ParameterLists + # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity def paginable_renderise(partial: nil, template: nil, controller: nil, action: nil, path_params: {}, query_params: {}, scope: nil, locals: {}, **options) unless scope.is_a?(ActiveRecord::Relation) - raise ArgumentError, _("scope should be an ActiveRecord::Relation object") + raise ArgumentError, _('scope should be an ActiveRecord::Relation object') end - raise ArgumentError, _("path_params should be a Hash object") unless path_params.is_a?(Hash) - raise ArgumentError, _("query_params should be a Hash object") unless query_params.is_a?(Hash) - raise ArgumentError, _("locals should be a Hash object") unless locals.is_a?(Hash) + raise ArgumentError, _('path_params should be a Hash object') unless path_params.is_a?(Hash) + raise ArgumentError, _('query_params should be a Hash object') unless query_params.is_a?(Hash) + raise ArgumentError, _('locals should be a Hash object') unless locals.is_a?(Hash) # Default options @paginable_options = {}.merge(options) @@ -61,12 +62,12 @@ def paginable_renderise(partial: nil, template: nil, controller: nil, action: ni # Additional path_params passed to this function got special treatment # (e.g. it is taking into account when building base_url) @paginable_path_params = path_params.symbolize_keys - if @args[:page] == "ALL" && + if @args[:page] == 'ALL' && @args[:search].blank? && @paginable_options[:view_all] == false render( status: :forbidden, - html: _("Restricted access to View All the records") + html: _('Restricted access to View All the records') ) else @refined_scope = refine_query(scope) @@ -78,17 +79,17 @@ def paginable_renderise(partial: nil, template: nil, controller: nil, action: ni ) # If this was an ajax call then render as JSON if options[:format] == :json - render json: { html: render_to_string(layout: "/layouts/paginable", + render json: { html: render_to_string(layout: '/layouts/paginable', partial: partial, locals: locals) } elsif partial.present? - render(layout: "/layouts/paginable", partial: partial, locals: locals) + render(layout: '/layouts/paginable', partial: partial, locals: locals) else render(template: template, locals: locals) end end end # rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/ParameterLists - # rubocop:enable + # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity # Returns the base url of the paginable route for a given page passed def paginable_base_url(page = 1) @@ -107,7 +108,7 @@ def paginable_sort_link(sort_field) link_to( sort_link_name(sort_field), sort_link_url(sort_field), - class: "paginable-action", + class: 'paginable-action', data: { remote: @paginable_options[:remote] }, aria: { label: sort_field } ) @@ -125,21 +126,21 @@ def paginable? # Refine a scope passed to this concern if any of the params (search, # sort_field or page) are present - # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/AbcSize, Metrics/MethodLength def refine_query(scope) @args = @args.with_indifferent_access - scope = scope.search(@args[:search]) if @args[:search].present? + scope = scope.search(@args[:search]).distinct if @args[:search].present? # Can raise NoMethodError if the scope does not define a search method if @args[:sort_field].present? frmt = @args[:sort_field][SORT_COLUMN_FORMAT] - raise ArgumentError, "sort_field param looks unsafe" unless frmt + raise ArgumentError, 'sort_field param looks unsafe' unless frmt # Can raise ActiveRecord::StatementInvalid (e.g. column does not # exist, ambiguity on column, etc) # how we contruct scope depends on whether sort field is in the # main table or in a related table scope_table = scope.klass.name.underscore - parts = @args[:sort_field].partition(".") + parts = @args[:sort_field].partition('.') table_part = parts.first column_part = parts.last if scope_table == table_part.singularize @@ -147,17 +148,18 @@ def refine_query(scope) scope = scope.order(order_field.to_sym => sort_direction.to_s) else order_field = ActiveRecord::Base.sanitize_sql(@args[:sort_field]) + sd = ActiveRecord::Base.sanitize_sql(sort_direction) scope = scope.includes(table_part.singularize.to_sym) - .order(order_field + " " + sort_direction.to_s) + .order("#{order_field} #{sd}") end end - if @args[:page] != "ALL" + if @args[:page] != 'ALL' # Can raise error if page is not a number scope = scope.page(@args[:page]) end scope end - # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/AbcSize, Metrics/MethodLength def sort_direction @sort_direction ||= SortDirection.new(@args[:sort_direction]) @@ -167,9 +169,9 @@ def sort_direction # html prevented of being escaped def sort_link_name(sort_field) @args = @args.with_indifferent_access - class_name = "fas fa-sort" - dir = "up" - dir = "down" if sort_direction.to_s == "DESC" + class_name = 'fas fa-sort' + dir = 'up' + dir = 'down' if sort_direction.to_s == 'DESC' class_name = "fas fa-sort-#{dir}" if @args[:sort_field] == sort_field <<~HTML.html_safe - #{_('Sort by %{sort_field}') % { sort_field: sort_field.split('.').first }} + #{format(_('Sort by %s'), sort_field: sort_field.split('.').first)} HTML end # Returns the sort url for a given sort_field. + # rubocop:disable Metrics/AbcSize def sort_link_url(sort_field) @args = @args.with_indifferent_access query_params = {} - query_params[:page] = @args[:page] == "ALL" ? "ALL" : 1 + query_params[:page] = @args[:page] == 'ALL' ? 'ALL' : 1 query_params[:sort_field] = sort_field query_params[:sort_direction] = if @args[:sort_field] == sort_field sort_direction.opposite @@ -200,6 +203,7 @@ def sort_link_url(sort_field) sort_url.to_s "#{sort_url}&#{stringify_nonpagination_query_params}" end + # rubocop:enable Metrics/AbcSize # Retrieve any query params that are not a part of the paginable concern def stringify_nonpagination_query_params @@ -211,10 +215,10 @@ def stringify_query_params(page: 1, search: @args[:search], sort_direction: nil) query_string = { page: page } - query_string["search"] = search if search.present? + query_string['search'] = search if search.present? if sort_field.present? - query_string["sort_field"] = sort_field - query_string["sort_direction"] = SortDirection.new(sort_direction) + query_string['sort_field'] = sort_field + query_string['sort_direction'] = SortDirection.new(sort_direction) end query_string.to_param end @@ -222,6 +226,5 @@ def stringify_query_params(page: 1, search: @args[:search], def paginable_params params.permit(PAGINATION_QUERY_PARAMS) end - end # rubocop:enable Metrics/ModuleLength diff --git a/app/controllers/concerns/template_methods.rb b/app/controllers/concerns/template_methods.rb index bebd8249a2..f1ebf5d862 100644 --- a/app/controllers/concerns/template_methods.rb +++ b/app/controllers/concerns/template_methods.rb @@ -1,13 +1,10 @@ # frozen_string_literal: true # This module holds helper controller methods for controllers that deal with Templates -# module TemplateMethods - private def template_type(template) - template.customization_of.present? ? _("customisation") : _("template") + template.customization_of.present? ? _('customisation') : _('template') end - end diff --git a/app/controllers/concerns/versionable.rb b/app/controllers/concerns/versionable.rb index 24233e4ef9..e242f16e78 100644 --- a/app/controllers/concerns/versionable.rb +++ b/app/controllers/concerns/versionable.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true +# Helpers that allow us to version Template-Phase-Section-Question module Versionable - private # Takes in a Template, phase, Section, Question, or Annotaion @@ -18,7 +18,7 @@ def get_modifiable(obj) template = obj else raise ArgumentError, - _("obj should be a Template, Phase, Section, Question, or Annotation") + _('obj should be a Template, Phase, Section, Question, or Annotation') end # raises RuntimeError if template is not latest @@ -39,11 +39,12 @@ def get_modifiable(obj) # generated and returns a modifiable version of that object # NOTE: the obj passed is still not saved however it should belongs to a # parent already - # rubocop:disable Metrics/MethodLength + # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + # rubocop:disable Metrics/CyclomaticComplexity def get_new(obj) unless obj.respond_to?(:template) raise ArgumentError, - _("obj should be a Phase, Section, Question, or Annotation") + _('obj should be a Phase, Section, Question, or Annotation') end template = obj.template @@ -62,7 +63,7 @@ def get_new(obj) belongs = :question else raise ArgumentError, - _("obj should be a Phase, Section, Question, or Annotation") + _('obj should be a Phase, Section, Question, or Annotation') end if belongs == :template @@ -76,21 +77,21 @@ def get_new(obj) end obj end - # rubocop:enable Metrics/MethodLength + # rubocop:enable Metrics/AbcSize, Metrics/MethodLength + # rubocop:enable Metrics/CyclomaticComplexity # Locates an object (e.g. phase, section, question, annotation) in a # search_space # (e.g. phases/sections/questions/annotations) by comparing either the number # method or the org_id and text for annotations # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity def find_in_space(obj, search_space) - unless search_space.respond_to?(:each) - raise ArgumentError, _("The search_space does not respond to each") - end + raise ArgumentError, _('The search_space does not respond to each') unless search_space.respond_to?(:each) if search_space.empty? raise ArgumentError, - _("The search space does not have elements associated") + _('The search space does not have elements associated') end if obj.is_a?(search_space.first.class) @@ -131,6 +132,5 @@ def find_in_space(obj, search_space) nil end # rubocop:enable Metrics/AbcSize, Metrics/MethodLength - # rubocop:enable - + # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity end diff --git a/app/controllers/contacts_controller.rb b/app/controllers/contacts_controller.rb index 9857fb6c1b..ca95d2fa3d 100644 --- a/app/controllers/contacts_controller.rb +++ b/app/controllers/contacts_controller.rb @@ -1,39 +1,43 @@ # frozen_string_literal: true -class ContactUs::ContactsController < ApplicationController +module ContactUs + # Controller for the Contact Us gem + class ContactsController < ApplicationController + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity + def create + @contact = ContactUs::Contact.new(params[:contact_us_contact]) - def create - @contact = ContactUs::Contact.new(params[:contact_us_contact]) - - if !user_signed_in? && Rails.configuration.x.recaptcha.enabled - unless verify_recaptcha(model: @contact) && @contact.save - flash[:alert] = _("Captcha verification failed, please retry.") + if !user_signed_in? && Rails.configuration.x.recaptcha.enabled && + !(verify_recaptcha(model: @contact) && @contact.save) + flash[:alert] = _('Captcha verification failed, please retry.') render_new_page and return end + if @contact.save + redirect_to(ContactUs.success_redirect || '/', + notice: _('Contact email was successfully sent.')) + else + flash[:alert] = _('Unable to submit your request') + render_new_page + end end - if @contact.save - redirect_to(ContactUs.success_redirect || "/", - notice: _("Contact email was successfully sent.")) - else - flash[:alert] = _("Unable to submit your request") + # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity + + def new + @contact = ContactUs::Contact.new render_new_page end - end - def new - @contact = ContactUs::Contact.new - render_new_page - end - - protected + protected - def render_new_page - case ContactUs.form_gem - when "formtastic" then render "new_formtastic" - when "simple_form" then render "new_simple_form" - else - render "new" + def render_new_page + case ContactUs.form_gem + when 'formtastic' then render 'new_formtastic' + when 'simple_form' then render 'new_simple_form' + else + render 'new' + end end end - end diff --git a/app/controllers/contributors_controller.rb b/app/controllers/contributors_controller.rb index 9dd63fca83..3c210b63d4 100644 --- a/app/controllers/contributors_controller.rb +++ b/app/controllers/contributors_controller.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true +# Controller for the Contributors page class ContributorsController < ApplicationController - include OrgSelectable helper PaginableHelper @@ -27,17 +27,17 @@ def edit authorize @plan end - # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/AbcSize, Metrics/MethodLength # POST /plans/:plan_id/contributors def create - authorize @plan + authorize @plan, :edit? args = translate_roles(hash: contributor_params) args = process_org(hash: args) if args.blank? @contributor = Contributor.new(args) - @contributor.errors.add(:affiliation, "invalid") - flash[:alert] = failure_message(@contributor, _("add")) + @contributor.errors.add(:affiliation, 'invalid') + flash[:alert] = failure_message(@contributor, _('add')) render :new else args = process_orcid_for_create(hash: args) @@ -51,14 +51,14 @@ def create save_orcid redirect_to plan_contributors_path(@plan), - notice: success_message(@contributor, _("added")) + notice: success_message(@contributor, _('added')) else - flash[:alert] = failure_message(@contributor, _("add")) + flash[:alert] = failure_message(@contributor, _('add')) render :new end end end - # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/AbcSize, Metrics/MethodLength # PUT /plans/:plan_id/contributors/:id def update @@ -69,9 +69,9 @@ def update if @contributor.update(args) redirect_to edit_plan_contributor_path(@plan, @contributor), - notice: success_message(@contributor, _("saved")) + notice: success_message(@contributor, _('saved')) else - flash.now[:alert] = failure_message(@contributor, _("save")) + flash.now[:alert] = failure_message(@contributor, _('save')) render :edit end end @@ -81,10 +81,10 @@ def update def destroy authorize @plan if @contributor.destroy - msg = success_message(@contributor, _("removed")) + msg = success_message(@contributor, _('removed')) redirect_to plan_contributors_path(@plan), notice: msg else - flash.now[:alert] = failure_message(@contributor, _("remove")) + flash.now[:alert] = failure_message(@contributor, _('remove')) render :edit end end @@ -105,7 +105,7 @@ def contributor_params # Translate the check boxes values of "1" and "0" to true/false def translate_roles(hash:) roles = Contributor.new.all_roles - roles.each { |role| hash[role.to_sym] = hash[role.to_sym] == "1" } + roles.each { |role| hash[role.to_sym] = hash[role.to_sym] == '1' } hash end @@ -131,7 +131,7 @@ def process_org(hash:) def process_orcid_for_create(hash:) return hash unless hash[:identifiers_attributes].present? - id_hash = hash[:identifiers_attributes][:"0"] + id_hash = hash[:identifiers_attributes][:'0'] return hash unless id_hash[:value].blank? hash.delete(:identifiers_attributes) @@ -142,10 +142,10 @@ def process_orcid_for_create(hash:) def process_orcid_for_update(hash:) return hash unless hash[:identifiers_attributes].present? - id_hash = hash[:identifiers_attributes][:"0"] + id_hash = hash[:identifiers_attributes][:'0'] return hash unless id_hash[:value].blank? - existing = @contributor.identifier_for_scheme(scheme: "orcid") + existing = @contributor.identifier_for_scheme(scheme: 'orcid') existing.destroy if existing.present? hash.delete(:identifiers_attributes) hash @@ -158,7 +158,7 @@ def fetch_plan @plan = Plan.includes(:contributors).find_by(id: params[:plan_id]) return true if @plan.present? - redirect_to root_path, alert: _("plan not found") + redirect_to root_path, alert: _('plan not found') end def fetch_contributor @@ -166,7 +166,7 @@ def fetch_contributor return true if @contributor.present? && @plan.contributors.include?(@contributor) - redirect_to plan_contributors_path, alert: _("contributor not found") + redirect_to plan_contributors_path, alert: _('contributor not found') end # The following 2 methods address an issue with using Rails normal @@ -196,5 +196,4 @@ def save_orcid @cached_orcid.save @contributor.reload end - end diff --git a/app/controllers/feedback_requests_controller.rb b/app/controllers/feedback_requests_controller.rb index 21ad271218..a8d91c4976 100644 --- a/app/controllers/feedback_requests_controller.rb +++ b/app/controllers/feedback_requests_controller.rb @@ -1,13 +1,13 @@ # frozen_string_literal: true +# Controller that handles requests for Admin Feedback class FeedbackRequestsController < ApplicationController - include FeedbacksHelper after_action :verify_authorized - ALERT = _("Unable to submit your request for feedback at this time.") - ERROR = _("An error occurred when requesting feedback for this plan.") + ALERT = _('Unable to submit your request for feedback at this time.') + ERROR = _('An error occurred when requesting feedback for this plan.') def create @plan = Plan.find(params[:plan_id]) @@ -34,5 +34,4 @@ def request_feedback_flash_notice text = current_user.org.feedback_msg || feedback_confirmation_default_message feedback_constant_to_text(text, current_user, @plan, current_user.org) end - end diff --git a/app/controllers/guidance_groups_controller.rb b/app/controllers/guidance_groups_controller.rb index a20b2dd233..611d5ef4d4 100644 --- a/app/controllers/guidance_groups_controller.rb +++ b/app/controllers/guidance_groups_controller.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true +# Controller for the Guidances page that handles Group info class GuidanceGroupsController < ApplicationController - after_action :verify_authorized respond_to :html @@ -18,6 +18,7 @@ def admin_new end # POST /org/admin/guidancegroup/:id/admin_create + # rubocop:disable Metrics/AbcSize def admin_create # Ensure that the user can only create GuidanceGroups for their Org args = guidance_group_params.to_h.merge({ org_id: current_user.org.id }) @@ -25,13 +26,14 @@ def admin_create authorize @guidance_group if @guidance_group.save - flash.now[:notice] = success_message(@guidance_group, _("created")) + flash.now[:notice] = success_message(@guidance_group, _('created')) render :admin_edit else - flash.now[:alert] = failure_message(@guidance_group, _("create")) + flash.now[:alert] = failure_message(@guidance_group, _('create')) render :admin_new end end + # rubocop:enable Metrics/AbcSize # GET /org/admin/guidancegroup/:id/admin_edit def admin_edit @@ -40,17 +42,19 @@ def admin_edit end # PUT /org/admin/guidancegroup/:id/admin_update + # rubocop:disable Metrics/AbcSize def admin_update @guidance_group = GuidanceGroup.find(params[:id]) authorize @guidance_group if @guidance_group.update(guidance_group_params) - flash.now[:notice] = success_message(@guidance_group, _("saved")) + flash.now[:notice] = success_message(@guidance_group, _('saved')) else - flash.now[:alert] = failure_message(@guidance_group, _("save")) + flash.now[:alert] = failure_message(@guidance_group, _('save')) end render :admin_edit end + # rubocop:enable Metrics/AbcSize # PUT /org/admin/guidancegroup/:id/admin_update_publish def admin_update_publish @@ -58,10 +62,10 @@ def admin_update_publish authorize @guidance_group if @guidance_group.update(published: true) - flash[:notice] = _("Your guidance group has been published and is now available to users.") + flash[:notice] = _('Your guidance group has been published and is now available to users.') else - flash[:alert] = failure_message(@guidance_group, _("publish")) + flash[:alert] = failure_message(@guidance_group, _('publish')) end redirect_to admin_index_guidance_path end @@ -72,11 +76,9 @@ def admin_update_unpublish authorize @guidance_group if @guidance_group.update(published: false) - # rubocop:disable Layout/LineLength - flash[:notice] = _("Your guidance group is no longer published and will not be available to users.") - # rubocop:enable Layout/LineLength + flash[:notice] = _('Your guidance group is no longer published and will not be available to users.') else - flash[:alert] = failure_message(@guidance_group, _("unpublish")) + flash[:alert] = failure_message(@guidance_group, _('unpublish')) end redirect_to admin_index_guidance_path end @@ -86,9 +88,9 @@ def admin_destroy @guidance_group = GuidanceGroup.find(params[:id]) authorize @guidance_group if @guidance_group.destroy - flash[:notice] = success_message(@guidance_group, _("deleted")) + flash[:notice] = success_message(@guidance_group, _('deleted')) else - flash[:alert] = failure_message(@guidance_group, _("delete")) + flash[:alert] = failure_message(@guidance_group, _('delete')) end redirect_to admin_index_guidance_path end @@ -98,5 +100,4 @@ def admin_destroy def guidance_group_params params.require(:guidance_group).permit(:org_id, :name, :published, :optional_subset) end - end diff --git a/app/controllers/guidances_controller.rb b/app/controllers/guidances_controller.rb index 74835bd073..d3a193a12e 100644 --- a/app/controllers/guidances_controller.rb +++ b/app/controllers/guidances_controller.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true +# Controller for the Guidances page that handles Guidance operations class GuidancesController < ApplicationController - after_action :verify_authorized respond_to :html @@ -36,6 +36,7 @@ def admin_edit end # POST /org/admin/guidance/:id/admin_create + # rubocop:disable Metrics/AbcSize def admin_create @guidance = Guidance.new(guidance_params) authorize @guidance @@ -48,14 +49,16 @@ def admin_create guidance_group.save end end - flash.now[:notice] = success_message(@guidance, _("created")) + flash.now[:notice] = success_message(@guidance, _('created')) else - flash.now[:alert] = failure_message(@guidance, _("create")) + flash.now[:alert] = failure_message(@guidance, _('create')) end render :new_edit end + # rubocop:enable Metrics/AbcSize # PUT /org/admin/guidance/:id/admin_update + # rubocop:disable Metrics/AbcSize def admin_update @guidance = Guidance.find(params[:id]) authorize @guidance @@ -68,14 +71,16 @@ def admin_update guidance_group.save end end - flash.now[:notice] = success_message(@guidance, _("saved")) + flash.now[:notice] = success_message(@guidance, _('saved')) else - flash.now[:alert] = failure_message(@guidance, _("save")) + flash.now[:alert] = failure_message(@guidance, _('save')) end render :new_edit end + # rubocop:enable Metrics/AbcSize # DELETE /org/admin/guidance/:id/admin_destroy + # rubocop:disable Metrics/AbcSize def admin_destroy @guidance = Guidance.find(params[:id]) authorize @guidance @@ -85,46 +90,47 @@ def admin_destroy guidance_group.published = false guidance_group.save end - flash[:notice] = success_message(@guidance, _("deleted")) + flash[:notice] = success_message(@guidance, _('deleted')) else - flash[:alert] = failure_message(@guidance, _("delete")) + flash[:alert] = failure_message(@guidance, _('delete')) end redirect_to(action: :admin_index) end + # rubocop:enable Metrics/AbcSize # PUT /org/admin/guidance/:id/admin_publish + # rubocop:disable Metrics/AbcSize def admin_publish @guidance = Guidance.find(params[:id]) authorize @guidance if @guidance.update_attributes(published: true) guidance_group = GuidanceGroup.find(@guidance.guidance_group_id) - if !guidance_group.published? || guidance_group.published.nil? - guidance_group.update(published: true) - end - flash[:notice] = _("Your guidance has been published and is now available to users.") + guidance_group.update(published: true) if !guidance_group.published? || guidance_group.published.nil? + flash[:notice] = _('Your guidance has been published and is now available to users.') else - flash[:alert] = failure_message(@guidance, _("publish")) + flash[:alert] = failure_message(@guidance, _('publish')) end redirect_to(action: :admin_index) end + # rubocop:enable Metrics/AbcSize # PUT /org/admin/guidance/:id/admin_unpublish + # rubocop:disable Metrics/AbcSize def admin_unpublish @guidance = Guidance.find(params[:id]) authorize @guidance if @guidance.update_attributes(published: false) guidance_group = GuidanceGroup.find(@guidance.guidance_group_id) - unless guidance_group.guidances.where(published: true).exists? - guidance_group.update(published: false) - end - flash[:notice] = _("Your guidance is no longer published and will not be available to users.") + guidance_group.update(published: false) unless guidance_group.guidances.where(published: true).exists? + flash[:notice] = _('Your guidance is no longer published and will not be available to users.') else - flash[:alert] = failure_message(@guidance, _("unpublish")) + flash[:alert] = failure_message(@guidance, _('unpublish')) end redirect_to(action: :admin_index) end + # rubocop:enable Metrics/AbcSize private @@ -138,5 +144,4 @@ def ensure_default_group(org) GuidanceGroup.create_org_default(org) end - end diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb index e4ec75806a..e7409d75b3 100644 --- a/app/controllers/home_controller.rb +++ b/app/controllers/home_controller.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true +# Controller for the home page that users see when not logged in class HomeController < ApplicationController - include OrgSelectable respond_to :html @@ -18,15 +18,14 @@ def index name = current_user.name(false) # The RolesController defaults the firstname and surname (both required fields) # to 'FirstName' and 'Surname' when a plan is shared with an unknown user - if name == "First Name Surname" + if name == 'First Name Surname' redirect_to edit_user_registration_path else redirect_to plans_url end - elsif session["devise.shibboleth_data"].present? + elsif session['devise.shibboleth_data'].present? # NOTE: Update this to handle ORCiD as well when we enable it as a login method redirect_to new_user_registration_url end end - end diff --git a/app/controllers/identifiers_controller.rb b/app/controllers/identifiers_controller.rb index 9b90f2c8a3..07f3530b80 100644 --- a/app/controllers/identifiers_controller.rb +++ b/app/controllers/identifiers_controller.rb @@ -1,12 +1,12 @@ # frozen_string_literal: true +# Controller that handles a user disassociating their Shib or ORCID on the profile page class IdentifiersController < ApplicationController - respond_to :html after_action :verify_authorized # DELETE /users/identifiers - # --------------------------------------------------------------------- + # rubocop:disable Metrics/AbcSize def destroy authorize Identifier user = User.find(current_user.id) @@ -15,16 +15,14 @@ def destroy # If the requested identifier belongs to the current user remove it if user.identifiers.include?(identifier) identifier.destroy! - flash[:notice] = _("Successfully unlinked your account from %{is}.") % { - is: identifier.identifier_scheme&.description - } + flash[:notice] = + format(_('Successfully unlinked your account from %s.'), is: identifier.identifier_scheme&.description) else - flash[:alert] = _("Unable to unlink your account from %{is}.") % { - is: identifier.identifier_scheme&.description - } + flash[:alert] = + format(_('Unable to unlink your account from %s.'), is: identifier.identifier_scheme&.description) end redirect_to edit_user_registration_path end - + # rubocop:enable Metrics/AbcSize end diff --git a/app/controllers/notes_controller.rb b/app/controllers/notes_controller.rb index a623a64fce..ae863c5eff 100644 --- a/app/controllers/notes_controller.rb +++ b/app/controllers/notes_controller.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true +# Controller for the Comments section of the Write Plan page class NotesController < ApplicationController - include ConditionalUserMailer - require "pp" + require 'pp' after_action :verify_authorized respond_to :html @@ -11,11 +11,10 @@ class NotesController < ApplicationController # rubocop:disable Metrics/AbcSize, Metrics/MethodLength def create @note = Note.new - @note.user_id = note_params[:user_id] + # take user id from current user rather than form as form can be spoofed + @note.user_id = current_user.id # ensure user has access to plan BEFORE creating/finding answer - unless Plan.find_by(id: note_params[:plan_id]).readable_by?(@note.user_id) - raise Pundit::NotAuthorizedError - end + raise Pundit::NotAuthorizedError unless Plan.find_by(id: note_params[:plan_id]).readable_by?(@note.user_id) Answer.transaction do @answer = Answer.find_by( @@ -33,11 +32,9 @@ def create @note.answer = @answer @note.text = note_params[:text] - authorize @note @plan = @answer.plan - @question = Question.find(note_params[:question_id]) if @note.save @@ -45,31 +42,31 @@ def create answer = @note.answer plan = answer.plan owner = plan.owner - deliver_if(recipients: owner, key: "users.new_comment") do |_r| + deliver_if(recipients: owner, key: 'users.new_comment') do |_r| UserMailer.new_comment(current_user, plan, answer).deliver_now end - @notice = success_message(@note, _("created")) + @notice = success_message(@note, _('created')) render(json: { - "notes" => { - "id" => note_params[:question_id], - "html" => render_to_string(partial: "layout", locals: { + 'notes' => { + 'id' => note_params[:question_id], + 'html' => render_to_string(partial: 'layout', locals: { plan: @plan, question: @question, answer: @answer }, formats: [:html]) }, - "title" => { - "id" => note_params[:question_id], - "html" => render_to_string(partial: "title", locals: { + 'title' => { + 'id' => note_params[:question_id], + 'html' => render_to_string(partial: 'title', locals: { answer: @answer }, formats: [:html]) } }.to_json, status: :created) else @status = false - @notice = failure_message(@note, _("create")) + @notice = failure_message(@note, _('create')) render json: { - "msg" => @notice + 'msg' => @notice }.to_json, status: :bad_request end end @@ -89,27 +86,27 @@ def update question_id = @note.answer.question_id.to_s if @note.update(note_params) - @notice = success_message(@note, _("saved")) + @notice = success_message(@note, _('saved')) render(json: { - "notes" => { - "id" => question_id, - "html" => render_to_string(partial: "layout", locals: { + 'notes' => { + 'id' => question_id, + 'html' => render_to_string(partial: 'layout', locals: { plan: @plan, question: @question, answer: @answer }, formats: [:html]) }, - "title" => { - "id" => question_id, - "html" => render_to_string(partial: "title", locals: { + 'title' => { + 'id' => question_id, + 'html' => render_to_string(partial: 'title', locals: { answer: @answer }, formats: [:html]) } }.to_json, status: :ok) else - @notice = failure_message(@note, _("save")) + @notice = failure_message(@note, _('save')) render json: { - "msg" => @notice + 'msg' => @notice }.to_json, status: :bad_request end end @@ -131,27 +128,27 @@ def archive question_id = @note.answer.question_id.to_s if @note.update(note_params) - @notice = success_message(@note, _("removed")) + @notice = success_message(@note, _('removed')) render(json: { - "notes" => { - "id" => question_id, - "html" => render_to_string(partial: "layout", locals: { + 'notes' => { + 'id' => question_id, + 'html' => render_to_string(partial: 'layout', locals: { plan: @plan, question: @question, answer: @answer }, formats: [:html]) }, - "title" => { - "id" => question_id, - "html" => render_to_string(partial: "title", locals: { + 'title' => { + 'id' => question_id, + 'html' => render_to_string(partial: 'title', locals: { answer: @answer }, formats: [:html]) } }.to_json, status: :ok) else - @notice = failure_message(@note, _("remove")) + @notice = failure_message(@note, _('remove')) render json: { - "msg" => @notice + 'msg' => @notice }.to_json, status: :bad_request end end @@ -164,5 +161,4 @@ def note_params .permit(:text, :archived_by, :user_id, :answer_id, :plan_id, :question_id) end - end diff --git a/app/controllers/org_admin/conditions_controller.rb b/app/controllers/org_admin/conditions_controller.rb index 8c2c59376d..552ade2114 100644 --- a/app/controllers/org_admin/conditions_controller.rb +++ b/app/controllers/org_admin/conditions_controller.rb @@ -1,35 +1,36 @@ # frozen_string_literal: true -class OrgAdmin::ConditionsController < ApplicationController +module OrgAdmin + # Controller that handles conditional questions + class ConditionsController < ApplicationController + # /org_admin/questions/:question_id/conditions/new + def new + question = Question.find(params[:question_id]) + condition_no = new_condition_params[:condition_no] + next_condition_no = condition_no.to_i + 1 + render json: { add_link: render_to_string(partial: 'add', + formats: :html, + layout: false, + locals: { question: question, + condition_no: next_condition_no }), + attachment_partial: render_to_string(partial: 'form', + formats: :html, + layout: false, + locals: { + question: question, + cond: Condition.new(question: question), + condition_no: condition_no + }) } + end - # /org_admin/questions/:question_id/conditions/new - def new - question = Question.find(params[:question_id]) - condition_no = new_condition_params[:condition_no] - next_condition_no = condition_no.to_i + 1 - render json: { add_link: render_to_string(partial: "add", - formats: :html, - layout: false, - locals: { question: question, - condition_no: next_condition_no }), - attachment_partial: render_to_string(partial: "form", - formats: :html, - layout: false, - locals: { - question: question, - cond: Condition.new(question: question), - condition_no: condition_no - }) } - end - - private + private - def new_condition_params - params.permit(:condition_no) - end + def new_condition_params + params.permit(:condition_no) + end - def condition_params - params.require(:question_option_id, :action_type).permit(:remove_question_id, :condition_no) + def condition_params + params.require(:question_option_id, :action_type).permit(:remove_question_id, :condition_no) + end end - end diff --git a/app/controllers/org_admin/departments_controller.rb b/app/controllers/org_admin/departments_controller.rb index 8d52cf9293..8613a484c6 100644 --- a/app/controllers/org_admin/departments_controller.rb +++ b/app/controllers/org_admin/departments_controller.rb @@ -1,80 +1,83 @@ # frozen_string_literal: true -class OrgAdmin::DepartmentsController < ApplicationController +module OrgAdmin + # Controller that handles department operations + class DepartmentsController < ApplicationController + after_action :verify_authorized + respond_to :html - after_action :verify_authorized - respond_to :html - - # GET add new department - def new - @department = Department.new - @org_id = org_id - @department.org_id = @org_id - authorize @department - end + # GET add new department + def new + @department = Department.new + @org_id = org_id + @department.org_id = @org_id + authorize @department + end - # POST /departments - # POST /departments.json - def create - @department = Department.new(department_params) - @org_id = org_id + # POST /departments + # POST /departments.json + def create + @department = Department.new(department_params) + @org_id = org_id - authorize @department + authorize @department - if @department.save - flash.now[:notice] = success_message(@department, _("created")) - # reset value - @department = nil - else - flash.now[:alert] = failure_message(@department, _("create")) + if @department.save + flash.now[:notice] = success_message(@department, _('created')) + # reset value + @department = nil + else + flash.now[:alert] = failure_message(@department, _('create')) + end + render :new end - render :new - end - # GET /departments/1/edit - def edit - @department = Department.find(params[:id]) - @org_id = org_id - authorize @department - end + # GET /departments/1/edit + def edit + @department = Department.find(params[:id]) + @org_id = org_id + authorize @department + end - # PUT /departments/1 - def update - @department = Department.find(params[:id]) - @org_id = org_id - authorize @department + # PUT /departments/1 + # rubocop:disable Metrics/AbcSize + def update + @department = Department.find(params[:id]) + @org_id = org_id + authorize @department - if @department.update(department_params) - flash.now[:notice] = success_message(@department, _("saved")) - else - flash.now[:alert] = failure_message(@department, _("save")) + if @department.update(department_params) + flash.now[:notice] = success_message(@department, _('saved')) + else + flash.now[:alert] = failure_message(@department, _('save')) + end + render :edit end - render :edit - end + # rubocop:enable Metrics/AbcSize - # DELETE /departments/1 - def destroy - @department = Department.find(params[:id]) - @org_id = org_id - authorize @department - url = "#{admin_edit_org_path(@org_id)}\#departments" + # DELETE /departments/1 + def destroy + @department = Department.find(params[:id]) + @org_id = org_id + authorize @department + url = "#{admin_edit_org_path(@org_id)}\#departments" - if @department.destroy - flash[:notice] = success_message(@department, _("deleted")) - else - flash[:alert] = failure_message(@department, _("delete")) + if @department.destroy + flash[:notice] = success_message(@department, _('deleted')) + else + flash[:alert] = failure_message(@department, _('delete')) + end + redirect_to url end - redirect_to url - end - private + private - def department_params - params.require(:department).permit(:id, :name, :code, :org_id) - end + def department_params + params.require(:department).permit(:id, :name, :code, :org_id) + end - def org_id - current_user.can_super_admin? ? params[:org_id] : current_user.org_id + def org_id + current_user.can_super_admin? ? params[:org_id] : current_user.org_id + end end - end diff --git a/app/controllers/org_admin/phase_versions_controller.rb b/app/controllers/org_admin/phase_versions_controller.rb index c132c6c294..096acd4867 100644 --- a/app/controllers/org_admin/phase_versions_controller.rb +++ b/app/controllers/org_admin/phase_versions_controller.rb @@ -1,20 +1,21 @@ # frozen_string_literal: true -class OrgAdmin::PhaseVersionsController < ApplicationController +module OrgAdmin + # Controller that handles creating new versions of Phases + class PhaseVersionsController < ApplicationController + include Versionable - include Versionable - - # POST /org_admin/templates/:template_id/phases/:phase_id/versions - def create - @phase = Phase.find(params[:phase_id]) - authorize @phase, :create? - @new_phase = get_modifiable(@phase) - flash[:notice] = if @new_phase == @phase - "This template is already a draft" - else - "New version of Template created" - end - redirect_to org_admin_template_phase_url(@new_phase.template, @new_phase) + # POST /org_admin/templates/:template_id/phases/:phase_id/versions + def create + @phase = Phase.find(params[:phase_id]) + authorize @phase + @new_phase = get_modifiable(@phase) + flash[:notice] = if @new_phase == @phase + 'This template is already a draft' + else + 'New version of Template created' + end + redirect_to org_admin_template_phase_url(@new_phase.template, @new_phase) + end end - end diff --git a/app/controllers/org_admin/phases_controller.rb b/app/controllers/org_admin/phases_controller.rb index 3ef617119a..f27a3e7380 100644 --- a/app/controllers/org_admin/phases_controller.rb +++ b/app/controllers/org_admin/phases_controller.rb @@ -1,9 +1,8 @@ # frozen_string_literal: true module OrgAdmin - + # Controller that handles phases class PhasesController < ApplicationController - include Versionable after_action :verify_authorized @@ -15,7 +14,7 @@ def show authorize phase unless phase.template.latest? # rubocop:disable Layout/LineLength - flash[:notice] = _("You are viewing a historical version of this template. You will not be able to make changes.") + flash[:notice] = _('You are viewing a historical version of this template. You will not be able to make changes.') # rubocop:enable Layout/LineLength end sections = if phase.template.customization_of? && phase.template.latest? @@ -26,9 +25,9 @@ def show # will be readonly phase.sections.order(:number) end - render("container", + render('container', locals: { - partial_path: "show", + partial_path: 'show', template: phase.template, phase: phase, prefix_section: phase.prefix_section, @@ -40,7 +39,7 @@ def show # rubocop:enable Metrics/AbcSize # GET /org_admin/templates/:template_id/phases/:id/edit - # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/AbcSize, Metrics/MethodLength def edit phase = Phase.includes(:template).find(params[:id]) authorize phase @@ -52,9 +51,9 @@ def edit section: params[:section] ) else - render("container", + render('container', locals: { - partial_path: "edit", + partial_path: 'edit', template: phase.template, phase: phase, prefix_section: phase.prefix_section, @@ -65,7 +64,7 @@ def edit }) end end - # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/AbcSize, Metrics/MethodLength # preview a phase # GET /org_admin/templates/:template_id/phases/:id/preview @@ -78,6 +77,7 @@ def preview # add a new phase to a passed template # GET /org_admin/templates/:template_id/phases/new + # rubocop:disable Metrics/AbcSize, Metrics/MethodLength def new template = Template.includes(:phases).find(params[:template_id]) if template.latest? @@ -93,22 +93,23 @@ def new else org_admin_templates_path end - render("/org_admin/templates/container", + render('/org_admin/templates/container', locals: { - partial_path: "new", + partial_path: 'new', template: template, phase: phase, referrer: local_referrer }) else render org_admin_templates_path, - alert: _("You cannot add a phase to a historical version of a template.") + alert: _('You cannot add a phase to a historical version of a template.') end end + # rubocop:enable Metrics/AbcSize, Metrics/MethodLength # create a phase # POST /org_admin/templates/:template_id/phases - # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/AbcSize, Metrics/MethodLength def create phase = Phase.new(phase_params) phase.template = Template.find(params[:template_id]) @@ -117,12 +118,13 @@ def create phase = get_new(phase) phase.modifiable = true if phase.save - flash[:notice] = success_message(phase, _("created")) + flash[:notice] = success_message(phase, _('created')) else - flash[:alert] = failure_message(phase, _("create")) + flash[:alert] = failure_message(phase, _('create')) end rescue StandardError => e - flash[:alert] = _("Unable to create a new version of this template.") + "
    " + e.message + msg = _('Unable to create a new version of this template.
    ') + flash[:alert] = "#{msg}
    #{e.message}" end if flash[:alert].present? redirect_to new_org_admin_template_phase_path(template_id: phase.template.id) @@ -131,26 +133,29 @@ def create id: phase.id) end end - # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/AbcSize, Metrics/MethodLength # update a phase of a template # PUT /org_admin/templates/:template_id/phases/:id + # rubocop:disable Metrics/AbcSize def update phase = Phase.find(params[:id]) authorize phase begin phase = get_modifiable(phase) if phase.update(phase_params) - flash[:notice] = success_message(phase, _("updated")) + flash[:notice] = success_message(phase, _('updated')) else - flash[:alert] = failure_message(phase, _("update")) + flash[:alert] = failure_message(phase, _('update')) end rescue StandardError => e - flash[:alert] = _("Unable to create a new version of this template.") + "
    " + e.message + msg = _('Unable to create a new version of this template.') + flash[:alert] = "#{msg}
    #{e.message}" end redirect_to edit_org_admin_template_phase_path(template_id: phase.template.id, id: phase.id) end + # rubocop:enable Metrics/AbcSize # POST /org_admin/templates/:template_id/phases/:id/sort def sort @@ -171,12 +176,13 @@ def destroy phase = get_modifiable(phase) template = phase.template if phase.destroy! - flash[:notice] = success_message(phase, _("deleted")) + flash[:notice] = success_message(phase, _('deleted')) else - flash[:alert] = failure_message(phase, _("delete")) + flash[:alert] = failure_message(phase, _('delete')) end rescue StandardError => e - flash[:alert] = _("Unable to create a new version of this template.") + "
    " + e.message + msg = _('Unable to create a new version of this template.') + flash[:alert] = "#{msg}
    #{e.message}" end if flash[:alert].present? @@ -192,7 +198,5 @@ def destroy def phase_params params.require(:phase).permit(:title, :description, :number, sort_order: []) end - end - end diff --git a/app/controllers/org_admin/plans_controller.rb b/app/controllers/org_admin/plans_controller.rb index d04739b5fc..2c7ec2a396 100644 --- a/app/controllers/org_admin/plans_controller.rb +++ b/app/controllers/org_admin/plans_controller.rb @@ -1,83 +1,86 @@ # frozen_string_literal: true -class OrgAdmin::PlansController < ApplicationController +module OrgAdmin + # Controller that handles admin operations for plans + class PlansController < ApplicationController + # GET org_admin/plans + # rubocop:disable Metrics/AbcSize + def index + # Test auth directly and throw Pundit error sincePundit + # is unaware of namespacing + raise Pundit::NotAuthorizedError unless current_user.present? && current_user.can_org_admin? - # GET org_admin/plans - def index - # Test auth directly and throw Pundit error sincePundit - # is unaware of namespacing - raise Pundit::NotAuthorizedError unless current_user.present? && current_user.can_org_admin? + sql = 'users.org_id = ? AND plans.feedback_requested is TRUE AND roles.active is TRUE' + feedback_ids = Role.creator.joins(:user, :plan) + .where(sql, current_user.org_id).pluck(:plan_id) + @feedback_plans = Plan.where(id: feedback_ids).compact - sql = "users.org_id = ? AND plans.feedback_requested is TRUE AND roles.active is TRUE" - feedback_ids = Role.creator.joins(:user, :plan) - .where(sql, current_user.org_id).pluck(:plan_id) - @feedback_plans = Plan.where(id: feedback_ids).reject(&:nil?) - - @super_admin = current_user.can_super_admin? - @clicked_through = params[:click_through].present? - @plans = @super_admin ? Plan.all.page(1) : current_user.org.org_admin_plans.page(1) - end + @super_admin = current_user.can_super_admin? + @clicked_through = params[:click_through].present? + @plans = @super_admin ? Plan.all.page(1) : current_user.org.org_admin_plans.page(1) + end + # rubocop:enable Metrics/AbcSize - # GET org_admin/plans/:id/feedback_complete - def feedback_complete - plan = Plan.find(params[:id]) - # Test auth directly and throw Pundit error sincePundit is - # unaware of namespacing - raise Pundit::NotAuthorizedError unless current_user.present? && current_user.can_org_admin? - raise Pundit::NotAuthorizedError unless plan.reviewable_by?(current_user.id) + # GET org_admin/plans/:id/feedback_complete + # rubocop:disable Metrics/AbcSize + def feedback_complete + plan = Plan.find(params[:id]) + # Test auth directly and throw Pundit error sincePundit is + # unaware of namespacing + raise Pundit::NotAuthorizedError unless current_user.present? && current_user.can_org_admin? + raise Pundit::NotAuthorizedError unless plan.reviewable_by?(current_user.id) - if plan.complete_feedback(current_user) - # rubocop:disable Layout/LineLength - redirect_to(org_admin_plans_path, - notice: _("%{plan_owner} has been notified that you have finished providing feedback") % { - plan_owner: plan.owner.name(false) - }) - # rubocop:enable Layout/LineLength - else - redirect_to org_admin_plans_path, - alert: _("Unable to notify user that you have finished providing feedback.") + if plan.complete_feedback(current_user) + # rubocop:disable Layout/LineLength + redirect_to(org_admin_plans_path, + notice: format(_('%s has been notified that you have finished providing feedback'), plan_owner: plan.owner.name(false))) + # rubocop:enable Layout/LineLength + else + redirect_to org_admin_plans_path, + alert: _('Unable to notify user that you have finished providing feedback.') + end end - end + # rubocop:enable Metrics/AbcSize - # GET /org_admin/download_plans - # rubocop:disable Metrics/AbcSize, Metrics/MethodLength - def download_plans - # Test auth directly and throw Pundit error sincePundit - # is unaware of namespacing - raise Pundit::NotAuthorizedError unless current_user.present? && current_user.can_org_admin? + # GET /org_admin/download_plans + # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity + def download_plans + # Test auth directly and throw Pundit error sincePundit + # is unaware of namespacing + raise Pundit::NotAuthorizedError unless current_user.present? && current_user.can_org_admin? - org = current_user.org - file_name = org.name.gsub(/ /, "_") - .gsub(/[.;,]/, "") - header_cols = [ - _("Project title").to_s, - _("Template").to_s, - _("Organisation").to_s, - _("Owner name").to_s, - _("Owner email").to_s, - _("Updated").to_s, - _("Visibility").to_s - ] + org = current_user.org + file_name = org.name.gsub(/ /, '_') + .gsub(/[.;,]/, '') + header_cols = [ + _('Project title').to_s, + _('Template').to_s, + _('Organisation').to_s, + _('Owner name').to_s, + _('Owner email').to_s, + _('Updated').to_s, + _('Visibility').to_s + ] - plans = CSV.generate do |csv| - csv << header_cols - org.org_admin_plans.includes(template: :org).order(updated_at: :desc).each do |plan| - csv << [ - plan.title.to_s, - plan.template.title.to_s, - (plan.owner.org.present? ? plan.owner.org.name : "").to_s, - plan.owner.name(false).to_s, - plan.owner.email.to_s, - l(plan.latest_update.to_date, format: :csv).to_s, - Plan::VISIBILITY_MESSAGE[plan.visibility.to_sym].capitalize.to_s - ] + plans = CSV.generate do |csv| + csv << header_cols + org.org_admin_plans.includes(template: :org).order(updated_at: :desc).each do |plan| + csv << [ + plan.title.to_s, + plan.template.title.to_s, + (plan.owner&.org&.present? ? plan.owner.org.name : '').to_s, + plan.owner&.name(false)&.to_s, + plan.owner&.email&.to_s, + l(plan.latest_update.to_date, format: :csv).to_s, + Plan::VISIBILITY_MESSAGE[plan.visibility.to_sym].capitalize.to_s + ] + end end - end - respond_to do |format| - format.csv { send_data plans, filename: "#{file_name}.csv" } + respond_to do |format| + format.csv { send_data plans, filename: "#{file_name}.csv" } + end end + # rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity end - # rubocop:enable Metrics/AbcSize, Metrics/MethodLength - end diff --git a/app/controllers/org_admin/question_options_controller.rb b/app/controllers/org_admin/question_options_controller.rb index 1a5e1948ae..0bdfc7e8d2 100644 --- a/app/controllers/org_admin/question_options_controller.rb +++ b/app/controllers/org_admin/question_options_controller.rb @@ -1,14 +1,13 @@ # frozen_string_literal: true module OrgAdmin - + # Controller that handles question options class QuestionOptionsController < ApplicationController - include Versionable after_action :verify_authorized - # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/AbcSize, Metrics/MethodLength def destroy question_option = QuestionOption.find(params[:id]) option_id_to_remove = question_option.id.to_s @@ -22,12 +21,12 @@ def destroy question.conditions.each do |cond| cond.destroy if cond.option_list.include?(option_id_to_remove) end - flash[:notice] = success_message(question_option, _("deleted")) + flash[:notice] = success_message(question_option, _('deleted')) else - flash[:alert] = flash[:alert] = failure_message(question_option, _("delete")) + flash[:alert] = flash[:alert] = failure_message(question_option, _('delete')) end rescue StandardError - flash[:alert] = _("Unable to create a new version of this template.") + flash[:alert] = _('Unable to create a new version of this template.') end redirect_to edit_org_admin_template_phase_path( template_id: section.phase.template.id, @@ -35,8 +34,6 @@ def destroy section: section.id ) end - # rubocop:enable Metrics/AbcSize - + # rubocop:enable Metrics/AbcSize, Metrics/MethodLength end - end diff --git a/app/controllers/org_admin/questions_controller.rb b/app/controllers/org_admin/questions_controller.rb index f3061c764e..1b19d53074 100644 --- a/app/controllers/org_admin/questions_controller.rb +++ b/app/controllers/org_admin/questions_controller.rb @@ -1,9 +1,8 @@ # frozen_string_literal: true module OrgAdmin - + # Controller that handles questions class QuestionsController < ApplicationController - include AllowedQuestionFormats include Versionable include ConditionsHelper @@ -18,7 +17,7 @@ def show section: { phase: :template }) .find(params[:id]) authorize question - render json: { html: render_to_string(partial: "show", locals: { + render json: { html: render_to_string(partial: 'show', locals: { template: question.section.phase.template, section: question.section, question: question, @@ -31,7 +30,7 @@ def show def open_conditions question = Question.find(params[:question_id]) authorize question - render json: { container: render_to_string(partial: "org_admin/conditions/container", + render json: { container: render_to_string(partial: 'org_admin/conditions/container', formats: :html, layout: false, locals: { @@ -41,16 +40,14 @@ def open_conditions webhooks: webhook_hash(question.conditions) } end - # rubocop:disable Layout/LineLength # GET /org_admin/templates/[:template_id]/phases/[:phase_id]/sections/[:id]/questions/[:question_id]/edit - # rubocop:enable Layout/LineLength def edit question = Question.includes(:annotations, :question_options, section: { phase: :template }) .find(params[:id]) authorize question - render json: { html: render_to_string(partial: "edit", locals: { + render json: { html: render_to_string(partial: 'edit', locals: { template: question.section.phase.template, section: question.section, question: question, @@ -60,20 +57,21 @@ def edit end # GET /org_admin/templates/:template_id/phases/:phase_id/sections/:section_id/questions/new + # rubocop:disable Metrics/AbcSize def new section = Section.includes(:questions, phase: :template).find(params[:section_id]) nbr = section.questions.maximum(:number) - question_format = QuestionFormat.find_by(title: "Text area") + question_format = QuestionFormat.find_by(title: 'Text area') question = Question.new(section_id: section.id, question_format: question_format, number: nbr.present? ? nbr + 1 : 1) question_formats = allowed_question_formats authorize question - render json: { html: render_to_string(partial: "form", locals: { + render json: { html: render_to_string(partial: 'form', locals: { template: section.phase.template, section: section, question: question, - method: "post", + method: 'post', url: org_admin_template_phase_section_questions_path( template_id: section.phase.template.id, phase_id: section.phase.id, @@ -82,6 +80,7 @@ def new question_formats: question_formats }) } end + # rubocop:enable Metrics/AbcSize # POST /org_admin/templates/:template_id/phases/:phase_id/sections/:section_id/questions # rubocop:disable Metrics/AbcSize @@ -92,12 +91,12 @@ def create question = get_new(question) section = question.section if question.save - flash[:notice] = success_message(question, _("created")) + flash[:notice] = success_message(question, _('created')) else - flash[:alert] = failure_message(question, _("create")) + flash[:alert] = failure_message(question, _('create')) end rescue StandardError - flash[:alert] = _("Unable to create a new version of this template.") + flash[:alert] = _('Unable to create a new version of this template.') end redirect_to edit_org_admin_template_phase_path( template_id: section.phase.template.id, @@ -109,6 +108,7 @@ def create # PUT /org_admin/templates/:template_id/phases/:phase_id/sections/:section_id/questions/:id # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity def update question = Question.find(params[:id]) authorize question @@ -161,12 +161,12 @@ def update # add check for number present to ensure this is not just an annotation attrs[:theme_ids] = [] if attrs[:theme_ids].blank? && attrs[:number].present? if question.update(attrs) - if question.update_conditions(sanitize_hash(params["conditions"]), + if question.update_conditions(sanitize_hash(params['conditions']), old_to_new_opts, question_id_map) - flash[:notice] = success_message(question, _("updated")) + flash[:notice] = success_message(question, _('updated')) end else - flash[:alert] = flash[:alert] = failure_message(question, _("update")) + flash[:alert] = flash[:alert] = failure_message(question, _('update')) end if question.section.phase.template.customization_of.present? redirect_to org_admin_template_phase_path( @@ -183,7 +183,7 @@ def update end end # rubocop:enable Metrics/AbcSize, Metrics/MethodLength - # rubocop:enable + # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity # DELETE /org_admin/templates/:template_id/phases/:phase_id/sections/:section_id/questions/:id # rubocop:disable Metrics/AbcSize @@ -194,12 +194,12 @@ def destroy question = get_modifiable(question) section = question.section if question.destroy! - flash[:notice] = success_message(question, _("deleted")) + flash[:notice] = success_message(question, _('deleted')) else - flash[:alert] = flash[:alert] = failure_message(question, _("delete")) + flash[:alert] = flash[:alert] = failure_message(question, _('delete')) end rescue StandardError - flash[:alert] = _("Unable to create a new version of this template.") + flash[:alert] = _('Unable to create a new version of this template.') end redirect_to edit_org_admin_template_phase_path( template_id: section.phase.template.id, @@ -231,7 +231,7 @@ def sanitize_hash(param_conditions) hash_of_hashes.each do |cond_name, cond_hash| sanitized_hash = {} cond_hash.each do |k, v| - v = ActionController::Base.helpers.sanitize(v) if k.start_with?("webhook") + v = ActionController::Base.helpers.sanitize(v) if k.start_with?('webhook') sanitized_hash[k] = v end res[cond_name] = sanitized_hash @@ -256,11 +256,11 @@ def question_params # options are now out of sync with the params. # This sorts that out. def update_option_ids(attrs_in, opt_map) - qopts = attrs_in["question_options_attributes"] + qopts = attrs_in['question_options_attributes'] qopts.each_pair do |_, attr_hash| - old_id = attr_hash["id"] + old_id = attr_hash['id'] new_id = opt_map[old_id] - attr_hash["id"] = new_id + attr_hash['id'] = new_id end attrs_in end @@ -268,6 +268,7 @@ def update_option_ids(attrs_in, opt_map) # When a template gets versioned by changes to one of its questions we need to loop # through the incoming params and ensure that the annotations and question_options # get attached to the new question + # rubocop:disable Metrics/AbcSize def transfer_associations(attrs, question) if attrs[:annotations_attributes].present? attrs[:annotations_attributes].each_pair do |_, value| @@ -280,7 +281,6 @@ def transfer_associations(attrs, question) end attrs end - + # rubocop:enable Metrics/AbcSize end - end diff --git a/app/controllers/org_admin/sections_controller.rb b/app/controllers/org_admin/sections_controller.rb index 0d5b9b2b2f..2b8c74a698 100644 --- a/app/controllers/org_admin/sections_controller.rb +++ b/app/controllers/org_admin/sections_controller.rb @@ -1,22 +1,22 @@ # frozen_string_literal: true module OrgAdmin - + # Controller that handles sections class SectionsController < ApplicationController - include Versionable respond_to :html after_action :verify_authorized # GET /org_admin/templates/[:template_id]/phases/[:phase_id]/sections + # rubocop:disable Metrics/AbcSize def index authorize Section.new phase = Phase.includes(:template, :sections).find(params[:phase_id]) edit = phase.template.latest? && (current_user.can_modify_templates? && (phase.template.org_id == current_user.org_id)) - render partial: "index", + render partial: 'index', locals: { template: phase.template, phase: phase, @@ -28,6 +28,7 @@ def index edit: edit } end + # rubocop:enable Metrics/AbcSize # GET /org_admin/templates/[:template_id]/phases/[:phase_id]/sections/[:id] def show @@ -36,7 +37,7 @@ def show @section = Section.includes(questions: %i[annotations question_options]) .find(params[:id]) @template = Template.find(params[:template_id]) - render json: { html: render_to_string(partial: "show", + render json: { html: render_to_string(partial: 'show', locals: { template: @template, section: @section }) } end @@ -49,9 +50,9 @@ def edit # User cannot edit a section if its not modifiable or the template is not the # latest redirect to show partial_name = if section.modifiable? && section.phase.template.latest? - "edit" + 'edit' else - "show" + 'show' end render json: { html: render_to_string(partial: partial_name, locals: { @@ -62,12 +63,12 @@ def edit end # POST /org_admin/templates/[:template_id]/phases/[:phase_id]/sections - # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/AbcSize, Metrics/MethodLength def create @phase = Phase.find_by(id: params[:phase_id]) if @phase.nil? flash[:alert] = - _("Unable to create a new section. The phase you specified does not exist.") + _('Unable to create a new section. The phase you specified does not exist.') redirect_to edit_org_admin_template_path(template_id: params[:template_id]) return end @@ -75,21 +76,21 @@ def create authorize @section @section = get_new(@section) if @section.save - flash[:notice] = success_message(@section, _("created")) + flash[:notice] = success_message(@section, _('created')) redirect_to edit_org_admin_template_phase_path( id: @section.phase_id, template_id: @phase.template_id, section: @section.id ) else - flash[:alert] = failure_message(@section, _("create")) + flash[:alert] = failure_message(@section, _('create')) redirect_to edit_org_admin_template_phase_path( template_id: @phase.template_id, id: @section.phase_id ) end end - # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/AbcSize, Metrics/MethodLength # PUT /org_admin/templates/[:template_id]/phases/[:phase_id]/sections/[:id] # rubocop:disable Metrics/AbcSize @@ -99,12 +100,13 @@ def update begin section = get_modifiable(section) if section.update(section_params) - flash[:notice] = success_message(section, _("saved")) + flash[:notice] = success_message(section, _('saved')) else - flash[:alert] = failure_message(section, _("save")) + flash[:alert] = failure_message(section, _('save')) end rescue StandardError => e - flash[:alert] = _("Unable to create a new version of this template.") + "
    " + e.message + msg = _('Unable to create a new version of this template.') + flash[:alert] = "#{msg}
    #{e.message}" end redirect_to edit_org_admin_template_phase_path( @@ -123,12 +125,13 @@ def destroy section = get_modifiable(section) phase = section.phase if section.destroy! - flash[:notice] = success_message(section, _("deleted")) + flash[:notice] = success_message(section, _('deleted')) else - flash[:alert] = failure_message(section, _("delete")) + flash[:alert] = failure_message(section, _('delete')) end rescue StandardError => e - flash[:alert] = _("Unable to create a new version of this template.") + "
    " + e.message + msg = _('Unable to delete this version of the template.') + flash[:alert] = "#{msg}
    #{e.message}" end redirect_to(edit_org_admin_template_phase_path( @@ -143,7 +146,5 @@ def destroy def section_params params.require(:section).permit(:title, :description) end - end - end diff --git a/app/controllers/org_admin/template_copies_controller.rb b/app/controllers/org_admin/template_copies_controller.rb index db65f414bb..6e3c99af85 100644 --- a/app/controllers/org_admin/template_copies_controller.rb +++ b/app/controllers/org_admin/template_copies_controller.rb @@ -1,27 +1,30 @@ # frozen_string_literal: true -class OrgAdmin::TemplateCopiesController < ApplicationController +module OrgAdmin + # Controller that handles copying templates + class TemplateCopiesController < ApplicationController + include TemplateMethods - include TemplateMethods + after_action :verify_authorized - after_action :verify_authorized - - # POST /org_admin/templates/:id/copy (AJAX) - def create - @template = Template.find(params[:template_id]) - authorize @template, :copy? - begin - new_copy = @template.generate_copy!(current_user.org) - flash[:notice] = "#{template_type(@template).capitalize} was successfully copied." - redirect_to edit_org_admin_template_path(new_copy) - rescue StandardError - flash[:alert] = failure_message(_("copy"), template_type(@template)) - if request.referrer.present? - redirect_back(fallback_location: org_admin_templates_path) - else - redirect_to org_admin_templates_path + # POST /org_admin/templates/:id/copy (AJAX) + # rubocop:disable Metrics/AbcSize + def create + @template = Template.find(params[:template_id]) + authorize @template, :copy? + begin + new_copy = @template.generate_copy!(current_user.org) + flash[:notice] = "#{template_type(@template).capitalize} was successfully copied." + redirect_to edit_org_admin_template_path(new_copy) + rescue StandardError + flash[:alert] = failure_message(_('copy'), template_type(@template)) + if request.referrer.present? + redirect_back(fallback_location: org_admin_templates_path) + else + redirect_to org_admin_templates_path + end end end + # rubocop:enable Metrics/AbcSize end - end diff --git a/app/controllers/org_admin/template_customization_transfers_controller.rb b/app/controllers/org_admin/template_customization_transfers_controller.rb index 5268b2bcf0..f0b98e28b7 100644 --- a/app/controllers/org_admin/template_customization_transfers_controller.rb +++ b/app/controllers/org_admin/template_customization_transfers_controller.rb @@ -1,32 +1,33 @@ # frozen_string_literal: true -class OrgAdmin::TemplateCustomizationTransfersController < ApplicationController - - include Versionable - - after_action :verify_authorized - - # POST /org_admin/templates/:id/transfer_customization - # - # The funder template's id is passed through here - def create - @template = Template.find(params[:template_id]) - authorize @template, :transfer_customization? - if @template.upgrade_customization? - # If the customized template is not published it will not version, so publish it! - previously_published = @template.published? - @template.publish unless previously_published - - @new_customization = @template.upgrade_customization! - - # Reset the published flag if the customized template was not previously published - @template.update(published: false) unless previously_published - - redirect_to org_admin_template_path(@new_customization) - else - flash[:alert] = _("That template is no longer customizable.") - redirect_back(fallback_location: org_admin_templates_path) +module OrgAdmin + # Controller that handles transfering parent template changes to a customized template + class TemplateCustomizationTransfersController < ApplicationController + include Versionable + + after_action :verify_authorized + + # POST /org_admin/templates/:id/transfer_customization + # + # The funder template's id is passed through here + def create + @template = Template.find(params[:template_id]) + authorize @template, :transfer_customization? + if @template.upgrade_customization? + # If the customized template is not published it will not version, so publish it! + previously_published = @template.published? + @template.publish unless previously_published + + @new_customization = @template.upgrade_customization! + + # Reset the published flag if the customized template was not previously published + @template.update(published: false) unless previously_published + + redirect_to org_admin_template_path(@new_customization) + else + flash[:alert] = _('That template is no longer customizable.') + redirect_back(fallback_location: org_admin_templates_path) + end end end - end diff --git a/app/controllers/org_admin/template_customizations_controller.rb b/app/controllers/org_admin/template_customizations_controller.rb index 91094a5ead..15c37f533b 100644 --- a/app/controllers/org_admin/template_customizations_controller.rb +++ b/app/controllers/org_admin/template_customizations_controller.rb @@ -1,27 +1,30 @@ # frozen_string_literal: true -class OrgAdmin::TemplateCustomizationsController < ApplicationController +module OrgAdmin + # Controller that handles customizing a template + class TemplateCustomizationsController < ApplicationController + include Paginable + include Versionable + after_action :verify_authorized - include Paginable - include Versionable - after_action :verify_authorized - - # POST /org_admin/templates/:id/customize - def create - @template = Template.find(params[:template_id]) - authorize(@template, :customize?) - if @template.customize?(current_user.org) - begin - @customisation = @template.customize!(current_user.org) - redirect_to org_admin_template_path(@customisation) - return - rescue ArgumentError - flash[:alert] = _("Unable to customize that template.") + # POST /org_admin/templates/:id/customize + # rubocop:disable Metrics/AbcSize + def create + @template = Template.find(params[:template_id]) + authorize(@template, :customize?) + if @template.customize?(current_user.org) + begin + @customisation = @template.customize!(current_user.org) + redirect_to org_admin_template_path(@customisation) + return + rescue ArgumentError + flash[:alert] = _('Unable to customize that template.') + end + else + flash[:notice] = _('That template is not customizable.') end - else - flash[:notice] = _("That template is not customizable.") + redirect_back(fallback_location: org_admin_templates_path) end - redirect_back(fallback_location: org_admin_templates_path) + # rubocop:enable Metrics/AbcSize end - end diff --git a/app/controllers/org_admin/templates_controller.rb b/app/controllers/org_admin/templates_controller.rb index 3f26367365..1cc4c48d58 100644 --- a/app/controllers/org_admin/templates_controller.rb +++ b/app/controllers/org_admin/templates_controller.rb @@ -1,10 +1,8 @@ # frozen_string_literal: true module OrgAdmin - - # rubocop:disable Metrics/ClassLength + # Controller that handles templates class TemplatesController < ApplicationController - include Paginable include Versionable include TemplateMethods @@ -13,16 +11,16 @@ class TemplatesController < ApplicationController # The root version of index which returns all templates # GET /org_admin/templates - # ----------------------------------------------------- + # rubocop:disable Metrics/AbcSize def index authorize Template templates = Template.latest_version.where(customization_of: nil) published = templates.select { |t| t.published? || t.draft? }.length @orgs = Org.managed - @title = _("All Templates") + @title = _('All Templates') @templates = templates.includes(:org).page(1) - @query_params = { sort_field: "templates.title", sort_direction: "asc" } + @query_params = { sort_field: 'templates.title', sort_direction: 'asc' } @all_count = templates.length @published_count = published.present? ? published : 0 @unpublished_count = if published.present? @@ -32,11 +30,12 @@ def index end render :index end + # rubocop:enable Metrics/AbcSize # A version of index that displays only templates that belong to the user's org # GET /org_admin/templates/organisational # ----------------------------------------------------- - # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity def organisational authorize Template templates = Template.latest_version_per_org(current_user.org.id) @@ -45,12 +44,12 @@ def organisational @orgs = current_user.can_super_admin? ? Org.all : nil @title = if current_user.can_super_admin? - _("%{org_name} Templates") % { org_name: current_user.org.name } + format(_('%s Templates'), org_name: current_user.org.name) else - _("Own Templates") + _('Own Templates') end @templates = templates.page(1) - @query_params = { sort_field: "templates.title", sort_direction: "asc" } + @query_params = { sort_field: 'templates.title', sort_direction: 'asc' } @all_count = templates.length @published_count = published.present? ? published : 0 @unpublished_count = if published.present? @@ -60,12 +59,13 @@ def organisational end render :index end - # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/AbcSize, Metrics/PerceivedComplexity # A version of index that displays only templates that are customizable # GET /org_admin/templates/customisable # ----------------------------------------------------- - # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity def customisable authorize Template customizations = Template.latest_customized_version_per_org(current_user.org.id) @@ -81,10 +81,10 @@ def customisable published = customizations.select { |t| t.published? || t.draft? }.length @orgs = current_user.can_super_admin? ? Org.all : [] - @title = _("Customizable Templates") + @title = _('Customizable Templates') @templates = funder_templates @customizations = customizations - @query_params = { sort_field: "templates.title", sort_direction: "asc" } + @query_params = { sort_field: 'templates.title', sort_direction: 'asc' } @all_count = funder_templates.length @published_count = published.present? ? published : 0 @unpublished_count = if published.present? @@ -96,7 +96,8 @@ def customisable render :index end - # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/AbcSize, Metrics/MethodLength + # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity # GET /org_admin/templates/[:id] def show @@ -105,17 +106,17 @@ def show # Load the info needed for the overview section if the authorization check passes! phases = template.phases .includes(sections: { questions: :question_options }) - .order("phases.number", "sections.number", "questions.number", - "question_options.number") - .select("phases.title", "phases.description", "sections.title", - "questions.text", "question_options.text") + .order('phases.number', 'sections.number', 'questions.number', + 'question_options.number') + .select('phases.title', 'phases.description', 'sections.title', + 'questions.text', 'question_options.text') unless template.latest? # rubocop:disable Layout/LineLength - flash[:notice] = _("You are viewing a historical version of this template. You will not be able to make changes.") + flash[:notice] = _('You are viewing a historical version of this template. You will not be able to make changes.') # rubocop:enable Layout/LineLength end - render "container", locals: { - partial_path: "show", + render 'container', locals: { + partial_path: 'show', template: template, phases: phases, referrer: get_referrer(template, request.referrer) @@ -123,31 +124,33 @@ def show end # GET /org_admin/templates/:id/edit + # rubocop:disable Metrics/AbcSize, Metrics/MethodLength def edit template = Template.includes(:org, :phases).find(params[:id]) authorize template # Load the info needed for the overview section if the authorization check passes! phases = template.phases.includes(sections: { questions: :question_options }) - .order("phases.number", - "sections.number", - "questions.number", - "question_options.number") - .select("phases.title", - "phases.description", - "sections.title", - "questions.text", - "question_options.text") - if !template.latest? - redirect_to org_admin_template_path(id: template.id) - else - render "container", locals: { - partial_path: "edit", + .order('phases.number', + 'sections.number', + 'questions.number', + 'question_options.number') + .select('phases.title', + 'phases.description', + 'sections.title', + 'questions.text', + 'question_options.text') + if template.latest? + render 'container', locals: { + partial_path: 'edit', template: template, phases: phases, referrer: get_referrer(template, request.referrer) } + else + redirect_to org_admin_template_path(id: template.id) end end + # rubocop:enable Metrics/AbcSize, Metrics/MethodLength # GET /org_admin/templates/new def new @@ -167,16 +170,16 @@ def create @template = Template.new(args) @template.org_id = current_user.org.id @template.locale = current_org.language.abbreviation - @template.links = if params["template-links"].present? - ActiveSupport::JSON.decode(params["template-links"]) + @template.links = if params['template-links'].present? + ActiveSupport::JSON.decode(params['template-links']) else - { "funder": [], "sample_plan": [] } + { funder: [], sample_plan: [] } end if @template.save redirect_to edit_org_admin_template_path(@template), - notice: success_message(@template, _("created")) + notice: success_message(@template, _('created')) else - flash[:alert] = flash[:alert] = failure_message(@template, _("create")) + flash[:alert] = flash[:alert] = failure_message(@template, _('create')) render :new end end @@ -194,25 +197,23 @@ def update args[:visibility] = parse_visibility(args, current_user.org) template.assign_attributes(args) - if params["template-links"].present? - template.links = ActiveSupport::JSON.decode(params["template-links"]) - end + template.links = ActiveSupport::JSON.decode(params['template-links']) if params['template-links'].present? if template.save render(json: { status: 200, - msg: success_message(template, _("saved")) + msg: success_message(template, _('saved')) }) else render(json: { status: :bad_request, - msg: failure_message(template, _("save")) + msg: failure_message(template, _('save')) }) end rescue ActiveSupport::JSON.parse_error render(json: { status: :bad_request, - msg: _("Error parsing links for a %{template}") % - { template: template_type(template) } + msg: format(_('Error parsing links for a %