Implements story #136

Adds draft state for proposals
This commit is contained in:
Juan Salvador Pérez García
2018-06-07 12:19:26 +02:00
parent 83b8127b72
commit 77dd60427d
17 changed files with 176 additions and 38 deletions

View File

@@ -7,6 +7,7 @@
@import 'layout'; @import 'layout';
@import 'participation'; @import 'participation';
@import 'pages'; @import 'pages';
@import 'proposal';
@import 'legislation'; @import 'legislation';
@import 'legislation_process'; @import 'legislation_process';
@import 'community'; @import 'community';

View File

@@ -0,0 +1,11 @@
.proposal-show {
p.centered {
text-align: center;
}
div.centered {
display: flex;
justify-content: center;
align-items: center;
}
}

View File

@@ -11,6 +11,21 @@ class Management::ProposalsController < Management::BaseController
has_orders %w{confidence_score hot_score created_at most_commented random}, only: [:index, :print] has_orders %w{confidence_score hot_score created_at most_commented random}, only: [:index, :print]
has_orders %w{most_voted newest}, only: :show has_orders %w{most_voted newest}, only: :show
def create
@resource = resource_model.new(strong_params.merge(author: current_user, published_at: Time.now))
if @resource.save
track_event
redirect_path = url_for(controller: controller_name, action: :show, id: @resource.id)
redirect_to redirect_path, notice: t("flash.actions.create.#{resource_name.underscore}")
else
load_categories
load_geozones
set_resource_instance
render :new
end
end
def show def show
super super
@notifications = @proposal.notifications @notifications = @proposal.notifications
@@ -37,7 +52,8 @@ class Management::ProposalsController < Management::BaseController
def proposal_params def proposal_params
params.require(:proposal).permit(:title, :question, :summary, :description, :external_url, :video_url, params.require(:proposal).permit(:title, :question, :summary, :description, :external_url, :video_url,
:responsible_name, :tag_list, :terms_of_service, :geozone_id) :responsible_name, :tag_list, :terms_of_service, :geozone_id, :skip_map,
map_location_attributes: [:latitude, :longitude, :zoom])
end end
def resource_model def resource_model

View File

@@ -35,13 +35,16 @@ class ProposalsController < ApplicationController
@proposal = Proposal.new(proposal_params.merge(author: current_user)) @proposal = Proposal.new(proposal_params.merge(author: current_user))
if @proposal.save if @proposal.save
redirect_to share_proposal_path(@proposal), notice: I18n.t('flash.actions.create.proposal') redirect_to created_proposal_path(@proposal), notice: I18n.t('flash.actions.create.proposal')
else else
render :new render :new
end end
end end
def created; end
def index_customization def index_customization
discard_draft
discard_archived discard_archived
load_retired load_retired
load_successful_proposals load_successful_proposals
@@ -88,6 +91,11 @@ class ProposalsController < ApplicationController
end end
end end
def publish
@proposal.publish
redirect_to share_proposal_path(@proposal), notice: t('proposals.notice.published')
end
private private
def proposal_params def proposal_params
@@ -116,6 +124,10 @@ class ProposalsController < ApplicationController
@featured_proposals_votes = current_user ? current_user.proposal_votes(proposals) : {} @featured_proposals_votes = current_user ? current_user.proposal_votes(proposals) : {}
end end
def discard_draft
@resources = @resources.published
end
def discard_archived def discard_archived
@resources = @resources.not_archived unless @current_order == "archival_date" @resources = @resources.not_archived unless @current_order == "archival_date"
end end
@@ -158,5 +170,4 @@ class ProposalsController < ApplicationController
@recommended_proposals = Proposal.recommendations(current_user).sort_by_random.limit(3) @recommended_proposals = Proposal.recommendations(current_user).sort_by_random.limit(3)
end end
end end
end end

View File

