Merge pull request #4327 from consul/add_local_target_to_related_list_selector

Add local targets to related list selector
This commit is contained in:
Javi Martín
2021-01-26 19:30:19 +01:00
committed by GitHub
23 changed files with 209 additions and 69 deletions

View File

@@ -1,6 +1,6 @@
<div class="sdg-related-list-selector">
<div class="input-section">
<%= f.label :sdg_related_list %>
<%= f.label :related_sdg_list %>
<ul aria-label="<%= t("sdg.related_list_selector.goal_list") %>">
<% goals.each do |goal| %>
@@ -10,7 +10,7 @@
<% end %>
</ul>
<%= f.text_field :sdg_related_list,
<%= f.text_field :related_sdg_list,
class: "input",
label: false,
placeholder: t("sdg.related_list_selector.placeholder"),

View File

@@ -15,7 +15,8 @@ class SDG::RelatedListSelectorComponent < ApplicationComponent
def goals_and_targets
goals.map do |goal|
[goal, *goal.targets.sort]
global_and_local_targets = goal.targets + goal.local_targets
[goal, global_and_local_targets.sort]
end.flatten
end

View File

@@ -69,7 +69,7 @@ class Admin::Legislation::ProcessesController < Admin::Legislation::BaseControll
:custom_list,
:background_color,
:font_color,
:sdg_related_list,
:related_sdg_list,
translation_params(::Legislation::Process),
documents_attributes: [:id, :title, :attachment, :cached_attachment, :user_id, :_destroy],
image_attributes: image_attributes

View File

