Commit ba410802 authored by Valery Sizov's avatar Valery Sizov

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ee into ce-to-ee[ci skip]

parents 2116c02a 9f999549
...@@ -1069,6 +1069,13 @@ RSpec/NotToNot: ...@@ -1069,6 +1069,13 @@ RSpec/NotToNot:
RSpec/RepeatedDescription: RSpec/RepeatedDescription:
Enabled: false Enabled: false
# Ensure RSpec hook blocks are always multi-line.
RSpec/SingleLineHook:
Enabled: true
Exclude:
- 'spec/factories/*'
- 'spec/requests/api/v3/*'
# Checks for stubbed test subjects. # Checks for stubbed test subjects.
RSpec/SubjectStub: RSpec/SubjectStub:
Enabled: false Enabled: false
......
...@@ -274,6 +274,17 @@ gem 'gettext_i18n_rails', '~> 1.8.0' ...@@ -274,6 +274,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)
...@@ -67,13 +68,13 @@ GEM ...@@ -67,13 +68,13 @@ GEM
execjs execjs
json json
awesome_print (1.2.0) awesome_print (1.2.0)
aws-sdk (2.7.8) aws-sdk (2.9.32)
aws-sdk-resources (= 2.7.8) aws-sdk-resources (= 2.9.32)
aws-sdk-core (2.7.8) aws-sdk-core (2.9.32)
aws-sigv4 (~> 1.0) aws-sigv4 (~> 1.0)
jmespath (~> 1.0) jmespath (~> 1.0)
aws-sdk-resources (2.7.8) aws-sdk-resources (2.9.32)
aws-sdk-core (= 2.7.8) aws-sdk-core (= 2.9.32)
aws-sigv4 (1.0.0) aws-sigv4 (1.0.0)
axiom-types (0.1.1) axiom-types (0.1.1)
descendants_tracker (~> 0.0.4) descendants_tracker (~> 0.0.4)
...@@ -139,6 +140,8 @@ GEM ...@@ -139,6 +140,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)
...@@ -218,8 +221,8 @@ GEM ...@@ -218,8 +221,8 @@ GEM
multipart-post (>= 1.2, < 3) multipart-post (>= 1.2, < 3)
faraday_middleware (0.11.0.1) faraday_middleware (0.11.0.1)
faraday (>= 0.7.4, < 1.0) faraday (>= 0.7.4, < 1.0)
faraday_middleware-aws-signers-v4 (0.1.5) faraday_middleware-aws-signers-v4 (0.1.7)
aws-sdk (~> 2.1) aws-sdk-resources (~> 2)
faraday (~> 0.9) faraday (~> 0.9)
faraday_middleware-multi_json (0.0.6) faraday_middleware-multi_json (0.0.6)
faraday_middleware faraday_middleware
...@@ -576,6 +579,36 @@ GEM ...@@ -576,6 +579,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)
...@@ -1035,6 +1068,15 @@ DEPENDENCIES ...@@ -1035,6 +1068,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)
......
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
......
module Projects
class IssueLinksController < Projects::ApplicationController
before_action :authorize_admin_issue_link!, only: [:create, :destroy]
def index
render json: issues
end
def create
create_params = params.slice(:issue_references)
result = IssueLinks::CreateService.new(issue, current_user, create_params).execute
render json: { message: result[:message], issues: issues }, status: result[:http_status]
end
def destroy
issue_link = IssueLink.find(params[:id])
return render_403 unless can?(current_user, :admin_issue_link, issue_link.target.project)
IssueLinks::DestroyService.new(issue_link, current_user).execute
render json: { issues: issues }
end
private
def issues
IssueLinks::ListService.new(issue, current_user).execute
end
def authorize_admin_issue_link!
render_403 unless can?(current_user, :admin_issue_link, @project)
end
def issue
@issue ||=
IssuesFinder.new(current_user, project_id: @project.id)
.execute
.find_by!(iid: params[:issue_id])
end
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
......
...@@ -6,6 +6,8 @@ class GeoNodeStatus ...@@ -6,6 +6,8 @@ class GeoNodeStatus
def health def health
@health ||= HealthCheck::Utils.process_checks(['geo']) @health ||= HealthCheck::Utils.process_checks(['geo'])
rescue NotImplementedError => e
@health = e.to_s
end end
def healthy? def healthy?
......
class IssueLink < ActiveRecord::Base
belongs_to :source, class_name: 'Issue'
belongs_to :target, class_name: 'Issue'
validates :source, presence: true
validates :target, presence: true
validates :source, uniqueness: { scope: :target_id, message: 'is already related' }
validate :check_self_relation
private
def check_self_relation
return unless source && target
if source == target
errors.add(:source, 'cannot be related to itself')
end
end
end
...@@ -7,12 +7,16 @@ class License < ActiveRecord::Base ...@@ -7,12 +7,16 @@ 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
RELATED_ISSUES_FEATURE = 'RelatedIssues'.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,
related_issues: RELATED_ISSUES_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 +28,8 @@ class License < ActiveRecord::Base ...@@ -24,7 +28,8 @@ class License < ActiveRecord::Base
EARLY_ADOPTER_PLAN = 'early_adopter'.freeze EARLY_ADOPTER_PLAN = 'early_adopter'.freeze
EES_FEATURES = [ EES_FEATURES = [
# .. { ELASTIC_SEARCH_FEATURE => 1 },
{ RELATED_ISSUES_FEATURE => 1 }
].freeze ].freeze
EEP_FEATURES = [ EEP_FEATURES = [
...@@ -91,6 +96,8 @@ class License < ActiveRecord::Base ...@@ -91,6 +96,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 +190,7 @@ class License < ActiveRecord::Base ...@@ -183,7 +190,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
......
...@@ -344,10 +344,6 @@ class Project < ActiveRecord::Base ...@@ -344,10 +344,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
...@@ -408,8 +404,6 @@ class Project < ActiveRecord::Base ...@@ -408,8 +404,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
......
...@@ -3,7 +3,7 @@ class SystemNoteMetadata < ActiveRecord::Base ...@@ -3,7 +3,7 @@ class SystemNoteMetadata < ActiveRecord::Base
commit description merge confidential visible label assignee cross_reference commit description merge confidential visible label assignee cross_reference
title time_tracking branch milestone discussion task moved opened closed merged title time_tracking branch milestone discussion task moved opened closed merged
outdated outdated
approved unapproved approved unapproved relate unrelate
].freeze ].freeze
validates :note, presence: true validates :note, presence: true
......
...@@ -22,6 +22,11 @@ module EE ...@@ -22,6 +22,11 @@ module EE
cannot! :create_note cannot! :create_note
cannot! :read_project cannot! :read_project
end end
unless project.feature_available?(:related_issues)
cannot! :read_issue_link
cannot! :admin_issue_link
end
end end
end end
end end
...@@ -55,6 +55,9 @@ class ProjectPolicy < BasePolicy ...@@ -55,6 +55,9 @@ class ProjectPolicy < BasePolicy
can! :read_pipeline_schedule can! :read_pipeline_schedule
can! :read_build can! :read_build
end end
# EE-only
can! :read_issue_link
end end
def reporter_access! def reporter_access!
...@@ -79,6 +82,9 @@ class ProjectPolicy < BasePolicy ...@@ -79,6 +82,9 @@ class ProjectPolicy < BasePolicy
if project.feature_available?(:deploy_board) || Rails.env.development? if project.feature_available?(:deploy_board) || Rails.env.development?
can! :read_deploy_board can! :read_deploy_board
end end
# EE-only
can! :admin_issue_link
end end
# Permissions given when an user is team member of a project # Permissions given when an user is team member of a project
...@@ -321,5 +327,8 @@ class ProjectPolicy < BasePolicy ...@@ -321,5 +327,8 @@ class ProjectPolicy < BasePolicy
# NOTE: may be overridden by IssuePolicy # NOTE: may be overridden by IssuePolicy
can! :read_issue can! :read_issue
# EE-only
can! :read_issue_link
end end
end end
module IssueLinks
class CreateService < BaseService
def initialize(issue, user, params)
@issue, @current_user, @params = issue, user, params.dup
end
def execute
if referenced_issues.blank?
return error('No Issue found for given reference', 401)
end
create_issue_links
success
end
private
def create_issue_links
referenced_issues.each do |referenced_issue|
create_notes(referenced_issue) if relate_issues(referenced_issue)
end
end
def relate_issues(referenced_issue)
IssueLink.create(source: @issue, target: referenced_issue)
end
def create_notes(referenced_issue)
SystemNoteService.relate_issue(@issue, referenced_issue, current_user)
SystemNoteService.relate_issue(referenced_issue, @issue, current_user)
end
def referenced_issues
@referenced_issues ||= begin
issue_references = params[:issue_references]
text = issue_references.join(' ')
extractor = Gitlab::ReferenceExtractor.new(@issue.project, @current_user)
extractor.analyze(text)
extractor.issues.select do |issue|
can?(current_user, :admin_issue_link, issue)
end
end
end
end
end
module IssueLinks
class DestroyService < BaseService
def initialize(issue_link, user)
@issue_link = issue_link
@current_user = user
@issue = issue_link.source
@referenced_issue = issue_link.target
end
def execute
remove_relation
create_notes
success(message: 'Relation was removed')
end
private
def remove_relation
@issue_link.destroy!
end
def create_notes
SystemNoteService.unrelate_issue(@issue, @referenced_issue, current_user)
SystemNoteService.unrelate_issue(@referenced_issue, @issue, current_user)
end
end
end
module IssueLinks
class ListService
include Gitlab::Routing
def initialize(issue, user)
@issue, @current_user, @project = issue, user, issue.project
end
def execute
issues.map do |referenced_issue|
{
id: referenced_issue.id,
title: referenced_issue.title,
state: referenced_issue.state,
reference: referenced_issue.to_reference(@project),
path: namespace_project_issue_path(referenced_issue.project.namespace, referenced_issue.project, referenced_issue.iid),
destroy_relation_path: destroy_relation_path(referenced_issue)
}
end
end
private
def issues
related_issues = Issue
.select(['issues.*', 'issue_links.id AS issue_links_id'])
.joins("INNER JOIN issue_links ON
(issue_links.source_id = issues.id AND issue_links.target_id = #{@issue.id})
OR
(issue_links.target_id = issues.id AND issue_links.source_id = #{@issue.id})")
.preload(project: :namespace)
.reorder('issue_links_id')
Ability.issues_readable_by_user(related_issues, @current_user)
end
def destroy_relation_path(issue)
# Make sure the user can admin both the current issue AND the
# referenced issue projects in order to return the removal link.
if can_destroy_issue_link_on_current_project? && can_destroy_issue_link?(issue.project)
namespace_project_issue_link_path(@project.namespace,
@issue.project,
@issue.iid,
issue.issue_links_id)
end
end
def can_destroy_issue_link_on_current_project?
return @can_destroy_on_current_project if defined?(@can_destroy_on_current_project)
@can_destroy_on_current_project = can_destroy_issue_link?(@project)
end
def can_destroy_issue_link?(project)
Ability.allowed?(@current_user, :admin_issue_link, project)
end
end
end
...@@ -552,6 +552,38 @@ module SystemNoteService ...@@ -552,6 +552,38 @@ module SystemNoteService
create_note(NoteSummary.new(noteable, project, author, body, action: 'moved')) create_note(NoteSummary.new(noteable, project, author, body, action: 'moved'))
end end
#
# noteable - Noteable object
# noteable_ref - Referenced noteable object
# user - User performing reference
#
# Example Note text:
#
# "marked this issue as related to gitlab-ce#9001"
#
# Returns the created Note object
def relate_issue(noteable, noteable_ref, user)
body = "marked this issue as related to #{noteable_ref.to_reference(noteable.project)}"
create_note(NoteSummary.new(noteable, noteable.project, user, body, action: 'relate'))
end
#
# noteable - Noteable object
# noteable_ref - Referenced noteable object
# user - User performing reference
#
# Example Note text:
#
# "removed the relation with gitlab-ce#9001"
#
# Returns the created Note object
def unrelate_issue(noteable, noteable_ref, user)
body = "removed the relation with #{noteable_ref.to_reference(noteable.project)}"
create_note(NoteSummary.new(noteable, noteable.project, user, body, action: 'unrelate'))
end
# Called when the merge request is approved by user # Called when the merge request is approved by user
# #
# noteable - Noteable object # noteable - Noteable object
......
...@@ -120,8 +120,13 @@ class ObjectStoreUploader < CarrierWave::Uploader::Base ...@@ -120,8 +120,13 @@ class ObjectStoreUploader < CarrierWave::Uploader::Base
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
<<<<<<< HEAD
def exists? def exists?
file.try(:exists?) file.try(:exists?)
=======
def file_storage?
storage.is_a?(CarrierWave::Storage::File)
>>>>>>> 9f999549099fb5b254a3892d3b88284c39a4e12d
end end
private private
......
%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" } }...
...@@ -11,15 +11,17 @@ module GeoDynamicBackoff ...@@ -11,15 +11,17 @@ module GeoDynamicBackoff
end end
end end
private class_methods do
private
def linear_backoff_strategy(count) def linear_backoff_strategy(count)
rand(1..20) + count rand(1..20) + count
end end
def geometric_backoff_strategy(count) def geometric_backoff_strategy(count)
# This strategy is based on the original one from sidekiq # This strategy is based on the original one from sidekiq
count = count - 30 # we must start counting after 30 count = count - 30 # we must start counting after 30
(count**4) + 15 + (rand(30) * (count + 1)) (count**4) + 15 + (rand(30) * (count + 1))
end
end end
end end
---
title: Allows manually adding bi-directional relationships between issues in the issue page (EES feature)
merge_request:
author:
---
title: Add explicit licensing for Elasticsearch
merge_request: 2108
author:
---
title: 'Geo: fixed Dynamic Backoff strategy that was not being used by workers'
merge_request: 2128
author:
---
title: Adding support for AWS ec2 instance profile credentials with elasticsearch
merge_request:
author: Matt Gresko
---
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"
......
...@@ -8,7 +8,7 @@ end ...@@ -8,7 +8,7 @@ end
begin begin
# Avoid using the database if this is run in a Rake task # Avoid using the database if this is run in a Rake task
if Gitlab::Geo.primary_role_enabled? if Gitlab::Geo.primary_role_enabled?
Gitlab::Geo.current_node.update_clone_url! Gitlab::Geo.current_node&.update_clone_url!
end end
rescue => e rescue => e
warn "WARNING: Unable to check/update clone_url_prefix for Geo: #{e}" warn "WARNING: Unable to check/update clone_url_prefix for Geo: #{e}"
......
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
...@@ -3,6 +3,11 @@ ...@@ -3,6 +3,11 @@
en: en:
hello: "Hello world" hello: "Hello world"
activerecord:
attributes:
issue_link:
source: Source issue
target: Target issue
errors: errors:
messages: messages:
label_already_exists_at_group_level: "already exists at group level for %{group}. Please choose another one." label_already_exists_at_group_level: "already exists at group level for %{group}. Please choose another one."
......
...@@ -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
......
...@@ -311,6 +311,8 @@ constraints(ProjectUrlConstrainer.new) do ...@@ -311,6 +311,8 @@ constraints(ProjectUrlConstrainer.new) do
post :bulk_update post :bulk_update
post :export_csv post :export_csv
end end
resources :issue_links, only: [:index, :create, :destroy], as: 'links', path: 'links'
end end
resources :project_members, except: [:show, :new, :edit], constraints: { id: /[a-zA-Z.\/0-9_\-#%+]+/ }, concerns: :access_requestable do resources :project_members, except: [:show, :new, :edit], constraints: { id: /[a-zA-Z.\/0-9_\-#%+]+/ }, concerns: :access_requestable do
......
...@@ -71,6 +71,7 @@ var config = { ...@@ -71,6 +71,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: {
......
class CreateIssueLinksTable < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
create_table :issue_links do |t|
t.integer :source_id, null: false, index: true
t.integer :target_id, null: false, index: true
t.timestamps null: true
end
add_index :issue_links, [:source_id, :target_id], unique: true
add_concurrent_foreign_key :issue_links, :issues, column: :source_id
add_concurrent_foreign_key :issue_links, :issues, column: :target_id
end
def down
drop_table :issue_links
end
end
...@@ -654,6 +654,17 @@ ActiveRecord::Schema.define(version: 20170606202615) do ...@@ -654,6 +654,17 @@ ActiveRecord::Schema.define(version: 20170606202615) do
add_index "issue_assignees", ["issue_id", "user_id"], name: "index_issue_assignees_on_issue_id_and_user_id", unique: true, using: :btree add_index "issue_assignees", ["issue_id", "user_id"], name: "index_issue_assignees_on_issue_id_and_user_id", unique: true, using: :btree
add_index "issue_assignees", ["user_id"], name: "index_issue_assignees_on_user_id", using: :btree add_index "issue_assignees", ["user_id"], name: "index_issue_assignees_on_user_id", using: :btree
create_table "issue_links", force: :cascade do |t|
t.integer "source_id", null: false
t.integer "target_id", null: false
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "issue_links", ["source_id", "target_id"], name: "index_issue_links_on_source_id_and_target_id", unique: true, using: :btree
add_index "issue_links", ["source_id"], name: "index_issue_links_on_source_id", using: :btree
add_index "issue_links", ["target_id"], name: "index_issue_links_on_target_id", using: :btree
create_table "issue_metrics", force: :cascade do |t| create_table "issue_metrics", force: :cascade do |t|
t.integer "issue_id", null: false t.integer "issue_id", null: false
t.datetime "first_mentioned_in_commit_at" t.datetime "first_mentioned_in_commit_at"
...@@ -1779,6 +1790,8 @@ ActiveRecord::Schema.define(version: 20170606202615) do ...@@ -1779,6 +1790,8 @@ ActiveRecord::Schema.define(version: 20170606202615) do
add_foreign_key "geo_repository_updated_events", "projects", on_delete: :cascade add_foreign_key "geo_repository_updated_events", "projects", on_delete: :cascade
add_foreign_key "issue_assignees", "issues", name: "fk_b7d881734a", on_delete: :cascade add_foreign_key "issue_assignees", "issues", name: "fk_b7d881734a", on_delete: :cascade
add_foreign_key "issue_assignees", "users", name: "fk_5e0c8d9154", on_delete: :cascade add_foreign_key "issue_assignees", "users", name: "fk_5e0c8d9154", on_delete: :cascade
add_foreign_key "issue_links", "issues", column: "source_id", name: "fk_c900194ff2", on_delete: :cascade
add_foreign_key "issue_links", "issues", column: "target_id", name: "fk_e71bb44f1f", on_delete: :cascade
add_foreign_key "issue_metrics", "issues", on_delete: :cascade add_foreign_key "issue_metrics", "issues", on_delete: :cascade
add_foreign_key "label_priorities", "labels", on_delete: :cascade add_foreign_key "label_priorities", "labels", on_delete: :cascade
add_foreign_key "label_priorities", "projects", on_delete: :cascade add_foreign_key "label_priorities", "projects", on_delete: :cascade
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
>**Note:** >**Note:**
> >
> - [Introduced][ce-10308] in GitLab 9.1. > - [Introduced][ce-10308] in GitLab 9.1.
> - You need a personal access token in order to retrieve and import GitHub > - You need a personal access token in order to retrieve and import GitHub
> projects. You can get it from: https://github.com/settings/tokens > projects. You can get it from: https://github.com/settings/tokens
> - You also need to pass an username as the second argument to the rake task > - You also need to pass an username as the second argument to the rake task
> which will become the owner of the project. > which will become the owner of the project.
...@@ -19,7 +19,7 @@ bundle exec rake import:github[access_token,root,foo/bar] RAILS_ENV=production ...@@ -19,7 +19,7 @@ bundle exec rake import:github[access_token,root,foo/bar] RAILS_ENV=production
``` ```
In this case, `access_token` is your GitHub personal access token, `root` In this case, `access_token` is your GitHub personal access token, `root`
is your GitLab username, and `foo/bar` is the new GitLab namespace/project that is your GitLab username, and `foo/bar` is the new GitLab namespace/project that
will get created from your GitHub project. Subgroups are also possible: `foo/foo/bar`. will get created from your GitHub project. Subgroups are also possible: `foo/foo/bar`.
......
...@@ -56,6 +56,7 @@ following locations: ...@@ -56,6 +56,7 @@ following locations:
- [V3 to V4](v3_to_v4.md) - [V3 to V4](v3_to_v4.md)
- [Version](version.md) - [Version](version.md)
<<<<<<< HEAD
## Road to GraphQL ## Road to GraphQL
Going forward, we will start on moving to Going forward, we will start on moving to
...@@ -71,17 +72,37 @@ compatability layer on top of GraphQL. ...@@ -71,17 +72,37 @@ compatability layer on top of GraphQL.
### Internal CI API ### Internal CI API
=======
>>>>>>> 9f999549099fb5b254a3892d3b88284c39a4e12d
The following documentation is for the [internal CI API](ci/README.md): The following documentation is for the [internal CI API](ci/README.md):
- [Builds](ci/builds.md) - [Builds](ci/builds.md)
- [Runners](ci/runners.md) - [Runners](ci/runners.md)
## Road to GraphQL
Going forward, we will start on moving to
[GraphQL](http://graphql.org/learn/best-practices/) and deprecate the use of
controller-specific endpoints. GraphQL has a number of benefits:
1. We avoid having to maintain two different APIs.
2. Callers of the API can request only what they need.
3. It is versioned by default.
It will co-exist with the current v4 REST API. If we have a v5 API, this should
be a compatibility layer on top of GraphQL.
## Authentication ## Authentication
Most API requests require authentication via a session cookie or token. For those cases where it is not required, this will be mentioned in the documentation Most API requests require authentication via a session cookie or token. For
for each individual endpoint. For example, the [`/projects/:id` endpoint](projects.md). those cases where it is not required, this will be mentioned in the documentation
There are three types of tokens available: private tokens, OAuth 2 tokens, and personal for each individual endpoint. For example, the [`/projects/:id` endpoint](projects.md).
access tokens.
There are three types of access tokens available:
1. [OAuth2 tokens](#oauth2-tokens)
1. [Private tokens](#private-tokens)
1. [Personal access tokens](#personal-access-tokens)
If authentication information is invalid or omitted, an error message will be If authentication information is invalid or omitted, an error message will be
returned with status code `401`: returned with status code `401`:
...@@ -92,20 +113,13 @@ returned with status code `401`: ...@@ -92,20 +113,13 @@ returned with status code `401`:
} }
``` ```
### Session Cookie ### Session cookie
When signing in to GitLab as an ordinary user, a `_gitlab_session` cookie is When signing in to GitLab as an ordinary user, a `_gitlab_session` cookie is
set. The API will use this cookie for authentication if it is present, but using set. The API will use this cookie for authentication if it is present, but using
the API to generate a new session cookie is currently not supported. the API to generate a new session cookie is currently not supported.
### Private Tokens ### OAuth2 tokens
You need to pass a `private_token` parameter via query string or header. If passed as a
header, the header name must be `PRIVATE-TOKEN` (uppercase and with a dash instead of
an underscore). You can find or reset your private token in your account page
(`/profile/account`).
### OAuth 2 Tokens
You can use an OAuth 2 token to authenticate with the API by passing it either in the You can use an OAuth 2 token to authenticate with the API by passing it either in the
`access_token` parameter or in the `Authorization` header. `access_token` parameter or in the `Authorization` header.
...@@ -118,30 +132,31 @@ curl --header "Authorization: Bearer OAUTH-TOKEN" https://gitlab.example.com/api ...@@ -118,30 +132,31 @@ curl --header "Authorization: Bearer OAUTH-TOKEN" https://gitlab.example.com/api
Read more about [GitLab as an OAuth2 client](oauth2.md). Read more about [GitLab as an OAuth2 client](oauth2.md).
### Personal Access Tokens ### Private tokens
> [Introduced][ce-3749] in GitLab 8.8. Private tokens provide full access to the GitLab API. Anyone with access to
them can interact with GitLab as if they were you. You can find or reset your
private token in your account page (`/profile/account`).
You can create as many personal access tokens as you like from your GitLab For examples of usage, [read the basic usage section](#basic-usage).
profile (`/profile/personal_access_tokens`); perhaps one for each application
that needs access to the GitLab API.
Once you have your token, pass it to the API using either the `private_token` ### Personal access tokens
parameter or the `PRIVATE-TOKEN` header.
> [Introduced][ce-5951] in GitLab 8.15. Instead of using your private token which grants full access to your account,
personal access tokens could be a better fit because of their granular
permissions.
Personal Access Tokens can be created with one or more scopes that allow various actions Once you have your token, pass it to the API using either the `private_token`
that a given token can perform. Although there are only two scopes available at the parameter or the `PRIVATE-TOKEN` header. For examples of usage,
moment – `read_user` and `api` – the groundwork has been laid to add more scopes easily. [read the basic usage section](#basic-usage).
At any time you can revoke any personal access token by just clicking **Revoke**. [Read more about personal access tokens.][pat]
### Impersonation tokens ### Impersonation tokens
> [Introduced][ce-9099] in GitLab 9.0. Needs admin permissions. > [Introduced][ce-9099] in GitLab 9.0. Needs admin permissions.
Impersonation tokens are a type of [Personal Access Token](#personal-access-tokens) Impersonation tokens are a type of [personal access token][pat]
that can only be created by an admin for a specific user. that can only be created by an admin for a specific user.
They are a better alternative to using the user's password/private token They are a better alternative to using the user's password/private token
...@@ -150,9 +165,11 @@ or private token, since the password/token can change over time. Impersonation ...@@ -150,9 +165,11 @@ or private token, since the password/token can change over time. Impersonation
tokens are a great fit if you want to build applications or tools which tokens are a great fit if you want to build applications or tools which
authenticate with the API as a specific user. authenticate with the API as a specific user.
For more information about the usage please refer to the For more information, refer to the
[users API](users.md#retrieve-user-impersonation-tokens) docs. [users API](users.md#retrieve-user-impersonation-tokens) docs.
For examples of usage, [read the basic usage section](#basic-usage).
### Sudo ### Sudo
> Needs admin permissions. > Needs admin permissions.
...@@ -205,11 +222,16 @@ GET /projects?private_token=9koXpg98eAheJpvBs5tK&sudo=23 ...@@ -205,11 +222,16 @@ GET /projects?private_token=9koXpg98eAheJpvBs5tK&sudo=23
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "SUDO: 23" "https://gitlab.example.com/api/v4/projects" curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "SUDO: 23" "https://gitlab.example.com/api/v4/projects"
``` ```
## Basic Usage ## Basic usage
API requests should be prefixed with `api` and the API version. The API version API requests should be prefixed with `api` and the API version. The API version
is defined in [`lib/api.rb`][lib-api-url]. is defined in [`lib/api.rb`][lib-api-url].
For endpoints that require [authentication](#authentication), you need to pass
a `private_token` parameter via query string or header. If passed as a header,
the header name must be `PRIVATE-TOKEN` (uppercase and with a dash instead of
an underscore).
Example of a valid API request: Example of a valid API request:
``` ```
...@@ -222,6 +244,12 @@ Example of a valid API request using cURL and authentication via header: ...@@ -222,6 +244,12 @@ Example of a valid API request using cURL and authentication via header:
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects" curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects"
``` ```
Example of a valid API request using cURL and authentication via a query string:
```shell
curl "https://gitlab.example.com/api/v4/projects?private_token=9koXpg98eAheJpvBs5tK"
```
The API uses JSON to serialize data. You don't need to specify `.json` at the The API uses JSON to serialize data. You don't need to specify `.json` at the
end of an API URL. end of an API URL.
...@@ -437,3 +465,4 @@ programming languages. Visit the [GitLab website] for a complete list. ...@@ -437,3 +465,4 @@ programming languages. Visit the [GitLab website] for a complete list.
[ce-3749]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3749 [ce-3749]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3749
[ce-5951]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5951 [ce-5951]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5951
[ce-9099]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9099 [ce-9099]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9099
[pat]: ../user/profile/personal_access_tokens.md
...@@ -134,4 +134,4 @@ access_token = client.password.get_token('user@example.com', 'secret') ...@@ -134,4 +134,4 @@ access_token = client.password.get_token('user@example.com', 'secret')
puts access_token.token puts access_token.token
``` ```
[personal access tokens]: ./README.md#personal-access-tokens [personal access tokens]: ../user/profile/personal_access_tokens.md
# Session API # Session API
## Deprecation Notice >**Deprecation notice:**
Starting in GitLab 8.11, this feature has been **disabled** for users with
1. Starting in GitLab 8.11, this feature has been *disabled* for users with two-factor authentication turned on. [two-factor authentication][2fa] turned on. These users can access the API
2. These users can access the API using [personal access tokens] instead. using [personal access tokens] instead.
---
You can login with both GitLab and LDAP credentials in order to obtain the You can login with both GitLab and LDAP credentials in order to obtain the
private token. private token.
...@@ -52,4 +50,5 @@ Example response: ...@@ -52,4 +50,5 @@ Example response:
} }
``` ```
[personal access tokens]: ./README.md#personal-access-tokens [2fa]: ../user/profile/account/two_factor_authentication.md
[personal access tokens]: ../user/profile/personal_access_tokens.md
...@@ -809,7 +809,7 @@ Example response: ...@@ -809,7 +809,7 @@ Example response:
It creates a new impersonation token. Note that only administrators can do this. It creates a new impersonation token. Note that only administrators can do this.
You are only able to create impersonation tokens to impersonate the user and perform You are only able to create impersonation tokens to impersonate the user and perform
both API calls and Git reads and writes. The user will not see these tokens in his profile both API calls and Git reads and writes. The user will not see these tokens in their profile
settings page. settings page.
``` ```
......
...@@ -282,9 +282,9 @@ which can be avoided if a different driver is used, for example `overlay`. ...@@ -282,9 +282,9 @@ which can be avoided if a different driver is used, for example `overlay`.
> **Notes:** > **Notes:**
- This feature requires GitLab 8.8 and GitLab Runner 1.2. - This feature requires GitLab 8.8 and GitLab Runner 1.2.
- Starting from GitLab 8.12, if you have 2FA enabled in your account, you need - Starting from GitLab 8.12, if you have [2FA] enabled in your account, you need
to pass a personal access token instead of your password in order to login to to pass a [personal access token][pat] instead of your password in order to
GitLab's Container Registry. login to GitLab's Container Registry.
Once you've built a Docker image, you can push it up to the built-in Once you've built a Docker image, you can push it up to the built-in
[GitLab Container Registry](../../user/project/container_registry.md). For example, [GitLab Container Registry](../../user/project/container_registry.md). For example,
...@@ -409,3 +409,5 @@ Some things you should be aware of when using the Container Registry: ...@@ -409,3 +409,5 @@ Some things you should be aware of when using the Container Registry:
[docker-in-docker]: https://blog.docker.com/2013/09/docker-can-now-run-within-docker/ [docker-in-docker]: https://blog.docker.com/2013/09/docker-can-now-run-within-docker/
[docker-cap]: https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities [docker-cap]: https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities
[2fa]: ../../user/profile/account/two_factor_authentication.md
[pat]: ../../user/profile/personal_access_tokens.md
...@@ -27,7 +27,7 @@ download and analyze the report artifact in JSON format. ...@@ -27,7 +27,7 @@ download and analyze the report artifact in JSON format.
For GitLab [Enterprise Edition Starter][ee] users, this information can be automatically For GitLab [Enterprise Edition Starter][ee] users, this information can be automatically
extracted and shown right in the merge request widget. [Learn more on code quality extracted and shown right in the merge request widget. [Learn more on code quality
diffs in merge requests](http://docs.gitlab.com/ee/user/project/merge_requests/code_quality_diff.md). diffs in merge requests](../../user/project/merge_requests/code_quality_diff.md).
[cli]: https://github.com/codeclimate/codeclimate [cli]: https://github.com/codeclimate/codeclimate
[dind]: ../docker/using_docker_build.md#use-docker-in-docker-executor [dind]: ../docker/using_docker_build.md#use-docker-in-docker-executor
......
...@@ -31,6 +31,38 @@ Keep in mind that: ...@@ -31,6 +31,38 @@ Keep in mind that:
clone/pull from repositories (HTTP(S)/SSH). clone/pull from repositories (HTTP(S)/SSH).
- Primary talks to secondaries to notify for changes (API). - Primary talks to secondaries to notify for changes (API).
## Architecture
The following diagram illustrates the underlying architecture of GitLab Geo:
![GitLab Geo architecture](img/geo-architecture.png)
[Source diagram](https://docs.google.com/drawings/d/1VQIcj6jyE3idWKyt9MRUAaE3XXrkwx8g-Ne4pmURmwI/edit)
In this diagram, there is one Geo primary node and one secondary. The
secondary clones repositories via git over SSH. Attachments, LFS objects, and
other files are downloaded via HTTPS using a GitLab API to authenticate.
Writes to the database and Git repositories can only be performed on the Geo
primary node. The secondary node receives database updates via PostgreSQL
streaming replication.
Note that the secondary needs two different PostgreSQL databases: a read-only
instance that streams data from the main GitLab database and another used
internally by the secondary node to record what data has been replicated.
### LDAP
We recommend that if you use LDAP on your primary that you also set up a
secondary LDAP server for the secondary Geo node. Otherwise, users will not be
able to perform Git operations over HTTP(s) on the **secondary** Geo node
using HTTP Basic Authentication. However, Git via SSH and personal access
tokens will still work.
Check with your LDAP provider for instructions on on how to set up
replication. For example, OpenLDAP provides [these
instructions](https://www.openldap.org/doc/admin24/replication.html).
## Setup instructions ## Setup instructions
In order to set up one or more GitLab Geo instances, follow the steps below in In order to set up one or more GitLab Geo instances, follow the steps below in
...@@ -47,6 +79,7 @@ If you installed GitLab using the Omnibus packages (highly recommended): ...@@ -47,6 +79,7 @@ If you installed GitLab using the Omnibus packages (highly recommended):
1. [Upload the GitLab License](../user/admin_area/license.md) to the **primary** Geo Node to unlock GitLab Geo. 1. [Upload the GitLab License](../user/admin_area/license.md) to the **primary** Geo Node to unlock GitLab Geo.
1. [Setup the database replication](database.md) (`primary (read-write) <-> secondary (read-only)` topology). 1. [Setup the database replication](database.md) (`primary (read-write) <-> secondary (read-only)` topology).
1. [Configure GitLab](configuration.md) to set the primary and secondary nodes. 1. [Configure GitLab](configuration.md) to set the primary and secondary nodes.
1. Optional: [Configure a secondary LDAP server](../administration/auth/ldap.md) for the secondary. See [notes on LDAP](#ldap).
1. [Follow the after setup steps](after_setup.md). 1. [Follow the after setup steps](after_setup.md).
[install-ee]: https://about.gitlab.com/downloads-ee/ "GitLab Enterprise Edition Omnibus packages downloads page" [install-ee]: https://about.gitlab.com/downloads-ee/ "GitLab Enterprise Edition Omnibus packages downloads page"
......
...@@ -58,7 +58,7 @@ The following Elasticsearch settings are available: ...@@ -58,7 +58,7 @@ The following Elasticsearch settings are available:
| `Use experimental repository indexer` | Perform repository indexing using [GitLab Elasticsearch Indexer](https://gitlab.com/gitlab-org/gitlab-elasticsearch-indexer). | | `Use experimental repository indexer` | Perform repository indexing using [GitLab Elasticsearch Indexer](https://gitlab.com/gitlab-org/gitlab-elasticsearch-indexer). |
| `Search with Elasticsearch enabled` | Enables/disables using Elasticsearch in search. | | `Search with Elasticsearch enabled` | Enables/disables using Elasticsearch in search. |
| `URL` | The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., "http://host1, https://host2:9200"). | | `URL` | The URL to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., "http://host1, https://host2:9200"). |
| `Using AWS hosted Elasticsearch with IAM credentials` | Sign your Elasticsearch requests using [AWS IAM authorization][aws-iam]. The access key must be allowed to perform `es:*` actions. | | `Using AWS hosted Elasticsearch with IAM credentials` | Sign your Elasticsearch requests using [AWS IAM authorization][aws-iam] or [AWS EC2 Instance Profile Credentials][aws-instance-profile]. The policies must be configured to allow `es:*` actions. |
| `AWS Region` | The AWS region your Elasticsearch service is located in. | | `AWS Region` | The AWS region your Elasticsearch service is located in. |
| `AWS Access Key` | The AWS access key. | | `AWS Access Key` | The AWS access key. |
| `AWS Secret Access Key` | The AWS secret access key. | | `AWS Secret Access Key` | The AWS secret access key. |
...@@ -325,6 +325,7 @@ Make sure you indexed all the database data as stated above (`sudo gitlab-rake g ...@@ -325,6 +325,7 @@ Make sure you indexed all the database data as stated above (`sudo gitlab-rake g
[ee-1305]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1305 [ee-1305]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1305
[aws-elasticsearch]: http://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-gsg.html [aws-elasticsearch]: http://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-gsg.html
[aws-iam]: http://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html [aws-iam]: http://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html
[aws-instance-profile]: http://docs.aws.amazon.com/codedeploy/latest/userguide/getting-started-create-iam-instance-profile.html#getting-started-create-iam-instance-profile-cli
[ee-109]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/109 "Elasticsearch Merge Request" [ee-109]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/109 "Elasticsearch Merge Request"
[elasticsearch]: https://www.elastic.co/products/elasticsearch "Elasticsearch website" [elasticsearch]: https://www.elastic.co/products/elasticsearch "Elasticsearch website"
[install]: https://www.elastic.co/guide/en/elasticsearch/reference/current/_installation.html "Elasticsearch installation documentation" [install]: https://www.elastic.co/guide/en/elasticsearch/reference/current/_installation.html "Elasticsearch installation documentation"
......
...@@ -125,23 +125,14 @@ applications and U2F devices. ...@@ -125,23 +125,14 @@ applications and U2F devices.
## Personal access tokens ## Personal access tokens
When 2FA is enabled, you can no longer use your normal account password to When 2FA is enabled, you can no longer use your normal account password to
authenticate with Git over HTTPS on the command line, you must use a personal authenticate with Git over HTTPS on the command line or when using
access token instead. [GitLab's API][api], you must use a [personal access token][pat] instead.
1. Log in to your GitLab account.
1. Go to your **Profile Settings**.
1. Go to **Access Tokens**.
1. Choose a name and expiry date for the token.
1. Click on **Create Personal Access Token**.
1. Save the personal access token somewhere safe.
When using Git over HTTPS on the command line, enter the personal access token
into the password field.
## Recovery options ## Recovery options
To disable two-factor authentication on your account (for example, if you To disable two-factor authentication on your account (for example, if you
have lost your code generation device) you can: have lost your code generation device) you can:
* [Use a saved recovery code](#use-a-saved-recovery-code) * [Use a saved recovery code](#use-a-saved-recovery-code)
* [Generate new recovery codes using SSH](#generate-new-recovery-codes-using-ssh) * [Generate new recovery codes using SSH](#generate-new-recovery-codes-using-ssh)
* [Ask a GitLab administrator to disable two-factor authentication on your account](#ask-a-gitlab-administrator-to-disable-two-factor-authentication-on-your-account) * [Ask a GitLab administrator to disable two-factor authentication on your account](#ask-a-gitlab-administrator-to-disable-two-factor-authentication-on-your-account)
...@@ -154,8 +145,9 @@ codes. If you saved these codes, you can use one of them to sign in. ...@@ -154,8 +145,9 @@ codes. If you saved these codes, you can use one of them to sign in.
To use a recovery code, enter your username/email and password on the GitLab To use a recovery code, enter your username/email and password on the GitLab
sign-in page. When prompted for a two-factor code, enter the recovery code. sign-in page. When prompted for a two-factor code, enter the recovery code.
> **Note:** Once you use a recovery code, you cannot re-use it. You can still >**Note:**
use the other recovery codes you saved. Once you use a recovery code, you cannot re-use it. You can still use the other
recovery codes you saved.
### Generate new recovery codes using SSH ### Generate new recovery codes using SSH
...@@ -190,11 +182,14 @@ a new set of recovery codes with SSH. ...@@ -190,11 +182,14 @@ a new set of recovery codes with SSH.
two-factor code. Then, visit your Profile Settings and add a new device two-factor code. Then, visit your Profile Settings and add a new device
so you do not lose access to your account again. so you do not lose access to your account again.
``` ```
3. Go to the GitLab sign-in page and enter your username/email and password. When prompted for a two-factor code, enter one of the recovery codes obtained
from the command-line output.
> **Note:** After signing in, visit your **Profile Settings -> Account** immediately to set up two-factor authentication with a new 3. Go to the GitLab sign-in page and enter your username/email and password.
device. When prompted for a two-factor code, enter one of the recovery codes obtained
from the command-line output.
>**Note:**
After signing in, visit your **Profile settings > Account** immediately to set
up two-factor authentication with a new device.
### Ask a GitLab administrator to disable two-factor authentication on your account ### Ask a GitLab administrator to disable two-factor authentication on your account
...@@ -206,23 +201,23 @@ Sign in and re-enable two-factor authentication as soon as possible. ...@@ -206,23 +201,23 @@ Sign in and re-enable two-factor authentication as soon as possible.
## Note to GitLab administrators ## Note to GitLab administrators
- You need to take special care to that 2FA keeps working after - You need to take special care to that 2FA keeps working after
[restoring a GitLab backup](../../../raketasks/backup_restore.md). [restoring a GitLab backup](../../../raketasks/backup_restore.md).
- To ensure 2FA authorizes correctly with TOTP server, you may want to ensure - To ensure 2FA authorizes correctly with TOTP server, you may want to ensure
your GitLab server's time is synchronized via a service like NTP. Otherwise, your GitLab server's time is synchronized via a service like NTP. Otherwise,
you may have cases where authorization always fails because of time differences. you may have cases where authorization always fails because of time differences.
[Google Authenticator]: https://support.google.com/accounts/answer/1066447?hl=en
[FreeOTP]: https://freeotp.github.io/
[YubiKey]: https://www.yubico.com/products/yubikey-hardware/
- The GitLab U2F implementation does _not_ work when the GitLab instance is accessed from - The GitLab U2F implementation does _not_ work when the GitLab instance is accessed from
multiple hostnames, or FQDNs. Each U2F registration is linked to the _current hostname_ at multiple hostnames, or FQDNs. Each U2F registration is linked to the _current hostname_ at
the time of registration, and cannot be used for other hostnames/FQDNs. the time of registration, and cannot be used for other hostnames/FQDNs.
For example, if a user is trying to access a GitLab instance from `first.host.xyz` and `second.host.xyz`: For example, if a user is trying to access a GitLab instance from `first.host.xyz` and `second.host.xyz`:
- The user logs in via `first.host.xyz` and registers their U2F key. - The user logs in via `first.host.xyz` and registers their U2F key.
- The user logs out and attempts to log in via `first.host.xyz` - U2F authentication suceeds. - The user logs out and attempts to log in via `first.host.xyz` - U2F authentication suceeds.
- The user logs out and attempts to log in via `second.host.xyz` - U2F authentication fails, because - The user logs out and attempts to log in via `second.host.xyz` - U2F authentication fails, because
the U2F key has only been registered on `first.host.xyz`. the U2F key has only been registered on `first.host.xyz`.
[Google Authenticator]: https://support.google.com/accounts/answer/1066447?hl=en
[FreeOTP]: https://freeotp.github.io/
[YubiKey]: https://www.yubico.com/products/yubikey-hardware/
[api]: ../../../api/README.md
[pat]: ../personal_access_tokens.md
# Personal access tokens
> [Introduced][ce-3749] in GitLab 8.8.
Personal access tokens are useful if you need access to the [GitLab API][api].
Instead of using your private token which grants full access to your account,
personal access tokens could be a better fit because of their
[granular permissions](#limiting-scopes-of-a-personal-access-token).
You can also use them to authenticate against Git over HTTP. They are the only
accepted method of authentication when you have
[Two-Factor Authentication (2FA)][2fa] enabled.
Once you have your token, [pass it to the API][usage] using either the
`private_token` parameter or the `PRIVATE-TOKEN` header.
## Creating a personal access token
You can create as many personal access tokens as you like from your GitLab
profile.
1. Log in to your GitLab account.
1. Go to your **Profile settings**.
1. Go to **Access tokens**.
1. Choose a name and optionally an expiry date for the token.
1. Choose the [desired scopes](#limiting-scopes-of-a-personal-access-token).
1. Click on **Create personal access token**.
1. Save the personal access token somewhere safe. Once you leave or refresh
the page, you won't be able to access it again.
![Personal access tokens page](img/personal_access_tokens.png)
## Revoking a personal access token
At any time, you can revoke any personal access token by just clicking the
respective **Revoke** button under the 'Active personal access tokens' area.
## Limiting scopes of a personal access token
Personal access tokens can be created with one or more scopes that allow various
actions that a given token can perform. The available scopes are depicted in
the following table.
| Scope | Description |
| ----- | ----------- |
|`read_user` | Allows access to the read-only endpoints under `/users`. Essentially, any of the `GET` requests in the [Users API][users] are allowed ([introduced][ce-5951] in GitLab 8.15). |
| `api` | Grants complete access to the API (read/write) ([introduced][ce-5951] in GitLab 8.15). Required for accessing Git repositories over HTTP when 2FA is enabled. |
| `read_registry` | Allows to read [container registry] images if a project is private and authorization is required ([introduced][ce-11845] in GitLab 9.3). |
[2fa]: ../account/two_factor_authentication.md
[api]: ../../api/README.md
[ce-3749]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3749
[ce-5951]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5951
[ce-11845]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11845
[container registry]: ../project/container_registry.md
[users]: ../../api/users.md
[usage]: ../../api/README.md#basic-usage
...@@ -8,8 +8,8 @@ ...@@ -8,8 +8,8 @@
Registry across your GitLab instance, visit the Registry across your GitLab instance, visit the
[administrator documentation](../../administration/container_registry.md). [administrator documentation](../../administration/container_registry.md).
- Starting from GitLab 8.12, if you have 2FA enabled in your account, you need - Starting from GitLab 8.12, if you have 2FA enabled in your account, you need
to pass a personal access token instead of your password in order to login to to pass a [personal access token][pat] instead of your password in order to
GitLab's Container Registry. login to GitLab's Container Registry.
- Multiple level image names support was added in GitLab 9.1 - Multiple level image names support was added in GitLab 9.1
With the Docker Container Registry integrated into GitLab, every project can With the Docker Container Registry integrated into GitLab, every project can
...@@ -114,12 +114,11 @@ and [Using the GitLab Container Registry documentation](../../ci/docker/using_do ...@@ -114,12 +114,11 @@ and [Using the GitLab Container Registry documentation](../../ci/docker/using_do
## Using with private projects ## Using with private projects
If a project is private, credentials will need to be provided for authorization. > [Introduced][ce-11845] in GitLab 9.3.
The preferred way to do this, is by using personal access tokens, which can be
created under `/profile/personal_access_tokens`. The minimal scope needed is:
`read_registry`.
This feature was introduced in GitLab 9.3. If a project is private, credentials will need to be provided for authorization.
The preferred way to do this, is by using [personal access tokens][pat].
The minimal scope needed is `read_registry`.
## Troubleshooting the GitLab Container Registry ## Troubleshooting the GitLab Container Registry
...@@ -264,4 +263,6 @@ The solution: check the [IAM permissions again](https://docs.docker.com/registry ...@@ -264,4 +263,6 @@ The solution: check the [IAM permissions again](https://docs.docker.com/registry
Once the right permissions were set, the error will go away. Once the right permissions were set, the error will go away.
[ce-4040]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4040 [ce-4040]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4040
[ce-11845]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11845
[docker-docs]: https://docs.docker.com/engine/userguide/intro/ [docker-docs]: https://docs.docker.com/engine/userguide/intro/
[pat]: ../profile/personal_access_tokens.md
...@@ -212,9 +212,9 @@ Container Registries for private projects. ...@@ -212,9 +212,9 @@ Container Registries for private projects.
access token created explicitly for this purpose). This issue is resolved with access token created explicitly for this purpose). This issue is resolved with
latest changes in GitLab Runner 1.8 which receives GitLab credentials with latest changes in GitLab Runner 1.8 which receives GitLab credentials with
build data. build data.
- Starting with GitLab 8.12, if you have 2FA enabled in your account, you need - Starting from GitLab 8.12, if you have [2FA] enabled in your account, you need
to pass a personal access token instead of your password in order to login to to pass a [personal access token][pat] instead of your password in order to
GitLab's Container Registry. login to GitLab's Container Registry.
Your jobs can access all container images that you would normally have access Your jobs can access all container images that you would normally have access
to. The only implication is that you can push to the Container Registry of the to. The only implication is that you can push to the Container Registry of the
...@@ -239,3 +239,5 @@ test: ...@@ -239,3 +239,5 @@ test:
[update-docs]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update [update-docs]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update
[workhorse]: https://gitlab.com/gitlab-org/gitlab-workhorse [workhorse]: https://gitlab.com/gitlab-org/gitlab-workhorse
[jobenv]: ../../ci/variables/README.md#predefined-variables-environment-variables [jobenv]: ../../ci/variables/README.md#predefined-variables-environment-variables
[2fa]: ../profile/account/two_factor_authentication.md
[pat]: ../profile/personal_access_tokens.md
...@@ -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) |
......
...@@ -11,8 +11,10 @@ Capybara.register_driver :poltergeist do |app| ...@@ -11,8 +11,10 @@ Capybara.register_driver :poltergeist do |app|
js_errors: true, js_errors: true,
timeout: timeout, timeout: timeout,
window_size: [1366, 768], window_size: [1366, 768],
url_whitelist: %w[localhost 127.0.0.1],
url_blacklist: %w[.mp4 .png .gif .avi .bmp .jpg .jpeg],
phantomjs_options: [ phantomjs_options: [
'--load-images=no' '--load-images=yes'
] ]
) )
end end
......
...@@ -13,7 +13,7 @@ module Gitlab ...@@ -13,7 +13,7 @@ module Gitlab
base_config = { urls: config[:url] } base_config = { urls: config[:url] }
if config[:aws] if config[:aws]
creds = Aws::Credentials.new(config[:aws_access_key], config[:aws_secret_access_key]) creds = resolve_aws_credentials(config)
region = config[:aws_region] region = config[:aws_region]
::Elasticsearch::Client.new(base_config) do |fmid| ::Elasticsearch::Client.new(base_config) do |fmid|
...@@ -23,6 +23,19 @@ module Gitlab ...@@ -23,6 +23,19 @@ module Gitlab
::Elasticsearch::Client.new(base_config) ::Elasticsearch::Client.new(base_config)
end end
end end
def self.resolve_aws_credentials(config)
# Resolve credentials in order
# 1. Static config
# 2. ec2 instance profile
credentials = [
Aws::Credentials.new(config[:aws_access_key], config[:aws_secret_access_key]),
Aws::InstanceProfileCredentials.new
]
credentials.find do |creds|
creds&.set?
end
end
end end
end end
end end
...@@ -2,6 +2,8 @@ module Gitlab ...@@ -2,6 +2,8 @@ module Gitlab
module Geo module Geo
class HealthCheck class HealthCheck
def self.perform_checks def self.perform_checks
raise NotImplementedError.new('Geo is only compatible with PostgreSQL') unless Gitlab::Database.postgresql?
return '' unless Gitlab::Geo.secondary? return '' unless Gitlab::Geo.secondary?
return 'The Geo secondary role is disabled.' unless Gitlab::Geo.secondary_role_enabled? return 'The Geo secondary role is disabled.' unless Gitlab::Geo.secondary_role_enabled?
return 'The Geo database configuration file is missing.' unless self.geo_database_configured? return 'The Geo database configuration file is missing.' unless self.geo_database_configured?
...@@ -11,7 +13,8 @@ module Gitlab ...@@ -11,7 +13,8 @@ module Gitlab
migration_version = self.get_migration_version.to_i migration_version = self.get_migration_version.to_i
if database_version != migration_version if database_version != migration_version
"Current Geo database version (#{database_version}) does not match latest migration (#{migration_version})." "Current Geo database version (#{database_version}) does not match latest migration (#{migration_version}).\n"\
"You may have to run `gitlab-rake geo:db:migrate` as root on the secondary."
else else
'' ''
end end
......
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 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
module RuboCop
module Cop
module RSpec
# This cop checks for single-line hook blocks
#
# @example
#
# # bad
# before { do_something }
# after(:each) { undo_something }
#
# # good
# before do
# do_something
# end
#
# after(:each) do
# undo_something
# end
class SingleLineHook < RuboCop::Cop::RSpec::Cop
MESSAGE = "Don't use single-line hook blocks.".freeze
def_node_search :rspec_hook?, <<~PATTERN
(send nil {:after :around :before} ...)
PATTERN
def on_block(node)
return unless rspec_hook?(node)
return unless node.single_line?
add_offense(node, :expression, MESSAGE)
end
end
end
end
end
...@@ -12,3 +12,4 @@ require_relative 'cop/migration/remove_concurrent_index' ...@@ -12,3 +12,4 @@ require_relative 'cop/migration/remove_concurrent_index'
require_relative 'cop/migration/remove_index' require_relative 'cop/migration/remove_index'
require_relative 'cop/migration/reversible_add_column_with_default' require_relative 'cop/migration/reversible_add_column_with_default'
require_relative 'cop/migration/update_column_in_batches' require_relative 'cop/migration/update_column_in_batches'
require_relative 'cop/rspec/single_line_hook'
...@@ -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],
......
require 'spec_helper' require 'spec_helper'
describe Admin::GeoNodesController do describe Admin::GeoNodesController, :postgresql do
shared_examples 'unlicensed geo action' do shared_examples 'unlicensed geo action' do
it 'redirects to the license page' do it 'redirects to the license page' do
expect(response).to redirect_to(admin_license_path) expect(response).to redirect_to(admin_license_path)
......
...@@ -2,7 +2,10 @@ require 'spec_helper' ...@@ -2,7 +2,10 @@ require 'spec_helper'
describe Admin::IdentitiesController do describe Admin::IdentitiesController do
let(:admin) { create(:admin) } let(:admin) { create(:admin) }
before { sign_in(admin) }
before do
sign_in(admin)
end
describe 'UPDATE identity' do describe 'UPDATE identity' do
let(:user) { create(:omniauth_user, provider: 'ldapmain', extern_uid: 'uid=myuser,ou=people,dc=example,dc=com') } let(:user) { create(:omniauth_user, provider: 'ldapmain', extern_uid: 'uid=myuser,ou=people,dc=example,dc=com') }
......
...@@ -2,7 +2,10 @@ require 'spec_helper' ...@@ -2,7 +2,10 @@ require 'spec_helper'
describe Admin::LicensesController do describe Admin::LicensesController do
let(:admin) { create(:admin) } let(:admin) { create(:admin) }
before { sign_in(admin) }
before do
sign_in(admin)
end
describe 'Upload license' do describe 'Upload license' do
it 'redirects back when no license is entered/uploaded' do it 'redirects back when no license is entered/uploaded' do
......
...@@ -3,7 +3,9 @@ require 'spec_helper' ...@@ -3,7 +3,9 @@ require 'spec_helper'
describe Admin::ServicesController do describe Admin::ServicesController do
let(:admin) { create(:admin) } let(:admin) { create(:admin) }
before { sign_in(admin) } before do
sign_in(admin)
end
describe 'GET #edit' do describe 'GET #edit' do
let!(:project) { create(:empty_project) } let!(:project) { create(:empty_project) }
......
...@@ -216,6 +216,7 @@ describe AutocompleteController do ...@@ -216,6 +216,7 @@ describe AutocompleteController do
before do before do
sign_in(user) sign_in(user)
end end
<<<<<<< HEAD
it 'includes the author' do it 'includes the author' do
get(:users, author_id: non_member.id) get(:users, author_id: non_member.id)
...@@ -226,6 +227,18 @@ describe AutocompleteController do ...@@ -226,6 +227,18 @@ describe AutocompleteController do
it 'rejects non existent user ids' do it 'rejects non existent user ids' do
get(:users, author_id: 99999) get(:users, author_id: 99999)
=======
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)
>>>>>>> 9f999549099fb5b254a3892d3b88284c39a4e12d
expect(body.collect { |u| u['id'] }).not_to include(99999) expect(body.collect { |u| u['id'] }).not_to include(99999)
end end
end end
...@@ -240,7 +253,9 @@ describe AutocompleteController do ...@@ -240,7 +253,9 @@ describe AutocompleteController do
end end
context 'skip_users parameter included' do context 'skip_users parameter included' do
before { sign_in(user) } before do
sign_in(user)
end
it 'skips the user IDs passed' do it 'skips the user IDs passed' do
get(:users, skip_users: [user, user2].map(&:id)) get(:users, skip_users: [user, user2].map(&:id))
......
...@@ -16,10 +16,14 @@ describe Groups::GroupMembersController do ...@@ -16,10 +16,14 @@ describe Groups::GroupMembersController do
describe 'POST create' do describe 'POST create' do
let(:group_user) { create(:user) } let(:group_user) { create(:user) }
before { sign_in(user) } before do
sign_in(user)
end
context 'when user does not have enough rights' do context 'when user does not have enough rights' do
before { group.add_developer(user) } before do
group.add_developer(user)
end
it 'returns 403' do it 'returns 403' do
post :create, group_id: group, post :create, group_id: group,
...@@ -32,7 +36,9 @@ describe Groups::GroupMembersController do ...@@ -32,7 +36,9 @@ describe Groups::GroupMembersController do
end end
context 'when user has enough rights' do context 'when user has enough rights' do
before { group.add_owner(user) } before do
group.add_owner(user)
end
it 'adds user to members' do it 'adds user to members' do
post :create, group_id: group, post :create, group_id: group,
...@@ -59,7 +65,9 @@ describe Groups::GroupMembersController do ...@@ -59,7 +65,9 @@ describe Groups::GroupMembersController do
describe 'DELETE destroy' do describe 'DELETE destroy' do
let(:member) { create(:group_member, :developer, group: group) } let(:member) { create(:group_member, :developer, group: group) }
before { sign_in(user) } before do
sign_in(user)
end
context 'when member is not found' do context 'when member is not found' do
it 'returns 403' do it 'returns 403' do
...@@ -71,7 +79,9 @@ describe Groups::GroupMembersController do ...@@ -71,7 +79,9 @@ describe Groups::GroupMembersController do
context 'when member is found' do context 'when member is found' do
context 'when user does not have enough rights' do context 'when user does not have enough rights' do
before { group.add_developer(user) } before do
group.add_developer(user)
end
it 'returns 403' do it 'returns 403' do
delete :destroy, group_id: group, id: member delete :destroy, group_id: group, id: member
...@@ -82,7 +92,9 @@ describe Groups::GroupMembersController do ...@@ -82,7 +92,9 @@ describe Groups::GroupMembersController do
end end
context 'when user has enough rights' do context 'when user has enough rights' do
before { group.add_owner(user) } before do
group.add_owner(user)
end
it '[HTML] removes user from members' do it '[HTML] removes user from members' do
delete :destroy, group_id: group, id: member delete :destroy, group_id: group, id: member
...@@ -103,7 +115,9 @@ describe Groups::GroupMembersController do ...@@ -103,7 +115,9 @@ describe Groups::GroupMembersController do
end end
describe 'DELETE leave' do describe 'DELETE leave' do
before { sign_in(user) } before do
sign_in(user)
end
context 'when member is not found' do context 'when member is not found' do
it 'returns 404' do it 'returns 404' do
...@@ -115,7 +129,9 @@ describe Groups::GroupMembersController do ...@@ -115,7 +129,9 @@ describe Groups::GroupMembersController do
context 'when member is found' do context 'when member is found' do
context 'and is not an owner' do context 'and is not an owner' do
before { group.add_developer(user) } before do
group.add_developer(user)
end
it 'removes user from members' do it 'removes user from members' do
delete :leave, group_id: group delete :leave, group_id: group
...@@ -134,7 +150,9 @@ describe Groups::GroupMembersController do ...@@ -134,7 +150,9 @@ describe Groups::GroupMembersController do
end end
context 'and is an owner' do context 'and is an owner' do
before { group.add_owner(user) } before do
group.add_owner(user)
end
it 'cannot removes himself from the group' do it 'cannot removes himself from the group' do
delete :leave, group_id: group delete :leave, group_id: group
...@@ -144,7 +162,9 @@ describe Groups::GroupMembersController do ...@@ -144,7 +162,9 @@ describe Groups::GroupMembersController do
end end
context 'and is a requester' do context 'and is a requester' do
before { group.request_access(user) } before do
group.request_access(user)
end
it 'removes user from members' do it 'removes user from members' do
delete :leave, group_id: group delete :leave, group_id: group
...@@ -159,7 +179,9 @@ describe Groups::GroupMembersController do ...@@ -159,7 +179,9 @@ describe Groups::GroupMembersController do
end end
describe 'POST request_access' do describe 'POST request_access' do
before { sign_in(user) } before do
sign_in(user)
end
it 'creates a new GroupMember that is not a team member' do it 'creates a new GroupMember that is not a team member' do
post :request_access, group_id: group post :request_access, group_id: group
...@@ -174,7 +196,9 @@ describe Groups::GroupMembersController do ...@@ -174,7 +196,9 @@ describe Groups::GroupMembersController do
describe 'POST approve_access_request' do describe 'POST approve_access_request' do
let(:member) { create(:group_member, :access_request, group: group) } let(:member) { create(:group_member, :access_request, group: group) }
before { sign_in(user) } before do
sign_in(user)
end
context 'when member is not found' do context 'when member is not found' do
it 'returns 403' do it 'returns 403' do
...@@ -186,7 +210,9 @@ describe Groups::GroupMembersController do ...@@ -186,7 +210,9 @@ describe Groups::GroupMembersController do
context 'when member is found' do context 'when member is found' do
context 'when user does not have enough rights' do context 'when user does not have enough rights' do
before { group.add_developer(user) } before do
group.add_developer(user)
end
it 'returns 403' do it 'returns 403' do
post :approve_access_request, group_id: group, id: member post :approve_access_request, group_id: group, id: member
...@@ -197,7 +223,9 @@ describe Groups::GroupMembersController do ...@@ -197,7 +223,9 @@ describe Groups::GroupMembersController do
end end
context 'when user has enough rights' do context 'when user has enough rights' do
before { group.add_owner(user) } before do
group.add_owner(user)
end
it 'adds user to members' do it 'adds user to members' do
post :approve_access_request, group_id: group, id: member post :approve_access_request, group_id: group, id: member
......
...@@ -94,7 +94,10 @@ describe NotificationSettingsController do ...@@ -94,7 +94,10 @@ describe NotificationSettingsController do
context 'not authorized' do context 'not authorized' do
let(:private_project) { create(:empty_project, :private) } let(:private_project) { create(:empty_project, :private) }
before { sign_in(user) }
before do
sign_in(user)
end
it 'returns 404' do it 'returns 404' do
post :create, post :create,
...@@ -120,7 +123,9 @@ describe NotificationSettingsController do ...@@ -120,7 +123,9 @@ describe NotificationSettingsController do
end end
context 'when authorized' do context 'when authorized' do
before{ sign_in(user) } before do
sign_in(user)
end
it 'returns success' do it 'returns success' do
put :update, put :update,
...@@ -152,7 +157,9 @@ describe NotificationSettingsController do ...@@ -152,7 +157,9 @@ describe NotificationSettingsController do
context 'not authorized' do context 'not authorized' do
let(:other_user) { create(:user) } let(:other_user) { create(:user) }
before { sign_in(other_user) } before do
sign_in(other_user)
end
it 'returns 404' do it 'returns 404' do
put :update, put :update,
......
...@@ -4,7 +4,9 @@ describe Profiles::PersonalAccessTokensController do ...@@ -4,7 +4,9 @@ describe Profiles::PersonalAccessTokensController do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:token_attributes) { attributes_for(:personal_access_token) } let(:token_attributes) { attributes_for(:personal_access_token) }
before { sign_in(user) } before do
sign_in(user)
end
describe '#create' do describe '#create' do
def created_token def created_token
...@@ -38,7 +40,9 @@ describe Profiles::PersonalAccessTokensController do ...@@ -38,7 +40,9 @@ describe Profiles::PersonalAccessTokensController do
let!(:inactive_personal_access_token) { create(:personal_access_token, :revoked, user: user) } let!(:inactive_personal_access_token) { create(:personal_access_token, :revoked, user: user) }
let!(:impersonation_personal_access_token) { create(:personal_access_token, :impersonation, user: user) } let!(:impersonation_personal_access_token) { create(:personal_access_token, :impersonation, user: user) }
before { get :index } before do
get :index
end
it "retrieves active personal access tokens" do it "retrieves active personal access tokens" do
expect(assigns(:active_personal_access_tokens)).to include(active_personal_access_token) expect(assigns(:active_personal_access_tokens)).to include(active_personal_access_token)
......
...@@ -281,7 +281,9 @@ describe Projects::CommitController do ...@@ -281,7 +281,9 @@ describe Projects::CommitController do
end end
context 'when the path does not exist in the diff' do context 'when the path does not exist in the diff' do
before { diff_for_path(id: commit.id, old_path: existing_path.succ, new_path: existing_path.succ) } before do
diff_for_path(id: commit.id, old_path: existing_path.succ, new_path: existing_path.succ)
end
it 'returns a 404' do it 'returns a 404' do
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
...@@ -302,7 +304,9 @@ describe Projects::CommitController do ...@@ -302,7 +304,9 @@ describe Projects::CommitController do
end end
context 'when the commit does not exist' do context 'when the commit does not exist' do
before { diff_for_path(id: commit.id.succ, old_path: existing_path, new_path: existing_path) } before do
diff_for_path(id: commit.id.succ, old_path: existing_path, new_path: existing_path)
end
it 'returns a 404' do it 'returns a 404' do
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
......
...@@ -128,7 +128,9 @@ describe Projects::CompareController do ...@@ -128,7 +128,9 @@ describe Projects::CompareController do
end end
context 'when the path does not exist in the diff' do context 'when the path does not exist in the diff' do
before { diff_for_path(from: ref_from, to: ref_to, old_path: existing_path.succ, new_path: existing_path.succ) } before do
diff_for_path(from: ref_from, to: ref_to, old_path: existing_path.succ, new_path: existing_path.succ)
end
it 'returns a 404' do it 'returns a 404' do
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
...@@ -149,7 +151,9 @@ describe Projects::CompareController do ...@@ -149,7 +151,9 @@ describe Projects::CompareController do
end end
context 'when the from ref does not exist' do context 'when the from ref does not exist' do
before { diff_for_path(from: ref_from.succ, to: ref_to, old_path: existing_path, new_path: existing_path) } before do
diff_for_path(from: ref_from.succ, to: ref_to, old_path: existing_path, new_path: existing_path)
end
it 'returns a 404' do it 'returns a 404' do
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
...@@ -157,7 +161,9 @@ describe Projects::CompareController do ...@@ -157,7 +161,9 @@ describe Projects::CompareController do
end end
context 'when the to ref does not exist' do context 'when the to ref does not exist' do
before { diff_for_path(from: ref_from, to: ref_to.succ, old_path: existing_path, new_path: existing_path) } before do
diff_for_path(from: ref_from, to: ref_to.succ, old_path: existing_path, new_path: existing_path)
end
it 'returns a 404' do it 'returns a 404' do
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
......
...@@ -14,7 +14,9 @@ describe Projects::ForksController do ...@@ -14,7 +14,9 @@ describe Projects::ForksController do
end end
context 'when fork is public' do context 'when fork is public' do
before { forked_project.update_attribute(:visibility_level, Project::PUBLIC) } before do
forked_project.update_attribute(:visibility_level, Project::PUBLIC)
end
it 'is visible for non logged in users' do it 'is visible for non logged in users' do
get_forks get_forks
...@@ -35,7 +37,9 @@ describe Projects::ForksController do ...@@ -35,7 +37,9 @@ describe Projects::ForksController do
end end
context 'when user is logged in' do context 'when user is logged in' do
before { sign_in(project.creator) } before do
sign_in(project.creator)
end
context 'when user is not a Project member neither a group member' do context 'when user is not a Project member neither a group member' do
it 'does not see the Project listed' do it 'does not see the Project listed' do
...@@ -46,7 +50,9 @@ describe Projects::ForksController do ...@@ -46,7 +50,9 @@ describe Projects::ForksController do
end end
context 'when user is a member of the Project' do context 'when user is a member of the Project' do
before { forked_project.team << [project.creator, :developer] } before do
forked_project.team << [project.creator, :developer]
end
it 'sees the project listed' do it 'sees the project listed' do
get_forks get_forks
...@@ -56,7 +62,9 @@ describe Projects::ForksController do ...@@ -56,7 +62,9 @@ describe Projects::ForksController do
end end
context 'when user is a member of the Group' do context 'when user is a member of the Group' do
before { forked_project.group.add_developer(project.creator) } before do
forked_project.group.add_developer(project.creator)
end
it 'sees the project listed' do it 'sees the project listed' do
get_forks get_forks
......
...@@ -22,7 +22,10 @@ describe Projects::GroupLinksController do ...@@ -22,7 +22,10 @@ describe Projects::GroupLinksController do
end end
context 'when user has access to group he want to link project to' do context 'when user has access to group he want to link project to' do
before { group.add_developer(user) } before do
group.add_developer(user)
end
include_context 'link project to group' include_context 'link project to group'
it 'links project with selected group' do it 'links project with selected group' do
......
...@@ -212,7 +212,9 @@ describe Projects::IssuesController do ...@@ -212,7 +212,9 @@ describe Projects::IssuesController do
let(:another_project) { create(:empty_project, :private) } let(:another_project) { create(:empty_project, :private) }
context 'when user has access to move issue' do context 'when user has access to move issue' do
before { another_project.team << [user, :reporter] } before do
another_project.team << [user, :reporter]
end
it 'moves issue to another project' do it 'moves issue to another project' do
move_issue move_issue
...@@ -250,14 +252,18 @@ describe Projects::IssuesController do ...@@ -250,14 +252,18 @@ describe Projects::IssuesController do
end end
context 'when an issue is identified as spam' do context 'when an issue is identified as spam' do
before { allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true) } before do
allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true)
end
context 'when captcha is not verified' do context 'when captcha is not verified' do
def update_spam_issue def update_spam_issue
update_issue(title: 'Spam Title', description: 'Spam lives here') update_issue(title: 'Spam Title', description: 'Spam lives here')
end end
before { allow_any_instance_of(described_class).to receive(:verify_recaptcha).and_return(false) } before do
allow_any_instance_of(described_class).to receive(:verify_recaptcha).and_return(false)
end
it 'rejects an issue recognized as a spam' do it 'rejects an issue recognized as a spam' do
expect { update_spam_issue }.not_to change{ issue.reload.title } expect { update_spam_issue }.not_to change{ issue.reload.title }
...@@ -619,14 +625,18 @@ describe Projects::IssuesController do ...@@ -619,14 +625,18 @@ describe Projects::IssuesController do
end end
context 'when an issue is identified as spam' do context 'when an issue is identified as spam' do
before { allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true) } before do
allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true)
end
context 'when captcha is not verified' do context 'when captcha is not verified' do
def post_spam_issue def post_spam_issue
post_new_issue(title: 'Spam Title', description: 'Spam lives here') post_new_issue(title: 'Spam Title', description: 'Spam lives here')
end end
before { allow_any_instance_of(described_class).to receive(:verify_recaptcha).and_return(false) } before do
allow_any_instance_of(described_class).to receive(:verify_recaptcha).and_return(false)
end
it 'rejects an issue recognized as a spam' do it 'rejects an issue recognized as a spam' do
expect { post_spam_issue }.not_to change(Issue, :count) expect { post_spam_issue }.not_to change(Issue, :count)
...@@ -738,7 +748,10 @@ describe Projects::IssuesController do ...@@ -738,7 +748,10 @@ describe Projects::IssuesController do
describe "DELETE #destroy" do describe "DELETE #destroy" do
context "when the user is a developer" do context "when the user is a developer" do
before { sign_in(user) } before do
sign_in(user)
end
it "rejects a developer to destroy an issue" do it "rejects a developer to destroy an issue" do
delete :destroy, namespace_id: project.namespace, project_id: project, id: issue.iid delete :destroy, namespace_id: project.namespace, project_id: project, id: issue.iid
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
...@@ -750,7 +763,9 @@ describe Projects::IssuesController do ...@@ -750,7 +763,9 @@ describe Projects::IssuesController do
let(:namespace) { create(:namespace, owner: owner) } let(:namespace) { create(:namespace, owner: owner) }
let(:project) { create(:empty_project, namespace: namespace) } let(:project) { create(:empty_project, namespace: namespace) }
before { sign_in(owner) } before do
sign_in(owner)
end
it "deletes the issue" do it "deletes the issue" do
delete :destroy, namespace_id: project.namespace, project_id: project, id: issue.iid delete :destroy, namespace_id: project.namespace, project_id: project, id: issue.iid
......
...@@ -69,18 +69,11 @@ describe Projects::JobsController do ...@@ -69,18 +69,11 @@ 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(6).of(8)
end end
def create_build(name, status) def create_build(name, status)
......
...@@ -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)
......
...@@ -16,10 +16,14 @@ describe Projects::ProjectMembersController do ...@@ -16,10 +16,14 @@ describe Projects::ProjectMembersController do
describe 'POST create' do describe 'POST create' do
let(:project_user) { create(:user) } let(:project_user) { create(:user) }
before { sign_in(user) } before do
sign_in(user)
end
context 'when user does not have enough rights' do context 'when user does not have enough rights' do
before { project.team << [user, :developer] } before do
project.team << [user, :developer]
end
it 'returns 404' do it 'returns 404' do
post :create, namespace_id: project.namespace, post :create, namespace_id: project.namespace,
...@@ -33,7 +37,9 @@ describe Projects::ProjectMembersController do ...@@ -33,7 +37,9 @@ describe Projects::ProjectMembersController do
end end
context 'when user has enough rights' do context 'when user has enough rights' do
before { project.team << [user, :master] } before do
project.team << [user, :master]
end
it 'adds user to members' do it 'adds user to members' do
expect_any_instance_of(Members::CreateService).to receive(:execute).and_return(status: :success) expect_any_instance_of(Members::CreateService).to receive(:execute).and_return(status: :success)
...@@ -64,7 +70,9 @@ describe Projects::ProjectMembersController do ...@@ -64,7 +70,9 @@ describe Projects::ProjectMembersController do
describe 'DELETE destroy' do describe 'DELETE destroy' do
let(:member) { create(:project_member, :developer, project: project) } let(:member) { create(:project_member, :developer, project: project) }
before { sign_in(user) } before do
sign_in(user)
end
context 'when member is not found' do context 'when member is not found' do
it 'returns 404' do it 'returns 404' do
...@@ -78,7 +86,9 @@ describe Projects::ProjectMembersController do ...@@ -78,7 +86,9 @@ describe Projects::ProjectMembersController do
context 'when member is found' do context 'when member is found' do
context 'when user does not have enough rights' do context 'when user does not have enough rights' do
before { project.team << [user, :developer] } before do
project.team << [user, :developer]
end
it 'returns 404' do it 'returns 404' do
delete :destroy, namespace_id: project.namespace, delete :destroy, namespace_id: project.namespace,
...@@ -91,7 +101,9 @@ describe Projects::ProjectMembersController do ...@@ -91,7 +101,9 @@ describe Projects::ProjectMembersController do
end end
context 'when user has enough rights' do context 'when user has enough rights' do
before { project.team << [user, :master] } before do
project.team << [user, :master]
end
it '[HTML] removes user from members' do it '[HTML] removes user from members' do
delete :destroy, namespace_id: project.namespace, delete :destroy, namespace_id: project.namespace,
...@@ -117,7 +129,9 @@ describe Projects::ProjectMembersController do ...@@ -117,7 +129,9 @@ describe Projects::ProjectMembersController do
end end
describe 'DELETE leave' do describe 'DELETE leave' do
before { sign_in(user) } before do
sign_in(user)
end
context 'when member is not found' do context 'when member is not found' do
it 'returns 404' do it 'returns 404' do
...@@ -130,7 +144,9 @@ describe Projects::ProjectMembersController do ...@@ -130,7 +144,9 @@ describe Projects::ProjectMembersController do
context 'when member is found' do context 'when member is found' do
context 'and is not an owner' do context 'and is not an owner' do
before { project.team << [user, :developer] } before do
project.team << [user, :developer]
end
it 'removes user from members' do it 'removes user from members' do
delete :leave, namespace_id: project.namespace, delete :leave, namespace_id: project.namespace,
...@@ -145,7 +161,9 @@ describe Projects::ProjectMembersController do ...@@ -145,7 +161,9 @@ describe Projects::ProjectMembersController do
context 'and is an owner' do context 'and is an owner' do
let(:project) { create(:empty_project, namespace: user.namespace) } let(:project) { create(:empty_project, namespace: user.namespace) }
before { project.team << [user, :master] } before do
project.team << [user, :master]
end
it 'does not remove himself from the project' do it 'does not remove himself from the project' do
delete :leave, namespace_id: project.namespace, delete :leave, namespace_id: project.namespace,
...@@ -156,7 +174,9 @@ describe Projects::ProjectMembersController do ...@@ -156,7 +174,9 @@ describe Projects::ProjectMembersController do
end end
context 'and is a requester' do context 'and is a requester' do
before { project.request_access(user) } before do
project.request_access(user)
end
it 'removes user from members' do it 'removes user from members' do
delete :leave, namespace_id: project.namespace, delete :leave, namespace_id: project.namespace,
...@@ -172,7 +192,9 @@ describe Projects::ProjectMembersController do ...@@ -172,7 +192,9 @@ describe Projects::ProjectMembersController do
end end
describe 'POST request_access' do describe 'POST request_access' do
before { sign_in(user) } before do
sign_in(user)
end
it 'creates a new ProjectMember that is not a team member' do it 'creates a new ProjectMember that is not a team member' do
post :request_access, namespace_id: project.namespace, post :request_access, namespace_id: project.namespace,
...@@ -190,7 +212,9 @@ describe Projects::ProjectMembersController do ...@@ -190,7 +212,9 @@ describe Projects::ProjectMembersController do
describe 'POST approve' do describe 'POST approve' do
let(:member) { create(:project_member, :access_request, project: project) } let(:member) { create(:project_member, :access_request, project: project) }
before { sign_in(user) } before do
sign_in(user)
end
context 'when member is not found' do context 'when member is not found' do
it 'returns 404' do it 'returns 404' do
...@@ -204,7 +228,9 @@ describe Projects::ProjectMembersController do ...@@ -204,7 +228,9 @@ describe Projects::ProjectMembersController do
context 'when member is found' do context 'when member is found' do
context 'when user does not have enough rights' do context 'when user does not have enough rights' do
before { project.team << [user, :developer] } before do
project.team << [user, :developer]
end
it 'returns 404' do it 'returns 404' do
post :approve_access_request, namespace_id: project.namespace, post :approve_access_request, namespace_id: project.namespace,
...@@ -217,7 +243,9 @@ describe Projects::ProjectMembersController do ...@@ -217,7 +243,9 @@ describe Projects::ProjectMembersController do
end end
context 'when user has enough rights' do context 'when user has enough rights' do
before { project.team << [user, :master] } before do
project.team << [user, :master]
end
it 'adds user to members' do it 'adds user to members' do
post :approve_access_request, namespace_id: project.namespace, post :approve_access_request, namespace_id: project.namespace,
...@@ -252,7 +280,10 @@ describe Projects::ProjectMembersController do ...@@ -252,7 +280,10 @@ describe Projects::ProjectMembersController do
end end
context 'when user can access source project members' do context 'when user can access source project members' do
before { another_project.team << [user, :guest] } before do
another_project.team << [user, :guest]
end
include_context 'import applied' include_context 'import applied'
it 'imports source project members' do it 'imports source project members' do
......
...@@ -46,7 +46,9 @@ describe Projects::SnippetsController do ...@@ -46,7 +46,9 @@ describe Projects::SnippetsController do
end end
context 'when signed in as the author' do context 'when signed in as the author' do
before { sign_in(user) } before do
sign_in(user)
end
it 'renders the snippet' do it 'renders the snippet' do
get :index, namespace_id: project.namespace, project_id: project get :index, namespace_id: project.namespace, project_id: project
...@@ -57,7 +59,9 @@ describe Projects::SnippetsController do ...@@ -57,7 +59,9 @@ describe Projects::SnippetsController do
end end
context 'when signed in as a project member' do context 'when signed in as a project member' do
before { sign_in(user2) } before do
sign_in(user2)
end
it 'renders the snippet' do it 'renders the snippet' do
get :index, namespace_id: project.namespace, project_id: project get :index, namespace_id: project.namespace, project_id: project
...@@ -317,7 +321,9 @@ describe Projects::SnippetsController do ...@@ -317,7 +321,9 @@ describe Projects::SnippetsController do
end end
context 'when signed in as the author' do context 'when signed in as the author' do
before { sign_in(user) } before do
sign_in(user)
end
it 'renders the snippet' do it 'renders the snippet' do
get action, namespace_id: project.namespace, project_id: project, id: project_snippet.to_param get action, namespace_id: project.namespace, project_id: project, id: project_snippet.to_param
...@@ -328,7 +334,9 @@ describe Projects::SnippetsController do ...@@ -328,7 +334,9 @@ describe Projects::SnippetsController do
end end
context 'when signed in as a project member' do context 'when signed in as a project member' do
before { sign_in(user2) } before do
sign_in(user2)
end
it 'renders the snippet' do it 'renders the snippet' do
get action, namespace_id: project.namespace, project_id: project, id: project_snippet.to_param get action, namespace_id: project.namespace, project_id: project, id: project_snippet.to_param
...@@ -349,7 +357,9 @@ describe Projects::SnippetsController do ...@@ -349,7 +357,9 @@ describe Projects::SnippetsController do
end end
context 'when signed in' do context 'when signed in' do
before { sign_in(user) } before do
sign_in(user)
end
it 'responds with status 404' do it 'responds with status 404' do
get action, namespace_id: project.namespace, project_id: project, id: 42 get action, namespace_id: project.namespace, project_id: project, id: 42
......
...@@ -6,7 +6,9 @@ describe Projects::TagsController do ...@@ -6,7 +6,9 @@ describe Projects::TagsController do
let!(:invalid_release) { create(:release, project: project, tag: 'does-not-exist') } let!(:invalid_release) { create(:release, project: project, tag: 'does-not-exist') }
describe 'GET index' do describe 'GET index' do
before { get :index, namespace_id: project.namespace.to_param, project_id: project } before do
get :index, namespace_id: project.namespace.to_param, project_id: project
end
it 'returns the tags for the page' do it 'returns the tags for the page' do
expect(assigns(:tags).map(&:name)).to eq(['v1.1.0', 'v1.0.0']) expect(assigns(:tags).map(&:name)).to eq(['v1.1.0', 'v1.0.0'])
...@@ -19,7 +21,9 @@ describe Projects::TagsController do ...@@ -19,7 +21,9 @@ describe Projects::TagsController do
end end
describe 'GET show' do describe 'GET show' do
before { get :show, namespace_id: project.namespace.to_param, project_id: project, id: id } before do
get :show, namespace_id: project.namespace.to_param, project_id: project, id: id
end
context "valid tag" do context "valid tag" do
let(:id) { 'v1.0.0' } let(:id) { 'v1.0.0' }
......
...@@ -29,7 +29,9 @@ describe ProjectsController do ...@@ -29,7 +29,9 @@ describe ProjectsController do
describe "GET show" do describe "GET show" do
context "user not project member" do context "user not project member" do
before { sign_in(user) } before do
sign_in(user)
end
context "user does not have access to project" do context "user does not have access to project" do
let(:private_project) { create(:empty_project, :private) } let(:private_project) { create(:empty_project, :private) }
...@@ -108,7 +110,9 @@ describe ProjectsController do ...@@ -108,7 +110,9 @@ describe ProjectsController do
context "project with empty repo" do context "project with empty repo" do
let(:empty_project) { create(:project_empty_repo, :public) } let(:empty_project) { create(:project_empty_repo, :public) }
before { sign_in(user) } before do
sign_in(user)
end
User.project_views.keys.each do |project_view| User.project_views.keys.each do |project_view|
context "with #{project_view} view set" do context "with #{project_view} view set" do
...@@ -128,7 +132,9 @@ describe ProjectsController do ...@@ -128,7 +132,9 @@ describe ProjectsController do
context "project with broken repo" do context "project with broken repo" do
let(:empty_project) { create(:project_broken_repo, :public) } let(:empty_project) { create(:project_broken_repo, :public) }
before { sign_in(user) } before do
sign_in(user)
end
User.project_views.keys.each do |project_view| User.project_views.keys.each do |project_view|
context "with #{project_view} view set" do context "with #{project_view} view set" do
......
...@@ -18,7 +18,9 @@ describe SearchController do ...@@ -18,7 +18,9 @@ describe SearchController do
context 'on restricted projects' do context 'on restricted projects' do
context 'when signed out' do context 'when signed out' do
before { sign_out(user) } before do
sign_out(user)
end
it "doesn't expose comments on issues" do it "doesn't expose comments on issues" do
project = create(:empty_project, :public, :issues_private) project = create(:empty_project, :public, :issues_private)
......
...@@ -14,7 +14,9 @@ describe SentNotificationsController, type: :controller do ...@@ -14,7 +14,9 @@ describe SentNotificationsController, type: :controller do
describe 'GET unsubscribe' do describe 'GET unsubscribe' do
context 'when the user is not logged in' do context 'when the user is not logged in' do
context 'when the force param is passed' do context 'when the force param is passed' do
before { get(:unsubscribe, id: sent_notification.reply_key, force: true) } before do
get(:unsubscribe, id: sent_notification.reply_key, force: true)
end
it 'unsubscribes the user' do it 'unsubscribes the user' do
expect(issue.subscribed?(user, project)).to be_falsey expect(issue.subscribed?(user, project)).to be_falsey
...@@ -30,7 +32,9 @@ describe SentNotificationsController, type: :controller do ...@@ -30,7 +32,9 @@ describe SentNotificationsController, type: :controller do
end end
context 'when the force param is not passed' do context 'when the force param is not passed' do
before { get(:unsubscribe, id: sent_notification.reply_key) } before do
get(:unsubscribe, id: sent_notification.reply_key)
end
it 'does not unsubscribe the user' do it 'does not unsubscribe the user' do
expect(issue.subscribed?(user, project)).to be_truthy expect(issue.subscribed?(user, project)).to be_truthy
...@@ -47,10 +51,14 @@ describe SentNotificationsController, type: :controller do ...@@ -47,10 +51,14 @@ describe SentNotificationsController, type: :controller do
end end
context 'when the user is logged in' do context 'when the user is logged in' do
before { sign_in(user) } before do
sign_in(user)
end
context 'when the ID passed does not exist' do context 'when the ID passed does not exist' do
before { get(:unsubscribe, id: sent_notification.reply_key.reverse) } before do
get(:unsubscribe, id: sent_notification.reply_key.reverse)
end
it 'does not unsubscribe the user' do it 'does not unsubscribe the user' do
expect(issue.subscribed?(user, project)).to be_truthy expect(issue.subscribed?(user, project)).to be_truthy
...@@ -66,7 +74,9 @@ describe SentNotificationsController, type: :controller do ...@@ -66,7 +74,9 @@ describe SentNotificationsController, type: :controller do
end end
context 'when the force param is passed' do context 'when the force param is passed' do
before { get(:unsubscribe, id: sent_notification.reply_key, force: true) } before do
get(:unsubscribe, id: sent_notification.reply_key, force: true)
end
it 'unsubscribes the user' do it 'unsubscribes the user' do
expect(issue.subscribed?(user, project)).to be_falsey expect(issue.subscribed?(user, project)).to be_falsey
...@@ -89,7 +99,10 @@ describe SentNotificationsController, type: :controller do ...@@ -89,7 +99,10 @@ describe SentNotificationsController, type: :controller do
end end
end end
let(:sent_notification) { create(:sent_notification, project: project, noteable: merge_request, recipient: user) } let(:sent_notification) { create(:sent_notification, project: project, noteable: merge_request, recipient: user) }
before { get(:unsubscribe, id: sent_notification.reply_key) }
before do
get(:unsubscribe, id: sent_notification.reply_key)
end
it 'unsubscribes the user' do it 'unsubscribes the user' do
expect(merge_request.subscribed?(user, project)).to be_falsey expect(merge_request.subscribed?(user, project)).to be_falsey
......
...@@ -142,7 +142,9 @@ describe SessionsController do ...@@ -142,7 +142,9 @@ describe SessionsController do
end end
context 'when OTP is invalid' do context 'when OTP is invalid' do
before { authenticate_2fa(otp_attempt: 'invalid') } before do
authenticate_2fa(otp_attempt: 'invalid')
end
it 'does not authenticate' do it 'does not authenticate' do
expect(subject.current_user).not_to eq user expect(subject.current_user).not_to eq user
...@@ -169,7 +171,9 @@ describe SessionsController do ...@@ -169,7 +171,9 @@ describe SessionsController do
end end
context 'when OTP is invalid' do context 'when OTP is invalid' do
before { authenticate_2fa(otp_attempt: 'invalid') } before do
authenticate_2fa(otp_attempt: 'invalid')
end
it 'does not authenticate' do it 'does not authenticate' do
expect(subject.current_user).not_to eq user expect(subject.current_user).not_to eq user
......
...@@ -437,7 +437,9 @@ describe SnippetsController do ...@@ -437,7 +437,9 @@ describe SnippetsController do
end end
context 'when signed in user is the author' do context 'when signed in user is the author' do
before { get :raw, id: personal_snippet.to_param } before do
get :raw, id: personal_snippet.to_param
end
it 'responds with status 200' do it 'responds with status 200' do
expect(assigns(:snippet)).to eq(personal_snippet) expect(assigns(:snippet)).to eq(personal_snippet)
......
...@@ -43,7 +43,9 @@ describe UsersController do ...@@ -43,7 +43,9 @@ describe UsersController do
end end
context 'when logged in' do context 'when logged in' do
before { sign_in(user) } before do
sign_in(user)
end
it 'renders show' do it 'renders show' do
get :show, username: user.username get :show, username: user.username
...@@ -62,7 +64,9 @@ describe UsersController do ...@@ -62,7 +64,9 @@ describe UsersController do
end end
context 'when logged in' do context 'when logged in' do
before { sign_in(user) } before do
sign_in(user)
end
it 'renders 404' do it 'renders 404' do
get :show, username: 'nonexistent' get :show, username: 'nonexistent'
......
FactoryGirl.define do
factory :issue_link do
source factory: :issue
target factory: :issue
end
end
...@@ -134,7 +134,10 @@ describe "Admin Runners" do ...@@ -134,7 +134,10 @@ describe "Admin Runners" do
describe 'runners registration token' do describe 'runners registration token' do
let!(:token) { current_application_settings.runners_registration_token } let!(:token) { current_application_settings.runners_registration_token }
before { visit admin_runners_path }
before do
visit admin_runners_path
end
it 'has a registration token' do it 'has a registration token' do
expect(page).to have_content("Registration token is #{token}") expect(page).to have_content("Registration token is #{token}")
......
...@@ -12,7 +12,9 @@ describe 'Admin > Users > Impersonation Tokens', feature: true, js: true do ...@@ -12,7 +12,9 @@ describe 'Admin > Users > Impersonation Tokens', feature: true, js: true do
find(".table.inactive-tokens") find(".table.inactive-tokens")
end end
before { login_as(admin) } before do
login_as(admin)
end
describe "token creation" do describe "token creation" do
it "allows creation of a token" do it "allows creation of a token" do
......
...@@ -124,7 +124,10 @@ describe "Admin::Users", feature: true do ...@@ -124,7 +124,10 @@ describe "Admin::Users", feature: true do
describe 'Impersonation' do describe 'Impersonation' do
let(:another_user) { create(:user) } let(:another_user) { create(:user) }
before { visit admin_user_path(another_user) }
before do
visit admin_user_path(another_user)
end
context 'before impersonating' do context 'before impersonating' do
it 'shows impersonate button for other users' do it 'shows impersonate button for other users' do
...@@ -149,7 +152,9 @@ describe "Admin::Users", feature: true do ...@@ -149,7 +152,9 @@ describe "Admin::Users", feature: true do
end end
context 'when impersonating' do context 'when impersonating' do
before { click_link 'Impersonate' } before do
click_link 'Impersonate'
end
it 'logs in as the user when impersonate is clicked' do it 'logs in as the user when impersonate is clicked' do
expect(page.find(:css, '.header-user .profile-link')['data-user']).to eql(another_user.username) expect(page.find(:css, '.header-user .profile-link')['data-user']).to eql(another_user.username)
......
...@@ -12,7 +12,9 @@ describe 'Dashboard Merge Requests' do ...@@ -12,7 +12,9 @@ describe 'Dashboard Merge Requests' do
end end
describe 'new merge request dropdown' do describe 'new merge request dropdown' do
before { visit merge_requests_dashboard_path } before do
visit merge_requests_dashboard_path
end
it 'shows projects only with merge requests feature enabled', js: true do it 'shows projects only with merge requests feature enabled', js: true do
find('.new-project-item-select-button').trigger('click') find('.new-project-item-select-button').trigger('click')
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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