decidim-survey_results 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE-AGPLv3.txt +661 -0
- data/README.md +30 -0
- data/Rakefile +9 -0
- data/app/controllers/decidim/survey_results/admin/application_controller.rb +15 -0
- data/app/controllers/decidim/survey_results/application_controller.rb +13 -0
- data/app/controllers/decidim/survey_results/survey_results_controller.rb +32 -0
- data/app/helpers/decidim/survey_results/application_helper.rb +10 -0
- data/app/helpers/decidim/survey_results/survey_results_helper.rb +30 -0
- data/app/models/decidim/survey_results/application_record.rb +10 -0
- data/app/models/decidim/survey_results/files_question_results.rb +60 -0
- data/app/models/decidim/survey_results/full_questionnaire.rb +54 -0
- data/app/models/decidim/survey_results/matrix_question_results.rb +61 -0
- data/app/models/decidim/survey_results/options_question_results.rb +35 -0
- data/app/models/decidim/survey_results/results.rb +68 -0
- data/app/models/decidim/survey_results/separator_question_results.rb +16 -0
- data/app/models/decidim/survey_results/sorting_question_results.rb +71 -0
- data/app/models/decidim/survey_results/text_question_results.rb +61 -0
- data/app/overrides/decidim/forms/questionnaires/questionnaire_show.rb +9 -0
- data/app/packs/entrypoints/decidim_survey_results.js +6 -0
- data/app/packs/entrypoints/decidim_survey_results.scss +1 -0
- data/app/packs/images/decidim/survey_results/condition_question.png +0 -0
- data/app/packs/images/decidim/survey_results/icon.svg +1 -0
- data/app/packs/src/decidim/survey_results/application.js +2 -0
- data/app/packs/src/decidim/survey_results/chart_helper.js +24 -0
- data/app/packs/src/decidim/survey_results/charts.js +108 -0
- data/app/packs/stylesheets/decidim/survey_results/survey_results.scss +127 -0
- data/app/views/decidim/survey_results/survey_results/_answered_table.html.erb +21 -0
- data/app/views/decidim/survey_results/survey_results/_files_question_results.html.erb +9 -0
- data/app/views/decidim/survey_results/survey_results/_matrix_question_results.html.erb +26 -0
- data/app/views/decidim/survey_results/survey_results/_options_question_results.html.erb +24 -0
- data/app/views/decidim/survey_results/survey_results/_participants_icon.html.erb +15 -0
- data/app/views/decidim/survey_results/survey_results/_questions.html.erb +25 -0
- data/app/views/decidim/survey_results/survey_results/_separator_question_results.html.erb +1 -0
- data/app/views/decidim/survey_results/survey_results/_sorting_question_results.html.erb +26 -0
- data/app/views/decidim/survey_results/survey_results/_text_question_results.html.erb +9 -0
- data/app/views/decidim/survey_results/survey_results/_title_and_description.html.erb +3 -0
- data/app/views/decidim/survey_results/survey_results/show.html.erb +17 -0
- data/app/views/layouts/decidim/_head.html.erb +36 -0
- data/config/assets.rb +9 -0
- data/config/i18n-tasks.yml +144 -0
- data/config/locales/ca.yml +37 -0
- data/config/locales/en.yml +37 -0
- data/config/locales/es.yml +37 -0
- data/lib/decidim/survey_results/admin_engine.rb +26 -0
- data/lib/decidim/survey_results/engine.rb +32 -0
- data/lib/decidim/survey_results/extend_components.rb +6 -0
- data/lib/decidim/survey_results/test/factories.rb +13 -0
- data/lib/decidim/survey_results/version.rb +13 -0
- data/lib/decidim/survey_results.rb +11 -0
- data/lib/decidim-survey_results.rb +3 -0
- metadata +179 -0
data/README.md
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# Decidim::SurveyResults
|
2
|
+
|
3
|
+
This module enables the admins to show survey results to participants through a new survey results page.
|
4
|
+
WARNING: This module is in Beta, use it at your own risk.
|
5
|
+
|
6
|
+
## Usage
|
7
|
+
|
8
|
+
SurveyResults will be available as a Component for a Participatory Space.
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
Add this line to your application's Gemfile:
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
gem "decidim-survey_results"
|
16
|
+
```
|
17
|
+
|
18
|
+
And then execute:
|
19
|
+
|
20
|
+
```bash
|
21
|
+
bundle
|
22
|
+
```
|
23
|
+
|
24
|
+
## Contributing
|
25
|
+
|
26
|
+
See [Decidim](https://github.com/decidim/decidim).
|
27
|
+
|
28
|
+
## License
|
29
|
+
|
30
|
+
This engine is distributed under the GNU AFFERO GENERAL PUBLIC LICENSE.
|
data/Rakefile
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "decidim/dev/common_rake"
|
4
|
+
|
5
|
+
desc "Generates a dummy app for testing"
|
6
|
+
task test_app: "decidim:generate_external_test_app"
|
7
|
+
|
8
|
+
desc "Generates a development app."
|
9
|
+
task development_app: "decidim:generate_external_development_app"
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module SurveyResults
|
5
|
+
module Admin
|
6
|
+
# This controller is the abstract class from which all other controllers of
|
7
|
+
# this engine inherit.
|
8
|
+
#
|
9
|
+
# Note that it inherits from `Decidim::Admin::Components::BaseController`, which
|
10
|
+
# override its layout and provide all kinds of useful methods.
|
11
|
+
class ApplicationController < Decidim::Admin::Components::BaseController
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module SurveyResults
|
5
|
+
# This controller is the abstract class from which all other controllers of
|
6
|
+
# this engine inherit.
|
7
|
+
#
|
8
|
+
# Note that it inherits from `Decidim::Components::BaseController`, which
|
9
|
+
# override its layout and provide all kinds of useful methods.
|
10
|
+
class ApplicationController < Decidim::Components::BaseController
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module SurveyResults
|
5
|
+
class SurveyResultsController < ApplicationController
|
6
|
+
include Decidim::ApplicationHelper
|
7
|
+
include Decidim::SurveyResults::SurveyResultsHelper
|
8
|
+
|
9
|
+
def show
|
10
|
+
@full_questionnaire = FullQuestionnaire.new(questionnaire)
|
11
|
+
end
|
12
|
+
|
13
|
+
def current_component
|
14
|
+
@current_component ||= Decidim::Component.find(params[:component_id])
|
15
|
+
end
|
16
|
+
|
17
|
+
def current_participatory_space
|
18
|
+
@current_participatory_space ||= current_component.participatory_space
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def survey
|
24
|
+
@survey ||= ::Decidim::Surveys::Survey.find_by(component: current_component)
|
25
|
+
end
|
26
|
+
|
27
|
+
def questionnaire
|
28
|
+
@questionnaire ||= ::Decidim::Forms::Questionnaire.find_by(questionnaire_for: survey)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module SurveyResults
|
5
|
+
module SurveyResultsHelper
|
6
|
+
|
7
|
+
def render_question_results(full_questionnaire, question)
|
8
|
+
if question.question_type == "title_and_description"
|
9
|
+
render partial: question.question_type, locals: { question: question }
|
10
|
+
else
|
11
|
+
results= Results.for(full_questionnaire, question)
|
12
|
+
render partial: results.partial_name, locals: {
|
13
|
+
full_questionnaire:,
|
14
|
+
results:,
|
15
|
+
labels: results.x_labels,
|
16
|
+
datasets: results.datasets
|
17
|
+
}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def conditioned_question?(question)
|
22
|
+
Decidim::Forms::DisplayCondition.where(decidim_question_id: question).present?
|
23
|
+
end
|
24
|
+
|
25
|
+
def title_and_description_question?(question)
|
26
|
+
question.question_type == "title_and_description"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module SurveyResults
|
5
|
+
class FilesQuestionResults < Results
|
6
|
+
|
7
|
+
def initialize(full_questionnaire, question)
|
8
|
+
super(full_questionnaire, question, "files_question_results")
|
9
|
+
end
|
10
|
+
|
11
|
+
def compute_results
|
12
|
+
data= {
|
13
|
+
labels: [
|
14
|
+
I18n.t("survey_results.question_results.answered"),
|
15
|
+
I18n.t("survey_results.question_results.not_answered")
|
16
|
+
],
|
17
|
+
datasets: [
|
18
|
+
{data: [answered_count, not_answered_count]}
|
19
|
+
]
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
def answered_percentage
|
24
|
+
(total_answers_files_count[:with_attachments] * 100)/full_questionnaire.total_participants
|
25
|
+
end
|
26
|
+
|
27
|
+
def not_answered_percentage
|
28
|
+
(total_answers_files_count[:without_attachments] * 100)/full_questionnaire.total_participants
|
29
|
+
end
|
30
|
+
|
31
|
+
def answered_count
|
32
|
+
total_answers_files_count[:with_attachments]
|
33
|
+
end
|
34
|
+
|
35
|
+
def not_answered_count
|
36
|
+
total_answers_files_count[:without_attachments]
|
37
|
+
end
|
38
|
+
|
39
|
+
def total_answers_files_count
|
40
|
+
total_answers_files_count||= begin
|
41
|
+
|
42
|
+
total_answers_files = {
|
43
|
+
with_attachments: 0,
|
44
|
+
without_attachments: 0
|
45
|
+
}
|
46
|
+
|
47
|
+
full_questionnaire.answers.where(question: question).each do |answer|
|
48
|
+
if answer.attachments.present?
|
49
|
+
total_answers_files[:with_attachments] += 1
|
50
|
+
else
|
51
|
+
total_answers_files[:without_attachments] += 1
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
total_answers_files
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module SurveyResults
|
5
|
+
class FullQuestionnaire
|
6
|
+
|
7
|
+
delegate :title, to: :questionnaire
|
8
|
+
|
9
|
+
def initialize(questionnaire)
|
10
|
+
@questionnaire= questionnaire
|
11
|
+
end
|
12
|
+
|
13
|
+
def questionnaire
|
14
|
+
@questionnaire
|
15
|
+
end
|
16
|
+
|
17
|
+
def questions
|
18
|
+
@questions ||= Decidim::Forms::Question.includes([:matrix_rows, :answer_options]).where(questionnaire: questionnaire).order(:position, :id)
|
19
|
+
end
|
20
|
+
|
21
|
+
def pages
|
22
|
+
pages = []
|
23
|
+
current_page = []
|
24
|
+
pages << current_page
|
25
|
+
|
26
|
+
questions.each do |question|
|
27
|
+
if question.question_type != "separator"
|
28
|
+
current_page << question
|
29
|
+
else
|
30
|
+
current_page = []
|
31
|
+
pages << current_page
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
pages
|
36
|
+
end
|
37
|
+
|
38
|
+
def participants
|
39
|
+
@participants ||= ::Decidim::Forms::QuestionnaireParticipants.new(questionnaire).participants
|
40
|
+
end
|
41
|
+
|
42
|
+
def answers
|
43
|
+
@answers ||= Decidim::Forms::Answer.not_separator
|
44
|
+
.not_title_and_description
|
45
|
+
.joins(:question)
|
46
|
+
.where(questionnaire: questionnaire)
|
47
|
+
end
|
48
|
+
|
49
|
+
def total_participants
|
50
|
+
@total_participants||= participants.count
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module SurveyResults
|
5
|
+
class MatrixQuestionResults < Results
|
6
|
+
|
7
|
+
def initialize(full_questionnaire, question)
|
8
|
+
super(full_questionnaire, question, "matrix_question_results")
|
9
|
+
end
|
10
|
+
|
11
|
+
#-----------------------------------------------
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
#-----------------------------------------------
|
16
|
+
|
17
|
+
# labels: data_rows body
|
18
|
+
# datasets: An array of hashes with two fields, label and data.
|
19
|
+
# Each dataset contains the results of a column in its `data` field.
|
20
|
+
# This is, each dataset contains and array with the count of
|
21
|
+
# choices on each row of the column.
|
22
|
+
#
|
23
|
+
# For example, dataset data for column A: [
|
24
|
+
# 1, <- one user voted A on row 1
|
25
|
+
# 5, <- five users voted A on row 2
|
26
|
+
# 3, <- three users voted A on row 3
|
27
|
+
# ]
|
28
|
+
#
|
29
|
+
# NOTE: Correspondence between Decidim models and the matrix
|
30
|
+
# question.answer_options -> columns
|
31
|
+
# question.matrix_rows -> rows
|
32
|
+
# answers -> each is a user response to a question
|
33
|
+
# Answer has many choices (AnswerChoices)
|
34
|
+
#
|
35
|
+
# See the chart expected data here:
|
36
|
+
# https://www.chartjs.org/docs/latest/samples/bar/floating.html
|
37
|
+
def compute_results
|
38
|
+
user_question_answers= full_questionnaire.answers
|
39
|
+
choices_sums= Decidim::Forms::AnswerChoice.where("decidim_answer_id IN (#{user_question_answers.select(:id).where(question: question).to_sql})").group(:decidim_question_matrix_row_id, :decidim_answer_option_id).count
|
40
|
+
|
41
|
+
# labels: appear at the x axis
|
42
|
+
# datasets: one dataset for the columns of each row
|
43
|
+
data= {
|
44
|
+
labels: question.matrix_rows.map {|r| translated_attribute(r.body) },
|
45
|
+
datasets: []
|
46
|
+
}
|
47
|
+
question.answer_options.each do |answer_option|
|
48
|
+
dataset = {data: []}
|
49
|
+
dataset[:label]= translated_attribute(answer_option.body)
|
50
|
+
question.matrix_rows.each do |row|
|
51
|
+
key= [row.id, answer_option.id]
|
52
|
+
dataset[:data] << (choices_sums[key] || 0)
|
53
|
+
end
|
54
|
+
data[:datasets] << dataset
|
55
|
+
end
|
56
|
+
|
57
|
+
data
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module SurveyResults
|
5
|
+
class OptionsQuestionResults < Results
|
6
|
+
|
7
|
+
def initialize(full_questionnaire, question)
|
8
|
+
super(full_questionnaire, question, "options_question_results")
|
9
|
+
end
|
10
|
+
|
11
|
+
#-----------------------------------------------
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
#-----------------------------------------------
|
16
|
+
|
17
|
+
# labels: each anwer_option/column is a label.
|
18
|
+
# datasets: Returns only one dataset with the results of each column.
|
19
|
+
def compute_results
|
20
|
+
user_question_answers= full_questionnaire.answers
|
21
|
+
choices_sums= Decidim::Forms::AnswerChoice.where("decidim_answer_id IN (#{user_question_answers.select(:id).where(question: question).to_sql})").group(:decidim_answer_option_id).count
|
22
|
+
|
23
|
+
x_labels = []
|
24
|
+
dataset = {label: I18n.t("survey_results.question_results.number_of_votes"), data: []}
|
25
|
+
question.answer_options.map do |answer_option|
|
26
|
+
x_labels << translated_attribute(answer_option.body)
|
27
|
+
|
28
|
+
dataset[:data] << (choices_sums[answer_option.id] || 0)
|
29
|
+
end
|
30
|
+
|
31
|
+
{ labels: x_labels, datasets: [dataset] }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module SurveyResults
|
5
|
+
class Results
|
6
|
+
include Decidim::TranslatableAttributes
|
7
|
+
|
8
|
+
def self.for(full_questionnaire, question)
|
9
|
+
case question.question_type
|
10
|
+
when 'single_option', 'multiple_option'
|
11
|
+
OptionsQuestionResults.new(full_questionnaire, question)
|
12
|
+
when 'short_answer', 'long_answer'
|
13
|
+
TextQuestionResults.new(full_questionnaire, question)
|
14
|
+
when "files"
|
15
|
+
FilesQuestionResults.new(full_questionnaire, question)
|
16
|
+
when "matrix_single"
|
17
|
+
MatrixQuestionResults.new(full_questionnaire, question)
|
18
|
+
when "matrix_multiple"
|
19
|
+
MatrixQuestionResults.new(full_questionnaire, question)
|
20
|
+
when "sorting"
|
21
|
+
SortingQuestionResults.new(full_questionnaire, question)
|
22
|
+
when "separator"
|
23
|
+
SeparatorQuestionResults.new(full_questionnaire, question)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize(full_questionnaire, question, partial_name)
|
28
|
+
@full_questionnaire= full_questionnaire
|
29
|
+
@question= question
|
30
|
+
@partial_name= partial_name
|
31
|
+
end
|
32
|
+
|
33
|
+
attr_reader :question, :partial_name
|
34
|
+
attr :full_questionnaire
|
35
|
+
delegate :question_type, to: :question
|
36
|
+
|
37
|
+
def x_labels
|
38
|
+
results[:labels]
|
39
|
+
end
|
40
|
+
|
41
|
+
def datasets
|
42
|
+
results[:datasets]
|
43
|
+
end
|
44
|
+
|
45
|
+
# A Hash with the labels and datasets resulting from the current Question.
|
46
|
+
# In the format expected by Charts.js in the `data` json field.
|
47
|
+
#
|
48
|
+
# labels: main labels of the chart.
|
49
|
+
# datasets: data to be plotted.
|
50
|
+
def results
|
51
|
+
@results||= compute_results
|
52
|
+
end
|
53
|
+
|
54
|
+
def compute_results
|
55
|
+
fail NotImplementedError, "To be implemented by subclasses"
|
56
|
+
end
|
57
|
+
|
58
|
+
def total_answers
|
59
|
+
@total_answers||= full_questionnaire.answers.where(question: question).count
|
60
|
+
end
|
61
|
+
|
62
|
+
def answers_percentage(data)
|
63
|
+
return 0 if total_answers == 0
|
64
|
+
(data * 100)/total_answers
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module SurveyResults
|
5
|
+
class SeparatorQuestionResults < Results
|
6
|
+
|
7
|
+
def initialize(full_questionnaire, question)
|
8
|
+
super(full_questionnaire, question, "separator_question_results")
|
9
|
+
end
|
10
|
+
|
11
|
+
def compute_results
|
12
|
+
{}
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module SurveyResults
|
5
|
+
class SortingQuestionResults < Results
|
6
|
+
|
7
|
+
def initialize(full_questionnaire, question)
|
8
|
+
super(full_questionnaire, question, "sorting_question_results")
|
9
|
+
end
|
10
|
+
|
11
|
+
#---------------------------------------------------------------
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
#---------------------------------------------------------------
|
16
|
+
|
17
|
+
# labels: I18n position names, same number as available options to order.
|
18
|
+
# datasets: Each dataset is an Array where each item is the number of choices for the given option in that position.
|
19
|
+
def compute_results
|
20
|
+
user_question_answers= full_questionnaire.answers
|
21
|
+
choices_sums= Decidim::Forms::AnswerChoice.where("decidim_answer_id IN (#{user_question_answers.select(:id).where(question: question).to_sql})").group(:decidim_answer_option_id, :position).count
|
22
|
+
|
23
|
+
labels= []
|
24
|
+
datasets= []
|
25
|
+
positions= 0...question.answer_options.size
|
26
|
+
question.answer_options.each_with_index do |answer_option, idx|
|
27
|
+
position= idx + 1
|
28
|
+
labels << I18n.t("survey_results.question_results.position", position: position)
|
29
|
+
|
30
|
+
values= positions.map {|n| choices_sums[[answer_option.id, n]] || 0}
|
31
|
+
datasets << {label: translated_attribute(answer_option.body), data: values}
|
32
|
+
end
|
33
|
+
|
34
|
+
{labels: labels, datasets: datasets}
|
35
|
+
end
|
36
|
+
|
37
|
+
def sorting_question(question)
|
38
|
+
positions = question.answer_options.count
|
39
|
+
values = []
|
40
|
+
all_data = []
|
41
|
+
|
42
|
+
counts = []
|
43
|
+
question.answer_options.map do |answer_option|
|
44
|
+
values << translated_attribute(answer_option.body)
|
45
|
+
|
46
|
+
answers.where(question: question).each do |answer|
|
47
|
+
positions_count = []
|
48
|
+
positions.times do |position|
|
49
|
+
positions_count << {position: position, position_count: 0}
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
choice = answer.choices.find_by(decidim_answer_option_id: answer_option.id)
|
54
|
+
position_choice = choice.position
|
55
|
+
data = {choice: answer_option.id, positions_count: positions_count}
|
56
|
+
|
57
|
+
positions.times do |position|
|
58
|
+
if position_choice == position
|
59
|
+
data[:positions_count][position][:position_count] += 1
|
60
|
+
end
|
61
|
+
end
|
62
|
+
all_data << data
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
{ positions: positions, values: values, data: data }
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module SurveyResults
|
5
|
+
class TextQuestionResults < Results
|
6
|
+
|
7
|
+
def initialize(full_questionnaire, question)
|
8
|
+
super(full_questionnaire, question, "text_question_results")
|
9
|
+
end
|
10
|
+
|
11
|
+
def answered_count
|
12
|
+
@answered_count ||= @full_questionnaire.answers.where(question: @question).where.not(body: "").count
|
13
|
+
end
|
14
|
+
|
15
|
+
def not_answered_count
|
16
|
+
@not_answered_count||= full_questionnaire.total_participants - answered_count
|
17
|
+
end
|
18
|
+
|
19
|
+
def answered_percentage
|
20
|
+
@answered_percentage||= begin
|
21
|
+
if full_questionnaire.total_participants > 0
|
22
|
+
(answered_count * 100)/full_questionnaire.total_participants
|
23
|
+
else
|
24
|
+
0
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def not_answered_percentage
|
30
|
+
@not_answered_percentage||= begin
|
31
|
+
if full_questionnaire.total_participants > 0
|
32
|
+
((full_questionnaire.total_participants - answered_count) * 100)/full_questionnaire.total_participants
|
33
|
+
else
|
34
|
+
0
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
#--------------------------------------------
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
#--------------------------------------------
|
45
|
+
|
46
|
+
# labels: Corresponding translation for "Answered" and "Not Answered"
|
47
|
+
# datasets: Returns only one dataset with the results of each column.
|
48
|
+
def compute_results
|
49
|
+
data= {
|
50
|
+
labels: [
|
51
|
+
I18n.t("survey_results.question_results.answered"),
|
52
|
+
I18n.t("survey_results.question_results.not_answered")
|
53
|
+
],
|
54
|
+
datasets: [
|
55
|
+
{data: [answered_count, not_answered_count]}
|
56
|
+
]
|
57
|
+
}
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Deface::Override.new(virtual_path: "decidim/forms/questionnaires/show",
|
4
|
+
name: "add_button_to_questionnaire_answers",
|
5
|
+
original: "f2fe5ee7066f632fb4a930c922c680ddfbdc1520",
|
6
|
+
insert_after: "div.card",
|
7
|
+
text: "
|
8
|
+
<%= link_to I18n.t('survey_results.questionnaire_show.see_results'), decidim_survey_results.survey_results_path(component_id: current_component.id) %>
|
9
|
+
")
|
@@ -0,0 +1 @@
|
|
1
|
+
@import "stylesheets/decidim/survey_results/survey_results";
|
Binary file
|
@@ -0,0 +1 @@
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 35 35"><path d="M17.5 35A17.5 17.5 0 1 1 35 17.5 17.52 17.52 0 0 1 17.5 35zm0-33.06A15.56 15.56 0 1 0 33.06 17.5 15.57 15.57 0 0 0 17.5 1.94zm9.5 13.7H8a1 1 0 0 1 0-1.94h19a1 1 0 0 1 0 1.94zm0 3.68H8a1 1 0 0 1 0-1.94h19a1 1 0 0 1 0 1.94zM22.26 23H8a1 1 0 0 1 0-1.94h14.26a1 1 0 0 1 0 1.94z"/></svg>
|
@@ -0,0 +1,24 @@
|
|
1
|
+
export function normalizeLabels(labels) {
|
2
|
+
labels.forEach((label, index) => {
|
3
|
+
if (label.length > 28) {
|
4
|
+
let newLabel = splitLargeLabel(label);
|
5
|
+
labels[index] = newLabel
|
6
|
+
}
|
7
|
+
})
|
8
|
+
|
9
|
+
return labels;
|
10
|
+
};
|
11
|
+
|
12
|
+
function splitLargeLabel(label) {
|
13
|
+
let middle = Math.floor(label.length / 2);
|
14
|
+
let before = label.lastIndexOf(' ', middle);
|
15
|
+
let after = label.indexOf(' ', middle + 1);
|
16
|
+
|
17
|
+
if (middle - before < after - middle) {
|
18
|
+
middle = before;
|
19
|
+
} else {
|
20
|
+
middle = after;
|
21
|
+
}
|
22
|
+
|
23
|
+
return [label.substr(0, middle), label.substr(middle + 1)];
|
24
|
+
}
|