Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Export budgets projects #7021

Merged
merged 10 commits into from Jan 14, 2021
Expand Up @@ -10,7 +10,7 @@ def permissions
case permission_action.subject
when :budget
case permission_action.action
when :create, :read
when :create, :read, :export
allow!
when :update
toggle_allow(budget)
Expand Down
@@ -1,9 +1,17 @@
<div class="card">
<div class="card with-overflow">
<div class="card-divider">
<h2 class="card-title">
<%= t(".title") %>

<%= link_to t("actions.new", scope: "decidim.budgets", name: t("models.budget.name", scope: "decidim.budgets.admin")), new_budget_path, class: "button tiny button--title" if allowed_to? :create, :budget %>
<h2 class="card-title flex--sbc">
<div>
<%= t(".title") %>
</div>
<div class="flex--cc flex-gap--1">
<% if allowed_to? :export, :budget %>
<%= export_dropdown %>
<% end %>
<div id="js-other-actions-wrapper">
<%= link_to t("actions.new", scope: "decidim.budgets", name: t("models.budget.name", scope: "decidim.budgets.admin")), new_budget_path, class: "button tiny button--title" if allowed_to? :create, :budget %>
</div>
</div>
</h2>
</div>

Expand Down
@@ -1,10 +1,17 @@
<div class="card">
<div class="card with-overflow">
<div class="card-divider">
<h2 class="card-title">
<%= link_to translated_attribute(budget.title), budgets_path %> &gt;
<%= t(".title") %>
<%= link_to t("actions.new", scope: "decidim.budgets", name: t("models.project.name", scope: "decidim.budgets.admin")), new_budget_project_path, class: "button tiny button--title new" if allowed_to? :create, :project %>
<%= link_to t("actions.import", scope: "decidim.budgets", name: t("models.project.name", scope: "decidim.budgets.admin")), new_budget_proposals_import_path(budget), class: "button tiny button--title" if allowed_to? :import_proposals, :project %>
<h2 class="card-title flex--sbc">
<div>
<%= link_to translated_attribute(budget.title), budgets_path %> &gt;
<%= t(".title") %>
</div>
<div class="flex--cc flex-gap--1">
<%= link_to t("actions.import", scope: "decidim.budgets", name: t("models.project.name", scope: "decidim.budgets.admin")), new_budget_proposals_import_path(budget), class: "button tiny button--title" if allowed_to? :import_proposals, :project %>
<% if allowed_to? :export, :budget %>
<%= export_dropdown(current_component, budget.id) %>
<% end %>
<%= link_to t("actions.new", scope: "decidim.budgets", name: t("models.project.name", scope: "decidim.budgets.admin")), new_budget_project_path, class: "button tiny button--title new" if allowed_to? :create, :project %>
</div>
</h2>
</div>

Expand Down
2 changes: 2 additions & 0 deletions decidim-budgets/config/locales/en.yml
Expand Up @@ -53,6 +53,8 @@ en:
update:
invalid: There was a problem updating this budget
success: Budget successfully updated
exports:
projects: Projects
models:
budget:
name: Budget
Expand Down
1 change: 1 addition & 0 deletions decidim-budgets/lib/decidim/budgets.rb
Expand Up @@ -9,5 +9,6 @@
module Decidim
# Base module for this engine.
module Budgets
autoload :ProjectSerializer, "decidim/budgets/project_serializer"
end
end
13 changes: 13 additions & 0 deletions decidim-budgets/lib/decidim/budgets/component.rb
Expand Up @@ -62,6 +62,19 @@
Decidim::Follow.where(decidim_followable_type: "Decidim::Budgets::Project", decidim_followable_id: projects_ids).count
end

component.exports :projects do |exports|
exports.collection do |component_instance, _user, resource_id|
budgets = resource_id ? Decidim::Budgets::Budget.find(resource_id) : Decidim::Budgets::Budget.where(decidim_component_id: component_instance)
Decidim::Budgets::Project
.where(decidim_budgets_budget_id: budgets)
.includes(:category, :component)
end

exports.include_in_open_data = true

exports.serializer Decidim::Budgets::ProjectSerializer
end

component.settings(:global) do |settings|
settings.attribute :scopes_enabled, type: :boolean, default: false
settings.attribute :scope_id, type: :scope
Expand Down
81 changes: 81 additions & 0 deletions decidim-budgets/lib/decidim/budgets/project_serializer.rb
@@ -0,0 +1,81 @@
# frozen_string_literal: true

module Decidim
module Budgets
class ProjectSerializer < Decidim::Exporters::Serializer
include Decidim::ApplicationHelper
include Decidim::ResourceHelper
include Decidim::TranslationsHelper

# Public: Initializes the serializer with a project.
def initialize(project)
@project = project
end

# Public: Exports a hash with the serialized data for this project.
def serialize
{
id: project.id,
category: {
id: project.category.try(:id),
name: project.category.try(:name) || empty_translatable
},
scope: {
id: project.scope.try(:id),
name: project.scope.try(:name) || empty_translatable
},
participatory_space: {
id: project.participatory_space.id,
url: Decidim::ResourceLocatorPresenter.new(project.participatory_space).url
},
component: { id: component.id },
title: project.title,
description: project.description,
budget: { id: project.budget.id },
budget_amount: project.budget_amount,
confirmed_votes: project.confirmed_orders_count,
comments: project.comments.count,
created_at: project.created_at,
url: project.polymorphic_resource_url({}),
related_proposals: related_proposals,
related_proposal_titles: related_proposal_titles,
related_proposal_urls: related_proposal_urls
}
end

private

attr_reader :project

def component
project.component
end

def related_proposals
project.linked_resources(:proposals, "included_proposals").map(&:id)
end

