Skip to content

Commit

Permalink
Add templates support for proposal's answers (decidim#11824)
Browse files Browse the repository at this point in the history
* Add Templates support for Proposal Answers

* Running linters

* Fixing failing specs

* Check for existing element

* Apply suggestions from code review

Co-authored-by: Andrés Pereira de Lucena <[email protected]>

* Apply review recommendations

* Fix lint

* Apply suggestions from code review

Co-authored-by: Andrés Pereira de Lucena <[email protected]>

* Apply review recommendations

* Changing spec description for clarity

* Lint

---------

Co-authored-by: Andrés Pereira de Lucena <[email protected]>
  • Loading branch information
alecslupu and andreslucena authored Nov 14, 2023
1 parent d397113 commit d814fdc
Show file tree
Hide file tree
Showing 24 changed files with 1,241 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
<h2 class="card-title"><%= t ".title", title: present(proposal).title %></h2>
</div>

<% if defined?(Decidim::Templates) %>
<%= render "decidim/templates/admin/proposal_answer_templates/template_chooser", form: f %>
<% end %>

<div class="row column flex items-center gap-x-4 my-2">
<%= f.collection_radio_buttons :internal_state, [["not_answered", t(".not_answered")], ["accepted", t(".accepted")], ["rejected", t(".rejected")], ["evaluating", t(".evaluating")]], :first, :last, prompt: true do |builder|
builder.label { builder.radio_button + builder.text } end %>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# frozen_string_literal: true

module Decidim
module Templates
# A command with all the business logic when duplicating a proposal's answer template
module Admin
class CopyProposalAnswerTemplate < CopyTemplate
def copy_template
@copied_template = Template.create!(
organization: @template.organization,
name: @template.name,
description: @template.description,
target: :proposal_answer,
field_values: @template.field_values,
templatable: @template.templatable
)
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# frozen_string_literal: true

module Decidim
module Templates
module Admin
class CreateProposalAnswerTemplate < Decidim::Command
# Initializes the command.
#
# form - The source for this ProposalAnswerTemplate.
def initialize(form)
@form = form
end

def call
return broadcast(:invalid) unless @form.valid?

@template = Decidim.traceability.create!(
Template,
@form.current_user,
name: @form.name,
description: @form.description,
organization: @form.current_organization,
field_values: { internal_state: @form.internal_state },
target: :proposal_answer
)

@template.update!(templatable: identify_templateable_resource)

broadcast(:ok, @template)
end

private

def identify_templateable_resource
resource = @form.current_organization
if @form.component_constraint.present?
found_component = Decidim::Component.find_by(id: @form.component_constraint, manifest_name: "proposals")
if found_component.present?
resource = found_component&.participatory_space&.decidim_organization_id == @form.current_organization.id ? found_component : nil
end
end
resource
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# frozen_string_literal: true

module Decidim
module Templates
module Admin
class UpdateProposalAnswerTemplate < Decidim::Command
# Initializes the command.
#
# template - The Template to update.
# form - The form object containing the data to update.
# user - The user that updates the template.
def initialize(template, form, user)
@template = template
@form = form
@user = user
end

def call
return broadcast(:invalid) unless @form.valid?
return broadcast(:invalid) unless @user.organization == @template.organization

@template = Decidim.traceability.update!(
@template,
@user,
name: @form.name,
description: @form.description,
field_values: { internal_state: @form.internal_state },
target: :proposal_answer
)

@template.update!(templatable: identify_templateable_resource)

broadcast(:ok, @template)
end

private

def identify_templateable_resource
resource = @form.current_organization
if @form.component_constraint.present?
found_component = Decidim::Component.find_by(id: @form.component_constraint, manifest_name: "proposals")
if found_component.present?
resource = found_component&.participatory_space&.decidim_organization_id == @form.current_organization.id ? found_component : nil
end
end
resource
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
# frozen_string_literal: true

module Decidim
module Templates
module Admin
class ProposalAnswerTemplatesController < Decidim::Templates::Admin::ApplicationController
include Decidim::TranslatableAttributes
include Decidim::Paginable

helper_method :availability_option_as_text, :availability_options_for_select

def new
enforce_permission_to :create, :template
@form = form(ProposalAnswerTemplateForm).instance
end

def edit
enforce_permission_to(:update, :template, template:)
@form = form(ProposalAnswerTemplateForm).from_model(template)
end

def create
enforce_permission_to :create, :template

@form = form(ProposalAnswerTemplateForm).from_params(params)

CreateProposalAnswerTemplate.call(@form) do
on(:ok) do |_template|
flash[:notice] = I18n.t("templates.create.success", scope: "decidim.admin")
redirect_to proposal_answer_templates_path
end

on(:invalid) do
flash.now[:alert] = I18n.t("templates.create.error", scope: "decidim.admin")
render :new
end
end
end

