Allow users to remove their support on investments

Note we don't cast negative votes when users remove their support. That
way we provide compatibility for institutions who have implemented real
negative votes (in case there are / will be any), and we also keep the
database meaningful: it's not that users downvoted something; they
simply removed their upvote.

Co-Authored-By: Javi Martín <javim@elretirao.net>
Co-Authored-By: Julian Nicolas Herrero <microweb10@gmail.com>
This commit is contained in:
decabeza
2020-06-25 17:58:52 +02:00
committed by Javi Martín
parent 368023986a
commit a851048d56
17 changed files with 209 additions and 22 deletions

View File

@@ -5,19 +5,28 @@
</span> </span>
<div class="in-favor"> <div class="in-favor">
<% if user_voted_for? %> <% if investment.should_show_votes? %>
<div class="supported callout success"> <% if user_voted_for? %>
<%= t("budgets.investments.votes.already_supported") %> <div class="supported">
</div> <div class="callout success">
<% elsif investment.should_show_votes? %> <%= t("budgets.investments.votes.already_supported") %>
<%= button_to t("budgets.investments.votes.support"), vote_path, </div>
class: "button button-support small expanded", <%= button_to t("budgets.investments.votes.remove_support"), remove_support_path,
title: t("budgets.investments.investment.support_title"), class: "button button-remove-support expanded",
method: "post", method: "delete",
remote: !display_support_alert?, remote: true,
data: ({ confirm: confirm_vote_message } if display_support_alert?), "aria-label": remove_support_aria_label %>
disabled: !current_user, </div>
"aria-label": support_aria_label %> <% else %>
<%= button_to t("budgets.investments.votes.support"), support_path,
class: "button button-support expanded",
title: t("budgets.investments.investment.support_title"),
method: "post",
remote: !display_support_alert?,
data: ({ confirm: confirm_vote_message } if display_support_alert?),
disabled: !current_user,
"aria-label": support_aria_label %>
<% end %>
<% end %> <% end %>
</div> </div>

View File

@@ -7,7 +7,7 @@ class Budgets::Investments::VotesComponent < ApplicationComponent
@investment = investment @investment = investment
end end
def vote_path def support_path
case namespace case namespace
when "management" when "management"
management_budget_investment_votes_path(investment.budget, investment) management_budget_investment_votes_path(investment.budget, investment)
@@ -16,6 +16,17 @@ class Budgets::Investments::VotesComponent < ApplicationComponent
end end
end end
def remove_support_path
vote = investment.votes_for.find_by!(voter: current_user)
case namespace
when "management"
management_budget_investment_vote_path(investment.budget, investment, vote)
else
budget_investment_vote_path(investment.budget, investment, vote)
end
end
private private
def reason def reason
@@ -44,4 +55,8 @@ class Budgets::Investments::VotesComponent < ApplicationComponent
def support_aria_label def support_aria_label
t("budgets.investments.votes.support_label", investment: investment.title) t("budgets.investments.votes.support_label", investment: investment.title)
end end
def remove_support_aria_label
t("budgets.investments.votes.remove_support_label", investment: investment.title)
end
end end

View File

@@ -5,7 +5,6 @@
</header> </header>
<p><%= t("budgets.supports_info.next") %></p> <p><%= t("budgets.supports_info.next") %></p>
<p><%= sanitize(t("budgets.supports_info.remember")) %></p>
<p><%= t("budgets.supports_info.different") %></p> <p><%= t("budgets.supports_info.different") %></p>
</section> </section>

View File

@@ -3,6 +3,7 @@ module Budgets
class VotesController < ApplicationController class VotesController < ApplicationController
load_and_authorize_resource :budget load_and_authorize_resource :budget
load_and_authorize_resource :investment, through: :budget, class: "Budget::Investment" load_and_authorize_resource :investment, through: :budget, class: "Budget::Investment"
load_and_authorize_resource through: :investment, through_association: :votes_for, only: :destroy
def create def create
@investment.register_selection(current_user) @investment.register_selection(current_user)
@@ -13,7 +14,15 @@ module Budgets
notice: t("flash.actions.create.support") notice: t("flash.actions.create.support")
end end
format.js format.js { render :show }
end
end
def destroy
@investment.unliked_by(current_user)
respond_to do |format|
format.js { render :show }
end end
end end
end end

