Merge pull request #1213 from consul/archived-proposals

Archived proposals
This commit is contained in:
Juanjo Bazán
2016-09-15 13:22:05 +02:00
committed by GitHub
18 changed files with 237 additions and 49 deletions

View File

@@ -269,6 +269,21 @@
}
}
.message {
@include supports;
background: none;
border-top: 0;
@include breakpoint(medium) {
border-left: 1px solid $border;
margin: $line-height rem-calc(-25) 0 rem-calc(12);
}
p {
font-size: $small-font-size;
}
}
// 02. New participation
// ---------------------

View File

@@ -12,7 +12,7 @@ module CommentableActions
@tag_cloud = tag_cloud
@banners = Banner.with_active
set_resource_votes(@resources)
set_resources_instance
end

View File

@@ -12,7 +12,7 @@ class ProposalsController < ApplicationController
invisible_captcha only: [:create, :update], honeypot: :subtitle
has_orders %w{hot_score confidence_score created_at relevance}, only: :index
has_orders %w{hot_score confidence_score created_at relevance archival_date}, only: :index
has_orders %w{most_voted newest oldest}, only: :show
load_and_authorize_resource
@@ -26,6 +26,7 @@ class ProposalsController < ApplicationController
end
def index_customization
discard_archived
load_retired
load_featured
end
@@ -80,6 +81,10 @@ class ProposalsController < ApplicationController
@featured_proposals_votes = current_user ? current_user.proposal_votes(proposals) : {}
end
def discard_archived
@resources = @resources.not_archived unless @current_order == "archival_date"
end
def load_retired
if params[:retired].present?
@resources = @resources.retired
@@ -90,7 +95,7 @@ class ProposalsController < ApplicationController
end
def load_featured
@featured_proposals = Proposal.all.sort_by_confidence_score.limit(3) if (!@advanced_search_terms && @search_terms.blank? && @tag_filter.blank? && params[:retired].blank?)
@featured_proposals = Proposal.not_archived.sort_by_confidence_score.limit(3) if (!@advanced_search_terms && @search_terms.blank? && @tag_filter.blank? && params[:retired].blank?)
if @featured_proposals.present?
set_featured_proposal_votes(@featured_proposals)
@resources = @resources.where('proposals.id NOT IN (?)', @featured_proposals.map(&:id))

View File

@@ -44,6 +44,9 @@ class Proposal < 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_archival_date, -> { archived.order(created_at: :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) }
@@ -119,7 +122,7 @@ class Proposal < ActiveRecord::Base
end
def register_vote(user, vote_value)
if votable_by?(user)
if votable_by?(user) && !archived?
vote_by(voter: user, vote: vote_value)
end
end
@@ -155,6 +158,10 @@ class Proposal < ActiveRecord::Base
Setting['votes_for_proposal_success'].to_i
end
def archived?
self.created_at <= Setting["months_to_archive_proposals"].to_i.months.ago
end
def notifications
proposal_notifications
end

View File

@@ -50,9 +50,18 @@
</div>
</div>
<div id="<%= dom_id(proposal) %>_votes" class="small-12 medium-3 column text-center">
<%= render 'votes',
{ proposal: proposal, vote_url: vote_proposal_path(proposal, value: 'yes') } %>
<div id="<%= dom_id(proposal) %>_votes" class="small-12 medium-3 column">
<% if proposal.archived? %>
<div class="message">
<strong><%= t("proposals.proposal.supports", count: proposal.total_votes) %></strong>
<p><%= t("proposals.proposal.archived") %></p>
</div>
<% else %>
<div class="text-center">
<%= render 'votes',
{ proposal: proposal, vote_url: vote_proposal_path(proposal, value: 'yes') } %>
</div>
<% end %>
</div>
</div>

View File

