Merge branch 'master' into polls_minor_changes

This commit is contained in:
BertoCQ
2017-10-02 16:44:53 +02:00
committed by GitHub
60 changed files with 1219 additions and 204 deletions

View File

@@ -59,7 +59,7 @@ end
group :development, :test do
gem "bullet", '~> 5.5.1'
gem 'byebug', '~> 9.0.6'
gem 'byebug', '~> 9.1.0'
gem 'factory_girl_rails', '~> 4.8.0'
gem "faker", '~> 1.7.3'
gem 'i18n-tasks', '~> 0.9.15'

View File

@@ -71,7 +71,7 @@ GEM
bullet (5.5.1)
activesupport (>= 3.0.0)
uniform_notifier (~> 1.10.0)
byebug (9.0.6)
byebug (9.1.0)
cancancan (1.16.0)
capistrano (3.8.2)
airbrussh (>= 1.0.0)
@@ -251,7 +251,7 @@ GEM
mime-types-data (~> 3.2015)
mime-types-data (3.2016.0521)
mimemagic (0.3.2)
mini_portile2 (2.2.0)
mini_portile2 (2.3.0)
minitest (5.10.3)
mixlib-cli (1.7.0)
mixlib-config (2.2.4)
@@ -262,8 +262,8 @@ GEM
net-ssh (>= 2.6.5)
net-ssh (4.1.0)
newrelic_rpm (4.1.0.333)
nokogiri (1.8.0)
mini_portile2 (~> 2.2.0)
nokogiri (1.8.1)
mini_portile2 (~> 2.3.0)
nori (2.6.0)
oauth (0.5.3)
oauth2 (1.4.0)
@@ -494,7 +494,7 @@ DEPENDENCIES
ancestry (~> 2.2.2)
browser (~> 2.3.0)
bullet (~> 5.5.1)
byebug (~> 9.0.6)
byebug (~> 9.1.0)
cancancan (~> 1.16.0)
capistrano (~> 3.8.1)
capistrano-bundler (~> 1.2)

View File

@@ -411,7 +411,7 @@ $maincontent-shadow: 0 0 10px rgba($black, 0.5);
$orbit-bullet-background: $medium-gray;
$orbit-bullet-background-active: $dark-gray;
$orbit-bullet-diameter: 1.2rem;
$orbit-bullet-diameter: 0.8rem;
$orbit-bullet-margin: 0.1rem;
$orbit-bullet-margin-top: 0.8rem;
$orbit-bullet-margin-bottom: 0.8rem;

View File

@@ -18,7 +18,8 @@
// 16. Flags
// 17. Activity
// 18. Banners
// 19. Documents
// 19. Recommended Section Home
// 20. Documents
//
// 01. Global styles
@@ -341,6 +342,14 @@ a {
background: $brand;
}
.truncate-horizontal-text {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
-o-text-overflow: ellipsis;
-ms-text-overflow: ellipsis;
}
.align-top {
vertical-align: top;
}
@@ -612,7 +621,7 @@ header {
text-align: left;
@include breakpoint(medium) {
margin-right: $line-height * 1.5;
margin-right: rem-calc(24);
}
&:hover {
@@ -2161,6 +2170,142 @@ table {
}
}
// 19. Recommended Section Home
// -----------
.home-page {
.push {
display: none;
}
}
.section-recommended {
padding: $line-height * 2 0;
h2 {
margin-bottom: $line-height * 2;
}
.debates,
.proposals,
.budget-investments {
@include breakpoint(medium) {
margin-bottom: 0;
}
@include breakpoint(small) {
margin-bottom: $line-height;
}
.button.hollow {
margin-top: rem-calc(15);
}
}
.card {
.card-section {
padding: $line-height 0;
max-width: 300px;
margin: 0 auto;
p {
font-size: rem-calc(15);
text-align: left;
}
}
.orbit {
height: 300px;
.orbit-wrapper {
max-height: 250px;
overflow: hidden;
position: relative;
}
.orbit-bullets {
@include orbit-bullets;
width: 100%;
}
}
}
.card .orbit .orbit-wrapper .truncate {
background: image-url('truncate.png');
background-repeat: repeat-x;
bottom: 0;
height: 20px;
position: absolute;
width: 100%;
}
.debates-inner {
border-top: 4px solid $debates;
}
.proposals-inner {
border-top: 4px solid $proposals;
}
.budget-investments-inner {
border-top: 4px solid $budget;
}
.debates-inner,
.proposals-inner,
.budget-investments-inner {
background: #fff;
max-height: 350px;
@include breakpoint(small) {
max-height: 400px;
}
h4 {
margin-top: $line-height;
margin-bottom: 0;
font-size: rem-calc(18);
min-height: 50px;
}
h5 {
font-size: rem-calc(14);
text-align: left;
}
}
.carousel-image {
.card .orbit {
height: 480px;
.orbit-wrapper {
max-height: 450px;
}
}
.debates-inner,
.proposals-inner,
.budget-investments-inner {
max-height: 500px;
@include breakpoint(small) {
max-height: 600px;
}
}
}
.carousel-image .orbit-wrapper img {
display: block;
@include breakpoint(small) {
margin: 0 auto;
}
}
}
// 19. Documents
.documents-list {
@@ -2218,6 +2363,5 @@ table {
}
}
}
}

View File