View File

@@ -1,6 +1,7 @@
class Management::Budgets::Investments::VotesController < Management::BaseController class Management::Budgets::Investments::VotesController < Management::BaseController
load_resource :budget, find_by: :slug_or_id load_resource :budget, find_by: :slug_or_id
load_resource :investment, through: :budget, class: "Budget::Investment" load_resource :investment, through: :budget, class: "Budget::Investment"
load_and_authorize_resource through: :investment, through_association: :votes_for, only: :destroy
def create def create
@investment.register_selection(managed_user) @investment.register_selection(managed_user)
@@ -11,7 +12,15 @@ class Management::Budgets::Investments::VotesController < Management::BaseContro
notice: t("flash.actions.create.support") notice: t("flash.actions.create.support")
end end
format.js format.js { render :show }
end
end
def destroy
@investment.unliked_by(managed_user)
respond_to do |format|
format.js { render :show }
end end
end end
end end

View File

@@ -99,7 +99,7 @@ module Abilities
can :update, Budget::Investment, budget: { phase: "accepting" }, author_id: user.id can :update, Budget::Investment, budget: { phase: "accepting" }, author_id: user.id
can :suggest, Budget::Investment, budget: { phase: "accepting" } can :suggest, Budget::Investment, budget: { phase: "accepting" }
can :destroy, Budget::Investment, budget: { phase: ["accepting", "reviewing"] }, author_id: user.id can :destroy, Budget::Investment, budget: { phase: ["accepting", "reviewing"] }, author_id: user.id
can :create, ActsAsVotable::Vote, can [:create, :destroy], ActsAsVotable::Vote,
voter_id: user.id, voter_id: user.id,
votable_type: "Budget::Investment", votable_type: "Budget::Investment",
votable: { budget: { phase: "selecting" }} votable: { budget: { phase: "selecting" }}

View File

@@ -172,6 +172,8 @@ en:
confirm_group: confirm_group:
one: "You can only support investments in %{count} district. If you continue you cannot change the election of your district. Are you sure?" one: "You can only support investments in %{count} district. If you continue you cannot change the election of your district. Are you sure?"
other: "You can only support investments in %{count} districts. If you continue you cannot change the election of your district. Are you sure?" other: "You can only support investments in %{count} districts. If you continue you cannot change the election of your district. Are you sure?"
remove_support: "Remove your support"
remove_support_label: "Remove your support to %{investment}"
support: "Support" support: "Support"
support_label: "Support %{investment}" support_label: "Support %{investment}"
investments_list: investments_list:
@@ -186,7 +188,6 @@ en:
supports_info: supports_info:
different: "You may support on as many different projects as you would like." different: "You may support on as many different projects as you would like."
next: "Support the projects you would like to see move on to the next phase." next: "Support the projects you would like to see move on to the next phase."
remember: "<strong>Remember!</strong> You can only cast your support <strong>once</strong> for each project and each support is <strong>irreversible</strong>."
scrolling: "Keep scrolling to see all ideas" scrolling: "Keep scrolling to see all ideas"
share: "You can share the projects you have supported on through social media and attract more attention and support to them!" share: "You can share the projects you have supported on through social media and attract more attention and support to them!"
supported: supported:

View File

