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
return render_404 unless viewer
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
......
......@@ -226,7 +226,7 @@ module BlobHelper
def open_raw_blob_button(blob)
return if blob.empty?
if blob.raw_binary? || blob.stored_externally?
icon = icon('download')
title = 'Download'
......@@ -242,9 +242,9 @@ module BlobHelper
case viewer.render_error
when :too_large
max_size =
if viewer.absolutely_too_large?
viewer.absolute_max_size
elsif viewer.too_large?
if viewer.can_override_max_size?
viewer.overridable_max_size
else
viewer.max_size
end
"it is larger than #{number_to_human_size(max_size)}"
......
......@@ -5,8 +5,8 @@ module BlobViewer
included do
self.loading_partial_name = 'loading_auxiliary'
self.type = :auxiliary
self.overridable_max_size = 100.kilobytes
self.max_size = 100.kilobytes
self.absolute_max_size = 100.kilobytes
end
end
end
......@@ -2,11 +2,11 @@ module BlobViewer
class Base
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'
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_accessor :override_max_size
......@@ -35,12 +35,8 @@ module BlobViewer
type == :auxiliary
end
def self.client_side?
client_side
end
def self.server_side?
!client_side?
def self.load_async?
load_async
end
def self.binary?
......@@ -54,21 +50,33 @@ module BlobViewer
def self.can_render?(blob, verify_binary: true)
return false if verify_binary && binary? != blob.binary?
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
end
def too_large?
blob.raw_size > max_size
def load_async?
self.class.load_async? && render_error.nil?
end
def absolutely_too_large?
blob.raw_size > absolute_max_size
def exceeds_overridable_max_size?
overridable_max_size && blob.raw_size > overridable_max_size
end
def exceeds_max_size?
max_size && blob.raw_size > max_size
end
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
# This method is used on the server side to check whether we can attempt to
......@@ -83,29 +91,13 @@ module BlobViewer
# binary from `blob_raw_url` and does its own format validation and error
# rendering, especially for potentially large binary formats.
def render_error
return @render_error if defined?(@render_error)
@render_error =
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
if too_large?
:too_large
end
end
def prepare!
# To be overridden by subclasses
end
private
def server_side_but_stored_externally?
server_side? && blob.stored_externally?
end
end
end
......@@ -3,9 +3,9 @@ module BlobViewer
extend ActiveSupport::Concern
included do
self.client_side = true
self.max_size = 10.megabytes
self.absolute_max_size = 50.megabytes
self.load_async = false
self.overridable_max_size = 10.megabytes
self.max_size = 50.megabytes
end
end
end
module BlobViewer
class Download < Base
include Simple
# We treat the Download 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 Static
self.partial_name = 'download'
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
......@@ -5,7 +5,7 @@ module BlobViewer
self.partial_name = 'gitlab_ci_yml'
self.loading_partial_name = 'gitlab_ci_yml_loading'
self.file_type = :gitlab_ci
self.file_types = %i(gitlab_ci)
self.binary = false
def validation_message
......
module BlobViewer
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 Static
self.partial_name = 'license'
self.file_type = :license
self.file_types = %i(license)
self.binary = false
def license
......
......@@ -5,7 +5,7 @@ module BlobViewer
self.partial_name = 'route_map'
self.loading_partial_name = 'route_map_loading'
self.file_type = :route_map
self.file_types = %i(route_map)
self.binary = false
def validation_message
......
......@@ -3,9 +3,9 @@ module BlobViewer
extend ActiveSupport::Concern
included do
self.client_side = false
self.max_size = 2.megabytes
self.absolute_max_size = 5.megabytes
self.load_async = true
self.overridable_max_size = 2.megabytes
self.max_size = 5.megabytes
end
def prepare!
......@@ -13,5 +13,18 @@ module BlobViewer
blob.load_all_data!(blob.project.repository)
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
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
self.partial_name = 'text'
self.binary = false
self.max_size = 1.megabyte
self.absolute_max_size = 10.megabytes
self.overridable_max_size = 1.megabyte
self.max_size = 10.megabytes
end
end
- hidden = local_assigns.fetch(:hidden, false)
- 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) }
- if load_asynchronously
- if load_async
= render viewer.loading_partial_path, viewer: viewer
- elsif render_error
= render 'projects/blob/render_error', viewer: viewer
......
......@@ -116,10 +116,11 @@ describe BlobHelper do
let(:viewer_class) do
Class.new(BlobViewer::Base) do
self.max_size = 1.megabyte
self.absolute_max_size = 5.megabytes
include BlobViewer::ServerSide
self.overridable_max_size = 1.megabyte
self.max_size = 5.megabytes
self.type = :rich
self.client_side = false
end
end
......
......@@ -7,11 +7,12 @@ describe BlobViewer::Base, model: true do
let(:viewer_class) do
Class.new(described_class) do
include BlobViewer::ServerSide
self.extensions = %w(pdf)
self.binary = true
self.max_size = 1.megabyte
self.absolute_max_size = 5.megabytes
self.client_side = false
self.overridable_max_size = 1.megabyte
self.max_size = 5.megabytes
end
end
......@@ -38,10 +39,10 @@ describe BlobViewer::Base, model: true do
context 'when the file type is supported' do
before do
viewer_class.file_type = :license
viewer_class.file_types = %i(license)
viewer_class.binary = false
end
context 'when the binaryness matches' do
let(:blob) { fake_blob(path: 'LICENSE', binary: false) }
......@@ -68,45 +69,45 @@ describe BlobViewer::Base, model: true do
end
end
describe '#too_large?' do
context 'when the blob size is larger than the max size' do
describe '#exceeds_overridable_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) }
it 'returns true' do
expect(viewer.too_large?).to be_truthy
expect(viewer.exceeds_overridable_max_size?).to be_truthy
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) }
it 'returns false' do
expect(viewer.too_large?).to be_falsey
expect(viewer.exceeds_overridable_max_size?).to be_falsey
end
end
end
describe '#absolutely_too_large?' do
context 'when the blob size is larger than the absolute max size' do
describe '#exceeds_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) }
it 'returns true' do
expect(viewer.absolutely_too_large?).to be_truthy
expect(viewer.exceeds_max_size?).to be_truthy
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) }
it 'returns false' do
expect(viewer.absolutely_too_large?).to be_falsey
expect(viewer.exceeds_max_size?).to be_falsey
end
end
end
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 absolute 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 max size' do
let(:blob) { fake_blob(path: 'file.pdf', size: 10.megabytes) }
it 'returns false' do
......@@ -114,7 +115,7 @@ describe BlobViewer::Base, model: true do
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) }
it 'returns true' do
......@@ -123,7 +124,7 @@ describe BlobViewer::Base, model: true do
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) }
it 'returns false' do
......@@ -138,7 +139,7 @@ describe BlobViewer::Base, model: true do
viewer.override_max_size = true
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) }
it 'returns :too_large' do
......@@ -146,7 +147,7 @@ describe BlobViewer::Base, model: true do
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) }
it 'returns nil' do
......@@ -156,7 +157,7 @@ describe BlobViewer::Base, model: true do
end
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) }
it 'returns :too_large' do
......@@ -164,7 +165,7 @@ describe BlobViewer::Base, model: true do
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) }
it 'returns nil' do
......@@ -172,19 +173,5 @@ describe BlobViewer::Base, model: true do
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
......@@ -22,4 +22,20 @@ describe BlobViewer::ServerSide, model: true do
subject.prepare!
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
......@@ -10,9 +10,9 @@ describe 'projects/blob/_viewer.html.haml', :view do
include BlobViewer::Rich
self.partial_name = 'text'
self.max_size = 1.megabyte
self.absolute_max_size = 5.megabytes
self.client_side = false
self.overridable_max_size = 1.megabyte
self.max_size = 5.megabytes
self.load_async = true
end
end
......@@ -35,9 +35,9 @@ describe 'projects/blob/_viewer.html.haml', :view do
render partial: 'projects/blob/viewer', locals: { viewer: viewer }
end
context 'when the viewer is server side' do
context 'when the viewer is loaded asynchronously' do
before do
viewer_class.client_side = false
viewer_class.load_async = true
end
context 'when there is no render error' do
......@@ -65,9 +65,9 @@ describe 'projects/blob/_viewer.html.haml', :view do
end
end
context 'when the viewer is client side' do
context 'when the viewer is loaded synchronously' do
before do
viewer_class.client_side = true
viewer_class.load_async = false
end
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