@@ -13,7 +13,7 @@ class UsersController < ApplicationController
def set_activity_counts def set_activity_counts
@activity_counts = HashWithIndifferentAccess.new( @activity_counts = HashWithIndifferentAccess.new(
proposals: Proposal.where(author_id: @user.id).count, proposals: Proposal.created_by(@user).count,
debates: (Setting['feature.debates'] ? Debate.where(author_id: @user.id).count : 0), debates: (Setting['feature.debates'] ? Debate.where(author_id: @user.id).count : 0),
budget_investments: (Setting['feature.budgets'] ? Budget::Investment.where(author_id: @user.id).count : 0), budget_investments: (Setting['feature.budgets'] ? Budget::Investment.where(author_id: @user.id).count : 0),
comments: only_active_commentables.count, comments: only_active_commentables.count,
@@ -52,7 +52,7 @@ class UsersController < ApplicationController
end end
def load_proposals def load_proposals
@proposals = Proposal.where(author_id: @user.id).order(created_at: :desc).page(params[:page]) @proposals = Proposal.created_by(@user).order(created_at: :desc).page(params[:page])
end end
def load_debates def load_debates

View File

@@ -5,7 +5,7 @@ module Abilities
def initialize(user) def initialize(user)
merge Abilities::Everyone.new(user) merge Abilities::Everyone.new(user)
can [:read, :update], User, id: user.id can %i[read update], User, id: user.id
can :read, Debate can :read, Debate
can :update, Debate do |debate| can :update, Debate do |debate|
@@ -16,6 +16,10 @@ module Abilities
can :update, Proposal do |proposal| can :update, Proposal do |proposal|
proposal.editable_by?(user) proposal.editable_by?(user)
end end
can :publish, Proposal do |proposal|
proposal.draft? && proposal.author.id == user.id
end
can [:retire_form, :retire], Proposal, author_id: user.id can [:retire_form, :retire], Proposal, author_id: user.id
can :read, Legislation::Proposal can :read, Legislation::Proposal
@@ -26,7 +30,7 @@ module Abilities
can :create, Comment can :create, Comment
can :create, Debate can :create, Debate
can :create, Proposal can %i[create created], Proposal
can :create, Legislation::Proposal can :create, Legislation::Proposal
can :suggest, Debate can :suggest, Debate
@@ -60,7 +64,9 @@ module Abilities
end end
if user.level_two_or_three_verified? if user.level_two_or_three_verified?
can :vote, Proposal can :vote, Proposal do |proposal|
proposal.published?
end
can :vote_featured, Proposal can :vote_featured, Proposal
can :vote, SpendingProposal can :vote, SpendingProposal
can :create, SpendingProposal can :create, SpendingProposal

View File

@@ -71,11 +71,26 @@ class Proposal < ActiveRecord::Base
scope :unsuccessful, -> { 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 } scope :public_for_api, -> { all }
scope :not_supported_by_user, ->(user) { where.not(id: user.find_voted_items(votable_type: "Proposal").compact.map(&:id)) } scope :not_supported_by_user, ->(user) { where.not(id: user.find_voted_items(votable_type: "Proposal").compact.map(&:id)) }
scope :published, -> { where.not(published_at: nil) }
scope :draft, -> { where(published_at: nil) }
scope :created_by, ->(author) { unscoped.where(hidden_at: nil, author: author) }
def url def url
proposal_path(self) proposal_path(self)
end end
def publish
update(published_at: Time.now)
end
def published?
!published_at.nil?
end
def draft?
published_at.nil?
end
def self.recommendations(user) def self.recommendations(user)
tagged_with(user.interests, any: true) tagged_with(user.interests, any: true)
.where("author_id != ?", user.id) .where("author_id != ?", user.id)

View File

@@ -0,0 +1,12 @@
<div class=proposal-created>
<div id="<%= dom_id(@proposal) %>" class="row">
<div class="small-12 medium-12 column">
<h1><%= t '.title' %></h1>
<%= raw t '.motivation' %>
<%= link_to t('.dashboard'), '#', class: 'button' %>
<%= link_to t('.publish'), publish_proposal_path(@proposal), method: :patch, class: 'button' if can? :publish, @proposal %>
</div>
</div>
</div>

View File

@@ -27,6 +27,15 @@
description: @proposal.summary description: @proposal.summary
} %> } %>
<div class="callout light">
<p class="centered">
<strong><%= t '.improve_it' %></strong>
</p>
<div class="centered">
<%= link_to t('.dashboard'), '#', class: 'button' %>
</div>
</div>
<% if @proposal_improvement_path.present? %> <% if @proposal_improvement_path.present? %>
<div class="callout highlight margin-top text-center"> <div class="callout highlight margin-top text-center">
<p class="lead"><strong><%= t("proposals.proposal.improve_info") %></strong></p> <p class="lead"><strong><%= t("proposals.proposal.improve_info") %></strong></p>

