Merge branch 'master' into 28433-internationalise-cycle-analytics-page

This commit is contained in:
Phil Hughes 2017-04-14 15:42:10 +01:00
commit 302e855f52
91 changed files with 2567 additions and 2377 deletions

View File

@ -73,6 +73,9 @@ gem 'grape', '~> 0.19.0'
gem 'grape-entity', '~> 0.6.0'
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
gem 'kaminari', '~> 0.17.0'

View File

@ -336,7 +336,7 @@ GEM
grape-entity (0.6.0)
activesupport
multi_json (>= 1.3.2)
grpc (1.2.2)
grpc (1.1.2)
google-protobuf (~> 3.1)
googleauth (~> 0.5.1)
haml (4.0.7)
@ -352,6 +352,8 @@ GEM
tilt
hashdiff (0.3.2)
hashie (3.5.5)
hashie-forbidden_attributes (0.1.1)
hashie (>= 3.0)
health_check (2.6.0)
rails (>= 4.0)
hipchat (1.5.2)
@ -925,6 +927,7 @@ DEPENDENCIES
grape-entity (~> 0.6.0)
haml_lint (~> 0.21.0)
hamlit (~> 2.6.1)
hashie-forbidden_attributes
health_check (~> 2.6.0)
hipchat (~> 1.5.0)
html-pipeline (~> 1.11.0)

View File

@ -7,7 +7,6 @@ import boardBlankState from './board_blank_state';
require('./board_delete');
require('./board_list');
(() => {
const Store = gl.issueBoards.BoardsStore;
window.gl = window.gl || {};
@ -103,4 +102,3 @@ require('./board_list');
this.sortable = Sortable.create(this.$el.parentNode, this.sortableOptions);
},
});
})();

View File

@ -2,7 +2,6 @@
import Vue from 'vue';
(() => {
window.gl = window.gl || {};
window.gl.issueBoards = window.gl.issueBoards || {};
@ -20,4 +19,3 @@ import Vue from 'vue';
}
}
});
})();

View File

@ -8,7 +8,6 @@ import Vue from 'vue';
require('./sidebar/remove_issue');
(() => {
const Store = gl.issueBoards.BoardsStore;
window.gl = window.gl || {};
@ -70,4 +69,3 @@ require('./sidebar/remove_issue');
removeBtn: gl.issueBoards.RemoveIssueBtn,
},
});
})();

View File

@ -1,7 +1,6 @@
import Vue from 'vue';
import eventHub from '../eventhub';
(() => {
const Store = gl.issueBoards.BoardsStore;
window.gl = window.gl || {};
@ -138,4 +137,3 @@ import eventHub from '../eventhub';
</div>
`,
});
})();

View File

@ -1,6 +1,5 @@
import Vue from 'vue';
(() => {
const ModalStore = gl.issueBoards.ModalStore;
gl.issueBoards.ModalEmptyState = Vue.extend({
@ -68,4 +67,3 @@ import Vue from 'vue';
</section>
`,
});
})();

View File

@ -5,7 +5,6 @@ import Vue from 'vue';
require('./lists_dropdown');
(() => {
const ModalStore = gl.issueBoards.ModalStore;
gl.issueBoards.ModalFooter = Vue.extend({
@ -81,4 +80,3 @@ require('./lists_dropdown');
</footer>
`,
});
})();

View File

@ -3,7 +3,6 @@ import modalFilters from './filters';
require('./tabs');
(() => {
const ModalStore = gl.issueBoards.ModalStore;
gl.issueBoards.ModalHeader = Vue.extend({
@ -79,4 +78,3 @@ require('./tabs');
</div>
`,
});
})();

View File

@ -8,7 +8,6 @@ require('./list');
require('./footer');
require('./empty_state');
(() => {
const ModalStore = gl.issueBoards.ModalStore;
gl.issueBoards.IssuesModal = Vue.extend({
@ -164,4 +163,3 @@ require('./empty_state');
</div>
`,
});
})();

View File

@ -3,7 +3,6 @@
import Vue from 'vue';
(() => {
const ModalStore = gl.issueBoards.ModalStore;
gl.issueBoards.ModalList = Vue.extend({
@ -158,4 +157,3 @@ import Vue from 'vue';
</section>
`,
});
})();

View File

@ -1,6 +1,5 @@
import Vue from 'vue';
(() => {
const ModalStore = gl.issueBoards.ModalStore;
gl.issueBoards.ModalFooterListsDropdown = Vue.extend({
@ -54,4 +53,3 @@ import Vue from 'vue';
</div>
`,
});
})();

View File

@ -1,6 +1,5 @@
import Vue from 'vue';
(() => {
const ModalStore = gl.issueBoards.ModalStore;
gl.issueBoards.ModalTabs = Vue.extend({
@ -45,4 +44,3 @@ import Vue from 'vue';
</div>
`,
});
})();