@@ -172,6 +172,8 @@ es:
confirm_group: confirm_group:
one: "Sólo puedes apoyar proyectos en %{count} distrito. Si sigues adelante no podrás cambiar la elección de este distrito. ¿Estás seguro/a?" one: "Sólo puedes apoyar proyectos en %{count} distrito. Si sigues adelante no podrás cambiar la elección de este distrito. ¿Estás seguro/a?"
other: "Sólo puedes apoyar proyectos en %{count} distritos. Si sigues adelante no podrás cambiar la elección de este distrito. ¿Estás seguro/a?" other: "Sólo puedes apoyar proyectos en %{count} distritos. Si sigues adelante no podrás cambiar la elección de este distrito. ¿Estás seguro/a?"
remove_support: "Eliminar apoyo"
remove_support_label: "Eliminar tu apoyo a %{investment}"
support: "Apoyar" support: "Apoyar"
support_label: "Apoyar %{investment}" support_label: "Apoyar %{investment}"
investments_list: investments_list:
@@ -186,7 +188,6 @@ es:
supports_info: supports_info:
different: "Puedes apoyar tantos proyectos diferentes como quieras." different: "Puedes apoyar tantos proyectos diferentes como quieras."
next: "Apoya proyectos que te gustaría ver en la siguiente fase." next: "Apoya proyectos que te gustaría ver en la siguiente fase."
remember: "<strong>¡Recuerda!</strong> Solo puedes emitir tu apoyo <strong>una vez</strong> por cada proyecto y cada apoyo es <strong>irreversible</strong>."
scrolling: "Sigue desplazándote para ver todas las ideas" scrolling: "Sigue desplazándote para ver todas las ideas"
share: "Puedes compartir los proyectos que has apoyado en las redes sociales y ¡atraer más atención y apoyos para ellos!" share: "Puedes compartir los proyectos que has apoyado en las redes sociales y ¡atraer más atención y apoyos para ellos!"
supported: supported:

View File

@@ -8,7 +8,7 @@ resources :budgets, only: [:show, :index] do
collection { get :suggest } collection { get :suggest }
resources :votes, controller: "budgets/investments/votes", only: :create resources :votes, controller: "budgets/investments/votes", only: [:create, :destroy]
end end
resource :ballot, only: :show, controller: "budgets/ballots" do resource :ballot, only: :show, controller: "budgets/ballots" do

View File

@@ -41,7 +41,7 @@ namespace :management do
resources :investments, only: [:index, :new, :create, :show, :destroy], controller: "budgets/investments" do resources :investments, only: [:index, :new, :create, :show, :destroy], controller: "budgets/investments" do
get :print, on: :collection get :print, on: :collection
resources :votes, controller: "budgets/investments/votes", only: :create resources :votes, controller: "budgets/investments/votes", only: [:create, :destroy]
end end
end end
end end

View File

@@ -23,8 +23,21 @@ describe Budgets::Investments::VotesComponent, type: :component do
render_inline component render_inline component
expect(page).to have_button count: 1, disabled: :all
expect(page).to have_button "Support", disabled: true expect(page).to have_button "Support", disabled: true
end end
it "shows the button to remove support when users have supported the investment" do
user = create(:user)
user.up_votes(investment)
allow(controller).to receive(:current_user).and_return(user)
render_inline component
expect(page).to have_button count: 1, disabled: :all
expect(page).to have_button "Remove your support"
expect(page).to have_button "Remove your support to Renovate sidewalks in Main Street"
end
end end
end end
end end

View File

@@ -251,6 +251,9 @@ describe Abilities::Common do
it { should be_able_to(:create, user.votes.build(votable: investment_in_selecting_budget)) } it { should be_able_to(:create, user.votes.build(votable: investment_in_selecting_budget)) }
it { should_not be_able_to(:create, user.votes.build(votable: investment_in_accepting_budget)) } it { should_not be_able_to(:create, user.votes.build(votable: investment_in_accepting_budget)) }
it { should_not be_able_to(:create, user.votes.build(votable: investment_in_balloting_budget)) } it { should_not be_able_to(:create, user.votes.build(votable: investment_in_balloting_budget)) }
it { should be_able_to(:destroy, create(:vote, voter: user, votable: investment_in_selecting_budget)) }
it { should_not be_able_to(:destroy, create(:vote, voter: user, votable: investment_in_accepting_budget)) }
it { should_not be_able_to(:destroy, create(:vote, voter: user, votable: investment_in_balloting_budget)) }
it { should_not be_able_to(:destroy, investment_in_accepting_budget) } it { should_not be_able_to(:destroy, investment_in_accepting_budget) }
it { should_not be_able_to(:destroy, investment_in_reviewing_budget) } it { should_not be_able_to(:destroy, investment_in_reviewing_budget) }

