From 3d975f7b673a520280fba2400f443d326d59b08b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javi=20Mart=C3=ADn?= Date: Mon, 23 Nov 2020 20:51:51 +0100 Subject: [PATCH 01/12] Add basic SDG Management content section Note using `params[:relatable_type].classify` is recognized as a security risk by some tools. However, it's a false positive, since we've added constraints to the URL so that paramenter can only have the values we trust. --- app/assets/stylesheets/admin/menu.scss | 6 +- .../sdg_management/menu_component.rb | 25 ++++++- .../relations/index_component.html.erb | 23 ++++++ .../relations/index_component.rb | 19 +++++ .../sdg_management/relations_controller.rb | 11 +++ app/models/concerns/sdg/relatable.rb | 8 ++ .../sdg_management/relations/index.html.erb | 1 + config/locales/en/activerecord.yml | 1 + config/locales/en/sdg_management.yml | 5 ++ config/locales/es/activerecord.yml | 1 + config/locales/es/sdg_management.yml | 5 ++ config/routes/sdg_management.rb | 9 +++ spec/models/sdg/relatable_spec.rb | 16 ++++ spec/routing/sdg_management_routes_spec.rb | 32 ++++++++ spec/system/sdg_management/relations_spec.rb | 74 +++++++++++++++++++ 15 files changed, 233 insertions(+), 3 deletions(-) create mode 100644 app/components/sdg_management/relations/index_component.html.erb create mode 100644 app/components/sdg_management/relations/index_component.rb create mode 100644 app/controllers/sdg_management/relations_controller.rb create mode 100644 app/views/sdg_management/relations/index.html.erb create mode 100644 spec/routing/sdg_management_routes_spec.rb create mode 100644 spec/system/sdg_management/relations_spec.rb diff --git a/app/assets/stylesheets/admin/menu.scss b/app/assets/stylesheets/admin/menu.scss index f80f074cde71..a414614859a7 100644 --- a/app/assets/stylesheets/admin/menu.scss +++ b/app/assets/stylesheets/admin/menu.scss @@ -36,7 +36,8 @@ } &.budgets-link, - &.investments-link { + &.investments-link, + &.budget-investments-link { @include icon(chart-pie, solid); } @@ -56,7 +57,8 @@ @include icon(envelope, regular); } - &.legislation-link { + &.legislation-link, + &.legislation-processes-link { @include icon(file-alt, solid); } diff --git a/app/components/sdg_management/menu_component.rb b/app/components/sdg_management/menu_component.rb index e626b2736a07..0f719ab2b987 100644 --- a/app/components/sdg_management/menu_component.rb +++ b/app/components/sdg_management/menu_component.rb @@ -4,14 +4,37 @@ class SDGManagement::MenuComponent < ApplicationComponent private def links - [goals_link] + [goals_link, *relatable_links] end def goals_link [t("sdg_management.menu.sdg_content"), sdg_management_goals_path, sdg?, class: "goals-link"] end + def relatable_links + SDG::Related::RELATABLE_TYPES.map do |type| + [ + t("sdg_management.menu.#{table_name(type)}"), + relatable_type_path(type), + controller_name == "relations" && params[:relatable_type] == type.tableize, + class: "#{table_name(type).tr("_", "-")}-link" + ] + end + end + def sdg? %w[goals targets local_targets].include?(controller_name) end + + def relatable_type_path(type) + { + controller: "sdg_management/relations", + action: :index, + relatable_type: type.tableize + } + end + + def table_name(type) + type.constantize.table_name + end end diff --git a/app/components/sdg_management/relations/index_component.html.erb b/app/components/sdg_management/relations/index_component.html.erb new file mode 100644 index 000000000000..cf3592dc9f01 --- /dev/null +++ b/app/components/sdg_management/relations/index_component.html.erb @@ -0,0 +1,23 @@ +<%= header %> + + + + + + + + + + + + <% @records.each do |record| %> + + + + + + <% end %> + +
<%= model_class.human_attribute_name(:title) %><%= SDG::Goal.model_name.human(count: 2).upcase_first %><%= SDG::Target.model_name.human(count: 2).upcase_first %>
<%= record.title %><%= record.sdg_goal_list %><%= record.sdg_target_list %>
+ +<%= paginate(@records) %> diff --git a/app/components/sdg_management/relations/index_component.rb b/app/components/sdg_management/relations/index_component.rb new file mode 100644 index 000000000000..0aaed38d02f7 --- /dev/null +++ b/app/components/sdg_management/relations/index_component.rb @@ -0,0 +1,19 @@ +class SDGManagement::Relations::IndexComponent < ApplicationComponent + include Header + + attr_reader :records + + def initialize(records) + @records = records + end + + private + + def title + t("sdg_management.menu.#{model_class.table_name}") + end + + def model_class + records.model + end +end diff --git a/app/controllers/sdg_management/relations_controller.rb b/app/controllers/sdg_management/relations_controller.rb new file mode 100644 index 000000000000..f81ad8502d8b --- /dev/null +++ b/app/controllers/sdg_management/relations_controller.rb @@ -0,0 +1,11 @@ +class SDGManagement::RelationsController < SDGManagement::BaseController + def index + @records = relatable_class.accessible_by(current_ability).order(:id).page(params[:page]) + end + + private + + def relatable_class + params[:relatable_type].classify.constantize + end +end diff --git a/app/models/concerns/sdg/relatable.rb b/app/models/concerns/sdg/relatable.rb index 9da20b0d5368..64b75f672a15 100644 --- a/app/models/concerns/sdg/relatable.rb +++ b/app/models/concerns/sdg/relatable.rb @@ -15,4 +15,12 @@ module SDG::Relatable def related_sdgs sdg_relations.map(&:related_sdg) end + + def sdg_goal_list + sdg_goals.order(:code).map(&:code).join(", ") + end + + def sdg_target_list + sdg_targets.sort.map(&:code).join(", ") + end end diff --git a/app/views/sdg_management/relations/index.html.erb b/app/views/sdg_management/relations/index.html.erb new file mode 100644 index 000000000000..55878cd907ff --- /dev/null +++ b/app/views/sdg_management/relations/index.html.erb @@ -0,0 +1 @@ +<%= render SDGManagement::Relations::IndexComponent.new(@records) %> diff --git a/config/locales/en/activerecord.yml b/config/locales/en/activerecord.yml index bc561d09360f..01c79fc57c6b 100644 --- a/config/locales/en/activerecord.yml +++ b/config/locales/en/activerecord.yml @@ -287,6 +287,7 @@ en: responsible_name: "Person responsible for the group" poll: name: "Name" + title: "Name" starts_at: "Start Date" ends_at: "Closing Date" geozone_restricted: "Restricted by geozone" diff --git a/config/locales/en/sdg_management.yml b/config/locales/en/sdg_management.yml index 1e075fd76870..c39a444b10ff 100644 --- a/config/locales/en/sdg_management.yml +++ b/config/locales/en/sdg_management.yml @@ -3,6 +3,11 @@ en: header: title: "SDG content" menu: + budget_investments: "Participatory budgets" + debates: "Debates" + legislation_processes: "Collaborative legislation" + polls: "Polls" + proposals: "Proposals" sdg_content: "Goals and Targets" local_targets: create: diff --git a/config/locales/es/activerecord.yml b/config/locales/es/activerecord.yml index 1f505a6bca45..0c736934acd1 100644 --- a/config/locales/es/activerecord.yml +++ b/config/locales/es/activerecord.yml @@ -287,6 +287,7 @@ es: responsible_name: "Persona responsable del colectivo" poll: name: "Nombre" + title: "Nombre" starts_at: "Fecha de apertura" ends_at: "Fecha de cierre" geozone_restricted: "Restringida por zonas" diff --git a/config/locales/es/sdg_management.yml b/config/locales/es/sdg_management.yml index 4ee4d8893c61..ce517cf25aef 100644 --- a/config/locales/es/sdg_management.yml +++ b/config/locales/es/sdg_management.yml @@ -3,6 +3,11 @@ es: header: title: "Contenido ODS" menu: + budget_investments: "Presupuestos participativos" + debates: "Debates" + legislation_processes: "Legislación colaborativa" + polls: "Votaciones" + proposals: "Propuestas" sdg_content: "Objetivos y Metas" local_targets: create: diff --git a/config/routes/sdg_management.rb b/config/routes/sdg_management.rb index cad7cbe66de9..23a73d55d0b1 100644 --- a/config/routes/sdg_management.rb +++ b/config/routes/sdg_management.rb @@ -4,4 +4,13 @@ resources :goals, only: [:index] resources :targets, only: [:index] resources :local_targets, except: [:show] + + types = SDG::Related::RELATABLE_TYPES.map(&:tableize) + types_constraint = /#{types.join("|")}/ + + get "*relatable_type", to: "relations#index", as: "relations", relatable_type: types_constraint + + types.each do |type| + get type, to: "relations#index", as: type + end end diff --git a/spec/models/sdg/relatable_spec.rb b/spec/models/sdg/relatable_spec.rb index e6fd1b8d0c00..595ecad6104f 100644 --- a/spec/models/sdg/relatable_spec.rb +++ b/spec/models/sdg/relatable_spec.rb @@ -28,6 +28,14 @@ end end + describe "#sdg_goal_list" do + it "orders goals by code" do + relatable.sdg_goals = [SDG::Goal[1], SDG::Goal[3], SDG::Goal[2]] + + expect(relatable.sdg_goal_list).to eq "1, 2, 3" + end + end + describe "#sdg_targets" do it "can assign targets to a model" do relatable.sdg_targets = [target, another_target] @@ -46,6 +54,14 @@ end end + describe "#sdg_target_list" do + it "orders targets by code" do + relatable.sdg_targets = [SDG::Target[2.2], SDG::Target[1.2], SDG::Target[2.1]] + + expect(relatable.sdg_target_list).to eq "1.2, 2.1, 2.2" + end + end + describe "#sdg_local_targets" do it "can assign local targets to a model" do relatable.sdg_local_targets = [local_target, another_local_target] diff --git a/spec/routing/sdg_management_routes_spec.rb b/spec/routing/sdg_management_routes_spec.rb new file mode 100644 index 000000000000..c04748e47fa5 --- /dev/null +++ b/spec/routing/sdg_management_routes_spec.rb @@ -0,0 +1,32 @@ +require "rails_helper" + +describe "SDG Management routes" do + it "maps routes for relatable classes" do + expect(get("/sdg_management/proposals")).to route_to( + controller: "sdg_management/relations", + action: "index", + relatable_type: "proposals" + ) + end + + it "admits named routes" do + expect(get(sdg_management_polls_path)).to route_to( + controller: "sdg_management/relations", + action: "index", + relatable_type: "polls" + ) + end + + it "routes relatable types containing a slash" do + expect(url_for( + controller: "sdg_management/relations", + action: "index", + relatable_type: "legislation/processes", + only_path: true + )).to eq "/sdg_management/legislation/processes" + end + + it "does not accept non-relatable classes" do + expect(get("/sdg_management/tags")).not_to be_routable + end +end diff --git a/spec/system/sdg_management/relations_spec.rb b/spec/system/sdg_management/relations_spec.rb new file mode 100644 index 000000000000..94e479394560 --- /dev/null +++ b/spec/system/sdg_management/relations_spec.rb @@ -0,0 +1,74 @@ +require "rails_helper" + +describe "SDG Relations", :js do + before do + login_as(create(:administrator).user) + Setting["feature.sdg"] = true + end + + scenario "navigation" do + visit sdg_management_root_path + + within("#side_menu") { click_link "Participatory budgets" } + + expect(page).to have_current_path "/sdg_management/budget/investments" + expect(page).to have_css "h2", exact_text: "Participatory budgets" + + within("#side_menu") { click_link "Debates" } + + expect(page).to have_current_path "/sdg_management/debates" + expect(page).to have_css "h2", exact_text: "Debates" + + within("#side_menu") { click_link "Collaborative legislation" } + + expect(page).to have_current_path "/sdg_management/legislation/processes" + expect(page).to have_css "h2", exact_text: "Collaborative legislation" + + within("#side_menu") { click_link "Polls" } + + expect(page).to have_current_path "/sdg_management/polls" + expect(page).to have_css "h2", exact_text: "Polls" + + within("#side_menu") { click_link "Proposals" } + + expect(page).to have_current_path "/sdg_management/proposals" + expect(page).to have_css "h2", exact_text: "Proposals" + end + + describe "Index" do + scenario "list records for the current model" do + create(:debate, title: "I'm a debate") + create(:proposal, title: "And I'm a proposal") + + visit sdg_management_debates_path + + expect(page).to have_text "I'm a debate" + expect(page).not_to have_text "I'm a proposal" + + visit sdg_management_proposals_path + + expect(page).to have_text "I'm a proposal" + expect(page).not_to have_text "I'm a debate" + end + + scenario "list goals and target for all records" do + redistribute = create(:proposal, title: "Resources distribution") + redistribute.sdg_goals = [SDG::Goal[1]] + redistribute.sdg_targets = [SDG::Target["1.1"]] + + treatment = create(:proposal, title: "Treatment system") + treatment.sdg_goals = [SDG::Goal[6]] + treatment.sdg_targets = [SDG::Target["6.1"], SDG::Target["6.2"]] + + visit sdg_management_proposals_path + + within("tr", text: "Resources distribution") do + expect(page).to have_content "1.1" + end + + within("tr", text: "Treatment system") do + expect(page).to have_content "6.1, 6.2" + end + end + end +end From 562f324ba1336b04c1c03c3a30aef000e2a50a11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javi=20Mart=C3=ADn?= Date: Tue, 24 Nov 2020 17:48:38 +0100 Subject: [PATCH 02/12] Add link to an (empty) edit action --- .../relations/index_component.html.erb | 9 +++++++++ .../sdg_management/relations/index_component.rb | 9 +++++++++ .../sdg_management/relations_controller.rb | 4 ++++ app/views/sdg_management/relations/edit.html.erb | 1 + config/locales/en/sdg_management.yml | 2 ++ config/locales/es/sdg_management.yml | 2 ++ config/routes/sdg_management.rb | 2 ++ spec/system/sdg_management/relations_spec.rb | 12 ++++++++++++ 8 files changed, 41 insertions(+) create mode 100644 app/views/sdg_management/relations/edit.html.erb diff --git a/app/components/sdg_management/relations/index_component.html.erb b/app/components/sdg_management/relations/index_component.html.erb index cf3592dc9f01..7a9a1c5c0153 100644 --- a/app/components/sdg_management/relations/index_component.html.erb +++ b/app/components/sdg_management/relations/index_component.html.erb @@ -6,6 +6,7 @@ <%= model_class.human_attribute_name(:title) %> <%= SDG::Goal.model_name.human(count: 2).upcase_first %> <%= SDG::Target.model_name.human(count: 2).upcase_first %> + <%= t("admin.actions.actions") %> @@ -15,6 +16,14 @@ <%= record.title %> <%= record.sdg_goal_list %> <%= record.sdg_target_list %> + + <%= render Admin::TableActionsComponent.new( + record, + actions: [:edit], + edit_text: t("sdg_management.actions.edit"), + edit_path: edit_path_for(record) + ) %> + <% end %> diff --git a/app/components/sdg_management/relations/index_component.rb b/app/components/sdg_management/relations/index_component.rb index 0aaed38d02f7..8f561534b81d 100644 --- a/app/components/sdg_management/relations/index_component.rb +++ b/app/components/sdg_management/relations/index_component.rb @@ -16,4 +16,13 @@ def title def model_class records.model end + + def edit_path_for(record) + { + controller: "sdg_management/relations", + action: :edit, + relatable_type: record.class.name.tableize, + id: record + } + end end diff --git a/app/controllers/sdg_management/relations_controller.rb b/app/controllers/sdg_management/relations_controller.rb index f81ad8502d8b..a0edec379a45 100644 --- a/app/controllers/sdg_management/relations_controller.rb +++ b/app/controllers/sdg_management/relations_controller.rb @@ -3,6 +3,10 @@ def index @records = relatable_class.accessible_by(current_ability).order(:id).page(params[:page]) end + def edit + @record = relatable_class.find(params[:id]) + end + private def relatable_class diff --git a/app/views/sdg_management/relations/edit.html.erb b/app/views/sdg_management/relations/edit.html.erb new file mode 100644 index 000000000000..d916f0721f1b --- /dev/null +++ b/app/views/sdg_management/relations/edit.html.erb @@ -0,0 +1 @@ +

<%= @record.title %>

diff --git a/config/locales/en/sdg_management.yml b/config/locales/en/sdg_management.yml index c39a444b10ff..bacc84bbb5f5 100644 --- a/config/locales/en/sdg_management.yml +++ b/config/locales/en/sdg_management.yml @@ -1,5 +1,7 @@ en: sdg_management: + actions: + edit: "Manage goals and targets" header: title: "SDG content" menu: diff --git a/config/locales/es/sdg_management.yml b/config/locales/es/sdg_management.yml index ce517cf25aef..23c10bd4a73d 100644 --- a/config/locales/es/sdg_management.yml +++ b/config/locales/es/sdg_management.yml @@ -1,5 +1,7 @@ es: sdg_management: + actions: + edit: "Asignar objetivos y metas" header: title: "Contenido ODS" menu: diff --git a/config/routes/sdg_management.rb b/config/routes/sdg_management.rb index 23a73d55d0b1..5e856178486e 100644 --- a/config/routes/sdg_management.rb +++ b/config/routes/sdg_management.rb @@ -9,8 +9,10 @@ types_constraint = /#{types.join("|")}/ get "*relatable_type", to: "relations#index", as: "relations", relatable_type: types_constraint + get "*relatable_type/:id/edit", to: "relations#edit", as: "edit_relation", relatable_type: types_constraint types.each do |type| get type, to: "relations#index", as: type + get "#{type}/:id/edit", to: "relations#edit", as: "edit_#{type.singularize}" end end diff --git a/spec/system/sdg_management/relations_spec.rb b/spec/system/sdg_management/relations_spec.rb index 94e479394560..f4a405f9fd62 100644 --- a/spec/system/sdg_management/relations_spec.rb +++ b/spec/system/sdg_management/relations_spec.rb @@ -70,5 +70,17 @@ expect(page).to have_content "6.1, 6.2" end end + + scenario "shows link to edit a record" do + create(:budget_investment, title: "Build a hospital") + + visit sdg_management_budget_investments_path + + within("tr", text: "Build a hospital") do + click_link "Manage goals and targets" + end + + expect(page).to have_css "h2", exact_text: "Build a hospital" + end end end From a473c5ce6fabd1aaf04bcd2940793af9829f9b01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javi=20Mart=C3=ADn?= Date: Wed, 25 Nov 2020 12:02:03 +0100 Subject: [PATCH 03/12] Add form to assign targets to a record --- .../relations/edit_component.html.erb | 7 +++++ .../relations/edit_component.rb | 24 +++++++++++++++++ .../sdg_management/relations_controller.rb | 13 +++++++++- app/models/concerns/sdg/relatable.rb | 9 +++++++ .../sdg_management/relations/edit.html.erb | 2 +- config/locales/en/activerecord.yml | 1 + config/locales/es/activerecord.yml | 1 + config/routes/sdg_management.rb | 1 + spec/models/sdg/relatable_spec.rb | 26 +++++++++++++++++++ spec/system/sdg_management/relations_spec.rb | 15 +++++++++++ 10 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 app/components/sdg_management/relations/edit_component.html.erb create mode 100644 app/components/sdg_management/relations/edit_component.rb diff --git a/app/components/sdg_management/relations/edit_component.html.erb b/app/components/sdg_management/relations/edit_component.html.erb new file mode 100644 index 000000000000..807a4a2dc6f1 --- /dev/null +++ b/app/components/sdg_management/relations/edit_component.html.erb @@ -0,0 +1,7 @@ +<%= header %> + +<%= form_for record, url: update_path do |f| %> + <%= f.text_field :sdg_target_list %> + + <%= f.submit %> +<% end %> diff --git a/app/components/sdg_management/relations/edit_component.rb b/app/components/sdg_management/relations/edit_component.rb new file mode 100644 index 000000000000..b0dc3b7df6dc --- /dev/null +++ b/app/components/sdg_management/relations/edit_component.rb @@ -0,0 +1,24 @@ +class SDGManagement::Relations::EditComponent < ApplicationComponent + include Header + + attr_reader :record + + def initialize(record) + @record = record + end + + private + + def title + @record.title + end + + def update_path + { + controller: "sdg_management/relations", + action: :update, + relatable_type: record.class.name.tableize, + id: record + } + end +end diff --git a/app/controllers/sdg_management/relations_controller.rb b/app/controllers/sdg_management/relations_controller.rb index a0edec379a45..4f6ab77e6b50 100644 --- a/app/controllers/sdg_management/relations_controller.rb +++ b/app/controllers/sdg_management/relations_controller.rb @@ -1,14 +1,25 @@ class SDGManagement::RelationsController < SDGManagement::BaseController + before_action :load_record, only: [:edit, :update] + def index @records = relatable_class.accessible_by(current_ability).order(:id).page(params[:page]) end def edit - @record = relatable_class.find(params[:id]) + end + + def update + @record.sdg_target_list = params[@record.class.table_name.singularize][:sdg_target_list] + + redirect_to action: :index end private + def load_record + @record = relatable_class.find(params[:id]) + end + def relatable_class params[:relatable_type].classify.constantize end diff --git a/app/models/concerns/sdg/relatable.rb b/app/models/concerns/sdg/relatable.rb index 64b75f672a15..50f9b018369e 100644 --- a/app/models/concerns/sdg/relatable.rb +++ b/app/models/concerns/sdg/relatable.rb @@ -23,4 +23,13 @@ def sdg_goal_list def sdg_target_list sdg_targets.sort.map(&:code).join(", ") end + + def sdg_target_list=(codes) + targets = codes.tr(" ", "").split(",").map { |code| SDG::Target[code] } + + transaction do + self.sdg_targets = targets + self.sdg_goals = targets.map(&:goal).uniq + end + end end diff --git a/app/views/sdg_management/relations/edit.html.erb b/app/views/sdg_management/relations/edit.html.erb index d916f0721f1b..4092359d7eb7 100644 --- a/app/views/sdg_management/relations/edit.html.erb +++ b/app/views/sdg_management/relations/edit.html.erb @@ -1 +1 @@ -

<%= @record.title %>

+<%= render SDGManagement::Relations::EditComponent.new(@record) %> diff --git a/config/locales/en/activerecord.yml b/config/locales/en/activerecord.yml index 01c79fc57c6b..84264fbceedc 100644 --- a/config/locales/en/activerecord.yml +++ b/config/locales/en/activerecord.yml @@ -2,6 +2,7 @@ en: attributes: geozone_id: "Scope of operation" results_enabled: "Show results" + sdg_target_list: "Targets" stats_enabled: "Show stats" advanced_stats_enabled: "Show advanced stats" name: Name diff --git a/config/locales/es/activerecord.yml b/config/locales/es/activerecord.yml index 0c736934acd1..d59c643c4070 100644 --- a/config/locales/es/activerecord.yml +++ b/config/locales/es/activerecord.yml @@ -2,6 +2,7 @@ es: attributes: geozone_id: "Ámbito de actuación" results_enabled: "Mostrar resultados" + sdg_target_list: "Metas" stats_enabled: "Mostrar estadísticas" advanced_stats_enabled: "Mostrar estadísticas avanzadas" name: Nombre diff --git a/config/routes/sdg_management.rb b/config/routes/sdg_management.rb index 5e856178486e..6f303b45c4cd 100644 --- a/config/routes/sdg_management.rb +++ b/config/routes/sdg_management.rb @@ -10,6 +10,7 @@ get "*relatable_type", to: "relations#index", as: "relations", relatable_type: types_constraint get "*relatable_type/:id/edit", to: "relations#edit", as: "edit_relation", relatable_type: types_constraint + patch "*relatable_type/:id", to: "relations#update", as: "relation", relatable_type: types_constraint types.each do |type| get type, to: "relations#index", as: type diff --git a/spec/models/sdg/relatable_spec.rb b/spec/models/sdg/relatable_spec.rb index 595ecad6104f..d161d4ddb22b 100644 --- a/spec/models/sdg/relatable_spec.rb +++ b/spec/models/sdg/relatable_spec.rb @@ -90,4 +90,30 @@ expect(relatable.reload.related_sdgs).to match_array related_sdgs end end + + describe "#sdg_target_list=" do + it "assigns a single target" do + relatable.sdg_target_list = "1.1" + + expect(relatable.reload.sdg_targets).to match_array [SDG::Target["1.1"]] + end + + it "assigns multiple targets" do + relatable.sdg_target_list = "1.1,2.3" + + expect(relatable.reload.sdg_targets).to match_array [SDG::Target["1.1"], SDG::Target["2.3"]] + end + + it "ignores trailing spaces and spaces between commas" do + relatable.sdg_target_list = " 1.1, 2.3 " + + expect(relatable.reload.sdg_targets).to match_array [SDG::Target["1.1"], SDG::Target["2.3"]] + end + + it "assigns goals" do + relatable.sdg_target_list = "1.1,1.2,2.3" + + expect(relatable.reload.sdg_goals).to match_array [SDG::Goal[1], SDG::Goal[2]] + end + end end diff --git a/spec/system/sdg_management/relations_spec.rb b/spec/system/sdg_management/relations_spec.rb index f4a405f9fd62..af598c6c04d9 100644 --- a/spec/system/sdg_management/relations_spec.rb +++ b/spec/system/sdg_management/relations_spec.rb @@ -83,4 +83,19 @@ expect(page).to have_css "h2", exact_text: "Build a hospital" end end + + describe "Edit" do + scenario "allows changing the targets" do + process = create(:legislation_process, title: "SDG process") + process.sdg_targets = [SDG::Target["3.3"]] + + visit sdg_management_edit_legislation_process_path(process) + fill_in "Targets", with: "1.2, 2.1" + click_button "Update Process" + + within("tr", text: "SDG process") do + expect(page).to have_css "td", exact_text: "1.2, 2.1" + end + end + end end From 26c255a785bd309fcb8430c410f3caa9a5a60659 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javi=20Mart=C3=ADn?= Date: Wed, 2 Dec 2020 20:16:48 +0100 Subject: [PATCH 04/12] Disable SDG sections when settings are disabled --- .../sdg_management/menu_component.rb | 12 +++ .../sdg_management/relations_controller.rb | 9 ++ .../sdg_management/menu_component_spec.rb | 92 +++++++++++++++++ .../sdg_management/relations_spec.rb | 98 +++++++++++++++++++ spec/system/sdg_management/relations_spec.rb | 5 + 5 files changed, 216 insertions(+) create mode 100644 spec/components/sdg_management/menu_component_spec.rb create mode 100644 spec/controllers/sdg_management/relations_spec.rb diff --git a/app/components/sdg_management/menu_component.rb b/app/components/sdg_management/menu_component.rb index 0f719ab2b987..53e27c231a75 100644 --- a/app/components/sdg_management/menu_component.rb +++ b/app/components/sdg_management/menu_component.rb @@ -13,6 +13,8 @@ def goals_link def relatable_links SDG::Related::RELATABLE_TYPES.map do |type| + next unless setting["process.#{process_name(type)}"] && setting["sdg.process.#{process_name(type)}"] + [ t("sdg_management.menu.#{table_name(type)}"), relatable_type_path(type), @@ -37,4 +39,14 @@ def relatable_type_path(type) def table_name(type) type.constantize.table_name end + + def process_name(type) + process_name = type.split("::").first + + if process_name == "Legislation" + "legislation" + else + process_name.constantize.table_name + end + end end diff --git a/app/controllers/sdg_management/relations_controller.rb b/app/controllers/sdg_management/relations_controller.rb index 4f6ab77e6b50..8f29248649f4 100644 --- a/app/controllers/sdg_management/relations_controller.rb +++ b/app/controllers/sdg_management/relations_controller.rb @@ -1,4 +1,5 @@ class SDGManagement::RelationsController < SDGManagement::BaseController + before_action :check_feature_flags before_action :load_record, only: [:edit, :update] def index @@ -23,4 +24,12 @@ def load_record def relatable_class params[:relatable_type].classify.constantize end + + def check_feature_flags + process_name = params[:relatable_type].split("/").first + process_name = process_name.pluralize unless process_name == "legislation" + + check_feature_flag(process_name) + raise FeatureDisabled, process_name unless Setting["sdg.process.#{process_name}"] + end end diff --git a/spec/components/sdg_management/menu_component_spec.rb b/spec/components/sdg_management/menu_component_spec.rb new file mode 100644 index 000000000000..10547e4dd320 --- /dev/null +++ b/spec/components/sdg_management/menu_component_spec.rb @@ -0,0 +1,92 @@ +require "rails_helper" + +describe SDGManagement::MenuComponent, type: :component do + let(:component) { SDGManagement::MenuComponent.new } + + before do + Setting["sdg.process.budgets"] = true + Setting["sdg.process.debates"] = true + Setting["sdg.process.legislation"] = true + Setting["sdg.process.polls"] = true + Setting["sdg.process.proposals"] = true + end + + context "processes enabled" do + it "generates links to all processes" do + render_inline component + + expect(page).to have_link "Goals and Targets" + expect(page).to have_link "Participatory budgets" + expect(page).to have_link "Debates" + expect(page).to have_link "Collaborative legislation" + expect(page).to have_link "Polls" + expect(page).to have_link "Proposals" + end + end + + context "processes disabled" do + before do + Setting["process.budgets"] = false + Setting["process.debates"] = false + Setting["process.legislation"] = false + Setting["process.polls"] = false + Setting["process.proposals"] = false + end + + it "does not generate links to any processes" do + render_inline component + + expect(page).to have_css "a", count: 1 + expect(page).to have_link "Goals and Targets" + end + end + + context "SDG processes disabled" do + before do + Setting["sdg.process.budgets"] = false + Setting["sdg.process.debates"] = false + Setting["sdg.process.legislation"] = false + Setting["sdg.process.polls"] = false + Setting["sdg.process.proposals"] = false + end + + it "does not generate links to any processes" do + render_inline component + + expect(page).to have_css "a", count: 1 + expect(page).to have_link "Goals and Targets" + end + end + + context "one process disabled" do + before { Setting["process.debates"] = false } + + it "generates links to the enabled processes" do + render_inline component + + expect(page).to have_link "Goals and Targets" + expect(page).to have_link "Participatory budgets" + expect(page).to have_link "Collaborative legislation" + expect(page).to have_link "Polls" + expect(page).to have_link "Proposals" + + expect(page).not_to have_link "Debates" + end + end + + context "one SDG process disabled" do + before { Setting["sdg.process.legislation"] = false } + + it "generates links to the enabled processes" do + render_inline component + + expect(page).to have_link "Goals and Targets" + expect(page).to have_link "Debates" + expect(page).to have_link "Participatory budgets" + expect(page).to have_link "Polls" + expect(page).to have_link "Proposals" + + expect(page).not_to have_link "Collaborative legislation" + end + end +end diff --git a/spec/controllers/sdg_management/relations_spec.rb b/spec/controllers/sdg_management/relations_spec.rb new file mode 100644 index 000000000000..fc5e8ecb7d03 --- /dev/null +++ b/spec/controllers/sdg_management/relations_spec.rb @@ -0,0 +1,98 @@ +require "rails_helper" + +describe SDGManagement::RelationsController do + before do + sign_in create(:administrator).user + + Setting["feature.sdg"] = true + Setting["sdg.process.budgets"] = true + Setting["sdg.process.debates"] = true + Setting["sdg.process.legislation"] = true + Setting["sdg.process.polls"] = true + Setting["sdg.process.proposals"] = true + end + + context "processes disabled" do + it "raises feature disabled for budgets" do + Setting["process.budgets"] = false + + expect do + get :index, params: { relatable_type: "budget/investments" } + end.to raise_exception(FeatureFlags::FeatureDisabled) + end + + it "raises feature disabled for debates" do + Setting["process.debates"] = false + + expect do + get :index, params: { relatable_type: "debates" } + end.to raise_exception(FeatureFlags::FeatureDisabled) + end + + it "raises feature disabled for legislation processes" do + Setting["process.legislation"] = false + + expect do + get :index, params: { relatable_type: "legislation/processes" } + end.to raise_exception(FeatureFlags::FeatureDisabled) + end + + it "raises feature disabled for polls" do + Setting["process.polls"] = false + + expect do + get :index, params: { relatable_type: "polls" } + end.to raise_exception(FeatureFlags::FeatureDisabled) + end + + it "raises feature disabled for proposals" do + Setting["process.proposals"] = false + + expect do + get :index, params: { relatable_type: "proposals" } + end.to raise_exception(FeatureFlags::FeatureDisabled) + end + end + + context "SDG processes disabled" do + it "raises feature disabled for budgets" do + Setting["sdg.process.budgets"] = false + + expect do + get :index, params: { relatable_type: "budget/investments" } + end.to raise_exception(FeatureFlags::FeatureDisabled) + end + + it "raises feature disabled for debates" do + Setting["sdg.process.debates"] = false + + expect do + get :index, params: { relatable_type: "debates" } + end.to raise_exception(FeatureFlags::FeatureDisabled) + end + + it "raises feature disabled for legislation processes" do + Setting["sdg.process.legislation"] = false + + expect do + get :index, params: { relatable_type: "legislation/processes" } + end.to raise_exception(FeatureFlags::FeatureDisabled) + end + + it "raises feature disabled for polls" do + Setting["sdg.process.polls"] = false + + expect do + get :index, params: { relatable_type: "polls" } + end.to raise_exception(FeatureFlags::FeatureDisabled) + end + + it "raises feature disabled for proposals" do + Setting["sdg.process.proposals"] = false + + expect do + get :index, params: { relatable_type: "proposals" } + end.to raise_exception(FeatureFlags::FeatureDisabled) + end + end +end diff --git a/spec/system/sdg_management/relations_spec.rb b/spec/system/sdg_management/relations_spec.rb index af598c6c04d9..4b477ffdb5e5 100644 --- a/spec/system/sdg_management/relations_spec.rb +++ b/spec/system/sdg_management/relations_spec.rb @@ -4,6 +4,11 @@ before do login_as(create(:administrator).user) Setting["feature.sdg"] = true + Setting["sdg.process.budgets"] = true + Setting["sdg.process.debates"] = true + Setting["sdg.process.legislation"] = true + Setting["sdg.process.polls"] = true + Setting["sdg.process.proposals"] = true end scenario "navigation" do From b0f7567a30342d9c04ba5b2238b22ef02482c230 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javi=20Mart=C3=ADn?= Date: Wed, 16 Dec 2020 13:08:07 +0100 Subject: [PATCH 05/12] Rename spec file so it uses the source file path --- spec/lib/tasks/{load_sdg_spec.rb => db_spec.rb} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename spec/lib/tasks/{load_sdg_spec.rb => db_spec.rb} (100%) diff --git a/spec/lib/tasks/load_sdg_spec.rb b/spec/lib/tasks/db_spec.rb similarity index 100% rename from spec/lib/tasks/load_sdg_spec.rb rename to spec/lib/tasks/db_spec.rb From d01803e173f91ff989415d837e269b1d47f4ecfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javi=20Mart=C3=ADn?= Date: Wed, 16 Dec 2020 14:24:24 +0100 Subject: [PATCH 06/12] Add search method to polls So far the method does not take questions nor answers into account. This way we'll be able to search polls in the SDG Management section. --- app/models/poll.rb | 17 +++++++++++++ db/migrate/20201216132234_add_tsv_to_polls.rb | 5 ++++ db/schema.rb | 3 ++- lib/tasks/consul.rake | 3 ++- lib/tasks/db.rake | 5 ++++ spec/lib/tasks/db_spec.rb | 19 ++++++++++++++ spec/models/poll/poll_spec.rb | 25 +++++++++++++++++++ 7 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20201216132234_add_tsv_to_polls.rb diff --git a/app/models/poll.rb b/app/models/poll.rb index 0289a0fc2500..08e11f854a40 100644 --- a/app/models/poll.rb +++ b/app/models/poll.rb @@ -5,6 +5,7 @@ class Poll < ApplicationRecord acts_as_paranoid column: :hidden_at include ActsAsParanoidAliases include Notifiable + include Searchable include Sluggable include StatsVersionable include Reportable @@ -175,4 +176,20 @@ def answer_count def budget_poll? budget.present? end + + def searchable_translations_definitions + { + name => "A", + summary => "C", + description => "D" + } + end + + def searchable_values + searchable_globalized_values + end + + def self.search(terms) + pg_search(terms) + end end diff --git a/db/migrate/20201216132234_add_tsv_to_polls.rb b/db/migrate/20201216132234_add_tsv_to_polls.rb new file mode 100644 index 000000000000..a629bf975046 --- /dev/null +++ b/db/migrate/20201216132234_add_tsv_to_polls.rb @@ -0,0 +1,5 @@ +class AddTsvToPolls < ActiveRecord::Migration[5.2] + def change + add_column :polls, :tsv, :tsvector + end +end diff --git a/db/schema.rb b/db/schema.rb index 5f69e9ac43f1..58023c618d01 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2020_11_24_145559) do +ActiveRecord::Schema.define(version: 2020_12_16_132234) do # These are extensions that must be enabled in order to support this database enable_extension "pg_trgm" @@ -1166,6 +1166,7 @@ t.integer "budget_id" t.string "related_type" t.integer "related_id" + t.tsvector "tsv" t.index ["budget_id"], name: "index_polls_on_budget_id", unique: true t.index ["related_type", "related_id"], name: "index_polls_on_related_type_and_related_id" t.index ["starts_at", "ends_at"], name: "index_polls_on_starts_at_and_ends_at" diff --git a/lib/tasks/consul.rake b/lib/tasks/consul.rake index 911460c68607..4f169c387a50 100644 --- a/lib/tasks/consul.rake +++ b/lib/tasks/consul.rake @@ -6,6 +6,7 @@ namespace :consul do desc "Runs tasks needed to upgrade from 1.2.0 to 1.3.0" task "execute_release_1.3.0_tasks": [ - "db:load_sdg" + "db:load_sdg", + "db:calculate_tsv" ] end diff --git a/lib/tasks/db.rake b/lib/tasks/db.rake index a64643adc518..6413fc976d6f 100644 --- a/lib/tasks/db.rake +++ b/lib/tasks/db.rake @@ -10,4 +10,9 @@ namespace :db do ApplicationLogger.new.info "Adding Sustainable Development Goals content" load(Rails.root.join("db", "sdg.rb")) end + + desc "Calculates the TSV column for all polls" + task calculate_tsv: :environment do + Poll.find_each(&:calculate_tsvector) + end end diff --git a/spec/lib/tasks/db_spec.rb b/spec/lib/tasks/db_spec.rb index 6581ba7a5bfe..d4a725d29e8f 100644 --- a/spec/lib/tasks/db_spec.rb +++ b/spec/lib/tasks/db_spec.rb @@ -32,3 +32,22 @@ expect(SDG::Target.last.id).to eq target_id end end + +describe "rake db:calculate_tsv" do + before { Rake::Task["db:calculate_tsv"].reenable } + + let :run_rake_task do + Rake.application.invoke_task("db:calculate_tsv") + end + + it "calculates the tsvector for polls" do + poll = create(:poll) + poll.update_column(:tsv, nil) + + expect(poll.reload.tsv).to be nil + + run_rake_task + + expect(poll.reload.tsv).not_to be nil + end +end diff --git a/spec/models/poll/poll_spec.rb b/spec/models/poll/poll_spec.rb index 0a1fc94bdcc3..1223e53d62fe 100644 --- a/spec/models/poll/poll_spec.rb +++ b/spec/models/poll/poll_spec.rb @@ -433,4 +433,29 @@ expect(poll.recounts_confirmed?).to be true end end + + describe ".search" do + let!(:square) do + create(:poll, name: "Square reform", summary: "Next to the park", description: "Give it more space") + end + + let!(:park) do + create(:poll, name: "New park", summary: "Green spaces", description: "Next to the square") + end + + it "returns only matching polls" do + expect(Poll.search("reform")).to eq [square] + expect(Poll.search("green")).to eq [park] + expect(Poll.search("nothing here")).to be_empty + end + + it "gives more weight to name" do + expect(Poll.search("square")).to eq [square, park] + expect(Poll.search("park")).to eq [park, square] + end + + it "gives more weight to summary than description" do + expect(Poll.search("space")).to eq [park, square] + end + end end From b4316ff5934f3ab70ce64c0f49df73a54fbbbbda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javi=20Mart=C3=ADn?= Date: Wed, 16 Dec 2020 14:36:23 +0100 Subject: [PATCH 07/12] Add search method to legislation processes This way we'll be able to search processes in the SDG Management section. --- app/models/legislation/process.rb | 17 ++++++++++ ...132642_add_tsv_to_legislation_processes.rb | 5 +++ db/schema.rb | 3 +- lib/tasks/db.rake | 3 +- spec/lib/tasks/db_spec.rb | 11 +++++++ spec/models/legislation/process_spec.rb | 31 +++++++++++++++++++ 6 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20201216132642_add_tsv_to_legislation_processes.rb diff --git a/app/models/legislation/process.rb b/app/models/legislation/process.rb index f1a2e12c14ae..abf8875a7152 100644 --- a/app/models/legislation/process.rb +++ b/app/models/legislation/process.rb @@ -5,6 +5,7 @@ class Legislation::Process < ApplicationRecord include Imageable include Documentable include SDG::Relatable + include Searchable acts_as_paranoid column: :hidden_at acts_as_taggable_on :customs @@ -123,6 +124,22 @@ def status end end + def searchable_translations_definitions + { + title => "A", + summary => "C", + description => "D" + } + end + + def searchable_values + searchable_globalized_values + end + + def self.search(terms) + pg_search(terms) + end + private def valid_date_ranges diff --git a/db/migrate/20201216132642_add_tsv_to_legislation_processes.rb b/db/migrate/20201216132642_add_tsv_to_legislation_processes.rb new file mode 100644 index 000000000000..fdeb8fdd5a1c --- /dev/null +++ b/db/migrate/20201216132642_add_tsv_to_legislation_processes.rb @@ -0,0 +1,5 @@ +class AddTsvToLegislationProcesses < ActiveRecord::Migration[5.2] + def change + add_column :legislation_processes, :tsv, :tsvector + end +end diff --git a/db/schema.rb b/db/schema.rb index 58023c618d01..6a67f083fb4a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2020_12_16_132234) do +ActiveRecord::Schema.define(version: 2020_12_16_132642) do # These are extensions that must be enabled in order to support this database enable_extension "pg_trgm" @@ -747,6 +747,7 @@ t.boolean "homepage_enabled", default: false t.text "background_color" t.text "font_color" + t.tsvector "tsv" t.index ["allegations_end_date"], name: "index_legislation_processes_on_allegations_end_date" t.index ["allegations_start_date"], name: "index_legislation_processes_on_allegations_start_date" t.index ["debate_end_date"], name: "index_legislation_processes_on_debate_end_date" diff --git a/lib/tasks/db.rake b/lib/tasks/db.rake index 6413fc976d6f..622f9e9c541d 100644 --- a/lib/tasks/db.rake +++ b/lib/tasks/db.rake @@ -11,8 +11,9 @@ namespace :db do load(Rails.root.join("db", "sdg.rb")) end - desc "Calculates the TSV column for all polls" + desc "Calculates the TSV column for all polls and legislation processes" task calculate_tsv: :environment do Poll.find_each(&:calculate_tsvector) + Legislation::Process.find_each(&:calculate_tsvector) end end diff --git a/spec/lib/tasks/db_spec.rb b/spec/lib/tasks/db_spec.rb index d4a725d29e8f..b556b41b4db7 100644 --- a/spec/lib/tasks/db_spec.rb +++ b/spec/lib/tasks/db_spec.rb @@ -50,4 +50,15 @@ expect(poll.reload.tsv).not_to be nil end + + it "calculates the tsvector for legislation processes" do + process = create(:legislation_process) + process.update_column(:tsv, nil) + + expect(process.reload.tsv).to be nil + + run_rake_task + + expect(process.reload.tsv).not_to be nil + end end diff --git a/spec/models/legislation/process_spec.rb b/spec/models/legislation/process_spec.rb index 0ea89c71577d..5302497a07d5 100644 --- a/spec/models/legislation/process_spec.rb +++ b/spec/models/legislation/process_spec.rb @@ -229,4 +229,35 @@ end end end + + describe ".search" do + let!(:traffic) do + create(:legislation_process, + title: "Traffic regulation", + summary: "Lane structure", + description: "From top to bottom") + end + + let!(:animal_farm) do + create(:legislation_process, + title: "Hierarchy structure", + summary: "Pigs at the top", + description: "Napoleon in charge of the traffic") + end + + it "returns only matching polls" do + expect(Legislation::Process.search("lane")).to eq [traffic] + expect(Legislation::Process.search("pigs")).to eq [animal_farm] + expect(Legislation::Process.search("nothing here")).to be_empty + end + + it "gives more weight to name" do + expect(Legislation::Process.search("traffic")).to eq [traffic, animal_farm] + expect(Legislation::Process.search("structure")).to eq [animal_farm, traffic] + end + + it "gives more weight to summary than description" do + expect(Legislation::Process.search("top")).to eq [animal_farm, traffic] + end + end end From 987f1d5533a6b6e42d1cdeb67b78f2ff3dac581b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javi=20Mart=C3=ADn?= Date: Wed, 16 Dec 2020 17:14:07 +0100 Subject: [PATCH 08/12] Group admin search label translations together --- app/views/admin/debates/index.html.erb | 2 +- .../booth_assignments/_search_booths.html.erb | 2 +- app/views/admin/poll/booths/index.html.erb | 2 +- .../_search_officers.html.erb | 2 +- .../admin/poll/questions/_search.html.erb | 2 +- app/views/admin/proposals/index.html.erb | 2 +- app/views/admin/shared/_user_search.html.erb | 2 +- app/views/management/proposals/index.html.erb | 2 +- config/locales/en/admin.yml | 19 +++++++------------ config/locales/es/admin.yml | 19 +++++++------------ 10 files changed, 22 insertions(+), 32 deletions(-) diff --git a/app/views/admin/debates/index.html.erb b/app/views/admin/debates/index.html.erb index 7a743aea15c9..0d3afd507be4 100644 --- a/app/views/admin/debates/index.html.erb +++ b/app/views/admin/debates/index.html.erb @@ -5,7 +5,7 @@

<%= t("admin.debates.index.title") %>

<% if @debates.any? %> - <%= render Admin::SearchComponent.new(label: t("admin.shared.debate_search.placeholder")) %> + <%= render Admin::SearchComponent.new(label: t("admin.shared.search.label.debates")) %>

<%= page_entries_info @debates %>

diff --git a/app/views/admin/poll/booth_assignments/_search_booths.html.erb b/app/views/admin/poll/booth_assignments/_search_booths.html.erb index adf7ed00d8a5..bbed99f43850 100644 --- a/app/views/admin/poll/booth_assignments/_search_booths.html.erb +++ b/app/views/admin/poll/booth_assignments/_search_booths.html.erb @@ -3,7 +3,7 @@
<%= text_field_tag :search, @search, - placeholder: t("admin.shared.booths_search.placeholder"), id: "search-booths" %> + placeholder: t("admin.shared.search.label.booths"), id: "search-booths" %>
<%= submit_tag t("admin.shared.search.search"), class: "button" %>
diff --git a/app/views/admin/poll/booths/index.html.erb b/app/views/admin/poll/booths/index.html.erb index 4954801a2899..37937d89e685 100644 --- a/app/views/admin/poll/booths/index.html.erb +++ b/app/views/admin/poll/booths/index.html.erb @@ -4,7 +4,7 @@ <%= link_to t("admin.booths.index.add_booth"), new_admin_booth_path, class: "button float-right" %> <% end %> -<%= render Admin::SearchComponent.new(label: t("admin.shared.booths_search.placeholder")) %> +<%= render Admin::SearchComponent.new(label: t("admin.shared.search.label.booths")) %> <% if @booths.empty? %>
diff --git a/app/views/admin/poll/officer_assignments/_search_officers.html.erb b/app/views/admin/poll/officer_assignments/_search_officers.html.erb index b1602dc7e2ab..69f7e2b107ed 100644 --- a/app/views/admin/poll/officer_assignments/_search_officers.html.erb +++ b/app/views/admin/poll/officer_assignments/_search_officers.html.erb @@ -3,7 +3,7 @@
<%= text_field_tag :search, @search, - placeholder: t("admin.shared.poll_officers_search.placeholder"), id: "search-officers" %> + placeholder: t("admin.shared.search.label.poll_officers"), id: "search-officers" %>
<%= submit_tag t("admin.shared.search.search"), class: "button" %>
diff --git a/app/views/admin/poll/questions/_search.html.erb b/app/views/admin/poll/questions/_search.html.erb index 73730dcd62d8..6274fbea1aa9 100644 --- a/app/views/admin/poll/questions/_search.html.erb +++ b/app/views/admin/poll/questions/_search.html.erb @@ -1 +1 @@ -<%= render Admin::SearchComponent.new(label: t("admin.shared.poll_questions_search.placeholder")) %> +<%= render Admin::SearchComponent.new(label: t("admin.shared.search.label.poll_questions")) %> diff --git a/app/views/admin/proposals/index.html.erb b/app/views/admin/proposals/index.html.erb index 075e702789a7..2c62d0c25592 100644 --- a/app/views/admin/proposals/index.html.erb +++ b/app/views/admin/proposals/index.html.erb @@ -5,7 +5,7 @@

<%= t("admin.proposals.index.title") %>

<% if @proposals.any? %> - <%= render Admin::SearchComponent.new(label: t("admin.shared.proposal_search.placeholder")) %> + <%= render Admin::SearchComponent.new(label: t("admin.shared.search.label.proposals")) %>

<%= page_entries_info @proposals %>

diff --git a/app/views/admin/shared/_user_search.html.erb b/app/views/admin/shared/_user_search.html.erb index 2023fd0edec6..5a074eb1e68d 100644 --- a/app/views/admin/shared/_user_search.html.erb +++ b/app/views/admin/shared/_user_search.html.erb @@ -1 +1 @@ -<%= render Admin::SearchComponent.new(url: url, label: t("admin.shared.user_search.placeholder")) %> +<%= render Admin::SearchComponent.new(url: url, label: t("admin.shared.search.label.users")) %> diff --git a/app/views/management/proposals/index.html.erb b/app/views/management/proposals/index.html.erb index a5862961e244..549fc8f4849c 100644 --- a/app/views/management/proposals/index.html.erb +++ b/app/views/management/proposals/index.html.erb @@ -1,7 +1,7 @@

<%= t("management.proposals.index.title") %>

- <%= render Admin::SearchComponent.new(label: t("admin.shared.proposal_search.placeholder")) %> + <%= render Admin::SearchComponent.new(label: t("admin.shared.search.label.proposals")) %>
diff --git a/config/locales/en/admin.yml b/config/locales/en/admin.yml index df6d749902af..baeb2874d577 100644 --- a/config/locales/en/admin.yml +++ b/config/locales/en/admin.yml @@ -1254,19 +1254,14 @@ en: true_value: "Yes" false_value: "No" search: + label: + booths: "Search booth by name or location" + debates: "Search debates by title or description" + poll_officers: "Search poll officers" + poll_questions: "Search poll questions" + proposals: "Search proposals by title, code, description or question" + users: "Search user by name or email" search: "Search" - booths_search: - placeholder: Search booth by name or location - poll_officers_search: - placeholder: Search poll officers - poll_questions_search: - placeholder: Search poll questions - proposal_search: - placeholder: Search proposals by title, code, description or question - debate_search: - placeholder: Search debates by title or description - user_search: - placeholder: Search user by name or email search_results: "Search results" no_search_results: "No results found." actions: Actions diff --git a/config/locales/es/admin.yml b/config/locales/es/admin.yml index 81ecc1259d5c..dd8e7c8201f7 100644 --- a/config/locales/es/admin.yml +++ b/config/locales/es/admin.yml @@ -1253,19 +1253,14 @@ es: true_value: "Sí" false_value: "No" search: + label: + booths: "Buscar urna por nombre" + debates: "Buscar debates por título o descripción" + poll_officers: "Buscar presidentes de mesa" + poll_questions: "Buscar preguntas" + proposals: "Buscar propuestas por título, código, descripción o pregunta" + users: "Buscar usuario por nombre o email" search: "Buscar" - booths_search: - placeholder: Buscar urna por nombre - poll_officers_search: - placeholder: Buscar presidentes de mesa - poll_questions_search: - placeholder: Buscar preguntas - proposal_search: - placeholder: Buscar propuestas por título, código, descripción o pregunta - debate_search: - placeholder: Buscar debates por título o descripción - user_search: - placeholder: Buscar usuario por nombre o email search_results: "Resultados de la búsqueda" no_search_results: "No se han encontrado resultados." actions: Acciones From 52f7dd56e0d51581bf5a825f7968f1074e7fe320 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javi=20Mart=C3=ADn?= Date: Wed, 16 Dec 2020 17:25:01 +0100 Subject: [PATCH 09/12] Add search to SDG managed content --- .../relations/index_component.html.erb | 2 ++ .../sdg_management/relations/index_component.rb | 4 ++++ .../sdg_management/relations_controller.rb | 2 ++ config/locales/en/admin.yml | 3 +++ config/locales/es/admin.yml | 3 +++ spec/system/sdg_management/relations_spec.rb | 13 +++++++++++++ 6 files changed, 27 insertions(+) diff --git a/app/components/sdg_management/relations/index_component.html.erb b/app/components/sdg_management/relations/index_component.html.erb index 7a9a1c5c0153..43ba44b12626 100644 --- a/app/components/sdg_management/relations/index_component.html.erb +++ b/app/components/sdg_management/relations/index_component.html.erb @@ -1,5 +1,7 @@ <%= header %> +<%= render Admin::SearchComponent.new(label: search_label) %> + diff --git a/app/components/sdg_management/relations/index_component.rb b/app/components/sdg_management/relations/index_component.rb index 8f561534b81d..16a49194c87d 100644 --- a/app/components/sdg_management/relations/index_component.rb +++ b/app/components/sdg_management/relations/index_component.rb @@ -25,4 +25,8 @@ def edit_path_for(record) id: record } end + + def search_label + t("admin.shared.search.label.#{model_class.table_name}") + end end diff --git a/app/controllers/sdg_management/relations_controller.rb b/app/controllers/sdg_management/relations_controller.rb index 8f29248649f4..870dc5fd2293 100644 --- a/app/controllers/sdg_management/relations_controller.rb +++ b/app/controllers/sdg_management/relations_controller.rb @@ -4,6 +4,8 @@ class SDGManagement::RelationsController < SDGManagement::BaseController def index @records = relatable_class.accessible_by(current_ability).order(:id).page(params[:page]) + + @records = @records.search(params[:search]) if params[:search].present? end def edit diff --git a/config/locales/en/admin.yml b/config/locales/en/admin.yml index baeb2874d577..390c91efc892 100644 --- a/config/locales/en/admin.yml +++ b/config/locales/en/admin.yml @@ -1256,9 +1256,12 @@ en: search: label: booths: "Search booth by name or location" + budget_investments: "Search investments by title, description or heading" debates: "Search debates by title or description" + legislation_processes: "Search processes by title or description" poll_officers: "Search poll officers" poll_questions: "Search poll questions" + polls: "Search polls by name or description" proposals: "Search proposals by title, code, description or question" users: "Search user by name or email" search: "Search" diff --git a/config/locales/es/admin.yml b/config/locales/es/admin.yml index dd8e7c8201f7..867423c6e27e 100644 --- a/config/locales/es/admin.yml +++ b/config/locales/es/admin.yml @@ -1255,9 +1255,12 @@ es: search: label: booths: "Buscar urna por nombre" + budget_investments: "Buscar proyectos por título, descripción o partida" debates: "Buscar debates por título o descripción" + legislation_processes: "Buscar procesos por título o descripción" poll_officers: "Buscar presidentes de mesa" poll_questions: "Buscar preguntas" + polls: "Buscar votaciones por nombre o descripción" proposals: "Buscar propuestas por título, código, descripción o pregunta" users: "Buscar usuario por nombre o email" search: "Buscar" diff --git a/spec/system/sdg_management/relations_spec.rb b/spec/system/sdg_management/relations_spec.rb index 4b477ffdb5e5..c3c2bbb3f3d1 100644 --- a/spec/system/sdg_management/relations_spec.rb +++ b/spec/system/sdg_management/relations_spec.rb @@ -87,6 +87,19 @@ expect(page).to have_css "h2", exact_text: "Build a hospital" end + + scenario "search" do + create(:poll, name: "Internet speech freedom") + create(:poll, name: "SDG interest") + + visit sdg_management_polls_path + + fill_in "search", with: "speech" + click_button "Search" + + expect(page).to have_content "Internet speech freedom" + expect(page).not_to have_content "SDG interest" + end end describe "Edit" do From 370ec2be729823efcba73aa04e18dc8423e12c1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javi=20Mart=C3=ADn?= Date: Thu, 17 Dec 2020 18:40:52 +0100 Subject: [PATCH 10/12] Extract method to display a goal code and title This is a pattern we're going to use in many places. --- .../sdg_management/targets/index_component.html.erb | 2 +- app/models/sdg/goal.rb | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/components/sdg_management/targets/index_component.html.erb b/app/components/sdg_management/targets/index_component.html.erb index 161ffd35224c..b7e9315fe775 100644 --- a/app/components/sdg_management/targets/index_component.html.erb +++ b/app/components/sdg_management/targets/index_component.html.erb @@ -13,7 +13,7 @@ <% targets.group_by(&:goal).map do |goal, targets| %> diff --git a/app/models/sdg/goal.rb b/app/models/sdg/goal.rb index bd26438c8cff..b90a14dfd306 100644 --- a/app/models/sdg/goal.rb +++ b/app/models/sdg/goal.rb @@ -16,4 +16,8 @@ def description def self.[](code) find_by!(code: code) end + + def code_and_title + "#{code}. #{title}" + end end From 5e2d653e2378e5c508bfaa1a9a27cf54f54dd5cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javi=20Mart=C3=ADn?= Date: Thu, 17 Dec 2020 19:13:48 +0100 Subject: [PATCH 11/12] Add filter by goal --- app/assets/stylesheets/admin/search.css | 23 +++++++++++--- .../admin/search_component.html.erb | 1 + .../relations/index_component.html.erb | 6 +++- .../relations/index_component.rb | 12 ++++++++ .../sdg_management/relations_controller.rb | 6 +++- app/models/concerns/sdg/relatable.rb | 8 +++++ config/locales/en/admin.yml | 4 +++ config/locales/es/admin.yml | 4 +++ spec/models/sdg/relatable_spec.rb | 21 +++++++++++++ spec/system/sdg_management/relations_spec.rb | 30 ++++++++++++++----- 10 files changed, 101 insertions(+), 14 deletions(-) diff --git a/app/assets/stylesheets/admin/search.css b/app/assets/stylesheets/admin/search.css index f4e818abd9a2..0ece75fc50cf 100644 --- a/app/assets/stylesheets/admin/search.css +++ b/app/assets/stylesheets/admin/search.css @@ -1,6 +1,25 @@ .admin [role=search] { display: flex; + &.complex { + @include breakpoint(small only) { + flex-direction: column; + } + + @include breakpoint(medium) { + select { + height: $line-height * 2; + margin: 0 rem-calc(12); + } + } + } + + &:not(.complex) { + @include breakpoint(medium) { + width: 50%; + } + } + [type="submit"] { @include button($background: $link); border-radius: 0; @@ -10,8 +29,4 @@ @include button-disabled; } } - - @include breakpoint(medium) { - width: 50%; - } } diff --git a/app/components/admin/search_component.html.erb b/app/components/admin/search_component.html.erb index 0caf05d0e09e..0286dcb48811 100644 --- a/app/components/admin/search_component.html.erb +++ b/app/components/admin/search_component.html.erb @@ -1,4 +1,5 @@ <%= form_tag(url, options) do |f| %> <%= text_field_tag :search, search_terms.to_s, placeholder: label, "aria-label": label %> + <%= content %> <%= submit_tag t("admin.shared.search.search") %> <% end %> diff --git a/app/components/sdg_management/relations/index_component.html.erb b/app/components/sdg_management/relations/index_component.html.erb index 43ba44b12626..2cf9ca2be4fc 100644 --- a/app/components/sdg_management/relations/index_component.html.erb +++ b/app/components/sdg_management/relations/index_component.html.erb @@ -1,6 +1,10 @@ <%= header %> -<%= render Admin::SearchComponent.new(label: search_label) %> +<%= render Admin::SearchComponent.new(label: search_label, class: "complex") do |component| %> + <%= component.select_tag :goal_code, goal_options, + include_blank: goal_blank_option, + "aria-label": goal_label %> +<% end %>
- <%= goal.code %>. <%= goal.title %> + <%= goal.code_and_title %>
diff --git a/app/components/sdg_management/relations/index_component.rb b/app/components/sdg_management/relations/index_component.rb index 16a49194c87d..97d3cb49d25c 100644 --- a/app/components/sdg_management/relations/index_component.rb +++ b/app/components/sdg_management/relations/index_component.rb @@ -29,4 +29,16 @@ def edit_path_for(record) def search_label t("admin.shared.search.label.#{model_class.table_name}") end + + def goal_label + t("admin.shared.search.advanced_filters.sdg_goals.label") + end + + def goal_blank_option + t("admin.shared.search.advanced_filters.sdg_goals.all") + end + + def goal_options + options_from_collection_for_select(SDG::Goal.all, :code, :code_and_title, params[:goal_code]) + end end diff --git a/app/controllers/sdg_management/relations_controller.rb b/app/controllers/sdg_management/relations_controller.rb index 870dc5fd2293..aa03f5d26d0f 100644 --- a/app/controllers/sdg_management/relations_controller.rb +++ b/app/controllers/sdg_management/relations_controller.rb @@ -3,7 +3,11 @@ class SDGManagement::RelationsController < SDGManagement::BaseController before_action :load_record, only: [:edit, :update] def index - @records = relatable_class.accessible_by(current_ability).order(:id).page(params[:page]) + @records = relatable_class + .accessible_by(current_ability) + .by_goal(params[:goal_code]) + .order(:id) + .page(params[:page]) @records = @records.search(params[:search]) if params[:search].present? end diff --git a/app/models/concerns/sdg/relatable.rb b/app/models/concerns/sdg/relatable.rb index 50f9b018369e..e552fbd965bd 100644 --- a/app/models/concerns/sdg/relatable.rb +++ b/app/models/concerns/sdg/relatable.rb @@ -12,6 +12,14 @@ module SDG::Relatable end end + class_methods do + def by_goal(code) + return all if code.blank? + + joins(:sdg_goals).merge(SDG::Goal.where(code: code)) + end + end + def related_sdgs sdg_relations.map(&:related_sdg) end diff --git a/config/locales/en/admin.yml b/config/locales/en/admin.yml index 390c91efc892..65163157e7f2 100644 --- a/config/locales/en/admin.yml +++ b/config/locales/en/admin.yml @@ -1254,6 +1254,10 @@ en: true_value: "Yes" false_value: "No" search: + advanced_filters: + sdg_goals: + all: "All goals" + label: "By goal" label: booths: "Search booth by name or location" budget_investments: "Search investments by title, description or heading" diff --git a/config/locales/es/admin.yml b/config/locales/es/admin.yml index 867423c6e27e..57fddcc21fb1 100644 --- a/config/locales/es/admin.yml +++ b/config/locales/es/admin.yml @@ -1253,6 +1253,10 @@ es: true_value: "Sí" false_value: "No" search: + advanced_filters: + sdg_goals: + all: "Todos los objetivos" + label: "Por objetivo" label: booths: "Buscar urna por nombre" budget_investments: "Buscar proyectos por título, descripción o partida" diff --git a/spec/models/sdg/relatable_spec.rb b/spec/models/sdg/relatable_spec.rb index d161d4ddb22b..8f1e210d9dc9 100644 --- a/spec/models/sdg/relatable_spec.rb +++ b/spec/models/sdg/relatable_spec.rb @@ -116,4 +116,25 @@ expect(relatable.reload.sdg_goals).to match_array [SDG::Goal[1], SDG::Goal[2]] end end + + describe ".by_goal" do + it "returns everything if no code is provided" do + expect(relatable.class.by_goal("")).to eq [relatable] + expect(relatable.class.by_goal(nil)).to eq [relatable] + end + + it "returns records associated with that goal" do + same_association = create(:proposal, sdg_goals: [goal]) + both_associations = create(:proposal, sdg_goals: [goal, another_goal]) + + expect(relatable.class.by_goal(goal.code)).to match_array [same_association, both_associations] + end + + it "does not return records not associated with that goal" do + create(:proposal) + create(:proposal, sdg_goals: [another_goal]) + + expect(relatable.class.by_goal(goal.code)).to be_empty + end + end end diff --git a/spec/system/sdg_management/relations_spec.rb b/spec/system/sdg_management/relations_spec.rb index c3c2bbb3f3d1..3518617cd388 100644 --- a/spec/system/sdg_management/relations_spec.rb +++ b/spec/system/sdg_management/relations_spec.rb @@ -88,17 +88,31 @@ expect(page).to have_css "h2", exact_text: "Build a hospital" end - scenario "search" do - create(:poll, name: "Internet speech freedom") - create(:poll, name: "SDG interest") + describe "search" do + scenario "search by terms" do + create(:poll, name: "Internet speech freedom") + create(:poll, name: "SDG interest") - visit sdg_management_polls_path + visit sdg_management_polls_path - fill_in "search", with: "speech" - click_button "Search" + fill_in "search", with: "speech" + click_button "Search" - expect(page).to have_content "Internet speech freedom" - expect(page).not_to have_content "SDG interest" + expect(page).to have_content "Internet speech freedom" + expect(page).not_to have_content "SDG interest" + end + + scenario "goal filter" do + create(:budget_investment, title: "School", sdg_goals: [SDG::Goal[4]]) + create(:budget_investment, title: "Hospital", sdg_goals: [SDG::Goal[3]]) + + visit sdg_management_budget_investments_path + select "4. Quality Education", from: "goal_code" + click_button "Search" + + expect(page).to have_content "School" + expect(page).not_to have_content "Hospital" + end end end From 1a8687b24c130390e9059abc37c6059749a05bcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javi=20Mart=C3=ADn?= Date: Thu, 17 Dec 2020 19:29:43 +0100 Subject: [PATCH 12/12] Add filter by target --- .../relations/index_component.html.erb | 3 +++ .../relations/index_component.rb | 12 +++++++++++ .../sdg_management/relations_controller.rb | 1 + app/models/concerns/sdg/relatable.rb | 10 ++++++++- config/locales/en/admin.yml | 3 +++ config/locales/es/admin.yml | 3 +++ spec/models/sdg/relatable_spec.rb | 21 +++++++++++++++++++ spec/system/sdg_management/relations_spec.rb | 12 +++++++++++ 8 files changed, 64 insertions(+), 1 deletion(-) diff --git a/app/components/sdg_management/relations/index_component.html.erb b/app/components/sdg_management/relations/index_component.html.erb index 2cf9ca2be4fc..75e6b41705ce 100644 --- a/app/components/sdg_management/relations/index_component.html.erb +++ b/app/components/sdg_management/relations/index_component.html.erb @@ -4,6 +4,9 @@ <%= component.select_tag :goal_code, goal_options, include_blank: goal_blank_option, "aria-label": goal_label %> + <%= component.select_tag :target_code, target_options, + include_blank: target_blank_option, + "aria-label": target_label %> <% end %>
diff --git a/app/components/sdg_management/relations/index_component.rb b/app/components/sdg_management/relations/index_component.rb index 97d3cb49d25c..170fa7573cac 100644 --- a/app/components/sdg_management/relations/index_component.rb +++ b/app/components/sdg_management/relations/index_component.rb @@ -38,7 +38,19 @@ def goal_blank_option t("admin.shared.search.advanced_filters.sdg_goals.all") end + def target_label + t("admin.shared.search.advanced_filters.sdg_targets.label") + end + + def target_blank_option + t("admin.shared.search.advanced_filters.sdg_targets.all") + end + def goal_options options_from_collection_for_select(SDG::Goal.all, :code, :code_and_title, params[:goal_code]) end + + def target_options + options_from_collection_for_select(SDG::Target.all.sort, :code, :code, params[:target_code]) + end end diff --git a/app/controllers/sdg_management/relations_controller.rb b/app/controllers/sdg_management/relations_controller.rb index aa03f5d26d0f..e16c1f63be94 100644 --- a/app/controllers/sdg_management/relations_controller.rb +++ b/app/controllers/sdg_management/relations_controller.rb @@ -6,6 +6,7 @@ def index @records = relatable_class .accessible_by(current_ability) .by_goal(params[:goal_code]) + .by_target(params[:target_code]) .order(:id) .page(params[:page]) diff --git a/app/models/concerns/sdg/relatable.rb b/app/models/concerns/sdg/relatable.rb index e552fbd965bd..07d71afdc30e 100644 --- a/app/models/concerns/sdg/relatable.rb +++ b/app/models/concerns/sdg/relatable.rb @@ -14,9 +14,17 @@ module SDG::Relatable class_methods do def by_goal(code) + by_sdg_related(SDG::Goal, code) + end + + def by_target(code) + by_sdg_related(SDG::Target, code) + end + + def by_sdg_related(sdg_class, code) return all if code.blank? - joins(:sdg_goals).merge(SDG::Goal.where(code: code)) + joins(sdg_class.table_name.to_sym).merge(sdg_class.where(code: code)) end end diff --git a/config/locales/en/admin.yml b/config/locales/en/admin.yml index 65163157e7f2..18d9ceb534a2 100644 --- a/config/locales/en/admin.yml +++ b/config/locales/en/admin.yml @@ -1258,6 +1258,9 @@ en: sdg_goals: all: "All goals" label: "By goal" + sdg_targets: + all: "All targets" + label: "By target" label: booths: "Search booth by name or location" budget_investments: "Search investments by title, description or heading" diff --git a/config/locales/es/admin.yml b/config/locales/es/admin.yml index 57fddcc21fb1..c03515bf223b 100644 --- a/config/locales/es/admin.yml +++ b/config/locales/es/admin.yml @@ -1257,6 +1257,9 @@ es: sdg_goals: all: "Todos los objetivos" label: "Por objetivo" + sdg_targets: + all: "Todas las metas" + label: "Por meta" label: booths: "Buscar urna por nombre" budget_investments: "Buscar proyectos por título, descripción o partida" diff --git a/spec/models/sdg/relatable_spec.rb b/spec/models/sdg/relatable_spec.rb index 8f1e210d9dc9..036b5af96c8b 100644 --- a/spec/models/sdg/relatable_spec.rb +++ b/spec/models/sdg/relatable_spec.rb @@ -137,4 +137,25 @@ expect(relatable.class.by_goal(goal.code)).to be_empty end end + + describe ".by_target" do + it "returns everything if no code is provided" do + expect(relatable.class.by_target("")).to eq [relatable] + expect(relatable.class.by_target(nil)).to eq [relatable] + end + + it "returns records associated with that target" do + same_association = create(:proposal, sdg_targets: [target]) + both_associations = create(:proposal, sdg_targets: [target, another_target]) + + expect(relatable.class.by_target(target.code)).to match_array [same_association, both_associations] + end + + it "does not return records not associated with that target" do + create(:proposal) + create(:proposal, sdg_targets: [another_target]) + + expect(relatable.class.by_target(target.code)).to be_empty + end + end end diff --git a/spec/system/sdg_management/relations_spec.rb b/spec/system/sdg_management/relations_spec.rb index 3518617cd388..05b0a9965ce1 100644 --- a/spec/system/sdg_management/relations_spec.rb +++ b/spec/system/sdg_management/relations_spec.rb @@ -114,6 +114,18 @@ expect(page).not_to have_content "Hospital" end end + + scenario "target filter" do + create(:budget_investment, title: "School", sdg_targets: [SDG::Target[4.1]]) + create(:budget_investment, title: "Preschool", sdg_targets: [SDG::Target[4.2]]) + + visit sdg_management_budget_investments_path + select "4.1", from: "target_code" + click_button "Search" + + expect(page).to have_content "School" + expect(page).not_to have_content "Preschool" + end end describe "Edit" do