diff --git a/Gemfile b/Gemfile
index 7d45dca..d0a556b 100644
--- a/Gemfile
+++ b/Gemfile
@@ -15,7 +15,7 @@ DECIDIM_VERSION = Decidim::AccountabilitySimple.decidim_version
gem "decidim", DECIDIM_VERSION
gem "decidim-apifiles", github: "mainio/decidim-module-apifiles", branch: "main"
gem "decidim-favorites", github: "mainio/decidim-module-favorites", branch: "develop"
-gem "decidim-locations", github: "mainio/decidim-module-locations", branch: "main"
+gem "decidim-locations", github: "mainio/decidim-module-locations", branch: "develop"
gem "decidim-tags", github: "mainio/decidim-module-tags", branch: "main"
gem "decidim-accountability_simple", path: "."
diff --git a/Gemfile.lock b/Gemfile.lock
index e64953d..52aaa7e 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -16,12 +16,13 @@ GIT
GIT
remote: https://github.com/mainio/decidim-module-locations.git
- revision: c3991933b8c6899e70d9faac57e862c11bd7e039
- branch: main
+ revision: ce3b54b80f1502ef75acc662ca1decba70f76ff9
+ branch: develop
specs:
decidim-locations (0.27.4)
decidim-core (~> 0.27.4)
- rgeo-geojson (~> 2.1, >= 2.1.1)
+ rgeo (= 3.0.1)
+ rgeo-geojson (= 2.1.1)
GIT
remote: https://github.com/mainio/decidim-module-tags.git
diff --git a/app/cells/decidim/accountability/result_m_cell.rb b/app/cells/decidim/accountability/result_m_cell.rb
index 6d2dfe4..2d110eb 100644
--- a/app/cells/decidim/accountability/result_m_cell.rb
+++ b/app/cells/decidim/accountability/result_m_cell.rb
@@ -67,18 +67,39 @@ def render_column?
!context[:no_column].presence
end
- def has_image?
+ def has_list_image?
model.list_image && model.list_image.attached?
end
+ def has_main_image?
+ model.main_image && model.main_image.attached?
+ end
+
def has_category?
model.category.present?
end
def resource_image_path
- return unless has_image?
+ return model.attached_uploader(:list_image).variant_url(resource_image_variant) if has_list_image?
+ return model.attached_uploader(:main_image).variant_url(resource_image_variant) if has_main_image?
+ return unless has_category?
+
+ category_image_path(model.category)
+ end
+
+ def category_image_path(cat)
+ return unless cat.respond_to?(:category_image_url)
+ return unless cat.category_image.attached?
+
+ cat.category_image_url(category_image_variant)
+ end
+
+ def resource_image_variant
+ :thumbnail
+ end
- model.attached_uploader(:list_image).url
+ def category_image_variant
+ :card
end
def status_label
diff --git a/app/commands/concerns/decidim/accountability_simple/admin/create_result_extensions.rb b/app/commands/concerns/decidim/accountability_simple/admin/create_result_extensions.rb
index 98c9703..11b44bd 100644
--- a/app/commands/concerns/decidim/accountability_simple/admin/create_result_extensions.rb
+++ b/app/commands/concerns/decidim/accountability_simple/admin/create_result_extensions.rb
@@ -126,6 +126,7 @@ def create_result_links
def create_result_link(form_result_link)
result_link_attributes = {
+ link_collection: @result.result_link_collections.find_by(id: form_result_link.collection_id),
label: form_result_link.label,
url: form_result_link.url,
position: form_result_link.position,
diff --git a/app/commands/concerns/decidim/accountability_simple/admin/update_result_extensions.rb b/app/commands/concerns/decidim/accountability_simple/admin/update_result_extensions.rb
index 7434fbb..06a56f6 100644
--- a/app/commands/concerns/decidim/accountability_simple/admin/update_result_extensions.rb
+++ b/app/commands/concerns/decidim/accountability_simple/admin/update_result_extensions.rb
@@ -122,6 +122,7 @@ def update_result_links
def update_result_link(form_result_link)
result_link_attributes = {
+ link_collection: result.result_link_collections.find_by(id: form_result_link.collection_id),
label: form_result_link.label,
url: form_result_link.url,
position: form_result_link.position
diff --git a/app/commands/decidim/accountability/admin/create_link_collection.rb b/app/commands/decidim/accountability/admin/create_link_collection.rb
new file mode 100644
index 0000000..ce25dbc
--- /dev/null
+++ b/app/commands/decidim/accountability/admin/create_link_collection.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+module Decidim
+ module Accountability
+ module Admin
+ # This command is executed when the user creates a ResultLinkCollection
+ # for a Result from the admin panel.
+ class CreateLinkCollection < Decidim::Command
+ def initialize(form)
+ @form = form
+ end
+
+ # Creates the link collection if valid.
+ #
+ # Broadcasts :ok if successful, :invalid otherwise.
+ def call
+ return broadcast(:invalid) if @form.invalid?
+
+ transaction do
+ create_link_collection
+ end
+
+ broadcast(:ok, link_collection)
+ end
+
+ private
+
+ attr_reader :link_collection
+
+ def create_link_collection
+ @link_collection = Decidim.traceability.create!(
+ AccountabilitySimple::ResultLinkCollection,
+ @form.current_user,
+ params,
+ visibility: "all"
+ )
+ end
+
+ def params
+ {
+ decidim_accountability_result_id: @form.decidim_accountability_result_id,
+ key: @form.key,
+ name: @form.name,
+ position: @form.position
+ }
+ end
+ end
+ end
+ end
+end
diff --git a/app/commands/decidim/accountability/admin/update_link_collection.rb b/app/commands/decidim/accountability/admin/update_link_collection.rb
new file mode 100644
index 0000000..7465b5b
--- /dev/null
+++ b/app/commands/decidim/accountability/admin/update_link_collection.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+module Decidim
+ module Accountability
+ module Admin
+ # This command is executed when the user updates a ResultLinkCollection
+ # for a Result from the admin panel.
+ class UpdateLinkCollection < Decidim::Command
+ def initialize(form, link_collection)
+ @form = form
+ @link_collection = link_collection
+ end
+
+ # Updates the link collection if valid.
+ #
+ # Broadcasts :ok if successful, :invalid otherwise.
+ def call
+ return broadcast(:invalid) if @form.invalid?
+
+ transaction do
+ update_link_collection
+ end
+
+ broadcast(:ok, link_collection)
+ end
+
+ private
+
+ attr_reader :link_collection
+
+ def update_link_collection
+ Decidim.traceability.update!(
+ link_collection,
+ @form.current_user,
+ params
+ )
+ end
+
+ def params
+ {
+ key: @form.key,
+ name: @form.name,
+ position: @form.position
+ }
+ end
+ end
+ end
+ end
+end
diff --git a/app/controllers/concerns/decidim/accountability_simple/admin/statuses_controller_extensions.rb b/app/controllers/concerns/decidim/accountability_simple/admin/statuses_controller_extensions.rb
new file mode 100644
index 0000000..3a46df4
--- /dev/null
+++ b/app/controllers/concerns/decidim/accountability_simple/admin/statuses_controller_extensions.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module Decidim
+ module AccountabilitySimple
+ module Admin
+ module StatusesControllerExtensions
+ extend ActiveSupport::Concern
+
+ included do
+ private
+
+ # Order the statuses based on progress.
+ def statuses
+ @statuses ||= Decidim::Accountability::Status.where(component: current_component).order(:progress).page(params[:page]).per(15)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/app/controllers/decidim/accountability/admin/link_collections_controller.rb b/app/controllers/decidim/accountability/admin/link_collections_controller.rb
new file mode 100644
index 0000000..804c77e
--- /dev/null
+++ b/app/controllers/decidim/accountability/admin/link_collections_controller.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+module Decidim
+ module Accountability
+ module Admin
+ # Controller that allows managing all the link collections for a result.
+ class LinkCollectionsController < Admin::ApplicationController
+ helper_method :result
+
+ def index
+ enforce_permission_to :create, :result
+ end
+
+ def new
+ enforce_permission_to :create, :result
+ @form = form(AccountabilitySimple::Admin::ResultLinkCollectionForm).from_params({}, result: result)
+ end
+
+ def create
+ enforce_permission_to :create, :result
+ @form = form(AccountabilitySimple::Admin::ResultLinkCollectionForm).from_params(params, result: result)
+
+ CreateLinkCollection.call(@form) do
+ on(:ok) do
+ flash[:notice] = I18n.t("link_collections.create.success", scope: "decidim.accountability.admin")
+ redirect_to action: :index
+ end
+
+ on(:invalid) do
+ flash.now[:alert] = I18n.t("link_collections.create.error", scope: "decidim.accountability.admin")
+ render :new
+ end
+ end
+ end
+
+ def edit
+ @link_collection = collection.find(params[:id])
+ enforce_permission_to :update, :result, result: @link_collection.result
+ @form = form(AccountabilitySimple::Admin::ResultLinkCollectionForm).from_model(@link_collection, result: result)
+ end
+
+ def update
+ @link_collection = collection.find(params[:id])
+ enforce_permission_to :update, :result, result: @link_collection.result
+ @form = form(AccountabilitySimple::Admin::ResultLinkCollectionForm).from_params(params, result: result)
+
+ UpdateLinkCollection.call(@form, @link_collection) do
+ on(:ok) do
+ flash[:notice] = I18n.t("link_collections.update.success", scope: "decidim.accountability.admin")
+ redirect_to action: :index
+ end
+
+ on(:invalid) do
+ flash.now[:alert] = I18n.t("link_collections.update.error", scope: "decidim.accountability.admin")
+ render :edit
+ end
+ end
+ end
+
+ def destroy
+ @link_collection = collection.find(params[:id])
+ enforce_permission_to :destroy, :result, result: @link_collection.result
+
+ Decidim.traceability.perform_action!("delete", @link_collection, current_user) do
+ @link_collection.destroy!
+ end
+
+ flash[:notice] = I18n.t("link_collections.destroy.success", scope: "decidim.accountability.admin")
+
+ redirect_to action: :index
+ end
+
+ private
+
+ def result
+ @result ||= Result.where(component: current_component).find(params[:result_id])
+ end
+
+ def collection
+ @collection ||= result.result_link_collections
+ end
+ end
+ end
+ end
+end
diff --git a/app/forms/decidim/accountability_simple/admin/component_details_form.rb b/app/forms/decidim/accountability_simple/admin/component_details_form.rb
index 302e49d..4d515e8 100644
--- a/app/forms/decidim/accountability_simple/admin/component_details_form.rb
+++ b/app/forms/decidim/accountability_simple/admin/component_details_form.rb
@@ -16,7 +16,7 @@ def to_param
def map_model(model)
self.details = Decidim::AccountabilitySimple::ResultDetail.where(
accountability_result_detailable: model
- ).map do |detail|
+ ).order(:position).map do |detail|
DefaultDetailsForm.from_model(detail)
end
end
diff --git a/app/forms/decidim/accountability_simple/admin/result_default_details_form.rb b/app/forms/decidim/accountability_simple/admin/result_default_details_form.rb
index ba72492..3e598e5 100644
--- a/app/forms/decidim/accountability_simple/admin/result_default_details_form.rb
+++ b/app/forms/decidim/accountability_simple/admin/result_default_details_form.rb
@@ -7,6 +7,8 @@ module Admin
class ResultDefaultDetailsForm < Decidim::Form
include TranslatableAttributes
+ mimic :result_detail
+
translatable_attribute :title, String
translatable_attribute :description, String
diff --git a/app/forms/decidim/accountability_simple/admin/result_details_form.rb b/app/forms/decidim/accountability_simple/admin/result_details_form.rb
index 81d6f65..7d1053a 100644
--- a/app/forms/decidim/accountability_simple/admin/result_details_form.rb
+++ b/app/forms/decidim/accountability_simple/admin/result_details_form.rb
@@ -7,6 +7,8 @@ module Admin
class ResultDetailsForm < Decidim::Form
include TranslatableAttributes
+ mimic :result_detail
+
translatable_attribute :title, String
translatable_attribute :description, String
diff --git a/app/forms/decidim/accountability_simple/admin/result_link_collection_form.rb b/app/forms/decidim/accountability_simple/admin/result_link_collection_form.rb
new file mode 100644
index 0000000..e80e601
--- /dev/null
+++ b/app/forms/decidim/accountability_simple/admin/result_link_collection_form.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Decidim
+ module AccountabilitySimple
+ module Admin
+ # A form object to create or update attachment collections.
+ class ResultLinkCollectionForm < Form
+ include TranslatableAttributes
+
+ mimic :result_link_collection
+
+ attribute :key, String
+ attribute :position, Integer, default: 0
+ translatable_attribute :name, String
+
+ validates :name, translatable_presence: true
+ validates :decidim_accountability_result_id, presence: true
+
+ def decidim_accountability_result_id
+ context[:result]&.id
+ end
+ end
+ end
+ end
+end
diff --git a/app/forms/decidim/accountability_simple/admin/result_link_form.rb b/app/forms/decidim/accountability_simple/admin/result_link_form.rb
index 2d8e9f2..66289e2 100644
--- a/app/forms/decidim/accountability_simple/admin/result_link_form.rb
+++ b/app/forms/decidim/accountability_simple/admin/result_link_form.rb
@@ -7,9 +7,12 @@ module Admin
class ResultLinkForm < Decidim::Form
include TranslatableAttributes
+ mimic :result_link
+
translatable_attribute :label, String
translatable_attribute :url, String
+ attribute :collection_id, Integer
attribute :position, Integer
attribute :deleted, Boolean, default: false
@@ -17,6 +20,10 @@ class ResultLinkForm < Decidim::Form
validates :url, translatable_presence: true, unless: :deleted
validates :position, numericality: { greater_than_or_equal_to: 0 }, unless: :deleted
+ def map_model(model)
+ self.collection_id = model.decidim_accountability_simple_result_link_collection_id
+ end
+
def to_param
return id if id.present?
diff --git a/app/models/concerns/decidim/accountability_simple/result_extensions.rb b/app/models/concerns/decidim/accountability_simple/result_extensions.rb
index 0626761..48a035e 100644
--- a/app/models/concerns/decidim/accountability_simple/result_extensions.rb
+++ b/app/models/concerns/decidim/accountability_simple/result_extensions.rb
@@ -25,9 +25,17 @@ module ResultExtensions
has_many :result_details, -> { order(:position) }, as: :accountability_result_detailable,
class_name: "Decidim::AccountabilitySimple::ResultDetail"
has_many :result_detail_values, class_name: "Decidim::AccountabilitySimple::ResultDetailValue",
- foreign_key: "decidim_accountability_result_id"
+ foreign_key: "decidim_accountability_result_id",
+ dependent: :destroy
+ has_many :result_link_collections,
+ -> { order(:position) },
+ class_name: "Decidim::AccountabilitySimple::ResultLinkCollection",
+ foreign_key: "decidim_accountability_result_id",
+ inverse_of: :result,
+ dependent: :destroy
has_many :result_links, -> { order(:position) }, class_name: "Decidim::AccountabilitySimple::ResultLink",
- foreign_key: "decidim_accountability_result_id"
+ foreign_key: "decidim_accountability_result_id",
+ dependent: :destroy
def result_default_details
Decidim::AccountabilitySimple::ResultDetail.where(
diff --git a/app/models/decidim/accountability_simple/result_link.rb b/app/models/decidim/accountability_simple/result_link.rb
index 81f9298..bde31c7 100644
--- a/app/models/decidim/accountability_simple/result_link.rb
+++ b/app/models/decidim/accountability_simple/result_link.rb
@@ -6,6 +6,10 @@ module AccountabilitySimple
class ResultLink < Accountability::ApplicationRecord
belongs_to :result, foreign_key: "decidim_accountability_result_id",
class_name: "Decidim::Accountability::Result"
+ belongs_to :link_collection, foreign_key: "decidim_accountability_simple_result_link_collection_id",
+ class_name: "Decidim::AccountabilitySimple::ResultLinkCollection",
+ inverse_of: :links,
+ optional: true
end
end
end
diff --git a/app/models/decidim/accountability_simple/result_link_collection.rb b/app/models/decidim/accountability_simple/result_link_collection.rb
new file mode 100644
index 0000000..fdabbe7
--- /dev/null
+++ b/app/models/decidim/accountability_simple/result_link_collection.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Decidim
+ module AccountabilitySimple
+ # Stores links collections related to accountability results.
+ class ResultLinkCollection < Accountability::ApplicationRecord
+ belongs_to :result, foreign_key: "decidim_accountability_result_id",
+ class_name: "Decidim::Accountability::Result"
+ has_many :links, foreign_key: "decidim_accountability_simple_result_link_collection_id",
+ class_name: "Decidim::AccountabilitySimple::ResultLink",
+ inverse_of: :link_collection,
+ dependent: :nullify
+ end
+ end
+end
diff --git a/app/views/decidim/accountability/admin/link_collections/_form.html.erb b/app/views/decidim/accountability/admin/link_collections/_form.html.erb
new file mode 100644
index 0000000..e764490
--- /dev/null
+++ b/app/views/decidim/accountability/admin/link_collections/_form.html.erb
@@ -0,0 +1,19 @@
+
+ <%= f.submit t("link_collections.edit.update", scope: "decidim.accountability.admin") %>
+
+<% end %>
diff --git a/app/views/decidim/accountability/admin/link_collections/index.html.erb b/app/views/decidim/accountability/admin/link_collections/index.html.erb
new file mode 100644
index 0000000..d5c1179
--- /dev/null
+++ b/app/views/decidim/accountability/admin/link_collections/index.html.erb
@@ -0,0 +1,46 @@
+<% add_decidim_page_title(t("link_collections.index.link_collections_title", scope: "decidim.accountability.admin")) %>
+
+ <%= f.submit t("link_collections.new.create", scope: "decidim.accountability.admin") %>
+
+<% end %>
diff --git a/app/views/decidim/accountability/admin/results/_form.html.erb b/app/views/decidim/accountability/admin/results/_form.html.erb
index bbc34f2..c30bd31 100644
--- a/app/views/decidim/accountability/admin/results/_form.html.erb
+++ b/app/views/decidim/accountability/admin/results/_form.html.erb
@@ -48,7 +48,7 @@
- <%= form.select :decidim_accountability_status_id, statuses.map{|status| [translated_attribute(status.name), status.id, { "data-progress" => status.progress }] }, include_blank: true %>
+ <%= form.select :decidim_accountability_status_id, statuses.order(:progress).map{|status| [translated_attribute(status.name), status.id, { "data-progress" => status.progress }] }, include_blank: true %>
diff --git a/app/views/decidim/accountability/admin/results/_result_link.html.erb b/app/views/decidim/accountability/admin/results/_result_link.html.erb
index aafe8e8..86dd45a 100644
--- a/app/views/decidim/accountability/admin/results/_result_link.html.erb
+++ b/app/views/decidim/accountability/admin/results/_result_link.html.erb
@@ -22,6 +22,11 @@
+ <% if @result && (collections = @result.result_link_collections).any? %>
+
+ <%= form.select :collection_id, collections.map { |c| [translated_attribute(c.name), c.id] }, include_blank: true, tabs_id: tabs_id %>
+
+ <% end %>
<%= form.translated :text_field, :label, tabs_id: tabs_id %>
diff --git a/app/views/decidim/accountability/admin/results/index.html.erb b/app/views/decidim/accountability/admin/results/index.html.erb
index 4dc1b79..02cc826 100644
--- a/app/views/decidim/accountability/admin/results/index.html.erb
+++ b/app/views/decidim/accountability/admin/results/index.html.erb
@@ -58,6 +58,10 @@
<%= icon_link_to "paperclip", result_attachments_path(result), t("actions.attachments", scope: "decidim.accountability"), class: "action-icon--attachments" %>
<% end %>
+ <% if allowed_to? :update, :result, result: result %>
+ <%= icon_link_to "globe", result_link_collections_path(result), t("actions.link_collections", scope: "decidim.accountability"), class: "action-icon--link_collections" %>
+ <% end %>
+
<% if allowed_to? :destroy, :result, result: result %>
<%= icon_link_to "circle-x", result_path(result), t("actions.destroy", scope: "decidim.accountability"), class: "action-icon--remove", method: :delete, data: { confirm: t("actions.confirm_destroy", scope: "decidim.accountability", name: t("models.result.name", scope: "decidim.accountability.admin")) } %>
<% end %>
diff --git a/app/views/decidim/accountability/results/_result_collection_assets.html.erb b/app/views/decidim/accountability/results/_result_collection_assets.html.erb
new file mode 100644
index 0000000..452d1ba
--- /dev/null
+++ b/app/views/decidim/accountability/results/_result_collection_assets.html.erb
@@ -0,0 +1,2 @@
+<%= render partial: "result_collection_attachments", locals: { collection_key: collection_key } %>
+<%= render partial: "result_collection_links", locals: { collection_key: collection_key } %>
diff --git a/app/views/decidim/accountability/results/_result_collection_attachments.html.erb b/app/views/decidim/accountability/results/_result_collection_attachments.html.erb
new file mode 100644
index 0000000..97580f9
--- /dev/null
+++ b/app/views/decidim/accountability/results/_result_collection_attachments.html.erb
@@ -0,0 +1,19 @@
+<% collection = result.attachment_collections.find_by(key: collection_key) %>
+
+<% if collection && collection.attachments.any? %>
+ <% collection.attachments.each do |attachment| %>
+ <% next unless attachment.file.attached? %>
+
+ <% icon_type = attachment.photo? ? "image" : "file" %>
+
+ <%= link_to attachment.attached_uploader(:file).url, class: "asset", target: "_blank", data: { external_link_spacer: "" } do %>
+
+ <%= translated_attribute(attachment.title) %>
+ (<%= attachment.file_type %>, <%= number_to_human_size(attachment.file_size) %>)
+
+
+ <%= icon icon_type, role: "img", "aria-hidden": true %>
+
+ <% end %>
+ <% end %>
+<% end %>
diff --git a/app/views/decidim/accountability/results/_result_collection_links.html.erb b/app/views/decidim/accountability/results/_result_collection_links.html.erb
new file mode 100644
index 0000000..03d80bf
--- /dev/null
+++ b/app/views/decidim/accountability/results/_result_collection_links.html.erb
@@ -0,0 +1,12 @@
+<% collection = result.result_link_collections.find_by(key: collection_key) %>
+
+<% if collection && collection.links.any? %>
+ <% collection.links.each do |link| %>
+ <%= link_to translated_attribute(link.url), class: "asset", target: "_blank", data: { external_link_spacer: "" } do %>
+
<%= translated_attribute(link.label) %>
+
+ <%= icon "external-link", role: "img", "aria-hidden": true %>
+
+ <% end %>
+ <% end %>
+<% end %>
diff --git a/app/views/decidim/accountability/results/_show_parent.html.erb b/app/views/decidim/accountability/results/_show_parent.html.erb
index 9498151..82bdb9f 100644
--- a/app/views/decidim/accountability/results/_show_parent.html.erb
+++ b/app/views/decidim/accountability/results/_show_parent.html.erb
@@ -51,9 +51,9 @@ root_result = root_result.parent while root_result.parent
- <%= attachments_for result %>
+ <%#= attachments_for result %>
- <%# = render partial: "linked_resources" %>
+ <%#= render partial: "linked_resources" %>
diff --git a/app/views/decidim/accountability/results/_show_result_body.html.erb b/app/views/decidim/accountability/results/_show_result_body.html.erb
index e7cd410..1d52fe1 100644
--- a/app/views/decidim/accountability/results/_show_result_body.html.erb
+++ b/app/views/decidim/accountability/results/_show_result_body.html.erb
@@ -9,6 +9,4 @@
<%== cell("decidim/tags/tags", result) %>
<%= cell "decidim/tags", result, context: { extra_classes: ["tags--result"] } %>
-
- <%= render partial: "timeline", locals: { result: result } if result.timeline_entries.any? %>
-
+<%= render partial: "timeline", locals: { result: result } if result.timeline_entries.any? %>
diff --git a/app/views/decidim/accountability/results/_stats_box.html.erb b/app/views/decidim/accountability/results/_stats_box.html.erb
index 0f068fe..ed0d5cb 100644
--- a/app/views/decidim/accountability/results/_stats_box.html.erb
+++ b/app/views/decidim/accountability/results/_stats_box.html.erb
@@ -1,18 +1,38 @@
-<% if component_settings.display_progress_enabled? && result.progress.present? %>
+<% statuses = Decidim::Accountability::Status.where(component: current_component).order(:progress) %>
+
+<% if component_settings.display_progress_enabled? && (statuses.any? || result.progress.present?) %>
-
Toteutuksen vaiheet
+
<%= t(".implementation_phases") %>
+
+ <% if statuses.any? %>
+
+ <% end %>
-
-
Valmiusaste
-
<%= display_percentage(result.progress) %>
-
+ <% if result.progress.present? %>
+
+
<%= t(".progress") %>
+
<%= display_percentage(result.progress) %>
+
-
+
-
- <%= t(".average_progress", progress: display_percentage(progress_calculator(current_scope, nil))) %>
-
+
+ <%= t(".average_progress", progress: display_percentage(progress_calculator(current_scope, nil))) %>
+
+ <% end %>
<% end %>
diff --git a/app/views/decidim/accountability/results/_timeline.html.erb b/app/views/decidim/accountability/results/_timeline.html.erb
index 17ebdc0..6ccab2a 100644
--- a/app/views/decidim/accountability/results/_timeline.html.erb
+++ b/app/views/decidim/accountability/results/_timeline.html.erb
@@ -1,28 +1,22 @@
-
-
-
<%= t(".title") %>
-
- <% result.timeline_entries.each_with_index do |timeline_entry, i| %>
- - ">
-
- <%= i + 1 %>
-
-
-
-
- <% if translated_attribute(timeline_entry.title).empty? %>
- <%= human_range_time(timeline_entry.entry_date, timeline_entry.end_date) %>
- <% else %>
- <%= translated_attribute(timeline_entry.title) %>
- <% end %>
-
-
- <%= translated_attribute timeline_entry.description %>
-
-
-
-
- <% end %>
-
-
-
+
<%= t(".title") %>
+
+ <% result.timeline_entries.each_with_index do |timeline_entry, i| %>
+ - ">
+
+ <%= i + 1 %>
+
+
+
+ <% if translated_attribute(timeline_entry.title)&.strip.empty? %>
+ <%= human_range_time(timeline_entry.entry_date, timeline_entry.end_date) %>
+ <% else %>
+ <%= translated_attribute(timeline_entry.title) %>
+ <% end %>
+
+
+ <%= translated_attribute timeline_entry.description %>
+
+
+
+ <% end %>
+
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 3923b85..778569b 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -6,6 +6,18 @@ en:
list_image: List image
main_image: Main image
summary: Summary
+ result_detail:
+ description: Description
+ icon: Icon
+ title: Title
+ result_link:
+ collection_id: Link collection
+ label: Label
+ url: URL address
+ result_link_collection:
+ key: Key
+ name: Name
+ position: Order number
timeline_entry:
end_date: End date
title: Title (replaces the dates)
@@ -20,6 +32,7 @@ en:
attachment_collections: Folders
attachments: Attachments
default_details: Default details
+ link_collections: Link collections
admin:
details:
detail:
@@ -35,6 +48,26 @@ en:
update:
invalid: There was a problem updating the default details
success: Default details successfully updated
+ link_collections:
+ create:
+ error: There was a problem creating a new link collection.
+ success: Link collection successfully created.
+ destroy:
+ success: Link collection successfully deleted.
+ edit:
+ title: Edit link collection
+ update: Update link collection
+ index:
+ link_collections_title: Link collections
+ update:
+ error: There was a problem updating the link collection.
+ success: Link collection successfully updated.
+ new:
+ create: Create link collection
+ title: New link collection
+ models:
+ link_collection:
+ name: Link collection
results:
authors_picker:
collection_name: Projects
@@ -139,8 +172,10 @@ en:
subtitle: Project
stats_box:
average_progress: Average progress on all projects %{progress}.
+ implementation_phases: Phases
+ progress: Progress
timeline:
- title: Implementation timeline
+ title: Timeline
accountability_simple:
models:
result_detail:
diff --git a/config/locales/fi.yml b/config/locales/fi.yml
index dc95d88..bd591aa 100644
--- a/config/locales/fi.yml
+++ b/config/locales/fi.yml
@@ -6,6 +6,18 @@ fi:
list_image: Listauksen kuva
main_image: Pääkuva
summary: Tiivistelmä
+ result_detail:
+ description: Kuvaus
+ icon: Ikoni
+ title: Otsikko
+ result_link:
+ collection_id: Linkkikokoelma
+ label: Teksti
+ url: URL-osoite
+ result_link_collection:
+ key: Tunniste
+ name: Nimi
+ position: Järjestysnumero
timeline_entry:
end_date: Päättymispäivä
title: Otsake (päivämäärien tilalla)
@@ -20,6 +32,7 @@ fi:
attachment_collections: Kansiot
attachments: Liitteet
default_details: Oletustiedot
+ link_collections: Linkkikokoelmat
admin:
details:
detail:
@@ -35,6 +48,26 @@ fi:
update:
invalid: Oletustietojen päivitys epäonnistui
success: Oletustietojen päivitys onnistui
+ link_collections:
+ create:
+ error: Linkkikokoelman luonti epäonnistui.
+ success: Linkkikokoelman luonti onnistui.
+ destroy:
+ success: Linkkikokoelman poisto onnistui.
+ edit:
+ title: Muokkaa linkkikokoelmaa
+ update: Päivitä linkkikokoelmaa
+ index:
+ link_collections_title: Linkkikokoelmat
+ update:
+ error: Linkkikokoelman päivittäminen epäonnistui.
+ success: Linkkikokoelman päivittäminen onnistui.
+ new:
+ create: Luo linkkikokoelma
+ title: Uusi linkkikokoelma
+ models:
+ link_collection:
+ name: Linkkikokoelma
results:
authors_picker:
collection_name: Hankkeet
@@ -139,8 +172,10 @@ fi:
subtitle: Hanke
stats_box:
average_progress: Kaikkien hankkeiden edistymisen keskiarvo %{progress}.
+ implementation_phases: Toteutuksen vaiheet
+ progress: Valmiusaste
timeline:
- title: Toteutuksen aikajana
+ title: Aikataulu
accountability_simple:
models:
result_detail:
diff --git a/config/locales/sv.yml b/config/locales/sv.yml
index abd76a6..f530f10 100644
--- a/config/locales/sv.yml
+++ b/config/locales/sv.yml
@@ -6,6 +6,18 @@ sv:
list_image: Lista bild
main_image: Huvudbild
summary: Sammanfattning
+ result_detail:
+ description: Beskrivning
+ icon: Ikon
+ title: Titel
+ result_link:
+ collection_id: Länksamling
+ label: Etikett
+ url: URL-adress
+ result_link_collection:
+ key: Nyckel
+ name: Namn
+ position: Ordernummer
timeline_entry:
end_date: Slutdatum
title: Titel (ersätter datumen)
@@ -20,6 +32,7 @@ sv:
attachment_collections: Mappar
attachments: Bilagor
default_details: Default detaljer
+ link_collections: Länksamlingar
admin:
details:
detail:
@@ -35,6 +48,23 @@ sv:
update:
invalid: Det gick inte att uppdatera standardin detaljer
success: Standard detaljer har uppdaterats
+ link_collections:
+ create:
+ error: Det gick inte att skapa en ny länksamling.
+ success: Länksamling har skapats.
+ destroy:
+ success: Länksamlingen har raderats.
+ edit:
+ title: Redigera länksamling
+ update: Uppdatera länksamling
+ index:
+ link_collections_title: Länksamlingar
+ update:
+ error: Det uppstod ett problem med att uppdatera länksamlingen.
+ success: Länksamlingen har uppdaterats.
+ new:
+ create: Skapa länksamling
+ title: Ny länksamling
results:
authors_picker:
collection_name: Projekt
@@ -139,8 +169,10 @@ sv:
subtitle: Projekt
stats_box:
average_progress: Genomsnittlig framsteg på alla projekt %{progress}.
+ implementation_phases: Faser
+ progress: Genomförande
timeline:
- title: Implementeringstidslinje
+ title: Tidslinje
accountability_simple:
models:
result_detail:
diff --git a/db/migrate/20240325151040_create_decidim_accountability_result_link_collections.rb b/db/migrate/20240325151040_create_decidim_accountability_result_link_collections.rb
new file mode 100644
index 0000000..cef823c
--- /dev/null
+++ b/db/migrate/20240325151040_create_decidim_accountability_result_link_collections.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+class CreateDecidimAccountabilityResultLinkCollections < ActiveRecord::Migration[6.1]
+ def change
+ create_table :decidim_accountability_simple_result_link_collections do |t|
+ t.string :key, index: { name: "index_result_link_collections_on_key" }
+ t.jsonb :name, null: false
+ t.integer :position, null: false, default: 0
+ t.references(
+ :decidim_accountability_result,
+ foreign_key: true,
+ index: { name: "index_result_link_collections_on_result_id" }
+ )
+ end
+
+ # Add the foreign key to result links
+ add_column(
+ :decidim_accountability_simple_result_links,
+ :decidim_accountability_simple_result_link_collection_id,
+ :integer,
+ null: true
+ )
+ add_index(
+ :decidim_accountability_simple_result_links,
+ :decidim_accountability_simple_result_link_collection_id,
+ name: "index_result_link_link_collection_id"
+ )
+ add_foreign_key(
+ :decidim_accountability_simple_result_links,
+ :decidim_accountability_simple_result_link_collections,
+ column: :decidim_accountability_simple_result_link_collection_id,
+ on_delete: :nullify,
+ name: "fk_result_link_link_collection_id"
+ )
+ end
+end
diff --git a/lib/decidim/accountability_simple/api.rb b/lib/decidim/accountability_simple/api.rb
index b39a1dc..75cc7a5 100644
--- a/lib/decidim/accountability_simple/api.rb
+++ b/lib/decidim/accountability_simple/api.rb
@@ -9,6 +9,9 @@ module AccountabilitySimple
autoload :ResultDetailType, "decidim/api/result_detail_type"
autoload :ResultDetailValueType, "decidim/api/result_detail_value_type"
autoload :ResultLinkAttributes, "decidim/api/result_link_attributes"
+ autoload :ResultLinkCollectionAttributes, "decidim/api/result_link_collection_attributes"
+ autoload :ResultLinkCollectionInput, "decidim/api/result_link_collection_input"
+ autoload :ResultLinkCollectionType, "decidim/api/result_link_collection_type"
autoload :ResultLinkType, "decidim/api/result_link_type"
autoload :ResultMutationType, "decidim/api/result_mutation_type"
autoload :ResultTimelineEntryMutationType, "decidim/api/result_timeline_entry_mutation_type"
diff --git a/lib/decidim/accountability_simple/api/types/result_type_extensions.rb b/lib/decidim/accountability_simple/api/types/result_type_extensions.rb
index d27af87..7ace712 100644
--- a/lib/decidim/accountability_simple/api/types/result_type_extensions.rb
+++ b/lib/decidim/accountability_simple/api/types/result_type_extensions.rb
@@ -14,10 +14,25 @@ def self.included(type)
type.field :published_at, Decidim::Core::DateTimeType, "The date and time this result was published at", null: true
type.field :summary, Decidim::Core::TranslatedFieldType, "The summary for this result", null: false
type.field :main_image, GraphQL::Types::String, "The main image URL for this result", null: true
+ type.field :main_image_blob, Decidim::Apifiles::BlobType, "The main image file blob for this result", null: true
type.field :list_image, GraphQL::Types::String, "The list image (thumbnail) URL for this result", null: true
+ type.field :list_image_blob, Decidim::Apifiles::BlobType, "The list image (thumbnail) file blob for this result", null: true
type.field :default_details, [Decidim::AccountabilitySimple::ResultDetailType], "The default details for this result", null: false
type.field :details, [Decidim::AccountabilitySimple::ResultDetailType], "The details for this result", null: false
- type.field :links, [Decidim::AccountabilitySimple::ResultLinkType], "The links for this resource", method: :result_links, null: false
+ type.field(
+ :link_collections,
+ [Decidim::AccountabilitySimple::ResultLinkCollectionType],
+ "The links collections for this resource",
+ method: :result_link_collections,
+ null: false
+ )
+ type.field(
+ :links,
+ [Decidim::AccountabilitySimple::ResultLinkType],
+ "The links for this resource",
+ method: :result_links,
+ null: false
+ )
end
def main_image
@@ -26,12 +41,20 @@ def main_image
object.attached_uploader(:main_image).url
end
+ def main_image_blob
+ object.main_image.blob
+ end
+
def list_image
return unless object.list_image.attached?
object.attached_uploader(:list_image).url
end
+ def list_image_blob
+ object.list_image.blob
+ end
+
def default_details
context.scoped_context[:parent] = object
diff --git a/lib/decidim/accountability_simple/engine.rb b/lib/decidim/accountability_simple/engine.rb
index 6aff03c..2422abc 100644
--- a/lib/decidim/accountability_simple/engine.rb
+++ b/lib/decidim/accountability_simple/engine.rb
@@ -20,6 +20,7 @@ class Engine < ::Rails::Engine
resources :results, only: [] do
resources :attachment_collections
resources :attachments
+ resources :link_collections, except: [:show]
member do
put :publish
put :unpublish
@@ -57,6 +58,10 @@ class Engine < ::Rails::Engine
end
# Needed for the 0.25 active storage migration
+ #
+ # Note:
+ # Causes deprecation warnings due to some autoloading happening when
+ # loading the legacy uploaders.
initializer "decidim_accountability_simple.activestorage_migration" do
next unless Decidim.const_defined?("CarrierWaveMigratorService")
@@ -121,6 +126,11 @@ class Engine < ::Rails::Engine
Decidim::ScopesHelper.include(
Decidim::AccountabilitySimple::ScopesHelperExtensions
)
+
+ # Controller extensions
+ Decidim::Accountability::Admin::StatusesController.include(
+ Decidim::AccountabilitySimple::Admin::StatusesControllerExtensions
+ )
end
end
end
diff --git a/lib/decidim/api/result_link_attributes.rb b/lib/decidim/api/result_link_attributes.rb
index aa19dd0..15208dd 100644
--- a/lib/decidim/api/result_link_attributes.rb
+++ b/lib/decidim/api/result_link_attributes.rb
@@ -10,6 +10,7 @@ class ResultLinkAttributes < GraphQL::Schema::InputObject
argument :position, GraphQL::Types::Int, required: true
argument :label, GraphQL::Types::JSON, required: true
argument :url, GraphQL::Types::JSON, required: true
+ argument :collection, Decidim::AccountabilitySimple::ResultLinkCollectionInput, required: false
end
end
end
diff --git a/lib/decidim/api/result_link_collection_attributes.rb b/lib/decidim/api/result_link_collection_attributes.rb
new file mode 100644
index 0000000..a0565d9
--- /dev/null
+++ b/lib/decidim/api/result_link_collection_attributes.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Decidim
+ module AccountabilitySimple
+ # The result link collection attributes for managing a result link
+ # collection.
+ class ResultLinkCollectionAttributes < GraphQL::Schema::InputObject
+ description "Attributes for result link collections"
+
+ argument :position, GraphQL::Types::Int, description: "The result link collection position, lowest first", required: false, default_value: 0
+ argument :key, GraphQL::Types::String, description: "The attachment collection key, i.e. its technical handle", required: false
+ argument :name, GraphQL::Types::JSON, description: "The attachment collection name localized hash, e.g. {\"en\": \"English name\"}", required: true
+ end
+ end
+end
diff --git a/lib/decidim/api/result_link_collection_input.rb b/lib/decidim/api/result_link_collection_input.rb
new file mode 100644
index 0000000..0e3bbac
--- /dev/null
+++ b/lib/decidim/api/result_link_collection_input.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+module Decidim
+ module AccountabilitySimple
+ class ResultLinkCollectionInput < GraphQL::Schema::InputObject
+ graphql_name "ResultLinkCollectionInput"
+ description "A type used for mapping result links to collections"
+
+ argument :id, GraphQL::Types::ID, "Maps the collection using its ID", required: false
+ argument :key, GraphQL::Types::String, "Maps the collection using its key", required: false
+
+ def prepare
+ id = arguments[:id]
+ key = arguments[:key].presence
+
+ raise GraphQL::ExecutionError, "Either id or key needs to be provided." if id.blank? && key.blank?
+ raise GraphQL::ExecutionError, "Only one of id or key can be provided at a time." if id.present? && key.present?
+ raise GraphQL::ExecutionError, "The key cannot be empty." if !key.nil? && key.empty?
+
+ super
+ end
+
+ def id_value
+ return arguments[:id].to_i if arguments[:id].present?
+
+ key = arguments[:key].presence
+ raise GraphQL::ExecutionError, "The key cannot be empty." if key.blank?
+ raise GraphQL::ExecutionError, "Outside of object context." if context[:current_object].blank?
+
+ parent = context[:current_object].object
+ raise GraphQL::ExecutionError, "Outside of record context." unless parent
+
+ collection = parent.result_link_collections.find_by(key: key.strip)
+ raise GraphQL::ExecutionError, "Key not found within the record's collections." unless collection
+
+ collection.id
+ end
+ end
+ end
+end
diff --git a/lib/decidim/api/result_link_collection_type.rb b/lib/decidim/api/result_link_collection_type.rb
new file mode 100644
index 0000000..178ede6
--- /dev/null
+++ b/lib/decidim/api/result_link_collection_type.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Decidim
+ module AccountabilitySimple
+ class ResultLinkCollectionType < Decidim::Api::Types::BaseObject
+ description "A result link collection"
+
+ field :id, GraphQL::Types::ID, "The id of this link collection", null: false
+ field :position, GraphQL::Types::Int, "The position of this link collection", null: false
+ field :key, GraphQL::Types::String, "A technical key for the link collection to identify a specific correct collection", null: true
+ field :name, Decidim::Core::TranslatedFieldType, "The name of this link collection", null: false
+ field :links, [Decidim::AccountabilitySimple::ResultLinkType, { null: true }], "The collection's links", null: false
+ end
+ end
+end
diff --git a/lib/decidim/api/result_mutation_type.rb b/lib/decidim/api/result_mutation_type.rb
index 7818729..ebc443b 100644
--- a/lib/decidim/api/result_mutation_type.rb
+++ b/lib/decidim/api/result_mutation_type.rb
@@ -49,6 +49,23 @@ class ResultMutationType < GraphQL::Schema::Object
argument :published, GraphQL::Types::Boolean, description: "Set the record published (true) or unpublished (false)", required: true
end
+ field :create_link_collection, Decidim::AccountabilitySimple::ResultLinkCollectionType, null: false do
+ description "Creates a link collection."
+
+ argument :attributes, Decidim::AccountabilitySimple::ResultLinkCollectionAttributes, description: "Input attributes to create a link collection", required: true
+ end
+
+ field :update_link_collection, Decidim::AccountabilitySimple::ResultLinkCollectionType, null: false do
+ description "Updates a link collection."
+
+ argument :id, GraphQL::Types::ID, required: true
+ argument :attributes, Decidim::AccountabilitySimple::ResultLinkCollectionAttributes, description: "Input attributes to update a link collection", required: true
+ end
+
+ field :delete_link_collection, Decidim::AccountabilitySimple::ResultLinkCollectionType, null: false do
+ argument :id, GraphQL::Types::ID, required: true
+ end
+
def update(**args)
enforce_permission_to :update, :result, result: object
@@ -103,6 +120,87 @@ def publicity(published:)
end
end
+ def create_link_collection(attributes:)
+ enforce_permission_to :update, :result, result: object
+
+ form = Decidim::AccountabilitySimple::Admin::ResultLinkCollectionForm.from_params(
+ link_collection_params(attributes)
+ ).with_context(
+ current_organization: current_organization,
+ current_component: object.component,
+ current_user: current_user,
+ result: object
+ )
+
+ link_collection = nil
+ Decidim::Accountability::Admin::CreateLinkCollection.call(form) do
+ on(:ok) do |collection|
+ link_collection = collection
+ end
+ end
+ return link_collection if link_collection.present?
+
+ if form.errors.any?
+ return GraphQL::ExecutionError.new(
+ form.errors.full_messages.join(", ")
+ )
+ end
+
+ GraphQL::ExecutionError.new(
+ I18n.t("decidim.accountability.admin.link_collections.create.error")
+ )
+ end
+
+ def update_link_collection(id:, attributes:)
+ enforce_permission_to :update, :result, result: object
+
+ link_collection = object.result_link_collections.find_by(id: id)
+ raise GraphQL::ExecutionError, "Invalid link collection ID provided: #{id}" unless link_collection
+
+ params = link_collection_params(attributes)
+
+ # Keep the original key through the API if the key wasn't provided
+ params[:key] = link_collection.key if attributes.key.blank?
+
+ form = Decidim::AccountabilitySimple::Admin::ResultLinkCollectionForm.from_params(params).with_context(
+ current_organization: current_organization,
+ current_component: object.component,
+ current_user: current_user,
+ result: object
+ )
+
+ status = nil
+ Decidim::Accountability::Admin::UpdateLinkCollection.call(form, link_collection) do
+ on(:ok) do
+ status = :ok
+ end
+ end
+ return link_collection if status == :ok
+
+ if form.errors.any?
+ return GraphQL::ExecutionError.new(
+ form.errors.full_messages.join(", ")
+ )
+ end
+
+ GraphQL::ExecutionError.new(
+ I18n.t("decidim.accountability.admin.link_collections.update.error")
+ )
+ end
+
+ def delete_link_collection(id:)
+ enforce_permission_to :destroy, :result, result: object
+
+ link_collection = object.result_link_collections.find_by(id: id)
+ raise GraphQL::ExecutionError, "Invalid link collection ID provided: #{id}" unless link_collection
+
+ Decidim.traceability.perform_action!("delete", link_collection, current_user) do
+ link_collection.destroy!
+ end
+
+ link_collection
+ end
+
protected
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
@@ -124,6 +222,16 @@ def result_params(**args)
"plan_ids" => args[:plan_ids],
"project_ids" => args[:project_ids]
}
+
+ # Image attributes
+ [:main_image, :list_image].each do |img_key|
+ val = image_attributes(img_key, args)
+ next if val.blank?
+
+ params.merge!(val)
+ end
+
+ # Linked attributes
if args[:default_details]
params["result_default_details"] = args[:default_details].map do |detargs|
{ "id" => detargs[:detail_id], "description" => detargs[:description] }
@@ -156,7 +264,8 @@ def result_params(**args)
"id" => linkargs[:id],
"position" => linkargs[:position],
"label" => linkargs[:label],
- "url" => linkargs[:url]
+ "url" => linkargs[:url],
+ "collection_id" => linkargs.collection&.id_value
}
end
deleted_links = object.result_links.where.not(
@@ -185,6 +294,22 @@ def result_params(**args)
end
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
+ def image_attributes(image_key, attrs)
+ image_attr = attrs[image_key]
+ return unless image_attr
+ return { "remove_#{image_key}" => true } if image_attr.remove
+
+ { image_key.to_s => image_attr.blob } if image_attr.blob
+ end
+
+ def link_collection_params(attributes)
+ {
+ "position" => attributes.position,
+ "key" => attributes.key,
+ "name" => attributes.name
+ }
+ end
+
private
def current_organization
diff --git a/spec/types/decidim/accountability/result_type_spec.rb b/spec/types/decidim/accountability/result_type_spec.rb
index 1d218fa..adcbb17 100644
--- a/spec/types/decidim/accountability/result_type_spec.rb
+++ b/spec/types/decidim/accountability/result_type_spec.rb
@@ -35,8 +35,32 @@
)
end
- it "returns the result's main image" do
- expect(response["mainImage"]).to match(model.attached_uploader(:main_image).url)
+ it "returns the result's main image URL" do
+ expect(response["mainImage"]).to eq(model.attached_uploader(:main_image).url)
+ end
+ end
+
+ describe "mainImageBlob" do
+ let(:query) { "{ mainImageBlob { id } }" }
+
+ before do
+ model.main_image.attach(
+ io: File.open(Decidim::Dev.asset("city.jpeg")),
+ filename: "city.jpeg",
+ content_type: "image/jpeg"
+ )
+ end
+
+ it "does not return the blob for unauthorized users" do
+ expect(response["mainImageBlob"]).to be_nil
+ end
+
+ context "when signed in as an admin" do
+ let!(:current_user) { create(:user, :confirmed, :admin, organization: current_organization) }
+
+ it "returns the result's main image blob" do
+ expect(response["mainImageBlob"]).to include("id" => model.main_image.blob.id.to_s)
+ end
end
end
@@ -51,8 +75,32 @@
)
end
- it "returns the result's main image" do
- expect(response["listImage"]).to match(model.attached_uploader(:list_image).url)
+ it "returns the result's list image URL" do
+ expect(response["listImage"]).to eq(model.attached_uploader(:list_image).url)
+ end
+ end
+
+ describe "listImageBlob" do
+ let(:query) { "{ listImageBlob { id } }" }
+
+ before do
+ model.list_image.attach(
+ io: File.open(Decidim::Dev.asset("city.jpeg")),
+ filename: "city.jpeg",
+ content_type: "image/jpeg"
+ )
+ end
+
+ it "does not return the blob for unauthorized users" do
+ expect(response["mainImageBlob"]).to be_nil
+ end
+
+ context "when signed in as an admin" do
+ let!(:current_user) { create(:user, :confirmed, :admin, organization: current_organization) }
+
+ it "returns the result's list image blob" do
+ expect(response["listImageBlob"]).to include("id" => model.list_image.blob.id.to_s)
+ end
end
end
diff --git a/spec/types/decidim/accountability_simple/result_mutation_type_spec.rb b/spec/types/decidim/accountability_simple/result_mutation_type_spec.rb
index ce7dab9..2029520 100644
--- a/spec/types/decidim/accountability_simple/result_mutation_type_spec.rb
+++ b/spec/types/decidim/accountability_simple/result_mutation_type_spec.rb
@@ -119,6 +119,217 @@
expect(model.linked_resources(:proposals, "included_proposals")).to eq([proposal])
expect(model.linked_resources(:projects, "included_projects")).to eq([project])
end
+
+ context "with main image" do
+ let(:blob) do
+ ActiveStorage::Blob.create_and_upload!(
+ io: File.open(Decidim::Dev.asset("city.jpeg")),
+ filename: "city.jpeg",
+ content_type: "image/jpeg"
+ )
+ end
+ let(:query) do
+ %(
+ {
+ update(
+ title: #{convert_value(title)},
+ summary: #{convert_value(summary)},
+ description: #{convert_value(description)},
+ progress: 50,
+ mainImage: { blobId: #{blob.id} }
+ ) { id }
+ }
+ )
+ end
+
+ it "sets the main image" do
+ response
+ model.reload
+ expect(model.main_image.blob).to eq(blob)
+ end
+
+ context "when removing the image" do
+ let(:query) do
+ %(
+ {
+ update(
+ title: #{convert_value(title)},
+ summary: #{convert_value(summary)},
+ description: #{convert_value(description)},
+ progress: 50,
+ mainImage: { remove: true }
+ ) { id }
+ }
+ )
+ end
+
+ before { model.main_image.attach(blob) }
+
+ it "removes the blob attachment and destroys the blob" do
+ expect(model.main_image.attached?).to be(true)
+ response
+ model.reload
+ expect(model.main_image.attached?).to be(false)
+ end
+ end
+ end
+
+ context "with list image" do
+ let(:blob) do
+ ActiveStorage::Blob.create_and_upload!(
+ io: File.open(Decidim::Dev.asset("city.jpeg")),
+ filename: "city.jpeg",
+ content_type: "image/jpeg"
+ )
+ end
+ let(:query) do
+ %(
+ {
+ update(
+ title: #{convert_value(title)},
+ summary: #{convert_value(summary)},
+ description: #{convert_value(description)},
+ progress: 50,
+ listImage: { blobId: #{blob.id} }
+ ) { id }
+ }
+ )
+ end
+
+ it "sets the list image" do
+ response
+ model.reload
+ expect(model.list_image.blob).to eq(blob)
+ end
+
+ context "when removing the image" do
+ let(:query) do
+ %(
+ {
+ update(
+ title: #{convert_value(title)},
+ summary: #{convert_value(summary)},
+ description: #{convert_value(description)},
+ progress: 50,
+ listImage: { remove: true }
+ ) { id }
+ }
+ )
+ end
+
+ before { model.list_image.attach(blob) }
+
+ it "removes the blob attachment and destroys the blob" do
+ expect(model.list_image.attached?).to be(true)
+ response
+ model.reload
+ expect(model.list_image.attached?).to be(false)
+ end
+ end
+ end
+
+ context "with links" do
+ let(:query) do
+ %(
+ {
+ update(
+ title: #{convert_value(title)},
+ summary: #{convert_value(summary)},
+ description: #{convert_value(description)},
+ progress: 50,
+ links: [
+ {
+ position: 10,
+ label: #{convert_value(link_label)}
+ url: #{convert_value(link_url)}
+ }
+ ]
+ ) { id }
+ }
+ )
+ end
+
+ it "creates the links" do
+ expect { response }.to change(Decidim::AccountabilitySimple::ResultLink, :count).by(1)
+ model.reload
+ expect(model.result_links.count).to eq(1)
+
+ link = model.result_links.first
+ expect(link.position).to eq(10)
+ expect(link.label).to eq(link_label)
+ expect(link.url).to eq(link_url)
+ expect(link.link_collection).to be_nil
+ end
+ end
+
+ context "with link collections" do
+ let(:link_collection) do
+ model.result_link_collections.create!(
+ key: "testing",
+ name: generate_localized_title,
+ position: 0
+ )
+ end
+ let(:query) do
+ %(
+ {
+ update(
+ title: #{convert_value(title)},
+ summary: #{convert_value(summary)},
+ description: #{convert_value(description)},
+ progress: 50,
+ links: [
+ {
+ position: 10,
+ label: #{convert_value(link_label)}
+ url: #{convert_value(link_url)}
+ collection: { key: "#{link_collection.key}" }
+ }
+ ]
+ ) { id }
+ }
+ )
+ end
+
+ it "maps the link with the collection" do
+ expect { response }.to change(Decidim::AccountabilitySimple::ResultLink, :count).by(1)
+ model.reload
+ expect(model.result_links.count).to eq(1)
+
+ link = model.result_links.first
+ expect(link.link_collection).to eq(link_collection)
+ end
+
+ context "when using the collection ID" do
+ let(:query) do
+ %(
+ {
+ update(
+ title: #{convert_value(title)},
+ summary: #{convert_value(summary)},
+ description: #{convert_value(description)},
+ progress: 50,
+ links: [
+ {
+ position: 10,
+ label: #{convert_value(link_label)}
+ url: #{convert_value(link_url)}
+ collection: { id: "#{link_collection.id}" }
+ }
+ ]
+ ) { id }
+ }
+ )
+ end
+
+ it "maps the link with the collection" do
+ expect { response }.to change(Decidim::AccountabilitySimple::ResultLink, :count).by(1)
+ model.reload
+ expect(model.result_links.count).to eq(1)
+ expect(model.result_links.first.link_collection).to eq(link_collection)
+ end
+ end
+ end
end
describe "publicity" do
@@ -152,4 +363,98 @@
end
end
end
+
+ describe "createLinkCollection" do
+ let(:query) do
+ %(
+ {
+ createLinkCollection(
+ attributes: {
+ position: #{position},
+ key: "#{key}",
+ name: #{convert_value(name)}
+ }
+ ) { id }
+ }
+ )
+ end
+ let(:position) { 123 }
+ let(:key) { "testing" }
+ let(:name) { generate_localized_title }
+
+ it_behaves_like "when the user does not have permissions"
+
+ it "creates a new link collection" do
+ expect { response }.to change(Decidim::AccountabilitySimple::ResultLinkCollection, :count).by(1)
+ end
+
+ it "sets the correct details for the collection" do
+ collection = Decidim::AccountabilitySimple::ResultLinkCollection.find(response["createLinkCollection"]["id"])
+ expect(collection.result).to eq(model)
+ expect(collection.position).to eq(position)
+ expect(collection.key).to eq(key)
+ expect(collection.name).to eq(name)
+ end
+ end
+
+ describe "updateLinkCollection" do
+ let!(:collection) do
+ model.result_link_collections.create!(
+ position: 0,
+ key: "original",
+ name: generate_localized_title
+ )
+ end
+ let(:query) do
+ %(
+ {
+ updateLinkCollection(
+ id: "#{collection.id}",
+ attributes: {
+ position: #{position},
+ key: "#{key}",
+ name: #{convert_value(name)}
+ }
+ ) { id }
+ }
+ )
+ end
+ let(:position) { 123 }
+ let(:key) { "testing" }
+ let(:name) { generate_localized_title }
+
+ it_behaves_like "when the user does not have permissions"
+
+ it "updates the correct details for the collection" do
+ expect(response["updateLinkCollection"]["id"]).to eq(collection.id.to_s)
+
+ collection.reload
+ expect(collection.position).to eq(position)
+ expect(collection.key).to eq(key)
+ expect(collection.name).to eq(name)
+ end
+ end
+
+ describe "deleteLinkCollection" do
+ let!(:collection) do
+ model.result_link_collections.create!(
+ position: 0,
+ key: "original",
+ name: generate_localized_title
+ )
+ end
+ let(:query) do
+ %({ deleteLinkCollection(id: "#{collection.id}") { id } })
+ end
+
+ it_behaves_like "when the user does not have permissions"
+
+ it "creates an action log record" do
+ expect { response }.to change(Decidim::ActionLog, :count).by(1)
+ end
+
+ it "deletes the collection" do
+ expect { response }.to change(Decidim::AccountabilitySimple::ResultLinkCollection, :count).by(-1)
+ end
+ end
end