Commit 9e2184bd authored by Sean McGivern's avatar Sean McGivern

Merge branch 'ee-issue_44270' into 'master'

EE-PORT Show issues of subgroups in group-level issue board

See merge request gitlab-org/gitlab-ee!5234
parents 9fb979f1 3336800d
......@@ -61,10 +61,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
},
......@@ -92,7 +88,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;
})
......
......@@ -70,15 +70,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}`;
......@@ -155,13 +146,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>
<issue-card-weight
v-if="issue.weight"
......
......@@ -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: {
......
......@@ -8,6 +8,7 @@ export default class FilteredSearchBoards extends FilteredSearchManager {
super({
page: 'boards',
isGroup: true,
isGroupDecendent: true,
filteredSearchTokenKeys: FilteredSearchTokenKeysIssues,
stateFiltersSelector: '.issues-state-filters',
});
......
......@@ -26,6 +26,8 @@ class ListIssue {
weight: false,
};
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;
......@@ -102,7 +104,7 @@ class ListIssue {
this.isLoading[key] = value;
}
update (url) {
update () {
const data = {
issue: {
milestone_id: this.milestone ? this.milestone.id : null,
......@@ -117,7 +119,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);
}
}
......
......@@ -96,7 +96,8 @@ module Boards
resource.as_json(
only: [:id, :iid, :project_id, :title, :confidential, :due_date, :relative_position, :weight],
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] },
......
......@@ -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, :id) }
render json: milestones.map { |m| m.for_display.slice(:id, :title, :name) }
end
end
end
......
......@@ -296,11 +296,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)
......
......@@ -38,8 +38,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
......
......@@ -37,6 +37,8 @@ module Boards
def filter_params
set_parent
set_state
set_scope
params
end
......@@ -52,6 +54,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
......
......@@ -53,9 +53,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
......
......@@ -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
......
......@@ -26,6 +26,6 @@
= render "shared/boards/components/sidebar/weight"
= 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" }
......@@ -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
......
......@@ -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
......
......@@ -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')
......
......@@ -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
......
---
title: Show issues of subgroups in group-level issue board
merge_request:
author:
type: changed
......@@ -334,8 +334,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.
......
......@@ -38,7 +38,7 @@
Delete board
%board-form{ ":milestone-path" => "milestonePath",
"labels-path" => labels_filter_path(true),
"labels-path" => labels_filter_path(true, include_descendant_groups: true),
":project-id" => "Number(#{@project&.id})",
":group-id" => "Number(#{@group&.id})",
":can-admin-board" => can?(current_user, :admin_board, parent),
......
require 'spec_helper'
feature 'Labels Hierarchy', :js, :nested_groups do
let!(:user) { create(:user) }
let!(:grandparent) { create(:group) }
let!(:parent) { create(:group, parent: grandparent) }
let!(:child) { create(:group, parent: parent) }
let!(:project_1) { create(:project, namespace: child) }
let!(:grandparent_group_label) { create(:group_label, group: grandparent, title: 'Label_1') }
let!(:parent_group_label) { create(:group_label, group: parent, title: 'Label_2') }
let!(:child_group_label) { create(:group_label, group: child, title: 'Label_3') }
let!(:project_label_1) { create(:label, project: project_1, title: 'Label_4') }
let!(:labeled_issue_1) { create(:labeled_issue, project: project_1, labels: [grandparent_group_label, parent_group_label, child_group_label]) }
let!(:labeled_issue_2) { create(:labeled_issue, project: project_1, labels: [grandparent_group_label, parent_group_label]) }
let!(:labeled_issue_3) { create(:labeled_issue, project: project_1, labels: [grandparent_group_label, parent_group_label, child_group_label, project_label_1]) }
let!(:not_labeled) { create(:issue, project: project_1) }
before do
grandparent.add_owner(user)
sign_in(user)
end
shared_examples 'filter for scoped boards' do |project = false|
it 'scopes board to ancestor and descendant labels' do
labels = [grandparent_group_label, parent_group_label, child_group_label]
labels.push(project_label_1) if project
labels.each do |label|
page.within('.filter-dropdown-container') do
find('button', text: "Edit board").click
end
page.within('.js-labels-block') do
find('.edit-link').click
end
wait_for_requests
find('a.label-item', text: label.title).click
find('button', text: "Save changes").click
wait_for_requests
expect(page).to have_selector('.card', count: label.issues.count)
label.issues.each do |issue|
expect(page).to have_selector('.card-title') do |card|
expect(card).to have_selector('a', text: issue.title)
end
end
end
end
end
context 'scoped boards' do
context 'for group boards' do
let(:board) { create(:board, group: parent) }
before do
visit group_board_path(parent, board)
wait_for_requests
end
it_behaves_like 'filter for scoped boards'
end
context 'for project boards' do
let(:board) { create(:board, project: project_1) }
before do
visit project_board_path(project_1, board)
wait_for_requests
end
it_behaves_like 'filter for scoped boards', true
end
end
end
......@@ -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
......
......@@ -16,6 +16,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" }
......
......@@ -42,6 +42,8 @@ describe('Issue card component', () => {
confidential: false,
labels: [list.label],
assignees: [],
reference_path: '#1',
real_path: '/test/1',
});
component = new Vue({
......
......@@ -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
......@@ -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) }
......
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