diff --git a/.travis.yml b/.travis.yml index 4e4f093..3dc23ec 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,28 +1,64 @@ +sudo: false + +# Ruby is the main language of the project. language: ruby +bundler_args: --with development,ci + +# Cache third party dependencies for faster builds +cache: + apt: true + bundler: true + directories: + # Cache NPM packages + - lib/assets/node_modules + - $HOME/.npm + addons: + chrome: beta apt: sources: - google-chrome packages: + - nodejs - google-chrome-stable -rvm: - - 2.4.4 -before_install: - - curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash - && sudo apt-get install -y nodejs -before_script: - - cd lib/assets && npm install && npm run bundle --no-watch && cd - - - cp config/database_example.yml config/database.yml - - cp config/secrets_example.yml config/secrets.yml - - cp config/branding_example.yml config/branding.yml - - cp config/initializers/devise.rb.example config/initializers/devise.rb - - cp config/initializers/recaptcha.rb.example config/initializers/recaptcha.rb - - cp config/initializers/wicked_pdf.rb.example config/initializers/wicked_pdf.rb - - bundle exec rake db:drop RAILS_ENV=test - - bundle exec rake db:create RAILS_ENV=test - - bundle exec rake db:schema:load RAILS_ENV=test - - bundle exec rake db:migrate RAILS_ENV=test +matrix: + fast_finish: true +rvm: + # Use 2.4.1, since this is installed by default on Travis (1st Aug, 2018) + - 2.4.1 + +# These env variables will set up a separate testing environment for each +# combination of variables. +env: + # Run specs once with each database adapter we support + - DB_ADAPTER=postgresql + - DB_ADAPTER=mysql2 + +# Main test script script: - - bundle exec rspec spec + # Precompile the assets + - cd lib/assets && npm install && npm run bundle --no-watch && cd - + # Copy over config files needed for setup, and create DB + - bin/setup + # Default test stage: Run all specs, listing the 10 slowest. + - bundle exec rspec spec --profile=10 + +# Run these stages in this order: +stages: + - security + - test + +# Define each stage (test is already defined automatically) +jobs: + include: + # Run Brakeman check with warning level 2, except these two checks: + - stage: security + name: "Brakeman check" + script: bundle exec brakeman -w2 --except=Redirect,CrossSiteScripting + + - stage: security + name: "Bundle audit" + script: bundle exec bundle-audit check --update diff --git a/Gemfile b/Gemfile index 7837f0c..8e8d25f 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,6 @@ source 'https://rubygems.org' -ruby '>= 2.4.4' +ruby '>= 2.4.0' # ------------------------------------------------ # RAILS @@ -19,10 +19,13 @@ # ------------------------------------------------ # DATABASE/SERVER -# A simple, fast Mysql library for Ruby, binding to libmysql (http://github.com/brianmario/mysql2) + +# A simple, fast Mysql library for Ruby, binding to libmysql +# (http://github.com/brianmario/mysql2) gem 'mysql2', '~> 0.4.10' -# Pg is the Ruby interface to the {PostgreSQL RDBMS}[http://www.postgresql.org/] (https://bitbucket.org/ged/ruby-pg) +# Pg is the Ruby interface to the {PostgreSQL +# RDBMS}[http://www.postgresql.org/](https://bitbucket.org/ged/ruby-pg) gem 'pg', '~> 0.19.0' # Bit fields for ActiveRecord (https://github.com/pboling/flag_shih_tzu) @@ -137,9 +140,6 @@ # Library for stubbing HTTP requests in Ruby. (http://github.com/bblimke/webmock) gem 'webmock' - # This module allows Ruby programs to interface with the SQLite3 database engine (http://www.sqlite.org) (https://github.com/sparklemotion/sqlite3-ruby) - gem 'sqlite3' - # 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 @@ -174,6 +174,16 @@ gem 'chromedriver-helper' end +group :ci do + gem "brakeman" + + gem "rubocop" + + gem "rubocop-rspec" + + gem "bundle-audit" +end + group :development do # Better error page for Rails and other Rack apps (https://github.com/charliesome/better_errors) gem "better_errors" @@ -192,4 +202,5 @@ # Add comments to your Gemfile with each dependency's description. (https://github.com/ivantsepp/annotate_gem) gem "annotate_gem" + end diff --git a/Gemfile.lock b/Gemfile.lock index ae6aa00..67f5298 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -45,6 +45,7 @@ archive-zip (0.11.0) io-like (~> 0.3.0) arel (6.0.4) + ast (2.4.0) bcrypt (3.1.11) better_errors (2.4.0) coderay (>= 1.0.0) @@ -52,7 +53,13 @@ rack (>= 0.9.0) binding_of_caller (0.8.0) debug_inspector (>= 0.0.1) + brakeman (4.3.1) builder (3.2.3) + bundle-audit (0.1.0) + bundler-audit + bundler-audit (0.6.0) + bundler (~> 1.2) + thor (~> 0.18) byebug (10.0.2) capybara (3.4.2) addressable @@ -155,6 +162,7 @@ i18n (0.9.5) concurrent-ruby (~> 1.0) io-like (0.3.0) + jaro_winkler (1.5.1) jbuilder (2.6.0) activesupport (>= 3.0.0, < 5.1) multi_json (~> 1.2) @@ -222,6 +230,9 @@ omniauth-shibboleth (1.3.0) omniauth (>= 1.0.0) orm_adapter (0.5.0) + parallel (1.12.1) + parser (2.5.1.2) + ast (~> 2.4.0) pg (0.19.0) po_to_json (1.0.1) json (>= 1.6.0) @@ -229,6 +240,7 @@ coderay (~> 1.1.0) method_source (~> 0.9.0) public_suffix (3.0.2) + powerpack (0.1.2) pundit (1.1.0) activesupport (>= 3.0.0) rack (1.6.10) @@ -260,6 +272,7 @@ activesupport (= 4.2.10) rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) + rainbow (3.0.0) rake (12.3.1) rb-fsevent (0.10.3) rb-inotify (0.9.10) @@ -291,6 +304,16 @@ rspec-mocks (~> 3.7.0) rspec-support (~> 3.7.0) rspec-support (3.7.1) + rubocop (0.58.2) + jaro_winkler (~> 1.5.1) + parallel (~> 1.10) + parser (>= 2.5, != 2.5.1.1) + powerpack (~> 0.1) + rainbow (>= 2.2.2, < 4.0) + ruby-progressbar (~> 1.7) + unicode-display_width (~> 1.0, >= 1.0.1) + rubocop-rspec (1.27.0) + rubocop (>= 0.56.0) ruby-progressbar (1.9.0) ruby_dep (1.5.0) ruby_dig (0.0.2) @@ -316,14 +339,13 @@ activesupport (>= 4.2) spring-commands-rspec (1.0.4) spring (>= 0.9.1) - sprockets (3.7.1) + sprockets (3.7.2) concurrent-ruby (~> 1.0) rack (> 1, < 3) sprockets-rails (3.2.1) actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) - sqlite3 (1.3.13) text (1.3.1) thin (1.7.2) daemons (~> 1.0, >= 1.0.9) @@ -333,6 +355,7 @@ thread_safe (0.3.6) tzinfo (1.2.5) thread_safe (~> 0.1) + unicode-display_width (1.4.0) warden (1.2.7) rack (>= 1.0) web-console (3.3.0) @@ -356,6 +379,8 @@ annotate_gem better_errors binding_of_caller + brakeman + bundle-audit byebug capybara capybara-screenshot @@ -393,6 +418,8 @@ responders (~> 2.0) rspec-collection_matchers rspec-rails + rubocop + rubocop-rspec ruby_dig selenium-webdriver shoulda diff --git a/app/models/org.rb b/app/models/org.rb index 36e2bd0..cc259f9 100644 --- a/app/models/org.rb +++ b/app/models/org.rb @@ -11,7 +11,7 @@ # feedback_email_subject :string # feedback_enabled :boolean default(FALSE) # is_other :boolean -# links :text default({"org"=>[]}) +# links :text # logo_file_name :string # logo_name :string # logo_uid :string diff --git a/app/models/settings/template.rb b/app/models/settings/template.rb index 54a272f..d4cab3d 100644 --- a/app/models/settings/template.rb +++ b/app/models/settings/template.rb @@ -10,10 +10,6 @@ # updated_at :datetime not null # target_id :integer not null # -# Indexes -# -# index_settings_on_target_type_and_target_id_and_var (target_type,target_id,var) UNIQUE -# module Settings class Template < RailsSettings::SettingObject diff --git a/app/models/template.rb b/app/models/template.rb index a7d7dcd..b0ce36a 100644 --- a/app/models/template.rb +++ b/app/models/template.rb @@ -7,7 +7,7 @@ # customization_of :integer # description :text # is_default :boolean -# links :text default({"funder"=>[], "sample_plan"=>[]}) +# links :text # locale :string # published :boolean # title :string diff --git a/app/models/user.rb b/app/models/user.rb index 3c5957f..babd2fd 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -11,7 +11,7 @@ # confirmed_at :datetime # current_sign_in_at :datetime # current_sign_in_ip :string -# email :string default(""), not null +# email :string(80) default(""), not null # encrypted_password :string default("") # firstname :string # invitation_accepted_at :datetime diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 9f43109..5eff0ea 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -1,7 +1,7 @@ - <%= content_for?(:title) ? yield(:title) : _('%{application_name}') % { :application_name => Rails.configuration.branding[:application][:name] } %> + <title><%= content_for?(:title) ? yield(:title) : _('%{application_name}') % { :application_name => Rails.configuration.branding[:application][:name] } %> <%= favicon_link_tag "favicon.ico" %>
@@ -57,7 +57,7 @@
<%= (has_alert or has_notice) ? 'show' : 'hide' %>" role="<%= (has_notice ? 'status' : (has_alert ? 'alert' : '')) %>"> diff --git a/app/views/org_admin/phases/container.html.erb b/app/views/org_admin/phases/container.html.erb index fed9838..cd14438 100644 --- a/app/views/org_admin/phases/container.html.erb +++ b/app/views/org_admin/phases/container.html.erb @@ -31,18 +31,38 @@

