diff --git a/app/assets/javascripts/repository/queries/blob_info.query.graphql b/app/assets/javascripts/repository/queries/blob_info.query.graphql
index e0bbf12f3ebc346e38003db5394b7ea1194ef486..eb4b00d925192ebbcf4aab87c94b25e9326f1d6f 100644
--- a/app/assets/javascripts/repository/queries/blob_info.query.graphql
+++ b/app/assets/javascripts/repository/queries/blob_info.query.graphql
@@ -2,28 +2,25 @@ query getBlobInfo($projectPath: ID!, $filePath: String!) {
project(fullPath: $projectPath) {
id
repository {
- blobs(path: $filePath) {
- name
- size
- rawBlob
- type
- fileType
- tooLarge
- path
- editBlobPath
- ideEditPath
- storedExternally
- rawPath
- externalStorageUrl
- replacePath
- deletePath
- canLock
- isLocked
- lockLink
- canModifyBlob
- forkPath
- simpleViewer
- richViewer
+ blobs(paths: [$filePath]) {
+ nodes {
+ webPath
+ name
+ rawSize
+ rawTextBlob
+ fileType
+ path
+ editBlobPath
+ ideEditPath
+ storedExternally
+ rawPath
+ externalStorageUrl
+ replacePath
+ canModifyBlob
+ forkPath
+ simpleViewer
+ richViewer
+ }
}
}
}
diff --git a/app/graphql/types/blob_viewer_type.rb b/app/graphql/types/blob_viewer_type.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8d863c32bc7a2f58ea4ba6c33bb39fb2f91bfafa
--- /dev/null
+++ b/app/graphql/types/blob_viewer_type.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+module Types
+ class BlobViewerType < BaseObject # rubocop:disable Graphql/AuthorizeTypes
+ graphql_name 'BlobViewer'
+ description 'Represents how the blob content should be displayed'
+
+ field :type, Types::BlobViewers::TypeEnum,
+ description: 'Type of blob viewer.',
+ null: false
+
+ field :load_async, GraphQL::BOOLEAN_TYPE,
+ description: 'Shows whether the blob content is loaded asynchronously.',
+ null: false
+
+ field :collapsed, GraphQL::BOOLEAN_TYPE,
+ description: 'Shows whether the blob should be displayed collapsed.',
+ method: :collapsed?,
+ null: false
+
+ field :too_large, GraphQL::BOOLEAN_TYPE,
+ description: 'Shows whether the blob is too large to be displayed.',
+ method: :too_large?,
+ null: false
+
+ field :render_error, GraphQL::STRING_TYPE,
+ description: 'Error rendering the blob content.',
+ null: true
+
+ field :file_type, GraphQL::STRING_TYPE,
+ description: 'Content file type.',
+ method: :partial_name,
+ null: false
+
+ field :loading_partial_name, GraphQL::STRING_TYPE,
+ description: 'Loading partial name.',
+ null: false
+
+ def collapsed
+ !!object&.collapsed?
+ end
+
+ def too_large
+ !!object&.too_large?
+ end
+ end
+end
diff --git a/app/graphql/types/repository/blob_type.rb b/app/graphql/types/repository/blob_type.rb
index 912fc5f643affbee51106c3ade0b86bdbf6c443f..508389715f2644bccb0d9311cbc7887e12098b58 100644
--- a/app/graphql/types/repository/blob_type.rb
+++ b/app/graphql/types/repository/blob_type.rb
@@ -32,6 +32,45 @@ class BlobType < BaseObject
field :web_path, GraphQL::STRING_TYPE, null: true,
description: 'Web path of the blob.'
+ field :size, GraphQL::INT_TYPE, null: true,
+ description: 'Size (in bytes) of the blob.'
+
+ field :raw_size, GraphQL::INT_TYPE, null: true,
+ description: 'Size (in bytes) of the blob, or the blob target if stored externally.'
+
+ field :raw_blob, GraphQL::STRING_TYPE, null: true, method: :data,
+ description: 'The raw content of the blob.'
+
+ field :raw_text_blob, GraphQL::STRING_TYPE, null: true, method: :text_only_data,
+ description: 'The raw content of the blob, if the blob is text data.'
+
+ field :stored_externally, GraphQL::BOOLEAN_TYPE, null: true, method: :stored_externally?,
+ description: "Whether the blob's content is stored externally (for instance, in LFS)."
+
+ field :edit_blob_path, GraphQL::STRING_TYPE, null: true,
+ description: 'Web path to edit the blob in the old-style editor.'
+
+ field :raw_path, GraphQL::STRING_TYPE, null: true,
+ description: 'Web path to download the raw blob.'
+
+ field :replace_path, GraphQL::STRING_TYPE, null: true,
+ description: 'Web path to replace the blob content.'
+
+ field :file_type, GraphQL::STRING_TYPE, null: true,
+ description: 'The expected format of the blob based on the extension.'
+
+ field :simple_viewer, type: Types::BlobViewerType,
+ description: 'Blob content simple viewer.',
+ null: false
+
+ field :rich_viewer, type: Types::BlobViewerType,
+ description: 'Blob content rich viewer.',
+ null: true
+
+ def raw_text_blob
+ object.data unless object.binary?
+ end
+
def lfs_oid
Gitlab::Graphql::Loaders::BatchLfsOidLoader.new(object.repository, object.id).find
end
diff --git a/app/graphql/types/snippets/blob_viewer_type.rb b/app/graphql/types/snippets/blob_viewer_type.rb
index 9e77457c8434840296c13721ecaee94e19cebd3b..8b73234bbd907612ef9ff68524da40db1367a0d1 100644
--- a/app/graphql/types/snippets/blob_viewer_type.rb
+++ b/app/graphql/types/snippets/blob_viewer_type.rb
@@ -2,48 +2,10 @@
module Types
module Snippets
- class BlobViewerType < BaseObject # rubocop:disable Graphql/AuthorizeTypes
+ # Kept to avoid changing the type of existing fields. New fields should use
+ # ::Types::BlobViewerType directly
+ class BlobViewerType < ::Types::BlobViewerType # rubocop:disable Graphql/AuthorizeTypes
graphql_name 'SnippetBlobViewer'
- description 'Represents how the blob content should be displayed'
-
- field :type, Types::BlobViewers::TypeEnum,
- description: 'Type of blob viewer.',
- null: false
-
- field :load_async, GraphQL::BOOLEAN_TYPE,
- description: 'Shows whether the blob content is loaded asynchronously.',
- null: false
-
- field :collapsed, GraphQL::BOOLEAN_TYPE,
- description: 'Shows whether the blob should be displayed collapsed.',
- method: :collapsed?,
- null: false
-
- field :too_large, GraphQL::BOOLEAN_TYPE,
- description: 'Shows whether the blob too large to be displayed.',
- method: :too_large?,
- null: false
-
- field :render_error, GraphQL::STRING_TYPE,
- description: 'Error rendering the blob content.',
- null: true
-
- field :file_type, GraphQL::STRING_TYPE,
- description: 'Content file type.',
- method: :partial_name,
- null: false
-
- field :loading_partial_name, GraphQL::STRING_TYPE,
- description: 'Loading partial name.',
- null: false
-
- def collapsed
- !!object&.collapsed?
- end
-
- def too_large
- !!object&.too_large?
- end
end
end
end
diff --git a/app/presenters/blob_presenter.rb b/app/presenters/blob_presenter.rb
index cff935d51b5f0cf608a34fd5e4987d8e9b3c11de..000fc8214aa9c168019bf5c082cc25271466ea83 100644
--- a/app/presenters/blob_presenter.rb
+++ b/app/presenters/blob_presenter.rb
@@ -15,15 +15,39 @@ def highlight(to: nil, plain: nil)
end
def web_url
- Gitlab::Routing.url_helpers.project_blob_url(blob.repository.project, File.join(blob.commit_id, blob.path))
+ url_helpers.project_blob_url(project, ref_qualified_path)
end
def web_path
- Gitlab::Routing.url_helpers.project_blob_path(blob.repository.project, File.join(blob.commit_id, blob.path))
+ url_helpers.project_blob_path(project, ref_qualified_path)
+ end
+
+ def edit_blob_path
+ url_helpers.project_edit_blob_path(project, ref_qualified_path)
+ end
+
+ def raw_path
+ url_helpers.project_raw_path(project, ref_qualified_path)
+ end
+
+ def replace_path
+ url_helpers.project_create_blob_path(project, ref_qualified_path)
end
private
+ def url_helpers
+ Gitlab::Routing.url_helpers
+ end
+
+ def project
+ blob.repository.project
+ end
+
+ def ref_qualified_path
+ File.join(blob.commit_id, blob.path)
+ end
+
def load_all_blob_data
blob.load_all_data! if blob.respond_to?(:load_all_data!)
end
diff --git a/changelogs/unreleased/323195-more-blob-attributes.yml b/changelogs/unreleased/323195-more-blob-attributes.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0cd4ee0aa38baeae86f6adae244aa9c0a04acee1
--- /dev/null
+++ b/changelogs/unreleased/323195-more-blob-attributes.yml
@@ -0,0 +1,5 @@
+---
+title: Add more fields to the GraphQL blob type
+merge_request: 58906
+author:
+type: added
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 9773ff441c5fa4e67248fd548208983025d00ee0..765478c73cd3d944cc0cc58cb9882811a5207e1b 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -6742,6 +6742,22 @@ An emoji awarded by a user.
| `webPath` | [`String`](#string) | Web path of the blob. |
| `webUrl` | [`String`](#string) | Web URL of the blob. |
+### `BlobViewer`
+
+Represents how the blob content should be displayed.
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `collapsed` | [`Boolean!`](#boolean) | Shows whether the blob should be displayed collapsed. |
+| `fileType` | [`String!`](#string) | Content file type. |
+| `loadAsync` | [`Boolean!`](#boolean) | Shows whether the blob content is loaded asynchronously. |
+| `loadingPartialName` | [`String!`](#string) | Loading partial name. |
+| `renderError` | [`String`](#string) | Error rendering the blob content. |
+| `tooLarge` | [`Boolean!`](#boolean) | Shows whether the blob is too large to be displayed. |
+| `type` | [`BlobViewersType!`](#blobviewerstype) | Type of blob viewer. |
+
### `Board`
Represents a project or group issue board.
@@ -11257,12 +11273,23 @@ Returns [`Tree`](#tree).
| Name | Type | Description |
| ---- | ---- | ----------- |
+| `editBlobPath` | [`String`](#string) | Web path to edit the blob in the old-style editor. |
+| `fileType` | [`String`](#string) | The expected format of the blob based on the extension. |
| `id` | [`ID!`](#id) | ID of the blob. |
| `lfsOid` | [`String`](#string) | LFS OID of the blob. |
| `mode` | [`String`](#string) | Blob mode. |
| `name` | [`String`](#string) | Blob name. |
| `oid` | [`String!`](#string) | OID of the blob. |
| `path` | [`String!`](#string) | Path of the blob. |
+| `rawBlob` | [`String`](#string) | The raw content of the blob. |
+| `rawPath` | [`String`](#string) | Web path to download the raw blob. |
+| `rawSize` | [`Int`](#int) | Size (in bytes) of the blob, or the blob target if stored externally. |
+| `rawTextBlob` | [`String`](#string) | The raw content of the blob, if the blob is text data. |
+| `replacePath` | [`String`](#string) | Web path to replace the blob content. |
+| `richViewer` | [`BlobViewer`](#blobviewer) | Blob content rich viewer. |
+| `simpleViewer` | [`BlobViewer!`](#blobviewer) | Blob content simple viewer. |
+| `size` | [`Int`](#int) | Size (in bytes) of the blob. |
+| `storedExternally` | [`Boolean`](#boolean) | Whether the blob's content is stored externally (for instance, in LFS). |
| `webPath` | [`String`](#string) | Web path of the blob. |
### `Requirement`
@@ -11757,7 +11784,7 @@ Represents how the blob content should be displayed.
| `loadAsync` | [`Boolean!`](#boolean) | Shows whether the blob content is loaded asynchronously. |
| `loadingPartialName` | [`String!`](#string) | Loading partial name. |
| `renderError` | [`String`](#string) | Error rendering the blob content. |
-| `tooLarge` | [`Boolean!`](#boolean) | Shows whether the blob too large to be displayed. |
+| `tooLarge` | [`Boolean!`](#boolean) | Shows whether the blob is too large to be displayed. |
| `type` | [`BlobViewersType!`](#blobviewerstype) | Type of blob viewer. |
### `SnippetPermissions`
diff --git a/spec/graphql/types/blob_viewer_type_spec.rb b/spec/graphql/types/blob_viewer_type_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1c020c635359f36e2fdd3eac04c5949cd8f279ef
--- /dev/null
+++ b/spec/graphql/types/blob_viewer_type_spec.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['BlobViewer'] do
+ it 'has the correct fields' do
+ expected_fields = [:type, :load_async, :too_large, :collapsed,
+ :render_error, :file_type, :loading_partial_name]
+
+ expect(described_class).to have_graphql_fields(*expected_fields)
+ end
+end
diff --git a/spec/graphql/types/repository/blob_type_spec.rb b/spec/graphql/types/repository/blob_type_spec.rb
index f8647e4e9643384a8b84475bc243b27d6734b565..8accee90fa33e2feb3e92a10e280816cf60708fa 100644
--- a/spec/graphql/types/repository/blob_type_spec.rb
+++ b/spec/graphql/types/repository/blob_type_spec.rb
@@ -5,5 +5,26 @@
RSpec.describe Types::Repository::BlobType do
specify { expect(described_class.graphql_name).to eq('RepositoryBlob') }
- specify { expect(described_class).to have_graphql_fields(:id, :oid, :name, :path, :web_path, :lfs_oid, :mode) }
+ specify do
+ expect(described_class).to have_graphql_fields(
+ :id,
+ :oid,
+ :name,
+ :path,
+ :web_path,
+ :lfs_oid,
+ :mode,
+ :size,
+ :raw_size,
+ :raw_blob,
+ :raw_text_blob,
+ :file_type,
+ :edit_blob_path,
+ :stored_externally,
+ :raw_path,
+ :replace_path,
+ :simple_viewer,
+ :rich_viewer
+ )
+ end
end
diff --git a/spec/presenters/blob_presenter_spec.rb b/spec/presenters/blob_presenter_spec.rb
index 47402fea2b55a962daec86d5e7540de513b38420..d6acc20396f3ad0aace132f043e6a16a6b7c1ac9 100644
--- a/spec/presenters/blob_presenter_spec.rb
+++ b/spec/presenters/blob_presenter_spec.rb
@@ -2,52 +2,59 @@
require 'spec_helper'
-RSpec.describe BlobPresenter, :seed_helper do
- let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') }
-
- let(:git_blob) do
- Gitlab::Git::Blob.find(
- repository,
- 'fa1b1e6c004a68b7d8763b86455da9e6b23e36d6',
- 'files/ruby/regex.rb'
- )
+RSpec.describe BlobPresenter do
+ let_it_be(:project) { create(:project, :repository) }
+
+ let(:repository) { project.repository }
+ let(:blob) { repository.blob_at('HEAD', 'files/ruby/regex.rb') }
+
+ subject(:presenter) { described_class.new(blob) }
+
+ describe '#web_url' do
+ it { expect(presenter.web_url).to eq("http://localhost/#{project.full_path}/-/blob/#{blob.commit_id}/#{blob.path}") }
end
- let(:blob) { Blob.new(git_blob) }
+ describe '#web_path' do
+ it { expect(presenter.web_path).to eq("/#{project.full_path}/-/blob/#{blob.commit_id}/#{blob.path}") }
+ end
- describe '.web_url' do
- let(:project) { create(:project, :repository) }
- let(:repository) { project.repository }
- let(:blob) { Gitlab::Graphql::Representation::TreeEntry.new(repository.tree.blobs.first, repository) }
+ describe '#edit_blob_path' do
+ it { expect(presenter.edit_blob_path).to eq("/#{project.full_path}/-/edit/#{blob.commit_id}/#{blob.path}") }
+ end
- subject { described_class.new(blob) }
+ describe '#raw_path' do
+ it { expect(presenter.raw_path).to eq("/#{project.full_path}/-/raw/#{blob.commit_id}/#{blob.path}") }
+ end
- it { expect(subject.web_url).to eq("http://localhost/#{project.full_path}/-/blob/#{blob.commit_id}/#{blob.path}") }
+ describe '#replace_path' do
+ it { expect(presenter.replace_path).to eq("/#{project.full_path}/-/create/#{blob.commit_id}/#{blob.path}") }
end
- describe '#web_path' do
- let(:project) { create(:project, :repository) }
- let(:repository) { project.repository }
- let(:blob) { Gitlab::Graphql::Representation::TreeEntry.new(repository.tree.blobs.first, repository) }
+ context 'given a Gitlab::Graphql::Representation::TreeEntry' do
+ let(:blob) { Gitlab::Graphql::Representation::TreeEntry.new(super(), repository) }
- subject { described_class.new(blob) }
+ describe '#web_url' do
+ it { expect(presenter.web_url).to eq("http://localhost/#{project.full_path}/-/blob/#{blob.commit_id}/#{blob.path}") }
+ end
- it { expect(subject.web_path).to eq("/#{project.full_path}/-/blob/#{blob.commit_id}/#{blob.path}") }
+ describe '#web_path' do
+ it { expect(presenter.web_path).to eq("/#{project.full_path}/-/blob/#{blob.commit_id}/#{blob.path}") }
+ end
end
describe '#highlight' do
- subject { described_class.new(blob) }
+ let(:git_blob) { blob.__getobj__ }
it 'returns highlighted content' do
expect(Gitlab::Highlight).to receive(:highlight).with('files/ruby/regex.rb', git_blob.data, plain: nil, language: nil)
- subject.highlight
+ presenter.highlight
end
it 'returns plain content when :plain is true' do
expect(Gitlab::Highlight).to receive(:highlight).with('files/ruby/regex.rb', git_blob.data, plain: true, language: nil)
- subject.highlight(plain: true)
+ presenter.highlight(plain: true)
end
context '"to" param is present' do
@@ -60,7 +67,7 @@
it 'returns limited highlighted content' do
expect(Gitlab::Highlight).to receive(:highlight).with('files/ruby/regex.rb', "line one\n", plain: nil, language: nil)
- subject.highlight(to: 1)
+ presenter.highlight(to: 1)
end
end
@@ -72,7 +79,7 @@
it 'passes language to inner call' do
expect(Gitlab::Highlight).to receive(:highlight).with('files/ruby/regex.rb', git_blob.data, plain: nil, language: 'ruby')
- subject.highlight
+ presenter.highlight
end
end
end