diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb index a86cc58c02109cfa23a41ffd232284eedacf74f7..235b27f53a91f946ec0a118a7de3ba6c618e74af 100644 --- a/app/controllers/groups/milestones_controller.rb +++ b/app/controllers/groups/milestones_controller.rb @@ -13,14 +13,16 @@ class Groups::MilestonesController < ApplicationController def show project_milestones = Milestone.where(project_id: group.projects) - @group_milestones = Milestones::GroupService.new(project_milestones).milestone(title) + @group_milestone = Milestones::GroupService.new(project_milestones).milestone(title) + @project_issues = @group_milestone.filter_by(params[:status], "issues") + @project_merge_requests = @group_milestone.filter_by(params[:status], "merge_requests") end def update project_milestones = Milestone.where(project_id: group.projects) @group_milestones = Milestones::GroupService.new(project_milestones).milestone(title) - @group_milestones.each do |milestone| + @group_milestones.milestones.each do |milestone| Milestones::UpdateService.new(milestone.project, current_user, params[:milestone]).execute(milestone) end diff --git a/app/models/group_milestone.rb b/app/models/group_milestone.rb index 567e0c2b9fb6ddd95585f14f6497f284352b1887..0faff39ae7aa24c1aea5d03ed258681897023490 100644 --- a/app/models/group_milestone.rb +++ b/app/models/group_milestone.rb @@ -9,6 +9,10 @@ class GroupMilestone @title end + def safe_title + @title.gsub(".", "-") + end + def milestones @milestones end @@ -25,6 +29,10 @@ class GroupMilestone 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 @@ -42,10 +50,69 @@ class GroupMilestone def state state = milestones.map{ |milestone| milestone.state } - if state.count("active") == state.size + if state.count('active') == state.size 'active' else 'closed' end end + + def active? + state == 'active' + end + + def closed? + state == 'closed' + end + + def opened_unassigned_issues + milestones.map{ |milestone| milestone.issues.opened.unassigned } + end + + def opened_assigned_issues + milestones.map{ |milestone| milestone.issues.opened.assigned } + end + + def closed_issues + milestones.map{ |milestone| milestone.issues.closed } + end + + def participants + milestones.map{ |milestone| milestone.participants.uniq }.reject(&:empty?).flatten + end + + def filter_by(filter, entity) + if entity + milestones = self.milestones.sort_by(&:project_id) + entities = {} + milestones.each do |project_milestone| + next unless project_milestone.send(entity).any? + project_name = project_milestone.project.name + entities_by_state = state_filter(filter, project_milestone.send(entity)) + entities.store(project_name, entities_by_state) + end + entities + else + {} + end + end + + def state_filter(filter, entities) + if entities.present? + sorted_entities = entities.sort_by(&:position) + entities_by_state = case filter + when 'active'; sorted_entities.group_by(&:state)['opened'] + when 'closed'; sorted_entities.group_by(&:state)['closed'] + else sorted_entities + end + if entities_by_state.blank? + [] + else + entities_by_state + end + else + [] + end + end + end diff --git a/app/services/milestones/group_service.rb b/app/services/milestones/group_service.rb index 39ae913a72a8ce331a6dfac80eebc023cd5f1928..2d1aa878c24324be4bb1bcdfde9e52b8771506bf 100644 --- a/app/services/milestones/group_service.rb +++ b/app/services/milestones/group_service.rb @@ -5,16 +5,22 @@ module Milestones end def execute - @project_milestones.map{ |title, milestone| GroupMilestone.new(title, milestone) } + build(@project_milestones) end def milestone(title) if title - @project_milestones[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 diff --git a/app/views/groups/milestones/_issue.html.haml b/app/views/groups/milestones/_issue.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..7009400a46ccdea2683cf28a9eacb6d44911fba3 --- /dev/null +++ b/app/views/groups/milestones/_issue.html.haml @@ -0,0 +1,9 @@ +%li{ id: dom_id(issue, 'sortable'), class: 'issue-row', 'data-iid' => issue.iid } + %span.str-truncated + - project = issue.project + = 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" diff --git a/app/views/groups/milestones/_issues.html.haml b/app/views/groups/milestones/_issues.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..3fefb2f01079b824844fae903be83c2d8afe99fe --- /dev/null +++ b/app/views/groups/milestones/_issues.html.haml @@ -0,0 +1,6 @@ +.panel.panel-default + .panel-heading= name + %ul{ class: "well-list issues-sortable-list" } + - issues.each do |issue| + = render 'issue', issue: issue + diff --git a/app/views/groups/milestones/_merge_request.html.haml b/app/views/groups/milestones/_merge_request.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..4f11aaa74b7b7d167a0ea09f81df70d8c37c12bf --- /dev/null +++ b/app/views/groups/milestones/_merge_request.html.haml @@ -0,0 +1,6 @@ +%li{ id: dom_id(merge_request, 'sortable'), class: 'mr-row', 'data-iid' => merge_request.iid } + %span.str-truncated + - project = merge_request.project + = link_to [project, merge_request] do + %span.cgray ##{merge_request.iid} + = link_to_gfm truncate(merge_request.title, length: 60), [project, merge_request] diff --git a/app/views/groups/milestones/_merge_requests.html.haml b/app/views/groups/milestones/_merge_requests.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..9bb213ffe328e7e0427389bdbdfa8476d4962ef9 --- /dev/null +++ b/app/views/groups/milestones/_merge_requests.html.haml @@ -0,0 +1,5 @@ +.panel.panel-default + .panel-heading= name + %ul{ class: "well-list merge_requests-sortable-list" } + - merge_requests.sort_by(&:position).each do |merge_request| + = render 'merge_request', merge_request: merge_request diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml index b93ff09d25c12bc7c486bc4b74ed0b6034305505..e3dd1ae5ae07586ceeb4eafb3241b973dad877c2 100644 --- a/app/views/groups/milestones/index.html.haml +++ b/app/views/groups/milestones/index.html.haml @@ -22,21 +22,20 @@ .nothing-here-block No milestones to show - else - @group_milestones.each do |milestone| - %li{class: "milestone milestone-#{milestone.state == 'closed' ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) } + %li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) } .pull-right - - safe_title = milestone.title.gsub(".", "-") - - if milestone.state == 'closed' - = link_to 'Reopen Milestone', group_milestone_path(@group, safe_title, milestone: {state_event: :activate }), method: :put, class: "btn btn-small btn-grouped" + - if milestone.closed? + = link_to 'Reopen Milestone', group_milestone_path(@group, milestone.safe_title, milestone: {state_event: :activate }), method: :put, class: "btn btn-small btn-grouped" - else - = link_to 'Close Milestone', group_milestone_path(@group, safe_title, milestone: {state_event: :close }), method: :put, class: "btn btn-small btn-remove" + = link_to 'Close Milestone', group_milestone_path(@group, milestone.safe_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, safe_title) + = link_to_gfm truncate(milestone.title, length: 100), group_milestone_path(@group, milestone.safe_title) %div %div - = link_to root_path do + = link_to group_milestone_path(@group, milestone.safe_title) do = pluralize milestone.issue_count, 'Issue' - = link_to root_path do + = link_to group_milestone_path(@group, milestone.safe_title) do = pluralize milestone.merge_requests_count, 'Merge Request' %span.light #{milestone.percent_complete}% complete diff --git a/app/views/groups/milestones/show.html.haml b/app/views/groups/milestones/show.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..a450e7d09a51617c71b622a9061a6b2520412eb0 --- /dev/null +++ b/app/views/groups/milestones/show.html.haml @@ -0,0 +1,77 @@ +%h3.page-title + Milestone #{@group_milestone.title} + .pull-right + - if @group_milestone.active? + = link_to 'Close Milestone', group_milestone_path(@group, @group_milestone.safe_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, 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-4.responsive-side + = render 'groups/filter', entity: 'milestone' + .col-md-8 + - @project_issues.each do |name, issues| + = render 'issues', name: name, issues: issues + + .tab-pane#tab-merge-requests + .row + .col-md-4.responsive-side + = render 'groups/filter', entity: 'milestone' + .col-md-8 + - @project_merge_requests.each do |name, merge_requests| + = render 'merge_requests', name: name, merge_requests: 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