<%= _('Sections') %>

- <% if phase.sections.length > 1 %> -
- + +
+ +
+ <% if phase.sections.many? %> + + <% end %>
- <% end %> + +
+
+ + Drag arrows to rearrange sections +
+
+
+
- <%= render partial: 'org_admin/sections/index', locals: local_assigns.merge({ modifiable: modifiable }) %> + <%= render partial: 'org_admin/sections/index', + locals: local_assigns.merge(modifiable: modifiable) %>
diff --git a/app/views/org_admin/sections/_section.html.erb b/app/views/org_admin/sections/_section.html.erb index 1cf76ca..4b50ed7 100644 --- a/app/views/org_admin/sections/_section.html.erb +++ b/app/views/org_admin/sections/_section.html.erb @@ -17,9 +17,9 @@
<% if local_assigns[:draggable] %> - - + <% end %>
diff --git a/bin/setup b/bin/setup index acdb2c1..88bd345 100755 --- a/bin/setup +++ b/bin/setup @@ -1,5 +1,8 @@ #!/usr/bin/env ruby +# frozen_string_literal: true + require 'pathname' +require 'fileutils' # path to your application root. APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) @@ -7,23 +10,32 @@ Dir.chdir APP_ROOT do # This script is a starting point to setup your application. # Add necessary setup steps to this file: - puts "== Installing dependencies ==" system "gem install bundler --conservative" system "bundle check || bundle install" - # puts "\n== Copying sample files ==" - # unless File.exist?("config/database.yml") - # system "cp config/database.yml.sample config/database.yml" - # end + puts "== Copying config files ==" + system("cp -n config/database_example.yml config/database.yml") + system("cp -n config/secrets_example.yml config/secrets.yml") + system("cp -n config/branding_example.yml config/branding.yml") + system("cp -n config/initializers/devise.rb.example "\ + "config/initializers/devise.rb") + system("cp -n config/initializers/recaptcha.rb.example "\ + "config/initializers/recaptcha.rb") + system("cp -n config/initializers/wicked_pdf.rb.example "\ + "config/initializers/wicked_pdf.rb") + puts "\n== Preparing database ==" - system "bin/rake db:setup" + system "bundle exec rake db:create" + system "bundle exec rake db:schema:load" puts "\n== Removing old logs and tempfiles ==" - system "rm -f log/*" - system "rm -rf tmp/cache" + FileUtils.rm_f("./log/*") + FileUtils.rm_rf("./tmp/cache") puts "\n== Restarting application server ==" system "touch tmp/restart.txt" end + + diff --git a/config/database_example.yml b/config/database_example.yml index 1f6931b..a538d9b 100644 --- a/config/database_example.yml +++ b/config/database_example.yml @@ -1,22 +1,12 @@ -development: - adapter: mysql2 - database: roadmap - username: root - password: - encoding: utf8mb4 - -# Warning: The database defined as "test" will be erased and -# re-generated from your development database when you run "rake". -# Do not set this db to the same as development or production. -test: - adapter: sqlite3 - database: db/test.sqlite3 +defaults: &defaults + adapter: <%= ENV['DB_ADAPTER'] || 'postgresql' %> + encoding: <%= ENV['DB_ADAPTER'] == "mysql2" ? "utf8mb4" : "" %> + username: <%= ENV["DB_ADAPTER"] == "postgresql" ? 'postgres' : '' %> + database: roadmap_<%= ENV['RAILS_ENV'] %> pool: 5 - timeout: 5000 -production: - adapter: mysql2 - database: roadmap - username: root - password: - encoding: utf8mb4 +development: + <<: *defaults + +test: + <<: *defaults diff --git a/db/migrate/20180713145319_fix_invalid_mysql_indices.rb b/db/migrate/20180713145319_fix_invalid_mysql_indices.rb new file mode 100644 index 0000000..d2a3250 --- /dev/null +++ b/db/migrate/20180713145319_fix_invalid_mysql_indices.rb @@ -0,0 +1,20 @@ +class FixInvalidMysqlIndices < ActiveRecord::Migration + def up + if index_exists?("settings", ["target_type", "target_id", "var"]) + remove_index "settings", ["target_type", "target_id", "var"] + + add_index "settings", ["target_type", "target_id"], + name: "index_settings_on_target_type_and_target_id", + unique: true + end + end + + def down + if index_exists?("settings", ["target_type", "target_id"]) + remove_index "settings", ["target_type", "target_id"] + add_index "settings", ["target_type", "target_id", "var"], + name: "index_settings_on_target_type_and_target_id_and_var", + unique: true + end + end +end diff --git a/db/migrate/20180713145547_remove_defaults_from_links.rb b/db/migrate/20180713145547_remove_defaults_from_links.rb new file mode 100644 index 0000000..e9fea71 --- /dev/null +++ b/db/migrate/20180713145547_remove_defaults_from_links.rb @@ -0,0 +1,12 @@ +class RemoveDefaultsFromLinks < ActiveRecord::Migration + def up + change_column :templates, :links, :text, default: nil + change_column :orgs, :links, :text, default: nil + end + def down + change_column :templates, :links, :text, + default: "{\"funder\":[], \"sample_plan\":[]}" + change_column :orgs, :links, :text, + default: "{\"funder\":[], \"sample_plan\":[]}" + end +end diff --git a/db/migrate/20180713161007_remove_settings_indices.rb b/db/migrate/20180713161007_remove_settings_indices.rb new file mode 100644 index 0000000..62dfbba --- /dev/null +++ b/db/migrate/20180713161007_remove_settings_indices.rb @@ -0,0 +1,11 @@ +class RemoveSettingsIndices < ActiveRecord::Migration + def up + remove_index "settings", ["target_type", "target_id"] + end + def down + add_index "settings", ["target_type", "target_id"], + name: "index_settings_on_target_type_and_target_id", + unique: true, + using: :btree + end +end diff --git a/db/migrate/20180713164120_add_length_constraints_to_users_email.rb b/db/migrate/20180713164120_add_length_constraints_to_users_email.rb new file mode 100644 index 0000000..00a9afa --- /dev/null +++ b/db/migrate/20180713164120_add_length_constraints_to_users_email.rb @@ -0,0 +1,8 @@ +class AddLengthConstraintsToUsersEmail < ActiveRecord::Migration + def up + change_column :users, :email, :string, default: "", null: false, limit: 80 + end + def down + change_column :users, :email, :string, default: "", null: false, limit: nil + end +end diff --git a/db/schema.rb b/db/schema.rb index 8864996..2d5eebe 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,10 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20180508151824) do +ActiveRecord::Schema.define(version: 20180713164120) do + + # These are extensions that must be enabled in order to support this database + enable_extension "plpgsql" create_table "annotations", force: :cascade do |t| t.integer "question_id" @@ -22,7 +25,7 @@ t.datetime "updated_at" end - add_index "annotations", ["question_id"], name: "index_annotations_on_question_id" + add_index "annotations", ["question_id"], name: "index_annotations_on_question_id", using: :btree create_table "answers", force: :cascade do |t| t.text "text" @@ -34,15 +37,15 @@ t.integer "lock_version", default: 0 end - add_index "answers", ["plan_id"], name: "index_answers_on_plan_id" - add_index "answers", ["question_id"], name: "index_answers_on_question_id" + add_index "answers", ["plan_id"], name: "index_answers_on_plan_id", using: :btree + add_index "answers", ["question_id"], name: "index_answers_on_question_id", using: :btree create_table "answers_question_options", id: false, force: :cascade do |t| t.integer "answer_id", null: false t.integer "question_option_id", null: false end - add_index "answers_question_options", ["answer_id"], name: "index_answers_question_options_on_answer_id" + add_index "answers_question_options", ["answer_id"], name: "index_answers_question_options_on_answer_id", using: :btree create_table "exported_plans", force: :cascade do |t| t.integer "plan_id" @@ -83,7 +86,7 @@ t.boolean "published" end - add_index "guidance_groups", ["org_id"], name: "index_guidance_groups_on_org_id" + add_index "guidance_groups", ["org_id"], name: "index_guidance_groups_on_org_id", using: :btree create_table "guidances", force: :cascade do |t| t.text "text" @@ -94,7 +97,7 @@ t.boolean "published" end - add_index "guidances", ["guidance_group_id"], name: "index_guidances_on_guidance_group_id" + add_index "guidances", ["guidance_group_id"], name: "index_guidances_on_guidance_group_id", using: :btree create_table "identifier_schemes", force: :cascade do |t| t.string "name" @@ -123,7 +126,7 @@ t.datetime "updated_at" end - add_index "notes", ["answer_id"], name: "index_notes_on_answer_id" + add_index "notes", ["answer_id"], name: "index_notes_on_answer_id", using: :btree create_table "notification_acknowledgements", force: :cascade do |t| t.integer "user_id" @@ -132,8 +135,8 @@ t.datetime "updated_at" end - add_index "notification_acknowledgements", ["notification_id"], name: "index_notification_acknowledgements_on_notification_id" - add_index "notification_acknowledgements", ["user_id"], name: "index_notification_acknowledgements_on_user_id" + add_index "notification_acknowledgements", ["notification_id"], name: "index_notification_acknowledgements_on_notification_id", using: :btree + add_index "notification_acknowledgements", ["user_id"], name: "index_notification_acknowledgements_on_user_id", using: :btree create_table "notifications", force: :cascade do |t| t.integer "notification_type" @@ -163,15 +166,15 @@ t.datetime "updated_at" end - add_index "org_token_permissions", ["org_id"], name: "index_org_token_permissions_on_org_id" + add_index "org_token_permissions", ["org_id"], name: "index_org_token_permissions_on_org_id", using: :btree create_table "orgs", force: :cascade do |t| t.string "name" t.string "abbreviation" t.string "target_url" t.string "wayfless_entity" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.integer "parent_id" t.boolean "is_other" t.string "sort_name" @@ -182,7 +185,7 @@ t.string "logo_uid" t.string "logo_name" t.string "contact_email" - t.integer "org_type", default: 0, null: false + t.integer "org_type", default: 0, null: false t.text "links" t.string "contact_name" t.boolean "feedback_enabled", default: false @@ -207,7 +210,7 @@ t.boolean "modifiable" end - add_index "phases", ["template_id"], name: "index_phases_on_template_id" + add_index "phases", ["template_id"], name: "index_phases_on_template_id", using: :btree create_table "plans", force: :cascade do |t| t.string "title" @@ -231,14 +234,14 @@ t.boolean "complete", default: false end - add_index "plans", ["template_id"], name: "index_plans_on_template_id" + add_index "plans", ["template_id"], name: "index_plans_on_template_id", using: :btree create_table "plans_guidance_groups", force: :cascade do |t| t.integer "guidance_group_id" t.integer "plan_id" end - add_index "plans_guidance_groups", ["guidance_group_id", "plan_id"], name: "index_plans_guidance_groups_on_guidance_group_id_and_plan_id" + add_index "plans_guidance_groups", ["guidance_group_id", "plan_id"], name: "index_plans_guidance_groups_on_guidance_group_id_and_plan_id", using: :btree create_table "prefs", force: :cascade do |t| t.text "settings" @@ -263,7 +266,7 @@ t.datetime "updated_at" end - add_index "question_options", ["question_id"], name: "index_question_options_on_question_id" + add_index "question_options", ["question_id"], name: "index_question_options_on_question_id", using: :btree create_table "questions", force: :cascade do |t| t.text "text" @@ -277,14 +280,14 @@ t.boolean "modifiable" end - add_index "questions", ["section_id"], name: "index_questions_on_section_id" + add_index "questions", ["section_id"], name: "index_questions_on_section_id", using: :btree create_table "questions_themes", id: false, force: :cascade do |t| t.integer "question_id", null: false t.integer "theme_id", null: false end - add_index "questions_themes", ["question_id"], name: "index_questions_themes_on_question_id" + add_index "questions_themes", ["question_id"], name: "index_questions_themes_on_question_id", using: :btree create_table "regions", force: :cascade do |t| t.string "abbreviation" @@ -302,8 +305,8 @@ t.boolean "active", default: true end - add_index "roles", ["plan_id"], name: "index_roles_on_plan_id" - add_index "roles", ["user_id"], name: "index_roles_on_user_id" + add_index "roles", ["plan_id"], name: "index_roles_on_plan_id", using: :btree + add_index "roles", ["user_id"], name: "index_roles_on_user_id", using: :btree create_table "sections", force: :cascade do |t| t.string "title" @@ -316,7 +319,7 @@ t.boolean "modifiable" end - add_index "sections", ["phase_id"], name: "index_sections_on_phase_id" + add_index "sections", ["phase_id"], name: "index_sections_on_phase_id", using: :btree create_table "settings", force: :cascade do |t| t.string "var", null: false @@ -327,8 +330,6 @@ t.datetime "updated_at", null: false end - add_index "settings", ["target_type", "target_id", "var"], name: "index_settings_on_target_type_and_target_id_and_var", unique: true - create_table "splash_logs", force: :cascade do |t| t.string "destination" t.datetime "created_at", null: false @@ -352,11 +353,11 @@ t.text "links" end - add_index "templates", ["customization_of", "version", "org_id"], name: "index_templates_on_customization_of_and_version_and_org_id", unique: true - add_index "templates", ["family_id", "version"], name: "index_templates_on_family_id_and_version", unique: true - add_index "templates", ["family_id"], name: "index_templates_on_family_id" - add_index "templates", ["org_id", "family_id"], name: "template_organisation_dmptemplate_index" - add_index "templates", ["org_id"], name: "index_templates_on_org_id" + add_index "templates", ["customization_of", "version", "org_id"], name: "index_templates_on_customization_of_and_version_and_org_id", unique: true, using: :btree + add_index "templates", ["family_id", "version"], name: "index_templates_on_family_id_and_version", unique: true, using: :btree + add_index "templates", ["family_id"], name: "index_templates_on_family_id", using: :btree + add_index "templates", ["org_id", "family_id"], name: "template_organisation_dmptemplate_index", using: :btree + add_index "templates", ["org_id"], name: "index_templates_on_org_id", using: :btree create_table "themes", force: :cascade do |t| t.string "title" @@ -371,8 +372,8 @@ t.integer "guidance_id" end - add_index "themes_in_guidance", ["guidance_id"], name: "index_themes_in_guidance_on_guidance_id" - add_index "themes_in_guidance", ["theme_id"], name: "index_themes_in_guidance_on_theme_id" + add_index "themes_in_guidance", ["guidance_id"], name: "index_themes_in_guidance_on_guidance_id", using: :btree + add_index "themes_in_guidance", ["theme_id"], name: "index_themes_in_guidance_on_theme_id", using: :btree create_table "token_permission_types", force: :cascade do |t| t.string "token_type" @@ -389,21 +390,21 @@ t.integer "identifier_scheme_id" end - add_index "user_identifiers", ["user_id"], name: "index_user_identifiers_on_user_id" + add_index "user_identifiers", ["user_id"], name: "index_user_identifiers_on_user_id", using: :btree create_table "users", force: :cascade do |t| t.string "firstname" t.string "surname" - t.string "email", default: "", null: false + t.string "email", limit: 80, default: "", null: false t.string "orcid_id" t.string "shibboleth_id" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.string "encrypted_password", default: "" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "encrypted_password", default: "" t.string "reset_password_token" t.datetime "reset_password_sent_at" t.datetime "remember_created_at" - t.integer "sign_in_count", default: 0 + t.integer "sign_in_count", default: 0 t.datetime "current_sign_in_at" t.datetime "last_sign_in_at" t.string "current_sign_in_ip" @@ -423,11 +424,11 @@ t.string "invited_by_type" t.integer "language_id" t.string "recovery_email" - t.boolean "active", default: true + t.boolean "active", default: true end - add_index "users", ["email"], name: "index_users_on_email", unique: true - add_index "users", ["org_id"], name: "index_users_on_org_id" + add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree + add_index "users", ["org_id"], name: "index_users_on_org_id", using: :btree create_table "users_perms", id: false, force: :cascade do |t| t.integer "user_id" @@ -441,6 +442,8 @@ add_foreign_key "answers", "plans" add_foreign_key "answers", "questions" add_foreign_key "answers", "users" + add_foreign_key "answers_question_options", "answers" + add_foreign_key "answers_question_options", "question_options" add_foreign_key "guidance_groups", "orgs" add_foreign_key "guidances", "guidance_groups" add_foreign_key "notes", "answers" @@ -460,6 +463,8 @@ add_foreign_key "question_options", "questions" add_foreign_key "questions", "question_formats" add_foreign_key "questions", "sections" + add_foreign_key "questions_themes", "questions" + add_foreign_key "questions_themes", "themes" add_foreign_key "roles", "plans" add_foreign_key "roles", "users" add_foreign_key "sections", "phases" @@ -470,4 +475,6 @@ add_foreign_key "user_identifiers", "users" add_foreign_key "users", "languages" add_foreign_key "users", "orgs" + add_foreign_key "users_perms", "perms" + add_foreign_key "users_perms", "users" end diff --git a/lib/assets/javascripts/utils/notificationHelper.js b/lib/assets/javascripts/utils/notificationHelper.js index 9380dbc..7e29d57 100644 --- a/lib/assets/javascripts/utils/notificationHelper.js +++ b/lib/assets/javascripts/utils/notificationHelper.js @@ -1,32 +1,58 @@ import { isString, isObject } from './isType'; + /* Helpers that will display the specified message in in the notification area at the top of the page */ -export const renderNotice = (msg) => { + +export function hideNotifications() { + $('#notification-area') + .addClass('hide') + .removeClass('notification-area--floating'); +} + +function renderMessage(options = {}) { const notificationArea = $('#notification-area'); - if (isString(msg) && isObject(notificationArea)) { - notificationArea.removeClass('alert-warning').addClass('alert-info'); + if (isString(options.message) && isObject(notificationArea)) { + notificationArea + .removeClass('alert-info', 'alert-warning') + .addClass(options.className); + + if (options.floating) { + notificationArea.addClass('notification-area--floating'); + } + notificationArea.find('i, span').remove(); notificationArea.append(` - ${msg}`); + + ${options.message} + `); + notificationArea.removeClass('hide'); + + if (options.autoDismiss) { + setTimeout(() => { hideNotifications(); }, 5000); + } } -}; +} -export const renderAlert = (msg) => { - const notificationArea = $('#notification-area'); +export function renderNotice(msg, options = {}) { + renderMessage({ + message: msg, + icon: 'check-circle', + className: 'alert-info', + floating: options.floating === true, + autoDismiss: options.autoDismiss === true, + }); +} - if (isString(msg) && isObject(notificationArea)) { - notificationArea.removeClass('alert-info').addClass('alert-warning'); - notificationArea.find('i, span').remove(); - notificationArea.append(` - ${msg}`); - notificationArea.removeClass('hide'); - } -}; - -export const hideNotifications = () => { - $('#notification-area').addClass('hide'); -}; +export function renderAlert(msg, options = {}) { + renderMessage({ + message: msg, + icon: 'times-circle', + className: 'alert-warning', + floating: options.floating === true, + autoDismiss: options.autoDismiss === true, + }); +} diff --git a/lib/assets/javascripts/utils/panelHeading.js b/lib/assets/javascripts/utils/panelHeading.js index 194657f..670c048 100644 --- a/lib/assets/javascripts/utils/panelHeading.js +++ b/lib/assets/javascripts/utils/panelHeading.js @@ -1,5 +1,8 @@ $(() => { $('.heading-button').on('click', (e) => { - $(e.currentTarget).find('i.fa').toggleClass('fa-plus').toggleClass('fa-minus'); + $(e.currentTarget) + .find('i.fa-plus, i.fa-minus') + .toggleClass('fa-plus') + .toggleClass('fa-minus'); }); }); diff --git a/lib/assets/javascripts/views/org_admin/phases/show.js b/lib/assets/javascripts/views/org_admin/phases/show.js index b8be4fe..ed5b270 100644 --- a/lib/assets/javascripts/views/org_admin/phases/show.js +++ b/lib/assets/javascripts/views/org_admin/phases/show.js @@ -1,4 +1,5 @@ import 'jquery-ui/ui/widgets/sortable'; +import { renderAlert } from '../../../utils/notificationHelper'; $(() => { // Is there already one prefix section on this Phase? @@ -15,15 +16,18 @@ // Initialize the draggable-sections element as a jQuery sortable. // Read the docs here for more info: http://api.jqueryui.com/sortable/ $('.draggable-sections').sortable({ - handle: 'i.fa-bars', + handle: 'i.fa-arrows', axis: 'y', cursor: 'move', beforeStop() { if (prefixSectionExists($(this))) { // Prevent the sort action from completing. Moves element back to source $(this).sortable('cancel'); - // Display a wobble effec to signify error - $(this).effect('shake'); + + renderAlert(`You can only place one section before the funder template. + Multiple can go afterwards.`, { + floating: true, autoDismiss: true, + }); } }, update() { diff --git a/lib/assets/stylesheets/application.scss b/lib/assets/stylesheets/application.scss index d260a65..1eff42f 100644 --- a/lib/assets/stylesheets/application.scss +++ b/lib/assets/stylesheets/application.scss @@ -24,3 +24,21 @@ clear: left; margin-bottom: 10px; } + +// TODO: Move this into it's own file once CSS has been fixed +.notification-area { +} +.notification-area--floating { + position: fixed; + top: 4rem; + z-index: 1000; + // Take up width of the page on mobile + right: 5vw; + width: 90vw; + + // Non-mobile settings + @media (min-width: #{$screen-sm-min}) { + right: 4rem; + width: 300px; + } +} diff --git a/lib/assets/stylesheets/dmproadmap.scss b/lib/assets/stylesheets/dmproadmap.scss index 5b2909c..e4c162e 100644 --- a/lib/assets/stylesheets/dmproadmap.scss +++ b/lib/assets/stylesheets/dmproadmap.scss @@ -1,3 +1,5 @@ +@import "dmproadmap/notifications"; + /**** Main layout configuration ****/ /* For sticky footer */ diff --git a/lib/assets/stylesheets/overrides.scss b/lib/assets/stylesheets/overrides.scss index 814495f..c12b71c 100644 --- a/lib/assets/stylesheets/overrides.scss +++ b/lib/assets/stylesheets/overrides.scss @@ -200,7 +200,7 @@ } /* FONTAWESOME STYLING */ -.fa { +.fa:not(.small) { font-size: 2rem; } .fa-reverse { @@ -599,18 +599,18 @@ } /* Skip to main content link styling */ -div.skip a { - position:absolute; - left:-10000px; - top:auto; - width:1px; - height:1px; +div.skip a { + position:absolute; + left:-10000px; + top:auto; + width:1px; + height:1px; overflow: hidden !important; -} - -div.skip a:focus { - width:auto; - height:auto; +} + +div.skip a:focus { + width:auto; + height:auto; overflow:visible !important; }