@@ -1,6 +1,7 @@
// Table of Contents
//
// 01. Logo
// 02. Orbit bullets
//
// 01. Logo
@@ -31,7 +32,35 @@
}
}
// 02. Orbit bullet
// ----------------
@mixin orbit-bullets {
@include disable-mouse-outline;
position: relative;
margin-top: $orbit-bullet-margin-top;
margin-bottom: $orbit-bullet-margin-bottom;
text-align: center;
button {
width: $orbit-bullet-diameter;
height: $orbit-bullet-diameter;
margin: $orbit-bullet-margin;
border-radius: 50%;
background-color: $orbit-bullet-background;
&:hover {
background-color: $orbit-bullet-background-active;
}
&.is-active {
background-color: $orbit-bullet-background-active;
}
}
}
// 02. Direct uploads
// ------------------
@mixin direct-uploads {
.cached-image {
@@ -96,4 +125,5 @@
.loading-bar.no-transition {
transition: none;
}
}

View File

@@ -15,7 +15,7 @@ class Admin::Poll::BoothAssignmentsController < Admin::Poll::BaseController
end
def show
@booth_assignment = @poll.booth_assignments.includes(:total_results, :voters,
@booth_assignment = @poll.booth_assignments.includes(:recounts, :voters,
officer_assignments: [officer: [:user]]).find(params[:id])
@voters_by_date = @booth_assignment.voters.group_by {|v| v.created_at.to_date}
end

View File

@@ -18,7 +18,7 @@ class Admin::Poll::OfficerAssignmentsController < Admin::Poll::BaseController
@officer = ::Poll::Officer.includes(:user).find(officer_assignment_params[:officer_id])
@officer_assignments = ::Poll::OfficerAssignment.
joins(:booth_assignment).
includes(:total_results, booth_assignment: :booth).
includes(:recounts, booth_assignment: :booth).
where("officer_id = ? AND poll_booth_assignments.poll_id = ?", @officer.id, @poll.id).
order(:date)
end

View File

@@ -3,7 +3,7 @@ class Admin::Poll::RecountsController < Admin::Poll::BaseController
def index
@booth_assignments = @poll.booth_assignments.
includes(:booth, :total_results, :voters).
includes(:booth, :recounts, :voters).
order("poll_booths.name").
page(params[:page]).per(50)
end

View File

@@ -4,17 +4,22 @@ module CommentableActions
include Search
def index
@resources = @search_terms.present? ? resource_model.search(@search_terms) : resource_model.all
@resources = @advanced_search_terms.present? ? @resources.filter(@advanced_search_terms) : @resources
@resources = resource_model.all
@resources = @current_order == "recommendations" && current_user.present? ? @resources.recommendations(current_user) : @resources.for_render
@resources = @resources.search(@search_terms) if @search_terms.present?
@resources = @advanced_search_terms.present? ? @resources.filter(@advanced_search_terms) : @resources
@resources = @resources.tagged_with(@tag_filter) if @tag_filter
@resources = @resources.page(params[:page]).for_render.send("sort_by_#{@current_order}")
@resources = @resources.page(params[:page]).send("sort_by_#{@current_order}")
index_customization if index_customization.present?
@tag_cloud = tag_cloud
@banners = Banner.with_active
set_resource_votes(@resources)
set_resources_instance
end

View File

@@ -10,7 +10,7 @@ class DebatesController < ApplicationController
invisible_captcha only: [:create, :update], honeypot: :subtitle
has_orders %w{hot_score confidence_score created_at relevance}, only: :index
has_orders ->(c) { Debate.debates_orders(c.current_user) }, only: :index
has_orders %w{most_voted newest oldest}, only: :show
load_and_authorize_resource

View File

@@ -11,7 +11,7 @@ class NotificationsController < ApplicationController
def show
@notification = current_user.notifications.find(params[:id])
redirect_to url_for(@notification.linkable_resource)
redirect_to linkable_resource_path(@notification)
end
def mark_all_as_read
@@ -25,4 +25,13 @@ class NotificationsController < ApplicationController
@notification.mark_as_read
end
def linkable_resource_path(notification)
case notification.linkable_resource.class.name
when "Budget::Investment"
budget_investment_path @notification.linkable_resource.budget, @notification.linkable_resource
else
url_for @notification.linkable_resource
end
end
end

View File

@@ -26,9 +26,7 @@ class Officing::ResultsController < Officing::BaseController
@partial_results = ::Poll::PartialResult.includes(:question).
where(booth_assignment_id: index_params[:booth_assignment_id]).
where(date: index_params[:date])
@whites = ::Poll::WhiteResult.where(booth_assignment_id: @booth_assignment.id, date: index_params[:date]).sum(:amount)
@nulls = ::Poll::NullResult.where(booth_assignment_id: @booth_assignment.id, date: index_params[:date]).sum(:amount)
@total = ::Poll::TotalResult.where(booth_assignment_id: @booth_assignment.id, date: index_params[:date]).sum(:amount)
@recounts = ::Poll::Recount.where(booth_assignment_id: @booth_assignment.id, date: index_params[:date])
end
end
@@ -52,14 +50,14 @@ class Officing::ResultsController < Officing::BaseController
go_back_to_new if question.blank?
results.each_pair do |answer_index, count|
next unless count.present?
next if count.blank?
answer = question.valid_answers[answer_index.to_i]
go_back_to_new if question.blank?
partial_result = ::Poll::PartialResult.find_or_initialize_by(booth_assignment_id: @officer_assignment.booth_assignment_id,
date: results_params[:date],
question_id: question_id,
answer: answer)
date: results_params[:date],
question_id: question_id,
answer: answer)
partial_result.officer_assignment_id = @officer_assignment.id
partial_result.amount = count.to_i
partial_result.author = current_user
@@ -68,45 +66,21 @@ class Officing::ResultsController < Officing::BaseController
end
end
build_white_results
build_null_results
build_total_results
build_recounts
end
def build_white_results
if results_params[:whites].present?
white_result = ::Poll::WhiteResult.find_or_initialize_by(booth_assignment_id: @officer_assignment.booth_assignment_id,
date: results_params[:date])
white_result.officer_assignment_id = @officer_assignment.id
white_result.amount = results_params[:whites].to_i
white_result.author = current_user
white_result.origin = 'booth'
@results << white_result
end
end
def build_null_results
if results_params[:nulls].present?
null_result = ::Poll::NullResult.find_or_initialize_by(booth_assignment_id: @officer_assignment.booth_assignment_id,
date: results_params[:date])
null_result.officer_assignment_id = @officer_assignment.id
null_result.amount = results_params[:nulls].to_i
null_result.author = current_user
null_result.origin = 'booth'
@results << null_result
end
end
def build_total_results
if results_params[:total].present?
total_result = ::Poll::TotalResult.find_or_initialize_by(booth_assignment_id: @officer_assignment.booth_assignment_id,
date: results_params[:date])
total_result.officer_assignment_id = @officer_assignment.id
total_result.amount = results_params[:total].to_i
total_result.author = current_user
total_result.origin = 'booth'
@results << total_result
def build_recounts
recount = ::Poll::Recount.find_or_initialize_by(booth_assignment_id: @officer_assignment.booth_assignment_id,
date: results_params[:date])
recount.officer_assignment_id = @officer_assignment.id
recount.author = current_user
recount.origin = 'booth'
[:whites, :nulls, :total].each do |recount_type|
if results_params[recount_type].present?
recount["#{recount_type.to_s.singularize}_amount"] = results_params[recount_type].to_i
end
end
@results << recount
end
def go_back_to_new(alert = nil)

View File

@@ -9,7 +9,7 @@ class ProposalsController < ApplicationController
invisible_captcha only: [:create, :update], honeypot: :subtitle
has_orders %w{hot_score confidence_score created_at relevance archival_date}, only: :index
has_orders ->(c) { Proposal.proposals_orders(c.current_user) }, only: :index
has_orders %w{most_voted newest oldest}, only: :show
load_and_authorize_resource
@@ -113,7 +113,7 @@ class ProposalsController < ApplicationController
end
def load_featured
return unless !@advanced_search_terms && @search_terms.blank? && @tag_filter.blank? && params[:retired].blank?
return unless !@advanced_search_terms && @search_terms.blank? && @tag_filter.blank? && params[:retired].blank? && @current_order != "recommendations"
@featured_proposals = Proposal.not_archived.sort_by_confidence_score.limit(3)
if @featured_proposals.present?
set_featured_proposal_votes(@featured_proposals)

View File

@@ -1,12 +1,10 @@
class WelcomeController < ApplicationController
skip_authorization_check
before_action :set_user_recommendations, only: :index, if: :current_user
layout "devise", only: [:welcome, :verification]
def index
if current_user
redirect_to :proposals
end
end
def welcome
@@ -16,4 +14,11 @@ class WelcomeController < ApplicationController
redirect_to verification_path if signed_in?
end
private
def set_user_recommendations
@recommended_debates = Debate.recommendations(current_user).sort_by_recommendations.limit(3)
@recommended_proposals = Proposal.recommendations(current_user).sort_by_recommendations.limit(3)
end
end

View File

@@ -4,4 +4,12 @@ module DebatesHelper
Debate.all.featured.count > 0
end
end
def empty_recommended_debates_message_text(user)
if user.interests.any?
t('debates.index.recommendations.without_results')
else
t('debates.index.recommendations.without_interests')
end
end
end

View File

@@ -1,7 +1,7 @@
module PollRecountsHelper
def total_recounts_by_booth(booth_assignment)
booth_assignment.total_results.any? ? booth_assignment.total_results.to_a.sum(&:amount) : nil
booth_assignment.recounts.any? ? booth_assignment.recounts.to_a.sum(&:total_amount) : nil
end
end

View File

@@ -32,6 +32,14 @@ module ProposalsHelper
Proposal::RETIRE_OPTIONS.collect { |option| [ t("proposals.retire_options.#{option}"), option ] }
end
def empty_recommended_proposals_message_text(user)
if user.interests.any?
t('proposals.index.recommendations.without_results')
else
t('proposals.index.recommendations.without_interests')
end
end
def author_of_proposal?(proposal)
author_of?(proposal, current_user)
end
@@ -40,4 +48,4 @@ module ProposalsHelper
current_user && proposal.editable_by?(current_user)
end
end
end

View File

@@ -5,7 +5,7 @@ module ShiftsHelper
end
def shift_recount_scrutiny_dates(polls)
date_options(polls.map(&:ends_at).map(&:to_date).inject([]) { |total, date| total << (date..date + 1.week).to_a }.flatten.uniq)
date_options(polls.map(&:ends_at).map(&:to_date).sort.inject([]) { |total, date| total << (date..date + 1.week).to_a }.flatten.uniq)
end
def date_options(dates)

View File

@@ -0,0 +1,62 @@
module WelcomeHelper
def active_class(index)
"is-active is-in" if index == 0
end
def slide_display(index)
"display: none;" if index > 0
end
def recommended_path(recommended)
case recommended.class.name
when "Debate"
debate_path(recommended)
when "Proposal"
proposal_path(recommended)
else
'#'
end
end
def render_recommendation_image(recommended, image_default)
image_path = calculate_image_path(recommended, image_default)
image_tag(image_path) if image_path.present?
end
def calculate_image_path(recommended, image_default)
if recommended.try(:image) && recommended.image.present? && recommended.image.attachment.exists?
recommended.image.attachment.send("url", :medium)
elsif image_default.present?
image_default
end
end
def calculate_carousel_size(debates, proposals, apply_offset)
offset = calculate_offset(debates, proposals, apply_offset)
centered = calculate_centered(debates, proposals)
"#{offset if offset} #{centered if centered}"
end
def calculate_centered(debates, proposals)
if (debates.blank? && proposals.any?) ||
(debates.any? && proposals.blank?)
centered = "medium-centered large-centered"
end
end
def calculate_offset(debates, proposals, apply_offset)
if (debates.any? && proposals.any?)
if apply_offset
offset = "medium-offset-2 large-offset-2"
else
offset = "end"
end
end
end
def highlight_background
(feature?("user.recommendations") && current_user) ? "highlight" : ""
end
end

View File

@@ -4,6 +4,10 @@ module Followable
included do
has_many :follows, as: :followable, dependent: :destroy
has_many :followers, through: :follows, source: :user
scope :followed_by_user, -> (user){
joins(:follows).where("follows.user_id = ?", user.id)
}
end
def followed_by?(user)

View File

@@ -37,14 +37,21 @@ class Debate < ActiveRecord::Base
scope :sort_by_random, -> { reorder("RANDOM()") }
scope :sort_by_relevance, -> { all }
scope :sort_by_flags, -> { order(flags_count: :desc, updated_at: :desc) }
scope :sort_by_recommendations, -> { order(cached_votes_total: :desc) }
scope :last_week, -> { where("created_at >= ?", 7.days.ago)}
scope :featured, -> { where("featured_at is not null")}
scope :public_for_api, -> { all }
# Ahoy setup
visitable # Ahoy will automatically assign visit_id on create
attr_accessor :link_required
def self.recommendations(user)
tagged_with(user.interests, any: true).
where("author_id != ?", user.id)
end
def searchable_values
{ title => 'A',
author.username => 'B',
@@ -135,4 +142,9 @@ class Debate < ActiveRecord::Base
featured_at.present?
end
def self.debates_orders(user)
orders = %w{hot_score confidence_score created_at relevance}
orders << "recommendations" if user.present?
orders
end
end

View File

@@ -2,9 +2,7 @@ class Poll < ActiveRecord::Base
has_many :booth_assignments, class_name: "Poll::BoothAssignment"
has_many :booths, through: :booth_assignments
has_many :partial_results, through: :booth_assignments
has_many :white_results, through: :booth_assignments
has_many :null_results, through: :booth_assignments
has_many :total_results, through: :booth_assignments
has_many :recounts, through: :booth_assignments
has_many :voters
has_many :officer_assignments, through: :booth_assignments
has_many :officers, through: :officer_assignments
@@ -16,23 +14,23 @@ class Poll < ActiveRecord::Base
validate :date_range
scope :current, -> { where('starts_at <= ? and ? <= ends_at', Time.current, Time.current) }
scope :incoming, -> { where('? < starts_at', Time.current) }
scope :expired, -> { where('ends_at < ?', Time.current) }
scope :current, -> { where('starts_at <= ? and ? <= ends_at', Date.current.beginning_of_day, Date.current.beginning_of_day) }
scope :incoming, -> { where('? < starts_at', Date.current.beginning_of_day) }
scope :expired, -> { where('ends_at < ?', Date.current.beginning_of_day) }
scope :published, -> { where('published = ?', true) }
scope :by_geozone_id, ->(geozone_id) { where(geozones: {id: geozone_id}.joins(:geozones)) }
scope :sort_for_list, -> { order(:geozone_restricted, :starts_at, :name) }
def current?(timestamp = DateTime.current)
def current?(timestamp = Date.current.beginning_of_day)
starts_at <= timestamp && timestamp <= ends_at
end
def incoming?(timestamp = DateTime.current)
def incoming?(timestamp = Date.current.beginning_of_day)
timestamp < starts_at
end
def expired?(timestamp = DateTime.current)
def expired?(timestamp = Date.current.beginning_of_day)
ends_at < timestamp
end

View File

@@ -7,8 +7,6 @@ class Poll
has_many :officers, through: :officer_assignments
has_many :voters
has_many :partial_results
has_many :white_results
has_many :null_results
has_many :total_results
has_many :recounts
end
end

View File

@@ -3,9 +3,7 @@ class Poll
belongs_to :officer
belongs_to :booth_assignment
has_many :partial_results
has_many :white_results
has_many :null_results
has_many :total_results
has_many :recounts
has_many :voters
validates :officer_id, presence: true

View File

@@ -0,0 +1,36 @@
class Poll::Recount < ActiveRecord::Base
VALID_ORIGINS = %w{ web booth letter }
belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id'
belongs_to :booth_assignment
belongs_to :officer_assignment
validates :author, presence: true
validates :origin, inclusion: {in: VALID_ORIGINS}
scope :web, -> { where(origin: 'web') }
scope :booth, -> { where(origin: 'booth') }
scope :letter, -> { where(origin: 'letter') }
scope :by_author, ->(author_id) { where(author_id: author_id) }
before_save :update_logs
def update_logs
amounts_changed = false
[:white, :null, :total].each do |amount|
next unless send("#{amount}_amount_changed?") && send("#{amount}_amount_was").present?
self["#{amount}_amount_log"] += ":#{send("#{amount}_amount_was").to_s}"
amounts_changed = true
end
update_officer_author if amounts_changed
end
def update_officer_author
self.officer_assignment_id_log += ":#{officer_assignment_id_was.to_s}"
self.author_id_log += ":#{author_id_was.to_s}"
end
end

View File

@@ -58,14 +58,27 @@ class Proposal < ActiveRecord::Base
scope :sort_by_relevance, -> { all }
scope :sort_by_flags, -> { order(flags_count: :desc, updated_at: :desc) }
scope :sort_by_archival_date, -> { archived.sort_by_confidence_score }
scope :sort_by_recommendations, -> { order(cached_votes_up: :desc) }
scope :archived, -> { where("proposals.created_at <= ?", Setting["months_to_archive_proposals"].to_i.months.ago) }
scope :not_archived, -> { where("proposals.created_at > ?", Setting["months_to_archive_proposals"].to_i.months.ago) }
scope :last_week, -> { where("proposals.created_at >= ?", 7.days.ago)}
scope :retired, -> { where.not(retired_at: nil) }
scope :not_retired, -> { where(retired_at: nil) }
scope :successful, -> { where("cached_votes_up >= ?", Proposal.votes_needed_for_success) }
scope :unsuccessful, -> { where("cached_votes_up < ?", Proposal.votes_needed_for_success) }
scope :public_for_api, -> { all }
def self.recommendations(user)
tagged_with(user.interests, any: true).
where("author_id != ?", user.id).
unsuccessful.
not_followed_by_user(user)
end
def self.not_followed_by_user(user)
where.not(id: followed_by_user(user).pluck(:id))
end
def to_param
"#{id}-#{title}".parameterize
end
@@ -185,6 +198,12 @@ class Proposal < ActiveRecord::Base
(voters + followers).uniq
end
def self.proposals_orders(user)
orders = %w{hot_score confidence_score created_at relevance archival_date}
orders << "recommendations" if user.present?
orders
end
protected
def set_responsible_name

View File

@@ -88,12 +88,12 @@ class User < ActiveRecord::Base
end
def debate_votes(debates)
voted = votes.for_debates(debates)
voted = votes.for_debates(Array(debates).map(&:id))
voted.each_with_object({}) { |v, h| h[v.votable_id] = v.value }
end
def proposal_votes(proposals)
voted = votes.for_proposals(proposals)
voted = votes.for_proposals(Array(proposals).map(&:id))
voted.each_with_object({}) { |v, h| h[v.votable_id] = v.value }
end

View File

@@ -18,9 +18,9 @@
</thead>
<tbody>
<tr>
<td id="white_results"><%= @poll.white_results.sum(:amount) %></td>
<td id="null_results"><%= @poll.null_results.sum(:amount) %></td>
<td id="total_results"><%= @poll.total_results.sum(:amount) %></td>
<td id="white_results"><%= @poll.recounts.sum(:white_amount) %></td>
<td id="null_results"><%= @poll.recounts.sum(:null_amount) %></td>
<td id="total_results"><%= @poll.recounts.sum(:total_amount) %></td>
</tr>
</tbody>
</table>

View File

@@ -54,7 +54,11 @@
<%= link_to t("debates.index.start_debate"), new_debate_path, class: 'button expanded' %>
</div>
<%= render @debates %>
<% if @debates.any? || current_user.blank? %>
<%= render @debates %>
<% else %>
<%= empty_recommended_debates_message_text(current_user) %>
<% end %>
<%= paginate @debates %>
<% unless @search_terms || @advanced_search_terms || @tag_filter %>

View File

@@ -23,7 +23,7 @@
<%= setting['per_page_code_head'].try(:html_safe) %>
</head>
<body>
<body class="<%= yield (:body_class) %>">
<%= setting['per_page_code_body'].try(:html_safe) %>
<h1 class="show-for-sr"><%= setting['org_name'] %></h1>

View File

@@ -21,9 +21,9 @@
</thead>
<tbody>
<tr>
<td id="white_results"><%= @whites %></td>
<td id="null_results"><%= @nulls %></td>
<td id="total_results"><%= @total %></td>
<td id="white_results"><%= @recounts.sum(:white_amount) %></td>
<td id="null_results"><%= @recounts.sum(:null_amount) %></td>
<td id="total_results"><%= @recounts.sum(:total_amount) %></td>
</tr>
</tbody>
</table>

View File

@@ -14,13 +14,13 @@
<div class="row">
<div class="small-12 column">
<% if @search_terms || @advanced_search_terms %>
<h2><%= t("shared.search_results") %></h2>
<p>
<%= page_entries_info @proposals %>
<% if !@advanced_search_terms %>
<%= t("proposals.index.search_results_html", count: @proposals.size, search_term: @search_terms) %>
<% end %>
<p>
<h2><%= t("shared.search_results") %></h2>
<p>
<%= page_entries_info @proposals %>
<% if !@advanced_search_terms %>
<%= t("proposals.index.search_results_html", count: @proposals.size, search_term: @search_terms) %>
<% end %>
<p>
<% elsif @tag_filter %>
<h2><%= t("shared.search_results") %></h2>
<p>
@@ -68,7 +68,11 @@
<% end %>
<div id="proposals-list">
<%= render partial: 'proposals/proposal', collection: @proposals %>
<% if @proposals.any? || current_user.blank? %>
<%= render partial: 'proposals/proposal', collection: @proposals %>
<% else %>
<%= empty_recommended_proposals_message_text(current_user) %>
<% end %>
<%= paginate @proposals %>
<% unless @search_terms || @advanced_search_terms || @tag_filter %>

View File

@@ -0,0 +1,31 @@
<div class="small-12 column section-recommended padding">
<div class="row">
<h2 class="text-center"><%= t("welcome.recommended.title") %></h2>
<div class="small-12 column carousel-image">
<% if recommended_debates.any? %>
<% carousel_size = calculate_carousel_size(recommended_debates, recommended_proposals, true) %>
<%= render "recommended_carousel", recommendeds: recommended_debates,
key: "debates",
image_field: nil,
image_version: nil,
image_default: nil,
carousel_size: carousel_size,
btn_text_link: t("welcome.recommended.debates.btn_text_link"),
btn_path_link: debates_path(order: "recommendations") %>
<% end %>
<% if recommended_proposals.any? %>
<% carousel_size = calculate_carousel_size(recommended_debates, recommended_proposals, false) %>
<%= render "recommended_carousel", recommendeds: recommended_proposals,
key: "proposals",
image_field: :attachment,
image_version: :thumb,
image_default: nil,
carousel_size: carousel_size,
btn_text_link: t("welcome.recommended.proposals.btn_text_link"),
btn_path_link: proposals_path(order: "recommendations") %>
<% end %>
</div>
</div>
</div>

View File

@@ -0,0 +1,42 @@
<div class="small-12 medium-4 large-4 <%= carousel_size %> column text-center <%= key %> ">
<div class="card small-centered <%= key %>-inner">
<h4><%= t("welcome.recommended.#{key.underscore}.title") %></h4>
<div class="orbit" role="region" data-orbit data-use-m-u-i="false">
<div class="orbit-wrapper">
<ul class="orbit-container no-bullet" tabindex="0" >
<% recommendeds.each_with_index do |recommended, index| %>
<li class="orbit-slide <%= active_class(index) %>" data-slide="<%= index %>" style="position: relative; <%= slide_display(index) %>" aria-live="polite">
<div class="card">
<%= render_recommendation_image(recommended, image_default) %>
<div class="card-section">
<%= link_to recommended_path(recommended) do %>
<h5 class="truncate-horizontal-text"><%= recommended.title %></h5>
<% end %>
<p><%= recommended.description %></p>
</div>
</div>
</li>
<% end %>
</ul>
<div class="truncate"></div>
</div>
<nav class="orbit-bullets">
<% recommendeds.each_with_index do |recommended, index| %>
<button data-slide="<%= index %>" class="<%= active_class(index) %>">
<span class="show-for-sr">Second slide details.</span>
</button>
<% end %>
</nav>
</div>
</div>
<%= link_to btn_text_link, btn_path_link, class: 'button hollow expanded' %>
</div>

View File

@@ -1,3 +1,5 @@
<% content_for :body_class, "home-page" %>
<% content_for :canonical do %>
<%= render "shared/canonical", href: root_url %>
<% end %>
@@ -16,37 +18,35 @@
</div>
</div>
<% if feature?("user.recommendations") && (@recommended_debates.present? || @recommended_proposals.present?) %>
<%= render "recommended",
recommended_debates: @recommended_debates,
recommended_proposals: @recommended_proposals %>
<% end %>
<% cache [locale_and_user_status, @featured_debates, @featured_proposals, 'featured'] do %>
<main>
<div class="row">
<div class="small-12 medium-6 column">
<p>
<span class="lead"><strong><%= t("welcome.debates.title") %></strong></span><br>
<%= t("welcome.debates.description") %>
</p>
</div>
<div class="small-12 medium-6 column">
<p>
<span class="lead"><strong><%= t("welcome.proposal.title") %></strong></span><br>
<%= t("welcome.proposal.description") %>
</p>
<div class="small-12 column text-center <%= highlight_background %>">
<div class="row margin padding">
<div class="small-12 medium-3 column">
<h2><%= t("welcome.debates.title") %></h2>
<p><%= t("welcome.debates.description") %></p>
</div>
<div class="small-12 medium-3 column">
<h2><%= t("welcome.proposal.title") %></h2>
<p><%= t("welcome.proposal.description") %></p>
</div>
<div class="small-12 medium-3 column">
<h2><%= t("welcome.decide.title") %></h2>
<p><%= t("welcome.decide.description") %></p>
</div>
<div class="small-12 medium-3 column">
<h2><%= t("welcome.do.title") %></h2>
<p><%= t("welcome.do.description") %></p>
</div>
</div>
</div>
<div class="row margin-top">
<div class="small-12 medium-6 column">
<p>
<span class="lead"><strong><%= t("welcome.decide.title") %></strong></span><br>
<%= t("welcome.decide.description") %>
</p>
</div>
<div class="small-12 medium-6 column">
<p>
<span class="lead"><strong><%= t("welcome.do.title") %></strong></span><br>
<%= t("welcome.do.description") %>
</p>
</div>
</div>
</main>
<% end %>

View File

@@ -109,6 +109,10 @@ en:
hot_score: most active
most_commented: most commented
relevance: relevance
recommendations: recommendations
recommendations:
without_results: There are not debates related to your interests
without_interests: Follow proposals so we can give you recommendations
search_form:
button: Search
placeholder: Search debates...
@@ -348,6 +352,10 @@ en:
most_commented: most commented
relevance: relevance
archival_date: Archived
recommendations: recommendations
recommendations:
without_results: There are not proposals related to your interests
without_interests: Follow proposals so we can give you recommendations
retired_proposals: Retired proposals
retired_proposals_link: "Proposals retired by the author"
retired_links:
@@ -753,6 +761,16 @@ en:
proposal:
description: Open space for citizen proposals about the kind of city we want to live in.
title: You propose
recommended:
title: Recommendations that may interest you
debates:
title: Recommended debates
btn_text_link: All recommended debates
proposals:
title: Recommended proposals
btn_text_link: All recommended proposals
budget_investments:
title: Recommended investments
verification:
i_dont_have_an_account: I don't have an account
i_have_an_account: I already have an account

View File

@@ -39,6 +39,8 @@ en:
spending_proposal_features:
voting_allowed: Voting on investment projects
legislation: Legislation
user:
recommendations: Recommendeds
community: Community on proposals and investments
map: Proposals and budget investments geolocation
map_latitude: Latitude

View File

@@ -109,6 +109,10 @@ es:
hot_score: Más activos hoy
most_commented: Más comentados
relevance: Más relevantes
recommendations: Recomendaciones
recommendations:
without_results: No existen debates relacionados con tus intereses
without_interests: Sigue propuestas para que podamos darte recomendaciones
search_form:
button: Buscar
placeholder: Buscar debates...
@@ -348,6 +352,10 @@ es:
most_commented: Más comentadas
relevance: Más relevantes
archival_date: Archivadas
recommendations: Recomendaciones
recommendations:
without_results: No existen propuestas relacionadas con tus intereses
without_interests: Sigue propuestas para que podamos darte recomendaciones
retired_proposals: Propuestas retiradas
retired_proposals_link: "Propuestas retiradas por sus autores"
retired_links:
@@ -394,7 +402,7 @@ es:
proposal:
created: "¡Has creado una propuesta!"
share:
guide: "Compártela para que la gente empieze a apoyarla."
guide: "Compártela para que la gente empiece a apoyarla."
edit: "Antes de que se publique podrás modificar el texto a tu gusto."
view_proposal: "Ahora no, ir a mi propuesta"
improve_info: "Mejora tu campaña y consigue más apoyos"
@@ -753,6 +761,16 @@ es:
proposal:
description: Espacio abierto para propuestas ciudadanas sobre el tipo de ciudad en el que queremos vivir.
title: Propones
recommended:
title: Recomendaciones que te pueden interesar
debates:
title: Debates recomendados
btn_text_link: Todos los debates recomendados
proposals:
title: Propuestas recomendadas
btn_text_link: Todas las propuestas recomendadas
budget_investments:
title: Presupuestos recomendados
verification:
i_dont_have_an_account: No tengo cuenta, quiero crear una y verificarla
i_have_an_account: Ya tengo una cuenta que quiero verificar

View File

@@ -39,6 +39,8 @@ es:
spending_proposal_features:
voting_allowed: Votaciones sobre propuestas de inversión
legislation: Legislación
user:
recommendations: Recomendaciones
community: Comunidad en propuestas y proyectos de inversión
map: Geolocalización de propuestas y proyectos de inversión
map_latitude: Latitud

View File

@@ -36,6 +36,7 @@ Setting.create(key: 'feature.facebook_login', value: "true")
Setting.create(key: 'feature.google_login', value: "true")
Setting.create(key: 'feature.signature_sheets', value: "true")
Setting.create(key: 'feature.legislation', value: "true")
Setting.create(key: 'feature.user.recommendations', value: "true")
Setting.create(key: 'feature.community', value: "true")
Setting.create(key: 'feature.map', value: "true")
Setting.create(key: 'per_page_code_head', value: "")

View File

@@ -0,0 +1,24 @@
class CreatePollRecount < ActiveRecord::Migration
def change
create_table :poll_recounts do |t|
t.integer :author_id
t.string :origin
t.date :date
t.integer :booth_assignment_id
t.integer :officer_assignment_id
t.text :officer_assignment_id_log, default: ""
t.text :author_id_log, default: ""
t.integer :white_amount
t.text :white_amount_log, default: ""
t.integer :null_amount
t.text :null_amount_log, default: ""
t.integer :total_amount
t.text :total_amount_log, default: ""
end
add_index :poll_recounts, :booth_assignment_id
add_index :poll_recounts, :officer_assignment_id
add_foreign_key :poll_recounts, :poll_booth_assignments, column: :booth_assignment_id
add_foreign_key :poll_recounts, :poll_officer_assignments, column: :officer_assignment_id
end
end

View File

@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20170927110953) do
ActiveRecord::Schema.define(version: 20171002122312) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -690,6 +690,25 @@ ActiveRecord::Schema.define(version: 20170927110953) do
add_index "poll_questions", ["proposal_id"], name: "index_poll_questions_on_proposal_id", using: :btree
add_index "poll_questions", ["tsv"], name: "index_poll_questions_on_tsv", using: :gin
create_table "poll_recounts", force: :cascade do |t|
t.integer "author_id"
t.string "origin"
t.date "date"
t.integer "booth_assignment_id"
t.integer "officer_assignment_id"
t.text "officer_assignment_id_log", default: ""
t.text "author_id_log", default: ""
t.integer "white_amount"
t.text "white_amount_log", default: ""
t.integer "null_amount"
t.text "null_amount_log", default: ""
t.integer "total_amount"
t.text "total_amount_log", default: ""
end
add_index "poll_recounts", ["booth_assignment_id"], name: "index_poll_recounts_on_booth_assignment_id", using: :btree
add_index "poll_recounts", ["officer_assignment_id"], name: "index_poll_recounts_on_officer_assignment_id", using: :btree
create_table "poll_shifts", force: :cascade do |t|
t.integer "booth_id"
t.integer "officer_id"
@@ -1126,6 +1145,8 @@ ActiveRecord::Schema.define(version: 20170927110953) do
add_foreign_key "poll_questions", "polls"
add_foreign_key "poll_questions", "proposals"
add_foreign_key "poll_questions", "users", column: "author_id"
add_foreign_key "poll_recounts", "poll_booth_assignments", column: "booth_assignment_id"
add_foreign_key "poll_recounts", "poll_officer_assignments", column: "officer_assignment_id"
add_foreign_key "poll_voters", "polls"
add_foreign_key "poll_white_results", "poll_booth_assignments", column: "booth_assignment_id"
add_foreign_key "poll_white_results", "poll_officer_assignments", column: "officer_assignment_id"

View File

@@ -79,6 +79,7 @@ Setting['feature.public_stats'] = true
Setting['feature.budgets'] = true
Setting['feature.signature_sheets'] = true
Setting['feature.legislation'] = true
Setting['feature.user.recommendations'] = true
Setting['feature.community'] = true
Setting['feature.map'] = nil

View File

@@ -569,6 +569,11 @@ FactoryGirl.define do
origin { 'web' }
end
factory :poll_recount, class: 'Poll::Recount' do
association :author, factory: :user
origin { 'web' }
end
factory :officing_residence, class: 'Officing::Residence' do
user
association :officer, factory: :poll_officer

View File

@@ -201,18 +201,18 @@ feature 'Admin polls' do
booth_assignment_final_recounted = create(:poll_booth_assignment, poll: poll)
3.times do |i|
create(:poll_total_result,
create(:poll_recount,
booth_assignment: booth_assignment,
date: poll.starts_at + i.days,
amount: 21)
total_amount: 21)
end
2.times { create(:poll_voter, booth_assignment: booth_assignment_final_recounted) }
create(:poll_total_result,
create(:poll_recount,
booth_assignment: booth_assignment_final_recounted,
date: poll.ends_at,
amount: 55555)
total_amount: 55555)
visit admin_poll_path(poll)
@@ -270,12 +270,10 @@ feature 'Admin polls' do
answer: 'Tomorrow',
amount: 5)
end
create(:poll_white_result,
create(:poll_recount,
booth_assignment: booth_assignment_1,
amount: 21)
create(:poll_null_result,
booth_assignment: booth_assignment_3,
amount: 44)
white_amount: 21,
null_amount: 44)
visit admin_poll_path(poll)

