Commit 7175610f authored by Nathan Friend's avatar Nathan Friend Committed by Rémy Coutable

Add issue/MR search URLs to GraphQL releases

This commit adds a number of new URLs to the GraphQL Release type that
can be used to jump directly to an issue or MR search page pre-filtered
by the release and the state of the issue or MR.

This commit also deprecates two existing fields ("issuesUrl" and
"mergeRequestsUrl") in favor of the more specific fields added in this
commit.
parent e06b0940
......@@ -12,12 +12,25 @@ module Types
field :self_url, GraphQL::STRING_TYPE, null: true,
description: 'HTTP URL of the release'
field :merge_requests_url, GraphQL::STRING_TYPE, null: true,
description: 'HTTP URL of the merge request page filtered by this release'
field :issues_url, GraphQL::STRING_TYPE, null: true,
description: 'HTTP URL of the issues page filtered by this release'
field :edit_url, GraphQL::STRING_TYPE, null: true,
description: "HTTP URL of the release's edit page",
authorize: :update_release
field :open_merge_requests_url, GraphQL::STRING_TYPE, null: true,
description: 'HTTP URL of the merge request page, filtered by this release and `state=open`'
field :merged_merge_requests_url, GraphQL::STRING_TYPE, null: true,
description: 'HTTP URL of the merge request page , filtered by this release and `state=merged`'
field :closed_merge_requests_url, GraphQL::STRING_TYPE, null: true,
description: 'HTTP URL of the merge request page , filtered by this release and `state=closed`'
field :open_issues_url, GraphQL::STRING_TYPE, null: true,
description: 'HTTP URL of the issues page, filtered by this release and `state=open`'
field :closed_issues_url, GraphQL::STRING_TYPE, null: true,
description: 'HTTP URL of the issues page, filtered by this release and `state=closed`'
field :merge_requests_url, GraphQL::STRING_TYPE, null: true, method: :open_merge_requests_url,
description: 'HTTP URL of the merge request page filtered by this release',
deprecated: { reason: 'Use `open_merge_requests_url`', milestone: '13.6' }
field :issues_url, GraphQL::STRING_TYPE, null: true, method: :open_issues_url,
description: 'HTTP URL of the issues page filtered by this release',
deprecated: { reason: 'Use `open_issues_url`', milestone: '13.6' }
end
end
......@@ -23,18 +23,36 @@ class ReleasePresenter < Gitlab::View::Presenter::Delegated
project_release_url(project, release)
end
def merge_requests_url
def open_merge_requests_url
return unless release_mr_issue_urls_available?
project_merge_requests_url(project, params_for_issues_and_mrs)
end
def issues_url
def merged_merge_requests_url
return unless release_mr_issue_urls_available?
project_merge_requests_url(project, params_for_issues_and_mrs(state: 'merged'))
end
def closed_merge_requests_url
return unless release_mr_issue_urls_available?
project_merge_requests_url(project, params_for_issues_and_mrs(state: 'closed'))
end
def open_issues_url
return unless release_mr_issue_urls_available?
project_issues_url(project, params_for_issues_and_mrs)
end
def closed_issues_url
return unless release_mr_issue_urls_available?
project_issues_url(project, params_for_issues_and_mrs(state: 'closed'))
end
def edit_url
return unless release_edit_page_available?
......@@ -59,8 +77,8 @@ class ReleasePresenter < Gitlab::View::Presenter::Delegated
can?(current_user, :download_code, project)
end
def params_for_issues_and_mrs
{ scope: 'all', state: 'opened', release_tag: release.tag }
def params_for_issues_and_mrs(state: 'opened')
{ scope: 'all', state: state, release_tag: release.tag }
end
def release_mr_issue_urls_available?
......
---
title: Add links to GraphQL release object for searching related issues and merge
requests
merge_request: 46161
author:
type: added
......@@ -16663,20 +16663,45 @@ type ReleaseEvidenceEdge {
}
type ReleaseLinks {
"""
HTTP URL of the issues page, filtered by this release and `state=closed`
"""
closedIssuesUrl: String
"""
HTTP URL of the merge request page , filtered by this release and `state=closed`
"""
closedMergeRequestsUrl: String
"""
HTTP URL of the release's edit page
"""
editUrl: String
"""
HTTP URL of the issues page filtered by this release
HTTP URL of the issues page filtered by this release. Deprecated in 13.6: Use `open_issues_url`
"""
issuesUrl: String @deprecated(reason: "Use `open_issues_url`. Deprecated in 13.6")
"""
HTTP URL of the merge request page filtered by this release. Deprecated in 13.6: Use `open_merge_requests_url`
"""
mergeRequestsUrl: String @deprecated(reason: "Use `open_merge_requests_url`. Deprecated in 13.6")
"""
HTTP URL of the merge request page , filtered by this release and `state=merged`
"""
mergedMergeRequestsUrl: String
"""
HTTP URL of the issues page, filtered by this release and `state=open`
"""
issuesUrl: String
openIssuesUrl: String
"""
HTTP URL of the merge request page filtered by this release
HTTP URL of the merge request page, filtered by this release and `state=open`
"""
mergeRequestsUrl: String
openMergeRequestsUrl: String
"""
HTTP URL of the release
......
......@@ -47979,6 +47979,34 @@
"name": "ReleaseLinks",
"description": null,
"fields": [
{
"name": "closedIssuesUrl",
"description": "HTTP URL of the issues page, filtered by this release and `state=closed`",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "closedMergeRequestsUrl",
"description": "HTTP URL of the merge request page , filtered by this release and `state=closed`",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "editUrl",
"description": "HTTP URL of the release's edit page",
......@@ -47995,7 +48023,35 @@
},
{
"name": "issuesUrl",
"description": "HTTP URL of the issues page filtered by this release",
"description": "HTTP URL of the issues page filtered by this release. Deprecated in 13.6: Use `open_issues_url`",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": true,
"deprecationReason": "Use `open_issues_url`. Deprecated in 13.6"
},
{
"name": "mergeRequestsUrl",
"description": "HTTP URL of the merge request page filtered by this release. Deprecated in 13.6: Use `open_merge_requests_url`",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": true,
"deprecationReason": "Use `open_merge_requests_url`. Deprecated in 13.6"
},
{
"name": "mergedMergeRequestsUrl",
"description": "HTTP URL of the merge request page , filtered by this release and `state=merged`",
"args": [
],
......@@ -48008,8 +48064,22 @@
"deprecationReason": null
},
{
"name": "mergeRequestsUrl",
"description": "HTTP URL of the merge request page filtered by this release",
"name": "openIssuesUrl",
"description": "HTTP URL of the issues page, filtered by this release and `state=open`",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "openMergeRequestsUrl",
"description": "HTTP URL of the merge request page, filtered by this release and `state=open`",
"args": [
],
......@@ -2202,9 +2202,14 @@ Evidence for a release.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `closedIssuesUrl` | String | HTTP URL of the issues page, filtered by this release and `state=closed` |
| `closedMergeRequestsUrl` | String | HTTP URL of the merge request page , filtered by this release and `state=closed` |
| `editUrl` | String | HTTP URL of the release's edit page |
| `issuesUrl` | String | HTTP URL of the issues page filtered by this release |
| `mergeRequestsUrl` | String | HTTP URL of the merge request page filtered by this release |
| `issuesUrl` **{warning-solid}** | String | **Deprecated:** Use `open_issues_url`. Deprecated in 13.6 |
| `mergeRequestsUrl` **{warning-solid}** | String | **Deprecated:** Use `open_merge_requests_url`. Deprecated in 13.6 |
| `mergedMergeRequestsUrl` | String | HTTP URL of the merge request page , filtered by this release and `state=merged` |
| `openIssuesUrl` | String | HTTP URL of the issues page, filtered by this release and `state=open` |
| `openMergeRequestsUrl` | String | HTTP URL of the merge request page, filtered by this release and `state=open` |
| `selfUrl` | String | HTTP URL of the release |
### ReleaseSource
......
......@@ -30,8 +30,8 @@ module API
expose :evidences, using: Entities::Releases::Evidence, expose_nil: false, if: ->(_, _) { can_download_code? }
expose :_links do
expose :self_url, as: :self, expose_nil: false
expose :merge_requests_url, expose_nil: false
expose :issues_url, expose_nil: false
expose :open_merge_requests_url, as: :merge_requests_url, expose_nil: false
expose :open_issues_url, as: :issues_url, expose_nil: false
expose :edit_url, expose_nil: false
end
......
......@@ -8,9 +8,14 @@ RSpec.describe GitlabSchema.types['ReleaseLinks'] do
it 'has the expected fields' do
expected_fields = %w[
selfUrl
openMergeRequestsUrl
mergedMergeRequestsUrl
closedMergeRequestsUrl
openIssuesUrl
closedIssuesUrl
editUrl
mergeRequestsUrl
issuesUrl
editUrl
]
expect(described_class).to include_graphql_fields(*expected_fields)
......
......@@ -12,6 +12,11 @@ RSpec.describe ReleasePresenter do
let(:release) { create(:release, project: project) }
let(:presenter) { described_class.new(release, current_user: user) }
let(:base_url_params) { { scope: 'all', release_tag: release.tag } }
let(:opened_url_params) { { state: 'opened', **base_url_params } }
let(:merged_url_params) { { state: 'merged', **base_url_params } }
let(:closed_url_params) { { state: 'closed', **base_url_params } }
before do
project.add_developer(developer)
project.add_guest(guest)
......@@ -55,15 +60,63 @@ RSpec.describe ReleasePresenter do
subject { presenter.self_url }
it 'returns its own url' do
is_expected.to match /#{project_release_url(project, release)}/
is_expected.to eq(project_release_url(project, release))
end
end
describe '#open_merge_requests_url' do
subject { presenter.open_merge_requests_url }
it 'returns merge requests url with state=open' do
is_expected.to eq(project_merge_requests_url(project, opened_url_params))
end
context 'when release_mr_issue_urls feature flag is disabled' do
before do
stub_feature_flags(release_mr_issue_urls: false)
end
it { is_expected.to be_nil }
end
end
describe '#merged_merge_requests_url' do
subject { presenter.merged_merge_requests_url }
it 'returns merge requests url with state=merged' do
is_expected.to eq(project_merge_requests_url(project, merged_url_params))
end
context 'when release_mr_issue_urls feature flag is disabled' do
before do
stub_feature_flags(release_mr_issue_urls: false)
end
it { is_expected.to be_nil }
end
end
describe '#closed_merge_requests_url' do
subject { presenter.closed_merge_requests_url }
it 'returns merge requests url with state=closed' do
is_expected.to eq(project_merge_requests_url(project, closed_url_params))
end
context 'when release_mr_issue_urls feature flag is disabled' do
before do
stub_feature_flags(release_mr_issue_urls: false)
end
it { is_expected.to be_nil }
end
end
describe '#merge_requests_url' do
subject { presenter.merge_requests_url }
describe '#open_issues_url' do
subject { presenter.open_issues_url }
it 'returns merge requests url' do
is_expected.to match /#{project_merge_requests_url(project)}/
it 'returns issues url with state=open' do
is_expected.to eq(project_issues_url(project, opened_url_params))
end
context 'when release_mr_issue_urls feature flag is disabled' do
......@@ -75,11 +128,11 @@ RSpec.describe ReleasePresenter do
end
end
describe '#issues_url' do
subject { presenter.issues_url }
describe '#closed_issues_url' do
subject { presenter.closed_issues_url }
it 'returns merge requests url' do
is_expected.to match /#{project_issues_url(project)}/
it 'returns issues url with state=closed' do
is_expected.to eq(project_issues_url(project, closed_url_params))
end
context 'when release_mr_issue_urls feature flag is disabled' do
......@@ -95,7 +148,7 @@ RSpec.describe ReleasePresenter do
subject { presenter.edit_url }
it 'returns release edit url' do
is_expected.to match /#{edit_project_release_url(project, release)}/
is_expected.to eq(edit_project_release_url(project, release))
end
context 'when a user is not allowed to update a release' do
......
......@@ -13,7 +13,11 @@ RSpec.describe 'Query.project(fullPath).release(tagName)' do
let_it_be(:link_filepath) { '/direct/asset/link/path' }
let_it_be(:released_at) { Time.now - 1.day }
let(:params_for_issues_and_mrs) { { scope: 'all', state: 'opened', release_tag: release.tag } }
let(:base_url_params) { { scope: 'all', release_tag: release.tag } }
let(:opened_url_params) { { state: 'opened', **base_url_params } }
let(:merged_url_params) { { state: 'merged', **base_url_params } }
let(:closed_url_params) { { state: 'closed', **base_url_params } }
let(:post_query) { post_graphql(query, current_user: current_user) }
let(:path_prefix) { %w[project release] }
let(:data) { graphql_data.dig(*path) }
......@@ -180,6 +184,11 @@ RSpec.describe 'Query.project(fullPath).release(tagName)' do
let(:release_fields) do
query_graphql_field(:links, nil, %{
selfUrl
openMergeRequestsUrl
mergedMergeRequestsUrl
closedMergeRequestsUrl
openIssuesUrl
closedIssuesUrl
mergeRequestsUrl
issuesUrl
})
......@@ -190,8 +199,13 @@ RSpec.describe 'Query.project(fullPath).release(tagName)' do
expect(data).to eq(
'selfUrl' => project_release_url(project, release),
'mergeRequestsUrl' => project_merge_requests_url(project, params_for_issues_and_mrs),
'issuesUrl' => project_issues_url(project, params_for_issues_and_mrs)
'openMergeRequestsUrl' => project_merge_requests_url(project, opened_url_params),
'mergedMergeRequestsUrl' => project_merge_requests_url(project, merged_url_params),
'closedMergeRequestsUrl' => project_merge_requests_url(project, closed_url_params),
'openIssuesUrl' => project_issues_url(project, opened_url_params),
'closedIssuesUrl' => project_issues_url(project, closed_url_params),
'mergeRequestsUrl' => project_merge_requests_url(project, opened_url_params),
'issuesUrl' => project_issues_url(project, opened_url_params)
)
end
end
......
......@@ -10,6 +10,11 @@ RSpec.describe 'Query.project(fullPath).releases()' do
let_it_be(:reporter) { create(:user) }
let_it_be(:developer) { create(:user) }
let(:base_url_params) { { scope: 'all', release_tag: release.tag } }
let(:opened_url_params) { { state: 'opened', **base_url_params } }
let(:merged_url_params) { { state: 'merged', **base_url_params } }
let(:closed_url_params) { { state: 'closed', **base_url_params } }
let(:query) do
graphql_query_for(:project, { fullPath: project.full_path },
%{
......@@ -37,6 +42,11 @@ RSpec.describe 'Query.project(fullPath).releases()' do
}
links {
selfUrl
openMergeRequestsUrl
mergedMergeRequestsUrl
closedMergeRequestsUrl
openIssuesUrl
closedIssuesUrl
mergeRequestsUrl
issuesUrl
}
......@@ -101,8 +111,13 @@ RSpec.describe 'Query.project(fullPath).releases()' do
},
'links' => {
'selfUrl' => project_release_url(project, release),
'mergeRequestsUrl' => project_merge_requests_url(project, params_for_issues_and_mrs),
'issuesUrl' => project_issues_url(project, params_for_issues_and_mrs)
'openMergeRequestsUrl' => project_merge_requests_url(project, opened_url_params),
'mergedMergeRequestsUrl' => project_merge_requests_url(project, merged_url_params),
'closedMergeRequestsUrl' => project_merge_requests_url(project, closed_url_params),
'openIssuesUrl' => project_issues_url(project, opened_url_params),
'closedIssuesUrl' => project_issues_url(project, closed_url_params),
'mergeRequestsUrl' => project_merge_requests_url(project, opened_url_params),
'issuesUrl' => project_issues_url(project, opened_url_params)
}
)
end
......
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