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
...@@ -12,12 +12,13 @@ Get all labels for a given group. ...@@ -12,12 +12,13 @@ Get all labels for a given group.
GET /groups/:id/labels 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:
......
...@@ -8,12 +8,13 @@ Get all labels for a given project. ...@@ -8,12 +8,13 @@ Get all labels for a given project.
GET /projects/:id/labels 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,16 +1085,18 @@ module API ...@@ -1085,16 +1085,18 @@ module API
end end
class Label < LabelBasic class Label < LabelBasic
expose :open_issues_count do |label, options| with_options if: lambda { |_, options| options[:with_counts] } do
label.open_issues_count(options[:current_user]) expose :open_issues_count do |label, options|
end label.open_issues_count(options[:current_user])
end
expose :closed_issues_count do |label, options| expose :closed_issues_count do |label, options|
label.closed_issues_count(options[:current_user]) label.closed_issues_count(options[:current_user])
end end
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|
......
...@@ -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
it 'returns all available labels to the project' do let(:group) { create(:group) }
group = create(:group) let!(:group_label) { create(:group_label, title: 'feature', group: group) }
group_label = create(:group_label, title: 'feature', group: group)
project.update(group: group)
create(:labeled_issue, project: project, labels: [group_label], author: user)
create(:labeled_issue, project: project, labels: [label1], author: user, state: :closed)
create(:labeled_merge_request, labels: [priority_label], author: user, source_project: project )
expected_keys = %w( before do
id name color text_color description project.update!(group: group)
open_issues_count closed_issues_count open_merge_requests_count end
subscribed priority is_project_label
)
it 'returns all available labels to the project' do
get api("/projects/#{project.id}/labels", user) 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'))
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])
end
label1_response = json_response.find { |l| l['name'] == label1.title } context 'when the with_counts parameter is set' do
group_label_response = json_response.find { |l| l['name'] == group_label.title } before do
priority_label_response = json_response.find { |l| l['name'] == priority_label.title } create(:labeled_issue, project: project, labels: [group_label], author: user)
create(:labeled_issue, project: project, labels: [label1], author: user, state: :closed)
expect(label1_response['open_issues_count']).to eq(0) create(:labeled_merge_request, labels: [priority_label], author: user, source_project: project )
expect(label1_response['closed_issues_count']).to eq(1) end
expect(label1_response['open_merge_requests_count']).to eq(0)
expect(label1_response['name']).to eq(label1.name) it 'includes counts in the response' do
expect(label1_response['color']).to be_present get api("/projects/#{project.id}/labels", user), params: { with_counts: true }
expect(label1_response['text_color']).to be_present
expect(label1_response['description']).to be_nil expect(response).to have_gitlab_http_status(200)
expect(label1_response['priority']).to be_nil expect(response).to include_pagination_headers
expect(label1_response['subscribed']).to be_falsey expect(json_response).to all(match_schema('public_api/v4/labels/project_label_with_counts'))
expect(label1_response['is_project_label']).to be_truthy 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])
expect(group_label_response['open_issues_count']).to eq(1)
expect(group_label_response['closed_issues_count']).to eq(0) label1_response = json_response.find { |l| l['name'] == label1.title }
expect(group_label_response['open_merge_requests_count']).to eq(0) group_label_response = json_response.find { |l| l['name'] == group_label.title }
expect(group_label_response['name']).to eq(group_label.name) priority_label_response = json_response.find { |l| l['name'] == priority_label.title }
expect(group_label_response['color']).to be_present
expect(group_label_response['text_color']).to be_present expect(label1_response).to include('open_issues_count' => 0,
expect(group_label_response['description']).to be_nil 'closed_issues_count' => 1,
expect(group_label_response['priority']).to be_nil 'open_merge_requests_count' => 0,
expect(group_label_response['subscribed']).to be_falsey 'name' => label1.name,
expect(group_label_response['is_project_label']).to be_falsey 'description' => nil,
'color' => a_string_matching(/^#\h{6}$/),
expect(priority_label_response['open_issues_count']).to eq(0) 'text_color' => a_string_matching(/^#\h{6}$/),
expect(priority_label_response['closed_issues_count']).to eq(0) 'priority' => nil,
expect(priority_label_response['open_merge_requests_count']).to eq(1) 'subscribed' => false,
expect(priority_label_response['name']).to eq(priority_label.name) 'is_project_label' => true)
expect(priority_label_response['color']).to be_present
expect(priority_label_response['text_color']).to be_present expect(group_label_response).to include('open_issues_count' => 1,
expect(priority_label_response['description']).to be_nil 'closed_issues_count' => 0,
expect(priority_label_response['priority']).to eq(3) 'open_merge_requests_count' => 0,
expect(priority_label_response['subscribed']).to be_falsey 'name' => group_label.name,
expect(priority_label_response['is_project_label']).to be_truthy 'description' => nil,
'color' => a_string_matching(/^#\h{6}$/),
'text_color' => a_string_matching(/^#\h{6}$/),
'priority' => nil,
'subscribed' => false,
'is_project_label' => false)
expect(priority_label_response).to include('open_issues_count' => 0,
'closed_issues_count' => 0,
'open_merge_requests_count' => 1,
'name' => priority_label.name,
'description' => nil,
'color' => a_string_matching(/^#\h{6}$/),
'text_color' => a_string_matching(/^#\h{6}$/),
'priority' => 3,
'subscribed' => false,
'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