Commit a2ecebd84ea929b92b0829fb2289628131956eea

Authored by Victor Costa
1 parent 8eba02a9

proposals_discussion: added discussion phases

lib/ext/vote.rb 0 → 100644
... ... @@ -0,0 +1,15 @@
  1 +require_dependency 'models/vote'
  2 +
  3 +class Vote
  4 +
  5 + validate :proposals_discussion_plugin_modify_vote
  6 + before_destroy :proposals_discussion_plugin_modify_vote
  7 +
  8 + def proposals_discussion_plugin_modify_vote
  9 + if voteable.kind_of?(ProposalsDiscussionPlugin::Proposal) && !voteable.allow_vote?
  10 + errors.add(:base, _("Can't vote in this discussion anymore."))
  11 + false
  12 + end
  13 + end
  14 +
  15 +end
... ...
lib/proposals_discussion_plugin/discussion.rb
... ... @@ -19,8 +19,11 @@ class ProposalsDiscussionPlugin::Discussion < ProposalsDiscussionPlugin::Proposa
19 19  
20 20 settings_items :custom_body_label, :type => :string, :default => _('Body')
21 21 settings_items :allow_topics, :type => :boolean, :default => false
  22 + settings_items :phase, :type => :string, :default => :proposals
22 23  
23   - attr_accessible :custom_body_label, :allow_topics
  24 + attr_accessible :custom_body_label, :allow_topics, :phase
  25 +
  26 + AVAILABLE_PHASES = {:proposals => _('Proposals'), :vote => 'Vote', :finish => 'Announcement'}
24 27  
25 28 def self.short_description
26 29 _("Discussion")
... ... @@ -30,6 +33,14 @@ class ProposalsDiscussionPlugin::Discussion < ProposalsDiscussionPlugin::Proposa
30 33 _('Container for topics.')
31 34 end
32 35  
  36 + def available_phases
  37 + AVAILABLE_PHASES
  38 + end
  39 +
  40 + def allow_new_proposals?
  41 + phase.to_sym == :proposals
  42 + end
  43 +
33 44 def to_html(options = {})
34 45 discussion = self
35 46 proc do
... ...
lib/proposals_discussion_plugin/discussion_helper.rb 0 → 100644
... ... @@ -0,0 +1,22 @@
  1 +module ProposalsDiscussionPlugin::DiscussionHelper
  2 +
  3 + def link_to_new_proposal(discussion)
  4 + return '' unless discussion.allow_new_proposals?
  5 +
  6 + url = {:parent_id => discussion.id, :profile => discussion.profile.identifier}
  7 + if discussion.allow_topics
  8 + url.merge!(:controller => 'proposals_discussion_plugin_myprofile', :action => 'select_topic')
  9 + else
  10 + url.merge!(:controller => 'cms', :action => 'new', :type => "ProposalsDiscussionPlugin::Proposal")
  11 + end
  12 + link_to _("Send your proposal!"), url_for(url), :class => 'button with-text icon-add'
  13 + end
  14 +
  15 + def discussion_phases(discussion)
  16 + discussion.available_phases.map do |phase|
  17 + active = discussion.phase.to_sym == phase.first ? ' active' : ''
  18 + content_tag 'span', phase.second, :class => "phase #{phase.first}#{active}"
  19 + end.join
  20 + end
  21 +
  22 +end
... ...
lib/proposals_discussion_plugin/proposal.rb
... ... @@ -15,8 +15,18 @@ class ProposalsDiscussionPlugin::Proposal < TinyMceArticle
15 15  
16 16 validates_presence_of :abstract
17 17  
  18 + validate :discussion_phase_proposals
  19 +
  20 + def discussion_phase_proposals
  21 + errors.add(:base, _("Can't create a proposal at this phase.")) unless discussion.allow_new_proposals?
  22 + end
  23 +
  24 + def allow_vote?
  25 + discussion.phase.to_sym != :finish
  26 + end
  27 +
