decidim-surveys 0.10.1 → 0.11.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/app/assets/config/decidim_surveys_manifest.js +1 -0
  4. data/app/assets/javascripts/decidim/surveys/admin/auto_buttons_by_min_items.component.js.es6 +25 -0
  5. data/app/assets/javascripts/decidim/surveys/admin/auto_select_options_by_total_items.component.js.es6 +23 -0
  6. data/app/assets/javascripts/decidim/surveys/admin/surveys.js.es6 +146 -43
  7. data/app/assets/javascripts/decidim/surveys/autosortable_checkboxes.component.js.es6 +65 -0
  8. data/app/assets/javascripts/decidim/surveys/option_attached_inputs.component.js.es6 +32 -0
  9. data/app/assets/javascripts/decidim/surveys/surveys.js.es6 +20 -0
  10. data/app/assets/stylesheets/decidim/surveys/surveys.scss +20 -3
  11. data/app/commands/decidim/surveys/admin/update_survey.rb +36 -15
  12. data/app/commands/decidim/surveys/answer_survey.rb +12 -1
  13. data/app/commands/decidim/surveys/create_survey.rb +5 -5
  14. data/app/controllers/decidim/surveys/admin/application_controller.rb +2 -2
  15. data/app/controllers/decidim/surveys/admin/surveys_controller.rb +3 -3
  16. data/app/controllers/decidim/surveys/application_controller.rb +2 -2
  17. data/app/controllers/decidim/surveys/surveys_controller.rb +1 -1
  18. data/app/forms/decidim/surveys/admin/survey_answer_option_form.rb +23 -0
  19. data/app/forms/decidim/surveys/admin/survey_form.rb +7 -1
  20. data/app/forms/decidim/surveys/admin/survey_question_form.rb +12 -2
  21. data/app/forms/decidim/surveys/survey_answer_choice_form.rb +15 -0
  22. data/app/forms/decidim/surveys/survey_answer_form.rb +40 -6
  23. data/app/forms/decidim/surveys/survey_form.rb +1 -1
  24. data/app/helpers/decidim/surveys/admin/application_helper.rb +3 -19
  25. data/app/jobs/decidim/surveys/settings_change_job.rb +4 -4
  26. data/app/models/decidim/surveys/abilities/current_user_ability.rb +8 -8
  27. data/app/models/decidim/surveys/abilities/participatory_process_admin_ability.rb +1 -1
  28. data/app/models/decidim/surveys/survey.rb +2 -2
  29. data/app/models/decidim/surveys/survey_answer.rb +9 -1
  30. data/app/models/decidim/surveys/survey_answer_choice.rb +15 -0
  31. data/app/models/decidim/surveys/survey_answer_option.rb +9 -0
  32. data/app/models/decidim/surveys/survey_question.rb +21 -5
  33. data/app/views/decidim/surveys/admin/surveys/_answer_option.html.erb +28 -11
  34. data/app/views/decidim/surveys/admin/surveys/_form.html.erb +18 -14
  35. data/app/views/decidim/surveys/admin/surveys/_question.html.erb +74 -31
  36. data/app/views/decidim/surveys/admin/surveys/edit.html.erb +1 -1
  37. data/app/views/decidim/surveys/surveys/_answer.html.erb +94 -0
  38. data/app/views/decidim/surveys/surveys/show.html.erb +15 -42
  39. data/config/locales/ca.yml +33 -13
  40. data/config/locales/en.yml +33 -13
  41. data/config/locales/es.yml +33 -13
  42. data/config/locales/eu.yml +33 -13
  43. data/config/locales/fi.yml +33 -13
  44. data/config/locales/fr.yml +33 -13
  45. data/config/locales/gl.yml +33 -13
  46. data/config/locales/it.yml +33 -13
  47. data/config/locales/nl.yml +36 -16
  48. data/config/locales/pl.yml +33 -13
  49. data/config/locales/pt-BR.yml +33 -13
  50. data/config/locales/pt.yml +33 -13
  51. data/config/locales/ru.yml +0 -6
  52. data/config/locales/sv.yml +33 -13
  53. data/config/locales/uk.yml +0 -6
  54. data/db/migrate/20180305133837_rename_features_to_components_at_surveys.rb +11 -0
  55. data/db/migrate/20180314225829_add_max_choices_to_survey_questions.rb +7 -0
  56. data/db/migrate/20180321141024_add_description_to_decidim_survey_questions.rb +7 -0
  57. data/db/migrate/20180405014929_add_choices_to_decidim_survey_answers.rb +48 -0
  58. data/db/migrate/20180405015012_create_decidim_survey_answer_options.rb +43 -0
  59. data/db/migrate/20180405015147_create_decidim_survey_answer_choices.rb +61 -0
  60. data/db/migrate/20180405015258_add_free_text_to_survey_answer_options.rb +7 -0
  61. data/db/migrate/20180405015314_add_custom_body_to_survey_answer_choices.rb +7 -0
  62. data/db/migrate/20180406201818_add_position_to_decidim_survey_answer_choices.rb +7 -0
  63. data/lib/decidim/surveys.rb +1 -1
  64. data/lib/decidim/surveys/admin_engine.rb +0 -2
  65. data/lib/decidim/surveys/{feature.rb → component.rb} +38 -26
  66. data/lib/decidim/surveys/engine.rb +5 -1
  67. data/lib/decidim/surveys/test/factories.rb +22 -6
  68. data/lib/decidim/surveys/version.rb +1 -1
  69. metadata +33 -30
  70. data/app/assets/javascripts/decidim/surveys/admin/auto_label_by_position.component.js.es6 +0 -33
  71. data/app/assets/javascripts/decidim/surveys/admin/dynamic_fields.component.js.es6 +0 -95
  72. data/app/forms/decidim/surveys/admin/survey_question_answer_option_form.rb +0 -15