View File

@@ -12,7 +12,7 @@ feature 'Admin' do
visit admin_root_path
expect(current_path).not_to eq(admin_root_path)
expect(current_path).to eq(proposals_path)
expect(current_path).to eq(root_path)
expect(page).to have_content "You do not have permission to access this page"
end
@@ -22,7 +22,7 @@ feature 'Admin' do
visit admin_root_path
expect(current_path).not_to eq(admin_root_path)
expect(current_path).to eq(proposals_path)
expect(current_path).to eq(root_path)
expect(page).to have_content "You do not have permission to access this page"
end
@@ -32,7 +32,7 @@ feature 'Admin' do
visit admin_root_path
expect(current_path).not_to eq(admin_root_path)
expect(current_path).to eq(proposals_path)
expect(current_path).to eq(root_path)
expect(page).to have_content "You do not have permission to access this page"
end
@@ -42,7 +42,7 @@ feature 'Admin' do
visit admin_root_path
expect(current_path).not_to eq(admin_root_path)
expect(current_path).to eq(proposals_path)
expect(current_path).to eq(root_path)
expect(page).to have_content "You do not have permission to access this page"
end
@@ -52,7 +52,7 @@ feature 'Admin' do
visit admin_root_path
expect(current_path).not_to eq(admin_root_path)
expect(current_path).to eq(proposals_path)
expect(current_path).to eq(root_path)
expect(page).to have_content "You do not have permission to access this page"
end

