Commit a2ecebd84ea929b92b0829fb2289628131956eea
1 parent
8eba02a9
Exists in
master
and in
11 other branches
proposals_discussion: added discussion phases
Showing
14 changed files
with
259 additions
and
13 deletions
Show diff stats
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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 < 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 < 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 < 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 < 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 < 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 < 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__) + '/../test_helper' |
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 | ... | ... |
... | ... | @@ -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 %>"> | ... | ... |