Commit 903aa7c9 authored by Douwe Maan's avatar Douwe Maan

Merge branch 'issue_13621_2' into 'master'

Labels should be visible in dashboard and group milestone views

Closes #13621

See merge request !2931
parents 99f08b3f 95b06a62
...@@ -14,6 +14,7 @@ v 8.6.0 (unreleased) ...@@ -14,6 +14,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')
...@@ -112,7 +112,8 @@ class @Milestone ...@@ -112,7 +112,8 @@ class @Milestone
$(previousTabClass).hide() $(previousTabClass).hide()
$(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 { .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
...@@ -44,20 +45,15 @@ li.milestone { ...@@ -44,20 +45,15 @@ li.milestone {
} }
} }
.issues-sortable-list { .issues-sortable-list, .merge_requests-sortable-list {
.issue-detail { .issuable-detail {
display: block; display: block;
margin-top: 7px;
.issue-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
......
...@@ -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',
......
...@@ -29,12 +29,15 @@ module Issuable ...@@ -29,12 +29,15 @@ module Issuable
scope :assigned, -> { where("assignee_id IS NOT NULL") } scope :assigned, -> { where("assignee_id IS NOT NULL") }
scope :unassigned, -> { where("assignee_id IS NULL") } scope :unassigned, -> { where("assignee_id IS NULL") }
scope :of_projects, ->(ids) { where(project_id: ids) } scope :of_projects, ->(ids) { where(project_id: ids) }
scope :of_milestones, ->(ids) { where(milestone_id: ids) }
scope :opened, -> { with_state(:opened, :reopened) } scope :opened, -> { with_state(:opened, :reopened) }
scope :only_opened, -> { with_state(:opened) } scope :only_opened, -> { with_state(:opened) }
scope :only_reopened, -> { with_state(:reopened) } scope :only_reopened, -> { with_state(:reopened) }
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
...@@ -28,33 +30,7 @@ class GlobalMilestone ...@@ -28,33 +30,7 @@ class GlobalMilestone
end end
def projects def projects
milestones.map { |milestone| milestone.project } @projects ||= Project.for_milestones(milestones.map(&:id))
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
0
end end
def state def state
...@@ -76,35 +52,20 @@ class GlobalMilestone ...@@ -76,35 +52,20 @@ class GlobalMilestone
end end
def issues def issues
@issues ||= milestones.map(&:issues).flatten.group_by(&:state) @issues ||= Issue.of_milestones(milestones.map(&:id)).includes(:project)
end end
def merge_requests def merge_requests
@merge_requests ||= milestones.map(&:merge_requests).flatten.group_by(&:state) @merge_requests ||= MergeRequest.of_milestones(milestones.map(&:id)).includes(:target_project)
end end
def participants def participants
@participants ||= milestones.map(&:participants).flatten.compact.uniq @participants ||= milestones.map(&:participants).flatten.compact.uniq
end end
def opened_issues def labels
issues.values_at("opened", "reopened").compact.flatten @labels ||= GlobalLabel.build_collection(milestones.map(&:labels).flatten)
end .sort_by!(&:title)
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
def complete?
total_items_count == closed_items_count
end end
def due_date def due_date
......
...@@ -137,9 +137,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -137,9 +137,7 @@ 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, -> { with_state(:closed) }
scope :closed_and_merged, -> { with_states(:closed, :merged) } scope :closed_and_merged, -> { with_states(:closed, :merged) }
scope :join_project, -> { joins(:target_project) } scope :join_project, -> { joins(:target_project) }
......
...@@ -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,6 +215,7 @@ class Project < ActiveRecord::Base ...@@ -215,6 +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).distinct }
state_machine :import_status, initial: :none do state_machine :import_status, initial: :none do
event :import_start do event :import_start do
......
%li{ id: dom_id(issue, 'sortable'), class: 'issue-row', 'data-iid' => issue.iid }
%span.milestone-row
- project = issue.project
%strong #{project.name_with_namespace} &middot;
= link_to [project.namespace.becomes(Namespace), project, issue] do
%span.cgray ##{issue.iid}
= link_to_gfm issue.title, [project.namespace.becomes(Namespace), project, issue], title: issue.title
.pull-right.assignee-icon
- if issue.assignee
= image_tag avatar_icon(issue.assignee, 16), class: "avatar s16"
.panel.panel-default
.panel-heading= title
%ul{ class: "well-list issues-sortable-list" }
- if issues
- issues.each do |issue|
= render 'issue', issue: issue
%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_with_namespace} &middot;
= link_to [project.namespace.becomes(Namespace), project, merge_request] do
%span.cgray ##{merge_request.iid}
= link_to_gfm merge_request.title, [project.namespace.becomes(Namespace), project, merge_request], title: merge_request.title
.pull-right.assignee-icon
- if merge_request.assignee
= image_tag avatar_icon(merge_request.assignee, 16), class: "avatar s16"
.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
%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.issue_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
%p.lead
Progress:
#{@milestone.closed_items_count} closed
&ndash;
#{@milestone.open_items_count} open
= 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.issue_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
.tab-content
.tab-pane.active#tab-issues
.gray-content-block.middle-block
.pull-right
= link_to 'Browse Issues', issues_dashboard_path(milestone_title: @milestone.title), class: "btn btn-grouped"
.oneline
All issues in this milestone
.row.prepend-top-default
.col-md-6
= render 'issues', title: "Open", issues: @milestone.opened_issues
.col-md-6
= render 'issues', title: "Closed", issues: @milestone.closed_issues
.tab-pane#tab-merge-requests
.gray-content-block.middle-block
.pull-right
= link_to 'Browse Merge Requests', merge_requests_dashboard_path(milestone_title: @milestone.title), class: "btn btn-grouped"
.oneline
All merge requests in this milestone
.row.prepend-top-default
.col-md-6
= render 'merge_requests', title: "Open", merge_requests: @milestone.opened_merge_requests
.col-md-6
= render 'merge_requests', title: "Closed", merge_requests: @milestone.closed_merge_requests
.tab-pane#tab-participants
.gray-content-block.middle-block
.oneline
All participants to this milestone
%ul.bordered-list
- @milestone.participants.each do |user|
%li
= link_to user, title: user.name, class: "darken" do
= image_tag avatar_icon(user, 32), class: "avatar s32"
%strong= truncate(user.name, lenght: 40)
%br
%small.cgray= user.username
%li{ id: dom_id(issue, 'sortable'), class: 'issue-row', 'data-iid' => issue.iid }
%span.milestone-row
- project = issue.project
%strong #{project.name} &middot;
= link_to [project.namespace.becomes(Namespace), project, issue] do
%span.cgray ##{issue.iid}
= link_to_gfm issue.title, [project.namespace.becomes(Namespace), project, issue], title: issue.title
.pull-right.assignee-icon
- if issue.assignee
= image_tag avatar_icon(issue.assignee, 16), class: "avatar s16", alt: ''
.panel.panel-default
.panel-heading= title
%ul{ class: "well-list issues-sortable-list" }
- if issues
- issues.each do |issue|
= render 'issue', issue: issue
%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} &middot;
= link_to [project.namespace.becomes(Namespace), project, merge_request] do
%span.cgray ##{merge_request.iid}
= link_to_gfm merge_request.title, [project.namespace.becomes(Namespace), project, merge_request], title: merge_request.title
.pull-right.assignee-icon
- if merge_request.assignee
= image_tag avatar_icon(merge_request.assignee, 16), class: "avatar s16", alt: ''
.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
%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.issue_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
%p.lead
Progress:
#{@milestone.closed_items_count} closed
&ndash;
#{@milestone.open_items_count} open
= 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.issue_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
.tab-content
.tab-pane.active#tab-issues
.gray-content-block.middle-block
.pull-right
= link_to 'Browse Issues', issues_group_path(@group, milestone_title: @milestone.title), class: "btn btn-grouped"
.oneline
All issues in this milestone
.row.prepend-top-default
.col-md-6
= render 'issues', title: "Open", issues: @milestone.opened_issues
.col-md-6
= render 'issues', title: "Closed", issues: @milestone.closed_issues
.tab-pane#tab-merge-requests
.gray-content-block.middle-block
.pull-right
= link_to 'Browse Merge Requests', merge_requests_group_path(@group, milestone_title: @milestone.title), class: "btn btn-grouped"
.oneline
All merge requests in this milestone
.row.prepend-top-default
.col-md-6
= render 'merge_requests', title: "Open", merge_requests: @milestone.opened_merge_requests
.col-md-6
= render 'merge_requests', title: "Closed", merge_requests: @milestone.closed_merge_requests
.tab-pane#tab-participants
.gray-content-block.middle-block
.oneline
All participants to this milestone
%ul.bordered-list
- @milestone.participants.each do |user|
%li
= link_to user, title: user.name, class: "darken" do
= image_tag avatar_icon(user, 32), class: "avatar s32"
%strong= truncate(user.name, lenght: 40)
%br
%small.cgray= user.username
%li{ id: dom_id(issue, 'sortable'), class: 'issue-row', 'data-iid' => issue.iid, 'data-url' => issue_path(issue) }
%span
= link_to_gfm issue.title, [@project.namespace.becomes(Namespace), @project, issue], title: issue.title
.issue-detail
= link_to [@project.namespace.becomes(Namespace), @project, issue] do
%span.issue-number ##{issue.iid}
- issue.labels.each do |label|
= render_colored_label(label)
- if issue.assignee
= image_tag avatar_icon(issue.assignee, 16), class: "avatar s24", alt: ''
.panel.panel-default
.panel-heading
= title
.pull-right= issues.size
%ul{ class: "well-list issues-sortable-list", id: "issues-list-#{id}", "data-state" => id }
- issues.sort_by(&:position).each do |issue|
= render 'issue', issue: issue
%li{ id: dom_id(merge_request, 'sortable'), class: 'mr-row', 'data-iid' => merge_request.iid, 'data-url' => merge_request_path(merge_request) }
%span.str-truncated
= link_to [@project.namespace.becomes(Namespace), @project, merge_request] do
%span.cgray ##{merge_request.iid}
= link_to_gfm merge_request.title, [@project.namespace.becomes(Namespace), @project, merge_request], title: merge_request.title
.pull-right.assignee-icon
- if merge_request.assignee
= image_tag avatar_icon(merge_request.assignee, 16), class: "avatar s16", alt: ''
.panel.panel-default
.panel-heading= title
%ul{ class: "well-list merge_requests-sortable-list", id: "merge_requests-list-#{id}", "data-state" => id }
- merge_requests.sort_by(&:position).each do |merge_request|
= render 'merge_request', merge_request: merge_request
%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,102 +42,9 @@ ...@@ -42,102 +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
.row.prepend-top-default
.col-md-4
= render('issues', title: 'Unstarted Issues (open and unassigned)', issues: @issues.opened.unassigned, id: 'unassigned')
.col-md-4
= render('issues', title: 'Ongoing Issues (open and assigned)', issues: @issues.opened.assigned, id: 'ongoing')
.col-md-4
= render('issues', title: 'Completed Issues (closed)', issues: @issues.closed, id: 'closed')
.tab-pane#tab-merge-requests
.row.prepend-top-default
.col-md-3
= render('merge_requests', title: 'Work in progress (open and unassigned)', merge_requests: @merge_requests.opened.unassigned, id: 'unassigned')
.col-md-3
= render('merge_requests', title: 'Waiting for merge (open and assigned)', merge_requests: @merge_requests.opened.assigned, id: 'ongoing')
.col-md-3
= render('merge_requests', title: 'Rejected (closed)', merge_requests: @merge_requests.closed, id: 'closed')
.col-md-3
.panel.panel-primary
.panel-heading Merged
%ul.well-list
- @merge_requests.merged.each do |merge_request|
= render 'merge_request', merge_request: merge_request
.tab-pane#tab-participants
%ul.bordered-list
- @users.each do |user|
%li
= link_to user, title: user.name, class: "darken" do
= image_tag avatar_icon(user, 32), class: "avatar s32"
%strong= truncate(user.name, lenght: 40)
%br
%small.cgray= user.username
.tab-pane#tab-labels
%ul.bordered-list.manage-labels-list
- @labels.each do |label|
%li
= render_colored_label(label)
- args = [@milestone.project.namespace, @milestone.project, milestone_title: @milestone.title, label_name: label.title]
- options = args.extract_options!
%span.issues-count
= link_to namespace_project_issues_path(*args, options.merge(state: 'opened')) do
= pluralize label.open_issues_count, 'open issue'
%span.issues-count
= link_to namespace_project_issues_path(*args, options.merge(state: 'closed')) do
= pluralize label.closed_issues_count, 'closed issue'
-# @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: '')
- show_counter = local_assigns.fetch(:show_counter, false)
- primary = local_assigns.fetch(:primary, false)
- panel_class = primary ? 'panel-primary' : 'panel-default'
.panel{ class: panel_class }
.panel-heading
= title
- if show_counter
.pull-right= issuables.size
- class_prefix = dom_class(issuables).pluralize
%ul{ class: "well-list #{class_prefix}-sortable-list", id: "#{class_prefix}-list-#{id}", "data-state" => id }
= render partial: 'shared/milestones/issuable',
collection: issuables.sort_by(&:position),
as: :issuable,
locals: { show_project_name: show_project_name, show_full_project_name: show_full_project_name }
- args = { show_project_name: local_assigns.fetch(:show_project_name, false),
show_full_project_name: local_assigns.fetch(:show_full_project_name, false) }
.row.prepend-top-default
.col-md-4
= render 'shared/milestones/issuables', args.merge(title: 'Unstarted Issues (open and unassigned)', issuables: issues.opened.unassigned, id: 'unassigned', show_counter: true)
.col-md-4
= render 'shared/milestones/issuables', args.merge(title: 'Ongoing Issues (open and assigned)', issuables: issues.opened.assigned, id: 'ongoing', show_counter: true)
.col-md-4
= render 'shared/milestones/issuables', args.merge(title: 'Completed Issues (closed)', issuables: issues.closed, id: 'closed', show_counter: true)
%ul.bordered-list.manage-labels-list
- labels.each do |label|
- options = { milestone_title: @milestone.title, label_name: label.title }
%li
%span.label-row
= link_to milestones_label_path(options) do
- render_colored_label(label)
%span.prepend-left-10
= markdown(label.description, pipeline: :single_line)
.pull-right
%strong.issues-count
= link_to milestones_label_path(options.merge(state: 'opened')) do
- pluralize milestone_issues_by_label_count(@milestone, label, state: :opened), 'open issue'
%strong.issues-count
= link_to milestones_label_path(options.merge(state: 'closed')) do
- pluralize milestone_issues_by_label_count(@milestone, label, state: :closed), 'closed issue'
- args = { show_project_name: local_assigns.fetch(:show_project_name, false),
show_full_project_name: local_assigns.fetch(:show_full_project_name, false) }
.row.prepend-top-default
.col-md-3
= render 'shared/milestones/issuables', args.merge(title: 'Work in progress (open and unassigned)', issuables: merge_requests.opened.unassigned, id: 'unassigned')
.col-md-3
= render 'shared/milestones/issuables', args.merge(title: 'Waiting for merge (open and assigned)', issuables: merge_requests.opened.assigned, id: 'ongoing')
.col-md-3
= render 'shared/milestones/issuables', args.merge(title: 'Rejected (closed)', issuables: merge_requests.closed, id: 'closed')
.col-md-3
= 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
%ul.bordered-list
- users.each do |user|
%li
= link_to user, title: user.name, class: "darken" do
= image_tag avatar_icon(user, 32), class: "avatar s32"
%strong= truncate(user.name, lenght: 40)
%br
%small.cgray= user.username
- 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
...@@ -28,3 +28,20 @@ Feature: Group Milestones ...@@ -28,3 +28,20 @@ Feature: Group Milestones
And I fill milestone name And I fill milestone name
When I press create mileston button When I press create mileston button
Then milestone in each project should be created Then milestone in each project should be created
Scenario: I should see Issues listed with labels
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 the "bug" label
And I should see the "feature" label
And I should see the project name in the Issue row
Scenario: I should see the Labels tab
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
And I click on the "Labels" tab
Then I should see the list of labels
...@@ -24,6 +24,9 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps ...@@ -24,6 +24,9 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps
end end
step 'I click on one group milestone' do step 'I click on one group milestone' do
milestones = Milestone.where(title: 'GL-113')
@global_milestone = GlobalMilestone.new('GL-113', milestones)
click_link 'GL-113' click_link 'GL-113'
end end
...@@ -33,7 +36,7 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps ...@@ -33,7 +36,7 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps
step 'I should see group milestone with all issues and MRs assigned to that milestone' do step 'I should see group milestone with all issues and MRs assigned to that milestone' do
expect(page).to have_content('Milestone GL-113') expect(page).to have_content('Milestone GL-113')
expect(page).to have_content('Progress: 0 closed – 3 open') expect(page).to have_content('3 issues: 3 open and 0 closed')
issue = Milestone.find_by(name: 'GL-113').issues.first issue = Milestone.find_by(name: 'GL-113').issues.first
expect(page).to have_link(issue.title, href: namespace_project_issue_path(issue.project.namespace, issue.project, issue)) expect(page).to have_link(issue.title, href: namespace_project_issue_path(issue.project.namespace, issue.project, issue))
end end
...@@ -60,6 +63,39 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps ...@@ -60,6 +63,39 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps
end end
end end
step 'I should see the "bug" label' do
page.within('#tab-issues') do
expect(page).to have_content 'bug'
end
end
step 'I should see the "feature" label' do
page.within('#tab-issues') do
expect(page).to have_content 'bug'
end
end
step 'I should see the project name in the Issue row' do
page.within('#tab-issues') do
@global_milestone.projects.each do |project|
expect(page).to have_content project.name
end
end
end
step 'I click on the "Labels" tab' do
page.within('.nav-links') do
page.find(:xpath, "//a[@href='#tab-labels']").click
end
end
step 'I should see the list of labels' do
page.within('#tab-labels') do
expect(page).to have_content 'bug'
expect(page).to have_content 'feature'
end
end
private private
def group_milestone def group_milestone
...@@ -68,6 +104,10 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps ...@@ -68,6 +104,10 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps
%w(gitlabhq gitlab-ci cookbook-gitlab).each do |path| %w(gitlabhq gitlab-ci cookbook-gitlab).each do |path|
project = create :project, path: path, group: group project = create :project, path: path, group: group
milestone = create :milestone, title: "Version 7.2", project: project milestone = create :milestone, title: "Version 7.2", project: project
create(:label, project: project, title: 'bug')
create(:label, project: project, title: 'feature')
create :issue, create :issue,
project: project, project: project,
assignee: current_user, assignee: current_user,
...@@ -80,11 +120,14 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps ...@@ -80,11 +120,14 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps
due_date: '2114-08-20', due_date: '2114-08-20',
description: 'Lorem Ipsum is simply dummy text' description: 'Lorem Ipsum is simply dummy text'
create :issue, issue = create :issue,
project: project, project: project,
assignee: current_user, assignee: current_user,
author: current_user, author: current_user,
milestone: milestone milestone: milestone
issue.labels << project.labels.find_by(title: 'bug')
issue.labels << project.labels.find_by(title: 'feature')
end end
end end
end end
...@@ -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