diff options
Diffstat (limited to 'app')
20 files changed, 259 insertions, 67 deletions
diff --git a/app/assets/javascripts/blob/viewer/index.js b/app/assets/javascripts/blob/viewer/index.js index 69ff2f95799..849da633c89 100644 --- a/app/assets/javascripts/blob/viewer/index.js +++ b/app/assets/javascripts/blob/viewer/index.js @@ -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); + }); + }); } } diff --git a/app/controllers/concerns/renders_blob.rb b/app/controllers/concerns/renders_blob.rb index 9faf68e6d97..4a6630dfd90 100644 --- a/app/controllers/concerns/renders_blob.rb +++ b/app/controllers/concerns/renders_blob.rb @@ -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 diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 8c26348a975..78b54dc20e5 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -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 diff --git a/app/models/blob.rb b/app/models/blob.rb index 8452a96e909..63a81c0e3bd 100644 --- a/app/models/blob.rb +++ b/app/models/blob.rb @@ -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 diff --git a/app/models/blob_viewer/auxiliary.rb b/app/models/blob_viewer/auxiliary.rb new file mode 100644 index 00000000000..db124397b27 --- /dev/null +++ b/app/models/blob_viewer/auxiliary.rb @@ -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 diff --git a/app/models/blob_viewer/base.rb b/app/models/blob_viewer/base.rb index a8b91d8d6bc..4f38c31714b 100644 --- a/app/models/blob_viewer/base.rb +++ b/app/models/blob_viewer/base.rb @@ -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 diff --git a/app/models/blob_viewer/gitlab_ci_yml.rb b/app/models/blob_viewer/gitlab_ci_yml.rb new file mode 100644 index 00000000000..81afab2f49b --- /dev/null +++ b/app/models/blob_viewer/gitlab_ci_yml.rb @@ -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 diff --git a/app/models/blob_viewer/license.rb b/app/models/blob_viewer/license.rb new file mode 100644 index 00000000000..3ad49570c88 --- /dev/null +++ b/app/models/blob_viewer/license.rb @@ -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 diff --git a/app/models/blob_viewer/route_map.rb b/app/models/blob_viewer/route_map.rb new file mode 100644 index 00000000000..1ca730c1ea0 --- /dev/null +++ b/app/models/blob_viewer/route_map.rb @@ -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 diff --git a/app/models/blob_viewer/server_side.rb b/app/models/blob_viewer/server_side.rb index 899107d02ea..e8c5c17b824 100644 --- a/app/models/blob_viewer/server_side.rb +++ b/app/models/blob_viewer/server_side.rb @@ -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 diff --git a/app/models/repository.rb b/app/models/repository.rb index 9d9d94c4486..2aec7237640 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -549,6 +549,13 @@ class Repository end cache_method :license_key + def license + return @license if defined?(@license) + return unless license_key + + @license = Licensee::License.new(license_key) + end + def gitignore file_on_head(:gitignore) end @@ -1083,8 +1090,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 diff --git a/app/views/projects/blob/_blob.html.haml b/app/views/projects/blob/_blob.html.haml index f4307421ed0..8af945ddb2c 100644 --- a/app/views/projects/blob/_blob.html.haml +++ b/app/views/projects/blob/_blob.html.haml @@ -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 diff --git a/app/views/projects/blob/_viewer.html.haml b/app/views/projects/blob/_viewer.html.haml index 41187d5ce66..3d9c3a59980 100644 --- a/app/views/projects/blob/_viewer.html.haml +++ b/app/views/projects/blob/_viewer.html.haml @@ -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 diff --git a/app/views/projects/blob/viewers/_gitlab_ci_yml.html.haml b/app/views/projects/blob/viewers/_gitlab_ci_yml.html.haml new file mode 100644 index 00000000000..28c5be6ebf3 --- /dev/null +++ b/app/views/projects/blob/viewers/_gitlab_ci_yml.html.haml @@ -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') diff --git a/app/views/projects/blob/viewers/_gitlab_ci_yml_loading.html.haml b/app/views/projects/blob/viewers/_gitlab_ci_yml_loading.html.haml new file mode 100644 index 00000000000..10cbf6a2f7a --- /dev/null +++ b/app/views/projects/blob/viewers/_gitlab_ci_yml_loading.html.haml @@ -0,0 +1,4 @@ += icon('spinner spin fw') +Validating GitLab CI configuration… + += link_to 'Learn more', help_page_path('ci/yaml/README') diff --git a/app/views/projects/blob/viewers/_license.html.haml b/app/views/projects/blob/viewers/_license.html.haml new file mode 100644 index 00000000000..9a79d164692 --- /dev/null +++ b/app/views/projects/blob/viewers/_license.html.haml @@ -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' diff --git a/app/views/projects/blob/viewers/_loading.html.haml b/app/views/projects/blob/viewers/_loading.html.haml new file mode 100644 index 00000000000..120c0540335 --- /dev/null +++ b/app/views/projects/blob/viewers/_loading.html.haml @@ -0,0 +1,2 @@ +.text-center.prepend-top-default.append-bottom-default + = icon('spinner spin 2x', 'aria-hidden' => 'true', 'aria-label' => 'Loading content…') diff --git a/app/views/projects/blob/viewers/_loading_auxiliary.html.haml b/app/views/projects/blob/viewers/_loading_auxiliary.html.haml new file mode 100644 index 00000000000..058c74bce0d --- /dev/null +++ b/app/views/projects/blob/viewers/_loading_auxiliary.html.haml @@ -0,0 +1,2 @@ += icon('spinner spin fw') +Loading… diff --git a/app/views/projects/blob/viewers/_route_map.html.haml b/app/views/projects/blob/viewers/_route_map.html.haml new file mode 100644 index 00000000000..d0fcd55f6c1 --- /dev/null +++ b/app/views/projects/blob/viewers/_route_map.html.haml @@ -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') diff --git a/app/views/projects/blob/viewers/_route_map_loading.html.haml b/app/views/projects/blob/viewers/_route_map_loading.html.haml new file mode 100644 index 00000000000..2318cf82f58 --- /dev/null +++ b/app/views/projects/blob/viewers/_route_map_loading.html.haml @@ -0,0 +1,4 @@ += icon('spinner spin fw') +Validating Route Map… + += link_to 'Learn more', help_page_path('ci/environments', anchor: 'route-map') |