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:
Javi Martín
2020-11-23 20:51:51 +01:00
parent 5590ecaaa6
commit ed51c5dcd3
15 changed files with 233 additions and 3 deletions

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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) %>

View 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

View 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

View File

@@ -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

View File

@@ -0,0 +1 @@
<%= render SDGManagement::Relations::IndexComponent.new(@records) %>

View File

@@ -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"

View File

@@ -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:

View File

@@ -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"

View File

@@ -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:

View File

@@ -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

View File

@@ -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]

View 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

View 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