Commit 9ea658a6 authored by Alexandru Croitor's avatar Alexandru Croitor Committed by Alexandru Croitor

Extend Project and Group Labels API

Add support for search in Project and Group Labels API, as well as
support to include descendant groups and project labels within
Group Labels API
parent b0961026
---
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