Commit ff6864b8 authored by Lin Jen-Shin's avatar Lin Jen-Shin

Merge remote-tracking branch 'ee/master' into 2302-environment-specific-variables

* ee/master:
  Merge branch 'winh-ignore-CVE-2017-5029' into 'master'
  Treat licenses with no plan specified as EES
  Add explicit licensing for Elasticsearch
  removes redundant project pull mirroring code from a bad merge
  Add an optional performance bar to view performance metrics
  Use :request_store hooks on specs
  Override `file_storage` on the `ObjectStoreUploader`
  Update rename_system_namespace_spec to new validations.
  Make the uploader use the updated folder
  Bring in security changes from the 9.2.5 release
  Fix tests on `security-9-2-ee`
parents c565911f a4270596
...@@ -273,6 +273,17 @@ gem 'gettext_i18n_rails', '~> 1.8.0' ...@@ -273,6 +273,17 @@ gem 'gettext_i18n_rails', '~> 1.8.0'
gem 'gettext_i18n_rails_js', '~> 1.2.0' gem 'gettext_i18n_rails_js', '~> 1.2.0'
gem 'gettext', '~> 3.2.2', require: false, group: :development gem 'gettext', '~> 3.2.2', require: false, group: :development
# Perf bar
gem 'peek', '~> 1.0.1'
gem 'peek-gc', '~> 0.0.2'
gem 'peek-host', '~> 1.0.0'
gem 'peek-mysql2', '~> 1.1.0', group: :mysql
gem 'peek-performance_bar', '~> 1.2.1'
gem 'peek-pg', '~> 1.3.0', group: :postgres
gem 'peek-rblineprof', '~> 0.2.0'
gem 'peek-redis', '~> 1.2.0'
gem 'peek-sidekiq', '~> 1.0.3'
# Metrics # Metrics
group :metrics do group :metrics do
gem 'allocations', '~> 1.0', require: false, platform: :mri gem 'allocations', '~> 1.0', require: false, platform: :mri
......
...@@ -56,6 +56,7 @@ GEM ...@@ -56,6 +56,7 @@ GEM
asciidoctor-plantuml (0.0.7) asciidoctor-plantuml (0.0.7)
asciidoctor (~> 1.5) asciidoctor (~> 1.5)
ast (2.3.0) ast (2.3.0)
atomic (1.1.99)
attr_encrypted (3.0.3) attr_encrypted (3.0.3)
encryptor (~> 3.0.0) encryptor (~> 3.0.0)
attr_required (1.0.0) attr_required (1.0.0)
...@@ -137,6 +138,8 @@ GEM ...@@ -137,6 +138,8 @@ GEM
coffee-script-source (1.10.0) coffee-script-source (1.10.0)
colorize (0.7.7) colorize (0.7.7)
concurrent-ruby (1.0.5) concurrent-ruby (1.0.5)
concurrent-ruby-ext (1.0.5)
concurrent-ruby (= 1.0.5)
connection_pool (2.2.1) connection_pool (2.2.1)
crack (0.4.3) crack (0.4.3)
safe_yaml (~> 1.0.0) safe_yaml (~> 1.0.0)
...@@ -573,6 +576,36 @@ GEM ...@@ -573,6 +576,36 @@ GEM
parser (2.4.0.0) parser (2.4.0.0)
ast (~> 2.2) ast (~> 2.2)
path_expander (1.0.1) path_expander (1.0.1)
peek (1.0.1)
concurrent-ruby (>= 0.9.0)
concurrent-ruby-ext (>= 0.9.0)
railties (>= 4.0.0)
peek-gc (0.0.2)
peek
peek-host (1.0.0)
peek
peek-mysql2 (1.1.0)
atomic (>= 1.0.0)
mysql2
peek
peek-performance_bar (1.2.1)
peek (>= 0.1.0)
peek-pg (1.3.0)
concurrent-ruby
concurrent-ruby-ext
peek
pg
peek-rblineprof (0.2.0)
peek
rblineprof
peek-redis (1.2.0)
atomic (>= 1.0.0)
peek
redis
peek-sidekiq (1.0.3)
atomic (>= 1.0.0)
peek
sidekiq
pg (0.18.4) pg (0.18.4)
po_to_json (1.0.1) po_to_json (1.0.1)
json (>= 1.6.0) json (>= 1.6.0)
...@@ -1031,6 +1064,15 @@ DEPENDENCIES ...@@ -1031,6 +1064,15 @@ DEPENDENCIES
omniauth_crowd (~> 2.2.0) omniauth_crowd (~> 2.2.0)
org-ruby (~> 0.9.12) org-ruby (~> 0.9.12)
paranoia (~> 2.2) paranoia (~> 2.2)
peek (~> 1.0.1)
peek-gc (~> 0.0.2)
peek-host (~> 1.0.0)
peek-mysql2 (~> 1.1.0)
peek-performance_bar (~> 1.2.1)
peek-pg (~> 1.3.0)
peek-rblineprof (~> 0.2.0)
peek-redis (~> 1.2.0)
peek-sidekiq (~> 1.0.3)
pg (~> 0.18.2) pg (~> 0.18.2)
poltergeist (~> 1.9.0) poltergeist (~> 1.9.0)
premailer-rails (~> 1.9.0) premailer-rails (~> 1.9.0)
......
...@@ -1478,7 +1478,7 @@ const normalizeNewlines = function(str) { ...@@ -1478,7 +1478,7 @@ const normalizeNewlines = function(str) {
const cachedNoteBodyText = $noteBodyText.html(); const cachedNoteBodyText = $noteBodyText.html();
// Show updated comment content temporarily // Show updated comment content temporarily
$noteBodyText.html(formContent); $noteBodyText.html(_.escape(formContent));
$editingNote.removeClass('is-editing fade-in-full').addClass('being-posted fade-in-half'); $editingNote.removeClass('is-editing fade-in-full').addClass('being-posted fade-in-half');
$editingNote.find('.note-headline-meta a').html('<i class="fa fa-spinner fa-spin" aria-label="Comment is being updated" aria-hidden="true"></i>'); $editingNote.find('.note-headline-meta a').html('<i class="fa fa-spinner fa-spin" aria-label="Comment is being updated" aria-hidden="true"></i>');
...@@ -1491,7 +1491,7 @@ const normalizeNewlines = function(str) { ...@@ -1491,7 +1491,7 @@ const normalizeNewlines = function(str) {
}) })
.fail(() => { .fail(() => {
// Submission failed, revert back to original note // Submission failed, revert back to original note
$noteBodyText.html(cachedNoteBodyText); $noteBodyText.html(_.escape(cachedNoteBodyText));
$editingNote.removeClass('being-posted fade-in'); $editingNote.removeClass('being-posted fade-in');
$editingNote.find('.fa.fa-spinner').remove(); $editingNote.find('.fa.fa-spinner').remove();
......
import 'vendor/peek';
import 'vendor/peek.performance_bar';
$(document).on('click', '#peek-show-queries', (e) => {
e.preventDefault();
$('.peek-rblineprof-modal').hide();
const $modal = $('#modal-peek-pg-queries');
if ($modal.length) {
$modal.modal('toggle');
}
});
$(document).on('click', '.js-lineprof-file', (e) => {
e.preventDefault();
$(e.target).parents('.peek-rblineprof-file').find('.data').toggle();
});
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, prefer-arrow-callback, consistent-return, object-shorthand, no-unused-vars, one-var, one-var-declaration-per-line, no-else-return, comma-dangle, max-len */ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, prefer-arrow-callback, consistent-return, object-shorthand, no-unused-vars, one-var, one-var-declaration-per-line, no-else-return, comma-dangle, max-len */
/* global Mousetrap */ /* global Mousetrap */
/* global findFileURL */ /* global findFileURL */
import Cookies from 'js-cookie';
import findAndFollowLink from './shortcuts_dashboard_navigation'; import findAndFollowLink from './shortcuts_dashboard_navigation';
(function() { (function() {
...@@ -14,6 +16,7 @@ import findAndFollowLink from './shortcuts_dashboard_navigation'; ...@@ -14,6 +16,7 @@ import findAndFollowLink from './shortcuts_dashboard_navigation';
Mousetrap.bind('?', this.onToggleHelp); Mousetrap.bind('?', this.onToggleHelp);
Mousetrap.bind('s', Shortcuts.focusSearch); Mousetrap.bind('s', Shortcuts.focusSearch);
Mousetrap.bind('f', (e => this.focusFilter(e))); Mousetrap.bind('f', (e => this.focusFilter(e)));
Mousetrap.bind('p b', this.onTogglePerfBar);
const $globalDropdownMenu = $('.global-dropdown-menu'); const $globalDropdownMenu = $('.global-dropdown-menu');
const $globalDropdownToggle = $('.global-dropdown-toggle'); const $globalDropdownToggle = $('.global-dropdown-toggle');
...@@ -53,6 +56,17 @@ import findAndFollowLink from './shortcuts_dashboard_navigation'; ...@@ -53,6 +56,17 @@ import findAndFollowLink from './shortcuts_dashboard_navigation';
return Shortcuts.toggleHelp(this.enabledHelp); return Shortcuts.toggleHelp(this.enabledHelp);
}; };
Shortcuts.prototype.onTogglePerfBar = function(e) {
e.preventDefault();
const performanceBarCookieName = 'perf_bar_enabled';
if (Cookies.get(performanceBarCookieName) === 'true') {
Cookies.remove(performanceBarCookieName, { path: '/' });
} else {
Cookies.set(performanceBarCookieName, true, { path: '/' });
}
gl.utils.refreshCurrentPage();
};
Shortcuts.prototype.toggleMarkdownPreview = function(e) { Shortcuts.prototype.toggleMarkdownPreview = function(e) {
// Check if short-cut was triggered while in Write Mode // Check if short-cut was triggered while in Write Mode
const $target = $(e.target); const $target = $(e.target);
......
...@@ -9,6 +9,7 @@ class ApplicationController < ActionController::Base ...@@ -9,6 +9,7 @@ class ApplicationController < ActionController::Base
include SentryHelper include SentryHelper
include WorkhorseHelper include WorkhorseHelper
include EnforcesTwoFactorAuthentication include EnforcesTwoFactorAuthentication
include Peek::Rblineprof::CustomControllerHelpers
before_action :authenticate_user_from_private_token! before_action :authenticate_user_from_private_token!
before_action :authenticate_user_from_rss_token! before_action :authenticate_user_from_rss_token!
...@@ -18,7 +19,7 @@ class ApplicationController < ActionController::Base ...@@ -18,7 +19,7 @@ class ApplicationController < ActionController::Base
before_action :ldap_security_check before_action :ldap_security_check
before_action :sentry_context before_action :sentry_context
before_action :default_headers before_action :default_headers
before_action :add_gon_variables before_action :add_gon_variables, unless: -> { request.path.start_with?('/-/peek') }
before_action :configure_permitted_parameters, if: :devise_controller? before_action :configure_permitted_parameters, if: :devise_controller?
before_action :require_email, unless: :devise_controller? before_action :require_email, unless: :devise_controller?
...@@ -63,6 +64,21 @@ class ApplicationController < ActionController::Base ...@@ -63,6 +64,21 @@ class ApplicationController < ActionController::Base
end end
end end
def peek_enabled?
return false unless Gitlab::PerformanceBar.enabled?
return false unless current_user
if RequestStore.active?
if RequestStore.store.key?(:peek_enabled)
RequestStore.store[:peek_enabled]
else
RequestStore.store[:peek_enabled] = cookies[:perf_bar_enabled].present?
end
else
cookies[:perf_bar_enabled].present?
end
end
protected protected
# This filter handles both private tokens and personal access tokens # This filter handles both private tokens and personal access tokens
......
...@@ -21,7 +21,7 @@ class AutocompleteController < ApplicationController ...@@ -21,7 +21,7 @@ class AutocompleteController < ApplicationController
@users = [current_user, *@users].uniq @users = [current_user, *@users].uniq
end end
if params[:author_id].present? if params[:author_id].present? && current_user
author = User.find_by_id(params[:author_id]) author = User.find_by_id(params[:author_id])
@users = [author, *@users].uniq if author @users = [author, *@users].uniq if author
end end
......
...@@ -27,6 +27,7 @@ module NavHelper ...@@ -27,6 +27,7 @@ module NavHelper
def nav_header_class def nav_header_class
class_name = '' class_name = ''
class_name << " with-horizontal-nav" if defined?(nav) && nav class_name << " with-horizontal-nav" if defined?(nav) && nav
class_name << " with-peek" if peek_enabled?
class_name class_name
end end
......
...@@ -276,6 +276,16 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -276,6 +276,16 @@ class ApplicationSetting < ActiveRecord::Base
end end
end end
def elasticsearch_indexing
License.feature_available?(:elastic_search) && super
end
alias_method :elasticsearch_indexing?, :elasticsearch_indexing
def elasticsearch_search
License.feature_available?(:elastic_search) && super
end
alias_method :elasticsearch_search?, :elasticsearch_search
def elasticsearch_url def elasticsearch_url
read_attribute(:elasticsearch_url).split(',').map(&:strip) read_attribute(:elasticsearch_url).split(',').map(&:strip)
end end
......
...@@ -7,12 +7,14 @@ class License < ActiveRecord::Base ...@@ -7,12 +7,14 @@ class License < ActiveRecord::Base
AUDITOR_USER_FEATURE = 'GitLab_Auditor_User'.freeze AUDITOR_USER_FEATURE = 'GitLab_Auditor_User'.freeze
SERVICE_DESK_FEATURE = 'GitLab_ServiceDesk'.freeze SERVICE_DESK_FEATURE = 'GitLab_ServiceDesk'.freeze
OBJECT_STORAGE_FEATURE = 'GitLab_ObjectStorage'.freeze OBJECT_STORAGE_FEATURE = 'GitLab_ObjectStorage'.freeze
ELASTIC_SEARCH_FEATURE = 'GitLab_ElasticSearch'.freeze
FEATURE_CODES = { FEATURE_CODES = {
geo: GEO_FEATURE, geo: GEO_FEATURE,
auditor_user: AUDITOR_USER_FEATURE, auditor_user: AUDITOR_USER_FEATURE,
service_desk: SERVICE_DESK_FEATURE, service_desk: SERVICE_DESK_FEATURE,
object_storage: OBJECT_STORAGE_FEATURE, object_storage: OBJECT_STORAGE_FEATURE,
elastic_search: ELASTIC_SEARCH_FEATURE,
# Features that make sense to Namespace: # Features that make sense to Namespace:
deploy_board: DEPLOY_BOARD_FEATURE, deploy_board: DEPLOY_BOARD_FEATURE,
file_lock: FILE_LOCK_FEATURE file_lock: FILE_LOCK_FEATURE
...@@ -24,7 +26,7 @@ class License < ActiveRecord::Base ...@@ -24,7 +26,7 @@ class License < ActiveRecord::Base
EARLY_ADOPTER_PLAN = 'early_adopter'.freeze EARLY_ADOPTER_PLAN = 'early_adopter'.freeze
EES_FEATURES = [ EES_FEATURES = [
# .. { ELASTIC_SEARCH_FEATURE => 1 }
].freeze ].freeze
EEP_FEATURES = [ EEP_FEATURES = [
...@@ -91,6 +93,8 @@ class License < ActiveRecord::Base ...@@ -91,6 +93,8 @@ class License < ActiveRecord::Base
end end
end end
delegate :feature_available?, to: :current, allow_nil: true
def reset_current def reset_current
RequestStore.delete(:current_license) RequestStore.delete(:current_license)
end end
...@@ -183,7 +187,7 @@ class License < ActiveRecord::Base ...@@ -183,7 +187,7 @@ class License < ActiveRecord::Base
end end
def plan def plan
restricted_attr(:plan) restricted_attr(:plan, STARTER_PLAN)
end end
def current_active_users_count def current_active_users_count
......
...@@ -170,7 +170,7 @@ class Project < ActiveRecord::Base ...@@ -170,7 +170,7 @@ class Project < ActiveRecord::Base
has_many :audit_events, as: :entity, dependent: :destroy has_many :audit_events, as: :entity, dependent: :destroy
has_many :notification_settings, dependent: :destroy, as: :source has_many :notification_settings, dependent: :destroy, as: :source
has_one :import_data, dependent: :delete, class_name: "ProjectImportData" has_one :import_data, dependent: :delete, class_name: 'ProjectImportData'
has_one :project_feature, dependent: :destroy has_one :project_feature, dependent: :destroy
has_one :statistics, class_name: 'ProjectStatistics', dependent: :delete has_one :statistics, class_name: 'ProjectStatistics', dependent: :delete
has_many :container_repositories, dependent: :destroy has_many :container_repositories, dependent: :destroy
...@@ -346,10 +346,6 @@ class Project < ActiveRecord::Base ...@@ -346,10 +346,6 @@ class Project < ActiveRecord::Base
transition [:scheduled, :started] => :failed transition [:scheduled, :started] => :failed
end end
event :import_retry do
transition failed: :started
end
state :scheduled state :scheduled
state :started state :started
state :finished state :finished
...@@ -410,8 +406,6 @@ class Project < ActiveRecord::Base ...@@ -410,8 +406,6 @@ class Project < ActiveRecord::Base
after_transition [:finished, :failed] => [:scheduled, :started] do |project, _| after_transition [:finished, :failed] => [:scheduled, :started] do |project, _|
Gitlab::Mirror.increment_capacity(project.id) if project.mirror? Gitlab::Mirror.increment_capacity(project.id) if project.mirror?
end end
after_transition started: :finished, do: :reset_cache_and_import_attrs
end end
class << self class << self
......
class ProjectSnippetPolicy < BasePolicy class ProjectSnippetPolicy < BasePolicy
def rules def rules
# We have to check both project feature visibility and a snippet visibility and take the stricter one
# This will be simplified - check https://gitlab.com/gitlab-org/gitlab-ce/issues/27573
return unless @subject.project.feature_available?(:snippets, @user)
return unless Ability.allowed?(@user, :read_project, @subject.project)
can! :read_project_snippet if @subject.public? can! :read_project_snippet if @subject.public?
return unless @user return unless @user
......
...@@ -13,6 +13,13 @@ class FileUploader < GitlabUploader ...@@ -13,6 +13,13 @@ class FileUploader < GitlabUploader
) )
end end
# Not using `GitlabUploader.base_dir` because all project namespaces are in
# the `public/uploads` dir.
#
def self.base_dir
root_dir
end
# Returns the part of `store_dir` that can change based on the model's current # Returns the part of `store_dir` that can change based on the model's current
# path # path
# #
......
...@@ -3,16 +3,28 @@ class GitlabUploader < CarrierWave::Uploader::Base ...@@ -3,16 +3,28 @@ class GitlabUploader < CarrierWave::Uploader::Base
File.join(CarrierWave.root, upload_record.path) File.join(CarrierWave.root, upload_record.path)
end end
def self.base_dir def self.root_dir
'uploads' 'uploads'
end end
delegate :base_dir, to: :class # When object storage is used, keep the `root_dir` as `base_dir`.
# The files aren't really in folders there, they just have a name.
# The files that contain user input in their name, also contain a hash, so
# the names are still unique
#
# This method is overridden in the `FileUploader`
def self.base_dir
return root_dir unless file_storage?
File.join(root_dir, 'system')
end
def file_storage? def self.file_storage?
storage.is_a?(CarrierWave::Storage::File) self.storage == CarrierWave::Storage::File
end end
delegate :base_dir, :file_storage?, to: :class
def file_cache_storage? def file_cache_storage?
cache_storage.is_a?(CarrierWave::Storage::File) cache_storage.is_a?(CarrierWave::Storage::File)
end end
......
...@@ -112,6 +112,10 @@ class ObjectStoreUploader < GitlabUploader ...@@ -112,6 +112,10 @@ class ObjectStoreUploader < GitlabUploader
raise 'Object Storage feature is missing' unless subject.project.feature_available?(:object_storage) raise 'Object Storage feature is missing' unless subject.project.feature_available?(:object_storage)
end end
def file_storage?
storage.is_a?(CarrierWave::Storage::File)
end
private private
def set_default_local_store(new_file) def set_default_local_store(new_file)
......
%fieldset - if License.feature_available?(:elastic_search)
%legend Elasticsearch %fieldset
.form-group %legend Elasticsearch
.col-sm-offset-2.col-sm-10 .form-group
.checkbox .col-sm-offset-2.col-sm-10
= f.label :elasticsearch_indexing do .checkbox
= f.check_box :elasticsearch_indexing = f.label :elasticsearch_indexing do
Elasticsearch indexing = f.check_box :elasticsearch_indexing
Elasticsearch indexing
- missing = !Gitlab::Elastic::Indexer.experimental_indexer_present? - missing = !Gitlab::Elastic::Indexer.experimental_indexer_present?
.form-group .form-group
.col-sm-offset-2.col-sm-10 .col-sm-offset-2.col-sm-10
.checkbox .checkbox
= f.label :elasticsearch_experimental_indexer do = f.label :elasticsearch_experimental_indexer do
= f.check_box :elasticsearch_experimental_indexer, disabled: missing = f.check_box :elasticsearch_experimental_indexer, disabled: missing
Use <a href="https://gitlab.com/gitlab-org/gitlab-elasticsearch-indexer">experimental repository indexer</a> Use <a href="https://gitlab.com/gitlab-org/gitlab-elasticsearch-indexer">experimental repository indexer</a>
- if missing - if missing
(not installed) (not installed)
.form-group .form-group
.col-sm-offset-2.col-sm-10 .col-sm-offset-2.col-sm-10
.checkbox .checkbox
= f.label :elasticsearch_search do = f.label :elasticsearch_search do
= f.check_box :elasticsearch_search = f.check_box :elasticsearch_search
Search with Elasticsearch enabled Search with Elasticsearch enabled
.form-group .form-group
= f.label :elasticsearch_url, 'URL', class: 'control-label col-sm-2' = f.label :elasticsearch_url, 'URL', class: 'control-label col-sm-2'
.col-sm-10 .col-sm-10
= f.text_field :elasticsearch_url, value: @application_setting.elasticsearch_url.join(', '), class: 'form-control', placeholder: 'http://localhost:9200' = f.text_field :elasticsearch_url, value: @application_setting.elasticsearch_url.join(', '), class: 'form-control', placeholder: 'http://localhost:9200'
.help-block .help-block
The url to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., "http://localhost:9200, http://localhost:9201"). The url to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., "http://localhost:9200, http://localhost:9201").
%fieldset %fieldset
%legend Elasticsearch AWS IAM credentials %legend Elasticsearch AWS IAM credentials
.form-group .form-group
.col-sm-offset-2.col-sm-10 .col-sm-offset-2.col-sm-10
.checkbox .checkbox
= f.label :elasticsearch_aws do = f.label :elasticsearch_aws do
= f.check_box :elasticsearch_aws = f.check_box :elasticsearch_aws
Using AWS hosted Elasticsearch with IAM credentials Using AWS hosted Elasticsearch with IAM credentials
.form-group .form-group
= f.label :elasticsearch_aws_region, 'AWS region', class: 'control-label col-sm-2' = f.label :elasticsearch_aws_region, 'AWS region', class: 'control-label col-sm-2'
.col-sm-10 .col-sm-10
= f.text_field :elasticsearch_aws_region, value: @application_setting.elasticsearch_aws_region, class: 'form-control' = f.text_field :elasticsearch_aws_region, value: @application_setting.elasticsearch_aws_region, class: 'form-control'
.help-block .help-block
Region that elasticsearch is configured Region that elasticsearch is configured
.form-group .form-group
= f.label :elasticsearch_aws_access_key, 'AWS Access Key', class: 'control-label col-sm-2' = f.label :elasticsearch_aws_access_key, 'AWS Access Key', class: 'control-label col-sm-2'
.col-sm-10 .col-sm-10
= f.text_field :elasticsearch_aws_access_key, value: @application_setting.elasticsearch_aws_access_key, class: 'form-control' = f.text_field :elasticsearch_aws_access_key, value: @application_setting.elasticsearch_aws_access_key, class: 'form-control'
.help-block .help-block
AWS Access Key. Only required if not using role instance credentials AWS Access Key. Only required if not using role instance credentials
.form-group .form-group
= f.label :elasticsearch_aws_secret_access_key, 'AWS Secret Access Key', class: 'control-label col-sm-2' = f.label :elasticsearch_aws_secret_access_key, 'AWS Secret Access Key', class: 'control-label col-sm-2'
.col-sm-10 .col-sm-10
= f.password_field :elasticsearch_aws_secret_access_key, value: @application_setting.elasticsearch_aws_secret_access_key, class: 'form-control' = f.password_field :elasticsearch_aws_secret_access_key, value: @application_setting.elasticsearch_aws_secret_access_key, class: 'form-control'
.help-block .help-block
AWS Secret Access Key. Only required if not using role instance credentials AWS Secret Access Key. Only required if not using role instance credentials
...@@ -27,6 +27,10 @@ ...@@ -27,6 +27,10 @@
%td.shortcut %td.shortcut
.key f .key f
%td Focus Filter %td Focus Filter
%tr
%td.shortcut
.key p b
%td Show/hide the Performance Bar
%tr %tr
%td.shortcut %td.shortcut
.key ? .key ?
......
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
= stylesheet_link_tag "application", media: "all" = stylesheet_link_tag "application", media: "all"
= stylesheet_link_tag "print", media: "print" = stylesheet_link_tag "print", media: "print"
= stylesheet_link_tag "test", media: "all" if Rails.env.test? = stylesheet_link_tag "test", media: "all" if Rails.env.test?
= stylesheet_link_tag 'peek' if peek_enabled?
= Gon::Base.render_data = Gon::Base.render_data
...@@ -37,6 +38,7 @@ ...@@ -37,6 +38,7 @@
= webpack_bundle_tag "main" = webpack_bundle_tag "main"
= webpack_bundle_tag "raven" if current_application_settings.clientside_sentry_enabled = webpack_bundle_tag "raven" if current_application_settings.clientside_sentry_enabled
= webpack_bundle_tag "test" if Rails.env.test? = webpack_bundle_tag "test" if Rails.env.test?
= webpack_bundle_tag 'peek' if peek_enabled?
- if content_for?(:page_specific_javascripts) - if content_for?(:page_specific_javascripts)
= yield :page_specific_javascripts = yield :page_specific_javascripts
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
= render "layouts/head" = render "layouts/head"
%body{ class: @body_class, data: { page: body_data_page, project: "#{@project.path if @project}", group: "#{@group.path if @group}" } } %body{ class: @body_class, data: { page: body_data_page, project: "#{@project.path if @project}", group: "#{@group.path if @group}" } }
= render "layouts/init_auto_complete" if @gfm_form = render "layouts/init_auto_complete" if @gfm_form
= render 'peek/bar'
= render "layouts/header/default", title: header_title = render "layouts/header/default", title: header_title
= render 'layouts/page', sidebar: sidebar, nav: nav = render 'layouts/page', sidebar: sidebar, nav: nav
......
- local_assigns.fetch(:view)
= render 'peek/views/sql', view: view
mysql
- local_assigns.fetch(:view)
= render 'peek/views/sql', view: view
pg
%strong
%a#peek-show-queries{ href: '#' }
%span{ data: { defer_to: "#{view.defer_key}-duration" } }...
\/
%span{ data: { defer_to: "#{view.defer_key}-calls" } }...
#modal-peek-pg-queries.modal{ tabindex: -1 }
.modal-dialog
#modal-peek-pg-queries-content.modal-content
.modal-header
%a.close{ href: "#", "data-dismiss" => "modal" } ×
%h4
SQL queries
.modal-body{ data: { defer_to: "#{view.defer_key}-queries" } }...
---
title: Add explicit licensing for Elasticsearch
merge_request: 2108
author:
---
title: Add an optional performance bar to view performance metrics for the current page
merge_request: 11439
author:
...@@ -111,6 +111,7 @@ module Gitlab ...@@ -111,6 +111,7 @@ module Gitlab
config.assets.precompile << "katex.css" config.assets.precompile << "katex.css"
config.assets.precompile << "katex.js" config.assets.precompile << "katex.js"
config.assets.precompile << "xterm/xterm.css" config.assets.precompile << "xterm/xterm.css"
config.assets.precompile << "peek.css"
config.assets.precompile << "lib/ace.js" config.assets.precompile << "lib/ace.js"
config.assets.precompile << "vendor/assets/fonts/*" config.assets.precompile << "vendor/assets/fonts/*"
config.assets.precompile << "test.css" config.assets.precompile << "test.css"
......
Rails.application.config.peek.adapter = :redis, { client: ::Redis.new(Gitlab::Redis.params) }
Peek.into Peek::Views::Host
Peek.into Peek::Views::PerformanceBar
if Gitlab::Database.mysql?
require 'peek-mysql2'
PEEK_DB_CLIENT = ::Mysql2::Client
PEEK_DB_VIEW = Peek::Views::Mysql2
else
require 'peek-pg'
PEEK_DB_CLIENT = ::PG::Connection
PEEK_DB_VIEW = Peek::Views::PG
end
Peek.into PEEK_DB_VIEW
Peek.into Peek::Views::Redis
Peek.into Peek::Views::Sidekiq
Peek.into Peek::Views::Rblineprof
Peek.into Peek::Views::GC
# rubocop:disable Style/ClassAndModuleCamelCase
class PEEK_DB_CLIENT
class << self
attr_accessor :query_details
end
self.query_details = Concurrent::Array.new
end
PEEK_DB_VIEW.prepend ::Gitlab::PerformanceBar::PeekQueryTracker
class Peek::Views::PerformanceBar::ProcessUtilization
prepend ::Gitlab::PerformanceBar::PeekPerformanceBarWithRackBody
end
...@@ -51,6 +51,7 @@ Rails.application.routes.draw do ...@@ -51,6 +51,7 @@ Rails.application.routes.draw do
get 'liveness' => 'health#liveness' get 'liveness' => 'health#liveness'
get 'readiness' => 'health#readiness' get 'readiness' => 'health#readiness'
resources :metrics, only: [:index] resources :metrics, only: [:index]
mount Peek::Railtie => '/peek'
end end
# Koding route # Koding route
......
scope path: :uploads do scope path: :uploads do
# Note attachments and User/Group/Project avatars # Note attachments and User/Group/Project avatars
get ":model/:mounted_as/:id/:filename", get "system/:model/:mounted_as/:id/:filename",
to: "uploads#show", to: "uploads#show",
constraints: { model: /note|user|group|project/, mounted_as: /avatar|attachment/, filename: /[^\/]+/ } constraints: { model: /note|user|group|project/, mounted_as: /avatar|attachment/, filename: /[^\/]+/ }
...@@ -15,7 +15,7 @@ scope path: :uploads do ...@@ -15,7 +15,7 @@ scope path: :uploads do
constraints: { filename: /[^\/]+/ } constraints: { filename: /[^\/]+/ }
# Appearance # Appearance
get ":model/:mounted_as/:id/:filename", get "system/:model/:mounted_as/:id/:filename",
to: "uploads#show", to: "uploads#show",
constraints: { model: /appearance/, mounted_as: /logo|header_logo/, filename: /.+/ } constraints: { model: /appearance/, mounted_as: /logo|header_logo/, filename: /.+/ }
......
...@@ -70,6 +70,7 @@ var config = { ...@@ -70,6 +70,7 @@ var config = {
raven: './raven/index.js', raven: './raven/index.js',
vue_merge_request_widget: './vue_merge_request_widget/index.js', vue_merge_request_widget: './vue_merge_request_widget/index.js',
test: './test.js', test: './test.js',
peek: './peek.js',
}, },
output: { output: {
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class RenameSystemNamespaces < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
include Gitlab::ShellAdapter
disable_ddl_transaction!
class User < ActiveRecord::Base
self.table_name = 'users'
end
class Namespace < ActiveRecord::Base
self.table_name = 'namespaces'
belongs_to :parent, class_name: 'RenameSystemNamespaces::Namespace'
has_one :route, as: :source
has_many :children, class_name: 'RenameSystemNamespaces::Namespace', foreign_key: :parent_id
belongs_to :owner, class_name: 'RenameSystemNamespaces::User'
# Overridden to have the correct `source_type` for the `route` relation
def self.name
'Namespace'
end
def full_path
if route && route.path.present?
@full_path ||= route.path
else
update_route if persisted?
build_full_path
end
end
def build_full_path
if parent && path
parent.full_path + '/' + path
else
path
end
end
def update_route
prepare_route
route.save
end
def prepare_route
route || build_route(source: self)
route.path = build_full_path
route.name = build_full_name
@full_path = nil
@full_name = nil
end
def build_full_name
if parent && name
parent.human_name + ' / ' + name
else
name
end
end
def human_name
owner&.name
end
end
class Route < ActiveRecord::Base
self.table_name = 'routes'
belongs_to :source, polymorphic: true
end
class Project < ActiveRecord::Base
self.table_name = 'projects'
def repository_storage_path
Gitlab.config.repositories.storages[repository_storage]['path']
end
end
DOWNTIME = false
def up
return unless system_namespace
old_path = system_namespace.path
old_full_path = system_namespace.full_path
# Only remove the last occurrence of the path name to get the parent namespace path
namespace_path = remove_last_occurrence(old_full_path, old_path)
new_path = rename_path(namespace_path, old_path)
new_full_path = join_namespace_path(namespace_path, new_path)
Namespace.where(id: system_namespace).update_all(path: new_path) # skips callbacks & validations
replace_statement = replace_sql(Route.arel_table[:path], old_full_path, new_full_path)
route_matches = [old_full_path, "#{old_full_path}/%"]
update_column_in_batches(:routes, :path, replace_statement) do |table, query|
query.where(Route.arel_table[:path].matches_any(route_matches))
end
clear_cache_for_namespace(system_namespace)
# tasks here are based on `Namespace#move_dir`
move_repositories(system_namespace, old_full_path, new_full_path)
move_namespace_folders(uploads_dir, old_full_path, new_full_path) if file_storage?
move_namespace_folders(pages_dir, old_full_path, new_full_path)
end
def down
# nothing to do
end
def remove_last_occurrence(string, pattern)
string.reverse.sub(pattern.reverse, "").reverse
end
def move_namespace_folders(directory, old_relative_path, new_relative_path)
old_path = File.join(directory, old_relative_path)
return unless File.directory?(old_path)
new_path = File.join(directory, new_relative_path)
FileUtils.mv(old_path, new_path)
end
def move_repositories(namespace, old_full_path, new_full_path)
repo_paths_for_namespace(namespace).each do |repository_storage_path|
# Ensure old directory exists before moving it
gitlab_shell.add_namespace(repository_storage_path, old_full_path)
unless gitlab_shell.mv_namespace(repository_storage_path, old_full_path, new_full_path)
say "Exception moving path #{repository_storage_path} from #{old_full_path} to #{new_full_path}"
end
end
end
def rename_path(namespace_path, path_was)
counter = 0
path = "#{path_was}#{counter}"
while route_exists?(join_namespace_path(namespace_path, path))
counter += 1
path = "#{path_was}#{counter}"
end
path
end
def route_exists?(full_path)
Route.where(Route.arel_table[:path].matches(full_path)).any?
end
def join_namespace_path(namespace_path, path)
if namespace_path.present?
File.join(namespace_path, path)
else
path
end
end
def system_namespace
@system_namespace ||= Namespace.where(parent_id: nil).
where(arel_table[:path].matches(system_namespace_path)).
first
end
def system_namespace_path
"system"
end
def clear_cache_for_namespace(namespace)
project_ids = projects_for_namespace(namespace).pluck(:id)
update_column_in_batches(:projects, :description_html, nil) do |table, query|
query.where(table[:id].in(project_ids))
end
update_column_in_batches(:issues, :description_html, nil) do |table, query|
query.where(table[:project_id].in(project_ids))
end
update_column_in_batches(:merge_requests, :description_html, nil) do |table, query|
query.where(table[:target_project_id].in(project_ids))
end
update_column_in_batches(:notes, :note_html, nil) do |table, query|
query.where(table[:project_id].in(project_ids))
end
update_column_in_batches(:milestones, :description_html, nil) do |table, query|
query.where(table[:project_id].in(project_ids))
end
end
def projects_for_namespace(namespace)
namespace_ids = child_ids_for_parent(namespace, ids: [namespace.id])
namespace_or_children = Project.arel_table[:namespace_id].in(namespace_ids)
Project.unscoped.where(namespace_or_children)
end
# This won't scale to huge trees, but it should do for a handful of namespaces
# called `system`.
def child_ids_for_parent(namespace, ids: [])
namespace.children.each do |child|
ids << child.id
child_ids_for_parent(child, ids: ids) if child.children.any?
end
ids
end
def repo_paths_for_namespace(namespace)
projects_for_namespace(namespace).distinct.
select(:repository_storage).map(&:repository_storage_path)
end
def uploads_dir
File.join(Rails.root, "public", "uploads")
end
def pages_dir
Settings.pages.path
end
def file_storage?
CarrierWave::Uploader::Base.storage == CarrierWave::Storage::File
end
def arel_table
Namespace.arel_table
end
end
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class MoveUploadsToSystemDir < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
DOWNTIME = false
DIRECTORIES_TO_MOVE = %w(user project note group appearance).freeze
def up
return unless file_storage?
FileUtils.mkdir_p(new_upload_dir)
DIRECTORIES_TO_MOVE.each do |dir|
source = File.join(old_upload_dir, dir)
destination = File.join(new_upload_dir, dir)
next unless File.directory?(source)
next if File.directory?(destination)
say "Moving #{source} -> #{destination}"
FileUtils.mv(source, destination)
FileUtils.ln_s(destination, source)
end
end
def down
return unless file_storage?
return unless File.directory?(new_upload_dir)
DIRECTORIES_TO_MOVE.each do |dir|
source = File.join(new_upload_dir, dir)
destination = File.join(old_upload_dir, dir)
next unless File.directory?(source)
next if File.directory?(destination) && !File.symlink?(destination)
say "Moving #{source} -> #{destination}"
FileUtils.rm(destination) if File.symlink?(destination)
FileUtils.mv(source, destination)
end
end
def file_storage?
CarrierWave::Uploader::Base.storage == CarrierWave::Storage::File
end
def base_directory
Rails.root
end
def old_upload_dir
File.join(base_directory, "public", "uploads")
end
def new_upload_dir
File.join(base_directory, "public", "uploads", "system")
end
end
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class UpdateUploadPathsToSystem < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
AFFECTED_MODELS = %w(User Project Note Namespace Appearance)
def up
update_column_in_batches(:uploads, :path, replace_sql(arel_table[:path], base_directory, new_upload_dir)) do |_table, query|
query.where(uploads_to_switch_to_new_path)
end
end
def down
update_column_in_batches(:uploads, :path, replace_sql(arel_table[:path], new_upload_dir, base_directory)) do |_table, query|
query.where(uploads_to_switch_to_old_path)
end
end
# "SELECT \"uploads\".* FROM \"uploads\" WHERE \"uploads\".\"model_type\" IN ('User', 'Project', 'Note', 'Namespace', 'Appearance') AND (\"uploads\".\"path\" ILIKE 'uploads/%' AND NOT (\"uploads\".\"path\" ILIKE 'uploads/system/%'))"
def uploads_to_switch_to_new_path
affected_uploads.and(starting_with_base_directory).and(starting_with_new_upload_directory.not)
end
# "SELECT \"uploads\".* FROM \"uploads\" WHERE \"uploads\".\"model_type\" IN ('User', 'Project', 'Note', 'Namespace', 'Appearance') AND (\"uploads\".\"path\" ILIKE 'uploads/%' AND \"uploads\".\"path\" ILIKE 'uploads/system/%')"
def uploads_to_switch_to_old_path
affected_uploads.and(starting_with_new_upload_directory)
end
def starting_with_base_directory
arel_table[:path].matches("#{base_directory}/%")
end
def starting_with_new_upload_directory
arel_table[:path].matches("#{new_upload_dir}/%")
end
def affected_uploads
arel_table[:model_type].in(AFFECTED_MODELS)
end
def base_directory
"uploads"
end
def new_upload_dir
File.join(base_directory, "system")
end
def arel_table
Arel::Table.new(:uploads)
end
end
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class CleanUploadSymlinks < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
DOWNTIME = false
DIRECTORIES_TO_MOVE = %w(user project note group appeareance)
def up
return unless file_storage?
DIRECTORIES_TO_MOVE.each do |dir|
symlink_location = File.join(old_upload_dir, dir)
next unless File.symlink?(symlink_location)
say "removing symlink: #{symlink_location}"
FileUtils.rm(symlink_location)
end
end
def down
return unless file_storage?
DIRECTORIES_TO_MOVE.each do |dir|
symlink = File.join(old_upload_dir, dir)
destination = File.join(new_upload_dir, dir)
next if File.directory?(symlink)
next unless File.directory?(destination)
say "Creating symlink #{symlink} -> #{destination}"
FileUtils.ln_s(destination, symlink)
end
end
def file_storage?
CarrierWave::Uploader::Base.storage == CarrierWave::Storage::File
end
def base_directory
Rails.root
end
def old_upload_dir
File.join(base_directory, "public", "uploads")
end
def new_upload_dir
File.join(base_directory, "public", "uploads", "system")
end
end
class MoveAppearanceToSystemDir < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
DOWNTIME = false
DIRECTORY_TO_MOVE = 'appearance'.freeze
def up
source = File.join(old_upload_dir, DIRECTORY_TO_MOVE)
destination = File.join(new_upload_dir, DIRECTORY_TO_MOVE)
move_directory(source, destination)
end
def down
source = File.join(new_upload_dir, DIRECTORY_TO_MOVE)
destination = File.join(old_upload_dir, DIRECTORY_TO_MOVE)
move_directory(source, destination)
end
def move_directory(source, destination)
unless file_storage?
say 'Not using file storage, skipping'
return
end
unless File.directory?(source)
say "#{source} did not exist, skipping"
return
end
if File.directory?(destination)
say "#{destination} already existed, skipping"
return
end
say "Moving #{source} -> #{destination}"
FileUtils.mv(source, destination)
end
def file_storage?
CarrierWave::Uploader::Base.storage == CarrierWave::Storage::File
end
def base_directory
Rails.root
end
def old_upload_dir
File.join(base_directory, "public", "uploads")
end
def new_upload_dir
File.join(base_directory, "public", "uploads", "system")
end
end
...@@ -6,7 +6,10 @@ You can see GitLab's keyboard shortcuts by using 'shift + ?' ...@@ -6,7 +6,10 @@ You can see GitLab's keyboard shortcuts by using 'shift + ?'
| Keyboard Shortcut | Description | | Keyboard Shortcut | Description |
| ----------------- | ----------- | | ----------------- | ----------- |
| <kbd>n</kbd> | Main navigation |
| <kbd>s</kbd> | Focus search | | <kbd>s</kbd> | Focus search |
| <kbd>f</kbd> | Focus filter |
| <kbd>p b</kbd> | Show/hide the Performance Bar |
| <kbd>?</kbd> | Show/hide this dialog | | <kbd>?</kbd> | Show/hide this dialog |
| <kbd></kbd> + <kbd>shift</kbd> + <kbd>p</kbd> | Toggle markdown preview | | <kbd></kbd> + <kbd>shift</kbd> + <kbd>p</kbd> | Toggle markdown preview |
| <kbd></kbd> | Edit last comment (when focused on an empty textarea) | | <kbd></kbd> | Edit last comment (when focused on an empty textarea) |
......
...@@ -104,7 +104,7 @@ class Spinach::Features::Groups < Spinach::FeatureSteps ...@@ -104,7 +104,7 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
step 'I should see new group "Owned" avatar' do step 'I should see new group "Owned" avatar' do
expect(owned_group.avatar).to be_instance_of AvatarUploader expect(owned_group.avatar).to be_instance_of AvatarUploader
expect(owned_group.avatar.url).to eq "/uploads/group/avatar/#{Group.find_by(name: "Owned").id}/banana_sample.gif" expect(owned_group.avatar.url).to eq "/uploads/system/group/avatar/#{Group.find_by(name: "Owned").id}/banana_sample.gif"
end end
step 'I should see the "Remove avatar" button' do step 'I should see the "Remove avatar" button' do
......
...@@ -36,7 +36,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps ...@@ -36,7 +36,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
step 'I should see new avatar' do step 'I should see new avatar' do
expect(@user.avatar).to be_instance_of AvatarUploader expect(@user.avatar).to be_instance_of AvatarUploader
expect(@user.avatar.url).to eq "/uploads/user/avatar/#{@user.id}/banana_sample.gif" expect(@user.avatar.url).to eq "/uploads/system/user/avatar/#{@user.id}/banana_sample.gif"
end end
step 'I should see the "Remove avatar" button' do step 'I should see the "Remove avatar" button' do
......
...@@ -39,7 +39,7 @@ class Spinach::Features::Project < Spinach::FeatureSteps ...@@ -39,7 +39,7 @@ class Spinach::Features::Project < Spinach::FeatureSteps
step 'I should see new project avatar' do step 'I should see new project avatar' do
expect(@project.avatar).to be_instance_of AvatarUploader expect(@project.avatar).to be_instance_of AvatarUploader
url = @project.avatar.url url = @project.avatar.url
expect(url).to eq "/uploads/project/avatar/#{@project.id}/banana_sample.gif" expect(url).to eq "/uploads/system/project/avatar/#{@project.id}/banana_sample.gif"
end end
step 'I should see the "Remove avatar" button' do step 'I should see the "Remove avatar" button' do
......
...@@ -49,6 +49,7 @@ module API ...@@ -49,6 +49,7 @@ module API
end end
before { allow_access_with_scope :api } before { allow_access_with_scope :api }
before { header['X-Frame-Options'] = 'SAMEORIGIN' }
before { Gitlab::I18n.locale = current_user&.preferred_language } before { Gitlab::I18n.locale = current_user&.preferred_language }
after { Gitlab::I18n.use_default_locale } after { Gitlab::I18n.use_default_locale }
......
...@@ -62,7 +62,7 @@ module Banzai ...@@ -62,7 +62,7 @@ module Banzai
nodes.select do |node| nodes.select do |node|
if node.has_attribute?(project_attr) if node.has_attribute?(project_attr)
can_read_reference?(user, projects[node]) can_read_reference?(user, projects[node], node)
else else
true true
end end
...@@ -231,7 +231,7 @@ module Banzai ...@@ -231,7 +231,7 @@ module Banzai
# see reference comments. # see reference comments.
# Override this method on subclasses # Override this method on subclasses
# to check if user can read resource # to check if user can read resource
def can_read_reference?(user, ref_project) def can_read_reference?(user, ref_project, node)
raise NotImplementedError raise NotImplementedError
end end
......
...@@ -32,7 +32,7 @@ module Banzai ...@@ -32,7 +32,7 @@ module Banzai
private private
def can_read_reference?(user, ref_project) def can_read_reference?(user, ref_project, node)
can?(user, :download_code, ref_project) can?(user, :download_code, ref_project)
end end
end end
......
...@@ -36,7 +36,7 @@ module Banzai ...@@ -36,7 +36,7 @@ module Banzai
private private
def can_read_reference?(user, ref_project) def can_read_reference?(user, ref_project, node)
can?(user, :download_code, ref_project) can?(user, :download_code, ref_project)
end end
end end
......
...@@ -23,7 +23,7 @@ module Banzai ...@@ -23,7 +23,7 @@ module Banzai
private private
def can_read_reference?(user, ref_project) def can_read_reference?(user, ref_project, node)
can?(user, :read_issue, ref_project) can?(user, :read_issue, ref_project)
end end
end end
......
...@@ -9,7 +9,7 @@ module Banzai ...@@ -9,7 +9,7 @@ module Banzai
private private
def can_read_reference?(user, ref_project) def can_read_reference?(user, ref_project, node)
can?(user, :read_label, ref_project) can?(user, :read_label, ref_project)
end end
end end
......
...@@ -40,6 +40,10 @@ module Banzai ...@@ -40,6 +40,10 @@ module Banzai
self.class.data_attribute self.class.data_attribute
) )
end end
def can_read_reference?(user, ref_project, node)
can?(user, :read_merge_request, ref_project)
end
end end
end end
end end
...@@ -9,7 +9,7 @@ module Banzai ...@@ -9,7 +9,7 @@ module Banzai
private private
def can_read_reference?(user, ref_project) def can_read_reference?(user, ref_project, node)
can?(user, :read_milestone, ref_project) can?(user, :read_milestone, ref_project)
end end
end end
......
...@@ -9,8 +9,8 @@ module Banzai ...@@ -9,8 +9,8 @@ module Banzai
private private
def can_read_reference?(user, ref_project) def can_read_reference?(user, ref_project, node)
can?(user, :read_project_snippet, ref_project) can?(user, :read_project_snippet, referenced_by([node]).first)
end end
end end
end end
......
...@@ -103,7 +103,7 @@ module Banzai ...@@ -103,7 +103,7 @@ module Banzai
flat_map { |p| p.team.members.to_a } flat_map { |p| p.team.members.to_a }
end end
def can_read_reference?(user, ref_project) def can_read_reference?(user, ref_project, node)
can?(user, :read_project, ref_project) can?(user, :read_project, ref_project)
end end
end end
......
...@@ -49,6 +49,7 @@ module Gitlab ...@@ -49,6 +49,7 @@ module Gitlab
sent_notifications sent_notifications
services services
snippets snippets
system
teams teams
u u
unicorn_test unicorn_test
......
module Gitlab
module PerformanceBar
def self.enabled?
Feature.enabled?('gitlab_performance_bar')
end
end
end
# This solves a bug with a X-Senfile header that wouldn't be set properly, see
# https://github.com/peek/peek-performance_bar/pull/27
module Gitlab
module PerformanceBar
module PeekPerformanceBarWithRackBody
def call(env)
@env = env
reset_stats
@total_requests += 1
first_request if @total_requests == 1
env['process.request_start'] = @start.to_f
env['process.total_requests'] = total_requests
status, headers, body = @app.call(env)
body = Rack::BodyProxy.new(body) { record_request }
[status, headers, body]
end
end
end
end
# Inspired by https://github.com/peek/peek-pg/blob/master/lib/peek/views/pg.rb
module Gitlab
module PerformanceBar
module PeekQueryTracker
def sorted_queries
PEEK_DB_CLIENT.query_details.
sort { |a, b| b[:duration] <=> a[:duration] }
end
def results
super.merge(queries: sorted_queries)
end
private
def setup_subscribers
super
# Reset each counter when a new request starts
before_request do
PEEK_DB_CLIENT.query_details = []
end
subscribe('sql.active_record') do |_, start, finish, _, data|
if RequestStore.active? && RequestStore.store[:peek_enabled]
track_query(data[:sql].strip, data[:binds], start, finish)
end
end
end
def track_query(raw_query, bindings, start, finish)
query = Gitlab::Sherlock::Query.new(raw_query, start, finish)
query_info = { duration: '%.3f' % query.duration, sql: query.formatted_query }
PEEK_DB_CLIENT.query_details << query_info
end
end
end
end
module Gitlab module Gitlab
class UploadsTransfer < ProjectTransfer class UploadsTransfer < ProjectTransfer
def root_dir def root_dir
File.join(CarrierWave.root, GitlabUploader.base_dir) File.join(CarrierWave.root, FileUploader.base_dir)
end end
end end
end end
module Peek
module Rblineprof
module CustomControllerHelpers
extend ActiveSupport::Concern
# This will become useless once https://github.com/peek/peek-rblineprof/pull/5
# is merged
def pygmentize(file_name, code, lexer = nil)
if lexer.present?
Gitlab::Highlight.highlight(file_name, code)
else
"<pre>#{Rack::Utils.escape_html(code)}</pre>"
end
end
# rubocop:disable all
def inject_rblineprof
ret = nil
profile = lineprof(rblineprof_profiler_regex) do
ret = yield
end
if response.content_type =~ %r|text/html|
sort = params[:lineprofiler_sort]
mode = params[:lineprofiler_mode] || 'cpu'
min = (params[:lineprofiler_min] || 5).to_i * 1000
summary = params[:lineprofiler_summary]
# Sort each file by the longest calculated time
per_file = profile.map do |file, lines|
total, child, excl, total_cpu, child_cpu, excl_cpu = lines[0]
wall = summary == 'exclusive' ? excl : total
cpu = summary == 'exclusive' ? excl_cpu : total_cpu
idle = summary == 'exclusive' ? (excl - excl_cpu) : (total - total_cpu)
[
file, lines,
wall, cpu, idle,
sort == 'idle' ? idle : sort == 'cpu' ? cpu : wall
]
end.sort_by{ |a,b,c,d,e,f| -f }
output = ''
per_file.each do |file_name, lines, file_wall, file_cpu, file_idle, file_sort|
output << "<div class='peek-rblineprof-file'><div class='heading'>"
show_src = file_sort > min
tmpl = show_src ? "<a href='#' class='js-lineprof-file'>%s</a>" : "%s"
if mode == 'cpu'
output << sprintf("<span class='duration'>% 8.1fms + % 8.1fms</span> #{tmpl}", file_cpu / 1000.0, file_idle / 1000.0, file_name.sub(Rails.root.to_s + '/', ''))
else
output << sprintf("<span class='duration'>% 8.1fms</span> #{tmpl}", file_wall/1000.0, file_name.sub(Rails.root.to_s + '/', ''))
end
output << "</div>" # .heading
next unless show_src
output << "<div class='data'>"
code = []
times = []
File.readlines(file_name).each_with_index do |line, i|
code << line
wall, cpu, calls = lines[i + 1]
if calls && calls > 0
if mode == 'cpu'
idle = wall - cpu
times << sprintf("% 8.1fms + % 8.1fms (% 5d)", cpu / 1000.0, idle / 1000.0, calls)
else
times << sprintf("% 8.1fms (% 5d)", wall / 1000.0, calls)
end
else
times << ' '
end
end
output << "<pre class='duration'>#{times.join("\n")}</pre>"
# The following line was changed from
# https://github.com/peek/peek-rblineprof/blob/8d3b7a283a27de2f40abda45974516693d882258/lib/peek/rblineprof/controller_helpers.rb#L125
# This will become useless once https://github.com/peek/peek-rblineprof/pull/16
# is merged and is implemented.
output << "<pre class='code highlight white'>#{pygmentize(file_name, code.join, 'ruby')}</pre>"
output << "</div></div>" # .data then .peek-rblineprof-file
end
response.body += "<div class='peek-rblineprof-modal' id='line-profile'>#{output}</div>".html_safe
end
ret
end
end
end
end
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
require ::File.expand_path('../lib/gitlab/popen', __dir__) require ::File.expand_path('../lib/gitlab/popen', __dir__)
tasks = [ tasks = [
%w[bundle exec bundle-audit check --update --ignore CVE-2016-4658], %w[bundle exec bundle-audit check --update --ignore CVE-2016-4658 CVE-2017-5029],
%w[bundle exec rake config_lint], %w[bundle exec rake config_lint],
%w[bundle exec rake flay], %w[bundle exec rake flay],
%w[bundle exec rake haml_lint], %w[bundle exec rake haml_lint],
......
...@@ -210,22 +210,32 @@ describe AutocompleteController do ...@@ -210,22 +210,32 @@ describe AutocompleteController do
end end
context 'author of issuable included' do context 'author of issuable included' do
before do
sign_in(user)
end
let(:body) { JSON.parse(response.body) } let(:body) { JSON.parse(response.body) }
it 'includes the author' do context 'authenticated' do
get(:users, author_id: non_member.id) before do
sign_in(user)
end
it 'includes the author' do
get(:users, author_id: non_member.id)
expect(body.first["username"]).to eq non_member.username
end
it 'rejects non existent user ids' do
get(:users, author_id: 99999)
expect(body.first["username"]).to eq non_member.username expect(body.collect { |u| u['id'] }).not_to include(99999)
end
end end
it 'rejects non existent user ids' do context 'without authenticating' do
get(:users, author_id: 99999) it 'returns empty result' do
get(:users, author_id: non_member.id)
expect(body.collect { |u| u['id'] }).not_to include(99999) expect(body).to be_empty
end
end end
end end
......
...@@ -69,16 +69,9 @@ describe Projects::JobsController do ...@@ -69,16 +69,9 @@ describe Projects::JobsController do
Ci::Build::AVAILABLE_STATUSES.each do |status| Ci::Build::AVAILABLE_STATUSES.each do |status|
create_build(status, status) create_build(status, status)
end end
RequestStore.begin!
end
after do
RequestStore.end!
RequestStore.clear!
end end
it "verifies number of queries" do it 'verifies number of queries', :request_store do
recorded = ActiveRecord::QueryRecorder.new { get_index } recorded = ActiveRecord::QueryRecorder.new { get_index }
expect(recorded.count).to be_within(5).of(8) expect(recorded.count).to be_within(5).of(8)
end end
......
...@@ -282,10 +282,8 @@ describe Projects::MergeRequestsController do ...@@ -282,10 +282,8 @@ describe Projects::MergeRequestsController do
end end
end end
context 'number of queries' do context 'number of queries', :request_store do
it 'verifies number of queries' do it 'verifies number of queries' do
RequestStore.begin!
# pre-create objects # pre-create objects
merge_request merge_request
...@@ -293,9 +291,6 @@ describe Projects::MergeRequestsController do ...@@ -293,9 +291,6 @@ describe Projects::MergeRequestsController do
expect(recorded.count).to be_within(5).of(30) expect(recorded.count).to be_within(5).of(30)
expect(recorded.cached_count).to eq(0) expect(recorded.cached_count).to eq(0)
RequestStore.end!
RequestStore.clear!
end end
end end
end end
......
...@@ -49,21 +49,14 @@ describe Projects::PipelinesController do ...@@ -49,21 +49,14 @@ describe Projects::PipelinesController do
expect(json_response['details']).to have_key 'stages' expect(json_response['details']).to have_key 'stages'
end end
context 'when the pipeline has multiple stages and groups' do context 'when the pipeline has multiple stages and groups', :request_store do
before do before do
RequestStore.begin!
create_build('build', 0, 'build') create_build('build', 0, 'build')
create_build('test', 1, 'rspec 0') create_build('test', 1, 'rspec 0')
create_build('deploy', 2, 'production') create_build('deploy', 2, 'production')
create_build('post deploy', 3, 'pages 0') create_build('post deploy', 3, 'pages 0')
end end
after do
RequestStore.end!
RequestStore.clear!
end
let(:project) { create(:project) } let(:project) { create(:project) }
let(:pipeline) do let(:pipeline) do
create(:ci_empty_pipeline, project: project, user: user, sha: project.commit.id) create(:ci_empty_pipeline, project: project, user: user, sha: project.commit.id)
......
FactoryGirl.define do
factory :upload do
model { build(:project) }
path { "uploads/system/project/avatar/avatar.jpg" }
size 100.kilobytes
uploader "AvatarUploader"
end
end
...@@ -63,11 +63,11 @@ feature 'Admin Appearance', feature: true do ...@@ -63,11 +63,11 @@ feature 'Admin Appearance', feature: true do
end end
def logo_selector def logo_selector
'//img[@src^="/uploads/appearance/logo"]' '//img[@src^="/uploads/system/appearance/logo"]'
end end
def header_logo_selector def header_logo_selector
'//img[@src^="/uploads/appearance/header_logo"]' '//img[@src^="/uploads/system/appearance/header_logo"]'
end end
def logo_fixture def logo_fixture
......
...@@ -18,7 +18,7 @@ feature 'User uploads avatar to group', feature: true do ...@@ -18,7 +18,7 @@ feature 'User uploads avatar to group', feature: true do
visit group_path(group) visit group_path(group)
expect(page).to have_selector(%Q(img[src$="/uploads/group/avatar/#{group.id}/dk.png"])) expect(page).to have_selector(%Q(img[src$="/uploads/system/group/avatar/#{group.id}/dk.png"]))
# Cheating here to verify something that isn't user-facing, but is important # Cheating here to verify something that isn't user-facing, but is important
expect(group.reload.avatar.file).to exist expect(group.reload.avatar.file).to exist
......
...@@ -16,7 +16,7 @@ feature 'User uploads avatar to profile', feature: true do ...@@ -16,7 +16,7 @@ feature 'User uploads avatar to profile', feature: true do
visit user_path(user) visit user_path(user)
expect(page).to have_selector(%Q(img[src$="/uploads/user/avatar/#{user.id}/dk.png"])) expect(page).to have_selector(%Q(img[src$="/uploads/system/user/avatar/#{user.id}/dk.png"]))
# Cheating here to verify something that isn't user-facing, but is important # Cheating here to verify something that isn't user-facing, but is important
expect(user.reload.avatar.file).to exist expect(user.reload.avatar.file).to exist
......
require 'rails_helper'
describe 'User can display performacne bar', :js do
shared_examples 'performance bar is disabled' do
it 'does not show the performance bar by default' do
expect(page).not_to have_css('#peek')
end
context 'when user press `pb`' do
before do
find('body').native.send_keys('pb')
end
it 'does not show the performance bar by default' do
expect(page).not_to have_css('#peek')
end
end
end
shared_examples 'performance bar is enabled' do
it 'does not show the performance bar by default' do
expect(page).not_to have_css('#peek')
end
context 'when user press `pb`' do
before do
find('body').native.send_keys('pb')
end
it 'does not show the performance bar by default' do
expect(page).not_to have_css('#peek')
end
end
end
context 'when user is logged-out' do
before do
visit root_path
end
context 'when the gitlab_performance_bar feature is disabled' do
before do
Feature.disable('gitlab_performance_bar')
end
it_behaves_like 'performance bar is disabled'
end
context 'when the gitlab_performance_bar feature is enabled' do
before do
Feature.enable('gitlab_performance_bar')
end
it_behaves_like 'performance bar is disabled'
end
end
context 'when user is logged-in' do
before do
login_as :user
visit root_path
end
context 'when the gitlab_performance_bar feature is disabled' do
before do
Feature.disable('gitlab_performance_bar')
end
it_behaves_like 'performance bar is disabled'
end
context 'when the gitlab_performance_bar feature is enabled' do
before do
Feature.enable('gitlab_performance_bar')
end
it_behaves_like 'performance bar is enabled'
end
end
end
# coding: utf-8
require 'spec_helper' require 'spec_helper'
describe ApplicationHelper do describe ApplicationHelper do
...@@ -58,13 +59,13 @@ describe ApplicationHelper do ...@@ -58,13 +59,13 @@ describe ApplicationHelper do
describe 'project_icon' do describe 'project_icon' do
it 'returns an url for the avatar' do it 'returns an url for the avatar' do
project = create(:empty_project, avatar: File.open(uploaded_image_temp_path)) project = create(:empty_project, avatar: File.open(uploaded_image_temp_path))
avatar_url = "/uploads/project/avatar/#{project.id}/banana_sample.gif" avatar_url = "/uploads/system/project/avatar/#{project.id}/banana_sample.gif"
expect(helper.project_icon(project.full_path).to_s). expect(helper.project_icon(project.full_path).to_s).
to eq "<img src=\"#{avatar_url}\" alt=\"Banana sample\" />" to eq "<img src=\"#{avatar_url}\" alt=\"Banana sample\" />"
allow(ActionController::Base).to receive(:asset_host).and_return(gitlab_host) allow(ActionController::Base).to receive(:asset_host).and_return(gitlab_host)
avatar_url = "#{gitlab_host}/uploads/project/avatar/#{project.id}/banana_sample.gif" avatar_url = "#{gitlab_host}/uploads/system/project/avatar/#{project.id}/banana_sample.gif"
expect(helper.project_icon(project.full_path).to_s). expect(helper.project_icon(project.full_path).to_s).
to eq "<img src=\"#{avatar_url}\" alt=\"Banana sample\" />" to eq "<img src=\"#{avatar_url}\" alt=\"Banana sample\" />"
...@@ -84,12 +85,12 @@ describe ApplicationHelper do ...@@ -84,12 +85,12 @@ describe ApplicationHelper do
it 'returns an url for the avatar' do it 'returns an url for the avatar' do
user = create(:user, avatar: File.open(uploaded_image_temp_path)) user = create(:user, avatar: File.open(uploaded_image_temp_path))
avatar_url = "/uploads/user/avatar/#{user.id}/banana_sample.gif" avatar_url = "/uploads/system/user/avatar/#{user.id}/banana_sample.gif"
expect(helper.avatar_icon(user.email).to_s).to match(avatar_url) expect(helper.avatar_icon(user.email).to_s).to match(avatar_url)
allow(ActionController::Base).to receive(:asset_host).and_return(gitlab_host) allow(ActionController::Base).to receive(:asset_host).and_return(gitlab_host)
avatar_url = "#{gitlab_host}/uploads/user/avatar/#{user.id}/banana_sample.gif" avatar_url = "#{gitlab_host}/uploads/system/user/avatar/#{user.id}/banana_sample.gif"
expect(helper.avatar_icon(user.email).to_s).to match(avatar_url) expect(helper.avatar_icon(user.email).to_s).to match(avatar_url)
end end
...@@ -102,7 +103,7 @@ describe ApplicationHelper do ...@@ -102,7 +103,7 @@ describe ApplicationHelper do
user = create(:user, avatar: File.open(uploaded_image_temp_path)) user = create(:user, avatar: File.open(uploaded_image_temp_path))
expect(helper.avatar_icon(user.email).to_s). expect(helper.avatar_icon(user.email).to_s).
to match("/gitlab/uploads/user/avatar/#{user.id}/banana_sample.gif") to match("/gitlab/uploads/system/user/avatar/#{user.id}/banana_sample.gif")
end end
it 'calls gravatar_icon when no User exists with the given email' do it 'calls gravatar_icon when no User exists with the given email' do
...@@ -116,7 +117,7 @@ describe ApplicationHelper do ...@@ -116,7 +117,7 @@ describe ApplicationHelper do
user = create(:user, avatar: File.open(uploaded_image_temp_path)) user = create(:user, avatar: File.open(uploaded_image_temp_path))
expect(helper.avatar_icon(user).to_s). expect(helper.avatar_icon(user).to_s).
to match("/uploads/user/avatar/#{user.id}/banana_sample.gif") to match("/uploads/system/user/avatar/#{user.id}/banana_sample.gif")
end end
end end
end end
......
...@@ -52,7 +52,7 @@ describe EmailsHelper do ...@@ -52,7 +52,7 @@ describe EmailsHelper do
) )
expect(header_logo).to eq( expect(header_logo).to eq(
%{<img style="height: 50px" src="/uploads/appearance/header_logo/#{appearance.id}/dk.png" alt="Dk" />} %{<img style="height: 50px" src="/uploads/system/appearance/header_logo/#{appearance.id}/dk.png" alt="Dk" />}
) )
end end
end end
......
...@@ -10,7 +10,7 @@ describe GroupsHelper do ...@@ -10,7 +10,7 @@ describe GroupsHelper do
group.avatar = fixture_file_upload(avatar_file_path) group.avatar = fixture_file_upload(avatar_file_path)
group.save! group.save!
expect(group_icon(group.path).to_s). expect(group_icon(group.path).to_s).
to match("/uploads/group/avatar/#{group.id}/banana_sample.gif") to match("/uploads/system/group/avatar/#{group.id}/banana_sample.gif")
end end
it 'gives default avatar_icon when no avatar is present' do it 'gives default avatar_icon when no avatar is present' do
...@@ -29,7 +29,7 @@ describe GroupsHelper do ...@@ -29,7 +29,7 @@ describe GroupsHelper do
it 'returns an url for the avatar pointing to the primary node base url' do it 'returns an url for the avatar pointing to the primary node base url' do
group.avatar = fixture_file_upload(avatar_file_path) group.avatar = fixture_file_upload(avatar_file_path)
group.save! group.save!
expect(group_icon(group.path).to_s).to match("#{geo_url}/uploads/group/avatar/#{group.id}/banana_sample.gif") expect(group_icon(group.path).to_s).to match("#{geo_url}/uploads/system/group/avatar/#{group.id}/banana_sample.gif")
end end
it 'gives default avatar_icon when no avatar is present' do it 'gives default avatar_icon when no avatar is present' do
......
...@@ -60,7 +60,7 @@ describe PageLayoutHelper do ...@@ -60,7 +60,7 @@ describe PageLayoutHelper do
%w(project user group).each do |type| %w(project user group).each do |type|
context "with @#{type} assigned" do context "with @#{type} assigned" do
it "uses #{type.titlecase} avatar if available" do it "uses #{type.titlecase} avatar if available" do
object = double(avatar_url: 'http://example.com/uploads/avatar.png') object = double(avatar_url: 'http://example.com/uploads/system/avatar.png')
assign(type, object) assign(type, object)
expect(helper.page_image).to eq object.avatar_url expect(helper.page_image).to eq object.avatar_url
......
...@@ -461,6 +461,45 @@ import '~/notes'; ...@@ -461,6 +461,45 @@ import '~/notes';
}); });
}); });
describe('update comment with script tags', () => {
const sampleComment = '<script></script>';
const updatedComment = '<script></script>';
const note = {
id: 1234,
html: `<li class="note note-row-1234 timeline-entry" id="note_1234">
<div class="note-text">${sampleComment}</div>
</li>`,
note: sampleComment,
valid: true
};
let $form;
let $notesContainer;
beforeEach(() => {
this.notes = new Notes('', []);
window.gon.current_username = 'root';
window.gon.current_user_fullname = 'Administrator';
$form = $('form.js-main-target-form');
$notesContainer = $('ul.main-notes-list');
$form.find('textarea.js-note-text').html(sampleComment);
});
it('should not render a script tag', () => {
const deferred = $.Deferred();
spyOn($, 'ajax').and.returnValue(deferred.promise());
$('.js-comment-button').click();
deferred.resolve(note);
const $noteEl = $notesContainer.find(`#note_${note.id}`);
$noteEl.find('.js-note-edit').click();
$noteEl.find('textarea.js-note-text').html(updatedComment);
$noteEl.find('.js-comment-save-button').click();
const $updatedNoteEl = $notesContainer.find(`#note_${note.id}`).find('.js-task-list-container');
expect($updatedNoteEl.find('.note-text').text().trim()).toEqual('');
});
});
describe('getFormData', () => { describe('getFormData', () => {
let $form; let $form;
let sampleComment; let sampleComment;
......
...@@ -22,7 +22,7 @@ describe('Commit component', () => { ...@@ -22,7 +22,7 @@ describe('Commit component', () => {
shortSha: 'b7836edd', shortSha: 'b7836edd',
title: 'Commit message', title: 'Commit message',
author: { author: {
avatar_url: 'https://gitlab.com/uploads/user/avatar/300478/avatar.png', avatar_url: 'https://gitlab.com/uploads/system/user/avatar/300478/avatar.png',
web_url: 'https://gitlab.com/jschatz1', web_url: 'https://gitlab.com/jschatz1',
path: '/jschatz1', path: '/jschatz1',
username: 'jschatz1', username: 'jschatz1',
...@@ -45,7 +45,7 @@ describe('Commit component', () => { ...@@ -45,7 +45,7 @@ describe('Commit component', () => {
shortSha: 'b7836edd', shortSha: 'b7836edd',
title: 'Commit message', title: 'Commit message',
author: { author: {
avatar_url: 'https://gitlab.com/uploads/user/avatar/300478/avatar.png', avatar_url: 'https://gitlab.com/uploads/system/user/avatar/300478/avatar.png',
web_url: 'https://gitlab.com/jschatz1', web_url: 'https://gitlab.com/jschatz1',
path: '/jschatz1', path: '/jschatz1',
username: 'jschatz1', username: 'jschatz1',
......
...@@ -47,16 +47,7 @@ describe Banzai::Filter::AbstractReferenceFilter do ...@@ -47,16 +47,7 @@ describe Banzai::Filter::AbstractReferenceFilter do
end end
end end
context 'with RequestStore enabled' do context 'with RequestStore enabled', :request_store do
before do
RequestStore.begin!
end
after do
RequestStore.end!
RequestStore.clear!
end
it 'returns a list of Projects for a list of paths' do it 'returns a list of Projects for a list of paths' do
expect(filter.find_projects_for_paths([project.path_with_namespace])). expect(filter.find_projects_for_paths([project.path_with_namespace])).
to eq([project]) to eq([project])
......
...@@ -29,16 +29,7 @@ describe Banzai::IssuableExtractor, lib: true do ...@@ -29,16 +29,7 @@ describe Banzai::IssuableExtractor, lib: true do
expect(result).to eq(issue_link => issue, merge_request_link => merge_request) expect(result).to eq(issue_link => issue, merge_request_link => merge_request)
end end
describe 'caching' do describe 'caching', :request_store do
before do
RequestStore.begin!
end
after do
RequestStore.end!
RequestStore.clear!
end
it 'saves records to cache' do it 'saves records to cache' do
extractor.extract([issue_link, merge_request_link]) extractor.extract([issue_link, merge_request_link])
......
...@@ -30,7 +30,7 @@ describe Banzai::ReferenceParser::BaseParser, lib: true do ...@@ -30,7 +30,7 @@ describe Banzai::ReferenceParser::BaseParser, lib: true do
it 'checks if user can read the resource' do it 'checks if user can read the resource' do
link['data-project'] = project.id.to_s link['data-project'] = project.id.to_s
expect(subject).to receive(:can_read_reference?).with(user, project) expect(subject).to receive(:can_read_reference?).with(user, project, link)
subject.nodes_visible_to_user(user, [link]) subject.nodes_visible_to_user(user, [link])
end end
......
...@@ -4,20 +4,199 @@ describe Banzai::ReferenceParser::SnippetParser, lib: true do ...@@ -4,20 +4,199 @@ describe Banzai::ReferenceParser::SnippetParser, lib: true do
include ReferenceParserHelpers include ReferenceParserHelpers
let(:project) { create(:empty_project, :public) } let(:project) { create(:empty_project, :public) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:snippet) { create(:snippet, project: project) } let(:external_user) { create(:user, :external) }
let(:project_member) { create(:user) }
subject { described_class.new(project, user) } subject { described_class.new(project, user) }
let(:link) { empty_html_link } let(:link) { empty_html_link }
def visible_references(snippet_visibility, user = nil)
snippet = create(:project_snippet, snippet_visibility, project: project)
link['data-project'] = project.id.to_s
link['data-snippet'] = snippet.id.to_s
subject.nodes_visible_to_user(user, [link])
end
before do
project.add_user(project_member, :developer)
end
describe '#nodes_visible_to_user' do describe '#nodes_visible_to_user' do
context 'when the link has a data-issue attribute' do context 'when a project is public and the snippets feature is enabled for everyone' do
before { link['data-snippet'] = snippet.id.to_s } before do
project.project_feature.update_attribute(:snippets_access_level, ProjectFeature::ENABLED)
end
it 'creates a reference for guest for a public snippet' do
expect(visible_references(:public)).to eq([link])
end
it 'creates a reference for a regular user for a public snippet' do
expect(visible_references(:public, user)).to eq([link])
end
it 'creates a reference for a regular user for an internal snippet' do
expect(visible_references(:internal, user)).to eq([link])
end
it 'does not create a reference for an external user for an internal snippet' do
expect(visible_references(:internal, external_user)).to be_empty
end
it 'creates a reference for a project member for a private snippet' do
expect(visible_references(:private, project_member)).to eq([link])
end
it 'does not create a reference for a regular user for a private snippet' do
expect(visible_references(:private, user)).to be_empty
end
end
context 'when a project is public and the snippets feature is enabled for project team members' do
before do
project.project_feature.update_attribute(:snippets_access_level, ProjectFeature::PRIVATE)
end
it 'creates a reference for a project member for a public snippet' do
expect(visible_references(:public, project_member)).to eq([link])
end
it 'does not create a reference for guest for a public snippet' do
expect(visible_references(:public, nil)).to be_empty
end
it 'does not create a reference for a regular user for a public snippet' do
expect(visible_references(:public, user)).to be_empty
end
it 'creates a reference for a project member for an internal snippet' do
expect(visible_references(:internal, project_member)).to eq([link])
end
it 'does not create a reference for a regular user for an internal snippet' do
expect(visible_references(:internal, user)).to be_empty
end
it 'creates a reference for a project member for a private snippet' do
expect(visible_references(:private, project_member)).to eq([link])
end
it 'does not create a reference for a regular user for a private snippet' do
expect(visible_references(:private, user)).to be_empty
end
end
context 'when a project is internal and the snippets feature is enabled for everyone' do
before do
project.update_attribute(:visibility, Gitlab::VisibilityLevel::INTERNAL)
project.project_feature.update_attribute(:snippets_access_level, ProjectFeature::ENABLED)
end
it 'does not create a reference for guest for a public snippet' do
expect(visible_references(:public)).to be_empty
end
it 'does not create a reference for an external user for a public snippet' do
expect(visible_references(:public, external_user)).to be_empty
end
it_behaves_like "referenced feature visibility", "snippets" it 'creates a reference for a regular user for a public snippet' do
expect(visible_references(:public, user)).to eq([link])
end
it 'creates a reference for a regular user for an internal snippet' do
expect(visible_references(:internal, user)).to eq([link])
end
it 'does not create a reference for an external user for an internal snippet' do
expect(visible_references(:internal, external_user)).to be_empty
end
it 'creates a reference for a project member for a private snippet' do
expect(visible_references(:private, project_member)).to eq([link])
end
it 'does not create a reference for a regular user for a private snippet' do
expect(visible_references(:private, user)).to be_empty
end
end
context 'when a project is internal and the snippets feature is enabled for project team members' do
before do
project.update_attribute(:visibility, Gitlab::VisibilityLevel::INTERNAL)
project.project_feature.update_attribute(:snippets_access_level, ProjectFeature::PRIVATE)
end
it 'creates a reference for a project member for a public snippet' do
expect(visible_references(:public, project_member)).to eq([link])
end
it 'does not create a reference for guest for a public snippet' do
expect(visible_references(:public, nil)).to be_empty
end
it 'does not create reference for a regular user for a public snippet' do
expect(visible_references(:public, user)).to be_empty
end
it 'creates a reference for a project member for an internal snippet' do
expect(visible_references(:internal, project_member)).to eq([link])
end
it 'does not create a reference for a regular user for an internal snippet' do
expect(visible_references(:internal, user)).to be_empty
end
it 'creates a reference for a project member for a private snippet' do
expect(visible_references(:private, project_member)).to eq([link])
end
it 'does not create reference for a regular user for a private snippet' do
expect(visible_references(:private, user)).to be_empty
end
end
context 'when a project is private and the snippets feature is enabled for project team members' do
before do
project.update_attribute(:visibility, Gitlab::VisibilityLevel::PRIVATE)
project.project_feature.update_attribute(:snippets_access_level, ProjectFeature::PRIVATE)
end
it 'creates a reference for a project member for a public snippet' do
expect(visible_references(:public, project_member)).to eq([link])
end
it 'does not create a reference for guest for a public snippet' do
expect(visible_references(:public, nil)).to be_empty
end
it 'does not create a reference for a regular user for a public snippet' do
expect(visible_references(:public, user)).to be_empty
end
it 'creates a reference for a project member for an internal snippet' do
expect(visible_references(:internal, project_member)).to eq([link])
end
it 'does not create a reference for a regular user for an internal snippet' do
expect(visible_references(:internal, user)).to be_empty
end
it 'creates a reference for a project member for a private snippet' do
expect(visible_references(:private, project_member)).to eq([link])
end
it 'does not create a reference for a regular user for a private snippet' do
expect(visible_references(:private, user)).to be_empty
end
end end
end end
describe '#referenced_by' do describe '#referenced_by' do
let(:snippet) { create(:snippet, project: project) }
describe 'when the link has a data-snippet attribute' do describe 'when the link has a data-snippet attribute' do
context 'using an existing snippet ID' do context 'using an existing snippet ID' do
it 'returns an Array of snippets' do it 'returns an Array of snippets' do
...@@ -31,7 +210,7 @@ describe Banzai::ReferenceParser::SnippetParser, lib: true do ...@@ -31,7 +210,7 @@ describe Banzai::ReferenceParser::SnippetParser, lib: true do
it 'returns an empty Array' do it 'returns an empty Array' do
link['data-snippet'] = '' link['data-snippet'] = ''
expect(subject.referenced_by([link])).to eq([]) expect(subject.referenced_by([link])).to be_empty
end end
end end
end end
......
...@@ -43,18 +43,9 @@ describe Banzai::ReferenceParser::UserParser, lib: true do ...@@ -43,18 +43,9 @@ describe Banzai::ReferenceParser::UserParser, lib: true do
expect(subject.referenced_by([link])).to eq([user]) expect(subject.referenced_by([link])).to eq([user])
end end
context 'when RequestStore is active' do context 'when RequestStore is active', :request_store do
let(:other_user) { create(:user) } let(:other_user) { create(:user) }
before do
RequestStore.begin!
end
after do
RequestStore.end!
RequestStore.clear!
end
it 'does not return users from the first call in the second' do it 'does not return users from the first call in the second' do
link['data-user'] = user.id.to_s link['data-user'] = user.id.to_s
......
require 'spec_helper'
describe Gitlab::UploadsTransfer do
it 'leaves avatar uploads where they are' do
project_with_avatar = create(:empty_project, :with_avatar)
described_class.new.rename_namespace('project', 'project-renamed')
expect(File.exist?(project_with_avatar.avatar.path)).to be_truthy
end
end
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20170406111121_clean_upload_symlinks.rb')
describe CleanUploadSymlinks do
let(:migration) { described_class.new }
let(:test_dir) { File.join(Rails.root, "tmp", "tests", "move_uploads_test") }
let(:uploads_dir) { File.join(test_dir, "public", "uploads") }
let(:new_uploads_dir) { File.join(uploads_dir, "system") }
let(:original_path) { File.join(new_uploads_dir, 'user') }
let(:symlink_path) { File.join(uploads_dir, 'user') }
before do
FileUtils.remove_dir(test_dir) if File.directory?(test_dir)
FileUtils.mkdir_p(uploads_dir)
allow(migration).to receive(:base_directory).and_return(test_dir)
allow(migration).to receive(:say)
end
describe "#up" do
before do
FileUtils.mkdir_p(original_path)
FileUtils.ln_s(original_path, symlink_path)
end
it 'removes the symlink' do
migration.up
expect(File.symlink?(symlink_path)).to be(false)
end
end
describe '#down' do
before do
FileUtils.mkdir_p(File.join(original_path))
FileUtils.touch(File.join(original_path, 'dummy.file'))
end
it 'creates a symlink' do
expected_path = File.join(symlink_path, "dummy.file")
migration.down
expect(File.exist?(expected_path)).to be(true)
expect(File.symlink?(symlink_path)).to be(true)
end
end
end
require "spec_helper"
require Rails.root.join("db", "migrate", "20170316163845_move_uploads_to_system_dir.rb")
describe MoveUploadsToSystemDir do
let(:migration) { described_class.new }
let(:test_dir) { File.join(Rails.root, "tmp", "move_uploads_test") }
let(:uploads_dir) { File.join(test_dir, "public", "uploads") }
let(:new_uploads_dir) { File.join(uploads_dir, "system") }
before do
FileUtils.remove_dir(test_dir) if File.directory?(test_dir)
FileUtils.mkdir_p(uploads_dir)
allow(migration).to receive(:base_directory).and_return(test_dir)
allow(migration).to receive(:say)
end
describe "#up" do
before do
FileUtils.mkdir_p(File.join(uploads_dir, 'user'))
FileUtils.touch(File.join(uploads_dir, 'user', 'dummy.file'))
end
it 'moves the directory to the new path' do
expected_path = File.join(new_uploads_dir, 'user', 'dummy.file')
migration.up
expect(File.exist?(expected_path)).to be(true)
end
it 'creates a symlink in the old location' do
symlink_path = File.join(uploads_dir, 'user')
expected_path = File.join(symlink_path, 'dummy.file')
migration.up
expect(File.exist?(expected_path)).to be(true)
expect(File.symlink?(symlink_path)).to be(true)
end
end
describe "#down" do
before do
FileUtils.mkdir_p(File.join(new_uploads_dir, 'user'))
FileUtils.touch(File.join(new_uploads_dir, 'user', 'dummy.file'))
end
it 'moves the directory to the old path' do
expected_path = File.join(uploads_dir, 'user', 'dummy.file')
migration.down
expect(File.exist?(expected_path)).to be(true)
end
it 'removes the symlink if it existed' do
FileUtils.ln_s(File.join(new_uploads_dir, 'user'), File.join(uploads_dir, 'user'))
directory = File.join(uploads_dir, 'user')
expected_path = File.join(directory, 'dummy.file')
migration.down
expect(File.exist?(expected_path)).to be(true)
expect(File.symlink?(directory)).to be(false)
end
end
end
require "spec_helper"
require Rails.root.join("db", "migrate", "20170316163800_rename_system_namespaces.rb")
describe RenameSystemNamespaces, truncate: true do
let(:migration) { described_class.new }
let(:test_dir) { File.join(Rails.root, "tmp", "tests", "rename_namespaces_test") }
let(:uploads_dir) { File.join(test_dir, "public", "uploads") }
let(:system_namespace) do
namespace = build(:namespace, path: "system")
namespace.save(validate: false)
namespace
end
def save_invalid_routable(routable)
routable.__send__(:prepare_route)
routable.save(validate: false)
end
before do
FileUtils.remove_dir(test_dir) if File.directory?(test_dir)
FileUtils.mkdir_p(uploads_dir)
FileUtils.remove_dir(TestEnv.repos_path) if File.directory?(TestEnv.repos_path)
allow(migration).to receive(:say)
allow(migration).to receive(:uploads_dir).and_return(uploads_dir)
end
describe "#system_namespace" do
it "only root namespaces called with path `system`" do
system_namespace
system_namespace_with_parent = build(:namespace, path: 'system', parent: create(:namespace))
system_namespace_with_parent.save(validate: false)
expect(migration.system_namespace.id).to eq(system_namespace.id)
end
end
describe "#up" do
before do
system_namespace
end
it "doesn't break if there are no namespaces called system" do
Namespace.delete_all
migration.up
end
it "renames namespaces called system" do
migration.up
expect(system_namespace.reload.path).to eq("system0")
end
it "renames the route to the namespace" do
migration.up
expect(system_namespace.reload.full_path).to eq("system0")
end
it "renames the route for projects of the namespace" do
project = build(:project, path: "project-path", namespace: system_namespace)
save_invalid_routable(project)
migration.up
expect(project.route.reload.path).to eq("system0/project-path")
end
it "doesn't touch routes of namespaces that look like system" do
namespace = create(:group, path: 'systemlookalike')
project = create(:project, namespace: namespace, path: 'the-project')
migration.up
expect(project.route.reload.path).to eq('systemlookalike/the-project')
expect(namespace.route.reload.path).to eq('systemlookalike')
end
it "moves the the repository for a project in the namespace" do
project = build(:project, namespace: system_namespace, path: "system-project")
save_invalid_routable(project)
TestEnv.copy_repo(project,
bare_repo: TestEnv.factory_repo_path_bare,
refs: TestEnv::BRANCH_SHA)
expected_repo = File.join(TestEnv.repos_path, "system0", "system-project.git")
migration.up
expect(File.directory?(expected_repo)).to be(true)
end
it "moves the uploads for the namespace" do
allow(migration).to receive(:move_namespace_folders).with(Settings.pages.path, "system", "system0")
expect(migration).to receive(:move_namespace_folders).with(uploads_dir, "system", "system0")
migration.up
end
it "moves the pages for the namespace" do
allow(migration).to receive(:move_namespace_folders).with(uploads_dir, "system", "system0")
expect(migration).to receive(:move_namespace_folders).with(Settings.pages.path, "system", "system0")
migration.up
end
describe "clears the markdown cache for projects in the system namespace" do
let!(:project) do
project = build(:project, namespace: system_namespace)
save_invalid_routable(project)
project
end
it 'removes description_html from projects' do
migration.up
expect(project.reload.description_html).to be_nil
end
it 'removes issue descriptions' do
issue = create(:issue, project: project, description_html: 'Issue description')
migration.up
expect(issue.reload.description_html).to be_nil
end
it 'removes merge request descriptions' do
merge_request = create(:merge_request,
source_project: project,
target_project: project,
description_html: 'MergeRequest description')
migration.up
expect(merge_request.reload.description_html).to be_nil
end
it 'removes note html' do
note = create(:note,
project: project,
noteable: create(:issue, project: project),
note_html: 'note description')
migration.up
expect(note.reload.note_html).to be_nil
end
it 'removes milestone description' do
milestone = create(:milestone,
project: project,
description_html: 'milestone description')
migration.up
expect(milestone.reload.description_html).to be_nil
end
end
context "system namespace -> subgroup -> system0 project" do
it "updates the route of the project correctly" do
subgroup = build(:group, path: "subgroup", parent: system_namespace)
save_invalid_routable(subgroup)
project = build(:project, path: "system0", namespace: subgroup)
save_invalid_routable(project)
migration.up
expect(project.route.reload.path).to eq("system0/subgroup/system0")
end
end
end
describe "#move_repositories" do
let(:namespace) { create(:group, name: "hello-group") }
it "moves a project for a namespace" do
create(:project, namespace: namespace, path: "hello-project")
expected_path = File.join(TestEnv.repos_path, "bye-group", "hello-project.git")
migration.move_repositories(namespace, "hello-group", "bye-group")
expect(File.directory?(expected_path)).to be(true)
end
it "moves a namespace in a subdirectory correctly" do
child_namespace = create(:group, name: "sub-group", parent: namespace)
create(:project, namespace: child_namespace, path: "hello-project")
expected_path = File.join(TestEnv.repos_path, "hello-group", "renamed-sub-group", "hello-project.git")
migration.move_repositories(child_namespace, "hello-group/sub-group", "hello-group/renamed-sub-group")
expect(File.directory?(expected_path)).to be(true)
end
it "moves a parent namespace with subdirectories" do
child_namespace = create(:group, name: "sub-group", parent: namespace)
create(:project, namespace: child_namespace, path: "hello-project")
expected_path = File.join(TestEnv.repos_path, "renamed-group", "sub-group", "hello-project.git")
migration.move_repositories(child_namespace, "hello-group", "renamed-group")
expect(File.directory?(expected_path)).to be(true)
end
end
describe "#move_namespace_folders" do
it "moves a namespace with files" do
source = File.join(uploads_dir, "parent-group", "sub-group")
FileUtils.mkdir_p(source)
destination = File.join(uploads_dir, "parent-group", "moved-group")
FileUtils.touch(File.join(source, "test.txt"))
expected_file = File.join(destination, "test.txt")
migration.move_namespace_folders(uploads_dir, File.join("parent-group", "sub-group"), File.join("parent-group", "moved-group"))
expect(File.exist?(expected_file)).to be(true)
end
it "moves a parent namespace uploads" do
source = File.join(uploads_dir, "parent-group", "sub-group")
FileUtils.mkdir_p(source)
destination = File.join(uploads_dir, "moved-parent", "sub-group")
FileUtils.touch(File.join(source, "test.txt"))
expected_file = File.join(destination, "test.txt")
migration.move_namespace_folders(uploads_dir, "parent-group", "moved-parent")
expect(File.exist?(expected_file)).to be(true)
end
end
describe "#child_ids_for_parent" do
it "collects child ids for all levels" do
parent = create(:group)
first_child = create(:group, parent: parent)
second_child = create(:group, parent: parent)
third_child = create(:group, parent: second_child)
all_ids = [parent.id, first_child.id, second_child.id, third_child.id]
collected_ids = migration.child_ids_for_parent(parent, ids: [parent.id])
expect(collected_ids).to contain_exactly(*all_ids)
end
end
describe "#remove_last_ocurrence" do
it "removes only the last occurance of a string" do
input = "this/is/system/namespace/with/system"
expect(migration.remove_last_occurrence(input, "system")).to eq("this/is/system/namespace/with/")
end
end
end
require "spec_helper"
require Rails.root.join("db", "post_migrate", "20170317162059_update_upload_paths_to_system.rb")
describe UpdateUploadPathsToSystem do
let(:migration) { described_class.new }
before do
allow(migration).to receive(:say)
end
describe "#uploads_to_switch_to_new_path" do
it "contains only uploads with the old path for the correct models" do
_upload_for_other_type = create(:upload, model: create(:ci_pipeline), path: "uploads/ci_pipeline/avatar.jpg")
_upload_with_system_path = create(:upload, model: create(:empty_project), path: "uploads/system/project/avatar.jpg")
_upload_with_other_path = create(:upload, model: create(:empty_project), path: "thelongsecretforafileupload/avatar.jpg")
old_upload = create(:upload, model: create(:empty_project), path: "uploads/project/avatar.jpg")
group_upload = create(:upload, model: create(:group), path: "uploads/group/avatar.jpg")
expect(Upload.where(migration.uploads_to_switch_to_new_path)).to contain_exactly(old_upload, group_upload)
end
end
describe "#uploads_to_switch_to_old_path" do
it "contains only uploads with the new path for the correct models" do
_upload_for_other_type = create(:upload, model: create(:ci_pipeline), path: "uploads/ci_pipeline/avatar.jpg")
upload_with_system_path = create(:upload, model: create(:empty_project), path: "uploads/system/project/avatar.jpg")
_upload_with_other_path = create(:upload, model: create(:empty_project), path: "thelongsecretforafileupload/avatar.jpg")
_old_upload = create(:upload, model: create(:empty_project), path: "uploads/project/avatar.jpg")
expect(Upload.where(migration.uploads_to_switch_to_old_path)).to contain_exactly(upload_with_system_path)
end
end
describe "#up", truncate: true do
it "updates old upload records to the new path" do
old_upload = create(:upload, model: create(:empty_project), path: "uploads/project/avatar.jpg")
migration.up
expect(old_upload.reload.path).to eq("uploads/system/project/avatar.jpg")
end
end
describe "#down", truncate: true do
it "updates the new system patsh to the old paths" do
new_upload = create(:upload, model: create(:empty_project), path: "uploads/system/project/avatar.jpg")
migration.down
expect(new_upload.reload.path).to eq("uploads/project/avatar.jpg")
end
end
end
...@@ -303,6 +303,35 @@ describe ApplicationSetting, models: true do ...@@ -303,6 +303,35 @@ describe ApplicationSetting, models: true do
end end
end end
describe 'elasticsearch licensing' do
before do
setting.elasticsearch_search = true
setting.elasticsearch_indexing = true
end
def expect_is_es_licensed
expect(License).to receive(:feature_available?).with(:elastic_search).at_least(:once)
end
it 'disables elasticsearch when unlicensed' do
expect_is_es_licensed.and_return(false)
expect(setting.elasticsearch_indexing?).to be_falsy
expect(setting.elasticsearch_indexing).to be_falsy
expect(setting.elasticsearch_search?).to be_falsy
expect(setting.elasticsearch_search).to be_falsy
end
it 'enables elasticsearch when licensed' do
expect_is_es_licensed.and_return(true)
expect(setting.elasticsearch_indexing?).to be_truthy
expect(setting.elasticsearch_indexing).to be_truthy
expect(setting.elasticsearch_search?).to be_truthy
expect(setting.elasticsearch_search).to be_truthy
end
end
describe '#elasticsearch_url' do describe '#elasticsearch_url' do
it 'presents a single URL as a one-element array' do it 'presents a single URL as a one-element array' do
setting.elasticsearch_url = 'http://example.com' setting.elasticsearch_url = 'http://example.com'
......
...@@ -20,8 +20,8 @@ describe Commit, models: true do ...@@ -20,8 +20,8 @@ describe Commit, models: true do
end end
it 'caches the author' do it 'caches the author' do
allow(RequestStore).to receive(:active?).and_return(true)
user = create(:user, email: commit.author_email) user = create(:user, email: commit.author_email)
expect(RequestStore).to receive(:active?).twice.and_return(true)
expect_any_instance_of(Commit).to receive(:find_author_by_any_email).and_call_original expect_any_instance_of(Commit).to receive(:find_author_by_any_email).and_call_original
expect(commit.author).to eq(user) expect(commit.author).to eq(user)
......
...@@ -122,16 +122,7 @@ describe Group, 'Routable' do ...@@ -122,16 +122,7 @@ describe Group, 'Routable' do
it { expect(group.full_path).to eq(group.path) } it { expect(group.full_path).to eq(group.path) }
it { expect(nested_group.full_path).to eq("#{group.full_path}/#{nested_group.path}") } it { expect(nested_group.full_path).to eq("#{group.full_path}/#{nested_group.path}") }
context 'with RequestStore active' do context 'with RequestStore active', :request_store do
before do
RequestStore.begin!
end
after do
RequestStore.end!
RequestStore.clear!
end
it 'does not load the route table more than once' do it 'does not load the route table more than once' do
expect(group).to receive(:uncached_full_path).once.and_call_original expect(group).to receive(:uncached_full_path).once.and_call_original
......
...@@ -179,7 +179,7 @@ describe Group, models: true do ...@@ -179,7 +179,7 @@ describe Group, models: true do
let!(:group) { create(:group, :access_requestable, :with_avatar) } let!(:group) { create(:group, :access_requestable, :with_avatar) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:gitlab_host) { "http://#{Gitlab.config.gitlab.host}" } let(:gitlab_host) { "http://#{Gitlab.config.gitlab.host}" }
let(:avatar_path) { "/uploads/group/avatar/#{group.id}/dk.png" } let(:avatar_path) { "/uploads/system/group/avatar/#{group.id}/dk.png" }
context 'when avatar file is uploaded' do context 'when avatar file is uploaded' do
before { group.add_master(user) } before { group.add_master(user) }
......
...@@ -344,6 +344,20 @@ describe License do ...@@ -344,6 +344,20 @@ describe License do
end end
describe 'reading add-ons' do describe 'reading add-ons' do
describe '#plan' do
it 'interprets no plan as EES' do
license = build(:license, data: build(:gitlab_license, restrictions: { add_ons: {} }).export)
expect(license.plan).to eq(License::STARTER_PLAN)
end
it 'interprets an unknown plan as unknown' do
license = build_license_with_add_ons({}, plan: 'unknown')
expect(license.plan).to eq('unknown')
end
end
describe '#add_ons' do describe '#add_ons' do
context 'without add-ons' do context 'without add-ons' do
it 'returns an empty Hash' do it 'returns an empty Hash' do
......
...@@ -43,6 +43,12 @@ describe Namespace, models: true do ...@@ -43,6 +43,12 @@ describe Namespace, models: true do
end end
end end
context "is case insensitive" do
let(:group) { build(:group, path: "System") }
it { expect(group).not_to be_valid }
end
context 'top-level group' do context 'top-level group' do
let(:group) { build(:group, path: 'tree') } let(:group) { build(:group, path: 'tree') }
...@@ -178,8 +184,8 @@ describe Namespace, models: true do ...@@ -178,8 +184,8 @@ describe Namespace, models: true do
let(:parent) { create(:group, name: 'parent', path: 'parent') } let(:parent) { create(:group, name: 'parent', path: 'parent') }
let(:child) { create(:group, name: 'child', path: 'child', parent: parent) } let(:child) { create(:group, name: 'child', path: 'child', parent: parent) }
let!(:project) { create(:project_empty_repo, path: 'the-project', namespace: child) } let!(:project) { create(:project_empty_repo, path: 'the-project', namespace: child) }
let(:uploads_dir) { File.join(CarrierWave.root, 'uploads') } let(:uploads_dir) { File.join(CarrierWave.root, FileUploader.base_dir) }
let(:pages_dir) { TestEnv.pages_path } let(:pages_dir) { File.join(TestEnv.pages_path) }
before do before do
FileUtils.mkdir_p(File.join(uploads_dir, 'parent', 'child', 'the-project')) FileUtils.mkdir_p(File.join(uploads_dir, 'parent', 'child', 'the-project'))
......
...@@ -986,7 +986,7 @@ describe Project, models: true do ...@@ -986,7 +986,7 @@ describe Project, models: true do
context 'when avatar file is uploaded' do context 'when avatar file is uploaded' do
let(:project) { create(:empty_project, :with_avatar) } let(:project) { create(:empty_project, :with_avatar) }
let(:avatar_path) { "/uploads/project/avatar/#{project.id}/dk.png" } let(:avatar_path) { "/uploads/system/project/avatar/#{project.id}/dk.png" }
let(:gitlab_host) { "http://#{Gitlab.config.gitlab.host}" } let(:gitlab_host) { "http://#{Gitlab.config.gitlab.host}" }
it 'shows correct url' do it 'shows correct url' do
......
...@@ -389,16 +389,7 @@ describe ProjectTeam, models: true do ...@@ -389,16 +389,7 @@ describe ProjectTeam, models: true do
end end
describe '#max_member_access_for_user_ids' do describe '#max_member_access_for_user_ids' do
context 'with RequestStore enabled' do context 'with RequestStore enabled', :request_store do
before do
RequestStore.begin!
end
after do
RequestStore.end!
RequestStore.clear!
end
include_examples 'max member access for users' include_examples 'max member access for users'
def access_levels(users) def access_levels(users)
......
...@@ -1015,7 +1015,7 @@ describe User, models: true do ...@@ -1015,7 +1015,7 @@ describe User, models: true do
context 'when avatar file is uploaded' do context 'when avatar file is uploaded' do
let(:gitlab_host) { "http://#{Gitlab.config.gitlab.host}" } let(:gitlab_host) { "http://#{Gitlab.config.gitlab.host}" }
let(:avatar_path) { "/uploads/user/avatar/#{user.id}/dk.png" } let(:avatar_path) { "/uploads/system/user/avatar/#{user.id}/dk.png" }
it 'shows correct avatar url' do it 'shows correct avatar url' do
expect(user.avatar_url).to eq(avatar_path) expect(user.avatar_url).to eq(avatar_path)
......
...@@ -3,7 +3,7 @@ require 'spec_helper' ...@@ -3,7 +3,7 @@ require 'spec_helper'
describe ProjectSnippetPolicy, models: true do describe ProjectSnippetPolicy, models: true do
let(:regular_user) { create(:user) } let(:regular_user) { create(:user) }
let(:external_user) { create(:user, :external) } let(:external_user) { create(:user, :external) }
let(:project) { create(:empty_project) } let(:project) { create(:empty_project, :public) }
let(:author_permissions) do let(:author_permissions) do
[ [
...@@ -117,7 +117,7 @@ describe ProjectSnippetPolicy, models: true do ...@@ -117,7 +117,7 @@ describe ProjectSnippetPolicy, models: true do
end end
context 'snippet author' do context 'snippet author' do
let(:snippet) { create(:project_snippet, :private, author: regular_user) } let(:snippet) { create(:project_snippet, :private, author: regular_user, project: project) }
subject { described_class.abilities(regular_user, snippet).to_set } subject { described_class.abilities(regular_user, snippet).to_set }
......
...@@ -17,7 +17,7 @@ describe API::License, api: true do ...@@ -17,7 +17,7 @@ describe API::License, api: true do
expect(Date.parse(json_response['expires_at'])).to eq Date.today + 11.months expect(Date.parse(json_response['expires_at'])).to eq Date.today + 11.months
expect(json_response['active_users']).to eq 1 expect(json_response['active_users']).to eq 1
expect(json_response['licensee']).not_to be_empty expect(json_response['licensee']).not_to be_empty
expect(json_response['add_ons']).to eq({ 'GitLab_FileLocks' => 1, 'GitLab_Auditor_User' => 1 }) expect(json_response['add_ons']).to eq(license.add_ons)
end end
it 'denies access if not admin' do it 'denies access if not admin' do
......
...@@ -79,7 +79,7 @@ describe 'OpenID Connect requests' do ...@@ -79,7 +79,7 @@ describe 'OpenID Connect requests' do
'email_verified' => true, 'email_verified' => true,
'website' => 'https://example.com', 'website' => 'https://example.com',
'profile' => 'http://localhost/alice', 'profile' => 'http://localhost/alice',
'picture' => "http://localhost/uploads/user/avatar/#{user.id}/dk.png" 'picture' => "http://localhost/uploads/system/user/avatar/#{user.id}/dk.png"
}) })
end end
end end
......
...@@ -102,18 +102,11 @@ describe PipelineSerializer do ...@@ -102,18 +102,11 @@ describe PipelineSerializer do
Ci::Pipeline::AVAILABLE_STATUSES.each do |status| Ci::Pipeline::AVAILABLE_STATUSES.each do |status|
create_pipeline(status) create_pipeline(status)
end end
RequestStore.begin!
end
after do
RequestStore.end!
RequestStore.clear!
end end
it "verifies number of queries" do it 'verifies number of queries', :request_store do
recorded = ActiveRecord::QueryRecorder.new { subject } recorded = ActiveRecord::QueryRecorder.new { subject }
expect(recorded.count).to be_within(1).of(64) expect(recorded.count).to be_within(1).of(62)
expect(recorded.cached_count).to eq(0) expect(recorded.cached_count).to eq(0)
end end
......
...@@ -13,7 +13,7 @@ describe Projects::ParticipantsService, services: true do ...@@ -13,7 +13,7 @@ describe Projects::ParticipantsService, services: true do
groups = participants.groups groups = participants.groups
expect(groups.size).to eq 1 expect(groups.size).to eq 1
expect(groups.first[:avatar_url]).to eq("/uploads/group/avatar/#{group.id}/dk.png") expect(groups.first[:avatar_url]).to eq("/uploads/system/group/avatar/#{group.id}/dk.png")
end end
it 'should return an url for the avatar with relative url' do it 'should return an url for the avatar with relative url' do
...@@ -24,7 +24,7 @@ describe Projects::ParticipantsService, services: true do ...@@ -24,7 +24,7 @@ describe Projects::ParticipantsService, services: true do
groups = participants.groups groups = participants.groups
expect(groups.size).to eq 1 expect(groups.size).to eq 1
expect(groups.first[:avatar_url]).to eq("/gitlab/uploads/group/avatar/#{group.id}/dk.png") expect(groups.first[:avatar_url]).to eq("/gitlab/uploads/system/group/avatar/#{group.id}/dk.png")
end end
end end
end end
......
...@@ -80,6 +80,15 @@ RSpec.configure do |config| ...@@ -80,6 +80,15 @@ RSpec.configure do |config|
TestEnv.cleanup TestEnv.cleanup
end end
config.before(:example, :request_store) do
RequestStore.begin!
end
config.after(:example, :request_store) do
RequestStore.end!
RequestStore.clear!
end
if ENV['CI'] if ENV['CI']
# Retry only on feature specs that use JS # Retry only on feature specs that use JS
config.around :each, :js do |ex| config.around :each, :js do |ex|
......
...@@ -3,6 +3,17 @@ require 'spec_helper' ...@@ -3,6 +3,17 @@ require 'spec_helper'
describe AttachmentUploader do describe AttachmentUploader do
let(:uploader) { described_class.new(build_stubbed(:user)) } let(:uploader) { described_class.new(build_stubbed(:user)) }
describe "#store_dir" do
it "stores in the system dir" do
expect(uploader.store_dir).to start_with("uploads/system/user")
end
it "uses the old path when using object storage" do
expect(described_class).to receive(:file_storage?).and_return(false)
expect(uploader.store_dir).to start_with("uploads/user")
end
end
describe '#move_to_cache' do describe '#move_to_cache' do
it 'is true' do it 'is true' do
expect(uploader.move_to_cache).to eq(true) expect(uploader.move_to_cache).to eq(true)
......
...@@ -3,6 +3,17 @@ require 'spec_helper' ...@@ -3,6 +3,17 @@ require 'spec_helper'
describe AvatarUploader do describe AvatarUploader do
let(:uploader) { described_class.new(build_stubbed(:user)) } let(:uploader) { described_class.new(build_stubbed(:user)) }
describe "#store_dir" do
it "stores in the system dir" do
expect(uploader.store_dir).to start_with("uploads/system/user")
end
it "uses the old path when using object storage" do
expect(described_class).to receive(:file_storage?).and_return(false)
expect(uploader.store_dir).to start_with("uploads/user")
end
end
describe '#move_to_cache' do describe '#move_to_cache' do
it 'is false' do it 'is false' do
expect(uploader.move_to_cache).to eq(false) expect(uploader.move_to_cache).to eq(false)
......
...@@ -15,6 +15,16 @@ describe FileUploader do ...@@ -15,6 +15,16 @@ describe FileUploader do
end end
end end
describe "#store_dir" do
it "stores in the namespace path" do
project = build_stubbed(:empty_project)
uploader = described_class.new(project)
expect(uploader.store_dir).to include(project.path_with_namespace)
expect(uploader.store_dir).not_to include("system")
end
end
describe 'initialize' do describe 'initialize' do
it 'generates a secret if none is provided' do it 'generates a secret if none is provided' do
expect(SecureRandom).to receive(:hex).and_return('secret') expect(SecureRandom).to receive(:hex).and_return('secret')
......
(function($) {
var fetchRequestResults, getRequestId, peekEnabled, toggleBar, updatePerformanceBar;
getRequestId = function() {
return $('#peek').data('request-id');
};
peekEnabled = function() {
return $('#peek').length;
};
updatePerformanceBar = function(results) {
var key, label, data, table, html, tr, duration_td, sql_td, strong;
Object.keys(results.data).forEach(function(key) {
Object.keys(results.data[key]).forEach(function(label) {
data = results.data[key][label];
if (label == 'queries') {
table = document.createElement('table');
for (var i = 0; i < data.length; i += 1) {
tr = document.createElement('tr');
duration_td = document.createElement('td');
sql_td = document.createElement('td');
strong = document.createElement('strong');
strong.append(data[i]['duration'] + 'ms');
duration_td.appendChild(strong);
tr.appendChild(duration_td);
sql_td.appendChild(document.createTextNode(data[i]['sql']));
tr.appendChild(sql_td);
table.appendChild(tr);
}
table.className = 'table';
$("[data-defer-to=" + key + "-" + label + "]").html(table);
} else {
$("[data-defer-to=" + key + "-" + label + "]").text(results.data[key][label]);
}
});
});
return $(document).trigger('peek:render', [getRequestId(), results]);
};
toggleBar = function(event) {
var wrapper;
if ($(event.target).is(':input')) {
return;
}
if (event.which === 96 && !event.metaKey) {
wrapper = $('#peek');
if (wrapper.hasClass('disabled')) {
wrapper.removeClass('disabled');
return document.cookie = "peek=true; path=/";
} else {
wrapper.addClass('disabled');
return document.cookie = "peek=false; path=/";
}
}
};
fetchRequestResults = function() {
return $.ajax('/-/peek/results', {
data: {
request_id: getRequestId()
},
success: function(data, textStatus, xhr) {
return updatePerformanceBar(data);
},
error: function(xhr, textStatus, error) {}
});
};
$(document).on('keypress', toggleBar);
$(document).on('peek:update', fetchRequestResults);
return $(function() {
if (peekEnabled()) {
return $(this).trigger('peek:update');
}
});
})(jQuery);
var PerformanceBar, ajaxStart, renderPerformanceBar, updateStatus;
PerformanceBar = (function() {
PerformanceBar.prototype.appInfo = null;
PerformanceBar.prototype.width = null;
PerformanceBar.formatTime = function(value) {
if (value >= 1000) {
return ((value / 1000).toFixed(3)) + "s";
} else {
return (value.toFixed(0)) + "ms";
}
};
function PerformanceBar(options) {
var k, v;
if (options == null) {
options = {};
}
this.el = $('#peek-view-performance-bar .performance-bar');
for (k in options) {
v = options[k];
this[k] = v;
}
if (this.width == null) {
this.width = this.el.width();
}
if (this.timing == null) {
this.timing = window.performance.timing;
}
}
PerformanceBar.prototype.render = function(serverTime) {
var networkTime, perfNetworkTime;
if (serverTime == null) {
serverTime = 0;
}
this.el.empty();
this.addBar('frontend', '#90d35b', 'domLoading', 'domInteractive');
perfNetworkTime = this.timing.responseEnd - this.timing.requestStart;
if (serverTime && serverTime <= perfNetworkTime) {
networkTime = perfNetworkTime - serverTime;
this.addBar('latency / receiving', '#f1faff', this.timing.requestStart + serverTime, this.timing.requestStart + serverTime + networkTime);
this.addBar('app', '#90afcf', this.timing.requestStart, this.timing.requestStart + serverTime, this.appInfo);
} else {
this.addBar('backend', '#c1d7ee', 'requestStart', 'responseEnd');
}
this.addBar('tcp / ssl', '#45688e', 'connectStart', 'connectEnd');
this.addBar('redirect', '#0c365e', 'redirectStart', 'redirectEnd');
this.addBar('dns', '#082541', 'domainLookupStart', 'domainLookupEnd');
return this.el;
};
PerformanceBar.prototype.isLoaded = function() {
return this.timing.domInteractive;
};
PerformanceBar.prototype.start = function() {
return this.timing.navigationStart;
};
PerformanceBar.prototype.end = function() {
return this.timing.domInteractive;
};
PerformanceBar.prototype.total = function() {
return this.end() - this.start();
};
PerformanceBar.prototype.addBar = function(name, color, start, end, info) {
var bar, left, offset, time, title, width;
if (typeof start === 'string') {
start = this.timing[start];
}
if (typeof end === 'string') {
end = this.timing[end];
}
if (!((start != null) && (end != null))) {
return;
}
time = end - start;
offset = start - this.start();
left = this.mapH(offset);
width = this.mapH(time);
title = name + ": " + (PerformanceBar.formatTime(time));
bar = $('<li></li>', {
'data-title': title,
'data-toggle': 'tooltip',
'data-container': 'body'
});
bar.css({
width: width + "px",
left: left + "px",
background: color
});
return this.el.append(bar);
};
PerformanceBar.prototype.mapH = function(offset) {
return offset * (this.width / this.total());
};
return PerformanceBar;
})();
renderPerformanceBar = function() {
var bar, resp, span, time;
resp = $('#peek-server_response_time');
time = Math.round(resp.data('time') * 1000);
bar = new PerformanceBar;
bar.render(time);
span = $('<span>', {
'data-toggle': 'tooltip',
'data-title': 'Total navigation time for this page.',
'data-container': 'body'
}).text(PerformanceBar.formatTime(bar.total()));
return updateStatus(span);
};
updateStatus = function(html) {
return $('#serverstats').html(html);
};
ajaxStart = null;
$(document).on('pjax:start page:fetch turbolinks:request-start', function(event) {
return ajaxStart = event.timeStamp;
});
$(document).on('pjax:end page:load turbolinks:load', function(event, xhr) {
var ajaxEnd, serverTime, total;
if (ajaxStart == null) {
return;
}
ajaxEnd = event.timeStamp;
total = ajaxEnd - ajaxStart;
serverTime = xhr ? parseInt(xhr.getResponseHeader('X-Runtime')) : 0;
return setTimeout(function() {
var bar, now, span, tech;
now = new Date().getTime();
bar = new PerformanceBar({
timing: {
requestStart: ajaxStart,
responseEnd: ajaxEnd,
domLoading: ajaxEnd,
domInteractive: now
},
isLoaded: function() {
return true;
},
start: function() {
return ajaxStart;
},
end: function() {
return now;
}
});
bar.render(serverTime);
if ($.fn.pjax != null) {
tech = 'PJAX';
} else {
tech = 'Turbolinks';
}
span = $('<span>', {
'data-toggle': 'tooltip',
'data-title': tech + " navigation time",
'data-container': 'body'
}).text(PerformanceBar.formatTime(total));
updateStatus(span);
return ajaxStart = null;
}, 0);
});
$(function() {
if (window.performance) {
return renderPerformanceBar();
} else {
return $('#peek-view-performance-bar').remove();
}
});
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment