decidim-forms 0.30.2 → 0.31.0.rc1

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 (158) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +10 -10
  3. data/app/cells/decidim/forms/matrix_readonly/show.erb +1 -1
  4. data/app/cells/decidim/forms/matrix_readonly_cell.rb +3 -3
  5. data/app/cells/decidim/forms/question_readonly/show.erb +5 -5
  6. data/app/cells/decidim/forms/question_readonly/title_and_description.erb +3 -3
  7. data/app/cells/decidim/forms/response_readonly_cell.rb +9 -0
  8. data/app/cells/decidim/forms/step_navigation/show.erb +3 -3
  9. data/app/cells/decidim/forms/step_navigation_cell.rb +3 -3
  10. data/app/commands/decidim/forms/admin/update_questions.rb +6 -6
  11. data/app/commands/decidim/forms/{answer_questionnaire.rb → response_questionnaire.rb} +35 -35
  12. data/app/controllers/decidim/forms/admin/concerns/has_questionnaire.rb +12 -12
  13. data/app/controllers/decidim/forms/admin/concerns/{has_questionnaire_answers.rb → has_questionnaire_responses.rb} +23 -19
  14. data/app/controllers/decidim/forms/concerns/has_questionnaire.rb +38 -33
  15. data/app/forms/decidim/forms/admin/display_condition_form.rb +14 -14
  16. data/app/forms/decidim/forms/admin/question_form.rb +3 -3
  17. data/app/forms/decidim/forms/admin/{answer_option_form.rb → response_option_form.rb} +3 -3
  18. data/app/forms/decidim/forms/questionnaire_form.rb +8 -9
  19. data/app/forms/decidim/forms/{answer_choice_form.rb → response_choice_form.rb} +5 -5
  20. data/app/forms/decidim/forms/{answer_form.rb → response_form.rb} +9 -9
  21. data/app/helpers/decidim/forms/admin/application_helper.rb +2 -2
  22. data/app/helpers/decidim/forms/admin/concerns/{has_questionnaire_answers_pagination_helper.rb → has_questionnaire_responses_pagination_helper.rb} +4 -4
  23. data/app/helpers/decidim/forms/admin/concerns/{has_questionnaire_answers_url_helper.rb → has_questionnaire_responses_url_helper.rb} +5 -5
  24. data/app/helpers/decidim/forms/admin/questionnaire_responses_helper.rb +32 -0
  25. data/app/helpers/decidim/forms/application_helper.rb +1 -2
  26. data/app/jobs/decidim/forms/{export_questionnaire_answers_job.rb → export_questionnaire_responses_job.rb} +5 -5
  27. data/app/models/decidim/forms/display_condition.rb +20 -20
  28. data/app/models/decidim/forms/question.rb +14 -13
  29. data/app/models/decidim/forms/question_matrix_row.rb +1 -1
  30. data/app/models/decidim/forms/questionnaire.rb +11 -6
  31. data/app/models/decidim/forms/{answer.rb → response.rb} +9 -9
  32. data/app/models/decidim/forms/response_choice.rb +22 -0
  33. data/app/models/decidim/forms/{answer_option.rb → response_option.rb} +5 -5
  34. data/app/packs/entrypoints/decidim_forms_admin.js +3 -3
  35. data/app/packs/src/decidim/forms/admin/collapsible_questions.js +12 -10
  36. data/app/packs/src/decidim/forms/admin/forms.js +35 -35
  37. data/app/packs/src/decidim/forms/admin/{publish_answers_buttons.js → publish_responses_buttons.js} +12 -12
  38. data/app/packs/src/decidim/forms/display_conditions.component.js +12 -12
  39. data/app/packs/src/decidim/forms/forms.js +4 -4
  40. data/app/packs/stylesheets/decidim/forms/forms.scss +2 -2
  41. data/app/presenters/decidim/forms/admin/questionnaire_participant_presenter.rb +13 -13
  42. data/app/presenters/decidim/forms/admin/{questionnaire_answer_presenter.rb → questionnaire_response_presenter.rb} +15 -15
  43. data/app/presenters/decidim/forms/admin_log/question_presenter.rb +3 -3
  44. data/app/presenters/decidim/forms/{answer_option_presenter.rb → response_option_presenter.rb} +2 -2
  45. data/app/queries/decidim/forms/questionnaire_participant.rb +5 -5
  46. data/app/queries/decidim/forms/questionnaire_participants.rb +6 -6
  47. data/app/queries/decidim/forms/questionnaire_user_responses.rb +32 -0
  48. data/app/views/decidim/forms/admin/questionnaires/_display_condition.html.erb +9 -9
  49. data/app/views/decidim/forms/admin/questionnaires/_form.html.erb +2 -2
  50. data/app/views/decidim/forms/admin/questionnaires/_matrix_row.html.erb +3 -3
  51. data/app/views/decidim/forms/admin/questionnaires/_question.html.erb +16 -16
  52. data/app/views/decidim/forms/admin/questionnaires/_questions_form.html.erb +11 -10
  53. data/app/views/decidim/forms/admin/questionnaires/{_answer_option.html.erb → _response_option.html.erb} +7 -7
  54. data/app/views/decidim/forms/admin/questionnaires/_response_option_template.html.erb +7 -0
  55. data/app/views/decidim/forms/admin/questionnaires/_separator.html.erb +10 -7
  56. data/app/views/decidim/forms/admin/questionnaires/_title_and_description.html.erb +7 -7
  57. data/app/views/decidim/forms/admin/questionnaires/edit.html.erb +1 -1
  58. data/app/views/decidim/forms/admin/questionnaires/edit_questions.html.erb +8 -8
  59. data/app/views/decidim/forms/admin/questionnaires/responses/index.html.erb +80 -0
  60. data/app/views/decidim/forms/admin/questionnaires/responses/show.html.erb +43 -0
  61. data/app/views/decidim/forms/questionnaires/_questionnaire.html.erb +22 -26
  62. data/app/views/decidim/forms/questionnaires/_questionnaire_readonly.html.erb +8 -3
  63. data/app/views/decidim/forms/questionnaires/_response.html.erb +61 -0
  64. data/app/views/decidim/forms/questionnaires/edit.html.erb +2 -2
  65. data/app/views/decidim/forms/questionnaires/responses/_files.html.erb +1 -0
  66. data/app/views/decidim/forms/questionnaires/responses/_long_response.html.erb +3 -0
  67. data/app/views/decidim/forms/questionnaires/{answers → responses}/_matrix_multiple.html.erb +16 -15
  68. data/app/views/decidim/forms/questionnaires/{answers → responses}/_matrix_single.html.erb +17 -16
  69. data/app/views/decidim/forms/questionnaires/responses/_multiple_option.html.erb +26 -0
  70. data/app/views/decidim/forms/questionnaires/responses/_separator.html.erb +1 -0
  71. data/app/views/decidim/forms/questionnaires/responses/_short_response.html.erb +3 -0
  72. data/app/views/decidim/forms/questionnaires/responses/_single_option.html.erb +33 -0
  73. data/app/views/decidim/forms/questionnaires/responses/_sorting.html.erb +26 -0
  74. data/app/views/decidim/forms/questionnaires/responses/_title_and_description.html.erb +1 -0
  75. data/app/views/decidim/forms/questionnaires/show.html.erb +10 -10
  76. data/config/assets.rb +2 -2
  77. data/config/locales/ar.yml +0 -22
  78. data/config/locales/bg.yml +3 -36
  79. data/config/locales/ca-IT.yml +73 -73
  80. data/config/locales/ca.yml +73 -73
  81. data/config/locales/cs.yml +72 -75
  82. data/config/locales/de.yml +72 -72
  83. data/config/locales/el.yml +3 -36
  84. data/config/locales/en.yml +83 -83
  85. data/config/locales/es-MX.yml +75 -75
  86. data/config/locales/es-PY.yml +75 -75
  87. data/config/locales/es.yml +73 -73
  88. data/config/locales/eu.yml +71 -71
  89. data/config/locales/fi-plain.yml +71 -71
  90. data/config/locales/fi.yml +69 -69
  91. data/config/locales/fr-CA.yml +70 -67
  92. data/config/locales/fr.yml +70 -67
  93. data/config/locales/ga-IE.yml +0 -7
  94. data/config/locales/gl.yml +0 -22
  95. data/config/locales/hu.yml +0 -22
  96. data/config/locales/id-ID.yml +0 -22
  97. data/config/locales/it.yml +3 -40
  98. data/config/locales/ja.yml +80 -80
  99. data/config/locales/lb.yml +3 -36
  100. data/config/locales/lt.yml +3 -36
  101. data/config/locales/lv.yml +0 -22
  102. data/config/locales/nl.yml +3 -36
  103. data/config/locales/no.yml +3 -36
  104. data/config/locales/pl.yml +3 -36
  105. data/config/locales/pt-BR.yml +3 -36
  106. data/config/locales/pt.yml +3 -36
  107. data/config/locales/ro-RO.yml +22 -51
  108. data/config/locales/ru.yml +0 -9
  109. data/config/locales/sk.yml +0 -22
  110. data/config/locales/sl.yml +0 -5
  111. data/config/locales/sv.yml +82 -54
  112. data/config/locales/tr-TR.yml +3 -36
  113. data/config/locales/zh-CN.yml +3 -36
  114. data/config/locales/zh-TW.yml +3 -36
  115. data/db/migrate/20190315203056_add_session_token_to_decidim_forms_answers.rb +1 -1
  116. data/db/migrate/20250314150250_rename_answer_to_response_in_decidim_forms.rb +30 -0
  117. data/db/migrate/20250319130003_change_question_types_in_questions.rb +14 -0
  118. data/lib/decidim/api/question_matrix_row_type.rb +13 -0
  119. data/lib/decidim/api/question_type.rb +4 -3
  120. data/lib/decidim/api/questionnaire_type.rb +1 -0
  121. data/lib/decidim/api/response_option_type.rb +13 -0
  122. data/lib/decidim/exporters/form_pdf.rb +33 -33
  123. data/lib/decidim/forms/api.rb +2 -1
  124. data/lib/decidim/forms/{download_your_data_user_answers_serializer.rb → download_your_data_user_responses_serializer.rb} +3 -3
  125. data/lib/decidim/forms/engine.rb +2 -2
  126. data/lib/decidim/forms/test/factories.rb +24 -24
  127. data/lib/decidim/forms/test/shared_examples/has_questionnaire.rb +176 -176
  128. data/lib/decidim/forms/test/shared_examples/manage_questionnaire_responses.rb +159 -0
  129. data/lib/decidim/forms/test/shared_examples/manage_questionnaires/add_display_conditions.rb +19 -19
  130. data/lib/decidim/forms/test/shared_examples/manage_questionnaires/add_questions.rb +44 -44
  131. data/lib/decidim/forms/test/shared_examples/manage_questionnaires/update_display_conditions.rb +10 -10
  132. data/lib/decidim/forms/test/shared_examples/manage_questionnaires/update_questions.rb +18 -18
  133. data/lib/decidim/forms/test/shared_examples/manage_questionnaires.rb +4 -4
  134. data/lib/decidim/forms/test.rb +1 -1
  135. data/lib/decidim/forms/user_responses_serializer.rb +110 -0
  136. data/lib/decidim/forms/version.rb +1 -1
  137. data/lib/decidim/forms.rb +2 -2
  138. metadata +48 -45
  139. data/app/cells/decidim/forms/answer_readonly_cell.rb +0 -9
  140. data/app/helpers/decidim/forms/admin/questionnaire_answers_helper.rb +0 -30
  141. data/app/models/decidim/forms/answer_choice.rb +0 -22
  142. data/app/queries/decidim/forms/questionnaire_user_answers.rb +0 -32
  143. data/app/views/decidim/forms/admin/questionnaires/_answer_option_template.html.erb +0 -7
  144. data/app/views/decidim/forms/admin/questionnaires/answers/index.html.erb +0 -49
  145. data/app/views/decidim/forms/admin/questionnaires/answers/show.html.erb +0 -43
  146. data/app/views/decidim/forms/questionnaires/_answer.html.erb +0 -61
  147. data/app/views/decidim/forms/questionnaires/answers/_files.html.erb +0 -1
  148. data/app/views/decidim/forms/questionnaires/answers/_long_answer.html.erb +0 -3
  149. data/app/views/decidim/forms/questionnaires/answers/_multiple_option.html.erb +0 -25
  150. data/app/views/decidim/forms/questionnaires/answers/_separator.html.erb +0 -1
  151. data/app/views/decidim/forms/questionnaires/answers/_short_answer.html.erb +0 -3
  152. data/app/views/decidim/forms/questionnaires/answers/_single_option.html.erb +0 -32
  153. data/app/views/decidim/forms/questionnaires/answers/_sorting.html.erb +0 -26
  154. data/app/views/decidim/forms/questionnaires/answers/_title_and_description.html.erb +0 -1
  155. data/lib/decidim/api/answer_option_type.rb +0 -13
  156. data/lib/decidim/forms/test/shared_examples/manage_questionnaire_answers.rb +0 -149
  157. data/lib/decidim/forms/user_answers_serializer.rb +0 -105
  158. /data/app/cells/decidim/forms/{answer_readonly → response_readonly}/show.erb +0 -0
@@ -1,49 +0,0 @@
1
- <div class="card" id="answers">
2
- <div class="item_show__header">
3
- <h1 class="item_show__header-title">
4
- <%= t ".title", total: @total %>
5
- <%= link_to t("actions.back", scope: "decidim.forms.admin.questionnaires"), questionnaire_url, class: "button button__sm button__secondary new" %>
6
- </h1>
7
- </div>
8
- <div class="table-scroll">
9
- <table class="table-list">
10
- <thead>
11
- <tr>
12
- <th>#</th>
13
- <th><%= first_table_th(@participants.first) %></th>
14
- <th><%= t("user_status", scope: "decidim.forms.user_answers_serializer") %></th>
15
- <th><%= t("ip_hash", scope: "decidim.forms.user_answers_serializer") %></th>
16
- <th><%= t("completion", scope: "decidim.forms.user_answers_serializer") %></th>
17
- <th><%= t("created_at", scope: "decidim.forms.user_answers_serializer") %></th>
18
- <th></th>
19
- </tr>
20
- </thead>
21
- <tbody>
22
- <% @participants.each_with_index do |participant, idx| %>
23
- <tr>
24
- <td><%= idx + 1 + page_offset %></td>
25
- <td>
26
- <% if allowed_to? :show, :questionnaire_answers %>
27
- <%= link_to first_table_td(participant), questionnaire_participant_answers_url(participant.session_token) %>
28
- <% else %>
29
- <%= first_table_td(participant) %></td>
30
- <% end %>
31
- <td><%= participant.status %></td>
32
- <td><%= participant.ip_hash %></td>
33
- <td><%= display_percentage(participant.completion) %></td>
34
- <td><%= l participant.answered_at, format: :short %></td>
35
- <td class="table-list__actions">
36
- <% if allowed_to? :show, :questionnaire_answers %>
37
- <%= icon_link_to "eye-line", questionnaire_participant_answers_url(participant.session_token), t("actions.show", scope: "decidim.forms.admin.questionnaires.answers"), class: "action-icon--eye", target: "_blank", data: { "external-link": false } %>
38
- <% end %>
39
- <% if allowed_to? :export_response, :questionnaire_answers %>
40
- <%= icon_link_to "download-line", questionnaire_export_response_url(participant.session_token), t("actions.export", scope: "decidim.forms.admin.questionnaires.answers"), class: "action-icon--data-transfer-download" %>
41
- <% end %>
42
- </td>
43
- </tr>
44
- <% end %>
45
- </tbody>
46
- </table>
47
- </div>
48
- </div>
49
- <%= decidim_paginate @query %>
@@ -1,43 +0,0 @@
1
- <div class="card" id="answers">
2
- <div class="item_show__header">
3
- <h1 class="item_show__header-title">
4
- <%= t ".title", number: current_idx %>
5
-
6
- <%= link_to t("actions.next", scope: "decidim.forms.admin.questionnaires.answers").html_safe, next_url, rel: "next", class: "button button__sm button__secondary next" unless last? %>
7
- <%= link_to t("actions.previous", scope: "decidim.forms.admin.questionnaires.answers").html_safe, prev_url, rel: "prev", class: "button button__sm button__secondary prev" unless first? %>
8
- <%= link_to t("actions.export", scope: "decidim.forms.admin.questionnaires.answers"), questionnaire_export_response_url(@participant.session_token), class: "button button__sm button__secondary export" %>
9
- <%= link_to t("actions.back", scope: "decidim.forms.admin.questionnaires.answers"), questionnaire_participants_url, class: "button button__sm button__secondary back" %>
10
- </h1>
11
- </div>
12
- <div class="card-section">
13
- <div class="table">
14
- <table class="table-list">
15
- <thead>
16
- <tr>
17
- <th><%= t("session_token", scope: "decidim.forms.user_answers_serializer") %></th>
18
- <th><%= t("user_status", scope: "decidim.forms.user_answers_serializer") %></th>
19
- <th><%= t("ip_hash", scope: "decidim.forms.user_answers_serializer") %></th>
20
- <th><%= t("completion", scope: "decidim.forms.user_answers_serializer") %></th>
21
- <th><%= t("created_at", scope: "decidim.forms.user_answers_serializer") %></th>
22
- <th></th>
23
- </tr>
24
- </thead>
25
- <tbody>
26
- <tr>
27
- <td><%= @participant.session_token %></td>
28
- <td><%= @participant.status %></td>
29
- <td><%= @participant.ip_hash %></td>
30
- <td><%= display_percentage(@participant.completion) %></td>
31
- <td><%= l @participant.answered_at, format: :short %></td>
32
- </tr>
33
- </tbody>
34
- </table>
35
- </div>
36
- <dl class="p-4 [&_ul]:list-disc [&_ul]:pl-4">
37
- <% @participant.answers.each do |answer| %>
38
- <dt class="font-semibold"><%= answer.question %></dt>
39
- <dd><%= answer.body %></dd>
40
- <% end %>
41
- </dl>
42
- </div>
43
- </div>
@@ -1,61 +0,0 @@
1
- <% field_id = "questionnaire_responses_#{answer_idx}" %>
2
-
3
- <% if answer.question.separator? %>
4
-
5
- <%= render partial: "decidim/forms/questionnaires/answers/#{answer.question.question_type}", locals: { answer:, answer_form:, answer_idx:, field_id:, disabled: } %>
6
- <%= answer_form.hidden_field :question_id %>
7
-
8
- <% elsif answer.question.title_and_description? %>
9
-
10
- <div class="answer-questionnaire__step-heading">
11
- <h3 class="h3"><%= translated_attribute(answer.question.body) %></h3>
12
- <% if translated_attribute(answer.question.description).present? %>
13
- <div>
14
- <%= decidim_sanitize_editor_admin translated_attribute(answer.question.description) %>
15
- </div>
16
- <% end %>
17
- </div>
18
-
19
- <%= render partial: "decidim/forms/questionnaires/answers/#{answer.question.question_type}", locals: { answer:, answer_form:, answer_idx:, field_id:, disabled: } %>
20
- <%= answer_form.hidden_field :question_id %>
21
-
22
- <% else %>
23
-
24
- <div class="answer-questionnaire__question">
25
- <%
26
- label_options = {
27
- class: "answer-questionnaire__question-label questionnaire-question",
28
- data: { "answer-idx": cleaned_answer_idx }
29
- }
30
- label_options[:for] = nil if %w(matrix_single matrix_multiple single_option multiple_option).include?(answer.question.question_type)
31
- %>
32
-
33
- <%= label_tag field_id, **label_options do %>
34
- <%= translated_attribute(answer.question.body) %>
35
- <% if answer.question.mandatory? %>
36
- <%= answer_form.send(:required_indicator) %>
37
- <% end %>
38
- <% if answer.question.max_choices %>
39
- <%= " (#{answer.send(:max_choices_label)})" %>
40
- <% end %>
41
- <% end %>
42
-
43
- <% if translated_attribute(answer.question.description).present? %>
44
- <div class="answer-questionnaire__question-description">
45
- <%= decidim_sanitize_editor_admin translated_attribute(answer.question.description) %>
46
- </div>
47
- <% end %>
48
-
49
- <% if answer.question.max_choices %>
50
- <small class="form-error max-choices-alert"><%= t(".max_choices_alert") %></small>
51
- <% end %>
52
-
53
- <% %w(short_answer long_answer).exclude?(answer&.question&.question_type) && answer.errors.full_messages.each do |msg| %>
54
- <small class="form-error is-visible"><%= msg %></small>
55
- <% end %>
56
-
57
- <%= render partial: "decidim/forms/questionnaires/answers/#{answer.question.question_type}", locals: { answer:, answer_form:, answer_idx:, field_id:, disabled:, maxlength: (answer.question.max_characters if answer.question.max_characters.positive?) } %>
58
- <%= answer_form.hidden_field :question_id %>
59
- </div>
60
-
61
- <% end %>
@@ -1 +0,0 @@
1
- <%= answer_form.upload :add_documents, attachments: answer.documents, label: false, multiple: true, id: field_id, disabled:, button_class: "button button__lg button__transparent-secondary w-full" %>
@@ -1,3 +0,0 @@
1
- <div>
2
- <%= answer_form.text_area :body, label: false, id: field_id, rows: 10, disabled:, maxlength:, class: "w-full", required: false %>
3
- </div>
@@ -1,25 +0,0 @@
1
- <div class="answer-questionnaire__single-option js-check-box-collection">
2
- <% answer.question.answer_options.each_with_index do |answer_option, idx| %>
3
- <% choice = answer.selected_choices.find { |choice| choice.answer_option_id == answer_option.id } %>
4
-
5
- <div class="js-collection-input">
6
- <%= label_tag do %>
7
- <%= check_box_tag "questionnaire[responses][#{answer_idx}][choices][#{idx}][body]",
8
- translated_attribute(answer_option.body),
9
- choice.present?, disabled: %>
10
-
11
- <%= translated_attribute(answer_option.body) %>
12
-
13
- <%= hidden_field_tag "questionnaire[responses][#{answer_idx}][choices][#{idx}][answer_option_id]", answer_option.id %>
14
- <% end %>
15
-
16
- <% if answer_option.free_text %>
17
- <%= text_field_tag "questionnaire[responses][#{answer_idx}][choices][#{idx}][custom_body]",
18
- choice.try(:custom_body),
19
- disabled: true,
20
- maxlength:,
21
- class: "w-full" %>
22
- <% end %>
23
- </div>
24
- <% end %>
25
- </div>
@@ -1 +0,0 @@
1
- <%= answer_form.hidden_field :body, value: "separator", id: field_id, disabled: %>
@@ -1,3 +0,0 @@
1
- <div>
2
- <%= answer_form.text_field :body, label: false, id: field_id, disabled:, maxlength:, class: "w-full", required: false %>
3
- </div>
@@ -1,32 +0,0 @@
1
- <div class="answer-questionnaire__single-option js-radio-button-collection">
2
- <% choice = answer.choices.first %>
3
-
4
- <% answer.question.answer_options.each_with_index do |answer_option, idx| %>
5
- <% choice_id = "#{field_id}_choices_#{idx}" %>
6
-
7
- <div class="js-collection-input">
8
- <%= label_tag "#{choice_id}_body" do %>
9
- <%= radio_button_tag "questionnaire[responses][#{answer_idx}][choices][][body]",
10
- translated_attribute(answer_option.body),
11
- answer_option.id == choice.try(:answer_option_id),
12
- id: "#{choice_id}_body", disabled: %>
13
-
14
- <%= translated_attribute(answer_option.body) %>
15
-
16
- <%= hidden_field_tag "questionnaire[responses][#{answer_idx}][choices][][answer_option_id]",
17
- answer_option.id,
18
- id: "#{choice_id}_answer_option",
19
- disabled: true %>
20
- <% end %>
21
-
22
- <% if answer_option.free_text %>
23
- <%= text_field_tag "questionnaire[responses][#{answer_idx}][choices][][custom_body]",
24
- choice.try(:custom_body),
25
- id: "#{choice_id}_custom_body",
26
- disabled: true,
27
- maxlength:,
28
- class: "w-full" %>
29
- <% end %>
30
- </div>
31
- <% end %>
32
- </div>
@@ -1,26 +0,0 @@
1
- <div class="answer-questionnaire__sorting-container js-sortable-check-box-collection">
2
- <%
3
- # This is a block that aims to identify the order to the answer questions, and if there is an user answer, use that
4
- # to order the date that needs to be displayed. It is done like this to help with edit user answer form
5
- %>
6
- <% ordering = answer.question.answer_options.pluck(:id) %>
7
- <% if answer.selected_choices.present? %>
8
- <% ordering = answer.selected_choices.map(&:answer_option_id) %>
9
- <% end %>
10
-
11
- <% ids_for_order = ordering.map.with_index { |id, index| "#{id}=#{index + 1}" }.join(", ") %>
12
-
13
- <% answer.question.reload.answer_options.reorder(Arel.sql("POSITION(id::text in '#{ids_for_order}')")).each_with_index do |answer_option, idx| %>
14
- <% choice = answer.selected_choices.find { |choice| choice.answer_option_id == answer_option.id } %>
15
-
16
- <div class="answer-questionnaire__sorting js-collection-input" role="button">
17
- <%= translated_attribute(answer_option.body) %>
18
-
19
- <%= icon "drag-move-2-line" %>
20
-
21
- <%= hidden_field_tag "questionnaire[responses][#{answer_idx}][choices][#{idx}][position]", choice.try(:position) %>
22
- <%= hidden_field_tag "questionnaire[responses][#{answer_idx}][choices][#{idx}][body]", translated_attribute(answer_option.body) %>
23
- <%= hidden_field_tag "questionnaire[responses][#{answer_idx}][choices][#{idx}][answer_option_id]", answer_option.id %>
24
- </div>
25
- <% end %>
26
- </div>
@@ -1 +0,0 @@
1
- <%= answer_form.hidden_field :body, value: "title-and-description", id: field_id, disabled: %>
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Decidim
4
- module Forms
5
- class AnswerOptionType < Decidim::Api::Types::BaseObject
6
- description "An answer option for a multi-choice question in a questionnaire"
7
-
8
- field :body, Decidim::Core::TranslatedFieldType, "The text answer response option.", null: false
9
- field :free_text, GraphQL::Types::Boolean, "Whether if this answer accepts any free text from the user.", null: false
10
- field :id, GraphQL::Types::ID, "ID of this answer option", null: false
11
- end
12
- end
13
- end
@@ -1,149 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "spec_helper"
4
-
5
- shared_examples_for "manage questionnaire answers" do
6
- let(:first_type) { "short_answer" }
7
- let!(:first) do
8
- create(:questionnaire_question, questionnaire:, position: 1, question_type: first_type)
9
- end
10
- let!(:second) do
11
- create(:questionnaire_question, questionnaire:, position: 2, question_type: "single_option")
12
- end
13
- let!(:third) do
14
- create(:questionnaire_question, questionnaire:, position: 3, question_type: "files")
15
- end
16
- let(:questions) do
17
- [first, second, third]
18
- end
19
-
20
- context "when there are no answers" do
21
- it "do not answer admin link" do
22
- click_on "Manage questions"
23
- expect(page).to have_content("No answers yet")
24
- end
25
- end
26
-
27
- context "when there are answers" do
28
- let!(:answer1) { create(:answer, questionnaire:, question: first) }
29
- let!(:answer2) { create(:answer, body: "second answer", questionnaire:, question: first) }
30
- let!(:answer3) { create(:answer, questionnaire:, question: second) }
31
- let!(:file_answer) { create(:answer, :with_attachments, questionnaire:, question: third, body: nil, user: answer3.user, session_token: answer3.session_token) }
32
-
33
- it "shows the answer admin link" do
34
- click_on "Manage questions"
35
- expect(page).to have_content("Show responses")
36
- end
37
-
38
- context "and managing answers page" do
39
- before do
40
- click_on "Manage questions"
41
- click_on "Show responses"
42
- end
43
-
44
- it "shows the answers page" do
45
- expect(page).to have_content(answer1.body)
46
- expect(page).to have_content(answer1.question.body["en"])
47
- expect(page).to have_content(answer2.body)
48
- expect(page).to have_content(answer2.question.body["en"])
49
- end
50
-
51
- it "shows the percentage" do
52
- expect(page).to have_content("33%")
53
- end
54
-
55
- it "has a detail link" do
56
- expect(page).to have_link("Show answers")
57
- end
58
-
59
- it "has an export link" do
60
- expect(page).to have_link(answer1.body)
61
- expect(page).to have_link(answer2.body)
62
- expect(page).to have_link("Export")
63
- end
64
-
65
- context "when no short answer exist" do
66
- let(:first_type) { "long_answer" }
67
-
68
- it "shows session token" do
69
- expect(page).to have_no_content(answer1.body)
70
- expect(page).to have_content(answer1.session_token)
71
- expect(page).to have_content(answer2.session_token)
72
- expect(page).to have_content(answer3.session_token)
73
- expect(page).to have_content("User identifier")
74
- end
75
- end
76
-
77
- context "when multiple answer choice" do
78
- let(:first_type) { "multiple_option" }
79
- let!(:answer1) { create(:answer, questionnaire:, question: first, body: nil) }
80
- let!(:answer_option) { create(:answer_option, question: first) }
81
- let!(:answer_choice) { create(:answer_choice, answer: answer1, answer_option:, body: translated(answer_option.body, locale: I18n.locale)) }
82
-
83
- it "shows the answers page with custom body" do
84
- new_window = window_opened_by { find_all("a.action-icon.action-icon--eye").first.click }
85
-
86
- page.within_window(new_window) do
87
- within "#answers" do
88
- expect(page).to have_css("dt", text: translated(first.body))
89
- expect(page).to have_css("li", text: translated(answer_option.body))
90
- end
91
- end
92
- end
93
- end
94
- end
95
-
96
- context "and managing individual answer page" do
97
- let!(:answer11) { create(:answer, questionnaire:, body: "", user: answer1.user, question: second) }
98
-
99
- before do
100
- click_on "Manage questions"
101
- click_on "Show responses"
102
- end
103
-
104
- it "shows all the questions and responses" do
105
- click_on answer1.body, match: :first
106
- expect(page).to have_content(first.body["en"])
107
- expect(page).to have_content(second.body["en"])
108
- expect(page).to have_content(answer1.body)
109
- end
110
-
111
- it "first answer has a next link" do
112
- click_on answer1.body, match: :first
113
- expect(page).to have_link("Next ›")
114
- expect(page).to have_no_link("‹ Prev")
115
- end
116
-
117
- it "second answer has prev/next links" do
118
- click_on answer2.body, match: :first
119
- expect(page).to have_link("Next ›")
120
- expect(page).to have_link("‹ Prev")
121
- end
122
-
123
- it "third answer has prev link" do
124
- click_on answer3.session_token, match: :first
125
- expect(page).to have_no_link("Next ›")
126
- expect(page).to have_link("‹ Prev")
127
- end
128
-
129
- it "third answer has download link for the attachments" do
130
- click_on answer3.session_token, match: :first
131
- expect(page).to have_content(translated(file_answer.attachments.first.title))
132
- expect(page).to have_content(translated(file_answer.attachments.second.title))
133
- end
134
-
135
- context "when the file answer does not have a title for the attachment" do
136
- let!(:file_answer) { create(:answer, questionnaire:, question: third, body: nil, user: answer3.user, session_token: answer3.session_token) }
137
-
138
- before do
139
- create(:attachment, :with_image, attached_to: file_answer, title: {}, description: {})
140
- end
141
-
142
- it "third answer has download link for the attachments" do
143
- click_on answer3.session_token, match: :first
144
- expect(page).to have_content("Download attachment")
145
- end
146
- end
147
- end
148
- end
149
- end
@@ -1,105 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Decidim
4
- module Forms
5
- # This class serializes the answers given by a User for questionnaire so can be
6
- # exported to CSV, JSON or other formats.
7
- class UserAnswersSerializer < Decidim::Exporters::Serializer
8
- include Decidim::TranslationsHelper
9
-
10
- # Public: Initializes the serializer with a collection of Answers.
11
- def initialize(answers)
12
- @answers = answers
13
- end
14
-
15
- # Public: Exports a hash with the serialized data for the user answers.
16
- def serialize
17
- answers_hash = hash_for(@answers.first)
18
- answers_hash.merge!(questions_hash)
19
-
20
- @answers.each do |answer|
21
- answers_hash[translated_question_key(answer.question.position, answer.question.body)] = normalize_body(answer)
22
- end
23
-
24
- answers_hash
25
- end
26
-
27
- private
28
-
29
- attr_reader :answers
30
- alias resource answers
31
-
32
- def hash_for(answer)
33
- {
34
- answer_translated_attribute_name(:id) => answer&.session_token,
35
- answer_translated_attribute_name(:created_at) => answer&.created_at,
36
- answer_translated_attribute_name(:ip_hash) => answer&.ip_hash,
37
- answer_translated_attribute_name(:user_status) => answer_translated_attribute_name(answer&.decidim_user_id.present? ? "registered" : "unregistered")
38
- }
39
- end
40
-
41
- def questions_hash
42
- questionnaire_id = @answers.first&.decidim_questionnaire_id
43
- return {} unless questionnaire_id
44
-
45
- questions = Decidim::Forms::Question.where(decidim_questionnaire_id: questionnaire_id).order(:position)
46
- return {} if questions.none?
47
-
48
- questions.each.inject({}) do |serialized, question|
49
- serialized.update(
50
- translated_question_key(question.position, question.body) => ""
51
- )
52
- end
53
- end
54
-
55
- def translated_question_key(idx, body)
56
- "#{idx + 1}. #{translated_attribute(body)}"
57
- end
58
-
59
- def normalize_body(answer)
60
- answer.body ||
61
- normalize_attachments(answer) ||
62
- normalize_choices(answer, answer.choices)
63
- end
64
-
65
- def normalize_attachments(answer)
66
- return if answer.attachments.blank?
67
-
68
- answer.attachments.map(&:url)
69
- end
70
-
71
- def normalize_choices(answer, choices)
72
- if answer.question.matrix?
73
- normalize_matrix_choices(answer, choices)
74
- else
75
- choices.map do |choice|
76
- format_free_text_for choice
77
- end
78
- end
79
- end
80
-
81
- def normalize_matrix_choices(answer, choices)
82
- answer.question.matrix_rows.to_h do |matrix_row|
83
- row_body = translated_attribute(matrix_row.body)
84
-
85
- row_choices = answer.question.answer_options.map do |answer_option|
86
- choice = choices.find_by(matrix_row:, answer_option:)
87
- choice.try(:custom_body) || choice.try(:body)
88
- end
89
-
90
- [row_body, row_choices]
91
- end
92
- end
93
-
94
- def answer_translated_attribute_name(attribute)
95
- I18n.t(attribute.to_sym, scope: "decidim.forms.user_answers_serializer")
96
- end
97
-
98
- def format_free_text_for(choice)
99
- return choice.try(:body) if choice.try(:custom_body).blank?
100
-
101
- "#{choice.try(:body)} (#{choice.try(:custom_body)})"
102
- end
103
- end
104
- end
105
- end