Merge branch '30106-group-issues' into 'master'

View all issues of all subgroups on the group issue page

Closes #30106 and #39388

See merge request gitlab-org/gitlab-ce!16706
This commit is contained in:
Sean McGivern 2018-02-01 09:25:38 +00:00
commit 498d32363a
8 changed files with 198 additions and 49 deletions

View File

@ -94,6 +94,7 @@ module IssuableCollections
@filter_params[:project_id] = @project.id
elsif @group
@filter_params[:group_id] = @group.id
@filter_params[:include_subgroups] = true
else
# TODO: this filter ignore issues/mr created in public or
# internal repos where you are not a member. Enable this filter

View File

@ -87,8 +87,17 @@ class GroupProjectsFinder < ProjectsFinder
options.fetch(:only_shared, false)
end
# subgroups are supported only for owned projects not for shared
def include_subgroups?
options.fetch(:include_subgroups, false)
end
def owned_projects
group.projects
if include_subgroups?
Project.where(namespace_id: group.self_and_descendants.select(:id))
else
group.projects
end
end
def shared_projects

View File

@ -43,6 +43,7 @@ class IssuableFinder
search
sort
state
include_subgroups
].freeze
ARRAY_PARAMS = { label_name: [], iids: [], assignee_username: [] }.freeze
@ -148,7 +149,8 @@ class IssuableFinder
if current_user && params[:authorized_only].presence && !current_user_related?
current_user.authorized_projects
elsif group
GroupProjectsFinder.new(group: group, current_user: current_user).execute
finder_options = { include_subgroups: params[:include_subgroups], only_owned: true }
GroupProjectsFinder.new(group: group, current_user: current_user, options: finder_options).execute
else
ProjectsFinder.new(current_user: current_user, project_ids_relation: item_project_ids(items)).execute
end

View File

@ -0,0 +1,5 @@
---
title: Include subgroup issues and merge requests on the group page
merge_request:
author:
type: changed

View File

@ -3,40 +3,61 @@ require 'spec_helper'
feature 'Group issues page' do
include FilteredSearchHelpers
let(:path) { issues_group_path(group) }
let(:issuable) { create(:issue, project: project, title: "this is my created issuable")}
context 'with shared examples' do
let(:path) { issues_group_path(group) }
let(:issuable) { create(:issue, project: project, title: "this is my created issuable")}
include_examples 'project features apply to issuables', Issue
include_examples 'project features apply to issuables', Issue
context 'rss feed' do
let(:access_level) { ProjectFeature::ENABLED }
context 'rss feed' do
let(:access_level) { ProjectFeature::ENABLED }
context 'when signed in' do
let(:user) { user_in_group }
context 'when signed in' do
let(:user) { user_in_group }
it_behaves_like "it has an RSS button with current_user's RSS token"
it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
it_behaves_like "it has an RSS button with current_user's RSS token"
it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
end
context 'when signed out' do
let(:user) { nil }
it_behaves_like "it has an RSS button without an RSS token"
it_behaves_like "an autodiscoverable RSS feed without an RSS token"
end
end
context 'when signed out' do
let(:user) { nil }
context 'assignee', :js do
let(:access_level) { ProjectFeature::ENABLED }
let(:user) { user_in_group }
let(:user2) { user_outside_group }
let(:path) { issues_group_path(group) }
it_behaves_like "it has an RSS button without an RSS token"
it_behaves_like "an autodiscoverable RSS feed without an RSS token"
it 'filters by only group users' do
filtered_search.set('assignee:')
expect(find('#js-dropdown-assignee .filter-dropdown')).to have_content(user.name)
expect(find('#js-dropdown-assignee .filter-dropdown')).not_to have_content(user2.name)
end
end
end
context 'assignee', :js do
let(:access_level) { ProjectFeature::ENABLED }
let(:user) { user_in_group }
let(:user2) { user_outside_group }
let(:path) { issues_group_path(group) }
context 'issues list', :nested_groups do
let(:group) { create(:group)}
let(:subgroup) { create(:group, parent: group) }
let(:project) { create(:project, :public, group: group)}
let(:subgroup_project) { create(:project, :public, group: subgroup)}
let!(:issue) { create(:issue, project: project, title: 'root group issue') }
let!(:subgroup_issue) { create(:issue, project: subgroup_project, title: 'subgroup issue') }
it 'filters by only group users' do
filtered_search.set('assignee:')
it 'returns all group and subgroup issues' do
visit issues_group_path(group)
expect(find('#js-dropdown-assignee .filter-dropdown')).to have_content(user.name)
expect(find('#js-dropdown-assignee .filter-dropdown')).not_to have_content(user2.name)
page.within('.issuable-list') do
expect(page).to have_selector('li.issue', count: 2)
expect(page).to have_content('root group issue')
expect(page).to have_content('subgroup issue')
end
end
end
end

