Merge branch 'issue_44270' into 'master'

Show issues of subgroups in group-level issue board

Closes #44270

See merge request gitlab-org/gitlab-ce!18187
This commit is contained in:
Sean McGivern 2018-04-06 09:20:01 +00:00
commit ca330f7ea3
24 changed files with 75 additions and 55 deletions

View File

@ -60,10 +60,6 @@ gl.issueBoards.BoardSidebar = Vue.extend({
this.issue = this.detail.issue;
this.list = this.detail.list;
this.$nextTick(() => {
this.endpoint = this.$refs.assigneeDropdown.dataset.issueUpdate;
});
},
deep: true
},
@ -91,7 +87,7 @@ gl.issueBoards.BoardSidebar = Vue.extend({
saveAssignees () {
this.loadingAssignees = true;
gl.issueBoards.BoardsStore.detail.issue.update(this.endpoint)
gl.issueBoards.BoardsStore.detail.issue.update()
.then(() => {
this.loadingAssignees = false;
})

View File

@ -68,15 +68,6 @@ gl.issueBoards.IssueCardInner = Vue.extend({
return this.issue.assignees.length > this.numberOverLimit;
},
cardUrl() {
let baseUrl = this.issueLinkBase;
if (this.groupId && this.issue.project) {
baseUrl = this.issueLinkBase.replace(':project_path', this.issue.project.path);
}
return `${baseUrl}/${this.issue.iid}`;
},
issueId() {
if (this.issue.iid) {
return `#${this.issue.iid}`;
@ -153,13 +144,13 @@ gl.issueBoards.IssueCardInner = Vue.extend({
/>
<a
class="js-no-trigger"
:href="cardUrl"
:href="issue.path"
:title="issue.title">{{ issue.title }}</a>
<span
class="card-number"
v-if="issueId"
>
<template v-if="groupId && issue.project">{{issue.project.path}}</template>{{ issueId }}
{{ issue.referencePath }}
</span>
</h4>
<div class="card-assignee">

View File

@ -17,14 +17,10 @@ gl.issueBoards.RemoveIssueBtn = Vue.extend({
type: Object,
required: true,
},
issueUpdate: {
type: String,
required: true,
},
},
computed: {
updateUrl() {
return this.issueUpdate.replace(':project_path', this.issue.project.path);
return this.issue.path;
},
},
methods: {

View File

@ -6,6 +6,7 @@ export default class FilteredSearchBoards extends FilteredSearchManager {
constructor(store, updateUrl = false, cantEdit = []) {
super({
page: 'boards',
isGroupDecendent: true,
stateFiltersSelector: '.issues-state-filters',
});

View File

@ -23,6 +23,8 @@ class ListIssue {
};
this.isLoading = {};
this.sidebarInfoEndpoint = obj.issue_sidebar_endpoint;
this.referencePath = obj.reference_path;
this.path = obj.real_path;
this.toggleSubscriptionEndpoint = obj.toggle_subscription_endpoint;
this.milestone_id = obj.milestone_id;
this.project_id = obj.project_id;
@ -98,7 +100,7 @@ class ListIssue {
this.isLoading[key] = value;
}
update (url) {
update () {
const data = {
issue: {
milestone_id: this.milestone ? this.milestone.id : null,
@ -113,7 +115,7 @@ class ListIssue {
}
const projectPath = this.project ? this.project.path : '';
return Vue.http.patch(url.replace(':project_path', projectPath), data);
return Vue.http.patch(`${this.path}.json`, data);
}
}

View File

@ -96,7 +96,8 @@ module Boards
resource.as_json(
only: [:id, :iid, :project_id, :title, :confidential, :due_date, :relative_position],
labels: true,
sidebar_endpoints: true,
issue_endpoints: true,
include_full_project_path: board.group_board?,
include: {
project: { only: [:id, :path] },
assignees: { only: [:id, :name, :username], methods: [:avatar_url] },

View File

@ -12,7 +12,7 @@ class Groups::MilestonesController < Groups::ApplicationController
@milestones = Kaminari.paginate_array(milestones).page(params[:page])
end
format.json do
render json: milestones.map { |m| m.for_display.slice(:title, :name) }
render json: milestones.map { |m| m.for_display.slice(:id, :title, :name) }
end
end
end

View File

@ -272,11 +272,17 @@ class Issue < ActiveRecord::Base
def as_json(options = {})
super(options).tap do |json|
if options.key?(:sidebar_endpoints) && project
if options.key?(:issue_endpoints) && project
url_helper = Gitlab::Routing.url_helpers
json.merge!(issue_sidebar_endpoint: url_helper.project_issue_path(project, self, format: :json, serializer: 'sidebar'),
toggle_subscription_endpoint: url_helper.toggle_subscription_project_issue_path(project, self))
issue_reference = options[:include_full_project_path] ? to_reference(full: true) : to_reference
json.merge!(
reference_path: issue_reference,
real_path: url_helper.project_issue_path(project, self),
issue_sidebar_endpoint: url_helper.project_issue_path(project, self, format: :json, serializer: 'sidebar'),
toggle_subscription_endpoint: url_helper.toggle_subscription_project_issue_path(project, self)
)
end
if options.key?(:labels)

View File

@ -34,8 +34,8 @@ class Milestone < ActiveRecord::Base
scope :for_projects_and_groups, -> (project_ids, group_ids) do
conditions = []
conditions << arel_table[:project_id].in(project_ids) if project_ids.compact.any?
conditions << arel_table[:group_id].in(group_ids) if group_ids.compact.any?
conditions << arel_table[:project_id].in(project_ids) if project_ids&.compact&.any?
conditions << arel_table[:group_id].in(group_ids) if group_ids&.compact&.any?
where(conditions.reduce(:or))
end

View File

@ -35,6 +35,7 @@ module Boards
def filter_params
set_parent
set_state
set_scope
params
end
@ -51,6 +52,10 @@ module Boards
params[:state] = list && list.closed? ? 'closed' : 'opened'
end
def set_scope
params[:include_subgroups] = board.group_board?
end
def board_label_ids
@board_label_ids ||= board.lists.movable.pluck(:label_id)
end

View File

@ -51,9 +51,10 @@ class IssuableBaseService < BaseService
return unless milestone_id
params[:milestone_id] = '' if milestone_id == IssuableFinder::NONE
group_ids = project.group&.self_and_ancestors&.pluck(:id)
milestone =
Milestone.for_projects_and_groups([project.id], [project.group&.id]).find_by_id(milestone_id)
Milestone.for_projects_and_groups([project.id], group_ids).find_by_id(milestone_id)
params[:milestone_id] = '' unless milestone
end

View File

@ -90,7 +90,7 @@ module Issues
issue =
if board_group_id
IssuesFinder.new(current_user, group_id: board_group_id).find_by(id: id)
IssuesFinder.new(current_user, group_id: board_group_id, include_subgroups: true).find_by(id: id)
else
project.issues.find(id)
end

View File

@ -22,6 +22,6 @@
= render "shared/boards/components/sidebar/labels"
= render "shared/boards/components/sidebar/notifications"
%remove-btn{ ":issue" => "issue",
":issue-update" => "'#{build_issue_link_base}/' + issue.iid + '.json'",
":issue-update" => "issue.sidebarInfoEndpoint",
":list" => "list",
"v-if" => "canRemove" }

View File

@ -21,8 +21,7 @@
.dropdown
- dropdown_options = issue_assignees_dropdown_options
%button.dropdown-menu-toggle.js-user-search.js-author-search.js-multiselect.js-save-user-data.js-issue-board-sidebar{ type: 'button', ref: 'assigneeDropdown', data: board_sidebar_user_data,
":data-issuable-id" => "issue.iid",
":data-issue-update" => "'#{build_issue_link_base}/' + issue.iid + '.json'" }
":data-issuable-id" => "issue.iid" }
= dropdown_options[:title]
= icon("chevron-down")
.dropdown-menu.dropdown-select.dropdown-menu-user.dropdown-menu-selectable.dropdown-menu-author

View File

@ -22,8 +22,7 @@
":value" => "issue.dueDate" }
.dropdown
%button.dropdown-menu-toggle.js-due-date-select.js-issue-boards-due-date{ type: 'button',
data: { toggle: 'dropdown', field_name: "issue[due_date]", ability_name: "issue" },
":data-issue-update" => "'#{build_issue_link_base}/' + issue.iid + '.json'" }
data: { toggle: 'dropdown', field_name: "issue[due_date]", ability_name: "issue" } }
%span.dropdown-toggle-text Due date
= icon('chevron-down')
.dropdown-menu.dropdown-menu-due-date

View File

@ -26,8 +26,7 @@
project_id: @project&.try(:id),
labels: labels_filter_path(false),
namespace_path: @namespace_path,
project_path: @project.try(:path) },
":data-issue-update" => "'#{build_issue_link_base}/' + issue.iid + '.json'" }
project_path: @project.try(:path) } }
%span.dropdown-toggle-text
Label
= icon('chevron-down')

View File

@ -18,8 +18,7 @@
.dropdown
%button.dropdown-menu-toggle.js-milestone-select.js-issue-board-sidebar{ type: "button", data: { toggle: "dropdown", show_no: "true", field_name: "issue[milestone_id]", milestones: milestones_filter_path(format: :json), ability_name: "issue", use_id: "true", default_no: "true" },
":data-selected" => "milestoneTitle",
":data-issuable-id" => "issue.iid",
":data-issue-update" => "'#{build_issue_link_base}/' + issue.iid + '.json'" }
":data-issuable-id" => "issue.iid" }
Milestone
= icon("chevron-down")
.dropdown-menu.dropdown-select.dropdown-menu-selectable

View File

@ -0,0 +1,5 @@
---
title: Show issues of subgroups in group-level issue board
merge_request:
author:
type: changed

View File

@ -240,8 +240,7 @@ Issue Board, that is create/delete lists and drag issues around.
>Introduced in GitLab 10.6
Group issue board is analogous to project-level issue board and it is accessible at the group
navigation level. A group-level issue board allows you to view all issues from all projects in that group
(currently, it does not see issues from projects in subgroups). Similarly, you can only filter by group labels for these
navigation level. A group-level issue board allows you to view all issues from all projects in that group or descendant subgroups. Similarly, you can only filter by group labels for these
boards. When updating milestones and labels for an issue through the sidebar update mechanism, again only
group-level objects are available.

View File

@ -115,17 +115,17 @@ feature 'Labels Hierarchy', :js, :nested_groups do
it 'filters by descendant group labels' do
wait_for_requests
if board
pending("Waiting for https://gitlab.com/gitlab-org/gitlab-ce/issues/44270")
select_label_on_dropdown(group_label_3.title)
select_label_on_dropdown(group_label_3.title)
if board
expect(page).to have_selector('.card-title') do |card|
expect(card).not_to have_selector('a', text: labeled_issue_2.title)
end
expect(page).to have_selector('.card-title') do |card|
expect(card).to have_selector('a', text: labeled_issue_3.title)
end
else
select_label_on_dropdown(group_label_3.title)
expect_issues_list_count(1)
expect(page).to have_selector('span.issue-title-text', text: labeled_issue_3.title)
end

View File

@ -15,6 +15,8 @@
"relative_position": { "type": "integer" },
"issue_sidebar_endpoint": { "type": "string" },
"toggle_subscription_endpoint": { "type": "string" },
"reference_path": { "type": "string" },
"real_path": { "type": "string" },
"project": {
"id": { "type": "integer" },
"path": { "type": "string" }

View File

@ -41,6 +41,8 @@ describe('Issue card component', () => {
confidential: false,
labels: [list.label],
assignees: [],
reference_path: '#1',
real_path: '/test/1',
});
component = new Vue({

View File

@ -48,10 +48,8 @@ describe Boards::Issues::ListService do
context 'when parent is a group' do
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:project) { create(:project, :empty_repo, namespace: group) }
let(:project1) { create(:project, :empty_repo, namespace: group) }
let(:board) { create(:board, group: group) }
let(:m1) { create(:milestone, group: group) }
let(:m2) { create(:milestone, group: group) }
@ -92,13 +90,30 @@ describe Boards::Issues::ListService do
let!(:closed_issue4) { create(:labeled_issue, :closed, project: project1, labels: [p1, p1_project1]) }
let!(:closed_issue5) { create(:labeled_issue, :closed, project: project1, labels: [development]) }
let(:parent) { group }
before do
group.add_developer(user)
end
it_behaves_like 'issues list service'
context 'and group has no parent' do
let(:parent) { group }
let(:group) { create(:group) }
let(:board) { create(:board, group: group) }
it_behaves_like 'issues list service'
end
context 'and group is an ancestor', :nested_groups do
let(:parent) { create(:group) }
let(:group) { create(:group, parent: parent) }
let!(:backlog) { create(:backlog_list, board: board) }
let(:board) { create(:board, group: parent) }
before do
parent.add_developer(user)
end
it_behaves_like 'issues list service'
end
end
end
end

View File

@ -97,11 +97,13 @@ describe Issues::UpdateService, :mailer do
expect(issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position)
end
context 'when moving issue between issues from different projects' do
context 'when moving issue between issues from different projects', :nested_groups do
let(:group) { create(:group) }
let(:subgroup) { create(:group, parent: group) }
let(:project_1) { create(:project, namespace: group) }
let(:project_2) { create(:project, namespace: group) }
let(:project_3) { create(:project, namespace: group) }
let(:project_3) { create(:project, namespace: subgroup) }
let(:issue_1) { create(:issue, project: project_1) }
let(:issue_2) { create(:issue, project: project_2) }