diff --git a/app/graphql/types/repository/blob_type.rb b/app/graphql/types/repository/blob_type.rb
index 004bceea154025f0b2a9f8e3f815abeb1cce9082..8ed97d7e6634da57310a128e8fb5c828b14ec4ab 100644
--- a/app/graphql/types/repository/blob_type.rb
+++ b/app/graphql/types/repository/blob_type.rb
@@ -32,6 +32,15 @@ class BlobType < BaseObject
field :web_path, GraphQL::STRING_TYPE, null: true,
description: 'Web path of the blob.'
+ field :ide_edit_path, GraphQL::STRING_TYPE, null: true,
+ description: 'Web path to edit this blob in the Web IDE.'
+
+ field :fork_and_edit_path, GraphQL::STRING_TYPE, null: true,
+ description: 'Web path to edit this blob using a forked project.'
+
+ field :ide_fork_and_edit_path, GraphQL::STRING_TYPE, null: true,
+ description: 'Web path to edit this blob in the Web IDE using a forked project.'
+
field :size, GraphQL::INT_TYPE, null: true,
description: 'Size (in bytes) of the blob.'
@@ -53,6 +62,9 @@ class BlobType < BaseObject
field :raw_path, GraphQL::STRING_TYPE, null: true,
description: 'Web path to download the raw blob.'
+ field :external_storage_url, GraphQL::STRING_TYPE, null: true,
+ description: 'Web path to download the raw blob via external storage, if enabled.'
+
field :replace_path, GraphQL::STRING_TYPE, null: true,
description: 'Web path to replace the blob content.'
@@ -72,6 +84,10 @@ class BlobType < BaseObject
null: true,
calls_gitaly: true
+ field :can_modify_blob, GraphQL::BOOLEAN_TYPE, null: true, method: :can_modify_blob?,
+ calls_gitaly: true,
+ description: 'Whether the current user can modify the blob.'
+
def raw_text_blob
object.data unless object.binary?
end
diff --git a/app/presenters/blob_presenter.rb b/app/presenters/blob_presenter.rb
index cfe2bba8aa5b76cf06fb6f3942df05a6d5316a72..56dd056b9bc875bb523bca75a73d59edc5514dde 100644
--- a/app/presenters/blob_presenter.rb
+++ b/app/presenters/blob_presenter.rb
@@ -1,6 +1,12 @@
# frozen_string_literal: true
class BlobPresenter < Gitlab::View::Presenter::Delegated
+ include ApplicationHelper
+ include BlobHelper
+ include DiffHelper
+ include TreeHelper
+ include ChecksCollaboration
+
presents :blob
def highlight(to: nil, plain: nil)
@@ -40,6 +46,28 @@ def replace_path
url_helpers.project_create_blob_path(project, ref_qualified_path)
end
+ def fork_and_edit_path
+ fork_path_for_current_user(project, edit_blob_path)
+ end
+
+ def ide_fork_and_edit_path
+ fork_path_for_current_user(project, ide_edit_path)
+ end
+
+ def can_modify_blob?
+ super(blob, project, blob.commit_id)
+ end
+
+ def ide_edit_path
+ super(project, blob.commit_id, blob.path)
+ end
+
+ def external_storage_url
+ return unless static_objects_external_storage_enabled?
+
+ external_storage_url_or_path(url_helpers.project_raw_url(project, ref_qualified_path))
+ end
+
private
def url_helpers
diff --git a/changelogs/unreleased/323195-add-more-blob-attributes.yml b/changelogs/unreleased/323195-add-more-blob-attributes.yml
new file mode 100644
index 0000000000000000000000000000000000000000..83ddedd47f9de12e185ef1fab9a9250a16617662
--- /dev/null
+++ b/changelogs/unreleased/323195-add-more-blob-attributes.yml
@@ -0,0 +1,5 @@
+---
+title: Add more attributes to the blob GraphQL API
+merge_request: 61155
+author:
+type: added
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index a1c397890ef7eab581a4d3c6ffffa1c7ceaca0eb..df17e0f58fd6005a8e81362c19b5f18104b7213b 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -11784,9 +11784,14 @@ Returns [`Tree`](#tree).
| Name | Type | Description |
| ---- | ---- | ----------- |
+| `canModifyBlob` | [`Boolean`](#boolean) | Whether the current user can modify the blob. |
| `editBlobPath` | [`String`](#string) | Web path to edit the blob in the old-style editor. |
+| `externalStorageUrl` | [`String`](#string) | Web path to download the raw blob via external storage, if enabled. |
| `fileType` | [`String`](#string) | The expected format of the blob based on the extension. |
+| `forkAndEditPath` | [`String`](#string) | Web path to edit this blob using a forked project. |
| `id` | [`ID!`](#id) | ID of the blob. |
+| `ideEditPath` | [`String`](#string) | Web path to edit this blob in the Web IDE. |
+| `ideForkAndEditPath` | [`String`](#string) | Web path to edit this blob in the Web IDE using a forked project. |
| `lfsOid` | [`String`](#string) | LFS OID of the blob. |
| `mode` | [`String`](#string) | Blob mode. |
| `name` | [`String`](#string) | Blob name. |
diff --git a/spec/graphql/types/repository/blob_type_spec.rb b/spec/graphql/types/repository/blob_type_spec.rb
index c588f8230de00b8f6c346dd5448f133b5e330c7c..beab4dcebc2f904da37de1ada4899826dd09784e 100644
--- a/spec/graphql/types/repository/blob_type_spec.rb
+++ b/spec/graphql/types/repository/blob_type_spec.rb
@@ -25,7 +25,12 @@
:replace_path,
:simple_viewer,
:rich_viewer,
- :plain_data
+ :plain_data,
+ :can_modify_blob,
+ :ide_edit_path,
+ :external_storage_url,
+ :fork_and_edit_path,
+ :ide_fork_and_edit_path
)
end
end
diff --git a/spec/presenters/blob_presenter_spec.rb b/spec/presenters/blob_presenter_spec.rb
index 1fdd31b1f925ed09eeb501fb2c602a5591398c3d..38bdf3b936437f4aaba7f04b49f61445dd4c195c 100644
--- a/spec/presenters/blob_presenter_spec.rb
+++ b/spec/presenters/blob_presenter_spec.rb
@@ -4,11 +4,12 @@
RSpec.describe BlobPresenter do
let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:user) { project.owner }
let(:repository) { project.repository }
let(:blob) { repository.blob_at('HEAD', 'files/ruby/regex.rb') }
- subject(:presenter) { described_class.new(blob) }
+ subject(:presenter) { described_class.new(blob, current_user: user) }
describe '#web_url' do
it { expect(presenter.web_url).to eq("http://localhost/#{project.full_path}/-/blob/#{blob.commit_id}/#{blob.path}") }
@@ -30,6 +31,42 @@
it { expect(presenter.replace_path).to eq("/#{project.full_path}/-/create/#{blob.commit_id}/#{blob.path}") }
end
+ describe '#ide_edit_path' do
+ it { expect(presenter.ide_edit_path).to eq("/-/ide/project/#{project.full_path}/edit/HEAD/-/files/ruby/regex.rb") }
+ end
+
+ describe '#fork_and_edit_path' do
+ it 'generates expected URI + query' do
+ uri = URI.parse(presenter.fork_and_edit_path)
+ query = Rack::Utils.parse_query(uri.query)
+
+ expect(uri.path).to eq("/#{project.full_path}/-/forks")
+ expect(query).to include('continue[to]' => presenter.edit_blob_path, 'namespace_key' => user.namespace_id.to_s)
+ end
+
+ context 'current_user is nil' do
+ let(:user) { nil }
+
+ it { expect(presenter.fork_and_edit_path).to be_nil }
+ end
+ end
+
+ describe '#ide_fork_and_edit_path' do
+ it 'generates expected URI + query' do
+ uri = URI.parse(presenter.ide_fork_and_edit_path)
+ query = Rack::Utils.parse_query(uri.query)
+
+ expect(uri.path).to eq("/#{project.full_path}/-/forks")
+ expect(query).to include('continue[to]' => presenter.ide_edit_path, 'namespace_key' => user.namespace_id.to_s)
+ end
+
+ context 'current_user is nil' do
+ let(:user) { nil }
+
+ it { expect(presenter.ide_fork_and_edit_path).to be_nil }
+ end
+ end
+
context 'given a Gitlab::Graphql::Representation::TreeEntry' do
let(:blob) { Gitlab::Graphql::Representation::TreeEntry.new(super(), repository) }