diff --git a/app/controllers/admin/budget_investments_controller.rb b/app/controllers/admin/budget_investments_controller.rb index fa4608bbc..d036e8006 100644 --- a/app/controllers/admin/budget_investments_controller.rb +++ b/app/controllers/admin/budget_investments_controller.rb @@ -83,7 +83,7 @@ class Admin::BudgetInvestmentsController < Admin::BaseController params.require(:budget_investment) .permit(:title, :description, :external_url, :heading_id, :administrator_id, :tag_list, :valuation_tag_list, :incompatible, :visible_to_valuators, :selected, - valuator_ids: [], valuator_group_ids: []) + :milestone_tag_list, valuator_ids: [], valuator_group_ids: []) end def load_budget diff --git a/app/controllers/budgets/executions_controller.rb b/app/controllers/budgets/executions_controller.rb index 0d4c25134..63f74717e 100644 --- a/app/controllers/budgets/executions_controller.rb +++ b/app/controllers/budgets/executions_controller.rb @@ -12,15 +12,15 @@ module Budgets private def investments_by_heading + base = @budget.investments.winners + base = base.joins(milestones: :translations).includes(:milestones) + base = base.tagged_with(params[:milestone_tag]) if params[:milestone_tag].present? + if params[:status].present? - @budget.investments.winners - .with_milestone_status_id(params[:status]) - .uniq - .group_by(&:heading) + base = base.with_milestone_status_id(params[:status]) + base.uniq.group_by(&:heading) else - @budget.investments.winners - .joins(milestones: :translations) - .distinct.group_by(&:heading) + base.distinct.group_by(&:heading) end end diff --git a/app/helpers/budget_executions_helper.rb b/app/helpers/budget_executions_helper.rb index 90d0744b0..d8f20936f 100644 --- a/app/helpers/budget_executions_helper.rb +++ b/app/helpers/budget_executions_helper.rb @@ -4,6 +4,12 @@ module BudgetExecutionsHelper @budget.investments.winners.with_milestone_status_id(status).count end + def options_for_milestone_tags + @budget.milestone_tags.map do |tag| + ["#{tag} (#{@budget.investments.winners.tagged_with(tag).count})", tag] + end + end + def first_milestone_with_image(investment) investment.milestones.order_by_publication_date .select{ |milestone| milestone.image.present? }.last diff --git a/app/models/budget.rb b/app/models/budget.rb index b31f08ed5..3797be797 100644 --- a/app/models/budget.rb +++ b/app/models/budget.rb @@ -195,6 +195,10 @@ class Budget < ApplicationRecord investments.winners.any? end + def milestone_tags + investments.winners.map(&:milestone_tag_list).flatten.uniq.sort + end + private def sanitize_descriptions diff --git a/app/models/concerns/milestoneable.rb b/app/models/concerns/milestoneable.rb index 66de28d6c..0ed1b47f5 100644 --- a/app/models/concerns/milestoneable.rb +++ b/app/models/concerns/milestoneable.rb @@ -8,6 +8,8 @@ module Milestoneable has_many :progress_bars, as: :progressable + acts_as_taggable_on :milestone_tags + def primary_progress_bar progress_bars.primary.first end diff --git a/app/views/admin/budget_investments/edit.html.erb b/app/views/admin/budget_investments/edit.html.erb index 8d2ff842f..81c4a6895 100644 --- a/app/views/admin/budget_investments/edit.html.erb +++ b/app/views/admin/budget_investments/edit.html.erb @@ -105,6 +105,13 @@ +
+ <%= f.text_field :milestone_tag_list, + value: @investment.milestone_tag_list.sort.join(", "), + label: t("admin.budget_investments.edit.milestone_tags") %> +
+ +
<%= f.submit(class: "button", value: t("admin.budget_investments.edit.submit_button")) %>
diff --git a/app/views/admin/milestones/_milestones.html.erb b/app/views/admin/milestones/_milestones.html.erb index 25163ceb4..bcbf61885 100644 --- a/app/views/admin/milestones/_milestones.html.erb +++ b/app/views/admin/milestones/_milestones.html.erb @@ -4,6 +4,14 @@ polymorphic_path([:admin, *resource_hierarchy_for(milestoneable.progress_bars.new)]), class: "button hollow float-right" %> +<% if milestoneable.milestone_tag_list.any? %> +
+ + <%= t("admin.milestones.index.milestone_tags") %>: + <%= milestoneable.milestone_tag_list.sort.join(", ") %> +
+<% end %> + <% if milestoneable.milestones.any? %> diff --git a/app/views/budgets/executions/show.html.erb b/app/views/budgets/executions/show.html.erb index acab50de4..f9410fdc8 100644 --- a/app/views/budgets/executions/show.html.erb +++ b/app/views/budgets/executions/show.html.erb @@ -44,13 +44,24 @@
<%= form_tag(budget_executions_path(@budget), method: :get) do %>
- <%= label_tag :status, t("budgets.executions.filters.label") %> + <%= label_tag :milestone_tag, t("budgets.executions.filters.milestone_tag.label") %> + <%= select_tag :milestone_tag, + options_for_select( + options_for_milestone_tags, + params[:milestone_tag] + ), + class: "js-submit-on-change", + prompt: t("budgets.executions.filters.milestone_tag.all", + count: @budget.investments.winners.with_milestones.count) %> +
+
+ <%= label_tag :status, t("budgets.executions.filters.status.label") %> <%= select_tag :status, options_from_collection_for_select(@statuses, :id, lambda { |s| "#{s.name} (#{filters_select_counts(s.id)})" }, params[:status]), class: "js-submit-on-change", - prompt: t("budgets.executions.filters.all", + prompt: t("budgets.executions.filters.status.all", count: @budget.investments.winners.with_milestones.count) %>
<% end %> diff --git a/config/locales/ar/budgets.yml b/config/locales/ar/budgets.yml index 63c1b6322..7b5c4a0d1 100644 --- a/config/locales/ar/budgets.yml +++ b/config/locales/ar/budgets.yml @@ -168,8 +168,9 @@ ar: heading_selection_title: "جسب المنطقة" no_winner_investments: "لا يوجد استثمارات فائزة" filters: - label: "حالة المشروع الحالية" - all: "الكل (%{count})" + status: + label: "حالة المشروع الحالية" + all: "الكل (%{count})" phases: errors: dates_range_invalid: "تاريخ البدأ لا يمكن ان يكون مساو او يتجاوز تاريخ الانتهاء" diff --git a/config/locales/de-DE/budgets.yml b/config/locales/de-DE/budgets.yml index 30bb6305e..d69d70fd2 100644 --- a/config/locales/de-DE/budgets.yml +++ b/config/locales/de-DE/budgets.yml @@ -182,8 +182,9 @@ de: heading_selection_title: "Nach Bezirk" no_winner_investments: "Keine erfolgreichen Ausgabenvorschläge in diesem Status" filters: - label: "Aktueller Stand des Projekts" - all: "Alle (%{count})" + status: + label: "Aktueller Stand des Projekts" + all: "Alle (%{count})" phases: errors: dates_range_invalid: "Das Anfangsdatum kann nicht gleich oder später als das Enddatum sein" diff --git a/config/locales/en/admin.yml b/config/locales/en/admin.yml index b854fc616..fd472c7f1 100644 --- a/config/locales/en/admin.yml +++ b/config/locales/en/admin.yml @@ -281,6 +281,7 @@ en: tags_placeholder: "Write the tags you want separated by commas (,)" undefined: Undefined user_groups: "Groups" + milestone_tags: Milestone tags search_unfeasible: Search unfeasible milestones: index: @@ -297,6 +298,7 @@ en: documents: "Documents" milestone: Milestone new_milestone: Create new milestone + milestone_tags: Milestone Tags form: admin_statuses: Manage statuses no_statuses_defined: There are no defined statuses yet diff --git a/config/locales/en/budgets.yml b/config/locales/en/budgets.yml index 5f49b1d41..c209d4d2c 100644 --- a/config/locales/en/budgets.yml +++ b/config/locales/en/budgets.yml @@ -184,8 +184,12 @@ en: heading_selection_title: "By district" no_winner_investments: "No winner investments in this state" filters: - label: "Project's current state" - all: "All (%{count})" + status: + label: "Project's current state" + all: "All (%{count})" + milestone_tag: + label: "Milestone tag" + all: "All (%{count})" phases: errors: dates_range_invalid: "Start date can't be equal or later than End date" diff --git a/config/locales/es/admin.yml b/config/locales/es/admin.yml index 934f45d37..907fc15c7 100644 --- a/config/locales/es/admin.yml +++ b/config/locales/es/admin.yml @@ -281,6 +281,7 @@ es: tags_placeholder: "Escribe las etiquetas que desees separadas por comas (,)" undefined: Sin definir user_groups: "Grupos" + milestone_tags: Etiquetas de Seguimiento search_unfeasible: Buscar inviables milestones: index: @@ -297,6 +298,7 @@ es: documents: "Documentos" milestone: Seguimiento new_milestone: Crear nuevo hito + milestone_tags: Etiquetas de Seguimiento form: admin_statuses: Gestionar estados no_statuses_defined: No hay estados definidos diff --git a/config/locales/es/budgets.yml b/config/locales/es/budgets.yml index cac1f50bd..855598a0d 100644 --- a/config/locales/es/budgets.yml +++ b/config/locales/es/budgets.yml @@ -184,8 +184,12 @@ es: heading_selection_title: "Ámbito de actuación" no_winner_investments: "No hay proyectos de gasto ganadores en este estado" filters: - label: "Estado actual del proyecto" - all: "Todos (%{count})" + status: + label: "Estado actual del proyecto" + all: "Todos (%{count})" + milestone_tag: + label: "Etiquetas de Seguimiento" + all: "Todos (%{count})" phases: errors: dates_range_invalid: "La fecha de comienzo no puede ser igual o superior a la de finalización" diff --git a/config/locales/fi-FI/budgets.yml b/config/locales/fi-FI/budgets.yml index 80cdcbf29..4a956a286 100644 --- a/config/locales/fi-FI/budgets.yml +++ b/config/locales/fi-FI/budgets.yml @@ -50,4 +50,5 @@ fi: link: "Tavoitteet" page_title: "%{budget} - Tavoitteet" filters: - all: "Kaikki (%{count})" + status: + all: "Kaikki (%{count})" diff --git a/config/locales/gl/budgets.yml b/config/locales/gl/budgets.yml index e578b008f..f9df44e6c 100644 --- a/config/locales/gl/budgets.yml +++ b/config/locales/gl/budgets.yml @@ -182,8 +182,9 @@ gl: heading_selection_title: "Por ámbito de actuación" no_winner_investments: "Non hai investimentos gañadores neste estado" filters: - label: "Estado actual do proxecto" - all: "Todo (%{count})" + status: + label: "Estado actual do proxecto" + all: "Todo (%{count})" phases: errors: dates_range_invalid: "A data de comezo non pode ser igual ou superior á de remate" diff --git a/config/locales/it/budgets.yml b/config/locales/it/budgets.yml index 74df6ac9d..d2ae6a3ac 100644 --- a/config/locales/it/budgets.yml +++ b/config/locales/it/budgets.yml @@ -176,7 +176,8 @@ it: link: "Traguardi" heading_selection_title: "Per circoscrizione" filters: - all: "Tutto (%{count})" + status: + all: "Tutto (%{count})" phases: errors: dates_range_invalid: "La Data di inizio non può essere uguale o successiva alla Data finale" diff --git a/config/locales/nl/budgets.yml b/config/locales/nl/budgets.yml index 166c5419b..22bd22417 100644 --- a/config/locales/nl/budgets.yml +++ b/config/locales/nl/budgets.yml @@ -184,8 +184,9 @@ nl: heading_selection_title: "Per wijk" no_winner_investments: "Geen geaccepteerde investeringen in deze status" filters: - label: "Status van het project" - all: "Alle (%{count})" + status: + label: "Status van het project" + all: "Alle (%{count})" phases: errors: dates_range_invalid: "De begindatum dient kleiner te zijn dan de einddatum" diff --git a/config/locales/so-SO/budgets.yml b/config/locales/so-SO/budgets.yml index d47a4e51e..dbcf21d0c 100644 --- a/config/locales/so-SO/budgets.yml +++ b/config/locales/so-SO/budgets.yml @@ -181,8 +181,9 @@ so: heading_selection_title: "Degmo ahaan" no_winner_investments: "Mana jiraan maalgalin ku guuleysta gobolkan" filters: - label: "Mashruca hada gobolka kasocda" - all: "Dhaman%{count}" + status: + label: "Mashruca hada gobolka kasocda" + all: "Dhaman%{count}" phases: errors: dates_range_invalid: "Taariikhda bilowga ma noqon karto mid siman ama ka dambeysa taariikhda dhammaadka" diff --git a/config/locales/sq-AL/budgets.yml b/config/locales/sq-AL/budgets.yml index cb376b4f8..6d3cecdc6 100644 --- a/config/locales/sq-AL/budgets.yml +++ b/config/locales/sq-AL/budgets.yml @@ -49,7 +49,7 @@ sq: all_phases: Shihni të gjitha fazat all_phases: Fazat e investimeve buxhetore map: Propozimet e investimeve buxhetore të vendosura gjeografikisht - investment_proyects: Lista e të gjitha projekteve të investimeve + investment_proyects: Lista e të gjitha projekteve të investimeve unfeasible_investment_proyects: Lista e të gjitha projekteve të investimeve të papranueshme not_selected_investment_proyects: Lista e të gjitha projekteve të investimeve të pa zgjedhur për votim finished_budgets: Përfunduan buxhetet pjesëmarrëse. @@ -171,7 +171,7 @@ sq: accepted: "Propozimi i pranuar i shpenzimeve:" discarded: "Propozimi i hedhur poshtë i shpenzimeve:" incompatibles: I papajtueshëm - investment_proyects: Lista e të gjitha projekteve të investimeve + investment_proyects: Lista e të gjitha projekteve të investimeve unfeasible_investment_proyects: Lista e të gjitha projekteve të investimeve të papranueshme not_selected_investment_proyects: Lista e të gjitha projekteve të investimeve të pa zgjedhur për votim executions: @@ -181,8 +181,9 @@ sq: heading_selection_title: "Nga rrethi" no_winner_investments: "Asnjë investim fitues në këtë gjendje" filters: - label: "Gjendja aktuale e projektit" - all: "Të gjitha (%{count})" + status: + label: "Gjendja aktuale e projektit" + all: "Të gjitha (%{count})" phases: errors: dates_range_invalid: "Data e fillimit nuk mund të jetë e barabartë ose më vonë se data e përfundimit" diff --git a/config/locales/val/budgets.yml b/config/locales/val/budgets.yml index 1e2cd7bcb..2cd0e9207 100644 --- a/config/locales/val/budgets.yml +++ b/config/locales/val/budgets.yml @@ -182,8 +182,9 @@ val: heading_selection_title: "Àmbit d'actuació" no_winner_investments: "No hi ha propostes en aquest estat" filters: - label: "Estat actual del projecte" - all: "Tots (%{count})" + status: + label: "Estat actual del projecte" + all: "Tots (%{count})" phases: errors: dates_range_invalid: "La data d'inici no ha de ser igual o posterior a la data de fi" diff --git a/config/locales/zh-CN/budgets.yml b/config/locales/zh-CN/budgets.yml index f3588eb3a..5ac40d396 100644 --- a/config/locales/zh-CN/budgets.yml +++ b/config/locales/zh-CN/budgets.yml @@ -176,8 +176,9 @@ zh-CN: heading_selection_title: "按区域" no_winner_investments: "这个州里没有胜出的投资" filters: - label: "项目的当前状态" - all: "所有(%{count})" + status: + label: "项目的当前状态" + all: "所有(%{count})" phases: errors: dates_range_invalid: "开始日期不能等于或晚于结束日期" diff --git a/spec/factories/budgets.rb b/spec/factories/budgets.rb index fc60927cc..11645104f 100644 --- a/spec/factories/budgets.rb +++ b/spec/factories/budgets.rb @@ -166,6 +166,10 @@ FactoryBot.define do trait :with_confirmed_hide do confirmed_hide_at { Time.current } end + + trait :with_milestone_tags do + after(:create) { |investment| investment.milestone_tags << create(:tag, :milestone) } + end end factory :budget_phase, class: "Budget::Phase" do diff --git a/spec/factories/classifications.rb b/spec/factories/classifications.rb index e905ea3d4..e8721a50c 100644 --- a/spec/factories/classifications.rb +++ b/spec/factories/classifications.rb @@ -5,6 +5,10 @@ FactoryBot.define do trait :category do kind "category" end + + trait :milestone do + kind "milestone" + end end factory :tagging, class: "ActsAsTaggableOn::Tagging" do diff --git a/spec/factories/legislations.rb b/spec/factories/legislations.rb index ff149c905..d6ed4b20a 100644 --- a/spec/factories/legislations.rb +++ b/spec/factories/legislations.rb @@ -112,6 +112,10 @@ FactoryBot.define do result_publication_enabled false published true end + + trait :with_milestone_tags do + after(:create) { |legislation| legislation.milestone_tags << create(:tag, :milestone) } + end end factory :legislation_draft_version, class: "Legislation::DraftVersion" do diff --git a/spec/factories/proposals.rb b/spec/factories/proposals.rb index 7cbd44acb..92a925627 100644 --- a/spec/factories/proposals.rb +++ b/spec/factories/proposals.rb @@ -67,6 +67,10 @@ FactoryBot.define do trait :published do published_at { Time.current } end + + trait :with_milestone_tags do + after(:create) { |proposal| proposal.milestone_tags << create(:tag, :milestone) } + end end factory :proposal_notification do diff --git a/spec/features/admin/budget_investments_spec.rb b/spec/features/admin/budget_investments_spec.rb index 073efa2d3..8e870fa5a 100644 --- a/spec/features/admin/budget_investments_spec.rb +++ b/spec/features/admin/budget_investments_spec.rb @@ -1331,6 +1331,23 @@ describe "Admin budget investments" do expect(page).to have_content "can't be blank" end + scenario "Add milestone tags" do + budget_investment = create(:budget_investment) + + visit admin_budget_budget_investment_path(budget_investment.budget, budget_investment) + + expect(page).not_to have_content("Milestone Tags:") + + click_link "Edit classification" + + fill_in "budget_investment_milestone_tag_list", with: "tag1, tag2" + + click_button "Update" + + expect(page).to have_content "Investment project updated succesfully." + expect(page).to have_content("Milestone Tags: tag1, tag2") + end + end context "Selecting" do diff --git a/spec/features/budgets/executions_spec.rb b/spec/features/budgets/executions_spec.rb index 3083d1eca..579b7d038 100644 --- a/spec/features/budgets/executions_spec.rb +++ b/spec/features/budgets/executions_spec.rb @@ -235,6 +235,52 @@ describe "Executions" do select "Bidding (0)", from: "status" expect(page).not_to have_content(investment1.title) end + + scenario "by milestone tag, only display tags for winner investments", :js do + create(:milestone, milestoneable: investment1, status: status1) + create(:milestone, milestoneable: investment2, status: status2) + create(:milestone, milestoneable: investment3, status: status2) + investment1.milestone_tag_list.add("tag1", "tag2") + investment1.save + investment2.milestone_tag_list.add("tag2") + investment2.save + investment3.milestone_tag_list.add("tag2") + investment3.save + + visit budget_path(budget) + + click_link "See results" + click_link "Milestones" + + expect(page).to have_content(investment1.title) + expect(page).to have_content(investment2.title) + + select "tag2 (2)", from: "milestone_tag" + + expect(page).to have_content(investment1.title) + expect(page).to have_content(investment2.title) + + select "Studying the project (1)", from: "status" + + expect(page).to have_content(investment1.title) + expect(page).not_to have_content(investment2.title) + + select "Bidding (1)", from: "status" + + expect(page).not_to have_content(investment1.title) + expect(page).to have_content(investment2.title) + + select "tag1 (1)", from: "milestone_tag" + + expect(page).not_to have_content(investment1.title) + expect(page).not_to have_content(investment2.title) + + select "All (2)", from: "milestone_tag" + + expect(page).not_to have_content(investment1.title) + expect(page).to have_content(investment2.title) + end + end context "Heading Order" do diff --git a/spec/models/budget/investment_spec.rb b/spec/models/budget/investment_spec.rb index 587c34ba2..439938eff 100644 --- a/spec/models/budget/investment_spec.rb +++ b/spec/models/budget/investment_spec.rb @@ -1267,6 +1267,32 @@ describe Budget::Investment do investment.valuators << valuator investment.administrator = administrator expect(investment.admin_and_valuator_users_associated).to eq([valuator, administrator]) + + end + end + + describe "milestone_tags" do + context "without milestone_tags" do + let(:investment) {create(:budget_investment)} + + it "do not have milestone_tags" do + expect(investment.milestone_tag_list).to eq([]) + expect(investment.milestone_tags).to eq([]) + end + + it "add a new milestone_tag" do + investment.milestone_tag_list = "tag1,tag2" + + expect(investment.milestone_tag_list).to eq(["tag1", "tag2"]) + end + end + + context "with milestone_tags" do + let(:investment) {create(:budget_investment, :with_milestone_tags)} + + it "has milestone_tags" do + expect(investment.milestone_tag_list.count).to eq(1) + end end end end diff --git a/spec/models/budget_spec.rb b/spec/models/budget_spec.rb index 123035732..851d6076a 100644 --- a/spec/models/budget_spec.rb +++ b/spec/models/budget_spec.rb @@ -306,4 +306,46 @@ describe Budget do expect(budget.formatted_amount(1000.00)).to eq ("€1,000") end end + + describe "#milestone_tags" do + let(:investment1) { build(:budget_investment, :winner) } + let(:investment2) { build(:budget_investment, :winner) } + let(:investment3) { build(:budget_investment) } + + it "returns an empty array if not investments milestone_tags" do + budget.investments << investment1 + + expect(budget.milestone_tags).to eq([]) + end + + it "returns array of investments milestone_tags" do + investment1.milestone_tag_list = "tag1" + investment1.save + budget.investments << investment1 + + expect(budget.milestone_tags).to eq(["tag1"]) + end + + it "returns uniq list of investments milestone_tags" do + investment1.milestone_tag_list = "tag1" + investment1.save + investment2.milestone_tag_list = "tag1" + investment2.save + budget.investments << investment1 + budget.investments << investment2 + + expect(budget.milestone_tags).to eq(["tag1"]) + end + + it "returns tags only for winner investments" do + investment1.milestone_tag_list = "tag1" + investment1.save + investment3.milestone_tag_list = "tag2" + investment3.save + budget.investments << investment1 + budget.investments << investment3 + + expect(budget.milestone_tags).to eq(["tag1"]) + end + end end diff --git a/spec/models/legislation/process_spec.rb b/spec/models/legislation/process_spec.rb index c870a4049..0813b6064 100644 --- a/spec/models/legislation/process_spec.rb +++ b/spec/models/legislation/process_spec.rb @@ -194,4 +194,31 @@ describe Legislation::Process do end end + describe "milestone_tags" do + context "without milestone_tags" do + let(:process) {create(:legislation_process)} + + it "do not have milestone_tags" do + expect(process.milestone_tag_list).to eq([]) + expect(process.milestone_tags).to eq([]) + end + + it "add a new milestone_tag" do + process.milestone_tag_list = "tag1,tag2" + + expect(process.milestone_tag_list).to eq(["tag1", "tag2"]) + end + end + + context "with milestone_tags" do + + let(:process) {create(:legislation_process, :with_milestone_tags)} + + it "has milestone_tags" do + expect(process.milestone_tag_list.count).to eq(1) + end + end + + end + end diff --git a/spec/models/proposal_spec.rb b/spec/models/proposal_spec.rb index b55cbba9f..7d5d8067c 100644 --- a/spec/models/proposal_spec.rb +++ b/spec/models/proposal_spec.rb @@ -1084,4 +1084,32 @@ describe Proposal do end + describe "milestone_tags" do + + context "without milestone_tags" do + + let(:proposal) {create(:proposal)} + + it "do not have milestone_tags" do + expect(proposal.milestone_tag_list).to eq([]) + expect(proposal.milestone_tags).to eq([]) + end + + it "add a new milestone_tag" do + proposal.milestone_tag_list = "tag1,tag2" + + expect(proposal.milestone_tag_list).to eq(["tag1", "tag2"]) + end + end + + context "with milestone_tags" do + + let(:proposal) {create(:proposal, :with_milestone_tags)} + + it "has milestone_tags" do + expect(proposal.milestone_tag_list.count).to eq(1) + end + end + end + end