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 @@ +
+
+

<%= title %>

+
+ +
+
+ <%= form.translated :text_field, :name, autofocus: true %> +
+ +
+ <%= form.text_field :key %> +
+ +
+ <%= form.number_field :position %> +
+
+
diff --git a/app/views/decidim/accountability/admin/link_collections/edit.html.erb b/app/views/decidim/accountability/admin/link_collections/edit.html.erb new file mode 100644 index 0000000..21635e9 --- /dev/null +++ b/app/views/decidim/accountability/admin/link_collections/edit.html.erb @@ -0,0 +1,8 @@ +<% add_decidim_page_title(t("link_collections.edit.title", scope: "decidim.accountability.admin")) %> +<%= decidim_form_for(@form, url: result_link_collection_path(@link_collection.result, @link_collection), html: { class: "form edit_link_collection" }) do |f| %> + <%= render partial: "form", object: f, locals: { title: t("link_collections.edit.title", scope: "decidim.accountability.admin") } %> + +
+ <%= 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")) %> + diff --git a/app/views/decidim/accountability/admin/link_collections/new.html.erb b/app/views/decidim/accountability/admin/link_collections/new.html.erb new file mode 100644 index 0000000..64d2ccc --- /dev/null +++ b/app/views/decidim/accountability/admin/link_collections/new.html.erb @@ -0,0 +1,8 @@ +<% add_decidim_page_title(t("link_collections.new.title", scope: "decidim.accountability.admin")) %> +<%= decidim_form_for(@form, url: result_link_collections_path(result), html: { class: "form new_link_collection" }) do |f| %> + <%= render partial: "form", object: f, locals: { title: t("link_collections.new.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? %> +
+
    + <% active = result.status.present? %> + <% statuses.each do |status| %> +
  • "> +
    +
    <%= translated_attribute(status.name) %>
    +
  • + + <% active = false if status == result.status %> + <% end %> +
+
+ <% 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| %> -
  1. "> -
    - <%= 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 %> -

    -
    -
    -
  2. - <% end %> -
-
-
+

<%= t(".title") %>

+
    + <% result.timeline_entries.each_with_index do |timeline_entry, i| %> +
  1. "> +
    + <%= 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 %> +

    +
    +
  2. + <% 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