Commit 9a17ecc5 authored by Bob Van Landuyt's avatar Bob Van Landuyt

Merge branch 'fj-add-graphql-blob-viewer-to-snippets' into 'master'

Add blob and blob_viewer fields to graphql snippet type

See merge request gitlab-org/gitlab!22960
parents 16b1e794 f34fc09e
# frozen_string_literal: true
module Types
module BlobViewers
class TypeEnum < BaseEnum
graphql_name 'BlobViewersType'
description 'Types of blob viewers'
value 'rich', value: :rich
value 'simple', value: :simple
value 'auxiliary', value: :auxiliary
end
end
end
...@@ -36,10 +36,6 @@ module Types ...@@ -36,10 +36,6 @@ module Types
description: 'File Name of the snippet', description: 'File Name of the snippet',
null: true null: true
field :content, GraphQL::STRING_TYPE,
description: 'Content of the snippet',
null: false
field :description, GraphQL::STRING_TYPE, field :description, GraphQL::STRING_TYPE,
description: 'Description of the snippet', description: 'Description of the snippet',
null: true null: true
...@@ -64,6 +60,10 @@ module Types ...@@ -64,6 +60,10 @@ module Types
description: 'Raw URL of the snippet', description: 'Raw URL of the snippet',
null: false null: false
field :blob, type: Types::Snippets::BlobType,
description: 'Snippet blob',
null: false
markdown_field :description_html, null: true, method: :description markdown_field :description_html, null: true, method: :description
end end
end end
# frozen_string_literal: true
module Types
module Snippets
# rubocop: disable Graphql/AuthorizeTypes
class BlobType < BaseObject
graphql_name 'SnippetBlob'
description 'Represents the snippet blob'
present_using SnippetBlobPresenter
field :highlighted_data, GraphQL::STRING_TYPE,
description: 'Blob highlighted data',
null: true
field :raw_path, GraphQL::STRING_TYPE,
description: 'Blob raw content endpoint path',
null: false
field :size, GraphQL::INT_TYPE,
description: 'Blob size',
null: false
field :binary, GraphQL::BOOLEAN_TYPE,
description: 'Shows whether the blob is binary',
method: :binary?,
null: false
field :name, GraphQL::STRING_TYPE,
description: 'Blob name',
null: true
field :path, GraphQL::STRING_TYPE,
description: 'Blob path',
null: true
field :simple_viewer, type: Types::Snippets::BlobViewerType,
description: 'Blob content simple viewer',
null: false
field :rich_viewer, type: Types::Snippets::BlobViewerType,
description: 'Blob content rich viewer',
null: true
end
# rubocop: enable Graphql/AuthorizeTypes
end
end
# frozen_string_literal: true
module Types
module Snippets
class BlobViewerType < BaseObject # 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 async',
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
end
end
end
...@@ -9,7 +9,7 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated ...@@ -9,7 +9,7 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated
Gitlab::Highlight.highlight( Gitlab::Highlight.highlight(
blob.path, blob.path,
limited_blob_data(to: to), limited_blob_data(to: to),
language: blob.language_from_gitattributes, language: language,
plain: plain plain: plain
) )
end end
...@@ -37,4 +37,8 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated ...@@ -37,4 +37,8 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated
def all_lines def all_lines
@all_lines ||= blob.data.lines @all_lines ||= blob.data.lines
end end
def language
blob.language_from_gitattributes
end
end end
# frozen_string_literal: true
class SnippetBlobPresenter < BlobPresenter
def highlighted_data
return if blob.binary?
if blob.rich_viewer&.partial_name == 'markup'
blob.rendered_markup
else
highlight
end
end
def raw_path
if snippet.is_a?(ProjectSnippet)
raw_project_snippet_path(snippet.project, snippet)
else
raw_snippet_path(snippet)
end
end
private
def snippet
blob.snippet
end
def language
nil
end
end
---
title: Add blob and blob_viewer fields to graphql snippet type
merge_request: 22960
author:
type: changed
...@@ -150,6 +150,15 @@ type BlobEdge { ...@@ -150,6 +150,15 @@ type BlobEdge {
node: Blob node: Blob
} }
"""
Types of blob viewers
"""
enum BlobViewersType {
auxiliary
rich
simple
}
type Commit { type Commit {
""" """
Author of the commit Author of the commit
...@@ -5934,9 +5943,9 @@ type Snippet implements Noteable { ...@@ -5934,9 +5943,9 @@ type Snippet implements Noteable {
author: User! author: User!
""" """
Content of the snippet Snippet blob
""" """
content: String! blob: SnippetBlob!
""" """
Timestamp this snippet was created Timestamp this snippet was created
...@@ -6049,6 +6058,91 @@ type Snippet implements Noteable { ...@@ -6049,6 +6058,91 @@ type Snippet implements Noteable {
webUrl: String! webUrl: String!
} }
"""
Represents the snippet blob
"""
type SnippetBlob {
"""
Shows whether the blob is binary
"""
binary: Boolean!
"""
Blob highlighted data
"""
highlightedData: String
"""
Blob name
"""
name: String
"""
Blob path
"""
path: String
"""
Blob raw content endpoint path
"""
rawPath: String!
"""
Blob content rich viewer
"""
richViewer: SnippetBlobViewer
"""
Blob content simple viewer
"""
simpleViewer: SnippetBlobViewer!
"""
Blob size
"""
size: Int!
}
"""
Represents how the blob content should be displayed
"""
type SnippetBlobViewer {
"""
Shows whether the blob should be displayed collapsed
"""
collapsed: Boolean!
"""
Content file type
"""
fileType: String!
"""
Shows whether the blob content is loaded async
"""
loadAsync: Boolean!
"""
Loading partial name
"""
loadingPartialName: String!
"""
Error rendering the blob content
"""
renderError: String
"""
Shows whether the blob too large to be displayed
"""
tooLarge: Boolean!
"""
Type of blob viewer
"""
type: BlobViewersType!
}
""" """
The connection type for Snippet. The connection type for Snippet.
""" """
......
...@@ -6275,8 +6275,8 @@ ...@@ -6275,8 +6275,8 @@
"deprecationReason": null "deprecationReason": null
}, },
{ {
"name": "content", "name": "blob",
"description": "Content of the snippet", "description": "Snippet blob",
"args": [ "args": [
], ],
...@@ -6284,8 +6284,8 @@ ...@@ -6284,8 +6284,8 @@
"kind": "NON_NULL", "kind": "NON_NULL",
"name": null, "name": null,
"ofType": { "ofType": {
"kind": "SCALAR", "kind": "OBJECT",
"name": "String", "name": "SnippetBlob",
"ofType": null "ofType": null
} }
}, },
...@@ -7004,6 +7004,311 @@ ...@@ -7004,6 +7004,311 @@
], ],
"possibleTypes": null "possibleTypes": null
}, },
{
"kind": "OBJECT",
"name": "SnippetBlob",
"description": "Represents the snippet blob",
"fields": [
{
"name": "binary",
"description": "Shows whether the blob is binary",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "highlightedData",
"description": "Blob highlighted data",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "name",
"description": "Blob name",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "path",
"description": "Blob path",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "rawPath",
"description": "Blob raw content endpoint path",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "richViewer",
"description": "Blob content rich viewer",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "SnippetBlobViewer",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "simpleViewer",
"description": "Blob content simple viewer",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "SnippetBlobViewer",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "size",
"description": "Blob size",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "SnippetBlobViewer",
"description": "Represents how the blob content should be displayed",
"fields": [
{
"name": "collapsed",
"description": "Shows whether the blob should be displayed collapsed",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "fileType",
"description": "Content file type",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "loadAsync",
"description": "Shows whether the blob content is loaded async",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "loadingPartialName",
"description": "Loading partial name",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "renderError",
"description": "Error rendering the blob content",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "tooLarge",
"description": "Shows whether the blob too large to be displayed",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "type",
"description": "Type of blob viewer",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "ENUM",
"name": "BlobViewersType",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "ENUM",
"name": "BlobViewersType",
"description": "Types of blob viewers",
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": [
{
"name": "rich",
"description": null,
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "simple",
"description": null,
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "auxiliary",
"description": null,
"isDeprecated": false,
"deprecationReason": null
}
],
"possibleTypes": null
},
{ {
"kind": "ENUM", "kind": "ENUM",
"name": "VisibilityScopesEnum", "name": "VisibilityScopesEnum",
......
...@@ -926,15 +926,44 @@ Represents a snippet entry ...@@ -926,15 +926,44 @@ Represents a snippet entry
| `project` | Project | The project the snippet is associated with | | `project` | Project | The project the snippet is associated with |
| `author` | User! | The owner of the snippet | | `author` | User! | The owner of the snippet |
| `fileName` | String | File Name of the snippet | | `fileName` | String | File Name of the snippet |
| `content` | String! | Content of the snippet |
| `description` | String | Description of the snippet | | `description` | String | Description of the snippet |
| `visibilityLevel` | VisibilityLevelsEnum! | Visibility Level of the snippet | | `visibilityLevel` | VisibilityLevelsEnum! | Visibility Level of the snippet |
| `createdAt` | Time! | Timestamp this snippet was created | | `createdAt` | Time! | Timestamp this snippet was created |
| `updatedAt` | Time! | Timestamp this snippet was updated | | `updatedAt` | Time! | Timestamp this snippet was updated |
| `webUrl` | String! | Web URL of the snippet | | `webUrl` | String! | Web URL of the snippet |
| `rawUrl` | String! | Raw URL of the snippet | | `rawUrl` | String! | Raw URL of the snippet |
| `blob` | SnippetBlob! | Snippet blob |
| `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` | | `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` |
## SnippetBlob
Represents the snippet blob
| Name | Type | Description |
| --- | ---- | ---------- |
| `highlightedData` | String | Blob highlighted data |
| `rawPath` | String! | Blob raw content endpoint path |
| `size` | Int! | Blob size |
| `binary` | Boolean! | Shows whether the blob is binary |
| `name` | String | Blob name |
| `path` | String | Blob path |
| `simpleViewer` | SnippetBlobViewer! | Blob content simple viewer |
| `richViewer` | SnippetBlobViewer | Blob content rich viewer |
## SnippetBlobViewer
Represents how the blob content should be displayed
| Name | Type | Description |
| --- | ---- | ---------- |
| `type` | BlobViewersType! | Type of blob viewer |
| `loadAsync` | Boolean! | Shows whether the blob content is loaded async |
| `collapsed` | Boolean! | Shows whether the blob should be displayed collapsed |
| `tooLarge` | Boolean! | Shows whether the blob too large to be displayed |
| `renderError` | String | Error rendering the blob content |
| `fileType` | String! | Content file type |
| `loadingPartialName` | String! | Loading partial name |
## SnippetPermissions ## SnippetPermissions
| Name | Type | Description | | Name | Type | Description |
......
# frozen_string_literal: true
require 'spec_helper'
describe Types::BlobViewers::TypeEnum do
it { expect(described_class.graphql_name).to eq('BlobViewersType') }
it 'exposes all tree entry types' do
expect(described_class.values.keys).to include(*%w[rich simple auxiliary])
end
end
...@@ -5,10 +5,10 @@ require 'spec_helper' ...@@ -5,10 +5,10 @@ require 'spec_helper'
describe GitlabSchema.types['Snippet'] do describe GitlabSchema.types['Snippet'] do
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, :content, :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, :notes, :discussions,
:user_permissions, :description_html] :user_permissions, :description_html, :blob]
is_expected.to have_graphql_fields(*expected_fields) is_expected.to have_graphql_fields(*expected_fields)
end end
......
# frozen_string_literal: true
require 'spec_helper'
describe GitlabSchema.types['SnippetBlob'] do
it 'has the correct fields' do
expected_fields = [:highlighted_data, :raw_path,
:size, :binary, :name, :path,
:simple_viewer, :rich_viewer]
is_expected.to have_graphql_fields(*expected_fields)
end
end
# frozen_string_literal: true
require 'spec_helper'
describe GitlabSchema.types['SnippetBlobViewer'] do
it 'has the correct fields' do
expected_fields = [:type, :load_async, :too_large, :collapsed,
:render_error, :file_type, :loading_partial_name]
is_expected.to have_graphql_fields(*expected_fields)
end
end
# frozen_string_literal: true
require 'spec_helper'
describe SnippetBlobPresenter do
describe '#highlighted_data' do
let(:snippet) { build(:personal_snippet) }
subject { described_class.new(snippet.blob).highlighted_data }
it 'returns nil when the snippet blob is binary' do
allow(snippet.blob).to receive(:binary?).and_return(true)
expect(subject).to be_nil
end
it 'returns markdown content when snippet file is markup' do
snippet.file_name = 'test.md'
snippet.content = '*foo*'
expect(subject).to eq '<p data-sourcepos="1:1-1:5" dir="auto"><em>foo</em></p>'
end
it 'returns syntax highlighted content' do
snippet.file_name = 'test.rb'
snippet.content = 'class Foo;end'
expect(subject)
.to eq '<span id="LC1" class="line" lang="ruby"><span class="k">class</span> <span class="nc">Foo</span><span class="p">;</span><span class="k">end</span></span>'
end
it 'returns plain text highlighted content' do
snippet.file_name = 'test'
snippet.content = 'foo'
expect(described_class.new(snippet.blob).highlighted_data).to eq '<span id="LC1" class="line" lang="plaintext">foo</span>'
end
end
describe '#raw_path' do
subject { described_class.new(snippet.blob).raw_path }
context 'with ProjectSnippet' do
let!(:project) { create(:project) }
let(:snippet) { build(:project_snippet, project: project, id: 1) }
it 'returns the raw path' do
expect(subject).to eq "/#{snippet.project.full_path}/snippets/1/raw"
end
end
context 'with PersonalSnippet' do
let(:snippet) { build(:personal_snippet, id: 1) }
it 'returns the raw path' do
expect(subject).to eq "/snippets/1/raw"
end
end
end
end
...@@ -67,7 +67,7 @@ describe 'Creating a Snippet' do ...@@ -67,7 +67,7 @@ describe 'Creating a Snippet' do
it 'returns the created Snippet' do it 'returns the created Snippet' do
post_graphql_mutation(mutation, current_user: current_user) post_graphql_mutation(mutation, current_user: current_user)
expect(mutation_response['snippet']['content']).to eq(content) expect(mutation_response['snippet']['blob']['highlightedData']).to match(content)
expect(mutation_response['snippet']['title']).to eq(title) expect(mutation_response['snippet']['title']).to eq(title)
expect(mutation_response['snippet']['description']).to eq(description) expect(mutation_response['snippet']['description']).to eq(description)
expect(mutation_response['snippet']['fileName']).to eq(file_name) expect(mutation_response['snippet']['fileName']).to eq(file_name)
...@@ -92,7 +92,7 @@ describe 'Creating a Snippet' do ...@@ -92,7 +92,7 @@ describe 'Creating a Snippet' do
it 'returns the created Snippet' do it 'returns the created Snippet' do
post_graphql_mutation(mutation, current_user: current_user) post_graphql_mutation(mutation, current_user: current_user)
expect(mutation_response['snippet']['content']).to eq(content) expect(mutation_response['snippet']['blob']['highlightedData']).to match(content)
expect(mutation_response['snippet']['title']).to eq(title) expect(mutation_response['snippet']['title']).to eq(title)
expect(mutation_response['snippet']['description']).to eq(description) expect(mutation_response['snippet']['description']).to eq(description)
expect(mutation_response['snippet']['fileName']).to eq(file_name) expect(mutation_response['snippet']['fileName']).to eq(file_name)
......
...@@ -56,7 +56,7 @@ describe 'Updating a Snippet' do ...@@ -56,7 +56,7 @@ describe 'Updating a Snippet' do
it 'returns the updated Snippet' do it 'returns the updated Snippet' do
post_graphql_mutation(mutation, current_user: current_user) post_graphql_mutation(mutation, current_user: current_user)
expect(mutation_response['snippet']['content']).to eq(updated_content) expect(mutation_response['snippet']['blob']['highlightedData']).to match(updated_content)
expect(mutation_response['snippet']['title']).to eq(updated_title) expect(mutation_response['snippet']['title']).to eq(updated_title)
expect(mutation_response['snippet']['description']).to eq(updated_description) expect(mutation_response['snippet']['description']).to eq(updated_description)
expect(mutation_response['snippet']['fileName']).to eq(updated_file_name) expect(mutation_response['snippet']['fileName']).to eq(updated_file_name)
...@@ -77,7 +77,7 @@ describe 'Updating a Snippet' do ...@@ -77,7 +77,7 @@ describe 'Updating a Snippet' do
it 'returns the Snippet with its original values' do it 'returns the Snippet with its original values' do
post_graphql_mutation(mutation, current_user: current_user) post_graphql_mutation(mutation, current_user: current_user)
expect(mutation_response['snippet']['content']).to eq(original_content) expect(mutation_response['snippet']['blob']['highlightedData']).to match(original_content)
expect(mutation_response['snippet']['title']).to eq(original_title) expect(mutation_response['snippet']['title']).to eq(original_title)
expect(mutation_response['snippet']['description']).to eq(original_description) expect(mutation_response['snippet']['description']).to eq(original_description)
expect(mutation_response['snippet']['fileName']).to eq(original_file_name) expect(mutation_response['snippet']['fileName']).to eq(original_file_name)
......
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