helena 1.3.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +17 -10
  4. data/.travis.yml +16 -5
  5. data/Gemfile +6 -3
  6. data/Gemfile.lock +189 -211
  7. data/README.md +4 -5
  8. data/Rakefile +1 -2
  9. data/app/assets/javascripts/helena/application.js +0 -1
  10. data/app/assets/stylesheets/helena/{application.css.sass → application.scss} +0 -8
  11. data/app/controllers/helena/sessions_controller.rb +7 -1
  12. data/app/helpers/helena/application_helper.rb +18 -0
  13. data/app/models/helena/concerns/questions/validates_one_label.rb +1 -0
  14. data/app/models/helena/question.rb +1 -0
  15. data/app/models/helena/question_group.rb +8 -0
  16. data/app/models/helena/questions/static_text.rb +4 -0
  17. data/app/models/helena/session.rb +2 -20
  18. data/app/models/helena/sub_question.rb +1 -0
  19. data/app/models/helena/version.rb +4 -0
  20. data/app/views/helena/questions/_bipolar_radio_matrix.html.slim +36 -0
  21. data/app/views/helena/questions/{_checkbox_group.html.haml → _checkbox_group.html.slim} +6 -8
  22. data/app/views/helena/questions/_long_text.html.slim +9 -0
  23. data/app/views/helena/questions/{_radio_group.html.haml → _radio_group.html.slim} +5 -6
  24. data/app/views/helena/questions/_radio_matrix.html.slim +27 -0
  25. data/app/views/helena/questions/_short_text.html.slim +9 -0
  26. data/app/views/helena/questions/_static_text.html.slim +2 -4
  27. data/app/views/helena/sessions/_error_messages.html.slim +8 -0
  28. data/app/views/helena/sessions/edit.html.slim +11 -7
  29. data/app/views/helena/sessions/end_message.html.slim +1 -1
  30. data/config/locales/en.yml +13 -12
  31. data/config/locales/views/sessions/en.yml +1 -0
  32. data/db/seeds.rb +1 -1
  33. data/helena.gemspec +15 -20
  34. data/lib/helena.rb +0 -3
  35. data/lib/helena/engine.rb +1 -1
  36. data/lib/helena/survey_importer.rb +4 -1
  37. data/lib/helena/version.rb +1 -1
  38. data/spec/controllers/helena/sessions_controller_spec.rb +2 -2
  39. data/spec/dummy/Rakefile +1 -1
  40. data/spec/dummy/app/assets/config/manifest.js +2 -0
  41. data/spec/dummy/app/views/layouts/{application.html.haml → application.html.slim} +10 -10
  42. data/spec/dummy/bin/bundle +1 -1
  43. data/spec/dummy/bin/rails +1 -1
  44. data/spec/dummy/config/application.rb +1 -2
  45. data/spec/dummy/config/boot.rb +2 -2
  46. data/spec/dummy/config/environment.rb +1 -1
  47. data/spec/factories/helena/surveys.rb +1 -1
  48. data/spec/factories/helena/versions.rb +2 -2
  49. data/spec/features/helena/manage_session_spec.rb +40 -36
  50. data/spec/helpers/application_helper_spec.rb +10 -0
  51. data/spec/models/helena/session_spec.rb +1 -8
  52. data/spec/models/helena/sub_question_spec.rb +1 -1
  53. data/spec/spec_helper.rb +3 -1
  54. data/spec/support/rails_compatibility.rb +1 -1
  55. metadata +63 -141
  56. data/.coveralls.yml +0 -2
  57. data/Appraisals +0 -12
  58. data/app/assets/stylesheets/helena/forms.css.sass +0 -13
  59. data/app/assets/stylesheets/helena/layout.css.sass +0 -3
  60. data/app/assets/stylesheets/helena/question_groups.css.sass +0 -4
  61. data/app/views/helena/questions/_bipolar_radio_matrix.html.haml +0 -56
  62. data/app/views/helena/questions/_long_text.html.haml +0 -8
  63. data/app/views/helena/questions/_radio_matrix.html.haml +0 -53
  64. data/app/views/helena/questions/_short_text.html.haml +0 -8
  65. data/config/initializers/simple_form_bootstrap.rb +0 -132
  66. data/gemfiles/rails_4.2.gemfile +0 -22
  67. data/gemfiles/rails_4.2.gemfile.lock +0 -295
  68. data/gemfiles/rails_5.1.gemfile +0 -24
  69. data/gemfiles/rails_5.1.gemfile.lock +0 -305
