Commit 3579e559 authored by Markus Koller's avatar Markus Koller

Merge branch '259024-labels-api-search-support' into 'master'

Extend Project and Group Labels API

See merge request gitlab-org/gitlab!44415
parents 403362ba 9ea658a6
---
title: Add support for search and inclusion of project labels within Group Labels API
merge_request: 44415
author:
type: changed
...@@ -26,6 +26,9 @@ GET /groups/:id/labels ...@@ -26,6 +26,9 @@ GET /groups/:id/labels
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user. | | `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user. |
| `with_counts` | boolean | no | Whether or not to include issue and merge request counts. Defaults to `false`. _([Introduced in GitLab 12.2](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/31543))_ | | `with_counts` | boolean | no | Whether or not to include issue and merge request counts. Defaults to `false`. _([Introduced in GitLab 12.2](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/31543))_ |
| `include_ancestor_groups` | boolean | no | Include ancestor groups. Defaults to `true`. | | `include_ancestor_groups` | boolean | no | Include ancestor groups. Defaults to `true`. |
| `include_descendant_groups` | boolean | no | Include descendant groups. Defaults to `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/259024) in GitLab 13.6 |
| `only_group_labels` | boolean | no | Toggle to include only group labels or also project labels. Defaults to `true`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/259024) in GitLab 13.6 |
| `search` | string | no | Keyword to filter labels by. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/259024) in GitLab 13.6 |
```shell ```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/5/labels?with_counts=true" curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/5/labels?with_counts=true"
...@@ -75,6 +78,8 @@ GET /groups/:id/labels/:label_id ...@@ -75,6 +78,8 @@ GET /groups/:id/labels/:label_id
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user. | | `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user. |
| `label_id` | integer or string | yes | The ID or title of a group's label. | | `label_id` | integer or string | yes | The ID or title of a group's label. |
| `include_ancestor_groups` | boolean | no | Include ancestor groups. Defaults to `true`. | | `include_ancestor_groups` | boolean | no | Include ancestor groups. Defaults to `true`. |
| `include_descendant_groups` | boolean | no | Include descendant groups. Defaults to `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/259024) in GitLab 13.6 |
| `only_group_labels` | boolean | no | Toggle to include only group labels or also project labels. Defaults to `true`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/259024) in GitLab 13.6 |
```shell ```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/5/labels/bug" curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/5/labels/bug"
......
...@@ -24,6 +24,7 @@ GET /projects/:id/labels ...@@ -24,6 +24,7 @@ GET /projects/:id/labels
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | | `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `with_counts` | boolean | no | Whether or not to include issue and merge request counts. Defaults to `false`. _([Introduced in GitLab 12.2](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/31543))_ | | `with_counts` | boolean | no | Whether or not to include issue and merge request counts. Defaults to `false`. _([Introduced in GitLab 12.2](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/31543))_ |
| `include_ancestor_groups` | boolean | no | Include ancestor groups. Defaults to `true`. | | `include_ancestor_groups` | boolean | no | Include ancestor groups. Defaults to `true`. |
| `search` | string | no | Keyword to filter labels by. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/259024) in GitLab 13.6 |
```shell ```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/labels?with_counts=true" curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/labels?with_counts=true"
......
...@@ -20,10 +20,16 @@ module API ...@@ -20,10 +20,16 @@ module API
desc: 'Include issue and merge request counts' desc: 'Include issue and merge request counts'
optional :include_ancestor_groups, type: Boolean, default: true, optional :include_ancestor_groups, type: Boolean, default: true,
desc: 'Include ancestor groups' desc: 'Include ancestor groups'
optional :include_descendant_groups, type: Boolean, default: false,
desc: 'Include descendant groups. This feature was added in GitLab 13.6'
optional :only_group_labels, type: Boolean, default: true,
desc: 'Toggle to include only group labels or also project labels. This feature was added in GitLab 13.6'
optional :search, type: String,
desc: 'Keyword to filter labels by. This feature was added in GitLab 13.6'
use :pagination use :pagination
end end
get ':id/labels' do get ':id/labels' do
get_labels(user_group, Entities::GroupLabel, include_ancestor_groups: params[:include_ancestor_groups]) get_labels(user_group, Entities::GroupLabel, declared_params)
end end
desc 'Get a single label' do desc 'Get a single label' do
...@@ -33,9 +39,13 @@ module API ...@@ -33,9 +39,13 @@ module API
params do params do
optional :include_ancestor_groups, type: Boolean, default: true, optional :include_ancestor_groups, type: Boolean, default: true,
desc: 'Include ancestor groups' desc: 'Include ancestor groups'
optional :include_descendant_groups, type: Boolean, default: false,
desc: 'Include descendant groups. This feature was added in GitLab 13.6'
optional :only_group_labels, type: Boolean, default: true,
desc: 'Toggle to include only group labels or also project labels. This feature was added in GitLab 13.6'
end end
get ':id/labels/:name' do get ':id/labels/:name' do
get_label(user_group, Entities::GroupLabel, include_ancestor_groups: params[:include_ancestor_groups]) get_label(user_group, Entities::GroupLabel, declared_params)
end end
desc 'Create a new label' do desc 'Create a new label' do
......
...@@ -89,16 +89,15 @@ module API ...@@ -89,16 +89,15 @@ module API
@project ||= find_project!(params[:id]) @project ||= find_project!(params[:id])
end end
def available_labels_for(label_parent, include_ancestor_groups: true) def available_labels_for(label_parent, params = { include_ancestor_groups: true, only_group_labels: true })
search_params = { include_ancestor_groups: include_ancestor_groups }
if label_parent.is_a?(Project) if label_parent.is_a?(Project)
search_params[:project_id] = label_parent.id params.delete(:only_group_labels)
params[:project_id] = label_parent.id
else else
search_params.merge!(group_id: label_parent.id, only_group_labels: true) params[:group_id] = label_parent.id
end end
LabelsFinder.new(current_user, search_params).execute LabelsFinder.new(current_user, params).execute
end end
def find_user(id) def find_user(id)
......
...@@ -28,23 +28,23 @@ module API ...@@ -28,23 +28,23 @@ module API
at_least_one_of :new_name, :color, :description at_least_one_of :new_name, :color, :description
end end
def find_label(parent, id_or_title, include_ancestor_groups: true) def find_label(parent, id_or_title, params = { include_ancestor_groups: true })
labels = available_labels_for(parent, include_ancestor_groups: include_ancestor_groups) labels = available_labels_for(parent, params)
label = labels.find_by_id(id_or_title) || labels.find_by_title(id_or_title) label = labels.find_by_id(id_or_title) || labels.find_by_title(id_or_title)
label || not_found!('Label') label || not_found!('Label')
end end
def get_labels(parent, entity, include_ancestor_groups: true) def get_labels(parent, entity, params = {})
present paginate(available_labels_for(parent, include_ancestor_groups: include_ancestor_groups)), present paginate(available_labels_for(parent, params)),
with: entity, with: entity,
current_user: current_user, current_user: current_user,
parent: parent, parent: parent,
with_counts: params[:with_counts] with_counts: params[:with_counts]
end end
def get_label(parent, entity, include_ancestor_groups: true) def get_label(parent, entity, params = {})
label = find_label(parent, params_id_or_title, include_ancestor_groups: include_ancestor_groups) label = find_label(parent, params_id_or_title, params)
present label, with: entity, current_user: current_user, parent: parent present label, with: entity, current_user: current_user, parent: parent
end end
......
...@@ -19,10 +19,12 @@ module API ...@@ -19,10 +19,12 @@ module API
desc: 'Include issue and merge request counts' desc: 'Include issue and merge request counts'
optional :include_ancestor_groups, type: Boolean, default: true, optional :include_ancestor_groups, type: Boolean, default: true,
desc: 'Include ancestor groups' desc: 'Include ancestor groups'
optional :search, type: String,
desc: 'Keyword to filter labels by. This feature was added in GitLab 13.6'
use :pagination use :pagination
end end
get ':id/labels' do get ':id/labels' do
get_labels(user_project, Entities::ProjectLabel, include_ancestor_groups: params[:include_ancestor_groups]) get_labels(user_project, Entities::ProjectLabel, declared_params)
end end
desc 'Get a single label' do desc 'Get a single label' do
...@@ -34,7 +36,7 @@ module API ...@@ -34,7 +36,7 @@ module API
desc: 'Include ancestor groups' desc: 'Include ancestor groups'
end end
get ':id/labels/:name' do get ':id/labels/:name' do
get_label(user_project, Entities::ProjectLabel, include_ancestor_groups: params[:include_ancestor_groups]) get_label(user_project, Entities::ProjectLabel, declared_params)
end end
desc 'Create a new label' do desc 'Create a new label' do
......
...@@ -7,20 +7,22 @@ RSpec.describe API::GroupLabels do ...@@ -7,20 +7,22 @@ RSpec.describe API::GroupLabels do
let(:group) { create(:group) } let(:group) { create(:group) }
let(:subgroup) { create(:group, parent: group) } let(:subgroup) { create(:group, parent: group) }
let!(:group_member) { create(:group_member, group: group, user: user) } let!(:group_member) { create(:group_member, group: group, user: user) }
let!(:group_label1) { create(:group_label, title: 'feature', group: group) } let!(:group_label1) { create(:group_label, title: 'feature-label', group: group) }
let!(:group_label2) { create(:group_label, title: 'bug', group: group) } let!(:group_label2) { create(:group_label, title: 'bug', group: group) }
let!(:subgroup_label) { create(:group_label, title: 'support', group: subgroup) } let!(:subgroup_label) { create(:group_label, title: 'support-label', group: subgroup) }
describe 'GET :id/labels' do describe 'GET :id/labels' do
it 'returns all available labels for the group' do context 'get current group labels' do
get api("/groups/#{group.id}/labels", user) let(:request) { get api("/groups/#{group.id}/labels", user) }
let(:expected_labels) { [group_label1.name, group_label2.name] }
expect(response).to have_gitlab_http_status(:ok) it_behaves_like 'fetches labels'
expect(response).to include_pagination_headers
expect(json_response).to be_an Array context 'when search param is provided' do
expect(json_response).to all(match_schema('public_api/v4/labels/label')) let(:request) { get api("/groups/#{group.id}/labels?search=lab", user) }
expect(json_response.size).to eq(2) let(:expected_labels) { [group_label1.name] }
expect(json_response.map {|r| r['name'] }).to contain_exactly('feature', 'bug')
it_behaves_like 'fetches labels'
end end
context 'when the with_counts parameter is set' do context 'when the with_counts parameter is set' do
...@@ -35,32 +37,67 @@ RSpec.describe API::GroupLabels do ...@@ -35,32 +37,67 @@ RSpec.describe API::GroupLabels do
expect(json_response.map { |r| r['open_issues_count'] }).to contain_exactly(0, 0) expect(json_response.map { |r| r['open_issues_count'] }).to contain_exactly(0, 0)
end end
end end
context 'when include_descendant_groups param is provided' do
let!(:project) { create(:project, group: group) }
let!(:project_label1) { create(:label, title: 'project-label1', project: project, priority: 3) }
let!(:project_label2) { create(:label, title: 'project-bug', project: project) }
let(:request) { get api("/groups/#{group.id}/labels", user), params: { include_descendant_groups: true } }
let(:expected_labels) { [group_label1.name, group_label2.name, subgroup_label.name] }
it_behaves_like 'fetches labels'
context 'when search param is provided' do
let(:request) { get api("/groups/#{group.id}/labels", user), params: { search: 'lab', include_descendant_groups: true } }
let(:expected_labels) { [group_label1.name, subgroup_label.name] }
it_behaves_like 'fetches labels'
end
context 'when only_group_labels param is false' do
let(:request) { get api("/groups/#{group.id}/labels", user), params: { include_descendant_groups: true, only_group_labels: false } }
let(:expected_labels) { [group_label1.name, group_label2.name, subgroup_label.name, project_label1.name, project_label2.name] }
it_behaves_like 'fetches labels'
context 'when search param is provided' do
let(:request) { get api("/groups/#{group.id}/labels", user), params: { search: 'lab', include_descendant_groups: true, only_group_labels: false } }
let(:expected_labels) { [group_label1.name, subgroup_label.name, project_label1.name] }
it_behaves_like 'fetches labels'
end
end
end
end end
describe 'GET :subgroup_id/labels' do describe 'with subgroup labels' do
context 'when the include_ancestor_groups parameter is not set' do context 'when the include_ancestor_groups parameter is not set' do
it 'returns all available labels for the group and ancestor groups' do let(:request) { get api("/groups/#{subgroup.id}/labels", user) }
get api("/groups/#{subgroup.id}/labels", user) let(:expected_labels) { [group_label1.name, group_label2.name, subgroup_label.name] }
expect(response).to have_gitlab_http_status(:ok) it_behaves_like 'fetches labels'
expect(response).to include_pagination_headers
expect(json_response).to be_an Array context 'when search param is provided' do
expect(json_response).to all(match_schema('public_api/v4/labels/label')) let(:request) { get api("/groups/#{subgroup.id}/labels?search=lab", user) }
expect(json_response.size).to eq(3) let(:expected_labels) { [group_label1.name, subgroup_label.name] }
expect(json_response.map {|r| r['name'] }).to contain_exactly('feature', 'bug', 'support')
it_behaves_like 'fetches labels'
end end
end end
context 'when the include_ancestor_groups parameter is set to false' do context 'when the include_ancestor_groups parameter is set to false' do
it 'returns all available labels for the group but not for ancestor groups' do let(:request) { get api("/groups/#{subgroup.id}/labels", user), params: { include_ancestor_groups: false } }
get api("/groups/#{subgroup.id}/labels", user), params: { include_ancestor_groups: false } let(:expected_labels) { [subgroup_label.name] }
expect(response).to have_gitlab_http_status(:ok) it_behaves_like 'fetches labels'
expect(response).to include_pagination_headers
expect(json_response).to be_an Array context 'when search param is provided' do
expect(json_response).to all(match_schema('public_api/v4/labels/label')) let(:request) { get api("/groups/#{subgroup.id}/labels?search=lab", user), params: { include_ancestor_groups: false } }
expect(json_response.size).to eq(1) let(:expected_labels) { [subgroup_label.name] }
expect(json_response.map {|r| r['name'] }).to contain_exactly('support')
it_behaves_like 'fetches labels'
end
end end
end end
end end
...@@ -223,7 +260,7 @@ RSpec.describe API::GroupLabels do ...@@ -223,7 +260,7 @@ RSpec.describe API::GroupLabels do
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(subgroup.labels[0].name).to eq('New Label') expect(subgroup.labels[0].name).to eq('New Label')
expect(group_label1.name).to eq('feature') expect(group_label1.name).to eq(group_label1.title)
end end
it 'returns 404 if label does not exist' do it 'returns 404 if label does not exist' do
...@@ -278,7 +315,7 @@ RSpec.describe API::GroupLabels do ...@@ -278,7 +315,7 @@ RSpec.describe API::GroupLabels do
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(subgroup.labels[0].name).to eq('New Label') expect(subgroup.labels[0].name).to eq('New Label')
expect(group_label1.name).to eq('feature') expect(group_label1.name).to eq(group_label1.title)
end end
it 'returns 404 if label does not exist' do it 'returns 404 if label does not exist' do
......
...@@ -178,8 +178,8 @@ RSpec.describe API::Labels do ...@@ -178,8 +178,8 @@ RSpec.describe API::Labels do
end end
describe 'GET /projects/:id/labels' do describe 'GET /projects/:id/labels' do
let(:group) { create(:group) } let_it_be(:group) { create(:group) }
let!(:group_label) { create(:group_label, title: 'feature', group: group) } let_it_be(:group_label) { create(:group_label, title: 'feature label', group: group) }
before do before do
project.update!(group: group) project.update!(group: group)
...@@ -250,49 +250,41 @@ RSpec.describe API::Labels do ...@@ -250,49 +250,41 @@ RSpec.describe API::Labels do
end end
end end
context 'when the include_ancestor_groups parameter is not set' do context 'with subgroups' do
let(:group) { create(:group) } let_it_be(:subgroup) { create(:group, parent: group) }
let!(:group_label) { create(:group_label, title: 'feature', group: group) } let_it_be(:subgroup_label) { create(:group_label, title: 'support label', group: subgroup) }
let(:subgroup) { create(:group, parent: group) }
let!(:subgroup_label) { create(:group_label, title: 'support', group: subgroup) }
before do before do
subgroup.add_owner(user) subgroup.add_owner(user)
project.update!(group: subgroup) project.update!(group: subgroup)
end end
it 'returns all available labels for the project, parent group and ancestor groups' do context 'when the include_ancestor_groups parameter is not set' do
get api("/projects/#{project.id}/labels", user) let(:request) { get api("/projects/#{project.id}/labels", user) }
let(:expected_labels) { [priority_label.name, group_label.name, subgroup_label.name, label1.name] }
expect(response).to have_gitlab_http_status(:ok) it_behaves_like 'fetches labels'
expect(response).to include_pagination_headers
expect(json_response).to be_an Array context 'when search param is provided' do
expect(json_response).to all(match_schema('public_api/v4/labels/label')) let(:request) { get api("/projects/#{project.id}/labels?search=lab", user) }
expect(json_response.size).to eq(4) let(:expected_labels) { [group_label.name, subgroup_label.name, label1.name] }
expect(json_response.map {|r| r['name'] }).to contain_exactly(group_label.name, subgroup_label.name, priority_label.name, label1.name)
it_behaves_like 'fetches labels'
end end
end end
context 'when the include_ancestor_groups parameter is set to false' do context 'when the include_ancestor_groups parameter is set to false' do
let(:group) { create(:group) } let(:request) { get api("/projects/#{project.id}/labels", user), params: { include_ancestor_groups: false } }
let!(:group_label) { create(:group_label, title: 'feature', group: group) } let(:expected_labels) { [subgroup_label.name, priority_label.name, label1.name] }
let(:subgroup) { create(:group, parent: group) }
let!(:subgroup_label) { create(:group_label, title: 'support', group: subgroup) }
before do it_behaves_like 'fetches labels'
subgroup.add_owner(user)
project.update!(group: subgroup)
end
it 'returns all available labels for the project and the parent group only' do context 'when search param is provided' do
get api("/projects/#{project.id}/labels", user), params: { include_ancestor_groups: false } let(:request) { get api("/projects/#{project.id}/labels?search=lab", user), params: { include_ancestor_groups: false } }
let(:expected_labels) { [subgroup_label.name, label1.name] }
expect(response).to have_gitlab_http_status(:ok) it_behaves_like 'fetches labels'
expect(response).to include_pagination_headers end
expect(json_response).to be_an Array
expect(json_response).to all(match_schema('public_api/v4/labels/label'))
expect(json_response.size).to eq(3)
expect(json_response.map {|r| r['name'] }).to contain_exactly(subgroup_label.name, priority_label.name, label1.name)
end end
end end
end end
...@@ -513,7 +505,7 @@ RSpec.describe API::Labels do ...@@ -513,7 +505,7 @@ RSpec.describe API::Labels do
end end
describe 'PUT /projects/:id/labels/promote' do describe 'PUT /projects/:id/labels/promote' do
let(:group) { create(:group) } let_it_be(:group) { create(:group) }
before do before do
group.add_owner(user) group.add_owner(user)
......
# frozen_string_literal: true
RSpec.shared_examples 'fetches labels' do
it 'returns correct labels' do
request
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response).to all(match_schema('public_api/v4/labels/label'))
expect(json_response.size).to eq(expected_labels.size)
expect(json_response.map {|r| r['name'] }).to match_array(expected_labels)
end
end
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