Commit acffc062 authored by Douwe Maan's avatar Douwe Maan

Specify explicitly whether a blob viewer should be loaded asynchronously

parent 4328bc17
...@@ -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
......
...@@ -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_types, :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, :max_size, :absolute_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?
...@@ -59,6 +55,10 @@ module BlobViewer ...@@ -59,6 +55,10 @@ module BlobViewer
false false
end end
def load_async?
self.class.load_async? && render_error.nil?
end
def too_large? def too_large?
max_size && blob.raw_size > max_size max_size && blob.raw_size > max_size
end end
...@@ -83,29 +83,13 @@ module BlobViewer ...@@ -83,29 +83,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 override_max_size ? absolutely_too_large? : 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,7 +3,7 @@ module BlobViewer ...@@ -3,7 +3,7 @@ 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.max_size = 10.megabytes
self.absolute_max_size = 50.megabytes self.absolute_max_size = 50.megabytes
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
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_types = %i(license) self.file_types = %i(license)
......
...@@ -3,7 +3,7 @@ module BlobViewer ...@@ -3,7 +3,7 @@ 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.max_size = 2.megabytes
self.absolute_max_size = 5.megabytes self.absolute_max_size = 5.megabytes
end end
...@@ -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
- 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
include BlobViewer::ServerSide
self.max_size = 1.megabyte self.max_size = 1.megabyte
self.absolute_max_size = 5.megabytes self.absolute_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.max_size = 1.megabyte
self.absolute_max_size = 5.megabytes self.absolute_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) }
...@@ -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
...@@ -12,7 +12,7 @@ describe 'projects/blob/_viewer.html.haml', :view do ...@@ -12,7 +12,7 @@ describe 'projects/blob/_viewer.html.haml', :view do
self.partial_name = 'text' self.partial_name = 'text'
self.max_size = 1.megabyte self.max_size = 1.megabyte
self.absolute_max_size = 5.megabytes self.absolute_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