data/README.md CHANGED
@@ -1,15 +1,14 @@
1
- [![Build Status](https://img.shields.io/travis/gurix/helena/master.svg?style=flat)](https://travis-ci.org/gurix/helena)
2
- [![Code Climate](https://img.shields.io/codeclimate/github/gurix/helena.svg?style=flat)](https://codeclimate.com/github/gurix/helena)
1
+ [![Maintainability](https://api.codeclimate.com/v1/badges/0a74377cf11aeb621333/maintainability)](https://codeclimate.com/github/gurix/helena/maintainability)
2
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/0a74377cf11aeb621333/test_coverage)](https://codeclimate.com/github/gurix/helena/test_coverage)
3
3
  [![Rubygems Version](https://img.shields.io/gem/v/helena.svg?style=flat)](https://rubygems.org/gems/helena)
4
4
  # Helena
5
- Helena is an online survey/test framework designed for agile survey/test development, longitudinal studies and instant feedback.
5
+ Helena is an online survey/test framework designed for agile survey/test development, longitudinal studies and instant feedback. It was designed as modular part of the platform Laufbahndiagnostik (https://laufbahndiagnostik.ch/en).
6
6
 
7
7
  ![swiss made software](https://raw.githubusercontent.com/gurix/helena/master/app/assets/images/helena/swissmadesoftware.png "swiss made software")
8
8
 
9
- Demo: http://helena-demo.herokuapp.com (https://github.com/gurix/helena-demo)
10
9
  ## Requirements
11
10
  * Ruby (2.3.1 or higher)
12
- * Rails (4.2 or higher)
11
+ * Rails (5.1 or higher)
13
12
  * Mongoid (4.0.0 or higher)
14
13
  * MongoDB (2.4.10 or higher)
15
14
 
data/Rakefile CHANGED
@@ -1,4 +1,3 @@
1
-
2
1
  require 'rubygems'
3
2
  begin
4
3
  require 'bundler/setup'
@@ -17,7 +16,7 @@ RDoc::Task.new(:rdoc) do |rdoc|
17
16
  rdoc.rdoc_files.include('lib/**/*.rb')
18
17
  end
19
18
 
20
- APP_RAKEFILE = File.expand_path('../spec/dummy/Rakefile', __FILE__)
19
+ APP_RAKEFILE = File.expand_path('spec/dummy/Rakefile', __dir__)
21
20
  load 'rails/tasks/engine.rake'
22
21
 
23
22
  Bundler::GemHelper.install_tasks
@@ -1,4 +1,3 @@
1
1
  //= require jquery
2
2
  //= require jquery_ujs
3
3
  $(document).ready(function() { })
4
- //= require bootstrap
@@ -11,11 +11,3 @@
11
11
  *= require_self
12
12
  *= require_tree .
13
13
  */
14
-
15
- /* Needed because of http://stackoverflow.com/a/24958307/437892 */
16
- @import "bootstrap-sprockets"
17
-
18
- @import "bootstrap"
19
-
20
- .hide-text
21
- visibility: hidden
@@ -4,6 +4,7 @@ module Helena
4
4
  before_action :load_session, only: %i[edit update]
5
5
  before_action :update_answers, only: :update
6
6
  before_action :answer_errors, only: :update
7
+ before_action :flash_errors, only: :update
7
8
  after_action :update_last_question_group_id, only: :update
8
9
 
9
10
  def show
@@ -41,7 +42,7 @@ module Helena
41
42
  @session = Helena::Session.find_by token: params[:token]
42
43
  @survey = @session.survey
43
44
  @version = @survey.versions.find @session.version_id
44
- render plain: 'Version not active', status: '404' unless @version && @version.active
45
+ render plain: 'Version not active', status: '404' unless @version&.active
45
46
  @question_group = question_group
46
47
  end
47
48
 
@@ -60,6 +61,7 @@ module Helena
60
61
 
61
62
  def session_params
62
63
  return unless params[:session]
64
+
63
65
  params.require(:session).permit(answers: @question_group.question_codes, question_types: @question_group.question_codes)
64
66
  end
65
67
 
@@ -92,6 +94,10 @@ module Helena
92
94
  @errors = errors
93
95
  end
94
96
 
97
+ def flash_errors
98
+ flash.now[:danger] = t('errors.messages.any_errors_flash') if @errors.any?
99
+ end
100
+
95
101
  def session_report
96
102
  Slim::Template.new { @version.session_report }.render(self).html_safe if @version.session_report
97
103
  end
@@ -1,4 +1,22 @@
1
1
  module Helena
2
2
  module ApplicationHelper
3
+ def requeired_note
4
+ content_tag(:span, t('helena.shared.required_indicator'), aria: { hidden: true }) +
5
+ content_tag(:span, t('helena.shared.sr_required_indicator'), class: 'sr-only')
6
+ end
7
+
8
+ def question_label(question, options = {})
9
+ content_tag((options[:dummy] ? :span : :label), class: :label, for: "question_#{question.code}") do
10
+ raw(question.question_text) + (requeired_note if question.required?)
11
+ end
12
+ end
13
+
14
+ # Allows to set the header title within the text
15
+ # i.e `h1 = title 'This will be also shown in the title tag'`
16
+ # Do not forgot to set `content_for(:title)` within the title tag in the head.
17
+ def title(page_title)
18
+ content_for(:title, page_title)
19
+ page_title
20
+ end
3
21
  end
4
22
  end
@@ -13,6 +13,7 @@ module Helena
13
13
  def labels_preselection
14
14
  selected_labels = labels.select { |label| label.preselected == true }
15
15
  return if selected_labels.size <= 1
16
+
16
17
  selected_labels.each { |label| label.errors.add(:preselected, I18n.t(:taken, scope: 'activerecord.errors.messages')) }
17
18
  errors.add(:labels, I18n.t('helena.admin.radio_group.only_one_preselection_allowed')) # TODO: How to manage this translation?
18
19
  end
@@ -41,6 +41,7 @@ module Helena
41
41
 
42
42
  def uniqueness_of_code
43
43
  return unless question_group
44
+
44
45
  question_code_occurences = question_group.version.question_code_occurences
45
46
  errors.add :code, :taken if question_code_occurences[code] > 1
46
47
  end
@@ -15,5 +15,13 @@ module Helena
15
15
  def question_codes
16
16
  questions.map { |question| [question.code] + question.sub_questions.map(&:code) }.flatten
17
17
  end
18
+
19
+ def question_texts
20
+ questions.map { |question| [question.code, question.question_text] + subquestion_texts(question) }.flatten
21
+ end
22
+
23
+ def subquestion_texts(question)
24
+ question.sub_questions.map { |sub_question| [sub_question.code, sub_question.text] }
25
+ end
18
26
  end
19
27
  end
@@ -2,6 +2,10 @@ module Helena
2
2
  module Questions
3
3
  class StaticText < Helena::Question
4
4
  field :default_value, type: String
5
+
6
+ def required?
7
+ false
8
+ end
5
9
  end
6
10
  end
7
11
  end
@@ -24,9 +24,8 @@ module Helena
24
24
  end
25
25
 
26
26
  def answers_as_yaml=(yaml)
27
- parsed_answers = YAML.safe_load yaml
27
+ parsed_answers = YAML.load yaml # rubocop:disable Security/YAMLLoad
28
28
  update_answers parsed_answers
29
- remove_unparsed_answers parsed_answers
30
29
  end
31
30
 
32
31
  def reset_tokens
@@ -46,14 +45,6 @@ module Helena
46
45
  Hash[*answers.map { |answer| [answer[:code], answer[:value]] }.flatten]
47
46
  end
48
47
 
49
- def self.answer_codes
50
- answer_codes = []
51
- all.each do |session|
52
- answer_codes += session.answers.map(&:code) - answer_codes
53
- end
54
- answer_codes
55
- end
56
-
57
48
  private
58
49
 
59
50
  def generate_token(size)
@@ -65,19 +56,10 @@ module Helena
65
56
  end
66
57
 
67
58
  def update_answers(parsed_answers)
59
+ answers.delete_all
68
60
  parsed_answers.each do |code, value|
69
- answer = answers.where(code: code).first
70
- if answer
71
- next if answer.value == value
72
- answer.delete
73
- end
74
61
  answers << Helena::Answer.build_generic(code, value, '')
75
62
  end
76
63
  end
77
-
78
- def remove_unparsed_answers(parsed_answers)
79
- unparsed_answers = answers.map(&:code) - parsed_answers.keys
80
- answers.in(code: unparsed_answers).destroy
81
- end
82
64
  end
83
65
  end
@@ -25,6 +25,7 @@ module Helena
25
25
 
26
26
  def uniqueness_of_code
27
27
  return unless question
28
+
28
29
  question_code_occurences = question.question_group.version.question_code_occurences
29
30
 
30
31
  return true if question_code_occurences[code] <= 1
@@ -28,6 +28,10 @@ module Helena
28
28
  question_groups.map(&:question_codes).flatten
29
29
  end
30
30
 
31
+ def question_texts
32
+ question_groups.map(&:question_texts).flatten
33
+ end
34
+
31
35
  def question_code_occurences
32
36
  question_codes.each_with_object(Hash.new(0)) { |word, counts| counts[word] += 1 }
33
37
  end
@@ -0,0 +1,36 @@
1
+ - label_width = "#{60 / question.labels.count}%"
2
+ .radio_matrix class="#{'helena-error' if errors.present?}"
3
+ = question_label(question, dummy: true)
4
+
5
+ table
6
+ thead
7
+ tr
8
+ td
9
+ - question.labels.each do |label|
10
+ th width="#{label_width}"
11
+ = label.text
12
+ td
13
+
14
+ tbody
15
+ - question.sub_questions.each do |sub_question|
16
+ tr class="#{'error' if errors[sub_question.code]}" id="question_#{sub_question.code}"
17
+ td
18
+ = sub_question.parts.first
19
+ - if errors[sub_question.code]
20
+ .error= errors[sub_question.code]
21
+ - question.labels.each_with_index do |label, index|
22
+ - checked = answers[sub_question.code].to_s == label.value.to_s if answers[sub_question.code].present?
23
+ - checked ||= (label.preselected? ? true : false)
24
+ th width="#{label_width}"
25
+ label
26
+ = form.simple_fields_for :answers do |answer_form|
27
+ = answer_form.radio_button sub_question.code, label.value, checked: checked
28
+ span.label-text
29
+ - if index < (question.labels.size / 2)
30
+ = "#{label.text} #{sub_question.parts.first}"
31
+ - elsif index > (question.labels.size / 2)
32
+ = "#{label.text} #{sub_question.parts.last}"
33
+ - else
34
+ = label.text
35
+ td
36
+ = sub_question.parts.last if sub_question.splitted?
@@ -1,17 +1,15 @@
1
- .checkbox_group.form-group{ class: "#{'has-error' if errors[question.code]}" }
2
- %label
3
- != question.question_text
4
- = ' *' if question.required
1
+ fieldset.checkbox_group class="#{'helena-error' if errors[question.code]}"
2
+ legend= question_label(question, dummy: true)
3
+
5
4
  = form.simple_fields_for :answers do |answer_form|
6
5
  - question.sub_questions.each do |sub_question|
7
6
  .checkbox
8
- %label
7
+ label
9
8
  - checked = (answers[sub_question.code].to_s == sub_question.value.to_s) if sub_question.value
10
9
  - checked ||= sub_question.preselected
11
10
  = answer_form.input_field sub_question.code, as: :boolean,
12
11
  value: sub_question.value,
13
- checked: checked,
14
- class: 'form-control'
12
+ checked: checked
15
13
  = sub_question.text
16
14
  - if errors[question.code]
17
- .help-block.text-danger= errors[question.code]
15
+ .error= errors[question.code]
@@ -0,0 +1,9 @@
1
+ .long_text class="#{'helena-error' if errors[question.code]}"
2
+ = question_label(question)
3
+ = form.simple_fields_for :answers do |answer_form|
4
+ - params = { value: answers[question.code], id: "question_#{question.code}" }
5
+ - params = params.merge(aria: { describedby: "#{question.code}_error" }, invalid: true) if errors[question.code]
6
+ - params = params.merge(required: true) if question.required
7
+ = answer_form.text_area(question.code, params)
8
+ - if errors[question.code]
9
+ .error id="#{question.code}_error" = errors[question.code]
@@ -1,13 +1,12 @@
1
- .radio_group.form-group{ class: "#{'has-error' if errors[question.code]}" }
2
- %label
3
- != question.question_text
4
- = ' *' if question.required
1
+ fieldset.radio_group id="question_#{question.code}" class="#{'helena-error' if errors[question.code]}"
2
+ legend = question_label(question, dummy: true)
3
+
5
4
  - question.labels.each do |label|
6
5
  .radio
7
- %label
6
+ label
8
7
  - checked = (answers[question.code].to_s == label.value.to_s) || label.preselected
9
8
  = form.simple_fields_for :answers do |answer_form|
10
9
  = answer_form.radio_button question.code, label.value, checked: checked
11
10
  = label.text
12
11
  - if errors[question.code]
13
- .help-block.text-danger= errors[question.code]
12
+ .error= errors[question.code]
@@ -0,0 +1,27 @@
1
+ - label_width = "#{80 / question.labels.count}%"
2
+
3
+ .radio_matrix class="#{'helena-error' if errors.present?}"
4
+ = question_label(question, dummy: true)
5
+
6
+ table.table
7
+ thead
8
+ tr
9
+ th
10
+ - question.labels.each do |label|
11
+ th width="#{label_width}"
12
+ == label.text
13
+ tbody
14
+ - question.sub_questions.each do |sub_question|
15
+ tr class="#{'error' if errors[sub_question.code]}" id="question_#{sub_question.code}"
16
+ td
17
+ = sub_question.parts.first
18
+ - if errors[sub_question.code]
19
+ .error= errors[sub_question.code]
20
+ - question.labels.each do |label|
21
+ - checked = answers[sub_question.code].to_s == label.value.to_s if answers[sub_question.code].present?
22
+ - checked ||= (label.preselected? ? true : false)
23
+ th width="#{label_width}"
24
+ label
25
+ = form.simple_fields_for :answers do |answer_form|
26
+ = answer_form.radio_button sub_question.code, label.value, checked: checked
27
+ .label-text= label.text
@@ -0,0 +1,9 @@
1
+ .short_text class="#{'helena-error' if errors[question.code]}"
2
+ = question_label(question)
3
+ = form.simple_fields_for :answers do |answer_form|
4
+ - params = { value: answers[question.code], id: "question_#{question.code}" }
5
+ - params = params.merge(aria: { describedby: "#{question.code}_error" }, invalid: true) if errors[question.code]
6
+ - params = params.merge(required: true) if question.required
7
+ = answer_form.text_field(question.code, params)
8
+ - if errors[question.code]
9
+ .error id="#{question.code}_error" = errors[question.code]
@@ -1,6 +1,4 @@
1
- .short_text.form-group
1
+ .static_text
2
2
  - if question.question_text.present?
3
- label
4
- = question.question_text.html_safe
5
-
3
+ = question_label(question, dummy: true)
6
4
  = question.default_value.html_safe
@@ -0,0 +1,8 @@
1
+ #question_group_errors
2
+ p = t('errors.messages.any_errors')
3
+ .details
4
+ ul
5
+ - @errors.each do |key,value|
6
+ - text = Hash[*@session.version.question_texts][key]
7
+ li
8
+ a href="#question_#{key}" «#{text.sub('|', ' vs. ')}» #{value}.
@@ -1,18 +1,22 @@
1
1
  = render 'progressbar' if @version.settings && @version.settings[:display_progressbar] == "1"
2
2
  #survey_content
3
- h1.survey_title
4
- = @version.survey_detail.title
3
+ h1.survey_title = title @version.survey_detail.title
5
4
  fieldset id="#{dom_id @question_group}"
6
- legend= @question_group.title
5
+ legend
6
+ - if @question_group.title
7
+ = @question_group.title
8
+ - else
9
+ .sr-only= t('.question_group_legend', page: (@question_group.previous_items.size + 1).to_i)
7
10
  = simple_form_for(@session, url: helena.session_path(@session.token), as: :session) do |f|
11
+ = render 'error_messages'if @errors.any?
8
12
  - @questions.each do |question|
9
13
  = render "#{question.class.to_s.underscore}", form: f, question: question, answers: @answers, errors: @errors
10
14
 
11
15
  = hidden_field_tag :question_group, @question_group.id
12
16
 
13
17
  - if @question_group.previous_items.any? && @question_group.allow_to_go_back?
14
- = link_to t('.back'), edit_session_path(@session.token, question_group: @question_group.previous_item), class: 'btn btn-default'
18
+ = link_to t('.back'), edit_session_path(@session.token, question_group: @question_group.previous_item), class: 'back_button'
15
19
 
16
- = f.submit class: 'btn btn-success pull-right', value: @question_group.last? ? t('.save') : t('.next')
17
-
18
- = t('shared.required') if @questions.map{ |question| question.try :required }.any?
20
+ = f.submit class: 'next_button', value: @question_group.last? ? t('.save') : t('.next')
21
+ - if @questions.map{ |question| question.try :required }.any?
22
+ span aria-hidden="true" = t('helena.shared.required')
@@ -1,5 +1,5 @@
1
1
  h1.survey_title
2
2
  = @version.survey_detail.title
3
3
 
4
- .well
4
+ .end-message
5
5
  = t '.message'
@@ -1,19 +1,20 @@
1
1
  en:
2
- shared:
3
- delete_question: Are you sure?
4
- delete: Delete
5
- save: Save
6
- edit: Edit
7
- add: Add
8
- show: Show
9
- required: '* indicates required fields'
10
- code_hint: The code should consist only of lowercase letters, numbers and underscores. Don't begin with a digit or an underscore and don't end with an underscore.
11
- move_up: Move up
12
- move_down: Move down
13
- untitled: Untitled
2
+ helena:
3
+ shared:
4
+ required_indicator: "*"
5
+ sr_required_indicator: "(Mandatory)"
6
+ required: '* indicates required fields'
14
7
 
8
+ layouts:
9
+ application:
10
+ brand_name: Helena
15
11
  actions:
16
12
  deleted: "'%{resource}' successfully deleted"
17
13
  updated: "'%{resource}' successfully updated"
18
14
  created: "'%{resource}' successfully created"
19
15
  error: Ooopss... something is wrong, please check your input
16
+
17
+ errors:
18
+ messages:
19
+ any_errors: Problems have arisen in answering the questions. Please check your input.
20
+ any_errors_flash: One or more questions have not been answered correctly. You cannot continue until the correct answers have been given.