Commit 716a2595 authored by Rémy Coutable's avatar Rémy Coutable

Merge remote-tracking branch 'upstream/master' into ce-to-ee-2017-04-14

# Conflicts:
#	app/assets/javascripts/boards/components/modal/footer.js
#	app/assets/javascripts/boards/components/sidebar/remove_issue.js
#	app/assets/javascripts/boards/stores/boards_store.js
#	spec/javascripts/lib/utils/text_utility_spec.js
[ci skip]
parents b70efed6 e04aef5c
...@@ -75,6 +75,9 @@ gem 'grape', '~> 0.19.0' ...@@ -75,6 +75,9 @@ gem 'grape', '~> 0.19.0'
gem 'grape-entity', '~> 0.6.0' gem 'grape-entity', '~> 0.6.0'
gem 'rack-cors', '~> 0.4.0', require: 'rack/cors' gem 'rack-cors', '~> 0.4.0', require: 'rack/cors'
# Disable strong_params so that Mash does not respond to :permitted?
gem 'hashie-forbidden_attributes'
# Pagination # Pagination
gem 'kaminari', '~> 0.17.0' gem 'kaminari', '~> 0.17.0'
......
...@@ -363,7 +363,7 @@ GEM ...@@ -363,7 +363,7 @@ GEM
grape-entity (0.6.0) grape-entity (0.6.0)
activesupport activesupport
multi_json (>= 1.3.2) multi_json (>= 1.3.2)
grpc (1.2.2) grpc (1.1.2)
google-protobuf (~> 3.1) google-protobuf (~> 3.1)
googleauth (~> 0.5.1) googleauth (~> 0.5.1)
gssapi (1.2.0) gssapi (1.2.0)
...@@ -381,6 +381,8 @@ GEM ...@@ -381,6 +381,8 @@ GEM
tilt tilt
hashdiff (0.3.2) hashdiff (0.3.2)
hashie (3.5.5) hashie (3.5.5)
hashie-forbidden_attributes (0.1.1)
hashie (>= 3.0)
health_check (2.6.0) health_check (2.6.0)
rails (>= 4.0) rails (>= 4.0)
hipchat (1.5.2) hipchat (1.5.2)
...@@ -959,6 +961,7 @@ DEPENDENCIES ...@@ -959,6 +961,7 @@ DEPENDENCIES
gssapi gssapi
haml_lint (~> 0.21.0) haml_lint (~> 0.21.0)
hamlit (~> 2.6.1) hamlit (~> 2.6.1)
hashie-forbidden_attributes
health_check (~> 2.6.0) health_check (~> 2.6.0)
hipchat (~> 1.5.0) hipchat (~> 1.5.0)
html-pipeline (~> 1.11.0) html-pipeline (~> 1.11.0)
...@@ -1080,4 +1083,4 @@ DEPENDENCIES ...@@ -1080,4 +1083,4 @@ DEPENDENCIES
wikicloth (= 0.8.1) wikicloth (= 0.8.1)
BUNDLED WITH BUNDLED WITH
1.14.5 1.14.6
/* global Flash */
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import emojiMap from 'emojis/digests.json'; import emojiMap from 'emojis/digests.json';
...@@ -124,6 +126,8 @@ AwardsHandler.prototype.showEmojiMenu = function showEmojiMenu($addBtn) { ...@@ -124,6 +126,8 @@ AwardsHandler.prototype.showEmojiMenu = function showEmojiMenu($addBtn) {
} }
const $menu = $('.emoji-menu'); const $menu = $('.emoji-menu');
const $thumbsBtn = $menu.find('[data-name="thumbsup"], [data-name="thumbsdown"]').parent();
const $userAuthored = this.isUserAuthored($addBtn);
if ($menu.length) { if ($menu.length) {
if ($menu.is('.is-visible')) { if ($menu.is('.is-visible')) {
$addBtn.removeClass('is-active'); $addBtn.removeClass('is-active');
...@@ -147,6 +151,8 @@ AwardsHandler.prototype.showEmojiMenu = function showEmojiMenu($addBtn) { ...@@ -147,6 +151,8 @@ AwardsHandler.prototype.showEmojiMenu = function showEmojiMenu($addBtn) {
}, 200); }, 200);
}); });
} }
$thumbsBtn.toggleClass('disabled', $userAuthored);
}; };
// Create the emoji menu with the first category of emojis. // Create the emoji menu with the first category of emojis.
...@@ -259,7 +265,8 @@ AwardsHandler.prototype.addAward = function addAward( ...@@ -259,7 +265,8 @@ AwardsHandler.prototype.addAward = function addAward(
callback, callback,
) { ) {
const normalizedEmoji = this.normalizeEmojiName(emoji); const normalizedEmoji = this.normalizeEmojiName(emoji);
this.postEmoji(awardUrl, normalizedEmoji, () => { const $emojiButton = this.findEmojiIcon(votesBlock, normalizedEmoji).parent();
this.postEmoji($emojiButton, awardUrl, normalizedEmoji, () => {
this.addAwardToEmojiBar(votesBlock, normalizedEmoji, checkMutuality); this.addAwardToEmojiBar(votesBlock, normalizedEmoji, checkMutuality);
return typeof callback === 'function' ? callback() : undefined; return typeof callback === 'function' ? callback() : undefined;
}); });
...@@ -324,6 +331,10 @@ AwardsHandler.prototype.isActive = function isActive($emojiButton) { ...@@ -324,6 +331,10 @@ AwardsHandler.prototype.isActive = function isActive($emojiButton) {
return $emojiButton.hasClass('active'); return $emojiButton.hasClass('active');
}; };
AwardsHandler.prototype.isUserAuthored = function isUserAuthored($button) {
return $button.hasClass('js-user-authored');
};
AwardsHandler.prototype.decrementCounter = function decrementCounter($emojiButton, emoji) { AwardsHandler.prototype.decrementCounter = function decrementCounter($emojiButton, emoji) {
const counter = $('.js-counter', $emojiButton); const counter = $('.js-counter', $emojiButton);
const counterNumber = parseInt(counter.text(), 10); const counterNumber = parseInt(counter.text(), 10);
...@@ -428,20 +439,35 @@ AwardsHandler.prototype.createEmoji = function createEmoji(votesBlock, emoji) { ...@@ -428,20 +439,35 @@ AwardsHandler.prototype.createEmoji = function createEmoji(votesBlock, emoji) {
}); });
}; };
AwardsHandler.prototype.postEmoji = function postEmoji(awardUrl, emoji, callback) { AwardsHandler.prototype.postEmoji = function postEmoji($emojiButton, awardUrl, emoji, callback) {
return $.post(awardUrl, { if (this.isUserAuthored($emojiButton)) {
name: emoji, this.userAuthored($emojiButton);
}, (data) => { } else {
if (data.ok) { $.post(awardUrl, {
callback(); name: emoji,
} }, (data) => {
}); if (data.ok) {
callback();
}
}).fail(() => new Flash('Something went wrong on our end.'));
}
}; };
AwardsHandler.prototype.findEmojiIcon = function findEmojiIcon(votesBlock, emoji) { AwardsHandler.prototype.findEmojiIcon = function findEmojiIcon(votesBlock, emoji) {
return votesBlock.find(`.js-emoji-btn [data-name="${emoji}"]`); return votesBlock.find(`.js-emoji-btn [data-name="${emoji}"]`);
}; };
AwardsHandler.prototype.userAuthored = function userAuthored($emojiButton) {
const oldTitle = this.getAwardTooltip($emojiButton);
const newTitle = 'You cannot vote on your own issue, MR and note';
gl.utils.updateTooltipTitle($emojiButton, newTitle).tooltip('show');
// Restore tooltip back to award list
return setTimeout(() => {
$emojiButton.tooltip('hide');
gl.utils.updateTooltipTitle($emojiButton, oldTitle);
}, 2800);
};
AwardsHandler.prototype.scrollToAwards = function scrollToAwards() { AwardsHandler.prototype.scrollToAwards = function scrollToAwards() {
const options = { const options = {
scrollTop: $('.awards').offset().top - 110, scrollTop: $('.awards').offset().top - 110,
......
...@@ -34,7 +34,10 @@ gl.issueBoards.ModalFooter = Vue.extend({ ...@@ -34,7 +34,10 @@ gl.issueBoards.ModalFooter = Vue.extend({
// Post the data to the backend // Post the data to the backend
gl.boardService.bulkUpdate(issueIds, { gl.boardService.bulkUpdate(issueIds, {
add_label_ids: [list.label.id], add_label_ids: [list.label.id],
<<<<<<< HEAD
milestone_id: this.state.currentBoard.milestone_id, milestone_id: this.state.currentBoard.milestone_id,
=======
>>>>>>> upstream/master
}).catch(() => { }).catch(() => {
new Flash('Failed to update issues, please try again.', 'alert'); new Flash('Failed to update issues, please try again.', 'alert');
......
...@@ -24,6 +24,7 @@ gl.issueBoards.RemoveIssueBtn = Vue.extend({ ...@@ -24,6 +24,7 @@ gl.issueBoards.RemoveIssueBtn = Vue.extend({
const issue = this.issue; const issue = this.issue;
const lists = issue.getLists(); const lists = issue.getLists();
const labelIds = lists.map(list => list.label.id); const labelIds = lists.map(list => list.label.id);
<<<<<<< HEAD
const data = { const data = {
remove_label_ids: labelIds, remove_label_ids: labelIds,
}; };
...@@ -34,6 +35,13 @@ gl.issueBoards.RemoveIssueBtn = Vue.extend({ ...@@ -34,6 +35,13 @@ gl.issueBoards.RemoveIssueBtn = Vue.extend({
// Post the remove data // Post the remove data
gl.boardService.bulkUpdate([issue.globalId], data).catch(() => { gl.boardService.bulkUpdate([issue.globalId], data).catch(() => {
=======
// Post the remove data
gl.boardService.bulkUpdate([issue.globalId], {
remove_label_ids: labelIds,
}).catch(() => {
>>>>>>> upstream/master
new Flash('Failed to remove issue from board, please try again.', 'alert'); new Flash('Failed to remove issue from board, please try again.', 'alert');
lists.forEach((list) => { lists.forEach((list) => {
......
...@@ -23,11 +23,14 @@ gl.issueBoards.BoardsStore = { ...@@ -23,11 +23,14 @@ gl.issueBoards.BoardsStore = {
this.state.lists = []; this.state.lists = [];
this.filter.path = gl.utils.getUrlParamsArray().join('&'); this.filter.path = gl.utils.getUrlParamsArray().join('&');
}, },
<<<<<<< HEAD
createNewListDropdownData() { createNewListDropdownData() {
this.state.currentBoard = {}; this.state.currentBoard = {};
this.state.currentPage = ''; this.state.currentPage = '';
this.state.reload = false; this.state.reload = false;
}, },
=======
>>>>>>> upstream/master
addList (listObj) { addList (listObj) {
const list = new List(listObj); const list = new List(listObj);
this.state.lists.push(list); this.state.lists.push(list);
...@@ -60,6 +63,7 @@ gl.issueBoards.BoardsStore = { ...@@ -60,6 +63,7 @@ gl.issueBoards.BoardsStore = {
title: 'Welcome to your Issue Board!', title: 'Welcome to your Issue Board!',
position: 0 position: 0
}); });
<<<<<<< HEAD
this.state.lists = _.sortBy(this.state.lists, 'position'); this.state.lists = _.sortBy(this.state.lists, 'position');
}, },
...@@ -130,5 +134,73 @@ gl.issueBoards.BoardsStore = { ...@@ -130,5 +134,73 @@ gl.issueBoards.BoardsStore = {
} else { } else {
history.pushState(null, null, `?${this.filter.path}`); history.pushState(null, null, `?${this.filter.path}`);
} }
=======
this.state.lists = _.sortBy(this.state.lists, 'position');
},
removeBlankState () {
this.removeList('blank');
Cookies.set('issue_board_welcome_hidden', 'true', {
expires: 365 * 10,
path: ''
});
},
welcomeIsHidden () {
return Cookies.get('issue_board_welcome_hidden') === 'true';
},
removeList (id, type = 'blank') {
const list = this.findList('id', id, type);
if (!list) return;
this.state.lists = this.state.lists.filter(list => list.id !== id);
},
moveList (listFrom, orderLists) {
orderLists.forEach((id, i) => {
const list = this.findList('id', parseInt(id, 10));
list.position = i;
});
listFrom.update();
},
moveIssueToList (listFrom, listTo, issue, newIndex) {
const issueTo = listTo.findIssue(issue.id);
const issueLists = issue.getLists();
const listLabels = issueLists.map(listIssue => listIssue.label);
if (!issueTo) {
// Add to new lists issues if it doesn't already exist
listTo.addIssue(issue, listFrom, newIndex);
} else {
listTo.updateIssueLabel(issue, listFrom);
issueTo.removeLabel(listFrom.label);
}
if (listTo.type === 'closed') {
issueLists.forEach((list) => {
list.removeIssue(issue);
});
issue.removeLabels(listLabels);
} else {
listFrom.removeIssue(issue);
}
},
moveIssueInList (list, issue, oldIndex, newIndex, idArray) {
const beforeId = parseInt(idArray[newIndex - 1], 10) || null;
const afterId = parseInt(idArray[newIndex + 1], 10) || null;
list.moveIssue(issue, oldIndex, newIndex, beforeId, afterId);
},
findList (key, val, type = 'label') {
return this.state.lists.filter((list) => {
const byType = type ? list['type'] === type : true;
return list[key] === val && byType;
})[0];
},
updateFiltersUrl () {
history.pushState(null, null, `?${this.filter.path}`);
>>>>>>> upstream/master
} }
}; };
...@@ -47,6 +47,10 @@ ...@@ -47,6 +47,10 @@
} }
}; };
gl.utils.updateTooltipTitle = function($tooltipEl, newTitle) {
return $tooltipEl.attr('title', newTitle).tooltip('fixTitle');
};
w.gl.utils.disableButtonIfEmptyField = function(field_selector, button_selector, event_name) { w.gl.utils.disableButtonIfEmptyField = function(field_selector, button_selector, event_name) {
event_name = event_name || 'input'; event_name = event_name || 'input';
var closest_submit, field, that; var closest_submit, field, that;
......
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, max-len */ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, max-len */
(function() { import '~/lib/utils/url_utility';
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
(function() {
this.MergedButtons = (function() { this.MergedButtons = (function() {
function MergedButtons() { function MergedButtons() {
this.removeSourceBranch = bind(this.removeSourceBranch, this); this.removeSourceBranch = this.removeSourceBranch.bind(this);
this.removeBranchSuccess = this.removeBranchSuccess.bind(this);
this.removeBranchError = this.removeBranchError.bind(this);
this.$removeBranchWidget = $('.remove_source_branch_widget'); this.$removeBranchWidget = $('.remove_source_branch_widget');
this.$removeBranchProgress = $('.remove_source_branch_in_progress'); this.$removeBranchProgress = $('.remove_source_branch_in_progress');
this.$removeBranchFailed = $('.remove_source_branch_widget.failed'); this.$removeBranchFailed = $('.remove_source_branch_widget.failed');
...@@ -22,7 +24,7 @@ ...@@ -22,7 +24,7 @@
MergedButtons.prototype.initEventListeners = function() { MergedButtons.prototype.initEventListeners = function() {
$(document).on('click', '.remove_source_branch', this.removeSourceBranch); $(document).on('click', '.remove_source_branch', this.removeSourceBranch);
$(document).on('ajax:success', '.remove_source_branch', this.removeBranchSuccess); $(document).on('ajax:success', '.remove_source_branch', this.removeBranchSuccess);
return $(document).on('ajax:error', '.remove_source_branch', this.removeBranchError); $(document).on('ajax:error', '.remove_source_branch', this.removeBranchError);
}; };
MergedButtons.prototype.removeSourceBranch = function() { MergedButtons.prototype.removeSourceBranch = function() {
...@@ -31,7 +33,7 @@ ...@@ -31,7 +33,7 @@
}; };
MergedButtons.prototype.removeBranchSuccess = function() { MergedButtons.prototype.removeBranchSuccess = function() {
return location.reload(); gl.utils.refreshCurrentPage();
}; };
MergedButtons.prototype.removeBranchError = function() { MergedButtons.prototype.removeBranchError = function() {
......
...@@ -38,6 +38,15 @@ ...@@ -38,6 +38,15 @@
height: 300px; height: 300px;
overflow-y: scroll; overflow-y: scroll;
} }
.disabled {
cursor: default;
opacity: 0.5;
&:hover {
transform: none;
}
}
} }
.emoji-search { .emoji-search {
...@@ -154,6 +163,17 @@ ...@@ -154,6 +163,17 @@
} }
} }
&.user-authored {
cursor: default;
opacity: 0.65;
&:hover,
&:active {
background-color: $white-light;
border-color: $border-color;
}
}
&.btn { &.btn {
&:focus { &:focus {
outline: 0; outline: 0;
......
...@@ -158,6 +158,7 @@ ...@@ -158,6 +158,7 @@
li.task-list-item { li.task-list-item {
list-style-type: none; list-style-type: none;
position: relative; position: relative;
min-height: 22px;
padding-left: 28px; padding-left: 28px;
margin-left: 0 !important; margin-left: 0 !important;
......
...@@ -26,6 +26,7 @@ $gray-dark: darken($gray-light, $darken-dark-factor); ...@@ -26,6 +26,7 @@ $gray-dark: darken($gray-light, $darken-dark-factor);
$gray-darker: #eee; $gray-darker: #eee;
$gray-darkest: #c4c4c4; $gray-darkest: #c4c4c4;
$green-25: #f6fcf8;
$green-50: #e4f5eb; $green-50: #e4f5eb;
$green-100: #bae6cc; $green-100: #bae6cc;
$green-200: #8dd5aa; $green-200: #8dd5aa;
...@@ -37,6 +38,7 @@ $green-700: #12753a; ...@@ -37,6 +38,7 @@ $green-700: #12753a;
$green-800: #0e5a2d; $green-800: #0e5a2d;
$green-900: #0a4020; $green-900: #0a4020;
$blue-25: #f6fafd;
$blue-50: #e4eff9; $blue-50: #e4eff9;
$blue-100: #bcd7f1; $blue-100: #bcd7f1;
$blue-200: #8fbce8; $blue-200: #8fbce8;
...@@ -48,6 +50,7 @@ $blue-700: #17599c; ...@@ -48,6 +50,7 @@ $blue-700: #17599c;
$blue-800: #134a81; $blue-800: #134a81;
$blue-900: #0f3b66; $blue-900: #0f3b66;
$orange-25: #fffcf8;
$orange-50: #fff2e1; $orange-50: #fff2e1;
$orange-100: #fedfb3; $orange-100: #fedfb3;
$orange-200: #feca81; $orange-200: #feca81;
...@@ -59,6 +62,7 @@ $orange-700: #c26700; ...@@ -59,6 +62,7 @@ $orange-700: #c26700;
$orange-800: #a35100; $orange-800: #a35100;
$orange-900: #853b00; $orange-900: #853b00;
$red-25: #fef7f6;
$red-50: #fbe7e4; $red-50: #fbe7e4;
$red-100: #f4c4bc; $red-100: #f4c4bc;
$red-200: #ed9d90; $red-200: #ed9d90;
...@@ -147,7 +151,7 @@ $gl-sidebar-padding: 22px; ...@@ -147,7 +151,7 @@ $gl-sidebar-padding: 22px;
/* /*
* Misc * Misc
*/ */
$row-hover: lighten($blue-50, 2%); $row-hover: $blue-25;
$row-hover-border: $blue-100; $row-hover-border: $blue-100;
$progress-color: #c0392b; $progress-color: #c0392b;
$header-height: 50px; $header-height: 50px;
...@@ -226,18 +230,18 @@ $gl-btn-active-gradient: inset 0 2px 3px $gl-btn-active-background; ...@@ -226,18 +230,18 @@ $gl-btn-active-gradient: inset 0 2px 3px $gl-btn-active-background;
/* /*
* Commit Diff Colors * Commit Diff Colors
*/ */
$added: $green-300; $added: #63c363;
$deleted: $red-300; $deleted: #f77;
$line-added: $green-50; $line-added: #ecfdf0;
$line-added-dark: $green-100; $line-added-dark: #c7f0d2;
$line-removed: $red-50; $line-removed: #fbe9eb;
$line-removed-dark: $red-100; $line-removed-dark: #fac5cd;
$line-number-old: lighten($red-100, 5%); $line-number-old: #f9d7dc;
$line-number-new: lighten($green-100, 5%); $line-number-new: #ddfbe6;
$line-number-select: lighten($orange-100, 5%); $line-number-select: #fbf2da;
$line-target-blue: $blue-50; $line-target-blue: #f6faff;
$line-select-yellow: $orange-50; $line-select-yellow: #fcf8e7;
$line-select-yellow-dark: $orange-100; $line-select-yellow-dark: #f0e2bd;
$dark-diff-match-bg: rgba(255, 255, 255, 0.3); $dark-diff-match-bg: rgba(255, 255, 255, 0.3);
$dark-diff-match-color: rgba(255, 255, 255, 0.1); $dark-diff-match-color: rgba(255, 255, 255, 0.1);
$file-mode-changed: #777; $file-mode-changed: #777;
......
...@@ -627,7 +627,6 @@ ul.notes { ...@@ -627,7 +627,6 @@ ul.notes {
} }
&:not(.is-disabled):hover, &:not(.is-disabled):hover,
&:not(.is-disabled):focus,
&.is-active { &.is-active {
color: $gl-text-green; color: $gl-text-green;
...@@ -641,6 +640,11 @@ ul.notes { ...@@ -641,6 +640,11 @@ ul.notes {
height: 15px; height: 15px;
width: 15px; width: 15px;
} }
.loading {
margin: 0;
height: auto;
}
} }
.discussion-next-btn { .discussion-next-btn {
......
...@@ -60,7 +60,7 @@ class RegistrationsController < Devise::RegistrationsController ...@@ -60,7 +60,7 @@ class RegistrationsController < Devise::RegistrationsController
end end
def resource def resource
@resource ||= Users::CreateService.new(current_user, sign_up_params).build @resource ||= Users::BuildService.new(current_user, sign_up_params).execute
end end
def devise_mapping def devise_mapping
......
...@@ -110,6 +110,14 @@ module IssuesHelper ...@@ -110,6 +110,14 @@ module IssuesHelper
end end
end end
def award_user_authored_class(award)
if award == 'thumbsdown' || award == 'thumbsup'
'user-authored js-user-authored'
else
''
end
end
def awards_sort(awards) def awards_sort(awards)
awards.sort_by do |award, notes| awards.sort_by do |award, notes|
if award == "thumbsup" if award == "thumbsup"
......
...@@ -20,7 +20,8 @@ class ContainerRepository < ActiveRecord::Base ...@@ -20,7 +20,8 @@ class ContainerRepository < ActiveRecord::Base
end end
def path def path
@path ||= [project.full_path, name].select(&:present?).join('/') @path ||= [project.full_path, name]
.select(&:present?).join('/').downcase
end end
def location def location
......
...@@ -61,6 +61,9 @@ module Projects ...@@ -61,6 +61,9 @@ module Projects
fail(error: @project.errors.full_messages.join(', ')) fail(error: @project.errors.full_messages.join(', '))
end end
@project @project
rescue ActiveRecord::RecordInvalid => e
message = "Unable to save #{e.record.type}: #{e.record.errors.full_messages.join(", ")} "
fail(error: message)
rescue => e rescue => e
fail(error: e.message) fail(error: e.message)
end end
......
module Users
# Service for building a new user.
class BuildService < BaseService
def initialize(current_user, params = {})
@current_user = current_user
@params = params.dup
end
def execute
raise Gitlab::Access::AccessDeniedError unless can_create_user?
user = User.new(build_user_params)
if current_user&.admin?
if params[:reset_password]
user.generate_reset_token
params[:force_random_password] = true
end
if params[:force_random_password]
random_password = Devise.friendly_token.first(Devise.password_length.min)
user.password = user.password_confirmation = random_password
end
end
identity_attrs = params.slice(:extern_uid, :provider)
if identity_attrs.any?
user.identities.build(identity_attrs)
end
user
end
private
def can_create_user?
(current_user.nil? && current_application_settings.signup_enabled?) || current_user&.admin?
end
# Allowed params for creating a user (admins only)
def admin_create_params
[
:access_level,
:admin,
:avatar,
:bio,
:can_create_group,
:color_scheme_id,
:email,
:external,
:force_random_password,
:hide_no_password,
:hide_no_ssh_key,
:key_id,
:linkedin,
:name,
:password,
:password_automatically_set,
:password_expires_at,
:projects_limit,
:remember_me,
:skip_confirmation,
:skype,
:theme_id,
:twitter,
:username,
:website_url
]
end
# Allowed params for user signup
def signup_params
[
:email,
:email_confirmation,
:password_automatically_set,
:name,
:password,
:username
]
end
def build_user_params
if current_user&.admin?
user_params = params.slice(*admin_create_params)
user_params[:created_by_id] = current_user&.id
if params[:reset_password]
user_params.merge!(force_random_password: true, password_expires_at: nil)
end
else
user_params = params.slice(*signup_params)
user_params[:skip_confirmation] = !current_application_settings.send_user_confirmation_email
end
user_params
end
end
end
...@@ -6,34 +6,10 @@ module Users ...@@ -6,34 +6,10 @@ module Users
@params = params.dup @params = params.dup
end end
def build
raise Gitlab::Access::AccessDeniedError unless can_create_user?
user = User.new(build_user_params)
if current_user&.admin?
if params[:reset_password]
@reset_token = user.generate_reset_token
params[:force_random_password] = true
end
if params[:force_random_password]
random_password = Devise.friendly_token.first(Devise.password_length.min)
user.password = user.password_confirmation = random_password
end
end
identity_attrs = params.slice(:extern_uid, :provider)
if identity_attrs.any?
user.identities.build(identity_attrs)
end
user
end
def execute def execute
user = build user = Users::BuildService.new(current_user, params).execute
@reset_token = user.generate_reset_token if user.recently_sent_password_reset?
if user.save if user.save
log_info("User \"#{user.name}\" (#{user.email}) was created") log_info("User \"#{user.name}\" (#{user.email}) was created")
...@@ -43,70 +19,5 @@ module Users ...@@ -43,70 +19,5 @@ module Users
user user
end end
private
def can_create_user?
(current_user.nil? && current_application_settings.signup_enabled?) || current_user&.admin?
end
# Allowed params for creating a user (admins only)
def admin_create_params
[
:access_level,
:admin,
:avatar,
:bio,
:can_create_group,
:color_scheme_id,
:email,
:external,
:force_random_password,
:password_automatically_set,
:hide_no_password,
:hide_no_ssh_key,
:key_id,
:linkedin,
:name,
:password,
:password_expires_at,
:projects_limit,
:remember_me,
:skip_confirmation,
:skype,
:theme_id,
:twitter,
:username,
:website_url
]
end
# Allowed params for user signup
def signup_params
[
:email,
:email_confirmation,
:password_automatically_set,
:name,
:password,
:username
]
end
def build_user_params
if current_user&.admin?
user_params = params.slice(*admin_create_params)
user_params[:created_by_id] = current_user&.id
if params[:reset_password]
user_params.merge!(force_random_password: true, password_expires_at: nil)
end
else
user_params = params.slice(*signup_params)
user_params[:skip_confirmation] = !current_application_settings.send_user_confirmation_email
end
user_params
end
end end
end end
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
- @services.sort_by(&:title).each do |service| - @services.sort_by(&:title).each do |service|
%tr %tr
%td %td
= icon("copy", class: 'clgray') = boolean_to_icon service.activated?
%td %td
= link_to edit_admin_application_settings_service_path(service.id) do = link_to edit_admin_application_settings_service_path(service.id) do
%strong= service.title %strong= service.title
......
- grouped_emojis = awardable.grouped_awards(with_thumbs: inline) - grouped_emojis = awardable.grouped_awards(with_thumbs: inline)
- user_authored = awardable.user_authored?(current_user)
.awards.js-awards-block{ class: ("hidden" if !inline && grouped_emojis.empty?), data: { award_url: toggle_award_url(awardable) } } .awards.js-awards-block{ class: ("hidden" if !inline && grouped_emojis.empty?), data: { award_url: toggle_award_url(awardable) } }
- awards_sort(grouped_emojis).each do |emoji, awards| - awards_sort(grouped_emojis).each do |emoji, awards|
%button.btn.award-control.js-emoji-btn.has-tooltip{ type: "button", %button.btn.award-control.js-emoji-btn.has-tooltip{ type: "button",
class: (award_state_class(awards, current_user)), class: [(award_state_class(awards, current_user)), (award_user_authored_class(emoji) if user_authored)],
data: { placement: "bottom", title: award_user_list(awards, current_user) } } data: { placement: "bottom", title: award_user_list(awards, current_user) } }
= emoji_icon(emoji) = emoji_icon(emoji)
%span.award-control-text.js-counter %span.award-control-text.js-counter
...@@ -12,6 +13,7 @@ ...@@ -12,6 +13,7 @@
.award-menu-holder.js-award-holder .award-menu-holder.js-award-holder
%button.btn.award-control.has-tooltip.js-add-award{ type: 'button', %button.btn.award-control.has-tooltip.js-add-award{ type: 'button',
'aria-label': 'Add emoji', 'aria-label': 'Add emoji',
class: ("js-user-authored" if user_authored),
data: { title: 'Add emoji', placement: "bottom" } } data: { title: 'Add emoji', placement: "bottom" } }
%span{ class: "award-control-icon award-control-icon-neutral" }= custom_icon('emoji_slightly_smiling_face') %span{ class: "award-control-icon award-control-icon-neutral" }= custom_icon('emoji_slightly_smiling_face')
%span{ class: "award-control-icon award-control-icon-positive" }= custom_icon('emoji_smiley') %span{ class: "award-control-icon award-control-icon-positive" }= custom_icon('emoji_smiley')
......
...@@ -52,15 +52,15 @@ ...@@ -52,15 +52,15 @@
":aria-label" => "buttonText", ":aria-label" => "buttonText",
"@click" => "resolve", "@click" => "resolve",
":title" => "buttonText", ":title" => "buttonText",
"v-show" => "!loading",
":ref" => "'button'" } ":ref" => "'button'" }
= icon("spin spinner", "v-show" => "loading")
= render "shared/icons/icon_status_success.svg" = icon("spin spinner", "v-show" => "loading", class: 'loading')
%div{ 'v-show' => '!loading' }= render "shared/icons/icon_status_success.svg"
- if current_user - if current_user
- if note.emoji_awardable? - if note.emoji_awardable?
= link_to '#', title: 'Award Emoji', class: 'note-action-button note-emoji-button js-add-award js-note-emoji', data: { position: 'right' } do - user_authored = note.user_authored?(current_user)
= link_to '#', title: 'Award Emoji', class: "note-action-button note-emoji-button js-add-award js-note-emoji #{'js-user-authored' if user_authored}", data: { position: 'right' } do
= icon('spinner spin') = icon('spinner spin')
%span{ class: "link-highlight award-control-icon-neutral" }= custom_icon('emoji_slightly_smiling_face') %span{ class: "link-highlight award-control-icon-neutral" }= custom_icon('emoji_slightly_smiling_face')
%span{ class: "link-highlight award-control-icon-positive" }= custom_icon('emoji_smiley') %span{ class: "link-highlight award-control-icon-positive" }= custom_icon('emoji_smiley')
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
= render "projects/last_push" = render "projects/last_push"
= render "home_panel" = render "home_panel"
- if current_user && can?(current_user, :download_code, @project) - if can?(current_user, :download_code, @project)
%nav.project-stats{ class: container_class } %nav.project-stats{ class: container_class }
%ul.nav %ul.nav
%li %li
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
= f.label :import_url, class: 'control-label' do = f.label :import_url, class: 'control-label' do
%span Git repository URL %span Git repository URL
.col-sm-10 .col-sm-10
= f.text_field :import_url, autocomplete: 'off', class: 'form-control', placeholder: 'https://username:password@gitlab.company.com/group/project.git', disabled: true = f.text_field :import_url, autocomplete: 'off', class: 'form-control', placeholder: 'https://username:password@gitlab.company.com/group/project.git'
.well.prepend-top-20 .well.prepend-top-20
%ul %ul
......
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
Also, issues are searchable and filterable. Also, issues are searchable and filterable.
- if project_select_button - if project_select_button
= render 'shared/new_project_item_select', path: 'issues/new', label: 'New issue' = render 'shared/new_project_item_select', path: 'issues/new', label: 'New issue'
= link_to 'New issue', button_path, class: 'btn btn-new', title: 'New issue', id: 'new_issue_link'
- else - else
%h4 There are no issues to show. .text-center
= link_to 'New issue', button_path, class: 'btn btn-new', title: 'New issue', id: 'new_issue_link' %h4 There are no issues to show.
= link_to 'New issue', button_path, class: 'btn btn-new', title: 'New issue', id: 'new_issue_link'
---
title: Implement Users::BuildService
merge_request: 30349
author: George Andrinopoulos
---
title: 'Frontend prevent authored votes'
merge_request: 6260
author: Barthc
---
title: Fixed alignment of empty task list items
merge_request:
author:
---
title: Fix invalid encoding when showing some traces
merge_request: 10681
author:
---
title: Centered issues empty state
merge_request:
author:
---
title: Add lighter colors and fix existing light colors
merge_request: 10690
author:
---
title: Add hashie-forbidden_attributes gem
merge_request: 10579
author: Andy Brown
...@@ -50,20 +50,17 @@ update them are in [a separate document][omnidocker]. ...@@ -50,20 +50,17 @@ update them are in [a separate document][omnidocker].
## Upgrading without downtime ## Upgrading without downtime
Starting with GitLab 9.1.0 it's possible to upgrade to a newer version of GitLab Starting with GitLab 9.1.0 it's possible to upgrade to a newer major, minor, or patch version of GitLab
without having to take your GitLab instance offline. However, for this to work without having to take your GitLab instance offline. However, for this to work
there are the following requirements: there are the following requirements:
1. You can only upgrade 1 release at a time. For example, if 9.1.15 is the last 1. You can only upgrade 1 minor release at a time. So from 9.1 to 9.2, not to 9.3.
release of 9.1 then you can safely upgrade from that version to 9.2.0. 2. You have to be on the most recent patch release. For example, if 9.1.15 is the last
release of 9.1 then you can safely upgrade from that version to any 9.2.x version.
However, if you are running 9.1.14 you first need to upgrade to 9.1.15. However, if you are running 9.1.14 you first need to upgrade to 9.1.15.
2. You have to use [post-deployment 2. You have to use [post-deployment
migrations](../development/post_deployment_migrations.md). migrations](../development/post_deployment_migrations.md).
3. You are using PostgreSQL. If you are using MySQL you will still need downtime 3. You are using PostgreSQL. If you are using MySQL please look at the release post to see if downtime is required.
when upgrading.
This applies to major, minor, and patch releases unless stated otherwise in a
release post.
## Upgrading between editions ## Upgrading between editions
......
...@@ -13,8 +13,8 @@ module Banzai ...@@ -13,8 +13,8 @@ module Banzai
issuables = extractor.extract([doc]) issuables = extractor.extract([doc])
issuables.each do |node, issuable| issuables.each do |node, issuable|
if VISIBLE_STATES.include?(issuable.state) if VISIBLE_STATES.include?(issuable.state) && node.children.present?
node.children.last.content += " [#{issuable.state}]" node.add_child(Nokogiri::XML::Text.new(" [#{issuable.state}]", doc))
end end
end end
......
...@@ -15,7 +15,7 @@ module ContainerRegistry ...@@ -15,7 +15,7 @@ module ContainerRegistry
LEVELS_SUPPORTED = 3 LEVELS_SUPPORTED = 3
def initialize(path) def initialize(path)
@path = path @path = path.to_s.downcase
end end
def valid? def valid?
...@@ -25,7 +25,7 @@ module ContainerRegistry ...@@ -25,7 +25,7 @@ module ContainerRegistry
end end
def components def components
@components ||= @path.to_s.split('/') @components ||= @path.split('/')
end end
def nodes def nodes
......
...@@ -25,11 +25,10 @@ module Gitlab ...@@ -25,11 +25,10 @@ module Gitlab
end end
def limit(last_bytes = LIMIT_SIZE) def limit(last_bytes = LIMIT_SIZE)
stream_size = size if last_bytes < size
if stream_size < last_bytes stream.seek(-last_bytes, IO::SEEK_END)
last_bytes = stream_size stream.readline
end end
stream.seek(-last_bytes, IO::SEEK_END)
end end
def append(data, offset) def append(data, offset)
......
...@@ -148,7 +148,7 @@ module Gitlab ...@@ -148,7 +148,7 @@ module Gitlab
def build_new_user def build_new_user
user_params = user_attributes.merge(extern_uid: auth_hash.uid, provider: auth_hash.provider, skip_confirmation: true) user_params = user_attributes.merge(extern_uid: auth_hash.uid, provider: auth_hash.provider, skip_confirmation: true)
Users::CreateService.new(nil, user_params).build Users::BuildService.new(nil, user_params).execute
end end
def user_attributes def user_attributes
......
...@@ -7,10 +7,10 @@ namespace :gitlab do ...@@ -7,10 +7,10 @@ namespace :gitlab do
abort %(Please specify the directory where you want to install gitaly:\n rake "gitlab:gitaly:install[/home/git/gitaly]") abort %(Please specify the directory where you want to install gitaly:\n rake "gitlab:gitaly:install[/home/git/gitaly]")
end end
tag = "v#{Gitlab::GitalyClient.expected_server_version}" version = Gitlab::GitalyClient.expected_server_version
repo = 'https://gitlab.com/gitlab-org/gitaly.git' repo = 'https://gitlab.com/gitlab-org/gitaly.git'
checkout_or_clone_tag(tag: tag, repo: repo, target_dir: args.dir) checkout_or_clone_version(version: version, repo: repo, target_dir: args.dir)
_, status = Gitlab::Popen.popen(%w[which gmake]) _, status = Gitlab::Popen.popen(%w[which gmake])
command = status.zero? ? 'gmake' : 'make' command = status.zero? ? 'gmake' : 'make'
......
namespace :gitlab do namespace :gitlab do
namespace :shell do namespace :shell do
desc "GitLab | Install or upgrade gitlab-shell" desc "GitLab | Install or upgrade gitlab-shell"
task :install, [:tag, :repo] => :environment do |t, args| task :install, [:repo] => :environment do |t, args|
warn_user_is_not_gitlab warn_user_is_not_gitlab
default_version = Gitlab::Shell.version_required default_version = Gitlab::Shell.version_required
default_version_tag = "v#{default_version}" args.with_defaults(repo: 'https://gitlab.com/gitlab-org/gitlab-shell.git')
args.with_defaults(tag: default_version_tag, repo: 'https://gitlab.com/gitlab-org/gitlab-shell.git')
gitlab_url = Gitlab.config.gitlab.url gitlab_url = Gitlab.config.gitlab.url
# gitlab-shell requires a / at the end of the url # gitlab-shell requires a / at the end of the url
gitlab_url += '/' unless gitlab_url.end_with?('/') gitlab_url += '/' unless gitlab_url.end_with?('/')
target_dir = Gitlab.config.gitlab_shell.path target_dir = Gitlab.config.gitlab_shell.path
checkout_or_clone_tag(tag: default_version_tag, repo: args.repo, target_dir: target_dir) checkout_or_clone_version(version: default_version, repo: args.repo, target_dir: target_dir)
# Make sure we're on the right tag # Make sure we're on the right tag
Dir.chdir(target_dir) do Dir.chdir(target_dir) do
......
...@@ -147,41 +147,30 @@ module Gitlab ...@@ -147,41 +147,30 @@ module Gitlab
Rails.env.test? ? Rails.root.join('tmp/tests') : Gitlab.config.gitlab.user_home Rails.env.test? ? Rails.root.join('tmp/tests') : Gitlab.config.gitlab.user_home
end end
def checkout_or_clone_tag(tag:, repo:, target_dir:) def checkout_or_clone_version(version:, repo:, target_dir:)
if Dir.exist?(target_dir) version =
checkout_tag(tag, target_dir) if version.starts_with?("=")
else version.sub(/\A=/, '') # tag or branch
clone_repo(repo, target_dir) else
end "v#{version}" # tag
end
reset_to_tag(tag, target_dir) clone_repo(repo, target_dir) unless Dir.exist?(target_dir)
checkout_version(version, target_dir)
reset_to_version(version, target_dir)
end end
def clone_repo(repo, target_dir) def clone_repo(repo, target_dir)
run_command!(%W[#{Gitlab.config.git.bin_path} clone -- #{repo} #{target_dir}]) run_command!(%W[#{Gitlab.config.git.bin_path} clone -- #{repo} #{target_dir}])
end end
def checkout_tag(tag, target_dir) def checkout_version(version, target_dir)
run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} fetch --tags --quiet]) run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} fetch --quiet])
run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} checkout --quiet #{tag}]) run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} checkout --quiet #{version}])
end end
def reset_to_tag(tag_wanted, target_dir) def reset_to_version(version, target_dir)
tag = run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} reset --hard #{version}])
begin
# First try to checkout without fetching
# to avoid stalling tests if the Internet is down.
run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} describe -- #{tag_wanted}])
rescue Gitlab::TaskFailedError
run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} fetch origin])
run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} describe -- origin/#{tag_wanted}])
end
if tag
run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} reset --hard #{tag.strip}])
else
raise Gitlab::TaskFailedError
end
end end
end end
end end
...@@ -7,10 +7,10 @@ namespace :gitlab do ...@@ -7,10 +7,10 @@ namespace :gitlab do
abort %(Please specify the directory where you want to install gitlab-workhorse:\n rake "gitlab:workhorse:install[/home/git/gitlab-workhorse]") abort %(Please specify the directory where you want to install gitlab-workhorse:\n rake "gitlab:workhorse:install[/home/git/gitlab-workhorse]")
end end
tag = "v#{Gitlab::Workhorse.version}" version = Gitlab::Workhorse.version
repo = 'https://gitlab.com/gitlab-org/gitlab-workhorse.git' repo = 'https://gitlab.com/gitlab-org/gitlab-workhorse.git'
checkout_or_clone_tag(tag: tag, repo: repo, target_dir: args.dir) checkout_or_clone_version(version: version, repo: repo, target_dir: args.dir)
_, status = Gitlab::Popen.popen(%w[which gmake]) _, status = Gitlab::Popen.popen(%w[which gmake])
command = status.zero? ? 'gmake' : 'make' command = status.zero? ? 'gmake' : 'make'
......
...@@ -198,6 +198,8 @@ feature 'Diff notes resolve', feature: true, js: true do ...@@ -198,6 +198,8 @@ feature 'Diff notes resolve', feature: true, js: true do
it 'does not mark discussion as resolved when resolving single note' do it 'does not mark discussion as resolved when resolving single note' do
page.first '.diff-content .note' do page.first '.diff-content .note' do
first('.line-resolve-btn').click first('.line-resolve-btn').click
expect(page).to have_selector('.note-action-button .loading')
expect(first('.line-resolve-btn')['data-original-title']).to eq("Resolved by #{user.name}") expect(first('.line-resolve-btn')['data-original-title']).to eq("Resolved by #{user.name}")
end end
......
.
..
😺
ヾ(´༎ຶД༎ຶ`)ノ
許功蓋
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import AwardsHandler from '~/awards_handler'; import AwardsHandler from '~/awards_handler';
require('~/lib/utils/common_utils');
(function() { (function() {
var awardsHandler, lazyAssert, urlRoot, openAndWaitForEmojiMenu; var awardsHandler, lazyAssert, urlRoot, openAndWaitForEmojiMenu;
...@@ -28,7 +30,7 @@ import AwardsHandler from '~/awards_handler'; ...@@ -28,7 +30,7 @@ import AwardsHandler from '~/awards_handler';
loadFixtures('issues/issue_with_comment.html.raw'); loadFixtures('issues/issue_with_comment.html.raw');
awardsHandler = new AwardsHandler; awardsHandler = new AwardsHandler;
spyOn(awardsHandler, 'postEmoji').and.callFake((function(_this) { spyOn(awardsHandler, 'postEmoji').and.callFake((function(_this) {
return function(url, emoji, cb) { return function(button, url, emoji, cb) {
return cb(); return cb();
}; };
})(this)); })(this));
...@@ -115,6 +117,27 @@ import AwardsHandler from '~/awards_handler'; ...@@ -115,6 +117,27 @@ import AwardsHandler from '~/awards_handler';
return expect($emojiButton.next('.js-counter').text()).toBe('4'); return expect($emojiButton.next('.js-counter').text()).toBe('4');
}); });
}); });
describe('::userAuthored', function() {
it('should update tooltip to user authored title', function() {
var $thumbsUpEmoji, $votesBlock;
$votesBlock = $('.js-awards-block').eq(0);
$thumbsUpEmoji = $votesBlock.find('[data-name=thumbsup]').parent();
$thumbsUpEmoji.attr('data-title', 'sam');
awardsHandler.userAuthored($thumbsUpEmoji);
return expect($thumbsUpEmoji.data("original-title")).toBe("You cannot vote on your own issue, MR and note");
});
it('should restore tooltip back to initial vote list', function() {
var $thumbsUpEmoji, $votesBlock;
jasmine.clock().install();
$votesBlock = $('.js-awards-block').eq(0);
$thumbsUpEmoji = $votesBlock.find('[data-name=thumbsup]').parent();
$thumbsUpEmoji.attr('data-title', 'sam');
awardsHandler.userAuthored($thumbsUpEmoji);
jasmine.clock().tick(2801);
jasmine.clock().uninstall();
return expect($thumbsUpEmoji.data("original-title")).toBe("sam");
});
});
describe('::getAwardUrl', function() { describe('::getAwardUrl', function() {
return it('returns the url for request', function() { return it('returns the url for request', function() {
return expect(awardsHandler.getAwardUrl()).toBe('http://test.host/frontend-fixtures/issues-project/issues/1/toggle_award_emoji'); return expect(awardsHandler.getAwardUrl()).toBe('http://test.host/frontend-fixtures/issues-project/issues/1/toggle_award_emoji');
......
...@@ -7,6 +7,7 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont ...@@ -7,6 +7,7 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )} let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project, namespace: namespace, path: 'merge-requests-project') } let(:project) { create(:project, namespace: namespace, path: 'merge-requests-project') }
let(:merge_request) { create(:merge_request, :with_diffs, source_project: project, target_project: project, description: '- [ ] Task List Item') } let(:merge_request) { create(:merge_request, :with_diffs, source_project: project, target_project: project, description: '- [ ] Task List Item') }
let(:merged_merge_request) { create(:merge_request, :merged, source_project: project, target_project: project) }
let(:pipeline) do let(:pipeline) do
create( create(
:ci_pipeline, :ci_pipeline,
...@@ -32,6 +33,12 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont ...@@ -32,6 +33,12 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont
render_merge_request(example.description, merge_request) render_merge_request(example.description, merge_request)
end end
it 'merge_requests/merged_merge_request.html.raw' do |example|
allow_any_instance_of(MergeRequest).to receive(:source_branch_exists?).and_return(true)
allow_any_instance_of(MergeRequest).to receive(:can_remove_source_branch?).and_return(true)
render_merge_request(example.description, merged_merge_request)
end
private private
def render_merge_request(fixture_file_name, merge_request) def render_merge_request(fixture_file_name, merge_request)
......
...@@ -4,6 +4,8 @@ describe('text_utility', () => { ...@@ -4,6 +4,8 @@ describe('text_utility', () => {
describe('gl.text.getTextWidth', () => { describe('gl.text.getTextWidth', () => {
it('returns zero width when no text is passed', () => { it('returns zero width when no text is passed', () => {
expect(gl.text.getTextWidth('')).toBe(0); expect(gl.text.getTextWidth('')).toBe(0);
<<<<<<< HEAD
=======
}); });
it('returns zero width when no text is passed and font is passed', () => { it('returns zero width when no text is passed and font is passed', () => {
...@@ -14,6 +16,50 @@ describe('text_utility', () => { ...@@ -14,6 +16,50 @@ describe('text_utility', () => {
expect(gl.text.getTextWidth('foo') > 0).toBe(true); expect(gl.text.getTextWidth('foo') > 0).toBe(true);
}); });
it('returns bigger width when font is larger', () => {
const largeFont = gl.text.getTextWidth('foo', '100px sans-serif');
const regular = gl.text.getTextWidth('foo', '10px sans-serif');
expect(largeFont > regular).toBe(true);
>>>>>>> upstream/master
});
});
<<<<<<< HEAD
it('returns zero width when no text is passed and font is passed', () => {
expect(gl.text.getTextWidth('', '100px sans-serif')).toBe(0);
=======
describe('gl.text.pluralize', () => {
it('returns pluralized', () => {
expect(gl.text.pluralize('test', 2)).toBe('tests');
});
it('returns pluralized when count is 0', () => {
expect(gl.text.pluralize('test', 0)).toBe('tests');
});
it('does not return pluralized', () => {
expect(gl.text.pluralize('test', 1)).toBe('test');
>>>>>>> upstream/master
});
});
<<<<<<< HEAD
it('returns width when text is passed', () => {
expect(gl.text.getTextWidth('foo') > 0).toBe(true);
=======
describe('gl.text.highCountTrim', () => {
it('returns 99+ for count >= 100', () => {
expect(gl.text.highCountTrim(105)).toBe('99+');
expect(gl.text.highCountTrim(100)).toBe('99+');
});
it('returns exact number for count < 100', () => {
expect(gl.text.highCountTrim(45)).toBe(45);
>>>>>>> upstream/master
});
});
<<<<<<< HEAD
it('returns bigger width when font is larger', () => { it('returns bigger width when font is larger', () => {
const largeFont = gl.text.getTextWidth('foo', '100px sans-serif'); const largeFont = gl.text.getTextWidth('foo', '100px sans-serif');
const regular = gl.text.getTextWidth('foo', '10px sans-serif'); const regular = gl.text.getTextWidth('foo', '10px sans-serif');
...@@ -43,6 +89,65 @@ describe('text_utility', () => { ...@@ -43,6 +89,65 @@ describe('text_utility', () => {
it('returns exact number for count < 100', () => { it('returns exact number for count < 100', () => {
expect(gl.text.highCountTrim(45)).toBe(45); expect(gl.text.highCountTrim(45)).toBe(45);
=======
describe('gl.text.insertText', () => {
let textArea;
beforeAll(() => {
textArea = document.createElement('textarea');
document.querySelector('body').appendChild(textArea);
});
afterAll(() => {
textArea.parentNode.removeChild(textArea);
});
describe('without selection', () => {
it('inserts the tag on an empty line', () => {
const initialValue = '';
textArea.value = initialValue;
textArea.selectionStart = 0;
textArea.selectionEnd = 0;
gl.text.insertText(textArea, textArea.value, '*', null, '', false);
expect(textArea.value).toEqual(`${initialValue}* `);
});
it('inserts the tag on a new line if the current one is not empty', () => {
const initialValue = 'some text';
textArea.value = initialValue;
textArea.setSelectionRange(initialValue.length, initialValue.length);
gl.text.insertText(textArea, textArea.value, '*', null, '', false);
expect(textArea.value).toEqual(`${initialValue}\n* `);
});
it('inserts the tag on the same line if the current line only contains spaces', () => {
const initialValue = ' ';
textArea.value = initialValue;
textArea.setSelectionRange(initialValue.length, initialValue.length);
gl.text.insertText(textArea, textArea.value, '*', null, '', false);
expect(textArea.value).toEqual(`${initialValue}* `);
});
it('inserts the tag on the same line if the current line only contains tabs', () => {
const initialValue = '\t\t\t';
textArea.value = initialValue;
textArea.setSelectionRange(initialValue.length, initialValue.length);
gl.text.insertText(textArea, textArea.value, '*', null, '', false);
expect(textArea.value).toEqual(`${initialValue}* `);
});
>>>>>>> upstream/master
}); });
}); });
}); });
/* global MergedButtons */
import '~/merged_buttons';
describe('MergedButtons', () => {
const fixturesPath = 'merge_requests/merged_merge_request.html.raw';
preloadFixtures(fixturesPath);
beforeEach(() => {
loadFixtures(fixturesPath);
this.mergedButtons = new MergedButtons();
this.$removeBranchWidget = $('.remove_source_branch_widget:not(.failed)');
this.$removeBranchProgress = $('.remove_source_branch_in_progress');
this.$removeBranchFailed = $('.remove_source_branch_widget.failed');
this.$removeBranchButton = $('.remove_source_branch');
});
describe('removeSourceBranch', () => {
it('shows loader', () => {
$('.remove_source_branch').trigger('click');
expect(this.$removeBranchProgress).toBeVisible();
expect(this.$removeBranchWidget).not.toBeVisible();
});
});
describe('removeBranchSuccess', () => {
it('refreshes page when branch removed', () => {
spyOn(gl.utils, 'refreshCurrentPage').and.stub();
const response = { status: 200 };
this.$removeBranchButton.trigger('ajax:success', response, 'xhr');
expect(gl.utils.refreshCurrentPage).toHaveBeenCalled();
});
});
describe('removeBranchError', () => {
it('shows error message', () => {
const response = { status: 500 };
this.$removeBranchButton.trigger('ajax:error', response, 'xhr');
expect(this.$removeBranchFailed).toBeVisible();
expect(this.$removeBranchProgress).not.toBeVisible();
expect(this.$removeBranchWidget).not.toBeVisible();
});
});
});
...@@ -6,8 +6,8 @@ describe Banzai::Filter::IssuableStateFilter, lib: true do ...@@ -6,8 +6,8 @@ describe Banzai::Filter::IssuableStateFilter, lib: true do
let(:user) { create(:user) } let(:user) { create(:user) }
def create_link(data) def create_link(text, data)
link_to('text', '', class: 'gfm has-tooltip', data: data) link_to(text, '', class: 'gfm has-tooltip', data: data)
end end
it 'ignores non-GFM links' do it 'ignores non-GFM links' do
...@@ -19,16 +19,37 @@ describe Banzai::Filter::IssuableStateFilter, lib: true do ...@@ -19,16 +19,37 @@ describe Banzai::Filter::IssuableStateFilter, lib: true do
it 'ignores non-issuable links' do it 'ignores non-issuable links' do
project = create(:empty_project, :public) project = create(:empty_project, :public)
link = create_link(project: project, reference_type: 'issue') link = create_link('text', project: project, reference_type: 'issue')
doc = filter(link, current_user: user) doc = filter(link, current_user: user)
expect(doc.css('a').last.text).to eq('text') expect(doc.css('a').last.text).to eq('text')
end end
it 'ignores issuable links with empty content' do
issue = create(:issue, :closed)
link = create_link('', issue: issue.id, reference_type: 'issue')
doc = filter(link, current_user: user)
expect(doc.css('a').last.text).to eq('')
end
it 'adds text with standard formatting' do
issue = create(:issue, :closed)
link = create_link(
'something <strong>else</strong>'.html_safe,
issue: issue.id,
reference_type: 'issue'
)
doc = filter(link, current_user: user)
expect(doc.css('a').last.inner_html).
to eq('something <strong>else</strong> [closed]')
end
context 'for issue references' do context 'for issue references' do
it 'ignores open issue references' do it 'ignores open issue references' do
issue = create(:issue) issue = create(:issue)
link = create_link(issue: issue.id, reference_type: 'issue') link = create_link('text', issue: issue.id, reference_type: 'issue')
doc = filter(link, current_user: user) doc = filter(link, current_user: user)
expect(doc.css('a').last.text).to eq('text') expect(doc.css('a').last.text).to eq('text')
...@@ -36,7 +57,7 @@ describe Banzai::Filter::IssuableStateFilter, lib: true do ...@@ -36,7 +57,7 @@ describe Banzai::Filter::IssuableStateFilter, lib: true do
it 'ignores reopened issue references' do it 'ignores reopened issue references' do
reopened_issue = create(:issue, :reopened) reopened_issue = create(:issue, :reopened)
link = create_link(issue: reopened_issue.id, reference_type: 'issue') link = create_link('text', issue: reopened_issue.id, reference_type: 'issue')
doc = filter(link, current_user: user) doc = filter(link, current_user: user)
expect(doc.css('a').last.text).to eq('text') expect(doc.css('a').last.text).to eq('text')
...@@ -44,7 +65,7 @@ describe Banzai::Filter::IssuableStateFilter, lib: true do ...@@ -44,7 +65,7 @@ describe Banzai::Filter::IssuableStateFilter, lib: true do
it 'appends [closed] to closed issue references' do it 'appends [closed] to closed issue references' do
closed_issue = create(:issue, :closed) closed_issue = create(:issue, :closed)
link = create_link(issue: closed_issue.id, reference_type: 'issue') link = create_link('text', issue: closed_issue.id, reference_type: 'issue')
doc = filter(link, current_user: user) doc = filter(link, current_user: user)
expect(doc.css('a').last.text).to eq('text [closed]') expect(doc.css('a').last.text).to eq('text [closed]')
...@@ -54,7 +75,7 @@ describe Banzai::Filter::IssuableStateFilter, lib: true do ...@@ -54,7 +75,7 @@ describe Banzai::Filter::IssuableStateFilter, lib: true do
context 'for merge request references' do context 'for merge request references' do
it 'ignores open merge request references' do it 'ignores open merge request references' do
mr = create(:merge_request) mr = create(:merge_request)
link = create_link(merge_request: mr.id, reference_type: 'merge_request') link = create_link('text', merge_request: mr.id, reference_type: 'merge_request')
doc = filter(link, current_user: user) doc = filter(link, current_user: user)
expect(doc.css('a').last.text).to eq('text') expect(doc.css('a').last.text).to eq('text')
...@@ -62,7 +83,7 @@ describe Banzai::Filter::IssuableStateFilter, lib: true do ...@@ -62,7 +83,7 @@ describe Banzai::Filter::IssuableStateFilter, lib: true do
it 'ignores reopened merge request references' do it 'ignores reopened merge request references' do
mr = create(:merge_request, :reopened) mr = create(:merge_request, :reopened)
link = create_link(merge_request: mr.id, reference_type: 'merge_request') link = create_link('text', merge_request: mr.id, reference_type: 'merge_request')
doc = filter(link, current_user: user) doc = filter(link, current_user: user)
expect(doc.css('a').last.text).to eq('text') expect(doc.css('a').last.text).to eq('text')
...@@ -70,7 +91,7 @@ describe Banzai::Filter::IssuableStateFilter, lib: true do ...@@ -70,7 +91,7 @@ describe Banzai::Filter::IssuableStateFilter, lib: true do
it 'ignores locked merge request references' do it 'ignores locked merge request references' do
mr = create(:merge_request, :locked) mr = create(:merge_request, :locked)
link = create_link(merge_request: mr.id, reference_type: 'merge_request') link = create_link('text', merge_request: mr.id, reference_type: 'merge_request')
doc = filter(link, current_user: user) doc = filter(link, current_user: user)
expect(doc.css('a').last.text).to eq('text') expect(doc.css('a').last.text).to eq('text')
...@@ -78,7 +99,7 @@ describe Banzai::Filter::IssuableStateFilter, lib: true do ...@@ -78,7 +99,7 @@ describe Banzai::Filter::IssuableStateFilter, lib: true do
it 'appends [closed] to closed merge request references' do it 'appends [closed] to closed merge request references' do
mr = create(:merge_request, :closed) mr = create(:merge_request, :closed)
link = create_link(merge_request: mr.id, reference_type: 'merge_request') link = create_link('text', merge_request: mr.id, reference_type: 'merge_request')
doc = filter(link, current_user: user) doc = filter(link, current_user: user)
expect(doc.css('a').last.text).to eq('text [closed]') expect(doc.css('a').last.text).to eq('text [closed]')
...@@ -86,7 +107,7 @@ describe Banzai::Filter::IssuableStateFilter, lib: true do ...@@ -86,7 +107,7 @@ describe Banzai::Filter::IssuableStateFilter, lib: true do
it 'appends [merged] to merged merge request references' do it 'appends [merged] to merged merge request references' do
mr = create(:merge_request, :merged) mr = create(:merge_request, :merged)
link = create_link(merge_request: mr.id, reference_type: 'merge_request') link = create_link('text', merge_request: mr.id, reference_type: 'merge_request')
doc = filter(link, current_user: user) doc = filter(link, current_user: user)
expect(doc.css('a').last.text).to eq('text [merged]') expect(doc.css('a').last.text).to eq('text [merged]')
......
...@@ -33,10 +33,20 @@ describe ContainerRegistry::Path do ...@@ -33,10 +33,20 @@ describe ContainerRegistry::Path do
end end
describe '#to_s' do describe '#to_s' do
let(:path) { 'some/image' } context 'when path does not have uppercase characters' do
let(:path) { 'some/image' }
it 'return a string with a repository path' do it 'return a string with a repository path' do
expect(subject.to_s).to eq path expect(subject.to_s).to eq 'some/image'
end
end
context 'when path has uppercase characters' do
let(:path) { 'SoMe/ImAgE' }
it 'return a string with a repository path' do
expect(subject.to_s).to eq 'some/image'
end
end end
end end
...@@ -70,6 +80,12 @@ describe ContainerRegistry::Path do ...@@ -70,6 +80,12 @@ describe ContainerRegistry::Path do
it { is_expected.to be_valid } it { is_expected.to be_valid }
end end
context 'when path contains uppercase letters' do
let(:path) { 'Some/Registry' }
it { is_expected.to be_valid }
end
end end
describe '#has_repository?' do describe '#has_repository?' do
......
...@@ -17,12 +17,12 @@ describe Gitlab::Ci::Trace::Stream do ...@@ -17,12 +17,12 @@ describe Gitlab::Ci::Trace::Stream do
describe '#limit' do describe '#limit' do
let(:stream) do let(:stream) do
described_class.new do described_class.new do
StringIO.new("12345678") StringIO.new((1..8).to_a.join("\n"))
end end
end end
it 'if size is larger we start from beggining' do it 'if size is larger we start from beginning' do
stream.limit(10) stream.limit(20)
expect(stream.tell).to eq(0) expect(stream.tell).to eq(0)
end end
...@@ -30,7 +30,27 @@ describe Gitlab::Ci::Trace::Stream do ...@@ -30,7 +30,27 @@ describe Gitlab::Ci::Trace::Stream do
it 'if size is smaller we start from the end' do it 'if size is smaller we start from the end' do
stream.limit(2) stream.limit(2)
expect(stream.tell).to eq(6) expect(stream.raw).to eq("8")
end
context 'when the trace contains ANSI sequence and Unicode' do
let(:stream) do
described_class.new do
File.open(expand_fixture_path('trace/ansi-sequence-and-unicode'))
end
end
it 'forwards to the next linefeed, case 1' do
stream.limit(7)
expect(stream.raw).to eq('')
end
it 'forwards to the next linefeed, case 2' do
stream.limit(29)
expect(stream.raw).to eq("\e[01;32m許功蓋\e[0m\n")
end
end end
end end
......
...@@ -34,8 +34,18 @@ describe ContainerRepository do ...@@ -34,8 +34,18 @@ describe ContainerRepository do
end end
describe '#path' do describe '#path' do
it 'returns a full path to the repository' do context 'when project path does not contain uppercase letters' do
expect(repository.path).to eq('group/test/my_image') it 'returns a full path to the repository' do
expect(repository.path).to eq('group/test/my_image')
end
end
context 'when path contains uppercase letters' do
let(:project) { create(:project, path: 'MY_PROJECT', group: group) }
it 'returns a full path without capital letters' do
expect(repository.path).to eq('group/my_project/my_image')
end
end end
end end
......
...@@ -180,6 +180,20 @@ describe Projects::CreateService, '#execute', services: true do ...@@ -180,6 +180,20 @@ describe Projects::CreateService, '#execute', services: true do
end end
end end
context 'when a bad service template is created' do
before do
create(:service, type: 'DroneCiService', project: nil, template: true, active: true)
end
it 'reports an error in the imported project' do
opts[:import_url] = 'http://www.gitlab.com/gitlab-org/gitlab-ce'
project = create_project(user, opts)
expect(project.errors.full_messages_for(:base).first).to match /Unable to save project. Error: Unable to save DroneCiService/
expect(project.services.count).to eq 0
end
end
def create_project(user, opts) def create_project(user, opts)
Projects::CreateService.new(user, opts).execute Projects::CreateService.new(user, opts).execute
end end
......
require 'spec_helper'
describe Users::BuildService, services: true do
describe '#execute' do
let(:params) do
{ name: 'John Doe', username: 'jduser', email: 'jd@example.com', password: 'mydummypass' }
end
context 'with an admin user' do
let(:admin_user) { create(:admin) }
let(:service) { described_class.new(admin_user, params) }
it 'returns a valid user' do
expect(service.execute).to be_valid
end
end
context 'with non admin user' do
let(:user) { create(:user) }
let(:service) { described_class.new(user, params) }
it 'raises AccessDeniedError exception' do
expect { service.execute }.to raise_error Gitlab::Access::AccessDeniedError
end
end
context 'with nil user' do
let(:service) { described_class.new(nil, params) }
it 'returns a valid user' do
expect(service.execute).to be_valid
end
context 'when "send_user_confirmation_email" application setting is true' do
before do
stub_application_setting(send_user_confirmation_email: true, signup_enabled?: true)
end
it 'does not confirm the user' do
expect(service.execute).not_to be_confirmed
end
end
context 'when "send_user_confirmation_email" application setting is false' do
before do
stub_application_setting(send_user_confirmation_email: false, signup_enabled?: true)
end
it 'confirms the user' do
expect(service.execute).to be_confirmed
end
end
end
end
end
require 'spec_helper' require 'spec_helper'
describe Users::CreateService, services: true do describe Users::CreateService, services: true do
describe '#build' do
let(:params) do
{ name: 'John Doe', username: 'jduser', email: 'jd@example.com', password: 'mydummypass' }
end
context 'with an admin user' do
let(:admin_user) { create(:admin) }
let(:service) { described_class.new(admin_user, params) }
it 'returns a valid user' do
expect(service.build).to be_valid
end
end
context 'with non admin user' do
let(:user) { create(:user) }
let(:service) { described_class.new(user, params) }
it 'raises AccessDeniedError exception' do
expect { service.build }.to raise_error Gitlab::Access::AccessDeniedError
end
end
context 'with nil user' do
let(:service) { described_class.new(nil, params) }
it 'returns a valid user' do
expect(service.build).to be_valid
end
end
end
describe '#execute' do describe '#execute' do
let(:admin_user) { create(:admin) } let(:admin_user) { create(:admin) }
...@@ -185,40 +153,18 @@ describe Users::CreateService, services: true do ...@@ -185,40 +153,18 @@ describe Users::CreateService, services: true do
end end
let(:service) { described_class.new(nil, params) } let(:service) { described_class.new(nil, params) }
context 'when "send_user_confirmation_email" application setting is true' do it 'persists the given attributes' do
before do user = service.execute
current_application_settings = double(:current_application_settings, send_user_confirmation_email: true, signup_enabled?: true) user.reload
allow(service).to receive(:current_application_settings).and_return(current_application_settings)
end expect(user).to have_attributes(
name: params[:name],
it 'does not confirm the user' do username: params[:username],
expect(service.execute).not_to be_confirmed email: params[:email],
end password: params[:password],
end created_by_id: nil,
admin: false
context 'when "send_user_confirmation_email" application setting is false' do )
before do
current_application_settings = double(:current_application_settings, send_user_confirmation_email: false, signup_enabled?: true)
allow(service).to receive(:current_application_settings).and_return(current_application_settings)
end
it 'confirms the user' do
expect(service.execute).to be_confirmed
end
it 'persists the given attributes' do
user = service.execute
user.reload
expect(user).to have_attributes(
name: params[:name],
username: params[:username],
email: params[:email],
password: params[:password],
created_by_id: nil,
admin: false
)
end
end end
end end
end end
......
...@@ -9,8 +9,14 @@ require 'rspec/rails' ...@@ -9,8 +9,14 @@ require 'rspec/rails'
require 'shoulda/matchers' require 'shoulda/matchers'
require 'rspec/retry' require 'rspec/retry'
if (ENV['RSPEC_PROFILING_POSTGRES_URL'] || ENV['RSPEC_PROFILING']) && rspec_profiling_is_configured =
(!ENV.has_key?('CI') || ENV['CI_COMMIT_REF_NAME'] == 'master') ENV['RSPEC_PROFILING_POSTGRES_URL'] ||
ENV['RSPEC_PROFILING']
branch_can_be_profiled =
ENV['CI_COMMIT_REF_NAME'] == 'master' ||
ENV['CI_COMMIT_REF_NAME'] =~ /rspec-profile/
if rspec_profiling_is_configured && (!ENV.key?('CI') || branch_can_be_profiled)
require 'rspec_profiling/rspec' require 'rspec_profiling/rspec'
end end
......
module FixtureHelpers module FixtureHelpers
def fixture_file(filename) def fixture_file(filename)
return '' if filename.blank? return '' if filename.blank?
file_path = File.expand_path(Rails.root.join('spec/fixtures/', filename)) File.read(expand_fixture_path(filename))
File.read(file_path) end
def expand_fixture_path(filename)
File.expand_path(Rails.root.join('spec/fixtures/', filename))
end end
end end
......
...@@ -8,7 +8,7 @@ describe 'gitlab:gitaly namespace rake task' do ...@@ -8,7 +8,7 @@ describe 'gitlab:gitaly namespace rake task' do
describe 'install' do describe 'install' do
let(:repo) { 'https://gitlab.com/gitlab-org/gitaly.git' } let(:repo) { 'https://gitlab.com/gitlab-org/gitaly.git' }
let(:clone_path) { Rails.root.join('tmp/tests/gitaly').to_s } let(:clone_path) { Rails.root.join('tmp/tests/gitaly').to_s }
let(:tag) { "v#{File.read(Rails.root.join(Gitlab::GitalyClient::SERVER_VERSION_FILE)).chomp}" } let(:version) { File.read(Rails.root.join(Gitlab::GitalyClient::SERVER_VERSION_FILE)).chomp }
context 'no dir given' do context 'no dir given' do
it 'aborts and display a help message' do it 'aborts and display a help message' do
...@@ -21,7 +21,7 @@ describe 'gitlab:gitaly namespace rake task' do ...@@ -21,7 +21,7 @@ describe 'gitlab:gitaly namespace rake task' do
context 'when an underlying Git command fail' do context 'when an underlying Git command fail' do
it 'aborts and display a help message' do it 'aborts and display a help message' do
expect_any_instance_of(Object). expect_any_instance_of(Object).
to receive(:checkout_or_clone_tag).and_raise 'Git error' to receive(:checkout_or_clone_version).and_raise 'Git error'
expect { run_rake_task('gitlab:gitaly:install', clone_path) }.to raise_error 'Git error' expect { run_rake_task('gitlab:gitaly:install', clone_path) }.to raise_error 'Git error'
end end
...@@ -32,9 +32,9 @@ describe 'gitlab:gitaly namespace rake task' do ...@@ -32,9 +32,9 @@ describe 'gitlab:gitaly namespace rake task' do
expect(Dir).to receive(:chdir).with(clone_path) expect(Dir).to receive(:chdir).with(clone_path)
end end
it 'calls checkout_or_clone_tag with the right arguments' do it 'calls checkout_or_clone_version with the right arguments' do
expect_any_instance_of(Object). expect_any_instance_of(Object).
to receive(:checkout_or_clone_tag).with(tag: tag, repo: repo, target_dir: clone_path) to receive(:checkout_or_clone_version).with(version: version, repo: repo, target_dir: clone_path)
run_rake_task('gitlab:gitaly:install', clone_path) run_rake_task('gitlab:gitaly:install', clone_path)
end end
...@@ -48,7 +48,7 @@ describe 'gitlab:gitaly namespace rake task' do ...@@ -48,7 +48,7 @@ describe 'gitlab:gitaly namespace rake task' do
context 'gmake is available' do context 'gmake is available' do
before do before do
expect_any_instance_of(Object).to receive(:checkout_or_clone_tag) expect_any_instance_of(Object).to receive(:checkout_or_clone_version)
allow_any_instance_of(Object).to receive(:run_command!).with(['gmake']).and_return(true) allow_any_instance_of(Object).to receive(:run_command!).with(['gmake']).and_return(true)
end end
...@@ -62,7 +62,7 @@ describe 'gitlab:gitaly namespace rake task' do ...@@ -62,7 +62,7 @@ describe 'gitlab:gitaly namespace rake task' do
context 'gmake is not available' do context 'gmake is not available' do
before do before do
expect_any_instance_of(Object).to receive(:checkout_or_clone_tag) expect_any_instance_of(Object).to receive(:checkout_or_clone_version)
allow_any_instance_of(Object).to receive(:run_command!).with(['make']).and_return(true) allow_any_instance_of(Object).to receive(:run_command!).with(['make']).and_return(true)
end end
......
...@@ -10,19 +10,38 @@ describe Gitlab::TaskHelpers do ...@@ -10,19 +10,38 @@ describe Gitlab::TaskHelpers do
let(:repo) { 'https://gitlab.com/gitlab-org/gitlab-test.git' } let(:repo) { 'https://gitlab.com/gitlab-org/gitlab-test.git' }
let(:clone_path) { Rails.root.join('tmp/tests/task_helpers_tests').to_s } let(:clone_path) { Rails.root.join('tmp/tests/task_helpers_tests').to_s }
let(:version) { '1.1.0' }
let(:tag) { 'v1.1.0' } let(:tag) { 'v1.1.0' }
describe '#checkout_or_clone_tag' do describe '#checkout_or_clone_version' do
before do before do
allow(subject).to receive(:run_command!) allow(subject).to receive(:run_command!)
expect(subject).to receive(:reset_to_tag).with(tag, clone_path)
end end
context 'target_dir does not exist' do it 'checkout the version and reset to it' do
it 'clones the repo, retrieve the tag from origin, and checkout the tag' do expect(subject).to receive(:checkout_version).with(tag, clone_path)
expect(subject).to receive(:reset_to_version).with(tag, clone_path)
subject.checkout_or_clone_version(version: version, repo: repo, target_dir: clone_path)
end
context 'with a branch version' do
let(:version) { '=branch_name' }
let(:branch) { 'branch_name' }
it 'checkout the version and reset to it with a branch name' do
expect(subject).to receive(:checkout_version).with(branch, clone_path)
expect(subject).to receive(:reset_to_version).with(branch, clone_path)
subject.checkout_or_clone_version(version: version, repo: repo, target_dir: clone_path)
end
end
context "target_dir doesn't exist" do
it 'clones the repo' do
expect(subject).to receive(:clone_repo).with(repo, clone_path) expect(subject).to receive(:clone_repo).with(repo, clone_path)
subject.checkout_or_clone_tag(tag: tag, repo: repo, target_dir: clone_path) subject.checkout_or_clone_version(version: version, repo: repo, target_dir: clone_path)
end end
end end
...@@ -31,10 +50,10 @@ describe Gitlab::TaskHelpers do ...@@ -31,10 +50,10 @@ describe Gitlab::TaskHelpers do
expect(Dir).to receive(:exist?).and_return(true) expect(Dir).to receive(:exist?).and_return(true)
end end
it 'fetch and checkout the tag' do it "doesn't clone the repository" do
expect(subject).to receive(:checkout_tag).with(tag, clone_path) expect(subject).not_to receive(:clone_repo)
subject.checkout_or_clone_tag(tag: tag, repo: repo, target_dir: clone_path) subject.checkout_or_clone_version(version: version, repo: repo, target_dir: clone_path)
end end
end end
end end
...@@ -48,49 +67,23 @@ describe Gitlab::TaskHelpers do ...@@ -48,49 +67,23 @@ describe Gitlab::TaskHelpers do
end end
end end
describe '#checkout_tag' do describe '#checkout_version' do
it 'clones the repo in the target dir' do it 'clones the repo in the target dir' do
expect(subject). expect(subject).
to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} fetch --tags --quiet]) to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} fetch --quiet])
expect(subject). expect(subject).
to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} checkout --quiet #{tag}]) to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} checkout --quiet #{tag}])
subject.checkout_tag(tag, clone_path) subject.checkout_version(tag, clone_path)
end end
end end
describe '#reset_to_tag' do describe '#reset_to_version' do
let(:tag) { 'v1.1.0' } it 'resets --hard to the given version' do
before do
expect(subject). expect(subject).
to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} reset --hard #{tag}]) to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} reset --hard #{tag}])
end
context 'when the tag is not checked out locally' do subject.reset_to_version(tag, clone_path)
before do
expect(subject).
to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} describe -- #{tag}]).and_raise(Gitlab::TaskFailedError)
end
it 'fetch origin, ensure the tag exists, and resets --hard to the given tag' do
expect(subject).
to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} fetch origin])
expect(subject).
to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} describe -- origin/#{tag}]).and_return(tag)
subject.reset_to_tag(tag, clone_path)
end
end
context 'when the tag is checked out locally' do
before do
expect(subject).
to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} describe -- #{tag}]).and_return(tag)
end
it 'resets --hard to the given tag' do
subject.reset_to_tag(tag, clone_path)
end
end end
end end
end end
...@@ -8,7 +8,7 @@ describe 'gitlab:workhorse namespace rake task' do ...@@ -8,7 +8,7 @@ describe 'gitlab:workhorse namespace rake task' do
describe 'install' do describe 'install' do
let(:repo) { 'https://gitlab.com/gitlab-org/gitlab-workhorse.git' } let(:repo) { 'https://gitlab.com/gitlab-org/gitlab-workhorse.git' }
let(:clone_path) { Rails.root.join('tmp/tests/gitlab-workhorse').to_s } let(:clone_path) { Rails.root.join('tmp/tests/gitlab-workhorse').to_s }
let(:tag) { "v#{File.read(Rails.root.join(Gitlab::Workhorse::VERSION_FILE)).chomp}" } let(:version) { File.read(Rails.root.join(Gitlab::Workhorse::VERSION_FILE)).chomp }
context 'no dir given' do context 'no dir given' do
it 'aborts and display a help message' do it 'aborts and display a help message' do
...@@ -21,7 +21,7 @@ describe 'gitlab:workhorse namespace rake task' do ...@@ -21,7 +21,7 @@ describe 'gitlab:workhorse namespace rake task' do
context 'when an underlying Git command fail' do context 'when an underlying Git command fail' do
it 'aborts and display a help message' do it 'aborts and display a help message' do
expect_any_instance_of(Object). expect_any_instance_of(Object).
to receive(:checkout_or_clone_tag).and_raise 'Git error' to receive(:checkout_or_clone_version).and_raise 'Git error'
expect { run_rake_task('gitlab:workhorse:install', clone_path) }.to raise_error 'Git error' expect { run_rake_task('gitlab:workhorse:install', clone_path) }.to raise_error 'Git error'
end end
...@@ -32,9 +32,9 @@ describe 'gitlab:workhorse namespace rake task' do ...@@ -32,9 +32,9 @@ describe 'gitlab:workhorse namespace rake task' do
expect(Dir).to receive(:chdir).with(clone_path) expect(Dir).to receive(:chdir).with(clone_path)
end end
it 'calls checkout_or_clone_tag with the right arguments' do it 'calls checkout_or_clone_version with the right arguments' do
expect_any_instance_of(Object). expect_any_instance_of(Object).
to receive(:checkout_or_clone_tag).with(tag: tag, repo: repo, target_dir: clone_path) to receive(:checkout_or_clone_version).with(version: version, repo: repo, target_dir: clone_path)
run_rake_task('gitlab:workhorse:install', clone_path) run_rake_task('gitlab:workhorse:install', clone_path)
end end
...@@ -48,7 +48,7 @@ describe 'gitlab:workhorse namespace rake task' do ...@@ -48,7 +48,7 @@ describe 'gitlab:workhorse namespace rake task' do
context 'gmake is available' do context 'gmake is available' do
before do before do
expect_any_instance_of(Object).to receive(:checkout_or_clone_tag) expect_any_instance_of(Object).to receive(:checkout_or_clone_version)
allow_any_instance_of(Object).to receive(:run_command!).with(['gmake']).and_return(true) allow_any_instance_of(Object).to receive(:run_command!).with(['gmake']).and_return(true)
end end
...@@ -62,7 +62,7 @@ describe 'gitlab:workhorse namespace rake task' do ...@@ -62,7 +62,7 @@ describe 'gitlab:workhorse namespace rake task' do
context 'gmake is not available' do context 'gmake is not available' do
before do before do
expect_any_instance_of(Object).to receive(:checkout_or_clone_tag) expect_any_instance_of(Object).to receive(:checkout_or_clone_version)
allow_any_instance_of(Object).to receive(:run_command!).with(['make']).and_return(true) allow_any_instance_of(Object).to receive(:run_command!).with(['make']).and_return(true)
end end
......
...@@ -699,6 +699,48 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de ...@@ -699,6 +699,48 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
// //
// //
var renderer = new _marked2.default.Renderer();
/*
Regex to match KaTex blocks.
Supports the following:
\begin{equation}<math>\end{equation}
$$<math>$$
inline $<math>$
The matched text then goes through the KaTex renderer & then outputs the HTML
*/
var katexRegexString = '(\n ^\\\\begin{[a-zA-Z]+}\\s\n |\n ^\\$\\$\n |\n \\s\\$(?!\\$)\n)\n (.+?)\n(\n \\s\\\\end{[a-zA-Z]+}$\n |\n \\$\\$$\n |\n \\$\n)\n'.replace(/\s/g, '').trim();
renderer.paragraph = function (t) {
var text = t;
var inline = false;
if (typeof katex !== 'undefined') {
var katexString = text.replace(/\\/g, '\\');
var matches = new RegExp(katexRegexString, 'gi').exec(katexString);
if (matches && matches.length > 0) {
if (matches[1].trim() === '$' && matches[3].trim() === '$') {
inline = true;
text = katexString.replace(matches[0], '') + ' ' + katex.renderToString(matches[2]);
} else {
text = katex.renderToString(matches[2]);
}
}
}
return '<p class="' + (inline ? 'inline-katex' : '') + '">' + text + '</p>';
};
_marked2.default.setOptions({
sanitize: true,
renderer: renderer
});
exports.default = { exports.default = {
components: { components: {
prompt: _prompt2.default prompt: _prompt2.default
...@@ -711,20 +753,7 @@ exports.default = { ...@@ -711,20 +753,7 @@ exports.default = {
}, },
computed: { computed: {
markdown: function markdown() { markdown: function markdown() {
var regex = new RegExp('^\\$\\$(.*)\\$\\$$', 'g'); return (0, _marked2.default)(this.cell.source.join(''));
var source = this.cell.source.map(function (line) {
var matches = regex.exec(line.trim());
// Only render use the Katex library if it is actually loaded
if (matches && matches.length > 0 && typeof katex !== 'undefined') {
return katex.renderToString(matches[1]);
}
return line;
});
return (0, _marked2.default)(source.join(''));
} }
} }
}; };
...@@ -3047,7 +3076,7 @@ exports = module.exports = __webpack_require__(1)(undefined); ...@@ -3047,7 +3076,7 @@ exports = module.exports = __webpack_require__(1)(undefined);
// module // module
exports.push([module.i, ".markdown .katex{display:block;text-align:center}", ""]); exports.push([module.i, ".markdown .katex{display:block;text-align:center}.markdown .inline-katex .katex{display:inline;text-align:initial}", ""]);
// exports // exports
......
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