View File

@ -2,6 +2,7 @@ require 'spec_helper'
describe GroupProjectsFinder do
let(:group) { create(:group) }
let(:subgroup) { create(:group, parent: group) }
let(:current_user) { create(:user) }
let(:options) { {} }
@ -12,6 +13,8 @@ describe GroupProjectsFinder do
let!(:shared_project_1) { create(:project, :public, path: '3') }
let!(:shared_project_2) { create(:project, :private, path: '4') }
let!(:shared_project_3) { create(:project, :internal, path: '5') }
let!(:subgroup_project) { create(:project, :public, path: '6', group: subgroup) }
let!(:subgroup_private_project) { create(:project, :private, path: '7', group: subgroup) }
before do
shared_project_1.project_group_links.create(group_access: Gitlab::Access::MASTER, group: group)
@ -35,11 +38,31 @@ describe GroupProjectsFinder do
context "only owned" do
let(:options) { { only_owned: true } }
it { is_expected.to match_array([private_project, public_project]) }
context 'with subgroups projects', :nested_groups do
before do
options[:include_subgroups] = true
end
it { is_expected.to match_array([private_project, public_project, subgroup_project, subgroup_private_project]) }
end
context 'without subgroups projects' do
it { is_expected.to match_array([private_project, public_project]) }
end
end
context "all" do
it { is_expected.to match_array([shared_project_3, shared_project_2, shared_project_1, private_project, public_project]) }
context 'with subgroups projects', :nested_groups do
before do
options[:include_subgroups] = true
end
it { is_expected.to match_array([shared_project_3, shared_project_2, shared_project_1, private_project, public_project, subgroup_project, subgroup_private_project]) }
end
context 'without subgroups projects' do
it { is_expected.to match_array([shared_project_3, shared_project_2, shared_project_1, private_project, public_project]) }
end
end
end
@ -71,9 +94,20 @@ describe GroupProjectsFinder do
context "without external user" do
before do
private_project.add_master(current_user)
subgroup_private_project.add_master(current_user)
end
it { is_expected.to match_array([private_project, public_project]) }
context 'with subgroups projects', :nested_groups do
before do
options[:include_subgroups] = true
end
it { is_expected.to match_array([private_project, public_project, subgroup_project, subgroup_private_project]) }
end
context 'without subgroups projects' do
it { is_expected.to match_array([private_project, public_project]) }
end
end
context "with external user" do
@ -81,12 +115,32 @@ describe GroupProjectsFinder do
current_user.update_attributes(external: true)
end
it { is_expected.to eq([public_project]) }
context 'with subgroups projects', :nested_groups do
before do
options[:include_subgroups] = true
end
it { is_expected.to match_array([public_project, subgroup_project]) }
end
context 'without subgroups projects' do
it { is_expected.to eq([public_project]) }
end
end
end
context "all" do
it { is_expected.to match_array([shared_project_3, shared_project_2, shared_project_1, public_project]) }
context 'with subgroups projects', :nested_groups do
before do
options[:include_subgroups] = true
end
it { is_expected.to match_array([shared_project_3, shared_project_2, shared_project_1, public_project, subgroup_project]) }
end
context 'without subgroups projects' do
it { is_expected.to match_array([shared_project_3, shared_project_2, shared_project_1, public_project]) }
end
end
end
@ -100,7 +154,17 @@ describe GroupProjectsFinder do
context "only owned" do
let(:options) { { only_owned: true } }
it { is_expected.to eq([public_project]) }
context 'with subgroups projects', :nested_groups do
before do
options[:include_subgroups] = true
end
it { is_expected.to match_array([public_project, subgroup_project]) }
end
context 'without subgroups projects' do
it { is_expected.to eq([public_project]) }
end
end
end
end