@@ -76,7 +76,7 @@ class Admin::Poll::PollsController < Admin::Poll::BaseController
end
def poll_params
attributes = [:name, :starts_at, :ends_at, :geozone_restricted, :budget_id, :sdg_related_list,
attributes = [:name, :starts_at, :ends_at, :geozone_restricted, :budget_id, :related_sdg_list,
geozone_ids: [], image_attributes: image_attributes]
params.require(:poll).permit(*attributes, *report_attributes, translation_params(Poll))

View File

@@ -133,7 +133,7 @@ module Budgets
def investment_params
attributes = [:heading_id, :tag_list, :organization_name, :location,
:terms_of_service, :skip_map, :sdg_related_list,
:terms_of_service, :skip_map, :related_sdg_list,
image_attributes: image_attributes,
documents_attributes: [:id, :title, :attachment, :cached_attachment, :user_id, :_destroy],
map_location_attributes: [:latitude, :longitude, :zoom]]

View File

@@ -55,7 +55,7 @@ class DebatesController < ApplicationController
private
def debate_params
attributes = [:tag_list, :terms_of_service, :sdg_related_list]
attributes = [:tag_list, :terms_of_service, :related_sdg_list]
params.require(:debate).permit(attributes, translation_params(Debate))
end

View File

@@ -99,7 +99,7 @@ class ProposalsController < ApplicationController
def proposal_params
attributes = [:video_url, :responsible_name, :tag_list, :terms_of_service,
:geozone_id, :skip_map, :sdg_related_list,
:geozone_id, :skip_map, :related_sdg_list,
image_attributes: image_attributes,
documents_attributes: [:id, :title, :attachment, :cached_attachment,
:user_id, :_destroy],

View File

@@ -21,7 +21,7 @@ class SDGManagement::RelationsController < SDGManagement::BaseController
end
def update
@record.sdg_related_list = params[@record.class.table_name.singularize][:sdg_related_list]
@record.related_sdg_list = params[@record.class.table_name.singularize][:related_sdg_list]
redirect_to({ action: :index }, notice: update_notice)
end

View File

@@ -73,20 +73,21 @@ module SDG::Relatable
sdg_targets.sort.map(&:code).join(", ")
end
def sdg_related_list
sdg_goals.order(:code).map do |goal|
[goal, sdg_global_targets.where(goal: goal).sort]
end.flatten.map(&:code).join(", ")
def related_sdg_list
related_sdgs.sort.map(&:code).join(", ")
end
def sdg_related_list=(codes)
def related_sdg_list=(codes)
target_codes, goal_codes = codes.tr(" ", "").split(",").partition { |code| code.include?(".") }
targets = target_codes.map { |code| SDG::Target[code] }
local_targets_codes, global_targets_codes = target_codes.partition { |code| code.split(".")[2] }
global_targets = global_targets_codes.map { |code| SDG::Target[code] }
local_targets = local_targets_codes.map { |code| SDG::LocalTarget[code] }
goals = goal_codes.map { |code| SDG::Goal[code] }
transaction do
self.sdg_global_targets = targets
self.sdg_goals = (targets.map(&:goal) + goals).uniq
self.sdg_local_targets = local_targets
self.sdg_global_targets = global_targets
self.sdg_goals = (global_targets.map(&:goal) + local_targets.map(&:goal) + goals).uniq
end
end
end

View File

@@ -1,9 +1,11 @@
class SDG::Goal < ApplicationRecord
include Comparable
include SDG::Related
validates :code, presence: true, uniqueness: true, inclusion: { in: 1..17 }
has_many :targets, dependent: :destroy
has_many :local_targets, dependent: :destroy
def title
I18n.t("sdg.goals.goal_#{code}.title")
@@ -17,6 +19,14 @@ class SDG::Goal < ApplicationRecord
I18n.t("sdg.goals.goal_#{code}.description")
end
def <=>(goal_or_target)
if goal_or_target.class == self.class
code <=> goal_or_target.code
elsif goal_or_target.respond_to?(:goal)
[self, -1] <=> [goal_or_target.goal, 1]
end
end
def self.[](code)
find_by!(code: code)
end

View File

@@ -2,8 +2,6 @@ class SDG::LocalTarget < ApplicationRecord
include Comparable
include SDG::Related
delegate :goal, to: :target
translates :title, touch: true
translates :description, touch: true
include Globalizable
@@ -14,14 +12,22 @@ class SDG::LocalTarget < ApplicationRecord
validates :code, presence: true, uniqueness: true,
format: ->(local_target) { /\A#{local_target.target&.code}\.\d+/ }
validates :target, presence: true
validates :goal, presence: true
belongs_to :target
belongs_to :goal
def <=>(any_target)
if any_target.class == self.class
[target, numeric_subcode] <=> [any_target.target, any_target.numeric_subcode]
elsif any_target.class == target.class
-1 * (any_target <=> self)
before_validation :set_related_goal
def self.[](code)
find_by!(code: code)
end
def <=>(goal_or_target)
if goal_or_target.class == self.class
[target, numeric_subcode] <=> [goal_or_target.target, goal_or_target.numeric_subcode]
elsif [target.class, goal.class].include?(goal_or_target.class)
-1 * (goal_or_target <=> self)
end
end
@@ -36,4 +42,8 @@ class SDG::LocalTarget < ApplicationRecord
def subcode
code.split(".").last
end
def set_related_goal
self.goal ||= target&.goal
end
end

View File

@@ -12,11 +12,13 @@ class SDG::Target < ApplicationRecord
I18n.t("sdg.goals.goal_#{goal.code}.targets.target_#{code_key}.title")
end
def <=>(any_target)
if any_target.class == self.class
[goal.code, numeric_subcode] <=> [any_target.goal.code, any_target.numeric_subcode]
elsif any_target.class.name == "SDG::LocalTarget"
[self, -1] <=> [any_target.target, 1]
def <=>(goal_or_target)
if goal_or_target.class == self.class
[goal.code, numeric_subcode] <=> [goal_or_target.goal.code, goal_or_target.numeric_subcode]
elsif goal_or_target.class == goal.class
-1 * (goal_or_target <=> self)
elsif goal_or_target.class.name == "SDG::LocalTarget"
[self, -1] <=> [goal_or_target.target, 1]
end
end

View File

@@ -2,7 +2,7 @@ en:
attributes:
geozone_id: "Scope of operation"
results_enabled: "Show results"
sdg_related_list: "Sustainable Development Goals and Targets"
related_sdg_list: "Sustainable Development Goals and Targets"
stats_enabled: "Show stats"
advanced_stats_enabled: "Show advanced stats"
name: Name

View File

@@ -2,7 +2,7 @@ es:
attributes:
geozone_id: "Ámbito de actuación"
results_enabled: "Mostrar resultados"
sdg_related_list: "Objetivos de Desarrollo Sostenible y Metas"
related_sdg_list: "Objetivos de Desarrollo Sostenible y Metas"
stats_enabled: "Mostrar estadísticas"
advanced_stats_enabled: "Mostrar estadísticas avanzadas"
name: Nombre

View File

@@ -0,0 +1,5 @@
class AddGoalsToLocalTargets < ActiveRecord::Migration[5.2]
def change
add_reference :sdg_local_targets, :goal
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: 2021_01_07_125458) do
ActiveRecord::Schema.define(version: 2021_01_23_100638) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_trgm"
@@ -1322,7 +1322,9 @@ ActiveRecord::Schema.define(version: 2021_01_07_125458) do
t.string "code"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.bigint "goal_id"
t.index ["code"], name: "index_sdg_local_targets_on_code", unique: true
t.index ["goal_id"], name: "index_sdg_local_targets_on_goal_id"
t.index ["target_id"], name: "index_sdg_local_targets_on_target_id"
end

View File

@@ -26,7 +26,7 @@ describe SDG::RelatedListSelectorComponent, type: :component do
expect(page).not_to have_css ".sdg-related-list-selector"
end
it "renders sdg_related_list field" do
it "renders related_sdg_list field" do
render_inline component
expect(page).to have_css ".sdg-related-list-selector .input"
@@ -36,10 +36,12 @@ describe SDG::RelatedListSelectorComponent, type: :component do
describe "#goals_and_targets" do
it "return all goals and target with order" do
create(:sdg_local_target, code: "1.1.1")
goals_and_targets = component.goals_and_targets
expect(goals_and_targets.first).to eq SDG::Goal[1]
expect(goals_and_targets.second).to eq SDG::Target[1.1]
expect(goals_and_targets.third).to eq SDG::LocalTarget["1.1.1"]
expect(goals_and_targets.last).to eq SDG::Target[17.19]
end
end
@@ -56,7 +58,7 @@ describe SDG::RelatedListSelectorComponent, type: :component do
})
end
it "return suggestion tag for target" do
it "returns suggestion tag for global target" do
suggestion = component.suggestion_tag_for(SDG::Target[1.1])
expect(suggestion).to eq({
@@ -66,5 +68,17 @@ describe SDG::RelatedListSelectorComponent, type: :component do
value: "1.1"
})
end
it "returns suggestion tag for local target" do
create(:sdg_local_target, code: "1.1.1", title: "By 2030, eradicate extreme custom text")
suggestion = component.suggestion_tag_for(SDG::LocalTarget["1.1.1"])
expect(suggestion).to eq({
tag: "1.1.1. By 2030 eradicate extreme custom text",
display_text: "1.1.1",
title: "By 2030, eradicate extreme custom text",
value: "1.1.1"
})
end
end
end