View File

@@ -219,7 +219,7 @@ feature 'Debates' do
visit edit_debate_path(debate)
expect(current_path).not_to eq(edit_debate_path(debate))
expect(current_path).to eq(proposals_path)
expect(current_path).to eq(root_path)
expect(page).to have_content "You do not have permission to carry out the action 'edit' on debate."
end
@@ -234,7 +234,7 @@ feature 'Debates' do
visit edit_debate_path(debate)
expect(current_path).not_to eq(edit_debate_path(debate))
expect(current_path).to eq(proposals_path)
expect(current_path).to eq(root_path)
expect(page).to have_content 'You do not have permission to'
end
@@ -351,6 +351,69 @@ feature 'Debates' do
expect(current_url).to include('order=created_at')
expect(current_url).to include('page=1')
end
context 'Recommendations' do
background do
Setting['feature.user.recommendations'] = true
create(:debate, title: 'Best', cached_votes_total: 10, tag_list: "Sport")
create(:debate, title: 'Medium', cached_votes_total: 5, tag_list: "Sport")
create(:debate, title: 'Worst', cached_votes_total: 1, tag_list: "Sport")
end
after do
Setting['feature.user.recommendations'] = nil
end
scenario 'Debates can not ordered by recommendations when there is not an user logged', :js do
visit debates_path
expect(page).not_to have_selector('a', text: 'recommendations')
end
scenario 'Should display text when there are not recommendeds results', :js do
user = create(:user)
proposal = create(:proposal, tag_list: "Distinct_to_sport")
create(:follow, followable: proposal, user: user)
login_as(user)
visit debates_path
click_link 'recommendations'
expect(page).to have_content "There are not debates related to your interests"
end
scenario 'Should display text when user has not related interests', :js do
user = create(:user)
login_as(user)
visit debates_path
click_link 'recommendations'
expect(page).to have_content "Follow proposals so we can give you recommendations"
end
scenario 'Debates are ordered by recommendations when there is a user logged', :js do
proposal = create(:proposal, tag_list: "Sport" )
user = create(:user)
create(:follow, followable: proposal, user: user)
login_as(user)
visit debates_path
click_link 'recommendations'
expect(page).to have_selector('a.active', text: 'recommendations')
within '#debates' do
expect('Best').to appear_before('Medium')
expect('Medium').to appear_before('Worst')
end
expect(current_url).to include('order=recommendations')
expect(current_url).to include('page=1')
end
end
end
context "Search" do
@@ -759,6 +822,32 @@ feature 'Debates' do
end
end
scenario "Reorder by recommendations results maintaing search", :js do
Setting['feature.user.recommendations'] = true
user = create(:user)
login_as(user)
debate1 = create(:debate, title: "Show you got", cached_votes_total: 10, tag_list: "Sport")
debate2 = create(:debate, title: "Show what you got", cached_votes_total: 1, tag_list: "Sport")
debate3 = create(:debate, title: "Do not display with same tag", cached_votes_total: 100, tag_list: "Sport")
debate4 = create(:debate, title: "Do not display", cached_votes_total: 1)
proposal1 = create(:proposal, tag_list: "Sport")
create(:follow, followable: proposal1, user: user)
visit debates_path
fill_in "search", with: "Show you got"
click_button "Search"
click_link 'recommendations'
expect(page).to have_selector("a.active", text: "recommendations")
within("#debates") do
expect(all(".debate")[0].text).to match "Show you got"
expect(all(".debate")[1].text).to match "Show what you got"
expect(page).to_not have_content "Do not display with same tag"
expect(page).to_not have_content "Do not display"
end
Setting['feature.user.recommendations'] = nil
end
scenario 'After a search do not show featured debates' do
featured_debates = create_featured_debates
debate = create(:debate, title: "Abcdefghi")

