decidim-forms 0.19.1 → 0.23.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/app/assets/config/admin/decidim_forms_manifest.css +3 -0
- data/app/assets/config/admin/decidim_forms_manifest.js +1 -0
- data/app/assets/config/decidim_forms_manifest.css +1 -0
- data/app/assets/images/decidim/surveys/icon.svg +1 -19
- data/app/assets/javascripts/decidim/forms/admin/auto_select_options_from_url.component.js.es6 +40 -0
- data/app/assets/javascripts/decidim/forms/admin/collapsible_questions.js.es6 +13 -0
- data/app/assets/javascripts/decidim/forms/admin/forms.js.es6 +260 -16
- data/app/assets/javascripts/decidim/forms/admin/live_text_update.component.js.es6 +52 -0
- data/app/assets/javascripts/decidim/forms/autosortable_checkboxes.component.js.es6 +54 -34
- data/app/assets/javascripts/decidim/forms/display_conditions.component.js.es6 +204 -0
- data/app/assets/javascripts/decidim/forms/forms.js.es6 +49 -1
- data/app/assets/javascripts/decidim/forms/max_choices_alert.component.js.es6 +44 -0
- data/app/assets/stylesheets/decidim/forms/forms.scss +39 -0
- data/app/assets/stylesheets/decidim/forms/questionnaire-answers-pdf.scss +69 -0
- data/app/cells/decidim/forms/matrix_readonly/show.erb +5 -0
- data/app/cells/decidim/forms/matrix_readonly_cell.rb +12 -0
- data/app/cells/decidim/forms/question_readonly/show.erb +5 -1
- data/app/cells/decidim/forms/question_readonly_cell.rb +5 -0
- data/app/cells/decidim/forms/step_navigation/show.erb +35 -0
- data/app/cells/decidim/forms/step_navigation_cell.rb +46 -0
- data/app/commands/decidim/forms/admin/update_questionnaire.rb +33 -1
- data/app/commands/decidim/forms/answer_questionnaire.rb +7 -2
- data/app/controllers/decidim/forms/admin/concerns/has_questionnaire.rb +52 -2
- data/app/controllers/decidim/forms/admin/concerns/has_questionnaire_answers.rb +97 -0
- data/app/controllers/decidim/forms/concerns/has_questionnaire.rb +58 -2
- data/app/forms/decidim/forms/admin/display_condition_form.rb +100 -0
- data/app/forms/decidim/forms/admin/question_form.rb +21 -1
- data/app/forms/decidim/forms/admin/question_matrix_row_form.rb +26 -0
- data/app/forms/decidim/forms/answer_choice_form.rb +1 -0
- data/app/forms/decidim/forms/answer_form.rb +31 -2
- data/app/forms/decidim/forms/questionnaire_form.rb +33 -3
- data/app/helpers/decidim/forms/admin/application_helper.rb +37 -0
- data/app/helpers/decidim/forms/admin/concerns/has_questionnaire_answers_pagination_helper.rb +49 -0
- data/app/helpers/decidim/forms/admin/concerns/has_questionnaire_answers_url_helper.rb +40 -0
- data/app/helpers/decidim/forms/admin/questionnaire_answers_helper.rb +27 -0
- data/app/jobs/decidim/forms/export_questionnaire_answers_job.rb +19 -0
- data/app/models/decidim/forms/answer.rb +2 -5
- data/app/models/decidim/forms/answer_choice.rb +7 -0
- data/app/models/decidim/forms/answer_option.rb +14 -0
- data/app/models/decidim/forms/display_condition.rb +51 -0
- data/app/models/decidim/forms/question.rb +52 -2
- data/app/models/decidim/forms/question_matrix_row.rb +15 -0
- data/app/models/decidim/forms/questionnaire.rb +13 -2
- data/app/presenters/decidim/forms/admin/questionnaire_answer_presenter.rb +43 -0
- data/app/presenters/decidim/forms/admin/questionnaire_participant_presenter.rb +60 -0
- data/app/presenters/decidim/forms/answer_option_presenter.rb +20 -0
- data/app/presenters/decidim/forms/question_presenter.rb +16 -0
- data/app/queries/decidim/forms/questionnaire_participant.rb +35 -0
- data/app/queries/decidim/forms/questionnaire_participants.rb +43 -0
- data/app/queries/decidim/forms/questionnaire_user_answers.rb +1 -1
- data/app/types/decidim/forms/answer_option_type.rb +14 -0
- data/app/types/decidim/forms/question_type.rb +23 -0
- data/app/types/decidim/forms/questionnaire_type.rb +22 -0
- data/app/views/decidim/forms/admin/questionnaires/_answer_option_template.html.erb +1 -1
- data/app/views/decidim/forms/admin/questionnaires/_display_condition.html.erb +88 -0
- data/app/views/decidim/forms/admin/questionnaires/_display_condition_template.html.erb +7 -0
- data/app/views/decidim/forms/admin/questionnaires/_form.html.erb +67 -8
- data/app/views/decidim/forms/admin/questionnaires/_matrix_row.html.erb +34 -0
- data/app/views/decidim/forms/admin/questionnaires/_matrix_row_template.html.erb +7 -0
- data/app/views/decidim/forms/admin/questionnaires/_question.html.erb +45 -6
- data/app/views/decidim/forms/admin/questionnaires/_separator.html.erb +41 -0
- data/app/views/decidim/forms/admin/questionnaires/answers/export/_answer.html.erb +31 -0
- data/app/views/decidim/forms/admin/questionnaires/answers/export/pdf.html.erb +13 -0
- data/app/views/decidim/forms/admin/questionnaires/answers/index.html.erb +53 -0
- data/app/views/decidim/forms/admin/questionnaires/answers/show.html.erb +48 -0
- data/app/views/decidim/forms/admin/questionnaires/edit.html.erb +9 -5
- data/app/views/decidim/forms/questionnaires/_answer.html.erb +28 -99
- data/app/views/decidim/forms/questionnaires/answers/_long_answer.html.erb +1 -0
- data/app/views/decidim/forms/questionnaires/answers/_matrix_multiple.html.erb +43 -0
- data/app/views/decidim/forms/questionnaires/answers/_matrix_single.html.erb +43 -0
- data/app/views/decidim/forms/questionnaires/answers/_multiple_option.html.erb +23 -0
- data/app/views/decidim/forms/questionnaires/answers/_separator.html.erb +1 -0
- data/app/views/decidim/forms/questionnaires/answers/_short_answer.html.erb +1 -0
- data/app/views/decidim/forms/questionnaires/answers/_single_option.html.erb +30 -0
- data/app/views/decidim/forms/questionnaires/answers/_sorting.html.erb +23 -0
- data/app/views/decidim/forms/questionnaires/show.html.erb +81 -28
- data/app/views/layouts/decidim/forms/admin/questionnaires/questionnaire_answers.html.erb +12 -0
- data/config/initializers/wicked_pdf.rb +25 -0
- data/config/locales/am-ET.yml +1 -0
- data/config/locales/ar.yml +12 -4
- data/config/locales/bg-BG.yml +14 -0
- data/config/locales/bg.yml +14 -0
- data/config/locales/ca.yml +98 -8
- data/config/locales/cs.yml +94 -4
- data/config/locales/da-DK.yml +1 -0
- data/config/locales/da.yml +1 -0
- data/config/locales/de.yml +92 -4
- data/config/locales/el-GR.yml +1 -0
- data/config/locales/el.yml +118 -0
- data/config/locales/en.yml +94 -4
- data/config/locales/eo.yml +1 -0
- data/config/locales/es-MX.yml +94 -4
- data/config/locales/es-PY.yml +94 -4
- data/config/locales/es.yml +94 -4
- data/config/locales/et-EE.yml +1 -0
- data/config/locales/et.yml +1 -0
- data/config/locales/eu.yml +7 -4
- data/config/locales/fi-plain.yml +94 -4
- data/config/locales/fi.yml +94 -4
- data/config/locales/fr-CA.yml +172 -0
- data/config/locales/fr.yml +94 -4
- data/config/locales/ga-IE.yml +1 -0
- data/config/locales/gl.yml +7 -4
- data/config/locales/hr-HR.yml +1 -0
- data/config/locales/hr.yml +1 -0
- data/config/locales/hu.yml +16 -4
- data/config/locales/id-ID.yml +7 -4
- data/config/locales/is-IS.yml +1 -0
- data/config/locales/is.yml +1 -0
- data/config/locales/it.yml +95 -5
- data/config/locales/ja-JP.yml +170 -0
- data/config/locales/ja.yml +170 -0
- data/config/locales/ko-KR.yml +1 -0
- data/config/locales/ko.yml +1 -0
- data/config/locales/lt-LT.yml +1 -0
- data/config/locales/lt.yml +1 -0
- data/config/locales/lv.yml +118 -0
- data/config/locales/mt-MT.yml +1 -0
- data/config/locales/mt.yml +1 -0
- data/config/locales/nl.yml +97 -7
- data/config/locales/no.yml +98 -1
- data/config/locales/om-ET.yml +1 -0
- data/config/locales/pl.yml +113 -26
- data/config/locales/pt-BR.yml +8 -5
- data/config/locales/pt.yml +111 -24
- data/config/locales/ro-RO.yml +167 -0
- data/config/locales/ru.yml +4 -2
- data/config/locales/sk-SK.yml +88 -0
- data/config/locales/sk.yml +90 -0
- data/config/locales/sl.yml +12 -0
- data/config/locales/so-SO.yml +1 -0
- data/config/locales/sr-CS.yml +1 -0
- data/config/locales/sv.yml +91 -4
- data/config/locales/ti-ER.yml +1 -0
- data/config/locales/tr-TR.yml +7 -4
- data/config/locales/vi-VN.yml +1 -0
- data/config/locales/vi.yml +1 -0
- data/config/locales/zh-CN.yml +172 -0
- data/config/locales/zh-TW.yml +1 -0
- data/db/migrate/20190315203056_add_session_token_to_decidim_forms_answers.rb +17 -0
- data/db/migrate/20190930094710_add_ip_hash_to_decidim_form_answers.rb +12 -0
- data/db/migrate/20200130194123_create_decidim_forms_display_conditions.rb +20 -0
- data/db/migrate/20200225123810_create_decidim_forms_question_matrix_rows.rb +11 -0
- data/db/migrate/20200304152939_add_matrix_row_id_to_decidim_forms_answer_choices.rb +11 -0
- data/lib/decidim/api/questionnaire_entity_interface.rb +18 -0
- data/lib/decidim/exporters/form_pdf.rb +33 -0
- data/lib/decidim/exporters/form_pdf_controller_helper.rb +11 -0
- data/lib/decidim/forms.rb +6 -0
- data/lib/decidim/forms/admin_engine.rb +1 -1
- data/lib/decidim/forms/api.rb +7 -0
- data/lib/decidim/forms/test.rb +6 -0
- data/lib/decidim/forms/test/factories.rb +80 -6
- data/lib/decidim/forms/test/shared_examples/has_questionnaire.rb +924 -66
- data/lib/decidim/forms/test/shared_examples/manage_questionnaire_answers.rb +108 -0
- data/lib/decidim/forms/test/shared_examples/manage_questionnaires.rb +33 -575
- data/lib/decidim/forms/test/shared_examples/manage_questionnaires/add_display_conditions.rb +179 -0
- data/lib/decidim/forms/test/shared_examples/manage_questionnaires/add_questions.rb +463 -0
- data/lib/decidim/forms/test/shared_examples/manage_questionnaires/update_display_conditions.rb +93 -0
- data/lib/decidim/forms/test/shared_examples/manage_questionnaires/update_questions.rb +461 -0
- data/lib/decidim/forms/user_answers_serializer.rb +23 -4
- data/lib/decidim/forms/version.rb +1 -1
- metadata +136 -9
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Forms
|
5
|
+
module Admin
|
6
|
+
module Concerns
|
7
|
+
# Url helper for HasQuestionnaireAnswers controller concern
|
8
|
+
#
|
9
|
+
module HasQuestionnaireAnswersUrlHelper
|
10
|
+
def self.included(base)
|
11
|
+
base.helper_method :questionnaire_url, :questionnaire_participants_url,
|
12
|
+
:questionnaire_participant_answers_url, :questionnaire_export_response_url
|
13
|
+
end
|
14
|
+
|
15
|
+
# You can implement this method in your controller to change the URL
|
16
|
+
# where the questionnaire can be edited.
|
17
|
+
def questionnaire_url
|
18
|
+
url_for(questionnaire.questionnaire_for)
|
19
|
+
end
|
20
|
+
|
21
|
+
# You can implement this method in your controller to change the URL
|
22
|
+
# where the questionnaire participants' info will be shown.
|
23
|
+
def questionnaire_participants_url
|
24
|
+
url_for([:index, questionnaire.questionnaire_for, format: nil])
|
25
|
+
end
|
26
|
+
|
27
|
+
# You can implement this method in your controller to change the URL
|
28
|
+
# where the user's questionnaire answers will be shown.
|
29
|
+
def questionnaire_participant_answers_url(session_token)
|
30
|
+
url_for([:show, questionnaire.questionnaire_for, session_token: session_token])
|
31
|
+
end
|
32
|
+
|
33
|
+
def questionnaire_export_response_url(session_token)
|
34
|
+
url_for([:export_response, questionnaire.questionnaire_for, session_token: session_token, format: "pdf"])
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Forms
|
5
|
+
module Admin
|
6
|
+
# Custom helpers for questionnaire answers
|
7
|
+
#
|
8
|
+
module QuestionnaireAnswersHelper
|
9
|
+
def first_table_th(answer)
|
10
|
+
return translated_attribute answer.first_short_answer.question.body if answer.first_short_answer
|
11
|
+
|
12
|
+
t("session_token", scope: "decidim.forms.user_answers_serializer")
|
13
|
+
end
|
14
|
+
|
15
|
+
def first_table_td(answer)
|
16
|
+
return answer.first_short_answer.body if answer.first_short_answer
|
17
|
+
|
18
|
+
answer.session_token
|
19
|
+
end
|
20
|
+
|
21
|
+
def display_percentage(number)
|
22
|
+
number_to_percentage(number, precision: 0, strip_insignificant_zeros: true, locale: I18n.locale)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Forms
|
5
|
+
class ExportQuestionnaireAnswersJob < ApplicationJob
|
6
|
+
queue_as :default
|
7
|
+
|
8
|
+
def perform(user, title, answers)
|
9
|
+
return if user&.email.blank?
|
10
|
+
return if answers&.blank?
|
11
|
+
|
12
|
+
serializer = Decidim::Forms::UserAnswersSerializer
|
13
|
+
export_data = Decidim::Exporters::FormPDF.new(answers, serializer).export
|
14
|
+
|
15
|
+
ExportMailer.export(user, title, export_data).deliver_now
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -7,7 +7,7 @@ module Decidim
|
|
7
7
|
include Decidim::DataPortability
|
8
8
|
include Decidim::NewsletterParticipant
|
9
9
|
|
10
|
-
belongs_to :user, class_name: "Decidim::User", foreign_key: "decidim_user_id"
|
10
|
+
belongs_to :user, class_name: "Decidim::User", foreign_key: "decidim_user_id", optional: true
|
11
11
|
belongs_to :questionnaire, class_name: "Questionnaire", foreign_key: "decidim_questionnaire_id"
|
12
12
|
belongs_to :question, class_name: "Question", foreign_key: "decidim_question_id"
|
13
13
|
|
@@ -17,9 +17,6 @@ module Decidim
|
|
17
17
|
dependent: :destroy,
|
18
18
|
inverse_of: :answer
|
19
19
|
|
20
|
-
validates :body, presence: true, if: -> { question.mandatory_body? }
|
21
|
-
validates :choices, presence: true, if: -> { question.mandatory_choices? }
|
22
|
-
|
23
20
|
validate :user_questionnaire_same_organization
|
24
21
|
validate :question_belongs_to_questionnaire
|
25
22
|
|
@@ -45,7 +42,7 @@ module Decidim
|
|
45
42
|
private
|
46
43
|
|
47
44
|
def user_questionnaire_same_organization
|
48
|
-
return if user&.organization == questionnaire.questionnaire_for&.organization
|
45
|
+
return if user.nil? || user&.organization == questionnaire.questionnaire_for&.organization
|
49
46
|
|
50
47
|
errors.add(:user, :invalid)
|
51
48
|
end
|
@@ -10,6 +10,13 @@ module Decidim
|
|
10
10
|
belongs_to :answer_option,
|
11
11
|
class_name: "AnswerOption",
|
12
12
|
foreign_key: "decidim_answer_option_id"
|
13
|
+
|
14
|
+
belongs_to :matrix_row,
|
15
|
+
class_name: "QuestionMatrixRow",
|
16
|
+
foreign_key: "decidim_question_matrix_row_id",
|
17
|
+
optional: true
|
18
|
+
|
19
|
+
validates :matrix_row, presence: true, if: -> { answer.question.matrix? }
|
13
20
|
end
|
14
21
|
end
|
15
22
|
end
|
@@ -3,9 +3,23 @@
|
|
3
3
|
module Decidim
|
4
4
|
module Forms
|
5
5
|
class AnswerOption < Forms::ApplicationRecord
|
6
|
+
include Decidim::TranslatableResource
|
7
|
+
|
6
8
|
default_scope { order(arel_table[:id].asc) }
|
7
9
|
|
10
|
+
translatable_fields :body
|
11
|
+
|
8
12
|
belongs_to :question, class_name: "Question", foreign_key: "decidim_question_id"
|
13
|
+
|
14
|
+
has_many :display_conditions,
|
15
|
+
class_name: "DisplayCondition",
|
16
|
+
foreign_key: "decidim_answer_option_id",
|
17
|
+
dependent: :nullify,
|
18
|
+
inverse_of: :answer_option
|
19
|
+
|
20
|
+
def translated_body
|
21
|
+
Decidim::Forms::AnswerOptionPresenter.new(self).translated_body
|
22
|
+
end
|
9
23
|
end
|
10
24
|
end
|
11
25
|
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Forms
|
5
|
+
# The data store for a DisplayCondition in the Decidim::Forms component.
|
6
|
+
# A display condition is associated to two questions. :question is the question
|
7
|
+
# that we want to display or hide based on some conditions, and :condition_question
|
8
|
+
# is the question the answers of which are checked against the conditions.
|
9
|
+
# Conditions can be whether the question is answered ("answered") or it is not ("not_answered"),
|
10
|
+
# if the selected answer option is ("equal") or is not ("not_equal") a given one, or whethe
|
11
|
+
# the text value of the answer matches a string ("match").
|
12
|
+
class DisplayCondition < Forms::ApplicationRecord
|
13
|
+
enum condition_type: [:answered, :not_answered, :equal, :not_equal, :match], _prefix: true
|
14
|
+
|
15
|
+
# Question which will be displayed or hidden
|
16
|
+
belongs_to :question, class_name: "Question", foreign_key: "decidim_question_id", inverse_of: :display_conditions
|
17
|
+
|
18
|
+
# Question the answers of which are checked against conditions
|
19
|
+
belongs_to :condition_question, class_name: "Question", foreign_key: "decidim_condition_question_id", inverse_of: :display_conditions_for_other_questions
|
20
|
+
|
21
|
+
# Answer option provided to check for "equal" or "not_equal" (optional)
|
22
|
+
belongs_to :answer_option, class_name: "AnswerOption", foreign_key: "decidim_answer_option_id", optional: true
|
23
|
+
|
24
|
+
def fulfilled?(answer)
|
25
|
+
case condition_type
|
26
|
+
when "answered"
|
27
|
+
answer.present?
|
28
|
+
when "not_answered"
|
29
|
+
answer.blank?
|
30
|
+
when "equal"
|
31
|
+
answer.present? ? answer.choices.pluck(:decidim_answer_option_id).include?(answer_option.id) : false
|
32
|
+
when "not_equal"
|
33
|
+
answer.present? ? !answer.choices.pluck(:decidim_answer_option_id).include?(answer_option.id) : true
|
34
|
+
when "match"
|
35
|
+
answer.present? ? condition_value.values.any? { |value| answer.body.match?(Regexp.new(value, Regexp::IGNORECASE)) } : false
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_html_data
|
40
|
+
{
|
41
|
+
id: id,
|
42
|
+
type: condition_type,
|
43
|
+
condition: decidim_condition_question_id,
|
44
|
+
option: decidim_answer_option_id,
|
45
|
+
mandatory: mandatory,
|
46
|
+
value: condition_value&.dig(I18n.locale.to_s)
|
47
|
+
}.compact
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -4,20 +4,62 @@ module Decidim
|
|
4
4
|
module Forms
|
5
5
|
# The data store for a Question in the Decidim::Forms component.
|
6
6
|
class Question < Forms::ApplicationRecord
|
7
|
-
|
7
|
+
include Decidim::TranslatableResource
|
8
|
+
|
9
|
+
QUESTION_TYPES = %w(short_answer long_answer single_option multiple_option sorting matrix_single matrix_multiple).freeze
|
10
|
+
SEPARATOR_TYPE = "separator"
|
11
|
+
TYPES = (QUESTION_TYPES + [SEPARATOR_TYPE]).freeze
|
12
|
+
|
13
|
+
translatable_fields :body, :description
|
8
14
|
|
9
15
|
belongs_to :questionnaire, class_name: "Questionnaire", foreign_key: "decidim_questionnaire_id"
|
10
16
|
|
17
|
+
has_many :matrix_rows,
|
18
|
+
class_name: "QuestionMatrixRow",
|
19
|
+
foreign_key: "decidim_question_id",
|
20
|
+
dependent: :destroy,
|
21
|
+
inverse_of: :question
|
22
|
+
|
11
23
|
has_many :answer_options,
|
12
24
|
class_name: "AnswerOption",
|
13
25
|
foreign_key: "decidim_question_id",
|
14
26
|
dependent: :destroy,
|
15
27
|
inverse_of: :question
|
16
28
|
|
29
|
+
# Conditions to display this question in questionnaire
|
30
|
+
has_many :display_conditions,
|
31
|
+
class_name: "DisplayCondition",
|
32
|
+
foreign_key: "decidim_question_id",
|
33
|
+
dependent: :destroy,
|
34
|
+
inverse_of: :question
|
35
|
+
|
36
|
+
# Conditions to display other questions based on the value of this question's answer
|
37
|
+
has_many :display_conditions_for_other_questions,
|
38
|
+
class_name: "DisplayCondition",
|
39
|
+
foreign_key: "decidim_condition_question_id",
|
40
|
+
dependent: :destroy,
|
41
|
+
inverse_of: :question
|
42
|
+
|
43
|
+
# Questions which have display conditions based on the value of this question's answer
|
44
|
+
has_many :conditioned_questions,
|
45
|
+
through: :display_conditions_for_other_questions,
|
46
|
+
foreign_key: "decidim_condition_question_id",
|
47
|
+
class_name: "Question"
|
48
|
+
|
17
49
|
validates :question_type, inclusion: { in: TYPES }
|
18
50
|
|
51
|
+
scope :with_body, -> { where(question_type: %w(short_answer long_answer)) }
|
52
|
+
scope :with_choices, -> { where.not(question_type: %w(short_answer long_answer)) }
|
53
|
+
|
54
|
+
scope :conditioned, -> { includes(:display_conditions).where.not(decidim_forms_display_conditions: { id: nil }) }
|
55
|
+
scope :not_conditioned, -> { includes(:display_conditions).where(decidim_forms_display_conditions: { id: nil }) }
|
56
|
+
|
57
|
+
def matrix?
|
58
|
+
%w(matrix_single matrix_multiple).include?(question_type)
|
59
|
+
end
|
60
|
+
|
19
61
|
def multiple_choice?
|
20
|
-
%w(single_option multiple_option sorting).include?(question_type)
|
62
|
+
%w(single_option multiple_option sorting matrix_single matrix_multiple).include?(question_type)
|
21
63
|
end
|
22
64
|
|
23
65
|
def mandatory_body?
|
@@ -31,6 +73,14 @@ module Decidim
|
|
31
73
|
def number_of_options
|
32
74
|
answer_options.size
|
33
75
|
end
|
76
|
+
|
77
|
+
def translated_body
|
78
|
+
Decidim::Forms::QuestionPresenter.new(self).translated_body
|
79
|
+
end
|
80
|
+
|
81
|
+
def separator?
|
82
|
+
question_type.to_s == SEPARATOR_TYPE
|
83
|
+
end
|
34
84
|
end
|
35
85
|
end
|
36
86
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Forms
|
5
|
+
# The data store for a QuestionMatrix in the Decidim::Forms component.
|
6
|
+
class QuestionMatrixRow < Forms::ApplicationRecord
|
7
|
+
include Decidim::TranslatableResource
|
8
|
+
|
9
|
+
translatable_fields :body
|
10
|
+
belongs_to :question, class_name: "Question", foreign_key: "decidim_question_id"
|
11
|
+
|
12
|
+
delegate :answer_options, :mandatory, :max_choices, to: :question
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -4,6 +4,11 @@ module Decidim
|
|
4
4
|
module Forms
|
5
5
|
# The data store for a Questionnaire in the Decidim::Forms component.
|
6
6
|
class Questionnaire < Forms::ApplicationRecord
|
7
|
+
include Decidim::Templates::Templatable if defined? Decidim::Templates::Templatable
|
8
|
+
include Decidim::Publicable
|
9
|
+
include Decidim::TranslatableResource
|
10
|
+
|
11
|
+
translatable_fields :title, :description, :tos
|
7
12
|
belongs_to :questionnaire_for, polymorphic: true
|
8
13
|
|
9
14
|
has_many :questions, -> { order(:position) }, class_name: "Question", foreign_key: "decidim_questionnaire_id", dependent: :destroy
|
@@ -11,12 +16,18 @@ module Decidim
|
|
11
16
|
|
12
17
|
# Public: returns whether the questionnaire questions can be modified or not.
|
13
18
|
def questions_editable?
|
14
|
-
|
19
|
+
has_component = questionnaire_for.respond_to? :component
|
20
|
+
(has_component && !questionnaire_for.component.published?) || answers.empty?
|
15
21
|
end
|
16
22
|
|
17
23
|
# Public: returns whether the questionnaire is answered by the user or not.
|
18
24
|
def answered_by?(user)
|
19
|
-
|
25
|
+
query = user.is_a?(String) ? { session_token: user } : { user: user }
|
26
|
+
answers.where(query).any? if questions.present?
|
27
|
+
end
|
28
|
+
|
29
|
+
def pristine?
|
30
|
+
created_at.to_i == updated_at.to_i && questions.empty?
|
20
31
|
end
|
21
32
|
end
|
22
33
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Forms
|
5
|
+
module Admin
|
6
|
+
#
|
7
|
+
# Presenter for questionnaire answer
|
8
|
+
#
|
9
|
+
class QuestionnaireAnswerPresenter < Rectify::Presenter
|
10
|
+
include Decidim::TranslatableAttributes
|
11
|
+
|
12
|
+
attribute :answer, Decidim::Forms::Answer
|
13
|
+
|
14
|
+
def question
|
15
|
+
translated_attribute(answer.question.body)
|
16
|
+
end
|
17
|
+
|
18
|
+
def body
|
19
|
+
return answer.body if answer.body.present?
|
20
|
+
return "-" if answer.choices.empty?
|
21
|
+
|
22
|
+
choices = answer.choices.map do |choice|
|
23
|
+
choice.try(:custom_body) || choice.try(:body)
|
24
|
+
end
|
25
|
+
|
26
|
+
return choices.first if answer.question.question_type == "single_option"
|
27
|
+
|
28
|
+
content_tag(:ul) do
|
29
|
+
safe_join(choices.map { |c| choice(c) })
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def choice(choice_body)
|
36
|
+
content_tag :li do
|
37
|
+
choice_body
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Forms
|
5
|
+
module Admin
|
6
|
+
#
|
7
|
+
# Presenter for questionnaire response
|
8
|
+
#
|
9
|
+
class QuestionnaireParticipantPresenter < Rectify::Presenter
|
10
|
+
attribute :participant, Decidim::Forms::Answer
|
11
|
+
|
12
|
+
def session_token
|
13
|
+
participant.session_token || "-"
|
14
|
+
end
|
15
|
+
|
16
|
+
def ip_hash
|
17
|
+
participant.ip_hash || "-"
|
18
|
+
end
|
19
|
+
|
20
|
+
def answered_at
|
21
|
+
participant.created_at
|
22
|
+
end
|
23
|
+
|
24
|
+
delegate :questionnaire, to: :participant
|
25
|
+
|
26
|
+
def registered?
|
27
|
+
participant.decidim_user_id.present?
|
28
|
+
end
|
29
|
+
|
30
|
+
def status
|
31
|
+
t(registered? ? "registered" : "unregistered", scope: "decidim.forms.user_answers_serializer")
|
32
|
+
end
|
33
|
+
|
34
|
+
def answers
|
35
|
+
sibilings.map { |answer| QuestionnaireAnswerPresenter.new(answer: answer) }
|
36
|
+
end
|
37
|
+
|
38
|
+
def first_short_answer
|
39
|
+
short = sibilings.where("decidim_forms_questions.question_type in (?)", %w(short_answer))
|
40
|
+
short.first
|
41
|
+
end
|
42
|
+
|
43
|
+
def completion
|
44
|
+
with_body = sibilings.where("decidim_forms_questions.question_type in (?)", %w(short_answer long_answer))
|
45
|
+
.where.not(body: "").count
|
46
|
+
with_choices = sibilings.where.not("decidim_forms_questions.question_type in (?)", %w(short_answer long_answer))
|
47
|
+
.where("decidim_forms_answers.id IN (SELECT decidim_answer_id FROM decidim_forms_answer_choices)").count
|
48
|
+
|
49
|
+
(with_body + with_choices).to_f / questionnaire.questions.count * 100
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def sibilings
|
55
|
+
Answer.where(session_token: participant.session_token).joins(:question).order("decidim_forms_questions.position ASC")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Forms
|
5
|
+
#
|
6
|
+
# Decorator for answer_options
|
7
|
+
#
|
8
|
+
class AnswerOptionPresenter < SimpleDelegator
|
9
|
+
include Decidim::TranslationsHelper
|
10
|
+
|
11
|
+
def translated_body
|
12
|
+
@translated_body ||= translated_attribute body
|
13
|
+
end
|
14
|
+
|
15
|
+
def as_json(*_args)
|
16
|
+
{ id: id, body: translated_body }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|