decidim-only_forms 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,108 @@
1
+ <h1 align="center"><img src="https://github.com/octree-gva/meta/blob/main/decidim/static/header.png?raw=true" alt="Decidim - Octree Participatory democracy on a robust and open source solution" /></h1>
2
+ <h4 align="center">
3
+ <a href="https://www.octree.ch">Octree</a> |
4
+ <a href="https://octree.ch/en/contact-us/">Contact Us</a> |
5
+ <a href="https://blog.octree.ch">Our Blog (FR)</a><br/><br/>
6
+ <a href="https://decidim.org">Decidim</a> |
7
+ <a href="https://docs.decidim.org/en/">Decidim Docs</a> |
8
+ <a href="https://meta.decidim.org">Participatory Governance (meta decidim)</a><br/><br/>
9
+ <a href="https://matrix.to/#/+decidim:matrix.org">Decidim Community (Matrix+Element.io)</a>
10
+ </h4>
11
+ <p align="center">
12
+ <a href="https://mkutano.community"><img src="https://github.com/octree-gva/decidim-module-referral/blob/main/mkutano-logo.png?raw=true" alt="MKUTANO is a participatory platform where black canadians can effectively & democratically organize at scale" /></a>
13
+ </p>
14
+
15
+ # Decidim::OnlyForms
16
+
17
+ Component to create forms in a participatory space, sponsored by the [Mkutano Community](mkutano.community).
18
+
19
+ ## Usage
20
+
21
+ OnlyForms will be available as a Component for a Participatory Space.
22
+
23
+ ## Installation
24
+
25
+ Add this line to your application's Gemfile:
26
+
27
+ ```ruby
28
+ gem "decidim-only_forms"
29
+ ```
30
+
31
+ And then execute:
32
+
33
+ ```bash
34
+ bundle
35
+ bundle exec rails decidim_only_forms:install:migrations
36
+ bundle exec rails db:migrate
37
+ ```
38
+
39
+ ## Testing
40
+ ```
41
+ bundle exec rake test_app
42
+ ```
43
+
44
+ ## Local development
45
+ For decidim version 0.27, use Gemfile.0.27. For version 0.26, use Gemfile.0.26
46
+ ```
47
+ cp Gemfile.0.27 Gemfile
48
+ ```
49
+
50
+ First, you need to run an empty database with a decidim dev container which runs nothing.
51
+ ```
52
+ docker-compose down -v --remove-orphans
53
+ docker-compose up -d
54
+ ```
55
+
56
+ Once created, you access the decidim container
57
+ ```
58
+ # Get the id of the decidim dev container
59
+ docker ps --format {{.ID}} --filter=label=org.label-schema.name=decidim
60
+ # 841ae977c7da
61
+ docker exec -it 841ae977c7da bash
62
+ ```
63
+ You are now in bash, run manually. This will check your environment and do migrations if needed
64
+ ```
65
+ docker-entrypoint
66
+ ```
67
+
68
+ You are now ready to use your container in the way you want for development:
69
+
70
+ * Run a rails seed: `bundle exec rails db:seed`
71
+ * Have live-reload on your assets: `bin/webpack-dev-server`
72
+ * Execute tasks, like `bundle exec rails g migration AddSomeColumn`
73
+ * Run the rails server: `bundle exec rails s -b 0.0.0.0`
74
+ * etc.
75
+
76
+ To stop everything, uses:
77
+ - `docker-compose down` to stop the containers
78
+ - `docker-compose down -v` to stop the containers and remove all previously saved data.
79
+
80
+ ### Debugging
81
+ To debug something on the container:
82
+ 1. Ensure `decidim-app` is running
83
+ ```bash
84
+ docker ps --all
85
+ # CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
86
+ # 915e9fc474f2 decidim-module-only_forms-decidim-app "sleep infinity" 7 hours ago Up 7 hours 0.0.0.0:3000->3000/tcp, :::3000->3000/tcp, 0.0.0.0:3035->3035/tcp, :::3035->3035/tcp decidim-only-form-app
87
+ # 22304921b7eb postgres:14-alpine "docker-entrypoint.s…" 7 hours ago Up 7 hours (healthy) 0.0.0.0:5432->5432/tcp, :::5432->5432/tcp decidim-module-only_forms-pg-1
88
+
89
+ ```
90
+
91
+ 2. In another terminal, run `docker exec -it 915e9fc474f2 bash`
92
+ 3. Run
93
+ - `tail -f $ROOT/log/development.log` to **access logs**
94
+ - `bundle exec rails restart` to **restart rails server AND keeps webpacker running**
95
+ - `cd $ROOT` to access the `development_app`
96
+ - `cd $ROOT/../decidim_module_only_forms` to access the module directory
97
+ ## Contributing
98
+
99
+ See [Decidim](https://github.com/decidim/decidim).
100
+
101
+ ## License
102
+
103
+ This engine is distributed under the [GNU AFFERO GENERAL PUBLIC LICENSE](LICENSE-AGPLv3.txt)
104
+
105
+ <br /><br />
106
+ <p align="center">
107
+ <img src="https://raw.githubusercontent.com/octree-gva/meta/main/decidim/static/octree_and_decidim.png" height="90" alt="Decidim Installation by Octree" />
108
+ </p>
data/Rakefile ADDED
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "decidim/dev/common_rake"
4
+
5
+ def install_module(path)
6
+ Dir.chdir(path) do
7
+ # system("bundle exec rake decidim_vocacity_gem_tasks:install:migrations")
8
+ end
9
+ end
10
+
11
+ def seed_db(path)
12
+ Dir.chdir(path) do
13
+ system("bundle exec rake db:seed")
14
+ end
15
+ end
16
+
17
+ desc "Prepare for testing"
18
+ task :prepare_tests do
19
+ system("bundle add doorkeeper")
20
+ system("bundle exec rails generate doorkeeper:install")
21
+ system("bundle exec rails generate doorkeeper:migration")
22
+ # Remove previous existing db, and recreate one.
23
+ disable_docker_compose = ENV.fetch("DISABLED_DOCKER_COMPOSE", "false") == "true"
24
+ unless disable_docker_compose
25
+ system("sudo docker-compose down -v")
26
+ system("sudo docker-compose up -d --remove-orphans")
27
+ end
28
+ ENV["RAILS_ENV"] = "test"
29
+ databaseYml = {
30
+ "test" => {
31
+ "adapter" => "postgresql",
32
+ "encoding" => "unicode",
33
+ "host" => ENV.fetch("DATABASE_HOST", "localhost"),
34
+ "port" => ENV.fetch("DATABASE_PORT", "5432").to_i,
35
+ "username" => ENV.fetch("DATABASE_USERNAME", "decidim"),
36
+ "password" => ENV.fetch("DATABASE_PASSWORD", "TEST-baeGhi4Ohtahcee5eejoaxaiwaezaiGo"),
37
+ "database" => "decidim_test"
38
+ }
39
+ }
40
+ config_file = File.expand_path("spec/dummy/config/database.yml", __dir__)
41
+ File.open(config_file, "w") { |f| YAML.dump(databaseYml, f) }
42
+ Dir.chdir("spec/dummy") do
43
+ system("bundle exec rails db:migrate")
44
+ end
45
+ end
46
+
47
+ desc "Generates a dummy app for testing"
48
+ task :test_app do
49
+ Bundler.with_original_env do
50
+ generate_decidim_app(
51
+ "spec/dummy",
52
+ "--app_name",
53
+ "decidim_test",
54
+ "--path",
55
+ "../..",
56
+ "--skip_spring",
57
+ "--demo",
58
+ "--force_ssl",
59
+ "false",
60
+ "--locales",
61
+ "en,fr,es"
62
+ )
63
+ end
64
+ install_module("spec/dummy")
65
+ Rake::Task["prepare_tests"].invoke
66
+ end
67
+
68
+ desc "Generates a development app"
69
+ task :development_app do
70
+ Bundler.with_original_env do
71
+ generate_decidim_app(
72
+ "development_app",
73
+ "--app_name",
74
+ "#{base_app_name}_development_app",
75
+ "--path",
76
+ "..",
77
+ "--recreate_db",
78
+ "--demo"
79
+ )
80
+ end
81
+
82
+ system("bin/rails generate doorkeeper:install")
83
+ system("bin/rails generate doorkeeper:migration")
84
+
85
+ install_module("development_app")
86
+ seed_db("development_app")
87
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Forms
5
+ # This command is executed when the user answers a Questionnaire.
6
+ class AnswerQuestionnaire < Decidim::Forms::Command
7
+ include ::Decidim::MultipleAttachmentsMethods
8
+
9
+ # Initializes a AnswerQuestionnaire Command.
10
+ #
11
+ # form - The form from which to get the data.
12
+ # questionnaire - The current instance of the questionnaire to be answered.
13
+ def initialize(form, current_user, questionnaire)
14
+ @form = form
15
+ @current_user = current_user
16
+ @questionnaire = questionnaire
17
+ end
18
+
19
+ # Answers a questionnaire if it is valid
20
+ #
21
+ # Broadcasts :ok if successful, :invalid otherwise.
22
+ def call
23
+ return broadcast(:invalid) if @form.invalid? || user_already_answered?
24
+
25
+ answer_questionnaire
26
+
27
+ if @errors
28
+ reset_form_attachments
29
+ broadcast(:invalid)
30
+ else
31
+ broadcast(:ok)
32
+ end
33
+ end
34
+
35
+ attr_reader :form, :questionnaire, :current_user
36
+
37
+ private
38
+
39
+ # This method will add an error to the `add_documents` field only if there's
40
+ # any error in any other field or an error in another answer in the
41
+ # questionnaire. This is needed because when the form has
42
+ # an error, the attachments are lost, so we need a way to inform the user
43
+ # of this problem.
44
+ def reset_form_attachments
45
+ @form.responses.each do |answer|
46
+ answer.errors.add(:add_documents, :needs_to_be_reattached) if answer.has_attachments? || answer.has_error_in_attachments?
47
+ end
48
+ end
49
+
50
+ def answer_questionnaire
51
+ @main_form = @form
52
+ @errors = nil
53
+
54
+ Answer.transaction(requires_new: true) do
55
+ form.responses_by_step.flatten.select(&:display_conditions_fulfilled?).each do |form_answer|
56
+ answer = Answer.new(
57
+ user: @current_user,
58
+ questionnaire: @questionnaire,
59
+ question: form_answer.question,
60
+ body: form_answer.body,
61
+ session_token: form.context.session_token,
62
+ ip_hash: form.context.ip_hash
63
+ )
64
+
65
+ form_answer.selected_choices.each do |choice|
66
+ answer.choices.build(
67
+ body: choice.body,
68
+ custom_body: choice.custom_body,
69
+ decidim_answer_option_id: choice.answer_option_id,
70
+ decidim_question_matrix_row_id: choice.matrix_row_id,
71
+ position: choice.position
72
+ )
73
+ end
74
+
75
+ answer.save!
76
+
77
+ next unless form_answer.question.has_attachments?
78
+
79
+ # The attachments module expects `@form` to be the form with the
80
+ # attachments
81
+ @form = form_answer
82
+ @attached_to = answer
83
+
84
+ build_attachments
85
+
86
+ if attachments_invalid?
87
+ @errors = true
88
+ next
89
+ end
90
+
91
+ create_attachments if process_attachments?
92
+ document_cleanup!
93
+ end
94
+
95
+ @form = @main_form
96
+ raise ActiveRecord::Rollback if @errors
97
+ end
98
+ end
99
+
100
+ def user_already_answered?
101
+ return false if allow_multiple_answers?
102
+
103
+ questionnaire.answered_by?(current_user || form.context.session_token)
104
+ end
105
+
106
+ def allow_multiple_answers?
107
+ return current_settings.allow_multiple_answers if current_settings.respond_to?("allow_multiple_answers")
108
+ end
109
+
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Forms
5
+ # maintains compatibility with v0.26 which uses Rectify
6
+ if defined? ::Decidim::Command
7
+ class Command < ::Decidim::Command
8
+ end
9
+ else
10
+ class Command < ::Rectify::Command
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,154 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Forms
5
+ module Concerns
6
+ # Questionnaires can be related to any class in Decidim, in order to
7
+ # manage the questionnaires for a given type, you should create a new
8
+ # controller and include this concern.
9
+ #
10
+ # The only requirement is to define a `questionnaire_for` method that
11
+ # returns an instance of the model that questionnaire belongs to.
12
+ module HasQuestionnaire
13
+ extend ActiveSupport::Concern
14
+
15
+ included do
16
+ helper Decidim::Forms::ApplicationHelper
17
+ include FormFactory
18
+
19
+ helper_method :questionnaire_for, :questionnaire, :allow_answers?, :visitor_can_answer?, :visitor_already_answered?, :update_url, :form_path
20
+
21
+ invisible_captcha on_spam: :spam_detected
22
+
23
+ def show
24
+ @form = form(Decidim::Forms::QuestionnaireForm).from_model(questionnaire)
25
+ render template: "decidim/forms/questionnaires/show"
26
+ end
27
+
28
+ def answer
29
+ enforce_permission_to_answer_questionnaire
30
+
31
+ @form = form(Decidim::Forms::QuestionnaireForm).from_params(params, session_token: session_token, ip_hash: ip_hash)
32
+ Decidim::Forms::AnswerQuestionnaire.call(@form, current_user, questionnaire) do
33
+ on(:ok) do
34
+ # i18n-tasks-use t("decidim.forms.questionnaires.answer.success")
35
+ flash[:notice] = I18n.t("answer.success", scope: i18n_flashes_scope)
36
+ redirect_to after_answer_path
37
+ end
38
+
39
+ on(:invalid) do
40
+ # i18n-tasks-use t("decidim.forms.questionnaires.answer.invalid")
41
+ flash.now[:alert] = I18n.t("answer.invalid", scope: i18n_flashes_scope)
42
+ render template: "decidim/forms/questionnaires/show"
43
+ end
44
+ end
45
+ end
46
+
47
+ # Public: Method to be implemented at the controller. You need to
48
+ # return true if the questionnaire can receive answers
49
+ def allow_answers?
50
+ raise "#{self.class.name} is expected to implement #allow_answers?"
51
+ end
52
+
53
+ # Public: Method to be implemented at the controller if needed. You need to
54
+ # return true if the questionnaire can receive answers by unregistered users
55
+ def allow_unregistered?
56
+ false
57
+ end
58
+
59
+ # Public: return true if the current user (if logged) can answer the questionnaire
60
+ def visitor_can_answer?
61
+ current_user || allow_unregistered?
62
+ end
63
+
64
+ # Public: return false if survey allow multiple answers
65
+ # if not, return true if the current user (or session visitor) already answered the questionnaire
66
+ def visitor_already_answered?
67
+ return false if allow_multiple_answers?
68
+
69
+ questionnaire.answered_by?(current_user || tokenize(session[:session_id]))
70
+ end
71
+
72
+ # Public: Returns a String or Object that will be passed to `redirect_to` after
73
+ # answering the questionnaire. By default it redirects to the questionnaire_for.
74
+ #
75
+ # It can be redefined at controller level if you need to redirect elsewhere.
76
+ def after_answer_path
77
+ questionnaire_for
78
+ end
79
+
80
+ # You can implement this method in your controller to change the URL
81
+ # where the questionnaire will be submitted.
82
+ def update_url
83
+ url_for([questionnaire_for, { action: :answer }])
84
+ end
85
+
86
+ # Points to the shortest path accessing the current form. This will be
87
+ # used to detect whether a user is leaving the form with some partial
88
+ # answers, so that we can warn them.
89
+ #
90
+ # Overwrite this method at the controller.
91
+ def form_path
92
+ url_for([questionnaire_for, { only_path: true }])
93
+ end
94
+
95
+ # Public: Method to be implemented at the controller. You need to
96
+ # return the object that will hold the questionnaire.
97
+ def questionnaire_for
98
+ raise "#{self.class.name} is expected to implement #questionnaire_for"
99
+ end
100
+
101
+ private
102
+
103
+ def allow_multiple_answers?
104
+ return current_settings.allow_multiple_answers if current_settings.respond_to?("allow_multiple_answers")
105
+ end
106
+
107
+ def i18n_flashes_scope
108
+ "decidim.forms.questionnaires"
109
+ end
110
+
111
+ def questionnaire
112
+ @questionnaire ||= Questionnaire.includes(questions: :answer_options).find_by(questionnaire_for: questionnaire_for)
113
+ end
114
+
115
+ def spam_detected
116
+ enforce_permission_to_answer_questionnaire
117
+
118
+ @form = form(Decidim::Forms::QuestionnaireForm).from_params(params)
119
+
120
+ flash.now[:alert] = I18n.t("answer.spam_detected", scope: i18n_flashes_scope)
121
+ render template: "decidim/forms/questionnaires/show"
122
+ end
123
+
124
+ # You can implement this method in your controller to change the
125
+ # enforce_permission_to arguments.
126
+ def enforce_permission_to_answer_questionnaire
127
+ enforce_permission_to :answer, :questionnaire
128
+ end
129
+
130
+ def ip_hash
131
+ return nil unless request&.remote_ip
132
+
133
+ @ip_hash ||= tokenize(request&.remote_ip)
134
+ end
135
+
136
+ # token is used as a substitute of user_id if unregistered
137
+ def session_token
138
+ id = current_user&.id
139
+ session_id = request.session[:session_id] if request&.session
140
+
141
+ return nil unless id || session_id
142
+
143
+ @session_token ||= tokenize(id || session_id)
144
+ end
145
+
146
+ def tokenize(id, length: 10)
147
+ tokenizer = Decidim::Tokenizer.new(salt: questionnaire.salt || questionnaire.id, length: length)
148
+ tokenizer.int_digest(id).to_s
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Surveys
5
+ # The data store for a Survey in the Decidim::Surveys component.
6
+ class Survey < Surveys::ApplicationRecord
7
+ include Decidim::Resourceable
8
+ include Decidim::Forms::HasQuestionnaire
9
+ include Decidim::HasComponent
10
+
11
+ #component_manifest_name "surveys"
12
+
13
+ delegate :title, to: :questionnaire
14
+
15
+ validates :questionnaire, presence: true
16
+
17
+ def clean_after_publish?
18
+ component.settings.clean_after_publish?
19
+ end
20
+
21
+ def starts_at
22
+ component.settings.starts_at
23
+ end
24
+
25
+ def ends_at
26
+ component.settings.ends_at
27
+ end
28
+
29
+ def open?
30
+ return true if starts_at.blank? && ends_at.blank?
31
+ return true if ends_at.blank? && starts_at.past?
32
+ return true if starts_at.blank? && ends_at.future?
33
+
34
+ return Time.zone.now.between?(starts_at, ends_at) if starts_at.present? && ends_at.present?
35
+
36
+ false
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,10 @@
1
+ ---
2
+
3
+ base_locale: en
4
+ locales: [en]
5
+
6
+ ignore_unused:
7
+ - "decidim.components.only_forms.name"
8
+
9
+ ignore_missing:
10
+ - decidim.participatory_processes.scopes.global
@@ -0,0 +1,27 @@
1
+ ---
2
+ en:
3
+ decidim:
4
+ components:
5
+ only_forms:
6
+ name: Forms
7
+ settings:
8
+ global:
9
+ clean_after_publish: Delete answers when publishing the form
10
+ ends_at: Answers accepted until
11
+ ends_at_help: Leave blank for no specific date
12
+ scope_id: Scope
13
+ scopes_enabled: Scopes enabled
14
+ starts_at: Answers accepted from
15
+ starts_at_help: Leave blank for no specific date
16
+ announcement: Announcement
17
+ step:
18
+ allow_multiple_answers: Allow Multiple Answers
19
+ allow_answers: Allow answers
20
+ allow_unregistered: Allow unregistered users to answer the form
21
+ allow_unregistered_help: If active, no login will be required in order to answer the form. This may lead to poor or unreliable data and it will be more vulnerable to automated attacks. Use with caution!
22
+ announcement: Announcement
23
+ only_forms:
24
+ admin:
25
+ exports:
26
+ survey_user_answers: Forms participant answers
27
+
@@ -0,0 +1,27 @@
1
+ ---
2
+ fr:
3
+ decidim:
4
+ components:
5
+ only_forms:
6
+ name: Formulaire
7
+ settings:
8
+ global:
9
+ clean_after_publish: Supprimer les réponses après publication du formulaire
10
+ ends_at: Réponses acceptées jusqu'à
11
+ ends_at_help: Laisser vide si pas de limite de date pour recevoir des réponses
12
+ scope_id: Scope
13
+ scopes_enabled: Scopes enabled
14
+ starts_at: Réponses acceptées depuis
15
+ starts_at_help: Laisser vide si pas de limite de date pour débuter la réception des réponses
16
+ announcement: Annonces
17
+ step:
18
+ allow_multiple_answers: Autoriser les utilisateurs à répondre plusieurs fois
19
+ allow_answers: Autoriser les réponses
20
+ allow_unregistered: Autoriser les réponses pour les visiteurs
21
+ allow_unregistered_help: Si ceci est actif, les visiteurs n'auront pas besoin de compte pour répondre.
22
+ announcement: Annonce
23
+ only_forms:
24
+ admin:
25
+ exports:
26
+ survey_user_answers: Réponses au formulaire
27
+
@@ -0,0 +1,27 @@
1
+ ---
2
+ pt:
3
+ decidim:
4
+ components:
5
+ only_forms:
6
+ name: Formulario
7
+ settings:
8
+ global:
9
+ clean_after_publish: Deletar as respostas apos publicação
10
+ ends_at: Respostas aceitas até
11
+ ends_at_help: Deixa vazia se não tem limitas para receber respostas
12
+ scope_id: Scope
13
+ scopes_enabled: Scopes enabled
14
+ starts_at: Responstas ativas depois do
15
+ starts_at_help: Deixa vazia se não tem inicio para receber respostas
16
+ announcement: Anuncios
17
+ step:
18
+ allow_multiple_answers: Deixar os usarios responder mais que uma vez
19
+ allow_answers: Autorisar respostas
20
+ allow_unregistered: Autorisar respostas de visitores
21
+ allow_unregistered_help: Se isso esta ativos, pessoas sem contas poderam responder.
22
+ announcement: Anuncios
23
+ only_forms:
24
+ admin:
25
+ exports:
26
+ survey_user_answers: Responsas ao formulario
27
+
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module OnlyForms
5
+ # This module contains all the domain logic associated to Decidim's OnlyForms
6
+ # component admin panel.
7
+ module Admin
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module OnlyForms
5
+ # This is the engine that runs on the public interface of `OnlyForms`.
6
+ class AdminEngine < ::Rails::Engine
7
+ isolate_namespace Decidim::OnlyForms::Admin
8
+
9
+ paths["db/migrate"] = nil
10
+ paths["lib/tasks"] = nil
11
+
12
+ routes do
13
+ # Add admin engine routes here
14
+ end
15
+
16
+ def load_seed
17
+ nil
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency "decidim/components/namer"
4
+
5
+ Decidim.register_component(:only_forms) do |component|
6
+ survey_component = Decidim.find_component_manifest("surveys")
7
+ component.attributes = survey_component.attributes.deep_dup
8
+ component.name = "only_forms"
9
+
10
+ component.settings(:global) do |settings|
11
+ settings.attribute :scopes_enabled, type: :boolean, default: false
12
+ settings.attribute :scope_id, type: :scope
13
+ settings.attribute :starts_at, type: :time
14
+ settings.attribute :ends_at, type: :time
15
+ settings.attribute :announcement, type: :text, translated: true, editor: true
16
+ settings.attribute :clean_after_publish, type: :boolean, default: true
17
+ end
18
+
19
+ component.settings(:step) do |settings|
20
+ settings.attribute :allow_answers, type: :boolean, default: false
21
+ settings.attribute :allow_unregistered, type: :boolean, default: false
22
+ settings.attribute :allow_multiple_answers, type: :boolean, default: true
23
+ settings.attribute :announcement, type: :text, translated: true, editor: true
24
+ end
25
+
26
+ component.exports :survey_user_answers do |exports|
27
+ exports.collection do |f|
28
+ survey = Decidim::Surveys::Survey.find_by(component: f)
29
+ Decidim::Forms::QuestionnaireUserAnswers.for(survey.questionnaire)
30
+ end
31
+
32
+ exports.formats %w(CSV JSON Excel)
33
+
34
+ exports.serializer Decidim::Forms::UserAnswersSerializer
35
+ end
36
+
37
+ component.register_resource(:survey) do |resource|
38
+ resource.model_class_name = "Decidim::Surveys::Survey"
39
+ end
40
+ component
41
+ end