From 77ffac178f2ca41139678ab901234ecedca61860 Mon Sep 17 00:00:00 2001 From: Alexandru Croitor Date: Tue, 19 Nov 2019 13:01:27 +0200 Subject: [PATCH] Expose issuable references Expose issuable references in 3 options: short, relative and full. Short and full aways display corresponding issuable reference, while relative displays the reference depending on the context from which it was referenced. --- ...se-reference-path-in-api-for-issuables.yml | 5 ++ doc/api/epics.md | 79 +++++++++++++++- doc/api/issues.md | 89 ++++++++++++++++--- doc/api/merge_requests.md | 72 ++++++++++++++- ee/lib/api/epics.rb | 11 ++- ee/lib/api/helpers/epics_helpers.rb | 8 ++ ee/lib/ee/api/entities.rb | 5 ++ .../api/schemas/public_api/v4/epic.json | 5 ++ ee/spec/requests/api/epics_spec.rb | 34 +++++++ lib/api/entities.rb | 24 +++++ lib/api/issues.rb | 19 ++-- lib/api/merge_requests.rb | 8 +- .../api/schemas/public_api/v4/issue.json | 5 ++ .../schemas/public_api/v4/merge_request.json | 7 +- .../api/issues/get_group_issues_spec.rb | 27 ++++++ spec/requests/api/issues/issues_spec.rb | 11 +++ spec/requests/api/merge_requests_spec.rb | 30 +++++++ 17 files changed, 400 insertions(+), 39 deletions(-) create mode 100644 changelogs/unreleased/31301-expose-reference-path-in-api-for-issuables.yml diff --git a/changelogs/unreleased/31301-expose-reference-path-in-api-for-issuables.yml b/changelogs/unreleased/31301-expose-reference-path-in-api-for-issuables.yml new file mode 100644 index 00000000000000..f9f41ceb615e3f --- /dev/null +++ b/changelogs/unreleased/31301-expose-reference-path-in-api-for-issuables.yml @@ -0,0 +1,5 @@ +--- +title: Expose full reference path for issuables in API +merge_request: 20354 +author: +type: changed diff --git a/doc/api/epics.md b/doc/api/epics.md index 531c75fd8c5abd..4b20534eeed360 100644 --- a/doc/api/epics.md +++ b/doc/api/epics.md @@ -29,6 +29,14 @@ are paginated. Read more on [pagination](README.md#pagination). +CAUTION: **Deprecation** +> `reference` attribute in response is deprecated in favour of `references`. +> Introduced [GitLab 12.6](https://gitlab.com/gitlab-org/gitlab/merge_requests/20354) + +NOTE: **Note** +> `references.relative` is relative to the group that the epic is being requested. When epic is fetched from its origin group +> `relative` format would be the same as `short` format and when requested cross groups it is expected to be the same as `full` format. + ## List epics for a group Gets all epics of the requested group and its subgroups. @@ -73,6 +81,51 @@ Example response: "state": "opened", "web_url": "http://localhost:3001/groups/test/-/epics/4", "reference": "&4", + "references": { + "short": "&4", + "relative": "&4", + "full": "test&4" + }, + "author": { + "id": 10, + "name": "Lu Mayer", + "username": "kam", + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/018729e129a6f31c80a6327a30196823?s=80&d=identicon", + "web_url": "http://localhost:3001/kam" + }, + "start_date": null, + "start_date_is_fixed": false, + "start_date_fixed": null, + "start_date_from_milestones": null, //deprecated in favor of start_date_from_inherited_source + "start_date_from_inherited_source": null, + "end_date": "2018-07-31", //deprecated in favor of due_date + "due_date": "2018-07-31", + "due_date_is_fixed": false, + "due_date_fixed": null, + "due_date_from_milestones": "2018-07-31", //deprecated in favor of start_date_from_inherited_source + "due_date_from_inherited_source": "2018-07-31", + "created_at": "2018-07-17T13:36:22.770Z", + "updated_at": "2018-07-18T12:22:05.239Z", + "closed_at": "2018-08-18T12:22:05.239Z", + "labels": [], + "upvotes": 4, + "downvotes": 0 + }, + { + "id": 50, + "iid": 35, + "group_id": 17, + "title": "Accusamus iste et ullam ratione voluptatem omnis debitis dolor est.", + "description": "Molestias dolorem eos vitae expedita impedit necessitatibus quo voluptatum.", + "state": "opened", + "web_url": "http://localhost:3001/groups/test/sample/-/epics/4", + "reference": "&4", + "references": { + "short": "&4", + "relative": "sample&4", + "full": "test/sample&4" + }, "author": { "id": 10, "name": "Lu Mayer", @@ -131,6 +184,11 @@ Example response: "state": "opened", "web_url": "http://localhost:3001/groups/test/-/epics/5", "reference": "&5", + "references": { + "short": "&5", + "relative": "&5", + "full": "test&5" + }, "author":{ "id": 7, "name": "Pamella Huel", @@ -199,8 +257,13 @@ Example response: "title": "Epic", "description": "Epic description", "state": "opened", - "web_url": "http://localhost:3001/groups/test/-/epics/5", + "web_url": "http://localhost:3001/groups/test/-/epics/6", "reference": "&6", + "references": { + "short": "&6", + "relative": "&6", + "full": "test&6" + }, "author": { "name" : "Alexandra Bashirian", "avatar_url" : null, @@ -269,8 +332,13 @@ Example response: "title": "New Title", "description": "Epic description", "state": "opened", - "web_url": "http://localhost:3001/groups/test/-/epics/5", + "web_url": "http://localhost:3001/groups/test/-/epics/6", "reference": "&6", + "references": { + "short": "&6", + "relative": "&6", + "full": "test&6" + }, "author": { "name" : "Alexandra Bashirian", "avatar_url" : null, @@ -372,6 +440,13 @@ Example response: "avatar_url": "http://www.gravatar.com/avatar/a2f5c6fcef64c9c69cb8779cb292be1b?s=80&d=identicon", "web_url": "http://localhost:3001/arnita" }, + "web_url": "http://localhost:3001/groups/test/-/epics/5", + "reference": "&5", + "references": { + "short": "&5", + "relative": "&5", + "full": "test&5" + }, "start_date": null, "end_date": null, "created_at": "2018-01-21T06:21:13.165Z", diff --git a/doc/api/issues.md b/doc/api/issues.md index fe551cfb397ae3..383d190a0456c7 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -12,6 +12,14 @@ are paginated. Read more on [pagination](README.md#pagination). +CAUTION: **Deprecation** +> `reference` attribute in response is deprecated in favour of `references`. +> Introduced [GitLab 12.6](https://gitlab.com/gitlab-org/gitlab/merge_requests/20354) + +NOTE: **Note** +> `references.relative` is relative to the group / project that the issue is being requested. When issue is fetched from its project +> `relative` format would be the same as `short` format and when requested across groups / projects it is expected to be the same as `full` format. + ## List issues Get all issues the authenticated user has access to. By default it @@ -121,7 +129,12 @@ Example response: "merge_requests_count": 0, "user_notes_count": 1, "due_date": "2016-07-22", - "web_url": "http://example.com/example/example/issues/6", + "web_url": "http://example.com/my-group/my-project/issues/6", + "references": { + "short": "#6", + "relative": "my-group/my-project#6", + "full": "my-group/my-project#6" + }, "time_stats": { "time_estimate": 0, "total_time_spent": 0, @@ -270,7 +283,12 @@ Example response: "closed_by" : null, "user_notes_count": 1, "due_date": null, - "web_url": "http://example.com/example/example/issues/1", + "web_url": "http://example.com/my-group/my-project/issues/1", + "references": { + "short": "#1", + "relative": "my-project#1", + "full": "my-group/my-project#1" + }, "time_stats": { "time_estimate": 0, "total_time_spent": 0, @@ -426,7 +444,12 @@ Example response: }, "user_notes_count": 1, "due_date": "2016-07-22", - "web_url": "http://example.com/example/example/issues/1", + "web_url": "http://example.com/my-group/my-project/issues/1", + "references": { + "short": "#1", + "relative": "#1", + "full": "my-group/my-project#1" + }, "time_stats": { "time_estimate": 0, "total_time_spent": 0, @@ -543,7 +566,12 @@ Example response: "subscribed": false, "user_notes_count": 1, "due_date": null, - "web_url": "http://example.com/example/example/issues/1", + "web_url": "http://example.com/my-group/my-project/issues/1", + "references": { + "short": "#1", + "relative": "#1", + "full": "my-group/my-project#1" + }, "time_stats": { "time_estimate": 0, "total_time_spent": 0, @@ -668,7 +696,12 @@ Example response: "subscribed" : true, "user_notes_count": 0, "due_date": null, - "web_url": "http://example.com/example/example/issues/14", + "web_url": "http://example.com/my-group/my-project/issues/14", + "references": { + "short": "#14", + "relative": "#14", + "full": "my-group/my-project#14" + }, "time_stats": { "time_estimate": 0, "total_time_spent": 0, @@ -778,7 +811,12 @@ Example response: "subscribed" : true, "user_notes_count": 0, "due_date": "2016-07-22", - "web_url": "http://example.com/example/example/issues/15", + "web_url": "http://example.com/my-group/my-project/issues/15", + "references": { + "short": "#15", + "relative": "#15", + "full": "my-group/my-project#15" + }, "time_stats": { "time_estimate": 0, "total_time_spent": 0, @@ -900,7 +938,12 @@ Example response: "web_url": "https://gitlab.example.com/solon.cremin" }, "due_date": null, - "web_url": "http://example.com/example/example/issues/11", + "web_url": "http://example.com/my-group/my-project/issues/11", + "references": { + "short": "#11", + "relative": "#11", + "full": "my-group/my-project#11" + }, "time_stats": { "time_estimate": 0, "total_time_spent": 0, @@ -1001,7 +1044,12 @@ Example response: "web_url": "https://gitlab.example.com/solon.cremin" }, "due_date": null, - "web_url": "http://example.com/example/example/issues/11", + "web_url": "http://example.com/my-group/my-project/issues/11", + "references": { + "short": "#11", + "relative": "#11", + "full": "my-group/my-project#11" + }, "time_stats": { "time_estimate": 0, "total_time_spent": 0, @@ -1095,7 +1143,12 @@ Example response: }, "subscribed": false, "due_date": null, - "web_url": "http://example.com/example/example/issues/12", + "web_url": "http://example.com/my-group/my-project/issues/12", + "references": { + "short": "#12", + "relative": "#12", + "full": "my-group/my-project#12" + }, "confidential": false, "discussion_locked": false, "task_completion_status":{ @@ -1197,7 +1250,12 @@ Example response: "downvotes": 0, "merge_requests_count": 0, "due_date": null, - "web_url": "http://example.com/example/example/issues/110", + "web_url": "http://example.com/my-group/my-project/issues/10", + "references": { + "short": "#10", + "relative": "#10", + "full": "my-group/my-project#10" + }, "confidential": false, "discussion_locked": false, "task_completion_status":{ @@ -1436,6 +1494,11 @@ Example response: "force_remove_source_branch": false, "reference": "!11", "web_url": "https://gitlab.example.com/twitter/flight/merge_requests/4", + "references": { + "short": "!4", + "relative": "!4", + "full": "twitter/flight!4" + }, "time_stats": { "time_estimate": 0, "total_time_spent": 0, @@ -1566,6 +1629,12 @@ Example response: "should_remove_source_branch": null, "force_remove_source_branch": false, "web_url": "https://gitlab.example.com/gitlab-org/gitlab-test/merge_requests/6432", + "reference": "!6432", + "references": { + "short": "!6432", + "relative": "!6432", + "full": "gitlab-org/gitlab-test!6432" + }, "time_stats": { "time_estimate": 0, "total_time_spent": 0, diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index 541aa03450f5eb..d17db8deeb21c7 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -2,6 +2,14 @@ Every API call to merge requests must be authenticated. +CAUTION: **Deprecation** +> `reference` attribute in response is deprecated in favour of `references`. +> Introduced [GitLab 12.6](https://gitlab.com/gitlab-org/gitlab/merge_requests/20354) + +NOTE: **Note** +> `references.relative` is relative to the group / project that the merge request is being requested. When merge request is fetched from its project +> `relative` format would be the same as `short` format and when requested across groups / projects it is expected to be the same as `full` format. + ## List merge requests > [Introduced][ce-13060] in GitLab 9.5. @@ -134,6 +142,11 @@ Parameters: "allow_collaboration": false, "allow_maintainer_to_push": false, "web_url": "http://gitlab.example.com/my-group/my-project/merge_requests/1", + "references": { + "short": "!1", + "relative": "my-group/my-project!1", + "full": "my-group/my-project!1" + }, "time_stats": { "time_estimate": 0, "total_time_spent": 0, @@ -296,6 +309,11 @@ Parameters: "allow_collaboration": false, "allow_maintainer_to_push": false, "web_url": "http://gitlab.example.com/my-group/my-project/merge_requests/1", + "references": { + "short": "!1", + "relative": "!1", + "full": "my-group/my-project!1" + }, "time_stats": { "time_estimate": 0, "total_time_spent": 0, @@ -448,6 +466,11 @@ Parameters: "should_remove_source_branch": true, "force_remove_source_branch": false, "web_url": "http://gitlab.example.com/my-group/my-project/merge_requests/1", + "references": { + "short": "!1", + "relative": "my-project!1", + "full": "my-group/my-project!1" + }, "time_stats": { "time_estimate": 0, "total_time_spent": 0, @@ -574,6 +597,11 @@ Parameters: "allow_collaboration": false, "allow_maintainer_to_push": false, "web_url": "http://gitlab.example.com/my-group/my-project/merge_requests/1", + "references": { + "short": "!1", + "relative": "!1", + "full": "my-group/my-project!1" + }, "time_stats": { "time_estimate": 0, "total_time_spent": 0, @@ -779,7 +807,12 @@ Parameters: "should_remove_source_branch": true, "force_remove_source_branch": false, "squash": false, - "web_url": "http://example.com/example/example/merge_requests/1", + "web_url": "http://gitlab.example.com/my-group/my-project/merge_requests/1", + "references": { + "short": "!1", + "relative": "!1", + "full": "my-group/my-project!1" + }, "discussion_locked": false, "time_stats": { "time_estimate": 0, @@ -989,6 +1022,11 @@ order for it to take effect: "allow_collaboration": false, "allow_maintainer_to_push": false, "web_url": "http://gitlab.example.com/my-group/my-project/merge_requests/1", + "references": { + "short": "!1", + "relative": "!1", + "full": "my-group/my-project!1" + }, "time_stats": { "time_estimate": 0, "total_time_spent": 0, @@ -1143,6 +1181,11 @@ Must include at least one non-required attribute from above. "allow_collaboration": false, "allow_maintainer_to_push": false, "web_url": "http://gitlab.example.com/my-group/my-project/merge_requests/1", + "references": { + "short": "!1", + "relative": "!1", + "full": "my-group/my-project!1" + }, "time_stats": { "time_estimate": 0, "total_time_spent": 0, @@ -1313,6 +1356,11 @@ Parameters: "allow_collaboration": false, "allow_maintainer_to_push": false, "web_url": "http://gitlab.example.com/my-group/my-project/merge_requests/1", + "references": { + "short": "!1", + "relative": "!1", + "full": "my-group/my-project!1" + }, "time_stats": { "time_estimate": 0, "total_time_spent": 0, @@ -1486,6 +1534,11 @@ Parameters: "allow_collaboration": false, "allow_maintainer_to_push": false, "web_url": "http://gitlab.example.com/my-group/my-project/merge_requests/1", + "references": { + "short": "!1", + "relative": "!1", + "full": "my-group/my-project!1" + }, "time_stats": { "time_estimate": 0, "total_time_spent": 0, @@ -1772,6 +1825,11 @@ Example response: "allow_collaboration": false, "allow_maintainer_to_push": false, "web_url": "http://gitlab.example.com/my-group/my-project/merge_requests/1", + "references": { + "short": "!1", + "relative": "!1", + "full": "my-group/my-project!1" + }, "time_stats": { "time_estimate": 0, "total_time_spent": 0, @@ -1918,6 +1976,11 @@ Example response: "allow_collaboration": false, "allow_maintainer_to_push": false, "web_url": "http://gitlab.example.com/my-group/my-project/merge_requests/1", + "references": { + "short": "!1", + "relative": "!1", + "full": "my-group/my-project!1" + }, "time_stats": { "time_estimate": 0, "total_time_spent": 0, @@ -2078,7 +2141,12 @@ Example response: "should_remove_source_branch": true, "force_remove_source_branch": false, "squash": false, - "web_url": "http://example.com/example/example/merge_requests/1" + "web_url": "http://example.com/my-group/my-project/merge_requests/1", + "references": { + "short": "!1", + "relative": "!1", + "full": "my-group/my-project!1" + }, }, "target_url": "https://gitlab.example.com/gitlab-org/gitlab-ci/merge_requests/7", "body": "Et voluptas laudantium minus nihil recusandae ut accusamus earum aut non.", diff --git a/ee/lib/api/epics.rb b/ee/lib/api/epics.rb index 4af281cde430df..efdb997d4a300c 100644 --- a/ee/lib/api/epics.rb +++ b/ee/lib/api/epics.rb @@ -42,7 +42,8 @@ class Epics < Grape::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, with: EE::API::Entities::Epic, user: current_user, issuable_metadata: issuable_meta_data(epics, 'Epic') + + present epics, epic_options.merge(issuable_metadata: issuable_meta_data(epics, 'Epic')) end desc 'Get details of an epic' do @@ -54,9 +55,7 @@ class Epics < Grape::API get ':id/(-/)epics/:epic_iid' do authorize_can_read! - present epic, options, user: current_user, - with: EE::API::Entities::Epic, - include_subscribed: true + present epic, epic_options.merge(include_subscribed: true) end desc 'Create a new epic' do @@ -77,7 +76,7 @@ class Epics < Grape::API epic = ::Epics::CreateService.new(user_group, current_user, declared_params(include_missing: false)).execute if epic.valid? - present epic, with: EE::API::Entities::Epic, user: current_user + present epic, epic_options else render_validation_error!(epic) end @@ -106,7 +105,7 @@ class Epics < Grape::API result = ::Epics::UpdateService.new(user_group, current_user, update_params).execute(epic) if result.valid? - present result, with: EE::API::Entities::Epic, user: current_user + present result, epic_options else render_validation_error!(result) end diff --git a/ee/lib/api/helpers/epics_helpers.rb b/ee/lib/api/helpers/epics_helpers.rb index 42b208d3e4c44a..48e069da6504ca 100644 --- a/ee/lib/api/helpers/epics_helpers.rb +++ b/ee/lib/api/helpers/epics_helpers.rb @@ -41,6 +41,14 @@ def find_epics(finder_params: {}, preload: nil) end end # rubocop: enable CodeReuse/ActiveRecord + + def epic_options + { + with: EE::API::Entities::Epic, + user: current_user, + group: user_group + } + end end end end diff --git a/ee/lib/ee/api/entities.rb b/ee/lib/ee/api/entities.rb index 2fe8ddd2529905..822ebfe722f03c 100644 --- a/ee/lib/ee/api/entities.rb +++ b/ee/lib/ee/api/entities.rb @@ -325,6 +325,11 @@ class Epic < Grape::Entity expose :state expose :web_edit_url, if: can_admin_epic # @deprecated expose :web_url + expose :references, with: ::API::Entities::IssuableReferences do |epic| + epic + end + # reference is deprecated in favour of references + # Introduced [Gitlab 12.6](https://gitlab.com/gitlab-org/gitlab/merge_requests/20354) expose :reference, if: { with_reference: true } do |epic| epic.to_reference(full: true) end diff --git a/ee/spec/fixtures/api/schemas/public_api/v4/epic.json b/ee/spec/fixtures/api/schemas/public_api/v4/epic.json index 47334d413f77ae..ab3374da598003 100644 --- a/ee/spec/fixtures/api/schemas/public_api/v4/epic.json +++ b/ee/spec/fixtures/api/schemas/public_api/v4/epic.json @@ -45,6 +45,11 @@ "web_edit_url": { "type": "string" }, "web_url": { "type": "string" }, "reference": { "type": "string" }, + "references": { + "short": {"type": "string"}, + "relative": {"type": "string"}, + "full": {"type": "string"} + }, "subscribed": { "type": ["boolean", "null"] } }, "required": [ diff --git a/ee/spec/requests/api/epics_spec.rb b/ee/spec/requests/api/epics_spec.rb index fc5887c8c59c0b..05aa7b559ab96d 100644 --- a/ee/spec/requests/api/epics_spec.rb +++ b/ee/spec/requests/api/epics_spec.rb @@ -294,6 +294,32 @@ expect_paginated_array_response(epic.id) end + context "#to_reference" do + it 'exposes reference path' do + get api(url) + + expect(json_response.first['references']['short']).to eq("&#{epic2.iid}") + expect(json_response.first['references']['relative']).to eq("&#{epic2.iid}") + expect(json_response.first['references']['full']).to eq("#{epic2.group.path}&#{epic2.iid}") + end + + context 'referencing from parent group' do + let(:parent_group) { create(:group) } + + before do + group.update(parent_id: parent_group.id) + end + + it 'exposes full reference path' do + get api("/groups/#{parent_group.path}/epics") + + expect(json_response.first['references']['short']).to eq("&#{epic2.iid}") + expect(json_response.first['references']['relative']).to eq("#{parent_group.path}/#{epic2.group.path}&#{epic2.iid}") + expect(json_response.first['references']['full']).to eq("#{parent_group.path}/#{epic2.group.path}&#{epic2.iid}") + end + end + end + it_behaves_like 'can admin epics' end @@ -437,6 +463,14 @@ expect(json_response['closed_at']).to be_present end + it 'exposes full reference path' do + get api(url) + + expect(json_response['references']['short']).to eq("&#{epic.iid}") + expect(json_response['references']['relative']).to eq("&#{epic.iid}") + expect(json_response['references']['full']).to eq("#{epic.group.path}&#{epic.iid}") + end + it_behaves_like 'can admin epics' end end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index cc95be5e3bed1b..c82821c3ca44d7 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -569,6 +569,20 @@ def issuable_metadata(subject, options, method, args = nil) end end + class IssuableReferences < Grape::Entity + expose :short do |issuable| + issuable.to_reference + end + + expose :relative do |issuable, options| + issuable.to_reference(options[:group] || options[:project]) + end + + expose :full do |issuable| + issuable.to_reference(full: true) + end + end + class Diff < Grape::Entity expose :old_path, :new_path, :a_mode, :b_mode expose :new_file?, as: :new_file @@ -676,6 +690,10 @@ class Issue < IssueBasic end end + expose :references, with: IssuableReferences do |issue| + issue + end + # Calculating the value of subscribed field triggers Markdown # processing. We can't do that for multiple issues / merge # requests in a single API request. @@ -787,10 +805,16 @@ class MergeRequestBasic < IssuableEntity # Deprecated expose :allow_collaboration, as: :allow_maintainer_to_push, if: -> (merge_request, _) { merge_request.for_fork? } + # reference is deprecated in favour of references + # Introduced [Gitlab 12.6](https://gitlab.com/gitlab-org/gitlab/merge_requests/20354) expose :reference do |merge_request, options| merge_request.to_reference(options[:project]) end + expose :references, with: IssuableReferences do |merge_request| + merge_request + end + expose :web_url do |merge_request| Gitlab::UrlBuilder.build(merge_request) end diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 4208385a48d54c..1ef27d9f1b10a4 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -122,16 +122,15 @@ class Issues < Grape::API use :issues_params end get ":id/issues" do - group = find_group!(params[:id]) - - issues = paginate(find_issues(group_id: group.id, include_subgroups: true)) + issues = paginate(find_issues(group_id: user_group.id, include_subgroups: true)) options = { with: Entities::Issue, with_labels_details: declared_params[:with_labels_details], current_user: current_user, issuable_metadata: issuable_meta_data(issues, 'Issue', current_user), - include_subscribed: false + include_subscribed: false, + group: user_group } present issues, options @@ -142,9 +141,7 @@ class Issues < Grape::API use :issues_stats_params end get ":id/issues_statistics" do - group = find_group!(params[:id]) - - present issues_statistics(group_id: group.id, include_subgroups: true), with: Grape::Presenters::Presenter + present issues_statistics(group_id: user_group.id, include_subgroups: true), with: Grape::Presenters::Presenter end end @@ -161,9 +158,7 @@ class Issues < Grape::API use :issues_params end get ":id/issues" do - project = find_project!(params[:id]) - - issues = paginate(find_issues(project_id: project.id)) + issues = paginate(find_issues(project_id: user_project.id)) options = { with: Entities::Issue, @@ -182,9 +177,7 @@ class Issues < Grape::API use :issues_stats_params end get ":id/issues_statistics" do - project = find_project!(params[:id]) - - present issues_statistics(project_id: project.id), with: Grape::Presenters::Presenter + present issues_statistics(project_id: user_project.id), with: Grape::Presenters::Presenter end desc 'Get a single project issue' do diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 794237f8032b9c..0e161d5589aceb 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -157,11 +157,9 @@ def authorize_push_to_merge_request!(merge_request) use :merge_requests_params end get ":id/merge_requests" do - group = find_group!(params[:id]) + merge_requests = find_merge_requests(group_id: user_group.id, include_subgroups: true) - merge_requests = find_merge_requests(group_id: group.id, include_subgroups: true) - - present merge_requests, serializer_options_for(merge_requests) + present merge_requests, serializer_options_for(merge_requests).merge(group: user_group) end end @@ -215,7 +213,7 @@ def handle_merge_request_errors!(errors) merge_requests = find_merge_requests(project_id: user_project.id) - options = serializer_options_for(merge_requests) + options = serializer_options_for(merge_requests).merge(project: user_project) options[:project] = user_project present merge_requests, options diff --git a/spec/fixtures/api/schemas/public_api/v4/issue.json b/spec/fixtures/api/schemas/public_api/v4/issue.json index 147f53239e05ac..bf1b4a06f0b954 100644 --- a/spec/fixtures/api/schemas/public_api/v4/issue.json +++ b/spec/fixtures/api/schemas/public_api/v4/issue.json @@ -84,6 +84,11 @@ "total_time_spent": { "type": "integer" }, "human_time_estimate": { "type": ["string", "null"] }, "human_total_time_spent": { "type": ["string", "null"] } + }, + "references": { + "short": {"type": "string"}, + "relative": {"type": "string"}, + "full": {"type": "string"} } }, "required": [ diff --git a/spec/fixtures/api/schemas/public_api/v4/merge_request.json b/spec/fixtures/api/schemas/public_api/v4/merge_request.json index a423bf70b69436..3bf1299a1d8986 100644 --- a/spec/fixtures/api/schemas/public_api/v4/merge_request.json +++ b/spec/fixtures/api/schemas/public_api/v4/merge_request.json @@ -113,7 +113,12 @@ "human_total_time_spent": { "type": ["string", "null"] } }, "allow_collaboration": { "type": ["boolean", "null"] }, - "allow_maintainer_to_push": { "type": ["boolean", "null"] } + "allow_maintainer_to_push": { "type": ["boolean", "null"] }, + "references": { + "short": {"type": "string"}, + "relative": {"type": "string"}, + "full": {"type": "string"} + } }, "required": [ "id", "iid", "project_id", "title", "description", diff --git a/spec/requests/api/issues/get_group_issues_spec.rb b/spec/requests/api/issues/get_group_issues_spec.rb index 3ee08758f99455..ef63902ffd74e5 100644 --- a/spec/requests/api/issues/get_group_issues_spec.rb +++ b/spec/requests/api/issues/get_group_issues_spec.rb @@ -688,5 +688,32 @@ end end end + + context "#to_reference" do + it 'exposes reference path in context of group' do + get api(base_url, user) + + expect(json_response.first['references']['short']).to eq("##{group_closed_issue.iid}") + expect(json_response.first['references']['relative']).to eq("#{group_closed_issue.project.path}##{group_closed_issue.iid}") + expect(json_response.first['references']['full']).to eq("#{group_closed_issue.project.full_path}##{group_closed_issue.iid}") + end + + context 'referencing from parent group' do + let(:parent_group) { create(:group) } + + before do + group.update(parent_id: parent_group.id) + group_closed_issue.reload + end + + it 'exposes reference path in context of parent group' do + get api("/groups/#{parent_group.id}/issues") + + expect(json_response.first['references']['short']).to eq("##{group_closed_issue.iid}") + expect(json_response.first['references']['relative']).to eq("#{group_closed_issue.project.full_path}##{group_closed_issue.iid}") + expect(json_response.first['references']['full']).to eq("#{group_closed_issue.project.full_path}##{group_closed_issue.iid}") + end + end + end end end diff --git a/spec/requests/api/issues/issues_spec.rb b/spec/requests/api/issues/issues_spec.rb index 50a0a80b542fa0..a3538aa98b1be6 100644 --- a/spec/requests/api/issues/issues_spec.rb +++ b/spec/requests/api/issues/issues_spec.rb @@ -805,6 +805,17 @@ end end + describe 'GET /projects/:id/issues/:issue_iid' do + it 'exposes full reference path' do + get api("/projects/#{project.id}/issues/#{issue.iid}", user) + + expect(response).to have_gitlab_http_status(200) + expect(json_response['references']['short']).to eq("##{issue.iid}") + expect(json_response['references']['relative']).to eq("##{issue.iid}") + expect(json_response['references']['full']).to eq("#{project.parent.path}/#{project.path}##{issue.iid}") + end + end + describe 'DELETE /projects/:id/issues/:issue_iid' do it 'rejects a non member from deleting an issue' do delete api("/projects/#{project.id}/issues/#{issue.iid}", non_member) diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index e5ad1a6378e112..582bf19820d323 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -736,6 +736,33 @@ it_behaves_like 'merge requests list' end + + context "#to_reference" do + it 'exposes reference path in context of group' do + get api("/groups/#{group.id}/merge_requests", user) + + expect(json_response.first['references']['short']).to eq("!#{merge_request_merged.iid}") + expect(json_response.first['references']['relative']).to eq("#{merge_request_merged.target_project.path}!#{merge_request_merged.iid}") + expect(json_response.first['references']['full']).to eq("#{merge_request_merged.target_project.full_path}!#{merge_request_merged.iid}") + end + + context 'referencing from parent group' do + let(:parent_group) { create(:group) } + + before do + group.update(parent_id: parent_group.id) + merge_request_merged.reload + end + + it 'exposes reference path in context of parent group' do + get api("/groups/#{parent_group.id}/merge_requests") + + expect(json_response.first['references']['short']).to eq("!#{merge_request_merged.iid}") + expect(json_response.first['references']['relative']).to eq("#{merge_request_merged.target_project.full_path}!#{merge_request_merged.iid}") + expect(json_response.first['references']['full']).to eq("#{merge_request_merged.target_project.full_path}!#{merge_request_merged.iid}") + end + end + end end describe "GET /projects/:id/merge_requests/:merge_request_iid" do @@ -783,6 +810,9 @@ expect(json_response).not_to include('rebase_in_progress') expect(json_response['has_conflicts']).to be_falsy expect(json_response['blocking_discussions_resolved']).to be_truthy + expect(json_response['references']['short']).to eq("!#{merge_request.iid}") + expect(json_response['references']['relative']).to eq("!#{merge_request.iid}") + expect(json_response['references']['full']).to eq("#{merge_request.target_project.full_path}!#{merge_request.iid}") end it 'exposes description and title html when render_html is true' do -- GitLab