Commit f001097f authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch 'ce-to-ee' into 'master'

CE Upstream - Monday

See merge request !2453
parents 9bd93670 63174d23
......@@ -9,7 +9,7 @@
## Test coverage
- [![Ruby coverage](https://gitlab.com/gitlab-org/gitlab-ee/badges/master/coverage.svg?job=coverage)](https://gitlab-org.gitlab.io/gitlab-ee/coverage-ruby) Ruby
- [![JavaScript coverage](https://gitlab.com/gitlab-org/gitlab-ee/badges/master/coverage.svg?job=rake+karma)](https://gitlab-org.gitlab.io/gitlab-ee/coverage-javascript) JavaScript
- [![JavaScript coverage](https://gitlab.com/gitlab-org/gitlab-ee/badges/master/coverage.svg?job=karma)](https://gitlab-org.gitlab.io/gitlab-ee/coverage-javascript) JavaScript
## Canonical source
......
......@@ -149,7 +149,6 @@ import './star';
import './subscription';
import './subscription_select';
import './syntax_highlight';
import './user';
// EE-only scripts
import './admin_email_select';
......
/* eslint-disable func-names, space-before-function-paren, no-var, wrap-iife, camelcase, vars-on-top, object-shorthand, comma-dangle, eqeqeq, no-mixed-operators, no-return-assign, newline-per-chained-call, prefer-arrow-callback, consistent-return, one-var, one-var-declaration-per-line, prefer-template, quotes, no-unused-vars, no-else-return, max-len, class-methods-use-this */
import d3 from 'd3';
export default class ActivityCalendar {
constructor(timestamps, calendar_activities_path) {
this.calendar_activities_path = calendar_activities_path;
this.clickDay = this.clickDay.bind(this);
this.currentSelectedDate = '';
this.daySpace = 1;
this.daySize = 15;
this.daySizeWithSpace = this.daySize + (this.daySpace * 2);
this.monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
this.months = [];
// Loop through the timestamps to create a group of objects
// The group of objects will be grouped based on the day of the week they are
this.timestampsTmp = [];
var group = 0;
var today = new Date();
today.setHours(0, 0, 0, 0, 0);
var oneYearAgo = new Date(today);
oneYearAgo.setFullYear(today.getFullYear() - 1);
var days = gl.utils.getDayDifference(oneYearAgo, today);
for (var i = 0; i <= days; i += 1) {
var date = new Date(oneYearAgo);
date.setDate(date.getDate() + i);
var day = date.getDay();
var count = timestamps[date.format('yyyy-mm-dd')];
// Create a new group array if this is the first day of the week
// or if is first object
if ((day === 0 && i !== 0) || i === 0) {
this.timestampsTmp.push([]);
group += 1;
}
var innerArray = this.timestampsTmp[group - 1];
// Push to the inner array the values that will be used to render map
innerArray.push({
count: count || 0,
date: date,
day: day
});
}
// Init color functions
this.colorKey = this.initColorKey();
this.color = this.initColor();
// Init the svg element
this.renderSvg(group);
this.renderDays();
this.renderMonths();
this.renderDayTitles();
this.renderKey();
this.initTooltips();
}
// Add extra padding for the last month label if it is also the last column
getExtraWidthPadding(group) {
var extraWidthPadding = 0;
var lastColMonth = this.timestampsTmp[group - 1][0].date.getMonth();
var secondLastColMonth = this.timestampsTmp[group - 2][0].date.getMonth();
if (lastColMonth != secondLastColMonth) {
extraWidthPadding = 3;
}
return extraWidthPadding;
}
renderSvg(group) {
var width = (group + 1) * this.daySizeWithSpace + this.getExtraWidthPadding(group);
return this.svg = d3.select('.js-contrib-calendar').append('svg').attr('width', width).attr('height', 167).attr('class', 'contrib-calendar');
}
renderDays() {
return this.svg.selectAll('g').data(this.timestampsTmp).enter().append('g').attr('transform', (function(_this) {
return function(group, i) {
_.each(group, function(stamp, a) {
var lastMonth, lastMonthX, month, x;
if (a === 0 && stamp.day === 0) {
month = stamp.date.getMonth();
x = (_this.daySizeWithSpace * i + 1) + _this.daySizeWithSpace;
lastMonth = _.last(_this.months);
if (lastMonth != null) {
lastMonthX = lastMonth.x;
}
if (lastMonth == null) {
return _this.months.push({
month: month,
x: x
});
} else if (month !== lastMonth.month && x - _this.daySizeWithSpace !== lastMonthX) {
return _this.months.push({
month: month,
x: x
});
}
}
});
return "translate(" + ((_this.daySizeWithSpace * i + 1) + _this.daySizeWithSpace) + ", 18)";
};
})(this)).selectAll('rect').data(function(stamp) {
return stamp;
}).enter().append('rect').attr('x', '0').attr('y', (function(_this) {
return function(stamp, i) {
return _this.daySizeWithSpace * stamp.day;
};
})(this)).attr('width', this.daySize).attr('height', this.daySize).attr('title', (function(_this) {
return function(stamp) {
var contribText, date, dateText;
date = new Date(stamp.date);
contribText = 'No contributions';
if (stamp.count > 0) {
contribText = stamp.count + " contribution" + (stamp.count > 1 ? 's' : '');
}
dateText = date.format('mmm d, yyyy');
return contribText + "<br />" + (gl.utils.getDayName(date)) + " " + dateText;
};
})(this)).attr('class', 'user-contrib-cell js-tooltip').attr('fill', (function(_this) {
return function(stamp) {
if (stamp.count !== 0) {
return _this.color(Math.min(stamp.count, 40));
} else {
return '#ededed';
}
};
})(this)).attr('data-container', 'body').on('click', this.clickDay);
}
renderDayTitles() {
var days;
days = [
{
text: 'M',
y: 29 + (this.daySizeWithSpace * 1)
}, {
text: 'W',
y: 29 + (this.daySizeWithSpace * 3)
}, {
text: 'F',
y: 29 + (this.daySizeWithSpace * 5)
}
];
return this.svg.append('g').selectAll('text').data(days).enter().append('text').attr('text-anchor', 'middle').attr('x', 8).attr('y', function(day) {
return day.y;
}).text(function(day) {
return day.text;
}).attr('class', 'user-contrib-text');
}
renderMonths() {
return this.svg.append('g').attr('direction', 'ltr').selectAll('text').data(this.months).enter().append('text').attr('x', function(date) {
return date.x;
}).attr('y', 10).attr('class', 'user-contrib-text').text((function(_this) {
return function(date) {
return _this.monthNames[date.month];
};
})(this));
}
renderKey() {
const keyValues = ['no contributions', '1-9 contributions', '10-19 contributions', '20-29 contributions', '30+ contributions'];
const keyColors = ['#ededed', this.colorKey(0), this.colorKey(1), this.colorKey(2), this.colorKey(3)];
this.svg.append('g')
.attr('transform', `translate(18, ${this.daySizeWithSpace * 8 + 16})`)
.selectAll('rect')
.data(keyColors)
.enter()
.append('rect')
.attr('width', this.daySize)
.attr('height', this.daySize)
.attr('x', (color, i) => this.daySizeWithSpace * i)
.attr('y', 0)
.attr('fill', color => color)
.attr('class', 'js-tooltip')
.attr('title', (color, i) => keyValues[i])
.attr('data-container', 'body');
}
initColor() {
var colorRange;
colorRange = ['#ededed', this.colorKey(0), this.colorKey(1), this.colorKey(2), this.colorKey(3)];
return d3.scale.threshold().domain([0, 10, 20, 30]).range(colorRange);
}
initColorKey() {
return d3.scale.linear().range(['#acd5f2', '#254e77']).domain([0, 3]);
}
clickDay(stamp) {
var formatted_date;
if (this.currentSelectedDate !== stamp.date) {
this.currentSelectedDate = stamp.date;
formatted_date = this.currentSelectedDate.getFullYear() + "-" + (this.currentSelectedDate.getMonth() + 1) + "-" + this.currentSelectedDate.getDate();
return $.ajax({
url: this.calendar_activities_path,
data: {
date: formatted_date
},
cache: false,
dataType: 'html',
beforeSend: function() {
return $('.user-calendar-activities').html('<div class="text-center"><i class="fa fa-spinner fa-spin user-calendar-activities-loading"></i></div>');
},
success: function(data) {
return $('.user-calendar-activities').html(data);
}
});
} else {
this.currentSelectedDate = '';
return $('.user-calendar-activities').html('');
}
}
initTooltips() {
return $('.js-contrib-calendar .js-tooltip').tooltip({
html: true
});
}
}
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, camelcase, vars-on-top, object-shorthand, comma-dangle, eqeqeq, no-mixed-operators, no-return-assign, newline-per-chained-call, prefer-arrow-callback, consistent-return, one-var, one-var-declaration-per-line, prefer-template, quotes, no-unused-vars, no-else-return, max-len */
import d3 from 'd3';
(function() {
this.Calendar = (function() {
function Calendar(timestamps, calendar_activities_path) {
this.calendar_activities_path = calendar_activities_path;
this.clickDay = this.clickDay.bind(this);
this.currentSelectedDate = '';
this.daySpace = 1;
this.daySize = 15;
this.daySizeWithSpace = this.daySize + (this.daySpace * 2);
this.monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
this.months = [];
// Loop through the timestamps to create a group of objects
// The group of objects will be grouped based on the day of the week they are
this.timestampsTmp = [];
var group = 0;
var today = new Date();
today.setHours(0, 0, 0, 0, 0);
var oneYearAgo = new Date(today);
oneYearAgo.setFullYear(today.getFullYear() - 1);
var days = gl.utils.getDayDifference(oneYearAgo, today);
for (var i = 0; i <= days; i += 1) {
var date = new Date(oneYearAgo);
date.setDate(date.getDate() + i);
var day = date.getDay();
var count = timestamps[date.format('yyyy-mm-dd')];
// Create a new group array if this is the first day of the week
// or if is first object
if ((day === 0 && i !== 0) || i === 0) {
this.timestampsTmp.push([]);
group += 1;
}
var innerArray = this.timestampsTmp[group - 1];
// Push to the inner array the values that will be used to render map
innerArray.push({
count: count || 0,
date: date,
day: day
});
}
// Init color functions
this.colorKey = this.initColorKey();
this.color = this.initColor();
// Init the svg element
this.renderSvg(group);
this.renderDays();
this.renderMonths();
this.renderDayTitles();
this.renderKey();
this.initTooltips();
}
// Add extra padding for the last month label if it is also the last column
Calendar.prototype.getExtraWidthPadding = function(group) {
var extraWidthPadding = 0;
var lastColMonth = this.timestampsTmp[group - 1][0].date.getMonth();
var secondLastColMonth = this.timestampsTmp[group - 2][0].date.getMonth();
if (lastColMonth != secondLastColMonth) {
extraWidthPadding = 3;
}
return extraWidthPadding;
};
Calendar.prototype.renderSvg = function(group) {
var width = (group + 1) * this.daySizeWithSpace + this.getExtraWidthPadding(group);
return this.svg = d3.select('.js-contrib-calendar').append('svg').attr('width', width).attr('height', 167).attr('class', 'contrib-calendar');
};
Calendar.prototype.renderDays = function() {
return this.svg.selectAll('g').data(this.timestampsTmp).enter().append('g').attr('transform', (function(_this) {
return function(group, i) {
_.each(group, function(stamp, a) {
var lastMonth, lastMonthX, month, x;
if (a === 0 && stamp.day === 0) {
month = stamp.date.getMonth();
x = (_this.daySizeWithSpace * i + 1) + _this.daySizeWithSpace;
lastMonth = _.last(_this.months);
if (lastMonth != null) {
lastMonthX = lastMonth.x;
}
if (lastMonth == null) {
return _this.months.push({
month: month,
x: x
});
} else if (month !== lastMonth.month && x - _this.daySizeWithSpace !== lastMonthX) {
return _this.months.push({
month: month,
x: x
});
}
}
});
return "translate(" + ((_this.daySizeWithSpace * i + 1) + _this.daySizeWithSpace) + ", 18)";
};
})(this)).selectAll('rect').data(function(stamp) {
return stamp;
}).enter().append('rect').attr('x', '0').attr('y', (function(_this) {
return function(stamp, i) {
return _this.daySizeWithSpace * stamp.day;
};
})(this)).attr('width', this.daySize).attr('height', this.daySize).attr('title', (function(_this) {
return function(stamp) {
var contribText, date, dateText;
date = new Date(stamp.date);
contribText = 'No contributions';
if (stamp.count > 0) {
contribText = stamp.count + " contribution" + (stamp.count > 1 ? 's' : '');
}
dateText = date.format('mmm d, yyyy');
return contribText + "<br />" + (gl.utils.getDayName(date)) + " " + dateText;
};
})(this)).attr('class', 'user-contrib-cell js-tooltip').attr('fill', (function(_this) {
return function(stamp) {
if (stamp.count !== 0) {
return _this.color(Math.min(stamp.count, 40));
} else {
return '#ededed';
}
};
})(this)).attr('data-container', 'body').on('click', this.clickDay);
};
Calendar.prototype.renderDayTitles = function() {
var days;
days = [
{
text: 'M',
y: 29 + (this.daySizeWithSpace * 1)
}, {
text: 'W',
y: 29 + (this.daySizeWithSpace * 3)
}, {
text: 'F',
y: 29 + (this.daySizeWithSpace * 5)
}
];
return this.svg.append('g').selectAll('text').data(days).enter().append('text').attr('text-anchor', 'middle').attr('x', 8).attr('y', function(day) {
return day.y;
}).text(function(day) {
return day.text;
}).attr('class', 'user-contrib-text');
};
Calendar.prototype.renderMonths = function() {
return this.svg.append('g').attr('direction', 'ltr').selectAll('text').data(this.months).enter().append('text').attr('x', function(date) {
return date.x;
}).attr('y', 10).attr('class', 'user-contrib-text').text((function(_this) {
return function(date) {
return _this.monthNames[date.month];
};
})(this));
};
Calendar.prototype.renderKey = function() {
const keyValues = ['no contributions', '1-9 contributions', '10-19 contributions', '20-29 contributions', '30+ contributions'];
const keyColors = ['#ededed', this.colorKey(0), this.colorKey(1), this.colorKey(2), this.colorKey(3)];
this.svg.append('g')
.attr('transform', `translate(18, ${this.daySizeWithSpace * 8 + 16})`)
.selectAll('rect')
.data(keyColors)
.enter()
.append('rect')
.attr('width', this.daySize)
.attr('height', this.daySize)
.attr('x', (color, i) => this.daySizeWithSpace * i)
.attr('y', 0)
.attr('fill', color => color)
.attr('class', 'js-tooltip')
.attr('title', (color, i) => keyValues[i])
.attr('data-container', 'body');
};
Calendar.prototype.initColor = function() {
var colorRange;
colorRange = ['#ededed', this.colorKey(0), this.colorKey(1), this.colorKey(2), this.colorKey(3)];
return d3.scale.threshold().domain([0, 10, 20, 30]).range(colorRange);
};
Calendar.prototype.initColorKey = function() {
return d3.scale.linear().range(['#acd5f2', '#254e77']).domain([0, 3]);
};
Calendar.prototype.clickDay = function(stamp) {
var formatted_date;
if (this.currentSelectedDate !== stamp.date) {
this.currentSelectedDate = stamp.date;
formatted_date = this.currentSelectedDate.getFullYear() + "-" + (this.currentSelectedDate.getMonth() + 1) + "-" + this.currentSelectedDate.getDate();
return $.ajax({
url: this.calendar_activities_path,
data: {
date: formatted_date
},
cache: false,
dataType: 'html',
beforeSend: function() {
return $('.user-calendar-activities').html('<div class="text-center"><i class="fa fa-spinner fa-spin user-calendar-activities-loading"></i></div>');
},
success: function(data) {
return $('.user-calendar-activities').html(data);
}
});
} else {
this.currentSelectedDate = '';
return $('.user-calendar-activities').html('');
}
};
Calendar.prototype.initTooltips = function() {
return $('.js-contrib-calendar .js-tooltip').tooltip({
html: true
});
};
return Calendar;
})();
}).call(window);
import ActivityCalendar from './activity_calendar';
import User from './user';
// use legacy exports until embedded javascript is refactored
window.Calendar = ActivityCalendar;
window.gl = window.gl || {};
window.gl.User = User;
/* eslint-disable class-methods-use-this, comma-dangle, arrow-parens, no-param-reassign */
/* eslint-disable class-methods-use-this */
import Cookies from 'js-cookie';
import UserTabs from './user_tabs';
class User {
export default class User {
constructor({ action }) {
this.action = action;
this.placeProfileAvatarsToTop();
......@@ -13,25 +13,22 @@ class User {
placeProfileAvatarsToTop() {
$('.profile-groups-avatars').tooltip({
placement: 'top'
placement: 'top',
});
}
initTabs() {
return new UserTabs({
parentEl: '.user-profile',
action: this.action
action: this.action,
});
}
hideProjectLimitMessage() {
$('.hide-project-limit-message').on('click', e => {
$('.hide-project-limit-message').on('click', (e) => {
e.preventDefault();
Cookies.set('hide_project_limit_message', 'false');
$(this).parents('.project-limit-message').remove();
});
}
}
window.gl = window.gl || {};
window.gl.User = User;
......@@ -110,10 +110,10 @@ $well-light-text-color: #5b6169;
* Text
*/
$gl-font-size: 14px;
$gl-text-color: rgba(0, 0, 0, .85);
$gl-text-color-light: rgba(0, 0, 0, .7);
$gl-text-color-secondary: rgba(0, 0, 0, .55);
$gl-text-color-disabled: rgba(0, 0, 0, .35);
$gl-text-color: #2e2e2e;
$gl-text-color-secondary: #707070;
$gl-text-color-tertiary: #949494;
$gl-text-color-quaternary: #d6d6d6;
$gl-text-color-inverted: rgba(255, 255, 255, 1.0);
$gl-text-color-secondary-inverted: rgba(255, 255, 255, .85);
$gl-text-green: $green-600;
......@@ -127,7 +127,7 @@ $gl-gray-dark: #313236;
$gl-gray-light: #5c5c5c;
$gl-header-color: #4c4e54;
$gl-header-nav-hover-color: #434343;
$placeholder-text-color: rgba(0, 0, 0, .42);
$placeholder-text-color: $gl-text-color-tertiary;
/*
* Lists
......@@ -135,7 +135,7 @@ $placeholder-text-color: rgba(0, 0, 0, .42);
$list-font-size: $gl-font-size;
$list-title-color: $gl-text-color;
$list-text-color: $gl-text-color;
$list-text-disabled-color: $gl-text-color-disabled;
$list-text-disabled-color: $gl-text-color-tertiary;
$list-border-light: #eee;
$list-border: rgba(0, 0, 0, 0.05);
$list-text-height: 42px;
......
......@@ -284,7 +284,7 @@ header.navbar-gitlab-new {
position: relative;
top: -1px;
padding: 0 5px;
color: rgba($black, .65);
color: $gl-text-color-secondary;
font-size: 10px;
line-height: 1;
background: none;
......@@ -310,10 +310,10 @@ header.navbar-gitlab-new {
.breadcrumbs-links {
flex: 1;
align-self: center;
color: $black-transparent;
color: $gl-text-color-quaternary;
a {
color: rgba($black, .65);
color: $gl-text-color-secondary;
&:not(:first-child),
&.group-path {
......@@ -368,9 +368,10 @@ header.navbar-gitlab-new {
}
.breadcrumbs-sub-title {
margin: 2px 0 0;
margin: 2px 0;
font-size: 16px;
font-weight: normal;
line-height: 1;
ul {
margin: 0;
......
......@@ -35,6 +35,7 @@ $new-sidebar-width: 220px;
.avatar-container {
flex: 0 0 40px;
background-color: $white-light;
}
&:hover {
......
......@@ -315,7 +315,7 @@
text {
font-size: 13px;
fill: $gl-text-color-disabled;
fill: $gl-text-color-tertiary;
}
rect {
......
......@@ -5,10 +5,10 @@ class AutocompleteController < ApplicationController
def users
@users = @users.non_ldap if params[:skip_ldap] == 'true'
@users = @users.search(params[:search]) if params[:search].present?
@users = @users.where.not(id: params[:skip_users]) if params[:skip_users].present?
@users = @users.active
@users = @users.reorder(:name)
@users = @users.search(params[:search]) if params[:search].present?
@users = @users.where.not(id: params[:skip_users]) if params[:skip_users].present?
@users = load_users_by_ability || @users.page(params[:page]).per(params[:per_page])
if params[:todo_filter].present? && current_user
......
......@@ -50,10 +50,13 @@ class ProjectsController < Projects::ApplicationController
respond_to do |format|
if result[:status] == :success
flash[:notice] = _("Project '%{project_name}' was successfully updated.") % { project_name: @project.name }
format.html do
redirect_to(edit_project_path(@project))
end
else
flash[:alert] = result[:message]
format.html { render 'edit' }
end
......
......@@ -49,7 +49,7 @@ class SessionsController < Devise::SessionsController
private
def login_counter
@login_counter ||= Gitlab::Metrics.counter(:user_session_logins, 'User sign in count')
@login_counter ||= Gitlab::Metrics.counter(:user_session_logins_total, 'User sign in count')
end
# Handle an "initial setup" state, where there's only one user, it's an admin,
......
......@@ -485,7 +485,9 @@ class Project < ActiveRecord::Base
end
def has_container_registry_tags?
container_repositories.to_a.any?(&:has_tags?) ||
return @images if defined?(@images)
@images = container_repositories.to_a.any?(&:has_tags?) ||
has_root_container_repository_tags?
end
......@@ -976,8 +978,6 @@ class Project < ActiveRecord::Base
Rails.logger.error "Attempting to rename #{old_path_with_namespace} -> #{new_path_with_namespace}"
expire_caches_before_rename(old_path_with_namespace)
if has_container_registry_tags?
Rails.logger.error "Project #{old_path_with_namespace} cannot be renamed because container registry tags are present!"
......@@ -985,6 +985,8 @@ class Project < ActiveRecord::Base
raise StandardError.new('Project cannot be renamed, because images are present in its container registry')
end
expire_caches_before_rename(old_path_with_namespace)
if gitlab_shell.mv_repository(repository_storage_path, old_path_with_namespace, new_path_with_namespace)
# If repository moved successfully we need to send update instructions to users.
# However we cannot allow rollback since we moved repository
......
......@@ -328,7 +328,7 @@ class User < ActiveRecord::Base
table[:name].matches(pattern)
.or(table[:email].matches(pattern))
.or(table[:username].matches(pattern))
).reorder(order % { query: ActiveRecord::Base.connection.quote(query) }, id: :desc)
).reorder(order % { query: ActiveRecord::Base.connection.quote(query) }, :name)
end
# searches user by given pattern
......
module Projects
class UpdateService < BaseService
def execute
# check that user is allowed to set specified visibility_level
new_visibility = params[:visibility_level]
if new_visibility && new_visibility.to_i != project.visibility_level
unless can?(current_user, :change_visibility_level, project) &&
Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility)
deny_visibility_level(project, new_visibility)
return error('Visibility level unallowed')
end
end
# Repository size limit comes as MB from the view
limit = params.delete(:repository_size_limit)
project.repository_size_limit = Gitlab::Utils.try_megabytes_to_bytes(limit) if limit
new_branch = params.delete(:default_branch)
new_repository_storage = params.delete(:repository_storage)
unless visibility_level_allowed?
return error('New visibility level not allowed!')
end
if project.repository.exists?
if new_branch && new_branch != project.default_branch
project.change_head(new_branch)
if changing_storage_size?
project.change_repository_storage(params.delete(:repository_storage))
end
if new_repository_storage && can?(current_user, :change_repository_storage, project)
project.change_repository_storage(new_repository_storage)
if project.has_container_registry_tags?
return error('Cannot rename project because it contains container registry tags!')
end
if changing_default_branch?
project.change_head(params[:default_branch])
end
if project.update_attributes(params)
if project.update_attributes(params.except(:default_branch))
if project.previous_changes.include?('path')
project.rename_repo
else
......@@ -39,8 +30,40 @@ module Projects
success
else
error('Project could not be updated')
error('Project could not be updated!')
end
end
private
def visibility_level_allowed?
# check that user is allowed to set specified visibility_level
new_visibility = params[:visibility_level]
if new_visibility && new_visibility.to_i != project.visibility_level
unless can?(current_user, :change_visibility_level, project) &&
Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility)
deny_visibility_level(project, new_visibility)
return false
end
end
true
end
def changing_storage_size?
new_repository_storage = params[:repository_storage]
new_repository_storage && project.repository.exists? &&
can?(current_user, :change_repository_storage, project)
end
def changing_default_branch?
new_branch = params[:default_branch]
new_branch && project.repository.exists? &&
new_branch != project.default_branch
end
end
end
---
title: Exact matches of username and email are now on top of the user search
merge_request: 12868
author:
---
title: Recover from renaming project that has container images
merge_request: 12840
author:
---
title: Pass before_script and script as-is preserving arrays
merge_request:
author:
......@@ -145,7 +145,7 @@ Gitlab::Metrics::UnicornSampler.initialize_instance(Settings.monitoring.unicorn_
Gitlab::Application.configure do |config|
# 0 should be Sentry to catch errors in this middleware
config.middleware.insert(1, Gitlab::Metrics::ConnectionRackMiddleware)
config.middleware.insert(1, Gitlab::Metrics::RequestsRackMiddleware)
end
if Gitlab::Metrics.enabled?
......
......@@ -7,7 +7,7 @@
- aws_elb_request_count_sum
weight: 1
queries:
- query_range: 'sum(aws_elb_request_count_sum{%{environment_filter}}) * 60'
- query_range: 'sum(aws_elb_request_count_sum{%{environment_filter}}) / 60'
label: Total
unit: req / sec
- title: "Latency"
......
......@@ -72,7 +72,7 @@ var config = {
stl_viewer: './blob/stl_viewer.js',
terminal: './terminal/terminal_bundle.js',
u2f: ['vendor/u2f'],
users: './users/users_bundle.js',
users: './users/index.js',
raven: './raven/index.js',
vue_merge_request_widget: './vue_merge_request_widget/index.js',
test: './test.js',
......
......@@ -250,9 +250,17 @@ Tip: If you want to limit access to the nested members of an Active Directory
group you can use the following syntax:
```
(memberOf=CN=My Group,DC=Example,DC=com)
(memberOf:1.2.840.113556.1.4.1941=CN=My Group,DC=Example,DC=com)
```
Find more information about this "LDAP_MATCHING_RULE_IN_CHAIN" filter at
https://msdn.microsoft.com/en-us/library/aa746475(v=vs.85).aspx. Support for
nested members in the user filter should not be confused with
[group sync nested groups support (EE only)](https://docs.gitlab.com/ee/administration/auth/ldap-ee.html#supported-ldap-group-types-attributes).
Please note that GitLab does not support the custom filter syntax used by
omniauth-ldap.
### Escaping special characters
If the `user_filter` DN contains a special characters. For example a comma
......@@ -271,9 +279,6 @@ As an example the above DN would look like
OU=GitLab\\5C\\2C Inc,DC=gitlab,DC=com
```
Please note that GitLab does not support the custom filter syntax used by
omniauth-ldap.
## Enabling LDAP sign-in for existing GitLab users
When a user signs in to GitLab with LDAP for the first time, and their LDAP
......
......@@ -27,20 +27,23 @@ server, because the embedded server configuration is overwritten once every
In this experimental phase, only a few metrics are available:
| Metric | Type | Description |
| ------ | ---- | ----------- |
| --------------------------------- | --------- | ----------- |
| db_ping_timeout | Gauge | Whether or not the last database ping timed out |
| db_ping_success | Gauge | Whether or not the last database ping succeeded |
| db_ping_latency | Gauge | Round trip time of the database ping |
| db_ping_latency_seconds | Gauge | Round trip time of the database ping |
| filesystem_access_latency_seconds | Gauge | Latency in accessing a specific filesystem |
| filesystem_accessible | Gauge | Whether or not a specific filesystem is accessible |
| filesystem_write_latency_seconds | Gauge | Write latency of a specific filesystem |
| filesystem_writable | Gauge | Whether or not the filesystem is writable |
| filesystem_read_latency_seconds | Gauge | Read latency of a specific filesystem |
| filesystem_readable | Gauge | Whether or not the filesystem is readable |
| http_requests_total | Counter | Rack request count |
| http_request_duration_seconds | Histogram | HTTP response time from rack middleware |
| rack_uncaught_errors_total | Counter | Rack connections handling uncaught errors count |
| redis_ping_timeout | Gauge | Whether or not the last redis ping timed out |
| redis_ping_success | Gauge | Whether or not the last redis ping succeeded |
| redis_ping_latency | Gauge | Round trip time of the redis ping |
| filesystem_access_latency | gauge | Latency in accessing a specific filesystem |
| filesystem_accessible | gauge | Whether or not a specific filesystem is accessible |
| filesystem_write_latency | gauge | Write latency of a specific filesystem |
| filesystem_writable | gauge | Whether or not the filesystem is writable |
| filesystem_read_latency | gauge | Read latency of a specific filesystem |
| filesystem_readable | gauge | Whether or not the filesystem is readable |
| user_sessions_logins | Counter | Counter of how many users have logged in |
| redis_ping_latency_seconds | Gauge | Round trip time of the redis ping |
| user_session_logins_total | Counter | Counter of how many users have logged in |
[← Back to the main Prometheus page](index.md)
......
......@@ -388,8 +388,8 @@ the style below as a guide:
1. Save the file and [restart] GitLab for the changes to take effect.
[reconfigure]: path/to/administration/gitlab_restart.md#omnibus-gitlab-reconfigure
[restart]: path/to/administration/gitlab_restart.md#installations-from-source
[reconfigure]: path/to/administration/restart_gitlab.md#omnibus-gitlab-reconfigure
[restart]: path/to/administration/restart_gitlab.md#installations-from-source
````
In this case:
......
......@@ -2,13 +2,13 @@
![GCP landing page](img/gcp_landing.png)
>**Important note:**
GitLab has no official images in Google Cloud Platform yet. This guide serves
as a template for when the GitLab VM will be available.
The fastest way to get started on [Google Cloud Platform (GCP)][gcp] is through
the [Google Cloud Launcher][launcher] program.
GitLab's official Google Launcher apps:
1. [GitLab Community Edition](https://console.cloud.google.com/launcher/details/gitlab-public/gitlab-community-edition?project=gitlab-public)
2. [GitLab Enterprise Edition](https://console.cloud.google.com/launcher/details/gitlab-public/gitlab-enterprise-edition?project=gitlab-public)
## Prerequisites
There are only two prerequisites in order to install GitLab on GCP:
......
......@@ -224,8 +224,11 @@ To enable this feature, navigate to the group settings page. Select
access each project's settings, and remove any project from the same screen.
- **Webhooks**: configure [webhooks](../project/integrations/webhooks.md)
and [push rules](../../push_rules/push_rules.md) to your group
(Push Rules is available in [GitLab Enteprise Edition Starter][ee].)
- **Audit Events**: view [Audit Events](../../administration/audit_events.md)
for the group (GitLab admins only)
- **Pipelines quota**: keep track of the [pipeline quota](../admin_area/settings/continuous_integration.md) for the group
for the group (GitLab admins only, available in [GitLab Enterprise Edition Starter][ee])
- **Pipelines quota**: keep track of the
[pipeline quota](../admin_area/settings/continuous_integration.md) for the group
[permissions]: ../permissions.md#permissions
[ee]: https://about.gitlab.com/products/
......@@ -123,7 +123,7 @@ Multiple Issue Boards enables you to create more than one Issue Board per projec
It's great for large projects with more than one team or in situations where a
repository is used to host the code of multiple products.
_[Multiple Issue Boards](../issue_board.html#multiple-issue-boards)
_[Multiple Issue Boards](../issue_board.md#multiple-issue-boards)
are available only in [GitLab Enterprise Edition](https://about.gitlab.com/gitlab-ee/)._
### Export Issues to CSV (EES/EEP)
......
......@@ -26,6 +26,7 @@ With GitLab merge requests, you can:
With **[GitLab Enterprise Edition][ee]**, you can also:
- View the deployment process across projects with [Multi-Project Pipeline Graphs](../../../ci/multi_project_pipeline_graphs.md) (available only in GitLab Enterprise Edition Premium)
- Request [approvals](#merge-request-approvals) from your managers (available in GitLab Enterprise Edition Starter)
- Enable [fast-forward merge requests](#fast-forward-merge-requests) (available in GitLab Enterprise Edition Starter)
......@@ -189,6 +190,16 @@ specific commit page.
You can append `?w=1` while on the diffs page of a merge request to ignore any
whitespace changes.
## Live preview with Review Apps
If you configured [Review Apps](https://about.gitlab.com/features/review-apps/) for your project,
you can preview the changes submitted to a feature-branch through a merge request
in a per-branch basis. No need to checkout the branch, install and preview locally;
all your changes will be available to preview by anyone with the Review Apps link.
[Read more about Review Apps.](../../../ci/review_apps/index.md)
## Tips
Here are some tips that will help you be more efficient with merge requests in
......
......@@ -94,7 +94,8 @@ GitLab Pages for this project, the site will live under
Once you enable GitLab Pages for your project, your website
will be published under `https://john.gitlab.io`.
- Under your group `websites`, you created a project called
`websites.gitlab.io`. your project's URL will be `https://gitlab.com/websites/websites.gitlab.io`. Once you enable GitLab Pages for your project,
`websites.gitlab.io`. your project's URL will be `https://gitlab.com/websites/websites.gitlab.io`.
Once you enable GitLab Pages for your project,
your website will be published under `https://websites.gitlab.io`.
>**Note:**
......
This document was moved to [another location](../user/group/index.md).
......@@ -80,6 +80,8 @@ module Ci
artifacts: job[:artifacts],
cache: job[:cache],
dependencies: job[:dependencies],
before_script: job[:before_script],
script: job[:script],
after_script: job[:after_script],
environment: job[:environment]
}.compact }
......
......@@ -12,7 +12,8 @@ module Gitlab
class << self
def from_commands(job)
self.new(:script).tap do |step|
step.script = job.commands.split("\n")
step.script = job.options[:before_script].to_a + job.options[:script].to_a
step.script = job.commands.split("\n") if step.script.empty?
step.timeout = job.timeout
step.when = WHEN_ON_SUCCESS
end
......
......@@ -35,9 +35,9 @@ module Gitlab
repository_storages.flat_map do |storage_name|
tmp_file_path = tmp_file_path(storage_name)
[
operation_metrics(:filesystem_accessible, :filesystem_access_latency, -> { storage_stat_test(storage_name) }, shard: storage_name),
operation_metrics(:filesystem_writable, :filesystem_write_latency, -> { storage_write_test(tmp_file_path) }, shard: storage_name),
operation_metrics(:filesystem_readable, :filesystem_read_latency, -> { storage_read_test(tmp_file_path) }, shard: storage_name)
operation_metrics(:filesystem_accessible, :filesystem_access_latency_seconds, -> { storage_stat_test(storage_name) }, shard: storage_name),
operation_metrics(:filesystem_writable, :filesystem_write_latency_seconds, -> { storage_write_test(tmp_file_path) }, shard: storage_name),
operation_metrics(:filesystem_readable, :filesystem_read_latency_seconds, -> { storage_read_test(tmp_file_path) }, shard: storage_name)
].flatten
end
end
......
......@@ -20,7 +20,7 @@ module Gitlab
[
metric("#{metric_prefix}_timeout", result.is_a?(Timeout::Error) ? 1 : 0),
metric("#{metric_prefix}_success", is_successful?(result) ? 1 : 0),
metric("#{metric_prefix}_latency", elapsed)
metric("#{metric_prefix}_latency_seconds", elapsed)
]
end
end
......
module Gitlab
module Metrics
class ConnectionRackMiddleware
class RequestsRackMiddleware
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')
def self.http_request_total
@http_request_total ||= Gitlab::Metrics.counter(:http_requests_total, 'Request count')
end
def self.rack_uncaught_errors_count
@rack_uncaught_errors_count ||= Gitlab::Metrics.counter(:rack_uncaught_errors, 'Rack connections handling uncaught errors count')
@rack_uncaught_errors_count ||= Gitlab::Metrics.counter(:rack_uncaught_errors_total, 'Request 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])
def self.http_request_duration_seconds
@http_request_duration_seconds ||= Gitlab::Metrics.histogram(:http_request_duration_seconds, 'Request handling execution time',
{}, [0.05, 0.1, 0.25, 0.5, 0.7, 1, 2.5, 5, 10, 25])
end
def call(env)
method = env['REQUEST_METHOD'].downcase
started = Time.now.to_f
begin
ConnectionRackMiddleware.rack_request_count.increment(method: method)
RequestsRackMiddleware.http_request_total.increment(method: method)
status, headers, body = @app.call(env)
ConnectionRackMiddleware.rack_response_count.increment(method: method, status: status)
elapsed = Time.now.to_f - started
RequestsRackMiddleware.http_request_duration_seconds.observe({ method: method, status: status }, elapsed)
[status, headers, body]
rescue
ConnectionRackMiddleware.rack_uncaught_errors_count.increment
RequestsRackMiddleware.rack_uncaught_errors_count.increment
raise
ensure
elapsed = Time.now.to_f - started
ConnectionRackMiddleware.rack_execution_time.observe({}, elapsed)
end
end
end
......
......@@ -15,11 +15,11 @@ msgstr ""
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
msgid "%d additional commit has been omitted to prevent performance issues."
msgid "%s additional commit has been omitted to prevent performance issues."
msgid_plural ""
"%d additional commits have been omitted to prevent performance issues."
msgstr[0] "%d подаване беше пропуснато, за да не се натоварва системата."
msgstr[1] "%d подавания бяха пропуснати, за да не се натоварва системата."
"%s additional commits have been omitted to prevent performance issues."
msgstr[0] "%s подаване беше пропуснато, за да не се натоварва системата."
msgstr[1] "%s подавания бяха пропуснати, за да не се натоварва системата."
msgid "%d commit"
msgid_plural "%d commits"
......
......@@ -17,8 +17,8 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=n != 1;\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."
msgid "%s additional commit has been omitted to prevent performance issues."
msgid_plural "%s additional commits have been omitted to prevent performance issues."
msgstr[0] ""
msgstr[1] ""
......
......@@ -15,11 +15,11 @@ msgstr ""
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
msgid "%d additional commit has been omitted to prevent performance issues."
msgid "%s additional commit has been omitted to prevent performance issues."
msgid_plural ""
"%d additional commits have been omitted to prevent performance issues."
msgstr[0] "%d enmetado estis transsaltita, por ne troŝarĝi la sistemon."
msgstr[1] "%d enmetadoj estis transsaltitaj, por ne troŝarĝi la sistemon."
"%s additional commits have been omitted to prevent performance issues."
msgstr[0] "%s enmetado estis transsaltita, por ne troŝarĝi la sistemon."
msgstr[1] "%s enmetadoj estis transsaltitaj, por ne troŝarĝi la sistemon."
msgid "%d commit"
msgid_plural "%d commits"
......
......@@ -15,14 +15,14 @@ msgstr ""
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
msgid "%d additional commit has been omitted to prevent performance issues."
msgid "%s additional commit has been omitted to prevent performance issues."
msgid_plural ""
"%d additional commits have been omitted to prevent performance issues."
"%s additional commits have been omitted to prevent performance issues."
msgstr[0] ""
"%d commit aggiuntivo è stato omesso per evitare degradi di prestazioni negli "
"%s commit aggiuntivo è stato omesso per evitare degradi di prestazioni negli "
"issues."
msgstr[1] ""
"%d commit aggiuntivi sono stati omessi per evitare degradi di prestazioni "
"%s commit aggiuntivi sono stati omessi per evitare degradi di prestazioni "
"negli issues."
msgid "%d commit"
......
......@@ -15,10 +15,10 @@ msgstr ""
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=1; plural=0\n"
msgid "%d additional commit has been omitted to prevent performance issues."
msgid "%s additional commit has been omitted to prevent performance issues."
msgid_plural ""
"%d additional commits have been omitted to prevent performance issues."
msgstr[0] "为提高页面加载速度及性能,已省略了 %d 次提交。"
"%s additional commits have been omitted to prevent performance issues."
msgstr[0] "为提高页面加载速度及性能,已省略了 %s 次提交。"
msgid "%d commit"
msgid_plural "%d commits"
......
......@@ -14,10 +14,10 @@ msgstr ""
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=1; plural=0\n"
msgid "%d additional commit has been omitted to prevent performance issues."
msgid "%s additional commit has been omitted to prevent performance issues."
msgid_plural ""
"%d additional commits have been omitted to prevent performance issues."
msgstr[0] "為提高頁面加載速度及性能,已省略了 %d 次提交。"
"%s additional commits have been omitted to prevent performance issues."
msgstr[0] "為提高頁面加載速度及性能,已省略了 %s 次提交。"
msgid "%d commit"
msgid_plural "%d commits"
......
......@@ -18,10 +18,10 @@ msgstr ""
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=1; plural=0\n"
msgid "%d additional commit has been omitted to prevent performance issues."
msgid "%s additional commit has been omitted to prevent performance issues."
msgid_plural ""
"%d additional commits have been omitted to prevent performance issues."
msgstr[0] "因效能考量,不顯示 %d 個更動 (commit)。"
"%s additional commits have been omitted to prevent performance issues."
msgstr[0] "因效能考量,不顯示 %s 個更動 (commit)。"
msgid "%d commit"
msgid_plural "%d commits"
......
......@@ -137,6 +137,21 @@ describe AutocompleteController do
it { expect(body.size).to eq User.count }
end
context 'user order' do
it 'shows exact matches first' do
reported_user = create(:user, username: 'reported_user', name: 'Doug')
user = create(:user, username: 'user', name: 'User')
user1 = create(:user, username: 'user1', name: 'Ian')
sign_in(user)
get(:users, search: 'user')
response_usernames = JSON.parse(response.body).map { |user| user['username'] }
expect(response_usernames.take(3)).to match_array([user.username, reported_user.username, user1.username])
end
end
context 'limited users per page' do
let(:per_page) { 2 }
......
......@@ -24,7 +24,7 @@ describe MetricsController do
expect(response.body).to match(/^db_ping_timeout 0$/)
expect(response.body).to match(/^db_ping_success 1$/)
expect(response.body).to match(/^db_ping_latency [0-9\.]+$/)
expect(response.body).to match(/^db_ping_latency_seconds [0-9\.]+$/)
end
it 'returns Redis ping metrics' do
......@@ -32,7 +32,7 @@ describe MetricsController do
expect(response.body).to match(/^redis_ping_timeout 0$/)
expect(response.body).to match(/^redis_ping_success 1$/)
expect(response.body).to match(/^redis_ping_latency [0-9\.]+$/)
expect(response.body).to match(/^redis_ping_latency_seconds [0-9\.]+$/)
end
it 'returns Caching ping metrics' do
......@@ -40,7 +40,7 @@ describe MetricsController do
expect(response.body).to match(/^redis_cache_ping_timeout 0$/)
expect(response.body).to match(/^redis_cache_ping_success 1$/)
expect(response.body).to match(/^redis_cache_ping_latency [0-9\.]+$/)
expect(response.body).to match(/^redis_cache_ping_latency_seconds [0-9\.]+$/)
end
it 'returns Queues ping metrics' do
......@@ -48,7 +48,7 @@ describe MetricsController do
expect(response.body).to match(/^redis_queues_ping_timeout 0$/)
expect(response.body).to match(/^redis_queues_ping_success 1$/)
expect(response.body).to match(/^redis_queues_ping_latency [0-9\.]+$/)
expect(response.body).to match(/^redis_queues_ping_latency_seconds [0-9\.]+$/)
end
it 'returns SharedState ping metrics' do
......@@ -56,17 +56,17 @@ describe MetricsController do
expect(response.body).to match(/^redis_shared_state_ping_timeout 0$/)
expect(response.body).to match(/^redis_shared_state_ping_success 1$/)
expect(response.body).to match(/^redis_shared_state_ping_latency [0-9\.]+$/)
expect(response.body).to match(/^redis_shared_state_ping_latency_seconds [0-9\.]+$/)
end
it 'returns file system check metrics' do
get :index
expect(response.body).to match(/^filesystem_access_latency{shard="default"} [0-9\.]+$/)
expect(response.body).to match(/^filesystem_access_latency_seconds{shard="default"} [0-9\.]+$/)
expect(response.body).to match(/^filesystem_accessible{shard="default"} 1$/)
expect(response.body).to match(/^filesystem_write_latency{shard="default"} [0-9\.]+$/)
expect(response.body).to match(/^filesystem_write_latency_seconds{shard="default"} [0-9\.]+$/)
expect(response.body).to match(/^filesystem_writable{shard="default"} 1$/)
expect(response.body).to match(/^filesystem_read_latency{shard="default"} [0-9\.]+$/)
expect(response.body).to match(/^filesystem_read_latency_seconds{shard="default"} [0-9\.]+$/)
expect(response.body).to match(/^filesystem_readable{shard="default"} 1$/)
end
......
......@@ -234,27 +234,46 @@ describe ProjectsController do
let(:admin) { create(:admin) }
let(:project) { create(:project, :repository) }
let(:new_path) { 'renamed_path' }
let(:project_params) { { path: new_path } }
before do
sign_in(admin)
end
context 'when only renaming a project path' do
it "sets the repository to the right path after a rename" do
controller.instance_variable_set(:@project, project)
expect { update_project path: 'renamed_path' }
.to change { project.reload.path }
put :update,
namespace_id: project.namespace,
id: project.id,
project: project_params
expect(project.repository.path).to include(new_path)
expect(assigns(:repository).path).to eq(project.repository.path)
expect(project.path).to include 'renamed_path'
expect(assigns(:repository).path).to include project.path
expect(response).to have_http_status(302)
end
end
context 'when project has container repositories with tags' do
before do
stub_container_registry_config(enabled: true)
stub_container_registry_tags(repository: /image/, tags: %w[rc1])
create(:container_repository, project: project, name: :image)
end
it 'does not allow to rename the project' do
expect { update_project path: 'renamed_path' }
.not_to change { project.reload.path }
expect(controller).to set_flash[:alert].to(/container registry tags/)
expect(response).to have_http_status(200)
end
end
def update_project(**parameters)
put :update,
namespace_id: project.namespace.path,
id: project.path,
project: parameters
end
end
describe '#transfer' do
render_views
......
......@@ -163,7 +163,10 @@ module Ci
commands: "pwd\nrspec",
coverage_regex: nil,
tag_list: [],
options: {},
options: {
before_script: ["pwd"],
script: ["rspec"]
},
allow_failure: false,
when: "on_success",
environment: nil,
......@@ -616,6 +619,8 @@ module Ci
coverage_regex: nil,
tag_list: [],
options: {
before_script: ["pwd"],
script: ["rspec"],
image: { name: "ruby:2.1", entrypoint: ["/usr/local/bin/init", "run"] },
services: [{ name: "mysql" },
{ name: "docker:dind", alias: "docker", entrypoint: ["/usr/local/bin/init", "run"],
......@@ -649,6 +654,8 @@ module Ci
coverage_regex: nil,
tag_list: [],
options: {
before_script: ["pwd"],
script: ["rspec"],
image: { name: "ruby:2.5", entrypoint: ["/usr/local/bin/init", "run"] },
services: [{ name: "postgresql", alias: "db-pg", entrypoint: ["/usr/local/bin/init", "run"],
command: ["/usr/local/bin/init", "run"] },
......@@ -680,6 +687,8 @@ module Ci
coverage_regex: nil,
tag_list: [],
options: {
before_script: ["pwd"],
script: ["rspec"],
image: { name: "ruby:2.1" },
services: [{ name: "mysql" }, { name: "docker:dind" }]
},
......@@ -707,6 +716,8 @@ module Ci
coverage_regex: nil,
tag_list: [],
options: {
before_script: ["pwd"],
script: ["rspec"],
image: { name: "ruby:2.5" },
services: [{ name: "postgresql" }, { name: "docker:dind" }]
},
......@@ -951,6 +962,8 @@ module Ci
coverage_regex: nil,
tag_list: [],
options: {
before_script: ["pwd"],
script: ["rspec"],
image: { name: "ruby:2.1" },
services: [{ name: "mysql" }],
artifacts: {
......@@ -1162,7 +1175,9 @@ module Ci
commands: "test",
coverage_regex: nil,
tag_list: [],
options: {},
options: {
script: ["test"]
},
when: "on_success",
allow_failure: false,
environment: nil,
......@@ -1208,7 +1223,9 @@ module Ci
commands: "execute-script-for-job",
coverage_regex: nil,
tag_list: [],
options: {},
options: {
script: ["execute-script-for-job"]
},
when: "on_success",
allow_failure: false,
environment: nil,
......@@ -1221,7 +1238,9 @@ module Ci
commands: "execute-script-for-job",
coverage_regex: nil,
tag_list: [],
options: {},
options: {
script: ["execute-script-for-job"]
},
when: "on_success",
allow_failure: false,
environment: nil,
......
require 'spec_helper'
describe Gitlab::Ci::Build::Step do
let(:job) { create(:ci_build, :no_options, commands: "ls -la\ndate") }
describe '#from_commands' do
shared_examples 'has correct script' do
subject { described_class.from_commands(job) }
it 'fabricates an object' do
expect(subject.name).to eq(:script)
expect(subject.script).to eq(['ls -la', 'date'])
expect(subject.script).to eq(script)
expect(subject.timeout).to eq(job.timeout)
expect(subject.when).to eq('on_success')
expect(subject.allow_failure).to be_falsey
end
end
context 'when commands are specified' do
it_behaves_like 'has correct script' do
let(:job) { create(:ci_build, :no_options, commands: "ls -la\ndate") }
let(:script) { ['ls -la', 'date'] }
end
end
context 'when script option is specified' do
it_behaves_like 'has correct script' do
let(:job) { create(:ci_build, :no_options, options: { script: ["ls -la\necho aaa", "date"] }) }
let(:script) { ["ls -la\necho aaa", 'date'] }
end
end
context 'when before and script option is specified' do
it_behaves_like 'has correct script' do
let(:job) do
create(:ci_build, options: {
before_script: ["ls -la\necho aaa"],
script: ["date"]
})
end
let(:script) { ["ls -la\necho aaa", 'date'] }
end
end
end
describe '#from_after_script' do
let(:job) { create(:ci_build) }
subject { described_class.from_after_script(job) }
context 'when after_script is empty' do
......
......@@ -109,9 +109,9 @@ describe Gitlab::HealthChecks::FsShardsCheck do
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))
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))
expect(subject).to include(an_object_having_attributes(name: :filesystem_access_latency_seconds, value: be >= 0))
expect(subject).to include(an_object_having_attributes(name: :filesystem_read_latency_seconds, value: be >= 0))
expect(subject).to include(an_object_having_attributes(name: :filesystem_write_latency_seconds, value: be >= 0))
end
end
......@@ -127,9 +127,9 @@ describe Gitlab::HealthChecks::FsShardsCheck do
expect(subject).to include(an_object_having_attributes(name: :filesystem_readable, value: 1))
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))
expect(subject).to include(an_object_having_attributes(name: :filesystem_access_latency_seconds, value: be >= 0))
expect(subject).to include(an_object_having_attributes(name: :filesystem_read_latency_seconds, value: be >= 0))
expect(subject).to include(an_object_having_attributes(name: :filesystem_write_latency_seconds, value: be >= 0))
end
end
end
......@@ -159,9 +159,9 @@ describe Gitlab::HealthChecks::FsShardsCheck do
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))
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))
expect(subject).to include(an_object_having_attributes(name: :filesystem_access_latency_seconds, value: be >= 0))
expect(subject).to include(an_object_having_attributes(name: :filesystem_read_latency_seconds, value: be >= 0))
expect(subject).to include(an_object_having_attributes(name: :filesystem_write_latency_seconds, value: be >= 0))
end
end
end
......
......@@ -8,7 +8,7 @@ shared_context 'simple_check' do |metrics_prefix, check_name, success_result|
it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_success", value: 1)) }
it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_timeout", value: 0)) }
it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_latency", value: be >= 0)) }
it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_latency_seconds", value: be >= 0)) }
end
context 'Check is misbehaving' do
......@@ -18,7 +18,7 @@ shared_context 'simple_check' do |metrics_prefix, check_name, success_result|
it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_success", value: 0)) }
it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_timeout", value: 0)) }
it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_latency", value: be >= 0)) }
it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_latency_seconds", value: be >= 0)) }
end
context 'Check is timeouting' do
......@@ -28,7 +28,7 @@ shared_context 'simple_check' do |metrics_prefix, check_name, success_result|
it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_success", value: 0)) }
it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_timeout", value: 1)) }
it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_latency", value: be >= 0)) }
it { is_expected.to include(have_attributes(name: "#{metrics_prefix}_latency_seconds", value: be >= 0)) }
end
end
......
require 'spec_helper'
describe Gitlab::Metrics::ConnectionRackMiddleware do
describe Gitlab::Metrics::RequestsRackMiddleware do
let(:app) { double('app') }
subject { described_class.new(app) }
......@@ -22,14 +22,8 @@ describe Gitlab::Metrics::ConnectionRackMiddleware 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')
expect(described_class).to receive_message_chain(:http_request_total, :increment).with(method: 'get')
subject.call(env)
end
......@@ -38,20 +32,21 @@ describe Gitlab::Metrics::ConnectionRackMiddleware do
execution_time = 10
allow(app).to receive(:call) do |*args|
Timecop.freeze(execution_time.seconds)
[200, nil, nil]
end
expect(described_class).to receive_message_chain(:rack_execution_time, :observe).with({}, execution_time)
expect(described_class).to receive_message_chain(:http_request_duration_seconds, :observe).with({ status: 200, method: 'get' }, execution_time)
subject.call(env)
end
end
context '@app.call throws exception' do
let(:rack_response_count) { double('rack_response_count') }
let(:http_request_duration_seconds) { double('http_request_duration_seconds') }
before do
allow(app).to receive(:call).and_raise(StandardError)
allow(described_class).to receive(:rack_response_count).and_return(rack_response_count)
allow(described_class).to receive(:http_request_duration_seconds).and_return(http_request_duration_seconds)
end
it 'increments exceptions count' do
......@@ -61,25 +56,13 @@ describe Gitlab::Metrics::ConnectionRackMiddleware do
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(described_class).to receive_message_chain(:http_request_total, :increment).with(method: 'get')
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)
it "does't measure request execution time" do
expect(described_class.http_request_duration_seconds).not_to receive(:increment)
expect { subject.call(env) }.to raise_error(StandardError)
end
......
......@@ -1444,7 +1444,7 @@ describe Project, models: true do
subject { project.rename_repo }
it { expect{subject}.to raise_error(Exception) }
it { expect{subject}.to raise_error(StandardError) }
end
end
......
......@@ -791,7 +791,7 @@ describe User, models: true do
end
it 'returns users with a partially matching name' do
expect(described_class.search(user.name[0..2])).to eq([user2, user])
expect(described_class.search(user.name[0..2])).to eq([user, user2])
end
it 'returns users with a matching name regardless of the casing' do
......@@ -805,7 +805,7 @@ describe User, models: true do
end
it 'returns users with a partially matching Email' do
expect(described_class.search(user.email[0..2])).to eq([user2, user])
expect(described_class.search(user.email[0..2])).to eq([user, user2])
end
it 'returns users with a matching Email regardless of the casing' do
......@@ -819,7 +819,7 @@ describe User, models: true do
end
it 'returns users with a partially matching username' do
expect(described_class.search(user.username[0..2])).to eq([user2, user])
expect(described_class.search(user.username[0..2])).to eq([user, user2])
end
it 'returns users with a matching username regardless of the casing' do
......
require 'spec_helper'
describe Projects::UpdateService, services: true do
describe Projects::UpdateService, '#execute', :services do
let(:user) { create(:user) }
let(:admin) { create(:admin) }
let(:project) { create(:empty_project, creator_id: user.id, namespace: user.namespace) }
describe 'update_by_user' do
let(:project) do
create(:empty_project, creator: user, namespace: user.namespace)
end
context 'when changing visibility level' do
context 'when visibility_level is INTERNAL' do
it 'updates the project to internal' do
result = update_project(project, user, visibility_level: Gitlab::VisibilityLevel::INTERNAL)
......@@ -40,7 +43,7 @@ describe Projects::UpdateService, services: true do
it 'does not update the project to public' do
result = update_project(project, user, visibility_level: Gitlab::VisibilityLevel::PUBLIC)
expect(result).to eq({ status: :error, message: 'Visibility level unallowed' })
expect(result).to eq({ status: :error, message: 'New visibility level not allowed!' })
expect(project).to be_private
end
......@@ -55,12 +58,13 @@ describe Projects::UpdateService, services: true do
end
end
describe 'visibility_level' do
describe 'when updating project that has forks' do
let(:project) { create(:empty_project, :internal) }
let(:forked_project) { create(:forked_project_with_submodules, :internal) }
before do
forked_project.build_forked_project_link(forked_to_project_id: forked_project.id, forked_from_project_id: project.id)
forked_project.build_forked_project_link(forked_to_project_id: forked_project.id,
forked_from_project_id: project.id)
forked_project.save
end
......@@ -89,10 +93,38 @@ describe Projects::UpdateService, services: true do
end
end
context 'when updating a default branch' do
let(:project) { create(:project, :repository) }
it 'changes a default branch' do
update_project(project, admin, default_branch: 'feature')
expect(Project.find(project.id).default_branch).to eq 'feature'
end
end
context 'when renaming project that contains container images' do
before do
stub_container_registry_config(enabled: true)
stub_container_registry_tags(repository: /image/, tags: %w[rc1])
create(:container_repository, project: project, name: :image)
end
it 'does not allow to rename the project' do
result = update_project(project, admin, path: 'renamed')
expect(result).to include(status: :error)
expect(result[:message]).to match(/contains container registry tags/)
end
end
context 'when passing invalid parameters' do
it 'returns an error result when record cannot be updated' do
result = update_project(project, admin, { name: 'foo&bar' })
expect(result).to eq({ status: :error, message: 'Project could not be updated' })
expect(result).to eq({ status: :error,
message: 'Project could not be updated!' })
end
end
describe 'repository_storage' do
......@@ -158,7 +190,7 @@ describe Projects::UpdateService, services: true do
it 'returns an error result when record cannot be updated' do
result = update_project(project, admin, { name: 'foo&bar' })
expect(result).to eq({ status: :error, message: 'Project could not be updated' })
expect(result).to eq({ status: :error, message: 'Project could not be updated!' })
end
def update_project(project, user, opts)
......
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