helena 1.3.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +12 -7
  3. data/.travis.yml +11 -0
  4. data/Gemfile +6 -3
  5. data/Gemfile.lock +175 -188
  6. data/README.md +3 -4
  7. data/Rakefile +1 -2
  8. data/app/assets/javascripts/helena/application.js +0 -1
  9. data/app/assets/stylesheets/helena/{application.css.sass → application.scss} +0 -8
  10. data/app/controllers/helena/sessions_controller.rb +7 -1
  11. data/app/helpers/helena/application_helper.rb +18 -0
  12. data/app/models/helena/concerns/questions/validates_one_label.rb +1 -0
  13. data/app/models/helena/question.rb +1 -0
  14. data/app/models/helena/question_group.rb +8 -0
  15. data/app/models/helena/questions/static_text.rb +4 -0
  16. data/app/models/helena/session.rb +1 -19
  17. data/app/models/helena/sub_question.rb +1 -0
  18. data/app/models/helena/version.rb +4 -0
  19. data/app/views/helena/questions/_bipolar_radio_matrix.html.slim +36 -0
  20. data/app/views/helena/questions/{_checkbox_group.html.haml → _checkbox_group.html.slim} +6 -8
  21. data/app/views/helena/questions/_long_text.html.slim +9 -0
  22. data/app/views/helena/questions/{_radio_group.html.haml → _radio_group.html.slim} +5 -6
  23. data/app/views/helena/questions/_radio_matrix.html.slim +27 -0
  24. data/app/views/helena/questions/_short_text.html.slim +9 -0
  25. data/app/views/helena/questions/_static_text.html.slim +2 -4
  26. data/app/views/helena/sessions/_error_messages.html.slim +8 -0
  27. data/app/views/helena/sessions/edit.html.slim +11 -7
  28. data/app/views/helena/sessions/end_message.html.slim +1 -1
  29. data/config/locales/en.yml +13 -12
  30. data/config/locales/views/sessions/en.yml +1 -0
  31. data/gemfiles/rails_4.2.gemfile +18 -15
  32. data/gemfiles/rails_4.2.gemfile.lock +96 -117
  33. data/gemfiles/rails_5.1.gemfile +6 -3
  34. data/gemfiles/rails_5.1.gemfile.lock +127 -146
  35. data/helena.gemspec +16 -20
  36. data/lib/helena.rb +0 -3
  37. data/lib/helena/engine.rb +1 -1
  38. data/lib/helena/survey_importer.rb +4 -1
  39. data/lib/helena/version.rb +1 -1
  40. data/spec/controllers/helena/sessions_controller_spec.rb +2 -2
  41. data/spec/dummy/Rakefile +1 -1
  42. data/spec/dummy/app/views/layouts/{application.html.haml → application.html.slim} +10 -10
  43. data/spec/dummy/bin/bundle +1 -1
  44. data/spec/dummy/bin/rails +1 -1
  45. data/spec/dummy/config/application.rb +1 -2
  46. data/spec/dummy/config/boot.rb +2 -2
  47. data/spec/dummy/config/environment.rb +1 -1
  48. data/spec/factories/helena/surveys.rb +1 -1
  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. data/test_all_gemfiles.sh +7 -0
  56. metadata +72 -131
  57. data/.coveralls.yml +0 -2
  58. data/Appraisals +0 -12
  59. data/app/assets/stylesheets/helena/forms.css.sass +0 -13
  60. data/app/assets/stylesheets/helena/layout.css.sass +0 -3
  61. data/app/assets/stylesheets/helena/question_groups.css.sass +0 -4
  62. data/app/views/helena/questions/_bipolar_radio_matrix.html.haml +0 -56
  63. data/app/views/helena/questions/_long_text.html.haml +0 -8
  64. data/app/views/helena/questions/_radio_matrix.html.haml +0 -53
  65. data/app/views/helena/questions/_short_text.html.haml +0 -8
  66. data/config/initializers/simple_form_bootstrap.rb +0 -132
data/README.md CHANGED
@@ -1,12 +1,11 @@
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
11
  * Rails (4.2 or higher)
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
@@ -26,7 +26,6 @@ module Helena
26
26
  def answers_as_yaml=(yaml)
27
27
  parsed_answers = YAML.safe_load yaml
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.
@@ -5,6 +5,7 @@ en:
5
5
  next: Next
6
6
  save: Save
7
7
  back: Back
8
+ question_group_legend: Page %{page}
8
9
 
9
10
  end_message:
10
11
  message: Thank you for participating.