Commit 95b06a62 authored by Rubén Dávila's avatar Rubén Dávila

Updates from last code review.

parent c91554de
...@@ -12,6 +12,7 @@ v 8.6.0 (unreleased) ...@@ -12,6 +12,7 @@ v 8.6.0 (unreleased)
- Allow search for logged out users - Allow search for logged out users
- Don't show Issues/MRs from archived projects in Groups view - Don't show Issues/MRs from archived projects in Groups view
- Increase the notes polling timeout over time (Roberto Dip) - Increase the notes polling timeout over time (Roberto Dip)
- Show labels in dashboard and group milestone views
v 8.5.4 v 8.5.4
- Do not cache requests for badges (including builds badge) - Do not cache requests for badges (including builds badge)
......
...@@ -23,7 +23,7 @@ class Dispatcher ...@@ -23,7 +23,7 @@ class Dispatcher
new Issue() new Issue()
shortcut_handler = new ShortcutsIssuable() shortcut_handler = new ShortcutsIssuable()
new ZenMode() new ZenMode()
when 'projects:milestones:show' when 'projects:milestones:show', 'groups:milestones:show', 'dashboard:milestones:show'
new Milestone() new Milestone()
when 'projects:milestones:new', 'projects:milestones:edit' when 'projects:milestones:new', 'projects:milestones:edit'
new ZenMode() new ZenMode()
......
...@@ -69,7 +69,7 @@ class @Milestone ...@@ -69,7 +69,7 @@ class @Milestone
@bindIssuesSorting() @bindIssuesSorting()
@bindMergeRequestSorting() @bindMergeRequestSorting()
@bindTabsSwitching @bindTabsSwitching()
bindIssuesSorting: -> bindIssuesSorting: ->
$("#issues-list-unassigned, #issues-list-ongoing, #issues-list-closed").sortable( $("#issues-list-unassigned, #issues-list-ongoing, #issues-list-closed").sortable(
...@@ -104,7 +104,7 @@ class @Milestone ...@@ -104,7 +104,7 @@ class @Milestone
).disableSelection() ).disableSelection()
bindMergeRequestSorting: -> bindTabsSwitching: ->
$('a[data-toggle="tab"]').on 'show.bs.tab', (e) -> $('a[data-toggle="tab"]').on 'show.bs.tab', (e) ->
currentTabClass = $(e.target).data('show') currentTabClass = $(e.target).data('show')
previousTabClass = $(e.relatedTarget).data('show') previousTabClass = $(e.relatedTarget).data('show')
...@@ -113,6 +113,7 @@ class @Milestone ...@@ -113,6 +113,7 @@ class @Milestone
$(currentTabClass).removeClass('hidden') $(currentTabClass).removeClass('hidden')
$(currentTabClass).show() $(currentTabClass).show()
bindMergeRequestSorting: ->
$("#merge_requests-list-unassigned, #merge_requests-list-ongoing, #merge_requests-list-closed").sortable( $("#merge_requests-list-unassigned, #merge_requests-list-ongoing, #merge_requests-list-closed").sortable(
connectWith: ".merge_requests-sortable-list", connectWith: ".merge_requests-sortable-list",
dropOnEmpty: true, dropOnEmpty: true,
......
...@@ -19,10 +19,11 @@ li.milestone { ...@@ -19,10 +19,11 @@ li.milestone {
width: 105px; width: 105px;
} }
.issue-row, .merge_request-row { .issuable-row {
.color-label { .color-label {
border-radius: 2px; border-radius: 2px;
padding: 3px !important; padding: 3px !important;
margin-right: 7px;
} }
// Issue title // Issue title
...@@ -45,19 +46,14 @@ li.milestone { ...@@ -45,19 +46,14 @@ li.milestone {
} }
.issues-sortable-list, .merge_requests-sortable-list { .issues-sortable-list, .merge_requests-sortable-list {
.issue-detail, .merge_request-detail { .issuable-detail {
display: block; display: block;
margin-top: 7px;
.issue-number, .merge_request-number { .issuable-number {
color: rgba(0,0,0,0.44); color: rgba(0,0,0,0.44);
margin-right: 5px; margin-right: 5px;
} }
.color-label {
padding: 6px 10px;
margin-right: 7px;
margin-top: 10px;
}
.avatar { .avatar {
float: none; float: none;
} }
......
...@@ -32,10 +32,6 @@ class Projects::MilestonesController < Projects::ApplicationController ...@@ -32,10 +32,6 @@ class Projects::MilestonesController < Projects::ApplicationController
end end
def show def show
@issues = @milestone.issues
@users = @milestone.participants.uniq
@merge_requests = @milestone.merge_requests
@labels = @milestone.labels
end end
def create def create
......
class LabelWithMilestone
attr_reader :milestone
def initialize(label, milestone)
@label, @milestone = label, milestone
end
def method_missing(meth, *args)
if @label.respond_to?(meth)
@label.send(meth, *args)
else
super
end
end
def respond_to?(meth)
@label.respond_to?(meth)
end
end
...@@ -263,11 +263,9 @@ class IssuableFinder ...@@ -263,11 +263,9 @@ class IssuableFinder
def by_label(items) def by_label(items)
if labels? if labels?
if filter_by_no_label? if filter_by_no_label?
items = items. items = items.without_label
joins("LEFT OUTER JOIN label_links ON label_links.target_type = '#{klass.name}' AND label_links.target_id = #{klass.table_name}.id").
where(label_links: { id: nil })
else else
items = items.joins(:labels).where(labels: { title: label_names }) items = items.with_label(label_names)
if projects if projects
items = items.where(labels: { project_id: projects }) items = items.where(labels: { project_id: projects })
......
...@@ -9,6 +9,32 @@ module MilestonesHelper ...@@ -9,6 +9,32 @@ module MilestonesHelper
end end
end end
def milestones_label_path(opts = {})
if @project
namespace_project_issues_path(@project.namespace, @project, opts)
elsif @group
issues_group_path(@group, opts)
else
issues_dashboard_path(opts)
end
end
def milestones_browse_issuables_path(milestone, type:)
opts = { milestone_title: milestone.title }
if @project
polymorphic_path([@project.namespace.becomes(Namespace), @project, type], opts)
elsif @group
polymorphic_url([type, @group], opts)
else
polymorphic_url([type, :dashboard], opts)
end
end
def milestone_issues_by_label_count(milestone, label, state:)
milestone.issues.with_label(label.title).send(state).size
end
def milestone_progress_bar(milestone) def milestone_progress_bar(milestone)
options = { options = {
class: 'progress-bar progress-bar-success', class: 'progress-bar progress-bar-success',
......
...@@ -36,6 +36,8 @@ module Issuable ...@@ -36,6 +36,8 @@ module Issuable
scope :closed, -> { with_state(:closed) } scope :closed, -> { with_state(:closed) }
scope :order_milestone_due_desc, -> { joins(:milestone).reorder('milestones.due_date DESC, milestones.id DESC') } scope :order_milestone_due_desc, -> { joins(:milestone).reorder('milestones.due_date DESC, milestones.id DESC') }
scope :order_milestone_due_asc, -> { joins(:milestone).reorder('milestones.due_date ASC, milestones.id ASC') } scope :order_milestone_due_asc, -> { joins(:milestone).reorder('milestones.due_date ASC, milestones.id ASC') }
scope :with_label, ->(title) { joins(:labels).where(labels: { title: title }) }
scope :without_label, -> { joins("LEFT OUTER JOIN label_links ON label_links.target_type = '#{name}' AND label_links.target_id = #{table_name}.id").where(label_links: { id: nil }) }
scope :join_project, -> { joins(:project) } scope :join_project, -> { joins(:project) }
scope :references_project, -> { references(:project) } scope :references_project, -> { references(:project) }
......
module Milestoneish
def closed_items_count
issues.closed.size + merge_requests.closed_and_merged.size
end
def total_items_count
issues.size + merge_requests.size
end
def complete?
total_items_count == closed_items_count
end
def percent_complete
((closed_items_count * 100) / total_items_count).abs
rescue ZeroDivisionError
0
end
def remaining_days
return 0 if !due_date || expired?
(due_date - Date.today).to_i
end
end
...@@ -2,16 +2,19 @@ class GlobalLabel ...@@ -2,16 +2,19 @@ class GlobalLabel
attr_accessor :title, :labels attr_accessor :title, :labels
alias_attribute :name, :title alias_attribute :name, :title
delegate :color, :description, to: :@first_label
def self.build_collection(labels) def self.build_collection(labels)
labels = labels.group_by(&:title) labels = labels.group_by(&:title)
labels.map do |title, label| labels.map do |title, labels|
new(title, label) new(title, labels)
end end
end end
def initialize(title, labels) def initialize(title, labels)
@title = title @title = title
@labels = labels @labels = labels
@first_label = labels.find { |lbl| lbl.description.present? } || labels.first
end end
end end
class GlobalMilestone class GlobalMilestone
include Milestoneish
attr_accessor :title, :milestones attr_accessor :title, :milestones
alias_attribute :name, :title alias_attribute :name, :title
...@@ -31,32 +33,6 @@ class GlobalMilestone ...@@ -31,32 +33,6 @@ class GlobalMilestone
@projects ||= Project.for_milestones(milestones.map(&:id)) @projects ||= Project.for_milestones(milestones.map(&:id))
end end
def issues_count
issues.count
end
def merge_requests_count
merge_requests.count
end
def open_items_count
opened_issues.count + opened_merge_requests.count
end
def closed_items_count
closed_issues.count + closed_merge_requests.count
end
def total_items_count
issues_count + merge_requests_count
end
def percent_complete
((closed_items_count * 100) / total_items_count).abs
rescue ZeroDivisionError
0
end
def state def state
state = milestones.map { |milestone| milestone.state } state = milestones.map { |milestone| milestone.state }
...@@ -88,29 +64,8 @@ class GlobalMilestone ...@@ -88,29 +64,8 @@ class GlobalMilestone
end end
def labels def labels
@labels ||= milestones.map do |ms| @labels ||= GlobalLabel.build_collection(milestones.map(&:labels).flatten)
ms.labels.map { |label| LabelWithMilestone.new(label, ms) } .sort_by!(&:title)
end.flatten.sort_by!(&:title)
end
def opened_issues
issues.opened
end
def closed_issues
issues.closed
end
def opened_merge_requests
merge_requests.opened
end
def closed_merge_requests
merge_requests.with_states(:closed, :merged, :locked)
end
def complete?
total_items_count == closed_items_count
end end
def due_date def due_date
......
...@@ -137,7 +137,6 @@ class MergeRequest < ActiveRecord::Base ...@@ -137,7 +137,6 @@ class MergeRequest < ActiveRecord::Base
scope :by_milestone, ->(milestone) { where(milestone_id: milestone) } scope :by_milestone, ->(milestone) { where(milestone_id: milestone) }
scope :in_projects, ->(project_ids) { where("source_project_id in (:project_ids) OR target_project_id in (:project_ids)", project_ids: project_ids) } scope :in_projects, ->(project_ids) { where("source_project_id in (:project_ids) OR target_project_id in (:project_ids)", project_ids: project_ids) }
scope :of_projects, ->(ids) { where(target_project_id: ids) } scope :of_projects, ->(ids) { where(target_project_id: ids) }
scope :opened, -> { with_states(:opened, :reopened) }
scope :merged, -> { with_state(:merged) } scope :merged, -> { with_state(:merged) }
scope :closed_and_merged, -> { with_states(:closed, :merged) } scope :closed_and_merged, -> { with_states(:closed, :merged) }
......
...@@ -24,12 +24,13 @@ class Milestone < ActiveRecord::Base ...@@ -24,12 +24,13 @@ class Milestone < ActiveRecord::Base
include Sortable include Sortable
include Referable include Referable
include StripAttribute include StripAttribute
include Milestoneish
belongs_to :project belongs_to :project
has_many :issues has_many :issues
has_many :labels, -> { distinct.reorder('labels.title') }, through: :issues has_many :labels, -> { distinct.reorder('labels.title') }, through: :issues
has_many :merge_requests has_many :merge_requests
has_many :participants, through: :issues, source: :assignee has_many :participants, -> { distinct.reorder('users.name') }, through: :issues, source: :assignee
scope :active, -> { with_state(:active) } scope :active, -> { with_state(:active) }
scope :closed, -> { with_state(:closed) } scope :closed, -> { with_state(:closed) }
...@@ -92,30 +93,6 @@ class Milestone < ActiveRecord::Base ...@@ -92,30 +93,6 @@ class Milestone < ActiveRecord::Base
end end
end end
def open_items_count
self.issues.opened.count + self.merge_requests.opened.count
end
def closed_items_count
self.issues.closed.count + self.merge_requests.closed_and_merged.count
end
def total_items_count
self.issues.count + self.merge_requests.count
end
def percent_complete
((closed_items_count * 100) / total_items_count).abs
rescue ZeroDivisionError
0
end
def remaining_days
return 0 if !due_date || expired?
(due_date - Date.today).to_i
end
def expires_at def expires_at
if due_date if due_date
if due_date.past? if due_date.past?
......
...@@ -215,7 +215,7 @@ class Project < ActiveRecord::Base ...@@ -215,7 +215,7 @@ class Project < ActiveRecord::Base
scope :public_only, -> { where(visibility_level: Project::PUBLIC) } scope :public_only, -> { where(visibility_level: Project::PUBLIC) }
scope :public_and_internal_only, -> { where(visibility_level: Project.public_and_internal_levels) } scope :public_and_internal_only, -> { where(visibility_level: Project.public_and_internal_levels) }
scope :non_archived, -> { where(archived: false) } scope :non_archived, -> { where(archived: false) }
scope :for_milestones, ->(ids) { joins(:milestones).where('milestones.id' => ids) } scope :for_milestones, ->(ids) { joins(:milestones).where('milestones.id' => ids).distinct }
state_machine :import_status, initial: :none do state_machine :import_status, initial: :none do
event :import_start do event :import_start do
......
%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) } = render 'shared/milestones/milestone',
.row milestone_path: dashboard_milestone_path(milestone.safe_title, title: milestone.title),
.col-sm-6 issues_path: issues_dashboard_path(milestone_title: milestone.title),
%strong merge_requests_path: merge_requests_dashboard_path(milestone_title: milestone.title),
= link_to_gfm truncate(milestone.title, length: 100), dashboard_milestone_path(milestone.safe_title, title: milestone.title) milestone: milestone,
.col-sm-6 dashboard: true
.pull-right.light #{milestone.percent_complete}% complete
.row
.col-sm-6
= link_to issues_dashboard_path(milestone_title: milestone.title) do
= pluralize milestone.issues_count, 'Issue'
&middot;
= link_to merge_requests_dashboard_path(milestone_title: milestone.title) do
= pluralize milestone.merge_requests_count, 'Merge Request'
.col-sm-6
= milestone_progress_bar(milestone)
.row
.col-sm-6
.expiration
= render 'shared/milestone_expired', milestone: milestone
.projects
- milestone.milestones.each do |milestone|
= link_to milestone_path(milestone) do
%span.label.label-gray
= milestone.project.name_with_namespace
- page_title @milestone.title, "Milestones"
- header_title "Milestones", dashboard_milestones_path - header_title "Milestones", dashboard_milestones_path
.detail-page-header = render 'shared/milestones/top', milestone: @milestone
.status-box{ class: "status-box-#{@milestone.closed? ? 'closed' : 'open'}" } = render 'shared/milestones/summary', milestone: @milestone
- if @milestone.closed? = render 'shared/milestones/tabs', milestone: @milestone, show_full_project_name: true
Closed
- else
Open
%span.identifier
Milestone #{@milestone.title}
.detail-page-description.gray-content-block.second-block
%h2.title
= markdown escape_once(@milestone.title), pipeline: :single_line
- if @milestone.complete? && @milestone.active?
.alert.alert-success.prepend-top-default
%span All issues for this milestone are closed. Navigate to the project to close the milestone.
.table-holder
%table.table
%thead
%tr
%th Project
%th Open issues
%th State
%th Due date
- @milestone.milestones.each do |milestone|
%tr
%td
= link_to "#{milestone.project.name_with_namespace}", namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone)
%td
= milestone.issues.opened.count
%td
- if milestone.closed?
Closed
- else
Open
%td
= milestone.expires_at
.context
.milestone-summary
%h4 Progress
%strong= @milestone.issues.size
issues:
%span.milestone-stat
%strong= @milestone.opened_issues.size
open and
%strong= @milestone.closed_issues.size
closed
%span.milestone-stat
%strong== #{@milestone.percent_complete}%
complete
= milestone_progress_bar(@milestone)
%ul.nav-links.no-top.no-bottom
%li.active
= link_to '#tab-issues', 'data-toggle' => 'tab' do
Issues
%span.badge= @milestone.issues_count
%li
= link_to '#tab-merge-requests', 'data-toggle' => 'tab' do
Merge Requests
%span.badge= @milestone.merge_requests_count
%li
= link_to '#tab-participants', 'data-toggle' => 'tab' do
Participants
%span.badge= @milestone.participants.count
%li
= link_to '#tab-labels', 'data-toggle' => 'tab', 'data-show' => '.tab-issues-buttons' do
Labels
%span.badge= @milestone.labels.count
.tab-content.milestone-content
.tab-pane.active#tab-issues
= render 'shared/milestones/issues_tab', unassigned: @milestone.opened_issues.unassigned, assigned: @milestone.opened_issues.assigned, closed: @milestone.closed_issues, show_full_project_name: true
.tab-pane#tab-merge-requests
= render 'shared/milestones/merge_requests_tab', unassigned: @milestone.opened_merge_requests.unassigned, assigned: @milestone.opened_merge_requests.assigned, closed: @milestone.merge_requests.closed, merged: @milestone.merge_requests.merged, show_full_project_name: true
.tab-pane#tab-participants
= render 'shared/milestones/participants_tab', users: @milestone.participants
.tab-pane#tab-labels
= render 'shared/milestones/labels_tab', labels: @milestone.labels, show_full_project_name: true
%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) } = render 'shared/milestones/milestone',
.row milestone_path: group_milestone_path(@group, milestone.safe_title, title: milestone.title),
.col-sm-6 issues_path: issues_group_path(@group, milestone_title: milestone.title),
%strong merge_requests_path: merge_requests_group_path(@group, milestone_title: milestone.title),
= link_to_gfm truncate(milestone.title, length: 100), group_milestone_path(@group, milestone.safe_title, title: milestone.title) milestone: milestone
.col-sm-6
.pull-right.light #{milestone.percent_complete}% complete
.row
.col-sm-6
= link_to issues_group_path(@group, milestone_title: milestone.title) do
= pluralize milestone.issues_count, 'Issue'
&middot;
= link_to merge_requests_group_path(@group, milestone_title: milestone.title) do
= pluralize milestone.merge_requests_count, 'Merge Request'
.col-sm-6
= milestone_progress_bar(milestone)
.row
.col-sm-6
%div
- milestone.milestones.each do |milestone|
= link_to milestone_path(milestone) do
%span.label.label-gray
= milestone.project.name
.col-sm-6
- if can?(current_user, :admin_milestones, @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-xs btn-grouped btn-reopen"
- else
= link_to 'Close Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-xs btn-close"
- page_title @milestone.title, "Milestones"
= render "header_title" = render "header_title"
= render 'shared/milestones/top', milestone: @milestone, group: @group
.detail-page-header = render 'shared/milestones/summary', milestone: @milestone
.status-box{ class: "status-box-#{@milestone.closed? ? 'closed' : 'open'}" } = render 'shared/milestones/tabs', milestone: @milestone, show_project_name: true
- if @milestone.closed?
Closed
- else
Open
%span.identifier
Milestone #{@milestone.title}
.pull-right
- if can?(current_user, :admin_milestones, @group)
- if @milestone.active?
= link_to 'Close Milestone', group_milestone_path(@group, @milestone.safe_title, title: @milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-grouped btn-close"
- else
= link_to 'Reopen Milestone', group_milestone_path(@group, @milestone.safe_title, title: @milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-grouped btn-reopen"
.detail-page-description.gray-content-block.second-block
%h2.title
= markdown escape_once(@milestone.title), pipeline: :single_line
- if @milestone.complete? && @milestone.active?
.alert.alert-success.prepend-top-default
%span All issues for this milestone are closed. You may close the milestone now.
.table-holder
%table.table
%thead
%tr
%th Project
%th Open issues
%th State
%th Due date
- @milestone.milestones.each do |milestone|
%tr
%td
= link_to "#{milestone.project.name}", namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone)
%td
= milestone.issues.opened.count
%td
- if milestone.closed?
Closed
- else
Open
%td
= milestone.expires_at
.context
.milestone-summary
%h4 Progress
%strong= @milestone.issues.size
issues:
%span.milestone-stat
%strong= @milestone.opened_issues.size
open and
%strong= @milestone.closed_issues.size
closed
%span.milestone-stat
%strong== #{@milestone.percent_complete}%
complete
= milestone_progress_bar(@milestone)
%ul.nav-links.no-top.no-bottom
%li.active
= link_to '#tab-issues', 'data-toggle' => 'tab' do
Issues
%span.badge= @milestone.issues_count
%li
= link_to '#tab-merge-requests', 'data-toggle' => 'tab' do
Merge Requests
%span.badge= @milestone.merge_requests_count
%li
= link_to '#tab-participants', 'data-toggle' => 'tab' do
Participants
%span.badge= @milestone.participants.count
%li
= link_to '#tab-labels', 'data-toggle' => 'tab', 'data-show' => '.tab-issues-buttons' do
Labels
%span.badge= @milestone.labels.count
.tab-content.milestone-content
.tab-pane.active#tab-issues
= render 'shared/milestones/issues_tab', unassigned: @milestone.opened_issues.unassigned, assigned: @milestone.opened_issues.assigned, closed: @milestone.closed_issues, show_project_name: true
.tab-pane#tab-merge-requests
= render 'shared/milestones/merge_requests_tab', unassigned: @milestone.opened_merge_requests.unassigned, assigned: @milestone.opened_merge_requests.assigned, closed: @milestone.merge_requests.closed, merged: @milestone.merge_requests.merged, show_project_name: true
.tab-pane#tab-participants
= render 'shared/milestones/participants_tab', users: @milestone.participants
.tab-pane#tab-labels
= render 'shared/milestones/labels_tab', labels: @milestone.labels, show_project_name: true
%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone) } = render 'shared/milestones/milestone',
.row milestone_path: namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone),
.col-sm-6 issues_path: namespace_project_issues_path(milestone.project.namespace, milestone.project, milestone_title: milestone.title),
%strong merge_requests_path: namespace_project_merge_requests_path(milestone.project.namespace, milestone.project, milestone_title: milestone.title),
= link_to_gfm truncate(milestone.title, length: 100), namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone) milestone: milestone
.col-sm-6
.pull-right.light #{milestone.percent_complete}% complete
.row
.col-sm-6
= link_to namespace_project_issues_path(milestone.project.namespace, milestone.project, milestone_title: milestone.title) do
= pluralize milestone.issues.count, 'Issue'
&middot;
= link_to namespace_project_merge_requests_path(milestone.project.namespace, milestone.project, milestone_title: milestone.title) do
= pluralize milestone.merge_requests.count, 'Merge Request'
.col-sm-6
= milestone_progress_bar(milestone)
.row
.col-sm-6
= render 'shared/milestone_expired', milestone: milestone
.col-sm-6
- 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" do
= icon('pencil-square-o')
Edit
\
= link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-xs btn-close"
= link_to namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-xs btn-remove" do
= icon('trash-o')
Delete
...@@ -42,62 +42,9 @@ ...@@ -42,62 +42,9 @@
= preserve do = preserve do
= markdown @milestone.description = markdown @milestone.description
- if @milestone.issues.any? && @milestone.can_be_closed? - if @milestone.complete? && @milestone.active?
.alert.alert-success.prepend-top-default .alert.alert-success.prepend-top-default
%span All issues for this milestone are closed. You may close milestone now. %span All issues for this milestone are closed. You may close milestone now.
.context.prepend-top-default = render 'shared/milestones/summary', milestone: @milestone, project: @project
.milestone-summary = render 'shared/milestones/tabs', milestone: @milestone
%h4 Progress
%strong= @milestone.issues.count
issues:
%span.milestone-stat
%strong= @milestone.open_items_count
open and
%strong= @milestone.closed_items_count
closed
%span.milestone-stat
%strong== #{@milestone.percent_complete}%
complete
%span.milestone-stat
%span.remaining-days= milestone_remaining_days(@milestone)
%span.pull-right.tab-issues-buttons
- if can?(current_user, :create_issue, @project)
= link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { milestone_id: @milestone.id }), class: "btn btn-grouped", title: "New Issue" do
%i.fa.fa-plus
New Issue
- if can?(current_user, :read_issue, @project)
= link_to 'Browse Issues', namespace_project_issues_path(@milestone.project.namespace, @milestone.project, milestone_title: @milestone.title), class: "btn btn-grouped"
%span.pull-right.tab-merge-requests-buttons.hidden
- if can?(current_user, :read_merge_request, @project)
= link_to 'Browse Merge Requests', namespace_project_merge_requests_path(@milestone.project.namespace, @milestone.project, milestone_title: @milestone.title), class: "btn btn-grouped"
= milestone_progress_bar(@milestone)
%ul.nav-links.no-top.no-bottom
%li.active
= link_to '#tab-issues', 'data-toggle' => 'tab', 'data-show' => '.tab-issues-buttons' do
Issues
%span.badge= @issues.count
%li
= link_to '#tab-merge-requests', 'data-toggle' => 'tab', 'data-show' => '.tab-merge-requests-buttons' do
Merge Requests
%span.badge= @merge_requests.count
%li
= link_to '#tab-participants', 'data-toggle' => 'tab' do
Participants
%span.badge= @users.count
%li
= link_to '#tab-labels', 'data-toggle' => 'tab', 'data-show' => '.tab-issues-buttons' do
Labels
%span.badge= @labels.count
.tab-content.milestone-content
.tab-pane.active#tab-issues
= render 'shared/milestones/issues_tab', unassigned: @issues.opened.unassigned, assigned: @issues.opened.assigned, closed: @issues.closed
.tab-pane#tab-merge-requests
= render 'shared/milestones/merge_requests_tab', unassigned: @merge_requests.opened.unassigned, assigned: @merge_requests.opened.assigned, closed: @merge_requests.closed, merged: @merge_requests.merged
.tab-pane#tab-participants
= render 'shared/milestones/participants_tab', users: @users
.tab-pane#tab-labels
= render 'shared/milestones/labels_tab', labels: @labels
-# @project is present when viewing Project's milestone
- project = @project || issuable.project
- assignee = issuable.assignee
- issuable_type = issuable.class.table_name
- base_url_args = [project.namespace.becomes(Namespace), project, issuable_type]
%li{ id: dom_id(issuable, 'sortable'), class: "issuable-row", 'data-iid' => issuable.iid, 'data-url' => polymorphic_path(issuable) }
%span
- if show_project_name
%strong #{project.name} &middot;
- elsif show_full_project_name
%strong #{project.name_with_namespace} &middot;
= link_to_gfm issuable.title, [project.namespace.becomes(Namespace), project, issuable], title: issuable.title
%div{class: 'issuable-detail'}
= link_to [project.namespace.becomes(Namespace), project, issuable] do
%span{ class: 'issuable-number' }>= issuable.to_reference
- issuable.labels.each do |label|
= link_to polymorphic_path(base_url_args, { milestone_title: @milestone.title, label_name: label.title, state: 'all' }) do
- render_colored_label(label)
- if assignee
= link_to polymorphic_path(base_url_args, { milestone_title: @milestone.title, assignee_id: issuable.assignee_id, state: 'all' }),
class: 'has_tooltip', data: { 'original-title' => "Assigned to #{sanitize(assignee.name)}", container: 'body' } do
- image_tag(avatar_icon(issuable.assignee, 16), class: "avatar s16", alt: '')
...@@ -6,9 +6,11 @@ ...@@ -6,9 +6,11 @@
.panel-heading .panel-heading
= title = title
- if show_counter - if show_counter
.pull-right= records.size .pull-right= issuables.size
- class_prefix = dom_class(records).pluralize - class_prefix = dom_class(issuables).pluralize
%ul{ class: "well-list #{class_prefix}-sortable-list", id: "#{class_prefix}-list-#{id}", "data-state" => id } %ul{ class: "well-list #{class_prefix}-sortable-list", id: "#{class_prefix}-list-#{id}", "data-state" => id }
- records.sort_by(&:position).each do |record| = render partial: 'shared/milestones/issuable',
= render 'shared/milestones/record', record: record, show_project_name: show_project_name, show_full_project_name: show_full_project_name collection: issuables.sort_by(&:position),
as: :issuable,
locals: { show_project_name: show_project_name, show_full_project_name: show_full_project_name }
...@@ -3,8 +3,8 @@ ...@@ -3,8 +3,8 @@
.row.prepend-top-default .row.prepend-top-default
.col-md-4 .col-md-4
= render 'shared/milestones/records', args.merge({ title: 'Unstarted Issues (open and unassigned)', records: unassigned, id: 'unassigned', show_counter: true }) = render 'shared/milestones/issuables', args.merge(title: 'Unstarted Issues (open and unassigned)', issuables: issues.opened.unassigned, id: 'unassigned', show_counter: true)
.col-md-4 .col-md-4
= render 'shared/milestones/records', args.merge({ title: 'Ongoing Issues (open and assigned)', records: assigned, id: 'ongoing', show_counter: true }) = render 'shared/milestones/issuables', args.merge(title: 'Ongoing Issues (open and assigned)', issuables: issues.opened.assigned, id: 'ongoing', show_counter: true)
.col-md-4 .col-md-4
= render 'shared/milestones/records', args.merge({ title: 'Completed Issues (closed)', records: closed, id: 'closed', show_counter: true }) = render 'shared/milestones/issuables', args.merge(title: 'Completed Issues (closed)', issuables: issues.closed, id: 'closed', show_counter: true)
- show_project_name = local_assigns.fetch(:show_project_name, false)
- show_full_project_name = local_assigns.fetch(:show_full_project_name, false)
%ul.bordered-list.manage-labels-list %ul.bordered-list.manage-labels-list
- labels.each do |label| - labels.each do |label|
- milestone = @milestone.is_a?(Milestone) ? @milestone : label.milestone - options = { milestone_title: @milestone.title, label_name: label.title }
%li %li
= render_colored_label(label) %span.label-row
- if show_project_name = link_to milestones_label_path(options) do
%strong &middot; #{milestone.project.name} - render_colored_label(label)
- elsif show_full_project_name %span.prepend-left-10
%strong &middot; #{milestone.project.name_with_namespace} = markdown(label.description, pipeline: :single_line)
- args = [milestone.project.namespace, milestone.project, milestone_title: milestone.title, label_name: label.title] .pull-right
- options = args.extract_options! %strong.issues-count
%span.issues-count = link_to milestones_label_path(options.merge(state: 'opened')) do
= link_to namespace_project_issues_path(*args, options.merge(state: 'opened')) do - pluralize milestone_issues_by_label_count(@milestone, label, state: :opened), 'open issue'
= pluralize label.open_issues_count, 'open issue' %strong.issues-count
%span.issues-count = link_to milestones_label_path(options.merge(state: 'closed')) do
= link_to namespace_project_issues_path(*args, options.merge(state: 'closed')) do - pluralize milestone_issues_by_label_count(@milestone, label, state: :closed), 'closed issue'
= pluralize label.closed_issues_count, 'closed issue'
...@@ -3,10 +3,10 @@ ...@@ -3,10 +3,10 @@
.row.prepend-top-default .row.prepend-top-default
.col-md-3 .col-md-3
= render 'shared/milestones/records', args.merge({ title: 'Work in progress (open and unassigned)', records: unassigned, id: 'unassigned' }) = render 'shared/milestones/issuables', args.merge(title: 'Work in progress (open and unassigned)', issuables: merge_requests.opened.unassigned, id: 'unassigned')
.col-md-3 .col-md-3
= render 'shared/milestones/records', args.merge({ title: 'Waiting for merge (open and assigned)', records: assigned, id: 'ongoing' }) = render 'shared/milestones/issuables', args.merge(title: 'Waiting for merge (open and assigned)', issuables: merge_requests.opened.assigned, id: 'ongoing')
.col-md-3 .col-md-3
= render 'shared/milestones/records', args.merge({ title: 'Rejected (closed)', records: closed, id: 'closed' }) = render 'shared/milestones/issuables', args.merge(title: 'Rejected (closed)', issuables: merge_requests.closed, id: 'closed')
.col-md-3 .col-md-3
= render 'shared/milestones/records', args.merge({ title: 'Merged', records: merged, id: 'merged', primary: true }) = render 'shared/milestones/issuables', args.merge(title: 'Merged', issuables: merge_requests.merged, id: 'merged', primary: true)
- dashboard = local_assigns[:dashboard]
- custom_dom_id = dom_id(@project ? milestone : milestone.milestones.first)
%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: custom_dom_id }
.row
.col-sm-6
%strong= link_to_gfm truncate(milestone.title, length: 100), milestone_path
.col-sm-6
.pull-right.light #{milestone.percent_complete}% complete
.row
.col-sm-6
= link_to pluralize(milestone.issues.size, 'Issue'), issues_path
&middot;
= link_to pluralize(milestone.merge_requests.size, 'Merge Request'), merge_requests_path
.col-sm-6= milestone_progress_bar(milestone)
- if milestone.is_a?(GlobalMilestone)
.row
.col-sm-6
.expiration= render('shared/milestone_expired', milestone: milestone)
.projects
- milestone.milestones.each do |milestone|
= link_to milestone_path(milestone) do
%span.label.label-gray
= dashboard ? milestone.project.name_with_namespace : milestone.project.name
- if @group
.col-sm-6
- if can?(current_user, :admin_milestones, @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-xs btn-grouped btn-reopen"
- else
= link_to 'Close Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-xs btn-close"
- if @project
.row
.col-sm-6= render('shared/milestone_expired', milestone: milestone)
.col-sm-6
- 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" do
= icon('pencil-square-o')
Edit
\
= link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-xs btn-close"
= link_to namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-xs btn-remove" do
= icon('trash-o')
Delete
-# @project is present when viewing Project's milestone
- project = @project || record.project
- record_type = record.class.table_name
- base_url_args = [project.namespace.becomes(Namespace), project, record_type]
%li{ id: dom_id(record, 'sortable'), class: "#{dom_class(record)}-row", 'data-iid' => record.iid, 'data-url' => polymorphic_path(record) }
%span
- if show_project_name
%strong #{project.name} &middot;
- elsif show_full_project_name
%strong #{project.name_with_namespace} &middot;
= link_to_gfm record.title, [project.namespace.becomes(Namespace), project, record], title: record.title
%div{class: "#{dom_class(record)}-detail"}
= link_to [project.namespace.becomes(Namespace), project, record] do
%span{ class: "#{dom_class(record)}-number" } ##{record.iid}
- record.labels.each do |label|
%a{ href: polymorphic_path(base_url_args, { milestone_title: @milestone.title, label_name: label.title, state: 'all' }) }<
= render_colored_label(label)
- if record.assignee
%a{ href: polymorphic_path(base_url_args, { milestone_title: @milestone.title, assignee_id: record.assignee_id, state: 'all' }) }
= image_tag(avatar_icon(record.assignee, 16), class: "avatar s16", alt: '')
- project = local_assigns[:project]
.context.prepend-top-default
.milestone-summary
%h4 Progress
%strong= milestone.issues.size
issues:
%span.milestone-stat
%strong= milestone.issues.opened.size
open and
%strong= milestone.issues.closed.size
closed
%span.milestone-stat
%strong== #{milestone.percent_complete}%
complete
%span.milestone-stat
%span.remaining-days= milestone_remaining_days(milestone)
%span.pull-right.tab-issues-buttons
- if project && can?(current_user, :create_issue, project)
= link_to new_namespace_project_issue_path(project.namespace, project, issue: { milestone_id: milestone.id }), class: "btn btn-grouped", title: "New Issue" do
%i.fa.fa-plus
New Issue
= link_to 'Browse Issues', milestones_browse_issuables_path(milestone, type: :issues), class: "btn btn-grouped"
%span.pull-right.tab-merge-requests-buttons.hidden
= link_to 'Browse Merge Requests', milestones_browse_issuables_path(milestone, type: :merge_requests), class: "btn btn-grouped"
= milestone_progress_bar(milestone)
%ul.nav-links.no-top.no-bottom
%li.active
= link_to '#tab-issues', 'data-toggle' => 'tab', 'data-show' => '.tab-issues-buttons' do
Issues
%span.badge= milestone.issues.size
%li
= link_to '#tab-merge-requests', 'data-toggle' => 'tab', 'data-show' => '.tab-merge-requests-buttons' do
Merge Requests
%span.badge= milestone.merge_requests.size
%li
= link_to '#tab-participants', 'data-toggle' => 'tab' do
Participants
%span.badge= milestone.participants.count
%li
= link_to '#tab-labels', 'data-toggle' => 'tab' do
Labels
%span.badge= milestone.labels.count
- show_project_name = local_assigns.fetch(:show_project_name, false)
- show_full_project_name = local_assigns.fetch(:show_full_project_name, false)
.tab-content.milestone-content
.tab-pane.active#tab-issues
= render 'shared/milestones/issues_tab', issues: milestone.issues, show_project_name: show_project_name, show_full_project_name: show_full_project_name
.tab-pane#tab-merge-requests
= render 'shared/milestones/merge_requests_tab', merge_requests: milestone.merge_requests, show_project_name: show_project_name, show_full_project_name: show_full_project_name
.tab-pane#tab-participants
= render 'shared/milestones/participants_tab', users: milestone.participants
.tab-pane#tab-labels
= render 'shared/milestones/labels_tab', labels: milestone.labels
- page_title milestone.title, "Milestones"
- group = local_assigns[:group]
.detail-page-header
.status-box{ class: "status-box-#{milestone.closed? ? 'closed' : 'open'}" }
- if milestone.closed?
Closed
- elsif milestone.expired?
Expired
- else
Open
%span.identifier
Milestone #{milestone.title}
- if milestone.expires_at
%span.creator
&middot;
= milestone.expires_at
- if group
.pull-right
- if can?(current_user, :admin_milestones, group)
- if milestone.active?
= link_to 'Close Milestone', group_milestone_path(group, milestone.safe_title, title: milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-grouped btn-close"
- else
= link_to 'Reopen Milestone', group_milestone_path(group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-grouped btn-reopen"
.detail-page-description.gray-content-block.second-block
%h2.title
= markdown escape_once(milestone.title), pipeline: :single_line
- if milestone.complete? && milestone.active?
.alert.alert-success.prepend-top-default
- close_msg = group ? 'You may close the milestone now.' : 'Navigate to the project to close the milestone.'
%span All issues for this milestone are closed. #{close_msg}
.table-holder
%table.table
%thead
%tr
%th Project
%th Open issues
%th State
%th Due date
- milestone.milestones.each do |ms|
%tr
%td
- project_name = group ? ms.project.name : ms.project.name_with_namespace
= link_to project_name, namespace_project_milestone_path(ms.project.namespace, ms.project, ms)
%td
= ms.issues.opened.count
%td
- if ms.closed?
Closed
- else
Open
%td
= ms.expires_at
...@@ -15,7 +15,6 @@ module Gitlab ...@@ -15,7 +15,6 @@ module Gitlab
# Custom directories with classes and modules you want to be autoloadable. # Custom directories with classes and modules you want to be autoloadable.
config.autoload_paths.push(*%W(#{config.root}/lib config.autoload_paths.push(*%W(#{config.root}/lib
#{config.root}/app/decorators
#{config.root}/app/models/hooks #{config.root}/app/models/hooks
#{config.root}/app/models/concerns #{config.root}/app/models/concerns
#{config.root}/app/models/project_services #{config.root}/app/models/project_services
......
...@@ -45,4 +45,3 @@ Feature: Group Milestones ...@@ -45,4 +45,3 @@ Feature: Group Milestones
And I click on one group milestone And I click on one group milestone
And I click on the "Labels" tab And I click on the "Labels" tab
Then I should see the list of labels Then I should see the list of labels
And I should see the project name in the Label row
...@@ -96,14 +96,6 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps ...@@ -96,14 +96,6 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps
end end
end end
step 'I should see the project name in the Label row' do
page.within('#tab-labels') do
@global_milestone.projects.each do |project|
expect(page).to have_content project.name
end
end
end
private private
def group_milestone def group_milestone
......
...@@ -59,7 +59,7 @@ class Spinach::Features::ProjectIssuesMilestones < Spinach::FeatureSteps ...@@ -59,7 +59,7 @@ class Spinach::Features::ProjectIssuesMilestones < Spinach::FeatureSteps
end end
step 'I should see 3 issues' do step 'I should see 3 issues' do
expect(page).to have_selector('#tab-issues li.issue-row', count: 4) expect(page).to have_selector('#tab-issues li.issuable-row', count: 4)
end end
step 'I click link to remove milestone' do step 'I click link to remove milestone' do
......
...@@ -60,7 +60,7 @@ describe Milestone, models: true do ...@@ -60,7 +60,7 @@ describe Milestone, models: true do
end end
it "should recover from dividing by zero" do it "should recover from dividing by zero" do
expect(milestone.issues).to receive(:count).and_return(0) expect(milestone.issues).to receive(:size).and_return(0)
expect(milestone.percent_complete).to eq(0) expect(milestone.percent_complete).to eq(0)
end end
end end
...@@ -114,7 +114,6 @@ describe Milestone, models: true do ...@@ -114,7 +114,6 @@ describe Milestone, models: true do
end end
it { expect(milestone.closed_items_count).to eq(1) } it { expect(milestone.closed_items_count).to eq(1) }
it { expect(milestone.open_items_count).to eq(2) }
it { expect(milestone.total_items_count).to eq(3) } it { expect(milestone.total_items_count).to eq(3) }
it { expect(milestone.is_empty?).to be_falsey } it { expect(milestone.is_empty?).to be_falsey }
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment