Commit 3248f11f authored by Nick Thomas's avatar Nick Thomas

Add more fields to the GraphQL blob type

Currently, the blobs type is targeted towards viewing a whole directory
at a time, rather than zooming in to the detail of an individual blob.
Adding more fields allows it to tackle both at the same time.

Changelog: added
parent 5589ff35
...@@ -2,28 +2,25 @@ query getBlobInfo($projectPath: ID!, $filePath: String!) { ...@@ -2,28 +2,25 @@ query getBlobInfo($projectPath: ID!, $filePath: String!) {
project(fullPath: $projectPath) { project(fullPath: $projectPath) {
id id
repository { repository {
blobs(path: $filePath) { blobs(paths: [$filePath]) {
name nodes {
size webPath
rawBlob name
type rawSize
fileType rawTextBlob
tooLarge fileType
path path
editBlobPath editBlobPath
ideEditPath ideEditPath
storedExternally storedExternally
rawPath rawPath
externalStorageUrl externalStorageUrl
replacePath replacePath
deletePath canModifyBlob
canLock forkPath
isLocked simpleViewer
lockLink richViewer
canModifyBlob }
forkPath
simpleViewer
richViewer
} }
} }
} }
......
# 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
...@@ -32,6 +32,45 @@ module Types ...@@ -32,6 +32,45 @@ module Types
field :web_path, GraphQL::STRING_TYPE, null: true, field :web_path, GraphQL::STRING_TYPE, null: true,
description: 'Web path of the blob.' 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 def lfs_oid
Gitlab::Graphql::Loaders::BatchLfsOidLoader.new(object.repository, object.id).find Gitlab::Graphql::Loaders::BatchLfsOidLoader.new(object.repository, object.id).find
end end
......
...@@ -2,48 +2,10 @@ ...@@ -2,48 +2,10 @@
module Types module Types
module Snippets 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' 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 end
end end
...@@ -15,15 +15,39 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated ...@@ -15,15 +15,39 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated
end end
def web_url 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 end
def web_path 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 end
private 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 def load_all_blob_data
blob.load_all_data! if blob.respond_to?(:load_all_data!) blob.load_all_data! if blob.respond_to?(:load_all_data!)
end end
......
---
title: Add more fields to the GraphQL blob type
merge_request: 58906
author:
type: added
...@@ -6742,6 +6742,22 @@ An emoji awarded by a user. ...@@ -6742,6 +6742,22 @@ An emoji awarded by a user.
| <a id="blobwebpath"></a>`webPath` | [`String`](#string) | Web path of the blob. | | <a id="blobwebpath"></a>`webPath` | [`String`](#string) | Web path of the blob. |
| <a id="blobweburl"></a>`webUrl` | [`String`](#string) | Web URL of the blob. | | <a id="blobweburl"></a>`webUrl` | [`String`](#string) | Web URL of the blob. |
### `BlobViewer`
Represents how the blob content should be displayed.
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="blobviewercollapsed"></a>`collapsed` | [`Boolean!`](#boolean) | Shows whether the blob should be displayed collapsed. |
| <a id="blobviewerfiletype"></a>`fileType` | [`String!`](#string) | Content file type. |
| <a id="blobviewerloadasync"></a>`loadAsync` | [`Boolean!`](#boolean) | Shows whether the blob content is loaded asynchronously. |
| <a id="blobviewerloadingpartialname"></a>`loadingPartialName` | [`String!`](#string) | Loading partial name. |
| <a id="blobviewerrendererror"></a>`renderError` | [`String`](#string) | Error rendering the blob content. |
| <a id="blobviewertoolarge"></a>`tooLarge` | [`Boolean!`](#boolean) | Shows whether the blob is too large to be displayed. |
| <a id="blobviewertype"></a>`type` | [`BlobViewersType!`](#blobviewerstype) | Type of blob viewer. |
### `Board` ### `Board`
Represents a project or group issue board. Represents a project or group issue board.
...@@ -11257,12 +11273,23 @@ Returns [`Tree`](#tree). ...@@ -11257,12 +11273,23 @@ Returns [`Tree`](#tree).
| Name | Type | Description | | Name | Type | Description |
| ---- | ---- | ----------- | | ---- | ---- | ----------- |
| <a id="repositoryblobeditblobpath"></a>`editBlobPath` | [`String`](#string) | Web path to edit the blob in the old-style editor. |
| <a id="repositoryblobfiletype"></a>`fileType` | [`String`](#string) | The expected format of the blob based on the extension. |
| <a id="repositoryblobid"></a>`id` | [`ID!`](#id) | ID of the blob. | | <a id="repositoryblobid"></a>`id` | [`ID!`](#id) | ID of the blob. |
| <a id="repositorybloblfsoid"></a>`lfsOid` | [`String`](#string) | LFS OID of the blob. | | <a id="repositorybloblfsoid"></a>`lfsOid` | [`String`](#string) | LFS OID of the blob. |
| <a id="repositoryblobmode"></a>`mode` | [`String`](#string) | Blob mode. | | <a id="repositoryblobmode"></a>`mode` | [`String`](#string) | Blob mode. |
| <a id="repositoryblobname"></a>`name` | [`String`](#string) | Blob name. | | <a id="repositoryblobname"></a>`name` | [`String`](#string) | Blob name. |
| <a id="repositorybloboid"></a>`oid` | [`String!`](#string) | OID of the blob. | | <a id="repositorybloboid"></a>`oid` | [`String!`](#string) | OID of the blob. |
| <a id="repositoryblobpath"></a>`path` | [`String!`](#string) | Path of the blob. | | <a id="repositoryblobpath"></a>`path` | [`String!`](#string) | Path of the blob. |
| <a id="repositoryblobrawblob"></a>`rawBlob` | [`String`](#string) | The raw content of the blob. |
| <a id="repositoryblobrawpath"></a>`rawPath` | [`String`](#string) | Web path to download the raw blob. |
| <a id="repositoryblobrawsize"></a>`rawSize` | [`Int`](#int) | Size (in bytes) of the blob, or the blob target if stored externally. |
| <a id="repositoryblobrawtextblob"></a>`rawTextBlob` | [`String`](#string) | The raw content of the blob, if the blob is text data. |
| <a id="repositoryblobreplacepath"></a>`replacePath` | [`String`](#string) | Web path to replace the blob content. |
| <a id="repositoryblobrichviewer"></a>`richViewer` | [`BlobViewer`](#blobviewer) | Blob content rich viewer. |
| <a id="repositoryblobsimpleviewer"></a>`simpleViewer` | [`BlobViewer!`](#blobviewer) | Blob content simple viewer. |
| <a id="repositoryblobsize"></a>`size` | [`Int`](#int) | Size (in bytes) of the blob. |
| <a id="repositoryblobstoredexternally"></a>`storedExternally` | [`Boolean`](#boolean) | Whether the blob's content is stored externally (for instance, in LFS). |
| <a id="repositoryblobwebpath"></a>`webPath` | [`String`](#string) | Web path of the blob. | | <a id="repositoryblobwebpath"></a>`webPath` | [`String`](#string) | Web path of the blob. |
### `Requirement` ### `Requirement`
...@@ -11757,7 +11784,7 @@ Represents how the blob content should be displayed. ...@@ -11757,7 +11784,7 @@ Represents how the blob content should be displayed.
| <a id="snippetblobviewerloadasync"></a>`loadAsync` | [`Boolean!`](#boolean) | Shows whether the blob content is loaded asynchronously. | | <a id="snippetblobviewerloadasync"></a>`loadAsync` | [`Boolean!`](#boolean) | Shows whether the blob content is loaded asynchronously. |
| <a id="snippetblobviewerloadingpartialname"></a>`loadingPartialName` | [`String!`](#string) | Loading partial name. | | <a id="snippetblobviewerloadingpartialname"></a>`loadingPartialName` | [`String!`](#string) | Loading partial name. |
| <a id="snippetblobviewerrendererror"></a>`renderError` | [`String`](#string) | Error rendering the blob content. | | <a id="snippetblobviewerrendererror"></a>`renderError` | [`String`](#string) | Error rendering the blob content. |
| <a id="snippetblobviewertoolarge"></a>`tooLarge` | [`Boolean!`](#boolean) | Shows whether the blob too large to be displayed. | | <a id="snippetblobviewertoolarge"></a>`tooLarge` | [`Boolean!`](#boolean) | Shows whether the blob is too large to be displayed. |
| <a id="snippetblobviewertype"></a>`type` | [`BlobViewersType!`](#blobviewerstype) | Type of blob viewer. | | <a id="snippetblobviewertype"></a>`type` | [`BlobViewersType!`](#blobviewerstype) | Type of blob viewer. |
### `SnippetPermissions` ### `SnippetPermissions`
......
# 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
...@@ -5,5 +5,26 @@ require 'spec_helper' ...@@ -5,5 +5,26 @@ require 'spec_helper'
RSpec.describe Types::Repository::BlobType do RSpec.describe Types::Repository::BlobType do
specify { expect(described_class.graphql_name).to eq('RepositoryBlob') } 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 end
...@@ -2,52 +2,59 @@ ...@@ -2,52 +2,59 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe BlobPresenter, :seed_helper do RSpec.describe BlobPresenter do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') } let_it_be(:project) { create(:project, :repository) }
let(:git_blob) do let(:repository) { project.repository }
Gitlab::Git::Blob.find( let(:blob) { repository.blob_at('HEAD', 'files/ruby/regex.rb') }
repository,
'fa1b1e6c004a68b7d8763b86455da9e6b23e36d6', subject(:presenter) { described_class.new(blob) }
'files/ruby/regex.rb'
) describe '#web_url' do
it { expect(presenter.web_url).to eq("http://localhost/#{project.full_path}/-/blob/#{blob.commit_id}/#{blob.path}") }
end 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 describe '#edit_blob_path' do
let(:project) { create(:project, :repository) } it { expect(presenter.edit_blob_path).to eq("/#{project.full_path}/-/edit/#{blob.commit_id}/#{blob.path}") }
let(:repository) { project.repository } end
let(:blob) { Gitlab::Graphql::Representation::TreeEntry.new(repository.tree.blobs.first, repository) }
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 end
describe '#web_path' do context 'given a Gitlab::Graphql::Representation::TreeEntry' do
let(:project) { create(:project, :repository) } let(:blob) { Gitlab::Graphql::Representation::TreeEntry.new(super(), repository) }
let(:repository) { project.repository }
let(:blob) { Gitlab::Graphql::Representation::TreeEntry.new(repository.tree.blobs.first, 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 end
describe '#highlight' do describe '#highlight' do
subject { described_class.new(blob) } let(:git_blob) { blob.__getobj__ }
it 'returns highlighted content' do it 'returns highlighted content' do
expect(Gitlab::Highlight).to receive(:highlight).with('files/ruby/regex.rb', git_blob.data, plain: nil, language: nil) expect(Gitlab::Highlight).to receive(:highlight).with('files/ruby/regex.rb', git_blob.data, plain: nil, language: nil)
subject.highlight presenter.highlight
end end
it 'returns plain content when :plain is true' do 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) 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 end
context '"to" param is present' do context '"to" param is present' do
...@@ -60,7 +67,7 @@ RSpec.describe BlobPresenter, :seed_helper do ...@@ -60,7 +67,7 @@ RSpec.describe BlobPresenter, :seed_helper do
it 'returns limited highlighted content' do it 'returns limited highlighted content' do
expect(Gitlab::Highlight).to receive(:highlight).with('files/ruby/regex.rb', "line one\n", plain: nil, language: nil) 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
end end
...@@ -72,7 +79,7 @@ RSpec.describe BlobPresenter, :seed_helper do ...@@ -72,7 +79,7 @@ RSpec.describe BlobPresenter, :seed_helper do
it 'passes language to inner call' do 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') 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 end
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