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 */
|
||||
export default class BlobViewer {
|
||||
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.switcherBtns = document.querySelectorAll('.js-blob-viewer-switch-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)');
|
||||
if (!initialViewer) return;
|
||||
|
||||
let initialViewerName = initialViewer.getAttribute('data-type');
|
||||
this.simpleViewer = this.$fileHolder[0].querySelector('.blob-viewer[data-type="simple"]');
|
||||
this.richViewer = this.$fileHolder[0].querySelector('.blob-viewer[data-type="rich"]');
|
||||
|
||||
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) {
|
||||
initialViewerName = 'simple';
|
||||
}
|
||||
|
@ -64,40 +82,13 @@ export default class BlobViewer {
|
|||
$(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) {
|
||||
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;
|
||||
|
||||
const oldButton = document.querySelector('.js-blob-viewer-switch-btn.active');
|
||||
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) {
|
||||
oldButton.classList.remove('active');
|
||||
|
@ -118,6 +109,40 @@ export default class BlobViewer {
|
|||
|
||||
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)
|
||||
viewer =
|
||||
if params[:viewer] == 'rich'
|
||||
case params[:viewer]
|
||||
when 'rich'
|
||||
blob.rich_viewer
|
||||
when 'auxiliary'
|
||||
blob.auxiliary_viewer
|
||||
else
|
||||
blob.simple_viewer
|
||||
end
|
||||
|
|
|
@ -110,11 +110,8 @@ module ProjectsHelper
|
|||
end
|
||||
|
||||
def license_short_name(project)
|
||||
return 'LICENSE' if project.repository.license_key.nil?
|
||||
|
||||
license = Licensee::License.new(project.repository.license_key)
|
||||
|
||||
license.nickname || license.name
|
||||
license = project.repository.license
|
||||
license&.nickname || license&.name || 'LICENSE'
|
||||
end
|
||||
|
||||
def last_push_event
|
||||
|
|
|
@ -34,10 +34,13 @@ class Blob < SimpleDelegator
|
|||
|
||||
BlobViewer::BinarySTL,
|
||||
BlobViewer::TextSTL
|
||||
].freeze
|
||||
].sort_by { |v| v.binary? ? 0 : 1 }.freeze
|
||||
|
||||
BINARY_VIEWERS = RICH_VIEWERS.select(&:binary?).freeze
|
||||
TEXT_VIEWERS = RICH_VIEWERS.select(&:text?).freeze
|
||||
AUXILIARY_VIEWERS = [
|
||||
BlobViewer::GitlabCiYml,
|
||||
BlobViewer::RouteMap,
|
||||
BlobViewer::License
|
||||
].freeze
|
||||
|
||||
attr_reader :project
|
||||
|
||||
|
@ -154,6 +157,12 @@ class Blob < SimpleDelegator
|
|||
@rich_viewer = rich_viewer_class&.new(self)
|
||||
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)
|
||||
simple_viewer.text? && (ignore_errors || simple_viewer.render_error.nil?)
|
||||
end
|
||||
|
@ -180,17 +189,18 @@ class Blob < SimpleDelegator
|
|||
end
|
||||
|
||||
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?
|
||||
|
||||
classes =
|
||||
if stored_externally?
|
||||
BINARY_VIEWERS + TEXT_VIEWERS
|
||||
elsif binary?
|
||||
BINARY_VIEWERS
|
||||
else # text
|
||||
TEXT_VIEWERS
|
||||
end
|
||||
verify_binary = !stored_externally?
|
||||
|
||||
classes.find { |viewer_class| viewer_class.can_render?(self) }
|
||||
classes.find { |viewer_class| viewer_class.can_render?(self, verify_binary: verify_binary) }
|
||||
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
|
||||
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_accessor :override_max_size
|
||||
|
@ -12,7 +16,11 @@ module BlobViewer
|
|||
end
|
||||
|
||||
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
|
||||
|
||||
def self.rich?
|
||||
|
@ -23,6 +31,10 @@ module BlobViewer
|
|||
type == :simple
|
||||
end
|
||||
|
||||
def self.auxiliary?
|
||||
type == :auxiliary
|
||||
end
|
||||
|
||||
def self.client_side?
|
||||
client_side
|
||||
end
|
||||
|
@ -39,8 +51,12 @@ module BlobViewer
|
|||
!binary?
|
||||
end
|
||||
|
||||
def self.can_render?(blob)
|
||||
!extensions || extensions.include?(blob.extension)
|
||||
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
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
def too_large?
|
||||
|
@ -83,9 +99,7 @@ module BlobViewer
|
|||
end
|
||||
|
||||
def prepare!
|
||||
if server_side? && blob.project
|
||||
blob.load_all_data!(blob.project.repository)
|
||||
end
|
||||
# To be overridden by subclasses
|
||||
end
|
||||
|
||||
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.absolute_max_size = 5.megabytes
|
||||
end
|
||||
|
||||
def prepare!
|
||||
if blob.project
|
||||
blob.load_all_data!(blob.project.repository)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -30,7 +30,7 @@ class Repository
|
|||
METHOD_CACHES_FOR_FILE_TYPES = {
|
||||
readme: :rendered_readme,
|
||||
changelog: :changelog,
|
||||
license: %i(license_blob license_key),
|
||||
license: %i(license_blob license_key license),
|
||||
contributing: :contribution_guide,
|
||||
gitignore: :gitignore,
|
||||
koding: :koding_yml,
|
||||
|
@ -42,13 +42,13 @@ class Repository
|
|||
# variable.
|
||||
#
|
||||
# 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}"
|
||||
|
||||
alias_method(original, name)
|
||||
|
||||
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
|
||||
|
||||
|
@ -549,6 +549,13 @@ class Repository
|
|||
end
|
||||
cache_method :license_key
|
||||
|
||||
def license
|
||||
return unless license_key
|
||||
|
||||
Licensee::License.new(license_key)
|
||||
end
|
||||
cache_method :license, memoize_only: true
|
||||
|
||||
def gitignore
|
||||
file_on_head(:gitignore)
|
||||
end
|
||||
|
@ -1061,14 +1068,20 @@ class Repository
|
|||
#
|
||||
# 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.
|
||||
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)
|
||||
|
||||
if instance_variable_defined?(ivar)
|
||||
instance_variable_get(ivar)
|
||||
else
|
||||
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
|
||||
# if e.g. HEAD or the entire repository doesn't exist we want to
|
||||
# gracefully handle this and not cache anything.
|
||||
|
@ -1083,8 +1096,8 @@ class Repository
|
|||
|
||||
def file_on_head(type)
|
||||
if head = tree(:head)
|
||||
head.blobs.find do |file|
|
||||
Gitlab::FileDetector.type_of(file.name) == type
|
||||
head.blobs.find do |blob|
|
||||
Gitlab::FileDetector.type_of(blob.path) == type
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,6 +6,11 @@
|
|||
- blob_commit = @repository.last_commit_for_path(@commit.id, blob.path)
|
||||
= 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
|
||||
%article.file-holder
|
||||
= 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
|
||||
.blob-viewer{ data: { type: viewer.type, url: viewer_url }, class: ('hidden' if hidden) }
|
||||
- if load_asynchronously
|
||||
.text-center.prepend-top-default.append-bottom-default
|
||||
= icon('spinner spin 2x', 'aria-hidden' => 'true', 'aria-label' => 'Loading content')
|
||||
= render viewer.loading_partial_path, viewer: viewer
|
||||
- elsif render_error
|
||||
= render 'projects/blob/render_error', viewer: viewer
|
||||
- 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)
|
||||
|
||||
### 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.
|
||||
|
||||
|
|
|
@ -13,7 +13,8 @@ module Gitlab
|
|||
gitignore: '.gitignore',
|
||||
koding: '.koding.yml',
|
||||
gitlab_ci: '.gitlab-ci.yml',
|
||||
avatar: /\Alogo\.(png|jpg|gif)\z/
|
||||
avatar: /\Alogo\.(png|jpg|gif)\z/,
|
||||
route_map: 'route-map.yml'
|
||||
}.freeze
|
||||
|
||||
# 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)
|
||||
visit namespace_project_blob_path(project.namespace, project, File.join('master', path), anchor: fragment)
|
||||
|
||||
wait_for_ajax
|
||||
end
|
||||
|
||||
context 'Ruby file' do
|
||||
before do
|
||||
visit_blob('files/ruby/popen.rb')
|
||||
|
||||
wait_for_ajax
|
||||
end
|
||||
|
||||
it 'displays the blob' do
|
||||
|
@ -35,8 +35,6 @@ feature 'File blob', :js, feature: true do
|
|||
context 'visiting directly' do
|
||||
before do
|
||||
visit_blob('files/markdown/ruby-style-guide.md')
|
||||
|
||||
wait_for_ajax
|
||||
end
|
||||
|
||||
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
|
||||
before do
|
||||
visit_blob('files/markdown/ruby-style-guide.md', 'L1')
|
||||
|
||||
wait_for_ajax
|
||||
end
|
||||
|
||||
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)
|
||||
|
||||
visit_blob('files/lfs/file.md')
|
||||
|
||||
wait_for_ajax
|
||||
end
|
||||
|
||||
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
|
||||
before do
|
||||
visit_blob('files/lfs/file.md')
|
||||
|
||||
wait_for_ajax
|
||||
end
|
||||
|
||||
it 'displays the blob' do
|
||||
|
@ -235,8 +227,6 @@ feature 'File blob', :js, feature: true do
|
|||
).execute
|
||||
|
||||
visit_blob('files/test.pdf')
|
||||
|
||||
wait_for_ajax
|
||||
end
|
||||
|
||||
it 'displays the blob' do
|
||||
|
@ -263,8 +253,6 @@ feature 'File blob', :js, feature: true do
|
|||
project.update_attribute(:lfs_enabled, true)
|
||||
|
||||
visit_blob('files/lfs/lfs_object.iso')
|
||||
|
||||
wait_for_ajax
|
||||
end
|
||||
|
||||
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
|
||||
before do
|
||||
visit_blob('files/lfs/lfs_object.iso')
|
||||
|
||||
wait_for_ajax
|
||||
end
|
||||
|
||||
it 'displays the blob' do
|
||||
|
@ -312,8 +298,6 @@ feature 'File blob', :js, feature: true do
|
|||
context 'ZIP file' do
|
||||
before do
|
||||
visit_blob('Gemfile.zip')
|
||||
|
||||
wait_for_ajax
|
||||
end
|
||||
|
||||
it 'displays the blob' do
|
||||
|
@ -348,8 +332,6 @@ feature 'File blob', :js, feature: true do
|
|||
).execute
|
||||
|
||||
visit_blob('files/empty.md')
|
||||
|
||||
wait_for_ajax
|
||||
end
|
||||
|
||||
it 'displays an error' do
|
||||
|
@ -369,4 +351,80 @@ feature 'File blob', :js, feature: true do
|
|||
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
|
||||
|
|
|
@ -271,6 +271,52 @@ describe Blob do
|
|||
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
|
||||
context 'when ignoring errors' do
|
||||
context 'when the simple viewer is text-based' do
|
||||
|
|
|
@ -8,6 +8,7 @@ describe BlobViewer::Base, model: true do
|
|||
let(:viewer_class) do
|
||||
Class.new(described_class) do
|
||||
self.extensions = %w(pdf)
|
||||
self.binary = true
|
||||
self.max_size = 1.megabyte
|
||||
self.absolute_max_size = 5.megabytes
|
||||
self.client_side = false
|
||||
|
@ -18,14 +19,47 @@ describe BlobViewer::Base, model: true do
|
|||
|
||||
describe '.can_render?' 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
|
||||
expect(viewer_class.can_render?(blob)).to be_truthy
|
||||
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: 'file.pdf', binary: false) }
|
||||
|
||||
it 'returns false' do
|
||||
expect(viewer_class.can_render?(blob)).to be_falsey
|
||||
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') }
|
||||
|
||||
it 'returns false' do
|
||||
|
@ -153,34 +187,4 @@ describe BlobViewer::Base, model: true do
|
|||
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
|
||||
|
|
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
|
||||
include RepoHelpers
|
||||
TestBlob = Struct.new(:name)
|
||||
TestBlob = Struct.new(:path)
|
||||
|
||||
let(:project) { create(:project, :repository) }
|
||||
let(:repository) { project.repository }
|
||||
|
@ -565,31 +565,31 @@ describe Repository, models: true do
|
|||
it 'accepts changelog' do
|
||||
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
|
||||
|
||||
it 'accepts news instead of changelog' do
|
||||
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
|
||||
|
||||
it 'accepts history instead of changelog' do
|
||||
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
|
||||
|
||||
it 'accepts changes instead of changelog' do
|
||||
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
|
||||
|
||||
it 'is case-insensitive' do
|
||||
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
|
||||
|
||||
|
@ -624,7 +624,7 @@ describe Repository, models: true do
|
|||
repository.create_file(user, 'LICENSE', 'Copyright!',
|
||||
message: 'Add LICENSE', branch_name: 'master')
|
||||
|
||||
expect(repository.license_blob.name).to eq('LICENSE')
|
||||
expect(repository.license_blob.path).to eq('LICENSE')
|
||||
end
|
||||
|
||||
%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
|
||||
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!',
|
||||
message: 'Add LICENSE', branch_name: 'master')
|
||||
|
||||
|
@ -670,12 +670,45 @@ describe Repository, models: true do
|
|||
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
|
||||
it 'returns valid file' do
|
||||
files = [TestBlob.new('file'), TestBlob.new('.gitlab-ci.yml'), TestBlob.new('copying')]
|
||||
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
|
||||
|
||||
it 'returns nil if not exists' do
|
||||
|
@ -1825,11 +1858,12 @@ describe Repository, models: true do
|
|||
describe '#refresh_method_caches' do
|
||||
it 'refreshes the caches of the given types' do
|
||||
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(:license_blob)
|
||||
expect(repository).to receive(:license_key)
|
||||
expect(repository).to receive(:license)
|
||||
|
||||
repository.refresh_method_caches(%i(readme license))
|
||||
end
|
||||
|
|
|
@ -47,10 +47,10 @@ describe 'projects/blob/_viewer.html.haml', :view do
|
|||
expect(rendered).to have_css('.blob-viewer[data-url]')
|
||||
end
|
||||
|
||||
it 'displays a spinner' do
|
||||
it 'renders the loading indicator' do
|
||||
render_view
|
||||
|
||||
expect(rendered).to have_css('i[aria-label="Loading content"]')
|
||||
expect(view).to render_template('projects/blob/viewers/_loading')
|
||||
end
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in a new issue