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

Merge remote-tracking branch 'upstream/master' into 30634-protected-pipeline

* upstream/master: (67 commits)
  Revert "Merge branch 'revert-12499' into 'master'"
  Prevent accidental deletion of protected MR source branch by repeating checks before actual deletion
  Document that GitLab 9.3 requires the TRIGGER permission on MySQL
  Instrument Unicorn with Ruby exporter
  Remove group modal like remove project modal. Closes #33130
  Update prometheus client gem
  Enables the option in user preferences to turn on the new navigation
  Simplify authentication logic in the v4 users API for !12445.
  wait_for_requests is not needed when AJAX is not in play
  Don't resolve fork relationships for projects pending delete
  Clean up the ForkedProjectLink specs
  Remove unnecessary clear_stubs calls
  Add test for GitalyClient::Ref#find_ref_name
  DeleteMergedBranchesService should not delete protected branches
  Optimize creation of commit API by using Repository#commit instead of Repository#commits
  Update CHANGELOG.md for 9.3.4
  Make autosize fields more performant and remove broken autosize handle
  Update GITLAB_SHELL_VERSION to 5.1.1
  Fixed the y_label not setting correctly for each graph on the monitoring dashboard
  Refactor and copyedit "Using Docker images" docs
  ...
parents 2afa90b6 5af1fcd6
...@@ -2,6 +2,10 @@ ...@@ -2,6 +2,10 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. entry.
## 9.3.4 (2017-07-03)
- No changes.
## 9.3.3 (2017-06-30) ## 9.3.3 (2017-06-30)
- Fix head pipeline stored in merge request for external pipelines. !12478 - Fix head pipeline stored in merge request for external pipelines. !12478
......
...@@ -285,6 +285,7 @@ group :metrics do ...@@ -285,6 +285,7 @@ group :metrics do
# Prometheus # Prometheus
gem 'prometheus-client-mmap', '~>0.7.0.beta5' gem 'prometheus-client-mmap', '~>0.7.0.beta5'
gem 'raindrops', '~> 0.18'
end end
group :development do group :development do
......
...@@ -599,8 +599,8 @@ GEM ...@@ -599,8 +599,8 @@ GEM
premailer-rails (1.9.7) premailer-rails (1.9.7)
actionmailer (>= 3, < 6) actionmailer (>= 3, < 6)
premailer (~> 1.7, >= 1.7.9) premailer (~> 1.7, >= 1.7.9)
prometheus-client-mmap (0.7.0.beta5) prometheus-client-mmap (0.7.0.beta8)
mmap2 (~> 2.2.6) mmap2 (~> 2.2, >= 2.2.7)
pry (0.10.4) pry (0.10.4)
coderay (~> 1.1.0) coderay (~> 1.1.0)
method_source (~> 0.8.1) method_source (~> 0.8.1)
...@@ -658,7 +658,7 @@ GEM ...@@ -658,7 +658,7 @@ GEM
thor (>= 0.18.1, < 2.0) thor (>= 0.18.1, < 2.0)
rainbow (2.2.2) rainbow (2.2.2)
rake rake
raindrops (0.17.0) raindrops (0.18.0)
rake (10.5.0) rake (10.5.0)
rblineprof (0.3.6) rblineprof (0.3.6)
debugger-ruby_core_source (~> 1.3) debugger-ruby_core_source (~> 1.3)
...@@ -1062,6 +1062,7 @@ DEPENDENCIES ...@@ -1062,6 +1062,7 @@ DEPENDENCIES
rails-deprecated_sanitizer (~> 1.0.3) rails-deprecated_sanitizer (~> 1.0.3)
rails-i18n (~> 4.0.9) rails-i18n (~> 4.0.9)
rainbow (~> 2.2) rainbow (~> 2.2)
raindrops (~> 0.18)
rblineprof (~> 0.3.6) rblineprof (~> 0.3.6)
rdoc (~> 4.2) rdoc (~> 4.2)
recaptcha (~> 3.0) recaptcha (~> 3.0)
......
import autosize from 'vendor/autosize'; import autosize from 'vendor/autosize';
$(() => { document.addEventListener('DOMContentLoaded', () => {
const $fields = $('.js-autosize'); const autosizeEls = document.querySelectorAll('.js-autosize');
$fields.on('autosize:resized', function resized() { autosize(autosizeEls);
const $field = $(this); autosize.update(autosizeEls);
$field.data('height', $field.outerHeight());
});
$fields.on('resize.autosize', function resize() {
const $field = $(this);
if ($field.data('height') !== $field.outerHeight()) {
$field.data('height', $field.outerHeight());
autosize.destroy($field);
$field.css('max-height', window.outerHeight);
}
});
autosize($fields);
autosize.update($fields);
$fields.css('resize', 'vertical');
}); });
...@@ -104,7 +104,7 @@ ...@@ -104,7 +104,7 @@
} }
this.data = query.result[0].values; this.data = query.result[0].values;
this.unitOfDisplay = query.unit || 'N/A'; this.unitOfDisplay = query.unit || 'N/A';
this.yAxisLabel = this.columnData.y_axis || 'Values'; this.yAxisLabel = this.columnData.y_label || 'Values';
this.legendTitle = query.legend || 'Average'; this.legendTitle = query.legend || 'Average';
this.graphWidth = this.$refs.baseSvg.clientWidth - this.graphWidth = this.$refs.baseSvg.clientWidth -
this.margin.left - this.margin.right; this.margin.left - this.margin.right;
......
...@@ -206,8 +206,6 @@ function UsersSelect(currentUser, els) { ...@@ -206,8 +206,6 @@ function UsersSelect(currentUser, els) {
return $dropdown.glDropdown({ return $dropdown.glDropdown({
showMenuAbove: showMenuAbove, showMenuAbove: showMenuAbove,
data: function(term, callback) { data: function(term, callback) {
var isAuthorFilter;
isAuthorFilter = $('.js-author-search');
return _this.users(term, options, function(users) { return _this.users(term, options, function(users) {
// GitLabDropdownFilter returns this.instance // GitLabDropdownFilter returns this.instance
// GitLabDropdownRemote returns this.options.instance // GitLabDropdownRemote returns this.options.instance
......
...@@ -227,7 +227,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -227,7 +227,7 @@ class Projects::IssuesController < Projects::ApplicationController
def issue def issue
return @issue if defined?(@issue) return @issue if defined?(@issue)
# The Sortable default scope causes performance issues when used with find_by # The Sortable default scope causes performance issues when used with find_by
@noteable = @issue ||= @project.issues.where(iid: params[:id]).reorder(nil).take! @noteable = @issue ||= @project.issues.find_by!(iid: params[:id])
return render_404 unless can?(current_user, :read_issue, @issue) return render_404 unless can?(current_user, :read_issue, @issue)
......
...@@ -83,7 +83,12 @@ class LabelsFinder < UnionFinder ...@@ -83,7 +83,12 @@ class LabelsFinder < UnionFinder
def projects def projects
return @projects if defined?(@projects) return @projects if defined?(@projects)
@projects = skip_authorization ? Project.all : ProjectsFinder.new(current_user: current_user).execute @projects = if skip_authorization
Project.all
else
ProjectsFinder.new(params: { non_archived: true }, current_user: current_user).execute
end
@projects = @projects.in_namespace(params[:group_id]) if group? @projects = @projects.in_namespace(params[:group_id]) if group?
@projects = @projects.where(id: params[:project_ids]) if projects? @projects = @projects.where(id: params[:project_ids]) if projects?
@projects = @projects.reorder(nil) @projects = @projects.reorder(nil)
......
...@@ -60,13 +60,13 @@ class UsersFinder ...@@ -60,13 +60,13 @@ class UsersFinder
end end
def by_external_identity(users) def by_external_identity(users)
return users unless current_user.admin? && params[:extern_uid] && params[:provider] return users unless current_user&.admin? && params[:extern_uid] && params[:provider]
users.joins(:identities).merge(Identity.with_extern_uid(params[:provider], params[:extern_uid])) users.joins(:identities).merge(Identity.with_extern_uid(params[:provider], params[:extern_uid]))
end end
def by_external(users) def by_external(users)
return users = users.where.not(external: true) unless current_user.admin? return users = users.where.not(external: true) unless current_user&.admin?
return users unless params[:external] return users unless params[:external]
users.external users.external
......
...@@ -298,10 +298,6 @@ module ApplicationHelper ...@@ -298,10 +298,6 @@ module ApplicationHelper
end end
end end
def can_toggle_new_nav?
Rails.env.development?
end
def show_new_nav? def show_new_nav?
cookies["new_nav"] == "true" cookies["new_nav"] == "true"
end end
......
...@@ -16,8 +16,8 @@ module FormHelper ...@@ -16,8 +16,8 @@ module FormHelper
end end
end end
def issue_dropdown_options(issuable, has_multiple_assignees = true) def issue_assignees_dropdown_options
options = { {
toggle_class: 'js-user-search js-assignee-search js-multiselect js-save-user-data', toggle_class: 'js-user-search js-assignee-search js-multiselect js-save-user-data',
title: 'Select assignee', title: 'Select assignee',
filter: true, filter: true,
...@@ -27,8 +27,8 @@ module FormHelper ...@@ -27,8 +27,8 @@ module FormHelper
first_user: current_user&.username, first_user: current_user&.username,
null_user: true, null_user: true,
current_user: true, current_user: true,
project_id: issuable.project.try(:id), project_id: @project.id,
field_name: "#{issuable.class.model_name.param_key}[assignee_ids][]", field_name: 'issue[assignee_ids][]',
default_label: 'Unassigned', default_label: 'Unassigned',
'max-select': 1, 'max-select': 1,
'dropdown-header': 'Assignee', 'dropdown-header': 'Assignee',
...@@ -38,13 +38,5 @@ module FormHelper ...@@ -38,13 +38,5 @@ module FormHelper
current_user_info: current_user.to_json(only: [:id, :name]) current_user_info: current_user.to_json(only: [:id, :name])
} }
} }
if has_multiple_assignees
options[:title] = 'Select assignee(s)'
options[:data][:'dropdown-header'] = 'Assignee(s)'
options[:data].delete(:'max-select')
end
options
end end
end end
...@@ -58,6 +58,11 @@ module GroupsHelper ...@@ -58,6 +58,11 @@ module GroupsHelper
IssuesFinder.new(current_user, group_id: group.id).execute IssuesFinder.new(current_user, group_id: group.id).execute
end end
def remove_group_message(group)
_("You are going to remove %{group_name}.\nRemoved groups CANNOT be restored!\nAre you ABSOLUTELY sure?") %
{ group_name: group.name }
end
private private
def group_title_link(group, hidable: false) def group_title_link(group, hidable: false)
......
...@@ -126,6 +126,18 @@ module SearchHelper ...@@ -126,6 +126,18 @@ module SearchHelper
search_path(options) search_path(options)
end end
def search_filter_input_options(type)
{
id: "filtered-search-#{type}",
placeholder: 'Search or filter results...',
data: {
'project-id' => @project.id,
'username-params' => @users.to_json(only: [:id, :username]),
'base-endpoint' => namespace_project_path(@project.namespace, @project)
}
}
end
# Sanitize a HTML field for search display. Most tags are stripped out and the # Sanitize a HTML field for search display. Most tags are stripped out and the
# maximum length is set to 200 characters. # maximum length is set to 200 characters.
def search_md_sanitize(object, field) def search_md_sanitize(object, field)
......
...@@ -176,9 +176,12 @@ module Ci ...@@ -176,9 +176,12 @@ module Ci
# * Lowercased # * Lowercased
# * Anything not matching [a-z0-9-] is replaced with a - # * Anything not matching [a-z0-9-] is replaced with a -
# * Maximum length is 63 bytes # * Maximum length is 63 bytes
# * First/Last Character is not a hyphen
def ref_slug def ref_slug
slugified = ref.to_s.downcase ref.to_s
slugified.gsub(/[^a-z0-9]/, '-')[0..62] .downcase
.gsub(/[^a-z0-9]/, '-')[0..62]
.gsub(/(\A-+|-+\z)/, '')
end end
# Variables whose value does not depend on environment # Variables whose value does not depend on environment
......
...@@ -102,6 +102,14 @@ module Issuable ...@@ -102,6 +102,14 @@ module Issuable
def locking_enabled? def locking_enabled?
title_changed? || description_changed? title_changed? || description_changed?
end end
def allows_multiple_assignees?
false
end
def has_multiple_assignees?
assignees.count > 1
end
end end
module ClassMethods module ClassMethods
......
...@@ -5,6 +5,25 @@ ...@@ -5,6 +5,25 @@
module Sortable module Sortable
extend ActiveSupport::Concern extend ActiveSupport::Concern
module DropDefaultScopeOnFinders
# Override these methods to drop the `ORDER BY id DESC` default scope.
# See http://dba.stackexchange.com/a/110919 for why we do this.
%i[find find_by find_by!].each do |meth|
define_method meth do |*args, &block|
return super(*args, &block) if block
unordered_relation = unscope(:order)
# We cannot simply call `meth` on `unscope(:order)`, since that is also
# an instance of the same relation class this module is included into,
# which means we'd get infinite recursion.
# We explicitly use the original implementation to prevent this.
original_impl = method(__method__).super_method.unbind
original_impl.bind(unordered_relation).call(*args)
end
end
end
included do included do
# By default all models should be ordered # By default all models should be ordered
# by created_at field starting from newest # by created_at field starting from newest
...@@ -18,6 +37,10 @@ module Sortable ...@@ -18,6 +37,10 @@ module Sortable
scope :order_updated_asc, -> { reorder(updated_at: :asc) } scope :order_updated_asc, -> { reorder(updated_at: :asc) }
scope :order_name_asc, -> { reorder(name: :asc) } scope :order_name_asc, -> { reorder(name: :asc) }
scope :order_name_desc, -> { reorder(name: :desc) } scope :order_name_desc, -> { reorder(name: :desc) }
# All queries (relations) on this model are instances of this `relation_klass`.
relation_klass = relation_delegate_class(ActiveRecord::Relation)
relation_klass.prepend DropDefaultScopeOnFinders
end end
module ClassMethods module ClassMethods
......
class ForkedProjectLink < ActiveRecord::Base class ForkedProjectLink < ActiveRecord::Base
belongs_to :forked_to_project, class_name: 'Project' belongs_to :forked_to_project, -> { where.not(pending_delete: true) }, class_name: 'Project'
belongs_to :forked_from_project, class_name: 'Project' belongs_to :forked_from_project, -> { where.not(pending_delete: true) }, class_name: 'Project'
end end
...@@ -197,11 +197,19 @@ class MergeRequest < ActiveRecord::Base ...@@ -197,11 +197,19 @@ class MergeRequest < ActiveRecord::Base
} }
end end
# This method is needed for compatibility with issues to not mess view and other code # These method are needed for compatibility with issues to not mess view and other code
def assignees def assignees
Array(assignee) Array(assignee)
end end
def assignee_ids
Array(assignee_id)
end
def assignee_ids=(ids)
write_attribute(:assignee_id, ids.last)
end
def assignee_or_author?(user) def assignee_or_author?(user)
author_id == user.id || assignee_id == user.id author_id == user.id || assignee_id == user.id
end end
......
...@@ -815,7 +815,7 @@ class Project < ActiveRecord::Base ...@@ -815,7 +815,7 @@ class Project < ActiveRecord::Base
end end
def ci_service def ci_service
@ci_service ||= ci_services.reorder(nil).find_by(active: true) @ci_service ||= ci_services.find_by(active: true)
end end
def deployment_services def deployment_services
...@@ -823,7 +823,7 @@ class Project < ActiveRecord::Base ...@@ -823,7 +823,7 @@ class Project < ActiveRecord::Base
end end
def deployment_service def deployment_service
@deployment_service ||= deployment_services.reorder(nil).find_by(active: true) @deployment_service ||= deployment_services.find_by(active: true)
end end
def monitoring_services def monitoring_services
...@@ -831,7 +831,7 @@ class Project < ActiveRecord::Base ...@@ -831,7 +831,7 @@ class Project < ActiveRecord::Base
end end
def monitoring_service def monitoring_service
@monitoring_service ||= monitoring_services.reorder(nil).find_by(active: true) @monitoring_service ||= monitoring_services.find_by(active: true)
end end
def jira_tracker? def jira_tracker?
......
require_dependency 'declarative_policy' require_dependency 'declarative_policy'
class BasePolicy < DeclarativePolicy::Base class BasePolicy < DeclarativePolicy::Base
include Gitlab::CurrentSettings
desc "User is an instance admin" desc "User is an instance admin"
with_options scope: :user, score: 0 with_options scope: :user, score: 0
condition(:admin) { @user&.admin? } condition(:admin) { @user&.admin? }
...@@ -10,4 +12,9 @@ class BasePolicy < DeclarativePolicy::Base ...@@ -10,4 +12,9 @@ class BasePolicy < DeclarativePolicy::Base
with_options scope: :user, score: 0 with_options scope: :user, score: 0
condition(:can_create_group) { @user&.can_create_group } condition(:can_create_group) { @user&.can_create_group }
desc "The application is restricted from public visibility"
condition(:restricted_public_level, scope: :global) do
current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC)
end
end end
...@@ -11,10 +11,16 @@ class GlobalPolicy < BasePolicy ...@@ -11,10 +11,16 @@ class GlobalPolicy < BasePolicy
with_options scope: :user, score: 0 with_options scope: :user, score: 0
condition(:access_locked) { @user.access_locked? } condition(:access_locked) { @user.access_locked? }
rule { anonymous }.prevent_all rule { anonymous }.policy do
prevent :log_in
prevent :access_api
prevent :access_git
prevent :receive_notifications
prevent :use_quick_actions
prevent :create_group
end
rule { default }.policy do rule { default }.policy do
enable :read_users_list
enable :log_in enable :log_in
enable :access_api enable :access_api
enable :access_git enable :access_git
...@@ -37,4 +43,8 @@ class GlobalPolicy < BasePolicy ...@@ -37,4 +43,8 @@ class GlobalPolicy < BasePolicy
rule { access_locked }.policy do rule { access_locked }.policy do
prevent :log_in prevent :log_in
end end
rule { ~restricted_public_level }.policy do
enable :read_users_list
end
end end
class UserPolicy < BasePolicy class UserPolicy < BasePolicy
include Gitlab::CurrentSettings
desc "The application is restricted from public visibility"
condition(:restricted_public_level, scope: :global) do
current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC)
end
desc "The current user is the user in question" desc "The current user is the user in question"
condition(:user_is_self, score: 0) { @subject == @user } condition(:user_is_self, score: 0) { @subject == @user }
......
...@@ -10,6 +10,8 @@ class DeleteMergedBranchesService < BaseService ...@@ -10,6 +10,8 @@ class DeleteMergedBranchesService < BaseService
branches = branches.select { |branch| project.repository.merged_to_root_ref?(branch) } branches = branches.select { |branch| project.repository.merged_to_root_ref?(branch) }
# Prevent deletion of branches relevant to open merge requests # Prevent deletion of branches relevant to open merge requests
branches -= merge_request_branch_names branches -= merge_request_branch_names
# Prevent deletion of protected branches
branches -= project.protected_branches.pluck(:name)
branches.each do |branch| branches.each do |branch|
DeleteBranchService.new(project, current_user).execute(branch) DeleteBranchService.new(project, current_user).execute(branch)
......
...@@ -61,8 +61,12 @@ module MergeRequests ...@@ -61,8 +61,12 @@ module MergeRequests
MergeRequests::PostMergeService.new(project, current_user).execute(merge_request) MergeRequests::PostMergeService.new(project, current_user).execute(merge_request)
if params[:should_remove_source_branch].present? || @merge_request.force_remove_source_branch? if params[:should_remove_source_branch].present? || @merge_request.force_remove_source_branch?
DeleteBranchService.new(@merge_request.source_project, branch_deletion_user) # Verify again that the source branch can be removed, since branch may be protected,
.execute(merge_request.source_branch) # or the source branch may have been updated.
if @merge_request.can_remove_source_branch?(branch_deletion_user)
DeleteBranchService.new(@merge_request.source_project, branch_deletion_user)
.execute(merge_request.source_branch)
end
end end
end end
......
...@@ -92,9 +92,12 @@ module QuickActions ...@@ -92,9 +92,12 @@ module QuickActions
desc 'Assign' desc 'Assign'
explanation do |users| explanation do |users|
"Assigns #{users.first.to_reference}." if users.any? users = issuable.allows_multiple_assignees? ? users : users.take(1)
"Assigns #{users.map(&:to_reference).to_sentence}."
end
params do
issuable.allows_multiple_assignees? ? '@user1 @user2' : '@user'
end end
params '@user'
condition do condition do
current_user.can?(:"admin_#{issuable.to_ability_name}", project) current_user.can?(:"admin_#{issuable.to_ability_name}", project)
end end
...@@ -104,28 +107,69 @@ module QuickActions ...@@ -104,28 +107,69 @@ module QuickActions
command :assign do |users| command :assign do |users|
next if users.empty? next if users.empty?
if issuable.is_a?(Issue) @updates[:assignee_ids] =
@updates[:assignee_ids] = [users.last.id] if issuable.allows_multiple_assignees?
issuable.assignees.pluck(:id) + users.map(&:id)
else
[users.last.id]
end
end
desc do
if issuable.allows_multiple_assignees?
'Remove all or specific assignee(s)'
else else
@updates[:assignee_id] = users.last.id 'Remove assignee'
end end
end end
desc 'Remove assignee'
explanation do explanation do
"Removes assignee #{issuable.assignees.first.to_reference}." "Removes #{'assignee'.pluralize(issuable.assignees.size)} #{issuable.assignees.map(&:to_reference).to_sentence}."
end
params do
issuable.allows_multiple_assignees? ? '@user1 @user2' : ''
end end
condition do condition do
issuable.persisted? && issuable.persisted? &&
issuable.assignees.any? && issuable.assignees.any? &&
current_user.can?(:"admin_#{issuable.to_ability_name}", project) current_user.can?(:"admin_#{issuable.to_ability_name}", project)
end end
command :unassign do parse_params do |unassign_param|
if issuable.is_a?(Issue) # When multiple users are assigned, all will be unassigned if multiple assignees are no longer allowed
@updates[:assignee_ids] = [] extract_users(unassign_param) if issuable.allows_multiple_assignees?
else end
@updates[:assignee_id] = nil command :unassign do |users = nil|
end @updates[:assignee_ids] =
if users&.any?
issuable.assignees.pluck(:id) - users.map(&:id)
else
[]
end
end
desc do
"Change assignee#{'(s)' if issuable.allows_multiple_assignees?}"
end
explanation do |users|
users = issuable.allows_multiple_assignees? ? users : users.take(1)
"Change #{'assignee'.pluralize(users.size)} to #{users.map(&:to_reference).to_sentence}."
end
params do
issuable.allows_multiple_assignees? ? '@user1 @user2' : '@user'
end
condition do
issuable.persisted? &&
current_user.can?(:"admin_#{issuable.to_ability_name}", project)
end
parse_params do |assignee_param|
extract_users(assignee_param)
end
command :reassign do |users|
@updates[:assignee_ids] =
if issuable.allows_multiple_assignees?
users.map(&:id)
else
[users.last.id]
end
end end
desc 'Set milestone' desc 'Set milestone'
......
...@@ -45,10 +45,13 @@ ...@@ -45,10 +45,13 @@
.panel.panel-danger .panel.panel-danger
.panel-heading Remove group .panel-heading Remove group
.panel-body .panel-body
%p = form_tag(@group, method: :delete) do
Removing group will cause all child projects and resources to be removed. %p
%br Removing group will cause all child projects and resources to be removed.
%strong Removed group can not be restored! %br
%strong Removed group can not be restored!
.form-actions .form-actions
= link_to 'Remove group', @group, data: {confirm: 'Removed group can not be restored! Are you sure?'}, method: :delete, class: "btn btn-remove" = button_to 'Remove group', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_group_message(@group) }
= render 'shared/confirm_modal', phrase: @group.path
...@@ -74,9 +74,8 @@ ...@@ -74,9 +74,8 @@
= link_to "Profile", current_user, class: 'profile-link', data: { user: current_user.username } = link_to "Profile", current_user, class: 'profile-link', data: { user: current_user.username }
%li %li
= link_to "Settings", profile_path = link_to "Settings", profile_path
- if can_toggle_new_nav? %li
%li = link_to "Turn on new nav", profile_preferences_path(anchor: "new-navigation")
= link_to "Turn on new nav", profile_preferences_path(anchor: "new-navigation")
%li.divider %li.divider
%li %li
= link_to "Sign out", destroy_user_session_path, method: :delete, class: "sign-out-link" = link_to "Sign out", destroy_user_session_path, method: :delete, class: "sign-out-link"
......
...@@ -16,25 +16,22 @@ ...@@ -16,25 +16,22 @@
.preview= image_tag "#{scheme.css_class}-scheme-preview.png" .preview= image_tag "#{scheme.css_class}-scheme-preview.png"
= f.radio_button :color_scheme_id, scheme.id = f.radio_button :color_scheme_id, scheme.id
= scheme.name = scheme.name
- if can_toggle_new_nav? .col-sm-12
.col-sm-12 %hr
%hr .col-lg-3.profile-settings-sidebar#new-navigation
.col-lg-3.profile-settings-sidebar#new-navigation %h4.prepend-top-0
%h4.prepend-top-0 New Navigation
New Navigation %p
%p This setting allows you to turn on or off the new upcoming navigation concept.
This setting allows you to turn on or off the new upcoming navigation concept. .col-lg-9.syntax-theme
= succeed '.' do = label_tag do
= link_to 'Learn more', '', target: '_blank' .preview= image_tag "old_nav.png"
.col-lg-9.syntax-theme %input.js-experiment-feature-toggle{ type: "radio", value: "false", name: "new_nav", checked: !show_new_nav? }
= label_tag do Old
.preview= image_tag "old_nav.png" = label_tag do
%input.js-experiment-feature-toggle{ type: "radio", value: "false", name: "new_nav", checked: !show_new_nav? } .preview= image_tag "new_nav.png"
Old %input.js-experiment-feature-toggle{ type: "radio", value: "true", name: "new_nav", checked: show_new_nav? }
= label_tag do New
.preview= image_tag "new_nav.png"
%input.js-experiment-feature-toggle{ type: "radio", value: "true", name: "new_nav", checked: show_new_nav? }
New
.col-sm-12 .col-sm-12
%hr %hr
.col-lg-4.profile-settings-sidebar .col-lg-4.profile-settings-sidebar
......
...@@ -19,10 +19,11 @@ ...@@ -19,10 +19,11 @@
":data-name" => "assignee.name", ":data-name" => "assignee.name",
":data-username" => "assignee.username" } ":data-username" => "assignee.username" }
.dropdown .dropdown
%button.dropdown-menu-toggle.js-user-search.js-author-search.js-multiselect.js-save-user-data.js-issue-board-sidebar{ type: "button", ref: "assigneeDropdown", data: { toggle: "dropdown", field_name: "issue[assignee_ids][]", first_user: (current_user.username if current_user), current_user: "true", project_id: @project.id, null_user: "true", multi_select: "true", 'max-select' => 1, dropdown: { header: 'Assignee' } }, - dropdown_options = issue_assignees_dropdown_options
%button.dropdown-menu-toggle.js-user-search.js-author-search.js-multiselect.js-save-user-data.js-issue-board-sidebar{ type: 'button', ref: 'assigneeDropdown', data: { toggle: 'dropdown', field_name: 'issue[assignee_ids][]', first_user: current_user&.username, current_user: 'true', project_id: @project.id, null_user: 'true', multi_select: 'true', 'dropdown-header': dropdown_options[:data][:'dropdown-header'], 'max-select': dropdown_options[:data][:'max-select'] },
":data-issuable-id" => "issue.id", ":data-issuable-id" => "issue.id",
":data-issue-update" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '.json'" } ":data-issue-update" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '.json'" }
Select assignee = dropdown_options[:title]
= icon("chevron-down") = icon("chevron-down")
.dropdown-menu.dropdown-select.dropdown-menu-user.dropdown-menu-selectable.dropdown-menu-author .dropdown-menu.dropdown-select.dropdown-menu-user.dropdown-menu-selectable.dropdown-menu-author
= dropdown_title("Assign to") = dropdown_title("Assign to")
......
- @no_container = true - @no_container = true
- page_title "Charts", "Pipelines" - page_title _("Charts"), _("Pipelines")
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('common_d3') = page_specific_javascript_bundle_tag('common_d3')
= page_specific_javascript_bundle_tag('graphs') = page_specific_javascript_bundle_tag('graphs')
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
%div{ class: container_class } %div{ class: container_class }
.sub-header-block .sub-header-block
.oneline .oneline
A collection of graphs for Continuous Integration = _("A collection of graphs regarding Continuous Integration")
#charts.ci-charts #charts.ci-charts
.row .row
......
%h4 Overall stats %h4= s_("PipelineCharts|Overall statistics")
%ul %ul
%li %li
Total: = s_("PipelineCharts|Total:")
%strong= pluralize @counts[:total], 'pipeline' %strong= n_("1 pipeline", "%d pipelines", @counts[:total]) % @counts[:total]
%li %li
Successful: = s_("PipelineCharts|Successful:")
%strong= pluralize @counts[:success], 'pipeline' %strong= n_("1 pipeline", "%d pipelines", @counts[:success]) % @counts[:success]
%li %li
Failed: = s_("PipelineCharts|Failed:")
%strong= pluralize @counts[:failed], 'pipeline' %strong= n_("1 pipeline", "%d pipelines", @counts[:failed]) % @counts[:failed]
%li %li
Success ratio: = s_("PipelineCharts|Success ratio:")
%strong %strong
#{success_ratio(@counts)}% #{success_ratio(@counts)}%
%div %div
%p.light %p.light
Commit duration in minutes for last 30 commits = _("Commit duration in minutes for last 30 commits")
%canvas#build_timesChart{ height: 200 } %canvas#build_timesChart{ height: 200 }
......
%h4 Pipelines charts %h4= _("Pipelines charts")
%p %p
&nbsp; &nbsp;
%span.cgreen %span.cgreen
= icon("circle") = icon("circle")
success = s_("Pipeline|success")
&nbsp; &nbsp;
%span.cgray %span.cgray
= icon("circle") = icon("circle")
all = s_("Pipeline|all")
.prepend-top-default .prepend-top-default
%p.light %p.light
Jobs for last week = _("Jobs for last week")
(#{date_from_to(Date.today - 7.days, Date.today)}) (#{date_from_to(Date.today - 7.days, Date.today)})
%canvas#weekChart{ height: 200 } %canvas#weekChart{ height: 200 }
.prepend-top-default .prepend-top-default
%p.light %p.light
Jobs for last month = _("Jobs for last month")
(#{date_from_to(Date.today - 30.days, Date.today)}) (#{date_from_to(Date.today - 30.days, Date.today)})
%canvas#monthChart{ height: 200 } %canvas#monthChart{ height: 200 }
.prepend-top-default .prepend-top-default
%p.light %p.light
Jobs for last year = _("Jobs for last year")
%canvas#yearChart.padded{ height: 250 } %canvas#yearChart.padded{ height: 250 }
- [:week, :month, :year].each do |scope| - [:week, :month, :year].each do |scope|
......
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
.scroll-container .scroll-container
%ul.tokens-container.list-unstyled %ul.tokens-container.list-unstyled
%li.input-token %li.input-token
%input.form-control.filtered-search{ id: "filtered-search-#{type.to_s}", placeholder: 'Search or filter results...', data: { 'project-id' => @project.id, 'username-params' => @users.to_json(only: [:id, :username]), 'base-endpoint' => namespace_project_path(@project.namespace, @project) } } %input.form-control.filtered-search{ search_filter_input_options(type) }
= icon('filter') = icon('filter')
#js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown #js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown
%ul{ data: { dropdown: true } } %ul{ data: { dropdown: true } }
......
...@@ -37,19 +37,20 @@ ...@@ -37,19 +37,20 @@
- issuable.assignees.each do |assignee| - issuable.assignees.each do |assignee|
= hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", assignee.id, id: nil, data: { avatar_url: assignee.avatar_url, name: assignee.name, username: assignee.username } = hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", assignee.id, id: nil, data: { avatar_url: assignee.avatar_url, name: assignee.name, username: assignee.username }
- options = { toggle_class: 'js-user-search js-author-search', title: 'Assign to', filter: true, dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author', placeholder: 'Search users', data: { first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), author_id: issuable.author_id, field_name: "#{issuable.to_ability_name}[assignee_ids][]", issue_update: issuable_json_path(issuable), ability_name: issuable.to_ability_name, null_user: true } } - options = { toggle_class: 'js-user-search js-author-search', title: 'Assign to', filter: true, dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author', placeholder: 'Search users', data: { first_user: current_user&.username, current_user: true, project_id: @project&.id, author_id: issuable.author_id, field_name: "#{issuable.to_ability_name}[assignee_ids][]", issue_update: issuable_json_path(issuable), ability_name: issuable.to_ability_name, null_user: true } }
- title = 'Select assignee' - title = 'Select assignee'
- if issuable.is_a?(Issue) - if issuable.is_a?(Issue)
- unless issuable.assignees.any? - unless issuable.assignees.any?
= hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", 0, id: nil = hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", 0, id: nil
- dropdown_options = issue_assignees_dropdown_options
- title = dropdown_options[:title]
- options[:toggle_class] += ' js-multiselect js-save-user-data' - options[:toggle_class] += ' js-multiselect js-save-user-data'
- data = { field_name: "#{issuable.to_ability_name}[assignee_ids][]" } - data = { field_name: "#{issuable.to_ability_name}[assignee_ids][]" }
- data[:multi_select] = true - data[:multi_select] = true
- data['dropdown-title'] = title - data['dropdown-title'] = title
- data['dropdown-header'] = 'Assignee' - data['dropdown-header'] = dropdown_options[:data][:'dropdown-header']
- data['max-select'] = 1 - data['max-select'] = dropdown_options[:data][:'max-select']
- options[:data].merge!(data) - options[:data].merge!(data)
= dropdown_tag(title, options: options) = dropdown_tag(title, options: options)
...@@ -7,5 +7,5 @@ ...@@ -7,5 +7,5 @@
- if issuable.assignees.length === 0 - if issuable.assignees.length === 0
= hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", 0, id: nil, data: { meta: '' } = hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", 0, id: nil, data: { meta: '' }
= dropdown_tag(users_dropdown_label(issuable.assignees), options: issue_dropdown_options(issuable,false)) = dropdown_tag(users_dropdown_label(issuable.assignees), options: issue_assignees_dropdown_options)
= link_to 'Assign to me', '#', class: "assign-to-me-link #{'hide' if issuable.assignees.include?(current_user)}" = link_to 'Assign to me', '#', class: "assign-to-me-link #{'hide' if issuable.assignees.include?(current_user)}"
---
title: "Remove group modal like remove project modal (requires typing + confirmation)"
merge_request: 12569
author: Diego Souza
---
title: Allow unauthenticated access to the /api/v4/users API
merge_request: 12445
author:
---
title: Add Italian translation of Cycle Analytics Page & Project Page & Repository Page
merge_request: 12578
author: Huang Tao
---
title: Prevent accidental deletion of protected MR source branch by repeating checks
before actual deletion
merge_request:
author:
---
title: Improve performance of lookups of issues, merge requests etc by dropping unnecessary ORDER BY clause
merge_request:
author:
---
title: Omit trailing / leading hyphens in CI_COMMIT_REF_SLUG variable to make it usable as a hostname
merge_request: 11218
author: Stefan Hanreich
---
title: Hide archived project labels from group issue tracker
merge_request: 12547
author: Horacio Bertorello
---
title: Fixed the y_label not setting correctly for each graph on the monitoring dashboard
merge_request:
author:
---
title: Optimize creation of commit API by using Repository#commit instead of Repository#commits
merge_request:
author:
---
title: Do not delete protected branches when deleting all merged branches
merge_request: 12624
author:
...@@ -543,6 +543,10 @@ production: &base ...@@ -543,6 +543,10 @@ production: &base
# enabled: true # enabled: true
# host: localhost # host: localhost
# port: 3808 # port: 3808
prometheus:
# Time between sampling of unicorn socket metrics, in seconds
# unicorn_sampler_interval: 10
# #
# 5. Extra customization # 5. Extra customization
......
...@@ -494,6 +494,12 @@ Settings.webpack.dev_server['enabled'] ||= false ...@@ -494,6 +494,12 @@ Settings.webpack.dev_server['enabled'] ||= false
Settings.webpack.dev_server['host'] ||= 'localhost' Settings.webpack.dev_server['host'] ||= 'localhost'
Settings.webpack.dev_server['port'] ||= 3808 Settings.webpack.dev_server['port'] ||= 3808
#
# Prometheus metrics settings
#
Settings['prometheus'] ||= Settingslogic.new({})
Settings.prometheus['unicorn_sampler_interval'] ||= 10
# #
# Testing settings # Testing settings
# #
......
...@@ -119,6 +119,13 @@ def instrument_classes(instrumentation) ...@@ -119,6 +119,13 @@ def instrument_classes(instrumentation)
end end
# rubocop:enable Metrics/AbcSize # rubocop:enable Metrics/AbcSize
Gitlab::Metrics::UnicornSampler.initialize_instance(Settings.prometheus.unicorn_sampler_interval).start
Gitlab::Application.configure do |config|
# 0 should be Sentry to catch errors in this middleware
config.middleware.insert(1, Gitlab::Metrics::ConnectionRackMiddleware)
end
if Gitlab::Metrics.enabled? if Gitlab::Metrics.enabled?
require 'pathname' require 'pathname'
require 'influxdb' require 'influxdb'
...@@ -175,7 +182,7 @@ if Gitlab::Metrics.enabled? ...@@ -175,7 +182,7 @@ if Gitlab::Metrics.enabled?
GC::Profiler.enable GC::Profiler.enable
Gitlab::Metrics::Sampler.new.start Gitlab::Metrics::InfluxSampler.initialize_instance.start
module TrackNewRedisConnections module TrackNewRedisConnections
def connect(*args) def connect(*args)
......
This diff is collapsed.
...@@ -37,7 +37,7 @@ future GitLab releases.** ...@@ -37,7 +37,7 @@ future GitLab releases.**
|-------------------------------- |--------|--------|-------------| |-------------------------------- |--------|--------|-------------|
| **CI** | all | 0.4 | Mark that job is executed in CI environment | | **CI** | all | 0.4 | Mark that job is executed in CI environment |
| **CI_COMMIT_REF_NAME** | 9.0 | all | The branch or tag name for which project is built | | **CI_COMMIT_REF_NAME** | 9.0 | all | The branch or tag name for which project is built |
| **CI_COMMIT_REF_SLUG** | 9.0 | all | `$CI_COMMIT_REF_NAME` lowercased, shortened to 63 bytes, and with everything except `0-9` and `a-z` replaced with `-`. Use in URLs and domain names. | | **CI_COMMIT_REF_SLUG** | 9.0 | all | `$CI_COMMIT_REF_NAME` lowercased, shortened to 63 bytes, and with everything except `0-9` and `a-z` replaced with `-`. No leading / trailing `-`. Use in URLs, host names and domain names. |
| **CI_COMMIT_SHA** | 9.0 | all | The commit revision for which project is built | | **CI_COMMIT_SHA** | 9.0 | all | The commit revision for which project is built |
| **CI_COMMIT_TAG** | 9.0 | 0.5 | The commit tag name. Present only when building tags. | | **CI_COMMIT_TAG** | 9.0 | 0.5 | The commit tag name. Present only when building tags. |
| **CI_DEBUG_TRACE** | all | 1.7 | Whether [debug tracing](#debug-tracing) is enabled | | **CI_DEBUG_TRACE** | all | 1.7 | Whether [debug tracing](#debug-tracing) is enabled |
......
...@@ -43,7 +43,7 @@ mysql> SET GLOBAL innodb_file_per_table=1, innodb_file_format=Barracuda, innodb_ ...@@ -43,7 +43,7 @@ mysql> SET GLOBAL innodb_file_per_table=1, innodb_file_format=Barracuda, innodb_
mysql> CREATE DATABASE IF NOT EXISTS `gitlabhq_production` DEFAULT CHARACTER SET `utf8` COLLATE `utf8_general_ci`; mysql> CREATE DATABASE IF NOT EXISTS `gitlabhq_production` DEFAULT CHARACTER SET `utf8` COLLATE `utf8_general_ci`;
# Grant the GitLab user necessary permissions on the database # Grant the GitLab user necessary permissions on the database
mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, CREATE TEMPORARY TABLES, DROP, INDEX, ALTER, LOCK TABLES, REFERENCES ON `gitlabhq_production`.* TO 'git'@'localhost'; mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, CREATE TEMPORARY TABLES, DROP, INDEX, ALTER, LOCK TABLES, REFERENCES, TRIGGER ON `gitlabhq_production`.* TO 'git'@'localhost';
# Quit the database session # Quit the database session
mysql> \q mysql> \q
......
...@@ -156,7 +156,7 @@ See [smtp_settings.rb.sample] as an example. ...@@ -156,7 +156,7 @@ See [smtp_settings.rb.sample] as an example.
Ensure you're still up-to-date with the latest init script changes: Ensure you're still up-to-date with the latest init script changes:
sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
For Ubuntu 16.04.1 LTS: For Ubuntu 16.04.1 LTS:
sudo systemctl daemon-reload sudo systemctl daemon-reload
......
...@@ -156,7 +156,16 @@ sudo -u git -H git checkout v$(</home/git/gitlab/GITALY_SERVER_VERSION) ...@@ -156,7 +156,16 @@ sudo -u git -H git checkout v$(</home/git/gitlab/GITALY_SERVER_VERSION)
sudo -u git -H make sudo -u git -H make
``` ```
### 10. Update configuration files ### 10. Update MySQL permissions
If you are using MySQL you need to grant the GitLab user the necessary
permissions on the database:
```bash
mysql -u root -p -e "GRANT TRIGGER ON \`gitlabhq_production\`.* TO 'git'@'localhost';"
```
### 11. Update configuration files
#### New configuration options for `gitlab.yml` #### New configuration options for `gitlab.yml`
...@@ -230,7 +239,7 @@ For Ubuntu 16.04.1 LTS: ...@@ -230,7 +239,7 @@ For Ubuntu 16.04.1 LTS:
sudo systemctl daemon-reload sudo systemctl daemon-reload
``` ```
### 11. Install libs, migrations, etc. ### 12. Install libs, migrations, etc.
```bash ```bash
cd /home/git/gitlab cd /home/git/gitlab
...@@ -256,14 +265,14 @@ sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production ...@@ -256,14 +265,14 @@ sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production
**MySQL installations**: Run through the `MySQL strings limits` and `Tables and data conversion to utf8mb4` [tasks](../install/database_mysql.md). **MySQL installations**: Run through the `MySQL strings limits` and `Tables and data conversion to utf8mb4` [tasks](../install/database_mysql.md).
### 12. Start application ### 13. Start application
```bash ```bash
sudo service gitlab start sudo service gitlab start
sudo service nginx restart sudo service nginx restart
``` ```
### 13. Check application status ### 14. Check application status
Check if GitLab and its environment are configured correctly: Check if GitLab and its environment are configured correctly:
......
...@@ -67,7 +67,7 @@ module API ...@@ -67,7 +67,7 @@ module API
result = ::Files::MultiService.new(user_project, current_user, attrs).execute result = ::Files::MultiService.new(user_project, current_user, attrs).execute
if result[:status] == :success if result[:status] == :success
commit_detail = user_project.repository.commits(result[:result], limit: 1).first commit_detail = user_project.repository.commit(result[:result])
present commit_detail, with: Entities::RepoCommitDetail present commit_detail, with: Entities::RepoCommitDetail
else else
render_api_error!(result[:message], 400) render_api_error!(result[:message], 400)
......
...@@ -4,10 +4,13 @@ module API ...@@ -4,10 +4,13 @@ module API
before do before do
allow_access_with_scope :read_user if request.get? allow_access_with_scope :read_user if request.get?
authenticate!
end end
resource :users, requirements: { uid: /[0-9]*/, id: /[0-9]*/ } do resource :users, requirements: { uid: /[0-9]*/, id: /[0-9]*/ } do
before do
authenticate_non_get!
end
helpers do helpers do
def find_user(params) def find_user(params)
id = params[:user_id] || params[:id] id = params[:user_id] || params[:id]
...@@ -51,15 +54,22 @@ module API ...@@ -51,15 +54,22 @@ module API
use :pagination use :pagination
end end
get do get do
unless can?(current_user, :read_users_list)
render_api_error!("Not authorized.", 403)
end
authenticated_as_admin! if params[:external].present? || (params[:extern_uid].present? && params[:provider].present?) authenticated_as_admin! if params[:external].present? || (params[:extern_uid].present? && params[:provider].present?)
users = UsersFinder.new(current_user, params).execute users = UsersFinder.new(current_user, params).execute
entity = current_user.admin? ? Entities::UserWithAdmin : Entities::UserBasic authorized = can?(current_user, :read_users_list)
# When `current_user` is not present, require that the `username`
# parameter is passed, to prevent an unauthenticated user from accessing
# a list of all the users on the GitLab instance. `UsersFinder` performs
# an exact match on the `username` parameter, so we are guaranteed to
# get either 0 or 1 `users` here.
authorized &&= params[:username].present? if current_user.blank?
forbidden!("Not authorized to access /api/v4/users") unless authorized
entity = current_user&.admin? ? Entities::UserWithAdmin : Entities::UserBasic
present paginate(users), with: entity present paginate(users), with: entity
end end
...@@ -398,6 +408,10 @@ module API ...@@ -398,6 +408,10 @@ module API
end end
resource :user do resource :user do
before do
authenticate!
end
desc 'Get the currently authenticated user' do desc 'Get the currently authenticated user' do
success Entities::UserPublic success Entities::UserPublic
end end
......
...@@ -34,7 +34,7 @@ module Gitlab ...@@ -34,7 +34,7 @@ module Gitlab
commit_id: commit_id, commit_id: commit_id,
prefix: ref_prefix prefix: ref_prefix
) )
GitalyClient.call(@storage, :ref, :find_ref_name, request).name encode!(GitalyClient.call(@storage, :ref, :find_ref_name, request).name.dup)
end end
def count_tag_names def count_tag_names
......
...@@ -12,7 +12,8 @@ module Gitlab ...@@ -12,7 +12,8 @@ module Gitlab
'zh_HK' => '繁體中文(香港)', 'zh_HK' => '繁體中文(香港)',
'zh_TW' => '繁體中文(臺灣)', 'zh_TW' => '繁體中文(臺灣)',
'bg' => 'български', 'bg' => 'български',
'eo' => 'Esperanto' 'eo' => 'Esperanto',
'it' => 'Italiano'
}.freeze }.freeze
def available_locales def available_locales
......
require 'logger'
module Gitlab
module Metrics
class BaseSampler
def self.initialize_instance(*args)
raise "#{name} singleton instance already initialized" if @instance
@instance = new(*args)
at_exit(&@instance.method(:stop))
@instance
end
def self.instance
@instance
end
attr_reader :running
# interval - The sampling interval in seconds.
def initialize(interval)
interval_half = interval.to_f / 2
@interval = interval
@interval_steps = (-interval_half..interval_half).step(0.1).to_a
@mutex = Mutex.new
end
def enabled?
true
end
def start
return unless enabled?
@mutex.synchronize do
return if running
@running = true
@thread = Thread.new do
sleep(sleep_interval)
while running
safe_sample
sleep(sleep_interval)
end
end
end
end
def stop
@mutex.synchronize do
return unless running
@running = false
if @thread
@thread.wakeup if @thread.alive?
@thread.join
@thread = nil
end
end
end
def safe_sample
sample
rescue => e
Rails.logger.warn("#{self.class}: #{e}, stopping")
stop
end
def sample
raise NotImplementedError
end
# Returns the sleep interval with a random adjustment.
#
# The random adjustment is put in place to ensure we:
#
# 1. Don't generate samples at the exact same interval every time (thus
# potentially missing anything that happens in between samples).
# 2. Don't sample data at the same interval two times in a row.
def sleep_interval
while step = @interval_steps.sample
if step != @last_step
@last_step = step
return @interval + @last_step
end
end
end
end
end
end
module Gitlab
module Metrics
class ConnectionRackMiddleware
def initialize(app)
@app = app
end
def self.rack_request_count
@rack_request_count ||= Gitlab::Metrics.counter(:rack_request, 'Rack request count')
end
def self.rack_response_count
@rack_response_count ||= Gitlab::Metrics.counter(:rack_response, 'Rack response count')
end
def self.rack_uncaught_errors_count
@rack_uncaught_errors_count ||= Gitlab::Metrics.counter(:rack_uncaught_errors, 'Rack connections handling uncaught errors count')
end
def self.rack_execution_time
@rack_execution_time ||= Gitlab::Metrics.histogram(:rack_execution_time, 'Rack connection handling execution time',
{}, [0.05, 0.1, 0.25, 0.5, 0.7, 1, 1.5, 2, 2.5, 3, 5, 7, 10])
end
def call(env)
method = env['REQUEST_METHOD'].downcase
started = Time.now.to_f
begin
ConnectionRackMiddleware.rack_request_count.increment(method: method)
status, headers, body = @app.call(env)
ConnectionRackMiddleware.rack_response_count.increment(method: method, status: status)
[status, headers, body]
rescue
ConnectionRackMiddleware.rack_uncaught_errors_count.increment
raise
ensure
elapsed = Time.now.to_f - started
ConnectionRackMiddleware.rack_execution_time.observe({}, elapsed)
end
end
end
end
end
...@@ -5,14 +5,11 @@ module Gitlab ...@@ -5,14 +5,11 @@ module Gitlab
# This class is used to gather statistics that can't be directly associated # This class is used to gather statistics that can't be directly associated
# with a transaction such as system memory usage, garbage collection # with a transaction such as system memory usage, garbage collection
# statistics, etc. # statistics, etc.
class Sampler class InfluxSampler < BaseSampler
# interval - The sampling interval in seconds. # interval - The sampling interval in seconds.
def initialize(interval = Metrics.settings[:sample_interval]) def initialize(interval = Metrics.settings[:sample_interval])
interval_half = interval.to_f / 2 super(interval)
@last_step = nil
@interval = interval
@interval_steps = (-interval_half..interval_half).step(0.1).to_a
@last_step = nil
@metrics = [] @metrics = []
...@@ -26,18 +23,6 @@ module Gitlab ...@@ -26,18 +23,6 @@ module Gitlab
end end
end end
def start
Thread.new do
Thread.current.abort_on_exception = true
loop do
sleep(sleep_interval)
sample
end
end
end
def sample def sample
sample_memory_usage sample_memory_usage
sample_file_descriptors sample_file_descriptors
...@@ -86,7 +71,7 @@ module Gitlab ...@@ -86,7 +71,7 @@ module Gitlab
end end
def sample_gc def sample_gc
time = GC::Profiler.total_time * 1000.0 time = GC::Profiler.total_time * 1000.0
stats = GC.stat.merge(total_time: time) stats = GC.stat.merge(total_time: time)
# We want the difference of GC runs compared to the last sample, not the # We want the difference of GC runs compared to the last sample, not the
...@@ -111,23 +96,6 @@ module Gitlab ...@@ -111,23 +96,6 @@ module Gitlab
def sidekiq? def sidekiq?
Sidekiq.server? Sidekiq.server?
end end
# Returns the sleep interval with a random adjustment.
#
# The random adjustment is put in place to ensure we:
#
# 1. Don't generate samples at the exact same interval every time (thus
# potentially missing anything that happens in between samples).
# 2. Don't sample data at the same interval two times in a row.
def sleep_interval
while step = @interval_steps.sample
if step != @last_step
@last_step = step
return @interval + @last_step
end
end
end
end end
end end
end end
...@@ -29,8 +29,8 @@ module Gitlab ...@@ -29,8 +29,8 @@ module Gitlab
provide_metric(name) || registry.summary(name, docstring, base_labels) provide_metric(name) || registry.summary(name, docstring, base_labels)
end end
def gauge(name, docstring, base_labels = {}) def gauge(name, docstring, base_labels = {}, multiprocess_mode = :all)
provide_metric(name) || registry.gauge(name, docstring, base_labels) provide_metric(name) || registry.gauge(name, docstring, base_labels, multiprocess_mode)
end end
def histogram(name, docstring, base_labels = {}, buckets = ::Prometheus::Client::Histogram::DEFAULT_BUCKETS) def histogram(name, docstring, base_labels = {}, buckets = ::Prometheus::Client::Histogram::DEFAULT_BUCKETS)
......
module Gitlab
module Metrics
class UnicornSampler < BaseSampler
def initialize(interval)
super(interval)
end
def unicorn_active_connections
@unicorn_active_connections ||= Gitlab::Metrics.gauge(:unicorn_active_connections, 'Unicorn active connections', {}, :max)
end
def unicorn_queued_connections
@unicorn_queued_connections ||= Gitlab::Metrics.gauge(:unicorn_queued_connections, 'Unicorn queued connections', {}, :max)
end
def enabled?
# Raindrops::Linux.tcp_listener_stats is only present on Linux
unicorn_with_listeners? && Raindrops::Linux.respond_to?(:tcp_listener_stats)
end
def sample
Raindrops::Linux.tcp_listener_stats(tcp_listeners).each do |addr, stats|
unicorn_active_connections.set({ type: 'tcp', address: addr }, stats.active)
unicorn_queued_connections.set({ type: 'tcp', address: addr }, stats.queued)
end
Raindrops::Linux.unix_listener_stats(unix_listeners).each do |addr, stats|
unicorn_active_connections.set({ type: 'unix', address: addr }, stats.active)
unicorn_queued_connections.set({ type: 'unix', address: addr }, stats.queued)
end
end
private
def tcp_listeners
@tcp_listeners ||= Unicorn.listener_names.grep(%r{\A[^/]+:\d+\z})
end
def unix_listeners
@unix_listeners ||= Unicorn.listener_names - tcp_listeners
end
def unicorn_with_listeners?
defined?(Unicorn) && Unicorn.listener_names.any?
end
end
end
end
...@@ -17,9 +17,27 @@ msgstr "" ...@@ -17,9 +17,27 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=n != 1;\n" "Plural-Forms: nplurals=2; plural=n != 1;\n"
"\n" "\n"
msgid "%d additional commit has been omitted to prevent performance issues."
msgid_plural "%d additional commits have been omitted to prevent performance issues."
msgstr[0] ""
msgstr[1] ""
msgid "%d commit"
msgid_plural "%d commits"
msgstr[0] ""
msgstr[1] ""
msgid "%{commit_author_link} committed %{commit_timeago}" msgid "%{commit_author_link} committed %{commit_timeago}"
msgstr "" msgstr ""
msgid "1 pipeline"
msgid_plural "%d pipelines"
msgstr[0] ""
msgstr[1] ""
msgid "A collection of graphs regarding Continuous Integration"
msgstr ""
msgid "About auto deploy" msgid "About auto deploy"
msgstr "" msgstr ""
...@@ -61,9 +79,24 @@ msgstr[1] "" ...@@ -61,9 +79,24 @@ msgstr[1] ""
msgid "Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}" msgid "Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}"
msgstr "" msgstr ""
msgid "BranchSwitcherPlaceholder|Search branches"
msgstr ""
msgid "BranchSwitcherTitle|Switch branch"
msgstr ""
msgid "Branches" msgid "Branches"
msgstr "" msgstr ""
msgid "Browse Directory"
msgstr ""
msgid "Browse File"
msgstr ""
msgid "Browse Files"
msgstr ""
msgid "Browse files" msgid "Browse files"
msgstr "" msgstr ""
...@@ -159,6 +192,9 @@ msgid_plural "Commits" ...@@ -159,6 +192,9 @@ msgid_plural "Commits"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
msgid "Commit duration in minutes for last 30 commits"
msgstr ""
msgid "Commit message" msgid "Commit message"
msgstr "" msgstr ""
...@@ -171,6 +207,9 @@ msgstr "" ...@@ -171,6 +207,9 @@ msgstr ""
msgid "Commits" msgid "Commits"
msgstr "" msgstr ""
msgid "Commits feed"
msgstr ""
msgid "Commits|History" msgid "Commits|History"
msgstr "" msgstr ""
...@@ -195,6 +234,9 @@ msgstr "" ...@@ -195,6 +234,9 @@ msgstr ""
msgid "Create New Directory" msgid "Create New Directory"
msgstr "" msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
msgid "Create directory" msgid "Create directory"
msgstr "" msgstr ""
...@@ -213,6 +255,9 @@ msgstr "" ...@@ -213,6 +255,9 @@ msgstr ""
msgid "CreateTag|Tag" msgid "CreateTag|Tag"
msgstr "" msgstr ""
msgid "CreateTokenToCloneLink|create a personal access token"
msgstr ""
msgid "Cron Timezone" msgid "Cron Timezone"
msgstr "" msgstr ""
...@@ -323,6 +368,9 @@ msgstr "" ...@@ -323,6 +368,9 @@ msgstr ""
msgid "Files" msgid "Files"
msgstr "" msgstr ""
msgid "Filter by commit message"
msgstr ""
msgid "Find by path" msgid "Find by path"
msgstr "" msgstr ""
...@@ -370,6 +418,15 @@ msgstr "" ...@@ -370,6 +418,15 @@ msgstr ""
msgid "Introducing Cycle Analytics" msgid "Introducing Cycle Analytics"
msgstr "" msgstr ""
msgid "Jobs for last month"
msgstr ""
msgid "Jobs for last week"
msgstr ""
msgid "Jobs for last year"
msgstr ""
msgid "LFSStatus|Disabled" msgid "LFSStatus|Disabled"
msgstr "" msgstr ""
...@@ -535,6 +592,21 @@ msgstr "" ...@@ -535,6 +592,21 @@ msgstr ""
msgid "Pipeline Schedules" msgid "Pipeline Schedules"
msgstr "" msgstr ""
msgid "PipelineCharts|Failed:"
msgstr ""
msgid "PipelineCharts|Overall statistics"
msgstr ""
msgid "PipelineCharts|Success ratio:"
msgstr ""
msgid "PipelineCharts|Successful:"
msgstr ""
msgid "PipelineCharts|Total:"
msgstr ""
msgid "PipelineSchedules|Activated" msgid "PipelineSchedules|Activated"
msgstr "" msgstr ""
...@@ -565,6 +637,18 @@ msgstr "" ...@@ -565,6 +637,18 @@ msgstr ""
msgid "PipelineSheduleIntervalPattern|Custom" msgid "PipelineSheduleIntervalPattern|Custom"
msgstr "" msgstr ""
msgid "Pipelines"
msgstr ""
msgid "Pipelines charts"
msgstr ""
msgid "Pipeline|all"
msgstr ""
msgid "Pipeline|success"
msgstr ""
msgid "Pipeline|with stage" msgid "Pipeline|with stage"
msgstr "" msgstr ""
...@@ -688,7 +772,7 @@ msgstr "" ...@@ -688,7 +772,7 @@ msgstr ""
msgid "Select target branch" msgid "Select target branch"
msgstr "" msgstr ""
msgid "Set a password on your account to pull or push via %{protocol}" msgid "Set a password on your account to pull or push via %{protocol}."
msgstr "" msgstr ""
msgid "Set up CI" msgid "Set up CI"
...@@ -714,10 +798,7 @@ msgstr "" ...@@ -714,10 +798,7 @@ msgstr ""
msgid "StarProject|Star" msgid "StarProject|Star"
msgstr "" msgstr ""
msgid "Start a %{new_merge_request} with these changes" msgid "Start a %{new_merge_request} with these changes"
msgstr ""
msgid "Start a <strong>new merge request</strong> with these changes"
msgstr "" msgstr ""
msgid "Switch branch/tag" msgid "Switch branch/tag"
...@@ -948,9 +1029,15 @@ msgstr "" ...@@ -948,9 +1029,15 @@ msgstr ""
msgid "Upload file" msgid "Upload file"
msgstr "" msgstr ""
msgid "UploadLink|click to upload"
msgstr ""
msgid "Use your global notification setting" msgid "Use your global notification setting"
msgstr "" msgstr ""
msgid "View open merge request"
msgstr ""
msgid "VisibilityLevel|Internal" msgid "VisibilityLevel|Internal"
msgstr "" msgstr ""
......
...@@ -8,8 +8,8 @@ msgid "" ...@@ -8,8 +8,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: gitlab 1.0.0\n" "Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-06-19 15:50-0500\n" "POT-Creation-Date: 2017-06-28 13:32+0200\n"
"PO-Revision-Date: 2017-06-19 15:50-0500\n" "PO-Revision-Date: 2017-06-28 13:32+0200\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n" "Language: \n"
...@@ -18,7 +18,7 @@ msgstr "" ...@@ -18,7 +18,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
msgid "%d additional commit have been omitted to prevent performance issues." msgid "%d additional commit has been omitted to prevent performance issues."
msgid_plural "%d additional commits have been omitted to prevent performance issues." msgid_plural "%d additional commits have been omitted to prevent performance issues."
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
...@@ -31,6 +31,14 @@ msgstr[1] "" ...@@ -31,6 +31,14 @@ msgstr[1] ""
msgid "%{commit_author_link} committed %{commit_timeago}" msgid "%{commit_author_link} committed %{commit_timeago}"
msgstr "" msgstr ""
msgid "1 pipeline"
msgid_plural "%d pipelines"
msgstr[0] ""
msgstr[1] ""
msgid "A collection of graphs regarding Continuous Integration"
msgstr ""
msgid "About auto deploy" msgid "About auto deploy"
msgstr "" msgstr ""
...@@ -185,6 +193,9 @@ msgid_plural "Commits" ...@@ -185,6 +193,9 @@ msgid_plural "Commits"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
msgid "Commit duration in minutes for last 30 commits"
msgstr ""
msgid "Commit message" msgid "Commit message"
msgstr "" msgstr ""
...@@ -224,6 +235,9 @@ msgstr "" ...@@ -224,6 +235,9 @@ msgstr ""
msgid "Create New Directory" msgid "Create New Directory"
msgstr "" msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
msgid "Create directory" msgid "Create directory"
msgstr "" msgstr ""
...@@ -242,6 +256,9 @@ msgstr "" ...@@ -242,6 +256,9 @@ msgstr ""
msgid "CreateTag|Tag" msgid "CreateTag|Tag"
msgstr "" msgstr ""
msgid "CreateTokenToCloneLink|create a personal access token"
msgstr ""
msgid "Cron Timezone" msgid "Cron Timezone"
msgstr "" msgstr ""
...@@ -402,6 +419,15 @@ msgstr "" ...@@ -402,6 +419,15 @@ msgstr ""
msgid "Introducing Cycle Analytics" msgid "Introducing Cycle Analytics"
msgstr "" msgstr ""
msgid "Jobs for last month"
msgstr ""
msgid "Jobs for last week"
msgstr ""
msgid "Jobs for last year"
msgstr ""
msgid "LFSStatus|Disabled" msgid "LFSStatus|Disabled"
msgstr "" msgstr ""
...@@ -567,6 +593,21 @@ msgstr "" ...@@ -567,6 +593,21 @@ msgstr ""
msgid "Pipeline Schedules" msgid "Pipeline Schedules"
msgstr "" msgstr ""
msgid "PipelineCharts|Failed:"
msgstr ""
msgid "PipelineCharts|Overall statistics"
msgstr ""
msgid "PipelineCharts|Success ratio:"
msgstr ""
msgid "PipelineCharts|Successful:"
msgstr ""
msgid "PipelineCharts|Total:"
msgstr ""
msgid "PipelineSchedules|Activated" msgid "PipelineSchedules|Activated"
msgstr "" msgstr ""
...@@ -597,6 +638,18 @@ msgstr "" ...@@ -597,6 +638,18 @@ msgstr ""
msgid "PipelineSheduleIntervalPattern|Custom" msgid "PipelineSheduleIntervalPattern|Custom"
msgstr "" msgstr ""
msgid "Pipelines"
msgstr ""
msgid "Pipelines charts"
msgstr ""
msgid "Pipeline|all"
msgstr ""
msgid "Pipeline|success"
msgstr ""
msgid "Pipeline|with stage" msgid "Pipeline|with stage"
msgstr "" msgstr ""
...@@ -720,7 +773,7 @@ msgstr "" ...@@ -720,7 +773,7 @@ msgstr ""
msgid "Select target branch" msgid "Select target branch"
msgstr "" msgstr ""
msgid "Set a password on your account to pull or push via %{protocol}" msgid "Set a password on your account to pull or push via %{protocol}."
msgstr "" msgstr ""
msgid "Set up CI" msgid "Set up CI"
......
This diff is collapsed.
...@@ -135,7 +135,7 @@ feature 'Group', feature: true do ...@@ -135,7 +135,7 @@ feature 'Group', feature: true do
expect(page).not_to have_content('secret-group') expect(page).not_to have_content('secret-group')
end end
describe 'group edit' do describe 'group edit', js: true do
let(:group) { create(:group) } let(:group) { create(:group) }
let(:path) { edit_group_path(group) } let(:path) { edit_group_path(group) }
let(:new_name) { 'new-name' } let(:new_name) { 'new-name' }
...@@ -157,8 +157,8 @@ feature 'Group', feature: true do ...@@ -157,8 +157,8 @@ feature 'Group', feature: true do
end end
it 'removes group' do it 'removes group' do
click_link 'Remove group' expect { remove_with_confirm('Remove group', group.path) }.to change {Group.count}.by(-1)
expect(group.members.all.count).to be_zero
expect(page).to have_content "scheduled for deletion" expect(page).to have_content "scheduled for deletion"
end end
end end
...@@ -212,4 +212,10 @@ feature 'Group', feature: true do ...@@ -212,4 +212,10 @@ feature 'Group', feature: true do
expect(page).to have_content(nested_group.name) expect(page).to have_content(nested_group.name)
end end
end end
def remove_with_confirm(button_text, confirm_with)
click_button button_text
fill_in 'confirm_name_input', with: confirm_with
click_button 'Confirm'
end
end end
...@@ -31,8 +31,8 @@ describe 'New/edit issue', :feature, :js do ...@@ -31,8 +31,8 @@ describe 'New/edit issue', :feature, :js do
# the original method, resulting in infinite recurison when called. # the original method, resulting in infinite recurison when called.
# This is likely a bug with helper modules included into dynamically generated view classes. # This is likely a bug with helper modules included into dynamically generated view classes.
# To work around this, we have to hold on to and call to the original implementation manually. # To work around this, we have to hold on to and call to the original implementation manually.
original_issue_dropdown_options = FormHelper.instance_method(:issue_dropdown_options) original_issue_dropdown_options = FormHelper.instance_method(:issue_assignees_dropdown_options)
allow_any_instance_of(FormHelper).to receive(:issue_dropdown_options).and_wrap_original do |original, *args| allow_any_instance_of(FormHelper).to receive(:issue_assignees_dropdown_options).and_wrap_original do |original, *args|
options = original_issue_dropdown_options.bind(original.receiver).call(*args) options = original_issue_dropdown_options.bind(original.receiver).call(*args)
options[:data][:per_page] = 2 options[:data][:per_page] = 2
......
...@@ -41,13 +41,10 @@ feature 'issue move to another project' do ...@@ -41,13 +41,10 @@ feature 'issue move to another project' do
find('#issuable-move', visible: false).set(new_project.id) find('#issuable-move', visible: false).set(new_project.id)
click_button('Save changes') click_button('Save changes')
wait_for_requests
expect(current_url).to include project_path(new_project)
expect(page).to have_content("Text with #{cross_reference}#{mr.to_reference}") expect(page).to have_content("Text with #{cross_reference}#{mr.to_reference}")
expect(page).to have_content("moved from #{cross_reference}#{issue.to_reference}") expect(page).to have_content("moved from #{cross_reference}#{issue.to_reference}")
expect(page).to have_content(issue.title) expect(page).to have_content(issue.title)
expect(page.current_path).to include project_path(new_project)
end end
scenario 'searching project dropdown', js: true do scenario 'searching project dropdown', js: true do
......
...@@ -49,12 +49,12 @@ describe LabelsFinder do ...@@ -49,12 +49,12 @@ describe LabelsFinder do
end end
context 'filtering by group_id' do context 'filtering by group_id' do
it 'returns labels available for any project within the group' do it 'returns labels available for any non-archived project within the group' do
group_1.add_developer(user) group_1.add_developer(user)
project_1.archive!
finder = described_class.new(user, group_id: group_1.id) finder = described_class.new(user, group_id: group_1.id)
expect(finder.execute).to eq [group_label_2, project_label_1, group_label_1, project_label_5] expect(finder.execute).to eq [group_label_2, group_label_1, project_label_5]
end end
end end
......
require 'spec_helper' require 'spec_helper'
require_relative '../../config/initializers/8_metrics'
describe 'instrument_classes', lib: true do describe 'instrument_classes', lib: true do
let(:config) { double(:config) } let(:config) { double(:config) }
let(:unicorn_sampler) { double(:unicorn_sampler) }
let(:influx_sampler) { double(:influx_sampler) }
before do before do
allow(config).to receive(:instrument_method) allow(config).to receive(:instrument_method)
allow(config).to receive(:instrument_methods) allow(config).to receive(:instrument_methods)
allow(config).to receive(:instrument_instance_method) allow(config).to receive(:instrument_instance_method)
allow(config).to receive(:instrument_instance_methods) allow(config).to receive(:instrument_instance_methods)
allow(Gitlab::Metrics::UnicornSampler).to receive(:initialize_instance).and_return(unicorn_sampler)
allow(Gitlab::Metrics::InfluxSampler).to receive(:initialize_instance).and_return(influx_sampler)
allow(unicorn_sampler).to receive(:start)
allow(influx_sampler).to receive(:start)
allow(Gitlab::Application).to receive(:configure)
end end
it 'can autoload and instrument all files' do it 'can autoload and instrument all files' do
require_relative '../../config/initializers/8_metrics'
expect { instrument_classes(config) }.not_to raise_error expect { instrument_classes(config) }.not_to raise_error
end end
end end
...@@ -13,7 +13,7 @@ const metricsGroupsAPIResponse = { ...@@ -13,7 +13,7 @@ const metricsGroupsAPIResponse = {
'queries': [ 'queries': [
{ {
'query_range': 'avg(container_memory_usage_bytes{%{environment_filter}}) / 2^20', 'query_range': 'avg(container_memory_usage_bytes{%{environment_filter}}) / 2^20',
'label': 'Container memory', 'y_label': 'Memory',
'unit': 'MiB', 'unit': 'MiB',
'result': [ 'result': [
{ {
...@@ -2477,7 +2477,7 @@ export const singleRowMetrics = [ ...@@ -2477,7 +2477,7 @@ export const singleRowMetrics = [
{ {
'title': 'CPU usage', 'title': 'CPU usage',
'weight': 1, 'weight': 1,
'y_label': 'Values', 'y_label': 'Memory',
'queries': [ 'queries': [
{ {
'query_range': 'avg(rate(container_cpu_usage_seconds_total{%{environment_filter}}[2m])) * 100', 'query_range': 'avg(rate(container_cpu_usage_seconds_total{%{environment_filter}}[2m])) * 100',
......
...@@ -94,4 +94,15 @@ describe('MonitoringColumn', () => { ...@@ -94,4 +94,15 @@ describe('MonitoringColumn', () => {
done(); done();
}); });
}); });
it('has a title for the y-axis that comes from the backend', () => {
const component = createComponent({
columnData: singleRowMetrics[0],
classType: 'col-md-6',
updateAspectRatio: false,
deploymentData,
});
expect(component.yAxisLabel).toEqual(component.columnData.y_label);
});
}); });
...@@ -20,6 +20,7 @@ describe Gitlab::Git::Blame, seed_helper: true do ...@@ -20,6 +20,7 @@ describe Gitlab::Git::Blame, seed_helper: true do
expect(data.size).to eq(95) expect(data.size).to eq(95)
expect(data.first[:commit]).to be_kind_of(Gitlab::Git::Commit) expect(data.first[:commit]).to be_kind_of(Gitlab::Git::Commit)
expect(data.first[:line]).to eq("# Contribute to GitLab") expect(data.first[:line]).to eq("# Contribute to GitLab")
expect(data.first[:line]).to be_utf8
end end
end end
...@@ -40,6 +41,7 @@ describe Gitlab::Git::Blame, seed_helper: true do ...@@ -40,6 +41,7 @@ describe Gitlab::Git::Blame, seed_helper: true do
expect(data.size).to eq(1) expect(data.size).to eq(1)
expect(data.first[:commit]).to be_kind_of(Gitlab::Git::Commit) expect(data.first[:commit]).to be_kind_of(Gitlab::Git::Commit)
expect(data.first[:line]).to eq("Ä ü") expect(data.first[:line]).to eq("Ä ü")
expect(data.first[:line]).to be_utf8
end end
end end
...@@ -61,6 +63,7 @@ describe Gitlab::Git::Blame, seed_helper: true do ...@@ -61,6 +63,7 @@ describe Gitlab::Git::Blame, seed_helper: true do
expect(data.size).to eq(1) expect(data.size).to eq(1)
expect(data.first[:commit]).to be_kind_of(Gitlab::Git::Commit) expect(data.first[:commit]).to be_kind_of(Gitlab::Git::Commit)
expect(data.first[:line]).to eq(" ") expect(data.first[:line]).to eq(" ")
expect(data.first[:line]).to be_utf8
end end
end end
end end
...@@ -48,7 +48,7 @@ describe Gitlab::Git::Branch, seed_helper: true do ...@@ -48,7 +48,7 @@ describe Gitlab::Git::Branch, seed_helper: true do
expect(Gitlab::Git::Commit).to receive(:decorate) expect(Gitlab::Git::Commit).to receive(:decorate)
.with(hash_including(attributes)).and_call_original .with(hash_including(attributes)).and_call_original
expect(branch.dereferenced_target.message.encoding).to be(Encoding::UTF_8) expect(branch.dereferenced_target.message).to be_utf8
end end
end end
......
...@@ -180,7 +180,7 @@ EOT ...@@ -180,7 +180,7 @@ EOT
let(:raw_patch) { @raw_diff_hash[:diff].encode(Encoding::ASCII_8BIT) } let(:raw_patch) { @raw_diff_hash[:diff].encode(Encoding::ASCII_8BIT) }
it 'encodes diff patch to UTF-8' do it 'encodes diff patch to UTF-8' do
expect(diff.diff.encoding).to eq(Encoding::UTF_8) expect(diff.diff).to be_utf8
end end
end end
end end
......
...@@ -27,34 +27,24 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -27,34 +27,24 @@ describe Gitlab::Git::Repository, seed_helper: true do
end end
it 'returns UTF-8' do it 'returns UTF-8' do
expect(repository.root_ref.encoding).to eq(Encoding.find('UTF-8')) expect(repository.root_ref).to be_utf8
end end
context 'with gitaly enabled' do it 'gets the branch name from GitalyClient' do
before do expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:default_branch_name)
stub_gitaly repository.root_ref
end end
after do
Gitlab::GitalyClient.clear_stubs!
end
it 'gets the branch name from GitalyClient' do
expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:default_branch_name)
repository.root_ref
end
it 'wraps GRPC not found' do it 'wraps GRPC not found' do
expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:default_branch_name) expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:default_branch_name)
.and_raise(GRPC::NotFound) .and_raise(GRPC::NotFound)
expect { repository.root_ref }.to raise_error(Gitlab::Git::Repository::NoRepository) expect { repository.root_ref }.to raise_error(Gitlab::Git::Repository::NoRepository)
end end
it 'wraps GRPC exceptions' do it 'wraps GRPC exceptions' do
expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:default_branch_name) expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:default_branch_name)
.and_raise(GRPC::Unknown) .and_raise(GRPC::Unknown)
expect { repository.root_ref }.to raise_error(Gitlab::Git::CommandError) expect { repository.root_ref }.to raise_error(Gitlab::Git::CommandError)
end
end end
end end
...@@ -129,37 +119,27 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -129,37 +119,27 @@ describe Gitlab::Git::Repository, seed_helper: true do
end end
it 'returns UTF-8' do it 'returns UTF-8' do
expect(subject.first.encoding).to eq(Encoding.find('UTF-8')) expect(subject.first).to be_utf8
end end
it { is_expected.to include("master") } it { is_expected.to include("master") }
it { is_expected.not_to include("branch-from-space") } it { is_expected.not_to include("branch-from-space") }
context 'with gitaly enabled' do it 'gets the branch names from GitalyClient' do
before do expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:branch_names)
stub_gitaly subject
end end
after do
Gitlab::GitalyClient.clear_stubs!
end
it 'gets the branch names from GitalyClient' do
expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:branch_names)
subject
end
it 'wraps GRPC not found' do it 'wraps GRPC not found' do
expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:branch_names) expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:branch_names)
.and_raise(GRPC::NotFound) .and_raise(GRPC::NotFound)
expect { subject }.to raise_error(Gitlab::Git::Repository::NoRepository) expect { subject }.to raise_error(Gitlab::Git::Repository::NoRepository)
end end
it 'wraps GRPC other exceptions' do it 'wraps GRPC other exceptions' do
expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:branch_names) expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:branch_names)
.and_raise(GRPC::Unknown) .and_raise(GRPC::Unknown)
expect { subject }.to raise_error(Gitlab::Git::CommandError) expect { subject }.to raise_error(Gitlab::Git::CommandError)
end
end end
end end
...@@ -173,7 +153,7 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -173,7 +153,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
end end
it 'returns UTF-8' do it 'returns UTF-8' do
expect(subject.first.encoding).to eq(Encoding.find('UTF-8')) expect(subject.first).to be_utf8
end end
describe '#last' do describe '#last' do
...@@ -183,31 +163,21 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -183,31 +163,21 @@ describe Gitlab::Git::Repository, seed_helper: true do
it { is_expected.to include("v1.0.0") } it { is_expected.to include("v1.0.0") }
it { is_expected.not_to include("v5.0.0") } it { is_expected.not_to include("v5.0.0") }
context 'with gitaly enabled' do it 'gets the tag names from GitalyClient' do
before do expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:tag_names)
stub_gitaly subject
end end
after do
Gitlab::GitalyClient.clear_stubs!
end
it 'gets the tag names from GitalyClient' do
expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:tag_names)
subject
end
it 'wraps GRPC not found' do it 'wraps GRPC not found' do
expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:tag_names) expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:tag_names)
.and_raise(GRPC::NotFound) .and_raise(GRPC::NotFound)
expect { subject }.to raise_error(Gitlab::Git::Repository::NoRepository) expect { subject }.to raise_error(Gitlab::Git::Repository::NoRepository)
end end
it 'wraps GRPC exceptions' do it 'wraps GRPC exceptions' do
expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:tag_names) expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:tag_names)
.and_raise(GRPC::Unknown) .and_raise(GRPC::Unknown)
expect { subject }.to raise_error(Gitlab::Git::CommandError) expect { subject }.to raise_error(Gitlab::Git::CommandError)
end
end end
end end
...@@ -1281,42 +1251,31 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -1281,42 +1251,31 @@ describe Gitlab::Git::Repository, seed_helper: true do
expect(@repo.local_branches.any? { |branch| branch.name == 'local_branch' }).to eq(true) expect(@repo.local_branches.any? { |branch| branch.name == 'local_branch' }).to eq(true)
end end
context 'with gitaly enabled' do it 'returns a Branch with UTF-8 fields' do
before do branches = @repo.local_branches.to_a
stub_gitaly expect(branches.size).to be > 0
end branches.each do |branch|
expect(branch.name).to be_utf8
after do expect(branch.target).to be_utf8 unless branch.target.nil?
Gitlab::GitalyClient.clear_stubs!
end
it 'returns a Branch with UTF-8 fields' do
branches = @repo.local_branches.to_a
expect(branches.size).to be > 0
utf_8 = Encoding.find('utf-8')
branches.each do |branch|
expect(branch.name.encoding).to eq(utf_8)
expect(branch.target.encoding).to eq(utf_8) unless branch.target.nil?
end
end end
end
it 'gets the branches from GitalyClient' do it 'gets the branches from GitalyClient' do
expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:local_branches) expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:local_branches)
.and_return([]) .and_return([])
@repo.local_branches @repo.local_branches
end end
it 'wraps GRPC not found' do it 'wraps GRPC not found' do
expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:local_branches) expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:local_branches)
.and_raise(GRPC::NotFound) .and_raise(GRPC::NotFound)
expect { @repo.local_branches }.to raise_error(Gitlab::Git::Repository::NoRepository) expect { @repo.local_branches }.to raise_error(Gitlab::Git::Repository::NoRepository)
end end
it 'wraps GRPC exceptions' do it 'wraps GRPC exceptions' do
expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:local_branches) expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:local_branches)
.and_raise(GRPC::Unknown) .and_raise(GRPC::Unknown)
expect { @repo.local_branches }.to raise_error(Gitlab::Git::CommandError) expect { @repo.local_branches }.to raise_error(Gitlab::Git::CommandError)
end
end end
end end
...@@ -1395,11 +1354,4 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -1395,11 +1354,4 @@ describe Gitlab::Git::Repository, seed_helper: true do
sha = Rugged::Commit.create(repo, options) sha = Rugged::Commit.create(repo, options)
repo.lookup(sha) repo.lookup(sha)
end end
def stub_gitaly
allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_return(true)
stub = double(:stub)
allow(Gitaly::Ref::Stub).to receive(:new).and_return(stub)
end
end end
...@@ -6,17 +6,6 @@ describe Gitlab::GitalyClient::Ref do ...@@ -6,17 +6,6 @@ describe Gitlab::GitalyClient::Ref do
let(:relative_path) { project.path_with_namespace + '.git' } let(:relative_path) { project.path_with_namespace + '.git' }
let(:client) { described_class.new(project.repository) } let(:client) { described_class.new(project.repository) }
before do
allow(Gitlab.config.gitaly).to receive(:enabled).and_return(true)
end
after do
# When we say `expect_any_instance_of(Gitaly::Ref::Stub)` a double is created,
# and because GitalyClient shares stubs these will get passed from example to
# example, which will cause an error, so we clean the stubs after each example.
Gitlab::GitalyClient.clear_stubs!
end
describe '#branch_names' do describe '#branch_names' do
it 'sends a find_all_branch_names message' do it 'sends a find_all_branch_names message' do
expect_any_instance_of(Gitaly::Ref::Stub) expect_any_instance_of(Gitaly::Ref::Stub)
...@@ -82,4 +71,13 @@ describe Gitlab::GitalyClient::Ref do ...@@ -82,4 +71,13 @@ describe Gitlab::GitalyClient::Ref do
expect { client.local_branches(sort_by: 'invalid_sort') }.to raise_error(ArgumentError) expect { client.local_branches(sort_by: 'invalid_sort') }.to raise_error(ArgumentError)
end end
end end
describe '#find_ref_name', seed_helper: true do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH) }
let(:client) { described_class.new(repository) }
subject { client.find_ref_name(SeedRepo::Commit::ID, 'refs/heads/master') }
it { should be_utf8 }
it { should eq('refs/heads/master') }
end
end end
...@@ -97,30 +97,40 @@ describe Gitlab::HealthChecks::FsShardsCheck do ...@@ -97,30 +97,40 @@ describe Gitlab::HealthChecks::FsShardsCheck do
}.with_indifferent_access }.with_indifferent_access
end end
it { is_expected.to all(have_attributes(labels: { shard: :default })) } # Unsolved intermittent failure in CI https://gitlab.com/gitlab-org/gitlab-ce/issues/31128
around(:each) do |example| # rubocop:disable RSpec/AroundBlock
times_to_try = ENV['CI'] ? 4 : 1
example.run_with_retry retry: times_to_try
end
it { is_expected.to include(an_object_having_attributes(name: :filesystem_accessible, value: 0)) } it 'provides metrics' do
it { is_expected.to include(an_object_having_attributes(name: :filesystem_readable, value: 0)) } expect(subject).to all(have_attributes(labels: { shard: :default }))
it { is_expected.to include(an_object_having_attributes(name: :filesystem_writable, value: 0)) } expect(subject).to include(an_object_having_attributes(name: :filesystem_accessible, value: 0))
expect(subject).to include(an_object_having_attributes(name: :filesystem_readable, value: 0))
expect(subject).to include(an_object_having_attributes(name: :filesystem_writable, value: 0))
it { is_expected.to include(an_object_having_attributes(name: :filesystem_access_latency, value: be >= 0)) } expect(subject).to include(an_object_having_attributes(name: :filesystem_access_latency, value: be >= 0))
it { is_expected.to include(an_object_having_attributes(name: :filesystem_read_latency, value: be >= 0)) } expect(subject).to include(an_object_having_attributes(name: :filesystem_read_latency, value: be >= 0))
it { is_expected.to include(an_object_having_attributes(name: :filesystem_write_latency, value: be >= 0)) } expect(subject).to include(an_object_having_attributes(name: :filesystem_write_latency, value: be >= 0))
end
end end
context 'storage points to directory that has both read and write rights' do context 'storage points to directory that has both read and write rights' do
before do before do
FileUtils.chmod_R(0755, tmp_dir) FileUtils.chmod_R(0755, tmp_dir)
end end
it { is_expected.to all(have_attributes(labels: { shard: :default })) }
it { is_expected.to include(an_object_having_attributes(name: :filesystem_accessible, value: 1)) } it 'provides metrics' do
it { is_expected.to include(an_object_having_attributes(name: :filesystem_readable, value: 1)) } expect(subject).to all(have_attributes(labels: { shard: :default }))
it { is_expected.to include(an_object_having_attributes(name: :filesystem_writable, value: 1)) }
it { is_expected.to include(an_object_having_attributes(name: :filesystem_access_latency, value: be >= 0)) } expect(subject).to include(an_object_having_attributes(name: :filesystem_accessible, value: 1))
it { is_expected.to include(an_object_having_attributes(name: :filesystem_read_latency, value: be >= 0)) } expect(subject).to include(an_object_having_attributes(name: :filesystem_readable, value: 1))
it { is_expected.to include(an_object_having_attributes(name: :filesystem_write_latency, value: be >= 0)) } expect(subject).to include(an_object_having_attributes(name: :filesystem_writable, value: 1))
expect(subject).to include(an_object_having_attributes(name: :filesystem_access_latency, value: be >= 0))
expect(subject).to include(an_object_having_attributes(name: :filesystem_read_latency, value: be >= 0))
expect(subject).to include(an_object_having_attributes(name: :filesystem_write_latency, value: be >= 0))
end
end end
end end
end end
......
require 'spec_helper'
describe Gitlab::Metrics::ConnectionRackMiddleware do
let(:app) { double('app') }
subject { described_class.new(app) }
around do |example|
Timecop.freeze { example.run }
end
describe '#call' do
let(:status) { 100 }
let(:env) { { 'REQUEST_METHOD' => 'GET' } }
let(:stack_result) { [status, {}, 'body'] }
before do
allow(app).to receive(:call).and_return(stack_result)
end
context '@app.call succeeds with 200' do
before do
allow(app).to receive(:call).and_return([200, nil, nil])
end
it 'increments response count with status label' do
expect(described_class).to receive_message_chain(:rack_response_count, :increment).with(include(status: 200, method: 'get'))
subject.call(env)
end
it 'increments requests count' do
expect(described_class).to receive_message_chain(:rack_request_count, :increment).with(method: 'get')
subject.call(env)
end
it 'measures execution time' do
execution_time = 10
allow(app).to receive(:call) do |*args|
Timecop.freeze(execution_time.seconds)
end
expect(described_class).to receive_message_chain(:rack_execution_time, :observe).with({}, execution_time)
subject.call(env)
end
end
context '@app.call throws exception' do
let(:rack_response_count) { double('rack_response_count') }
before do
allow(app).to receive(:call).and_raise(StandardError)
allow(described_class).to receive(:rack_response_count).and_return(rack_response_count)
end
it 'increments exceptions count' do
expect(described_class).to receive_message_chain(:rack_uncaught_errors_count, :increment)
expect { subject.call(env) }.to raise_error(StandardError)
end
it 'increments requests count' do
expect(described_class).to receive_message_chain(:rack_request_count, :increment).with(method: 'get')
expect { subject.call(env) }.to raise_error(StandardError)
end
it "does't increment response count" do
expect(described_class.rack_response_count).not_to receive(:increment)
expect { subject.call(env) }.to raise_error(StandardError)
end
it 'measures execution time' do
execution_time = 10
allow(app).to receive(:call) do |*args|
Timecop.freeze(execution_time.seconds)
raise StandardError
end
expect(described_class).to receive_message_chain(:rack_execution_time, :observe).with({}, execution_time)
expect { subject.call(env) }.to raise_error(StandardError)
end
end
end
end
require 'spec_helper' require 'spec_helper'
describe Gitlab::Metrics::Sampler do describe Gitlab::Metrics::InfluxSampler do
let(:sampler) { described_class.new(5) } let(:sampler) { described_class.new(5) }
after do after do
...@@ -8,10 +8,10 @@ describe Gitlab::Metrics::Sampler do ...@@ -8,10 +8,10 @@ describe Gitlab::Metrics::Sampler do
end end
describe '#start' do describe '#start' do
it 'gathers a sample at a given interval' do it 'runs once and gathers a sample at a given interval' do
expect(sampler).to receive(:sleep).with(a_kind_of(Numeric)) expect(sampler).to receive(:sleep).with(a_kind_of(Numeric)).twice
expect(sampler).to receive(:sample) expect(sampler).to receive(:sample).once
expect(sampler).to receive(:loop).and_yield expect(sampler).to receive(:running).and_return(false, true, false)
sampler.start.join sampler.start.join
end end
......
require 'spec_helper'
describe Gitlab::Metrics::UnicornSampler do
subject { described_class.new(1.second) }
describe '#sample' do
let(:unicorn) { double('unicorn') }
let(:raindrops) { double('raindrops') }
let(:stats) { double('stats') }
before do
stub_const('Unicorn', unicorn)
stub_const('Raindrops::Linux', raindrops)
allow(raindrops).to receive(:unix_listener_stats).and_return({})
allow(raindrops).to receive(:tcp_listener_stats).and_return({})
end
context 'unicorn listens on unix sockets' do
let(:socket_address) { '/some/sock' }
let(:sockets) { [socket_address] }
before do
allow(unicorn).to receive(:listener_names).and_return(sockets)
end
it 'samples socket data' do
expect(raindrops).to receive(:unix_listener_stats).with(sockets)
subject.sample
end
context 'stats collected' do
before do
allow(stats).to receive(:active).and_return('active')
allow(stats).to receive(:queued).and_return('queued')
allow(raindrops).to receive(:unix_listener_stats).and_return({ socket_address => stats })
end
it 'updates metrics type unix and with addr' do
labels = { type: 'unix', address: socket_address }
expect(subject).to receive_message_chain(:unicorn_active_connections, :set).with(labels, 'active')
expect(subject).to receive_message_chain(:unicorn_queued_connections, :set).with(labels, 'queued')
subject.sample
end
end
end
context 'unicorn listens on tcp sockets' do
let(:tcp_socket_address) { '0.0.0.0:8080' }
let(:tcp_sockets) { [tcp_socket_address] }
before do
allow(unicorn).to receive(:listener_names).and_return(tcp_sockets)
end
it 'samples socket data' do
expect(raindrops).to receive(:tcp_listener_stats).with(tcp_sockets)
subject.sample
end
context 'stats collected' do
before do
allow(stats).to receive(:active).and_return('active')
allow(stats).to receive(:queued).and_return('queued')
allow(raindrops).to receive(:tcp_listener_stats).and_return({ tcp_socket_address => stats })
end
it 'updates metrics type unix and with addr' do
labels = { type: 'tcp', address: tcp_socket_address }
expect(subject).to receive_message_chain(:unicorn_active_connections, :set).with(labels, 'active')
expect(subject).to receive_message_chain(:unicorn_queued_connections, :set).with(labels, 'queued')
subject.sample
end
end
end
end
describe '#start' do
context 'when enabled' do
before do
allow(subject).to receive(:enabled?).and_return(true)
end
it 'creates new thread' do
expect(Thread).to receive(:new)
subject.start
end
end
context 'when disabled' do
before do
allow(subject).to receive(:enabled?).and_return(false)
end
it "doesn't create new thread" do
expect(Thread).not_to receive(:new)
subject.start
end
end
end
end
...@@ -998,13 +998,17 @@ describe Ci::Build, :models do ...@@ -998,13 +998,17 @@ describe Ci::Build, :models do
describe '#ref_slug' do describe '#ref_slug' do
{ {
'master' => 'master', 'master' => 'master',
'1-foo' => '1-foo', '1-foo' => '1-foo',
'fix/1-foo' => 'fix-1-foo', 'fix/1-foo' => 'fix-1-foo',
'fix-1-foo' => 'fix-1-foo', 'fix-1-foo' => 'fix-1-foo',
'a' * 63 => 'a' * 63, 'a' * 63 => 'a' * 63,
'a' * 64 => 'a' * 63, 'a' * 64 => 'a' * 63,
'FOO' => 'foo' 'FOO' => 'foo',
'-' + 'a' * 61 + '-' => 'a' * 61,
'-' + 'a' * 62 + '-' => 'a' * 62,
'-' + 'a' * 63 + '-' => 'a' * 62,
'a' * 62 + ' ' => 'a' * 62
}.each do |ref, slug| }.each do |ref, slug|
it "transforms #{ref} to #{slug}" do it "transforms #{ref} to #{slug}" do
build.ref = ref build.ref = ref
......
require 'spec_helper'
describe Sortable do
let(:relation) { Issue.all }
describe '#where' do
it 'orders by id, descending' do
order_node = relation.where(iid: 1).order_values.first
expect(order_node).to be_a(Arel::Nodes::Descending)
expect(order_node.expr.name).to eq(:id)
end
end
describe '#find_by' do
it 'does not order' do
expect(relation).to receive(:unscope).with(:order).and_call_original
relation.find_by(iid: 1)
end
end
end
...@@ -120,28 +120,17 @@ describe Environment, models: true do ...@@ -120,28 +120,17 @@ describe Environment, models: true do
let(:head_commit) { project.commit } let(:head_commit) { project.commit }
let(:commit) { project.commit.parent } let(:commit) { project.commit.parent }
context 'Gitaly find_ref_name feature disabled' do it 'returns deployment id for the environment' do
it 'returns deployment id for the environment' do expect(environment.first_deployment_for(commit)).to eq deployment1
expect(environment.first_deployment_for(commit)).to eq deployment1 end
end
it 'return nil when no deployment is found' do it 'return nil when no deployment is found' do
expect(environment.first_deployment_for(head_commit)).to eq nil expect(environment.first_deployment_for(head_commit)).to eq nil
end
end end
# TODO: Uncomment when feature is reenabled it 'returns a UTF-8 ref' do
# context 'Gitaly find_ref_name feature enabled' do expect(environment.first_deployment_for(commit).ref).to be_utf8
# before do end
# allow(Gitlab::GitalyClient).to receive(:feature_enabled?).with(:find_ref_name).and_return(true)
# end
#
# it 'calls GitalyClient' do
# expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:find_ref_name)
#
# environment.first_deployment_for(commit)
# end
# end
end end
describe '#environment_type' do describe '#environment_type' do
......
...@@ -2,53 +2,75 @@ require 'spec_helper' ...@@ -2,53 +2,75 @@ require 'spec_helper'
describe ForkedProjectLink, "add link on fork" do describe ForkedProjectLink, "add link on fork" do
let(:project_from) { create(:project, :repository) } let(:project_from) { create(:project, :repository) }
let(:project_to) { fork_project(project_from, user) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:namespace) { user.namespace } let(:namespace) { user.namespace }
before do before do
create(:project_member, :reporter, user: user, project: project_from) project_from.add_reporter(user)
@project_to = fork_project(project_from, user) end
it 'project_from knows its forks' do
_ = project_to
expect(project_from.forks.count).to eq(1)
end end
it "project_to knows it is forked" do it "project_to knows it is forked" do
expect(@project_to.forked?).to be_truthy expect(project_to.forked?).to be_truthy
end end
it "project knows who it is forked from" do it "project knows who it is forked from" do
expect(@project_to.forked_from_project).to eq(project_from) expect(project_to.forked_from_project).to eq(project_from)
end end
end
describe '#forked?' do context 'project_to is pending_delete' do
let(:forked_project_link) { build(:forked_project_link) } before do
let(:project_from) { create(:project, :repository) } project_to.update!(pending_delete: true)
let(:project_to) { create(:project, forked_project_link: forked_project_link) } end
before :each do it { expect(project_from.forks.count).to eq(0) }
forked_project_link.forked_from_project = project_from
forked_project_link.forked_to_project = project_to
forked_project_link.save!
end end
it "project_to knows it is forked" do context 'project_from is pending_delete' do
expect(project_to.forked?).to be_truthy before do
end project_from.update!(pending_delete: true)
end
it "project_from is not forked" do it { expect(project_to.forked_from_project).to be_nil }
expect(project_from.forked?).to be_falsey
end end
it "project_to.destroy destroys fork_link" do describe '#forked?' do
expect(forked_project_link).to receive(:destroy) let(:project_to) { create(:project, forked_project_link: forked_project_link) }
project_to.destroy let(:forked_project_link) { build(:forked_project_link) }
before do
forked_project_link.forked_from_project = project_from
forked_project_link.forked_to_project = project_to
forked_project_link.save!
end
it "project_to knows it is forked" do
expect(project_to.forked?).to be_truthy
end
it "project_from is not forked" do
expect(project_from.forked?).to be_falsey
end
it "project_to.destroy destroys fork_link" do
expect(forked_project_link).to receive(:destroy)
project_to.destroy
end
end end
end
def fork_project(from_project, user) def fork_project(from_project, user)
shell = double('gitlab_shell', fork_repository: true) service = Projects::ForkService.new(from_project, user)
shell = double('gitlab_shell', fork_repository: true)
service = Projects::ForkService.new(from_project, user) allow(service).to receive(:gitlab_shell).and_return(shell)
allow(service).to receive(:gitlab_shell).and_return(shell)
service.execute service.execute
end
end end
...@@ -105,6 +105,22 @@ describe MergeRequest, models: true do ...@@ -105,6 +105,22 @@ describe MergeRequest, models: true do
end end
end end
describe '#assignee_ids' do
it 'returns an array of the assigned user id' do
subject.assignee_id = 123
expect(subject.assignee_ids).to eq([123])
end
end
describe '#assignee_ids=' do
it 'sets assignee_id to the last id in the array' do
subject.assignee_ids = [123, 456]
expect(subject.assignee_id).to eq(456)
end
end
describe '#assignee_or_author?' do describe '#assignee_or_author?' do
let(:user) { create(:user) } let(:user) { create(:user) }
......
require 'spec_helper'
describe GlobalPolicy, models: true do
let(:current_user) { create(:user) }
let(:user) { create(:user) }
subject { GlobalPolicy.new(current_user, [user]) }
describe "reading the list of users" do
context "for a logged in user" do
it { is_expected.to be_allowed(:read_users_list) }
end
context "for an anonymous user" do
let(:current_user) { nil }
context "when the public level is restricted" do
before do
stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
end
it { is_expected.not_to be_allowed(:read_users_list) }
end
context "when the public level is not restricted" do
before do
stub_application_setting(restricted_visibility_levels: [])
end
it { is_expected.to be_allowed(:read_users_list) }
end
end
end
end
...@@ -13,9 +13,40 @@ describe API::Users do ...@@ -13,9 +13,40 @@ describe API::Users do
describe 'GET /users' do describe 'GET /users' do
context "when unauthenticated" do context "when unauthenticated" do
it "returns authentication error" do it "returns authorization error when the `username` parameter is not passed" do
get api("/users") get api("/users")
expect(response).to have_http_status(401)
expect(response).to have_http_status(403)
end
it "returns the user when a valid `username` parameter is passed" do
user = create(:user)
get api("/users"), username: user.username
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.size).to eq(1)
expect(json_response[0]['id']).to eq(user.id)
expect(json_response[0]['username']).to eq(user.username)
end
it "returns authorization error when the `username` parameter refers to an inaccessible user" do
user = create(:user)
stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
get api("/users"), username: user.username
expect(response).to have_http_status(403)
end
it "returns an empty response when an invalid `username` parameter is passed" do
get api("/users"), username: 'invalid'
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.size).to eq(0)
end end
end end
...@@ -138,6 +169,7 @@ describe API::Users do ...@@ -138,6 +169,7 @@ describe API::Users do
describe "GET /users/:id" do describe "GET /users/:id" do
it "returns a user by id" do it "returns a user by id" do
get api("/users/#{user.id}", user) get api("/users/#{user.id}", user)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response['username']).to eq(user.username) expect(json_response['username']).to eq(user.username)
end end
...@@ -148,9 +180,22 @@ describe API::Users do ...@@ -148,9 +180,22 @@ describe API::Users do
expect(json_response['is_admin']).to be_nil expect(json_response['is_admin']).to be_nil
end end
it "returns a 401 if unauthenticated" do context 'for an anonymous user' do
get api("/users/9998") it "returns a user by id" do
expect(response).to have_http_status(401) get api("/users/#{user.id}")
expect(response).to have_http_status(200)
expect(json_response['username']).to eq(user.username)
end
it "returns a 404 if the target user is present but inaccessible" do
allow(Ability).to receive(:allowed?).and_call_original
allow(Ability).to receive(:allowed?).with(nil, :read_user, user).and_return(false)
get api("/users/#{user.id}")
expect(response).to have_http_status(404)
end
end end
it "returns a 404 error if user id not found" do it "returns a 404 error if user id not found" do
......
...@@ -24,6 +24,14 @@ describe DeleteMergedBranchesService, services: true do ...@@ -24,6 +24,14 @@ describe DeleteMergedBranchesService, services: true do
expect(project.repository.branch_names).to include('master') expect(project.repository.branch_names).to include('master')
end end
it 'keeps protected branches' do
create(:protected_branch, project: project, name: 'improve/awesome')
service.execute
expect(project.repository.branch_names).to include('improve/awesome')
end
context 'user without rights' do context 'user without rights' do
let(:user) { create(:user) } let(:user) { create(:user) }
......
...@@ -3,7 +3,7 @@ require 'spec_helper' ...@@ -3,7 +3,7 @@ require 'spec_helper'
describe MergeRequests::MergeService, services: true do describe MergeRequests::MergeService, services: true do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:user2) { create(:user) } let(:user2) { create(:user) }
let(:merge_request) { create(:merge_request, assignee: user2) } let(:merge_request) { create(:merge_request, :simple, author: user2, assignee: user2) }
let(:project) { merge_request.project } let(:project) { merge_request.project }
before do before do
...@@ -133,18 +133,65 @@ describe MergeRequests::MergeService, services: true do ...@@ -133,18 +133,65 @@ describe MergeRequests::MergeService, services: true do
it { expect(todo).to be_done } it { expect(todo).to be_done }
end end
context 'remove source branch by author' do context 'source branch removal' do
let(:service) do context 'when the source branch is protected' do
merge_request.merge_params['force_remove_source_branch'] = '1' let(:service) do
merge_request.save! MergeRequests::MergeService.new(project, user, should_remove_source_branch: '1')
MergeRequests::MergeService.new(project, user, commit_message: 'Awesome message') end
before do
create(:protected_branch, project: project, name: merge_request.source_branch)
end
it 'does not delete the source branch' do
expect(DeleteBranchService).not_to receive(:new)
service.execute(merge_request)
end
end end
it 'removes the source branch' do context 'when the source branch is the default branch' do
expect(DeleteBranchService).to receive(:new) let(:service) do
.with(merge_request.source_project, merge_request.author) MergeRequests::MergeService.new(project, user, should_remove_source_branch: '1')
.and_call_original end
service.execute(merge_request)
before do
allow(project).to receive(:root_ref?).with(merge_request.source_branch).and_return(true)
end
it 'does not delete the source branch' do
expect(DeleteBranchService).not_to receive(:new)
service.execute(merge_request)
end
end
context 'when the source branch can be removed' do
context 'when MR author set the source branch to be removed' do
let(:service) do
merge_request.merge_params['force_remove_source_branch'] = '1'
merge_request.save!
MergeRequests::MergeService.new(project, user, commit_message: 'Awesome message')
end
it 'removes the source branch using the author user' do
expect(DeleteBranchService).to receive(:new)
.with(merge_request.source_project, merge_request.author)
.and_call_original
service.execute(merge_request)
end
end
context 'when MR merger set the source branch to be removed' do
let(:service) do
MergeRequests::MergeService.new(project, user, commit_message: 'Awesome message', should_remove_source_branch: '1')
end
it 'removes the source branch using the current user' do
expect(DeleteBranchService).to receive(:new)
.with(merge_request.source_project, user)
.and_call_original
service.execute(merge_request)
end
end
end end
end end
......
...@@ -359,18 +359,18 @@ describe QuickActions::InterpretService, services: true do ...@@ -359,18 +359,18 @@ describe QuickActions::InterpretService, services: true do
let(:content) { "/assign @#{developer.username}" } let(:content) { "/assign @#{developer.username}" }
context 'Issue' do context 'Issue' do
it 'fetches assignee and populates assignee_id if content contains /assign' do it 'fetches assignee and populates assignee_ids if content contains /assign' do
_, updates = service.execute(content, issue) _, updates = service.execute(content, issue)
expect(updates).to eq(assignee_ids: [developer.id]) expect(updates[:assignee_ids]).to match_array([developer.id])
end end
end end
context 'Merge Request' do context 'Merge Request' do
it 'fetches assignee and populates assignee_id if content contains /assign' do it 'fetches assignee and populates assignee_ids if content contains /assign' do
_, updates = service.execute(content, merge_request) _, updates = service.execute(content, merge_request)
expect(updates).to eq(assignee_id: developer.id) expect(updates).to eq(assignee_ids: [developer.id])
end end
end end
end end
...@@ -383,7 +383,7 @@ describe QuickActions::InterpretService, services: true do ...@@ -383,7 +383,7 @@ describe QuickActions::InterpretService, services: true do
end end
context 'Issue' do context 'Issue' do
it 'fetches assignee and populates assignee_id if content contains /assign' do it 'fetches assignee and populates assignee_ids if content contains /assign' do
_, updates = service.execute(content, issue) _, updates = service.execute(content, issue)
expect(updates[:assignee_ids]).to match_array([developer.id]) expect(updates[:assignee_ids]).to match_array([developer.id])
...@@ -391,10 +391,10 @@ describe QuickActions::InterpretService, services: true do ...@@ -391,10 +391,10 @@ describe QuickActions::InterpretService, services: true do
end end
context 'Merge Request' do context 'Merge Request' do
it 'fetches assignee and populates assignee_id if content contains /assign' do it 'fetches assignee and populates assignee_ids if content contains /assign' do
_, updates = service.execute(content, merge_request) _, updates = service.execute(content, merge_request)
expect(updates).to eq(assignee_id: developer.id) expect(updates).to eq(assignee_ids: [developer.id])
end end
end end
end end
...@@ -422,11 +422,27 @@ describe QuickActions::InterpretService, services: true do ...@@ -422,11 +422,27 @@ describe QuickActions::InterpretService, services: true do
end end
context 'Merge Request' do context 'Merge Request' do
it 'populates assignee_id: nil if content contains /unassign' do it 'populates assignee_ids: [] if content contains /unassign' do
merge_request.update(assignee_id: developer.id) merge_request.update(assignee_ids: [developer.id])
_, updates = service.execute(content, merge_request) _, updates = service.execute(content, merge_request)
expect(updates).to eq(assignee_id: nil) expect(updates).to eq(assignee_ids: [])
end
end
end
context 'reassign command' do
let(:content) { '/reassign' }
context 'Issue' do
it 'reassigns user if content contains /reassign @user' do
user = create(:user)
issue.update(assignee_ids: [developer.id])
_, updates = service.execute("/reassign @#{user.username}", issue)
expect(updates).to eq(assignee_ids: [user.id])
end end
end end
end end
......
RSpec::Matchers.define :be_utf8 do |_|
match do |actual|
actual.is_a?(String) && actual.encoding == Encoding.find('UTF-8')
end
description do
"be a String with encoding UTF-8"
end
end
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