From 1b407b0702a4bc7833dc378469ce4ac7ef2a1ef8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javi=20Mart=C3=ADn?= Date: Tue, 31 Aug 2021 00:04:08 +0200 Subject: [PATCH] Move budget ballot actions to admin budget page The buttons to create polls associated with a budget were too prominent, appearing on the table as if they were as used as the link to manage investments. Most CONSUL installations don't use physical booths, and would probably wonder what that button is about. We're moving it to a more discrete place, at the bottom of the budget page. This way we can also split the action in two: on budgets not having a poll, we display the button in a not-so-accessible position (at the bottom of the page), since this button will only be used once per budget at most. Once the poll has been created, it means this feature is going to be used, so we display a link to manage ballots more prominently at the top of the page. If the budget has finished the final voting stage without creating a poll, we don't show either the link or the button because this feature can no longer be used. We're also adding some texts indicating what this feature is about, since it's probably one of the least understood features in CONSUL (probably because the interface is very confusing... but that's a different story). Since now from the budget page we can access every feature related to the budget, we can remove the "preview" action from the budgets index table, since this feature isn't that useful for budgets once they're published. Now the budgets table doesn't take as much space as it used to, although it's still too wide to be handled properly on devices with a small screen. --- .../stylesheets/admin/budgets/actions.scss | 5 +++ .../stylesheets/admin/budgets/links.scss | 5 +++ app/assets/stylesheets/mixins/icons.scss | 3 +- .../admin/budgets/actions_component.rb | 25 ++++++++++++++ .../admin/budgets/links_component.html.erb | 6 ++++ .../budgets/table_actions_component.html.erb | 14 -------- .../admin/budgets/table_actions_component.rb | 11 ------ config/locales/en/admin.yml | 3 ++ config/locales/es/admin.yml | 3 ++ .../admin/budgets/actions_component_spec.rb | 34 +++++++++++++++++++ .../admin/budgets/links_component_spec.rb | 20 +++++++++++ .../budgets/table_actions_component_spec.rb | 22 ++---------- spec/system/budget_polls/budgets_spec.rb | 29 +++++----------- 13 files changed, 114 insertions(+), 66 deletions(-) create mode 100644 spec/components/admin/budgets/actions_component_spec.rb diff --git a/app/assets/stylesheets/admin/budgets/actions.scss b/app/assets/stylesheets/admin/budgets/actions.scss index 03b82fd6b..c72a6bfac 100644 --- a/app/assets/stylesheets/admin/budgets/actions.scss +++ b/app/assets/stylesheets/admin/budgets/actions.scss @@ -42,6 +42,10 @@ @include hollow-button; } + .ballots-link { + @include regular-button; + } + .destroy-link { @include regular-button($alert-color); @@ -56,6 +60,7 @@ } .calculate-winners-link, + .ballots-link, .destroy-link { margin-bottom: 0; } diff --git a/app/assets/stylesheets/admin/budgets/links.scss b/app/assets/stylesheets/admin/budgets/links.scss index a107f700b..3326ba75b 100644 --- a/app/assets/stylesheets/admin/budgets/links.scss +++ b/app/assets/stylesheets/admin/budgets/links.scss @@ -26,6 +26,11 @@ @include icon-on-top; } + .ballots-link { + @include has-fa-icon(archive, solid); + color: $link; + } + .results-link { @include has-fa-icon(poll, solid); } diff --git a/app/assets/stylesheets/mixins/icons.scss b/app/assets/stylesheets/mixins/icons.scss index 8c7317e78..5b68f75a1 100644 --- a/app/assets/stylesheets/mixins/icons.scss +++ b/app/assets/stylesheets/mixins/icons.scss @@ -1618,8 +1618,7 @@ $font-awesome-icons: ( color: $color-success; } - .manage-link, - .ballots-link { + .manage-link { @include has-fa-icon(archive, solid); color: $link; } diff --git a/app/components/admin/budgets/actions_component.rb b/app/components/admin/budgets/actions_component.rb index 0728f4a93..07c515b81 100644 --- a/app/components/admin/budgets/actions_component.rb +++ b/app/components/admin/budgets/actions_component.rb @@ -22,6 +22,10 @@ class Admin::Budgets::ActionsComponent < ApplicationComponent hint: winners_hint, html: winners_action }, + ballots: { + hint: t("admin.budgets.actions.descriptions.ballots"), + html: ballots_action + }, destroy: { hint: destroy_hint, html: destroy_action @@ -55,6 +59,27 @@ class Admin::Budgets::ActionsComponent < ApplicationComponent end end + def ballots_action + if budget.published? && !budget.balloting_finished? && !budget.poll.present? + action(:ballots, + text: t("admin.budgets.actions.ballots"), + path: create_budget_poll_path, + method: :post, + confirm: t("admin.budgets.actions.confirm.ballots")) + end + end + + def create_budget_poll_path + balloting_phase = budget.phases.find_by(kind: "balloting") + + admin_polls_path(poll: { + name: budget.name, + budget_id: budget.id, + starts_at: balloting_phase.starts_at, + ends_at: balloting_phase.ends_at + }) + end + def descriptor_id(action_name) "#{dom_id(budget, action_name)}_descriptor" end diff --git a/app/components/admin/budgets/links_component.html.erb b/app/components/admin/budgets/links_component.html.erb index c8afc722b..6b73f9f3b 100644 --- a/app/components/admin/budgets/links_component.html.erb +++ b/app/components/admin/budgets/links_component.html.erb @@ -5,6 +5,12 @@ path: admin_budget_budget_investments_path(budget)) %> <% end %> + <% if budget.poll.present? %> + <%= action(:ballots, + text: t("admin.budgets.index.admin_ballots"), + path: admin_poll_booth_assignments_path(budget.poll)) %> + <% end %> + <% if can?(:read_results, budget) %> <%= action(:results, text: results_text, path: budget_results_path(budget)) %> <% else %> diff --git a/app/components/admin/budgets/table_actions_component.html.erb b/app/components/admin/budgets/table_actions_component.html.erb index c6f5ada52..45f7e831b 100644 --- a/app/components/admin/budgets/table_actions_component.html.erb +++ b/app/components/admin/budgets/table_actions_component.html.erb @@ -2,18 +2,4 @@ <%= actions.action(:investments, text: t("admin.budgets.index.budget_investments"), path: admin_budget_budget_investments_path(budget)) %> - <% if budget.poll.present? %> - <%= actions.action(:ballots, - text: t("admin.budgets.index.admin_ballots"), - path: admin_poll_booth_assignments_path(budget.poll)) %> - <% else %> - <%= actions.action(:ballots, - text: t("admin.budgets.index.admin_ballots"), - path: create_budget_poll_path, - method: :post) %> - <% end %> - <%= actions.action(:preview, - text: t("admin.budgets.actions.preview"), - path: budget_path(budget), - target: "_blank") %> <% end %> diff --git a/app/components/admin/budgets/table_actions_component.rb b/app/components/admin/budgets/table_actions_component.rb index 92277c152..2ac537bb3 100644 --- a/app/components/admin/budgets/table_actions_component.rb +++ b/app/components/admin/budgets/table_actions_component.rb @@ -7,17 +7,6 @@ class Admin::Budgets::TableActionsComponent < ApplicationComponent private - def create_budget_poll_path - balloting_phase = budget.phases.find_by(kind: "balloting") - - admin_polls_path(poll: { - name: budget.name, - budget_id: budget.id, - starts_at: balloting_phase.starts_at, - ends_at: balloting_phase.ends_at - }) - end - def actions_component Admin::TableActionsComponent.new( budget, diff --git a/config/locales/en/admin.yml b/config/locales/en/admin.yml index c834727f9..5e04255ff 100644 --- a/config/locales/en/admin.yml +++ b/config/locales/en/admin.yml @@ -69,9 +69,12 @@ en: no_activity: There are no moderators activity. budgets: actions: + ballots: "Create booths" confirm: + ballots: "Are you sure? This action is only necessary if you're going to use physical booths during the final voting phase. Citizens will still be able to vote via web with or without physical booths." destroy: "Are you sure? This will delete the budget and all its associated groups and headings. This action cannot be undone." descriptions: + ballots: "This will let you manage physical ballots. Only use this option if you're going to use physical booths to collect ballots during the final voting phase." calculate_winners: 'After calculating the results, only administrators will be able to access them. They will be public when the project reaches the "%{phase}" phase if the option "Show results" is enabled when editing the budget.' destroy: "This will delete the budget and all its associated groups and headers. This action cannot be undone." edit: "Edit budget" diff --git a/config/locales/es/admin.yml b/config/locales/es/admin.yml index d9122a158..0269be9d0 100644 --- a/config/locales/es/admin.yml +++ b/config/locales/es/admin.yml @@ -69,9 +69,12 @@ es: no_activity: No hay actividad de moderadores. budgets: actions: + ballots: "Crear urnas" confirm: + ballots: "¿Seguro? Esta acción solo es necesaria si vas a utilizar urnas físicas durante la votación final. Los ciudadanos podrán votar mediante la web con o sin urnas físicas." destroy: "¿Seguro? Esta acción borrará el presupuesto y todos sus grupos y partidas. Esta acción no se puede deshacer." descriptions: + ballots: "Esta acción te permitirá gestionar las urnas de votación. Usa esta opción solamente si vas a utilizar urnas físicas para recoger votos durante la votación final." calculate_winners: 'Tras calcular los resultados, solamente los administradores tendrán acceso a los mismos. Se harán públicos cuando el presupuesto llegue a la fase "%{phase}" si se habilita la opción "Mostrar resultados" al editar el presupuesto.' destroy: "Esta acción borrará el presupuesto y todos sus grupos y partidas. Esta acción no se puede deshacer." edit: "Editar presupuesto" diff --git a/spec/components/admin/budgets/actions_component_spec.rb b/spec/components/admin/budgets/actions_component_spec.rb new file mode 100644 index 000000000..106b9b3ea --- /dev/null +++ b/spec/components/admin/budgets/actions_component_spec.rb @@ -0,0 +1,34 @@ +require "rails_helper" + +describe Admin::Budgets::ActionsComponent, controller: Admin::BaseController do + before { sign_in(create(:administrator).user) } + + let(:budget) { create(:budget) } + let(:component) { Admin::Budgets::ActionsComponent.new(budget) } + + describe "Create booths button" do + it "is rendered for budgets without polls" do + render_inline component + + expect(page.find("form[action*='polls'][method='post']")).to have_button "Create booths" + end + + it "is not rendered for finished budgets without polls" do + budget.update!(phase: "finished") + + render_inline component + + expect(page).not_to have_css "form[action*='polls']" + expect(page).not_to have_button "Create booths" + end + + it "is not rendered for budgets with polls" do + budget.poll = create(:poll, budget: budget) + + render_inline component + + expect(page).not_to have_css "form[action*='polls']" + expect(page).not_to have_button "Create booths" + end + end +end diff --git a/spec/components/admin/budgets/links_component_spec.rb b/spec/components/admin/budgets/links_component_spec.rb index 3013d5d25..997b1b6bd 100644 --- a/spec/components/admin/budgets/links_component_spec.rb +++ b/spec/components/admin/budgets/links_component_spec.rb @@ -97,4 +97,24 @@ describe Admin::Budgets::LinksComponent, controller: Admin::BaseController do expect(page).not_to have_link "Investment projects" end end + + describe "ballots link" do + let(:budget) { create(:budget) } + let(:component) { Admin::Budgets::LinksComponent.new(budget) } + + it "is rendered for budgets with polls" do + budget.poll = create(:poll, budget: budget) + path = Rails.application.routes.url_helpers.admin_poll_booth_assignments_path(budget.poll) + + render_inline component + + expect(page).to have_link "Ballots", href: path + end + + it "is not rendered for budgets without polls" do + render_inline component + + expect(page).not_to have_link "Ballots" + end + end end diff --git a/spec/components/admin/budgets/table_actions_component_spec.rb b/spec/components/admin/budgets/table_actions_component_spec.rb index 7f320da9e..39dd13b43 100644 --- a/spec/components/admin/budgets/table_actions_component_spec.rb +++ b/spec/components/admin/budgets/table_actions_component_spec.rb @@ -4,29 +4,13 @@ describe Admin::Budgets::TableActionsComponent, controller: Admin::BaseControlle let(:budget) { create(:budget) } let(:component) { Admin::Budgets::TableActionsComponent.new(budget) } - it "renders actions to edit budget, manage investments and manage ballots" do + it "renders actions to edit budget and manage investments" do render_inline component - expect(page).to have_link count: 3 + expect(page).to have_link count: 2 expect(page).to have_link "Investment projects", href: /investments/ expect(page).to have_link "Edit", href: /#{budget.id}\Z/ - expect(page).to have_link "Preview", href: /budgets/ - expect(page).to have_button count: 1 - expect(page).to have_button "Ballots" - end - - it "renders button to create new poll for budgets without polls" do - render_inline component - - expect(page).to have_css "form[action*='polls'][method='post']", exact_text: "Ballots" - end - - it "renders link to manage ballots for budgets with polls" do - budget.poll = create(:poll, budget: budget) - - render_inline component - - expect(page).to have_link "Ballots", href: /booth_assignments/ + expect(page).not_to have_button end end diff --git a/spec/system/budget_polls/budgets_spec.rb b/spec/system/budget_polls/budgets_spec.rb index a92fdf800..bb5152f76 100644 --- a/spec/system/budget_polls/budgets_spec.rb +++ b/spec/system/budget_polls/budgets_spec.rb @@ -6,9 +6,9 @@ describe "Admin Budgets", :admin do budget = create(:budget) balloting_phase = budget.phases.balloting - visit admin_budgets_path + visit admin_budget_path(budget) - click_button "Ballots" + accept_confirm { click_button "Create booths" } expect(page).to have_current_path(/admin\/polls\/\d+/) expect(page).to have_content(budget.name) @@ -17,28 +17,17 @@ describe "Admin Budgets", :admin do end scenario "Create poll in current locale if the budget does not have a poll associated" do - create(:budget, - name_en: "Budget for climate change", - name_fr: "Budget pour le changement climatique") + budget = create(:budget, + name_en: "Budget for climate change", + name_es: "Presupuesto por el cambio climático") - visit admin_budgets_path - select "Français", from: "Language:" + visit admin_budget_path(budget) + select "Español", from: "Language:" - click_button "Bulletins de l’admin" + accept_confirm { click_button "Crear urnas" } expect(page).to have_current_path(/admin\/polls\/\d+/) - expect(page).to have_content("Budget pour le changement climatique") - end - - scenario "Display link to poll if the budget has a poll associated" do - budget = create(:budget) - poll = create(:poll, budget: budget) - - visit admin_budgets_path - - within "#budget_#{budget.id}" do - expect(page).to have_link "Ballots", href: admin_poll_booth_assignments_path(poll) - end + expect(page).to have_content "Presupuesto por el cambio climático" end end