@@ -33,24 +33,45 @@ module Decidim
33
33
 
34
34
  def update_survey_questions
35
35
  @form.questions.each do |form_question|
36
- question_attributes = {
37
- body: form_question.body,
38
- position: form_question.position,
39
- mandatory: form_question.mandatory,
40
- question_type: form_question.question_type,
41
- answer_options: form_question.answer_options.map { |answer| { "body" => answer.body } }
42
- }
36
+ update_survey_question(form_question)
37
+ end
38
+ end
39
+
40
+ def update_survey_question(form_question)
41
+ question_attributes = {
42
+ body: form_question.body,
43
+ description: form_question.description,
44
+ position: form_question.position,
45
+ mandatory: form_question.mandatory,
46
+ question_type: form_question.question_type,
47
+ max_choices: form_question.max_choices
48
+ }
49
+
50
+ update_nested_model(form_question, question_attributes, @survey.questions) do |question|
51
+ form_question.answer_options.each do |form_answer_option|
52
+ answer_option_attributes = {
53
+ body: form_answer_option.body,
54
+ free_text: form_answer_option.free_text
55
+ }
56
+
57
+ update_nested_model(form_answer_option, answer_option_attributes, question.answer_options)
58
+ end
59
+ end
60
+ end
61
+
62
+ def update_nested_model(form, attributes, parent_association)
63
+ record = parent_association.find_by(id: form.id) || parent_association.build(attributes)
64
+
65
+ yield record if block_given?
43
66
 
44
- if form_question.id.present?
45
- question = @survey.questions.where(id: form_question.id).first
46
- if form_question.deleted?
47
- question.destroy!
48
- else
49
- question.update!(question_attributes)
50
- end
67
+ if record.persisted?
68
+ if form.deleted?
69
+ record.destroy!
51
70
  else
52
- @survey.questions.create!(question_attributes)
71
+ record.update!(attributes)
53
72
  end
73
+ else
74
+ record.save!
54
75
  end
55
76
  end
56
77
 
@@ -29,12 +29,23 @@ module Decidim
29
29
  def answer_survey
30
30
  SurveyAnswer.transaction do
31
31
  @form.answers.each do |form_answer|
