Commit 37f8a7f0 authored by Sean McGivern's avatar Sean McGivern

Merge branch '38347-expose-labels-description-html' into 'master'

Expose description_html for labels

See merge request gitlab-org/gitlab!21413
parents b22d58b2 f79f6a20
---
title: Expose description_html for labels
merge_request: 21413
author:
type: changed
......@@ -53,6 +53,7 @@ GET /groups/:id/epics?state=opened
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
| `author_id` | integer | no | Return epics created by the given user `id` |
| `labels` | string | no | Return epics matching a comma separated list of labels names. Label names from the epic group or a parent group can be used |
| `with_labels_details` | Boolean | no | If `true`, response will return more details for each label in labels field: `:name`, `:color`, `:description`, `:description_html`, `:text_color`. Default is `false`. Introduced in [GitLab 12.7](https://gitlab.com/gitlab-org/gitlab/merge_requests/21413)|
| `order_by` | string | no | Return epics ordered by `created_at` or `updated_at` fields. Default is `created_at` |
| `sort` | string | no | Return epics sorted in `asc` or `desc` order. Default is `desc` |
| `search` | string | no | Search epics against their `title` and `description` |
......
......@@ -4,6 +4,9 @@
This API supports managing of [group labels](../user/project/labels.md#project-labels-and-group-labels). It allows to list, create, update, and delete group labels. Furthermore, users can subscribe and unsubscribe to and from group labels.
NOTE: **Note:**
The `description_html` - was added to response JSON in [GitLab 12.7](https://gitlab.com/gitlab-org/gitlab/merge_requests/21413).
## List group labels
Get all labels for a given group.
......@@ -32,6 +35,7 @@ Example response:
"color": "#FF0000",
"text_color" : "#FFFFFF",
"description": null,
"description_html": null,
"open_issues_count": 0,
"closed_issues_count": 0,
"open_merge_requests_count": 0,
......@@ -43,6 +47,7 @@ Example response:
"color": "#228B22",
"text_color" : "#FFFFFF",
"description": null,
"description_html": null,
"open_issues_count": 0,
"closed_issues_count": 0,
"open_merge_requests_count": 0,
......@@ -78,6 +83,7 @@ Example response:
"color": "#FF0000",
"text_color" : "#FFFFFF",
"description": null,
"description_html": null,
"open_issues_count": 0,
"closed_issues_count": 0,
"open_merge_requests_count": 0,
......@@ -113,6 +119,7 @@ Example response:
"color": "#FFA500",
"text_color" : "#FFFFFF",
"description": "Describes new ideas",
"description_html": "Describes new ideas",
"open_issues_count": 0,
"closed_issues_count": 0,
"open_merge_requests_count": 0,
......@@ -149,6 +156,7 @@ Example response:
"color": "#FFA500",
"text_color" : "#FFFFFF",
"description": "Describes new ideas",
"description_html": "Describes new ideas",
"open_issues_count": 0,
"closed_issues_count": 0,
"open_merge_requests_count": 0,
......@@ -204,6 +212,7 @@ Example response:
"color": "#FFA500",
"text_color" : "#FFFFFF",
"description": "Describes new ideas",
"description_html": "Describes new ideas",
"open_issues_count": 0,
"closed_issues_count": 0,
"open_merge_requests_count": 0,
......@@ -239,6 +248,7 @@ Example response:
"color": "#FFA500",
"text_color" : "#FFFFFF",
"description": "Describes new ideas",
"description_html": "Describes new ideas",
"open_issues_count": 0,
"closed_issues_count": 0,
"open_merge_requests_count": 0,
......
......@@ -47,7 +47,7 @@ GET /issues?confidential=true
| ------------------- | ---------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `state` | string | no | Return `all` issues or just those that are `opened` or `closed` |
| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `None` lists all issues with no labels. `Any` lists all issues with at least one label. `No+Label` (Deprecated) lists all issues with no labels. Predefined names are case-insensitive. |
| `with_labels_details` | Boolean | no | If `true`, response will return more details for each label in labels field: `:name`, `:color`, `:description`, `:text_color`. Default is `false`. |
| `with_labels_details` | Boolean | no | If `true`, response will return more details for each label in labels field: `:name`, `:color`, `:description`, `:description_html`, `:text_color`. Default is `false`. The `description_html` attribute was introduced in [GitLab 12.7](https://gitlab.com/gitlab-org/gitlab/merge_requests/21413)|
| `milestone` | string | no | The milestone title. `None` lists all issues with no milestone. `Any` lists all issues that have an assigned milestone. |
| `scope` | string | no | Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`. Defaults to `created_by_me`<br> For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead.<br> _([Introduced][ce-13004] in GitLab 9.5. [Changed to snake_case][ce-18935] in GitLab 11.0)_ |
| `author_id` | integer | no | Return issues created by the given user `id`. Mutually exclusive with `author_username`. Combine with `scope=all` or `scope=assigned_to_me`. _([Introduced][ce-13004] in GitLab 9.5)_ |
......@@ -203,7 +203,7 @@ GET /groups/:id/issues?confidential=true
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
| `state` | string | no | Return all issues or just those that are `opened` or `closed` |
| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `None` lists all issues with no labels. `Any` lists all issues with at least one label. `No+Label` (Deprecated) lists all issues with no labels. Predefined names are case-insensitive. |
| `with_labels_details` | Boolean | no | If `true`, response will return more details for each label in labels field: `:name`, `:color`, `:description`, `:text_color`. Default is `false`. |
| `with_labels_details` | Boolean | no | If `true`, response will return more details for each label in labels field: `:name`, `:color`, `:description`, `:description_html`, `:text_color`. Default is `false`. The `description_html` attribute was introduced in [GitLab 12.7](https://gitlab.com/gitlab-org/gitlab/merge_requests/21413) |
| `iids[]` | integer array | no | Return only the issues having the given `iid` |
| `milestone` | string | no | The milestone title. `None` lists all issues with no milestone. `Any` lists all issues that have an assigned milestone. |
| `scope` | string | no | Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`.<br> For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead.<br> _([Introduced][ce-13004] in GitLab 9.5. [Changed to snake_case][ce-18935] in GitLab 11.0)_ |
......@@ -358,7 +358,7 @@ GET /projects/:id/issues?confidential=true
| `iids[]` | integer array | no | Return only the milestone having the given `iid` |
| `state` | string | no | Return all issues or just those that are `opened` or `closed` |
| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `None` lists all issues with no labels. `Any` lists all issues with at least one label. `No+Label` (Deprecated) lists all issues with no labels. Predefined names are case-insensitive. |
| `with_labels_details` | Boolean | no | If `true`, response will return more details for each label in labels field: `:name`, `:color`, `:description`, `:text_color`. Default is `false`. |
| `with_labels_details` | Boolean | no | If `true`, response will return more details for each label in labels field: `:name`, `:color`, `:description`, `:description_html`, `:text_color`. Default is `false`. `description_html` Introduced in [GitLab 12.7](https://gitlab.com/gitlab-org/gitlab/merge_requests/21413) |
| `milestone` | string | no | The milestone title. `None` lists all issues with no milestone. `Any` lists all issues that have an assigned milestone. |
| `scope` | string | no | Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`.<br> For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead.<br> _([Introduced][ce-13004] in GitLab 9.5. [Changed to snake_case][ce-18935] in GitLab 11.0)_ |
| `author_id` | integer | no | Return issues created by the given user `id`. Mutually exclusive with `author_username`. Combine with `scope=all` or `scope=assigned_to_me`. _([Introduced][ce-13004] in GitLab 9.5)_ |
......
# Labels API
NOTE: **Note:**
The `description_html` - was added to response JSON in [GitLab 12.7](https://gitlab.com/gitlab-org/gitlab/merge_requests/21413).
## List labels
Get all labels for a given project.
......@@ -28,6 +31,7 @@ Example response:
"color" : "#d9534f",
"text_color" : "#FFFFFF",
"description": "Bug reported by user",
"description_html": "Bug reported by user",
"open_issues_count": 1,
"closed_issues_count": 0,
"open_merge_requests_count": 1,
......@@ -41,6 +45,7 @@ Example response:
"text_color" : "#FFFFFF",
"name" : "confirmed",
"description": "Confirmed issue",
"description_html": "Confirmed issue",
"open_issues_count": 2,
"closed_issues_count": 5,
"open_merge_requests_count": 0,
......@@ -54,6 +59,7 @@ Example response:
"color" : "#d9534f",
"text_color" : "#FFFFFF",
"description": "Critical issue. Need fix ASAP",
"description_html": "Critical issue. Need fix ASAP",
"open_issues_count": 1,
"closed_issues_count": 3,
"open_merge_requests_count": 1,
......@@ -67,6 +73,7 @@ Example response:
"color" : "#f0ad4e",
"text_color" : "#FFFFFF",
"description": "Issue about documentation",
"description_html": "Issue about documentation",
"open_issues_count": 1,
"closed_issues_count": 0,
"open_merge_requests_count": 2,
......@@ -80,6 +87,7 @@ Example response:
"text_color" : "#FFFFFF",
"name" : "enhancement",
"description": "Enhancement proposal",
"description_html": "Enhancement proposal",
"open_issues_count": 1,
"closed_issues_count": 0,
"open_merge_requests_count": 1,
......@@ -117,6 +125,7 @@ Example response:
"color" : "#d9534f",
"text_color" : "#FFFFFF",
"description": "Bug reported by user",
"description_html": "Bug reported by user",
"open_issues_count": 1,
"closed_issues_count": 0,
"open_merge_requests_count": 1,
......@@ -155,6 +164,7 @@ Example response:
"color" : "#5843AD",
"text_color" : "#FFFFFF",
"description":null,
"description_html":null,
"open_issues_count": 0,
"closed_issues_count": 0,
"open_merge_requests_count": 0,
......@@ -214,6 +224,7 @@ Example response:
"color" : "#8E44AD",
"text_color" : "#FFFFFF",
"description": "Documentation",
"description_html": "Documentation",
"open_issues_count": 1,
"closed_issues_count": 0,
"open_merge_requests_count": 2,
......@@ -252,6 +263,7 @@ Example response:
"name" : "documentation",
"color" : "#8E44AD",
"description": "Documentation",
"description_html": "Documentation",
"open_issues_count": 1,
"closed_issues_count": 0,
"open_merge_requests_count": 2,
......@@ -289,6 +301,7 @@ Example response:
"color" : "#d9534f",
"text_color" : "#FFFFFF",
"description": "Bug reported by user",
"description_html": "Bug reported by user",
"open_issues_count": 1,
"closed_issues_count": 0,
"open_merge_requests_count": 1,
......
......@@ -45,6 +45,7 @@ Parameters:
| `milestone` | string | no | Return merge requests for a specific milestone. `None` returns merge requests with no milestone. `Any` returns merge requests that have an assigned milestone. |
| `view` | string | no | If `simple`, returns the `iid`, URL, title, description, and basic state of merge request |
| `labels` | string | no | Return merge requests matching a comma separated list of labels. `None` lists all merge requests with no labels. `Any` lists all merge requests with at least one label. `No+Label` (Deprecated) lists all merge requests with no labels. Predefined names are case-insensitive. |
| `with_labels_details` | Boolean | no | If `true`, response will return more details for each label in labels field: `:name`, `:color`, `:description`, `:description_html`, `:text_color`. Default is `false`. Introduced in [GitLab 12.7](https://gitlab.com/gitlab-org/gitlab/merge_requests/21413) |
| `created_after` | datetime | no | Return merge requests created on or after the given time |
| `created_before` | datetime | no | Return merge requests created on or before the given time |
| `updated_after` | datetime | no | Return merge requests updated on or after the given time |
......@@ -213,6 +214,7 @@ Parameters:
| `milestone` | string | no | Return merge requests for a specific milestone. `None` returns merge requests with no milestone. `Any` returns merge requests that have an assigned milestone. |
| `view` | string | no | If `simple`, returns the `iid`, URL, title, description, and basic state of merge request |
| `labels` | string | no | Return merge requests matching a comma separated list of labels. `None` lists all merge requests with no labels. `Any` lists all merge requests with at least one label. `No+Label` (Deprecated) lists all merge requests with no labels. Predefined names are case-insensitive. |
| `with_labels_details` | Boolean | no | If `true`, response will return more details for each label in labels field: `:name`, `:color`, `:description`, `:description_html`, `:text_color`. Default is `false`. Introduced in [GitLab 12.7](https://gitlab.com/gitlab-org/gitlab/merge_requests/21413) |
| `created_after` | datetime | no | Return merge requests created on or after the given time |
| `created_before` | datetime | no | Return merge requests created on or before the given time |
| `updated_after` | datetime | no | Return merge requests updated on or after the given time |
......@@ -373,6 +375,7 @@ Parameters:
| `milestone` | string | no | Return merge requests for a specific milestone. `None` returns merge requests with no milestone. `Any` returns merge requests that have an assigned milestone. |
| `view` | string | no | If `simple`, returns the `iid`, URL, title, description, and basic state of merge request |
| `labels` | string | no | Return merge requests matching a comma separated list of labels. `None` lists all merge requests with no labels. `Any` lists all merge requests with at least one label. `No+Label` (Deprecated) lists all merge requests with no labels. Predefined names are case-insensitive. |
| `with_labels_details` | Boolean | no | If `true`, response will return more details for each label in labels field: `:name`, `:color`, `:description`, `:description_html`, `:text_color`. Default is `false`. Introduced in [GitLab 12.7](https://gitlab.com/gitlab-org/gitlab/merge_requests/21413)|
| `created_after` | datetime | no | Return merge requests created on or after the given time |
| `created_before` | datetime | no | Return merge requests created on or before the given time |
| `updated_after` | datetime | no | Return merge requests updated on or after the given time |
......
......@@ -30,6 +30,7 @@ module API
desc: 'Return opened, closed, or all epics'
optional :author_id, type: Integer, desc: 'Return epics which are authored by the user with the given ID'
optional :labels, type: Array[String], coerce_with: Validations::Types::LabelsList.coerce, desc: 'Comma-separated list of label names'
optional :with_labels_details, type: Boolean, desc: 'Return titles of labels and other details', default: false
optional :created_after, type: DateTime, desc: 'Return epics created after the specified time'
optional :created_before, type: DateTime, desc: 'Return epics created before the specified time'
optional :updated_after, type: DateTime, desc: 'Return epics updated after the specified time'
......@@ -42,8 +43,8 @@ module API
epics = paginate(find_epics(finder_params: { group_id: user_group.id })).with_api_entity_associations
# issuable_metadata is the standard used by the Todo API
present epics, epic_options.merge(issuable_metadata: issuable_meta_data(epics, 'Epic'))
extra_options = { issuable_metadata: issuable_meta_data(epics, 'Epic'), with_labels_details: declared_params[:with_labels_details] }
present epics, epic_options.merge(extra_options)
end
desc 'Get details of an epic' do
......
......@@ -336,9 +336,12 @@ module EE
expose :created_at
expose :updated_at
expose :closed_at
expose :labels do |epic|
# Avoids an N+1 query since labels are preloaded
epic.labels.map(&:title).sort
expose :labels do |epic, options|
if options[:with_labels_details]
::API::Entities::LabelBasic.represent(epic.labels.sort_by(&:title))
else
epic.labels.map(&:title).sort
end
end
expose :upvotes do |epic, options|
if options[:issuable_metadata]
......
......@@ -7,7 +7,7 @@ describe API::Epics do
let(:group) { create(:group) }
let(:project) { create(:project, :public, group: group) }
let(:label) { create(:label) }
let(:epic) { create(:labeled_epic, group: group, labels: [label]) }
let!(:epic) { create(:labeled_epic, group: group, labels: [label]) }
let(:params) { nil }
shared_examples 'error requests' do
......@@ -85,7 +85,6 @@ describe API::Epics do
end
it 'avoids N+1 queries', :request_store do
epic
# Avoid polluting queries with inserts for personal access token
pat = create(:personal_access_token, user: user)
subgroup_1 = create(:group, parent: group)
......@@ -103,6 +102,51 @@ describe API::Epics do
expect { get api(url, personal_access_token: pat), params: params }.not_to exceed_all_query_limit(control)
expect(response).to have_gitlab_http_status(200)
end
context 'with_label_details' do
let(:params) do
{
include_descendant_groups: true,
with_labels_details: true
}
end
it 'avoids N+1 queries', :request_store do
# Avoid polluting queries with inserts for personal access token
pat = create(:personal_access_token, user: user)
subgroup_1 = create(:group, parent: group)
subgroup_2 = create(:group, parent: subgroup_1)
label_1 = create(:group_label, title: 'foo', group: group)
epic1 = create(:epic, group: subgroup_2)
create(:label_link, label: label_1, target: epic1)
control = ActiveRecord::QueryRecorder.new(skip_cached: false) do
get api(url, personal_access_token: pat), params: params
end.count
label_2 = create(:label)
create_list(:labeled_epic, 4, group: group, labels: [label_2])
expect do
get api(url, personal_access_token: pat), params: params
end.not_to exceed_all_query_limit(control)
end
it 'returns labels with details' do
label_1 = create(:group_label, title: 'foo', group: group)
label_2 = create(:label, title: 'bar', project: project)
create(:label_link, label: label_1, target: epic)
create(:label_link, label: label_2, target: epic)
get api(url), params: { labels: [label.title, label_1.title, label_2.title], with_labels_details: true }
expect(response).to have_gitlab_http_status(200)
expect_paginated_array_response([epic.id])
expect(json_response.first['labels'].pluck('name')).to match_array([label.title, label_1.title, label_2.title])
expect(json_response.last['labels'].first).to match_schema('/public_api/v4/label_basic')
end
end
end
context 'with multiple epics' do
......@@ -373,8 +417,6 @@ describe API::Epics do
before do
stub_licensed_features(epics: true)
epic
end
it 'excludes descendant group epics' do
......@@ -386,7 +428,7 @@ describe API::Epics do
it 'includes ancestor group epics' do
get api(url), params: { include_ancestor_groups: true }
expect_paginated_array_response([epic.id, subgroup2_epic.id, subgroup_epic.id])
expect_paginated_array_response([subgroup2_epic.id, subgroup_epic.id, epic.id])
end
end
......@@ -411,14 +453,14 @@ describe API::Epics do
end
context 'when viewing the first page' do
let(:expected) { [epic3.id, epic2.id] }
let(:expected) { [epic.id, epic3.id] }
let(:page) { 1 }
it_behaves_like 'paginated API endpoint'
end
context 'viewing the second page' do
let(:expected) { [epic1.id] }
let(:expected) { [epic2.id, epic1.id] }
let(:page) { 2 }
it_behaves_like 'paginated API endpoint'
......
......@@ -779,9 +779,12 @@ module API
expose :author, :assignees, using: Entities::UserBasic
expose :source_project_id, :target_project_id
expose :labels do |merge_request|
# Avoids an N+1 query since labels are preloaded
merge_request.labels.map(&:title).sort
expose :labels do |merge_request, options|
if options[:with_labels_details]
::API::Entities::LabelBasic.represent(merge_request.labels.sort_by(&:title))
else
merge_request.labels.map(&:title).sort
end
end
expose :work_in_progress?, as: :work_in_progress
expose :milestone, using: Entities::Milestone
......@@ -1166,7 +1169,7 @@ module API
end
class LabelBasic < Grape::Entity
expose :id, :name, :color, :description, :text_color
expose :id, :name, :color, :description, :description_html, :text_color
end
class Label < LabelBasic
......
......@@ -48,7 +48,7 @@ module API
end
params :issues_params do
optional :with_labels_details, type: Boolean, desc: 'Return more label data than just lable title', default: false
optional :with_labels_details, type: Boolean, desc: 'Return titles of labels and other details', default: false
optional :state, type: String, values: %w[opened closed all], default: 'all',
desc: 'Return opened, closed, or all issues'
optional :order_by, type: String, values: Helpers::IssuesHelpers.sort_options, default: 'created_at',
......
......@@ -73,7 +73,7 @@ module API
end
def serializer_options_for(merge_requests)
options = { with: Entities::MergeRequestBasic, current_user: current_user }
options = { with: Entities::MergeRequestBasic, current_user: current_user, with_labels_details: declared_params[:with_labels_details] }
if params[:view] == 'simple'
options[:with] = Entities::MergeRequestSimple
......@@ -106,6 +106,7 @@ module API
desc: 'Return merge requests sorted in `asc` or `desc` order.'
optional :milestone, type: String, desc: 'Return merge requests for a specific milestone'
optional :labels, type: Array[String], coerce_with: Validations::Types::LabelsList.coerce, desc: 'Comma-separated list of label names'
optional :with_labels_details, type: Boolean, desc: 'Return titles of labels and other details', default: false
optional :created_after, type: DateTime, desc: 'Return merge requests created after the specified time'
optional :created_before, type: DateTime, desc: 'Return merge requests created before the specified time'
optional :updated_after, type: DateTime, desc: 'Return merge requests updated after the specified time'
......
......@@ -5,6 +5,7 @@
"name",
"color",
"description",
"description_html",
"text_color"
],
"properties": {
......@@ -15,6 +16,7 @@
"pattern": "^#[0-9A-Fa-f]{3}{1,2}$"
},
"description": { "type": ["string", "null"] },
"description_html": { "type": ["string", "null"] },
"text_color": {
"type": "string",
"pattern": "^#[0-9A-Fa-f]{3}{1,2}$"
......
......@@ -299,6 +299,26 @@ describe API::Issues do
it_behaves_like 'labeled issues with labels and label_name params'
end
context 'with_labels_details' do
let(:label_b) { create(:label, title: 'foo', project: project) }
let(:label_c) { create(:label, title: 'bar', project: project) }
it 'avoids N+1 queries' do
control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do
get api("/projects/#{project.id}/issues?with_labels_details=true", user)
end.count
new_issue = create(:issue, project: project)
create(:label_link, label: label, target: new_issue)
create(:label_link, label: label_b, target: new_issue)
create(:label_link, label: label_c, target: new_issue)
expect do
get api("/projects/#{project.id}/issues?with_labels_details=true", user)
end.not_to exceed_all_query_limit(control_count)
end
end
it 'returns issues matching given search string for title' do
get api("#{base_url}/issues?search=#{issue.title}", user)
......
......@@ -88,6 +88,34 @@ describe API::MergeRequests do
expect(json_response.first['merge_commit_sha']).not_to be_nil
expect(json_response.first['merge_commit_sha']).to eq(merge_request_merged.merge_commit_sha)
end
context 'with labels_details' do
it 'returns labels with details' do
path = endpoint_path + "?with_labels_details=true"
get api(path, user)
expect(response).to have_gitlab_http_status(200)
expect(json_response.last['labels'].pluck('name')).to eq([label2.title, label.title])
expect(json_response.last['labels'].first).to match_schema('/public_api/v4/label_basic')
end
it 'avoids N+1 queries' do
path = endpoint_path + "?with_labels_details=true"
control = ActiveRecord::QueryRecorder.new do
get api(path, user)
end.count
mr = create(:merge_request)
create(:label_link, label: label, target: mr)
create(:label_link, label: label2, target: mr)
expect do
get api(path, user)
end.not_to exceed_query_limit(control)
end
end
end
it 'returns an array of all merge_requests using simple mode' do
......
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