Merge pull request #1213 from consul/archived-proposals
Archived proposals
This commit is contained in:
@@ -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
|
||||
// ---------------------
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ module CommentableActions
|
||||
|
||||
@tag_cloud = tag_cloud
|
||||
@banners = Banner.with_active
|
||||
|
||||
|
||||
set_resource_votes(@resources)
|
||||
set_resources_instance
|
||||
end
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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:'
|
||||
|
||||
@@ -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:'
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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"] = ''
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user