Commit b3a0fe04 authored by Alex Pooley's avatar Alex Pooley

Add snippet repo URLs to REST and GraphQL APIs

Snippet repositories will be phased in. Currently endpoint URLs are
dependent on the snippet itself being attached to a repository, and the
version_snippet feature flag setting.
parent 3a166309
...@@ -65,6 +65,14 @@ module Types ...@@ -65,6 +65,14 @@ module Types
calls_gitaly: true, calls_gitaly: true,
null: false null: false
field :ssh_url_to_repo, type: GraphQL::STRING_TYPE,
description: 'SSH URL to the snippet repository',
null: true
field :http_url_to_repo, type: GraphQL::STRING_TYPE,
description: 'HTTP URL to the snippet repository',
null: true
markdown_field :description_html, null: true, method: :description markdown_field :description_html, null: true, method: :description
end end
end end
...@@ -312,6 +312,10 @@ class Snippet < ApplicationRecord ...@@ -312,6 +312,10 @@ class Snippet < ApplicationRecord
Digest::SHA256.hexdigest("#{title}#{description}#{created_at}#{updated_at}") Digest::SHA256.hexdigest("#{title}#{description}#{created_at}#{updated_at}")
end end
def versioned_enabled_for?(user)
repository_exists? && ::Feature.enabled?(:version_snippets, user)
end
class << self class << self
# Searches for snippets with a matching title, description or file name. # Searches for snippets with a matching title, description or file name.
# #
......
...@@ -11,6 +11,14 @@ class SnippetPresenter < Gitlab::View::Presenter::Delegated ...@@ -11,6 +11,14 @@ class SnippetPresenter < Gitlab::View::Presenter::Delegated
Gitlab::UrlBuilder.build(snippet, raw: true) Gitlab::UrlBuilder.build(snippet, raw: true)
end end
def ssh_url_to_repo
snippet.ssh_url_to_repo if snippet.versioned_enabled_for?(current_user)
end
def http_url_to_repo
snippet.http_url_to_repo if snippet.versioned_enabled_for?(current_user)
end
def can_read_snippet? def can_read_snippet?
can_access_resource?("read") can_access_resource?("read")
end end
......
...@@ -7355,6 +7355,11 @@ type Snippet implements Noteable { ...@@ -7355,6 +7355,11 @@ type Snippet implements Noteable {
""" """
fileName: String fileName: String
"""
HTTP URL to the snippet repository
"""
httpUrlToRepo: String
""" """
Id of the snippet Id of the snippet
""" """
...@@ -7395,6 +7400,11 @@ type Snippet implements Noteable { ...@@ -7395,6 +7400,11 @@ type Snippet implements Noteable {
""" """
rawUrl: String! rawUrl: String!
"""
SSH URL to the snippet repository
"""
sshUrlToRepo: String
""" """
Title of the snippet Title of the snippet
""" """
......
...@@ -22213,6 +22213,20 @@ ...@@ -22213,6 +22213,20 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "httpUrlToRepo",
"description": "HTTP URL to the snippet repository",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "id", "name": "id",
"description": "Id of the snippet", "description": "Id of the snippet",
...@@ -22320,6 +22334,20 @@ ...@@ -22320,6 +22334,20 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "sshUrlToRepo",
"description": "SSH URL to the snippet repository",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "title", "name": "title",
"description": "Title of the snippet", "description": "Title of the snippet",
......
...@@ -1161,9 +1161,11 @@ Represents a snippet entry ...@@ -1161,9 +1161,11 @@ Represents a snippet entry
| `description` | String | Description of the snippet | | `description` | String | Description of the snippet |
| `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` | | `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` |
| `fileName` | String | File Name of the snippet | | `fileName` | String | File Name of the snippet |
| `httpUrlToRepo` | String | HTTP URL to the snippet repository |
| `id` | ID! | Id of the snippet | | `id` | ID! | Id of the snippet |
| `project` | Project | The project the snippet is associated with | | `project` | Project | The project the snippet is associated with |
| `rawUrl` | String! | Raw URL of the snippet | | `rawUrl` | String! | Raw URL of the snippet |
| `sshUrlToRepo` | String | SSH URL to the snippet repository |
| `title` | String! | Title of the snippet | | `title` | String! | Title of the snippet |
| `updatedAt` | Time! | Timestamp this snippet was updated | | `updatedAt` | Time! | Timestamp this snippet was updated |
| `userPermissions` | SnippetPermissions! | Permissions for the current user on the resource | | `userPermissions` | SnippetPermissions! | Permissions for the current user on the resource |
......
...@@ -10,6 +10,7 @@ module API ...@@ -10,6 +10,7 @@ module API
expose :web_url do |snippet| expose :web_url do |snippet|
Gitlab::UrlBuilder.build(snippet) Gitlab::UrlBuilder.build(snippet)
end end
expose :ssh_url_to_repo, :http_url_to_repo, if: ->(snippet) { snippet.versioned_enabled_for?(options[:current_user]) }
end end
end end
end end
...@@ -3,12 +3,15 @@ ...@@ -3,12 +3,15 @@
require 'spec_helper' require 'spec_helper'
describe GitlabSchema.types['Snippet'] do describe GitlabSchema.types['Snippet'] do
let_it_be(:user) { create(:user) }
it 'has the correct fields' do it 'has the correct fields' do
expected_fields = [:id, :title, :project, :author, expected_fields = [:id, :title, :project, :author,
:file_name, :description, :file_name, :description,
:visibility_level, :created_at, :updated_at, :visibility_level, :created_at, :updated_at,
:web_url, :raw_url, :notes, :discussions, :web_url, :raw_url, :ssh_url_to_repo, :http_url_to_repo,
:user_permissions, :description_html, :blob] :notes, :discussions, :user_permissions,
:description_html, :blob]
expect(described_class).to have_graphql_fields(*expected_fields) expect(described_class).to have_graphql_fields(*expected_fields)
end end
...@@ -17,8 +20,55 @@ describe GitlabSchema.types['Snippet'] do ...@@ -17,8 +20,55 @@ describe GitlabSchema.types['Snippet'] do
it { expect(described_class).to require_graphql_authorizations(:read_snippet) } it { expect(described_class).to require_graphql_authorizations(:read_snippet) }
end end
shared_examples 'response without repository URLs' do
it 'does not respond with repository URLs' do
expect(response['sshUrlToRepo']).to be_nil
expect(response['httpUrlToRepo']).to be_nil
end
end
describe 'Repository URLs' do
let(:query) do
%(
{
snippets {
nodes {
sshUrlToRepo
httpUrlToRepo
}
}
}
)
end
let(:response) { subject.dig('data', 'snippets', 'nodes')[0] }
subject { GitlabSchema.execute(query, context: { current_user: user }).as_json }
context 'when snippet has repository' do
let!(:snippet) { create(:personal_snippet, :repository, :public, author: user) }
it 'responds with repository URLs' do
expect(response['sshUrlToRepo']).to eq(snippet.ssh_url_to_repo)
expect(response['httpUrlToRepo']).to eq(snippet.http_url_to_repo)
end
context 'when version_snippets feature is disabled' do
before do
stub_feature_flags(version_snippets: false)
end
it_behaves_like 'response without repository URLs'
end
end
context 'when snippet does not have a repository' do
let!(:snippet) { create(:personal_snippet, :public, author: user) }
it_behaves_like 'response without repository URLs'
end
end
describe '#blob' do describe '#blob' do
let_it_be(:user) { create(:user) }
let(:query_blob) { subject.dig('data', 'snippets', 'edges')[0]['node']['blob'] } let(:query_blob) { subject.dig('data', 'snippets', 'edges')[0]['node']['blob'] }
let(:query) do let(:query) do
%( %(
......
...@@ -713,4 +713,32 @@ describe Snippet do ...@@ -713,4 +713,32 @@ describe Snippet do
it { is_expected.to eq(Gitlab.config.gitlab_shell.ssh_path_prefix + "#{snippet.project.full_path}/snippets/#{snippet.id}.git") } it { is_expected.to eq(Gitlab.config.gitlab_shell.ssh_path_prefix + "#{snippet.project.full_path}/snippets/#{snippet.id}.git") }
end end
end end
describe '#versioned_enabled_for?' do
let_it_be(:user) { create(:user) }
subject { snippet.versioned_enabled_for?(user) }
context 'with repository and version_snippets enabled' do
let!(:snippet) { create(:personal_snippet, :repository, author: user) }
it { is_expected.to be_truthy }
end
context 'without repository' do
let!(:snippet) { create(:personal_snippet, author: user) }
it { is_expected.to be_falsy }
end
context 'without version_snippets feature disabled' do
let!(:snippet) { create(:personal_snippet, :repository, author: user) }
before do
stub_feature_flags(version_snippets: false)
end
it { is_expected.to be_falsy }
end
end
end end
...@@ -85,7 +85,7 @@ describe API::ProjectSnippets do ...@@ -85,7 +85,7 @@ describe API::ProjectSnippets do
describe 'GET /projects/:project_id/snippets/:id' do describe 'GET /projects/:project_id/snippets/:id' do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:snippet) { create(:project_snippet, :public, project: project) } let(:snippet) { create(:project_snippet, :public, :repository, project: project) }
it 'returns snippet json' do it 'returns snippet json' do
get api("/projects/#{project.id}/snippets/#{snippet.id}", user) get api("/projects/#{project.id}/snippets/#{snippet.id}", user)
...@@ -95,6 +95,18 @@ describe API::ProjectSnippets do ...@@ -95,6 +95,18 @@ describe API::ProjectSnippets do
expect(json_response['title']).to eq(snippet.title) expect(json_response['title']).to eq(snippet.title)
expect(json_response['description']).to eq(snippet.description) expect(json_response['description']).to eq(snippet.description)
expect(json_response['file_name']).to eq(snippet.file_name) expect(json_response['file_name']).to eq(snippet.file_name)
expect(json_response['ssh_url_to_repo']).to eq(snippet.ssh_url_to_repo)
expect(json_response['http_url_to_repo']).to eq(snippet.http_url_to_repo)
end
context 'when feature flag :version_snippets is disabled' do
before do
stub_feature_flags(version_snippets: false)
get api("/projects/#{project.id}/snippets/#{snippet.id}", user)
end
it_behaves_like 'snippet response without repository URLs'
end end
it 'returns 404 for invalid snippet id' do it 'returns 404 for invalid snippet id' do
......
...@@ -139,8 +139,8 @@ describe API::Snippets do ...@@ -139,8 +139,8 @@ describe API::Snippets do
describe 'GET /snippets/:id' do describe 'GET /snippets/:id' do
let_it_be(:admin) { create(:user, :admin) } let_it_be(:admin) { create(:user, :admin) }
let_it_be(:author) { create(:user) } let_it_be(:author) { create(:user) }
let_it_be(:private_snippet) { create(:personal_snippet, :private, author: author) } let_it_be(:private_snippet) { create(:personal_snippet, :repository, :private, author: author) }
let_it_be(:internal_snippet) { create(:personal_snippet, :internal, author: author) } let_it_be(:internal_snippet) { create(:personal_snippet, :repository, :internal, author: author) }
it 'requires authentication' do it 'requires authentication' do
get api("/snippets/#{private_snippet.id}", nil) get api("/snippets/#{private_snippet.id}", nil)
...@@ -157,6 +157,18 @@ describe API::Snippets do ...@@ -157,6 +157,18 @@ describe API::Snippets do
expect(json_response['description']).to eq(private_snippet.description) expect(json_response['description']).to eq(private_snippet.description)
expect(json_response['file_name']).to eq(private_snippet.file_name) expect(json_response['file_name']).to eq(private_snippet.file_name)
expect(json_response['visibility']).to eq(private_snippet.visibility) expect(json_response['visibility']).to eq(private_snippet.visibility)
expect(json_response['ssh_url_to_repo']).to eq(private_snippet.ssh_url_to_repo)
expect(json_response['http_url_to_repo']).to eq(private_snippet.http_url_to_repo)
end
context 'when feature flag :version_snippets is disabled' do
before do
stub_feature_flags(version_snippets: false)
get api("/snippets/#{private_snippet.id}", author)
end
it_behaves_like 'snippet response without repository URLs'
end end
it 'shows private snippets to an admin' do it 'shows private snippets to an admin' do
......
...@@ -41,3 +41,10 @@ RSpec.shared_examples 'update with repository actions' do ...@@ -41,3 +41,10 @@ RSpec.shared_examples 'update with repository actions' do
end end
end end
end end
RSpec.shared_examples 'snippet response without repository URLs' do
it 'skip inclusion of repository URLs' do
expect(json_response).not_to have_key('ssh_url_to_repo')
expect(json_response).not_to have_key('http_url_to_repo')
end
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