View File

@@ -3,20 +3,143 @@ require 'rails_helper'
feature "Home" do
feature "For not logged users" do
scenario 'Welcome message' do
visit root_path
expect(page).to have_content "Love the city, and it will become a city you love"
end
scenario 'Not display recommended section' do
debate = create(:debate)
visit root_path
expect(page).not_to have_content "Recommendations that may interest you"
end
end
feature "For signed in users" do
scenario 'Redirect to proposals' do
login_as(create(:user))
visit root_path
expect(current_path).to eq proposals_path
before do
# user = create(:user)
# login_as(user)
end
feature "Recommended" do
background do
Setting['feature.user.recommendations'] = true
user = create(:user)
proposal = create(:proposal, tag_list: "Sport" )
create(:follow, followable: proposal, user: user)
login_as(user)
end
after do
Setting['feature.user.recommendations'] = nil
end
scenario 'Display recommended section' do
debate = create(:debate, tag_list: "Sport")
visit root_path
expect(page).to have_content "Recommendations that may interest you"
end
scenario 'Display recommended section when feature flag recommended is active' do
debate = create(:debate, tag_list: "Sport")
visit root_path
expect(page).to have_content "Recommendations that may interest you"
end
scenario 'Not display recommended section when feature flag recommended is not active' do
debate = create(:debate, tag_list: "Sport")
Setting['feature.user.recommendations'] = false
visit root_path
expect(page).not_to have_content "Recommendations that may interest you"
end
scenario 'Display debates' do
debate = create(:debate, tag_list: "Sport")
visit root_path
expect(page).to have_content debate.title
expect(page).to have_content debate.description
end
scenario 'Display all recommended debates link' do
debate = create(:debate, tag_list: "Sport")
visit root_path
expect(page).to have_link("All recommended debates", href: debates_path(order: "recommendations"))
end
scenario 'Display proposal' do
proposal = create(:proposal, tag_list: "Sport")
visit root_path
expect(page).to have_content proposal.title
expect(page).to have_content proposal.description
end
scenario 'Display all recommended proposals link' do
debate = create(:proposal, tag_list: "Sport")
visit root_path
expect(page).to have_link("All recommended proposals", href: proposals_path(order: "recommendations"))
end
scenario 'Display orbit carrousel' do
create_list(:debate, 3, tag_list: "Sport")
visit root_path
expect(page).to have_selector('li[data-slide="0"]')
expect(page).to have_selector('li[data-slide="1"]', visible: false)
expect(page).to have_selector('li[data-slide="2"]', visible: false)
end
scenario 'Display recommended show when click on carousel' do
debate = create(:debate, tag_list: "Sport")
visit root_path
click_on debate.title
expect(current_path).to eq debate_path(debate)
end
scenario 'Do not display recommended section when there are not debates and proposals' do
visit root_path
expect(page).not_to have_content "Recommendations that may interest you"
end
feature 'Carousel size' do
scenario 'Display debates centered when there are no proposals' do
debate = create(:debate, tag_list: "Sport")
visit root_path
expect(page).to have_selector('.medium-centered.large-centered')
end
scenario 'Correct display debates and proposals' do
proposal = create(:proposal, tag_list: "Sport")
debates = create(:debate, tag_list: "Sport")
visit root_path
expect(page).to have_selector('.debates.medium-offset-2.large-offset-2')
expect(page).not_to have_selector('.proposals.medium-offset-2.large-offset-2')
expect(page).not_to have_selector('.debates.end')
expect(page).to have_selector('.proposals.end')
expect(page).not_to have_selector('.medium-centered.large-centered')
end
end
end
end
feature 'IE alert' do