@@ -30,7 +30,7 @@
<% if has_banners %>
<%= render "shared/banner" %>
<% end %>
<% if @featured_proposals.present? %>
<div id="featured-proposals" class="row featured-proposals">
<div class="small-12 column">
@@ -52,8 +52,10 @@
<%= link_to t("proposals.index.start_proposal"), new_proposal_path, class: 'button expanded' %>
</div>
<%= render partial: 'proposals/proposal', collection: @proposals %>
<%= paginate @proposals %>
<div id="proposals-list">
<%= render partial: 'proposals/proposal', collection: @proposals %>
<%= paginate @proposals %>
</div>
</div>
<div class="small-12 medium-3 column">

View File

@@ -102,12 +102,19 @@
<aside class="small-12 medium-3 column">
<div class="sidebar-divider"></div>
<h3><%= t("votes.supports") %></h3>
<div class="text-center">
<div id="<%= dom_id(@proposal) %>_votes">
<%= render 'votes',
{ proposal: @proposal, vote_url: vote_proposal_path(@proposal, value: 'yes') } %>
<% if @proposal.archived? %>
<p class="text-center">
<strong><%= t("proposals.proposal.supports", count: @proposal.total_votes) %></strong>
</p>
<p><%= t("proposals.proposal.archived") %></p>
<% else %>
<div class="text-center">
<div id="<%= dom_id(@proposal) %>_votes">
<%= render 'votes',
{ proposal: @proposal, vote_url: vote_proposal_path(@proposal, value: 'yes') } %>
</div>
</div>
</div>
<% end %>
<div id="social-share" class="sidebar-divider"></div>
<h3><%= t("proposals.show.share") %></h3>
<div class="social-share-full">

View File

@@ -301,6 +301,7 @@ en:
hot_score: most active
most_commented: most commented
relevance: relevance
archival_date: Archived
retired_proposals: Retired proposals
retired_proposals_link: "Proposals retired by the author"
retired_links:
@@ -350,6 +351,7 @@ en:
zero: No supports
supports_necessary: "%{number} supports needed"
total_percent: 100%
archived: "This proposal has been archived and can't collect supports."
show:
author_deleted: User deleted
code: 'Proposal code:'

View File

@@ -301,6 +301,7 @@ es:
hot_score: Más activas hoy
most_commented: Más comentadas
relevance: Más relevantes
archival_date: Archivadas
retired_proposals: Propuestas retiradas
retired_proposals_link: "Propuestas retiradas por sus autores"
retired_links:
@@ -350,6 +351,7 @@ es:
zero: Sin apoyos
supports_necessary: "%{number} apoyos necesarios"
total_percent: 100%
archived: "Esta propuesta ha sido archivada y ya no puede recoger apoyos."
show:
author_deleted: Usuario eliminado
code: 'Código de la propuesta:'

View File

@@ -11,6 +11,7 @@ en:
max_votes_for_debate_edit: "Number of votes from which a Debate can no longer be edited"
proposal_code_prefix: "Prefix for Proposal codes"
votes_for_proposal_success: "Number of votes necessary for approval of a Proposal"
months_to_archive_proposals: "Months to archive Proposals"
email_domain_for_officials: "Email domain for public officials"
per_page_code: "Code to be included on every page"
feature:

View File

@@ -11,6 +11,7 @@ es:
max_votes_for_debate_edit: "Número de votos en que un Debate deja de poderse editar"
proposal_code_prefix: "Prefijo para los códigos de Propuestas"
votes_for_proposal_success: "Número de votos necesarios para aprobar una Propuesta"
months_to_archive_proposals: "Meses para archivar las Propuestas"
email_domain_for_officials: "Dominio de email para cargos públicos"
per_page_code: "Código a incluir en cada página"
feature:

View File

@@ -13,6 +13,7 @@ Setting.create(key: 'max_votes_for_debate_edit', value: '1000')
Setting.create(key: 'max_votes_for_proposal_edit', value: '1000')
Setting.create(key: 'proposal_code_prefix', value: 'MAD')
Setting.create(key: 'votes_for_proposal_success', value: '100')
Setting.create(key: 'months_to_archive_proposals', value: '12')
Setting.create(key: 'comments_body_max_length', value: '1000')
Setting.create(key: 'twitter_handle', value: '@consul_dev')

View File

@@ -31,6 +31,9 @@ Setting["proposal_code_prefix"] = 'MAD'
# Number of votes needed for proposal success
Setting["votes_for_proposal_success"] = 53726
# Months to archive proposals
Setting["months_to_archive_proposals"] = 12
# Users with this email domain will automatically be marked as level 1 officials
# Emails under the domain's subdomains will also be included
Setting["email_domain_for_officials"] = ''

View File

@@ -163,6 +163,10 @@ FactoryGirl.define do
end
end
trait :archived do
created_at 25.months.ago
end
trait :with_hot_score do
before(:save) { |d| d.calculate_hot_score }
end

View File

@@ -50,7 +50,7 @@ feature 'Official positions' do
@proposal1 = create(:proposal, author: @user1)
@proposal2 = create(:proposal, author: @user2)
featured_proposals = 3.times { create(:proposal) }
create_featured_proposals
end
scenario "Index" do

View File

@@ -3,48 +3,50 @@ require 'rails_helper'
feature 'Proposals' do
scenario 'Index' do
featured_proposals = create_featured_proposals
proposals = [create(:proposal), create(:proposal), create(:proposal)]
context 'Index' do
scenario 'Lists featured and regular proposals' do
featured_proposals = create_featured_proposals
proposals = [create(:proposal), create(:proposal), create(:proposal)]
visit proposals_path
visit proposals_path
expect(page).to have_selector('#proposals .proposal-featured', count: 3)
featured_proposals.each do |featured_proposal|
within('#featured-proposals') do
expect(page).to have_content featured_proposal.title
expect(page).to have_css("a[href='#{proposal_path(featured_proposal)}']")
expect(page).to have_selector('#proposals .proposal-featured', count: 3)
featured_proposals.each do |featured_proposal|
within('#featured-proposals') do
expect(page).to have_content featured_proposal.title
expect(page).to have_css("a[href='#{proposal_path(featured_proposal)}']")
end
end
expect(page).to have_selector('#proposals .proposal', count: 3)
proposals.each do |proposal|
within('#proposals') do
expect(page).to have_content proposal.title
expect(page).to have_css("a[href='#{proposal_path(proposal)}']", text: proposal.title)
expect(page).to have_css("a[href='#{proposal_path(proposal)}']", text: proposal.summary)
end
end
end
expect(page).to have_selector('#proposals .proposal', count: 3)
proposals.each do |proposal|
within('#proposals') do
expect(page).to have_content proposal.title
expect(page).to have_css("a[href='#{proposal_path(proposal)}']", text: proposal.title)
expect(page).to have_css("a[href='#{proposal_path(proposal)}']", text: proposal.summary)
scenario 'Pagination' do
per_page = Kaminari.config.default_per_page
(per_page + 5).times { create(:proposal) }
visit proposals_path
expect(page).to have_selector('#proposals .proposal', count: per_page)
within("ul.pagination") do
expect(page).to have_content("1")
expect(page).to have_content("2")
expect(page).to_not have_content("3")
click_link "Next", exact: false
end
expect(page).to have_selector('#proposals .proposal', count: 2)
end
end
scenario 'Paginated Index' do
per_page = Kaminari.config.default_per_page
(per_page + 5).times { create(:proposal) }
visit proposals_path
expect(page).to have_selector('#proposals .proposal', count: per_page)
within("ul.pagination") do
expect(page).to have_content("1")
expect(page).to have_content("2")
expect(page).to_not have_content("3")
click_link "Next", exact: false
end
expect(page).to have_selector('#proposals .proposal', count: 2)
end
scenario 'Show' do
proposal = create(:proposal)
@@ -676,6 +678,95 @@ feature 'Proposals' do
end
end
feature 'Archived proposals' do
scenario 'show on archived tab' do
create_featured_proposals
archived_proposals = create_archived_proposals
visit proposals_path
click_link 'Archived'
within("#proposals-list") do
archived_proposals.each do |proposal|
expect(page).to have_content(proposal.title)
end
end
end
scenario 'do not show in other index tabs' do
create_featured_proposals
archived_proposal = create(:proposal, :archived)
visit proposals_path
within("#proposals-list") do
expect(page).to_not have_content archived_proposal.title
end
orders = %w{hot_score confidence_score created_at relevance}
orders.each do |order|
visit proposals_path(order: order)
within("#proposals-list") do
expect(page).to_not have_content archived_proposal.title
end
end
end
scenario 'do not show support buttons in index' do
create_featured_proposals
archived_proposals = create_archived_proposals
visit proposals_path(order: 'archival_date')
within("#proposals-list") do
archived_proposals.each do |proposal|
within("#proposal_#{proposal.id}_votes") do
expect(page).to_not have_css(".supports")
expect(page).to have_content "This proposal has been archived and can't collect supports"
end
end
end
end
scenario 'do not show support buttons in show' do
archived_proposal = create(:proposal, :archived)
visit proposal_path(archived_proposal)
expect(page).to_not have_css(".supports")
expect(page).to have_content "This proposal has been archived and can't collect supports"
end
scenario 'do not show in featured proposals section' do
featured_proposal = create(:proposal, :with_confidence_score, cached_votes_up: 100)
archived_proposal = create(:proposal, :archived, :with_confidence_score, cached_votes_up: 10000)
visit proposals_path
within("#featured-proposals") do
expect(page).to have_content(featured_proposal.title)
expect(page).to_not have_content(archived_proposal.title)
end
within("#proposals-list") do
expect(page).to_not have_content(featured_proposal.title)
expect(page).to_not have_content(archived_proposal.title)
end
click_link "Archived"
within("#featured-proposals") do
expect(page).to have_content(featured_proposal.title)
expect(page).to_not have_content(archived_proposal.title)
end
within("#proposals-list") do
expect(page).to_not have_content(featured_proposal.title)
expect(page).to have_content(archived_proposal.title)
end
end
end
context "Search" do
context "Basic search" do

