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
|
@ -64,6 +64,7 @@ class @Milestone
|
|||
constructor: ->
|
||||
@bindIssuesSorting()
|
||||
@bindMergeRequestSorting()
|
||||
@bindTabsSwitching
|
||||
|
||||
bindIssuesSorting: ->
|
||||
$("#issues-list-unassigned, #issues-list-ongoing, #issues-list-closed").sortable(
|
||||
|
@ -122,3 +123,12 @@ class @Milestone
|
|||
Milestone.updateMergeRequest(ui.item, merge_request_url, data)
|
||||
|
||||
).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;
|
||||
}
|
||||
}
|
||||
|
||||
.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
|
||||
@users = @milestone.participants.uniq
|
||||
@merge_requests = @milestone.merge_requests
|
||||
@labels = @milestone.labels
|
||||
end
|
||||
|
||||
def create
|
||||
|
|
|
@ -85,6 +85,10 @@ class Label < ActiveRecord::Base
|
|||
issues.opened.count
|
||||
end
|
||||
|
||||
def closed_issues_count
|
||||
issues.closed.count
|
||||
end
|
||||
|
||||
def template?
|
||||
template
|
||||
end
|
||||
|
|
|
@ -27,6 +27,7 @@ class Milestone < ActiveRecord::Base
|
|||
|
||||
belongs_to :project
|
||||
has_many :issues
|
||||
has_many :labels, through: :issues
|
||||
has_many :merge_requests
|
||||
has_many :participants, through: :issues, source: :assignee
|
||||
|
||||
|
@ -109,6 +110,19 @@ class Milestone < ActiveRecord::Base
|
|||
0
|
||||
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
|
||||
if due_date
|
||||
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) }
|
||||
.pull-right.assignee-icon
|
||||
- if issue.assignee
|
||||
= image_tag avatar_icon(issue.assignee, 16), class: "avatar s16", alt: ''
|
||||
%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
|
||||
|
||||
.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-heading= title
|
||||
.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.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 }
|
||||
- merge_requests.sort_by(&:position).each do |merge_request|
|
||||
= render 'merge_request', merge_request: merge_request
|
||||
%li.light.ui-sort-disabled Drag and drop available
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
- 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 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')
|
||||
Delete
|
||||
|
||||
|
@ -32,7 +32,7 @@
|
|||
= icon('pencil-square-o')
|
||||
Edit
|
||||
|
||||
.detail-page-description.content-block
|
||||
.detail-page-description.milestone-detail.second-block
|
||||
%h2.title
|
||||
= markdown escape_once(@milestone.title), pipeline: :single_line
|
||||
%div
|
||||
|
@ -47,44 +47,55 @@
|
|||
%span All issues for this milestone are closed. You may close milestone now.
|
||||
|
||||
.context.prepend-top-default
|
||||
%p.lead
|
||||
Progress:
|
||||
#{@milestone.closed_items_count} closed
|
||||
–
|
||||
#{@milestone.open_items_count} open
|
||||
|
||||
%span.light #{@milestone.percent_complete}% complete
|
||||
%span.pull-right= @milestone.expires_at
|
||||
= milestone_progress_bar(@milestone)
|
||||
|
||||
%ul.nav-links.no-top.no-bottom
|
||||
%li.active
|
||||
= link_to '#tab-issues', 'data-toggle' => 'tab' do
|
||||
Issues
|
||||
%span.badge= @issues.count
|
||||
%li
|
||||
= link_to '#tab-merge-requests', 'data-toggle' => 'tab' do
|
||||
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
|
||||
.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.time-elapsed
|
||||
%strong== #{@milestone.percent_time_used}%
|
||||
time elapsed
|
||||
%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"
|
||||
|
||||
.oneline
|
||||
All issues in this milestone
|
||||
= 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')
|
||||
|
@ -94,14 +105,6 @@
|
|||
= render('issues', title: 'Completed Issues (closed)', issues: @issues.closed, id: 'closed')
|
||||
|
||||
.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
|
||||
.col-md-3
|
||||
= 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
|
||||
|
||||
.tab-pane#tab-participants
|
||||
.content-block.oneline-block
|
||||
All participants to this milestone
|
||||
|
||||
%ul.bordered-list
|
||||
- @users.each do |user|
|
||||
%li
|
||||
|
@ -128,3 +128,18 @@
|
|||
%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'
|
||||
|
|
|
@ -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"
|
|
@ -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 New Issue