Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
8fb76a83
Commit
8fb76a83
authored
Jul 01, 2014
by
Dmitriy Zaporozhets
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'group_milestones' into 'master'
Group milestones
parents
bcfcd243
3e52517d
Changes
17
Hide whitespace changes
Inline
Side-by-side
Showing
17 changed files
with
547 additions
and
0 deletions
+547
-0
app/assets/stylesheets/sections/groups.scss
app/assets/stylesheets/sections/groups.scss
+4
-0
app/controllers/groups/milestones_controller.rb
app/controllers/groups/milestones_controller.rb
+54
-0
app/helpers/groups_helper.rb
app/helpers/groups_helper.rb
+11
-0
app/models/group_milestone.rb
app/models/group_milestone.rb
+95
-0
app/services/milestones/group_service.rb
app/services/milestones/group_service.rb
+26
-0
app/views/groups/_filter.html.haml
app/views/groups/_filter.html.haml
+12
-0
app/views/groups/milestones/_issue.html.haml
app/views/groups/milestones/_issue.html.haml
+10
-0
app/views/groups/milestones/_issues.html.haml
app/views/groups/milestones/_issues.html.haml
+6
-0
app/views/groups/milestones/_merge_request.html.haml
app/views/groups/milestones/_merge_request.html.haml
+10
-0
app/views/groups/milestones/_merge_requests.html.haml
app/views/groups/milestones/_merge_requests.html.haml
+6
-0
app/views/groups/milestones/index.html.haml
app/views/groups/milestones/index.html.haml
+49
-0
app/views/groups/milestones/show.html.haml
app/views/groups/milestones/show.html.haml
+76
-0
app/views/layouts/nav/_group.html.haml
app/views/layouts/nav/_group.html.haml
+3
-0
config/routes.rb
config/routes.rb
+2
-0
features/group.feature
features/group.feature
+19
-0
features/steps/group/group.rb
features/steps/group/group.rb
+94
-0
spec/services/milestones/group_service_spec.rb
spec/services/milestones/group_service_spec.rb
+70
-0
No files found.
app/assets/stylesheets/sections/groups.scss
View file @
8fb76a83
...
...
@@ -7,3 +7,7 @@
.member-search-form
{
float
:
left
;
}
.milestone-row
{
@include
str-truncated
(
90%
);
}
app/controllers/groups/milestones_controller.rb
0 → 100644
View file @
8fb76a83
class
Groups::MilestonesController
<
ApplicationController
layout
'group'
before_filter
:authorize_group_milestone!
,
only: :update
def
index
project_milestones
=
Milestone
.
where
(
project_id:
group
.
projects
)
@group_milestones
=
Milestones
::
GroupService
.
new
(
project_milestones
).
execute
@group_milestones
=
case
params
[
:status
]
when
'all'
;
@group_milestones
when
'closed'
;
status
(
'closed'
)
else
status
(
'active'
)
end
end
def
show
project_milestones
=
Milestone
.
where
(
project_id:
group
.
projects
)
@group_milestone
=
Milestones
::
GroupService
.
new
(
project_milestones
).
milestone
(
title
)
end
def
update
project_milestones
=
Milestone
.
where
(
project_id:
group
.
projects
)
@group_milestones
=
Milestones
::
GroupService
.
new
(
project_milestones
).
milestone
(
title
)
@group_milestones
.
milestones
.
each
do
|
milestone
|
Milestones
::
UpdateService
.
new
(
milestone
.
project
,
current_user
,
params
[
:milestone
]).
execute
(
milestone
)
end
respond_to
do
|
format
|
format
.
js
format
.
html
do
redirect_to
group_milestones_path
(
group
)
end
end
end
private
def
group
@group
||=
Group
.
find_by
(
path:
params
[
:group_id
])
end
def
title
params
[
:title
]
end
def
status
(
state
)
@group_milestones
.
map
{
|
milestone
|
next
if
milestone
.
state
!=
state
;
milestone
}.
compact
end
def
authorize_group_milestone!
return
render_404
unless
can?
(
current_user
,
:manage_group
,
group
)
end
end
app/helpers/groups_helper.rb
View file @
8fb76a83
...
...
@@ -31,6 +31,17 @@ module GroupsHelper
end
title
end
def
group_filter_path
(
entity
,
options
=
{})
exist_opts
=
{
status:
params
[
:status
]
}
options
=
exist_opts
.
merge
(
options
)
path
=
request
.
path
path
<<
"?
#{
options
.
to_param
}
"
path
end
end
app/models/group_milestone.rb
0 → 100644
View file @
8fb76a83
class
GroupMilestone
def
initialize
(
title
,
milestones
)
@title
=
title
@milestones
=
milestones
end
def
title
@title
end
def
safe_title
@title
.
parameterize
end
def
milestones
@milestones
end
def
projects
milestones
.
map
{
|
milestone
|
milestone
.
project
}
end
def
issue_count
milestones
.
map
{
|
milestone
|
milestone
.
issues
.
count
}.
sum
end
def
merge_requests_count
milestones
.
map
{
|
milestone
|
milestone
.
merge_requests
.
count
}.
sum
end
def
open_items_count
milestones
.
map
{
|
milestone
|
milestone
.
open_items_count
}.
sum
end
def
closed_items_count
milestones
.
map
{
|
milestone
|
milestone
.
closed_items_count
}.
sum
end
def
total_items_count
milestones
.
map
{
|
milestone
|
milestone
.
total_items_count
}.
sum
end
def
percent_complete
((
closed_items_count
*
100
)
/
total_items_count
).
abs
rescue
ZeroDivisionError
100
end
def
state
state
=
milestones
.
map
{
|
milestone
|
milestone
.
state
}
if
state
.
count
(
'active'
)
==
state
.
size
'active'
else
'closed'
end
end
def
active?
state
==
'active'
end
def
closed?
state
==
'closed'
end
def
issues
@group_issues
||=
milestones
.
map
{
|
milestone
|
milestone
.
issues
}.
flatten
.
group_by
(
&
:state
)
end
def
merge_requests
@group_merge_requests
||=
milestones
.
map
{
|
milestone
|
milestone
.
merge_requests
}.
flatten
.
group_by
(
&
:state
)
end
def
participants
milestones
.
map
{
|
milestone
|
milestone
.
participants
.
uniq
}.
reject
(
&
:empty?
).
flatten
end
def
opened_issues
issues
.
values_at
(
"opened"
,
"reopened"
).
compact
.
flatten
end
def
closed_issues
issues
[
'closed'
]
end
def
opened_merge_requests
merge_requests
.
values_at
(
"opened"
,
"reopened"
).
compact
.
flatten
end
def
closed_merge_requests
merge_requests
.
values_at
(
"closed"
,
"merged"
,
"locked"
).
compact
.
flatten
end
end
app/services/milestones/group_service.rb
0 → 100644
View file @
8fb76a83
module
Milestones
class
GroupService
<
Milestones
::
BaseService
def
initialize
(
project_milestones
)
@project_milestones
=
project_milestones
.
group_by
(
&
:title
)
end
def
execute
build
(
@project_milestones
)
end
def
milestone
(
title
)
if
title
group_milestone
=
@project_milestones
[
title
].
group_by
(
&
:title
)
build
(
group_milestone
).
first
else
nil
end
end
private
def
build
(
milestone
)
milestone
.
map
{
|
title
,
milestones
|
GroupMilestone
.
new
(
title
,
milestones
)
}
end
end
end
app/views/groups/_filter.html.haml
0 → 100644
View file @
8fb76a83
=
form_tag
group_filter_path
(
entity
),
method:
'get'
do
%fieldset
%ul
.nav.nav-pills.nav-stacked
%li
{
class:
(
"active"
if
(
params
[
:status
]
==
'active'
||
!
params
[
:status
]))}
=
link_to
group_filter_path
(
entity
,
status:
'active'
)
do
Active
%li
{
class:
(
"active"
if
params
[
:status
]
==
'closed'
)}
=
link_to
group_filter_path
(
entity
,
status:
'closed'
)
do
Closed
%li
{
class:
(
"active"
if
params
[
:status
]
==
'all'
)}
=
link_to
group_filter_path
(
entity
,
status:
'all'
)
do
All
app/views/groups/milestones/_issue.html.haml
0 → 100644
View file @
8fb76a83
%li
{
id:
dom_id
(
issue
,
'sortable'
),
class:
'issue-row'
,
'data-iid'
=>
issue
.
iid
}
%span
.milestone-row
-
project
=
issue
.
project
%strong
#{
project
.
name
}
·
=
link_to
[
project
,
issue
]
do
%span
.cgray
##{issue.iid}
=
link_to_gfm
issue
.
title
,
[
project
,
issue
]
.pull-right.assignee-icon
-
if
issue
.
assignee
=
image_tag
avatar_icon
(
issue
.
assignee
.
email
,
16
),
class:
"avatar s16"
app/views/groups/milestones/_issues.html.haml
0 → 100644
View file @
8fb76a83
.panel.panel-default
.panel-heading
=
title
%ul
{
class:
"well-list issues-sortable-list"
}
-
if
issues
-
issues
.
each
do
|
issue
|
=
render
'issue'
,
issue:
issue
app/views/groups/milestones/_merge_request.html.haml
0 → 100644
View file @
8fb76a83
%li
{
id:
dom_id
(
merge_request
,
'sortable'
),
class:
'mr-row'
,
'data-iid'
=>
merge_request
.
iid
}
%span
.milestone-row
-
project
=
merge_request
.
project
%strong
#{
project
.
name
}
·
=
link_to
[
project
,
merge_request
]
do
%span
.cgray
##{merge_request.iid}
=
link_to_gfm
merge_request
.
title
,
[
project
,
merge_request
]
.pull-right.assignee-icon
-
if
merge_request
.
assignee
=
image_tag
avatar_icon
(
merge_request
.
assignee
.
email
,
16
),
class:
"avatar s16"
app/views/groups/milestones/_merge_requests.html.haml
0 → 100644
View file @
8fb76a83
.panel.panel-default
.panel-heading
=
title
%ul
{
class:
"well-list merge_requests-sortable-list"
}
-
if
merge_requests
-
merge_requests
.
each
do
|
merge_request
|
=
render
'merge_request'
,
merge_request:
merge_request
app/views/groups/milestones/index.html.haml
0 → 100644
View file @
8fb76a83
%h3
.page-title
Milestones
%span
.pull-right
#{
@group_milestones
.
count
}
milestones
%p
.light
Only milestones from
%strong
#{
@group
.
name
}
group are listed here.
%hr
.row
.fixed.sidebar-expand-button.hidden-lg.hidden-md
%i
.icon-list.icon-2x
.col-md-3.responsive-side
=
render
'groups/filter'
,
entity:
'milestone'
.col-md-9
.panel.panel-default
%ul
.well-list
-
if
@group_milestones
.
blank?
%li
.nothing-here-block
No milestones to show
-
else
-
@group_milestones
.
each
do
|
milestone
|
%li
{
class:
"milestone milestone-#{milestone.closed? ? 'closed' : 'open'}"
,
id:
dom_id
(
milestone
.
milestones
.
first
)
}
.pull-right
-
if
can?
(
current_user
,
:manage_group
,
@group
)
-
if
milestone
.
closed?
=
link_to
'Reopen Milestone'
,
group_milestone_path
(
@group
,
milestone
.
safe_title
,
title:
milestone
.
title
,
milestone:
{
state_event: :activate
}),
method: :put
,
class:
"btn btn-small btn-grouped"
-
else
=
link_to
'Close Milestone'
,
group_milestone_path
(
@group
,
milestone
.
safe_title
,
title:
milestone
.
title
,
milestone:
{
state_event: :close
}),
method: :put
,
class:
"btn btn-small btn-remove"
%h4
=
link_to_gfm
truncate
(
milestone
.
title
,
length:
100
),
group_milestone_path
(
@group
,
milestone
.
safe_title
,
title:
milestone
.
title
)
%div
%div
=
link_to
group_milestone_path
(
@group
,
milestone
.
safe_title
,
title:
milestone
.
title
)
do
=
pluralize
milestone
.
issue_count
,
'Issue'
=
link_to
group_milestone_path
(
@group
,
milestone
.
safe_title
,
title:
milestone
.
title
)
do
=
pluralize
milestone
.
merge_requests_count
,
'Merge Request'
%span
.light
#{
milestone
.
percent_complete
}
% complete
.progress.progress-info
.progress-bar
{
style:
"width: #{milestone.percent_complete}%;"
}
%div
%br
-
milestone
.
projects
.
each
do
|
project
|
%span
.label.label-default
=
project
.
name
app/views/groups/milestones/show.html.haml
0 → 100644
View file @
8fb76a83
%h3
.page-title
Milestone
#{
@group_milestone
.
title
}
.pull-right
-
if
can?
(
current_user
,
:manage_group
,
@group
)
-
if
@group_milestone
.
active?
=
link_to
'Close Milestone'
,
group_milestone_path
(
@group
,
@group_milestone
.
safe_title
,
title:
@group_milestone
.
title
,
milestone:
{
state_event: :close
}),
method: :put
,
class:
"btn btn-small btn-remove"
-
else
=
link_to
'Reopen Milestone'
,
group_milestone_path
(
@group
,
@group_milestone
.
safe_title
,
title:
@group_milestone
.
title
,
milestone:
{
state_event: :activate
}),
method: :put
,
class:
"btn btn-small btn-grouped"
-
if
(
@group_milestone
.
total_items_count
==
@group_milestone
.
closed_items_count
)
&&
@group_milestone
.
active?
.alert.alert-success
%span
All issues for this milestone are closed. You may close the milestone now.
.back-link
=
link_to
group_milestones_path
(
@group
)
do
←
To milestones list
.issue-box
{
class:
"issue-box-#{@group_milestone.closed? ? 'closed' : 'open'}"
}
.state.clearfix
.state-label
-
if
@group_milestone
.
closed?
Closed
-
else
Open
%h4
.title
=
gfm
escape_once
(
@group_milestone
.
title
)
.context
%p
Progress:
#{
@group_milestone
.
closed_items_count
}
closed
–
#{
@group_milestone
.
open_items_count
}
open
.progress.progress-info
.progress-bar
{
style:
"width: #{@group_milestone.percent_complete}%;"
}
%ul
.nav.nav-tabs
%li
.active
=
link_to
'#tab-issues'
,
'data-toggle'
=>
'tab'
do
Issues
%span
.badge
=
@group_milestone
.
issue_count
%li
=
link_to
'#tab-merge-requests'
,
'data-toggle'
=>
'tab'
do
Merge Requests
%span
.badge
=
@group_milestone
.
merge_requests_count
%li
=
link_to
'#tab-participants'
,
'data-toggle'
=>
'tab'
do
Participants
%span
.badge
=
@group_milestone
.
participants
.
count
.tab-content
.tab-pane.active
#tab-issues
.row
.col-md-6
=
render
'issues'
,
title:
"Open"
,
issues:
@group_milestone
.
opened_issues
.col-md-6
=
render
'issues'
,
title:
"Closed"
,
issues:
@group_milestone
.
closed_issues
.tab-pane
#tab-merge-requests
.row
.col-md-6
=
render
'merge_requests'
,
title:
"Open"
,
merge_requests:
@group_milestone
.
opened_merge_requests
.col-md-6
=
render
'merge_requests'
,
title:
"Closed"
,
merge_requests:
@group_milestone
.
closed_merge_requests
.tab-pane
#tab-participants
%ul
.bordered-list
-
@group_milestone
.
participants
.
each
do
|
user
|
%li
=
link_to
user
,
title:
user
.
name
,
class:
"darken"
do
=
image_tag
avatar_icon
(
user
.
email
,
32
),
class:
"avatar s32"
%strong
=
truncate
(
user
.
name
,
lenght:
40
)
%br
%small
.cgray
=
user
.
username
app/views/layouts/nav/_group.html.haml
View file @
8fb76a83
...
...
@@ -2,6 +2,9 @@
=
nav_link
(
path:
'groups#show'
,
html_options:
{
class:
'home'
})
do
=
link_to
group_path
(
@group
),
title:
"Home"
do
Activity
=
nav_link
(
path:
'groups#milestones'
)
do
=
link_to
group_milestones_path
(
@group
)
do
Milestones
=
nav_link
(
path:
'groups#issues'
)
do
=
link_to
issues_group_path
(
@group
)
do
Issues
...
...
config/routes.rb
View file @
8fb76a83
...
...
@@ -151,8 +151,10 @@ Gitlab::Application.routes.draw do
end
resources
:users_groups
,
only:
[
:create
,
:update
,
:destroy
]
scope
module: :groups
do
resource
:avatar
,
only:
[
:destroy
]
resources
:milestones
end
end
...
...
features/group.feature
View file @
8fb76a83
...
...
@@ -120,3 +120,22 @@ Feature: Groups
When
I search for 'Mary' member
Then
I should see user
"Mary Jane"
in team list
Then
I should not see user
"John Doe"
in team list
Scenario
:
I
should see group
"Owned"
milestone index page with no milestones
When
I visit group
"Owned"
page
And
I click on group milestones
Then
I should see group milestones index page has no milestones
Scenario
:
I
should see group
"Owned"
milestone index page with milestones
Given
Group has projects with milestones
When
I visit group
"Owned"
page
And
I click on group milestones
Then
I should see group milestones index page with milestones
Scenario
:
I
should see group
"Owned"
milestone show page
Given
Group has projects with milestones
When
I visit group
"Owned"
page
And
I click on group milestones
And
I click on one group milestone
Then
I should see group milestone with all issues and MRs assigned to that milestone
features/steps/group/group.rb
View file @
8fb76a83
...
...
@@ -164,6 +164,36 @@ class Groups < Spinach::FeatureSteps
end
end
step
'I click on group milestones'
do
click_link
'Milestones'
end
step
'I should see group milestones index page has no milestones'
do
page
.
should
have_content
(
'No milestones to show'
)
end
step
'Group has projects with milestones'
do
group_milestone
end
step
'I should see group milestones index page with milestones'
do
page
.
should
have_content
(
'Version 7.2'
)
page
.
should
have_content
(
'GL-113'
)
page
.
should
have_link
(
'2 Issues'
,
href:
group_milestone_path
(
"owned"
,
"version-7-2"
,
title:
"Version 7.2"
))
page
.
should
have_link
(
'3 Merge Requests'
,
href:
group_milestone_path
(
"owned"
,
"gl-113"
,
title:
"GL-113"
))
end
step
'I click on one group milestone'
do
click_link
'GL-113'
end
step
'I should see group milestone with all issues and MRs assigned to that milestone'
do
page
.
should
have_content
(
'Milestone GL-113'
)
page
.
should
have_content
(
'Progress: 0 closed – 4 open'
)
page
.
should
have_link
(
@issue1
.
title
,
href:
project_issue_path
(
@project1
,
@issue1
))
page
.
should
have_link
(
@mr3
.
title
,
href:
project_merge_request_path
(
@project3
,
@mr3
))
end
protected
def
assigned_to_me
key
...
...
@@ -173,4 +203,68 @@ class Groups < Spinach::FeatureSteps
def
project
Group
.
find_by
(
name:
"Owned"
).
projects
.
first
end
def
group_milestone
group
=
Group
.
find_by
(
name:
"Owned"
)
@project1
=
create
:project
,
group:
group
project2
=
create
:project
,
path:
'gitlab-ci'
,
group:
group
@project3
=
create
:project
,
path:
'cookbook-gitlab'
,
group:
group
milestone1_project1
=
create
:milestone
,
title:
"Version 7.2"
,
project:
@project1
milestone1_project2
=
create
:milestone
,
title:
"Version 7.2"
,
project:
project2
milestone1_project3
=
create
:milestone
,
title:
"Version 7.2"
,
project:
@project3
milestone2_project1
=
create
:milestone
,
title:
"GL-113"
,
project:
@project1
milestone2_project2
=
create
:milestone
,
title:
"GL-113"
,
project:
project2
milestone2_project3
=
create
:milestone
,
title:
"GL-113"
,
project:
@project3
@issue1
=
create
:issue
,
project:
@project1
,
assignee:
current_user
,
author:
current_user
,
milestone:
milestone2_project1
issue2
=
create
:issue
,
project:
project2
,
assignee:
current_user
,
author:
current_user
,
milestone:
milestone1_project2
issue3
=
create
:issue
,
project:
@project3
,
assignee:
current_user
,
author:
current_user
,
milestone:
milestone1_project1
mr1
=
create
:merge_request
,
source_project:
@project1
,
target_project:
@project1
,
assignee:
current_user
,
author:
current_user
,
milestone:
milestone2_project1
mr2
=
create
:merge_request
,
source_project:
project2
,
target_project:
project2
,
assignee:
current_user
,
author:
current_user
,
milestone:
milestone2_project2
@mr3
=
create
:merge_request
,
source_project:
@project3
,
target_project:
@project3
,
assignee:
current_user
,
author:
current_user
,
milestone:
milestone2_project3
end
end
spec/services/milestones/group_service_spec.rb
0 → 100644
View file @
8fb76a83
require
'spec_helper'
describe
Milestones
::
GroupService
do
let
(
:user
)
{
create
(
:user
)
}
let
(
:user2
)
{
create
(
:user
)
}
let
(
:group
)
{
create
(
:group
)
}
let
(
:project1
)
{
create
(
:project
,
group:
group
)
}
let
(
:project2
)
{
create
(
:project
,
path:
'gitlab-ci'
,
group:
group
)
}
let
(
:project3
)
{
create
(
:project
,
path:
'cookbook-gitlab'
,
group:
group
)
}
let
(
:milestone1_project1
)
{
create
(
:milestone
,
title:
"Milestone v1.2"
,
project:
project1
)
}
let
(
:milestone1_project2
)
{
create
(
:milestone
,
title:
"Milestone v1.2"
,
project:
project2
)
}
let
(
:milestone1_project3
)
{
create
(
:milestone
,
title:
"Milestone v1.2"
,
project:
project3
)
}
let
(
:milestone2_project1
)
{
create
(
:milestone
,
title:
"VD-123"
,
project:
project1
)
}
let
(
:milestone2_project2
)
{
create
(
:milestone
,
title:
"VD-123"
,
project:
project2
)
}
let
(
:milestone2_project3
)
{
create
(
:milestone
,
title:
"VD-123"
,
project:
project3
)
}
describe
'execute'
do
context
'with valid projects'
do
before
do
milestones
=
[
milestone1_project1
,
milestone1_project2
,
milestone1_project3
,
milestone2_project1
,
milestone2_project2
,
milestone2_project3
]
@group_milestones
=
Milestones
::
GroupService
.
new
(
milestones
).
execute
end
it
'should have all project milestones'
do
expect
(
@group_milestones
.
count
).
to
eq
(
2
)
end
it
'should have all project milestones titles'
do
expect
(
@group_milestones
.
map
{
|
group_milestone
|
group_milestone
.
title
}).
to
match_array
([
'Milestone v1.2'
,
'VD-123'
])
end
it
'should have all project milestones'
do
expect
(
@group_milestones
.
map
{
|
group_milestone
|
group_milestone
.
milestones
.
count
}.
sum
).
to
eq
(
6
)
end
end
end
describe
'milestone'
do
context
'with valid title'
do
before
do
milestones
=
[
milestone1_project1
,
milestone1_project2
,
milestone1_project3
,
milestone2_project1
,
milestone2_project2
,
milestone2_project3
]
@group_milestones
=
Milestones
::
GroupService
.
new
(
milestones
).
milestone
(
'Milestone v1.2'
)
end
it
'should have exactly one group milestone'
do
expect
(
@group_milestones
.
title
).
to
eq
(
'Milestone v1.2'
)
end
it
'should have all project milestones with the same title'
do
expect
(
@group_milestones
.
milestones
.
count
).
to
eq
(
3
)
end
end
end
end
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment