Commit e6dc5168 authored by Sean McGivern's avatar Sean McGivern

Remove label issue and MR counts from default API responses

These counts significantly increase the load time for these
requests. Users can now opt in to receiving the counts by setting
`with_counts=true` in requests. This is a breaking change, but hopefully
a fairly minor one.
parent 26087322
---
title: Remove counts from default labels API responses
merge_request: 31543
author:
type: changed
...@@ -15,9 +15,10 @@ GET /groups/:id/labels ...@@ -15,9 +15,10 @@ GET /groups/:id/labels
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `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-ce/merge_requests/31543))_ |
```bash ```bash
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/labels curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/labels?with_counts=true
``` ```
Example response: Example response:
......
...@@ -11,9 +11,10 @@ GET /projects/:id/labels ...@@ -11,9 +11,10 @@ GET /projects/:id/labels
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ------- | -------- | --------------------- | | --------- | ------- | -------- | --------------------- |
| `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-ce/merge_requests/31543))_ |
```bash ```bash
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/labels curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/labels?with_counts=true
``` ```
Example response: Example response:
......
...@@ -1085,6 +1085,7 @@ module API ...@@ -1085,6 +1085,7 @@ module API
end end
class Label < LabelBasic class Label < LabelBasic
with_options if: lambda { |_, options| options[:with_counts] } do
expose :open_issues_count do |label, options| expose :open_issues_count do |label, options|
label.open_issues_count(options[:current_user]) label.open_issues_count(options[:current_user])
end end
...@@ -1096,6 +1097,7 @@ module API ...@@ -1096,6 +1097,7 @@ module API
expose :open_merge_requests_count do |label, options| expose :open_merge_requests_count do |label, options|
label.open_merge_requests_count(options[:current_user]) label.open_merge_requests_count(options[:current_user])
end end
end
expose :subscribed do |label, options| expose :subscribed do |label, options|
label.subscribed?(options[:current_user], options[:parent]) label.subscribed?(options[:current_user], options[:parent])
......
...@@ -16,6 +16,8 @@ module API ...@@ -16,6 +16,8 @@ module API
success Entities::GroupLabel success Entities::GroupLabel
end end
params do params do
optional :with_counts, type: Boolean, default: false,
desc: 'Include issue and merge request counts'
use :pagination use :pagination
end end
get ':id/labels' do get ':id/labels' do
......
...@@ -19,7 +19,11 @@ module API ...@@ -19,7 +19,11 @@ module API
end end
def get_labels(parent, entity) def get_labels(parent, entity)
present paginate(available_labels_for(parent)), with: entity, current_user: current_user, parent: parent present paginate(available_labels_for(parent)),
with: entity,
current_user: current_user,
parent: parent,
with_counts: params[:with_counts]
end end
def create_label(parent, entity) def create_label(parent, entity)
......
...@@ -15,6 +15,8 @@ module API ...@@ -15,6 +15,8 @@ module API
success Entities::ProjectLabel success Entities::ProjectLabel
end end
params do params do
optional :with_counts, type: Boolean, default: false,
desc: 'Include issue and merge request counts'
use :pagination use :pagination
end end
get ':id/labels' do get ':id/labels' do
......
{
"type": "array",
"items": {
"type": "object",
"properties" : {
"id" : { "type": "integer" },
"name" : { "type": "string "},
"color" : { "type": "string "},
"text_color" : { "type": "string "},
"description" : { "type": "string "},
"open_issues_count" : { "type": "integer "},
"closed_issues_count" : { "type": "integer "},
"open_merge_requests_count" : { "type": "integer "},
"subscribed" : { "type": "boolean" },
"priority" : { "type": "null" }
},
"additionalProperties": false
}
}
{
"type": "object",
"properties": {
"id": { "type": "integer" },
"name": { "type": "string" },
"color": { "type": "string" },
"text_color": { "type": "string" },
"description": { "type": ["string", "null"] },
"subscribed": { "type": "boolean" }
}
}
{
"type": "object",
"properties": {
"allOf": [
{ "$ref": "label.json" },
{
"type": "object",
"properties": {
"open_issues_count": { "type": "integer" },
"closed_issues_count": { "type": "integer" },
"open_merge_requests_count": { "type": "integer" }
}
}
]
}
}
{
"type": "object",
"properties": {
"allOf": [
{ "$ref": "label.json" },
{
"type": "object",
"properties": {
"priority": { "type": ["integer", "null"] },
"is_project_label": { "type": "boolean" }
}
}
]
}
}
{
"type": "object",
"properties": {
"allOf": [
{ "$ref": "project_label.json" },
{ "$ref": "label_with_counts.json" }
]
}
}
...@@ -14,12 +14,25 @@ describe API::GroupLabels do ...@@ -14,12 +14,25 @@ describe API::GroupLabels do
get api("/groups/#{group.id}/labels", user) get api("/groups/#{group.id}/labels", user)
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
expect(response).to match_response_schema('public_api/v4/group_labels')
expect(response).to include_pagination_headers expect(response).to include_pagination_headers
expect(json_response).to be_an Array 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.size).to eq(2)
expect(json_response.map {|r| r['name'] }).to contain_exactly('feature', 'bug') expect(json_response.map {|r| r['name'] }).to contain_exactly('feature', 'bug')
end 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(200)
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
end end
describe 'POST /groups/:id/labels' do describe 'POST /groups/:id/labels' do
......
...@@ -11,65 +11,76 @@ describe API::Labels do ...@@ -11,65 +11,76 @@ describe API::Labels do
end end
describe 'GET /projects/:id/labels' do describe 'GET /projects/:id/labels' do
let(:group) { create(:group) }
let!(:group_label) { create(:group_label, title: 'feature', group: group) }
before do
project.update!(group: group)
end
it 'returns all available labels to the project' do it 'returns all available labels to the project' do
group = create(:group) get api("/projects/#{project.id}/labels", user)
group_label = create(:group_label, title: 'feature', group: group)
project.update(group: group) expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to all(match_schema('public_api/v4/labels/project_label'))
expect(json_response.size).to eq(3)
expect(json_response.map { |l| l['name'] }).to match_array([group_label.name, priority_label.name, label1.name])
end
context 'when the with_counts parameter is set' do
before do
create(:labeled_issue, project: project, labels: [group_label], author: user) create(:labeled_issue, project: project, labels: [group_label], author: user)
create(:labeled_issue, project: project, labels: [label1], author: user, state: :closed) create(:labeled_issue, project: project, labels: [label1], author: user, state: :closed)
create(:labeled_merge_request, labels: [priority_label], author: user, source_project: project ) create(:labeled_merge_request, labels: [priority_label], author: user, source_project: project )
end
expected_keys = %w( it 'includes counts in the response' do
id name color text_color description get api("/projects/#{project.id}/labels", user), params: { with_counts: true }
open_issues_count closed_issues_count open_merge_requests_count
subscribed priority is_project_label
)
get api("/projects/#{project.id}/labels", user)
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers expect(response).to include_pagination_headers
expect(json_response).to be_an Array expect(json_response).to all(match_schema('public_api/v4/labels/project_label_with_counts'))
expect(json_response.size).to eq(3) expect(json_response.size).to eq(3)
expect(json_response.first.keys).to match_array expected_keys
expect(json_response.map { |l| l['name'] }).to match_array([group_label.name, priority_label.name, label1.name]) expect(json_response.map { |l| l['name'] }).to match_array([group_label.name, priority_label.name, label1.name])
label1_response = json_response.find { |l| l['name'] == label1.title } label1_response = json_response.find { |l| l['name'] == label1.title }
group_label_response = json_response.find { |l| l['name'] == group_label.title } group_label_response = json_response.find { |l| l['name'] == group_label.title }
priority_label_response = json_response.find { |l| l['name'] == priority_label.title } priority_label_response = json_response.find { |l| l['name'] == priority_label.title }
expect(label1_response['open_issues_count']).to eq(0) expect(label1_response).to include('open_issues_count' => 0,
expect(label1_response['closed_issues_count']).to eq(1) 'closed_issues_count' => 1,
expect(label1_response['open_merge_requests_count']).to eq(0) 'open_merge_requests_count' => 0,
expect(label1_response['name']).to eq(label1.name) 'name' => label1.name,
expect(label1_response['color']).to be_present 'description' => nil,
expect(label1_response['text_color']).to be_present 'color' => a_string_matching(/^#\h{6}$/),
expect(label1_response['description']).to be_nil 'text_color' => a_string_matching(/^#\h{6}$/),
expect(label1_response['priority']).to be_nil 'priority' => nil,
expect(label1_response['subscribed']).to be_falsey 'subscribed' => false,
expect(label1_response['is_project_label']).to be_truthy 'is_project_label' => true)
expect(group_label_response['open_issues_count']).to eq(1) expect(group_label_response).to include('open_issues_count' => 1,
expect(group_label_response['closed_issues_count']).to eq(0) 'closed_issues_count' => 0,
expect(group_label_response['open_merge_requests_count']).to eq(0) 'open_merge_requests_count' => 0,
expect(group_label_response['name']).to eq(group_label.name) 'name' => group_label.name,
expect(group_label_response['color']).to be_present 'description' => nil,
expect(group_label_response['text_color']).to be_present 'color' => a_string_matching(/^#\h{6}$/),
expect(group_label_response['description']).to be_nil 'text_color' => a_string_matching(/^#\h{6}$/),
expect(group_label_response['priority']).to be_nil 'priority' => nil,
expect(group_label_response['subscribed']).to be_falsey 'subscribed' => false,
expect(group_label_response['is_project_label']).to be_falsey 'is_project_label' => false)
expect(priority_label_response['open_issues_count']).to eq(0) expect(priority_label_response).to include('open_issues_count' => 0,
expect(priority_label_response['closed_issues_count']).to eq(0) 'closed_issues_count' => 0,
expect(priority_label_response['open_merge_requests_count']).to eq(1) 'open_merge_requests_count' => 1,
expect(priority_label_response['name']).to eq(priority_label.name) 'name' => priority_label.name,
expect(priority_label_response['color']).to be_present 'description' => nil,
expect(priority_label_response['text_color']).to be_present 'color' => a_string_matching(/^#\h{6}$/),
expect(priority_label_response['description']).to be_nil 'text_color' => a_string_matching(/^#\h{6}$/),
expect(priority_label_response['priority']).to eq(3) 'priority' => 3,
expect(priority_label_response['subscribed']).to be_falsey 'subscribed' => false,
expect(priority_label_response['is_project_label']).to be_truthy 'is_project_label' => true)
end
end end
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