View File

@ -3,13 +3,17 @@ require 'spec_helper'
describe IssuesFinder do
set(:user) { create(:user) }
set(:user2) { create(:user) }
set(:project1) { create(:project) }
set(:group) { create(:group) }
set(:subgroup) { create(:group, parent: group) }
set(:project1) { create(:project, group: group) }
set(:project2) { create(:project) }
set(:project3) { create(:project, group: subgroup) }
set(:milestone) { create(:milestone, project: project1) }
set(:label) { create(:label, project: project2) }
set(:issue1) { create(:issue, author: user, assignees: [user], project: project1, milestone: milestone, title: 'gitlab', created_at: 1.week.ago) }
set(:issue2) { create(:issue, author: user, assignees: [user], project: project2, description: 'gitlab') }
set(:issue3) { create(:issue, author: user2, assignees: [user2], project: project2, title: 'tanuki', description: 'tanuki', created_at: 1.week.from_now) }
set(:issue4) { create(:issue, project: project3) }
set(:award_emoji1) { create(:award_emoji, name: 'thumbsup', user: user, awardable: issue1) }
set(:award_emoji2) { create(:award_emoji, name: 'thumbsup', user: user2, awardable: issue2) }
set(:award_emoji3) { create(:award_emoji, name: 'thumbsdown', user: user, awardable: issue3) }
@ -25,10 +29,12 @@ describe IssuesFinder do
project1.add_master(user)
project2.add_developer(user)
project2.add_developer(user2)
project3.add_developer(user)
issue1
issue2
issue3
issue4
award_emoji1
award_emoji2
@ -39,7 +45,7 @@ describe IssuesFinder do
let(:scope) { 'all' }
it 'returns all issues' do
expect(issues).to contain_exactly(issue1, issue2, issue3)
expect(issues).to contain_exactly(issue1, issue2, issue3, issue4)
end
context 'filtering by assignee ID' do
@ -50,6 +56,26 @@ describe IssuesFinder do
end
end
context 'filtering by group_id' do
let(:params) { { group_id: group.id } }
context 'when include_subgroup param not set' do
it 'returns all group issues' do
expect(issues).to contain_exactly(issue1)
end
end
context 'when include_subgroup param is true', :nested_groups do
before do
params[:include_subgroups] = true
end
it 'returns all group and subgroup issues' do
expect(issues).to contain_exactly(issue1, issue4)
end
end
end
context 'filtering by author ID' do
let(:params) { { author_id: user2.id } }
@ -87,7 +113,7 @@ describe IssuesFinder do
let(:params) { { milestone_title: Milestone::None.title } }
it 'returns issues with no milestone' do
expect(issues).to contain_exactly(issue2, issue3)
expect(issues).to contain_exactly(issue2, issue3, issue4)
end
end
@ -185,7 +211,7 @@ describe IssuesFinder do
let(:params) { { label_name: Label::None.title } }
it 'returns issues with no labels' do
expect(issues).to contain_exactly(issue1, issue3)
expect(issues).to contain_exactly(issue1, issue3, issue4)
end
end
@ -210,7 +236,7 @@ describe IssuesFinder do
let(:params) { { state: 'opened' } }
it 'returns only opened issues' do
expect(issues).to contain_exactly(issue1, issue2, issue3)
expect(issues).to contain_exactly(issue1, issue2, issue3, issue4)
end
end
@ -226,7 +252,7 @@ describe IssuesFinder do
let(:params) { { state: 'all' } }
it 'returns all issues' do
expect(issues).to contain_exactly(issue1, issue2, issue3, closed_issue)
expect(issues).to contain_exactly(issue1, issue2, issue3, closed_issue, issue4)
end
end
@ -234,7 +260,7 @@ describe IssuesFinder do
let(:params) { { state: 'invalid_state' } }
it 'returns all issues' do
expect(issues).to contain_exactly(issue1, issue2, issue3, closed_issue)
expect(issues).to contain_exactly(issue1, issue2, issue3, closed_issue, issue4)
end
end
end
@ -338,7 +364,7 @@ describe IssuesFinder do
end
it "doesn't return issues if feature disabled" do
[project1, project2].each do |project|
[project1, project2, project3].each do |project|
project.project_feature.update!(issues_access_level: ProjectFeature::DISABLED)
end
@ -351,7 +377,7 @@ describe IssuesFinder do
it 'returns the number of rows for the default state' do
finder = described_class.new(user)
expect(finder.row_count).to eq(3)
expect(finder.row_count).to eq(4)
end
it 'returns the number of rows for a given state' do