View File

@ -1,6 +1,5 @@
/* eslint-disable comma-dangle, func-names, no-new, space-before-function-paren, one-var */
(() => {
window.gl = window.gl || {};
window.gl.issueBoards = window.gl.issueBoards || {};
@ -73,4 +72,3 @@
});
});
};
})();

View File

@ -3,7 +3,6 @@
import Vue from 'vue';
(() => {
const Store = gl.issueBoards.BoardsStore;
window.gl = window.gl || {};
@ -58,4 +57,3 @@ import Vue from 'vue';
</div>
`,
});
})();

View File

@ -1,4 +1,3 @@
(() => {
const ModalStore = gl.issueBoards.ModalStore;
gl.issueBoards.ModalMixins = {
@ -11,4 +10,3 @@
},
},
};
})();

View File

@ -1,7 +1,6 @@
/* eslint-disable no-unused-vars, no-mixed-operators, comma-dangle */
/* global DocumentTouch */
((w) => {
window.gl = window.gl || {};
window.gl.issueBoards = window.gl.issueBoards || {};
@ -36,4 +35,3 @@
Object.keys(obj).forEach((key) => { defaultSortOptions[key] = obj[key]; });
return defaultSortOptions;
};
})(window);

View File

@ -3,7 +3,6 @@
import Cookies from 'js-cookie';
(() => {
window.gl = window.gl || {};
window.gl.issueBoards = window.gl.issueBoards || {};
@ -124,4 +123,3 @@ import Cookies from 'js-cookie';
history.pushState(null, null, `?${this.filter.path}`);
}
};
})();

View File

@ -1,4 +1,3 @@
(() => {
window.gl = window.gl || {};
window.gl.issueBoards = window.gl.issueBoards || {};
@ -97,4 +96,3 @@
}
gl.issueBoards.ModalStore = new ModalStore();
})();

View File

@ -2,7 +2,7 @@
import Vue from 'vue';
((global) => {
const global = window.gl || (window.gl = {});
global.cycleAnalytics = global.cycleAnalytics || {};
global.cycleAnalytics.StageCodeComponent = Vue.extend({
@ -44,4 +44,3 @@ import Vue from 'vue';
</div>
`,
});
})(window.gl || (window.gl = {}));

View File

@ -2,7 +2,7 @@
import Vue from 'vue';
((global) => {
const global = window.gl || (window.gl = {});
global.cycleAnalytics = global.cycleAnalytics || {};
global.cycleAnalytics.StageIssueComponent = Vue.extend({
@ -46,4 +46,3 @@ import Vue from 'vue';
</div>
`,
});
})(window.gl || (window.gl = {}));

View File

@ -2,7 +2,7 @@
import Vue from 'vue';
import iconCommit from '../svg/icon_commit.svg';
((global) => {
const global = window.gl || (window.gl = {});
global.cycleAnalytics = global.cycleAnalytics || {};
global.cycleAnalytics.StagePlanComponent = Vue.extend({
@ -48,4 +48,3 @@ import iconCommit from '../svg/icon_commit.svg';
</div>
`,
});
})(window.gl || (window.gl = {}));

View File

@ -2,7 +2,7 @@
import Vue from 'vue';
((global) => {
const global = window.gl || (window.gl = {});
global.cycleAnalytics = global.cycleAnalytics || {};
global.cycleAnalytics.StageProductionComponent = Vue.extend({
@ -46,4 +46,3 @@ import Vue from 'vue';
</div>
`,
});
})(window.gl || (window.gl = {}));

View File

@ -2,7 +2,7 @@
import Vue from 'vue';
((global) => {
const global = window.gl || (window.gl = {});
global.cycleAnalytics = global.cycleAnalytics || {};
global.cycleAnalytics.StageReviewComponent = Vue.extend({
@ -56,4 +56,3 @@ import Vue from 'vue';
</div>
`,
});
})(window.gl || (window.gl = {}));

View File

@ -2,7 +2,7 @@
import Vue from 'vue';
import iconBranch from '../svg/icon_branch.svg';
((global) => {
const global = window.gl || (window.gl = {});
global.cycleAnalytics = global.cycleAnalytics || {};
global.cycleAnalytics.StageStagingComponent = Vue.extend({
@ -46,4 +46,3 @@ import iconBranch from '../svg/icon_branch.svg';
</div>
`,
});
})(window.gl || (window.gl = {}));

View File

@ -3,7 +3,7 @@ import Vue from 'vue';
import iconBuildStatus from '../svg/icon_build_status.svg';
import iconBranch from '../svg/icon_branch.svg';
((global) => {
const global = window.gl || (window.gl = {});
global.cycleAnalytics = global.cycleAnalytics || {};
global.cycleAnalytics.StageTestComponent = Vue.extend({
@ -47,4 +47,3 @@ import iconBranch from '../svg/icon_branch.svg';
</div>
`,
});
})(window.gl || (window.gl = {}));

