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
| `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))_ |
| `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
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
| `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. |
| `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
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
| `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))_ |
| `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
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
desc: 'Include issue and merge request counts'
optional :include_ancestor_groups, type: Boolean, default: true,
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
end
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
desc 'Get a single label' do
......@@ -33,9 +39,13 @@ module API
params do
optional :include_ancestor_groups, type: Boolean, default: true,
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
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
desc 'Create a new label' do
......
......@@ -89,16 +89,15 @@ module API
@project ||= find_project!(params[:id])
end
def available_labels_for(label_parent, include_ancestor_groups: true)
search_params = { include_ancestor_groups: include_ancestor_groups }
def available_labels_for(label_parent, params = { include_ancestor_groups: true, only_group_labels: true })
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
search_params.merge!(group_id: label_parent.id, only_group_labels: true)
params[:group_id] = label_parent.id
end
LabelsFinder.new(current_user, search_params).execute
LabelsFinder.new(current_user, params).execute
end
def find_user(id)
......
......@@ -28,23 +28,23 @@ module API
at_least_one_of :new_name, :color, :description
end
def find_label(parent, id_or_title, include_ancestor_groups: true)
labels = available_labels_for(parent, include_ancestor_groups: include_ancestor_groups)
def find_label(parent, id_or_title, params = { include_ancestor_groups: true })
labels = available_labels_for(parent, params)
label = labels.find_by_id(id_or_title) || labels.find_by_title(id_or_title)
label || not_found!('Label')
end
def get_labels(parent, entity, include_ancestor_groups: true)
present paginate(available_labels_for(parent, include_ancestor_groups: include_ancestor_groups)),
def get_labels(parent, entity, params = {})
present paginate(available_labels_for(parent, params)),
with: entity,
current_user: current_user,
parent: parent,
with_counts: params[:with_counts]
end
def get_label(parent, entity, include_ancestor_groups: true)
label = find_label(parent, params_id_or_title, include_ancestor_groups: include_ancestor_groups)
def get_label(parent, entity, params = {})
label = find_label(parent, params_id_or_title, params)
present label, with: entity, current_user: current_user, parent: parent
end
......
......@@ -19,10 +19,12 @@ module API
desc: 'Include issue and merge request counts'
optional :include_ancestor_groups, type: Boolean, default: true,
desc: 'Include ancestor groups'
optional :search, type: String,
desc: 'Keyword to filter labels by. This feature was added in GitLab 13.6'
use :pagination
end
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
desc 'Get a single label' do
......@@ -34,7 +36,7 @@ module API
desc: 'Include ancestor groups'
end
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
desc 'Create a new label' do
......
......@@ -7,60 +7,97 @@ RSpec.describe API::GroupLabels do
let(:group) { create(:group) }
let(:subgroup) { create(:group, parent: group) }
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!(: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
it 'returns all available labels for the group' do
get api("/groups/#{group.id}/labels", user)
context 'get current group labels' do
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)
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(2)
expect(json_response.map {|r| r['name'] }).to contain_exactly('feature', 'bug')
end
context 'when the with_counts parameter is set' do
it 'includes counts in the response' do
get api("/groups/#{group.id}/labels", user), params: { with_counts: true }
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_with_counts'))
expect(json_response.size).to eq(2)
expect(json_response.map { |r| r['open_issues_count'] }).to contain_exactly(0, 0)
it_behaves_like 'fetches labels'
context 'when search param is provided' do
let(:request) { get api("/groups/#{group.id}/labels?search=lab", user) }
let(:expected_labels) { [group_label1.name] }
it_behaves_like 'fetches labels'
end
end
end
describe 'GET :subgroup_id/labels' do
context 'when the include_ancestor_groups parameter is not set' do
it 'returns all available labels for the group and ancestor groups' do
get api("/groups/#{subgroup.id}/labels", user)
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(3)
expect(json_response.map {|r| r['name'] }).to contain_exactly('feature', 'bug', 'support')
context 'when the with_counts parameter is set' do
it 'includes counts in the response' do
get api("/groups/#{group.id}/labels", user), params: { with_counts: true }
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_with_counts'))
expect(json_response.size).to eq(2)
expect(json_response.map { |r| r['open_issues_count'] }).to contain_exactly(0, 0)
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
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
get api("/groups/#{subgroup.id}/labels", user), params: { include_ancestor_groups: false }
describe 'with subgroup labels' do
context 'when the include_ancestor_groups parameter is not set' do
let(:request) { get api("/groups/#{subgroup.id}/labels", user) }
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/#{subgroup.id}/labels?search=lab", user) }
let(:expected_labels) { [group_label1.name, subgroup_label.name] }
it_behaves_like 'fetches labels'
end
end
context 'when the include_ancestor_groups parameter is set to false' do
let(:request) { get api("/groups/#{subgroup.id}/labels", user), params: { include_ancestor_groups: false } }
let(:expected_labels) { [subgroup_label.name] }
it_behaves_like 'fetches labels'
context 'when search param is provided' do
let(:request) { get api("/groups/#{subgroup.id}/labels?search=lab", user), params: { include_ancestor_groups: false } }
let(:expected_labels) { [subgroup_label.name] }
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(1)
expect(json_response.map {|r| r['name'] }).to contain_exactly('support')
it_behaves_like 'fetches labels'
end
end
end
end
......@@ -223,7 +260,7 @@ RSpec.describe API::GroupLabels do
expect(response).to have_gitlab_http_status(:ok)
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
it 'returns 404 if label does not exist' do
......@@ -278,7 +315,7 @@ RSpec.describe API::GroupLabels do
expect(response).to have_gitlab_http_status(:ok)
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
it 'returns 404 if label does not exist' do
......
......@@ -178,8 +178,8 @@ RSpec.describe API::Labels do
end
describe 'GET /projects/:id/labels' do
let(:group) { create(:group) }
let!(:group_label) { create(:group_label, title: 'feature', group: group) }
let_it_be(:group) { create(:group) }
let_it_be(:group_label) { create(:group_label, title: 'feature label', group: group) }
before do
project.update!(group: group)
......@@ -250,49 +250,41 @@ RSpec.describe API::Labels do
end
end
context 'when the include_ancestor_groups parameter is not set' do
let(:group) { create(:group) }
let!(:group_label) { create(:group_label, title: 'feature', group: group) }
let(:subgroup) { create(:group, parent: group) }
let!(:subgroup_label) { create(:group_label, title: 'support', group: subgroup) }
context 'with subgroups' do
let_it_be(:subgroup) { create(:group, parent: group) }
let_it_be(:subgroup_label) { create(:group_label, title: 'support label', group: subgroup) }
before do
subgroup.add_owner(user)
project.update!(group: subgroup)
end
it 'returns all available labels for the project, parent group and ancestor groups' do
get api("/projects/#{project.id}/labels", user)
context 'when the include_ancestor_groups parameter is not set' do
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)
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(4)
expect(json_response.map {|r| r['name'] }).to contain_exactly(group_label.name, subgroup_label.name, priority_label.name, label1.name)
end
end
it_behaves_like 'fetches labels'
context 'when the include_ancestor_groups parameter is set to false' do
let(:group) { create(:group) }
let!(:group_label) { create(:group_label, title: 'feature', group: group) }
let(:subgroup) { create(:group, parent: group) }
let!(:subgroup_label) { create(:group_label, title: 'support', group: subgroup) }
context 'when search param is provided' do
let(:request) { get api("/projects/#{project.id}/labels?search=lab", user) }
let(:expected_labels) { [group_label.name, subgroup_label.name, label1.name] }
before do
subgroup.add_owner(user)
project.update!(group: subgroup)
it_behaves_like 'fetches labels'
end
end
it 'returns all available labels for the project and the parent group only' do
get api("/projects/#{project.id}/labels", user), params: { include_ancestor_groups: false }
context 'when the include_ancestor_groups parameter is set to false' do
let(:request) { get api("/projects/#{project.id}/labels", user), params: { include_ancestor_groups: false } }
let(:expected_labels) { [subgroup_label.name, priority_label.name, label1.name] }
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(3)
expect(json_response.map {|r| r['name'] }).to contain_exactly(subgroup_label.name, priority_label.name, label1.name)
it_behaves_like 'fetches labels'
context 'when search param is provided' do
let(:request) { get api("/projects/#{project.id}/labels?search=lab", user), params: { include_ancestor_groups: false } }
let(:expected_labels) { [subgroup_label.name, label1.name] }
it_behaves_like 'fetches labels'
end
end
end
end
......@@ -513,7 +505,7 @@ RSpec.describe API::Labels do
end
describe 'PUT /projects/:id/labels/promote' do
let(:group) { create(:group) }
let_it_be(:group) { create(:group) }
before do
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