View File

@@ -13,6 +13,7 @@ FactoryBot.define do
sequence(:description) { |n| "Help for Local Target #{n}" }
target { SDG::Target[code.rpartition(".").first] }
goal { SDG::Goal[code.split(".")[0]] }
end
factory :sdg_phase, class: "SDG::Phase" do

View File

@@ -21,6 +21,34 @@ describe SDG::Goal do
end
end
describe "#<=>" do
let(:goal) { SDG::Goal[10] }
it "can be compared against goals" do
lesser_goal = SDG::Goal[9]
greater_goal = SDG::Goal[11]
expect(goal).to be > lesser_goal
expect(goal).to be < greater_goal
end
it "can be compared against global targets" do
lesser_target = build(:sdg_target, code: "9.A", goal: SDG::Goal[9])
greater_target = build(:sdg_target, code: "10.1", goal: SDG::Goal[10])
expect(goal).to be > lesser_target
expect(goal).to be < greater_target
end
it "can be compared against local targets" do
lesser_local_target = build(:sdg_local_target, code: "9.B.12")
greater_local_target = build(:sdg_local_target, code: "10.1.4")
expect(goal).to be > lesser_local_target
expect(goal).to be < greater_local_target
end
end
describe ".[]" do
it "finds existing goals by code" do
expect(SDG::Goal[1].code).to be 1

