Merge branch 'gitlab-ce-milestone-tab-badges' into 'master'

Milestone tab badges

### What does this MR do?
Adds count badges to the tabs on the Milestone page to mirror the Issue page.

### Are there points in the code the reviewer needs to double check?
CSS: Padding was added to the `.milestone` element to make expired milestones look good.

### Why was this MR needed?
For consistency with other tabs

### What are the relevant issue numbers?
Closes #20114 

### Screenshots (if relevant)

**Before**
<img src="/uploads/1f75853a863a9058ee409916b7ee4057/Screen_Shot_2016-07-22_at_13.22.20.png" width="362">

**After**
<img src="/uploads/8a058290850f571db249dd9b23f5138d/Screen_Shot_2016-07-22_at_13.21.58.png"  width="303">

![Screen_Shot_2016-08-23_at_1.42.56_AM](/uploads/b072dc6bdf505b2fce46660beaf0eae7/Screen_Shot_2016-08-23_at_1.42.56_AM.png)

![Screen_Shot_2016-08-23_at_1.43.15_AM](/uploads/e7440bf2aa628aa4dba63863083699c0/Screen_Shot_2016-08-23_at_1.43.15_AM.png)

### Does this MR meet the acceptance criteria?

- [x] [CHANGELOG](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CHANGELOG) entry added
- [ ] [Documentation created/updated](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/development/doc_styleguide.md)
- [ ] API support added
- Tests
  - [x] Added for this feature/bug
  - [x] All builds are passing
- [ ] Conform by the [style guides](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#style-guides)
- [x] Branch has no merge conflicts with `master` (if you do - rebase it please)
- [ ] [Squashed related commits together](https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits)

See merge request !5946
This commit is contained in:
Fatih Acet 2016-09-24 10:49:45 +00:00
commit 513629f346
7 changed files with 99 additions and 12 deletions

View file

@ -87,6 +87,7 @@ v 8.12.0
- Fix markdown anchor icon interaction (ClemMakesApps)
- Test migration paths from 8.5 until current release !4874
- Replace animateEmoji timeout with eventListener (ClemMakesApps)
- Show badges in Milestone tabs. !5946 (Dan Rowden)
- Optimistic locking for Issues and Merge Requests (title and description overriding prevention)
- Require confirmation when not logged in for unsubscribe links !6223 (Maximiliano Perez Coto)
- Add `wiki_page_events` to project hook APIs (Ben Boeckel)

View file

@ -2,7 +2,10 @@
max-width: 90%;
}
li.milestone {
.milestones {
.milestone {
padding: 10px 16px;
h4 {
font-weight: bold;
}
@ -11,6 +14,7 @@ li.milestone {
height: 6px;
}
}
}
.milestone-content {
.issues-count {
@ -64,3 +68,14 @@ li.milestone {
border-bottom: 1px solid $border-color;
padding: 20px 0;
}
@media (max-width: $screen-sm-min) {
.milestone-actions {
@include clearfix();
padding-top: $gl-vert-padding;
.btn:first-child {
margin-left: 0;
}
}
}

View file

@ -35,6 +35,30 @@ module MilestonesHelper
milestone.issues.with_label(label.title).send(state).size
end
# Returns count of milestones for different states
# Uses explicit hash keys as the 'opened' state URL params differs from the db value
# and we need to add the total
def milestone_counts(milestones)
counts = milestones.reorder(nil).group(:state).count
{
opened: counts['active'] || 0,
closed: counts['closed'] || 0,
all: counts.values.sum || 0
}
end
# Show 'active' class if provided GET param matches check
# `or_blank` allows the function to return 'active' when given an empty param
# Could be refactored to be simpler but that may make it harder to read
def milestone_class_for_state(param, check, match_blank_param = false)
if match_blank_param
'active' if param.blank? || param == check
else
'active' if param == check
end
end
def milestone_progress_bar(milestone)
options = {
class: 'progress-bar progress-bar-success',

View file

@ -1,10 +1,19 @@
- if @project
- counts = milestone_counts(@project.milestones)
%ul.nav-links
%li{class: ("active" if params[:state].blank? || params[:state] == 'opened')}
%li{class: milestone_class_for_state(params[:state], 'opened', true)}
= link_to milestones_filter_path(state: 'opened') do
Open
%li{class: ("active" if params[:state] == 'closed')}
- if @project
%span.badge #{counts[:opened]}
%li{class: milestone_class_for_state(params[:state], 'closed')}
= link_to milestones_filter_path(state: 'closed') do
Closed
%li{class: ("active" if params[:state] == 'all')}
- if @project
%span.badge #{counts[:closed]}
%li{class: milestone_class_for_state(params[:state], 'all')}
= link_to milestones_filter_path(state: 'all') do
All
- if @project
%span.badge #{counts[:all]}

View file

@ -33,7 +33,7 @@
- if @project
.row
.col-sm-6= render('shared/milestone_expired', milestone: milestone)
.col-sm-6
.col-sm-6.milestone-actions
- if can?(current_user, :admin_milestone, milestone.project) and milestone.active?
= link_to edit_namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), class: "btn btn-xs btn-grouped" do
Edit

View file

@ -3,10 +3,15 @@ FactoryGirl.define do
title
project
trait :closed do
state :closed
trait :active do
state "active"
end
trait :closed do
state "closed"
end
factory :active_milestone, traits: [:active]
factory :closed_milestone, traits: [:closed]
end
end

View file

@ -0,0 +1,33 @@
require 'spec_helper'
describe MilestonesHelper do
describe '#milestone_counts' do
let(:project) { FactoryGirl.create(:project) }
let(:counts) { helper.milestone_counts(project.milestones) }
context 'when there are milestones' do
let!(:milestone_1) { FactoryGirl.create(:active_milestone, project: project) }
let!(:milestone_2) { FactoryGirl.create(:active_milestone, project: project) }
let!(:milestone_3) { FactoryGirl.create(:closed_milestone, project: project) }
it 'returns the correct counts' do
expect(counts).to eq(opened: 2, closed: 1, all: 3)
end
end
context 'when there are only milestones of one type' do
let!(:milestone_1) { FactoryGirl.create(:active_milestone, project: project) }
let!(:milestone_2) { FactoryGirl.create(:active_milestone, project: project) }
it 'returns the correct counts' do
expect(counts).to eq(opened: 2, closed: 0, all: 2)
end
end
context 'when there are no milestones' do
it 'returns the correct counts' do
expect(counts).to eq(opened: 0, closed: 0, all: 0)
end
end
end
end