Commit a555a227 authored by Kamil Trzciński's avatar Kamil Trzciński Committed by Rémy Coutable

Merge branch 'filter-confidential-issues' into 'master'

Ability to filter confidential issues

Closes #50747

See merge request gitlab-org/gitlab-ce!24960
parent afd290a4
...@@ -96,6 +96,11 @@ export default class FilteredSearchDropdownManager { ...@@ -96,6 +96,11 @@ export default class FilteredSearchDropdownManager {
gl: DropdownNonUser, gl: DropdownNonUser,
element: this.container.querySelector('#js-dropdown-wip'), element: this.container.querySelector('#js-dropdown-wip'),
}, },
confidential: {
reference: null,
gl: DropdownNonUser,
element: this.container.querySelector('#js-dropdown-confidential'),
},
status: { status: {
reference: null, reference: null,
gl: NullDropdown, gl: NullDropdown,
......
...@@ -72,6 +72,23 @@ export default class FilteredSearchTokenKeys { ...@@ -72,6 +72,23 @@ export default class FilteredSearchTokenKeys {
); );
} }
addExtraTokensForIssues() {
const confidentialToken = {
key: 'confidential',
type: 'string',
param: '',
symbol: '',
icon: 'eye-slash',
tag: 'Yes or No',
lowercaseValueOnSubmit: true,
uppercaseTokenName: false,
capitalizeTokenValue: true,
};
this.tokenKeys.push(confidentialToken);
this.tokenKeysWithAlternative.push(confidentialToken);
}
addExtraTokensForMergeRequests() { addExtraTokensForMergeRequests() {
const wipToken = { const wipToken = {
key: 'wip', key: 'wip',
......
...@@ -4,6 +4,8 @@ import { FILTERED_SEARCH } from '~/pages/constants'; ...@@ -4,6 +4,8 @@ import { FILTERED_SEARCH } from '~/pages/constants';
import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys'; import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
IssuableFilteredSearchTokenKeys.addExtraTokensForIssues();
initFilteredSearch({ initFilteredSearch({
page: FILTERED_SEARCH.ISSUES, page: FILTERED_SEARCH.ISSUES,
isGroupDecendent: true, isGroupDecendent: true,
......
...@@ -9,6 +9,8 @@ import { FILTERED_SEARCH } from '~/pages/constants'; ...@@ -9,6 +9,8 @@ import { FILTERED_SEARCH } from '~/pages/constants';
import { ISSUABLE_INDEX } from '~/pages/projects/constants'; import { ISSUABLE_INDEX } from '~/pages/projects/constants';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
IssuableFilteredSearchTokenKeys.addExtraTokensForIssues();
initFilteredSearch({ initFilteredSearch({
page: FILTERED_SEARCH.ISSUES, page: FILTERED_SEARCH.ISSUES,
filteredSearchTokenKeys: IssuableFilteredSearchTokenKeys, filteredSearchTokenKeys: IssuableFilteredSearchTokenKeys,
......
...@@ -91,6 +91,7 @@ module IssuableCollections ...@@ -91,6 +91,7 @@ module IssuableCollections
options = { options = {
scope: params[:scope], scope: params[:scope],
state: params[:state], state: params[:state],
confidential: Gitlab::Utils.to_boolean(params[:confidential]),
sort: set_sort_order sort: set_sort_order
} }
......
...@@ -69,7 +69,16 @@ class IssuesFinder < IssuableFinder ...@@ -69,7 +69,16 @@ class IssuesFinder < IssuableFinder
end end
def filter_items(items) def filter_items(items)
by_due_date(super) issues = super
issues = by_due_date(issues)
issues = by_confidential(issues)
issues
end
def by_confidential(items)
return items if params[:confidential].nil?
params[:confidential] ? items.confidential_only : items.public_only
end end
def by_due_date(items) def by_due_date(items)
......
...@@ -66,6 +66,7 @@ class Issue < ActiveRecord::Base ...@@ -66,6 +66,7 @@ class Issue < ActiveRecord::Base
scope :preload_associations, -> { preload(:labels, project: :namespace) } scope :preload_associations, -> { preload(:labels, project: :namespace) }
scope :public_only, -> { where(confidential: false) } scope :public_only, -> { where(confidential: false) }
scope :confidential_only, -> { where(confidential: true) }
after_save :expire_etag_cache after_save :expire_etag_cache
after_save :ensure_metrics, unless: :imported? after_save :ensure_metrics, unless: :imported?
......
...@@ -128,6 +128,14 @@ ...@@ -128,6 +128,14 @@
%li.filter-dropdown-item{ data: { value: 'no', capitalize: true } } %li.filter-dropdown-item{ data: { value: 'no', capitalize: true } }
%button.btn.btn-link{ type: 'button' } %button.btn.btn-link{ type: 'button' }
= _('No') = _('No')
#js-dropdown-confidential.filtered-search-input-dropdown-menu.dropdown-menu
%ul.filter-dropdown{ data: { dropdown: true } }
%li.filter-dropdown-item{ data: { value: 'yes', capitalize: true } }
%button.btn.btn-link{ type: 'button' }
= _('Yes')
%li.filter-dropdown-item{ data: { value: 'no', capitalize: true } }
%button.btn.btn-link{ type: 'button' }
= _('No')
= render_if_exists 'shared/issuable/filter_weight', type: type = render_if_exists 'shared/issuable/filter_weight', type: type
......
---
title: Ability to filter confidential issues
merge_request: 24960
author: Robert Schilling
type: added
...@@ -32,6 +32,7 @@ GET /issues?author_id=5 ...@@ -32,6 +32,7 @@ GET /issues?author_id=5
GET /issues?assignee_id=5 GET /issues?assignee_id=5
GET /issues?my_reaction_emoji=star GET /issues?my_reaction_emoji=star
GET /issues?search=foo&in=title GET /issues?search=foo&in=title
GET /issues?confidential=true
``` ```
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
...@@ -52,6 +53,7 @@ GET /issues?search=foo&in=title ...@@ -52,6 +53,7 @@ GET /issues?search=foo&in=title
| `created_before` | datetime | no | Return issues created on or before the given time | | `created_before` | datetime | no | Return issues created on or before the given time |
| `updated_after` | datetime | no | Return issues updated on or after the given time | | `updated_after` | datetime | no | Return issues updated on or after the given time |
| `updated_before` | datetime | no | Return issues updated on or before the given time | | `updated_before` | datetime | no | Return issues updated on or before the given time |
| `confidential ` | Boolean | no | Filter confidential or public issues. |
```bash ```bash
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/issues curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/issues
...@@ -148,6 +150,7 @@ GET /groups/:id/issues?search=issue+title+or+description ...@@ -148,6 +150,7 @@ GET /groups/:id/issues?search=issue+title+or+description
GET /groups/:id/issues?author_id=5 GET /groups/:id/issues?author_id=5
GET /groups/:id/issues?assignee_id=5 GET /groups/:id/issues?assignee_id=5
GET /groups/:id/issues?my_reaction_emoji=star GET /groups/:id/issues?my_reaction_emoji=star
GET /groups/:id/issues?confidential=true
``` ```
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
...@@ -168,6 +171,7 @@ GET /groups/:id/issues?my_reaction_emoji=star ...@@ -168,6 +171,7 @@ GET /groups/:id/issues?my_reaction_emoji=star
| `created_before` | datetime | no | Return issues created on or before the given time | | `created_before` | datetime | no | Return issues created on or before the given time |
| `updated_after` | datetime | no | Return issues updated on or after the given time | | `updated_after` | datetime | no | Return issues updated on or after the given time |
| `updated_before` | datetime | no | Return issues updated on or before the given time | | `updated_before` | datetime | no | Return issues updated on or before the given time |
| `confidential ` | Boolean | no | Filter confidential or public issues. |
```bash ```bash
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/4/issues curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/4/issues
...@@ -264,6 +268,7 @@ GET /projects/:id/issues?search=issue+title+or+description ...@@ -264,6 +268,7 @@ GET /projects/:id/issues?search=issue+title+or+description
GET /projects/:id/issues?author_id=5 GET /projects/:id/issues?author_id=5
GET /projects/:id/issues?assignee_id=5 GET /projects/:id/issues?assignee_id=5
GET /projects/:id/issues?my_reaction_emoji=star GET /projects/:id/issues?my_reaction_emoji=star
GET /projects/:id/issues?confidential=true
``` ```
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
...@@ -284,6 +289,8 @@ GET /projects/:id/issues?my_reaction_emoji=star ...@@ -284,6 +289,8 @@ GET /projects/:id/issues?my_reaction_emoji=star
| `created_before` | datetime | no | Return issues created on or before the given time | | `created_before` | datetime | no | Return issues created on or before the given time |
| `updated_after` | datetime | no | Return issues updated on or after the given time | | `updated_after` | datetime | no | Return issues updated on or after the given time |
| `updated_before` | datetime | no | Return issues updated on or before the given time | | `updated_before` | datetime | no | Return issues updated on or before the given time |
| `confidential ` | Boolean | no | Filter confidential or public issues. |
```bash ```bash
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/4/issues curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/4/issues
......
...@@ -54,6 +54,7 @@ module API ...@@ -54,6 +54,7 @@ module API
optional :scope, type: String, values: %w[created-by-me assigned-to-me created_by_me assigned_to_me all], optional :scope, type: String, values: %w[created-by-me assigned-to-me created_by_me assigned_to_me all],
desc: 'Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`' desc: 'Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`'
optional :my_reaction_emoji, type: String, desc: 'Return issues reacted by the authenticated user by the given emoji' optional :my_reaction_emoji, type: String, desc: 'Return issues reacted by the authenticated user by the given emoji'
optional :confidential, type: Boolean, desc: 'Filter confidential or public issues'
use :pagination use :pagination
use :issues_params_ee use :issues_params_ee
......
...@@ -112,7 +112,8 @@ describe IssuableCollections do ...@@ -112,7 +112,8 @@ describe IssuableCollections do
assignee_username: 'user1', assignee_username: 'user1',
author_id: '2', author_id: '2',
author_username: 'user2', author_username: 'user2',
authorized_only: 'true', authorized_only: 'yes',
confidential: true,
due_date: '2017-01-01', due_date: '2017-01-01',
group_id: '3', group_id: '3',
iids: '4', iids: '4',
...@@ -140,6 +141,7 @@ describe IssuableCollections do ...@@ -140,6 +141,7 @@ describe IssuableCollections do
'assignee_username' => 'user1', 'assignee_username' => 'user1',
'author_id' => '2', 'author_id' => '2',
'author_username' => 'user2', 'author_username' => 'user2',
'confidential' => true,
'label_name' => 'foo', 'label_name' => 'foo',
'milestone_title' => 'bar', 'milestone_title' => 'bar',
'my_reaction_emoji' => 'thumbsup', 'my_reaction_emoji' => 'thumbsup',
......
...@@ -66,7 +66,7 @@ describe 'Dropdown hint', :js do ...@@ -66,7 +66,7 @@ describe 'Dropdown hint', :js do
it 'filters with text' do it 'filters with text' do
filtered_search.set('a') filtered_search.set('a')
expect(find(js_dropdown_hint)).to have_selector('.filter-dropdown .filter-dropdown-item', count: 4) expect(find(js_dropdown_hint)).to have_selector('.filter-dropdown .filter-dropdown-item', count: 5)
end end
end end
...@@ -119,6 +119,15 @@ describe 'Dropdown hint', :js do ...@@ -119,6 +119,15 @@ describe 'Dropdown hint', :js do
expect_tokens([{ name: 'my-reaction' }]) expect_tokens([{ name: 'my-reaction' }])
expect_filtered_search_input_empty expect_filtered_search_input_empty
end end
it 'opens the yes-no dropdown when you click on confidential' do
click_hint('confidential')
expect(page).to have_css(js_dropdown_hint, visible: false)
expect(page).to have_css('#js-dropdown-confidential', visible: true)
expect_tokens([{ name: 'confidential' }])
expect_filtered_search_input_empty
end
end end
describe 'selecting from dropdown with some input' do describe 'selecting from dropdown with some input' do
......
...@@ -100,7 +100,7 @@ describe 'Search bar', :js do ...@@ -100,7 +100,7 @@ describe 'Search bar', :js do
find('.filtered-search-box .clear-search').click find('.filtered-search-box .clear-search').click
filtered_search.click filtered_search.click
expect(find('#js-dropdown-hint')).to have_selector('.filter-dropdown .filter-dropdown-item', count: 5) expect(find('#js-dropdown-hint')).to have_selector('.filter-dropdown .filter-dropdown-item', count: 6)
expect(get_left_style(find('#js-dropdown-hint')['style'])).to eq(hint_offset) expect(get_left_style(find('#js-dropdown-hint')['style'])).to eq(hint_offset)
end end
end end
......
...@@ -490,6 +490,32 @@ describe IssuesFinder do ...@@ -490,6 +490,32 @@ describe IssuesFinder do
end end
end end
context 'filtering by confidential' do
set(:confidential_issue) { create(:issue, project: project1, confidential: true) }
context 'no filtering' do
it 'returns all issues' do
expect(issues).to contain_exactly(issue1, issue2, issue3, issue4, confidential_issue)
end
end
context 'user filters confidential issues' do
let(:params) { { confidential: true } }
it 'returns only confdential issues' do
expect(issues).to contain_exactly(confidential_issue)
end
end
context 'user filters only public issues' do
let(:params) { { confidential: false } }
it 'returns only confdential issues' do
expect(issues).to contain_exactly(issue1, issue2, issue3, issue4)
end
end
end
context 'when the user is unauthorized' do context 'when the user is unauthorized' do
let(:search_user) { nil } let(:search_user) { nil }
...@@ -556,7 +582,7 @@ describe IssuesFinder do ...@@ -556,7 +582,7 @@ describe IssuesFinder do
it 'returns the number of rows for the default state' do it 'returns the number of rows for the default state' do
finder = described_class.new(user) finder = described_class.new(user)
expect(finder.row_count).to eq(4) expect(finder.row_count).to eq(5)
end end
it 'returns the number of rows for a given state' do it 'returns the number of rows for a given state' do
......
...@@ -765,6 +765,15 @@ describe Issue do ...@@ -765,6 +765,15 @@ describe Issue do
end end
end end
describe '.confidential_only' do
it 'only returns confidential_only issues' do
create(:issue)
confidential_issue = create(:issue, confidential: true)
expect(described_class.confidential_only).to eq([confidential_issue])
end
end
it_behaves_like 'throttled touch' do it_behaves_like 'throttled touch' do
subject { create(:issue, updated_at: 1.hour.ago) } subject { create(:issue, updated_at: 1.hour.ago) }
end end
......
...@@ -183,6 +183,18 @@ describe API::Issues do ...@@ -183,6 +183,18 @@ describe API::Issues do
expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id]) expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id])
end end
it 'returns only confidential issues' do
get api('/issues', user), params: { confidential: true, scope: 'all' }
expect_paginated_array_response(confidential_issue.id)
end
it 'returns only public issues' do
get api('/issues', user), params: { confidential: false }
expect_paginated_array_response([issue.id, closed_issue.id])
end
it 'returns issues reacted by the authenticated user' do it 'returns issues reacted by the authenticated user' do
issue2 = create(:issue, project: project, author: user, assignees: [user]) issue2 = create(:issue, project: project, author: user, assignees: [user])
create(:award_emoji, awardable: issue2, user: user2, name: 'star') create(:award_emoji, awardable: issue2, user: user2, name: 'star')
...@@ -557,6 +569,18 @@ describe API::Issues do ...@@ -557,6 +569,18 @@ describe API::Issues do
expect_paginated_array_response([group_confidential_issue.id, group_issue.id]) expect_paginated_array_response([group_confidential_issue.id, group_issue.id])
end end
it 'returns only confidential issues' do
get api(base_url, user), params: { confidential: true }
expect_paginated_array_response(group_confidential_issue.id)
end
it 'returns only public issues' do
get api(base_url, user), params: { confidential: false }
expect_paginated_array_response([group_closed_issue.id, group_issue.id])
end
it 'returns an array of labeled group issues' do it 'returns an array of labeled group issues' do
get api(base_url, user), params: { labels: group_label.title } get api(base_url, user), params: { labels: group_label.title }
...@@ -782,6 +806,18 @@ describe API::Issues do ...@@ -782,6 +806,18 @@ describe API::Issues do
expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id]) expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id])
end end
it 'returns only confidential issues' do
get api("#{base_url}/issues", author), params: { confidential: true }
expect_paginated_array_response(confidential_issue.id)
end
it 'returns only public issues' do
get api("#{base_url}/issues", author), params: { confidential: false }
expect_paginated_array_response([issue.id, closed_issue.id])
end
it 'returns project confidential issues for assignee' do it 'returns project confidential issues for assignee' do
get api("#{base_url}/issues", assignee) get api("#{base_url}/issues", assignee)
......
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