18 28 def discussion
19   - parent.kind_of?(ProposalsDiscussionPlugin::Discussion) ? parent : parent.discussion
  29 + @discussion ||= parent.kind_of?(ProposalsDiscussionPlugin::Discussion) ? parent : parent.discussion
20 30 end
21 31  
22 32 def to_html(options = {})
... ...
public/style.css
  1 +.article-body-proposals-discussion-plugin_discussion .phases {
  2 + margin: 20px;
  3 + color: white;
  4 +}
  5 +.article-body-proposals-discussion-plugin_discussion .phases .phase {
  6 + padding: 8px;
  7 + background-color: gray;
  8 + min-width: 70px;
  9 + display: inline-block;
  10 + text-align: center;
  11 + font-size: 14px;
  12 + font-weight: bold;
  13 + opacity: 0.3;
  14 +}
  15 +.article-body-proposals-discussion-plugin_discussion .phases .proposals {
  16 + background-color: rgb(55, 186, 211);
  17 +}
  18 +.article-body-proposals-discussion-plugin_discussion .phases .vote {
  19 + background-color: rgb(236, 141, 52);
  20 +}
  21 +.article-body-proposals-discussion-plugin_discussion .phases .finish {
  22 + background-color: rgb(57, 175, 142);
  23 +}
  24 +.article-body-proposals-discussion-plugin_discussion .phases .active {
  25 + opacity: 1;
  26 +}
  27 +
1 28 .proposals_list .proposal .abstract {
2 29 color: rgb(160, 160, 160);
3 30 margin-bottom: 15px;
... ...
test/functional/cms_controller_test.rb
... ... @@ -30,4 +30,9 @@ class CmsControllerTest < ActionController::TestCase
30 30 assert_tag :tag => 'label', :attributes => {:class => 'formlabel'}, :content => 'My Custom Label'
31 31 end
32 32  
  33 + should 'display available phases when edit a proposal' do
  34 + get :edit, :id => discussion.id, :profile => profile.identifier
  35 + assert_tag :tag => 'select', :attributes => {:id => 'article_phase'}
  36 + end
  37 +
33 38 end
... ...
test/unit/discussion_helper_test.rb 0 → 100644
... ... @@ -0,0 +1,28 @@
  1 +require_relative '../test_helper'
  2 +
  3 +class DiscussionHelperTest < ActionView::TestCase
  4 +
  5 + def setup
  6 + @profile = fast_create(Community)
  7 + @discussion = ProposalsDiscussionPlugin::Discussion.create!(:name => 'discussion', :profile => @profile, :name => 'discussion')
  8 + end
  9 +
  10 + include ProposalsDiscussionPlugin::DiscussionHelper
  11 +
  12 + attr_reader :profile, :discussion
  13 +
  14 + should 'display new proposal link when discussion is in proposals phase' do
  15 + assert !link_to_new_proposal(discussion).blank?
  16 + end
  17 +
  18 + should 'not display new proposal link when discussion is in vote phase' do
  19 + discussion.update_attribute(:phase, :vote)
  20 + assert link_to_new_proposal(discussion).blank?
  21 + end
  22 +
  23 + should 'not display new proposal link when discussion is in finish phase' do
  24 + discussion.update_attribute(:phase, :finish)
  25 + assert link_to_new_proposal(discussion).blank?
  26 + end
  27 +
  28 +end
... ...
test/unit/discussion_test.rb
... ... @@ -34,4 +34,19 @@ class DiscussionTest &lt; ActiveSupport::TestCase
34 34 assert_equal 10, discussion.max_score
35 35 end
36 36  
  37 + should 'allow new proposals if discussion phase is proposals' do
  38 + discussion.phase = :proposals
  39 + assert discussion.allow_new_proposals?
  40 + end
  41 +
  42 + should 'not allow new proposals if discussion phase is vote' do
  43 + discussion.phase = :vote
  44 + assert !discussion.allow_new_proposals?
  45 + end
  46 +
  47 + should 'not allow new proposals if discussion phase is finish' do
  48 + discussion.phase = :finish
  49 + assert !discussion.allow_new_proposals?
  50 + end
  51 +
37 52 end
... ...
test/unit/proposal_test.rb
... ... @@ -5,11 +5,12 @@ class ProposalTest &lt; ActiveSupport::TestCase
5 5 def setup
6 6 @profile = fast_create(Community)
7 7 @person = fast_create(Person)
8   - @proposal = ProposalsDiscussionPlugin::Proposal.new(:name => 'test', :profile => @profile)
  8 + @discussion = ProposalsDiscussionPlugin::Discussion.create!(:name => 'discussion', :profile => person, :name => 'discussion')
  9 + @proposal = ProposalsDiscussionPlugin::Proposal.new(:name => 'test', :abstract => 'abstract', :profile => @profile, :parent => @discussion)
9 10 @proposal.created_by = @person
10 11 end
11 12  
12   - attr_reader :profile, :proposal, :person
  13 + attr_reader :profile, :proposal, :person, :discussion
13 14  
14 15 should 'save a proposal' do
15 16 proposal.abstract = 'abstract'
... ... @@ -17,6 +18,7 @@ class ProposalTest &lt; ActiveSupport::TestCase
17 18 end
18 19  
19 20 should 'do not save a proposal without abstract' do
  21 + proposal.abstract = nil
20 22 proposal.save
21 23 assert proposal.errors['abstract'].present?
22 24 end
... ... @@ -42,7 +44,6 @@ class ProposalTest &lt; ActiveSupport::TestCase
42 44 end
43 45  
44 46 should 'return proposals by discussion' do
45   - discussion = fast_create(ProposalsDiscussionPlugin::Discussion)
46 47 topic = fast_create(ProposalsDiscussionPlugin::Topic, :parent_id => discussion.id)
47 48 proposal1 = fast_create(ProposalsDiscussionPlugin::Proposal, :parent_id => topic.id)
48 49 proposal2 = fast_create(ProposalsDiscussionPlugin::Proposal)
... ... @@ -51,8 +52,17 @@ class ProposalTest &lt; ActiveSupport::TestCase
51 52 assert_equivalent [proposal1, proposal3], ProposalsDiscussionPlugin::Proposal.from_discussion(discussion)
52 53 end
53 54  
  55 + should 'return discussion associated with a proposal' do
  56 + assert_equal discussion, proposal.discussion
  57 + end
  58 +
  59 + should 'return discussion associated with a proposal topic' do
  60 + topic = fast_create(ProposalsDiscussionPlugin::Topic, :parent_id => discussion.id)
  61 + proposal = fast_create(ProposalsDiscussionPlugin::Proposal, :parent_id => topic.id)
  62 + assert_equal discussion, proposal.discussion
  63 + end
  64 +
54 65 should 'return normalized score' do
55   - discussion = ProposalsDiscussionPlugin::Discussion.create!(:profile => person, :name => 'discussion')
56 66 proposal1 = ProposalsDiscussionPlugin::Proposal.create!(:parent => discussion, :profile => profile, :name => "proposal1", :abstract => 'abstract')
57 67 proposal2 = ProposalsDiscussionPlugin::Proposal.create!(:parent => discussion, :profile => profile, :name => "proposal2", :abstract => 'abstract')
58 68 10.times { Comment.create!(:source => proposal1, :body => "comment", :author => person) }
... ... @@ -61,4 +71,50 @@ class ProposalTest &lt; ActiveSupport::TestCase
61 71 assert_equal 0.5, proposal2.reload.normalized_score
62 72 end
63 73  
  74 + should 'create a new proposal if the current phase is proposals' do
  75 + discussion.update_attribute(:phase, :proposals)
  76 + assert proposal.save
  77 + end
  78 +
  79 + should 'do not create a new proposal if the current phase is vote' do
  80 + discussion.update_attribute(:phase, :vote)
  81 + assert !proposal.save
  82 + end
  83 +
  84 + should 'do not create a new proposal if the current phase is finish' do
  85 + discussion.update_attribute(:phase, :finish)
  86 + assert !proposal.save
  87 + end
  88 +
  89 + should 'do not create a new proposal if the current phase is invalid' do
  90 + discussion.update_attribute(:phase, '')
  91 + assert !proposal.save
  92 + end
  93 +
  94 + should 'do not update a proposal if a discussion is not in proposals phase' do
  95 + discussion.update_attribute(:phase, :vote)
  96 + proposal.body = "changed"
  97 + assert !proposal.save
  98 + end
  99 +
  100 + should 'allow update of proposals if a discussion is in proposals phase' do
  101 + proposal.body = "changed"
  102 + assert proposal.save
  103 + end
  104 +
  105 + should 'allow vote if discussion phase is vote' do
  106 + discussion.update_attribute(:phase, :vote)
  107 + assert proposal.allow_vote?
  108 + end
  109 +
  110 + should 'allow vote if discussion phase is proposals' do
  111 + discussion.update_attribute(:phase, :proposals)
  112 + assert proposal.allow_vote?
  113 + end
  114 +
  115 + should 'not allow vote if discussion phase is finish' do
  116 + discussion.update_attribute(:phase, :finish)
  117 + assert !proposal.allow_vote?
  118 + end
  119 +
64 120 end
... ...
test/unit/topic_test.rb
... ... @@ -3,8 +3,9 @@ require File.dirname(__FILE__) + &#39;/../test_helper&#39;
3 3 class TopicTest < ActiveSupport::TestCase
4 4  
5 5 def setup
  6 + @discussion = fast_create(ProposalsDiscussionPlugin::Discussion)
6 7 @profile = fast_create(Community)
7   - @topic = ProposalsDiscussionPlugin::Topic.new(:name => 'test', :profile => @profile)
  8 + @topic = ProposalsDiscussionPlugin::Topic.new(:name => 'test', :profile => @profile, :parent => @discussion)
8 9 end
9 10  
10 11 attr_reader :profile, :topic
... ...
test/unit/vote_test.rb 0 → 100644
... ... @@ -0,0 +1,53 @@
  1 +require_relative '../test_helper'
  2 +
  3 +class VoteTest < ActiveSupport::TestCase
  4 +
  5 + def setup
  6 + @person = fast_create(Person)
  7 + @profile = fast_create(Community)
  8 + @discussion = ProposalsDiscussionPlugin::Discussion.create!(:name => 'discussion', :profile => @person, :name => 'discussion')
  9 + @proposal = ProposalsDiscussionPlugin::Proposal.create!(:name => 'test', :abstract => 'abstract', :profile => @profile, :parent => @discussion)
  10 + end
  11 +
  12 + attr_reader :profile, :proposal, :person, :discussion
  13 +
  14 + should 'vote for articles that are not proposals' do
  15 + article = fast_create(Article)
  16 + vote = Vote.new(:voteable => article, :voter => person, :vote => 1)
  17 + assert vote.save
  18 + end
  19 +
  20 + should 'vote for a proposal of a discussion in proposals phase' do
  21 + proposal.discussion.phase = :proposals
  22 + vote = Vote.new(:voteable => proposal, :voter => person, :vote => 1)
  23 + assert vote.save
  24 + end
  25 +
  26 + should 'vote for a proposal of a discussion in vote phase' do
  27 + proposal.discussion.phase = :vote
  28 + vote = Vote.new(:voteable => proposal, :voter => person, :vote => 1)
  29 + assert vote.save
  30 + end
  31 +
  32 + should 'not vote for a proposal of a finished discussion' do
  33 + proposal.discussion.phase = :finish
  34 + vote = Vote.new(:voteable => proposal, :voter => person, :vote => 1)
  35 + assert !vote.save
  36 + end
  37 +
  38 + should 'not destroy a proposal vote of a finished discussion' do
  39 + proposal.discussion.phase = :vote
  40 + vote = Vote.new(:voteable => proposal, :voter => person, :vote => 1)
  41 + assert vote.save
  42 + proposal.discussion.phase = :finish
  43 + assert !vote.destroy
  44 + end
  45 +
  46 + should 'destroy a proposal vote of a discussion in vote phase' do
  47 + proposal.discussion.phase = :vote
  48 + vote = Vote.new(:voteable => proposal, :voter => person, :vote => 1)
  49 + assert vote.save
  50 + assert vote.destroy
  51 + end
  52 +
  53 +end
... ...
views/cms/proposals_discussion_plugin/_discussion.html.erb
... ... @@ -6,4 +6,5 @@
6 6 <%= labelled_form_field(_('Description:'), text_area(:article, :body, :rows => 3, :cols => 64)) %>
7 7  
8 8 <%= f.text_field(:custom_body_label) %>
  9 +<%= labelled_form_field _('Current Phase'), f.select(:phase, ProposalsDiscussionPlugin::Discussion::AVAILABLE_PHASES.map{|k,v| [v,k]} ) %>
9 10 <%= labelled_form_field check_box(:article, :allow_topics) + _('Allow topics'), '' %>
... ...
views/content_viewer/discussion.html.erb
  1 +<% extend ProposalsDiscussionPlugin::DiscussionHelper %>
1 2 <%= javascript_include_tag 'plugins/proposals_discussion/proposals_list.js' %>
2 3  
3 4 <%= add_rss_feed_to_head(discussion.name, discussion.feed.url) if discussion.feed %>
... ... @@ -6,6 +7,10 @@
6 7 <%= discussion.body %>
7 8 </div>
8 9  
  10 +<div class="phases">
  11 + <%= discussion_phases(discussion) %>
  12 +</div>
  13 +
9 14 <% if discussion.allow_create?(user) %>
10 15 <div class="actions">
11 16 <%= link_to({:controller => :proposals_discussion_plugin_profile, :action => :export, :format => :csv, :article_id => discussion.id}, :class => 'button with-text icon-spread') do %>
... ... @@ -21,9 +26,7 @@
21 26 </div>
22 27  
23 28 <div class="new-proposal">
24   - <%= link_to url_for({:controller => 'cms', :action => 'new', :type => "ProposalsDiscussionPlugin::Proposal", :parent_id => discussion.id}), :class => 'button with-text icon-add' do %>
25   - <strong><%= _("Send your proposal!") %></strong>
26   - <% end %>
  29 + <%= link_to_new_proposal(discussion) %>
27 30 </div>
28 31  
29 32 <%= render :partial => 'content_viewer/proposals_list', :locals => {:holder => discussion} %>
... ...
views/content_viewer/discussion_topics.html.erb
  1 +<% extend ProposalsDiscussionPlugin::DiscussionHelper %>
1 2 <%= javascript_include_tag 'plugins/proposals_discussion/proposals_list.js' %>
2 3  
3 4 <%= add_rss_feed_to_head(discussion.name, discussion.feed.url) if discussion.feed %>
... ... @@ -20,9 +21,7 @@
20 21 <div class="topics js-masonry" data-masonry-options='{ "itemSelector": ".topic-item", "columnWidth": 200 }'>
21 22 <div class="actions topic-item">
22 23 <div class="topic-color"></div>
23   - <%= link_to url_for({:controller => 'proposals_discussion_plugin_myprofile', :action => 'select_topic', :parent_id => discussion.id}), :class => 'button with-text icon-add' do %>
24   - <strong><%= _("Send your proposal!") %></strong>
25   - <% end %>
  24 + <%= link_to_new_proposal(discussion) %>
26 25 </div>
27 26 <% discussion.topics.includes(:profile).each do |topic| %>
28 27 <div class="topic-item" id="topic-<%= topic.id %>">
... ...