diff --git a/app/assets/javascripts/work_items/graphql/list/get_work_item_state_counts.query.graphql b/app/assets/javascripts/work_items/graphql/list/get_work_item_state_counts.query.graphql index fdb4b9d00f557e94c711888faa997c876fed0893..0f842a4777a6b1980f702f7efad22a11afed79a8 100644 --- a/app/assets/javascripts/work_items/graphql/list/get_work_item_state_counts.query.graphql +++ b/app/assets/javascripts/work_items/graphql/list/get_work_item_state_counts.query.graphql @@ -1,5 +1,7 @@ query getWorkItemStateCounts( + $excludeProjects: Boolean = false $includeDescendants: Boolean = true + $isGroup: Boolean = true $fullPath: ID! $search: String $sort: WorkItemSort @@ -16,11 +18,11 @@ query getWorkItemStateCounts( $in: [IssuableSearchableField!] $not: NegatedWorkItemFilterInput $or: UnionedWorkItemFilterInput - $isGroup: Boolean = true ) { group(fullPath: $fullPath) @include(if: $isGroup) { id workItemStateCounts( + excludeProjects: $excludeProjects includeDescendants: $includeDescendants search: $search sort: $sort diff --git a/app/assets/javascripts/work_items/graphql/list/get_work_items.query.graphql b/app/assets/javascripts/work_items/graphql/list/get_work_items.query.graphql index 5a76a0f8c0a721e9d18576d15dc3da30205f2192..1f2b0f98c5d823f6c26bb8bcc5b800c24b1ffe03 100644 --- a/app/assets/javascripts/work_items/graphql/list/get_work_items.query.graphql +++ b/app/assets/javascripts/work_items/graphql/list/get_work_items.query.graphql @@ -2,6 +2,9 @@ #import "ee_else_ce/work_items/graphql/list/work_item_widgets.fragment.graphql" query getWorkItems( + $excludeProjects: Boolean = false + $includeDescendants: Boolean = true + $isGroup: Boolean = true $fullPath: ID! $search: String $sort: WorkItemSort @@ -22,15 +25,13 @@ query getWorkItems( $beforeCursor: String $firstPageSize: Int $lastPageSize: Int - $isGroup: Boolean = true - $excludeProjects: Boolean ) { group(fullPath: $fullPath) @include(if: $isGroup) { id name workItems( excludeProjects: $excludeProjects - includeDescendants: true + includeDescendants: $includeDescendants search: $search sort: $sort state: $state diff --git a/app/assets/javascripts/work_items/pages/work_items_list_app.vue b/app/assets/javascripts/work_items/pages/work_items_list_app.vue index 4e5a5e2e7eaec9832154d3851a3a9fbb1d48ce45..8e703bdf75461bed419a26d640809f1f6eca48ad 100644 --- a/app/assets/javascripts/work_items/pages/work_items_list_app.vue +++ b/app/assets/javascripts/work_items/pages/work_items_list_app.vue @@ -260,6 +260,7 @@ export default { return this.isGroup ? WORKSPACE_GROUP : WORKSPACE_PROJECT; }, queryVariables() { + const hasGroupFilter = Boolean(this.urlFilterParams.group_path); return { fullPath: this.fullPath, sort: this.sortKey, @@ -267,8 +268,8 @@ export default { search: this.searchQuery, ...this.apiFilterParams, ...this.pageParams, - excludeProjects: this.isEpicsList, - includeDescendants: !this.apiFilterParams.fullPath, + excludeProjects: hasGroupFilter || this.isEpicsList, + includeDescendants: !hasGroupFilter, types: this.apiFilterParams.types || this.workItemType || this.defaultWorkItemTypes, isGroup: this.isGroup, }; diff --git a/ee/spec/features/work_items/work_items_list_filters_spec.rb b/ee/spec/features/work_items/work_items_list_filters_spec.rb index a54284edbeda946ddc1343a3f6ecb9a9df5d008b..67e5854b2fb58bd209cfa375bd96376eaf6a69e2 100644 --- a/ee/spec/features/work_items/work_items_list_filters_spec.rb +++ b/ee/spec/features/work_items/work_items_list_filters_spec.rb @@ -9,8 +9,16 @@ let_it_be(:user) { create(:user) } let_it_be(:group) { create(:group) } + let_it_be(:sub_group) { create(:group, parent: group) } + let_it_be(:sub_group_project) { create(:project, :public, group: sub_group, developers: user) } + let_it_be(:sub_sub_group) { create(:group, parent: sub_group) } let_it_be(:project) { create(:project, :public, group: group, developers: user) } + let_it_be(:epic) { create(:work_item, :epic_with_legacy_epic, namespace: group) } + let_it_be(:sub_epic) { create(:work_item, :epic_with_legacy_epic, namespace: sub_group) } + let_it_be(:sub_issue) { create(:issue, project: sub_group_project) } + let_it_be(:sub_sub_epic) { create(:work_item, :epic_with_legacy_epic, namespace: sub_sub_group) } + let_it_be(:incident) { create(:incident, project: project) } let_it_be(:issue) { create(:issue, project: project) } let_it_be(:task) { create(:work_item, :task, project: project) } @@ -18,19 +26,43 @@ context 'for signed in user' do before do - stub_licensed_features(quality_management: true) + stub_licensed_features(epics: true, quality_management: true, subepics: true) sign_in(user) visit group_work_items_path(group) close_work_item_feedback_popover_if_present end + describe 'group' do + it 'filters', :aggregate_failures do + select_tokens 'Group', group.name, submit: true + + expect(page).to have_css('.issue', count: 1) + expect(page).to have_link(epic.title) + + click_button 'Clear' + + select_tokens 'Group', sub_group.name, submit: true + + expect(page).to have_css('.issue', count: 1) + expect(page).to have_link(sub_epic.title) + + click_button 'Clear' + + select_tokens 'Group', sub_sub_group.name, submit: true + + expect(page).to have_css('.issue', count: 1) + expect(page).to have_link(sub_sub_epic.title) + end + end + describe 'type' do it 'filters', :aggregate_failures do select_tokens 'Type', 'Issue', submit: true - expect(page).to have_css('.issue', count: 1) + expect(page).to have_css('.issue', count: 2) expect(page).to have_link(issue.title) + expect(page).to have_link(sub_issue.title) click_button 'Clear' diff --git a/spec/features/work_items/work_items_list_filters_spec.rb b/spec/features/work_items/work_items_list_filters_spec.rb index 262826a6c21b662b8b7bbacfbb01a099c0d0b659..383556b6518e0826bd707dce2824c0e58334612a 100644 --- a/spec/features/work_items/work_items_list_filters_spec.rb +++ b/spec/features/work_items/work_items_list_filters_spec.rb @@ -10,9 +10,7 @@ let_it_be(:user2) { create(:user) } let_it_be(:group) { create(:group) } - let_it_be(:sub_group) { create(:group, parent: group) } let_it_be(:project) { create(:project, :public, group: group, developers: [user1, user2]) } - let_it_be(:sub_group_project) { create(:project, :public, group: sub_group, developers: [user1, user2]) } let_it_be(:label1) { create(:label, project: project) } let_it_be(:label2) { create(:label, project: project) } @@ -37,7 +35,7 @@ end let_it_be(:task) do - create(:work_item, :task, project: sub_group_project, + create(:work_item, :task, project: project, assignees: [user2], author: user2, confidential: true, @@ -137,15 +135,6 @@ end end - describe 'group' do - it 'filters', :aggregate_failures do - select_tokens 'Group', sub_group.name, submit: true - - expect(page).to have_css('.issue', count: 1) - expect(page).to have_link(task.title) - end - end - describe 'label' do it 'filters', :aggregate_failures do select_tokens 'Label', '=', label1.title, submit: true diff --git a/spec/frontend/work_items/list/components/work_items_list_app_spec.js b/spec/frontend/work_items/list/components/work_items_list_app_spec.js index 55752f9c421b2af82494c4bfd2245995133fa9d3..edeee4a3bad4c209cf46d8eb50a4b90d62ef7d86 100644 --- a/spec/frontend/work_items/list/components/work_items_list_app_spec.js +++ b/spec/frontend/work_items/list/components/work_items_list_app_spec.js @@ -749,4 +749,47 @@ describeSkipVue3(skipReason, () => { expect(findIssuableList().props('issuables')).toEqual([]); }); }); + + describe('group filter', () => { + describe('filtering by group', () => { + it('query excludes descendants and excludes projects', async () => { + mountComponent(); + await waitForPromises(); + + findIssuableList().vm.$emit('filter', [ + { + type: TOKEN_TYPE_GROUP, + value: { data: 'path/to/another/group', operator: OPERATOR_IS }, + }, + ]); + await nextTick(); + + expect(defaultQueryHandler).toHaveBeenCalledWith( + expect.objectContaining({ + excludeProjects: true, + includeDescendants: false, + }), + ); + }); + }); + + describe('not filtering by group', () => { + it('query includes descendants and includes projects', async () => { + mountComponent(); + await waitForPromises(); + + findIssuableList().vm.$emit('filter', [ + { type: TOKEN_TYPE_AUTHOR, value: { data: 'homer', operator: OPERATOR_IS } }, + ]); + await nextTick(); + + expect(defaultQueryHandler).toHaveBeenCalledWith( + expect.objectContaining({ + excludeProjects: false, + includeDescendants: true, + }), + ); + }); + }); + }); });