View File

@ -6,31 +6,36 @@ describe MergeRequestsFinder do
let(:user) { create :user }
let(:user2) { create :user }
let(:project1) { create(:project, :public) }
let(:group) { create(:group) }
let(:subgroup) { create(:group, parent: group) }
let(:project1) { create(:project, :public, group: group) }
let(:project2) { fork_project(project1, user) }
let(:project3) do
p = fork_project(project1, user)
p.update!(archived: true)
p
end
let(:project4) { create(:project, :public, group: subgroup) }
let!(:merge_request1) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1) }
let!(:merge_request2) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1, state: 'closed') }
let!(:merge_request3) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project2) }
let!(:merge_request4) { create(:merge_request, :simple, author: user, source_project: project3, target_project: project3) }
let!(:merge_request5) { create(:merge_request, :simple, author: user, source_project: project4, target_project: project4) }
before do
project1.add_master(user)
project2.add_developer(user)
project3.add_developer(user)
project2.add_developer(user2)
project4.add_developer(user)
end
describe "#execute" do
it 'filters by scope' do
params = { scope: 'authored', state: 'opened' }
merge_requests = described_class.new(user, params).execute
expect(merge_requests.size).to eq(3)
expect(merge_requests.size).to eq(4)
end
it 'filters by project' do
@ -39,10 +44,26 @@ describe MergeRequestsFinder do
expect(merge_requests.size).to eq(1)
end
it 'filters by group' do
params = { group_id: group.id }
merge_requests = described_class.new(user, params).execute
expect(merge_requests.size).to eq(2)
end
it 'filters by group including subgroups', :nested_groups do
params = { group_id: group.id, include_subgroups: true }
merge_requests = described_class.new(user, params).execute
expect(merge_requests.size).to eq(3)
end
it 'filters by non_archived' do
params = { non_archived: true }
merge_requests = described_class.new(user, params).execute
expect(merge_requests.size).to eq(3)
expect(merge_requests.size).to eq(4)
end
it 'filters by iid' do
@ -73,14 +94,14 @@ describe MergeRequestsFinder do
end
context 'with created_after and created_before params' do
let(:project4) { create(:project, forked_from_project: project1) }
let(:new_project) { create(:project, forked_from_project: project1) }
let!(:new_merge_request) do
create(:merge_request,
:simple,
author: user,
created_at: 1.week.from_now,
source_project: project4,
source_project: new_project,
target_project: project1)
end
@ -89,12 +110,12 @@ describe MergeRequestsFinder do
:simple,
author: user,
created_at: 1.week.ago,
source_project: project4,
target_project: project4)
source_project: new_project,
target_project: new_project)
end
before do
project4.add_master(user)
new_project.add_master(user)
end
it 'filters by created_after' do
@ -106,7 +127,7 @@ describe MergeRequestsFinder do
end
it 'filters by created_before' do
params = { project_id: project4.id, created_before: old_merge_request.created_at + 1.second }
params = { project_id: new_project.id, created_before: old_merge_request.created_at + 1.second }
merge_requests = described_class.new(user, params).execute
@ -119,7 +140,7 @@ describe MergeRequestsFinder do
it 'returns the number of rows for the default state' do
finder = described_class.new(user)
expect(finder.row_count).to eq(3)
expect(finder.row_count).to eq(4)
end
it 'returns the number of rows for a given state' do