diff --git a/app/controllers/projects/merge_requests/creations_controller.rb b/app/controllers/projects/merge_requests/creations_controller.rb index c6473391477..23222cbd37c 100644 --- a/app/controllers/projects/merge_requests/creations_controller.rb +++ b/app/controllers/projects/merge_requests/creations_controller.rb @@ -12,10 +12,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap before_action :build_merge_request, except: [:create] def new - # n+1: https://gitlab.com/gitlab-org/gitlab-foss/issues/40934 - Gitlab::GitalyClient.allow_n_plus_1_calls do - define_new_vars - end + define_new_vars end def create diff --git a/changelogs/unreleased/38347-expose-labels-description-html.yml b/changelogs/unreleased/38347-expose-labels-description-html.yml new file mode 100644 index 00000000000..ea3972297e3 --- /dev/null +++ b/changelogs/unreleased/38347-expose-labels-description-html.yml @@ -0,0 +1,5 @@ +--- +title: Expose description_html for labels +merge_request: 21413 +author: +type: changed diff --git a/doc/api/epics.md b/doc/api/epics.md index 4b20534eeed..109c12c1052 100644 --- a/doc/api/epics.md +++ b/doc/api/epics.md @@ -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` | diff --git a/doc/api/group_labels.md b/doc/api/group_labels.md index f3c3a821354..f41f3a0a402 100644 --- a/doc/api/group_labels.md +++ b/doc/api/group_labels.md @@ -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, diff --git a/doc/api/issues.md b/doc/api/issues.md index 383d190a045..3c28b55d1d6 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -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`
For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead.
_([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`.
For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead.
_([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`.
For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead.
_([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)_ | diff --git a/doc/api/labels.md b/doc/api/labels.md index 525dbe02e5f..ac5156e8c20 100644 --- a/doc/api/labels.md +++ b/doc/api/labels.md @@ -1,5 +1,8 @@ # 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, diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index d17db8deeb2..c722c966273 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -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 | diff --git a/lib/api/entities.rb b/lib/api/entities.rb index c82821c3ca4..a86fb44caa1 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -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 diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 1ef27d9f1b1..d0772c70ffb 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -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', diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 0e161d5589a..b7c9217988e 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -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' diff --git a/spec/fixtures/api/schemas/public_api/v4/label_basic.json b/spec/fixtures/api/schemas/public_api/v4/label_basic.json index 37bbdcb14fe..a501bc2ec56 100644 --- a/spec/fixtures/api/schemas/public_api/v4/label_basic.json +++ b/spec/fixtures/api/schemas/public_api/v4/label_basic.json @@ -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}$" diff --git a/spec/frontend/test_setup.js b/spec/frontend/test_setup.js index ab42dbe7cd1..203781bb6fc 100644 --- a/spec/frontend/test_setup.js +++ b/spec/frontend/test_setup.js @@ -57,6 +57,11 @@ Object.assign(global, { // custom-jquery-matchers was written for an old Jest version, we need to make it compatible Object.entries(jqueryMatchers).forEach(([matcherName, matcherFactory]) => { + // Don't override existing Jest matcher + if (matcherName === 'toHaveLength') { + return; + } + expect.extend({ [matcherName]: matcherFactory().compare, }); diff --git a/spec/requests/api/issues/get_project_issues_spec.rb b/spec/requests/api/issues/get_project_issues_spec.rb index 59aeb91edd2..e031cc9b0c6 100644 --- a/spec/requests/api/issues/get_project_issues_spec.rb +++ b/spec/requests/api/issues/get_project_issues_spec.rb @@ -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) diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 582bf19820d..2e9031c49bb 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -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