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:
parent
2608732271
commit
e6dc5168b8
14 changed files with 158 additions and 85 deletions
5
changelogs/unreleased/speed-up-labels-api.yml
Normal file
5
changelogs/unreleased/speed-up-labels-api.yml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
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.
|
||||||
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.
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
11
spec/fixtures/api/schemas/public_api/v4/labels/label.json
vendored
Normal file
11
spec/fixtures/api/schemas/public_api/v4/labels/label.json
vendored
Normal 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" }
|
||||||
|
}
|
||||||
|
}
|
16
spec/fixtures/api/schemas/public_api/v4/labels/label_with_counts.json
vendored
Normal file
16
spec/fixtures/api/schemas/public_api/v4/labels/label_with_counts.json
vendored
Normal 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" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
15
spec/fixtures/api/schemas/public_api/v4/labels/project_label.json
vendored
Normal file
15
spec/fixtures/api/schemas/public_api/v4/labels/project_label.json
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"allOf": [
|
||||||
|
{ "$ref": "label.json" },
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"priority": { "type": ["integer", "null"] },
|
||||||
|
"is_project_label": { "type": "boolean" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
9
spec/fixtures/api/schemas/public_api/v4/labels/project_label_with_counts.json
vendored
Normal file
9
spec/fixtures/api/schemas/public_api/v4/labels/project_label_with_counts.json
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"allOf": [
|
||||||
|
{ "$ref": "project_label.json" },
|
||||||
|
{ "$ref": "label_with_counts.json" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
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.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)
|
||||||
|
create(:labeled_merge_request, labels: [priority_label], author: user, source_project: project )
|
||||||
|
end
|
||||||
|
|
||||||
expect(label1_response['open_issues_count']).to eq(0)
|
it 'includes counts in the response' do
|
||||||
expect(label1_response['closed_issues_count']).to eq(1)
|
get api("/projects/#{project.id}/labels", user), params: { with_counts: true }
|
||||||
expect(label1_response['open_merge_requests_count']).to eq(0)
|
|
||||||
expect(label1_response['name']).to eq(label1.name)
|
|
||||||
expect(label1_response['color']).to be_present
|
|
||||||
expect(label1_response['text_color']).to be_present
|
|
||||||
expect(label1_response['description']).to be_nil
|
|
||||||
expect(label1_response['priority']).to be_nil
|
|
||||||
expect(label1_response['subscribed']).to be_falsey
|
|
||||||
expect(label1_response['is_project_label']).to be_truthy
|
|
||||||
|
|
||||||
expect(group_label_response['open_issues_count']).to eq(1)
|
expect(response).to have_gitlab_http_status(200)
|
||||||
expect(group_label_response['closed_issues_count']).to eq(0)
|
expect(response).to include_pagination_headers
|
||||||
expect(group_label_response['open_merge_requests_count']).to eq(0)
|
expect(json_response).to all(match_schema('public_api/v4/labels/project_label_with_counts'))
|
||||||
expect(group_label_response['name']).to eq(group_label.name)
|
expect(json_response.size).to eq(3)
|
||||||
expect(group_label_response['color']).to be_present
|
expect(json_response.map { |l| l['name'] }).to match_array([group_label.name, priority_label.name, label1.name])
|
||||||
expect(group_label_response['text_color']).to be_present
|
|
||||||
expect(group_label_response['description']).to be_nil
|
|
||||||
expect(group_label_response['priority']).to be_nil
|
|
||||||
expect(group_label_response['subscribed']).to be_falsey
|
|
||||||
expect(group_label_response['is_project_label']).to be_falsey
|
|
||||||
|
|
||||||
expect(priority_label_response['open_issues_count']).to eq(0)
|
label1_response = json_response.find { |l| l['name'] == label1.title }
|
||||||
expect(priority_label_response['closed_issues_count']).to eq(0)
|
group_label_response = json_response.find { |l| l['name'] == group_label.title }
|
||||||
expect(priority_label_response['open_merge_requests_count']).to eq(1)
|
priority_label_response = json_response.find { |l| l['name'] == priority_label.title }
|
||||||
expect(priority_label_response['name']).to eq(priority_label.name)
|
|
||||||
expect(priority_label_response['color']).to be_present
|
expect(label1_response).to include('open_issues_count' => 0,
|
||||||
expect(priority_label_response['text_color']).to be_present
|
'closed_issues_count' => 1,
|
||||||
expect(priority_label_response['description']).to be_nil
|
'open_merge_requests_count' => 0,
|
||||||
expect(priority_label_response['priority']).to eq(3)
|
'name' => label1.name,
|
||||||
expect(priority_label_response['subscribed']).to be_falsey
|
'description' => nil,
|
||||||
expect(priority_label_response['is_project_label']).to be_truthy
|
'color' => a_string_matching(/^#\h{6}$/),
|
||||||
|
'text_color' => a_string_matching(/^#\h{6}$/),
|
||||||
|
'priority' => nil,
|
||||||
|
'subscribed' => false,
|
||||||
|
'is_project_label' => true)
|
||||||
|
|
||||||
|
expect(group_label_response).to include('open_issues_count' => 1,
|
||||||
|
'closed_issues_count' => 0,
|
||||||
|
'open_merge_requests_count' => 0,
|
||||||
|
'name' => group_label.name,
|
||||||
|
'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
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue