Merge branch 'issue_13621_2' into 'master'
Labels should be visible in dashboard and group milestone views Closes #13621 See merge request !2931
This commit is contained in:
commit
903aa7c95e
|
@ -14,6 +14,7 @@ v 8.6.0 (unreleased)
|
|||
- Allow search for logged out users
|
||||
- Don't show Issues/MRs from archived projects in Groups view
|
||||
- Increase the notes polling timeout over time (Roberto Dip)
|
||||
- Show labels in dashboard and group milestone views
|
||||
|
||||
v 8.5.4
|
||||
- Do not cache requests for badges (including builds badge)
|
||||
|
|
|
@ -23,7 +23,7 @@ class Dispatcher
|
|||
new Issue()
|
||||
shortcut_handler = new ShortcutsIssuable()
|
||||
new ZenMode()
|
||||
when 'projects:milestones:show'
|
||||
when 'projects:milestones:show', 'groups:milestones:show', 'dashboard:milestones:show'
|
||||
new Milestone()
|
||||
when 'projects:milestones:new', 'projects:milestones:edit'
|
||||
new ZenMode()
|
||||
|
|
|
@ -69,7 +69,7 @@ class @Milestone
|
|||
|
||||
@bindIssuesSorting()
|
||||
@bindMergeRequestSorting()
|
||||
@bindTabsSwitching
|
||||
@bindTabsSwitching()
|
||||
|
||||
bindIssuesSorting: ->
|
||||
$("#issues-list-unassigned, #issues-list-ongoing, #issues-list-closed").sortable(
|
||||
|
@ -104,7 +104,7 @@ class @Milestone
|
|||
|
||||
).disableSelection()
|
||||
|
||||
bindMergeRequestSorting: ->
|
||||
bindTabsSwitching: ->
|
||||
$('a[data-toggle="tab"]').on 'show.bs.tab', (e) ->
|
||||
currentTabClass = $(e.target).data('show')
|
||||
previousTabClass = $(e.relatedTarget).data('show')
|
||||
|
@ -112,7 +112,8 @@ class @Milestone
|
|||
$(previousTabClass).hide()
|
||||
$(currentTabClass).removeClass('hidden')
|
||||
$(currentTabClass).show()
|
||||
|
||||
|
||||
bindMergeRequestSorting: ->
|
||||
$("#merge_requests-list-unassigned, #merge_requests-list-ongoing, #merge_requests-list-closed").sortable(
|
||||
connectWith: ".merge_requests-sortable-list",
|
||||
dropOnEmpty: true,
|
||||
|
|
|
@ -19,10 +19,11 @@ li.milestone {
|
|||
width: 105px;
|
||||
}
|
||||
|
||||
.issue-row {
|
||||
.issuable-row {
|
||||
.color-label {
|
||||
border-radius: 2px;
|
||||
padding: 3px !important;
|
||||
margin-right: 7px;
|
||||
}
|
||||
|
||||
// Issue title
|
||||
|
@ -44,20 +45,15 @@ li.milestone {
|
|||
}
|
||||
}
|
||||
|
||||
.issues-sortable-list {
|
||||
.issue-detail {
|
||||
.issues-sortable-list, .merge_requests-sortable-list {
|
||||
.issuable-detail {
|
||||
display: block;
|
||||
margin-top: 7px;
|
||||
|
||||
.issue-number{
|
||||
.issuable-number {
|
||||
color: rgba(0,0,0,0.44);
|
||||
margin-right: 5px;
|
||||
}
|
||||
.color-label {
|
||||
padding: 6px 10px;
|
||||
margin-right: 7px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
float: none;
|
||||
}
|
||||
|
|
|
@ -32,10 +32,6 @@ class Projects::MilestonesController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
def show
|
||||
@issues = @milestone.issues
|
||||
@users = @milestone.participants.uniq
|
||||
@merge_requests = @milestone.merge_requests
|
||||
@labels = @milestone.labels
|
||||
end
|
||||
|
||||
def create
|
||||
|
|
|
@ -263,11 +263,9 @@ class IssuableFinder
|
|||
def by_label(items)
|
||||
if labels?
|
||||
if filter_by_no_label?
|
||||
items = items.
|
||||
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 })
|
||||
items = items.without_label
|
||||
else
|
||||
items = items.joins(:labels).where(labels: { title: label_names })
|
||||
items = items.with_label(label_names)
|
||||
|
||||
if projects
|
||||
items = items.where(labels: { project_id: projects })
|
||||
|
|
|
@ -9,6 +9,32 @@ module MilestonesHelper
|
|||
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)
|
||||
options = {
|
||||
class: 'progress-bar progress-bar-success',
|
||||
|
|
|
@ -29,12 +29,15 @@ module Issuable
|
|||
scope :assigned, -> { where("assignee_id IS NOT NULL") }
|
||||
scope :unassigned, -> { where("assignee_id IS NULL") }
|
||||
scope :of_projects, ->(ids) { where(project_id: ids) }
|
||||
scope :of_milestones, ->(ids) { where(milestone_id: ids) }
|
||||
scope :opened, -> { with_state(:opened, :reopened) }
|
||||
scope :only_opened, -> { with_state(:opened) }
|
||||
scope :only_reopened, -> { with_state(:reopened) }
|
||||
scope :closed, -> { with_state(:closed) }
|
||||
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 :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 :references_project, -> { references(:project) }
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
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
|
|||
attr_accessor :title, :labels
|
||||
alias_attribute :name, :title
|
||||
|
||||
delegate :color, :description, to: :@first_label
|
||||
|
||||
def self.build_collection(labels)
|
||||
labels = labels.group_by(&:title)
|
||||
|
||||
labels.map do |title, label|
|
||||
new(title, label)
|
||||
labels.map do |title, labels|
|
||||
new(title, labels)
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(title, labels)
|
||||
@title = title
|
||||
@labels = labels
|
||||
@first_label = labels.find { |lbl| lbl.description.present? } || labels.first
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
class GlobalMilestone
|
||||
include Milestoneish
|
||||
|
||||
attr_accessor :title, :milestones
|
||||
alias_attribute :name, :title
|
||||
|
||||
|
@ -28,33 +30,7 @@ class GlobalMilestone
|
|||
end
|
||||
|
||||
def projects
|
||||
milestones.map { |milestone| milestone.project }
|
||||
end
|
||||
|
||||
def issue_count
|
||||
milestones.map { |milestone| milestone.issues.count }.sum
|
||||
end
|
||||
|
||||
def merge_requests_count
|
||||
milestones.map { |milestone| milestone.merge_requests.count }.sum
|
||||
end
|
||||
|
||||
def open_items_count
|
||||
milestones.map { |milestone| milestone.open_items_count }.sum
|
||||
end
|
||||
|
||||
def closed_items_count
|
||||
milestones.map { |milestone| milestone.closed_items_count }.sum
|
||||
end
|
||||
|
||||
def total_items_count
|
||||
milestones.map { |milestone| milestone.total_items_count }.sum
|
||||
end
|
||||
|
||||
def percent_complete
|
||||
((closed_items_count * 100) / total_items_count).abs
|
||||
rescue ZeroDivisionError
|
||||
0
|
||||
@projects ||= Project.for_milestones(milestones.map(&:id))
|
||||
end
|
||||
|
||||
def state
|
||||
|
@ -76,35 +52,20 @@ class GlobalMilestone
|
|||
end
|
||||
|
||||
def issues
|
||||
@issues ||= milestones.map(&:issues).flatten.group_by(&:state)
|
||||
@issues ||= Issue.of_milestones(milestones.map(&:id)).includes(:project)
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
def participants
|
||||
@participants ||= milestones.map(&:participants).flatten.compact.uniq
|
||||
end
|
||||
|
||||
def opened_issues
|
||||
issues.values_at("opened", "reopened").compact.flatten
|
||||
end
|
||||
|
||||
def closed_issues
|
||||
issues['closed']
|
||||
end
|
||||
|
||||
def opened_merge_requests
|
||||
merge_requests.values_at("opened", "reopened").compact.flatten
|
||||
end
|
||||
|
||||
def closed_merge_requests
|
||||
merge_requests.values_at("closed", "merged", "locked").compact.flatten
|
||||
end
|
||||
|
||||
def complete?
|
||||
total_items_count == closed_items_count
|
||||
def labels
|
||||
@labels ||= GlobalLabel.build_collection(milestones.map(&:labels).flatten)
|
||||
.sort_by!(&:title)
|
||||
end
|
||||
|
||||
def due_date
|
||||
|
|
|
@ -137,9 +137,7 @@ class MergeRequest < ActiveRecord::Base
|
|||
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 :of_projects, ->(ids) { where(target_project_id: ids) }
|
||||
scope :opened, -> { with_states(:opened, :reopened) }
|
||||
scope :merged, -> { with_state(:merged) }
|
||||
scope :closed, -> { with_state(:closed) }
|
||||
scope :closed_and_merged, -> { with_states(:closed, :merged) }
|
||||
|
||||
scope :join_project, -> { joins(:target_project) }
|
||||
|
|
|
@ -24,12 +24,13 @@ class Milestone < ActiveRecord::Base
|
|||
include Sortable
|
||||
include Referable
|
||||
include StripAttribute
|
||||
include Milestoneish
|
||||
|
||||
belongs_to :project
|
||||
has_many :issues
|
||||
has_many :labels, -> { distinct.reorder('labels.title') }, through: :issues
|
||||
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 :closed, -> { with_state(:closed) }
|
||||
|
@ -92,30 +93,6 @@ class Milestone < ActiveRecord::Base
|
|||
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
|
||||
if due_date
|
||||
if due_date.past?
|
||||
|
|
|
@ -215,6 +215,7 @@ class Project < ActiveRecord::Base
|
|||
scope :public_only, -> { where(visibility_level: Project::PUBLIC) }
|
||||
scope :public_and_internal_only, -> { where(visibility_level: Project.public_and_internal_levels) }
|
||||
scope :non_archived, -> { where(archived: false) }
|
||||
scope :for_milestones, ->(ids) { joins(:milestones).where('milestones.id' => ids).distinct }
|
||||
|
||||
state_machine :import_status, initial: :none do
|
||||
event :import_start do
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
%li{ id: dom_id(issue, 'sortable'), class: 'issue-row', 'data-iid' => issue.iid }
|
||||
%span.milestone-row
|
||||
- project = issue.project
|
||||
%strong #{project.name_with_namespace} ·
|
||||
= 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"
|
|
@ -1,6 +0,0 @@
|
|||
.panel.panel-default
|
||||
.panel-heading= title
|
||||
%ul{ class: "well-list issues-sortable-list" }
|
||||
- if issues
|
||||
- issues.each do |issue|
|
||||
= render 'issue', issue: issue
|
|
@ -1,10 +0,0 @@
|
|||
%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} ·
|
||||
= 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"
|
|
@ -1,6 +0,0 @@
|
|||
.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
|
|
@ -1,25 +1,6 @@
|
|||
%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) }
|
||||
.row
|
||||
.col-sm-6
|
||||
%strong
|
||||
= link_to_gfm truncate(milestone.title, length: 100), dashboard_milestone_path(milestone.safe_title, title: milestone.title)
|
||||
.col-sm-6
|
||||
.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'
|
||||
·
|
||||
= 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
|
||||
= render 'shared/milestones/milestone',
|
||||
milestone_path: dashboard_milestone_path(milestone.safe_title, title: milestone.title),
|
||||
issues_path: issues_dashboard_path(milestone_title: milestone.title),
|
||||
merge_requests_path: merge_requests_dashboard_path(milestone_title: milestone.title),
|
||||
milestone: milestone,
|
||||
dashboard: true
|
||||
|
|
|
@ -1,105 +1,5 @@
|
|||
- page_title @milestone.title, "Milestones"
|
||||
- header_title "Milestones", dashboard_milestones_path
|
||||
|
||||
.detail-page-header
|
||||
.status-box{ class: "status-box-#{@milestone.closed? ? 'closed' : 'open'}" }
|
||||
- if @milestone.closed?
|
||||
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
|
||||
–
|
||||
#{@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
|
||||
= render 'shared/milestones/top', milestone: @milestone
|
||||
= render 'shared/milestones/summary', milestone: @milestone
|
||||
= render 'shared/milestones/tabs', milestone: @milestone, show_full_project_name: true
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
%li{ id: dom_id(issue, 'sortable'), class: 'issue-row', 'data-iid' => issue.iid }
|
||||
%span.milestone-row
|
||||
- project = issue.project
|
||||
%strong #{project.name} ·
|
||||
= link_to [project.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: ''
|
|
@ -1,6 +0,0 @@
|
|||
.panel.panel-default
|
||||
.panel-heading= title
|
||||
%ul{ class: "well-list issues-sortable-list" }
|
||||
- if issues
|
||||
- issues.each do |issue|
|
||||
= render 'issue', issue: issue
|
|
@ -1,10 +0,0 @@
|
|||
%li{ id: dom_id(merge_request, 'sortable'), class: 'mr-row', 'data-iid' => merge_request.iid }
|
||||
%span.milestone-row
|
||||
- project = merge_request.project
|
||||
%strong #{project.name} ·
|
||||
= link_to [project.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: ''
|
|
@ -1,6 +0,0 @@
|
|||
.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
|
|
@ -1,29 +1,5 @@
|
|||
%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) }
|
||||
.row
|
||||
.col-sm-6
|
||||
%strong
|
||||
= link_to_gfm truncate(milestone.title, length: 100), group_milestone_path(@group, milestone.safe_title, title: milestone.title)
|
||||
.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'
|
||||
·
|
||||
= 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"
|
||||
= render 'shared/milestones/milestone',
|
||||
milestone_path: group_milestone_path(@group, milestone.safe_title, title: milestone.title),
|
||||
issues_path: issues_group_path(@group, milestone_title: milestone.title),
|
||||
merge_requests_path: merge_requests_group_path(@group, milestone_title: milestone.title),
|
||||
milestone: milestone
|
||||
|
|
|
@ -1,112 +1,4 @@
|
|||
- page_title @milestone.title, "Milestones"
|
||||
= render "header_title"
|
||||
|
||||
.detail-page-header
|
||||
.status-box{ class: "status-box-#{@milestone.closed? ? 'closed' : 'open'}" }
|
||||
- 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
|
||||
–
|
||||
#{@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
|
||||
= render 'shared/milestones/top', milestone: @milestone, group: @group
|
||||
= render 'shared/milestones/summary', milestone: @milestone
|
||||
= render 'shared/milestones/tabs', milestone: @milestone, show_project_name: true
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
%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: ''
|
|
@ -1,7 +0,0 @@
|
|||
.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
|
|
@ -1,8 +0,0 @@
|
|||
%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: ''
|
|
@ -1,5 +0,0 @@
|
|||
.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
|
|
@ -1,31 +1,5 @@
|
|||
%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone) }
|
||||
.row
|
||||
.col-sm-6
|
||||
%strong
|
||||
= link_to_gfm truncate(milestone.title, length: 100), namespace_project_milestone_path(milestone.project.namespace, milestone.project, 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'
|
||||
·
|
||||
= 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
|
||||
= render 'shared/milestones/milestone',
|
||||
milestone_path: namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone),
|
||||
issues_path: namespace_project_issues_path(milestone.project.namespace, milestone.project, milestone_title: milestone.title),
|
||||
merge_requests_path: namespace_project_merge_requests_path(milestone.project.namespace, milestone.project, milestone_title: milestone.title),
|
||||
milestone: milestone
|
||||
|
|
|
@ -42,102 +42,9 @@
|
|||
= preserve do
|
||||
= markdown @milestone.description
|
||||
|
||||
- if @milestone.issues.any? && @milestone.can_be_closed?
|
||||
- if @milestone.complete? && @milestone.active?
|
||||
.alert.alert-success.prepend-top-default
|
||||
%span All issues for this milestone are closed. You may close milestone now.
|
||||
|
||||
.context.prepend-top-default
|
||||
.milestone-summary
|
||||
%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'
|
||||
= render 'shared/milestones/summary', milestone: @milestone, project: @project
|
||||
= render 'shared/milestones/tabs', milestone: @milestone
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
-# @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} ·
|
||||
- elsif show_full_project_name
|
||||
%strong #{project.name_with_namespace} ·
|
||||
= 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: '')
|
|
@ -0,0 +1,16 @@
|
|||
- 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 }
|
|
@ -0,0 +1,10 @@
|
|||
- 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)
|
|
@ -0,0 +1,18 @@
|
|||
%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'
|
|
@ -0,0 +1,12 @@
|
|||
- 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)
|
|
@ -0,0 +1,45 @@
|
|||
- 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
|
||||
·
|
||||
= 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
|
|
@ -0,0 +1,8 @@
|
|||
%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
|
|
@ -0,0 +1,28 @@
|
|||
- 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)
|
|
@ -0,0 +1,30 @@
|
|||
%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
|
|
@ -0,0 +1,58 @@
|
|||
- 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
|
||||
·
|
||||
= 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
|
|||
And I fill milestone name
|
||||
When I press create mileston button
|
||||
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
|
|||
end
|
||||
|
||||
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'
|
||||
end
|
||||
|
||||
|
@ -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
|
||||
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
|
||||
expect(page).to have_link(issue.title, href: namespace_project_issue_path(issue.project.namespace, issue.project, issue))
|
||||
end
|
||||
|
@ -60,6 +63,39 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps
|
|||
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
|
||||
|
||||
def group_milestone
|
||||
|
@ -68,6 +104,10 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps
|
|||
%w(gitlabhq gitlab-ci cookbook-gitlab).each do |path|
|
||||
project = create :project, path: path, group: group
|
||||
milestone = create :milestone, title: "Version 7.2", project: project
|
||||
|
||||
create(:label, project: project, title: 'bug')
|
||||
create(:label, project: project, title: 'feature')
|
||||
|
||||
create :issue,
|
||||
project: project,
|
||||
assignee: current_user,
|
||||
|
@ -80,11 +120,14 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps
|
|||
due_date: '2114-08-20',
|
||||
description: 'Lorem Ipsum is simply dummy text'
|
||||
|
||||
create :issue,
|
||||
issue = create :issue,
|
||||
project: project,
|
||||
assignee: current_user,
|
||||
author: current_user,
|
||||
milestone: milestone
|
||||
|
||||
issue.labels << project.labels.find_by(title: 'bug')
|
||||
issue.labels << project.labels.find_by(title: 'feature')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -59,7 +59,7 @@ class Spinach::Features::ProjectIssuesMilestones < Spinach::FeatureSteps
|
|||
end
|
||||
|
||||
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
|
||||
|
||||
step 'I click link to remove milestone' do
|
||||
|
|
|
@ -60,7 +60,7 @@ describe Milestone, models: true do
|
|||
end
|
||||
|
||||
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)
|
||||
end
|
||||
end
|
||||
|
@ -114,7 +114,6 @@ describe Milestone, models: true do
|
|||
end
|
||||
|
||||
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.is_empty?).to be_falsey }
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue