Commit 67312fce authored by Robert Speicher's avatar Robert Speicher

Merge branch 'dm-blob-viewer-concerns' into 'master'

Move some blob viewer stuff around without changing behavior

See merge request !11358
parents e66b811d b0a16320
...@@ -14,7 +14,7 @@ module RendersBlob ...@@ -14,7 +14,7 @@ module RendersBlob
return render_404 unless viewer return render_404 unless viewer
render json: { render json: {
html: view_to_html_string("projects/blob/_viewer", viewer: viewer, load_asynchronously: false) html: view_to_html_string("projects/blob/_viewer", viewer: viewer, load_async: false)
} }
end end
......
...@@ -226,7 +226,7 @@ module BlobHelper ...@@ -226,7 +226,7 @@ module BlobHelper
def open_raw_blob_button(blob) def open_raw_blob_button(blob)
return if blob.empty? return if blob.empty?
if blob.raw_binary? || blob.stored_externally? if blob.raw_binary? || blob.stored_externally?
icon = icon('download') icon = icon('download')
title = 'Download' title = 'Download'
...@@ -242,9 +242,9 @@ module BlobHelper ...@@ -242,9 +242,9 @@ module BlobHelper
case viewer.render_error case viewer.render_error
when :too_large when :too_large
max_size = max_size =
if viewer.absolutely_too_large? if viewer.can_override_max_size?
viewer.absolute_max_size viewer.overridable_max_size
elsif viewer.too_large? else
viewer.max_size viewer.max_size
end end
"it is larger than #{number_to_human_size(max_size)}" "it is larger than #{number_to_human_size(max_size)}"
......
...@@ -5,8 +5,8 @@ module BlobViewer ...@@ -5,8 +5,8 @@ module BlobViewer
included do included do
self.loading_partial_name = 'loading_auxiliary' self.loading_partial_name = 'loading_auxiliary'
self.type = :auxiliary self.type = :auxiliary
self.overridable_max_size = 100.kilobytes
self.max_size = 100.kilobytes self.max_size = 100.kilobytes
self.absolute_max_size = 100.kilobytes
end end
end end
end end
...@@ -2,11 +2,11 @@ module BlobViewer ...@@ -2,11 +2,11 @@ module BlobViewer
class Base class Base
PARTIAL_PATH_PREFIX = 'projects/blob/viewers'.freeze PARTIAL_PATH_PREFIX = 'projects/blob/viewers'.freeze
class_attribute :partial_name, :loading_partial_name, :type, :extensions, :file_type, :client_side, :binary, :switcher_icon, :switcher_title, :max_size, :absolute_max_size class_attribute :partial_name, :loading_partial_name, :type, :extensions, :file_types, :load_async, :binary, :switcher_icon, :switcher_title, :overridable_max_size, :max_size
self.loading_partial_name = 'loading' self.loading_partial_name = 'loading'
delegate :partial_path, :loading_partial_path, :rich?, :simple?, :client_side?, :server_side?, :text?, :binary?, to: :class delegate :partial_path, :loading_partial_path, :rich?, :simple?, :text?, :binary?, to: :class
attr_reader :blob attr_reader :blob
attr_accessor :override_max_size attr_accessor :override_max_size
...@@ -35,12 +35,8 @@ module BlobViewer ...@@ -35,12 +35,8 @@ module BlobViewer
type == :auxiliary type == :auxiliary
end end
def self.client_side? def self.load_async?
client_side load_async
end
def self.server_side?
!client_side?
end end
def self.binary? def self.binary?
...@@ -54,21 +50,33 @@ module BlobViewer ...@@ -54,21 +50,33 @@ module BlobViewer
def self.can_render?(blob, verify_binary: true) def self.can_render?(blob, verify_binary: true)
return false if verify_binary && binary? != blob.binary? return false if verify_binary && binary? != blob.binary?
return true if extensions&.include?(blob.extension) return true if extensions&.include?(blob.extension)
return true if file_type && Gitlab::FileDetector.type_of(blob.path) == file_type return true if file_types&.include?(Gitlab::FileDetector.type_of(blob.path))
false false
end end
def too_large? def load_async?
blob.raw_size > max_size self.class.load_async? && render_error.nil?
end end
def absolutely_too_large? def exceeds_overridable_max_size?
blob.raw_size > absolute_max_size overridable_max_size && blob.raw_size > overridable_max_size
end
def exceeds_max_size?
max_size && blob.raw_size > max_size
end end
def can_override_max_size? def can_override_max_size?
too_large? && !absolutely_too_large? exceeds_overridable_max_size? && !exceeds_max_size?
end
def too_large?
if override_max_size
exceeds_max_size?
else
exceeds_overridable_max_size?
end
end end
# This method is used on the server side to check whether we can attempt to # This method is used on the server side to check whether we can attempt to
...@@ -83,29 +91,13 @@ module BlobViewer ...@@ -83,29 +91,13 @@ module BlobViewer
# binary from `blob_raw_url` and does its own format validation and error # binary from `blob_raw_url` and does its own format validation and error
# rendering, especially for potentially large binary formats. # rendering, especially for potentially large binary formats.
def render_error def render_error
return @render_error if defined?(@render_error) if too_large?
:too_large
@render_error = end
if server_side_but_stored_externally?
# Files that are not stored in the repository, like LFS files and
# build artifacts, can only be rendered using a client-side viewer,
# since we do not want to read large amounts of data into memory on the
# server side. Client-side viewers use JS and can fetch the file from
# `blob_raw_url` using AJAX.
:server_side_but_stored_externally
elsif override_max_size ? absolutely_too_large? : too_large?
:too_large
end
end end
def prepare! def prepare!
# To be overridden by subclasses # To be overridden by subclasses
end end
private
def server_side_but_stored_externally?
server_side? && blob.stored_externally?
end
end end
end end
...@@ -3,9 +3,9 @@ module BlobViewer ...@@ -3,9 +3,9 @@ module BlobViewer
extend ActiveSupport::Concern extend ActiveSupport::Concern
included do included do
self.client_side = true self.load_async = false
self.max_size = 10.megabytes self.overridable_max_size = 10.megabytes
self.absolute_max_size = 50.megabytes self.max_size = 50.megabytes
end end
end end
end end
module BlobViewer module BlobViewer
class Download < Base class Download < Base
include Simple include Simple
# We treat the Download viewer as if it renders the content client-side, include Static
# so that it doesn't attempt to load the entire blob contents and is
# rendered synchronously instead of loaded asynchronously.
include ClientSide
self.partial_name = 'download' self.partial_name = 'download'
self.binary = true self.binary = true
# We can always render the Download viewer, even if the blob is in LFS or too large.
def render_error
nil
end
end end
end end
...@@ -5,7 +5,7 @@ module BlobViewer ...@@ -5,7 +5,7 @@ module BlobViewer
self.partial_name = 'gitlab_ci_yml' self.partial_name = 'gitlab_ci_yml'
self.loading_partial_name = 'gitlab_ci_yml_loading' self.loading_partial_name = 'gitlab_ci_yml_loading'
self.file_type = :gitlab_ci self.file_types = %i(gitlab_ci)
self.binary = false self.binary = false
def validation_message def validation_message
......
module BlobViewer module BlobViewer
class License < Base class License < Base
# We treat the License viewer as if it renders the content client-side,
# so that it doesn't attempt to load the entire blob contents and is
# rendered synchronously instead of loaded asynchronously.
include ClientSide
include Auxiliary include Auxiliary
include Static
self.partial_name = 'license' self.partial_name = 'license'
self.file_type = :license self.file_types = %i(license)
self.binary = false self.binary = false
def license def license
......
...@@ -5,7 +5,7 @@ module BlobViewer ...@@ -5,7 +5,7 @@ module BlobViewer
self.partial_name = 'route_map' self.partial_name = 'route_map'
self.loading_partial_name = 'route_map_loading' self.loading_partial_name = 'route_map_loading'
self.file_type = :route_map self.file_types = %i(route_map)
self.binary = false self.binary = false
def validation_message def validation_message
......
...@@ -3,9 +3,9 @@ module BlobViewer ...@@ -3,9 +3,9 @@ module BlobViewer
extend ActiveSupport::Concern extend ActiveSupport::Concern
included do included do
self.client_side = false self.load_async = true
self.max_size = 2.megabytes self.overridable_max_size = 2.megabytes
self.absolute_max_size = 5.megabytes self.max_size = 5.megabytes
end end
def prepare! def prepare!
...@@ -13,5 +13,18 @@ module BlobViewer ...@@ -13,5 +13,18 @@ module BlobViewer
blob.load_all_data!(blob.project.repository) blob.load_all_data!(blob.project.repository)
end end
end end
def render_error
if blob.stored_externally?
# Files that are not stored in the repository, like LFS files and
# build artifacts, can only be rendered using a client-side viewer,
# since we do not want to read large amounts of data into memory on the
# server side. Client-side viewers use JS and can fetch the file from
# `blob_raw_url` using AJAX.
return :server_side_but_stored_externally
end
super
end
end end
end end
module BlobViewer
module Static
extend ActiveSupport::Concern
included do
self.load_async = false
end
# We can always render a static viewer, even if the blob is too large.
def render_error
nil
end
end
end
...@@ -5,7 +5,7 @@ module BlobViewer ...@@ -5,7 +5,7 @@ module BlobViewer
self.partial_name = 'text' self.partial_name = 'text'
self.binary = false self.binary = false
self.max_size = 1.megabyte self.overridable_max_size = 1.megabyte
self.absolute_max_size = 10.megabytes self.max_size = 10.megabytes
end end
end end
- hidden = local_assigns.fetch(:hidden, false) - hidden = local_assigns.fetch(:hidden, false)
- render_error = viewer.render_error - render_error = viewer.render_error
- load_asynchronously = local_assigns.fetch(:load_asynchronously, viewer.server_side?) && render_error.nil? - load_async = local_assigns.fetch(:load_async, viewer.load_async?)
- viewer_url = local_assigns.fetch(:viewer_url) { url_for(params.merge(viewer: viewer.type, format: :json)) } if load_asynchronously - viewer_url = local_assigns.fetch(:viewer_url) { url_for(params.merge(viewer: viewer.type, format: :json)) } if load_async
.blob-viewer{ data: { type: viewer.type, url: viewer_url }, class: ('hidden' if hidden) } .blob-viewer{ data: { type: viewer.type, url: viewer_url }, class: ('hidden' if hidden) }
- if load_asynchronously - if load_async
= render viewer.loading_partial_path, viewer: viewer = render viewer.loading_partial_path, viewer: viewer
- elsif render_error - elsif render_error
= render 'projects/blob/render_error', viewer: viewer = render 'projects/blob/render_error', viewer: viewer
......
...@@ -116,10 +116,11 @@ describe BlobHelper do ...@@ -116,10 +116,11 @@ describe BlobHelper do
let(:viewer_class) do let(:viewer_class) do
Class.new(BlobViewer::Base) do Class.new(BlobViewer::Base) do
self.max_size = 1.megabyte include BlobViewer::ServerSide
self.absolute_max_size = 5.megabytes
self.overridable_max_size = 1.megabyte
self.max_size = 5.megabytes
self.type = :rich self.type = :rich
self.client_side = false
end end
end end
......
...@@ -7,11 +7,12 @@ describe BlobViewer::Base, model: true do ...@@ -7,11 +7,12 @@ describe BlobViewer::Base, model: true do
let(:viewer_class) do let(:viewer_class) do
Class.new(described_class) do Class.new(described_class) do
include BlobViewer::ServerSide
self.extensions = %w(pdf) self.extensions = %w(pdf)
self.binary = true self.binary = true
self.max_size = 1.megabyte self.overridable_max_size = 1.megabyte
self.absolute_max_size = 5.megabytes self.max_size = 5.megabytes
self.client_side = false
end end
end end
...@@ -38,10 +39,10 @@ describe BlobViewer::Base, model: true do ...@@ -38,10 +39,10 @@ describe BlobViewer::Base, model: true do
context 'when the file type is supported' do context 'when the file type is supported' do
before do before do
viewer_class.file_type = :license viewer_class.file_types = %i(license)
viewer_class.binary = false viewer_class.binary = false
end end
context 'when the binaryness matches' do context 'when the binaryness matches' do
let(:blob) { fake_blob(path: 'LICENSE', binary: false) } let(:blob) { fake_blob(path: 'LICENSE', binary: false) }
...@@ -68,45 +69,45 @@ describe BlobViewer::Base, model: true do ...@@ -68,45 +69,45 @@ describe BlobViewer::Base, model: true do
end end
end end
describe '#too_large?' do describe '#exceeds_overridable_max_size?' do
context 'when the blob size is larger than the max size' do context 'when the blob size is larger than the overridable max size' do
let(:blob) { fake_blob(path: 'file.pdf', size: 2.megabytes) } let(:blob) { fake_blob(path: 'file.pdf', size: 2.megabytes) }
it 'returns true' do it 'returns true' do
expect(viewer.too_large?).to be_truthy expect(viewer.exceeds_overridable_max_size?).to be_truthy
end end
end end
context 'when the blob size is smaller than the max size' do context 'when the blob size is smaller than the overridable max size' do
let(:blob) { fake_blob(path: 'file.pdf', size: 10.kilobytes) } let(:blob) { fake_blob(path: 'file.pdf', size: 10.kilobytes) }
it 'returns false' do it 'returns false' do
expect(viewer.too_large?).to be_falsey expect(viewer.exceeds_overridable_max_size?).to be_falsey
end end
end end
end end
describe '#absolutely_too_large?' do describe '#exceeds_max_size?' do
context 'when the blob size is larger than the absolute max size' do context 'when the blob size is larger than the max size' do
let(:blob) { fake_blob(path: 'file.pdf', size: 10.megabytes) } let(:blob) { fake_blob(path: 'file.pdf', size: 10.megabytes) }
it 'returns true' do it 'returns true' do
expect(viewer.absolutely_too_large?).to be_truthy expect(viewer.exceeds_max_size?).to be_truthy
end end
end end
context 'when the blob size is smaller than the absolute max size' do context 'when the blob size is smaller than the max size' do
let(:blob) { fake_blob(path: 'file.pdf', size: 2.megabytes) } let(:blob) { fake_blob(path: 'file.pdf', size: 2.megabytes) }
it 'returns false' do it 'returns false' do
expect(viewer.absolutely_too_large?).to be_falsey expect(viewer.exceeds_max_size?).to be_falsey
end end
end end
end end
describe '#can_override_max_size?' do describe '#can_override_max_size?' do
context 'when the blob size is larger than the max size' do context 'when the blob size is larger than the overridable max size' do
context 'when the blob size is larger than the absolute max size' do context 'when the blob size is larger than the max size' do
let(:blob) { fake_blob(path: 'file.pdf', size: 10.megabytes) } let(:blob) { fake_blob(path: 'file.pdf', size: 10.megabytes) }
it 'returns false' do it 'returns false' do
...@@ -114,7 +115,7 @@ describe BlobViewer::Base, model: true do ...@@ -114,7 +115,7 @@ describe BlobViewer::Base, model: true do
end end
end end
context 'when the blob size is smaller than the absolute max size' do context 'when the blob size is smaller than the max size' do
let(:blob) { fake_blob(path: 'file.pdf', size: 2.megabytes) } let(:blob) { fake_blob(path: 'file.pdf', size: 2.megabytes) }
it 'returns true' do it 'returns true' do
...@@ -123,7 +124,7 @@ describe BlobViewer::Base, model: true do ...@@ -123,7 +124,7 @@ describe BlobViewer::Base, model: true do
end end
end end
context 'when the blob size is smaller than the max size' do context 'when the blob size is smaller than the overridable max size' do
let(:blob) { fake_blob(path: 'file.pdf', size: 10.kilobytes) } let(:blob) { fake_blob(path: 'file.pdf', size: 10.kilobytes) }
it 'returns false' do it 'returns false' do
...@@ -138,7 +139,7 @@ describe BlobViewer::Base, model: true do ...@@ -138,7 +139,7 @@ describe BlobViewer::Base, model: true do
viewer.override_max_size = true viewer.override_max_size = true
end end
context 'when the blob size is larger than the absolute max size' do context 'when the blob size is larger than the max size' do
let(:blob) { fake_blob(path: 'file.pdf', size: 10.megabytes) } let(:blob) { fake_blob(path: 'file.pdf', size: 10.megabytes) }
it 'returns :too_large' do it 'returns :too_large' do
...@@ -146,7 +147,7 @@ describe BlobViewer::Base, model: true do ...@@ -146,7 +147,7 @@ describe BlobViewer::Base, model: true do
end end
end end
context 'when the blob size is smaller than the absolute max size' do context 'when the blob size is smaller than the max size' do
let(:blob) { fake_blob(path: 'file.pdf', size: 2.megabytes) } let(:blob) { fake_blob(path: 'file.pdf', size: 2.megabytes) }
it 'returns nil' do it 'returns nil' do
...@@ -156,7 +157,7 @@ describe BlobViewer::Base, model: true do ...@@ -156,7 +157,7 @@ describe BlobViewer::Base, model: true do
end end
context 'when the max size is not overridden' do context 'when the max size is not overridden' do
context 'when the blob size is larger than the max size' do context 'when the blob size is larger than the overridable max size' do
let(:blob) { fake_blob(path: 'file.pdf', size: 2.megabytes) } let(:blob) { fake_blob(path: 'file.pdf', size: 2.megabytes) }
it 'returns :too_large' do it 'returns :too_large' do
...@@ -164,7 +165,7 @@ describe BlobViewer::Base, model: true do ...@@ -164,7 +165,7 @@ describe BlobViewer::Base, model: true do
end end
end end
context 'when the blob size is smaller than the max size' do context 'when the blob size is smaller than the overridable max size' do
let(:blob) { fake_blob(path: 'file.pdf', size: 10.kilobytes) } let(:blob) { fake_blob(path: 'file.pdf', size: 10.kilobytes) }
it 'returns nil' do it 'returns nil' do
...@@ -172,19 +173,5 @@ describe BlobViewer::Base, model: true do ...@@ -172,19 +173,5 @@ describe BlobViewer::Base, model: true do
end end
end end
end end
context 'when the viewer is server side but the blob is stored externally' do
let(:project) { build(:empty_project, lfs_enabled: true) }
let(:blob) { fake_blob(path: 'file.pdf', lfs: true) }
before do
allow(Gitlab.config.lfs).to receive(:enabled).and_return(true)
end
it 'return :server_side_but_stored_externally' do
expect(viewer.render_error).to eq(:server_side_but_stored_externally)
end
end
end end
end end
...@@ -22,4 +22,20 @@ describe BlobViewer::ServerSide, model: true do ...@@ -22,4 +22,20 @@ describe BlobViewer::ServerSide, model: true do
subject.prepare! subject.prepare!
end end
end end
describe '#render_error' do
context 'when the blob is stored externally' do
let(:project) { build(:empty_project, lfs_enabled: true) }
let(:blob) { fake_blob(path: 'file.pdf', lfs: true) }
before do
allow(Gitlab.config.lfs).to receive(:enabled).and_return(true)
end
it 'return :server_side_but_stored_externally' do
expect(subject.render_error).to eq(:server_side_but_stored_externally)
end
end
end
end end
...@@ -10,9 +10,9 @@ describe 'projects/blob/_viewer.html.haml', :view do ...@@ -10,9 +10,9 @@ describe 'projects/blob/_viewer.html.haml', :view do
include BlobViewer::Rich include BlobViewer::Rich
self.partial_name = 'text' self.partial_name = 'text'
self.max_size = 1.megabyte self.overridable_max_size = 1.megabyte
self.absolute_max_size = 5.megabytes self.max_size = 5.megabytes
self.client_side = false self.load_async = true
end end
end end
...@@ -35,9 +35,9 @@ describe 'projects/blob/_viewer.html.haml', :view do ...@@ -35,9 +35,9 @@ describe 'projects/blob/_viewer.html.haml', :view do
render partial: 'projects/blob/viewer', locals: { viewer: viewer } render partial: 'projects/blob/viewer', locals: { viewer: viewer }
end end
context 'when the viewer is server side' do context 'when the viewer is loaded asynchronously' do
before do before do
viewer_class.client_side = false viewer_class.load_async = true
end end
context 'when there is no render error' do context 'when there is no render error' do
...@@ -65,9 +65,9 @@ describe 'projects/blob/_viewer.html.haml', :view do ...@@ -65,9 +65,9 @@ describe 'projects/blob/_viewer.html.haml', :view do
end end
end end
context 'when the viewer is client side' do context 'when the viewer is loaded synchronously' do
before do before do
viewer_class.client_side = true viewer_class.load_async = false
end end
context 'when there is no render error' do context 'when there is no render error' do
......
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