Merge branch 'dm-auxiliary-viewers' into 'master'
Implement auxiliary blob viewers See merge request !11195
This commit is contained in:
commit
1c75e4e6a0
32 changed files with 616 additions and 140 deletions
|
@ -1,20 +1,38 @@
|
||||||
/* global Flash */
|
/* global Flash */
|
||||||
export default class BlobViewer {
|
export default class BlobViewer {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
BlobViewer.initAuxiliaryViewer();
|
||||||
|
|
||||||
|
this.initMainViewers();
|
||||||
|
}
|
||||||
|
|
||||||
|
static initAuxiliaryViewer() {
|
||||||
|
const auxiliaryViewer = document.querySelector('.blob-viewer[data-type="auxiliary"]');
|
||||||
|
if (!auxiliaryViewer) return;
|
||||||
|
|
||||||
|
BlobViewer.loadViewer(auxiliaryViewer);
|
||||||
|
}
|
||||||
|
|
||||||
|
initMainViewers() {
|
||||||
|
this.$fileHolder = $('.file-holder');
|
||||||
|
if (!this.$fileHolder.length) return;
|
||||||
|
|
||||||
this.switcher = document.querySelector('.js-blob-viewer-switcher');
|
this.switcher = document.querySelector('.js-blob-viewer-switcher');
|
||||||
this.switcherBtns = document.querySelectorAll('.js-blob-viewer-switch-btn');
|
this.switcherBtns = document.querySelectorAll('.js-blob-viewer-switch-btn');
|
||||||
this.copySourceBtn = document.querySelector('.js-copy-blob-source-btn');
|
this.copySourceBtn = document.querySelector('.js-copy-blob-source-btn');
|
||||||
this.simpleViewer = document.querySelector('.blob-viewer[data-type="simple"]');
|
|
||||||
this.richViewer = document.querySelector('.blob-viewer[data-type="rich"]');
|
|
||||||
this.$fileHolder = $('.file-holder');
|
|
||||||
|
|
||||||
const initialViewer = document.querySelector('.blob-viewer:not(.hidden)');
|
this.simpleViewer = this.$fileHolder[0].querySelector('.blob-viewer[data-type="simple"]');
|
||||||
if (!initialViewer) return;
|
this.richViewer = this.$fileHolder[0].querySelector('.blob-viewer[data-type="rich"]');
|
||||||
|
|
||||||
let initialViewerName = initialViewer.getAttribute('data-type');
|
|
||||||
|
|
||||||
this.initBindings();
|
this.initBindings();
|
||||||
|
|
||||||
|
this.switchToInitialViewer();
|
||||||
|
}
|
||||||
|
|
||||||
|
switchToInitialViewer() {
|
||||||
|
const initialViewer = this.$fileHolder[0].querySelector('.blob-viewer:not(.hidden)');
|
||||||
|
let initialViewerName = initialViewer.getAttribute('data-type');
|
||||||
|
|
||||||
if (this.switcher && location.hash.indexOf('#L') === 0) {
|
if (this.switcher && location.hash.indexOf('#L') === 0) {
|
||||||
initialViewerName = 'simple';
|
initialViewerName = 'simple';
|
||||||
}
|
}
|
||||||
|
@ -64,40 +82,13 @@ export default class BlobViewer {
|
||||||
$(this.copySourceBtn).tooltip('fixTitle');
|
$(this.copySourceBtn).tooltip('fixTitle');
|
||||||
}
|
}
|
||||||
|
|
||||||
loadViewer(viewerParam) {
|
|
||||||
const viewer = viewerParam;
|
|
||||||
const url = viewer.getAttribute('data-url');
|
|
||||||
|
|
||||||
if (!url || viewer.getAttribute('data-loaded') || viewer.getAttribute('data-loading')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
viewer.setAttribute('data-loading', 'true');
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
url,
|
|
||||||
dataType: 'JSON',
|
|
||||||
})
|
|
||||||
.fail(() => new Flash('Error loading source view'))
|
|
||||||
.done((data) => {
|
|
||||||
viewer.innerHTML = data.html;
|
|
||||||
$(viewer).syntaxHighlight();
|
|
||||||
|
|
||||||
viewer.setAttribute('data-loaded', 'true');
|
|
||||||
|
|
||||||
this.$fileHolder.trigger('highlight:line');
|
|
||||||
|
|
||||||
this.toggleCopyButtonState();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
switchToViewer(name) {
|
switchToViewer(name) {
|
||||||
const newViewer = document.querySelector(`.blob-viewer[data-type='${name}']`);
|
const newViewer = this.$fileHolder[0].querySelector(`.blob-viewer[data-type='${name}']`);
|
||||||
if (this.activeViewer === newViewer) return;
|
if (this.activeViewer === newViewer) return;
|
||||||
|
|
||||||
const oldButton = document.querySelector('.js-blob-viewer-switch-btn.active');
|
const oldButton = document.querySelector('.js-blob-viewer-switch-btn.active');
|
||||||
const newButton = document.querySelector(`.js-blob-viewer-switch-btn[data-viewer='${name}']`);
|
const newButton = document.querySelector(`.js-blob-viewer-switch-btn[data-viewer='${name}']`);
|
||||||
const oldViewer = document.querySelector(`.blob-viewer:not([data-type='${name}'])`);
|
const oldViewer = this.$fileHolder[0].querySelector(`.blob-viewer:not([data-type='${name}'])`);
|
||||||
|
|
||||||
if (oldButton) {
|
if (oldButton) {
|
||||||
oldButton.classList.remove('active');
|
oldButton.classList.remove('active');
|
||||||
|
@ -118,6 +109,40 @@ export default class BlobViewer {
|
||||||
|
|
||||||
this.toggleCopyButtonState();
|
this.toggleCopyButtonState();
|
||||||
|
|
||||||
this.loadViewer(newViewer);
|
BlobViewer.loadViewer(newViewer)
|
||||||
|
.then((viewer) => {
|
||||||
|
$(viewer).syntaxHighlight();
|
||||||
|
|
||||||
|
this.$fileHolder.trigger('highlight:line');
|
||||||
|
|
||||||
|
this.toggleCopyButtonState();
|
||||||
|
})
|
||||||
|
.catch(() => new Flash('Error loading viewer'));
|
||||||
|
}
|
||||||
|
|
||||||
|
static loadViewer(viewerParam) {
|
||||||
|
const viewer = viewerParam;
|
||||||
|
const url = viewer.getAttribute('data-url');
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!url || viewer.getAttribute('data-loaded') || viewer.getAttribute('data-loading')) {
|
||||||
|
resolve(viewer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
viewer.setAttribute('data-loading', 'true');
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url,
|
||||||
|
dataType: 'JSON',
|
||||||
|
})
|
||||||
|
.fail(reject)
|
||||||
|
.done((data) => {
|
||||||
|
viewer.innerHTML = data.html;
|
||||||
|
viewer.setAttribute('data-loaded', 'true');
|
||||||
|
|
||||||
|
resolve(viewer);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,11 @@ module RendersBlob
|
||||||
|
|
||||||
def render_blob_json(blob)
|
def render_blob_json(blob)
|
||||||
viewer =
|
viewer =
|
||||||
if params[:viewer] == 'rich'
|
case params[:viewer]
|
||||||
|
when 'rich'
|
||||||
blob.rich_viewer
|
blob.rich_viewer
|
||||||
|
when 'auxiliary'
|
||||||
|
blob.auxiliary_viewer
|
||||||
else
|
else
|
||||||
blob.simple_viewer
|
blob.simple_viewer
|
||||||
end
|
end
|
||||||
|
|
|
@ -110,11 +110,8 @@ module ProjectsHelper
|
||||||
end
|
end
|
||||||
|
|
||||||
def license_short_name(project)
|
def license_short_name(project)
|
||||||
return 'LICENSE' if project.repository.license_key.nil?
|
license = project.repository.license
|
||||||
|
license&.nickname || license&.name || 'LICENSE'
|
||||||
license = Licensee::License.new(project.repository.license_key)
|
|
||||||
|
|
||||||
license.nickname || license.name
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def last_push_event
|
def last_push_event
|
||||||
|
|
|
@ -34,10 +34,13 @@ class Blob < SimpleDelegator
|
||||||
|
|
||||||
BlobViewer::BinarySTL,
|
BlobViewer::BinarySTL,
|
||||||
BlobViewer::TextSTL
|
BlobViewer::TextSTL
|
||||||
].freeze
|
].sort_by { |v| v.binary? ? 0 : 1 }.freeze
|
||||||
|
|
||||||
BINARY_VIEWERS = RICH_VIEWERS.select(&:binary?).freeze
|
AUXILIARY_VIEWERS = [
|
||||||
TEXT_VIEWERS = RICH_VIEWERS.select(&:text?).freeze
|
BlobViewer::GitlabCiYml,
|
||||||
|
BlobViewer::RouteMap,
|
||||||
|
BlobViewer::License
|
||||||
|
].freeze
|
||||||
|
|
||||||
attr_reader :project
|
attr_reader :project
|
||||||
|
|
||||||
|
@ -154,6 +157,12 @@ class Blob < SimpleDelegator
|
||||||
@rich_viewer = rich_viewer_class&.new(self)
|
@rich_viewer = rich_viewer_class&.new(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def auxiliary_viewer
|
||||||
|
return @auxiliary_viewer if defined?(@auxiliary_viewer)
|
||||||
|
|
||||||
|
@auxiliary_viewer = auxiliary_viewer_class&.new(self)
|
||||||
|
end
|
||||||
|
|
||||||
def rendered_as_text?(ignore_errors: true)
|
def rendered_as_text?(ignore_errors: true)
|
||||||
simple_viewer.text? && (ignore_errors || simple_viewer.render_error.nil?)
|
simple_viewer.text? && (ignore_errors || simple_viewer.render_error.nil?)
|
||||||
end
|
end
|
||||||
|
@ -180,17 +189,18 @@ class Blob < SimpleDelegator
|
||||||
end
|
end
|
||||||
|
|
||||||
def rich_viewer_class
|
def rich_viewer_class
|
||||||
|
viewer_class_from(RICH_VIEWERS)
|
||||||
|
end
|
||||||
|
|
||||||
|
def auxiliary_viewer_class
|
||||||
|
viewer_class_from(AUXILIARY_VIEWERS)
|
||||||
|
end
|
||||||
|
|
||||||
|
def viewer_class_from(classes)
|
||||||
return if empty? || external_storage_error?
|
return if empty? || external_storage_error?
|
||||||
|
|
||||||
classes =
|
verify_binary = !stored_externally?
|
||||||
if stored_externally?
|
|
||||||
BINARY_VIEWERS + TEXT_VIEWERS
|
|
||||||
elsif binary?
|
|
||||||
BINARY_VIEWERS
|
|
||||||
else # text
|
|
||||||
TEXT_VIEWERS
|
|
||||||
end
|
|
||||||
|
|
||||||
classes.find { |viewer_class| viewer_class.can_render?(self) }
|
classes.find { |viewer_class| viewer_class.can_render?(self, verify_binary: verify_binary) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
12
app/models/blob_viewer/auxiliary.rb
Normal file
12
app/models/blob_viewer/auxiliary.rb
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
module BlobViewer
|
||||||
|
module Auxiliary
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
self.loading_partial_name = 'loading_auxiliary'
|
||||||
|
self.type = :auxiliary
|
||||||
|
self.max_size = 100.kilobytes
|
||||||
|
self.absolute_max_size = 100.kilobytes
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,8 +1,12 @@
|
||||||
module BlobViewer
|
module BlobViewer
|
||||||
class Base
|
class Base
|
||||||
class_attribute :partial_name, :type, :extensions, :client_side, :binary, :switcher_icon, :switcher_title, :max_size, :absolute_max_size
|
PARTIAL_PATH_PREFIX = 'projects/blob/viewers'.freeze
|
||||||
|
|
||||||
delegate :partial_path, :rich?, :simple?, :client_side?, :server_side?, :text?, :binary?, to: :class
|
class_attribute :partial_name, :loading_partial_name, :type, :extensions, :file_type, :client_side, :binary, :switcher_icon, :switcher_title, :max_size, :absolute_max_size
|
||||||
|
|
||||||
|
self.loading_partial_name = 'loading'
|
||||||
|
|
||||||
|
delegate :partial_path, :loading_partial_path, :rich?, :simple?, :client_side?, :server_side?, :text?, :binary?, to: :class
|
||||||
|
|
||||||
attr_reader :blob
|
attr_reader :blob
|
||||||
attr_accessor :override_max_size
|
attr_accessor :override_max_size
|
||||||
|
@ -12,7 +16,11 @@ module BlobViewer
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.partial_path
|
def self.partial_path
|
||||||
"projects/blob/viewers/#{partial_name}"
|
File.join(PARTIAL_PATH_PREFIX, partial_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.loading_partial_path
|
||||||
|
File.join(PARTIAL_PATH_PREFIX, loading_partial_name)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.rich?
|
def self.rich?
|
||||||
|
@ -23,6 +31,10 @@ module BlobViewer
|
||||||
type == :simple
|
type == :simple
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.auxiliary?
|
||||||
|
type == :auxiliary
|
||||||
|
end
|
||||||
|
|
||||||
def self.client_side?
|
def self.client_side?
|
||||||
client_side
|
client_side
|
||||||
end
|
end
|
||||||
|
@ -39,8 +51,12 @@ module BlobViewer
|
||||||
!binary?
|
!binary?
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.can_render?(blob)
|
def self.can_render?(blob, verify_binary: true)
|
||||||
!extensions || extensions.include?(blob.extension)
|
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
|
||||||
|
|
||||||
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
def too_large?
|
def too_large?
|
||||||
|
@ -83,9 +99,7 @@ module BlobViewer
|
||||||
end
|
end
|
||||||
|
|
||||||
def prepare!
|
def prepare!
|
||||||
if server_side? && blob.project
|
# To be overridden by subclasses
|
||||||
blob.load_all_data!(blob.project.repository)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
23
app/models/blob_viewer/gitlab_ci_yml.rb
Normal file
23
app/models/blob_viewer/gitlab_ci_yml.rb
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
module BlobViewer
|
||||||
|
class GitlabCiYml < Base
|
||||||
|
include ServerSide
|
||||||
|
include Auxiliary
|
||||||
|
|
||||||
|
self.partial_name = 'gitlab_ci_yml'
|
||||||
|
self.loading_partial_name = 'gitlab_ci_yml_loading'
|
||||||
|
self.file_type = :gitlab_ci
|
||||||
|
self.binary = false
|
||||||
|
|
||||||
|
def validation_message
|
||||||
|
return @validation_message if defined?(@validation_message)
|
||||||
|
|
||||||
|
prepare!
|
||||||
|
|
||||||
|
@validation_message = Ci::GitlabCiYamlProcessor.validation_message(blob.data)
|
||||||
|
end
|
||||||
|
|
||||||
|
def valid?
|
||||||
|
validation_message.blank?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
23
app/models/blob_viewer/license.rb
Normal file
23
app/models/blob_viewer/license.rb
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
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
|
||||||
|
|
||||||
|
self.partial_name = 'license'
|
||||||
|
self.file_type = :license
|
||||||
|
self.binary = false
|
||||||
|
|
||||||
|
def license
|
||||||
|
blob.project.repository.license
|
||||||
|
end
|
||||||
|
|
||||||
|
def render_error
|
||||||
|
return if license
|
||||||
|
|
||||||
|
:unknown_license
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
30
app/models/blob_viewer/route_map.rb
Normal file
30
app/models/blob_viewer/route_map.rb
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
module BlobViewer
|
||||||
|
class RouteMap < Base
|
||||||
|
include ServerSide
|
||||||
|
include Auxiliary
|
||||||
|
|
||||||
|
self.partial_name = 'route_map'
|
||||||
|
self.loading_partial_name = 'route_map_loading'
|
||||||
|
self.file_type = :route_map
|
||||||
|
self.binary = false
|
||||||
|
|
||||||
|
def validation_message
|
||||||
|
return @validation_message if defined?(@validation_message)
|
||||||
|
|
||||||
|
prepare!
|
||||||
|
|
||||||
|
@validation_message =
|
||||||
|
begin
|
||||||
|
Gitlab::RouteMap.new(blob.data)
|
||||||
|
|
||||||
|
nil
|
||||||
|
rescue Gitlab::RouteMap::FormatError => e
|
||||||
|
e.message
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def valid?
|
||||||
|
validation_message.blank?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -7,5 +7,11 @@ module BlobViewer
|
||||||
self.max_size = 2.megabytes
|
self.max_size = 2.megabytes
|
||||||
self.absolute_max_size = 5.megabytes
|
self.absolute_max_size = 5.megabytes
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def prepare!
|
||||||
|
if blob.project
|
||||||
|
blob.load_all_data!(blob.project.repository)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -30,7 +30,7 @@ class Repository
|
||||||
METHOD_CACHES_FOR_FILE_TYPES = {
|
METHOD_CACHES_FOR_FILE_TYPES = {
|
||||||
readme: :rendered_readme,
|
readme: :rendered_readme,
|
||||||
changelog: :changelog,
|
changelog: :changelog,
|
||||||
license: %i(license_blob license_key),
|
license: %i(license_blob license_key license),
|
||||||
contributing: :contribution_guide,
|
contributing: :contribution_guide,
|
||||||
gitignore: :gitignore,
|
gitignore: :gitignore,
|
||||||
koding: :koding_yml,
|
koding: :koding_yml,
|
||||||
|
@ -42,13 +42,13 @@ class Repository
|
||||||
# variable.
|
# variable.
|
||||||
#
|
#
|
||||||
# This only works for methods that do not take any arguments.
|
# This only works for methods that do not take any arguments.
|
||||||
def self.cache_method(name, fallback: nil)
|
def self.cache_method(name, fallback: nil, memoize_only: false)
|
||||||
original = :"_uncached_#{name}"
|
original = :"_uncached_#{name}"
|
||||||
|
|
||||||
alias_method(original, name)
|
alias_method(original, name)
|
||||||
|
|
||||||
define_method(name) do
|
define_method(name) do
|
||||||
cache_method_output(name, fallback: fallback) { __send__(original) }
|
cache_method_output(name, fallback: fallback, memoize_only: memoize_only) { __send__(original) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -549,6 +549,13 @@ class Repository
|
||||||
end
|
end
|
||||||
cache_method :license_key
|
cache_method :license_key
|
||||||
|
|
||||||
|
def license
|
||||||
|
return unless license_key
|
||||||
|
|
||||||
|
Licensee::License.new(license_key)
|
||||||
|
end
|
||||||
|
cache_method :license, memoize_only: true
|
||||||
|
|
||||||
def gitignore
|
def gitignore
|
||||||
file_on_head(:gitignore)
|
file_on_head(:gitignore)
|
||||||
end
|
end
|
||||||
|
@ -1061,14 +1068,20 @@ class Repository
|
||||||
#
|
#
|
||||||
# key - The name of the key to cache the data in.
|
# key - The name of the key to cache the data in.
|
||||||
# fallback - A value to fall back to in the event of a Git error.
|
# fallback - A value to fall back to in the event of a Git error.
|
||||||
def cache_method_output(key, fallback: nil, &block)
|
def cache_method_output(key, fallback: nil, memoize_only: false, &block)
|
||||||
ivar = cache_instance_variable_name(key)
|
ivar = cache_instance_variable_name(key)
|
||||||
|
|
||||||
if instance_variable_defined?(ivar)
|
if instance_variable_defined?(ivar)
|
||||||
instance_variable_get(ivar)
|
instance_variable_get(ivar)
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
instance_variable_set(ivar, cache.fetch(key, &block))
|
value =
|
||||||
|
if memoize_only
|
||||||
|
yield
|
||||||
|
else
|
||||||
|
cache.fetch(key, &block)
|
||||||
|
end
|
||||||
|
instance_variable_set(ivar, value)
|
||||||
rescue Rugged::ReferenceError, Gitlab::Git::Repository::NoRepository
|
rescue Rugged::ReferenceError, Gitlab::Git::Repository::NoRepository
|
||||||
# if e.g. HEAD or the entire repository doesn't exist we want to
|
# if e.g. HEAD or the entire repository doesn't exist we want to
|
||||||
# gracefully handle this and not cache anything.
|
# gracefully handle this and not cache anything.
|
||||||
|
@ -1083,8 +1096,8 @@ class Repository
|
||||||
|
|
||||||
def file_on_head(type)
|
def file_on_head(type)
|
||||||
if head = tree(:head)
|
if head = tree(:head)
|
||||||
head.blobs.find do |file|
|
head.blobs.find do |blob|
|
||||||
Gitlab::FileDetector.type_of(file.name) == type
|
Gitlab::FileDetector.type_of(blob.path) == type
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,6 +6,11 @@
|
||||||
- blob_commit = @repository.last_commit_for_path(@commit.id, blob.path)
|
- blob_commit = @repository.last_commit_for_path(@commit.id, blob.path)
|
||||||
= render blob_commit, project: @project, ref: @ref
|
= render blob_commit, project: @project, ref: @ref
|
||||||
|
|
||||||
|
- auxiliary_viewer = blob.auxiliary_viewer
|
||||||
|
- if auxiliary_viewer && !auxiliary_viewer.render_error
|
||||||
|
.well-segment.blob-auxiliary-viewer
|
||||||
|
= render 'projects/blob/viewer', viewer: auxiliary_viewer
|
||||||
|
|
||||||
#blob-content-holder.blob-content-holder
|
#blob-content-holder.blob-content-holder
|
||||||
%article.file-holder
|
%article.file-holder
|
||||||
= render "projects/blob/header", blob: blob
|
= render "projects/blob/header", blob: blob
|
||||||
|
|
|
@ -5,8 +5,7 @@
|
||||||
- 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_asynchronously
|
||||||
.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_asynchronously
|
||||||
.text-center.prepend-top-default.append-bottom-default
|
= render viewer.loading_partial_path, viewer: viewer
|
||||||
= icon('spinner spin 2x', 'aria-hidden' => 'true', 'aria-label' => 'Loading content')
|
|
||||||
- elsif render_error
|
- elsif render_error
|
||||||
= render 'projects/blob/render_error', viewer: viewer
|
= render 'projects/blob/render_error', viewer: viewer
|
||||||
- else
|
- else
|
||||||
|
|
9
app/views/projects/blob/viewers/_gitlab_ci_yml.html.haml
Normal file
9
app/views/projects/blob/viewers/_gitlab_ci_yml.html.haml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
- if viewer.valid?
|
||||||
|
= icon('check fw')
|
||||||
|
This GitLab CI configuration is valid.
|
||||||
|
- else
|
||||||
|
= icon('warning fw')
|
||||||
|
This GitLab CI configuration is invalid:
|
||||||
|
= viewer.validation_message
|
||||||
|
|
||||||
|
= link_to 'Learn more', help_page_path('ci/yaml/README')
|
|
@ -0,0 +1,4 @@
|
||||||
|
= icon('spinner spin fw')
|
||||||
|
Validating GitLab CI configuration…
|
||||||
|
|
||||||
|
= link_to 'Learn more', help_page_path('ci/yaml/README')
|
8
app/views/projects/blob/viewers/_license.html.haml
Normal file
8
app/views/projects/blob/viewers/_license.html.haml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
- license = viewer.license
|
||||||
|
|
||||||
|
= icon('balance-scale fw')
|
||||||
|
This project is licensed under the
|
||||||
|
= succeed '.' do
|
||||||
|
%strong= license.name
|
||||||
|
|
||||||
|
= link_to 'Learn more about this license', license.url, target: '_blank', rel: 'noopener noreferrer'
|
2
app/views/projects/blob/viewers/_loading.html.haml
Normal file
2
app/views/projects/blob/viewers/_loading.html.haml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
.text-center.prepend-top-default.append-bottom-default
|
||||||
|
= icon('spinner spin 2x', 'aria-hidden' => 'true', 'aria-label' => 'Loading content…')
|
|
@ -0,0 +1,2 @@
|
||||||
|
= icon('spinner spin fw')
|
||||||
|
Loading…
|
9
app/views/projects/blob/viewers/_route_map.html.haml
Normal file
9
app/views/projects/blob/viewers/_route_map.html.haml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
- if viewer.valid?
|
||||||
|
= icon('check fw')
|
||||||
|
This Route Map is valid.
|
||||||
|
- else
|
||||||
|
= icon('warning fw')
|
||||||
|
This Route Map is invalid:
|
||||||
|
= viewer.validation_message
|
||||||
|
|
||||||
|
= link_to 'Learn more', help_page_path('ci/environments', anchor: 'route-map')
|
|
@ -0,0 +1,4 @@
|
||||||
|
= icon('spinner spin fw')
|
||||||
|
Validating Route Map…
|
||||||
|
|
||||||
|
= link_to 'Learn more', help_page_path('ci/environments', anchor: 'route-map')
|
5
changelogs/unreleased/dm-auxiliary-viewers.yml
Normal file
5
changelogs/unreleased/dm-auxiliary-viewers.yml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Display extra info about files on .gitlab-ci.yml, .gitlab/route-map.yml and
|
||||||
|
LICENSE blob pages
|
||||||
|
merge_request:
|
||||||
|
author:
|
|
@ -442,7 +442,8 @@ and/or `production`) you can see this information in the merge request itself.
|
||||||
|
|
||||||
![Environment URLs in merge request](img/environments_link_url_mr.png)
|
![Environment URLs in merge request](img/environments_link_url_mr.png)
|
||||||
|
|
||||||
### Go directly from source files to public pages on the environment
|
### <a name="route-map"></a>Go directly from source files to public pages on the environment
|
||||||
|
|
||||||
|
|
||||||
> Introduced in GitLab 8.17.
|
> Introduced in GitLab 8.17.
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,8 @@ module Gitlab
|
||||||
gitignore: '.gitignore',
|
gitignore: '.gitignore',
|
||||||
koding: '.koding.yml',
|
koding: '.koding.yml',
|
||||||
gitlab_ci: '.gitlab-ci.yml',
|
gitlab_ci: '.gitlab-ci.yml',
|
||||||
avatar: /\Alogo\.(png|jpg|gif)\z/
|
avatar: /\Alogo\.(png|jpg|gif)\z/,
|
||||||
|
route_map: 'route-map.yml'
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
# Returns an Array of file types based on the given paths.
|
# Returns an Array of file types based on the given paths.
|
||||||
|
|
|
@ -5,13 +5,13 @@ feature 'File blob', :js, feature: true do
|
||||||
|
|
||||||
def visit_blob(path, fragment = nil)
|
def visit_blob(path, fragment = nil)
|
||||||
visit namespace_project_blob_path(project.namespace, project, File.join('master', path), anchor: fragment)
|
visit namespace_project_blob_path(project.namespace, project, File.join('master', path), anchor: fragment)
|
||||||
|
|
||||||
|
wait_for_ajax
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'Ruby file' do
|
context 'Ruby file' do
|
||||||
before do
|
before do
|
||||||
visit_blob('files/ruby/popen.rb')
|
visit_blob('files/ruby/popen.rb')
|
||||||
|
|
||||||
wait_for_ajax
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'displays the blob' do
|
it 'displays the blob' do
|
||||||
|
@ -35,8 +35,6 @@ feature 'File blob', :js, feature: true do
|
||||||
context 'visiting directly' do
|
context 'visiting directly' do
|
||||||
before do
|
before do
|
||||||
visit_blob('files/markdown/ruby-style-guide.md')
|
visit_blob('files/markdown/ruby-style-guide.md')
|
||||||
|
|
||||||
wait_for_ajax
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'displays the blob using the rich viewer' do
|
it 'displays the blob using the rich viewer' do
|
||||||
|
@ -104,8 +102,6 @@ feature 'File blob', :js, feature: true do
|
||||||
context 'visiting with a line number anchor' do
|
context 'visiting with a line number anchor' do
|
||||||
before do
|
before do
|
||||||
visit_blob('files/markdown/ruby-style-guide.md', 'L1')
|
visit_blob('files/markdown/ruby-style-guide.md', 'L1')
|
||||||
|
|
||||||
wait_for_ajax
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'displays the blob using the simple viewer' do
|
it 'displays the blob using the simple viewer' do
|
||||||
|
@ -148,8 +144,6 @@ feature 'File blob', :js, feature: true do
|
||||||
project.update_attribute(:lfs_enabled, true)
|
project.update_attribute(:lfs_enabled, true)
|
||||||
|
|
||||||
visit_blob('files/lfs/file.md')
|
visit_blob('files/lfs/file.md')
|
||||||
|
|
||||||
wait_for_ajax
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'displays an error' do
|
it 'displays an error' do
|
||||||
|
@ -198,8 +192,6 @@ feature 'File blob', :js, feature: true do
|
||||||
context 'when LFS is disabled on the project' do
|
context 'when LFS is disabled on the project' do
|
||||||
before do
|
before do
|
||||||
visit_blob('files/lfs/file.md')
|
visit_blob('files/lfs/file.md')
|
||||||
|
|
||||||
wait_for_ajax
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'displays the blob' do
|
it 'displays the blob' do
|
||||||
|
@ -235,8 +227,6 @@ feature 'File blob', :js, feature: true do
|
||||||
).execute
|
).execute
|
||||||
|
|
||||||
visit_blob('files/test.pdf')
|
visit_blob('files/test.pdf')
|
||||||
|
|
||||||
wait_for_ajax
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'displays the blob' do
|
it 'displays the blob' do
|
||||||
|
@ -263,8 +253,6 @@ feature 'File blob', :js, feature: true do
|
||||||
project.update_attribute(:lfs_enabled, true)
|
project.update_attribute(:lfs_enabled, true)
|
||||||
|
|
||||||
visit_blob('files/lfs/lfs_object.iso')
|
visit_blob('files/lfs/lfs_object.iso')
|
||||||
|
|
||||||
wait_for_ajax
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'displays the blob' do
|
it 'displays the blob' do
|
||||||
|
@ -287,8 +275,6 @@ feature 'File blob', :js, feature: true do
|
||||||
context 'when LFS is disabled on the project' do
|
context 'when LFS is disabled on the project' do
|
||||||
before do
|
before do
|
||||||
visit_blob('files/lfs/lfs_object.iso')
|
visit_blob('files/lfs/lfs_object.iso')
|
||||||
|
|
||||||
wait_for_ajax
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'displays the blob' do
|
it 'displays the blob' do
|
||||||
|
@ -312,8 +298,6 @@ feature 'File blob', :js, feature: true do
|
||||||
context 'ZIP file' do
|
context 'ZIP file' do
|
||||||
before do
|
before do
|
||||||
visit_blob('Gemfile.zip')
|
visit_blob('Gemfile.zip')
|
||||||
|
|
||||||
wait_for_ajax
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'displays the blob' do
|
it 'displays the blob' do
|
||||||
|
@ -348,8 +332,6 @@ feature 'File blob', :js, feature: true do
|
||||||
).execute
|
).execute
|
||||||
|
|
||||||
visit_blob('files/empty.md')
|
visit_blob('files/empty.md')
|
||||||
|
|
||||||
wait_for_ajax
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'displays an error' do
|
it 'displays an error' do
|
||||||
|
@ -369,4 +351,80 @@ feature 'File blob', :js, feature: true do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context '.gitlab-ci.yml' do
|
||||||
|
before do
|
||||||
|
project.add_master(project.creator)
|
||||||
|
|
||||||
|
Files::CreateService.new(
|
||||||
|
project,
|
||||||
|
project.creator,
|
||||||
|
start_branch: 'master',
|
||||||
|
branch_name: 'master',
|
||||||
|
commit_message: "Add .gitlab-ci.yml",
|
||||||
|
file_path: '.gitlab-ci.yml',
|
||||||
|
file_content: File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml'))
|
||||||
|
).execute
|
||||||
|
|
||||||
|
visit_blob('.gitlab-ci.yml')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'displays an auxiliary viewer' do
|
||||||
|
aggregate_failures do
|
||||||
|
# shows that configuration is valid
|
||||||
|
expect(page).to have_content('This GitLab CI configuration is valid.')
|
||||||
|
|
||||||
|
# shows a learn more link
|
||||||
|
expect(page).to have_link('Learn more')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context '.gitlab/route-map.yml' do
|
||||||
|
before do
|
||||||
|
project.add_master(project.creator)
|
||||||
|
|
||||||
|
Files::CreateService.new(
|
||||||
|
project,
|
||||||
|
project.creator,
|
||||||
|
start_branch: 'master',
|
||||||
|
branch_name: 'master',
|
||||||
|
commit_message: "Add .gitlab/route-map.yml",
|
||||||
|
file_path: '.gitlab/route-map.yml',
|
||||||
|
file_content: <<-MAP.strip_heredoc
|
||||||
|
# Team data
|
||||||
|
- source: 'data/team.yml'
|
||||||
|
public: 'team/'
|
||||||
|
MAP
|
||||||
|
).execute
|
||||||
|
|
||||||
|
visit_blob('.gitlab/route-map.yml')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'displays an auxiliary viewer' do
|
||||||
|
aggregate_failures do
|
||||||
|
# shows that map is valid
|
||||||
|
expect(page).to have_content('This Route Map is valid.')
|
||||||
|
|
||||||
|
# shows a learn more link
|
||||||
|
expect(page).to have_link('Learn more')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'LICENSE' do
|
||||||
|
before do
|
||||||
|
visit_blob('LICENSE')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'displays an auxiliary viewer' do
|
||||||
|
aggregate_failures do
|
||||||
|
# shows license
|
||||||
|
expect(page).to have_content('This project is licensed under the MIT License.')
|
||||||
|
|
||||||
|
# shows a learn more link
|
||||||
|
expect(page).to have_link('Learn more about this license', 'http://choosealicense.com/licenses/mit/')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -271,6 +271,52 @@ describe Blob do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#auxiliary_viewer' do
|
||||||
|
context 'when the blob has an external storage error' do
|
||||||
|
before do
|
||||||
|
project.lfs_enabled = false
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns nil' do
|
||||||
|
blob = fake_blob(path: 'LICENSE', lfs: true)
|
||||||
|
|
||||||
|
expect(blob.auxiliary_viewer).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the blob is empty' do
|
||||||
|
it 'returns nil' do
|
||||||
|
blob = fake_blob(data: '')
|
||||||
|
|
||||||
|
expect(blob.auxiliary_viewer).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the blob is stored externally' do
|
||||||
|
it 'returns a matching viewer' do
|
||||||
|
blob = fake_blob(path: 'LICENSE', lfs: true)
|
||||||
|
|
||||||
|
expect(blob.auxiliary_viewer).to be_a(BlobViewer::License)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the blob is binary' do
|
||||||
|
it 'returns nil' do
|
||||||
|
blob = fake_blob(path: 'LICENSE', binary: true)
|
||||||
|
|
||||||
|
expect(blob.auxiliary_viewer).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the blob is text-based' do
|
||||||
|
it 'returns a matching text-based viewer' do
|
||||||
|
blob = fake_blob(path: 'LICENSE')
|
||||||
|
|
||||||
|
expect(blob.auxiliary_viewer).to be_a(BlobViewer::License)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe '#rendered_as_text?' do
|
describe '#rendered_as_text?' do
|
||||||
context 'when ignoring errors' do
|
context 'when ignoring errors' do
|
||||||
context 'when the simple viewer is text-based' do
|
context 'when the simple viewer is text-based' do
|
||||||
|
|
|
@ -8,6 +8,7 @@ describe BlobViewer::Base, model: true do
|
||||||
let(:viewer_class) do
|
let(:viewer_class) do
|
||||||
Class.new(described_class) do
|
Class.new(described_class) do
|
||||||
self.extensions = %w(pdf)
|
self.extensions = %w(pdf)
|
||||||
|
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
|
self.client_side = false
|
||||||
|
@ -18,14 +19,47 @@ describe BlobViewer::Base, model: true do
|
||||||
|
|
||||||
describe '.can_render?' do
|
describe '.can_render?' do
|
||||||
context 'when the extension is supported' do
|
context 'when the extension is supported' do
|
||||||
let(:blob) { fake_blob(path: 'file.pdf') }
|
context 'when the binaryness matches' do
|
||||||
|
let(:blob) { fake_blob(path: 'file.pdf', binary: true) }
|
||||||
|
|
||||||
it 'returns true' do
|
it 'returns true' do
|
||||||
expect(viewer_class.can_render?(blob)).to be_truthy
|
expect(viewer_class.can_render?(blob)).to be_truthy
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the binaryness does not match' do
|
||||||
|
let(:blob) { fake_blob(path: 'file.pdf', binary: false) }
|
||||||
|
|
||||||
|
it 'returns false' do
|
||||||
|
expect(viewer_class.can_render?(blob)).to be_falsey
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when the extension is not supported' do
|
context 'when the file type is supported' do
|
||||||
|
before do
|
||||||
|
viewer_class.file_type = :license
|
||||||
|
viewer_class.binary = false
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the binaryness matches' do
|
||||||
|
let(:blob) { fake_blob(path: 'LICENSE', binary: false) }
|
||||||
|
|
||||||
|
it 'returns true' do
|
||||||
|
expect(viewer_class.can_render?(blob)).to be_truthy
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the binaryness does not match' do
|
||||||
|
let(:blob) { fake_blob(path: 'LICENSE', binary: true) }
|
||||||
|
|
||||||
|
it 'returns false' do
|
||||||
|
expect(viewer_class.can_render?(blob)).to be_falsey
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the extension and file type are not supported' do
|
||||||
let(:blob) { fake_blob(path: 'file.txt') }
|
let(:blob) { fake_blob(path: 'file.txt') }
|
||||||
|
|
||||||
it 'returns false' do
|
it 'returns false' do
|
||||||
|
@ -153,34 +187,4 @@ describe BlobViewer::Base, model: true do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#prepare!' do
|
|
||||||
context 'when the viewer is server side' do
|
|
||||||
let(:blob) { fake_blob(path: 'file.md') }
|
|
||||||
|
|
||||||
before do
|
|
||||||
viewer_class.client_side = false
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'loads all blob data' do
|
|
||||||
expect(blob).to receive(:load_all_data!)
|
|
||||||
|
|
||||||
viewer.prepare!
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when the viewer is client side' do
|
|
||||||
let(:blob) { fake_blob(path: 'file.md') }
|
|
||||||
|
|
||||||
before do
|
|
||||||
viewer_class.client_side = true
|
|
||||||
end
|
|
||||||
|
|
||||||
it "doesn't load all blob data" do
|
|
||||||
expect(blob).not_to receive(:load_all_data!)
|
|
||||||
|
|
||||||
viewer.prepare!
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
32
spec/models/blob_viewer/gitlab_ci_yml_spec.rb
Normal file
32
spec/models/blob_viewer/gitlab_ci_yml_spec.rb
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe BlobViewer::GitlabCiYml, model: true do
|
||||||
|
include FakeBlobHelpers
|
||||||
|
|
||||||
|
let(:project) { build(:project) }
|
||||||
|
let(:data) { File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) }
|
||||||
|
let(:blob) { fake_blob(path: '.gitlab-ci.yml', data: data) }
|
||||||
|
subject { described_class.new(blob) }
|
||||||
|
|
||||||
|
describe '#validation_message' do
|
||||||
|
it 'calls prepare! on the viewer' do
|
||||||
|
expect(subject).to receive(:prepare!)
|
||||||
|
|
||||||
|
subject.validation_message
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the configuration is valid' do
|
||||||
|
it 'returns nil' do
|
||||||
|
expect(subject.validation_message).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the configuration is invalid' do
|
||||||
|
let(:data) { 'oof' }
|
||||||
|
|
||||||
|
it 'returns the error message' do
|
||||||
|
expect(subject.validation_message).to eq('Invalid configuration format')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
34
spec/models/blob_viewer/license_spec.rb
Normal file
34
spec/models/blob_viewer/license_spec.rb
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe BlobViewer::License, model: true do
|
||||||
|
include FakeBlobHelpers
|
||||||
|
|
||||||
|
let(:project) { create(:project, :repository) }
|
||||||
|
let(:blob) { fake_blob(path: 'LICENSE') }
|
||||||
|
subject { described_class.new(blob) }
|
||||||
|
|
||||||
|
describe '#license' do
|
||||||
|
it 'returns the blob project repository license' do
|
||||||
|
expect(subject.license).not_to be_nil
|
||||||
|
expect(subject.license).to eq(project.repository.license)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#render_error' do
|
||||||
|
context 'when there is no license' do
|
||||||
|
before do
|
||||||
|
allow(project.repository).to receive(:license).and_return(nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns :unknown_license' do
|
||||||
|
expect(subject.render_error).to eq(:unknown_license)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when there is a license' do
|
||||||
|
it 'returns nil' do
|
||||||
|
expect(subject.render_error).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
38
spec/models/blob_viewer/route_map_spec.rb
Normal file
38
spec/models/blob_viewer/route_map_spec.rb
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe BlobViewer::RouteMap, model: true do
|
||||||
|
include FakeBlobHelpers
|
||||||
|
|
||||||
|
let(:project) { build(:project) }
|
||||||
|
let(:data) do
|
||||||
|
<<-MAP.strip_heredoc
|
||||||
|
# Team data
|
||||||
|
- source: 'data/team.yml'
|
||||||
|
public: 'team/'
|
||||||
|
MAP
|
||||||
|
end
|
||||||
|
let(:blob) { fake_blob(path: '.gitlab/route-map.yml', data: data) }
|
||||||
|
subject { described_class.new(blob) }
|
||||||
|
|
||||||
|
describe '#validation_message' do
|
||||||
|
it 'calls prepare! on the viewer' do
|
||||||
|
expect(subject).to receive(:prepare!)
|
||||||
|
|
||||||
|
subject.validation_message
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the configuration is valid' do
|
||||||
|
it 'returns nil' do
|
||||||
|
expect(subject.validation_message).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the configuration is invalid' do
|
||||||
|
let(:data) { 'oof' }
|
||||||
|
|
||||||
|
it 'returns the error message' do
|
||||||
|
expect(subject.validation_message).to eq('Route map is not an array')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
25
spec/models/blob_viewer/server_side_spec.rb
Normal file
25
spec/models/blob_viewer/server_side_spec.rb
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe BlobViewer::ServerSide, model: true do
|
||||||
|
include FakeBlobHelpers
|
||||||
|
|
||||||
|
let(:project) { build(:empty_project) }
|
||||||
|
|
||||||
|
let(:viewer_class) do
|
||||||
|
Class.new(BlobViewer::Base) do
|
||||||
|
include BlobViewer::ServerSide
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
subject { viewer_class.new(blob) }
|
||||||
|
|
||||||
|
describe '#prepare!' do
|
||||||
|
let(:blob) { fake_blob(path: 'file.txt') }
|
||||||
|
|
||||||
|
it 'loads all blob data' do
|
||||||
|
expect(blob).to receive(:load_all_data!)
|
||||||
|
|
||||||
|
subject.prepare!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -2,7 +2,7 @@ require 'spec_helper'
|
||||||
|
|
||||||
describe Repository, models: true do
|
describe Repository, models: true do
|
||||||
include RepoHelpers
|
include RepoHelpers
|
||||||
TestBlob = Struct.new(:name)
|
TestBlob = Struct.new(:path)
|
||||||
|
|
||||||
let(:project) { create(:project, :repository) }
|
let(:project) { create(:project, :repository) }
|
||||||
let(:repository) { project.repository }
|
let(:repository) { project.repository }
|
||||||
|
@ -565,31 +565,31 @@ describe Repository, models: true do
|
||||||
it 'accepts changelog' do
|
it 'accepts changelog' do
|
||||||
expect(repository.tree).to receive(:blobs).and_return([TestBlob.new('changelog')])
|
expect(repository.tree).to receive(:blobs).and_return([TestBlob.new('changelog')])
|
||||||
|
|
||||||
expect(repository.changelog.name).to eq('changelog')
|
expect(repository.changelog.path).to eq('changelog')
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'accepts news instead of changelog' do
|
it 'accepts news instead of changelog' do
|
||||||
expect(repository.tree).to receive(:blobs).and_return([TestBlob.new('news')])
|
expect(repository.tree).to receive(:blobs).and_return([TestBlob.new('news')])
|
||||||
|
|
||||||
expect(repository.changelog.name).to eq('news')
|
expect(repository.changelog.path).to eq('news')
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'accepts history instead of changelog' do
|
it 'accepts history instead of changelog' do
|
||||||
expect(repository.tree).to receive(:blobs).and_return([TestBlob.new('history')])
|
expect(repository.tree).to receive(:blobs).and_return([TestBlob.new('history')])
|
||||||
|
|
||||||
expect(repository.changelog.name).to eq('history')
|
expect(repository.changelog.path).to eq('history')
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'accepts changes instead of changelog' do
|
it 'accepts changes instead of changelog' do
|
||||||
expect(repository.tree).to receive(:blobs).and_return([TestBlob.new('changes')])
|
expect(repository.tree).to receive(:blobs).and_return([TestBlob.new('changes')])
|
||||||
|
|
||||||
expect(repository.changelog.name).to eq('changes')
|
expect(repository.changelog.path).to eq('changes')
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'is case-insensitive' do
|
it 'is case-insensitive' do
|
||||||
expect(repository.tree).to receive(:blobs).and_return([TestBlob.new('CHANGELOG')])
|
expect(repository.tree).to receive(:blobs).and_return([TestBlob.new('CHANGELOG')])
|
||||||
|
|
||||||
expect(repository.changelog.name).to eq('CHANGELOG')
|
expect(repository.changelog.path).to eq('CHANGELOG')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -624,7 +624,7 @@ describe Repository, models: true do
|
||||||
repository.create_file(user, 'LICENSE', 'Copyright!',
|
repository.create_file(user, 'LICENSE', 'Copyright!',
|
||||||
message: 'Add LICENSE', branch_name: 'master')
|
message: 'Add LICENSE', branch_name: 'master')
|
||||||
|
|
||||||
expect(repository.license_blob.name).to eq('LICENSE')
|
expect(repository.license_blob.path).to eq('LICENSE')
|
||||||
end
|
end
|
||||||
|
|
||||||
%w[LICENSE LICENCE LiCensE LICENSE.md LICENSE.foo COPYING COPYING.md].each do |filename|
|
%w[LICENSE LICENCE LiCensE LICENSE.md LICENSE.foo COPYING COPYING.md].each do |filename|
|
||||||
|
@ -654,7 +654,7 @@ describe Repository, models: true do
|
||||||
expect(repository.license_key).to be_nil
|
expect(repository.license_key).to be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'detects license file with no recognizable open-source license content' do
|
it 'returns nil when the content is not recognizable' do
|
||||||
repository.create_file(user, 'LICENSE', 'Copyright!',
|
repository.create_file(user, 'LICENSE', 'Copyright!',
|
||||||
message: 'Add LICENSE', branch_name: 'master')
|
message: 'Add LICENSE', branch_name: 'master')
|
||||||
|
|
||||||
|
@ -670,12 +670,45 @@ describe Repository, models: true do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#license' do
|
||||||
|
before do
|
||||||
|
repository.delete_file(user, 'LICENSE',
|
||||||
|
message: 'Remove LICENSE', branch_name: 'master')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns nil when no license is detected' do
|
||||||
|
expect(repository.license).to be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns nil when the repository does not exist' do
|
||||||
|
expect(repository).to receive(:exists?).and_return(false)
|
||||||
|
|
||||||
|
expect(repository.license).to be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns nil when the content is not recognizable' do
|
||||||
|
repository.create_file(user, 'LICENSE', 'Copyright!',
|
||||||
|
message: 'Add LICENSE', branch_name: 'master')
|
||||||
|
|
||||||
|
expect(repository.license).to be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns the license' do
|
||||||
|
license = Licensee::License.new('mit')
|
||||||
|
repository.create_file(user, 'LICENSE',
|
||||||
|
license.content,
|
||||||
|
message: 'Add LICENSE', branch_name: 'master')
|
||||||
|
|
||||||
|
expect(repository.license).to eq(license)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "#gitlab_ci_yml", caching: true do
|
describe "#gitlab_ci_yml", caching: true do
|
||||||
it 'returns valid file' do
|
it 'returns valid file' do
|
||||||
files = [TestBlob.new('file'), TestBlob.new('.gitlab-ci.yml'), TestBlob.new('copying')]
|
files = [TestBlob.new('file'), TestBlob.new('.gitlab-ci.yml'), TestBlob.new('copying')]
|
||||||
expect(repository.tree).to receive(:blobs).and_return(files)
|
expect(repository.tree).to receive(:blobs).and_return(files)
|
||||||
|
|
||||||
expect(repository.gitlab_ci_yml.name).to eq('.gitlab-ci.yml')
|
expect(repository.gitlab_ci_yml.path).to eq('.gitlab-ci.yml')
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns nil if not exists' do
|
it 'returns nil if not exists' do
|
||||||
|
@ -1825,11 +1858,12 @@ describe Repository, models: true do
|
||||||
describe '#refresh_method_caches' do
|
describe '#refresh_method_caches' do
|
||||||
it 'refreshes the caches of the given types' do
|
it 'refreshes the caches of the given types' do
|
||||||
expect(repository).to receive(:expire_method_caches).
|
expect(repository).to receive(:expire_method_caches).
|
||||||
with(%i(rendered_readme license_blob license_key))
|
with(%i(rendered_readme license_blob license_key license))
|
||||||
|
|
||||||
expect(repository).to receive(:rendered_readme)
|
expect(repository).to receive(:rendered_readme)
|
||||||
expect(repository).to receive(:license_blob)
|
expect(repository).to receive(:license_blob)
|
||||||
expect(repository).to receive(:license_key)
|
expect(repository).to receive(:license_key)
|
||||||
|
expect(repository).to receive(:license)
|
||||||
|
|
||||||
repository.refresh_method_caches(%i(readme license))
|
repository.refresh_method_caches(%i(readme license))
|
||||||
end
|
end
|
||||||
|
|
|
@ -47,10 +47,10 @@ describe 'projects/blob/_viewer.html.haml', :view do
|
||||||
expect(rendered).to have_css('.blob-viewer[data-url]')
|
expect(rendered).to have_css('.blob-viewer[data-url]')
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'displays a spinner' do
|
it 'renders the loading indicator' do
|
||||||
render_view
|
render_view
|
||||||
|
|
||||||
expect(rendered).to have_css('i[aria-label="Loading content"]')
|
expect(view).to render_template('projects/blob/viewers/_loading')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue