Merge pull request #4324 from consul/sdg_target_filter

Add SDG target tags and filter
This commit is contained in:
Javi Martín
2021-01-27 16:48:12 +01:00
committed by GitHub
39 changed files with 427 additions and 133 deletions

View File

@@ -22,10 +22,10 @@
.date-filters {
float: left;
width: 50%;
width: 25%;
.filter {
width: 50%;
width: 100%;
}
.custom-date-filters {

View File

@@ -29,6 +29,7 @@
@import "jquery-ui/sortable";
@import "leaflet";
@import "sticky_overrides";
@import "tags";
@import "admin/*";
@import "sdg/**/*";
@import "sdg_management/*";

View File

@@ -927,25 +927,11 @@ footer {
// 04. Tags
// --------
.tags a ,
.tag-cloud a,
.categories a,
.geozone a,
.sdg-goal-tag-list .more-goals,
.sidebar-links a,
.tags span {
background: #ececec;
border-radius: rem-calc(6);
color: $text;
display: inline-block;
font-size: $small-font-size;
margin-bottom: $line-height / 3;
padding: $line-height / 4 $line-height / 3;
text-decoration: none;
&:hover {
background: #e0e0e0;
}
.sidebar-links a {
@extend %tag;
}
.categories a,

View File

@@ -226,3 +226,51 @@
}
}
}
%sdg-goal-list {
display: flex;
flex-wrap: wrap;
list-style: none;
margin-bottom: 0;
margin-left: 0;
li {
margin-bottom: 1ch;
margin-right: 1ch;
&:last-child {
margin-right: 0;
}
}
}
%tags {
margin-bottom: 0;
margin-left: 0;
li {
display: inline-block;
margin-bottom: 0;
}
a,
span {
@extend %tag;
}
}
%tag {
background: #ececec;
border-radius: rem-calc(6);
color: $text;
display: inline-block;
font-size: $small-font-size;
margin-bottom: $line-height / 3;
padding: $line-height / 4 $line-height / 3;
text-decoration: none;
&:hover {
background: #e0e0e0;
text-decoration: none;
}
}

View File

@@ -408,10 +408,10 @@
}
}
.tags,
.geozone {
li {
display: inline-block;
margin-bottom: 0;
}
}
@@ -487,8 +487,11 @@
}
.tags {
display: block;
margin-bottom: 0;
@extend %tags;
}
.tags,
.sdg-target-tag-list {
a {
margin-right: rem-calc(6);
@@ -673,9 +676,11 @@
}
.tags {
display: block;
margin-bottom: 0;
@extend %tags;
}
.tags,
.sdg-target-tag-list {
a {
font-size: $tiny-font-size;
}

View File

@@ -0,0 +1,6 @@
.sdg-goal-filter-links {
.sdg-goal-tag-list {
@extend %sdg-goal-list;
}
}

View File

@@ -1,21 +1,19 @@
.sdg-goals-index {
.sdg-goal-list {
@extend %sdg-goal-list;
@include grid-row;
@include grid-column-gutter;
list-style: none;
margin-bottom: 0;
max-width: 40rem;
li {
display: inline-block;
$spacing: 1vw;
line-height: 0;
margin-bottom: $spacing;
padding-left: $spacing / 2;
padding-right: $spacing / 2;
width: 1 * 100% / 6;
margin-left: $spacing / 2;
margin-right: $spacing / 2;
width: calc(100% / 6 - #{$spacing});
.sdg-goal-icon {
width: 100%;

View File

@@ -1,14 +0,0 @@
.sdg-goal-tag-list {
list-style: none;
margin-bottom: 0;
margin-left: 0;
li {
display: inline-block;
margin-bottom: 1ch;
&:not(:last-child) {
padding-right: 1ch;
}
}
}

View File

@@ -13,12 +13,9 @@
}
label + ul {
list-style: none;
margin-bottom: 0;
margin-left: 0;
@extend %sdg-goal-list;
li {
display: inline-block;
&[aria-checked=true] img {
opacity: 0.15;

View File

@@ -0,0 +1,28 @@
.sdg-tag-list {
.sdg-goal-tag-list {
@extend %sdg-goal-list;
.more-goals {
@extend %tag;
}
}
.sdg-target-tag-list {
@extend %tags;
a:not(.more-targets) {
color: $white;
}
@each $code, $color in $sdg-colors {
[data-code^="#{$code}"] {
background-color: $color;
&:hover {
background-color: darken($color, 10%);
}
}
}
}
}

View File

@@ -0,0 +1,6 @@
ul {
&.tags {
@extend %tags;
}
}

View File

@@ -1,7 +0,0 @@
module SDG::Goals::OptionsForSelect
extend ActiveSupport::Concern
def goal_options(selected_code = nil)
options_from_collection_for_select(SDG::Goal.order(:code), :code, :code_and_title, selected_code)
end
end

View File

@@ -0,0 +1,13 @@
module SDG::OptionsForSelect
extend ActiveSupport::Concern
def goal_options(selected_code = nil)
options_from_collection_for_select(SDG::Goal.order(:code), :code, :code_and_title, selected_code)
end
def target_options(selected_code = nil)
targets = SDG::Target.all + SDG::LocalTarget.all
options_from_collection_for_select(targets.sort, :code, :code, selected_code)
end
end

View File

@@ -0,0 +1,50 @@
module SDG::TagList
extend ActiveSupport::Concern
attr_reader :record_or_name, :limit
delegate :link_list, to: :helpers
def initialize(record_or_name, limit: nil)
@record_or_name = record_or_name
@limit = limit
end
def render?
process.enabled?
end
def see_more_link(collection)
count = count_out_of_limit(collection)
if count > 0
[
"#{count}+",
polymorphic_path(record),
class: "more-#{i18n_namespace}", title: t("sdg.#{i18n_namespace}.filter.more", count: count)
]
end
end
def filter_text(goal_or_target)
t("sdg.#{i18n_namespace}.filter.link",
resources: model.model_name.human(count: :other),
code: goal_or_target.code)
end
def index_by(advanced_search)
polymorphic_path(model, advanced_search: advanced_search)
end
def count_out_of_limit(collection)
return 0 unless limit
collection.size - limit
end
def process
@process ||= SDG::ProcessEnabled.new(record_or_name)
end
def model
process.name.constantize
end
end

View File

@@ -1,24 +1,18 @@
class SDG::Goals::TagListComponent < ApplicationComponent
attr_reader :record_or_name, :limit
delegate :link_list, to: :helpers
def initialize(record_or_name, limit: nil)
@record_or_name = record_or_name
@limit = limit
end
def render?
process.enabled?
end
include SDG::TagList
private
def record
record_or_name if record_or_name.respond_to?(:sdg_goals)
end
def links
[*goal_links, see_more_link]
[*goal_links, see_more_link(goals)]
end
def goal_links
goals.limit(limit).map do |goal|
goals.order(:code).limit(limit).map do |goal|
[
render(SDG::Goals::IconComponent.new(goal)),
index_by_goal(goal),
@@ -28,42 +22,14 @@ class SDG::Goals::TagListComponent < ApplicationComponent
end
def goals
if record_or_name.respond_to?(:sdg_goals)
record_or_name.sdg_goals.order(:code)
else
SDG::Goal.order(:code)
end
end
def see_more_link
return unless limit && count_out_of_limit > 0
[
"#{count_out_of_limit}+",
polymorphic_path(record_or_name),
class: "more-goals", title: t("sdg.goals.filter.more", count: count_out_of_limit)
]
record&.sdg_goals || SDG::Goal.all
end
def index_by_goal(goal)
polymorphic_path(model, advanced_search: { goal: goal.code })
index_by(goal: goal.code)
end
def filter_text(goal)
t("sdg.goals.filter.link",
resources: model.model_name.human(count: :other),
code: goal.code)
end
def count_out_of_limit
goals.size - limit
end
def model
process.name.constantize
end
def process
@process ||= SDG::ProcessEnabled.new(record_or_name)
def i18n_namespace
"goals"
end
end

View File

@@ -0,0 +1,4 @@
<div class="sdg-tag-list">
<%= render SDG::Goals::TagListComponent.new(record, limit: limit) %>
<%= render SDG::Targets::TagListComponent.new(record, limit: limit) %>
</div>

View File

@@ -0,0 +1,8 @@
class SDG::TagListComponent < ApplicationComponent
attr_reader :record, :limit
def initialize(record, limit: nil)
@record = record
@limit = limit
end
end

View File

@@ -0,0 +1 @@
<%= link_list(*links, class: "sdg-target-tag-list") %>

View File

@@ -0,0 +1,36 @@
class SDG::Targets::TagListComponent < ApplicationComponent
include SDG::TagList
private
def record
record_or_name
end
def links
[*target_links, see_more_link(targets)]
end
def target_links
targets.sort[0..(limit.to_i - 1)].map do |target|
[
"#{SDG::Target.model_name.human} #{target.code}",
index_by_target(target),
title: filter_text(target),
data: { code: target.code }
]
end
end
def targets
record.sdg_targets
end
def index_by_target(target)
index_by(target: target.code)
end
def i18n_namespace
"targets"
end
end

View File

@@ -1,6 +1,6 @@
class SDGManagement::Relations::IndexComponent < ApplicationComponent
include Header
include SDG::Goals::OptionsForSelect
include SDG::OptionsForSelect
delegate :valid_filters, :current_filter, to: :helpers
attr_reader :records
@@ -53,8 +53,6 @@ class SDGManagement::Relations::IndexComponent < ApplicationComponent
end
def target_options
targets = SDG::Target.all + SDG::LocalTarget.all
options_from_collection_for_select(targets.sort, :code, :code, params[:target_code])
super(params[:target_code])
end
end

View File

@@ -14,20 +14,6 @@
placeholder: t("shared.advanced_search.general_placeholder") %>
</div>
<div class="filter">
<label for="advanced_search_official_level"><%= t("shared.advanced_search.author_type") %></label>
<%= select_tag("advanced_search[official_level]", official_level_search_options,
include_blank: t("shared.advanced_search.author_type_blank")) %>
</div>
<% if sdg? %>
<div class="filter">
<label for="advanced_search_goal"><%= t("shared.advanced_search.goal") %></label>
<%= select_tag("advanced_search[goal]", goal_options,
include_blank: t("shared.advanced_search.goal_blank")) %>
</div>
<% end %>
<div class="date-filters">
<div class="filter">
<label for="js-advanced-search-date-min"><%= t("shared.advanced_search.date") %></label>
@@ -56,6 +42,25 @@
</div>
</div>
<div class="filter">
<label for="advanced_search_official_level"><%= t("shared.advanced_search.author_type") %></label>
<%= select_tag("advanced_search[official_level]", official_level_search_options,
include_blank: t("shared.advanced_search.author_type_blank")) %>
</div>
<% if sdg? %>
<div class="filter">
<label for="advanced_search_goal"><%= t("shared.advanced_search.goal") %></label>
<%= select_tag("advanced_search[goal]", goal_options,
include_blank: t("shared.advanced_search.goal_blank")) %>
</div>
<div class="filter">
<label for="advanced_search_target"><%= t("shared.advanced_search.target") %></label>
<%= select_tag("advanced_search[target]", target_options,
include_blank: t("shared.advanced_search.target_blank")) %>
</div>
<% end %>
<div class="submit">
<%= submit_tag t("shared.advanced_search.search"), class: "button expanded" %>
</div>

View File

@@ -1,5 +1,5 @@
class Shared::AdvancedSearchComponent < ApplicationComponent
include SDG::Goals::OptionsForSelect
include SDG::OptionsForSelect
private
@@ -34,6 +34,10 @@ class Shared::AdvancedSearchComponent < ApplicationComponent
super(advanced_search[:goal])
end
def target_options
super(advanced_search[:target])
end
def sdg?
SDG::ProcessEnabled.new(controller_path).enabled?
end

View File

@@ -7,7 +7,7 @@ module LinkListHelper
tag.li(({ "aria-current": true } if current)) do
link_to text, url, link_options
end
end)
end, "\n")
end
end
end

View File

@@ -20,7 +20,7 @@ module Filterable
def allowed_filter?(filter, value)
return if value.blank?
["official_level", "date_range", "goal"].include?(filter)
["official_level", "date_range", "goal", "target"].include?(filter)
end
end
end

View File

@@ -47,9 +47,9 @@
<small><%= t("polls.index.geozone_info") %></small>
</p>
<% end %>
<ul class="no-bullet inline-block tags">
<ul class="tags">
<% poll.geozones.each do |g| %>
<li class="inline-block"><span><%= g.name %></span></li>
<li><span><%= g.name %></span></li>
<% end %>
</ul>
</div>

View File

@@ -12,9 +12,9 @@
<%= auto_link_already_sanitized_html simple_format(@poll.summary) %>
<% if @poll.geozones.any? %>
<ul class="no-bullet margin-top tags">
<ul class="margin-top tags">
<% @poll.geozones.each do |g| %>
<li class="inline-block"><span><%= g.name %></span></li>
<li><span><%= g.name %></span></li>
<% end %>
</ul>
<% end %>

View File

@@ -1,20 +1,20 @@
<%- limit ||= nil %>
<%= render SDG::TagListComponent.new(taggable, limit: limit) %>
<% if taggable.tags.any? %>
<ul id="tags_<%= dom_id(taggable) %>" class="no-bullet tags">
<ul id="tags_<%= dom_id(taggable) %>" class="tags">
<% taggable.tag_list_with_limit(limit).each do |tag| %>
<li class="inline-block">
<li>
<%= link_to sanitize(tag.name),
polymorphic_path(taggable.class, search: tag.name) %></li>
<% end %>
<% if taggable.tags_count_out_of_limit(limit) > 0 %>
<li class="inline-block">
<li>
<%= link_to "#{taggable.tags_count_out_of_limit(limit)}+",
polymorphic_path(taggable) %>
</li>
<% end %>
</ul>
<% end %>
<%= render SDG::Goals::TagListComponent.new(taggable, limit: limit) %>

View File

@@ -2,9 +2,9 @@
<h4><%= interests_title_text(user) %></h4>
<% if user.interests.any? %>
<ul class="no-bullet tags">
<ul class="tags">
<% user.interests.each do |interest| %>
<li class="inline-block"><span><%= interest %></span></li>
<li><span><%= interest %></span></li>
<% end %>
</ul>
<% end %>

View File

@@ -219,6 +219,7 @@ ignore_unused:
- "seeds.settings.*"
- "dashboard.polls.*.submit"
- "sdg.goals.goal_*"
- "sdg.*.filter.more.*"
- "sdg_management.relations.index.filter*"
####
## Exclude these keys from the `i18n-tasks eq-base" report:

View File

@@ -697,6 +697,8 @@ en:
goal: "By SDG"
goal_blank: "Select a goal"
search: "Filter"
target: "By target"
target_blank: "Select a target"
title: "Advanced search"
to: "To"
author_info:

View File

@@ -459,3 +459,9 @@ en:
hint: "You can introduce the code of a specific goal/target or a text to find one"
placeholder: "Write a goal or target code or description"
remove_tag: "Remove"
targets:
filter:
link: "See all %{resources} related to target %{code}"
more:
one: "One more target"
other: "%{count} more targets"

View File

@@ -697,6 +697,8 @@ es:
goal: "Por ODS"
goal_blank: "Elige un objetivo"
search: "Filtrar"
target: "Por meta"
target_blank: "Elige una meta"
title: "Búsqueda avanzada"
to: "Hasta"
author_info:

View File

@@ -456,6 +456,12 @@ es:
goal_list: "Listado de objetivos"
help:
title: "¿Qué ODS y metas se alinean con mi %{record}?"
hint: "Puedes introducir el código de un objetivo/meta específico o un texto para encontrar uno"
hint: "Puedes introducir el código de un objetivo/meta específico o un texto para encontrar uno"
placeholder: "Escribe las etiquetas que desees"
remove_tag: "Eliminar"
targets:
filter:
link: "Ver %{resources} de la meta %{code}"
more:
one: "Una meta más"
other: "%{count} metas más"

View File

@@ -0,0 +1,70 @@
require "rails_helper"
describe SDG::Targets::TagListComponent, type: :component do
let(:debate) do
create(:debate,
sdg_targets: [SDG::Target[1.1], SDG::Target[3.2], create(:sdg_local_target, code: "3.2.1")]
)
end
let(:component) { SDG::Targets::TagListComponent.new(debate) }
before do
Setting["feature.sdg"] = true
Setting["sdg.process.debates"] = true
end
it "does not render when the feature is disabled" do
Setting["feature.sdg"] = false
render_inline component
expect(page).not_to have_css "li"
end
it "does not render when the SDG process feature is disabled" do
Setting["sdg.process.debates"] = false
render_inline component
expect(page).not_to have_css "li"
end
it "renders a list of targets" do
render_inline component
expect(page).to have_css "li", count: 3
end
it "renders links for each target" do
render_inline component
expect(page).to have_css "li", count: 3
expect(page).to have_link "target 1.1",
title: "See all Debates related to target 1.1",
href: "/debates?advanced_search#{CGI.escape("[target]")}=1.1"
expect(page).to have_link "target 3.2",
title: "See all Debates related to target 3.2",
href: "/debates?advanced_search#{CGI.escape("[target]")}=3.2"
expect(page).to have_link "target 3.2.1",
title: "See all Debates related to target 3.2.1",
href: "/debates?advanced_search#{CGI.escape("[target]")}=3.2.1"
end
it "orders targets by code" do
render_inline component
expect(page.first("a")[:title]).to end_with "target 1.1"
end
it "renders a link for more targets when out of limit" do
component = SDG::Targets::TagListComponent.new(debate, limit: 1)
render_inline component
expect(page).to have_selector "a", count: 2
expect(page).to have_link "target 1.1"
expect(page).to have_link "2+",
title: "2 more targets",
href: "/debates/#{debate.to_param}"
end
end

View File

@@ -17,6 +17,7 @@ describe Shared::AdvancedSearchComponent, type: :component do
render_inline component
expect(page).not_to have_selector "#advanced_search_goal", visible: :all
expect(page).not_to have_selector "#advanced_search_target", visible: :all
end
it "does not render when the SDG process feature is disabled" do
@@ -25,12 +26,14 @@ describe Shared::AdvancedSearchComponent, type: :component do
render_inline component
expect(page).not_to have_selector "#advanced_search_goal", visible: :all
expect(page).not_to have_selector "#advanced_search_target", visible: :all
end
it "renders when both features are enabled" do
render_inline component
expect(page).to have_selector "#advanced_search_goal", visible: :all
expect(page).to have_selector "#advanced_search_target", visible: :all
end
end
end

View File

@@ -13,7 +13,9 @@ describe LinkListHelper do
it "generates a list of links" do
list = helper.link_list(["Home", "/"], ["Info", "/info"], class: "menu")
expect(list).to eq '<ul class="menu"><li><a href="/">Home</a></li><li><a href="/info">Info</a></li></ul>'
expect(list).to eq '<ul class="menu">' +
'<li><a href="/">Home</a></li>' + "\n" +
'<li><a href="/info">Info</a></li></ul>'
expect(list).to be_html_safe
end

View File

@@ -500,6 +500,27 @@ describe "Budget Investments" do
end
end
end
scenario "Search by SDG target", :js do
Setting["feature.sdg"] = true
Setting["sdg.process.budgets"] = true
create(:budget_investment, heading: heading, title: "Unrelated")
create(:budget_investment, heading: heading, title: "High school", sdg_targets: [SDG::Target["4.1"]])
create(:budget_investment, heading: heading, title: "Preschool", sdg_targets: [SDG::Target["4.2"]])
visit budget_investments_path(budget)
click_link "Advanced search"
select "4.2", from: "By target"
click_button "Filter"
expect(page).to have_content("There is 1 investment")
within("#budget-investments") do
expect(page).to have_content("Preschool")
expect(page).not_to have_content("High school")
expect(page).not_to have_content("Unrelated")
end
end
end
context("Filters") do

View File

@@ -866,6 +866,27 @@ describe "Debates" do
end
end
end
scenario "Search by SDG target", :js do
Setting["feature.sdg"] = true
Setting["sdg.process.debates"] = true
create(:debate, title: "Unrelated")
create(:debate, title: "High school", sdg_targets: [SDG::Target["4.1"]])
create(:debate, title: "Preschool", sdg_targets: [SDG::Target["4.2"]])
visit debates_path
click_link "Advanced search"
select "4.2", from: "By target"
click_button "Filter"
expect(page).to have_content("There is 1 debate")
within("#debates") do
expect(page).to have_content("Preschool")
expect(page).not_to have_content("High school")
expect(page).not_to have_content("Unrelated")
end
end
end
scenario "Order by relevance by default", :js do

View File

@@ -1482,6 +1482,27 @@ describe "Proposals" do
end
end
end
scenario "Search by SDG target", :js do
Setting["feature.sdg"] = true
Setting["sdg.process.proposals"] = true
create(:proposal, title: "Unrelated")
create(:proposal, title: "High school", sdg_targets: [SDG::Target["4.1"]])
create(:proposal, title: "Preschool", sdg_targets: [SDG::Target["4.2"]])
visit proposals_path
click_link "Advanced search"
select "4.2", from: "By target"
click_button "Filter"
expect(page).to have_content("There is 1 citizen proposal")
within("#proposals") do
expect(page).to have_content("Preschool")
expect(page).not_to have_content("High school")
expect(page).not_to have_content("Unrelated")
end
end
end
scenario "Order by relevance by default", :js do