View File

@@ -432,6 +432,42 @@ describe "Budgets" do
expect(page).to have_content "It's time to support projects!" expect(page).to have_content "It's time to support projects!"
expect(page).to have_content "So far you've supported 3 projects." expect(page).to have_content "So far you've supported 3 projects."
end end
scenario "Show supports only if the support has not been removed" do
voter = create(:user, :level_two)
budget = create(:budget, phase: "selecting")
investment = create(:budget_investment, :selected, budget: budget)
login_as(voter)
visit budget_path(budget)
expect(page).to have_content "So far you've supported 0 projects."
visit budget_investment_path(budget, investment)
within("#budget_investment_#{investment.id}_votes") do
click_button "Support"
expect(page).to have_content "You have already supported this investment project."
end
visit budget_path(budget)
expect(page).to have_content "So far you've supported 1 project."
visit budget_investment_path(budget, investment)
within("#budget_investment_#{investment.id}_votes") do
click_button "Remove your support"
expect(page).to have_content "No supports"
end
visit budget_path(budget)
expect(page).to have_content "So far you've supported 0 projects."
end
end end
context "In Drafting phase" do context "In Drafting phase" do

View File

@@ -1177,6 +1177,48 @@ describe "Budget Investments" do
expect(page).to have_content "SUPPORTS" expect(page).to have_content "SUPPORTS"
end end
end end
scenario "Remove a support from show view" do
investment = create(:budget_investment, budget: budget)
login_as(author)
visit budget_investment_path(budget, investment)
within("aside") do
expect(page).to have_content "No supports"
click_button "Support"
expect(page).to have_content "1 support"
expect(page).to have_content "You have already supported this investment project."
click_button "Remove your support"
expect(page).to have_content "No supports"
expect(page).to have_button "Support"
end
end
scenario "Remove a support from index view" do
investment = create(:budget_investment, budget: budget)
login_as(author)
visit budget_investments_path(budget)
within("#budget_investment_#{investment.id}") do
expect(page).to have_content "No supports"
click_button "Support"
expect(page).to have_content "1 support"
expect(page).to have_content "You have already supported this investment project."
click_button "Remove your support"
expect(page).to have_content "No supports"
expect(page).to have_button "Support"
end
end
end end
context "Evaluating Phase" do context "Evaluating Phase" do

View File

@@ -333,6 +333,56 @@ describe "Budget Investments" do
expect(page).to have_content "CONSUL\nMANAGEMENT" expect(page).to have_content "CONSUL\nMANAGEMENT"
end end
scenario "Remove support on behalf of someone else in index view" do
create(:budget_investment, heading: heading)
login_managed_user(user)
login_as_manager(manager)
visit management_budget_investments_path(budget)
click_button "Support"
expect(page).to have_content "1 support"
expect(page).to have_content "You have already supported this investment project. Share it!"
expect(page).not_to have_button "Support"
click_button "Remove your support"
expect(page).to have_content "No supports"
expect(page).to have_button "Support"
expect(page).not_to have_button "Remove your support"
end
scenario "Remove support on behalf of someone else in show view" do
create(:budget_investment, heading: heading, title: "Don't support me!")
login_managed_user(user)
login_as_manager(manager)
visit management_budget_investments_path(budget)
click_link "Don't support me!"
expect(page).to have_css "h1", exact_text: "Don't support me!"
click_button "Support"
expect(page).to have_content "1 support"
expect(page).to have_content "You have already supported this investment project. Share it!"
expect(page).not_to have_button "Support"
click_button "Remove your support"
expect(page).to have_content "No supports"
expect(page).to have_button "Support"
expect(page).not_to have_button "Remove your support"
refresh
expect(page).to have_content "No supports"
expect(page).to have_button "Support"
expect(page).not_to have_button "Remove your support"
end
scenario "Should not allow unverified users to vote proposals" do scenario "Should not allow unverified users to vote proposals" do
login_managed_user(create(:user)) login_managed_user(create(:user))
create(:budget_investment, budget: budget) create(:budget_investment, budget: budget)