Merge branch 'issue_3276' into 'master'
Labels should be visible in milestone view Closes #3276 See merge request !2599
This commit is contained in:
commit
de84fa365e
11 changed files with 223 additions and 45 deletions
|
@ -64,6 +64,7 @@ class @Milestone
|
||||||
constructor: ->
|
constructor: ->
|
||||||
@bindIssuesSorting()
|
@bindIssuesSorting()
|
||||||
@bindMergeRequestSorting()
|
@bindMergeRequestSorting()
|
||||||
|
@bindTabsSwitching
|
||||||
|
|
||||||
bindIssuesSorting: ->
|
bindIssuesSorting: ->
|
||||||
$("#issues-list-unassigned, #issues-list-ongoing, #issues-list-closed").sortable(
|
$("#issues-list-unassigned, #issues-list-ongoing, #issues-list-closed").sortable(
|
||||||
|
@ -122,3 +123,12 @@ class @Milestone
|
||||||
Milestone.updateMergeRequest(ui.item, merge_request_url, data)
|
Milestone.updateMergeRequest(ui.item, merge_request_url, data)
|
||||||
|
|
||||||
).disableSelection()
|
).disableSelection()
|
||||||
|
|
||||||
|
bindMergeRequestSorting: ->
|
||||||
|
$('a[data-toggle="tab"]').on 'show.bs.tab', (e) ->
|
||||||
|
currentTabClass = $(e.target).data('show')
|
||||||
|
previousTabClass = $(e.relatedTarget).data('show')
|
||||||
|
|
||||||
|
$(previousTabClass).hide()
|
||||||
|
$(currentTabClass).removeClass('hidden')
|
||||||
|
$(currentTabClass).show()
|
||||||
|
|
|
@ -11,3 +11,60 @@ li.milestone {
|
||||||
height: 6px;
|
height: 6px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.milestone-content {
|
||||||
|
.issues-count {
|
||||||
|
margin-right: 17px;
|
||||||
|
float: right;
|
||||||
|
width: 105px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.issue-row {
|
||||||
|
.color-label {
|
||||||
|
border-radius: 2px;
|
||||||
|
padding: 3px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Issue title
|
||||||
|
span a {
|
||||||
|
color: rgba(0,0,0,0.64);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.milestone-summary {
|
||||||
|
margin-bottom: 25px;
|
||||||
|
|
||||||
|
.milestone-stat {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-elapsed {
|
||||||
|
color: $orange-light;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.issues-sortable-list {
|
||||||
|
.issue-detail {
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
.issue-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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.milestone-detail {
|
||||||
|
border-bottom: 1px solid $border-color;
|
||||||
|
padding: 20px 0;
|
||||||
|
}
|
||||||
|
|
|
@ -35,6 +35,7 @@ class Projects::MilestonesController < Projects::ApplicationController
|
||||||
@issues = @milestone.issues
|
@issues = @milestone.issues
|
||||||
@users = @milestone.participants.uniq
|
@users = @milestone.participants.uniq
|
||||||
@merge_requests = @milestone.merge_requests
|
@merge_requests = @milestone.merge_requests
|
||||||
|
@labels = @milestone.labels
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
|
|
|
@ -85,6 +85,10 @@ class Label < ActiveRecord::Base
|
||||||
issues.opened.count
|
issues.opened.count
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def closed_issues_count
|
||||||
|
issues.closed.count
|
||||||
|
end
|
||||||
|
|
||||||
def template?
|
def template?
|
||||||
template
|
template
|
||||||
end
|
end
|
||||||
|
|
|
@ -27,6 +27,7 @@ class Milestone < ActiveRecord::Base
|
||||||
|
|
||||||
belongs_to :project
|
belongs_to :project
|
||||||
has_many :issues
|
has_many :issues
|
||||||
|
has_many :labels, through: :issues
|
||||||
has_many :merge_requests
|
has_many :merge_requests
|
||||||
has_many :participants, through: :issues, source: :assignee
|
has_many :participants, through: :issues, source: :assignee
|
||||||
|
|
||||||
|
@ -109,6 +110,19 @@ class Milestone < ActiveRecord::Base
|
||||||
0
|
0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Returns the elapsed time (in percent) since the Milestone creation date until today.
|
||||||
|
# If the Milestone doesn't have a due_date then returns 0 since we can't calculate the elapsed time.
|
||||||
|
# If the Milestone is overdue then it returns 100%.
|
||||||
|
def percent_time_used
|
||||||
|
return 0 unless due_date
|
||||||
|
return 100 if expired?
|
||||||
|
|
||||||
|
duration = ((created_at - due_date.to_datetime) / 1.day)
|
||||||
|
days_elapsed = ((created_at - Time.now) / 1.day)
|
||||||
|
|
||||||
|
((days_elapsed.to_f / duration) * 100).floor
|
||||||
|
end
|
||||||
|
|
||||||
def expires_at
|
def expires_at
|
||||||
if due_date
|
if due_date
|
||||||
if due_date.past?
|
if due_date.past?
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
%li{ id: dom_id(issue, 'sortable'), class: 'issue-row', 'data-iid' => issue.iid, 'data-url' => issue_path(issue) }
|
%li{ id: dom_id(issue, 'sortable'), class: 'issue-row', 'data-iid' => issue.iid, 'data-url' => issue_path(issue) }
|
||||||
.pull-right.assignee-icon
|
|
||||||
- if issue.assignee
|
|
||||||
= image_tag avatar_icon(issue.assignee, 16), class: "avatar s16", alt: ''
|
|
||||||
%span
|
%span
|
||||||
= 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
|
= 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,6 +1,7 @@
|
||||||
.panel.panel-default
|
.panel.panel-default
|
||||||
.panel-heading= title
|
.panel-heading
|
||||||
|
= title
|
||||||
|
.pull-right= issues.size
|
||||||
%ul{ class: "well-list issues-sortable-list", id: "issues-list-#{id}", "data-state" => id }
|
%ul{ class: "well-list issues-sortable-list", id: "issues-list-#{id}", "data-state" => id }
|
||||||
- issues.sort_by(&:position).each do |issue|
|
- issues.sort_by(&:position).each do |issue|
|
||||||
= render 'issue', issue: issue
|
= render 'issue', issue: issue
|
||||||
%li.light.ui-sort-disabled Drag and drop available
|
|
||||||
|
|
|
@ -3,4 +3,3 @@
|
||||||
%ul{ class: "well-list merge_requests-sortable-list", id: "merge_requests-list-#{id}", "data-state" => id }
|
%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|
|
- merge_requests.sort_by(&:position).each do |merge_request|
|
||||||
= render 'merge_request', merge_request: merge_request
|
= render 'merge_request', merge_request: merge_request
|
||||||
%li.light.ui-sort-disabled Drag and drop available
|
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
- else
|
- else
|
||||||
= link_to 'Reopen Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-nr btn-grouped"
|
= link_to 'Reopen Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-nr btn-grouped"
|
||||||
|
|
||||||
= link_to namespace_project_milestone_path(@project.namespace, @project, @milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-grouped btn-nr btn-remove" do
|
= link_to namespace_project_milestone_path(@project.namespace, @project, @milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-grouped btn-nr" do
|
||||||
= icon('trash-o')
|
= icon('trash-o')
|
||||||
Delete
|
Delete
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@
|
||||||
= icon('pencil-square-o')
|
= icon('pencil-square-o')
|
||||||
Edit
|
Edit
|
||||||
|
|
||||||
.detail-page-description.content-block
|
.detail-page-description.milestone-detail.second-block
|
||||||
%h2.title
|
%h2.title
|
||||||
= markdown escape_once(@milestone.title), pipeline: :single_line
|
= markdown escape_once(@milestone.title), pipeline: :single_line
|
||||||
%div
|
%div
|
||||||
|
@ -47,44 +47,55 @@
|
||||||
%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
|
.context.prepend-top-default
|
||||||
%p.lead
|
.milestone-summary
|
||||||
Progress:
|
%h4 Progress
|
||||||
#{@milestone.closed_items_count} closed
|
%strong= @milestone.issues.count
|
||||||
–
|
issues:
|
||||||
#{@milestone.open_items_count} open
|
%span.milestone-stat
|
||||||
|
%strong= @milestone.open_items_count
|
||||||
%span.light #{@milestone.percent_complete}% complete
|
open and
|
||||||
%span.pull-right= @milestone.expires_at
|
%strong= @milestone.closed_items_count
|
||||||
= milestone_progress_bar(@milestone)
|
closed
|
||||||
|
%span.milestone-stat
|
||||||
%ul.nav-links.no-top.no-bottom
|
%strong== #{@milestone.percent_complete}%
|
||||||
%li.active
|
complete
|
||||||
= link_to '#tab-issues', 'data-toggle' => 'tab' do
|
%span.milestone-stat
|
||||||
Issues
|
%span.time-elapsed
|
||||||
%span.badge= @issues.count
|
%strong== #{@milestone.percent_time_used}%
|
||||||
%li
|
time elapsed
|
||||||
= link_to '#tab-merge-requests', 'data-toggle' => 'tab' do
|
%span.pull-right.tab-issues-buttons
|
||||||
Merge Requests
|
|
||||||
%span.badge= @merge_requests.count
|
|
||||||
%li
|
|
||||||
= link_to '#tab-participants', 'data-toggle' => 'tab' do
|
|
||||||
Participants
|
|
||||||
%span.badge= @users.count
|
|
||||||
|
|
||||||
.tab-content
|
|
||||||
.tab-pane.active#tab-issues
|
|
||||||
.content-block.oneline-block
|
|
||||||
.controls
|
|
||||||
- if can?(current_user, :create_issue, @project)
|
- 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
|
= 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
|
%i.fa.fa-plus
|
||||||
New Issue
|
New Issue
|
||||||
- if can?(current_user, :read_issue, @project)
|
- 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"
|
= 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"
|
||||||
|
|
||||||
.oneline
|
= milestone_progress_bar(@milestone)
|
||||||
All issues in this 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
|
.row.prepend-top-default
|
||||||
.col-md-4
|
.col-md-4
|
||||||
= render('issues', title: 'Unstarted Issues (open and unassigned)', issues: @issues.opened.unassigned, id: 'unassigned')
|
= render('issues', title: 'Unstarted Issues (open and unassigned)', issues: @issues.opened.unassigned, id: 'unassigned')
|
||||||
|
@ -94,14 +105,6 @@
|
||||||
= render('issues', title: 'Completed Issues (closed)', issues: @issues.closed, id: 'closed')
|
= render('issues', title: 'Completed Issues (closed)', issues: @issues.closed, id: 'closed')
|
||||||
|
|
||||||
.tab-pane#tab-merge-requests
|
.tab-pane#tab-merge-requests
|
||||||
.content-block.oneline-block
|
|
||||||
.controls
|
|
||||||
- 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"
|
|
||||||
|
|
||||||
.oneline
|
|
||||||
All merge requests in this milestone
|
|
||||||
|
|
||||||
.row.prepend-top-default
|
.row.prepend-top-default
|
||||||
.col-md-3
|
.col-md-3
|
||||||
= render('merge_requests', title: 'Work in progress (open and unassigned)', merge_requests: @merge_requests.opened.unassigned, id: 'unassigned')
|
= render('merge_requests', title: 'Work in progress (open and unassigned)', merge_requests: @merge_requests.opened.unassigned, id: 'unassigned')
|
||||||
|
@ -117,9 +120,6 @@
|
||||||
= render 'merge_request', merge_request: merge_request
|
= render 'merge_request', merge_request: merge_request
|
||||||
|
|
||||||
.tab-pane#tab-participants
|
.tab-pane#tab-participants
|
||||||
.content-block.oneline-block
|
|
||||||
All participants to this milestone
|
|
||||||
|
|
||||||
%ul.bordered-list
|
%ul.bordered-list
|
||||||
- @users.each do |user|
|
- @users.each do |user|
|
||||||
%li
|
%li
|
||||||
|
@ -128,3 +128,18 @@
|
||||||
%strong= truncate(user.name, lenght: 40)
|
%strong= truncate(user.name, lenght: 40)
|
||||||
%br
|
%br
|
||||||
%small.cgray= user.username
|
%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'
|
||||||
|
|
23
features/project/milestone.feature
Normal file
23
features/project/milestone.feature
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
Feature: Project Milestone
|
||||||
|
Background:
|
||||||
|
Given I sign in as a user
|
||||||
|
And I own project "Shop"
|
||||||
|
And project "Shop" has labels: "bug", "feature", "enhancement"
|
||||||
|
And project "Shop" has milestone "v2.2"
|
||||||
|
And milestone has issue "Bugfix1" with labels: "bug", "feature"
|
||||||
|
And milestone has issue "Bugfix2" with labels: "bug", "enhancement"
|
||||||
|
|
||||||
|
|
||||||
|
@javascript
|
||||||
|
Scenario: Listing issues from issues tab
|
||||||
|
Given I visit project "Shop" milestones page
|
||||||
|
And I click link "v2.2"
|
||||||
|
Then I should see the labels "bug", "enhancement" and "feature"
|
||||||
|
|
||||||
|
@javascript
|
||||||
|
Scenario: Listing labels from labels tab
|
||||||
|
Given I visit project "Shop" milestones page
|
||||||
|
And I click link "v2.2"
|
||||||
|
And I click link "Labels"
|
||||||
|
Then I should see the list of labels
|
||||||
|
And I should see the labels "bug", "enhancement" and "feature"
|
53
features/steps/project/project_milestone.rb
Normal file
53
features/steps/project/project_milestone.rb
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
class Spinach::Features::ProjectMilestone < Spinach::FeatureSteps
|
||||||
|
include SharedAuthentication
|
||||||
|
include SharedProject
|
||||||
|
include SharedPaths
|
||||||
|
|
||||||
|
step 'milestone has issue "Bugfix1" with labels: "bug", "feature"' do
|
||||||
|
project = Project.find_by(name: "Shop")
|
||||||
|
milestone = project.milestones.find_by(title: 'v2.2')
|
||||||
|
issue = create(:issue, title: "Bugfix1", project: project, milestone: milestone)
|
||||||
|
issue.labels << project.labels.find_by(title: 'bug')
|
||||||
|
issue.labels << project.labels.find_by(title: 'feature')
|
||||||
|
end
|
||||||
|
|
||||||
|
step 'milestone has issue "Bugfix2" with labels: "bug", "enhancement"' do
|
||||||
|
project = Project.find_by(name: "Shop")
|
||||||
|
milestone = project.milestones.find_by(title: 'v2.2')
|
||||||
|
issue = create(:issue, title: "Bugfix2", project: project, milestone: milestone)
|
||||||
|
issue.labels << project.labels.find_by(title: 'bug')
|
||||||
|
issue.labels << project.labels.find_by(title: 'enhancement')
|
||||||
|
end
|
||||||
|
|
||||||
|
step 'project "Shop" has milestone "v2.2"' do
|
||||||
|
project = Project.find_by(name: "Shop")
|
||||||
|
milestone = create(:milestone,
|
||||||
|
title: "v2.2",
|
||||||
|
project: project,
|
||||||
|
description: "# Description header"
|
||||||
|
)
|
||||||
|
3.times { create(:issue, project: project, milestone: milestone) }
|
||||||
|
end
|
||||||
|
|
||||||
|
step 'I should see the list of labels' do
|
||||||
|
expect(page).to have_selector('ul.manage-labels-list')
|
||||||
|
end
|
||||||
|
|
||||||
|
step 'I should see the labels "bug", "enhancement" and "feature"' do
|
||||||
|
page.within('#tab-issues') do
|
||||||
|
expect(page).to have_content 'bug'
|
||||||
|
expect(page).to have_content 'enhancement'
|
||||||
|
expect(page).to have_content 'feature'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
step 'I click link "v2.2"' do
|
||||||
|
click_link "v2.2"
|
||||||
|
end
|
||||||
|
|
||||||
|
step 'I click link "Labels"' do
|
||||||
|
page.within('.nav-links') do
|
||||||
|
page.find(:xpath, "//a[@href='#tab-labels']").click
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue