diff --git a/decidim-templates/app/views/decidim/templates/admin/proposal_answer_templates/new.html.erb b/decidim-templates/app/views/decidim/templates/admin/proposal_answer_templates/new.html.erb
new file mode 100644
index 000000000000..3be1fd1edb83
--- /dev/null
+++ b/decidim-templates/app/views/decidim/templates/admin/proposal_answer_templates/new.html.erb
@@ -0,0 +1,7 @@
+<% content_for :title do %>
+ <%= t("templates", scope: "decidim.admin.titles") %>
+<% end %>
+
+<%= decidim_form_for(@form, url: proposal_answer_templates_path, html: { class: "form new_proposal_answer_template" }) do |f| %>
+ <%= render partial: "form", object: f %>
+<% end %>
diff --git a/decidim-templates/config/assets.rb b/decidim-templates/config/assets.rb
index 7c60dfefc6a8..b755f0cdad02 100644
--- a/decidim-templates/config/assets.rb
+++ b/decidim-templates/config/assets.rb
@@ -4,5 +4,6 @@
Decidim::Webpacker.register_path("#{base_path}/app/packs")
Decidim::Webpacker.register_entrypoints(
- decidim_templates: "#{base_path}/app/packs/entrypoints/decidim_templates.js"
+ decidim_templates: "#{base_path}/app/packs/entrypoints/decidim_templates.js",
+ decidim_templates_admin: "#{base_path}/app/packs/entrypoints/decidim_templates_admin.js"
)
diff --git a/decidim-templates/config/locales/en.yml b/decidim-templates/config/locales/en.yml
index 5682ec36398b..a97337b0c834 100644
--- a/decidim-templates/config/locales/en.yml
+++ b/decidim-templates/config/locales/en.yml
@@ -7,6 +7,7 @@ en:
template:
description: Description
name: Name
+ scope_for_availability: Restrict availability to the component
decidim:
admin:
menu:
@@ -24,6 +25,7 @@ en:
destroy:
success: Template deleted successfully
empty: There are no templates yet.
+ missing_resource: "(missing resource)"
update:
error: There was a problem updating this template.
success: Template updated successfully
@@ -42,6 +44,24 @@ en:
name: Template
templates:
admin:
+ proposal_answer_templates:
+ form:
+ answer_template: Answer template
+ hint: "Hint: You can use these variables anywhere on the answer template that will be replaced when using the template"
+ hint1: "%{organization} will be replaced by the organization's name"
+ hint2: "%{name} will be replaced by the author's name"
+ hint3: "%{admin} will be replaced by the admin's name (the one answering the proposal)"
+ save: Save
+ scope_for_availability_help: Note that only participatory spaces having components of the type "proposals" will be listed.
+ template_title: Template information
+ index:
+ confirm_delete: Are you sure you want to delete this template?
+ global_scope: Global (available everywhere)
+ internal_state: Internal State
+ scope_for_availability: Scope
+ title: Proposal answers
+ template_chooser:
+ select_template: Select a template answer
questionnaire_templates:
choose:
create_from_template: Create from template
@@ -65,4 +85,5 @@ en:
of_total_steps: of %{total_steps}
tos_agreement: By participating you accept its Terms of Service
template_types:
+ proposal_answer_templates: Proposal Answers
questionnaires: Questionnaires
diff --git a/decidim-templates/db/migrate/20221006055954_add_field_values_to_decidim_templates.rb b/decidim-templates/db/migrate/20221006055954_add_field_values_to_decidim_templates.rb
new file mode 100644
index 000000000000..0d129239f85a
--- /dev/null
+++ b/decidim-templates/db/migrate/20221006055954_add_field_values_to_decidim_templates.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class AddFieldValuesToDecidimTemplates < ActiveRecord::Migration[6.0]
+ def change
+ add_column :decidim_templates_templates, :field_values, :json, default: {}
+ end
+end
diff --git a/decidim-templates/db/migrate/20221006184809_add_target_to_decidim_templates_templates.rb b/decidim-templates/db/migrate/20221006184809_add_target_to_decidim_templates_templates.rb
new file mode 100644
index 000000000000..e4e1ffba58b8
--- /dev/null
+++ b/decidim-templates/db/migrate/20221006184809_add_target_to_decidim_templates_templates.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class AddTargetToDecidimTemplatesTemplates < ActiveRecord::Migration[6.0]
+ def change
+ add_column :decidim_templates_templates, :target, :string
+ end
+end
diff --git a/decidim-templates/db/migrate/20221006184905_migrate_templateable.rb b/decidim-templates/db/migrate/20221006184905_migrate_templateable.rb
new file mode 100644
index 000000000000..0034e8821aa0
--- /dev/null
+++ b/decidim-templates/db/migrate/20221006184905_migrate_templateable.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class MigrateTemplateable < ActiveRecord::Migration[6.0]
+ def self.up
+ Decidim::Templates::Template.find_each do |template|
+ template.update(target: template.templatable_type.demodulize.tableize.singularize)
+ end
+ end
+
+ def self.down; end
+end
diff --git a/decidim-templates/lib/decidim/templates/admin_engine.rb b/decidim-templates/lib/decidim/templates/admin_engine.rb
index 314483e1c1ed..f0d7fb74cdf7 100644
--- a/decidim-templates/lib/decidim/templates/admin_engine.rb
+++ b/decidim-templates/lib/decidim/templates/admin_engine.rb
@@ -11,6 +11,14 @@ class AdminEngine < ::Rails::Engine
routes do
## Routes for Questionnaire Templates
+ resources :proposal_answer_templates do
+ member do
+ post :copy
+ end
+ collection do
+ get :fetch
+ end
+ end
resources :questionnaire_templates do
member do
post :copy
diff --git a/decidim-templates/lib/decidim/templates/test/factories.rb b/decidim-templates/lib/decidim/templates/test/factories.rb
index ed94c23d09fe..a96618a0adbd 100644
--- a/decidim-templates/lib/decidim/templates/test/factories.rb
+++ b/decidim-templates/lib/decidim/templates/test/factories.rb
@@ -9,6 +9,12 @@
templatable { build(:dummy_resource) }
name { Decidim::Faker::Localized.sentence }
+ trait :proposal_answer do
+ templatable { organization }
+ target { :proposal_answer }
+ field_values { { internal_state: :accepted } }
+ end
+
## Questionnaire templates
factory :questionnaire_template do
trait :with_questions do
diff --git a/decidim-templates/spec/commands/decidim/templates/admin/copy_proposal_answer_template_spec.rb b/decidim-templates/spec/commands/decidim/templates/admin/copy_proposal_answer_template_spec.rb
new file mode 100644
index 000000000000..cc1156b89fa8
--- /dev/null
+++ b/decidim-templates/spec/commands/decidim/templates/admin/copy_proposal_answer_template_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+module Decidim
+ module Templates
+ module Admin
+ describe CopyProposalAnswerTemplate do
+ let(:template) { create(:template, :proposal_answer) }
+
+ describe "when the template is invalid" do
+ before do
+ template.update(name: nil)
+ end
+
+ it "broadcasts invalid" do
+ expect { described_class.call(template) }.to broadcast(:invalid)
+ end
+ end
+
+ describe "when the template is valid" do
+ let(:destination_template) do
+ events = described_class.call(template)
+ # events => { :ok => copied_template }
+ expect(events).to have_key(:ok)
+ events[:ok]
+ end
+
+ it "applies template attributes to the questionnaire" do
+ expect(destination_template.name).to eq(template.name)
+ expect(destination_template.description).to eq(template.description)
+ expect(destination_template.field_values).to eq(template.field_values)
+ expect(destination_template.templatable).to eq(template.templatable)
+ expect(destination_template.target).to eq(template.target)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/decidim-templates/spec/commands/decidim/templates/admin/destroy_questionnaire_template_spec.rb b/decidim-templates/spec/commands/decidim/templates/admin/destroy_questionnaire_template_spec.rb
new file mode 100644
index 000000000000..a3a0dfe00d5f
--- /dev/null
+++ b/decidim-templates/spec/commands/decidim/templates/admin/destroy_questionnaire_template_spec.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+module Decidim
+ module Templates
+ module Admin
+ describe DestroyQuestionnaireTemplate do
+ let(:template) { create(:questionnaire_template) }
+ let(:admin) { create(:user, :admin) }
+ let!(:templatable) { template.templatable }
+
+ it "destroy the templatable" do
+ described_class.call(template, admin)
+ expect { templatable.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+
+ it "destroy the template" do
+ described_class.call(template, admin)
+ expect { template.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
+ end
+ end
+end
diff --git a/decidim-templates/spec/commands/decidim/templates/admin/destroy_template_spec.rb b/decidim-templates/spec/commands/decidim/templates/admin/destroy_template_spec.rb
new file mode 100644
index 000000000000..59a67f238c28
--- /dev/null
+++ b/decidim-templates/spec/commands/decidim/templates/admin/destroy_template_spec.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+module Decidim
+ module Templates
+ module Admin
+ describe DestroyTemplate do
+ let(:template) { create(:questionnaire_template) }
+ let(:admin) { create(:user, :admin) }
+ let!(:templatable) { template.templatable }
+
+ it "destroy the template" do
+ described_class.call(template, admin)
+ expect { template.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
+ end
+ end
+end
diff --git a/decidim-templates/spec/forms/decidim/templates/admin/proposal_answer_template_form_spec.rb b/decidim-templates/spec/forms/decidim/templates/admin/proposal_answer_template_form_spec.rb
new file mode 100644
index 000000000000..622ad8d4a619
--- /dev/null
+++ b/decidim-templates/spec/forms/decidim/templates/admin/proposal_answer_template_form_spec.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+module Decidim
+ module Templates
+ module Admin
+ describe ProposalAnswerTemplateForm do
+ subject do
+ described_class.from_params(attributes).with_context(
+ current_organization: current_organization
+ )
+ end
+
+ let(:current_organization) { create(:organization) }
+
+ let(:name) do
+ {
+ "en" => name_english,
+ "ca" => "Nom",
+ "es" => "Nombre"
+ }
+ end
+
+ let(:description) do
+ {
+ "en" => "
Content
",
+ "ca" => "
Contingut
",
+ "es" => "
Contenido
"
+ }
+ end
+
+ let(:internal_state) { :accepted }
+
+ let(:name_english) { "Name" }
+
+ let(:attributes) do
+ {
+ "name" => name,
+ "description" => description,
+ "internal_state" => internal_state
+ }
+ end
+
+ context "when everything is OK" do
+ it { is_expected.to be_valid }
+ end
+
+ context "when name is not valid" do
+ let(:name_english) { "" }
+
+ it { is_expected.not_to be_valid }
+ end
+
+ context "when internal_state is not valid" do
+ let(:internal_state) { "" }
+
+ it { is_expected.not_to be_valid }
+ end
+ end
+ end
+ end
+end
diff --git a/decidim-templates/spec/forms/decidim/templates/admin/questionnaire_form_spec.rb b/decidim-templates/spec/forms/decidim/templates/admin/template_form_spec.rb
similarity index 100%
rename from decidim-templates/spec/forms/decidim/templates/admin/questionnaire_form_spec.rb
rename to decidim-templates/spec/forms/decidim/templates/admin/template_form_spec.rb
diff --git a/decidim-templates/spec/models/decidim/templates/template_spec.rb b/decidim-templates/spec/models/decidim/templates/template_spec.rb
index edc3f35122ee..cbe58b813391 100644
--- a/decidim-templates/spec/models/decidim/templates/template_spec.rb
+++ b/decidim-templates/spec/models/decidim/templates/template_spec.rb
@@ -27,14 +27,14 @@ module Templates
expect(subject.templatable).to be_a(Decidim::DummyResources::DummyResource)
end
- describe "on destroy" do
- let(:templatable) { template.templatable }
-
- it "destroys the templatable" do
- template.destroy!
- expect { templatable.reload }.to raise_error(ActiveRecord::RecordNotFound)
- end
- end
+ # describe "on destroy" do
+ # let(:templatable) { template.templatable }
+ #
+ # it "destroys the templatable" do
+ # template.destroy!
+ # expect { templatable.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ # end
+ # end
describe "#resource_name" do
it "returns the templatable model name without namespace, downcased and postfixed with _templates" do
diff --git a/decidim-templates/spec/system/admin/admin_manages_proposal_answer_templates_spec.rb b/decidim-templates/spec/system/admin/admin_manages_proposal_answer_templates_spec.rb
new file mode 100644
index 000000000000..498258b20b41
--- /dev/null
+++ b/decidim-templates/spec/system/admin/admin_manages_proposal_answer_templates_spec.rb
@@ -0,0 +1,217 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+require "decidim/proposals/test/factories"
+
+describe "Admin manages proposal answer templates", type: :system do
+ let!(:organization) { create :organization }
+ let!(:user) { create :user, :admin, :confirmed, organization: organization }
+
+ before do
+ switch_to_host(organization.host)
+ login_as user, scope: :user
+ visit decidim_admin_templates.proposal_answer_templates_path
+ end
+
+ describe "listing templates" do
+ let!(:template) { create(:template, :proposal_answer, organization: organization) }
+
+ before do
+ visit decidim_admin_templates.proposal_answer_templates_path
+ end
+
+ it "shows a table with the templates info" do
+ within ".questionnaire-templates" do
+ expect(page).to have_i18n_content(template.name)
+ expect(page).to have_i18n_content("Global (available everywhere)")
+ end
+ end
+
+ context "when a template is scoped to an invalid resource" do
+ let!(:template) { create(:template, :proposal_answer, organization: organization, templatable: create(:dummy_resource)) }
+
+ it "shows a table info about the invalid resource" do
+ within ".questionnaire-templates" do
+ expect(page).to have_i18n_content(template.name)
+ expect(page).to have_i18n_content("(missing resource)")
+ end
+ end
+ end
+ end
+
+ describe "creating a proposal_answer_template" do
+ let(:participatory_process) { create :participatory_process, title: { en: "A participatory process" }, organization: organization }
+ let!(:proposals_component) { create :component, manifest_name: :proposals, name: { en: "A component" }, participatory_space: participatory_process }
+
+ before do
+ within ".layout-content" do
+ click_link("New")
+ end
+ end
+
+ shared_examples "creates a new template with scopes" do |scope_name|
+ it "creates a new template" do
+ within ".new_proposal_answer_template" do
+ fill_in_i18n(
+ :proposal_answer_template_name,
+ "#proposal_answer_template-name-tabs",
+ en: "My template",
+ es: "Mi plantilla",
+ ca: "La meva plantilla"
+ )
+ fill_in_i18n_editor(
+ :proposal_answer_template_description,
+ "#proposal_answer_template-description-tabs",
+ en: "Description",
+ es: "DescripciĆ³n",
+ ca: "DescripciĆ³"
+ )
+
+ choose "Not answered"
+ select scope_name, from: :proposal_answer_template_scope_for_availability
+
+ page.find("*[type=submit]").click
+ end
+
+ expect(page).to have_admin_callout("successfully")
+ expect(page).to have_current_path decidim_admin_templates.proposal_answer_templates_path
+ within ".questionnaire-templates" do
+ expect(page).to have_i18n_content(scope_name)
+ expect(page).to have_content("My template")
+ end
+ end
+ end
+
+ it_behaves_like "creates a new template with scopes", "Global (available everywhere)"
+ it_behaves_like "creates a new template with scopes", "Participatory process: A participatory process > A component"
+ end
+
+ describe "updating a template" do
+ let!(:template) { create(:template, :proposal_answer, organization: organization) }
+ let(:participatory_process) { create :participatory_process, title: { en: "A participatory process" }, organization: organization }
+ let!(:proposals_component) { create :component, manifest_name: :proposals, name: { en: "A component" }, participatory_space: participatory_process }
+
+ before do
+ visit decidim_admin_templates.proposal_answer_templates_path
+ click_link translated(template.name)
+ end
+
+ shared_examples "updates a template with scopes" do |scope_name|
+ it "updates a template" do
+ fill_in_i18n(
+ :proposal_answer_template_name,
+ "#proposal_answer_template-name-tabs",
+ en: "My new name",
+ es: "Mi nuevo nombre",
+ ca: "El meu nou nom"
+ )
+
+ select scope_name, from: :proposal_answer_template_scope_for_availability
+
+ within ".edit_proposal_answer_template" do
+ page.find("*[type=submit]").click
+ end
+
+ expect(page).to have_admin_callout("successfully")
+ expect(page).to have_current_path decidim_admin_templates.proposal_answer_templates_path
+ within ".questionnaire-templates" do
+ expect(page).to have_i18n_content(scope_name)
+ expect(page).to have_content("My new name")
+ end
+ end
+ end
+
+ it_behaves_like "updates a template with scopes", "Global (available everywhere)"
+ it_behaves_like "updates a template with scopes", "Participatory process: A participatory process > A component"
+ end
+
+ describe "updating a template with invalid values" do
+ let!(:template) { create(:template, :proposal_answer, organization: organization) }
+
+ before do
+ visit decidim_admin_templates.proposal_answer_templates_path
+ click_link translated(template.name)
+ end
+
+ it "does not update the template" do
+ fill_in_i18n(
+ :proposal_answer_template_name,
+ "#proposal_answer_template-name-tabs",
+ en: "",
+ es: "",
+ ca: ""
+ )
+
+ within ".edit_proposal_answer_template" do
+ find("*[type=submit]").click
+ end
+
+ expect(page).to have_admin_callout("problem")
+ end
+ end
+
+ describe "copying a template" do
+ let!(:template) { create(:template, :proposal_answer, organization: organization) }
+
+ before do
+ visit decidim_admin_templates.proposal_answer_templates_path
+ end
+
+ it "copies the template" do
+ within find("tr", text: translated(template.name)) do
+ click_link "Duplicate"
+ end
+
+ expect(page).to have_admin_callout("successfully")
+ expect(page).to have_content(template.name["en"], count: 2)
+ end
+ end
+
+ describe "destroying a template" do
+ let!(:template) { create(:template, :proposal_answer, organization: organization) }
+
+ before do
+ visit decidim_admin_templates.proposal_answer_templates_path
+ end
+
+ it "destroys the template" do
+ within find("tr", text: translated(template.name)) do
+ accept_confirm { click_link "Delete" }
+ end
+
+ expect(page).to have_admin_callout("successfully")
+ expect(page).to have_no_i18n_content(template.name)
+ end
+ end
+
+ describe "using a proposal_answer_template" do
+ let(:participatory_process) { create :participatory_process, title: { en: "A participatory process" }, organization: organization }
+ let!(:component) { create :component, manifest_name: :proposals, name: { en: "A component" }, participatory_space: participatory_process }
+
+ let(:description) { "Some meaningful answer" }
+ let(:values) do
+ { internal_state: "rejected" }
+ end
+ let!(:template) { create(:template, :proposal_answer, description: { en: description }, field_values: values, organization: organization, templatable: component) }
+ let!(:proposal) { create(:proposal, component: component) }
+
+ before do
+ visit Decidim::EngineRouter.admin_proxy(component).root_path
+ find("a", class: "action-icon--show-proposal").click
+ end
+
+ it "uses the template" do
+ within ".edit_proposal_answer" do
+ select template.name["en"], from: :proposal_answer_template_chooser
+ expect(page).to have_content(description)
+ click_button "Answer"
+ end
+
+ expect(page).to have_admin_callout("Proposal successfully answered")
+
+ within find("tr", text: proposal.title["en"]) do
+ expect(page).to have_content("Rejected")
+ end
+ end
+ end
+end