View File

@@ -11,7 +11,7 @@ feature 'Moderation' do
visit moderation_root_path
expect(current_path).not_to eq(moderation_root_path)
expect(current_path).to eq(proposals_path)
expect(current_path).to eq(root_path)
expect(page).to have_content "You do not have permission to access this page"
end
@@ -25,7 +25,7 @@ feature 'Moderation' do
visit moderation_root_path
expect(current_path).not_to eq(moderation_root_path)
expect(current_path).to eq(proposals_path)
expect(current_path).to eq(root_path)
expect(page).to have_content "You do not have permission to access this page"
end
@@ -39,7 +39,7 @@ feature 'Moderation' do
visit moderation_root_path
expect(current_path).not_to eq(moderation_root_path)
expect(current_path).to eq(proposals_path)
expect(current_path).to eq(root_path)
expect(page).to have_content "You do not have permission to access this page"
end
@@ -53,7 +53,7 @@ feature 'Moderation' do
visit moderation_root_path
expect(current_path).not_to eq(moderation_root_path)
expect(current_path).to eq(proposals_path)
expect(current_path).to eq(root_path)
expect(page).to have_content "You do not have permission to access this page"
end

View File

@@ -15,16 +15,7 @@ feature "Notifications" do
let(:legislation_annotation) { create(:legislation_annotation, author: author) }
scenario "User commented on my debate", :js do
login_as user
visit debate_path debate
fill_in "comment-body-debate_#{debate.id}", with: "I commented on your debate"
click_button "Publish comment"
within "#comments" do
expect(page).to have_content "I commented on your debate"
end
logout
create(:notification, notifiable: debate, user: author)
login_as author
visit root_path
@@ -37,17 +28,7 @@ feature "Notifications" do
end
scenario "User commented on my legislation question", :js do
verified_user = create(:user, :level_two)
login_as verified_user
visit legislation_process_question_path legislation_question.process, legislation_question
fill_in "comment-body-legislation_question_#{legislation_question.id}", with: "I answered your question"
click_button "Publish answer"
within "#comments" do
expect(page).to have_content "I answered your question"
end
logout
create(:notification, notifiable: legislation_question, user: administrator)
login_as administrator
visit root_path
@@ -82,6 +63,7 @@ feature "Notifications" do
logout
login_as author
visit root_path
visit root_path
find(".icon-notification").click
@@ -107,8 +89,10 @@ feature "Notifications" do
end
logout
login_as author
visit root_path
visit root_path
find(".icon-notification").click
@@ -137,6 +121,7 @@ feature "Notifications" do
login_as author
visit root_path
visit root_path
find(".icon-notification").click
@@ -208,6 +193,7 @@ feature "Notifications" do
logout
login_as user1
visit root_path
visit root_path
find(".icon-notification").click
@@ -219,6 +205,7 @@ feature "Notifications" do
logout
login_as user2
visit root_path
visit root_path
find(".icon-notification").click
@@ -230,6 +217,7 @@ feature "Notifications" do
logout
login_as user3
visit root_path
visit root_path
find(".icon-no-notification").click