View File

@@ -204,6 +204,13 @@ describe Proposal do
expect {proposal.register_vote(user, 'yes')}.to change{proposal.reload.votes_for.size}.by(0)
end
end
it "should not register vote for archived proposals" do
user = create(:user, verified_at: Time.now)
archived_proposal = create(:proposal, :archived)
expect {archived_proposal.register_vote(user, 'yes')}.to change{proposal.reload.votes_for.size}.by(0)
end
end
describe '#cached_votes_up' do
@@ -811,4 +818,30 @@ describe Proposal do
end
end
describe "archived" do
before(:each) do
@new_proposal = create(:proposal)
@archived_proposal = create(:proposal, :archived)
end
it "archived? is true only for proposals created more than n (configured months) ago" do
expect(@new_proposal.archived?).to eq false
expect(@archived_proposal.archived?).to eq true
end
it "scope archived" do
archived = Proposal.archived
expect(archived.size).to eq(1)
expect(archived.first).to eq(@archived_proposal)
end
it "scope archived" do
not_archived = Proposal.not_archived
expect(not_archived.size).to eq(1)
expect(not_archived.first).to eq(@new_proposal)
end
end
end

View File

@@ -192,6 +192,11 @@ module CommonActions
create(:debate, :with_confidence_score, cached_votes_up: 80)]
end
def create_archived_proposals
[create(:proposal, title: "This is an expired proposal", created_at: Setting["months_to_archive_proposals"].to_i.months.ago),
create(:proposal, title: "This is an oldest expired proposal", created_at: (Setting["months_to_archive_proposals"].to_i + 2).months.ago)]
end
def tag_names(tag_cloud)
tag_cloud.tags.map(&:name)
end