Commit 5f03cce6 authored by Dylan Griffith's avatar Dylan Griffith

Merge branch...

Merge branch '337043-add-the-ability-to-filter-issue-boards-by-confidentiality-in-graphql-be' into 'master'

Add confidential filter for board list issues

See merge request gitlab-org/gitlab!75380
parents 08b8605d 28d98d1a
...@@ -17,6 +17,10 @@ module Types ...@@ -17,6 +17,10 @@ module Types
argument :assignee_wildcard_id, ::Types::Boards::AssigneeWildcardIdEnum, argument :assignee_wildcard_id, ::Types::Boards::AssigneeWildcardIdEnum,
required: false, required: false,
description: 'Filter by assignee wildcard. Incompatible with assigneeUsername.' description: 'Filter by assignee wildcard. Incompatible with assigneeUsername.'
argument :confidential, GraphQL::Types::Boolean,
required: false,
description: 'Filter by confidentiality.'
end end
end end
end end
......
...@@ -18359,6 +18359,7 @@ Field that are available while modifying the custom mapping attributes for an HT ...@@ -18359,6 +18359,7 @@ Field that are available while modifying the custom mapping attributes for an HT
| <a id="boardissueinputassigneeusername"></a>`assigneeUsername` | [`[String]`](#string) | Filter by assignee username. | | <a id="boardissueinputassigneeusername"></a>`assigneeUsername` | [`[String]`](#string) | Filter by assignee username. |
| <a id="boardissueinputassigneewildcardid"></a>`assigneeWildcardId` | [`AssigneeWildcardId`](#assigneewildcardid) | Filter by assignee wildcard. Incompatible with assigneeUsername. | | <a id="boardissueinputassigneewildcardid"></a>`assigneeWildcardId` | [`AssigneeWildcardId`](#assigneewildcardid) | Filter by assignee wildcard. Incompatible with assigneeUsername. |
| <a id="boardissueinputauthorusername"></a>`authorUsername` | [`String`](#string) | Filter by author username. | | <a id="boardissueinputauthorusername"></a>`authorUsername` | [`String`](#string) | Filter by author username. |
| <a id="boardissueinputconfidential"></a>`confidential` | [`Boolean`](#boolean) | Filter by confidentiality. |
| <a id="boardissueinputepicid"></a>`epicId` | [`EpicID`](#epicid) | Filter by epic ID. Incompatible with epicWildcardId. | | <a id="boardissueinputepicid"></a>`epicId` | [`EpicID`](#epicid) | Filter by epic ID. Incompatible with epicWildcardId. |
| <a id="boardissueinputepicwildcardid"></a>`epicWildcardId` | [`EpicWildcardId`](#epicwildcardid) | Filter by epic ID wildcard. Incompatible with epicId. | | <a id="boardissueinputepicwildcardid"></a>`epicWildcardId` | [`EpicWildcardId`](#epicwildcardid) | Filter by epic ID wildcard. Incompatible with epicId. |
| <a id="boardissueinputiids"></a>`iids` | [`[String!]`](#string) | List of IIDs of issues. For example `["1", "2"]`. | | <a id="boardissueinputiids"></a>`iids` | [`[String!]`](#string) | List of IIDs of issues. For example `["1", "2"]`. |
...@@ -18485,6 +18486,7 @@ Input type for DastSiteProfile authentication. ...@@ -18485,6 +18486,7 @@ Input type for DastSiteProfile authentication.
| Name | Type | Description | | Name | Type | Description |
| ---- | ---- | ----------- | | ---- | ---- | ----------- |
| <a id="epicfiltersauthorusername"></a>`authorUsername` | [`String`](#string) | Filter by author username. | | <a id="epicfiltersauthorusername"></a>`authorUsername` | [`String`](#string) | Filter by author username. |
| <a id="epicfiltersconfidential"></a>`confidential` | [`Boolean`](#boolean) | Filter by confidentiality. |
| <a id="epicfilterslabelname"></a>`labelName` | [`[String]`](#string) | Filter by label name. | | <a id="epicfilterslabelname"></a>`labelName` | [`[String]`](#string) | Filter by label name. |
| <a id="epicfiltersmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. Wildcard values "NONE" and "ANY" are supported. | | <a id="epicfiltersmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. Wildcard values "NONE" and "ANY" are supported. |
| <a id="epicfiltersnot"></a>`not` | [`NegatedEpicBoardIssueInput`](#negatedepicboardissueinput) | Negated epic arguments. | | <a id="epicfiltersnot"></a>`not` | [`NegatedEpicBoardIssueInput`](#negatedepicboardissueinput) | Negated epic arguments. |
...@@ -15,6 +15,10 @@ module Types ...@@ -15,6 +15,10 @@ module Types
argument :search, GraphQL::Types::String, argument :search, GraphQL::Types::String,
required: false, required: false,
description: 'Search query for epic title or description.' description: 'Search query for epic title or description.'
argument :confidential, GraphQL::Types::Boolean,
required: false,
description: 'Filter by confidentiality.'
end end
end end
end end
...@@ -86,8 +86,9 @@ RSpec.describe 'get list of epic boards' do ...@@ -86,8 +86,9 @@ RSpec.describe 'get list of epic boards' do
# filtering. # filtering.
create(:labeled_epic, group: group) create(:labeled_epic, group: group)
create(:labeled_epic, group: group, labels: [label]) create(:labeled_epic, group: group, labels: [label])
create(:labeled_epic, group: group, labels: [label], confidential: true)
params = { epicFilters: { labelName: label.title } } params = { epicFilters: { labelName: label.title, confidential: false } }
post_graphql(pagination_query(params), current_user: current_user) post_graphql(pagination_query(params), current_user: current_user)
assert_field_value('epicsCount', [1, 0, 0]) assert_field_value('epicsCount', [1, 0, 0])
......
...@@ -25,7 +25,7 @@ RSpec.describe Resolvers::BoardListIssuesResolver do ...@@ -25,7 +25,7 @@ RSpec.describe Resolvers::BoardListIssuesResolver do
let(:wildcard_started) { 'STARTED' } let(:wildcard_started) { 'STARTED' }
let(:filters) { { milestone_title: ["started"], milestone_wildcard_id: wildcard_started } } let(:filters) { { milestone_title: ["started"], milestone_wildcard_id: wildcard_started } }
it 'raises a mutually exclusive filter error when milstone wildcard and title are provided' do it 'raises a mutually exclusive filter error when milestone wildcard and title are provided' do
expect do expect do
resolve_board_list_issues(args: { filters: filters }) resolve_board_list_issues(args: { filters: filters })
end.to raise_error(Gitlab::Graphql::Errors::ArgumentError) end.to raise_error(Gitlab::Graphql::Errors::ArgumentError)
...@@ -80,6 +80,16 @@ RSpec.describe Resolvers::BoardListIssuesResolver do ...@@ -80,6 +80,16 @@ RSpec.describe Resolvers::BoardListIssuesResolver do
expect(result).to match_array([]) expect(result).to match_array([])
end end
context 'when filtering by confidential' do
let(:confidential_issue) { create(:issue, project: project, labels: [label], relative_position: nil, confidential: true) }
it 'returns matching issue' do
result = resolve_board_list_issues(args: { filters: { confidential: true } })
expect(result).to contain_exactly(confidential_issue)
end
end
end end
end end
......
...@@ -7,7 +7,7 @@ RSpec.describe GitlabSchema.types['BoardIssueInput'] do ...@@ -7,7 +7,7 @@ RSpec.describe GitlabSchema.types['BoardIssueInput'] do
it 'has specific fields' do it 'has specific fields' do
allowed_args = %w(labelName milestoneTitle assigneeUsername authorUsername allowed_args = %w(labelName milestoneTitle assigneeUsername authorUsername
releaseTag myReactionEmoji not search assigneeWildcardId) releaseTag myReactionEmoji not search assigneeWildcardId confidential)
expect(described_class.arguments.keys).to include(*allowed_args) expect(described_class.arguments.keys).to include(*allowed_args)
expect(described_class.arguments['not'].type).to eq(Types::Boards::NegatedBoardIssueInputType) expect(described_class.arguments['not'].type).to eq(Types::Boards::NegatedBoardIssueInputType)
......
...@@ -16,6 +16,7 @@ RSpec.describe 'get board lists' do ...@@ -16,6 +16,7 @@ RSpec.describe 'get board lists' do
let(:params) { '' } let(:params) { '' }
let(:board) { } let(:board) { }
let(:confidential) { false }
let(:board_parent_type) { board_parent.class.to_s.downcase } let(:board_parent_type) { board_parent.class.to_s.downcase }
let(:board_data) { graphql_data[board_parent_type]['boards']['nodes'][0] } let(:board_data) { graphql_data[board_parent_type]['boards']['nodes'][0] }
let(:lists_data) { board_data['lists']['nodes'][0] } let(:lists_data) { board_data['lists']['nodes'][0] }
...@@ -30,7 +31,7 @@ RSpec.describe 'get board lists' do ...@@ -30,7 +31,7 @@ RSpec.describe 'get board lists' do
nodes { nodes {
lists { lists {
nodes { nodes {
issues(filters: {labelName: "#{label2.title}"}, first: 3) { issues(filters: {labelName: "#{label2.title}", confidential: #{confidential}}, first: 3) {
count count
nodes { nodes {
#{all_graphql_fields_for('issues'.classify)} #{all_graphql_fields_for('issues'.classify)}
...@@ -57,14 +58,15 @@ RSpec.describe 'get board lists' do ...@@ -57,14 +58,15 @@ RSpec.describe 'get board lists' do
end end
shared_examples 'group and project board list issues query' do shared_examples 'group and project board list issues query' do
let!(:board) { create(:board, resource_parent: board_parent) } let_it_be(:board) { create(:board, resource_parent: board_parent) }
let!(:label_list) { create(:list, board: board, label: label, position: 10) } let_it_be(:label_list) { create(:list, board: board, label: label, position: 10) }
let!(:issue1) { create(:issue, project: issue_project, labels: [label, label2], relative_position: 9) } let_it_be(:issue1) { create(:issue, project: issue_project, labels: [label, label2], relative_position: 9) }
let!(:issue2) { create(:issue, project: issue_project, labels: [label, label2], relative_position: 2) } let_it_be(:issue2) { create(:issue, project: issue_project, labels: [label, label2], relative_position: 2) }
let!(:issue3) { create(:issue, project: issue_project, labels: [label, label2], relative_position: nil) } let_it_be(:issue3) { create(:issue, project: issue_project, labels: [label, label2], relative_position: nil) }
let!(:issue4) { create(:issue, project: issue_project, labels: [label], relative_position: 9) } let_it_be(:issue4) { create(:issue, project: issue_project, labels: [label], relative_position: 9) }
let!(:issue5) { create(:issue, project: issue_project, labels: [label2], relative_position: 432) } let_it_be(:issue5) { create(:issue, project: issue_project, labels: [label2], relative_position: 432) }
let!(:issue6) { create(:issue, project: issue_project, labels: [label, label2], relative_position: nil) } let_it_be(:issue6) { create(:issue, project: issue_project, labels: [label, label2], relative_position: nil) }
let_it_be(:issue7) { create(:issue, project: issue_project, labels: [label, label2], relative_position: 5, confidential: true) }
context 'when the user does not have access to the board' do context 'when the user does not have access to the board' do
it 'returns nil' do it 'returns nil' do
...@@ -90,23 +92,33 @@ RSpec.describe 'get board lists' do ...@@ -90,23 +92,33 @@ RSpec.describe 'get board lists' do
expect(issue_id).not_to include(issue6.id) expect(issue_id).not_to include(issue6.id)
expect(issue3.relative_position).to be_nil expect(issue3.relative_position).to be_nil
end end
context 'when filtering by confidential' do
let(:confidential) { true }
it 'returns matching issue' do
expect(issue_titles).to match_array([issue7.title])
expect(issue_relative_positions).not_to include(nil)
end
end
end end
end end
describe 'for a project' do describe 'for a project' do
let(:board_parent) { project } let_it_be(:board_parent) { project }
let(:label) { project_label } let_it_be(:label) { project_label }
let(:label2) { project_label2 } let_it_be(:label2) { project_label2 }
let(:issue_project) { project } let_it_be(:issue_project) { project }
it_behaves_like 'group and project board list issues query' it_behaves_like 'group and project board list issues query'
end end
describe 'for a group' do describe 'for a group' do
let(:board_parent) { group } let_it_be(:board_parent) { group }
let(:label) { group_label } let_it_be(:label) { group_label }
let(:label2) { group_label2 } let_it_be(:label2) { group_label2 }
let(:issue_project) { create(:project, :private, group: group) }
let_it_be(:issue_project) { create(:project, :private, group: group) }
before do before do
allow(board_parent).to receive(:multiple_issue_boards_available?).and_return(false) allow(board_parent).to receive(:multiple_issue_boards_available?).and_return(false)
......
...@@ -12,6 +12,7 @@ RSpec.describe 'Querying a Board list' do ...@@ -12,6 +12,7 @@ RSpec.describe 'Querying a Board list' do
let_it_be(:list) { create(:list, board: board, label: label) } let_it_be(:list) { create(:list, board: board, label: label) }
let_it_be(:issue1) { create(:issue, project: project, labels: [label]) } let_it_be(:issue1) { create(:issue, project: project, labels: [label]) }
let_it_be(:issue2) { create(:issue, project: project, labels: [label], assignees: [current_user]) } let_it_be(:issue2) { create(:issue, project: project, labels: [label], assignees: [current_user]) }
let_it_be(:issue3) { create(:issue, project: project, labels: [label], confidential: true) }
let(:filters) { {} } let(:filters) { {} }
let(:query) do let(:query) do
...@@ -37,19 +38,33 @@ RSpec.describe 'Querying a Board list' do ...@@ -37,19 +38,33 @@ RSpec.describe 'Querying a Board list' do
it { is_expected.to include({ 'issuesCount' => 2, 'title' => list.title }) } it { is_expected.to include({ 'issuesCount' => 2, 'title' => list.title }) }
context 'with matching issue filters' do describe 'issue filters' do
let(:filters) { { assigneeUsername: current_user.username } } context 'with matching assignee username issue filters' do
let(:filters) { { assigneeUsername: current_user.username } }
it 'filters issues metadata' do it 'filters issues metadata' do
is_expected.to include({ 'issuesCount' => 1, 'title' => list.title }) is_expected.to include({ 'issuesCount' => 1, 'title' => list.title })
end
end end
end
context 'with unmatching issue filters' do context 'with unmatching assignee username issue filters' do
let(:filters) { { assigneeUsername: 'foo' } } let(:filters) { { assigneeUsername: 'foo' } }
it 'filters issues metadata' do
is_expected.to include({ 'issuesCount' => 0, 'title' => list.title })
end
end
context 'when filtering by confidential' do
let(:filters) { { confidential: true } }
before_all do
project.add_developer(current_user)
end
it 'filters issues metadata' do it 'filters issues metadata' do
is_expected.to include({ 'issuesCount' => 0, 'title' => list.title }) is_expected.to include({ 'issuesCount' => 1, 'title' => list.title })
end
end end
end end
end end
......
...@@ -109,9 +109,15 @@ RSpec.describe 'get board lists' do ...@@ -109,9 +109,15 @@ RSpec.describe 'get board lists' do
it 'returns the correct list with issue count for matching issue filters' do it 'returns the correct list with issue count for matching issue filters' do
label_list = create(:list, board: board, label: label, position: 10) label_list = create(:list, board: board, label: label, position: 10)
create(:issue, project: project, labels: [label, label2]) create(:issue, project: project, labels: [label, label2])
create(:issue, project: project, labels: [label, label2], confidential: true)
create(:issue, project: project, labels: [label]) create(:issue, project: project, labels: [label])
post_graphql(query(id: global_id_of(label_list), issueFilters: { labelName: label2.title }), current_user: user) post_graphql(
query(
id: global_id_of(label_list),
issueFilters: { labelName: label2.title, confidential: false }
), current_user: user
)
aggregate_failures do aggregate_failures do
list_node = lists_data[0]['node'] list_node = lists_data[0]['node']
......
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