Merge pull request #4396 from consul/investment_filters

Add filters on budget investments index page
This commit is contained in:
Javi Martín
2021-03-17 18:05:11 +01:00
committed by GitHub
25 changed files with 195 additions and 119 deletions

View File

@@ -70,6 +70,7 @@
//= require advanced_search
//= require registration_form
//= require suggest
//= require filter_selector
//= require forms
//= require valuation_budget_investment_form
//= require embed_video
@@ -133,6 +134,7 @@ var initialize_modules = function() {
App.RegistrationForm.initialize();
App.Suggest.initialize();
App.Forms.initialize();
App.FilterSelector.initialize();
App.ValuationBudgetInvestmentForm.initialize();
App.EmbedVideo.initialize();
App.FixedBar.initialize();

View File

@@ -0,0 +1,8 @@
(function() {
"use strict";
App.FilterSelector = {
initialize: function() {
App.Forms.submitOnChange(".filter-selector select");
}
};
}).call(this);

View File

@@ -8,8 +8,8 @@
}
});
},
submitOnChange: function() {
$("body").on("change", ".js-submit-on-change", function() {
submitOnChange: function(selector) {
$("body").on("change", selector, function() {
$(this).closest("form").submit();
return false;
});
@@ -58,7 +58,7 @@
},
initialize: function() {
App.Forms.disableEnter();
App.Forms.submitOnChange();
App.Forms.submitOnChange(".js-submit-on-change");
App.Forms.toggleLink();
App.Forms.synchronizeInputs();
App.Forms.hideOrShowFieldsAfterSelection();

View File

@@ -12,6 +12,7 @@
@import "milestones";
@import "pages";
@import "dashboard";
@import "filter_selector";
@import "legislation";
@import "legislation_process";
@import "legislation_process_form";

View File

@@ -0,0 +1,14 @@
.filter-selector {
text-align: right;
label {
display: inline-block;
font-size: $small-font-size;
margin-right: $line-height / 2;
padding-top: $line-height / 2;
}
select {
width: auto;
}
}

View File

@@ -0,0 +1,5 @@
<%= form_tag({}, method: :get, enforce_utf8: false, class: "filter-selector") do %>
<%= query_parameters_tags %>
<%= label_tag "filter_selector_filter", t("#{i18n_namespace}.filter") %>
<%= select_tag "filter", options_for_select(filter_options, current_filter), id: "filter_selector_filter" %>
<% end %>

View File

@@ -0,0 +1,22 @@
class Shared::FilterSelectorComponent < ApplicationComponent
delegate :valid_filters, :current_filter, to: :helpers
attr_reader :i18n_namespace
def initialize(i18n_namespace:)
@i18n_namespace = i18n_namespace
end
private
def query_parameters_tags
safe_join(request.query_parameters.reject do |name, _|
["page", "filter"].include?(name)
end.map do |name, value|
hidden_field_tag name, value, id: "filter_selector_#{name}"
end)
end
def filter_options
valid_filters.map { |filter| [t("#{i18n_namespace}.filters.#{filter}"), filter] }
end
end

View File

@@ -111,14 +111,6 @@ class ApplicationController < ActionController::Base
end
end
def set_default_budget_filter
if @budget&.balloting? || @budget&.publishing_prices?
params[:filter] ||= "selected"
elsif @budget&.finished?
params[:filter] ||= "winners"
end
end
def current_budget
Budget.current
end

View File

@@ -1,12 +1,14 @@
module Budgets
class GroupsController < ApplicationController
include InvestmentFilters
before_action :load_budget
before_action :load_group
authorize_resource :budget
authorize_resource :group, class: "Budget::Group"
before_action :set_default_budget_filter, only: :show
has_filters %w[not_unfeasible feasible unfeasible unselected selected winners], only: [:show]
before_action :set_default_investment_filter, only: :show
has_filters investment_filters, only: [:show]
def show
end

View File

@@ -6,6 +6,7 @@ module Budgets
include RandomSeed
include ImageAttributes
include Translatable
include InvestmentFilters
PER_PAGE = 10
@@ -20,7 +21,7 @@ module Budgets
before_action :load_heading, only: [:index, :show]
before_action :set_random_seed, only: :index
before_action :load_categories, only: [:index, :new, :create, :edit, :update]
before_action :set_default_budget_filter, only: :index
before_action :set_default_investment_filter, only: :index
before_action :set_view, only: :index
before_action :load_content_blocks, only: :index
@@ -31,8 +32,7 @@ module Budgets
has_orders %w[most_voted newest oldest], only: :show
has_orders ->(c) { c.instance_variable_get(:@budget).investments_orders }, only: :index
valid_filters = %w[not_unfeasible feasible unfeasible unselected selected winners]
has_filters valid_filters, only: [:index, :show, :suggest]
has_filters investment_filters, only: [:index, :show, :suggest]
invisible_captcha only: [:create, :update], honeypot: :subtitle, scope: :budget_investment

View File

@@ -1,12 +1,13 @@
class BudgetsController < ApplicationController
include FeatureFlags
include BudgetsHelper
include InvestmentFilters
feature_flag :budgets
before_action :load_budget, only: :show
load_and_authorize_resource
before_action :set_default_budget_filter, only: :show
has_filters %w[not_unfeasible feasible unfeasible unselected selected winners], only: :show
before_action :set_default_investment_filter, only: :show
has_filters investment_filters, only: :show
respond_to :html, :js

View File

@@ -9,7 +9,7 @@ module HasFilters
class_methods do
def has_filters(valid_filters, *args)
before_action(*args) do
@valid_filters = valid_filters
@valid_filters = valid_filters.respond_to?(:call) ? valid_filters.call(self) : valid_filters
@current_filter = @valid_filters.include?(params[:filter]) ? params[:filter] : @valid_filters.first
end
end

View File

@@ -0,0 +1,27 @@
module InvestmentFilters
extend ActiveSupport::Concern
class_methods do
def investment_filters
->(controller) { controller.investment_filters }
end
end
def set_default_investment_filter
if @budget&.finished?
params[:filter] ||= "winners"
elsif @budget&.publishing_prices_or_later?
params[:filter] ||= "selected"
end
end
def investment_filters
[
"not_unfeasible",
"unfeasible",
("unselected" if @budget.publishing_prices_or_later?),
("selected" if @budget.publishing_prices_or_later?),
("winners" if @budget.finished?)
].compact
end
end

View File

@@ -1,8 +1,4 @@
module BudgetsHelper
def show_links_to_budget_investments(budget)
["balloting", "reviewing_ballots", "finished"].include? budget.phase
end
def budget_voting_styles_select_options
Budget::VOTING_STYLES.map do |style|
[Budget.human_attribute_name("voting_style_#{style}"), style]

View File

@@ -55,7 +55,7 @@
<div class="small-12 column">
<small>
<%= link_to t("budgets.groups.show.unfeasible"),
budget_path(@budget, filter: "unfeasible") %>
budget_group_path(@budget, @group, filter: "unfeasible") %>
</small>
</div>
</div>
@@ -66,7 +66,7 @@
<div class="small-12 column">
<small>
<%= link_to t("budgets.groups.show.unselected"),
budget_path(@budget, filter: "unselected") %>
budget_group_path(@budget, @group, filter: "unselected") %>
</small>
</div>
</div>

View File

@@ -55,29 +55,6 @@
<h3><%= t("budgets.index.map") %></h3>
<%= render_map(nil, "budgets", false, nil, @budgets_coordinates) %>
</div>
<ul class="no-bullet margin-top">
<% show_links = show_links_to_budget_investments(current_budget) %>
<% if show_links %>
<li>
<%= link_to budget_path(current_budget) do %>
<small><%= t("budgets.index.investment_proyects") %></small>
<% end %>
</li>
<% end %>
<li>
<%= link_to budget_path(current_budget, filter: "unfeasible") do %>
<small><%= t("budgets.index.unfeasible_investment_proyects") %></small>
<% end %>
</li>
<% if show_links %>
<li>
<%= link_to budget_path(current_budget, filter: "unselected") do %>
<small><%= t("budgets.index.not_selected_investment_proyects") %></small>
<% end %>
</li>
<% end %>
</ul>
<% end %>
</div>
</div>

View File

@@ -1,11 +0,0 @@
<div class="sidebar-divider"></div>
<h2 class="sidebar-title"><%= t("budgets.investments.index.sidebar.by_feasibility") %></h2>
<br>
<% if params[:unfeasible].present? %>
<%= link_to t("budgets.investments.index.sidebar.feasible"),
budget_investments_path(@budget, heading_id: @heading, unfeasible: nil) %>
<% else %>
<%= link_to t("budgets.investments.index.sidebar.unfeasible"),
budget_investments_path(@budget, heading_id: @heading, unfeasible: 1) %>
<% end %>

View File

@@ -42,9 +42,9 @@
<% if @current_filter == "unfeasible" %>
<div class="small-12 margin-bottom">
<h2><%= t("budgets.investments.index.unfeasible") %>: <%= @heading.name %></h2>
<h2><%= t("budgets.investments.index.unfeasible") %></h2>
<div class="callout primary margin">
<%= t("budgets.investments.index.unfeasible_text") %>
<%= t("budgets.investments.index.unfeasible_text") %>
</div>
</div>
<% elsif @heading.present? %>
@@ -70,6 +70,8 @@
<%= render("shared/order_links", i18n_namespace: "budgets.investments.index") %>
<% end %>
<%= render Shared::FilterSelectorComponent.new(i18n_namespace: "budgets.investments.index") %>
<% if investments_default_view? %>
<% @investments.each do |investment| %>

View File

@@ -127,6 +127,7 @@ ignore_missing:
## Consider these keys used:
ignore_unused:
- "budgets.phase.*"
- "budgets.investments.index.filter*"
- "budgets.investments.index.orders.*"
- "budgets.index.section_header.*"
- "budgets.ballots.show.amount_available.*"

View File

@@ -69,9 +69,6 @@ en:
prev_phase: Previous phase
current_phase: Current phase
map: Budget investments' proposals located geographically
investment_proyects: List of all investment projects
unfeasible_investment_proyects: List of all unfeasible investment projects
not_selected_investment_proyects: List of all investment projects not selected for balloting
finished_budgets: Finished participatory budgets
see_results: See results
section_footer:
@@ -120,9 +117,13 @@ en:
verified_only: "To create a new budget investment %{verify}."
create: "Create a budget investment"
not_logged_in: "To create a new budget investment you must %{sign_in} or %{sign_up}."
by_feasibility: By feasibility
feasible: Feasible projects
unfeasible: Unfeasible projects
filter: "Filtering projects by"
filters:
not_unfeasible: "Not unfeasible"
selected: "Selected"
unfeasible: "Unfeasible"
unselected: "Unselected"
winners: "Winners"
orders:
random: random
confidence_score: highest rated

View File

@@ -69,9 +69,6 @@ es:
prev_phase: Fase anterior
current_phase: Fase actual
map: Proyectos localizables geográficamente
investment_proyects: Ver lista completa de proyectos de gasto
unfeasible_investment_proyects: Ver lista de proyectos de gasto inviables
not_selected_investment_proyects: Ver lista de proyectos de gasto no seleccionados para la votación final
finished_budgets: Presupuestos participativos terminados
see_results: Ver resultados
section_footer:
@@ -120,9 +117,13 @@ es:
verified_only: "Para crear un nuevo proyecto de gasto %{verify}."
create: "Crear proyecto de gasto"
not_logged_in: "Para crear un nuevo proyecto de gasto debes %{sign_in} o %{sign_up}."
by_feasibility: Por viabilidad
feasible: Ver los proyectos viables
unfeasible: Ver los proyectos inviables
filter: "Filtrando proyectos"
filters:
not_unfeasible: "No inviables"
selected: "Seleccionados"
unfeasible: "Inviables"
unselected: "No seleccionados"
winners: "Ganadores"
orders:
random: Aleatorios
confidence_score: Mejor valorados

View File

@@ -0,0 +1,15 @@
require "rails_helper"
describe Shared::FilterSelectorComponent, type: :component do
it "renders a form with a select" do
component = Shared::FilterSelectorComponent.new(i18n_namespace: "budgets.investments.index")
allow(component).to receive(:url_for).and_return("/")
allow(component).to receive(:valid_filters).and_return(["unfeasible", "winners"])
allow(component).to receive(:current_filter).and_return(nil)
render_inline component
expect(page).to have_select "Filtering projects by"
expect(page).to have_selector "form[method='get'].filter-selector select"
end
end

View File

@@ -139,53 +139,10 @@ describe "Budgets" do
expect(page).not_to have_link "#{heading.name} €1,000,000"
expect(page).to have_content "#{heading.name} €1,000,000"
expect(page).to have_link "List of all investment projects",
href: budget_path(budget)
expect(page).to have_link "List of all unfeasible investment projects",
href: budget_path(budget, filter: "unfeasible")
expect(page).to have_link "List of all investment projects not selected for balloting",
href: budget_path(budget, filter: "unselected")
expect(page).to have_css("div.map")
end
end
scenario "Show investment links only on balloting or later" do
budget = create(:budget)
create(:budget_heading, budget: budget)
allowed_phase_list.each do |phase|
budget.update!(phase: phase)
visit budgets_path
expect(page).to have_content(I18n.t("budgets.index.investment_proyects"))
expect(page).to have_content(I18n.t("budgets.index.unfeasible_investment_proyects"))
expect(page).to have_content(I18n.t("budgets.index.not_selected_investment_proyects"))
end
end
scenario "Not show investment links earlier of balloting " do
budget = create(:budget)
create(:budget_heading, budget: budget)
phases_without_links = ["informing"]
not_allowed_phase_list = Budget::Phase::PHASE_KINDS -
phases_without_links -
allowed_phase_list
not_allowed_phase_list.each do |phase|
budget.update!(phase: phase)
visit budgets_path
expect(page).not_to have_content(I18n.t("budgets.index.investment_proyects"))
expect(page).to have_content(I18n.t("budgets.index.unfeasible_investment_proyects"))
expect(page).not_to have_content(I18n.t("budgets.index.not_selected_investment_proyects"))
end
end
scenario "No budgets" do
Budget.destroy_all

View File

@@ -49,5 +49,24 @@ describe "Budget Groups" do
expect(first_heading.name).to appear_before(last_heading.name)
end
scenario "Links to investment filters", :js do
create(:budget_heading, group: group, name: "Southwest")
budget.update!(phase: "finished")
visit budget_group_path(budget, group)
click_link "See unfeasible investments"
expect(page).to have_css "h3", exact_text: "Unfeasible investments"
expect(page).to have_link "Southwest"
expect(page).not_to have_link "See unfeasible investments"
click_link "See investments not selected for balloting phase"
expect(page).to have_css "h3", exact_text: "Investments not selected for balloting phase"
expect(page).to have_link "Southwest"
expect(page).not_to have_link "See investments not selected for balloting phase unfeasible investments"
end
end
end

View File

@@ -151,6 +151,51 @@ describe "Budget Investments" do
end
end
scenario "Index filter by status", :js do
budget.update!(phase: "finished")
create(:budget_investment, :feasible, heading: heading, title: "Feasible investment")
create(:budget_investment, :unfeasible, heading: heading, title: "Unfeasible investment")
create(:budget_investment, :unselected, heading: heading, title: "Unselected investment")
create(:budget_investment, :selected, heading: heading, title: "Selected investment")
create(:budget_investment, :winner, heading: heading, title: "Winner investment")
visit budget_investments_path(budget, heading_id: heading.id)
expect(page).to have_select "Filtering projects by",
options: ["Not unfeasible", "Unfeasible", "Unselected", "Selected", "Winners"]
select "Unfeasible", from: "Filtering projects by"
expect(page).to have_css ".budget-investment", count: 1
expect(page).to have_content "Unfeasible investment"
select "Unselected", from: "Filtering projects by"
expect(page).to have_css ".budget-investment", count: 2
expect(page).to have_content "Unselected investment"
expect(page).to have_content "Feasible investment"
select "Selected", from: "Filtering projects by"
expect(page).to have_css ".budget-investment", count: 2
expect(page).to have_content "Selected investment"
expect(page).to have_content "Winner investment"
select "Winners", from: "Filtering projects by"
expect(page).to have_css ".budget-investment", count: 1
expect(page).to have_content "Winner investment"
select "Not unfeasible", from: "Filtering projects by"
expect(page).to have_css ".budget-investment", count: 4
expect(page).to have_content "Selected investment"
expect(page).to have_content "Unselected investment"
expect(page).to have_content "Feasible investment"
expect(page).to have_content "Winner investment"
end
context("Search") do
scenario "Search by text" do
investment1 = create(:budget_investment, heading: heading, title: "Get Schwifty")
@@ -503,7 +548,9 @@ describe "Budget Investments" do
expect(page).not_to have_content "by price"
expect(page).not_to have_content "highest rated"
end
end
Budget::Phase.kind_or_later("publishing_prices").each do |phase|
visit budget_investments_path(budget, heading_id: heading.id, filter: "unselected")
within(".submenu") do
@@ -1325,10 +1372,7 @@ describe "Budget Investments" do
expect(page).to have_css("#budget_heading_#{heading_1.id}.is-active")
expect(page).to have_css("#budget_heading_#{heading_2.id}")
visit budget_group_path(budget, group)
click_link "See unfeasible investments"
click_link "Health"
within("#headings") do
expect(page).to have_css("#budget_heading_#{heading_1.id}")