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.
This commit is contained in:
@@ -36,7 +36,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.budgets-link,
|
&.budgets-link,
|
||||||
&.investments-link {
|
&.investments-link,
|
||||||
|
&.budget-investments-link {
|
||||||
@include icon(chart-pie, solid);
|
@include icon(chart-pie, solid);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +57,8 @@
|
|||||||
@include icon(envelope, regular);
|
@include icon(envelope, regular);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.legislation-link {
|
&.legislation-link,
|
||||||
|
&.legislation-processes-link {
|
||||||
@include icon(file-alt, solid);
|
@include icon(file-alt, solid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,14 +4,37 @@ class SDGManagement::MenuComponent < ApplicationComponent
|
|||||||
private
|
private
|
||||||
|
|
||||||
def links
|
def links
|
||||||
[goals_link]
|
[goals_link, *relatable_links]
|
||||||
end
|
end
|
||||||
|
|
||||||
def goals_link
|
def goals_link
|
||||||
[t("sdg_management.menu.sdg_content"), sdg_management_goals_path, sdg?, class: "goals-link"]
|
[t("sdg_management.menu.sdg_content"), sdg_management_goals_path, sdg?, class: "goals-link"]
|
||||||
end
|
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?
|
def sdg?
|
||||||
%w[goals targets local_targets].include?(controller_name)
|
%w[goals targets local_targets].include?(controller_name)
|
||||||
end
|
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
|
end
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<%= header %>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th><%= model_class.human_attribute_name(:title) %></th>
|
||||||
|
<th><%= SDG::Goal.model_name.human(count: 2).upcase_first %></th>
|
||||||
|
<th><%= SDG::Target.model_name.human(count: 2).upcase_first %></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
<% @records.each do |record| %>
|
||||||
|
<tr>
|
||||||
|
<td><%= record.title %></td>
|
||||||
|
<td><%= record.sdg_goal_list %></td>
|
||||||
|
<td><%= record.sdg_target_list %></td>
|
||||||
|
</tr>
|
||||||
|
<% end %>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<%= paginate(@records) %>
|
||||||
19
app/components/sdg_management/relations/index_component.rb
Normal file
19
app/components/sdg_management/relations/index_component.rb
Normal file
@@ -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
|
||||||
11
app/controllers/sdg_management/relations_controller.rb
Normal file
11
app/controllers/sdg_management/relations_controller.rb
Normal file
@@ -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
|
||||||
@@ -15,4 +15,12 @@ module SDG::Relatable
|
|||||||
def related_sdgs
|
def related_sdgs
|
||||||
sdg_relations.map(&:related_sdg)
|
sdg_relations.map(&:related_sdg)
|
||||||
end
|
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
|
end
|
||||||
|
|||||||
1
app/views/sdg_management/relations/index.html.erb
Normal file
1
app/views/sdg_management/relations/index.html.erb
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<%= render SDGManagement::Relations::IndexComponent.new(@records) %>
|
||||||
@@ -287,6 +287,7 @@ en:
|
|||||||
responsible_name: "Person responsible for the group"
|
responsible_name: "Person responsible for the group"
|
||||||
poll:
|
poll:
|
||||||
name: "Name"
|
name: "Name"
|
||||||
|
title: "Name"
|
||||||
starts_at: "Start Date"
|
starts_at: "Start Date"
|
||||||
ends_at: "Closing Date"
|
ends_at: "Closing Date"
|
||||||
geozone_restricted: "Restricted by geozone"
|
geozone_restricted: "Restricted by geozone"
|
||||||
|
|||||||
@@ -3,6 +3,11 @@ en:
|
|||||||
header:
|
header:
|
||||||
title: "SDG content"
|
title: "SDG content"
|
||||||
menu:
|
menu:
|
||||||
|
budget_investments: "Participatory budgets"
|
||||||
|
debates: "Debates"
|
||||||
|
legislation_processes: "Collaborative legislation"
|
||||||
|
polls: "Polls"
|
||||||
|
proposals: "Proposals"
|
||||||
sdg_content: "Goals and Targets"
|
sdg_content: "Goals and Targets"
|
||||||
local_targets:
|
local_targets:
|
||||||
create:
|
create:
|
||||||
|
|||||||
@@ -287,6 +287,7 @@ es:
|
|||||||
responsible_name: "Persona responsable del colectivo"
|
responsible_name: "Persona responsable del colectivo"
|
||||||
poll:
|
poll:
|
||||||
name: "Nombre"
|
name: "Nombre"
|
||||||
|
title: "Nombre"
|
||||||
starts_at: "Fecha de apertura"
|
starts_at: "Fecha de apertura"
|
||||||
ends_at: "Fecha de cierre"
|
ends_at: "Fecha de cierre"
|
||||||
geozone_restricted: "Restringida por zonas"
|
geozone_restricted: "Restringida por zonas"
|
||||||
|
|||||||
@@ -3,6 +3,11 @@ es:
|
|||||||
header:
|
header:
|
||||||
title: "Contenido ODS"
|
title: "Contenido ODS"
|
||||||
menu:
|
menu:
|
||||||
|
budget_investments: "Presupuestos participativos"
|
||||||
|
debates: "Debates"
|
||||||
|
legislation_processes: "Legislación colaborativa"
|
||||||
|
polls: "Votaciones"
|
||||||
|
proposals: "Propuestas"
|
||||||
sdg_content: "Objetivos y Metas"
|
sdg_content: "Objetivos y Metas"
|
||||||
local_targets:
|
local_targets:
|
||||||
create:
|
create:
|
||||||
|
|||||||
@@ -4,4 +4,13 @@ namespace :sdg_management do
|
|||||||
resources :goals, only: [:index]
|
resources :goals, only: [:index]
|
||||||
resources :targets, only: [:index]
|
resources :targets, only: [:index]
|
||||||
resources :local_targets, except: [:show]
|
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
|
end
|
||||||
|
|||||||
@@ -28,6 +28,14 @@ describe SDG::Relatable do
|
|||||||
end
|
end
|
||||||
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
|
describe "#sdg_targets" do
|
||||||
it "can assign targets to a model" do
|
it "can assign targets to a model" do
|
||||||
relatable.sdg_targets = [target, another_target]
|
relatable.sdg_targets = [target, another_target]
|
||||||
@@ -46,6 +54,14 @@ describe SDG::Relatable do
|
|||||||
end
|
end
|
||||||
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
|
describe "#sdg_local_targets" do
|
||||||
it "can assign local targets to a model" do
|
it "can assign local targets to a model" do
|
||||||
relatable.sdg_local_targets = [local_target, another_local_target]
|
relatable.sdg_local_targets = [local_target, another_local_target]
|
||||||
|
|||||||
32
spec/routing/sdg_management_routes_spec.rb
Normal file
32
spec/routing/sdg_management_routes_spec.rb
Normal file
@@ -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
|
||||||
74
spec/system/sdg_management/relations_spec.rb
Normal file
74
spec/system/sdg_management/relations_spec.rb
Normal file
@@ -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
|
||||||
Reference in New Issue
Block a user