View File

@@ -127,21 +127,13 @@ feature 'Officing Results' do
date: @poll.ends_at,
question: @question_1,
amount: 33)
white_result = create(:poll_white_result,
poll_recount = create(:poll_recount,
officer_assignment: @officer_assignment,
booth_assignment: @officer_assignment.booth_assignment,
date: @poll.ends_at,
amount: 21)
null_result = create(:poll_null_result,
officer_assignment: @officer_assignment,
booth_assignment: @officer_assignment.booth_assignment,
date: @poll.ends_at,
amount: 44)
total_result = create(:poll_total_result,
officer_assignment: @officer_assignment,
booth_assignment: @officer_assignment.booth_assignment,
date: @poll.ends_at,
amount: 66)
white_amount: 21,
null_amount: 44,
total_amount: 66)
visit officing_poll_results_path(@poll,
date: I18n.l(@poll.ends_at.to_date),

View File

@@ -11,7 +11,7 @@ feature 'Poll Officing' do
visit officing_root_path
expect(current_path).not_to eq(officing_root_path)
expect(current_path).to eq(proposals_path)
expect(current_path).to eq(root_path)
expect(page).to have_content "You do not have permission to access this page"
end
@@ -24,7 +24,7 @@ feature 'Poll Officing' do
visit officing_root_path
expect(current_path).not_to eq(officing_root_path)
expect(current_path).to eq(proposals_path)
expect(current_path).to eq(root_path)
expect(page).to have_content "You do not have permission to access this page"
end
@@ -37,7 +37,7 @@ feature 'Poll Officing' do
visit officing_root_path
expect(current_path).not_to eq(officing_root_path)
expect(current_path).to eq(proposals_path)
expect(current_path).to eq(root_path)
expect(page).to have_content "You do not have permission to access this page"
end
@@ -50,7 +50,7 @@ feature 'Poll Officing' do
visit officing_root_path
expect(current_path).not_to eq(officing_root_path)
expect(current_path).to eq(proposals_path)
expect(current_path).to eq(root_path)
expect(page).to have_content "You do not have permission to access this page"
end

View File

@@ -189,7 +189,7 @@ feature 'Proposal Notifications' do
login_as(user)
visit new_proposal_notification_path(proposal_id: proposal.id)
expect(current_path).to eq(proposals_path)
expect(current_path).to eq(root_path)
expect(page).to have_content("You do not have permission to carry out the action")
end

View File

@@ -556,7 +556,7 @@ feature 'Proposals' do
visit edit_proposal_path(proposal)
expect(current_path).not_to eq(edit_proposal_path(proposal))
expect(current_path).to eq(proposals_path)
expect(current_path).to eq(root_path)
expect(page).to have_content 'You do not have permission'
end
@@ -571,7 +571,7 @@ feature 'Proposals' do
visit edit_proposal_path(proposal)
expect(current_path).not_to eq(edit_proposal_path(proposal))
expect(current_path).to eq(proposals_path)
expect(current_path).to eq(root_path)
expect(page).to have_content 'You do not have permission'
Setting["max_votes_for_proposal_edit"] = 1000
end
@@ -663,6 +663,69 @@ feature 'Proposals' do
expect(current_url).to include('order=created_at')
expect(current_url).to include('page=1')
end
context 'Recommendations' do
before do
Setting['feature.user.recommendations'] = true
create(:proposal, title: 'Best', cached_votes_up: 10, tag_list: "Sport")
create(:proposal, title: 'Medium', cached_votes_up: 5, tag_list: "Sport")
create(:proposal, title: 'Worst', cached_votes_up: 1, tag_list: "Sport")
end
after do
Setting['feature.user.recommendations'] = nil
end
scenario 'Proposals can not ordered by recommendations when there is not an user logged', :js do
visit proposals_path
expect(page).not_to have_selector('a', text: 'recommendations')
end
scenario 'Should display text when there are not recommendeds results', :js do
user = create(:user)
proposal = create(:proposal, tag_list: "Distinct_to_sport")
create(:follow, followable: proposal, user: user)
login_as(user)
visit proposals_path
click_link 'recommendations'
expect(page).to have_content "There are not proposals related to your interests"
end
scenario 'Should display text when user has not related interests', :js do
user = create(:user)
login_as(user)
visit proposals_path
click_link 'recommendations'
expect(page).to have_content "Follow proposals so we can give you recommendations"
end
scenario 'Proposals are ordered by recommendations when there is an user logged', :js do
user = create(:user)
proposal = create(:proposal, tag_list: "Sport")
create(:follow, followable: proposal, user: user)
login_as(user)
visit proposals_path
click_link 'recommendations'
expect(page).to have_selector('a.active', text: 'recommendations')
within '#proposals-list' do
expect('Best').to appear_before('Medium')
expect('Medium').to appear_before('Worst')
end
expect(current_url).to include('order=recommendations')
expect(current_url).to include('page=1')
end
end
end
feature 'Archived proposals' do
@@ -1198,6 +1261,32 @@ feature 'Proposals' do
end
end
scenario "Reorder by recommendations results maintaing search", :js do
Setting['feature.user.recommendations'] = true
user = create(:user)
login_as(user)
proposal1 = create(:proposal, title: "Show you got", cached_votes_up: 10, tag_list: "Sport")
proposal2 = create(:proposal, title: "Show what you got", cached_votes_up: 1, tag_list: "Sport")
proposal3 = create(:proposal, title: "Do not display with same tag", cached_votes_up: 100, tag_list: "Sport")
proposal4 = create(:proposal, title: "Do not display", cached_votes_up: 1)
proposal5 = create(:proposal, tag_list: "Sport")
create(:follow, followable: proposal5, user: user)
visit proposals_path
fill_in "search", with: "Show you got"
click_button "Search"
click_link 'recommendations'
expect(page).to have_selector("a.active", text: "recommendations")
within("#proposals") do
expect(all(".proposal")[0].text).to match "Show you got"
expect(all(".proposal")[1].text).to match "Show what you got"
expect(page).to_not have_content "Do not display with same tag"
expect(page).to_not have_content "Do not display"
end
Setting['feature.user.recommendations'] = nil
end
scenario 'After a search do not show featured proposals' do
featured_proposals = create_featured_proposals
proposal = create(:proposal, title: "Abcdefghi")

View File

@@ -21,7 +21,7 @@ feature 'Valuation' do
visit valuation_root_path
expect(current_path).not_to eq(valuation_root_path)
expect(current_path).to eq(proposals_path)
expect(current_path).to eq(root_path)
expect(page).to have_content "You do not have permission to access this page"
end
@@ -34,7 +34,7 @@ feature 'Valuation' do
visit valuation_root_path
expect(current_path).not_to eq(valuation_root_path)
expect(current_path).to eq(proposals_path)
expect(current_path).to eq(root_path)
expect(page).to have_content "You do not have permission to access this page"
end
@@ -47,7 +47,7 @@ feature 'Valuation' do
visit valuation_root_path
expect(current_path).not_to eq(valuation_root_path)
expect(current_path).to eq(proposals_path)
expect(current_path).to eq(root_path)
expect(page).to have_content "You do not have permission to access this page"
end
@@ -60,7 +60,7 @@ feature 'Valuation' do
visit valuation_root_path
expect(current_path).not_to eq(valuation_root_path)
expect(current_path).to eq(proposals_path)
expect(current_path).to eq(root_path)
expect(page).to have_content "You do not have permission to access this page"
end

View File

@@ -33,7 +33,7 @@ feature "Welcome screen" do
login_through_form_as(user)
expect(current_path).to eq(proposals_path)
expect(current_path).to eq(root_path)
end
scenario 'is not shown to organizations' do
@@ -41,7 +41,7 @@ feature "Welcome screen" do
login_through_form_as(organization.user)
expect(current_path).to eq(proposals_path)
expect(current_path).to eq(root_path)
end
scenario 'it is not shown to level-2 users' do
@@ -49,7 +49,7 @@ feature "Welcome screen" do
login_through_form_as(user)
expect(current_path).to eq(proposals_path)
expect(current_path).to eq(root_path)
end
scenario 'it is not shown to level-3 users' do
@@ -57,7 +57,7 @@ feature "Welcome screen" do
login_through_form_as(user)
expect(current_path).to eq(proposals_path)
expect(current_path).to eq(root_path)
end
scenario 'is not shown to administrators' do
@@ -65,7 +65,7 @@ feature "Welcome screen" do
login_through_form_as(administrator.user)
expect(current_path).to eq(proposals_path)
expect(current_path).to eq(root_path)
end
end

View File

@@ -714,4 +714,53 @@ describe Debate do
end
end
describe "#recommendations" do
let(:user) { create(:user) }
it "Should not return any debates when user has not interests" do
create(:debate)
expect(Debate.recommendations(user).size).to eq 0
end
it "Should return debates ordered by cached_votes_total" do
debate1 = create(:debate, cached_votes_total: 1, tag_list: "Sport" )
debate2 = create(:debate, cached_votes_total: 5, tag_list: "Sport" )
debate3 = create(:debate, cached_votes_total: 10, tag_list: "Sport" )
proposal = create(:proposal, tag_list: "Sport" )
create(:follow, followable: proposal, user: user)
result = Debate.recommendations(user).sort_by_recommendations
expect(result.first).to eq debate3
expect(result.second).to eq debate2
expect(result.third).to eq debate1
end
it "Should return debates related with user interests" do
debate1 = create(:debate, tag_list: "Sport")
debate2 = create(:debate, tag_list: "Politics")
proposal1 = create(:proposal, tag_list: "Sport")
create(:follow, followable: proposal1, user: user)
result = Debate.recommendations(user)
expect(result.size).to eq 1
expect(result).to eq [debate1]
end
it "Should not return debates when user is the author" do
debate1 = create(:debate, author: user, tag_list: "Sport")
debate2 = create(:debate, tag_list: "Sport")
proposal = create(:proposal, tag_list: "Sport" )
create(:follow, followable: proposal, user: user)
result = Debate.recommendations(user)
expect(result.size).to eq 1
expect(result).to eq [debate2]
end
end
end

View File

@@ -0,0 +1,102 @@
require 'rails_helper'
describe Poll::Recount do
describe "logging changes" do
let(:poll_recount) { create(:poll_recount) }
it "should update white_amount_log if white_amount changes" do
poll_recount.white_amount = 33
expect(poll_recount.white_amount_log).to eq("")
poll_recount.white_amount = 33
poll_recount.save
poll_recount.white_amount = 32
poll_recount.save
poll_recount.white_amount = 34
poll_recount.save
expect(poll_recount.white_amount_log).to eq(":33:32")
end
it "should update null_amount_log if null_amount changes" do
poll_recount.null_amount = 33
expect(poll_recount.null_amount_log).to eq("")
poll_recount.null_amount = 33
poll_recount.save
poll_recount.null_amount = 32
poll_recount.save
poll_recount.null_amount = 34
poll_recount.save
expect(poll_recount.null_amount_log).to eq(":33:32")
end
it "should update total_amount_log if total_amount changes" do
poll_recount.total_amount = 33
expect(poll_recount.total_amount_log).to eq("")
poll_recount.total_amount = 33
poll_recount.save
poll_recount.total_amount = 32
poll_recount.save
poll_recount.total_amount = 34
poll_recount.save
expect(poll_recount.total_amount_log).to eq(":33:32")
end
it "should update officer_assignment_id_log if amount changes" do
poll_recount.white_amount = 33
expect(poll_recount.white_amount_log).to eq("")
expect(poll_recount.officer_assignment_id_log).to eq("")
poll_recount.white_amount = 33
poll_recount.officer_assignment = create(:poll_officer_assignment, id: 101)
poll_recount.save
poll_recount.white_amount = 32
poll_recount.officer_assignment = create(:poll_officer_assignment, id: 102)
poll_recount.save
poll_recount.white_amount = 34
poll_recount.officer_assignment = create(:poll_officer_assignment, id: 103)
poll_recount.save
expect(poll_recount.white_amount_log).to eq(":33:32")
expect(poll_recount.officer_assignment_id_log).to eq(":101:102")
end
it "should update author_id if amount changes" do
poll_recount.white_amount = 33
expect(poll_recount.white_amount_log).to eq("")
expect(poll_recount.author_id_log).to eq("")
author_A = create(:poll_officer).user
author_B = create(:poll_officer).user
author_C = create(:poll_officer).user
poll_recount.white_amount = 33
poll_recount.author_id = author_A.id
poll_recount.save!
poll_recount.white_amount = 32
poll_recount.author_id = author_B.id
poll_recount.save!
poll_recount.white_amount = 34
poll_recount.author_id = author_C.id
poll_recount.save!
expect(poll_recount.white_amount_log).to eq(":33:32")
expect(poll_recount.author_id_log).to eq(":#{author_A.id}:#{author_B.id}")
end
end
end

View File

@@ -891,4 +891,63 @@ describe Proposal do
end
end
describe "#recommendations" do
let(:user) { create(:user) }
it "Should not return any proposals when user has not interests" do
create(:proposal)
expect(Proposal.recommendations(user).size).to eq 0
end
it "Should return proposals ordered by cached_votes_up" do
proposal1 = create(:proposal, cached_votes_up: 1, tag_list: "Sport" )
proposal2 = create(:proposal, cached_votes_up: 5, tag_list: "Sport" )
proposal3 = create(:proposal, cached_votes_up: 10, tag_list: "Sport" )
proposal4 = create(:proposal, tag_list: "Sport" )
create(:follow, followable: proposal4, user: user)
result = Proposal.recommendations(user).sort_by_recommendations
expect(result.first).to eq proposal3
expect(result.second).to eq proposal2
expect(result.third).to eq proposal1
end
it "Should return proposals related with user interests" do
proposal1 = create(:proposal, tag_list: "Sport")
proposal2 = create(:proposal, tag_list: "Sport")
proposal3 = create(:proposal, tag_list: "Politics")
create(:follow, followable: proposal1, user: user)
result = Proposal.recommendations(user)
expect(result.size).to eq 1
expect(result).to eq [proposal2]
end
it "Should not return proposals when user is follower" do
proposal1 = create(:proposal, tag_list: "Sport")
create(:follow, followable: proposal1, user: user)
result = Proposal.recommendations(user)
expect(result.size).to eq 0
end
it "Should not return proposals when user is the author" do
proposal1 = create(:proposal, author: user, tag_list: "Sport")
proposal2 = create(:proposal, tag_list: "Sport")
proposal3 = create(:proposal, tag_list: "Sport")
create(:follow, followable: proposal3, user: user)
result = Proposal.recommendations(user)
expect(result.size).to eq 1
expect(result).to eq [proposal2]
end
end
end

View File

@@ -0,0 +1,43 @@
require "rails_helper"
RSpec.describe "welcome/index" do
it 'Display images on orbit carrousel when we have defined image_default' do
debate = create(:debate)
render template: "welcome/_recommended_carousel.html.erb",
locals: { key: "debates",
recommendeds: [debate],
image_field: nil,
image_version: nil,
image_default: "https://dummyimage.com/600x400/000/fff",
carousel_size: "medium-6 large-6 medium-centered large-centered",
btn_text_link: t("welcome.recommended.debates.btn_text_link"),
btn_path_link: debates_path(order: "recommendations")}
within 'li[data-slide="0"] .card' do
expect(page).to have_selector("img")
end
end
it 'Not display images on orbit carrousel when we have not defined image_default' do
debate = create(:debate)
render template: "welcome/_recommended_carousel.html.erb",
locals: { key: "debates",
recommendeds: [debate],
image_field: nil,
image_version: nil,
image_default: nil,
carousel_size: "medium-6 large-6 medium-centered large-centered",
btn_text_link: t("welcome.recommended.debates.btn_text_link"),
btn_path_link: debates_path(order: "recommendations")}
within 'li[data-slide="0"] .card' do
expect(page).not_to have_selector("img")
end
end
end