View File

@@ -165,34 +165,35 @@
</div> </div>
<% end %> <% end %>
<div class="sidebar-divider"></div> <% if @proposal.published? %>
<h2><%= t("votes.supports") %></h2> <div class="sidebar-divider"></div>
<div id="<%= dom_id(@proposal) %>_votes"> <h2><%= t("votes.supports") %></h2>
<div id="<%= dom_id(@proposal) %>_votes">
<% if @proposal.successful? %> <% if @proposal.successful? %>
<p>
<%= t("proposals.proposal.successful",
voting: link_to(t("proposals.proposal.voting"), polls_path)).html_safe %>
</p>
<% if can? :create, Poll::Question %>
<p class="text-center">
<%= link_to t('poll_questions.create_question'),
new_admin_question_path(proposal_id: @proposal.id),
class: "button hollow expanded" %>
</p>
<% end %>
<% elsif @proposal.archived? %>
<div class="padding text-center">
<p> <p>
<strong><%= t("proposals.proposal.supports", count: @proposal.total_votes) %></strong> <%= t("proposals.proposal.successful",
voting: link_to(t("proposals.proposal.voting"), polls_path)).html_safe %>
</p> </p>
<p><%= t("proposals.proposal.archived") %></p> <% if can? :create, Poll::Question %>
</div> <p class="text-center">
<% else %> <%= link_to t('poll_questions.create_question'),
<%= render 'votes', new_admin_question_path(proposal_id: @proposal.id),
{ proposal: @proposal, vote_url: vote_proposal_path(@proposal, value: 'yes') } %> class: "button hollow expanded" %>
<% end %> </p>
</div> <% end %>
<% elsif @proposal.archived? %>
<div class="padding text-center">
<p>
<strong><%= t("proposals.proposal.supports", count: @proposal.total_votes) %></strong>
</p>
<p><%= t("proposals.proposal.archived") %></p>
</div>
<% else %>
<%= render 'votes',
{ proposal: @proposal, vote_url: vote_proposal_path(@proposal, value: 'yes') } %>
<% end %>
</div>
<% end %>
<%= render partial: 'shared/social_share', locals: { <%= render partial: 'shared/social_share', locals: {
share_title: t("proposals.show.share"), share_title: t("proposals.show.share"),
title: @proposal.title, title: @proposal.title,

View File

@@ -312,6 +312,22 @@ en:
create: create:
form: form:
submit_button: Create proposal submit_button: Create proposal
created:
title: Congratulations! You have taken the first step.
motivation: |
<p>
It is important to have at least 100 supports on the first day of the campaign to be able to
boost your proposal.
</p>
<p>
Do you think you need help to achieve this goal? If so, leave your proposal
as a draft and we will guide you.
</p>
publish: No, I want to publish the proposal
dashboard: Yes, I want help and I'll publish later
share:
improve_it: Improve your campaign and get more support.
dashboard: See more information
edit: edit:
editing: Edit proposal editing: Edit proposal
form: form:
@@ -415,6 +431,7 @@ en:
start_new: Create new proposal start_new: Create new proposal
notice: notice:
retired: Proposal retired retired: Proposal retired
published: The proposal has been published
proposal: proposal:
created: "You've created a proposal!" created: "You've created a proposal!"
share: share:

View File

@@ -312,6 +312,22 @@ es:
create: create:
form: form:
submit_button: Crear propuesta submit_button: Crear propuesta
created:
title: ¡Enhorabuena! Has dado el primer paso.
motivation: |
<p>
Es importante contar al menos con 100 apoyos el primer día de campaña para poder
impulsar tu propuesta.
</p>
<p>
¿Crees que necesitas ayuda para alcanzar esta meta? Si es así, deja tu propuesta
como borrador y te guiaremos.
</p>
publish: No, quiero publicar la propuesta ya
dashboard: Si, quiero ayuda y publicaré mas tarde
share:
improve_it: Mejora tu campaña y consigue mas apoyos.
dashboard: Ver mas información
edit: edit:
editing: Editar propuesta editing: Editar propuesta
form: form:
@@ -415,6 +431,7 @@ es:
start_new: Crear una propuesta start_new: Crear una propuesta
notice: notice:
retired: Propuesta retirada retired: Propuesta retirada
published: La propuesta ha sido publicada
proposal: proposal:
created: "¡Has creado una propuesta!" created: "¡Has creado una propuesta!"
share: share:

View File

@@ -6,7 +6,9 @@ resources :proposals do
put :unflag put :unflag
get :retire_form get :retire_form
get :share get :share
get :created
patch :retire patch :retire
patch :publish
end end
collection do collection do

View File

@@ -37,7 +37,8 @@ section "Creating Proposals" do
tag_list: tags.sample(3).join(','), tag_list: tags.sample(3).join(','),
geozone: Geozone.all.sample, geozone: Geozone.all.sample,
skip_map: "1", skip_map: "1",
terms_of_service: "1") terms_of_service: "1",
published_at: Time.now)
add_image_to proposal add_image_to proposal
end end
end end
@@ -58,7 +59,8 @@ section "Creating Archived Proposals" do
geozone: Geozone.all.sample, geozone: Geozone.all.sample,
skip_map: "1", skip_map: "1",
terms_of_service: "1", terms_of_service: "1",
created_at: Setting["months_to_archive_proposals"].to_i.months.ago) created_at: Setting["months_to_archive_proposals"].to_i.months.ago,
published_at: Setting["months_to_archive_proposals"].to_i.months.ago)
add_image_to proposal add_image_to proposal
end end
end end
@@ -80,7 +82,8 @@ section "Creating Successful Proposals" do
geozone: Geozone.all.sample, geozone: Geozone.all.sample,
skip_map: "1", skip_map: "1",
terms_of_service: "1", terms_of_service: "1",
cached_votes_up: Setting["votes_for_proposal_success"]) cached_votes_up: Setting["votes_for_proposal_success"],
published_at: Time.now)
add_image_to proposal add_image_to proposal
end end
@@ -99,7 +102,8 @@ section "Creating Successful Proposals" do
tag_list: tags.sample(3).join(','), tag_list: tags.sample(3).join(','),
geozone: Geozone.all.sample, geozone: Geozone.all.sample,
skip_map: "1", skip_map: "1",
terms_of_service: "1") terms_of_service: "1",
published_at: Time.now)
add_image_to proposal add_image_to proposal
end end
end end
@@ -110,5 +114,6 @@ section "Creating proposal notifications" do
body: "Proposal notification body #{i}", body: "Proposal notification body #{i}",
author: User.all.sample, author: User.all.sample,
proposal: Proposal.all.sample) proposal: Proposal.all.sample)
end end
end end