32
- SurveyAnswer.create!(
32
+ answer = SurveyAnswer.new(
33
33
  user: @current_user,
34
34
  survey: @survey,
35
35
  question: form_answer.question,
36
36
  body: form_answer.body
37
37
  )
38
+
39
+ form_answer.selected_choices.each do |choice|
40
+ answer.choices.build(
41
+ body: choice.body,
42
+ custom_body: choice.custom_body,
43
+ decidim_survey_answer_option_id: choice.answer_option_id,
44
+ position: choice.position
45
+ )
46
+ end
47
+
48
+ answer.save!
38
49
  end
39
50
  end
40
51
  end
@@ -2,15 +2,15 @@
2
2
 
3
3
  module Decidim
4
4
  module Surveys
5
- # Command that gets called whenever a feature's survey has to be created. It
6
- # usually happens as a callback when the feature itself is created.
5
+ # Command that gets called whenever a component's survey has to be created. It
6
+ # usually happens as a callback when the component itself is created.
7
7
  class CreateSurvey < Rectify::Command
8
- def initialize(feature)
9
- @feature = feature
8
+ def initialize(component)
9
+ @component = component
10
10
  end
11
11
 
12
12
  def call
13
- @survey = Survey.new(feature: @feature)
13
+ @survey = Survey.new(component: @component)
14
14
 
15
15
  @survey.save ? broadcast(:ok) : broadcast(:invalid)
16
16
  end
@@ -6,9 +6,9 @@ module Decidim
6
6
  # This controller is the abstract class from which all other controllers of
7
7
  # this engine inherit.
8
8
  #
9
- # Note that it inherits from `Decidim::Admin::Features::BaseController`, which
9
+ # Note that it inherits from `Decidim::Admin::Components::BaseController`, which
10
10
  # override its layout and provide all kinds of useful methods.
11
- class ApplicationController < Decidim::Admin::Features::BaseController
11
+ class ApplicationController < Decidim::Admin::Components::BaseController
12
12
  end
13
13
  end
14
14
  end
@@ -33,15 +33,15 @@ module Decidim
33
33
  private
34
34
 
35
35
  def survey
36
- @survey ||= Survey.find_by(feature: current_feature)
36
+ @survey ||= Survey.find_by(component: current_component)
37
37
  end
38
38
 
39
39
  def blank_question
40
- @blank_question ||= survey.questions.build(body: {}, answer_options: [])
40
+ @blank_question ||= Admin::SurveyQuestionForm.new
41
41
  end
42
42
 
43
43
  def blank_answer_option
44
- @blank_answer_option ||= OpenStruct.new(body: {})
44
+ @blank_answer_option ||= Admin::SurveyAnswerOptionForm.new
45
45
  end
46
46
 
47
47
  def question_types
@@ -5,9 +5,9 @@ module Decidim
5
5
  # This controller is the abstract class from which all other controllers of
6
6
  # this engine inherit.
7
7
  #
8
- # Note that it inherits from `Decidim::Features::BaseController`, which
8
+ # Note that it inherits from `Decidim::Components::BaseController`, which
9
9
  # override its layout and provide all kinds of useful methods.
10
- class ApplicationController < Decidim::Features::BaseController
10
+ class ApplicationController < Decidim::Components::BaseController
11
11
  end
12
12
  end
13
13
  end
@@ -32,7 +32,7 @@ module Decidim
32
32
  private
33
33
 
34
34
  def survey
35
- @survey ||= Survey.find_by(feature: current_feature)
35
+ @survey ||= Survey.find_by(component: current_component)
36
36
  end
37
37
  end
38
38
  end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Surveys
5
+ module Admin
6
+ # This class holds a Form to update survey question answer options
7
+ class SurveyAnswerOptionForm < Decidim::Form
8
+ include TranslatableAttributes
9
+
10
+ attribute :deleted, Boolean, default: false
11
+ attribute :free_text, Boolean
12
+
13
+ translatable_attribute :body, String
14
+
15
+ validates :body, translatable_presence: true, unless: :deleted
16
+
17
+ def to_param
18
+ id || "survey-question-answer-option-id"
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -10,11 +10,17 @@ module Decidim
10
10
  translatable_attribute :title, String
11
11
  translatable_attribute :description, String
12
12
  translatable_attribute :tos, String
13
- attribute :published_at, DateTime
14
13
 
14
+ attribute :published_at, DateTime
15
15
  attribute :questions, Array[SurveyQuestionForm]
16
16
 
17
17
  validates :title, :tos, translatable_presence: true
18
+
19
+ def map_model(model)
20
+ self.questions = model.questions.map do |question|
21
+ SurveyQuestionForm.from_model(question)
22
+ end
23
+ end
18
24
  end
19
25
  end
20
26
  end
@@ -7,18 +7,28 @@ module Decidim
7
7
  class SurveyQuestionForm < Decidim::Form
8
8
  include TranslatableAttributes
9
9
 
10
- attribute :id, String
11
10
  attribute :position, Integer
12
11
  attribute :mandatory, Boolean, default: false
13
12
  attribute :question_type, String
14
- attribute :answer_options, Array[SurveyQuestionAnswerOptionForm]
13
+ attribute :answer_options, Array[SurveyAnswerOptionForm]
14
+ attribute :max_choices, Integer
15
15
  attribute :deleted, Boolean, default: false
16
16
 
17
17
  translatable_attribute :body, String
18
+ translatable_attribute :description, String
18
19
 
19
20
  validates :position, numericality: { greater_than_or_equal_to: 0 }
20
21
  validates :question_type, inclusion: { in: SurveyQuestion::TYPES }
22
+ validates :max_choices, numericality: { only_integer: true, greater_than: 1, less_than_or_equal_to: ->(form) { form.number_of_options } }, allow_blank: true
21
23
  validates :body, translatable_presence: true, unless: :deleted
24
+
25
+ def to_param
26
+ id || "survey-question-id"
27
+ end
28
+
29
+ def number_of_options
30
+ answer_options.size
31
+ end
22
32
  end
23
33
  end
24
34
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Surveys
5
+ # This class holds a Form to update survey question answer options
6
+ class SurveyAnswerChoiceForm < Decidim::Form
7
+ attribute :body, String
8
+ attribute :custom_body, String
9
+ attribute :position, Integer
10
+ attribute :answer_option_id, Integer
11
+
12
+ validates :answer_option_id, presence: true
13
+ end
14
+ end
15
+ end
@@ -4,32 +4,66 @@ module Decidim
4
4
  module Surveys
5
5
  # This class holds a Form to update survey unswers from Decidim's public page
6
6
  class SurveyAnswerForm < Decidim::Form
7
+ include Decidim::TranslationsHelper
8
+
7
9
  attribute :question_id, String
8
10
  attribute :body, String
11
+ attribute :choices, Array[SurveyAnswerChoiceForm]
12
+
13
+ validates :body, presence: true, if: :mandatory_body?
14
+ validates :selected_choices, presence: true, if: :mandatory_choices?
9
15
 
10
- validates :body, presence: true, if: -> { question.mandatory? }
11
- validate :body_not_blank, if: -> { question.mandatory? }
16
+ validate :max_choices, if: -> { question.max_choices }
17
+ validate :all_choices, if: -> { question.question_type == "sorting" }
18
+
19
+ delegate :mandatory_body?, :mandatory_choices?, to: :question
12
20
 
13
21
  def question
14
22
  @question ||= survey.questions.find(question_id)
15
23
  end
16
24
 
25
+ def label(idx)
26
+ base = "#{idx + 1}. #{translated_attribute(question.body)}"
27
+ base += " #{mandatory_label}" if question.mandatory?
28
+ base += " (#{max_choices_label})" if question.max_choices
29
+ base
30
+ end
31
+
17
32
  # Public: Map the correct fields.
18
33
  #
19
34
  # Returns nothing.
20
35
  def map_model(model)
21
36
  self.question_id = model.decidim_survey_question_id
37
+
38
+ self.choices = model.choices.map do |choice|
39
+ SurveyAnswerChoiceForm.from_model(choice)
40
+ end
41
+ end
42
+
43
+ def selected_choices
44
+ choices.select(&:body)
22
45
  end
23
46
 
24
47
  private
25
48
 
26
49
  def survey
27
- @survey ||= Survey.where(feature: current_feature).first
50
+ @survey ||= Survey.find_by(component: current_component)
51
+ end
52
+
53
+ def max_choices
54
+ errors.add(:choices, :too_many) if selected_choices.size > question.max_choices
55
+ end
56
+
57
+ def all_choices
58
+ errors.add(:choices, :missing) if selected_choices.size != question.number_of_options
59
+ end
60
+
61
+ def mandatory_label
62
+ "*"
28
63
  end
29
64
 
30
- def body_not_blank
31
- return if body.nil?
32
- errors.add("body", :blank) if body.all?(&:blank?)
65
+ def max_choices_label
66
+ I18n.t("surveys.question.max_choices", scope: "decidim.surveys", n: question.max_choices)
33
67
  end
34
68
  end
35
69
  end
@@ -14,7 +14,7 @@ module Decidim
14
14
  # Returns nothing.
15
15
  def map_model(model)
16
16
  self.answers = model.questions.map do |question|
17
- SurveyAnswerForm.from_params(question_id: question.id)
17
+ SurveyAnswerForm.from_model(SurveyAnswer.new(question: question))
18
18
  end
19
19
  end
20
20
  end
@@ -7,27 +7,11 @@ module Decidim
7
7
  #
8
8
  module ApplicationHelper
9
9
  def tabs_id_for_question(question)
10
- return "survey-question-#{question.id}" if question.persisted?
11
- "${tabsId}"
10
+ "survey_question_#{question.to_param}"
12
11
  end
13
12
 
14
- def tabs_id_for_question_answer_option(question, idx)
15
- return "survey-question-answer-option-#{question.id}-#{idx}" if question.present?
16
- "${tabsId}"
17
- end
18
-
19
- def mandatory_id_for_question(question)
20
- return "survey_questions_#{question.id}_mandatory" if question.persisted?
21
- "${tabsId}_mandatory"
22
- end
23
-
24
- def question_type_id_for_question(question)
25
- return "survey_questions_#{question.id}_question_type" if question.persisted?
26
- "${tabsId}_question_type"
27
- end
28
-
29
- def disabled_for_question(survey, question)
30
- !question.persisted? || !survey.questions_editable?
13
+ def tabs_id_for_question_answer_option(question, answer_option)
14
+ "survey_question_#{question.to_param}_answer_option_#{answer_option.to_param}"
31
15
  end
32
16
  end
33
17
  end
@@ -3,10 +3,10 @@
3
3
  module Decidim
4
4
  module Surveys
5
5
  class SettingsChangeJob < ApplicationJob
6
- def perform(feature_id, previous_settings, current_settings)
6
+ def perform(component_id, previous_settings, current_settings)
7
7
  return if unchanged?(previous_settings, current_settings)
8
8
 
9
- feature = Decidim::Feature.find(feature_id)
9
+ component = Decidim::Component.find(component_id)
10
10
 
11
11
  if survey_opened?(previous_settings, current_settings)
12
12
  event = "decidim.events.surveys.survey_opened"
@@ -19,8 +19,8 @@ module Decidim
19
19
  Decidim::EventsManager.publish(
20
20
  event: event,
21
21
  event_class: event_class,
22
- resource: feature,
23
- recipient_ids: feature.participatory_space.followers.pluck(:id)
22
+ resource: component,
23
+ recipient_ids: component.participatory_space.followers.pluck(:id)
24
24
  )
25
25
  end
26
26
 
@@ -22,24 +22,24 @@ module Decidim
22
22
  private
23
23
 
24
24
  def authorized?(action)
25
- return unless feature
25
+ return unless component
26
26
 
27
- ActionAuthorizer.new(user, feature, action).authorize.ok?
27
+ ActionAuthorizer.new(user, component, action).authorize.ok?
28
28
  end
29
29
 
30
30
  def current_settings
31
31
  context.fetch(:current_settings, nil)
32
32
  end
33
33
 
34
- def feature_settings
35
- context.fetch(:feature_settings, nil)
34
+ def component_settings
35
+ context.fetch(:component_settings, nil)
36
36
  end
37
37
 
38
- def feature
39
- feature = context.fetch(:current_feature, nil)
40
- return nil unless feature && feature.manifest.name == :surveys
38
+ def component
39
+ component = context.fetch(:current_component, nil)
40
+ return nil unless component && component.manifest.name == :surveys
41
41
 
42
- feature
42
+ component
43
43
  end
44
44
  end
45
45
  end
@@ -10,7 +10,7 @@ module Decidim
10
10
  super
11
11
 
12
12
  can :manage, Survey do |survey|
13
- can_manage_process?(survey.feature.participatory_space)
13
+ can_manage_process?(survey.component.participatory_space)
14
14
  end
15
15
  end
16
16
  end
@@ -4,9 +4,9 @@ module Decidim
4
4
  module Surveys
5
5
  # The data store for a Survey in the Decidim::Surveys component.
6
6
  class Survey < Surveys::ApplicationRecord
7
- include Decidim::HasFeature
7
+ include Decidim::HasComponent
8
8
 
9
- feature_manifest_name "surveys"
9
+ component_manifest_name "surveys"
10
10
 
11
11
  has_many :questions, -> { order(:position) }, class_name: "SurveyQuestion", foreign_key: "decidim_survey_id", dependent: :destroy
12
12
  has_many :answers, class_name: "SurveyAnswer", foreign_key: "decidim_survey_id", dependent: :destroy