Commit c3241009 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent d8ccc7a0
...@@ -73,9 +73,14 @@ const handleUserPopoverMouseOver = event => { ...@@ -73,9 +73,14 @@ const handleUserPopoverMouseOver = event => {
location: userData.location, location: userData.location,
bio: userData.bio, bio: userData.bio,
organization: userData.organization, organization: userData.organization,
status: userData.status,
loaded: true, loaded: true,
}); });
if (userData.status) {
return Promise.resolve();
}
return UsersCache.retrieveStatusById(userId); return UsersCache.retrieveStatusById(userId);
}) })
.then(status => { .then(status => {
......
...@@ -51,7 +51,7 @@ export default { ...@@ -51,7 +51,7 @@ export default {
</script> </script>
<template> <template>
<gl-popover :target="target" boundary="viewport" placement="top" show> <gl-popover :target="target" boundary="viewport" placement="top" offset="0, 1" show>
<div class="user-popover d-flex"> <div class="user-popover d-flex">
<div class="p-1 flex-shrink-1"> <div class="p-1 flex-shrink-1">
<user-avatar-image :img-src="user.avatarUrl" :size="60" css-classes="mr-2" /> <user-avatar-image :img-src="user.avatarUrl" :size="60" css-classes="mr-2" />
...@@ -90,7 +90,7 @@ export default { ...@@ -90,7 +90,7 @@ export default {
name="location" name="location"
class="category-icon flex-shrink-0" class="category-icon flex-shrink-0"
/> />
<span class="ml-1">{{ user.location }}</span> <span v-if="user.location" class="ml-1">{{ user.location }}</span>
<gl-skeleton-loading <gl-skeleton-loading
v-if="locationIsLoading" v-if="locationIsLoading"
:lines="1" :lines="1"
......
...@@ -53,12 +53,10 @@ module MilestoneActions ...@@ -53,12 +53,10 @@ module MilestoneActions
# rubocop:disable Gitlab/ModuleWithInstanceVariables # rubocop:disable Gitlab/ModuleWithInstanceVariables
def milestone_redirect_path def milestone_redirect_path
if @project if @milestone.global_milestone?
project_milestone_path(@project, @milestone) url_for(action: :show, title: @milestone.title)
elsif @group
group_milestone_path(@group, @milestone.safe_title, title: @milestone.title)
else else
dashboard_milestone_path(@milestone.safe_title, title: @milestone.title) url_for(action: :show)
end end
end end
# rubocop:enable Gitlab/ModuleWithInstanceVariables # rubocop:enable Gitlab/ModuleWithInstanceVariables
......
...@@ -4,6 +4,18 @@ module MilestonesHelper ...@@ -4,6 +4,18 @@ module MilestonesHelper
include EntityDateHelper include EntityDateHelper
include Gitlab::Utils::StrongMemoize include Gitlab::Utils::StrongMemoize
def milestone_status_string(milestone)
if milestone.closed?
_('Closed')
elsif milestone.expired?
_('Past due')
elsif milestone.upcoming?
_('Upcoming')
else
_('Open')
end
end
def milestones_filter_path(opts = {}) def milestones_filter_path(opts = {})
if @project if @project
project_milestones_path(@project, opts) project_milestones_path(@project, opts)
...@@ -213,33 +225,19 @@ module MilestonesHelper ...@@ -213,33 +225,19 @@ module MilestonesHelper
end end
end end
def milestone_merge_request_tab_path(milestone) def milestone_tab_path(milestone, tab)
if @project if milestone.global_milestone?
merge_requests_project_milestone_path(@project, milestone, format: :json) url_for(action: tab, title: milestone.title, format: :json)
elsif @group
merge_requests_group_milestone_path(@group, milestone.safe_title, title: milestone.title, format: :json)
else else
merge_requests_dashboard_milestone_path(milestone, title: milestone.title, format: :json) url_for(action: tab, format: :json)
end end
end end
def milestone_participants_tab_path(milestone) def update_milestone_path(milestone, params = {})
if @project if milestone.project_milestone?
participants_project_milestone_path(@project, milestone, format: :json) project_milestone_path(milestone.project, milestone, milestone: params)
elsif @group
participants_group_milestone_path(@group, milestone.safe_title, title: milestone.title, format: :json)
else else
participants_dashboard_milestone_path(milestone, title: milestone.title, format: :json) group_milestone_route(milestone, params)
end
end
def milestone_labels_tab_path(milestone)
if @project
labels_project_milestone_path(@project, milestone, format: :json)
elsif @group
labels_group_milestone_path(@group, milestone.safe_title, title: milestone.title, format: :json)
else
labels_dashboard_milestone_path(milestone, title: milestone.title, format: :json)
end end
end end
...@@ -264,6 +262,14 @@ module MilestonesHelper ...@@ -264,6 +262,14 @@ module MilestonesHelper
milestone_path(milestone.milestone, params) milestone_path(milestone.milestone, params)
end end
def edit_milestone_path(milestone)
if milestone.group_milestone?
edit_group_milestone_path(milestone.group, milestone)
elsif milestone.project_milestone?
edit_project_milestone_path(milestone.project, milestone)
end
end
def can_admin_project_milestones? def can_admin_project_milestones?
strong_memoize(:can_admin_project_milestones) do strong_memoize(:can_admin_project_milestones) do
can?(current_user, :admin_milestone, @project) can?(current_user, :admin_milestone, @project)
......
...@@ -101,6 +101,10 @@ module Milestoneish ...@@ -101,6 +101,10 @@ module Milestoneish
false false
end end
def global_milestone?
false
end
def total_issue_time_spent def total_issue_time_spent
@total_issue_time_spent ||= issues.joins(:timelogs).sum(:time_spent) @total_issue_time_spent ||= issues.joins(:timelogs).sum(:time_spent)
end end
......
...@@ -18,4 +18,8 @@ class DashboardGroupMilestone < GlobalMilestone ...@@ -18,4 +18,8 @@ class DashboardGroupMilestone < GlobalMilestone
milestones = milestones.search_title(params[:search_title]) if params[:search_title].present? milestones = milestones.search_title(params[:search_title]) if params[:search_title].present?
Milestone.filter_by_state(milestones, params[:state]).map { |m| new(m) } Milestone.filter_by_state(milestones, params[:state]).map { |m| new(m) }
end end
def dashboard_milestone?
true
end
end end
...@@ -100,4 +100,8 @@ class GlobalMilestone ...@@ -100,4 +100,8 @@ class GlobalMilestone
def labels def labels
@labels ||= GlobalLabel.build_collection(milestone.labels).sort_by!(&:title) @labels ||= GlobalLabel.build_collection(milestone.labels).sort_by!(&:title)
end end
def global_milestone?
true
end
end end
...@@ -330,6 +330,6 @@ class Milestone < ApplicationRecord ...@@ -330,6 +330,6 @@ class Milestone < ApplicationRecord
end end
def issues_finder_params def issues_finder_params
{ project_id: project_id } { project_id: project_id, group_id: group_id }.compact
end end
end end
= render "header_title" = render "header_title"
= render 'shared/milestones/top', milestone: @milestone, group: @group = render 'shared/milestones/top', milestone: @milestone, group: @group
= render 'shared/milestones/tabs', milestone: @milestone, show_project_name: true if @milestone.legacy_group_milestone? = render 'shared/milestones/tabs', milestone: @milestone, show_project_name: true
= render 'shared/milestones/sidebar', milestone: @milestone, affix_offset: 102 = render 'shared/milestones/sidebar', milestone: @milestone, affix_offset: 102
...@@ -3,57 +3,8 @@ ...@@ -3,57 +3,8 @@
- page_title @milestone.title, _('Milestones') - page_title @milestone.title, _('Milestones')
- page_description @milestone.description - page_description @milestone.description
.detail-page-header.milestone-page-header = render 'shared/milestones/header', milestone: @milestone
.status-box{ class: status_box_class(@milestone) } = render 'shared/milestones/description', milestone: @milestone
- if @milestone.closed?
= _('Closed')
- elsif @milestone.expired?
= _('Past due')
- elsif @milestone.upcoming?
= _('Upcoming')
- else
= _('Open')
.header-text-content
%span.identifier
%strong
= _('Milestone')
- if @milestone.due_date || @milestone.start_date
= milestone_date_range(@milestone)
.milestone-buttons
- if can?(current_user, :admin_milestone, @project)
= link_to edit_project_milestone_path(@project, @milestone), class: 'btn btn-grouped btn-nr' do
= _('Edit')
- if @project.group
%button.js-promote-project-milestone-button.btn.btn-grouped{ data: { toggle: 'modal',
target: '#promote-milestone-modal',
milestone_title: @milestone.title,
group_name: @project.group.name,
url: promote_project_milestone_path(@milestone.project, @milestone),
container: 'body' },
disabled: true,
type: 'button' }
= _('Promote')
#promote-milestone-modal
- if @milestone.active?
= link_to _('Close milestone'), project_milestone_path(@project, @milestone, milestone: {state_event: :close }), method: :put, class: 'btn btn-close btn-nr btn-grouped'
- else
= link_to _('Reopen milestone'), project_milestone_path(@project, @milestone, milestone: {state_event: :activate }), method: :put, class: 'btn btn-reopen btn-nr btn-grouped'
= render 'shared/milestones/delete_button'
%a.btn.btn-default.btn-grouped.float-right.d-block.d-sm-none.js-sidebar-toggle{ href: '#' }
= icon('angle-double-left')
.detail-page-description.milestone-detail
%h2.title.qa-milestone-title
= markdown_field(@milestone, :title)
%div
- if @milestone.description.present?
.description.md
= markdown_field(@milestone, :description)
= render_if_exists 'shared/milestones/burndown', milestone: @milestone, project: @project = render_if_exists 'shared/milestones/burndown', milestone: @milestone, project: @project
......
.detail-page-description.milestone-detail
%h2.title
= markdown_field(milestone, :title)
- if milestone.try(:description).present?
%div
.description.md
= markdown_field(milestone, :description)
.detail-page-header.milestone-page-header
.status-box{ class: status_box_class(milestone) }
= milestone_status_string(milestone)
.header-text-content
%span.identifier
%strong
= _('Milestone')
- if milestone.due_date || milestone.start_date
= milestone_date_range(milestone)
.milestone-buttons
- if can?(current_user, :admin_milestone, @group || @project)
- unless milestone.legacy_group_milestone?
= link_to _('Edit'), edit_milestone_path(milestone), class: 'btn btn-grouped'
- if milestone.project_milestone? && milestone.project.group
%button.js-promote-project-milestone-button.btn.btn-grouped{ data: { toggle: 'modal',
target: '#promote-milestone-modal',
milestone_title: milestone.title,
group_name: milestone.project.group.name,
url: promote_project_milestone_path(milestone.project, milestone),
container: 'body' },
disabled: true,
type: 'button' }
= _('Promote')
#promote-milestone-modal
- if milestone.active?
= link_to _('Close milestone'), update_milestone_path(milestone, { state_event: :close }), method: :put, class: 'btn btn-grouped btn-close'
- else
= link_to _('Reopen milestone'), update_milestone_path(milestone, { state_event: :activate }), method: :put, class: 'btn btn-grouped btn-reopen'
- unless milestone.legacy_group_milestone?
= render 'shared/milestones/delete_button'
%button.btn.btn-default.btn-grouped.float-right.d-block.d-sm-none.js-sidebar-toggle{ type: 'button' }
= icon('angle-double-left')
- issues_accessible = milestone.is_a?(GlobalMilestone) || can?(current_user, :read_issue, @project)
.scrolling-tabs-container.inner-page-scroll-tabs.is-smaller .scrolling-tabs-container.inner-page-scroll-tabs.is-smaller
.fade-left= icon('angle-left') .fade-left= icon('angle-left')
.fade-right= icon('angle-right') .fade-right= icon('angle-right')
%ul.nav-links.scrolling-tabs.js-milestone-tabs.nav.nav-tabs %ul.nav-links.scrolling-tabs.js-milestone-tabs.nav.nav-tabs
- if issues_accessible
%li.nav-item
= link_to '#tab-issues', class: 'nav-link active', 'data-toggle' => 'tab', 'data-show' => '.tab-issues-buttons' do
Issues
%span.badge.badge-pill= milestone.issues_visible_to_user(current_user).size
%li.nav-item
= link_to '#tab-merge-requests', class: 'nav-link', 'data-toggle' => 'tab', 'data-endpoint': milestone_merge_request_tab_path(milestone) do
Merge Requests
%span.badge.badge-pill= milestone.merge_requests_visible_to_user(current_user).size
- else
%li.nav-item
= link_to '#tab-merge-requests', class: 'nav-link active', 'data-toggle' => 'tab', 'data-endpoint': milestone_merge_request_tab_path(milestone) do
Merge Requests
%span.badge.badge-pill= milestone.merge_requests.size
%li.nav-item %li.nav-item
= link_to '#tab-participants', class: 'nav-link', 'data-toggle' => 'tab', 'data-endpoint': milestone_participants_tab_path(milestone) do = link_to '#tab-issues', class: 'nav-link active', data: { toggle: 'tab', show: '.tab-issues-buttons' } do
Participants = _('Issues')
%span.badge.badge-pill= milestone.issues_visible_to_user(current_user).size
%li.nav-item
= link_to '#tab-merge-requests', class: 'nav-link', data: { toggle: 'tab', endpoint: milestone_tab_path(milestone, 'merge_requests') } do
= _('Merge Requests')
%span.badge.badge-pill= milestone.merge_requests_visible_to_user(current_user).size
%li.nav-item
= link_to '#tab-participants', class: 'nav-link', data: { toggle: 'tab', endpoint: milestone_tab_path(milestone, 'participants') } do
= _('Participants')
%span.badge.badge-pill= milestone.issue_participants_visible_by_user(current_user).count %span.badge.badge-pill= milestone.issue_participants_visible_by_user(current_user).count
%li.nav-item %li.nav-item
= link_to '#tab-labels', class: 'nav-link', 'data-toggle' => 'tab', 'data-endpoint': milestone_labels_tab_path(milestone) do = link_to '#tab-labels', class: 'nav-link', data: { toggle: 'tab', endpoint: milestone_tab_path(milestone, 'labels') } do
Labels = _('Labels')
%span.badge.badge-pill= milestone.issue_labels_visible_by_user(current_user).count %span.badge.badge-pill= milestone.issue_labels_visible_by_user(current_user).count
- issues = milestone.sorted_issues(current_user) - issues = milestone.sorted_issues(current_user)
...@@ -32,16 +24,11 @@ ...@@ -32,16 +24,11 @@
- show_full_project_name = local_assigns.fetch(:show_full_project_name, false) - show_full_project_name = local_assigns.fetch(:show_full_project_name, false)
.tab-content.milestone-content .tab-content.milestone-content
- if issues_accessible .tab-pane.active#tab-issues{ data: { sort_endpoint: (sort_issues_project_milestone_path(@project, @milestone) if @project && current_user) } }
.tab-pane.active#tab-issues{ data: { sort_endpoint: (sort_issues_project_milestone_path(@project, @milestone) if @project && current_user) } } = render 'shared/milestones/issues_tab', issues: issues, show_project_name: show_project_name, show_full_project_name: show_full_project_name
= render 'shared/milestones/issues_tab', issues: issues, show_project_name: show_project_name, show_full_project_name: show_full_project_name .tab-pane#tab-merge-requests
.tab-pane#tab-merge-requests -# loaded async
-# loaded async = render "shared/milestones/tab_loading"
= render "shared/milestones/tab_loading"
- else
.tab-pane.active#tab-merge-requests
-# loaded async
= render "shared/milestones/tab_loading"
.tab-pane#tab-participants .tab-pane#tab-participants
-# loaded async -# loaded async
= render "shared/milestones/tab_loading" = render "shared/milestones/tab_loading"
......
...@@ -4,54 +4,15 @@ ...@@ -4,54 +4,15 @@
- group = local_assigns[:group] - group = local_assigns[:group]
- is_dynamic_milestone = milestone.legacy_group_milestone? || milestone.dashboard_milestone? - is_dynamic_milestone = milestone.legacy_group_milestone? || milestone.dashboard_milestone?
.detail-page-header.milestone-page-header = render 'shared/milestones/header', milestone: milestone
.status-box{ class: "status-box-#{milestone.closed? ? 'closed' : 'open'}" }
- if milestone.closed?
Closed
- elsif milestone.expired?
Expired
- else
Open
.header-text-content
%span.identifier
Milestone #{milestone.title}
- if milestone.due_date || milestone.start_date
%span.creator
&nbsp;&middot;
= milestone_date_range(milestone)
.milestone-buttons
- if group
- if can?(current_user, :admin_milestone, group)
- if milestone.group_milestone?
= link_to edit_group_milestone_path(group, milestone), class: "btn btn btn-grouped" do
Edit
- if milestone.active?
= link_to 'Close Milestone', group_milestone_route(milestone, {state_event: :close }), method: :put, class: "btn btn-grouped btn-close"
- else
= link_to 'Reopen Milestone', group_milestone_route(milestone, {state_event: :activate }), method: :put, class: "btn btn-grouped btn-reopen"
- unless is_dynamic_milestone
= render 'shared/milestones/delete_button'
%a.btn.btn-default.btn-grouped.float-right.d-block.d-sm-none.js-sidebar-toggle{ href: "#" }
= icon('angle-double-left')
= render 'shared/milestones/deprecation_message' if is_dynamic_milestone = render 'shared/milestones/deprecation_message' if is_dynamic_milestone
= render 'shared/milestones/description', milestone: milestone
.detail-page-description.milestone-detail
%h2.title
= markdown_field(milestone, :title)
- if milestone.group_milestone? && milestone.description.present?
%div
.description.md
= markdown_field(milestone, :description)
- if milestone.complete?(current_user) && milestone.active? - if milestone.complete?(current_user) && milestone.active?
.alert.alert-success.prepend-top-default .alert.alert-success.prepend-top-default
- close_msg = group ? 'You may close the milestone now.' : 'Navigate to the project to close the milestone.' %span
%span All issues for this milestone are closed. #{close_msg} = _('All issues for this milestone are closed.')
= group ? _('You may close the milestone now.') : _('Navigate to the project to close the milestone.')
= render_if_exists 'shared/milestones/burndown', milestone: milestone, project: @project = render_if_exists 'shared/milestones/burndown', milestone: milestone, project: @project
...@@ -77,10 +38,3 @@ ...@@ -77,10 +38,3 @@
Open Open
%td %td
= milestone.expires_at = milestone.expires_at
- elsif milestone.group_milestone?
%br
View
= link_to 'Issues', issues_group_path(@group, milestone_title: milestone.title)
or
= link_to 'Merge Requests', merge_requests_group_path(@group, milestone_title: milestone.title)
in this milestone
---
title: Add issues, MRs, participants, and labels tabs in group milestone page
merge_request: 18818
author:
type: added
---
title: Fix user popover not being displayed when the user has a status message
merge_request: 19519
author:
type: fixed
---
title: Add maven cli opts flag to maven security analyzer (part of dependency scanning)
merge_request: 19174
author:
type: changed
...@@ -39,16 +39,20 @@ Follow [Git for enormous repositories](https://gitlab.com/groups/gitlab-org/-/ep ...@@ -39,16 +39,20 @@ Follow [Git for enormous repositories](https://gitlab.com/groups/gitlab-org/-/ep
## Enabling partial clone ## Enabling partial clone
GitLab 12.1 uses Git 2.21.0 which has an arbitrary file access security > [Introduced](https://gitlab.com/gitlab-org/gitaly/issues/1553) in GitLab 12.4.
vulnerability when `uploadpack.allowFilter` is enabled, and should not be
enabled in production environments.
A feature flag is planned to enable `uploadpack.allowFilter` and To enable partial clone, use the [feature flags API](../../api/features.md).
`uploadpack.allowAnySHA1InWant` once the version of Git used by GitLab has been For example:
updated to Git 2.22.0.
Follow [this issue](https://gitlab.com/gitlab-org/gitaly/issues/1553) for ```sh
updated. curl --data "value=true" --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/features/gitaly_upload_pack_filter
```
Alternatively, flip the switch and enable the feature flag:
```ruby
Feature.enable(:gitaly_upload_pack_filter)
```
## Excluding objects by size ## Excluding objects by size
......
...@@ -140,6 +140,33 @@ using environment variables. ...@@ -140,6 +140,33 @@ using environment variables.
| `DS_RUN_ANALYZER_TIMEOUT` | Time limit when running an analyzer. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. For example, `300ms`, `1.5h`, or `2h45m`. | | | `DS_RUN_ANALYZER_TIMEOUT` | Time limit when running an analyzer. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. For example, `300ms`, `1.5h`, or `2h45m`. | |
| `PIP_INDEX_URL` | Base URL of Python Package Index (default `https://pypi.org/simple`). | | | `PIP_INDEX_URL` | Base URL of Python Package Index (default `https://pypi.org/simple`). | |
| `PIP_EXTRA_INDEX_URL` | Array of [extra URLs](https://pip.pypa.io/en/stable/reference/pip_install/#cmdoption-extra-index-url) of package indexes to use in addition to `PIP_INDEX_URL`. Comma separated. | | | `PIP_EXTRA_INDEX_URL` | Array of [extra URLs](https://pip.pypa.io/en/stable/reference/pip_install/#cmdoption-extra-index-url) of package indexes to use in addition to `PIP_INDEX_URL`. Comma separated. | |
| `MAVEN_CLI_OPTS` | List of command line arguments that will be passed to the maven analyzer during the project's build phase (see example for [using private repos](#using-private-maven-repos)). | |
### Using private Maven repos
If you have a private Maven repository which requires login credentials,
you can use the `MAVEN_CLI_OPTS` environment variable to pass variables
specified in your settings (e.g., username, password, etc.).
For example, if you have a settings file in your project source (e.g., `mysettings.xml`)
that looks like the following, you can specify the variables
[by adding an entry under your project's settings](../../../ci/variables/README.md#via-the-ui),
so that you don't have to expose your private data in `.gitlab-ci.yml` (e.g., adding
`MAVEN_CLI_OPTS` with value `--settings mysettings.xml -Dprivate.username=foo -Dprivate.password=bar`).
```xml
<!-- mysettings.xml -->
<settings>
...
<servers>
<server>
<id>private_server</id>
<username>${private.username}</username>
<password>${private.password}</password>
</server>
</servers>
</settings>
```
## Interacting with the vulnerabilities ## Interacting with the vulnerabilities
......
...@@ -103,30 +103,18 @@ When filtering by milestone, in addition to choosing a specific project mileston ...@@ -103,30 +103,18 @@ When filtering by milestone, in addition to choosing a specific project mileston
## Milestone view ## Milestone view
Not all features in the project milestone view are available in the group milestone view. This table summarizes the differences:
| Feature | Project milestone view | Group milestone view |
|--------------------------------------|:----------------------:|:--------------------:|
| Title and description | ✓ | ✓ |
| Issues assigned to milestone | ✓ | |
| Merge requests assigned to milestone | ✓ | |
| Participants and labels used | ✓ | |
| Percentage complete | ✓ | ✓ |
| Start date and due date | ✓ | ✓ |
| Total issue time spent | ✓ | ✓ |
| Total issue weight | ✓ | |
| Burndown chart **[STARTER}** | ✓ | ✓ |
The milestone view shows the title and description. The milestone view shows the title and description.
### Project milestone features There are also tabs below these that show the following:
These features are only available for project milestones and not group milestones.
- Issues assigned to the milestone are displayed in three columns: Unstarted issues, ongoing issues, and completed issues. - Issues
- Merge requests assigned to the milestone are displayed in four columns: Work in progress merge requests, waiting for merge, rejected, and closed. Shows all issues assigned to the milestone. These are displayed in three columns: Unstarted issues, ongoing issues, and completed issues.
- Participants and labels that are used in issues and merge requests that have the milestone assigned are displayed. - Merge requests
- [Burndown chart](#project-burndown-charts-starter). Shows all merge requests assigned to the milestone. These are displayed in four columns: Work in progress merge requests, waiting for merge, rejected, and closed.
- Participants
Shows all assignees of issues assigned to the milestone.
- Labels
Shows all labels that are used in issues assigned to the milestone.
### Project Burndown Charts **(STARTER)** ### Project Burndown Charts **(STARTER)**
...@@ -144,9 +132,8 @@ The milestone sidebar on the milestone view shows the following: ...@@ -144,9 +132,8 @@ The milestone sidebar on the milestone view shows the following:
- Percentage complete, which is calculated as number of closed issues divided by total number of issues. - Percentage complete, which is calculated as number of closed issues divided by total number of issues.
- The start date and due date. - The start date and due date.
- The total time spent on all issues that have the milestone assigned. - The total time spent on all issues assigned to the milestone.
- The total issue weight of all issues assigned to the milestone.
For project milestones only, the milestone sidebar shows the total issue weight of all issues that have the milestone assigned.
![Project milestone page](img/milestones_project_milestone_page.png) ![Project milestone page](img/milestones_project_milestone_page.png)
......
...@@ -51,6 +51,7 @@ dependency_scanning: ...@@ -51,6 +51,7 @@ dependency_scanning:
DS_PIP_DEPENDENCY_PATH \ DS_PIP_DEPENDENCY_PATH \
PIP_INDEX_URL \ PIP_INDEX_URL \
PIP_EXTRA_INDEX_URL \ PIP_EXTRA_INDEX_URL \
MAVEN_CLI_OPTS \
) \ ) \
--volume "$PWD:/code" \ --volume "$PWD:/code" \
--volume /var/run/docker.sock:/var/run/docker.sock \ --volume /var/run/docker.sock:/var/run/docker.sock \
......
...@@ -1396,6 +1396,9 @@ msgstr "" ...@@ -1396,6 +1396,9 @@ msgstr ""
msgid "All groups and projects" msgid "All groups and projects"
msgstr "" msgstr ""
msgid "All issues for this milestone are closed."
msgstr ""
msgid "All issues for this milestone are closed. You may close this milestone now." msgid "All issues for this milestone are closed. You may close this milestone now."
msgstr "" msgstr ""
...@@ -10941,6 +10944,9 @@ msgstr "" ...@@ -10941,6 +10944,9 @@ msgstr ""
msgid "Naming, visibility" msgid "Naming, visibility"
msgstr "" msgstr ""
msgid "Navigate to the project to close the milestone."
msgstr ""
msgid "Nav|Help" msgid "Nav|Help"
msgstr "" msgstr ""
...@@ -11782,6 +11788,9 @@ msgstr "" ...@@ -11782,6 +11788,9 @@ msgstr ""
msgid "Part of merge request changes" msgid "Part of merge request changes"
msgstr "" msgstr ""
msgid "Participants"
msgstr ""
msgid "Passed" msgid "Passed"
msgstr "" msgstr ""
...@@ -19497,6 +19506,9 @@ msgstr "" ...@@ -19497,6 +19506,9 @@ msgstr ""
msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>." msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
msgstr "" msgstr ""
msgid "You may close the milestone now."
msgstr ""
msgid "You must accept our Terms of Service and privacy policy in order to register an account" msgid "You must accept our Terms of Service and privacy policy in order to register an account"
msgstr "" msgstr ""
......
...@@ -3,9 +3,9 @@ ...@@ -3,9 +3,9 @@
require 'spec_helper' require 'spec_helper'
describe 'Group milestones' do describe 'Group milestones' do
let(:group) { create(:group) } let_it_be(:group) { create(:group) }
let!(:project) { create(:project_empty_repo, group: group) } let_it_be(:project) { create(:project_empty_repo, group: group) }
let(:user) { create(:group_member, :maintainer, user: create(:user), group: group ).user } let_it_be(:user) { create(:group_member, :maintainer, user: create(:user), group: group ).user }
around do |example| around do |example|
Timecop.freeze { example.run } Timecop.freeze { example.run }
...@@ -71,9 +71,9 @@ describe 'Group milestones' do ...@@ -71,9 +71,9 @@ describe 'Group milestones' do
end end
context 'when milestones exists' do context 'when milestones exists' do
let!(:other_project) { create(:project_empty_repo, group: group) } let_it_be(:other_project) { create(:project_empty_repo, group: group) }
let!(:active_project_milestone1) do let_it_be(:active_project_milestone1) do
create( create(
:milestone, :milestone,
project: project, project: project,
...@@ -83,12 +83,12 @@ describe 'Group milestones' do ...@@ -83,12 +83,12 @@ describe 'Group milestones' do
description: 'Lorem Ipsum is simply dummy text' description: 'Lorem Ipsum is simply dummy text'
) )
end end
let!(:active_project_milestone2) { create(:milestone, project: other_project, state: 'active', title: 'v1.1') } let_it_be(:active_project_milestone2) { create(:milestone, project: other_project, state: 'active', title: 'v1.1') }
let!(:closed_project_milestone1) { create(:milestone, project: project, state: 'closed', title: 'v2.0') } let_it_be(:closed_project_milestone1) { create(:milestone, project: project, state: 'closed', title: 'v2.0') }
let!(:closed_project_milestone2) { create(:milestone, project: other_project, state: 'closed', title: 'v2.0') } let_it_be(:closed_project_milestone2) { create(:milestone, project: other_project, state: 'closed', title: 'v2.0') }
let!(:active_group_milestone) { create(:milestone, group: group, state: 'active', title: 'GL-113') } let_it_be(:active_group_milestone) { create(:milestone, group: group, state: 'active', title: 'GL-113') }
let!(:closed_group_milestone) { create(:milestone, group: group, state: 'closed') } let_it_be(:closed_group_milestone) { create(:milestone, group: group, state: 'closed') }
let!(:issue) do let_it_be(:issue) do
create :issue, project: project, assignees: [user], author: user, milestone: active_project_milestone1 create :issue, project: project, assignees: [user], author: user, milestone: active_project_milestone1
end end
...@@ -143,38 +143,111 @@ describe 'Group milestones' do ...@@ -143,38 +143,111 @@ describe 'Group milestones' do
expect(page).to have_content('Issues 1 Open: 1 Closed: 0') expect(page).to have_content('Issues 1 Open: 1 Closed: 0')
expect(page).to have_link(issue.title, href: project_issue_path(issue.project, issue)) expect(page).to have_link(issue.title, href: project_issue_path(issue.project, issue))
end end
end
end
describe 'milestone tabs', :js do
context 'for a legacy group milestone' do
let_it_be(:milestone) { create(:milestone, project: project) }
let_it_be(:label) { create(:label, project: project) }
let_it_be(:issue) { create(:labeled_issue, project: project, milestone: milestone, labels: [label], assignees: [create(:user)]) }
let_it_be(:mr) { create(:merge_request, source_project: project, milestone: milestone) }
before do
visit group_milestone_path(group, milestone.title, title: milestone.title)
end
it 'renders the issues tab' do
within('#tab-issues') do
expect(page).to have_content issue.title
end
end
it 'renders the merge requests tab' do
within('.js-milestone-tabs') do
click_link('Merge Requests')
end
describe 'labels' do within('#tab-merge-requests') do
before do expect(page).to have_content mr.title
create(:label, project: project, title: 'bug') do |label| end
issue.labels << label end
end
it 'renders the participants tab' do
within('.js-milestone-tabs') do
click_link('Participants')
end
create(:label, project: project, title: 'feature') do |label| within('#tab-participants') do
issue.labels << label expect(page).to have_content issue.assignees.first.name
end
end end
end
it 'renders labels' do it 'renders the labels tab' do
click_link 'v1.0' within('.js-milestone-tabs') do
click_link('Labels')
end
page.within('#tab-issues') do within('#tab-labels') do
expect(page).to have_content 'bug' expect(page).to have_content label.title
expect(page).to have_content 'feature'
end
end end
end
end
context 'for a group milestone' do
let_it_be(:other_project) { create(:project_empty_repo, group: group) }
let_it_be(:milestone) { create(:milestone, group: group) }
it 'renders labels list', :js do let_it_be(:project_label) { create(:label, project: project) }
click_link 'v1.0' let_it_be(:other_project_label) { create(:label, project: other_project) }
page.within('.content .nav-links') do let_it_be(:project_issue) { create(:labeled_issue, project: project, milestone: milestone, labels: [project_label], assignees: [create(:user)]) }
page.find(:xpath, "//a[@href='#tab-labels']").click let_it_be(:other_project_issue) { create(:labeled_issue, project: other_project, milestone: milestone, labels: [other_project_label], assignees: [create(:user)]) }
end
let_it_be(:project_mr) { create(:merge_request, source_project: project, milestone: milestone) }
let_it_be(:other_project_mr) { create(:merge_request, source_project: other_project, milestone: milestone) }
before do
visit group_milestone_path(group, milestone)
end
it 'renders the issues tab' do
within('#tab-issues') do
expect(page).to have_content project_issue.title
expect(page).to have_content other_project_issue.title
end
end
it 'renders the merge requests tab' do
within('.js-milestone-tabs') do
click_link('Merge Requests')
end
within('#tab-merge-requests') do
expect(page).to have_content project_mr.title
expect(page).to have_content other_project_mr.title
end
end
it 'renders the participants tab' do
within('.js-milestone-tabs') do
click_link('Participants')
end
within('#tab-participants') do
expect(page).to have_content project_issue.assignees.first.name
expect(page).to have_content other_project_issue.assignees.first.name
end
end
it 'renders the labels tab' do
within('.js-milestone-tabs') do
click_link('Labels')
end
page.within('#tab-labels') do within('#tab-labels') do
expect(page).to have_content 'bug' expect(page).to have_content project_label.title
expect(page).to have_content 'feature' expect(page).to have_content other_project_label.title
end
end end
end end
end end
......
...@@ -51,15 +51,16 @@ describe 'Project milestone' do ...@@ -51,15 +51,16 @@ describe 'Project milestone' do
context 'when project has disabled issues' do context 'when project has disabled issues' do
before do before do
create(:issue, project: project, milestone: milestone)
project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED) project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED)
visit project_milestone_path(project, milestone) visit project_milestone_path(project, milestone)
end end
it 'hides issues tab' do it 'does not show any issues under the issues tab' do
within('#content-body') do within('#content-body') do
expect(page).not_to have_link 'Issues', href: '#tab-issues' expect(find('.nav-links li a.active')).to have_content 'Issues'
expect(page).to have_selector '.nav-links li a.active', count: 1 expect(page).not_to have_selector '.issuable-row'
expect(find('.nav-links li a.active')).to have_content 'Merge Requests'
end end
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