Merge pull request #4269 from consul/sdg_management

Add SDG content management section
This commit is contained in:
Javi Martín
2020-12-21 18:52:25 +01:00
committed by GitHub
48 changed files with 949 additions and 59 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

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

View File

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

View File

@@ -1,9 +1 @@
<%= link_list(
[
t("sdg_management.menu.sdg_content"),
sdg_management_goals_path,
sdg?,
class: "goals-link"
],
class: "sdg-content-menu"
) %>
<%= link_list(*links, class: "sdg-content-menu") %>

View File

@@ -3,7 +3,50 @@ class SDGManagement::MenuComponent < ApplicationComponent
private
def links
[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|
next unless setting["process.#{process_name(type)}"] && setting["sdg.process.#{process_name(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
def process_name(type)
process_name = type.split("::").first
if process_name == "Legislation"
"legislation"
else
process_name.constantize.table_name
end
end
end

View File

@@ -0,0 +1,7 @@
<%= header %>
<%= form_for record, url: update_path do |f| %>
<%= f.text_field :sdg_target_list %>
<%= f.submit %>
<% end %>

View File

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

View File

@@ -0,0 +1,41 @@
<%= header %>
<%= 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 %>
<%= component.select_tag :target_code, target_options,
include_blank: target_blank_option,
"aria-label": target_label %>
<% end %>
<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>
<th><%= t("admin.actions.actions") %></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>
<td>
<%= render Admin::TableActionsComponent.new(
record,
actions: [:edit],
edit_text: t("sdg_management.actions.edit"),
edit_path: edit_path_for(record)
) %>
</td>
</tr>
<% end %>
</tbody>
</table>
<%= paginate(@records) %>

View File

@@ -0,0 +1,56 @@
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
def edit_path_for(record)
{
controller: "sdg_management/relations",
action: :edit,
relatable_type: record.class.name.tableize,
id: record
}
end
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 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

View File

@@ -13,7 +13,7 @@
<% targets.group_by(&:goal).map do |goal, targets| %>
<tr class="goal-header">
<th id="<%= header_id(goal) %>" colspan="2" scope="colgroup">
<%= goal.code %>. <%= goal.title %>
<%= goal.code_and_title %>
</th>
</tr>

View File

@@ -0,0 +1,42 @@
class SDGManagement::RelationsController < SDGManagement::BaseController
before_action :check_feature_flags
before_action :load_record, only: [:edit, :update]
def index
@records = relatable_class
.accessible_by(current_ability)
.by_goal(params[:goal_code])
.by_target(params[:target_code])
.order(:id)
.page(params[:page])
@records = @records.search(params[:search]) if params[:search].present?
end
def edit
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
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

View File

@@ -12,7 +12,40 @@ module SDG::Relatable
end
end
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_class.table_name.to_sym).merge(sdg_class.where(code: code))
end
end
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
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

View File

@@ -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 @@ class Legislation::Process < ApplicationRecord
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

View File

@@ -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 @@ class Poll < ApplicationRecord
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

View File

@@ -16,4 +16,8 @@ class SDG::Goal < ApplicationRecord
def self.[](code)
find_by!(code: code)
end
def code_and_title
"#{code}. #{title}"
end
end

View File

@@ -5,7 +5,7 @@
<h2><%= t("admin.debates.index.title") %></h2>
<% 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")) %>
<h3 class="inline-block"><%= page_entries_info @debates %></h3>

View File

@@ -9,7 +9,7 @@
class: "button float-right hollow" %>
<%= render Admin::SearchComponent.new(
label: t("admin.local_census_records.index.search.placeholder"),
label: t("admin.shared.search.label.local_census_records"),
remote: true
) %>

View File

@@ -2,7 +2,7 @@
<%= render Admin::SearchComponent.new(
url: search_admin_organizations_path,
label: t("admin.organizations.index.search_placeholder")
label: t("admin.shared.search.label.organizations")
) %>
<%= render "shared/filter_subnav", i18n_namespace: "admin.organizations.index" %>

View File

@@ -1,6 +1,6 @@
<h2><%= t("admin.organizations.search.title") %></h2>
<%= render Admin::SearchComponent.new(label: t("admin.organizations.index.search_placeholder")) %>
<%= render Admin::SearchComponent.new(label: t("admin.shared.search.label.organizations")) %>
<div id="search-results">
<% if @organizations.any? %>

View File

@@ -3,7 +3,7 @@
<div class="input-group">
<%= 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" %>
<div class="input-group-button">
<%= submit_tag t("admin.shared.search.search"), class: "button" %>
</div>

View File

@@ -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? %>
<div class="callout primary">

View File

@@ -3,7 +3,7 @@
<div class="input-group">
<%= 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" %>
<div class="input-group-button">
<%= submit_tag t("admin.shared.search.search"), class: "button" %>
</div>

View File

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

View File

@@ -5,7 +5,7 @@
<h2><%= t("admin.proposals.index.title") %></h2>
<% 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")) %>
<h3><%= page_entries_info @proposals %></h3>

View File

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

View File

@@ -1,7 +1,7 @@
<main>
<h2><%= t("management.proposals.index.title") %></h2>
<%= render Admin::SearchComponent.new(label: t("admin.shared.proposal_search.placeholder")) %>
<%= render Admin::SearchComponent.new(label: t("admin.shared.search.label.proposals")) %>
<div class="management-list">
<div class="proposals-list">

View File

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

View File

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

View File

@@ -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
@@ -287,6 +288,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

@@ -1169,7 +1169,6 @@ en:
no_organizations: There are no organizations.
reject: Reject
rejected: Rejected
search_placeholder: Name, email or phone number
title: Organisations
verified: Verified
verify: Verify
@@ -1254,19 +1253,26 @@ en:
true_value: "Yes"
false_value: "No"
search:
advanced_filters:
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"
debates: "Search debates by title or description"
legislation_processes: "Search processes by title or description"
local_census_records: "Search by document number"
organizations: "Name, email or phone number"
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"
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
@@ -1572,8 +1578,6 @@ en:
document_number: Document number
date_of_birth: Date of birth
postal_code: Postal code
search:
placeholder: Search by document number
import: Import CSV
new:
creating: Creating new local census record

View File

@@ -1,8 +1,15 @@
en:
sdg_management:
actions:
edit: "Manage goals and targets"
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

@@ -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
@@ -287,6 +288,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

@@ -1168,7 +1168,6 @@ es:
no_organizations: No hay organizaciones.
reject: Rechazar
rejected: Rechazada
search_placeholder: Nombre, email o teléfono
title: Organizaciones
verified: Verificada
verify: Verificar
@@ -1253,19 +1252,26 @@ es:
true_value: "Sí"
false_value: "No"
search:
advanced_filters:
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"
debates: "Buscar debates por título o descripción"
legislation_processes: "Buscar procesos por título o descripción"
local_census_records: "Búsqueda por número de documento"
organizations: "Nombre, email o teléfono"
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"
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
@@ -1571,8 +1577,6 @@ es:
document_number: Número de documento
date_of_birth: Fecha de nacimiento
postal_code: Código postal
search:
placeholder: Búsqueda por número de documento
import: Importar CSV
new:
creating: Creando nuevo registro de censo local

View File

@@ -1,8 +1,15 @@
es:
sdg_management:
actions:
edit: "Asignar objetivos y metas"
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,16 @@ 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
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
get "#{type}/:id/edit", to: "relations#edit", as: "edit_#{type.singularize}"
end
end

View File

@@ -0,0 +1,5 @@
class AddTsvToPolls < ActiveRecord::Migration[5.2]
def change
add_column :polls, :tsv, :tsvector
end
end

View File

@@ -0,0 +1,5 @@
class AddTsvToLegislationProcesses < ActiveRecord::Migration[5.2]
def change
add_column :legislation_processes, :tsv, :tsvector
end
end

View File

@@ -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_132642) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_trgm"
@@ -747,6 +747,7 @@ ActiveRecord::Schema.define(version: 2020_11_24_145559) do
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"
@@ -1166,6 +1167,7 @@ ActiveRecord::Schema.define(version: 2020_11_24_145559) do
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"

View File

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

View File

@@ -10,4 +10,10 @@ 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 and legislation processes"
task calculate_tsv: :environment do
Poll.find_each(&:calculate_tsvector)
Legislation::Process.find_each(&:calculate_tsvector)
end
end

View File

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

View File

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

View File

@@ -32,3 +32,33 @@ describe "rake db:load_sdg" do
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
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

View File

@@ -229,4 +229,35 @@ describe Legislation::Process do
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

View File

@@ -433,4 +433,29 @@ describe Poll do
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

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]
@@ -74,4 +90,72 @@ describe SDG::Relatable do
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
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
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

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,145 @@
require "rails_helper"
describe "SDG Relations", :js do
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
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
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
describe "search" do
scenario "search by terms" 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
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
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
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