Commit 40eec08c99fe29963afed0f073b7bdbbfe31ac59

Authored by Dmitriy Zaporozhets
2 parents d8f6d38d 0bfcc574

Merge pull request #1409 from riyad/update-votes

Update votes for issues and merge requests
app/assets/stylesheets/common.scss
@@ -415,13 +415,48 @@ p.time { @@ -415,13 +415,48 @@ p.time {
415 } 415 }
416 } 416 }
417 417
418 -.upvotes {  
419 - font-size: 14px;  
420 - font-weight: bold;  
421 - color: #468847;  
422 - text-align: right;  
423 - padding: 4px;  
424 - margin: 2px; 418 +.votes {
  419 + font-size: 13px;
  420 + line-height: 15px;
  421 + .progress {
  422 + height: 4px;
  423 + margin: 0;
  424 + .bar {
  425 + float: left;
  426 + height: 100%;
  427 + }
  428 + .bar-success {
  429 + background-color: #468847;
  430 + @include bg-gradient(#62C462, #51A351);
  431 + }
  432 + .bar-danger {
  433 + background-color: #B94A48;
  434 + @include bg-gradient(#EE5F5B, #BD362F);
  435 + }
  436 + }
  437 + .upvotes {
  438 + display: inline-block;
  439 + color: #468847;
  440 + }
  441 + .downvotes {
  442 + display: inline-block;
  443 + color: #B94A48;
  444 + }
  445 +}
  446 +.votes-block {
  447 + margin: 14px 6px 6px 0;
  448 + .downvotes {
  449 + float: right;
  450 + }
  451 +}
  452 +.votes-inline {
  453 + display: inline-block;
  454 + margin: 0 8px;
  455 + .progress {
  456 + display: inline-block;
  457 + padding: 0 0 2px;
  458 + width: 45px;
  459 + }
425 } 460 }
426 461
427 /* Fix for readme code (stopped it from being yellow) */ 462 /* Fix for readme code (stopped it from being yellow) */
app/models/issue.rb
1 class Issue < ActiveRecord::Base 1 class Issue < ActiveRecord::Base
2 include IssueCommonality 2 include IssueCommonality
3 - include Upvote 3 + include Votes
4 4
5 acts_as_taggable_on :labels 5 acts_as_taggable_on :labels
6 6
app/models/merge_request.rb
@@ -2,7 +2,7 @@ require File.join(Rails.root, &quot;app/models/commit&quot;) @@ -2,7 +2,7 @@ require File.join(Rails.root, &quot;app/models/commit&quot;)
2 2
3 class MergeRequest < ActiveRecord::Base 3 class MergeRequest < ActiveRecord::Base
4 include IssueCommonality 4 include IssueCommonality
5 - include Upvote 5 + include Votes
6 6
7 BROKEN_DIFF = "--broken-diff" 7 BROKEN_DIFF = "--broken-diff"
8 8
app/models/note.rb
@@ -105,6 +105,12 @@ class Note &lt; ActiveRecord::Base @@ -105,6 +105,12 @@ class Note &lt; ActiveRecord::Base
105 def upvote? 105 def upvote?
106 note.start_with?('+1') || note.start_with?(':+1:') 106 note.start_with?('+1') || note.start_with?(':+1:')
107 end 107 end
  108 +
  109 + # Returns true if this is a downvote note,
  110 + # otherwise false is returned
  111 + def downvote?
  112 + note.start_with?('-1') || note.start_with?(':-1:')
  113 + end
108 end 114 end
109 # == Schema Information 115 # == Schema Information
110 # 116 #
app/roles/upvote.rb
@@ -1,6 +0,0 @@ @@ -1,6 +0,0 @@
1 -module Upvote  
2 - # Return the number of +1 comments (upvotes)  
3 - def upvotes  
4 - notes.select(&:upvote?).size  
5 - end  
6 -end  
app/roles/votes.rb 0 → 100644
@@ -0,0 +1,32 @@ @@ -0,0 +1,32 @@
  1 +module Votes
  2 + # Return the number of +1 comments (upvotes)
  3 + def upvotes
  4 + notes.select(&:upvote?).size
  5 + end
  6 +
  7 + def upvotes_in_percent
  8 + if votes_count.zero?
  9 + 0
  10 + else
  11 + 100.0 / votes_count * upvotes
  12 + end
  13 + end
  14 +
  15 + # Return the number of -1 comments (downvotes)
  16 + def downvotes
  17 + notes.select(&:downvote?).size
  18 + end
  19 +
  20 + def downvotes_in_percent
  21 + if votes_count.zero?
  22 + 0
  23 + else
  24 + 100.0 - upvotes_in_percent
  25 + end
  26 + end
  27 +
  28 + # Return the total number of votes
  29 + def votes_count
  30 + upvotes + downvotes
  31 + end
  32 +end
app/views/issues/_show.html.haml
@@ -34,5 +34,5 @@ @@ -34,5 +34,5 @@
34 - else 34 - else
35 &nbsp; 35 &nbsp;
36 36
37 - - if issue.upvotes > 0  
38 - %span.badge.badge-success= "+#{issue.upvotes}" 37 + - if issue.votes_count > 0
  38 + = render 'votes/votes_inline', votable: issue
app/views/issues/show.html.haml
@@ -8,22 +8,22 @@ @@ -8,22 +8,22 @@
8 %span.right 8 %span.right
9 - if can?(current_user, :admin_project, @project) || @issue.author == current_user 9 - if can?(current_user, :admin_project, @project) || @issue.author == current_user
10 - if @issue.closed 10 - if @issue.closed
11 - = link_to 'Reopen', project_issue_path(@project, @issue, issue: {closed: false }, status_only: true), method: :put, class: "btn small" 11 + = link_to 'Reopen', project_issue_path(@project, @issue, issue: {closed: false }, status_only: true), method: :put, class: "btn grouped success"
12 - else 12 - else
13 - = link_to 'Close', project_issue_path(@project, @issue, issue: {closed: true }, status_only: true), method: :put, class: "btn small", title: "Close Issue" 13 + = link_to 'Close', project_issue_path(@project, @issue, issue: {closed: true }, status_only: true), method: :put, class: "btn grouped danger", title: "Close Issue"
14 - if can?(current_user, :admin_project, @project) || @issue.author == current_user 14 - if can?(current_user, :admin_project, @project) || @issue.author == current_user
15 - = link_to edit_project_issue_path(@project, @issue), class: "btn small" do 15 + = link_to edit_project_issue_path(@project, @issue), class: "btn grouped" do
16 %i.icon-edit 16 %i.icon-edit
17 Edit 17 Edit
18 18
19 - %br  
20 - - if @issue.upvotes > 0  
21 - .upvotes#upvotes= "+#{pluralize @issue.upvotes, 'upvote'}" 19 +.right
  20 + .span3#votes= render 'votes/votes_block', votable: @issue
22 21
23 .back_link 22 .back_link
24 = link_to project_issues_path(@project) do 23 = link_to project_issues_path(@project) do
25 &larr; To issues list 24 &larr; To issues list
26 25
  26 +
27 .main_box 27 .main_box
28 .top_box_content 28 .top_box_content
29 %h4 29 %h4
app/views/merge_requests/_merge_request.html.haml
@@ -23,5 +23,6 @@ @@ -23,5 +23,6 @@
23 authored by #{merge_request.author_name} 23 authored by #{merge_request.author_name}
24 = time_ago_in_words(merge_request.created_at) 24 = time_ago_in_words(merge_request.created_at)
25 ago 25 ago
26 - - if merge_request.upvotes > 0  
27 - %span.badge.badge-success= "+#{merge_request.upvotes}" 26 +
  27 + - if merge_request.votes_count > 0
  28 + = render 'votes/votes_inline', votable: merge_request
app/views/merge_requests/show/_mr_title.html.haml
@@ -23,10 +23,8 @@ @@ -23,10 +23,8 @@
23 %i.icon-edit 23 %i.icon-edit
24 Edit 24 Edit
25 25
26 - %br  
27 - - if @merge_request.upvotes > 0  
28 - .upvotes#upvotes= "+#{pluralize @merge_request.upvotes, 'upvote'}"  
29 - 26 +.right
  27 + .span3#votes= render 'votes/votes_block', votable: @merge_request
30 28
31 .back_link 29 .back_link
32 = link_to project_merge_requests_path(@project) do 30 = link_to project_merge_requests_path(@project) do
app/views/votes/_votes_block.html.haml 0 → 100644
@@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
  1 +.votes.votes-block
  2 + .progress
  3 + .bar.bar-success{style: "width: #{votable.upvotes_in_percent}%;"}
  4 + .bar.bar-danger{style: "width: #{votable.downvotes_in_percent}%;"}
  5 + .upvotes= "#{votable.upvotes} up"
  6 + .downvotes= "#{votable.downvotes} down"
app/views/votes/_votes_inline.html.haml 0 → 100644
@@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
  1 +.votes.votes-inline
  2 + .upvotes= votable.upvotes
  3 + .progress
  4 + .bar.bar-success{style: "width: #{votable.upvotes_in_percent}%;"}
  5 + .bar.bar-danger{style: "width: #{votable.downvotes_in_percent}%;"}
  6 + .downvotes= votable.downvotes
spec/models/issue_spec.rb
@@ -12,7 +12,7 @@ describe Issue do @@ -12,7 +12,7 @@ describe Issue do
12 12
13 describe 'modules' do 13 describe 'modules' do
14 it { should include_module(IssueCommonality) } 14 it { should include_module(IssueCommonality) }
15 - it { should include_module(Upvote) } 15 + it { should include_module(Votes) }
16 end 16 end
17 17
18 subject { Factory.create(:issue) } 18 subject { Factory.create(:issue) }
spec/models/merge_request_spec.rb
@@ -8,6 +8,6 @@ describe MergeRequest do @@ -8,6 +8,6 @@ describe MergeRequest do
8 8
9 describe 'modules' do 9 describe 'modules' do
10 it { should include_module(IssueCommonality) } 10 it { should include_module(IssueCommonality) }
11 - it { should include_module(Upvote) } 11 + it { should include_module(Votes) }
12 end 12 end
13 end 13 end
spec/models/note_spec.rb
@@ -24,6 +24,13 @@ describe Note do @@ -24,6 +24,13 @@ describe Note do
24 it "recognizes a neutral note" do 24 it "recognizes a neutral note" do
25 note = Factory(:note, note: "This is not a +1 note") 25 note = Factory(:note, note: "This is not a +1 note")
26 note.should_not be_upvote 26 note.should_not be_upvote
  27 + note.should_not be_downvote
  28 + end
  29 +
  30 + it "recognizes a neutral emoji note" do
  31 + note = build(:note, note: "I would :+1: this, but I don't want to")
  32 + note.should_not be_upvote
  33 + note.should_not be_downvote
27 end 34 end
28 35
29 it "recognizes a +1 note" do 36 it "recognizes a +1 note" do
@@ -31,19 +38,19 @@ describe Note do @@ -31,19 +38,19 @@ describe Note do
31 note.should be_upvote 38 note.should be_upvote
32 end 39 end
33 40
34 - it "recognizes a -1 note as no vote" do  
35 - note = Factory(:note, note: "-1 for this")  
36 - note.should_not be_upvote  
37 - end  
38 -  
39 it "recognizes a +1 emoji as a vote" do 41 it "recognizes a +1 emoji as a vote" do
40 note = build(:note, note: ":+1: for this") 42 note = build(:note, note: ":+1: for this")
41 note.should be_upvote 43 note.should be_upvote
42 end 44 end
43 45
44 - it "recognizes a neutral emoji note" do  
45 - note = build(:note, note: "I would :+1: this, but I don't want to")  
46 - note.should_not be_upvote 46 + it "recognizes a -1 note" do
  47 + note = Factory(:note, note: "-1 for this")
  48 + note.should be_downvote
  49 + end
  50 +
  51 + it "recognizes a -1 emoji as a vote" do
  52 + note = build(:note, note: ":-1: for this")
  53 + note.should be_downvote
47 end 54 end
48 end 55 end
49 56
spec/roles/upvote_spec.rb
@@ -1,27 +0,0 @@ @@ -1,27 +0,0 @@
1 -require 'spec_helper'  
2 -  
3 -describe Issue, "Upvote" do  
4 - let(:issue) { create(:issue) }  
5 -  
6 - it "with no notes has a 0/0 score" do  
7 - issue.upvotes.should == 0  
8 - end  
9 -  
10 - it "should recognize non-+1 notes" do  
11 - issue.notes << create(:note, note: "No +1 here")  
12 - issue.should have(1).note  
13 - issue.notes.first.upvote?.should be_false  
14 - issue.upvotes.should == 0  
15 - end  
16 -  
17 - it "should recognize a single +1 note" do  
18 - issue.notes << create(:note, note: "+1 This is awesome")  
19 - issue.upvotes.should == 1  
20 - end  
21 -  
22 - it "should recognize multiple +1 notes" do  
23 - issue.notes << create(:note, note: "+1 This is awesome")  
24 - issue.notes << create(:note, note: "+1 I want this")  
25 - issue.upvotes.should == 2  
26 - end  
27 -end  
spec/roles/votes_spec.rb 0 → 100644
@@ -0,0 +1,132 @@ @@ -0,0 +1,132 @@
  1 +require 'spec_helper'
  2 +
  3 +describe Issue do
  4 + let(:issue) { create(:issue) }
  5 +
  6 + describe "#upvotes" do
  7 + it "with no notes has a 0/0 score" do
  8 + issue.upvotes.should == 0
  9 + end
  10 +
  11 + it "should recognize non-+1 notes" do
  12 + issue.notes << create(:note, note: "No +1 here")
  13 + issue.should have(1).note
  14 + issue.notes.first.upvote?.should be_false
  15 + issue.upvotes.should == 0
  16 + end
  17 +
  18 + it "should recognize a single +1 note" do
  19 + issue.notes << create(:note, note: "+1 This is awesome")
  20 + issue.upvotes.should == 1
  21 + end
  22 +
  23 + it "should recognize multiple +1 notes" do
  24 + issue.notes << create(:note, note: "+1 This is awesome")
  25 + issue.notes << create(:note, note: "+1 I want this")
  26 + issue.upvotes.should == 2
  27 + end
  28 + end
  29 +
  30 + describe "#downvotes" do
  31 + it "with no notes has a 0/0 score" do
  32 + issue.downvotes.should == 0
  33 + end
  34 +
  35 + it "should recognize non--1 notes" do
  36 + issue.notes << create(:note, note: "Almost got a -1")
  37 + issue.should have(1).note
  38 + issue.notes.first.downvote?.should be_false
  39 + issue.downvotes.should == 0
  40 + end
  41 +
  42 + it "should recognize a single -1 note" do
  43 + issue.notes << create(:note, note: "-1 This is bad")
  44 + issue.downvotes.should == 1
  45 + end
  46 +
  47 + it "should recognize multiple -1 notes" do
  48 + issue.notes << create(:note, note: "-1 This is bad")
  49 + issue.notes << create(:note, note: "-1 Away with this")
  50 + issue.downvotes.should == 2
  51 + end
  52 + end
  53 +
  54 + describe "#votes_count" do
  55 + it "with no notes has a 0/0 score" do
  56 + issue.votes_count.should == 0
  57 + end
  58 +
  59 + it "should recognize non notes" do
  60 + issue.notes << create(:note, note: "No +1 here")
  61 + issue.should have(1).note
  62 + issue.votes_count.should == 0
  63 + end
  64 +
  65 + it "should recognize a single +1 note" do
  66 + issue.notes << create(:note, note: "+1 This is awesome")
  67 + issue.votes_count.should == 1
  68 + end
  69 +
  70 + it "should recognize a single -1 note" do
  71 + issue.notes << create(:note, note: "-1 This is bad")
  72 + issue.votes_count.should == 1
  73 + end
  74 +
  75 + it "should recognize multiple notes" do
  76 + issue.notes << create(:note, note: "+1 This is awesome")
  77 + issue.notes << create(:note, note: "-1 This is bad")
  78 + issue.notes << create(:note, note: "+1 I want this")
  79 + issue.votes_count.should == 3
  80 + end
  81 + end
  82 +
  83 + describe "#upvotes_in_percent" do
  84 + it "with no notes has a 0% score" do
  85 + issue.upvotes_in_percent.should == 0
  86 + end
  87 +
  88 + it "should count a single 1 note as 100%" do
  89 + issue.notes << create(:note, note: "+1 This is awesome")
  90 + issue.upvotes_in_percent.should == 100
  91 + end
  92 +
  93 + it "should count multiple +1 notes as 100%" do
  94 + issue.notes << create(:note, note: "+1 This is awesome")
  95 + issue.notes << create(:note, note: "+1 I want this")
  96 + issue.upvotes_in_percent.should == 100
  97 + end
  98 +
  99 + it "should count fractions for multiple +1 and -1 notes correctly" do
  100 + issue.notes << create(:note, note: "+1 This is awesome")
  101 + issue.notes << create(:note, note: "+1 I want this")
  102 + issue.notes << create(:note, note: "-1 This is bad")
  103 + issue.notes << create(:note, note: "+1 me too")
  104 + issue.upvotes_in_percent.should == 75
  105 + end
  106 + end
  107 +
  108 + describe "#downvotes_in_percent" do
  109 + it "with no notes has a 0% score" do
  110 + issue.downvotes_in_percent.should == 0
  111 + end
  112 +
  113 + it "should count a single -1 note as 100%" do
  114 + issue.notes << create(:note, note: "-1 This is bad")
  115 + issue.downvotes_in_percent.should == 100
  116 + end
  117 +
  118 + it "should count multiple -1 notes as 100%" do
  119 + issue.notes << create(:note, note: "-1 This is bad")
  120 + issue.notes << create(:note, note: "-1 Away with this")
  121 + issue.downvotes_in_percent.should == 100
  122 + end
  123 +
  124 + it "should count fractions for multiple +1 and -1 notes correctly" do
  125 + issue.notes << create(:note, note: "+1 This is awesome")
  126 + issue.notes << create(:note, note: "+1 I want this")
  127 + issue.notes << create(:note, note: "-1 This is bad")
  128 + issue.notes << create(:note, note: "+1 me too")
  129 + issue.downvotes_in_percent.should == 25
  130 + end
  131 + end
  132 +end