def destroy
enforce_permission_to(:destroy, :template, template:)

DestroyTemplate.call(template, current_user) do
on(:ok) do
flash[:notice] = I18n.t("templates.destroy.success", scope: "decidim.admin")
redirect_to action: :index
end
end
end

def fetch
enforce_permission_to(:read, :template, template:)

response_object = {
state: template.field_values["internal_state"],
template: populate_template_interpolations(proposal)
}

respond_to do |format|
format.json do
render json: response_object.to_json
end
end
end

def update
enforce_permission_to(:update, :template, template:)
@form = form(ProposalAnswerTemplateForm).from_params(params)
UpdateProposalAnswerTemplate.call(template, @form, current_user) do
on(:ok) do |_questionnaire_template|
flash[:notice] = I18n.t("templates.update.success", scope: "decidim.admin")
redirect_to proposal_answer_templates_path
end

on(:invalid) do |template|
@template = template
flash.now[:error] = I18n.t("templates.update.error", scope: "decidim.admin")
render action: :edit
end
end
end

def copy
enforce_permission_to :copy, :template

CopyProposalAnswerTemplate.call(template, current_user) do
on(:ok) do
flash[:notice] = I18n.t("templates.copy.success", scope: "decidim.admin")
redirect_to action: :index
end

on(:invalid) do
flash[:alert] = I18n.t("templates.copy.error", scope: "decidim.admin")
redirect_to action: :index
end
end
end

def index
enforce_permission_to :index, :templates
@templates = collection

respond_to do |format|
format.html { render :index }
format.json do
term = params[:term]

@templates = search(term)

render json: @templates.map { |t| { value: t.id, label: translated_attribute(t.name) } }
end
end
end

private

def populate_template_interpolations(proposal)
template.description.to_h do |language, value|
value.gsub!("%{organization}", proposal.organization.name)
value.gsub!("%{name}", author_name(proposal))
value.gsub!("%{admin}", current_user.name)

[language, value]
end
end

def author_name(proposal)
proposal.creator_author.try(:title) || proposal.creator_author.try(:name)
end

def proposal
@proposal ||= Decidim::Proposals::Proposal.find(params[:proposalId])
end

def availability_option_as_text(template)
return unless template.templatable_type
return t("global_scope", scope: "decidim.templates.admin.proposal_answer_templates.index") if template.templatable == current_organization

avaliablity_options.select { |a| a.last == template.templatable_id }&.flatten&.first || t("templates.missing_resource", scope: "decidim.admin")
end

def availability_options_for_select
avaliablity_options
end

def avaliablity_options
@avaliablity_options = []
@avaliablity_options.push [t("global_scope", scope: "decidim.templates.admin.proposal_answer_templates.index"), 0]

Decidim::Component.includes(:participatory_space).where(manifest_name: [:proposals])
.select { |a| a.participatory_space.decidim_organization_id == current_organization.id }.each do |component|
@avaliablity_options.push [formatted_name(component), component.id]
end

@avaliablity_options
end

def formatted_name(component)
space_type = t(component.participatory_space.class.name.underscore, scope: "activerecord.models", count: 1)
"#{space_type}: #{translated_attribute(component.participatory_space.title)} > #{translated_attribute(component.name)}"
end

def template
@template ||= Template.find_by(id: params[:id])
end

def collection
@collection ||= paginate(current_organization.templates.where(target: :proposal_answer).order(:id))
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# frozen_string_literal: true

module Decidim
module Templates
module Admin
class ProposalAnswerTemplateForm < TemplateForm
attribute :internal_state, String
attribute :component_constraint, Integer

validates :internal_state, presence: true

def map_model(model)
self.internal_state = model.field_values["internal_state"]
self.component_constraint = if model.templatable_type == "Decidim::Organization"
0
else
model.templatable&.id
end
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
import "src/decidim/templates/admin/block_template_chooser"
import "src/decidim/templates/admin/block_user_template_chooser"
import "src/decidim/templates/admin/proposal_answer_template_chooser"

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Choose a Block User Message template, get it by AJAX and add the Template in the justification textarea
document.addEventListener("DOMContentLoaded", () => {
const blockTemplateChooser = document.getElementById("block_template_chooser");
if (blockTemplateChooser) {
blockTemplateChooser.addEventListener("change", () => {
const dropdown = document.getElementById("block_template_chooser");
const url = dropdown.getAttribute("data-url");
const templateId = dropdown.value;

if (templateId === "") {
return;
}
fetch(`${new URL(url).pathname}?${new URLSearchParams({ id: templateId })}`).
then((response) => response.json()).
then((data) => {
document.getElementById("block_user_justification").value = data.template;
})
});
}
});
Loading

0 comments on commit d814fdc

Please sign in to comment.