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.
This commit is contained in:
Sean McGivern 2019-08-06 17:27:46 +01:00
parent 2608732271
commit e6dc5168b8
14 changed files with 158 additions and 85 deletions

View file

@ -0,0 +1,5 @@
---
title: Remove counts from default labels API responses
merge_request: 31543
author:
type: changed

View file

@ -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:

View file

@ -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:

View file

@ -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
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])

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -1,19 +0,0 @@
{
"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
}
}

View file

@ -0,0 +1,11 @@
{
"type": "object",
"properties": {
"id": { "type": "integer" },
"name": { "type": "string" },
"color": { "type": "string" },
"text_color": { "type": "string" },
"description": { "type": ["string", "null"] },
"subscribed": { "type": "boolean" }
}
}

View file

@ -0,0 +1,16 @@
{
"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" }
}
}
]
}
}

View file

@ -0,0 +1,15 @@
{
"type": "object",
"properties": {
"allOf": [
{ "$ref": "label.json" },
{
"type": "object",
"properties": {
"priority": { "type": ["integer", "null"] },
"is_project_label": { "type": "boolean" }
}
}
]
}
}

View file

@ -0,0 +1,9 @@
{
"type": "object",
"properties": {
"allOf": [
{ "$ref": "project_label.json" },
{ "$ref": "label_with_counts.json" }
]
}
}

View file

@ -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

View file

@ -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)
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(
id name color text_color description
open_issues_count closed_issues_count open_merge_requests_count
subscribed priority is_project_label
)
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.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: [label1], author: user, state: :closed)
create(:labeled_merge_request, labels: [priority_label], author: user, source_project: project )
end
it 'includes counts in the response' do
get api("/projects/#{project.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 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