View File

@@ -18,7 +18,7 @@ describe SDG::LocalTarget do
end
it "is not valid without a code" do
expect(build(:sdg_local_target, code: nil, target: SDG::Target[1.1])).not_to be_valid
expect(build(:sdg_local_target, code: nil, target: SDG::Target[1.1], goal: SDG::Goal[1])).not_to be_valid
end
it "is not valid when code does not include associated target code" do
@@ -47,6 +47,15 @@ describe SDG::LocalTarget do
expect(build(:sdg_local_target, target: nil)).not_to be_valid
end
describe "#set_related_goal" do
it "before validation set related goal" do
local_target = build(:sdg_local_target, code: "1.1.1", target: SDG::Target["1.1"], goal: nil)
expect(local_target).to be_valid
expect(local_target.goal).to eq(SDG::Goal[1])
end
end
describe "#goal" do
it "returns the target goal" do
local_target = create(:sdg_local_target, code: "1.1.1")
@@ -81,5 +90,25 @@ describe SDG::LocalTarget do
expect(local_target).to be > lesser_target
expect(local_target).to be < greater_target
end
it "can be compared against goals" do
lesser_goal = build(:sdg_goal, code: "10")
greater_goal = build(:sdg_goal, code: "11")
expect(local_target).to be > lesser_goal
expect(local_target).to be < greater_goal
end
end
describe ".[]" do
it "finds existing local targets by code" do
create(:sdg_local_target, code: "1.1.1")
expect(SDG::LocalTarget["1.1.1"].code).to eq "1.1.1"
end
it "raises an exception for non-existing codes" do
expect { SDG::LocalTarget["1.1.99"] }.to raise_exception ActiveRecord::RecordNotFound
end
end
end

View File

@@ -99,12 +99,13 @@ describe SDG::Relatable do
end
end
describe "#sdg_related_list" do
describe "#related_sdg_list" do
it "orders related list by code" do
relatable.sdg_goals = [SDG::Goal[1], SDG::Goal[3], SDG::Goal[2]]
relatable.sdg_targets = [SDG::Target[2.2], SDG::Target[1.2], SDG::Target[2.1]]
local_targets = %w[2.2.2 2.2.1 3.1.1].map { |code| create(:sdg_local_target, code: code) }
relatable.sdg_targets = [SDG::Target[2.2], SDG::Target[1.2], SDG::Target[2.1]] + local_targets
expect(relatable.sdg_related_list).to eq "1, 1.2, 2, 2.1, 2.2, 3"
expect(relatable.related_sdg_list).to eq "1, 1.2, 2, 2.1, 2.2, 2.2.1, 2.2.2, 3, 3.1.1"
end
end
@@ -119,52 +120,67 @@ describe SDG::Relatable do
end
end
describe "#sdg_related_list=" do
describe "#related_sdg_list=" do
it "assigns a single goal" do
relatable.sdg_related_list = "1"
relatable.related_sdg_list = "1"
expect(relatable.reload.sdg_goals).to match_array [SDG::Goal[1]]
end
it "assigns a single target" do
relatable.sdg_related_list = "1.1"
relatable.related_sdg_list = "1.1"
expect(relatable.reload.sdg_goals).to match_array [SDG::Goal[1]]
expect(relatable.reload.sdg_targets).to match_array [SDG::Target[1.1]]
end
it "assigns a single local target" do
relatable.related_sdg_list = local_target.code
expect(relatable.reload.sdg_goals).to match_array [local_target.goal]
expect(relatable.reload.sdg_local_targets).to match_array [local_target]
end
it "assigns multiple targets" do
relatable.sdg_related_list = "1.1,2.3"
relatable.related_sdg_list = "1.1,2.3"
expect(relatable.reload.sdg_goals).to match_array [SDG::Goal[1], SDG::Goal[2]]
expect(relatable.reload.sdg_targets).to match_array [SDG::Target[1.1], SDG::Target[2.3]]
end
it "assigns multiple goals" do
relatable.sdg_related_list = "3,2,1"
relatable.related_sdg_list = "3,2,1"
expect(relatable.reload.sdg_goals).to match_array [SDG::Goal[1], SDG::Goal[2], SDG::Goal[3]]
end
it "assigns multiple local targets" do
relatable.related_sdg_list = "#{local_target.code}, #{another_local_target.code}"
expect(relatable.reload.sdg_goals).to match_array [local_target.goal, another_local_target.goal]
expect(relatable.reload.sdg_local_targets).to match_array [local_target, another_local_target]
end
it "ignores trailing spaces and spaces between commas" do
relatable.sdg_related_list = " 1.1, 2.3 "
relatable.related_sdg_list = " 1.1, 2.3 "
expect(relatable.reload.sdg_goals).to match_array [SDG::Goal[1], SDG::Goal[2]]
expect(relatable.reload.sdg_targets).to match_array [SDG::Target[1.1], SDG::Target[2.3]]
end
it "assigns goals and targets" do
relatable.sdg_related_list = "1.1,3,4,4.1"
it "assigns goals, targets and local_targets" do
relatable.related_sdg_list = "1.1,3,4,4.1,#{another_local_target.code}"
expect(relatable.reload.sdg_goals).to match_array [SDG::Goal[1], SDG::Goal[3], SDG::Goal[4]]
expect(relatable.reload.sdg_targets).to match_array [SDG::Target[1.1], SDG::Target[4.1]]
expect(relatable.reload.sdg_goals).to match_array [SDG::Goal[1], another_local_target.goal, SDG::Goal[3], SDG::Goal[4]]
expect(relatable.reload.sdg_global_targets).to match_array [SDG::Target[1.1], SDG::Target[4.1]]
expect(relatable.reload.sdg_local_targets).to match_array [another_local_target]
end
it "touches the associated record" do
relatable.sdg_related_list = "1.1, 2.1, 2.2"
relatable.related_sdg_list = "1.1, 2.1, 2.2"
travel(10.seconds) do
relatable.sdg_related_list = "1.1, 2.1, 2.2, 3.1"
relatable.related_sdg_list = "1.1, 2.1, 2.2, 3.1"
expect(relatable.updated_at).to eq Time.current
end