def related_proposal_titles
project.linked_resources(:proposals, "included_proposals").map do |proposal|
Decidim::Proposals::ProposalPresenter.new(proposal).title
end
end

def related_proposal_urls
project.linked_resources(:proposals, "included_proposals").map do |proposal|
Decidim::ResourceLocatorPresenter.new(proposal).url
end
end

def url
Decidim::ResourceLocatorPresenter.new(project).url
end

def empty_translatable(locales = Decidim.available_locales)
locales.each_with_object({}) do |locale, result|
result[locale.to_s] = ""
end
end
end
end
end
56 changes: 56 additions & 0 deletions decidim-budgets/spec/lib/decidim/budgets/admin/component_spec.rb
Expand Up @@ -243,4 +243,60 @@ def new_settings(name, data)
end
end
end

describe "component projects exporter" do
subject do
component
.manifest
.export_manifests
.find { |manifest| manifest.name == :projects }
.collection
.call(component, user)
end

let(:component) { create(:budgets_component) }
let(:component2) { create(:budgets_component) }
let(:budget) { create(:budget, component: component) }
let(:budget2) { create(:budget, component: component2) }
let(:budget3) { create(:budget, component: component2) }
let!(:components_projects) { create_list(:project, 2, budget: budget) }
let!(:another_component_projects) { create_list(:project, 3, budget: budget2) }
let!(:another_component_projects2) { create_list(:project, 4, budget: budget3) }
let(:organization) { component.participatory_space.organization }

context "when the user is an admin" do
let!(:user) { create :user, admin: true, organization: organization }

it "exports all budgets from the component" do
expect(subject.count).to eq(2)
expect(subject).to match_array(components_projects)
end
end
end

describe "budget projects exporter" do
subject do
component
.manifest
.export_manifests
.find { |manifest| manifest.name == :projects }
.collection
.call(component, user, budget1.id)
end

let(:component) { create(:budgets_component) }
let(:budget1) { create(:budget, component: component) }
let(:budget2) { create(:budget, component: component) }
let!(:budget1_projects) { create_list(:project, 3, budget: budget1) }
let!(:budget2_projects) { create_list(:project, 2, budget: budget2) }
let(:organization) { component.participatory_space.organization }

context "when the user is an admin" do
let!(:user) { create :user, admin: true, organization: organization }

it "exports projects of individual budget" do
expect(subject.count).to eq(3)
end
end
end
end
92 changes: 92 additions & 0 deletions decidim-budgets/spec/serializers/project_serializer_spec.rb
@@ -0,0 +1,92 @@
# frozen_string_literal: true

require "spec_helper"

module Decidim::Budgets
describe ProjectSerializer do
let(:budget) { create(:budget) }
let(:category) { create(:category, participatory_space: budget.component.participatory_space) }
let(:scope) { create(:scope, organization: category.participatory_space.organization) }
let(:project) { create(:project, budget: budget, category: category, scope: scope) }
let(:subject) { described_class.new(project) }
let(:serialized) { subject.serialize }
let(:attachment) { create :attachment, attached_to: project }
let(:proposals_component) { create(:component, manifest_name: "proposals", participatory_space: project.participatory_space) }
let(:proposals) { create_list(:proposal, 3, component: proposals_component) }

describe "#serializer" do
before { project.link_resources(proposals, "included_proposals") }

it "includes the id" do
expect(serialized).to include(id: project.id)
end

it "includes the category" do
expect(serialized[:category]).to include(id: category.id)
expect(serialized[:category]).to include(name: category.name)
end

it "includes the scope" do
expect(serialized[:scope]).to include(id: project.scope.id)
expect(serialized[:scope]).to include(name: project.scope.name)
end

it "includes the participatory space" do
expect(serialized[:participatory_space]).to include(id: project.budget.component.participatory_space.id)
expect(serialized[:participatory_space]).to include(url: Decidim::ResourceLocatorPresenter.new(project.participatory_space).url)
end

it "includes the component" do
expect(serialized[:component]).to include(id: project.component.id)
end

it "includes the title" do
expect(serialized[:title]).to include(project.title)
end

it "includes the description" do
expect(serialized[:description]).to include(project.description)
end

it "includes the budget id" do
expect(serialized[:budget]).to eq(id: project.budget.id)
end

it "includes the budget amount" do
expect(serialized[:budget_amount]).to eq(project.budget_amount)
end

it "includes count of confirmed votes" do
expect(serialized[:confirmed_votes]).to eq(project.confirmed_orders_count)
end

it "includes comment count" do
expect(serialized[:comments]).to eq(project.comments.count)
end

it "includes the created at" do
expect(serialized).to include(created_at: project.created_at)
end

it "includes the url" do
expect(serialized[:url]).to eq(project.polymorphic_resource_url({}))
end

it "includes related proposal ids" do
expect(serialized[:related_proposals]).to match_array(project.linked_resources(:proposals, "included_proposals").map(&:id))
end

it "includes related proposal titles" do
expect(serialized[:related_proposal_titles]).to include(proposals.first.title["en"])
expect(serialized[:related_proposal_titles]).to include(proposals.second.title["en"])
expect(serialized[:related_proposal_titles]).to include(proposals.last.title["en"])
end

it "includes related proposal urls" do
expect(serialized[:related_proposal_urls]).to include(Decidim::ResourceLocatorPresenter.new(proposals.first).url)
expect(serialized[:related_proposal_urls]).to include(Decidim::ResourceLocatorPresenter.new(proposals.second).url)
expect(serialized[:related_proposal_urls]).to include(Decidim::ResourceLocatorPresenter.new(proposals.last).url)
end
end
end
end