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'
gem 'gettext_i18n_rails_js', '~> 1.2.0'
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
group :metrics do
gem 'allocations', '~> 1.0', require: false, platform: :mri
......
......@@ -56,6 +56,7 @@ GEM
asciidoctor-plantuml (0.0.7)
asciidoctor (~> 1.5)
ast (2.3.0)
atomic (1.1.99)
attr_encrypted (3.0.3)
encryptor (~> 3.0.0)
attr_required (1.0.0)
......@@ -137,6 +138,8 @@ GEM
coffee-script-source (1.10.0)
colorize (0.7.7)
concurrent-ruby (1.0.5)
concurrent-ruby-ext (1.0.5)
concurrent-ruby (= 1.0.5)
connection_pool (2.2.1)
crack (0.4.3)
safe_yaml (~> 1.0.0)
......@@ -573,6 +576,36 @@ GEM
parser (2.4.0.0)
ast (~> 2.2)
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)
po_to_json (1.0.1)
json (>= 1.6.0)
......@@ -1031,6 +1064,15 @@ DEPENDENCIES
omniauth_crowd (~> 2.2.0)
org-ruby (~> 0.9.12)
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)
poltergeist (~> 1.9.0)
premailer-rails (~> 1.9.0)
......
......@@ -1478,7 +1478,7 @@ const normalizeNewlines = function(str) {
const cachedNoteBodyText = $noteBodyText.html();
// 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.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) {
})
.fail(() => {
// Submission failed, revert back to original note
$noteBodyText.html(cachedNoteBodyText);
$noteBodyText.html(_.escape(cachedNoteBodyText));
$editingNote.removeClass('being-posted fade-in');
$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 */
/* global Mousetrap */
/* global findFileURL */
import Cookies from 'js-cookie';
import findAndFollowLink from './shortcuts_dashboard_navigation';
(function() {
......@@ -14,6 +16,7 @@ import findAndFollowLink from './shortcuts_dashboard_navigation';
Mousetrap.bind('?', this.onToggleHelp);
Mousetrap.bind('s', Shortcuts.focusSearch);
Mousetrap.bind('f', (e => this.focusFilter(e)));
Mousetrap.bind('p b', this.onTogglePerfBar);
const $globalDropdownMenu = $('.global-dropdown-menu');
const $globalDropdownToggle = $('.global-dropdown-toggle');
......@@ -53,6 +56,17 @@ import findAndFollowLink from './shortcuts_dashboard_navigation';
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) {
// Check if short-cut was triggered while in Write Mode
const $target = $(e.target);
......
......@@ -9,6 +9,7 @@ class ApplicationController < ActionController::Base
include SentryHelper
include WorkhorseHelper
include EnforcesTwoFactorAuthentication
include Peek::Rblineprof::CustomControllerHelpers
before_action :authenticate_user_from_private_token!
before_action :authenticate_user_from_rss_token!
......@@ -18,7 +19,7 @@ class ApplicationController < ActionController::Base
before_action :ldap_security_check
before_action :sentry_context
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 :require_email, unless: :devise_controller?
......@@ -63,6 +64,21 @@ class ApplicationController < ActionController::Base
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
# This filter handles both private tokens and personal access tokens
......
......@@ -21,7 +21,7 @@ class AutocompleteController < ApplicationController
@users = [current_user, *@users].uniq
end
if params[:author_id].present?
if params[:author_id].present? && current_user
author = User.find_by_id(params[:author_id])
@users = [author, *@users].uniq if author
end
......
......@@ -27,6 +27,7 @@ module NavHelper
def nav_header_class
class_name = ''
class_name << " with-horizontal-nav" if defined?(nav) && nav
class_name << " with-peek" if peek_enabled?
class_name
end
......
......@@ -276,6 +276,16 @@ class ApplicationSetting < ActiveRecord::Base
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
read_attribute(:elasticsearch_url).split(',').map(&:strip)
end
......
......@@ -7,12 +7,14 @@ class License < ActiveRecord::Base
AUDITOR_USER_FEATURE = 'GitLab_Auditor_User'.freeze
SERVICE_DESK_FEATURE = 'GitLab_ServiceDesk'.freeze
OBJECT_STORAGE_FEATURE = 'GitLab_ObjectStorage'.freeze
ELASTIC_SEARCH_FEATURE = 'GitLab_ElasticSearch'.freeze
FEATURE_CODES = {
geo: GEO_FEATURE,
auditor_user: AUDITOR_USER_FEATURE,
service_desk: SERVICE_DESK_FEATURE,
object_storage: OBJECT_STORAGE_FEATURE,
elastic_search: ELASTIC_SEARCH_FEATURE,
# Features that make sense to Namespace:
deploy_board: DEPLOY_BOARD_FEATURE,
file_lock: FILE_LOCK_FEATURE
......@@ -24,7 +26,7 @@ class License < ActiveRecord::Base
EARLY_ADOPTER_PLAN = 'early_adopter'.freeze
EES_FEATURES = [
# ..
{ ELASTIC_SEARCH_FEATURE => 1 }
].freeze
EEP_FEATURES = [
......@@ -91,6 +93,8 @@ class License < ActiveRecord::Base
end
end
delegate :feature_available?, to: :current, allow_nil: true
def reset_current
RequestStore.delete(:current_license)
end
......@@ -183,7 +187,7 @@ class License < ActiveRecord::Base
end
def plan
restricted_attr(:plan)
restricted_attr(:plan, STARTER_PLAN)
end
def current_active_users_count
......
......@@ -170,7 +170,7 @@ class Project < ActiveRecord::Base
has_many :audit_events, as: :entity, dependent: :destroy
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 :statistics, class_name: 'ProjectStatistics', dependent: :delete
has_many :container_repositories, dependent: :destroy
......@@ -346,10 +346,6 @@ class Project < ActiveRecord::Base
transition [:scheduled, :started] => :failed
end
event :import_retry do
transition failed: :started
end
state :scheduled
state :started
state :finished
......@@ -410,8 +406,6 @@ class Project < ActiveRecord::Base
after_transition [:finished, :failed] => [:scheduled, :started] do |project, _|
Gitlab::Mirror.increment_capacity(project.id) if project.mirror?
end
after_transition started: :finished, do: :reset_cache_and_import_attrs
end
class << self
......
class ProjectSnippetPolicy < BasePolicy
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?
return unless @user
......
......@@ -13,6 +13,13 @@ class FileUploader < GitlabUploader
)
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
# path
#
......
......@@ -3,16 +3,28 @@ class GitlabUploader < CarrierWave::Uploader::Base
File.join(CarrierWave.root, upload_record.path)
end
def self.base_dir
def self.root_dir
'uploads'
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?
storage.is_a?(CarrierWave::Storage::File)
def self.file_storage?
self.storage == CarrierWave::Storage::File
end
delegate :base_dir, :file_storage?, to: :class
def file_cache_storage?
cache_storage.is_a?(CarrierWave::Storage::File)
end
......
......@@ -112,6 +112,10 @@ class ObjectStoreUploader < GitlabUploader
raise 'Object Storage feature is missing' unless subject.project.feature_available?(:object_storage)
end
def file_storage?
storage.is_a?(CarrierWave::Storage::File)
end
private
def set_default_local_store(new_file)
......
%fieldset
%legend Elasticsearch
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :elasticsearch_indexing do
= f.check_box :elasticsearch_indexing
Elasticsearch indexing
- if License.feature_available?(:elastic_search)
%fieldset
%legend Elasticsearch
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :elasticsearch_indexing do
= f.check_box :elasticsearch_indexing
Elasticsearch indexing
- missing = !Gitlab::Elastic::Indexer.experimental_indexer_present?
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :elasticsearch_experimental_indexer do
= f.check_box :elasticsearch_experimental_indexer, disabled: missing
Use <a href="https://gitlab.com/gitlab-org/gitlab-elasticsearch-indexer">experimental repository indexer</a>
- if missing
(not installed)
- missing = !Gitlab::Elastic::Indexer.experimental_indexer_present?
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :elasticsearch_experimental_indexer do
= f.check_box :elasticsearch_experimental_indexer, disabled: missing
Use <a href="https://gitlab.com/gitlab-org/gitlab-elasticsearch-indexer">experimental repository indexer</a>
- if missing
(not installed)
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :elasticsearch_search do
= f.check_box :elasticsearch_search
Search with Elasticsearch enabled
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :elasticsearch_search do
= f.check_box :elasticsearch_search
Search with Elasticsearch enabled
.form-group
= f.label :elasticsearch_url, 'URL', class: 'control-label col-sm-2'
.col-sm-10
= f.text_field :elasticsearch_url, value: @application_setting.elasticsearch_url.join(', '), class: 'form-control', placeholder: 'http://localhost:9200'
.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").
.form-group
= f.label :elasticsearch_url, 'URL', class: 'control-label col-sm-2'
.col-sm-10
= f.text_field :elasticsearch_url, value: @application_setting.elasticsearch_url.join(', '), class: 'form-control', placeholder: 'http://localhost:9200'
.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").
%fieldset
%legend Elasticsearch AWS IAM credentials
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :elasticsearch_aws do
= f.check_box :elasticsearch_aws
Using AWS hosted Elasticsearch with IAM credentials
.form-group
= f.label :elasticsearch_aws_region, 'AWS region', class: 'control-label col-sm-2'
.col-sm-10
= f.text_field :elasticsearch_aws_region, value: @application_setting.elasticsearch_aws_region, class: 'form-control'
.help-block
Region that elasticsearch is configured
%fieldset
%legend Elasticsearch AWS IAM credentials
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :elasticsearch_aws do
= f.check_box :elasticsearch_aws
Using AWS hosted Elasticsearch with IAM credentials
.form-group
= f.label :elasticsearch_aws_region, 'AWS region', class: 'control-label col-sm-2'
.col-sm-10
= f.text_field :elasticsearch_aws_region, value: @application_setting.elasticsearch_aws_region, class: 'form-control'
.help-block
Region that elasticsearch is configured
.form-group
= f.label :elasticsearch_aws_access_key, 'AWS Access Key', class: 'control-label col-sm-2'
.col-sm-10
= f.text_field :elasticsearch_aws_access_key, value: @application_setting.elasticsearch_aws_access_key, class: 'form-control'
.help-block
AWS Access Key. Only required if not using role instance credentials
.form-group
= f.label :elasticsearch_aws_access_key, 'AWS Access Key', class: 'control-label col-sm-2'
.col-sm-10
= f.text_field :elasticsearch_aws_access_key, value: @application_setting.elasticsearch_aws_access_key, class: 'form-control'
.help-block
AWS Access Key. Only required if not using role instance credentials
.form-group
= f.label :elasticsearch_aws_secret_access_key, 'AWS Secret Access Key', class: 'control-label col-sm-2'
.col-sm-10
= f.password_field :elasticsearch_aws_secret_access_key, value: @application_setting.elasticsearch_aws_secret_access_key, class: 'form-control'
.help-block
AWS Secret Access Key. Only required if not using role instance credentials
.form-group
= f.label :elasticsearch_aws_secret_access_key, 'AWS Secret Access Key', class: 'control-label col-sm-2'
.col-sm-10
= f.password_field :elasticsearch_aws_secret_access_key, value: @application_setting.elasticsearch_aws_secret_access_key, class: 'form-control'
.help-block
AWS Secret Access Key. Only required if not using role instance credentials
......@@ -27,6 +27,10 @@
%td.shortcut
.key f
%td Focus Filter
%tr
%td.shortcut
.key p b
%td Show/hide the Performance Bar
%tr
%td.shortcut
.key ?
......
......@@ -28,6 +28,7 @@
= stylesheet_link_tag "application", media: "all"
= stylesheet_link_tag "print", media: "print"
= stylesheet_link_tag "test", media: "all" if Rails.env.test?
= stylesheet_link_tag 'peek' if peek_enabled?
= Gon::Base.render_data
......@@ -37,6 +38,7 @@
= webpack_bundle_tag "main"
= webpack_bundle_tag "raven" if current_application_settings.clientside_sentry_enabled
= webpack_bundle_tag "test" if Rails.env.test?
= webpack_bundle_tag 'peek' if peek_enabled?
- if content_for?(:page_specific_javascripts)
= yield :page_specific_javascripts
......
......@@ -3,6 +3,7 @@
= render "layouts/head"
%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 'peek/bar'
= render "layouts/header/default", title: header_title
= 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
config.assets.precompile << "katex.css"
config.assets.precompile << "katex.js"
config.assets.precompile << "xterm/xterm.css"
config.assets.precompile << "peek.css"
config.assets.precompile << "lib/ace.js"
config.assets.precompile << "vendor/assets/fonts/*"
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
get 'liveness' => 'health#liveness'
get 'readiness' => 'health#readiness'
resources :metrics, only: [:index]
mount Peek::Railtie => '/peek'
end
# Koding route
......
scope path: :uploads do
# Note attachments and User/Group/Project avatars
get ":model/:mounted_as/:id/:filename",
get "system/:model/:mounted_as/:id/:filename",
to: "uploads#show",
constraints: { model: /note|user|group|project/, mounted_as: /avatar|attachment/, filename: /[^\/]+/ }
......@@ -15,7 +15,7 @@ scope path: :uploads do
constraints: { filename: /[^\/]+/ }
# Appearance
get ":model/:mounted_as/:id/:filename",
get "system/:model/:mounted_as/:id/:filename",
to: "uploads#show",
constraints: { model: /appearance/, mounted_as: /logo|header_logo/, filename: /.+/ }
......
......@@ -70,6 +70,7 @@ var config = {
raven: './raven/index.js',
vue_merge_request_widget: './vue_merge_request_widget/index.js',
test: './test.js',
peek: './peek.js',
},
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 + ?'
| Keyboard Shortcut | Description |
| ----------------- | ----------- |
| <kbd>n</kbd> | Main navigation |
| <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> + <kbd>shift</kbd> + <kbd>p</kbd> | Toggle markdown preview |
| <kbd></kbd> | Edit last comment (when focused on an empty textarea) |
......
......@@ -104,7 +104,7 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
step 'I should see new group "Owned" avatar' do
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
step 'I should see the "Remove avatar" button' do
......
......@@ -36,7 +36,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
step 'I should see new avatar' do
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
step 'I should see the "Remove avatar" button' do
......
......@@ -39,7 +39,7 @@ class Spinach::Features::Project < Spinach::FeatureSteps
step 'I should see new project avatar' do
expect(@project.avatar).to be_instance_of AvatarUploader
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
step 'I should see the "Remove avatar" button' do
......
......@@ -49,6 +49,7 @@ module API
end
before { allow_access_with_scope :api }
before { header['X-Frame-Options'] = 'SAMEORIGIN' }
before { Gitlab::I18n.locale = current_user&.preferred_language }
after { Gitlab::I18n.use_default_locale }
......
......@@ -62,7 +62,7 @@ module Banzai
nodes.select do |node|
if node.has_attribute?(project_attr)
can_read_reference?(user, projects[node])
can_read_reference?(user, projects[node], node)
else
true
end
......@@ -231,7 +231,7 @@ module Banzai
# see reference comments.
# Override this method on subclasses
# to check if user can read resource
def can_read_reference?(user, ref_project)
def can_read_reference?(user, ref_project, node)
raise NotImplementedError
end
......
......@@ -32,7 +32,7 @@ module Banzai
private
def can_read_reference?(user, ref_project)
def can_read_reference?(user, ref_project, node)
can?(user, :download_code, ref_project)
end
end
......
......@@ -36,7 +36,7 @@ module Banzai
private
def can_read_reference?(user, ref_project)
def can_read_reference?(user, ref_project, node)
can?(user, :download_code, ref_project)
end
end
......
......@@ -23,7 +23,7 @@ module Banzai
private
def can_read_reference?(user, ref_project)
def can_read_reference?(user, ref_project, node)
can?(user, :read_issue, ref_project)
end
end
......
......@@ -9,7 +9,7 @@ module Banzai
private
def can_read_reference?(user, ref_project)
def can_read_reference?(user, ref_project, node)
can?(user, :read_label, ref_project)
end
end
......
......@@ -40,6 +40,10 @@ module Banzai
self.class.data_attribute
)
end
def can_read_reference?(user, ref_project, node)
can?(user, :read_merge_request, ref_project)
end
end
end
end
......@@ -9,7 +9,7 @@ module Banzai
private
def can_read_reference?(user, ref_project)
def can_read_reference?(user, ref_project, node)
can?(user, :read_milestone, ref_project)
end
end
......
......@@ -9,8 +9,8 @@ module Banzai
private
def can_read_reference?(user, ref_project)
can?(user, :read_project_snippet, ref_project)
def can_read_reference?(user, ref_project, node)
can?(user, :read_project_snippet, referenced_by([node]).first)
end
end
end
......
......@@ -103,7 +103,7 @@ module Banzai
flat_map { |p| p.team.members.to_a }
end
def can_read_reference?(user, ref_project)
def can_read_reference?(user, ref_project, node)
can?(user, :read_project, ref_project)
end
end
......
......@@ -49,6 +49,7 @@ module Gitlab
sent_notifications
services
snippets
system
teams
u
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
class UploadsTransfer < ProjectTransfer
def root_dir
File.join(CarrierWave.root, GitlabUploader.base_dir)
File.join(CarrierWave.root, FileUploader.base_dir)
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 @@
require ::File.expand_path('../lib/gitlab/popen', __dir__)
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 flay],
%w[bundle exec rake haml_lint],
......
......@@ -210,22 +210,32 @@ describe AutocompleteController do
end
context 'author of issuable included' do
before do
sign_in(user)
end
let(:body) { JSON.parse(response.body) }
it 'includes the author' do
get(:users, author_id: non_member.id)
context 'authenticated' do
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
it 'rejects non existent user ids' do
get(:users, author_id: 99999)
context 'without authenticating' do
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
......
......@@ -69,16 +69,9 @@ describe Projects::JobsController do
Ci::Build::AVAILABLE_STATUSES.each do |status|
create_build(status, status)
end
RequestStore.begin!
end
after do
RequestStore.end!
RequestStore.clear!
end
it "verifies number of queries" do
it 'verifies number of queries', :request_store do
recorded = ActiveRecord::QueryRecorder.new { get_index }
expect(recorded.count).to be_within(5).of(8)
end
......
......@@ -282,10 +282,8 @@ describe Projects::MergeRequestsController do
end
end
context 'number of queries' do
context 'number of queries', :request_store do
it 'verifies number of queries' do
RequestStore.begin!
# pre-create objects
merge_request
......@@ -293,9 +291,6 @@ describe Projects::MergeRequestsController do
expect(recorded.count).to be_within(5).of(30)
expect(recorded.cached_count).to eq(0)
RequestStore.end!
RequestStore.clear!
end
end
end
......
......@@ -49,21 +49,14 @@ describe Projects::PipelinesController do
expect(json_response['details']).to have_key 'stages'
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
RequestStore.begin!
create_build('build', 0, 'build')
create_build('test', 1, 'rspec 0')
create_build('deploy', 2, 'production')
create_build('post deploy', 3, 'pages 0')
end
after do
RequestStore.end!
RequestStore.clear!
end
let(:project) { create(:project) }
let(:pipeline) do
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
end
def logo_selector
'//img[@src^="/uploads/appearance/logo"]'
'//img[@src^="/uploads/system/appearance/logo"]'
end
def header_logo_selector
'//img[@src^="/uploads/appearance/header_logo"]'
'//img[@src^="/uploads/system/appearance/header_logo"]'
end
def logo_fixture
......
......@@ -18,7 +18,7 @@ feature 'User uploads avatar to group', feature: true do
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
expect(group.reload.avatar.file).to exist
......
......@@ -16,7 +16,7 @@ feature 'User uploads avatar to profile', feature: true do
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
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'
describe ApplicationHelper do
......@@ -58,13 +59,13 @@ describe ApplicationHelper do
describe 'project_icon' do
it 'returns an url for the avatar' do
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).
to eq "<img src=\"#{avatar_url}\" alt=\"Banana sample\" />"
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).
to eq "<img src=\"#{avatar_url}\" alt=\"Banana sample\" />"
......@@ -84,12 +85,12 @@ describe ApplicationHelper do
it 'returns an url for the avatar' do
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)
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)
end
......@@ -102,7 +103,7 @@ describe ApplicationHelper do
user = create(:user, avatar: File.open(uploaded_image_temp_path))
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
it 'calls gravatar_icon when no User exists with the given email' do
......@@ -116,7 +117,7 @@ describe ApplicationHelper do
user = create(:user, avatar: File.open(uploaded_image_temp_path))
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
......
......@@ -52,7 +52,7 @@ describe EmailsHelper do
)
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
......
......@@ -10,7 +10,7 @@ describe GroupsHelper do
group.avatar = fixture_file_upload(avatar_file_path)
group.save!
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
it 'gives default avatar_icon when no avatar is present' do
......@@ -29,7 +29,7 @@ describe GroupsHelper 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.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
it 'gives default avatar_icon when no avatar is present' do
......
......@@ -60,7 +60,7 @@ describe PageLayoutHelper do
%w(project user group).each do |type|
context "with @#{type} assigned" 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)
expect(helper.page_image).to eq object.avatar_url
......
......@@ -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', () => {
let $form;
let sampleComment;
......
......@@ -22,7 +22,7 @@ describe('Commit component', () => {
shortSha: 'b7836edd',
title: 'Commit message',
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',
path: '/jschatz1',
username: 'jschatz1',
......@@ -45,7 +45,7 @@ describe('Commit component', () => {
shortSha: 'b7836edd',
title: 'Commit message',
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',
path: '/jschatz1',
username: 'jschatz1',
......
......@@ -47,16 +47,7 @@ describe Banzai::Filter::AbstractReferenceFilter do
end
end
context 'with RequestStore enabled' do
before do
RequestStore.begin!
end
after do
RequestStore.end!
RequestStore.clear!
end
context 'with RequestStore enabled', :request_store do
it 'returns a list of Projects for a list of paths' do
expect(filter.find_projects_for_paths([project.path_with_namespace])).
to eq([project])
......
......@@ -29,16 +29,7 @@ describe Banzai::IssuableExtractor, lib: true do
expect(result).to eq(issue_link => issue, merge_request_link => merge_request)
end
describe 'caching' do
before do
RequestStore.begin!
end
after do
RequestStore.end!
RequestStore.clear!
end
describe 'caching', :request_store do
it 'saves records to cache' do
extractor.extract([issue_link, merge_request_link])
......
......@@ -30,7 +30,7 @@ describe Banzai::ReferenceParser::BaseParser, lib: true do
it 'checks if user can read the resource' do
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])
end
......
......@@ -4,20 +4,199 @@ describe Banzai::ReferenceParser::SnippetParser, lib: true do
include ReferenceParserHelpers
let(:project) { create(:empty_project, :public) }
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) }
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
context 'when the link has a data-issue attribute' do
before { link['data-snippet'] = snippet.id.to_s }
context 'when a project is public and the snippets feature is enabled for everyone' do
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
describe '#referenced_by' do
let(:snippet) { create(:snippet, project: project) }
describe 'when the link has a data-snippet attribute' do
context 'using an existing snippet ID' do
it 'returns an Array of snippets' do
......@@ -31,7 +210,7 @@ describe Banzai::ReferenceParser::SnippetParser, lib: true do
it 'returns an empty Array' do
link['data-snippet'] = ''
expect(subject.referenced_by([link])).to eq([])
expect(subject.referenced_by([link])).to be_empty
end
end
end
......
......@@ -43,18 +43,9 @@ describe Banzai::ReferenceParser::UserParser, lib: true do
expect(subject.referenced_by([link])).to eq([user])
end
context 'when RequestStore is active' do
context 'when RequestStore is active', :request_store do
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
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
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
it 'presents a single URL as a one-element array' do
setting.elasticsearch_url = 'http://example.com'
......
......@@ -20,8 +20,8 @@ describe Commit, models: true do
end
it 'caches the author' do
allow(RequestStore).to receive(:active?).and_return(true)
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(commit.author).to eq(user)
......
......@@ -122,16 +122,7 @@ describe Group, 'Routable' do
it { expect(group.full_path).to eq(group.path) }
it { expect(nested_group.full_path).to eq("#{group.full_path}/#{nested_group.path}") }
context 'with RequestStore active' do
before do
RequestStore.begin!
end
after do
RequestStore.end!
RequestStore.clear!
end
context 'with RequestStore active', :request_store do
it 'does not load the route table more than once' do
expect(group).to receive(:uncached_full_path).once.and_call_original
......
......@@ -179,7 +179,7 @@ describe Group, models: true do
let!(:group) { create(:group, :access_requestable, :with_avatar) }
let(:user) { create(:user) }
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
before { group.add_master(user) }
......
......@@ -344,6 +344,20 @@ describe License do
end
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
context 'without add-ons' do
it 'returns an empty Hash' do
......
......@@ -43,6 +43,12 @@ describe Namespace, models: true do
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
let(:group) { build(:group, path: 'tree') }
......@@ -178,8 +184,8 @@ describe Namespace, models: true do
let(:parent) { create(:group, name: 'parent', path: 'parent') }
let(:child) { create(:group, name: 'child', path: 'child', parent: parent) }
let!(:project) { create(:project_empty_repo, path: 'the-project', namespace: child) }
let(:uploads_dir) { File.join(CarrierWave.root, 'uploads') }
let(:pages_dir) { TestEnv.pages_path }
let(:uploads_dir) { File.join(CarrierWave.root, FileUploader.base_dir) }
let(:pages_dir) { File.join(TestEnv.pages_path) }
before do
FileUtils.mkdir_p(File.join(uploads_dir, 'parent', 'child', 'the-project'))
......
......@@ -986,7 +986,7 @@ describe Project, models: true do
context 'when avatar file is uploaded' do
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}" }
it 'shows correct url' do
......
......@@ -389,16 +389,7 @@ describe ProjectTeam, models: true do
end
describe '#max_member_access_for_user_ids' do
context 'with RequestStore enabled' do
before do
RequestStore.begin!
end
after do
RequestStore.end!
RequestStore.clear!
end
context 'with RequestStore enabled', :request_store do
include_examples 'max member access for users'
def access_levels(users)
......
......@@ -1015,7 +1015,7 @@ describe User, models: true do
context 'when avatar file is uploaded' do
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
expect(user.avatar_url).to eq(avatar_path)
......
......@@ -3,7 +3,7 @@ require 'spec_helper'
describe ProjectSnippetPolicy, models: true do
let(:regular_user) { create(:user) }
let(:external_user) { create(:user, :external) }
let(:project) { create(:empty_project) }
let(:project) { create(:empty_project, :public) }
let(:author_permissions) do
[
......@@ -117,7 +117,7 @@ describe ProjectSnippetPolicy, models: true do
end
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 }
......
......@@ -17,7 +17,7 @@ describe API::License, api: true do
expect(Date.parse(json_response['expires_at'])).to eq Date.today + 11.months
expect(json_response['active_users']).to eq 1
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
it 'denies access if not admin' do
......
......@@ -79,7 +79,7 @@ describe 'OpenID Connect requests' do
'email_verified' => true,
'website' => 'https://example.com',
'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
......
......@@ -102,18 +102,11 @@ describe PipelineSerializer do
Ci::Pipeline::AVAILABLE_STATUSES.each do |status|
create_pipeline(status)
end
RequestStore.begin!
end
after do
RequestStore.end!
RequestStore.clear!
end
it "verifies number of queries" do
it 'verifies number of queries', :request_store do
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)
end
......
......@@ -13,7 +13,7 @@ describe Projects::ParticipantsService, services: true do
groups = participants.groups
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
it 'should return an url for the avatar with relative url' do
......@@ -24,7 +24,7 @@ describe Projects::ParticipantsService, services: true do
groups = participants.groups
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
......
......@@ -80,6 +80,15 @@ RSpec.configure do |config|
TestEnv.cleanup
end
config.before(:example, :request_store) do
RequestStore.begin!
end
config.after(:example, :request_store) do
RequestStore.end!
RequestStore.clear!
end
if ENV['CI']
# Retry only on feature specs that use JS
config.around :each, :js do |ex|
......
......@@ -3,6 +3,17 @@ require 'spec_helper'
describe AttachmentUploader do
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
it 'is true' do
expect(uploader.move_to_cache).to eq(true)
......
......@@ -3,6 +3,17 @@ require 'spec_helper'
describe AvatarUploader do
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
it 'is false' do
expect(uploader.move_to_cache).to eq(false)
......
......@@ -15,6 +15,16 @@ describe FileUploader do
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
it 'generates a secret if none is provided' do
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