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 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 462 /* Fix for readme code (stopped it from being yellow) */
... ...
app/models/issue.rb
1 1 class Issue < ActiveRecord::Base
2 2 include IssueCommonality
3   - include Upvote
  3 + include Votes
4 4  
5 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 2  
3 3 class MergeRequest < ActiveRecord::Base
4 4 include IssueCommonality
5   - include Upvote
  5 + include Votes
6 6  
7 7 BROKEN_DIFF = "--broken-diff"
8 8  
... ...
app/models/note.rb
... ... @@ -105,6 +105,12 @@ class Note &lt; ActiveRecord::Base
105 105 def upvote?
106 106 note.start_with?('+1') || note.start_with?(':+1:')
107 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 114 end
109 115 # == Schema Information
110 116 #
... ...
app/roles/upvote.rb
... ... @@ -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 @@
  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 34 - else
35 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 8 %span.right
9 9 - if can?(current_user, :admin_project, @project) || @issue.author == current_user
10 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 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 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 16 %i.icon-edit
17 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 22 .back_link
24 23 = link_to project_issues_path(@project) do
25 24 &larr; To issues list
26 25  
  26 +
27 27 .main_box
28 28 .top_box_content
29 29 %h4
... ...
app/views/merge_requests/_merge_request.html.haml
... ... @@ -23,5 +23,6 @@
23 23 authored by #{merge_request.author_name}
24 24 = time_ago_in_words(merge_request.created_at)
25 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 23 %i.icon-edit
24 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 29 .back_link
32 30 = link_to project_merge_requests_path(@project) do
... ...
app/views/votes/_votes_block.html.haml 0 → 100644
... ... @@ -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 @@
  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 12  
13 13 describe 'modules' do
14 14 it { should include_module(IssueCommonality) }
15   - it { should include_module(Upvote) }
  15 + it { should include_module(Votes) }
16 16 end
17 17  
18 18 subject { Factory.create(:issue) }
... ...
spec/models/merge_request_spec.rb
... ... @@ -8,6 +8,6 @@ describe MergeRequest do
8 8  
9 9 describe 'modules' do
10 10 it { should include_module(IssueCommonality) }
11   - it { should include_module(Upvote) }
  11 + it { should include_module(Votes) }
12 12 end
13 13 end
... ...
spec/models/note_spec.rb
... ... @@ -24,6 +24,13 @@ describe Note do
24 24 it "recognizes a neutral note" do
25 25 note = Factory(:note, note: "This is not a +1 note")
26 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 34 end
28 35  
29 36 it "recognizes a +1 note" do
... ... @@ -31,19 +38,19 @@ describe Note do
31 38 note.should be_upvote
32 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 41 it "recognizes a +1 emoji as a vote" do
40 42 note = build(:note, note: ":+1: for this")
41 43 note.should be_upvote
42 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 54 end
48 55 end
49 56  
... ...
spec/roles/upvote_spec.rb
... ... @@ -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 @@
  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
... ...