View File

@@ -0,0 +1,9 @@
class AddPublishedAtToProposal < ActiveRecord::Migration
def change
add_column :proposals, :published_at, :datetime, null: true
Proposal.draft.find_each do |proposal|
proposal.update(published_at: proposal.created_at)
end
end
end

View File

@@ -916,6 +916,7 @@ ActiveRecord::Schema.define(version: 20180711224810) do
t.string "retired_reason" t.string "retired_reason"
t.text "retired_explanation" t.text "retired_explanation"
t.integer "community_id" t.integer "community_id"
t.datetime "published_at"
end end
add_index "proposals", ["author_id", "hidden_at"], name: "index_proposals_on_author_id_and_hidden_at", using: :btree add_index "proposals", ["author_id", "hidden_at"], name: "index_proposals_on_author_id_and_hidden_at", using: :btree

View File

@@ -170,6 +170,7 @@ FactoryBot.define do
responsible_name 'John Snow' responsible_name 'John Snow'
terms_of_service '1' terms_of_service '1'
skip_map '1' skip_map '1'
published_at { Time.now }
association :author, factory: :user association :author, factory: :user
trait :hidden do trait :hidden do
@@ -212,6 +213,10 @@ FactoryBot.define do
trait :successful do trait :successful do
cached_votes_up { Proposal.votes_needed_for_success + 100 } cached_votes_up { Proposal.votes_needed_for_success + 100 }
end end
trait :draft do
published_at nil
end
end end
factory :spending_proposal do factory :spending_proposal do