View File

@ -2,7 +2,7 @@
import Vue from 'vue';
((global) => {
const global = window.gl || (window.gl = {});
global.cycleAnalytics = global.cycleAnalytics || {};
global.cycleAnalytics.TotalTimeComponent = Vue.extend({
@ -23,4 +23,3 @@ import Vue from 'vue';
</span>
`,
});
})(window.gl || (window.gl = {}));

View File

@ -1,5 +1,6 @@
/* eslint-disable no-param-reassign */
((global) => {
const global = window.gl || (window.gl = {});
global.cycleAnalytics = global.cycleAnalytics || {};
class CycleAnalyticsService {
@ -38,4 +39,3 @@
}
global.cycleAnalytics.CycleAnalyticsService = CycleAnalyticsService;
})(window.gl || (window.gl = {}));

View File

@ -3,7 +3,7 @@
require('../lib/utils/text_utility');
const DEFAULT_EVENT_OBJECTS = require('./default_event_objects');
((global) => {
const global = window.gl || (window.gl = {});
global.cycleAnalytics = global.cycleAnalytics || {};
const EMPTY_STAGE_TEXTS = {
@ -101,4 +101,3 @@ const DEFAULT_EVENT_OBJECTS = require('./default_event_objects');
return this.state.stages.find(stage => stage.active);
},
};
})(window.gl || (window.gl = {}));

View File

@ -1,10 +1,8 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, quotes, one-var, one-var-declaration-per-line, operator-assignment, no-else-return, prefer-template, prefer-arrow-callback, no-empty, max-len, consistent-return, no-unused-vars, no-return-assign, max-len */
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, quotes, one-var, one-var-declaration-per-line, operator-assignment, no-else-return, prefer-template, prefer-arrow-callback, no-empty, max-len, consistent-return, no-unused-vars, no-return-assign, max-len, vars-on-top */
require('vendor/latinise');
(function() {
(function(w) {
var base;
var w = window;
if (w.gl == null) {
w.gl = {};
}
@ -188,5 +186,3 @@ require('vendor/latinise');
gl.text.slugify = function(str) {
return str.trim().toLowerCase().latinise();
};
})(window);
}).call(window);

View File

@ -1,7 +1,6 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, one-var, one-var-declaration-per-line, no-void, guard-for-in, no-restricted-syntax, prefer-template, quotes, max-len */
(function() {
(function(w) {
var base;
var w = window;
if (w.gl == null) {
w.gl = {};
}
@ -89,5 +88,3 @@
w.gl.utils.visitUrl = (url) => {
document.location.href = url;
};
})(window);
}).call(window);

View File

@ -1,11 +1,13 @@
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, max-len */
(function() {
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
import '~/lib/utils/url_utility';
(function() {
this.MergedButtons = (function() {
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.$removeBranchProgress = $('.remove_source_branch_in_progress');
this.$removeBranchFailed = $('.remove_source_branch_widget.failed');
@ -22,7 +24,7 @@
MergedButtons.prototype.initEventListeners = function() {
$(document).on('click', '.remove_source_branch', this.removeSourceBranch);
$(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() {
@ -31,7 +33,7 @@
};
MergedButtons.prototype.removeBranchSuccess = function() {
return location.reload();
gl.utils.refreshCurrentPage();
};
MergedButtons.prototype.removeBranchError = function() {

View File

@ -246,17 +246,17 @@
}
}
.filtered-search-history-dropdown-toggle-button {
.filtered-search-history-dropdown-wrapper {
position: static;
display: flex;
align-items: center;
width: auto;
height: 100%;
padding-top: 0;
padding-left: 0.75em;
padding-bottom: 0;
padding-right: 0.5em;
flex-direction: column;
}
.filtered-search-history-dropdown-toggle-button {
flex: 1;
width: auto;
padding-right: 10px;
background-color: transparent;
border-radius: 0;
border-top: 0;
border-left: 0;
@ -264,6 +264,7 @@
border-right: 1px solid $border-color;
color: $gl-text-color-secondary;
line-height: 1;
transition: color 0.1s linear;
@ -275,24 +276,21 @@
}
.dropdown-toggle-text {
display: inline-block;
color: inherit;
.fa {
vertical-align: middle;
color: inherit;
}
}
.fa {
position: initial;
position: static;
}
}
.filtered-search-history-dropdown-wrapper {
position: initial;
flex-shrink: 0;
}
.filtered-search-history-dropdown {
width: 40%;

View File

@ -158,6 +158,7 @@
li.task-list-item {
list-style-type: none;
position: relative;
min-height: 22px;
padding-left: 28px;
margin-left: 0 !important;

View File

@ -26,6 +26,7 @@ $gray-dark: darken($gray-light, $darken-dark-factor);
$gray-darker: #eee;
$gray-darkest: #c4c4c4;
$green-25: #f6fcf8;
$green-50: #e4f5eb;
$green-100: #bae6cc;
$green-200: #8dd5aa;
@ -37,6 +38,7 @@ $green-700: #12753a;
$green-800: #0e5a2d;
$green-900: #0a4020;
$blue-25: #f6fafd;
$blue-50: #e4eff9;
$blue-100: #bcd7f1;
$blue-200: #8fbce8;
@ -48,6 +50,7 @@ $blue-700: #17599c;
$blue-800: #134a81;
$blue-900: #0f3b66;
$orange-25: #fffcf8;
$orange-50: #fff2e1;
$orange-100: #fedfb3;
$orange-200: #feca81;
@ -59,6 +62,7 @@ $orange-700: #c26700;
$orange-800: #a35100;
$orange-900: #853b00;
$red-25: #fef7f6;
$red-50: #fbe7e4;
$red-100: #f4c4bc;
$red-200: #ed9d90;
@ -147,7 +151,7 @@ $gl-sidebar-padding: 22px;
/*
* Misc
*/
$row-hover: lighten($blue-50, 2%);
$row-hover: $blue-25;
$row-hover-border: $blue-100;
$progress-color: #c0392b;
$header-height: 50px;
@ -223,18 +227,18 @@ $gl-btn-active-gradient: inset 0 2px 3px $gl-btn-active-background;
/*
* Commit Diff Colors
*/
$added: $green-300;
$deleted: $red-300;
$line-added: $green-50;
$line-added-dark: $green-100;
$line-removed: $red-50;
$line-removed-dark: $red-100;
$line-number-old: lighten($red-100, 5%);
$line-number-new: lighten($green-100, 5%);
$line-number-select: lighten($orange-100, 5%);
$line-target-blue: $blue-50;
$line-select-yellow: $orange-50;
$line-select-yellow-dark: $orange-100;
$added: #63c363;
$deleted: #f77;
$line-added: #ecfdf0;
$line-added-dark: #c7f0d2;
$line-removed: #fbe9eb;
$line-removed-dark: #fac5cd;
$line-number-old: #f9d7dc;
$line-number-new: #ddfbe6;
$line-number-select: #fbf2da;
$line-target-blue: #f6faff;
$line-select-yellow: #fcf8e7;
$line-select-yellow-dark: #f0e2bd;
$dark-diff-match-bg: rgba(255, 255, 255, 0.3);
$dark-diff-match-color: rgba(255, 255, 255, 0.1);
$file-mode-changed: #777;

View File

@ -627,7 +627,6 @@ ul.notes {
}
&:not(.is-disabled):hover,
&:not(.is-disabled):focus,
&.is-active {
color: $gl-text-green;
@ -641,6 +640,11 @@ ul.notes {
height: 15px;
width: 15px;
}
.loading {
margin: 0;
height: auto;
}
}
.discussion-next-btn {

View File

@ -60,7 +60,7 @@ class RegistrationsController < Devise::RegistrationsController
end
def resource
@resource ||= Users::CreateService.new(current_user, sign_up_params).build
@resource ||= Users::BuildService.new(current_user, sign_up_params).execute
end
def devise_mapping

View File

@ -20,7 +20,8 @@ class ContainerRepository < ActiveRecord::Base
end
def path
@path ||= [project.full_path, name].select(&:present?).join('/')
@path ||= [project.full_path, name]
.select(&:present?).join('/').downcase
end
def location

View File

@ -58,6 +58,9 @@ module Projects
fail(error: @project.errors.full_messages.join(', '))
end
@project
rescue ActiveRecord::RecordInvalid => e
message = "Unable to save #{e.record.type}: #{e.record.errors.full_messages.join(", ")} "
fail(error: message)
rescue => e
fail(error: e.message)
end

View File

@ -0,0 +1,100 @@
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

View File

@ -6,34 +6,10 @@ module Users
@params = params.dup
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
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
log_info("User \"#{user.name}\" (#{user.email}) was created")
@ -43,70 +19,5 @@ module Users
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,
: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

View File

@ -13,7 +13,7 @@
- @services.sort_by(&:title).each do |service|
%tr
%td
= icon("copy", class: 'clgray')
= boolean_to_icon service.activated?
%td
= link_to edit_admin_application_settings_service_path(service.id) do
%strong= service.title

View File

@ -23,7 +23,7 @@
Registry
- if project_nav_tab? :issues
= nav_link(controller: [:issues, :labels, :milestones, :boards]) do
= nav_link(controller: @project.default_issues_tracker? ? [:issues, :labels, :milestones, :boards] : :issues) do
= link_to namespace_project_issues_path(@project.namespace, @project), title: 'Issues', class: 'shortcuts-issues' do
%span
Issues
@ -31,7 +31,7 @@
%span.badge.count.issue_counter= number_with_delimiter(IssuesFinder.new(current_user, project_id: @project.id).execute.opened.count)
- if project_nav_tab? :merge_requests
= nav_link(controller: :merge_requests) do
= nav_link(controller: @project.default_issues_tracker? ? :merge_requests : [:merge_requests, :labels, :milestones]) do
= link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do
%span
Merge Requests

View File

@ -1,6 +1,6 @@
- @no_container = true
- page_title "Edit", @label.name, "Labels"
= render "projects/issues/head"
= render "shared/mr_head"
%div{ class: container_class }
%h3.page-title

View File

@ -1,7 +1,7 @@
- @no_container = true
- page_title "Labels"
- hide_class = ''
= render "projects/issues/head"
= render "shared/mr_head"
- if @labels.exists? || @prioritized_labels.exists?
%div{ class: container_class }

View File

@ -1,6 +1,6 @@
- @no_container = true
- page_title "New Label"
= render "projects/issues/head"
= render "shared/mr_head"
%div{ class: container_class }
%h3.page-title

View File

@ -0,0 +1,21 @@
= content_for :sub_nav do
.scrolling-tabs-container.sub-nav-scroll
= render 'shared/nav_scroll'
.nav-links.sub-nav.scrolling-tabs
%ul{ class: (container_class) }
= nav_link(controller: :merge_requests) do
= link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests' do
%span
List
- if project_nav_tab? :labels
= nav_link(controller: :labels) do
= link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels' do
%span
Labels
- if project_nav_tab? :milestones
= nav_link(controller: :milestones) do
= link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones' do
%span
Milestones

View File

@ -2,6 +2,9 @@
- @bulk_edit = can?(current_user, :admin_merge_request, @project)
- page_title "Merge Requests"
- unless @project.default_issues_tracker?
= content_for :sub_nav do
= render "projects/merge_requests/head"
= render 'projects/last_push'
- content_for :page_specific_javascripts do

View File

@ -1,6 +1,6 @@
- @no_container = true
- page_title "Edit", @milestone.title, "Milestones"
= render "projects/issues/head"
= render "shared/mr_head"
%div{ class: container_class }

View File

@ -1,6 +1,6 @@
- @no_container = true
- page_title 'Milestones'
= render 'projects/issues/head'
= render "shared/mr_head"
%div{ class: container_class }
.top-area

View File

@ -1,6 +1,6 @@
- @no_container = true
- page_title "New Milestone"
= render "projects/issues/head"
= render "shared/mr_head"
%div{ class: container_class }
%h3.page-title

View File

@ -1,7 +1,7 @@
- @no_container = true
- page_title @milestone.title, "Milestones"
- page_description @milestone.description
= render "projects/issues/head"
= render "shared/mr_head"
%div{ class: container_class }
.detail-page-header.milestone-page-header

View File

@ -52,11 +52,10 @@
":aria-label" => "buttonText",
"@click" => "resolve",
":title" => "buttonText",
"v-show" => "!loading",
":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 note.emoji_awardable?

View File

@ -12,7 +12,7 @@
= render "projects/last_push"
= 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 }
%ul.nav
%li

View File

@ -2,7 +2,7 @@
= f.label :import_url, class: 'control-label' do
%span Git repository URL
.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
%ul

View File

@ -0,0 +1,4 @@
- if @project.default_issues_tracker?
= render "projects/issues/head"
- else
= render "projects/merge_requests/head"

View File

@ -16,6 +16,8 @@
Also, issues are searchable and filterable.
- if project_select_button
= 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
.text-center
%h4 There are no issues to show.
= link_to 'New issue', button_path, class: 'btn btn-new', title: 'New issue', id: 'new_issue_link'

View File

@ -0,0 +1,4 @@
---
title: Implement Users::BuildService
merge_request: 30349
author: George Andrinopoulos

View File

@ -0,0 +1,4 @@
---
title: Show sub-nav under Merge Requests when issue tracker is non-default.
merge_request: 10658
author:

View File

@ -0,0 +1,4 @@
---
title: Fixed alignment of empty task list items
merge_request:
author:

View File

@ -0,0 +1,4 @@
---
title: Fix invalid encoding when showing some traces
merge_request: 10681
author:

View File

@ -0,0 +1,4 @@
---
title: Centered issues empty state
merge_request:
author:

View File

@ -0,0 +1,4 @@
---
title: Add lighter colors and fix existing light colors
merge_request: 10690
author:

View File

@ -0,0 +1,4 @@
---
title: Add hashie-forbidden_attributes gem
merge_request: 10579
author: Andy Brown

View File

@ -50,20 +50,17 @@ update them are in [a separate document][omnidocker].
## 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
there are the following requirements:
1. You can only upgrade 1 release at a time. For example, if 9.1.15 is the last
release of 9.1 then you can safely upgrade from that version to 9.2.0.
1. You can only upgrade 1 minor release at a time. So from 9.1 to 9.2, not to 9.3.
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.
2. You have to use [post-deployment
migrations](../development/post_deployment_migrations.md).
3. You are using PostgreSQL. If you are using MySQL you will still need downtime
when upgrading.
This applies to major, minor, and patch releases unless stated otherwise in a
release post.
3. You are using PostgreSQL. If you are using MySQL please look at the release post to see if downtime is required.
## Upgrading between editions

View File

@ -13,8 +13,8 @@ module Banzai
issuables = extractor.extract([doc])
issuables.each do |node, issuable|
if VISIBLE_STATES.include?(issuable.state)
node.children.last.content += " [#{issuable.state}]"
if VISIBLE_STATES.include?(issuable.state) && node.children.present?
node.add_child(Nokogiri::XML::Text.new(" [#{issuable.state}]", doc))
end
end

View File

@ -15,7 +15,7 @@ module ContainerRegistry
LEVELS_SUPPORTED = 3
def initialize(path)
@path = path
@path = path.to_s.downcase
end
def valid?
@ -25,7 +25,7 @@ module ContainerRegistry
end
def components
@components ||= @path.to_s.split('/')
@components ||= @path.split('/')
end
def nodes

View File

@ -25,11 +25,10 @@ module Gitlab
end
def limit(last_bytes = LIMIT_SIZE)
stream_size = size
if stream_size < last_bytes
last_bytes = stream_size
end
if last_bytes < size
stream.seek(-last_bytes, IO::SEEK_END)
stream.readline
end
end
def append(data, offset)

View File

@ -148,7 +148,7 @@ module Gitlab
def build_new_user
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
def user_attributes

View File

@ -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]")
end
tag = "v#{Gitlab::GitalyClient.expected_server_version}"
version = Gitlab::GitalyClient.expected_server_version
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])
command = status.zero? ? 'gmake' : 'make'

View File

@ -1,19 +1,18 @@
namespace :gitlab do
namespace :shell do
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
default_version = Gitlab::Shell.version_required
default_version_tag = "v#{default_version}"
args.with_defaults(tag: default_version_tag, repo: 'https://gitlab.com/gitlab-org/gitlab-shell.git')
args.with_defaults(repo: 'https://gitlab.com/gitlab-org/gitlab-shell.git')
gitlab_url = Gitlab.config.gitlab.url
# gitlab-shell requires a / at the end of the url
gitlab_url += '/' unless gitlab_url.end_with?('/')
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
Dir.chdir(target_dir) do

View File

@ -147,41 +147,30 @@ module Gitlab
Rails.env.test? ? Rails.root.join('tmp/tests') : Gitlab.config.gitlab.user_home
end
def checkout_or_clone_tag(tag:, repo:, target_dir:)
if Dir.exist?(target_dir)
checkout_tag(tag, target_dir)
def checkout_or_clone_version(version:, repo:, target_dir:)
version =
if version.starts_with?("=")
version.sub(/\A=/, '') # tag or branch
else
clone_repo(repo, target_dir)
"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
def clone_repo(repo, target_dir)
run_command!(%W[#{Gitlab.config.git.bin_path} clone -- #{repo} #{target_dir}])
end
def checkout_tag(tag, 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} checkout --quiet #{tag}])
def checkout_version(version, target_dir)
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 #{version}])
end
def reset_to_tag(tag_wanted, target_dir)
tag =
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
def reset_to_version(version, target_dir)
run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} reset --hard #{version}])
end
end
end

View File

@ -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]")
end
tag = "v#{Gitlab::Workhorse.version}"
version = Gitlab::Workhorse.version
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])
command = status.zero? ? 'gmake' : 'make'

View File

@ -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
page.first '.diff-content .note' do
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}")
end

View File

@ -0,0 +1,5 @@
.
..
😺
ヾ(´༎ຶД༎ຶ`)ノ
許功蓋

View File

@ -7,6 +7,7 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
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(:merged_merge_request) { create(:merge_request, :merged, source_project: project, target_project: project) }
let(:pipeline) do
create(
:ci_pipeline,
@ -32,6 +33,12 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont
render_merge_request(example.description, merge_request)
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
def render_merge_request(fixture_file_name, merge_request)

View File

@ -1,6 +1,5 @@
require('~/lib/utils/text_utility');
(() => {
describe('text_utility', () => {
describe('gl.text.getTextWidth', () => {
it('returns zero width when no text is passed', () => {
@ -107,4 +106,3 @@ require('~/lib/utils/text_utility');
});
});
});
})();

View File

@ -0,0 +1,44 @@
/* 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();
});
});
});

View File

@ -6,8 +6,8 @@ describe Banzai::Filter::IssuableStateFilter, lib: true do
let(:user) { create(:user) }
def create_link(data)
link_to('text', '', class: 'gfm has-tooltip', data: data)
def create_link(text, data)
link_to(text, '', class: 'gfm has-tooltip', data: data)
end
it 'ignores non-GFM links' do
@ -19,16 +19,37 @@ describe Banzai::Filter::IssuableStateFilter, lib: true do
it 'ignores non-issuable links' do
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)
expect(doc.css('a').last.text).to eq('text')
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
it 'ignores open issue references' do
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)
expect(doc.css('a').last.text).to eq('text')
@ -36,7 +57,7 @@ describe Banzai::Filter::IssuableStateFilter, lib: true do
it 'ignores reopened issue references' do
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)
expect(doc.css('a').last.text).to eq('text')
@ -44,7 +65,7 @@ describe Banzai::Filter::IssuableStateFilter, lib: true do
it 'appends [closed] to closed issue references' do
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)
expect(doc.css('a').last.text).to eq('text [closed]')
@ -54,7 +75,7 @@ describe Banzai::Filter::IssuableStateFilter, lib: true do
context 'for merge request references' do
it 'ignores open merge request references' do
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)
expect(doc.css('a').last.text).to eq('text')
@ -62,7 +83,7 @@ describe Banzai::Filter::IssuableStateFilter, lib: true do
it 'ignores reopened merge request references' do
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)
expect(doc.css('a').last.text).to eq('text')
@ -70,7 +91,7 @@ describe Banzai::Filter::IssuableStateFilter, lib: true do
it 'ignores locked merge request references' do
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)
expect(doc.css('a').last.text).to eq('text')
@ -78,7 +99,7 @@ describe Banzai::Filter::IssuableStateFilter, lib: true do
it 'appends [closed] to closed merge request references' do
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)
expect(doc.css('a').last.text).to eq('text [closed]')
@ -86,7 +107,7 @@ describe Banzai::Filter::IssuableStateFilter, lib: true do
it 'appends [merged] to merged merge request references' do
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)
expect(doc.css('a').last.text).to eq('text [merged]')

View File

@ -33,10 +33,20 @@ describe ContainerRegistry::Path do
end
describe '#to_s' do
context 'when path does not have uppercase characters' do
let(:path) { 'some/image' }
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
@ -70,6 +80,12 @@ describe ContainerRegistry::Path do
it { is_expected.to be_valid }
end
context 'when path contains uppercase letters' do
let(:path) { 'Some/Registry' }
it { is_expected.to be_valid }
end
end
describe '#has_repository?' do

View File

@ -17,12 +17,12 @@ describe Gitlab::Ci::Trace::Stream do
describe '#limit' do
let(:stream) do
described_class.new do
StringIO.new("12345678")
StringIO.new((1..8).to_a.join("\n"))
end
end
it 'if size is larger we start from beggining' do
stream.limit(10)
it 'if size is larger we start from beginning' do
stream.limit(20)
expect(stream.tell).to eq(0)
end
@ -30,7 +30,27 @@ describe Gitlab::Ci::Trace::Stream do
it 'if size is smaller we start from the end' do
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

View File

@ -34,11 +34,21 @@ describe ContainerRepository do
end
describe '#path' do
context 'when project path does not contain uppercase letters' do
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
describe '#manifest' do
it 'returns non-empty manifest' do
expect(repository.manifest).not_to be_nil

View File

@ -144,6 +144,20 @@ describe Projects::CreateService, '#execute', services: true do
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)
Projects::CreateService.new(user, opts).execute
end

View File

@ -0,0 +1,55 @@
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

View File

@ -1,38 +1,6 @@
require 'spec_helper'
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
let(:admin_user) { create(:admin) }
@ -185,27 +153,6 @@ describe Users::CreateService, services: true do
end
let(:service) { described_class.new(nil, params) }
context 'when "send_user_confirmation_email" application setting is true' do
before do
current_application_settings = double(:current_application_settings, send_user_confirmation_email: true, signup_enabled?: true)
allow(service).to receive(:current_application_settings).and_return(current_application_settings)
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
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
@ -222,4 +169,3 @@ describe Users::CreateService, services: true do
end
end
end
end

View File

@ -9,8 +9,14 @@ require 'rspec/rails'
require 'shoulda/matchers'
require 'rspec/retry'
if (ENV['RSPEC_PROFILING_POSTGRES_URL'] || ENV['RSPEC_PROFILING']) &&
(!ENV.has_key?('CI') || ENV['CI_COMMIT_REF_NAME'] == 'master')
rspec_profiling_is_configured =
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'
end

View File

@ -1,8 +1,11 @@
module FixtureHelpers
def fixture_file(filename)
return '' if filename.blank?
file_path = File.expand_path(Rails.root.join('spec/fixtures/', filename))
File.read(file_path)
File.read(expand_fixture_path(filename))
end
def expand_fixture_path(filename)
File.expand_path(Rails.root.join('spec/fixtures/', filename))
end
end

View File

@ -8,7 +8,7 @@ describe 'gitlab:gitaly namespace rake task' do
describe 'install' do
let(:repo) { 'https://gitlab.com/gitlab-org/gitaly.git' }
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
it 'aborts and display a help message' do
@ -21,7 +21,7 @@ describe 'gitlab:gitaly namespace rake task' do
context 'when an underlying Git command fail' do
it 'aborts and display a help message' do
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'
end
@ -32,9 +32,9 @@ describe 'gitlab:gitaly namespace rake task' do
expect(Dir).to receive(:chdir).with(clone_path)
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).
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)
end
@ -48,7 +48,7 @@ describe 'gitlab:gitaly namespace rake task' do
context 'gmake is available' 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)
end
@ -62,7 +62,7 @@ describe 'gitlab:gitaly namespace rake task' do
context 'gmake is not available' 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)
end

View File

@ -10,19 +10,38 @@ describe Gitlab::TaskHelpers do
let(:repo) { 'https://gitlab.com/gitlab-org/gitlab-test.git' }
let(:clone_path) { Rails.root.join('tmp/tests/task_helpers_tests').to_s }
let(:version) { '1.1.0' }
let(:tag) { 'v1.1.0' }
describe '#checkout_or_clone_tag' do
describe '#checkout_or_clone_version' do
before do
allow(subject).to receive(:run_command!)
expect(subject).to receive(:reset_to_tag).with(tag, clone_path)
end
context 'target_dir does not exist' do
it 'clones the repo, retrieve the tag from origin, and checkout the tag' do
it 'checkout the version and reset to it' 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)
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
@ -31,10 +50,10 @@ describe Gitlab::TaskHelpers do
expect(Dir).to receive(:exist?).and_return(true)
end
it 'fetch and checkout the tag' do
expect(subject).to receive(:checkout_tag).with(tag, clone_path)
it "doesn't clone the repository" do
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
@ -48,49 +67,23 @@ describe Gitlab::TaskHelpers do
end
end
describe '#checkout_tag' do
describe '#checkout_version' do
it 'clones the repo in the target dir' do
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).
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
describe '#reset_to_tag' do
let(:tag) { 'v1.1.0' }
before do
describe '#reset_to_version' do
it 'resets --hard to the given version' do
expect(subject).
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
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
subject.reset_to_version(tag, clone_path)
end
end
end

View File

@ -8,7 +8,7 @@ describe 'gitlab:workhorse namespace rake task' do
describe 'install' do
let(:repo) { 'https://gitlab.com/gitlab-org/gitlab-workhorse.git' }
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
it 'aborts and display a help message' do
@ -21,7 +21,7 @@ describe 'gitlab:workhorse namespace rake task' do
context 'when an underlying Git command fail' do
it 'aborts and display a help message' do
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'
end
@ -32,9 +32,9 @@ describe 'gitlab:workhorse namespace rake task' do
expect(Dir).to receive(:chdir).with(clone_path)
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).
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)
end
@ -48,7 +48,7 @@ describe 'gitlab:workhorse namespace rake task' do
context 'gmake is available' 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)
end
@ -62,7 +62,7 @@ describe 'gitlab:workhorse namespace rake task' do
context 'gmake is not available' 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)
end

View File

@ -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 = {
components: {
prompt: _prompt2.default
@ -711,20 +753,7 @@ exports.default = {
},
computed: {
markdown: function markdown() {
var regex = new RegExp('^\\$\\$(.*)\\$\\$$', 'g');
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(''));
return (0, _marked2.default)(this.cell.source.join(''));
}
}
};
@ -3047,7 +3076,7 @@ exports = module.exports = __webpack_require__(1)(undefined);
// 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