summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/boards/components/board_sidebar.js6
-rw-r--r--app/assets/javascripts/boards/components/issue_card_inner.js13
-rw-r--r--app/assets/javascripts/boards/components/sidebar/remove_issue.js6
-rw-r--r--app/assets/javascripts/boards/filtered_search_boards.js1
-rw-r--r--app/assets/javascripts/boards/models/issue.js6
-rw-r--r--app/controllers/boards/issues_controller.rb3
-rw-r--r--app/controllers/groups/milestones_controller.rb2
-rw-r--r--app/models/issue.rb12
-rw-r--r--app/models/milestone.rb4
-rw-r--r--app/services/boards/issues/list_service.rb5
-rw-r--r--app/services/issuable_base_service.rb3
-rw-r--r--app/services/issues/update_service.rb2
-rw-r--r--app/views/shared/boards/components/_sidebar.html.haml2
-rw-r--r--app/views/shared/boards/components/sidebar/_assignee.html.haml3
-rw-r--r--app/views/shared/boards/components/sidebar/_due_date.html.haml3
-rw-r--r--app/views/shared/boards/components/sidebar/_labels.html.haml3
-rw-r--r--app/views/shared/boards/components/sidebar/_milestone.html.haml3
-rw-r--r--changelogs/unreleased/issue_44270.yml5
-rw-r--r--doc/user/project/issue_board.md3
-rw-r--r--spec/features/labels_hierarchy_spec.rb10
-rw-r--r--spec/fixtures/api/schemas/issue.json2
-rw-r--r--spec/javascripts/boards/issue_card_spec.js2
-rw-r--r--spec/services/boards/issues/list_service_spec.rb25
-rw-r--r--spec/services/issues/update_service_spec.rb6
24 files changed, 75 insertions, 55 deletions
diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js
index a44969272a1..c4ee4f6c855 100644
--- a/app/assets/javascripts/boards/components/board_sidebar.js
+++ b/app/assets/javascripts/boards/components/board_sidebar.js
@@ -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;
})
diff --git a/app/assets/javascripts/boards/components/issue_card_inner.js b/app/assets/javascripts/boards/components/issue_card_inner.js
index 8aee5b23c76..84fe9b1288a 100644
--- a/app/assets/javascripts/boards/components/issue_card_inner.js
+++ b/app/assets/javascripts/boards/components/issue_card_inner.js
@@ -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">
diff --git a/app/assets/javascripts/boards/components/sidebar/remove_issue.js b/app/assets/javascripts/boards/components/sidebar/remove_issue.js
index 09c683ff621..0a0820ec5fd 100644
--- a/app/assets/javascripts/boards/components/sidebar/remove_issue.js
+++ b/app/assets/javascripts/boards/components/sidebar/remove_issue.js
@@ -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: {
diff --git a/app/assets/javascripts/boards/filtered_search_boards.js b/app/assets/javascripts/boards/filtered_search_boards.js
index fb40b9f5565..70367c4f711 100644
--- a/app/assets/javascripts/boards/filtered_search_boards.js
+++ b/app/assets/javascripts/boards/filtered_search_boards.js
@@ -6,6 +6,7 @@ export default class FilteredSearchBoards extends FilteredSearchManager {
constructor(store, updateUrl = false, cantEdit = []) {
super({
page: 'boards',
+ isGroupDecendent: true,
stateFiltersSelector: '.issues-state-filters',
});
diff --git a/app/assets/javascripts/boards/models/issue.js b/app/assets/javascripts/boards/models/issue.js
index 4c5079efc8b..b381d48d625 100644
--- a/app/assets/javascripts/boards/models/issue.js
+++ b/app/assets/javascripts/boards/models/issue.js
@@ -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);
}
}
diff --git a/app/controllers/boards/issues_controller.rb b/app/controllers/boards/issues_controller.rb
index 19dbee84c11..7d7ff217e5d 100644
--- a/app/controllers/boards/issues_controller.rb
+++ b/app/controllers/boards/issues_controller.rb
@@ -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] },
diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb
index acf6aaf57f4..5903689dc62 100644
--- a/app/controllers/groups/milestones_controller.rb
+++ b/app/controllers/groups/milestones_controller.rb
@@ -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
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 13abc6c1a0d..13d9e42bcc8 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -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)
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index dafae58d121..a66a0015827 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -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
diff --git a/app/services/boards/issues/list_service.rb b/app/services/boards/issues/list_service.rb
index ecd74b74f8a..ac70a99c2c5 100644
--- a/app/services/boards/issues/list_service.rb
+++ b/app/services/boards/issues/list_service.rb
@@ -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
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index 91ec702fbc6..1f67e3ecf9d 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -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
diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb
index 4161932ad2a..1374f10c586 100644
--- a/app/services/issues/update_service.rb
+++ b/app/services/issues/update_service.rb
@@ -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
diff --git a/app/views/shared/boards/components/_sidebar.html.haml b/app/views/shared/boards/components/_sidebar.html.haml
index 8e5e32e9f16..b385cc3f962 100644
--- a/app/views/shared/boards/components/_sidebar.html.haml
+++ b/app/views/shared/boards/components/_sidebar.html.haml
@@ -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" }
diff --git a/app/views/shared/boards/components/sidebar/_assignee.html.haml b/app/views/shared/boards/components/sidebar/_assignee.html.haml
index 3d2e8471a60..1374da9d82c 100644
--- a/app/views/shared/boards/components/sidebar/_assignee.html.haml
+++ b/app/views/shared/boards/components/sidebar/_assignee.html.haml
@@ -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
diff --git a/app/views/shared/boards/components/sidebar/_due_date.html.haml b/app/views/shared/boards/components/sidebar/_due_date.html.haml
index db794d6f855..d13b998e6f4 100644
--- a/app/views/shared/boards/components/sidebar/_due_date.html.haml
+++ b/app/views/shared/boards/components/sidebar/_due_date.html.haml
@@ -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
diff --git a/app/views/shared/boards/components/sidebar/_labels.html.haml b/app/views/shared/boards/components/sidebar/_labels.html.haml
index dfc0f9be321..87e6b52f46e 100644
--- a/app/views/shared/boards/components/sidebar/_labels.html.haml
+++ b/app/views/shared/boards/components/sidebar/_labels.html.haml
@@ -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')
diff --git a/app/views/shared/boards/components/sidebar/_milestone.html.haml b/app/views/shared/boards/components/sidebar/_milestone.html.haml
index d09c7c218e0..f51c4a97f2b 100644
--- a/app/views/shared/boards/components/sidebar/_milestone.html.haml
+++ b/app/views/shared/boards/components/sidebar/_milestone.html.haml
@@ -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
diff --git a/changelogs/unreleased/issue_44270.yml b/changelogs/unreleased/issue_44270.yml
new file mode 100644
index 00000000000..6234162be30
--- /dev/null
+++ b/changelogs/unreleased/issue_44270.yml
@@ -0,0 +1,5 @@
+---
+title: Show issues of subgroups in group-level issue board
+merge_request:
+author:
+type: changed
diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md
index b4a842f33d6..7eab825fa32 100644
--- a/doc/user/project/issue_board.md
+++ b/doc/user/project/issue_board.md
@@ -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.
diff --git a/spec/features/labels_hierarchy_spec.rb b/spec/features/labels_hierarchy_spec.rb
index 99e1fb30d5b..3e05e7b7f38 100644
--- a/spec/features/labels_hierarchy_spec.rb
+++ b/spec/features/labels_hierarchy_spec.rb
@@ -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
diff --git a/spec/fixtures/api/schemas/issue.json b/spec/fixtures/api/schemas/issue.json
index b579e32c9aa..8833825e3fb 100644
--- a/spec/fixtures/api/schemas/issue.json
+++ b/spec/fixtures/api/schemas/issue.json
@@ -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" }
diff --git a/spec/javascripts/boards/issue_card_spec.js b/spec/javascripts/boards/issue_card_spec.js
index 37088a6421c..be1ea0b57b4 100644
--- a/spec/javascripts/boards/issue_card_spec.js
+++ b/spec/javascripts/boards/issue_card_spec.js
@@ -41,6 +41,8 @@ describe('Issue card component', () => {
confidential: false,
labels: [list.label],
assignees: [],
+ reference_path: '#1',
+ real_path: '/test/1',
});
component = new Vue({
diff --git a/spec/services/boards/issues/list_service_spec.rb b/spec/services/boards/issues/list_service_spec.rb
index b4efa3e44b6..27a7bf0e605 100644
--- a/spec/services/boards/issues/list_service_spec.rb
+++ b/spec/services/boards/issues/list_service_spec.rb
@@ -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
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index f95474208f3..23b1134b5a3 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -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) }