Skip to content
Permalink
Browse files

Budgets: Sort projects by different criteria (#5808)

* Order budget projects

* Add tests

* Fix rubocop complaints

* Update changelog

* Simplify cases
  • Loading branch information
mrcasals committed Mar 16, 2020
1 parent 0ab094f commit 11bc5cabb3cddb47513b8eeb813deae8aba2b35f
@@ -20,6 +20,7 @@ After this, `Decidim::Proposals::ProposalEndorsement` and the corresponding coun
- **decidim-core**: Support node.js semver rules for release candidates. [\#5828](https://github.com/decidim/decidim/pull/5828)
- **decidim-proposals**, **decidim-core**, **decidim-blogs**: Extract proposals' endorsements into a polymorphic concern that can now be applied no any resource. It has, in turn, been aplied to blog posts. [\#5542](https://github.com/decidim/decidim/pull/5542)
- **decidim-proposals**, **decidim-core**, **decidim-blogs**: Apply generalized endorsements to the GraphQL API and add it to the blog posts query. [\#5847](https://github.com/decidim/decidim/pull/5847)
- **decidim-budgets**: Allow projects to be sorted by different criteria [\#5808](https://github.com/decidim/decidim/pull/5808)

### Changed

@@ -204,6 +204,7 @@ ignore_unused:
- decidim.proposals.models.proposal.fields.*
- decidim.admin_terms_of_use.default_body
- decidim.admin.admin_terms_of_use.required_review.alert
- decidim.budgets.projects.orders.*
- layouts.decidim.widget.*
- layouts.decidim.assembly_widgets.*
- layouts.decidim.conference_widgets.*
@@ -0,0 +1,61 @@
# frozen_string_literal: true

require "active_support/concern"

module Decidim
module Budgets
# Common logic to sorting resources
module Orderable
extend ActiveSupport::Concern

included do
include Decidim::Orderable

private

# Available orders based on enabled settings
def available_orders
@available_orders ||= begin
available_orders = []
available_orders << "random" if voting_is_open? || !votes_are_visible?
available_orders << "most_voted" if votes_are_visible?
available_orders += %w(highest_cost lowest_cost)
available_orders
end
end

def default_order
available_orders.first
end

def voting_is_open?
current_settings.votes_enabled?
end

def votes_are_visible?
current_settings.show_votes?
end

def reorder(projects)
case order
when "highest_cost"
projects.order(budget: :desc)
when "lowest_cost"
projects.order(budget: :asc)
when "most_voted"
if votes_are_visible?
ids = projects.sort_by(&:confirmed_orders_count).map(&:id).reverse
projects.ordered_ids(ids)
else
projects
end
when "random"
projects.order_randomly(random_seed)
else
projects
end
end
end
end
end
end
@@ -7,13 +7,17 @@ class ProjectsController < Decidim::Budgets::ApplicationController
include FilterResource
include NeedsCurrentOrder
include Orderable
include Decidim::Budgets::Orderable

helper_method :projects, :project

private

def projects
@projects ||= search.results.order_randomly(random_seed).page(params[:page]).per(current_component.settings.projects_per_page)
return @projects if @projects

@projects = search.results.page(params[:page]).per(current_component.settings.projects_per_page)
@projects = reorder(@projects)
end

def project
@@ -31,6 +31,10 @@ class Project < Budgets::ApplicationRecord
datetime: :created_at
)

def self.ordered_ids(ids)
order(Arel.sql("position(id::text in '#{ids.join(",")}')"))
end

def self.log_presenter_class_for(_log)
Decidim::Budgets::AdminLog::ProjectPresenter
end
@@ -19,4 +19,6 @@
<% if current_component.categories.any? %>
<%= form.check_boxes_tree :category_id, filter_categories_values, legend_title: t(".category") %>
<% end %>

<%= hidden_field_tag :order, order, id: nil, class: "order_filter" %>
<% end %>
@@ -1,3 +1,9 @@
<div class="collection-sort-controls row small-up-1 medium-up-3 card-grid">
<div class="column">
<%= order_selector available_orders, i18n_scope: "decidim.budgets.projects.orders" %>
</div>
</div>

<div class="card card--list budget-list">
<% projects.each do |project| %>
<%= render partial: "project", locals: { project: project } %>
@@ -6,7 +6,7 @@
</div>
<% end %>
<div class="row columns">
<h2 class="section-heading">
<h2 id="projects-count" class="section-heading">
<%= render partial: "count" %>
</h2>
</div>
@@ -1,2 +1,10 @@
var $projects = $('#projects');
var $projectsCount = $('#projects-count');
var $orderFilterInput = $('.order_filter');

$projects.html('<%= j(render partial: "projects").strip.html_safe %>');
$projectsCount.html('<%= j(render partial: "count").strip.html_safe %>');
$orderFilterInput.val('<%= order %>');

var $dropdownMenu = $('.dropdown.menu', $projects);
$dropdownMenu.foundation();
@@ -108,6 +108,12 @@ en:
one: project selected
other: projects selected
view: View
orders:
highest_cost: Highest cost
label: Order projects by
lowest_cost: Lowest cost
most_voted: Most voted
random: Random order
project:
add: Add
count:
@@ -45,6 +45,17 @@
}
end
end

trait :with_voting_finished do
step_settings do
{
participatory_space.active_step.id => {
votes_enabled: false,
show_votes: true
}
}
end
end
end

factory :project, class: "Decidim::Budgets::Project" do
@@ -0,0 +1,97 @@
# frozen_string_literal: true

require "spec_helper"

describe "Sorting projects", type: :system do
include_context "with a component"
let(:manifest_name) { "budgets" }

let(:organization) { create :organization }
let!(:user) { create :user, :confirmed, organization: organization }
let(:project) { projects.first }

let!(:component) do
create(:budget_component,
:with_total_budget_and_vote_threshold_percent,
manifest: manifest,
participatory_space: participatory_process)
end

let!(:project1) { create(:project, component: component, budget: 25_000_000) }
let!(:project2) { create(:project, component: component, budget: 50_000_000) }

before do
login_as user, scope: :user
visit_component
end

shared_examples "ordering projects by selected option" do |selected_option|
before do
visit_component
within ".order-by" do
expect(page).to have_selector("ul[data-dropdown-menu$=dropdown-menu]", text: "Random order")
page.find("a", text: "Random order").click
click_link(selected_option)
end
end

it "lists the projects ordered by selected option" do
# expect(page).to have_selector("#projects li.is-dropdown-submenu-parent a", text: selected_option)
within "#projects li.is-dropdown-submenu-parent a" do
expect(page).to have_no_content("Random order", wait: 20)
expect(page).to have_content(selected_option)
end

expect(page).to have_selector("#projects .card--list .card--list__item:first-child", text: translated(first_project.title))
expect(page).to have_selector("#projects .card--list .card--list__item:last-child", text: translated(last_project.title))
end
end

context "when ordering by highest cost" do
it_behaves_like "ordering projects by selected option", "Highest cost" do
let(:first_project) { project2 }
let(:last_project) { project1 }
end
end

context "when ordering by lowest cost" do
it_behaves_like "ordering projects by selected option", "Lowest cost" do
let(:first_project) { project1 }
let(:last_project) { project2 }
end
end

describe "when the voting is finished" do
let!(:component) do
create(
:budget_component,
:with_voting_finished,
manifest: manifest,
participatory_space: participatory_process
)
end
let!(:project1) { create(:project, component: component, budget: 25_000_000) }
let!(:project2) { create(:project, component: component, budget: 77_000_000) }

context "when ordering by most votes" do
before do
order = build :order, component: component
create :line_item, order: order, project: project2
order = Decidim::Budgets::Order.last
order.checked_out_at = Time.zone.now
order.save
end

it "automatically sorts by votes" do
visit_component

within "#projects li.is-dropdown-submenu-parent a" do
expect(page).to have_content("Most voted")
end

expect(page).to have_selector("#projects .card--list .card--list__item:first-child", text: translated(project2.title))
expect(page).to have_selector("#projects .card--list .card--list__item:last-child", text: translated(project1.title))
end
end
end
end

0 comments on commit 11bc5ca

Please sign in to comment.
You can’t perform that action at this time.