Merge pull request #4396 from consul/investment_filters
Add filters on budget investments index page
This commit is contained in:
@@ -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();
|
||||
|
||||
8
app/assets/javascripts/filter_selector.js
Normal file
8
app/assets/javascripts/filter_selector.js
Normal file
@@ -0,0 +1,8 @@
|
||||
(function() {
|
||||
"use strict";
|
||||
App.FilterSelector = {
|
||||
initialize: function() {
|
||||
App.Forms.submitOnChange(".filter-selector select");
|
||||
}
|
||||
};
|
||||
}).call(this);
|
||||
@@ -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();
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
@import "milestones";
|
||||
@import "pages";
|
||||
@import "dashboard";
|
||||
@import "filter_selector";
|
||||
@import "legislation";
|
||||
@import "legislation_process";
|
||||
@import "legislation_process_form";
|
||||
|
||||
14
app/assets/stylesheets/filter_selector.scss
Normal file
14
app/assets/stylesheets/filter_selector.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
5
app/components/shared/filter_selector_component.html.erb
Normal file
5
app/components/shared/filter_selector_component.html.erb
Normal 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 %>
|
||||
22
app/components/shared/filter_selector_component.rb
Normal file
22
app/components/shared/filter_selector_component.rb
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
27
app/controllers/concerns/investment_filters.rb
Normal file
27
app/controllers/concerns/investment_filters.rb
Normal 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
|
||||
@@ -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]
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 %>
|
||||
@@ -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| %>
|
||||
|
||||
@@ -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.*"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
15
spec/components/shared/filter_selector_component_spec.rb
Normal file
15
spec/components/shared/filter_selector_component_spec.rb
Normal 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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}")
|
||||
|
||||
Reference in New Issue
Block a user