View File

@@ -87,6 +87,14 @@ describe SDG::Target do
expect(target).to be < local_target
end
end
it "can be compared against goals" do
lesser_goal = build(:sdg_goal, code: "9")
greater_goal = build(:sdg_goal, code: "11")
expect(target).to be > lesser_goal
expect(target).to be < greater_goal
end
end
describe ".[]" do

View File

@@ -168,11 +168,10 @@ describe "SDG Relations", :js do
end
scenario "local target filter" do
schools = create(:sdg_local_target, code: "4.1.1")
teachers = create(:sdg_local_target, code: "4.1.2")
create(:debate, title: "Rebuild local schools", sdg_local_targets: [schools])
create(:debate, title: "Hire teachers", sdg_local_targets: [teachers])
create(:sdg_local_target, code: "4.1.1")
create(:sdg_local_target, code: "4.1.2")
create(:debate, title: "Rebuild local schools", sdg_local_targets: [SDG::LocalTarget["4.1.1"]])
create(:debate, title: "Hire teachers", sdg_local_targets: [SDG::LocalTarget["4.1.2"]])
visit sdg_management_debates_path
select "4.1.1", from: "target_code"
@@ -205,14 +204,16 @@ describe "SDG Relations", :js do
end
describe "Edit" do
scenario "allows adding the goals and targets and marks the resource as reviewed" do
scenario "allows adding the goals, global targets and local targets and marks the resource as reviewed" do
process = create(:legislation_process, title: "SDG process")
process.sdg_goals = [SDG::Goal[3]]
process.sdg_targets = [SDG::Target[3.3]]
create(:sdg_local_target, code: "1.1.1")
create(:sdg_local_target, code: "3.3.3")
process.sdg_goals = [SDG::Goal[3], SDG::Goal[4]]
process.sdg_targets = [SDG::Target[3.3], SDG::LocalTarget["3.3.3"]]
visit sdg_management_edit_legislation_process_path(process)
find(:css, ".sdg-related-list-selector-input").set("1.2, 2,")
find(:css, ".sdg-related-list-selector-input").set("1.2, 2, 1.1.1, ")
click_button "Update Process"
@@ -221,20 +222,22 @@ describe "SDG Relations", :js do
click_link "Marked as reviewed"
within("tr", text: "SDG process") do
expect(page).to have_css "td", exact_text: "1.2, 3.3"
expect(page).to have_css "td", exact_text: "1, 2, 3"
expect(page).to have_css "td", exact_text: "1.1.1, 1.2, 3.3, 3.3.3"
expect(page).to have_css "td", exact_text: "1, 2, 3, 4"
end
end
scenario "allows removing the goals and targets" do
scenario "allows removing the goals, global target and local_targets" do
process = create(:legislation_process, title: "SDG process")
process.sdg_goals = [SDG::Goal[2], SDG::Goal[3]]
process.sdg_targets = [SDG::Target[2.1], SDG::Target[3.3]]
create(:sdg_local_target, code: "1.1.1")
process.sdg_goals = [SDG::Goal[1], SDG::Goal[2], SDG::Goal[3]]
process.sdg_targets = [SDG::Target[2.1], SDG::Target[3.3], SDG::LocalTarget["1.1.1"]]
visit sdg_management_edit_legislation_process_path(process)
remove_sdg_goal_or_target_tag(2)
remove_sdg_goal_or_target_tag(3.3)
remove_sdg_goal_or_target_tag("1.1.1")
click_button "Update Process"
@@ -243,14 +246,13 @@ describe "SDG Relations", :js do
click_link "Marked as reviewed"
within("tr", text: "SDG process") do
expect(page).to have_css "td", exact_text: "2, 3"
expect(page).to have_css "td", exact_text: "1, 2, 3"
expect(page).to have_css "td", exact_text: "2.1"
end
end
scenario "does not show the review notice when resource was already reviewed" do
debate = create(:sdg_review, relatable: create(:debate, title: "SDG debate")).relatable
debate.sdg_targets = [SDG::Target[3.3]]
visit sdg_management_edit_debate_path(debate, filter: "sdg_reviewed")
find(:css, ".sdg-related-list-selector-input").set("1.2, 2.1,")
@@ -266,8 +268,9 @@ describe "SDG Relations", :js do
end
end
scenario "allows adding the goals and targets with autocomplete" do
scenario "allows adding the goals, global targets and local targets with autocomplete" do
process = create(:legislation_process, title: "SDG process")
create(:sdg_local_target, code: "1.1.1")
visit sdg_management_edit_legislation_process_path(process)
fill_in "Sustainable Development Goals and Targets", with: "3"
@@ -280,12 +283,17 @@ describe "SDG Relations", :js do
within(".amsify-suggestags-input-area") { expect(page).to have_content "1.1" }
fill_in "Sustainable Development Goals and Targets", with: "1.1.1"
within(".amsify-list") { find(:css, "[data-val='1.1.1']").click }
within(".amsify-suggestags-input-area") { expect(page).to have_content "1.1.1" }
click_button "Update Process"
click_link "Marked as reviewed"
within("tr", text: "SDG process") do
expect(page).to have_css "td", exact_text: "1, 3"
expect(page).to have_css "td", exact_text: "1.1"
expect(page).to have_css "td", exact_text: "1.1, 1.1.1"
end
end
@@ -339,8 +347,9 @@ describe "SDG Relations", :js do
scenario "when remove a last tag related to a Goal, the icon will not be checked" do
process = create(:legislation_process, title: "SDG process")
create(:sdg_local_target, code: "1.1.1")
process.sdg_goals = [SDG::Goal[1]]
process.sdg_targets = [SDG::Target[1.1]]
process.sdg_targets = [SDG::Target[1.1], SDG::LocalTarget["1.1.1"]]
visit sdg_management_edit_legislation_process_path(process)
remove_sdg_goal_or_target_tag(1)
@@ -349,6 +358,10 @@ describe "SDG Relations", :js do
remove_sdg_goal_or_target_tag(1.1)
expect(find("li[data-code='1']")["aria-checked"]).to eq "true"
remove_sdg_goal_or_target_tag("1.1.1")
expect(find("li[data-code='1']")["aria-checked"]).to eq "false"
end
end