diff --git a/app/assets/stylesheets/admin/menu.scss b/app/assets/stylesheets/admin/menu.scss index f80f074cd..a41461485 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 e626b2736..0f719ab2b 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 000000000..cf3592dc9 --- /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 000000000..0aaed38d0 --- /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 000000000..f81ad8502 --- /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 9da20b0d5..64b75f672 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 000000000..55878cd90 --- /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 bc561d093..01c79fc57 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 1e075fd76..c39a444b1 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 1f505a6bc..0c736934a 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 4ee4d8893..ce517cf25 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 cad7cbe66..23a73d55d 100644 --- a/config/routes/sdg_management.rb +++ b/config/routes/sdg_management.rb @@ -4,4 +4,13 @@ namespace :sdg_management do 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 e6fd1b8d0..595ecad61 100644 --- a/spec/models/sdg/relatable_spec.rb +++ b/spec/models/sdg/relatable_spec.rb @@ -28,6 +28,14 @@ describe SDG::Relatable do 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 @@ describe SDG::Relatable do 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 000000000..c04748e47 --- /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 000000000..94e479394 --- /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