Commit 9e7a691b authored by James Lopez's avatar James Lopez

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ee into dev-master

# Conflicts:
#	CHANGELOG.md
parents e832c9c7 25bdf50b
......@@ -426,7 +426,7 @@ merge request:
1. [Ruby](https://github.com/bbatsov/ruby-style-guide).
Important sections include [Source Code Layout][rss-source] and
[Naming][rss-naming]. Use:
- multi-line method chaining style **Option B**: dot `.` on previous line
- multi-line method chaining style **Option A**: dot `.` on the second line
- string literal quoting style **Option A**: single quoted by default
1. [Rails](https://github.com/bbatsov/rails-style-guide)
1. [Newlines styleguide][newlines-styleguide]
......
......@@ -20,7 +20,7 @@ gem 'rugged', '~> 0.24.0'
# Authentication libraries
gem 'devise', '~> 4.2'
gem 'doorkeeper', '~> 4.2.0'
gem 'omniauth', '~> 1.3.2'
gem 'omniauth', '~> 1.4.2'
gem 'omniauth-auth0', '~> 1.4.1'
gem 'omniauth-azure-oauth2', '~> 0.0.6'
gem 'omniauth-cas3', '~> 1.1.2'
......
......@@ -352,7 +352,7 @@ GEM
temple (~> 0.7.6)
thor
tilt
hashie (3.5.1)
hashie (3.5.5)
health_check (2.2.1)
rails (>= 4.0)
hipchat (1.5.2)
......@@ -465,7 +465,7 @@ GEM
octokit (4.6.2)
sawyer (~> 0.8.0, >= 0.5.3)
oj (2.17.4)
omniauth (1.3.2)
omniauth (1.4.2)
hashie (>= 1.2, < 4)
rack (>= 1.0, < 3)
omniauth-auth0 (1.4.1)
......@@ -951,7 +951,7 @@ DEPENDENCIES
oauth2 (~> 1.2.0)
octokit (~> 4.6.2)
oj (~> 2.17.4)
omniauth (~> 1.3.2)
omniauth (~> 1.4.2)
omniauth-auth0 (~> 1.4.1)
omniauth-authentiq (~> 0.3.0)
omniauth-azure-oauth2 (~> 0.0.6)
......
......@@ -7,10 +7,6 @@
/* global Aside */
window.$ = window.jQuery = require('jquery');
require('jquery-ui/ui/autocomplete');
require('jquery-ui/ui/draggable');
require('jquery-ui/ui/effect-highlight');
require('jquery-ui/ui/sortable');
require('jquery-ujs');
require('vendor/jquery.endless-scroll');
require('vendor/jquery.highlight');
......@@ -108,6 +104,7 @@ require('./droplab/droplab_filter');
require('./abuse_reports');
require('./activities');
require('./admin');
require('./ajax_loading_spinner');
require('./api');
require('./aside');
require('./autosave');
......
/* global Vue */
require('./issue_card_inner');
const Store = gl.issueBoards.BoardsStore;
module.exports = {
name: 'BoardsIssueCard',
template: `
<li class="card"
:class="{ 'user-can-drag': !disabled && issue.id, 'is-disabled': disabled || !issue.id, 'is-active': issueDetailVisible }"
:index="index"
:data-issue-id="issue.id"
@mousedown="mouseDown"
@mousemove="mouseMove"
@mouseup="showIssue($event)">
<issue-card-inner
:list="list"
:issue="issue"
:issue-link-base="issueLinkBase"
:root-path="rootPath" />
</li>
`,
components: {
'issue-card-inner': gl.issueBoards.IssueCardInner,
},
props: {
list: Object,
issue: Object,
issueLinkBase: String,
disabled: Boolean,
index: Number,
rootPath: String,
},
data() {
return {
showDetail: false,
detailIssue: Store.detail,
};
},
computed: {
issueDetailVisible() {
return this.detailIssue.issue && this.detailIssue.issue.id === this.issue.id;
},
},
methods: {
mouseDown() {
this.showDetail = true;
},
mouseMove() {
this.showDetail = false;
},
showIssue(e) {
const targetTagName = e.target.tagName.toLowerCase();
if (targetTagName === 'a' || targetTagName === 'button') return;
if (this.showDetail) {
this.showDetail = false;
if (Store.detail.issue && Store.detail.issue.id === this.issue.id) {
Store.detail.issue = {};
} else {
Store.detail.issue = this.issue;
Store.detail.list = this.list;
}
}
},
},
};
/* eslint-disable comma-dangle, space-before-function-paren, dot-notation */
/* global Vue */
require('./issue_card_inner');
(() => {
const Store = gl.issueBoards.BoardsStore;
window.gl = window.gl || {};
window.gl.issueBoards = window.gl.issueBoards || {};
gl.issueBoards.BoardCard = Vue.extend({
template: '#js-board-list-card',
components: {
'issue-card-inner': gl.issueBoards.IssueCardInner,
},
props: {
list: Object,
issue: Object,
issueLinkBase: String,
disabled: Boolean,
index: Number,
rootPath: String,
},
data () {
return {
showDetail: false,
detailIssue: Store.detail
};
},
computed: {
issueDetailVisible () {
return this.detailIssue.issue && this.detailIssue.issue.id === this.issue.id;
}
},
methods: {
mouseDown () {
this.showDetail = true;
},
mouseMove() {
this.showDetail = false;
},
showIssue (e) {
const targetTagName = e.target.tagName.toLowerCase();
if (targetTagName === 'a' || targetTagName === 'button') return;
if (this.showDetail) {
this.showDetail = false;
if (Store.detail.issue && Store.detail.issue.id === this.issue.id) {
Store.detail.issue = {};
} else {
Store.detail.issue = this.issue;
Store.detail.list = this.list;
}
}
}
}
});
})();
......@@ -2,7 +2,7 @@
/* global Vue */
/* global Sortable */
require('./board_card');
const boardCard = require('./board_card');
require('./board_new_issue');
(() => {
......@@ -14,7 +14,7 @@ require('./board_new_issue');
gl.issueBoards.BoardList = Vue.extend({
template: '#js-board-list-template',
components: {
'board-card': gl.issueBoards.BoardCard,
boardCard,
'board-new-issue': gl.issueBoards.BoardNewIssue
},
props: {
......
......@@ -123,13 +123,17 @@ class List {
if (listFrom) {
this.issuesSize += 1;
this.updateIssueLabel(issue, listFrom);
}
}
}
updateIssueLabel(issue, listFrom) {
gl.boardService.moveIssue(issue.id, listFrom.id, this.id)
.then(() => {
listFrom.getIssues(false);
});
}
}
}
findIssue (id) {
return this.issues.filter(issue => issue.id === id)[0];
......
......@@ -97,9 +97,12 @@
const issueLists = issue.getLists();
const listLabels = issueLists.map(listIssue => listIssue.label);
// Add to new lists issues if it doesn't already exist
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 === 'done') {
......
......@@ -7,7 +7,7 @@
var DOWN_BUILD_TRACE = '#down-build-trace';
this.Build = (function() {
Build.interval = null;
Build.timeout = null;
Build.state = null;
......@@ -31,7 +31,7 @@
this.$scrollBottomBtn = $('#scroll-bottom');
this.$buildRefreshAnimation = $('.js-build-refresh');
clearInterval(Build.interval);
clearTimeout(Build.timeout);
// Init breakpoint checker
this.bp = Breakpoints.get();
......@@ -52,17 +52,7 @@
this.getInitialBuildTrace();
this.initScrollButtonAffix();
}
if (this.buildStatus === "running" || this.buildStatus === "pending") {
Build.interval = setInterval((function(_this) {
// Check for new build output if user still watching build page
// Only valid for runnig build when output changes during time
return function() {
if (_this.location() === _this.pageUrl) {
return _this.getBuildTrace();
}
};
})(this), 4000);
}
this.invokeBuildTrace();
}
Build.prototype.initSidebar = function() {
......@@ -75,6 +65,22 @@
return window.location.href.split("#")[0];
};
Build.prototype.invokeBuildTrace = function() {
var continueRefreshStatuses = ['running', 'pending'];
// Continue to update build trace when build is running or pending
if (continueRefreshStatuses.indexOf(this.buildStatus) !== -1) {
// Check for new build output if user still watching build page
// Only valid for runnig build when output changes during time
Build.timeout = setTimeout((function(_this) {
return function() {
if (_this.location() === _this.pageUrl) {
return _this.getBuildTrace();
}
};
})(this), 4000);
}
};
Build.prototype.getInitialBuildTrace = function() {
var removeRefreshStatuses = ['success', 'failed', 'canceled', 'skipped'];
......@@ -86,7 +92,7 @@
if (window.location.hash === DOWN_BUILD_TRACE) {
$("html,body").scrollTop(this.$buildTrace.height());
}
if (removeRefreshStatuses.indexOf(buildData.status) >= 0) {
if (removeRefreshStatuses.indexOf(buildData.status) !== -1) {
this.$buildRefreshAnimation.remove();
return this.initScrollMonitor();
}
......@@ -105,6 +111,7 @@
if (log.state) {
_this.state = log.state;
}
_this.invokeBuildTrace();
if (log.status === "running") {
if (log.append) {
$('.js-build-output').append(log.html);
......
......@@ -52,6 +52,30 @@
return this.views[viewMode].call(this);
};
ImageFile.prototype.initDraggable = function($el, padding, callback) {
var dragging = false;
var $body = $('body');
var $offsetEl = $el.parent();
$el.off('mousedown').on('mousedown', function() {
dragging = true;
$body.css('user-select', 'none');
});
$body.off('mouseup').off('mousemove').on('mouseup', function() {
dragging = false;
$body.css('user-select', '');
})
.on('mousemove', function(e) {
var left;
if (!dragging) return;
left = e.pageX - ($offsetEl.offset().left + padding);
callback(e, left);
});
};
prepareFrames = function(view) {
var maxHeight, maxWidth;
maxWidth = 0;
......@@ -96,26 +120,30 @@
maxHeight = 0;
return $('.swipe.view', this.file).each((function(_this) {
return function(index, view) {
var ref;
var $swipeWrap, $swipeBar, $swipeFrame, wrapPadding, ref;
ref = prepareFrames(view), maxWidth = ref[0], maxHeight = ref[1];
$('.swipe-frame', view).css({
$swipeFrame = $('.swipe-frame', view);
$swipeWrap = $('.swipe-wrap', view);
$swipeBar = $('.swipe-bar', view);
$swipeFrame.css({
width: maxWidth + 16,
height: maxHeight + 28
});
$('.swipe-wrap', view).css({
$swipeWrap.css({
width: maxWidth + 1,
height: maxHeight + 2
});
return $('.swipe-bar', view).css({
$swipeBar.css({
left: 0
}).draggable({
axis: 'x',
containment: 'parent',
drag: function(event) {
return $('.swipe-wrap', view).width((maxWidth + 1) - $(this).position().left);
},
stop: function(event) {
return $('.swipe-wrap', view).width((maxWidth + 1) - $(this).position().left);
});
wrapPadding = parseInt($swipeWrap.css('right').replace('px', ''), 10);
_this.initDraggable($swipeBar, wrapPadding, function(e, left) {
if (left > 0 && left < $swipeFrame.width() - (wrapPadding * 2)) {
$swipeWrap.width((maxWidth + 1) - left);
$swipeBar.css('left', left);
}
});
};
......@@ -128,9 +156,14 @@
dragTrackWidth = $('.drag-track', this.file).width() - $('.dragger', this.file).width();
return $('.onion-skin.view', this.file).each((function(_this) {
return function(index, view) {
var ref;
var $frame, $track, $dragger, $frameAdded, framePadding, ref, dragging = false;
ref = prepareFrames(view), maxWidth = ref[0], maxHeight = ref[1];
$('.onion-skin-frame', view).css({
$frame = $('.onion-skin-frame', view);
$frameAdded = $('.frame.added', view);
$track = $('.drag-track', view);
$dragger = $('.dragger', $track);
$frame.css({
width: maxWidth + 16,
height: maxHeight + 28
});
......@@ -138,16 +171,18 @@
width: maxWidth + 1,
height: maxHeight + 2
});
return $('.dragger', view).css({
$dragger.css({
left: dragTrackWidth
}).draggable({
axis: 'x',
containment: 'parent',
drag: function(event) {
return $('.frame.added', view).css('opacity', $(this).position().left / dragTrackWidth);
},
stop: function(event) {
return $('.frame.added', view).css('opacity', $(this).position().left / dragTrackWidth);
});
framePadding = parseInt($frameAdded.css('right').replace('px', ''), 10);
_this.initDraggable($dragger, framePadding, function(e, left) {
var opacity = left / dragTrackWidth;
if (opacity >= 0 && opacity <= 1) {
$dragger.css('left', left);
$frameAdded.css('opacity', opacity);
}
});
};
......
......@@ -38,6 +38,7 @@
/* global AdminEmailSelect */
const ShortcutsBlob = require('./shortcuts_blob');
const UserCallout = require('./user_callout');
(function() {
var Dispatcher;
......@@ -287,6 +288,9 @@ const ShortcutsBlob = require('./shortcuts_blob');
case 'ci:lints:show':
new gl.CILintEditor();
break;
case 'users:show':
new UserCallout();
break;
}
switch (path.first()) {
case 'sessions':
......@@ -326,6 +330,7 @@ const ShortcutsBlob = require('./shortcuts_blob');
case 'dashboard':
case 'root':
shortcut_handler = new ShortcutsDashboardNavigation();
new UserCallout();
break;
case 'profiles':
new NotificationsForm();
......
......@@ -523,9 +523,8 @@ module.exports = Vue.component('environment-item', {
</span>
</td>
<td class="hidden-xs">
<div v-if="!model.isFolder">
<div class="btn-group" role="group">
<td class="hidden-xs environments-actions">
<div v-if="!model.isFolder" class="btn-group pull-right" role="group">
<actions-component v-if="hasManualActions && canCreateDeployment"
:play-icon-svg="playIconSvg"
:actions="manualActions">
......@@ -549,7 +548,6 @@ module.exports = Vue.component('environment-item', {
:retry-url="retryUrl">
</rollback-component>
</div>
</div>
</td>
</tr>
`,
......
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, max-len, one-var, one-var-declaration-per-line, quotes, prefer-template, newline-per-chained-call, comma-dangle, new-cap, no-else-return, consistent-return */
/* global FilesCommentButton */
/* global notes */
(function() {
let $commentButtonTemplate;
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
this.FilesCommentButton = (function() {
var COMMENT_BUTTON_CLASS, COMMENT_BUTTON_TEMPLATE, DEBOUNCE_TIMEOUT_DURATION, EMPTY_CELL_CLASS, LINE_COLUMN_CLASSES, LINE_CONTENT_CLASS, LINE_HOLDER_CLASS, LINE_NUMBER_CLASS, OLD_LINE_CLASS, TEXT_FILE_SELECTOR, UNFOLDABLE_LINE_CLASS;
var COMMENT_BUTTON_CLASS, EMPTY_CELL_CLASS, LINE_COLUMN_CLASSES, LINE_CONTENT_CLASS, LINE_HOLDER_CLASS, LINE_NUMBER_CLASS, OLD_LINE_CLASS, TEXT_FILE_SELECTOR, UNFOLDABLE_LINE_CLASS;
COMMENT_BUTTON_CLASS = '.add-diff-note';
COMMENT_BUTTON_TEMPLATE = _.template('<button name="button" type="submit" class="btn <%- COMMENT_BUTTON_CLASS %> js-add-diff-note-button" title="Add a comment to this line"><i class="fa fa-comment-o"></i></button>');
LINE_HOLDER_CLASS = '.line_holder';
LINE_NUMBER_CLASS = 'diff-line-num';
......@@ -27,26 +27,29 @@
TEXT_FILE_SELECTOR = '.text-file';
DEBOUNCE_TIMEOUT_DURATION = 100;
function FilesCommentButton(filesContainerElement) {
var debounce;
this.filesContainerElement = filesContainerElement;
this.destroy = bind(this.destroy, this);
this.render = bind(this.render, this);
this.VIEW_TYPE = $('input#view[type=hidden]').val();
debounce = _.debounce(this.render, DEBOUNCE_TIMEOUT_DURATION);
$(this.filesContainerElement).off('mouseover', LINE_COLUMN_CLASSES).off('mouseleave', LINE_COLUMN_CLASSES).on('mouseover', LINE_COLUMN_CLASSES, debounce).on('mouseleave', LINE_COLUMN_CLASSES, this.destroy);
this.hideButton = bind(this.hideButton, this);
this.isParallelView = notes.isParallelView();
filesContainerElement.on('mouseover', LINE_COLUMN_CLASSES, this.render)
.on('mouseleave', LINE_COLUMN_CLASSES, this.hideButton);
}
FilesCommentButton.prototype.render = function(e) {
var $currentTarget, buttonParentElement, lineContentElement, textFileElement;
var $currentTarget, buttonParentElement, lineContentElement, textFileElement, $button;
$currentTarget = $(e.currentTarget);
buttonParentElement = this.getButtonParent($currentTarget);
if (!this.validateButtonParent(buttonParentElement)) return;
lineContentElement = this.getLineContent($currentTarget);
if (!this.validateLineContent(lineContentElement)) return;
buttonParentElement = this.getButtonParent($currentTarget);
if (!this.validateButtonParent(buttonParentElement) || !this.validateLineContent(lineContentElement)) return;
$button = $(COMMENT_BUTTON_CLASS, buttonParentElement);
buttonParentElement.addClass('is-over')
.nextUntil(`.${LINE_CONTENT_CLASS}`).addClass('is-over');
if ($button.length) {
return;
}
textFileElement = this.getTextFileElement($currentTarget);
buttonParentElement.append(this.buildButton({
......@@ -61,19 +64,16 @@
}));
};
FilesCommentButton.prototype.destroy = function(e) {
if (this.isMovingToSameType(e)) {
return;
}
$(COMMENT_BUTTON_CLASS, this.getButtonParent($(e.currentTarget))).remove();
FilesCommentButton.prototype.hideButton = function(e) {
var $currentTarget = $(e.currentTarget);
var buttonParentElement = this.getButtonParent($currentTarget);
buttonParentElement.removeClass('is-over')
.nextUntil(`.${LINE_CONTENT_CLASS}`).removeClass('is-over');
};
FilesCommentButton.prototype.buildButton = function(buttonAttributes) {
var initializedButtonTemplate;
initializedButtonTemplate = COMMENT_BUTTON_TEMPLATE({
COMMENT_BUTTON_CLASS: COMMENT_BUTTON_CLASS.substr(1)
});
return $(initializedButtonTemplate).attr({
return $commentButtonTemplate.clone().attr({
'data-noteable-type': buttonAttributes.noteableType,
'data-noteable-id': buttonAttributes.noteableID,
'data-commit-id': buttonAttributes.commitID,
......@@ -86,14 +86,14 @@
};
FilesCommentButton.prototype.getTextFileElement = function(hoveredElement) {
return $(hoveredElement.closest(TEXT_FILE_SELECTOR));
return hoveredElement.closest(TEXT_FILE_SELECTOR);
};
FilesCommentButton.prototype.getLineContent = function(hoveredElement) {
if (hoveredElement.hasClass(LINE_CONTENT_CLASS)) {
return hoveredElement;
}
if (this.VIEW_TYPE === 'inline') {
if (!this.isParallelView) {
return $(hoveredElement).closest(LINE_HOLDER_CLASS).find("." + LINE_CONTENT_CLASS);
} else {
return $(hoveredElement).next("." + LINE_CONTENT_CLASS);
......@@ -101,7 +101,7 @@
};
FilesCommentButton.prototype.getButtonParent = function(hoveredElement) {
if (this.VIEW_TYPE === 'inline') {
if (!this.isParallelView) {
if (hoveredElement.hasClass(OLD_LINE_CLASS)) {
return hoveredElement;
}
......@@ -114,17 +114,8 @@
}
};
FilesCommentButton.prototype.isMovingToSameType = function(e) {
var newButtonParent;
newButtonParent = this.getButtonParent($(e.toElement));
if (!newButtonParent) {
return false;
}
return newButtonParent.is(this.getButtonParent($(e.currentTarget)));
};
FilesCommentButton.prototype.validateButtonParent = function(buttonParentElement) {
return !buttonParentElement.hasClass(EMPTY_CELL_CLASS) && !buttonParentElement.hasClass(UNFOLDABLE_LINE_CLASS) && $(COMMENT_BUTTON_CLASS, buttonParentElement).length === 0;
return !buttonParentElement.hasClass(EMPTY_CELL_CLASS) && !buttonParentElement.hasClass(UNFOLDABLE_LINE_CLASS);
};
FilesCommentButton.prototype.validateLineContent = function(lineContentElement) {
......@@ -135,6 +126,8 @@
})();
$.fn.filesCommentButton = function() {
$commentButtonTemplate = $('<button name="button" type="submit" class="add-diff-note js-add-diff-note-button" title="Add a comment to this line"><i class="fa fa-comment-o"></i></button>');
if (!(this && (this.parent().data('can-create-note') != null))) {
return;
}
......
......@@ -48,7 +48,11 @@
}
setOffset(offset = 0) {
if (window.innerWidth > 480) {
this.dropdown.style.left = `${offset}px`;
} else {
this.dropdown.style.left = '0px';
}
}
renderContent(forceShowList = false) {
......
......@@ -63,7 +63,7 @@
}
GitLabDropdownFilter.prototype.shouldBlur = function(keyCode) {
return BLUR_KEYCODES.indexOf(keyCode) >= 0;
return BLUR_KEYCODES.indexOf(keyCode) !== -1;
};
GitLabDropdownFilter.prototype.filter = function(search_text) {
......@@ -605,7 +605,7 @@
var occurrences;
occurrences = fuzzaldrinPlus.match(text, term);
return text.split('').map(function(character, i) {
if (indexOf.call(occurrences, i) >= 0) {
if (indexOf.call(occurrences, i) !== -1) {
return "<b>" + character + "</b>";
} else {
return character;
......@@ -748,7 +748,7 @@
return function(e) {
var $listItems, PREV_INDEX, currentKeyCode;
currentKeyCode = e.which;
if (ARROW_KEY_CODES.indexOf(currentKeyCode) >= 0) {
if (ARROW_KEY_CODES.indexOf(currentKeyCode) !== -1) {
e.preventDefault();
e.stopImmediatePropagation();
PREV_INDEX = currentIndex;
......
require('./stat_graph_contributors_graph');
require('./stat_graph_contributors_util');
require('./stat_graph_contributors');
require('./stat_graph');
import ContributorsStatGraph from './stat_graph_contributors';
// export to global scope
window.ContributorsStatGraph = ContributorsStatGraph;
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-return-assign, max-len */
(function() {
this.StatGraph = (function() {
function StatGraph() {}
StatGraph.log = {};
StatGraph.get_log = function() {
return this.log;
};
StatGraph.set_log = function(data) {
return this.log = data;
};
return StatGraph;
})();
}).call(window);
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, camelcase, one-var-declaration-per-line, quotes, no-param-reassign, quote-props, comma-dangle, prefer-template, max-len, no-return-assign */
/* global ContributorsGraph */
/* global ContributorsAuthorGraph */
/* global ContributorsMasterGraph */
/* global ContributorsStatGraphUtil */
/* global d3 */
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, camelcase, one-var-declaration-per-line, quotes, no-param-reassign, quote-props, comma-dangle, prefer-template, max-len, no-return-assign, no-shadow */
window.d3 = require('d3');
import d3 from 'd3';
import { ContributorsGraph, ContributorsAuthorGraph, ContributorsMasterGraph } from './stat_graph_contributors_graph';
import ContributorsStatGraphUtil from './stat_graph_contributors_util';
(function() {
this.ContributorsStatGraph = (function() {
export default (function() {
function ContributorsStatGraph() {}
ContributorsStatGraph.prototype.init = function(log) {
......@@ -112,5 +108,4 @@ window.d3 = require('d3');
};
return ContributorsStatGraph;
})();
}).call(window);
})();
/* eslint-disable func-names, space-before-function-paren, one-var, no-var, prefer-rest-params, max-len, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, comma-dangle, no-return-assign, prefer-arrow-callback, quotes, prefer-template, newline-per-chained-call, no-else-return */
/* global d3 */
/* global ContributorsGraph */
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, max-len, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, comma-dangle, no-return-assign, prefer-arrow-callback, quotes, prefer-template, newline-per-chained-call, no-else-return, no-shadow */
window.d3 = require('d3');
import d3 from 'd3';
(function() {
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; },
extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
hasProp = {}.hasOwnProperty;
const bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
const extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
const hasProp = {}.hasOwnProperty;
this.ContributorsGraph = (function() {
export const ContributorsGraph = (function() {
function ContributorsGraph() {}
ContributorsGraph.prototype.MARGIN = {
......@@ -91,9 +88,9 @@ window.d3 = require('d3');
};
return ContributorsGraph;
})();
})();
this.ContributorsMasterGraph = (function(superClass) {
export const ContributorsMasterGraph = (function(superClass) {
extend(ContributorsMasterGraph, superClass);
function ContributorsMasterGraph(data1) {
......@@ -195,9 +192,9 @@ window.d3 = require('d3');
};
return ContributorsMasterGraph;
})(ContributorsGraph);
})(ContributorsGraph);
this.ContributorsAuthorGraph = (function(superClass) {
export const ContributorsAuthorGraph = (function(superClass) {
extend(ContributorsAuthorGraph, superClass);
function ContributorsAuthorGraph(data1) {
......@@ -272,5 +269,4 @@ window.d3 = require('d3');
};
return ContributorsAuthorGraph;
})(ContributorsGraph);
}).call(window);
})(ContributorsGraph);
/* eslint-disable func-names, space-before-function-paren, object-shorthand, no-var, one-var, camelcase, one-var-declaration-per-line, comma-dangle, no-param-reassign, no-return-assign, quotes, prefer-arrow-callback, wrap-iife, consistent-return, no-unused-vars, max-len, no-cond-assign, no-else-return, max-len */
(function() {
window.ContributorsStatGraphUtil = {
export default {
parse_log: function(log) {
var by_author, by_email, data, entry, i, len, total, normalized_email;
total = {};
......@@ -134,5 +134,4 @@
return false;
}
}
};
}).call(window);
};
......@@ -116,7 +116,7 @@
formData = $.param(formData);
formAction = form.attr('action');
issuesUrl = formAction;
issuesUrl += "" + (formAction.indexOf('?') < 0 ? '?' : '&');
issuesUrl += "" + (formAction.indexOf('?') === -1 ? '?' : '&');
issuesUrl += formData;
return gl.utils.visitUrl(issuesUrl);
};
......
......@@ -329,17 +329,18 @@
* ```
*/
w.gl.utils.backOff = (fn, timeout = 60000) => {
const maxInterval = 32000;
let nextInterval = 2000;
const startTime = (+new Date());
const startTime = Date.now();
return new Promise((resolve, reject) => {
const stop = arg => ((arg instanceof Error) ? reject(arg) : resolve(arg));
const next = () => {
if (new Date().getTime() - startTime < timeout) {
if (Date.now() - startTime < timeout) {
setTimeout(fn.bind(null, next, stop), nextInterval);
nextInterval *= 2;
nextInterval = Math.min(nextInterval + nextInterval, maxInterval);
} else {
reject(new Error('BACKOFF_TIMEOUT'));
}
......
......@@ -7,4 +7,4 @@ const statusCodes = {
OK: 200,
};
export default statusCodes;
module.exports = statusCodes;
......@@ -82,7 +82,7 @@ require('./smart_interval');
return function() {
var page;
page = $('body').data('page').split(':').last();
if (allowedPages.indexOf(page) < 0) {
if (allowedPages.indexOf(page) === -1) {
return _this.clearEventListeners();
}
};
......@@ -255,7 +255,7 @@ require('./smart_interval');
}
$('.ci_widget').hide();
allowed_states = ["failed", "canceled", "running", "pending", "success", "success_with_warnings", "skipped", "not_found"];
if (indexOf.call(allowed_states, state) >= 0) {
if (indexOf.call(allowed_states, state) !== -1) {
$('.ci_widget.ci-' + state).show();
switch (state) {
case "failed":
......
......@@ -78,7 +78,6 @@
} else {
$(element).find('.assignee-icon').empty();
}
return $(element).effect('highlight');
};
function Milestone() {
......
......@@ -39,7 +39,7 @@
$value = $block.find('.value');
$loading = $block.find('.block-loading').fadeOut();
if (issueUpdateURL) {
milestoneLinkTemplate = _.template('<a href="/<%- namespace %>/<%- path %>/milestones/<%- iid %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %></a>');
milestoneLinkTemplate = _.template('<a href="/<%- full_path %>/milestones/<%- iid %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %></a>');
milestoneLinkNoneTemplate = '<span class="no-value">None</span>';
collapsedSidebarLabelTemplate = _.template('<span class="has-tooltip" data-container="body" title="<%- remaining %>" data-placement="left"> <%- title %> </span>');
}
......@@ -181,8 +181,7 @@
$selectbox.hide();
$value.css('display', '');
if (data.milestone != null) {
data.milestone.namespace = _this.currentProject.namespace;
data.milestone.path = _this.currentProject.path;
data.milestone.full_path = _this.currentProject.full_path;
data.milestone.remaining = gl.utils.timeFor(data.milestone.due_date);
$value.html(milestoneLinkTemplate(data.milestone));
return $sidebarCollapsedValue.find('span').html(collapsedSidebarLabelTemplate(data.milestone));
......
/* eslint-disable func-names, space-before-function-paren, no-var, one-var, prefer-rest-params, max-len, vars-on-top, wrap-iife, consistent-return, comma-dangle, one-var-declaration-per-line, quotes, no-return-assign, prefer-arrow-callback, prefer-template, no-shadow, no-else-return, max-len */
/* eslint-disable func-names, space-before-function-paren, no-var, one-var, prefer-rest-params, max-len, vars-on-top, wrap-iife, consistent-return, comma-dangle, one-var-declaration-per-line, quotes, no-return-assign, prefer-arrow-callback, prefer-template, no-shadow, no-else-return, max-len, object-shorthand */
(function() {
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; },
indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i += 1) { if (i in this && this[i] === item) return i; } return -1; };
......@@ -20,15 +20,35 @@
};
NewBranchForm.prototype.init = function() {
if (this.name.val().length > 0) {
if (this.name.length && this.name.val().length > 0) {
return this.name.trigger('blur');
}
};
NewBranchForm.prototype.setupAvailableRefs = function(availableRefs) {
return this.ref.autocomplete({
source: availableRefs,
minLength: 1
var $branchSelect = $('.js-branch-select');
$branchSelect.glDropdown({
data: availableRefs,
filterable: true,
filterByText: true,
remote: false,
fieldName: $branchSelect.data('field-name'),
selectable: true,
isSelectable: function(branch, $el) {
return !$el.hasClass('is-active');
},
text: function(branch) {
return branch;
},
id: function(branch) {
return branch;
},
toggleLabel: function(branch) {
if (branch) {
return branch;
}
}
});
};
......@@ -61,7 +81,7 @@
var errorMessage, errors, formatter, unique, validator;
this.branchNameError.empty();
unique = function(values, value) {
if (indexOf.call(values, value) < 0) {
if (indexOf.call(values, value) === -1) {
values.push(value);
}
return values;
......
......@@ -84,13 +84,14 @@
}
$(function() {
$(document).on('focusout.ssh_key', '#key_key', function() {
$(document).on('input.ssh_key', '#key_key', function() {
const $title = $('#key_title');
const comment = $(this).val().match(/^\S+ \S+ (.+)\n?$/);
if (comment && comment.length > 1 && $title.val() === '') {
// Extract the SSH Key title from its comment
if (comment && comment.length > 1) {
return $title.val(comment[1]).change();
}
// Extract the SSH Key title from its comment
});
if (global.utils.getPagePath() === 'profiles') {
return new Profile();
......
// require everything else in this directory
function requireAll(context) { return context.keys().map(context); }
requireAll(require.context('.', false, /^\.\/(?!profile_bundle).*\.(js|es6)$/));
require('./gl_crop');
require('./profile');
......@@ -123,7 +123,7 @@
if ($('input[name="ref"]').length) {
var $form = $dropdown.closest('form');
var action = $form.attr('action');
var divider = action.indexOf('?') < 0 ? '?' : '&';
var divider = action.indexOf('?') === -1 ? '?' : '&';
gl.utils.visitUrl(action + '' + divider + '' + $form.serialize());
}
}
......
......@@ -79,7 +79,6 @@
protected_branch: formData
},
success: (response) => {
this.$wrap.effect('highlight');
this.hasChanges = false;
for (const ACCESS_LEVEL in ACCESS_LEVELS) {
......@@ -93,6 +92,9 @@
$.scrollTo(0);
new Flash('Failed to update branch!');
}
}).always(() => {
this.$allowedToMergeDropdown.enable();
this.$allowedToPushDropdown.enable();
});
}
......
// require everything else in this directory
function requireAll(context) { return context.keys().map(context); }
requireAll(require.context('.', false, /^\.\/(?!protected_branches_bundle).*\.(js|es6)$/));
require('./protected_branch_access_dropdown');
require('./protected_branch_create');
require('./protected_branch_dropdown');
require('./protected_branch_edit');
require('./protected_branch_edit_list');
/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, quotes, max-len */
/* global ace */
// require everything else in this directory
function requireAll(context) { return context.keys().map(context); }
requireAll(require.context('.', false, /^\.\/(?!snippet_bundle).*\.(js|es6)$/));
(function() {
$(function() {
var editor = ace.edit("editor");
......
/* global Flash */
require('vendor/task_list');
class TaskList {
......@@ -6,6 +7,16 @@ class TaskList {
this.dataType = options.dataType;
this.fieldName = options.fieldName;
this.onSuccess = options.onSuccess || (() => {});
this.onError = function showFlash(response) {
let errorMessages = '';
if (response.responseJSON) {
errorMessages = response.responseJSON.errors.join(' ');
}
return new Flash(errorMessages || 'Update failed', 'alert');
};
this.init();
}
......@@ -32,6 +43,7 @@ class TaskList {
url: $target.data('update-url') || $('form.js-issuable-update').attr('action'),
data: patchData,
success: this.onSuccess,
error: this.onError,
});
}
}
......
/* global Cookies */
const userCalloutElementName = '.user-callout';
const closeButton = '.close-user-callout';
const userCalloutBtn = '.user-callout-btn';
const userCalloutSvgAttrName = 'callout-svg';
const USER_CALLOUT_COOKIE = 'user_callout_dismissed';
const USER_CALLOUT_TEMPLATE = `
<div class="bordered-box landing content-block">
<button class="btn btn-default close close-user-callout" type="button">
<i class="fa fa-times dismiss-icon"></i>
</button>
<div class="row">
<div class="col-sm-3 col-xs-12 svg-container">
</div>
<div class="col-sm-8 col-xs-12 inner-content">
<h4>
Customize your experience
</h4>
<p>
Change syntax themes, default project pages, and more in preferences.
</p>
<a class="btn user-callout-btn" href="/profile/preferences">Check it out</a>
</div>
</div>
</div>`;
class UserCallout {
constructor() {
this.isCalloutDismissed = Cookies.get(USER_CALLOUT_COOKIE);
this.userCalloutBody = $(userCalloutElementName);
this.userCalloutSvg = $(userCalloutElementName).attr(userCalloutSvgAttrName);
$(userCalloutElementName).removeAttr(userCalloutSvgAttrName);
this.init();
}
init() {
const $template = $(USER_CALLOUT_TEMPLATE);
if (!this.isCalloutDismissed || this.isCalloutDismissed === 'false') {
$template.find('.svg-container').append(this.userCalloutSvg);
this.userCalloutBody.append($template);
$template.find(closeButton).on('click', e => this.dismissCallout(e));
$template.find(userCalloutBtn).on('click', e => this.dismissCallout(e));
}
}
dismissCallout(e) {
Cookies.set(USER_CALLOUT_COOKIE, 'true');
const $currentTarget = $(e.currentTarget);
if ($currentTarget.hasClass('close-user-callout')) {
this.userCalloutBody.empty();
}
}
}
module.exports = UserCallout;
// require everything else in this directory
function requireAll(context) { return context.keys().map(context); }
requireAll(require.context('.', false, /^\.\/(?!users_bundle).*\.(js|es6)$/));
require('./calendar');
/* global Vue, Flash, gl */
/* eslint-disable no-param-reassign */
/* eslint-disable no-param-reassign, no-alert */
((gl) => {
gl.VuePipelineActions = Vue.extend({
......@@ -16,21 +16,33 @@
download(name) {
return `Download ${name} artifacts`;
},
/**
* Shows a dialog when the user clicks in the cancel button.
* We need to prevent the default behavior and stop propagation because the
* link relies on UJS.
*
* @param {Event} event
*/
confirmAction(event) {
if (!confirm('Are you sure you want to cancel this pipeline?')) {
event.preventDefault();
event.stopPropagation();
}
},
},
template: `
<td class="pipeline-actions hidden-xs">
<div class="controls pull-right">
<div class="btn-group inline">
<div class="pull-right">
<div class="btn-group">
<div class="btn-group" v-if="actions">
<button
v-if='actions'
class="dropdown-toggle btn btn-default has-tooltip js-pipeline-dropdown-manual-actions"
data-toggle="dropdown"
title="Manual job"
data-placement="top"
aria-label="Manual job"
>
<span v-html='svgs.iconPlay' aria-hidden="true"></span>
aria-label="Manual job">
<span v-html="svgs.iconPlay" aria-hidden="true"></span>
<i class="fa fa-caret-down" aria-hidden="true"></i>
</button>
<ul class="dropdown-menu dropdown-menu-align-right">
......@@ -38,23 +50,21 @@
<a
rel="nofollow"
data-method="post"
:href='action.path'
>
<span v-html='svgs.iconPlay' aria-hidden="true"></span>
:href="action.path">
<span v-html="svgs.iconPlay" aria-hidden="true"></span>
<span>{{action.name}}</span>
</a>
</li>
</ul>
</div>
<div class="btn-group">
<div class="btn-group" v-if="artifacts">
<button
v-if='artifacts'
class="dropdown-toggle btn btn-default build-artifacts has-tooltip js-pipeline-dropdown-download"
title="Artifacts"
data-placement="top"
data-toggle="dropdown"
aria-label="Artifacts"
>
aria-label="Artifacts">
<i class="fa fa-download" aria-hidden="true"></i>
<i class="fa fa-caret-down" aria-hidden="true"></i>
</button>
......@@ -62,20 +72,16 @@
<li v-for='artifact in pipeline.details.artifacts'>
<a
rel="nofollow"
download
:href='artifact.path'
>
:href="artifact.path">
<i class="fa fa-download" aria-hidden="true"></i>
<span>{{download(artifact.name)}}</span>
</a>
</li>
</ul>
</div>
</div>
<div class="cancel-retry-btns inline">
<div class="btn-group" v-if="pipeline.flags.retryable">
<a
v-if='pipeline.flags.retryable'
class="btn has-tooltip"
class="btn btn-default btn-retry has-tooltip"
title="Retry"
rel="nofollow"
data-method="post"
......@@ -85,8 +91,9 @@
aria-label="Retry">
<i class="fa fa-repeat" aria-hidden="true"></i>
</a>
</div>
<div class="btn-group" v-if="pipeline.flags.cancelable">
<a
v-if='pipeline.flags.cancelable'
class="btn btn-remove has-tooltip"
title="Cancel"
rel="nofollow"
......@@ -99,6 +106,7 @@
</a>
</div>
</div>
</div>
</td>
`,
});
......
......@@ -23,6 +23,13 @@
required: true,
},
},
updated() {
if (this.builds) {
this.stopDropdownClickPropagation();
}
},
methods: {
fetchBuilds(e) {
const areaExpanded = e.currentTarget.attributes['aria-expanded'];
......@@ -37,17 +44,19 @@
return flash;
});
},
keepGraph(e) {
const { target } = e;
if (target.className.indexOf('js-ci-action-icon') >= 0) return null;
if (
target.parentElement &&
(target.parentElement.className.indexOf('js-ci-action-icon') >= 0)
) return null;
return e.stopPropagation();
/**
* When the user right clicks or cmd/ctrl + click in the job name
* the dropdown should not be closed and the link should open in another tab,
* so we stop propagation of the click event inside the dropdown.
*
* Since this component is rendered multiple times per page we need to guarantee we only
* target the click event of this component.
*/
stopDropdownClickPropagation() {
$(this.$el.querySelectorAll('.js-builds-dropdown-list a.mini-pipeline-graph-dropdown-item')).on('click', (e) => {
e.stopPropagation();
});
},
},
computed: {
......@@ -76,13 +85,13 @@
template: `
<div>
<button
@click='fetchBuilds($event)'
@click="fetchBuilds($event)"
:class="triggerButtonClass"
:title='stage.title'
:title="stage.title"
data-placement="top"
data-toggle="dropdown"
type="button"
:aria-label='stage.title'
:aria-label="stage.title"
>
<span v-html="svg" aria-hidden="true"></span>
<i class="fa fa-caret-down" aria-hidden="true"></i>
......@@ -90,7 +99,6 @@
<ul class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container">
<div class="arrow-up" aria-hidden="true"></div>
<div
@click='keepGraph($event)'
:class="dropdownClass"
class="js-builds-dropdown-list scrollable-menu"
v-html="buildsOrSpinner"
......
......@@ -54,7 +54,7 @@ require('../lib/utils/datetime_utility');
},
},
template: `
<td>
<td class="pipelines-time-ago">
<p class="duration" v-if='duration'>
<span v-html='svgs.iconTimer'></span>
{{duration}}
......@@ -65,8 +65,7 @@ require('../lib/utils/datetime_utility');
data-toggle="tooltip"
data-placement="top"
data-container="body"
:data-original-title='localTimeFinished'
>
:data-original-title='localTimeFinished'>
{{timeStopped.words}}
</time>
</p>
......
......@@ -2,7 +2,6 @@
* This is a manifest file that'll automatically include all the stylesheets available in this directory
* and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
* the top of the compiled file, but it's generally better to create a new file per style scope.
*= require jquery-ui/autocomplete
*= require jquery.atwho
*= require select2
*= require_self
......
......@@ -229,7 +229,7 @@
.controls {
float: right;
margin-top: 8px;
padding-bottom: 7px;
padding-bottom: 8px;
border-bottom: 1px solid $border-color;
}
}
......
......@@ -26,6 +26,11 @@
.filtered-search-container {
display: -webkit-flex;
display: flex;
@media (max-width: $screen-xs-min) {
-webkit-flex-direction: column;
flex-direction: column;
}
}
.filtered-search-input-container {
......@@ -34,6 +39,20 @@
position: relative;
width: 100%;
@media (max-width: $screen-xs-min) {
-webkit-flex: 1 1 100%;
flex: 1 1 100%;
margin-bottom: 10px;
.dropdown-menu {
width: auto;
left: 0;
right: 0;
max-width: none;
min-width: 100%;
}
}
.form-control {
padding-left: 25px;
padding-right: 25px;
......@@ -79,6 +98,31 @@
overflow: auto;
}
@media (max-width: $screen-xs-min) {
.issues-details-filters {
padding: 0 0 10px;
background-color: $white-light;
border-top: 0;
}
.filter-dropdown-container {
.dropdown-toggle,
.dropdown {
width: 100%;
}
.dropdown {
margin-left: 0;
}
.fa-chevron-down {
position: absolute;
right: 10px;
top: 10px;
}
}
}
%filter-dropdown-item-btn-hover {
background-color: $dropdown-hover-color;
color: $white-light;
......
......@@ -2,17 +2,6 @@
font-family: $regular_font;
font-size: $font-size-base;
&.ui-autocomplete {
border-color: $jq-ui-border;
padding: 0;
margin-top: 2px;
z-index: 1001;
.ui-menu-item a {
padding: 4px 10px;
}
}
.ui-state-default {
border: 1px solid $white-light;
background: $white-light;
......
......@@ -29,16 +29,14 @@
}
}
@media (min-width: $screen-sm-min) {
.content-wrapper {
padding-right: $gutter_collapsed_width;
}
}
.right-sidebar-collapsed {
padding-right: 0;
@media (min-width: $screen-sm-min) {
.content-wrapper {
padding-right: $gutter_collapsed_width;
}
.merge-request-tabs-holder.affix {
right: $gutter_collapsed_width;
}
......@@ -56,6 +54,12 @@
.right-sidebar-expanded {
padding-right: 0;
@media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) {
.content-wrapper {
padding-right: $gutter_collapsed_width;
}
}
@media (min-width: $screen-md-min) {
.content-wrapper {
padding-right: $gutter_width;
......
......@@ -20,6 +20,7 @@ $dark-highlight-bg: #ffe792;
$dark-highlight-color: $black;
$dark-pre-hll-bg: #373b41;
$dark-hll-bg: #373b41;
$dark-over-bg: #9f9ab5;
$dark-c: #969896;
$dark-err: #c66;
$dark-k: #b294bb;
......@@ -139,6 +140,18 @@ $dark-il: #de935f;
}
}
.diff-line-num {
&.is-over,
&.hll:not(.empty-cell).is-over {
background-color: $dark-over-bg;
border-color: darken($dark-over-bg, 5%);
a {
color: darken($dark-over-bg, 15%);
}
}
}
.line_content.match {
@include dark-diff-match-line;
}
......
......@@ -13,6 +13,7 @@ $monokai-line-empty-bg: #49483e;
$monokai-line-empty-border: darken($monokai-line-empty-bg, 15%);
$monokai-diff-border: #808080;
$monokai-highlight-bg: #ffe792;
$monokai-over-bg: #9f9ab5;
$monokai-new-bg: rgba(166, 226, 46, 0.1);
$monokai-new-idiff: rgba(166, 226, 46, 0.15);
......@@ -139,6 +140,18 @@ $monokai-gi: #a6e22e;
}
}
.diff-line-num {
&.is-over,
&.hll:not(.empty-cell).is-over {
background-color: $monokai-over-bg;
border-color: darken($monokai-over-bg, 5%);
a {
color: darken($monokai-over-bg, 15%);
}
}
}
.line_content.match {
@include dark-diff-match-line;
}
......
......@@ -17,6 +17,7 @@ $solarized-dark-line-color-new: #5a766c;
$solarized-dark-line-color-old: #7a6c71;
$solarized-dark-highlight: #094554;
$solarized-dark-hll-bg: #174652;
$solarized-dark-over-bg: #9f9ab5;
$solarized-dark-c: #586e75;
$solarized-dark-err: #93a1a1;
$solarized-dark-g: #93a1a1;
......@@ -143,6 +144,18 @@ $solarized-dark-il: #2aa198;
}
}
.diff-line-num {
&.is-over,
&.hll:not(.empty-cell).is-over {
background-color: $solarized-dark-over-bg;
border-color: darken($solarized-dark-over-bg, 5%);
a {
color: darken($solarized-dark-over-bg, 15%);
}
}
}
.line_content.match {
@include dark-diff-match-line;
}
......
......@@ -18,6 +18,7 @@ $solarized-light-line-color-new: #a1a080;
$solarized-light-line-color-old: #ad9186;
$solarized-light-highlight: #eee8d5;
$solarized-light-hll-bg: #ddd8c5;
$solarized-light-over-bg: #ded7fc;
$solarized-light-c: #93a1a1;
$solarized-light-err: #586e75;
$solarized-light-g: #586e75;
......@@ -150,6 +151,18 @@ $solarized-light-il: #2aa198;
}
}
.diff-line-num {
&.is-over,
&.hll:not(.empty-cell).is-over {
background-color: $solarized-light-over-bg;
border-color: darken($solarized-light-over-bg, 5%);
a {
color: darken($solarized-light-over-bg, 15%);
}
}
}
.line_content.match {
@include matchLine;
}
......
......@@ -7,6 +7,7 @@ $white-code-color: $gl-text-color;
$white-highlight: #fafe3d;
$white-pre-hll-bg: #f8eec7;
$white-hll-bg: #f8f8f8;
$white-over-bg: #ded7fc;
$white-c: #998;
$white-err: #a61717;
$white-err-bg: #e3d2d2;
......@@ -123,6 +124,16 @@ $white-gc-bg: #eaf2f5;
}
}
&.is-over,
&.hll:not(.empty-cell).is-over {
background-color: $white-over-bg;
border-color: darken($white-over-bg, 5%);
a {
color: darken($white-over-bg, 15%);
}
}
&.hll:not(.empty-cell) {
background-color: $line-number-select;
border-color: $line-select-yellow-dark;
......
......@@ -89,6 +89,10 @@
.diff-line-num {
width: 50px;
a {
transition: none;
}
}
.line_holder td {
......@@ -109,10 +113,6 @@
td.line_content.parallel {
width: 46%;
}
.add-diff-note {
margin-left: -65px;
}
}
.old_line,
......
......@@ -452,36 +452,37 @@ ul.notes {
* Line note button on the side of diffs
*/
.diff-file tr.line_holder {
@mixin show-add-diff-note {
display: inline-block;
}
.add-diff-note {
margin-top: -8px;
border-radius: 40px;
.add-diff-note {
display: none;
margin-top: -2px;
border-radius: 50%;
background: $white-light;
padding: 4px;
font-size: 16px;
padding: 1px 5px;
font-size: 12px;
color: $gl-link-color;
margin-left: -56px;
margin-left: -55px;
position: absolute;
z-index: 10;
width: 32px;
// "hide" it by default
display: none;
width: 23px;
height: 23px;
border: 1px solid $border-color;
transition: transform .1s ease-in-out;
&:hover {
background: $gl-info;
color: $white-light;
@include show-add-diff-note;
transform: scale(1.15);
}
&:active {
outline: 0;
}
}
// "show" the icon also if we just hover somewhere over the line
&:hover > td {
.diff-file {
.is-over {
.add-diff-note {
@include show-add-diff-note;
display: inline-block;
}
}
}
......
......@@ -13,21 +13,16 @@
white-space: nowrap;
}
.commit-title {
margin: 0;
}
.controls {
white-space: nowrap;
.table-holder {
width: 100%;
overflow: auto;
}
.btn {
margin: 4px;
.commit-title {
margin: 0;
}
.table.ci-table {
min-width: 1200px;
table-layout: fixed;
.label {
margin-bottom: 3px;
......@@ -37,16 +32,72 @@
color: $black;
}
.pipeline-date,
.pipeline-status {
width: 10%;
.stage-cell {
min-width: 130px; // Guarantees we show at least 4 stages in line
width: 20%;
}
.pipelines-time-ago {
text-align: right;
}
.pipeline-info,
.pipeline-commit,
.pipeline-stages,
.pipeline-actions {
width: 20%;
padding-right: 0;
min-width: 170px; //Guarantees buttons don't break in several lines.
.btn-default {
color: $gl-text-color-secondary;
}
.btn.btn-retry:hover,
.btn.btn-retry:focus {
border-color: $gray-darkest;
background-color: $white-normal;
}
svg path {
fill: $gl-text-color-secondary;
}
.dropdown-menu {
max-height: 250px;
overflow-y: auto;
}
.dropdown-toggle,
.dropdown-menu {
color: $gl-text-color-secondary;
.fa {
color: $gl-text-color-secondary;
font-size: 14px;
}
svg,
.fa {
margin-right: 0;
}
}
.btn-group {
&.open {
.btn-default {
background-color: $white-normal;
border-color: $border-white-normal;
}
}
.btn {
.icon-play {
height: 13px;
width: 12px;
}
}
}
.tooltip {
white-space: nowrap;
}
}
}
}
......@@ -61,28 +112,11 @@
}
}
.content-list.pipelines .table-holder {
min-height: 300px;
}
.pipeline-holder {
width: 100%;
overflow: auto;
}
.table.ci-table {
min-width: 900px;
&.pipeline {
min-width: 650px;
}
&.builds-page {
tr {
&.builds-page tr {
height: 71px;
}
}
tr {
th {
......@@ -94,6 +128,10 @@
padding: 10px 8px;
}
td.environments-actions {
padding-right: 0;
}
td.stage-cell {
padding: 10px 0;
}
......@@ -103,7 +141,7 @@
}
.commit-link {
padding: 9px 8px 10px;
padding: 9px 8px 10px 2px;
}
}
......@@ -210,73 +248,9 @@
}
}
.pipeline-actions {
min-width: 140px;
.btn {
margin: 0;
color: $gl-text-color-secondary;
}
.cancel-retry-btns {
vertical-align: middle;
.btn:not(:first-child) {
margin-left: 8px;
}
}
.dropdown-menu {
max-height: 250px;
overflow-y: auto;
}
.dropdown-toggle,
.dropdown-menu {
color: $gl-text-color-secondary;
.fa {
color: $gl-text-color-secondary;
font-size: 14px;
}
svg,
.fa {
margin-right: 0;
}
}
.btn-remove {
color: $white-light;
}
.btn-group {
&.open {
.btn-default {
background-color: $white-normal;
border-color: $border-white-normal;
}
}
.btn {
.icon-play {
height: 13px;
width: 12px;
}
}
}
.tooltip {
white-space: nowrap;
}
}
.build-link {
a {
.build-link a {
color: $gl-text-color;
}
}
.btn-group.open .dropdown-toggle {
box-shadow: none;
......@@ -339,32 +313,9 @@
}
.tab-pane {
&.pipelines {
.ci-table {
min-width: 900px;
}
.content-list.pipelines {
overflow: auto;
}
.stage {
max-width: 100px;
width: 100px;
}
.pipeline-actions {
min-width: initial;
}
}
&.builds {
.ci-table {
tr {
&.builds .ci-table tr {
height: 71px;
}
}
}
}
// Pipeline graph
......
......@@ -277,3 +277,41 @@ table.u2f-registrations {
padding-left: 18px;
}
}
.user-callout {
margin: 24px auto 0;
.bordered-box {
border: 1px solid $border-color;
border-radius: $border-radius-default;
}
.landing {
margin-bottom: $gl-padding;
.close {
margin-right: 20px;
}
.dismiss-icon {
float: right;
cursor: pointer;
color: $cycle-analytics-dismiss-icon-color;
}
.svg-container {
text-align: center;
svg {
width: 136px;
height: 136px;
}
}
}
@media(max-width: $screen-xs-max) {
.inner-content {
padding-left: 30px;
}
}
}
......@@ -638,14 +638,6 @@ pre.light-well {
margin: 0;
}
.activity-filter-block {
.controls {
padding-bottom: 7px;
margin-top: 8px;
border-bottom: 1px solid $border-color;
}
}
.commits-search-form {
.input-short {
min-width: 200px;
......
......@@ -92,6 +92,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:akismet_api_key,
:akismet_enabled,
:container_registry_token_expire_delay,
:default_artifacts_expire_in,
:default_branch_protection,
:default_group_visibility,
:default_project_visibility,
......
......@@ -26,6 +26,23 @@ module IssuableActions
private
def render_conflict_response
respond_to do |format|
format.html do
@conflict = true
render :edit
end
format.json do
render json: {
errors: [
"Someone edited this #{issuable.human_class_name} at the same time you did. Please refresh your browser and make sure your changes will not unintentionally remove theirs."
]
}, status: 409
end
end
end
def labels
@labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute
end
......
class Projects::BranchesController < Projects::ApplicationController
include ActionView::Helpers::SanitizeHelper
include SortingHelper
# Authorize
before_action :require_non_empty_project
before_action :require_non_empty_project, except: :create
before_action :authorize_download_code!
before_action :authorize_push_code!, only: [:new, :create, :destroy, :destroy_all_merged]
......@@ -32,6 +33,8 @@ class Projects::BranchesController < Projects::ApplicationController
branch_name = sanitize(strip_tags(params[:branch_name]))
branch_name = Addressable::URI.unescape(branch_name)
redirect_to_autodeploy = project.empty_repo? && project.deployment_services.present?
result = CreateBranchService.new(project, current_user).
execute(branch_name, ref)
......@@ -42,8 +45,15 @@ class Projects::BranchesController < Projects::ApplicationController
if result[:status] == :success
@branch = result[:branch]
if redirect_to_autodeploy
redirect_to(
url_to_autodeploy_setup(project, branch_name),
notice: view_context.autodeploy_flash_notice(branch_name))
else
redirect_to namespace_project_tree_path(@project.namespace, @project,
@branch.name)
end
else
@error = result[:message]
render action: 'new'
......@@ -76,7 +86,19 @@ class Projects::BranchesController < Projects::ApplicationController
ref_escaped = sanitize(strip_tags(params[:ref]))
Addressable::URI.unescape(ref_escaped)
else
@project.default_branch
@project.default_branch || 'master'
end
end
def url_to_autodeploy_setup(project, branch_name)
namespace_project_new_blob_path(
project.namespace,
project,
branch_name,
file_name: '.gitlab-ci.yml',
commit_message: 'Set up auto deploy',
target_branch: branch_name,
context: 'autodeploy'
)
end
end
......@@ -139,8 +139,7 @@ class Projects::IssuesController < Projects::ApplicationController
end
rescue ActiveRecord::StaleObjectError
@conflict = true
render :edit
render_conflict_response
end
def referenced_merge_requests
......
......@@ -305,24 +305,23 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_request = MergeRequests::UpdateService.new(project, current_user, update_params).execute(@merge_request)
if @merge_request.valid?
respond_to do |format|
format.html do
redirect_to([@merge_request.target_project.namespace.becomes(Namespace),
@merge_request.target_project, @merge_request])
if @merge_request.valid?
redirect_to([@merge_request.target_project.namespace.becomes(Namespace), @merge_request.target_project, @merge_request])
else
set_suggested_approvers
render :edit
end
end
format.json do
render json: @merge_request.to_json(include: { milestone: {}, assignee: { methods: :avatar_url }, labels: { methods: :text_color } }, methods: [:task_status, :task_status_short])
end
end
else
set_suggested_approvers
render "edit"
end
rescue ActiveRecord::StaleObjectError
@conflict = true
render :edit
render_conflict_response
end
def remove_wip
......
......@@ -15,4 +15,11 @@ module BuildsHelper
log_state: @build.trace_with_state[:state].to_s
}
end
def build_failed_issue_options
{
title: "Build Failed ##{@build.id}",
description: namespace_project_build_url(@project.namespace, @project, @build)
}
end
end
......@@ -34,7 +34,7 @@ module ButtonHelper
content_tag (append_link ? :a : :span), protocol,
class: klass,
href: (project.http_url_to_repo if append_link),
href: (project.http_url_to_repo(current_user) if append_link),
data: {
html: true,
placement: placement,
......
......@@ -52,7 +52,7 @@ module IssuablesHelper
field_name: 'issuable_template',
selected: selected_template(issuable),
project_path: ref_project.path,
namespace_path: ref_project.namespace.path
namespace_path: ref_project.namespace.full_path
}
}
......
......@@ -150,6 +150,15 @@ module ProjectsHelper
).html_safe
end
def link_to_autodeploy_doc
link_to 'About auto deploy', help_page_path('ci/autodeploy/index'), target: '_blank'
end
def autodeploy_flash_notice(branch_name)
"Branch <strong>#{truncate(sanitize(branch_name))}</strong> was created. To set up auto deploy, \
choose a GitLab CI Yaml template and commit your changes. #{link_to_autodeploy_doc}".html_safe
end
private
def repo_children_classes(field)
......@@ -244,7 +253,7 @@ module ProjectsHelper
when 'ssh'
project.ssh_url_to_repo
else
project.http_url_to_repo
project.http_url_to_repo(current_user)
end
end
......
......@@ -81,6 +81,12 @@ class ApplicationSetting < ActiveRecord::Base
presence: true,
numericality: { only_integer: true, greater_than_or_equal_to: 0 }
validates :max_artifacts_size,
presence: true,
numericality: { only_integer: true, greater_than: 0 }
validates :default_artifacts_expire_in, presence: true, duration: true
validates :container_registry_token_expire_delay,
presence: true,
numericality: { only_integer: true, greater_than: 0 }
......@@ -187,6 +193,7 @@ class ApplicationSetting < ActiveRecord::Base
after_sign_up_text: nil,
akismet_enabled: false,
container_registry_token_expire_delay: 5,
default_artifacts_expire_in: '30 days',
default_branch_protection: Settings.gitlab['default_branch_protection'],
default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'],
default_projects_limit: Settings.gitlab['default_projects_limit'],
......@@ -220,9 +227,9 @@ class ApplicationSetting < ActiveRecord::Base
sign_in_text: nil,
signin_enabled: Settings.gitlab['signin_enabled'],
signup_enabled: Settings.gitlab['signup_enabled'],
terminal_max_session_time: 0,
two_factor_grace_period: 48,
user_default_external: false,
terminal_max_session_time: 0
user_default_external: false
}
end
......@@ -243,6 +250,14 @@ class ApplicationSetting < ActiveRecord::Base
create(defaults)
end
def self.human_attribute_name(attr, _options = {})
if attr == :default_artifacts_expire_in
'Default artifacts expiration'
else
super
end
end
def update_mirror_cron_jobs
Project.mirror.where('sync_time < ?', minimum_mirror_sync_time)
.update_all(sync_time: minimum_mirror_sync_time)
......
......@@ -485,7 +485,7 @@ module Ci
def artifacts_expire_in=(value)
self.artifacts_expire_at =
if value
Time.now + ChronicDuration.parse(value)
ChronicDuration.parse(value)&.seconds&.from_now
end
end
......
......@@ -36,7 +36,7 @@ class Event < ActiveRecord::Base
scope :code_push, -> { where(action: PUSHED) }
scope :in_projects, ->(projects) do
where(project_id: projects).recent
where(project_id: projects.pluck(:id)).recent
end
scope :with_associations, -> { includes(:author, :project, project: :namespace).preload(:target) }
......
......@@ -21,8 +21,6 @@ class Issue < ActiveRecord::Base
DueThisWeek = DueDateStruct.new('Due This Week', 'week').freeze
DueThisMonth = DueDateStruct.new('Due This Month', 'month').freeze
ActsAsTaggableOn.strict_case_match = true
belongs_to :project
belongs_to :moved_to, class_name: 'Issue'
......
......@@ -64,8 +64,7 @@ class Project < ActiveRecord::Base
after_validation :check_pending_delete
ActsAsTaggableOn.strict_case_match = true
acts_as_taggable_on :tags
acts_as_taggable
attr_accessor :new_default_branch
attr_accessor :old_path_with_namespace
......@@ -399,7 +398,7 @@ class Project < ActiveRecord::Base
end
def reference_pattern
name_pattern = Gitlab::Regex::NAMESPACE_REGEX_STR
name_pattern = Gitlab::Regex::FULL_NAMESPACE_REGEX_STR
%r{
((?<namespace>#{name_pattern})\/)?
......@@ -958,10 +957,6 @@ class Project < ActiveRecord::Base
gitlab_shell.url_to_repo(path_with_namespace)
end
def namespace_dir
namespace.try(:path) || ''
end
def repo_exists?
@repo_exists ||= repository.exists?
rescue
......@@ -984,8 +979,14 @@ class Project < ActiveRecord::Base
url_to_repo
end
def http_url_to_repo
"#{web_url}.git"
def http_url_to_repo(user = nil)
url = web_url
if user
url.sub!(%r{\Ahttps?://}) { |protocol| "#{protocol}#{user.username}@" }
end
"#{url}.git"
end
# No need to have a Kerberos Web url. Kerberos URL will be used only to clone
......@@ -1015,8 +1016,8 @@ class Project < ActiveRecord::Base
def rename_repo
path_was = previous_changes['path'].first
old_path_with_namespace = File.join(namespace_dir, path_was)
new_path_with_namespace = File.join(namespace_dir, path)
old_path_with_namespace = File.join(namespace.full_path, path_was)
new_path_with_namespace = File.join(namespace.full_path, path)
Rails.logger.error "Attempting to rename #{old_path_with_namespace} -> #{new_path_with_namespace}"
......
......@@ -15,10 +15,10 @@ class MattermostService < ChatNotificationService
'This service sends notifications about projects events to Mattermost channels.<br />
To set up this service:
<ol>
<li><a href="https://docs.mattermost.com/developer/webhooks-incoming.html#enabling-incoming-webhooks">Enable incoming webhooks</a> in your Mattermost installation. </li>
<li><a href="https://docs.mattermost.com/developer/webhooks-incoming.html#creating-integrations-using-incoming-webhooks">Add an incoming webhook</a> in your Mattermost team. The default channel can be overridden for each event. </li>
<li>Paste the webhook <strong>URL</strong> into the field bellow. </li>
<li>Select events below to enable notifications. The channel and username are optional. </li>
<li><a href="https://docs.mattermost.com/developer/webhooks-incoming.html#enabling-incoming-webhooks">Enable incoming webhooks</a> in your Mattermost installation.</li>
<li><a href="https://docs.mattermost.com/developer/webhooks-incoming.html#creating-integrations-using-incoming-webhooks">Add an incoming webhook</a> in your Mattermost team. The default channel can be overridden for each event.</li>
<li>Paste the webhook <strong>URL</strong> into the field below.</li>
<li>Select events below to enable notifications. The <strong>Channel handle</strong> and <strong>Username</strong> fields are optional.</li>
</ol>'
end
......@@ -28,14 +28,14 @@ class MattermostService < ChatNotificationService
def default_fields
[
{ type: 'text', name: 'webhook', placeholder: 'http://mattermost_host/hooks/...' },
{ type: 'text', name: 'username', placeholder: 'username' },
{ type: 'text', name: 'webhook', placeholder: 'e.g. http://mattermost_host/hooks/…' },
{ type: 'text', name: 'username', placeholder: 'e.g. GitLab' },
{ type: 'checkbox', name: 'notify_only_broken_builds' },
{ type: 'checkbox', name: 'notify_only_broken_pipelines' },
]
end
def default_channel_placeholder
"town-square"
"Channel handle (e.g. town-square)"
end
end
......@@ -13,11 +13,11 @@ class SlackService < ChatNotificationService
def help
'This service sends notifications about projects events to Slack channels.<br />
To setup this service:
To set up this service:
<ol>
<li><a href="https://slack.com/apps/A0F7XDUAZ-incoming-webhooks">Add an incoming webhook</a> in your Slack team. The default channel can be overridden for each event. </li>
<li>Paste the <strong>Webhook URL</strong> into the field below. </li>
<li>Select events below to enable notifications. The channel and username are optional. </li>
<li><a href="https://slack.com/apps/A0F7XDUAZ-incoming-webhooks">Add an incoming webhook</a> in your Slack team. The default channel can be overridden for each event.</li>
<li>Paste the <strong>Webhook URL</strong> into the field below.</li>
<li>Select events below to enable notifications. The <strong>Channel name</strong> and <strong>Username</strong> fields are optional.</li>
</ol>'
end
......@@ -27,14 +27,14 @@ class SlackService < ChatNotificationService
def default_fields
[
{ type: 'text', name: 'webhook', placeholder: 'https://hooks.slack.com/services/...' },
{ type: 'text', name: 'username', placeholder: 'username' },
{ type: 'text', name: 'webhook', placeholder: 'e.g. https://hooks.slack.com/services/…' },
{ type: 'text', name: 'username', placeholder: 'e.g. GitLab' },
{ type: 'checkbox', name: 'notify_only_broken_builds' },
{ type: 'checkbox', name: 'notify_only_broken_pipelines' },
]
end
def default_channel_placeholder
"#general"
"Channel name (e.g. general)"
end
end
......@@ -116,9 +116,7 @@ class Repository
offset: offset,
after: after,
before: before,
# --follow doesn't play well with --skip. See:
# https://gitlab.com/gitlab-org/gitlab-ce/issues/3574#note_3040520
follow: false,
follow: path.present?,
skip_merges: skip_merges
}
......
......@@ -364,7 +364,7 @@ class User < ActiveRecord::Base
def reference_pattern
%r{
#{Regexp.escape(reference_prefix)}
(?<user>#{Gitlab::Regex::NAMESPACE_REF_REGEX_STR})
(?<user>#{Gitlab::Regex::FULL_NAMESPACE_REGEX_STR})
}x
end
......
class CreateBranchService < BaseService
def execute(branch_name, ref)
create_master_branch if project.empty_repo?
result = ValidateNewBranchService.new(project, current_user)
.execute(branch_name)
......@@ -19,4 +21,16 @@ class CreateBranchService < BaseService
def success(branch)
super().merge(branch: branch)
end
private
def create_master_branch
project.repository.commit_file(
current_user,
'/README.md',
'',
message: 'Add README.md',
branch_name: 'master',
update: false)
end
end
......@@ -18,7 +18,8 @@ module Groups
end
group.children.each do |group|
DestroyService.new(group, current_user).async_execute
# This needs to be synchronous since the namespace gets destroyed below
DestroyService.new(group, current_user).execute
end
group.really_destroy!
......
......@@ -6,6 +6,8 @@ module MergeRequests
# Executed when you do merge via GitLab UI
#
class MergeService < MergeRequests::BaseService
MergeError = Class.new(StandardError)
attr_reader :merge_request, :source
def execute(merge_request)
......@@ -20,11 +22,7 @@ module MergeRequests
return log_merge_error('Merge request is not mergeable', save_message_on_model: true)
end
if @merge_request.target_project.above_size_limit?
message = Gitlab::RepositorySizeError.new(@merge_request.target_project).merge_error
return log_merge_error(message, save_message_on_model: true)
end
check_size_limit
@source = find_merge_source
......@@ -38,6 +36,8 @@ module MergeRequests
success
end
end
rescue MergeError => e
log_merge_error(e.message, save_message_on_model: true)
end
def hooks_validation_pass?(merge_request)
......@@ -74,19 +74,13 @@ module MergeRequests
commit_id = repository.merge(current_user, source, merge_request, options)
if commit_id
raise MergeError, 'Conflicts detected during merge' unless commit_id
merge_request.update(merge_commit_sha: commit_id)
else
log_merge_error('Conflicts detected during merge', save_message_on_model: true)
false
end
rescue GitHooksService::PreReceiveError => e
log_merge_error(e.message, save_message_on_model: true)
false
raise MergeError, e.message
rescue StandardError => e
merge_request.update(merge_error: "Something went wrong during merge: #{e.message}")
log_merge_error(e.message)
false
raise MergeError, "Something went wrong during merge: #{e.message}"
ensure
merge_request.update(in_progress_merge_commit_sha: nil)
end
......@@ -114,17 +108,24 @@ module MergeRequests
merge_request.to_reference(full: true)
end
def check_size_limit
if @merge_request.target_project.above_size_limit?
message = Gitlab::RepositorySizeError.new(@merge_request.target_project).merge_error
raise MergeError, message
end
end
def find_merge_source
return merge_request.diff_head_sha unless merge_request.squash
squash_result = SquashService.new(project, current_user, params).execute(merge_request)
if squash_result[:status] == :success
case squash_result[:status]
when :success
squash_result[:squash_sha]
else
log_merge_error("Squashing #{merge_request_info} failed")
nil
when :error
raise MergeError, squash_result[:message]
end
end
end
......
......@@ -18,8 +18,7 @@ module MergeRequests
end
if merge_request.squash_in_progress?
log_error('Squash task canceled: Another squash is already in progress')
return false
return error('Squash task canceled: another squash is already in progress')
end
run_git_command(
......
......@@ -27,10 +27,6 @@ class ArtifactUploader < GitlabUploader
File.join(self.class.artifacts_cache_path, @build.artifacts_path)
end
def file_storage?
self.class.storage == CarrierWave::Storage::File
end
def filename
file.try(:filename)
end
......
......@@ -4,6 +4,6 @@ class AttachmentUploader < GitlabUploader
storage :file
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
"#{base_dir}/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
end
......@@ -4,7 +4,7 @@ class AvatarUploader < GitlabUploader
storage :file
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
"#{base_dir}/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
def exists?
......
......@@ -4,15 +4,12 @@ class FileUploader < GitlabUploader
storage :file
attr_accessor :project, :secret
attr_accessor :project
attr_reader :secret
def initialize(project, secret = nil)
@project = project
@secret = secret || self.class.generate_secret
end
def base_dir
"uploads"
@secret = secret || generate_secret
end
def store_dir
......@@ -23,10 +20,6 @@ class FileUploader < GitlabUploader
File.join(base_dir, 'tmp', @project.path_with_namespace, @secret)
end
def secure_url
File.join("/uploads", @secret, file.filename)
end
def to_markdown
to_h[:markdown]
end
......@@ -35,17 +28,23 @@ class FileUploader < GitlabUploader
filename = image_or_video? ? self.file.basename : self.file.filename
escaped_filename = filename.gsub("]", "\\]")
markdown = "[#{escaped_filename}](#{self.secure_url})"
markdown = "[#{escaped_filename}](#{secure_url})"
markdown.prepend("!") if image_or_video? || dangerous?
{
alt: filename,
url: self.secure_url,
url: secure_url,
markdown: markdown
}
end
def self.generate_secret
private
def generate_secret
SecureRandom.hex
end
def secure_url
File.join('/uploads', @secret, file.filename)
end
end
class GitlabUploader < CarrierWave::Uploader::Base
def self.base_dir
'uploads'
end
delegate :base_dir, to: :class
def file_storage?
self.class.storage == CarrierWave::Storage::File
end
# Reduce disk IO
def move_to_cache
true
......
......@@ -27,6 +27,8 @@ module UploaderHelper
extension_match?(DANGEROUS_EXT)
end
private
def extension_match?(extensions)
return false unless file
......@@ -40,8 +42,4 @@ module UploaderHelper
extensions.include?(extension.downcase)
end
def file_storage?
self.class.storage == CarrierWave::Storage::File
end
end
# DurationValidator
#
# Validate the format conforms with ChronicDuration
#
# Example:
#
# class ApplicationSetting < ActiveRecord::Base
# validates :default_artifacts_expire_in, presence: true, duration: true
# end
#
class DurationValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
ChronicDuration.parse(value)
rescue ChronicDuration::DurationParseError
record.errors.add(attribute, "is not a correct duration")
end
end
......@@ -232,8 +232,16 @@
.col-sm-10
= f.number_field :max_artifacts_size, class: 'form-control'
.help-block
Set the maximum file size each jobs's artifacts can have
= link_to "(?)", help_page_path("user/admin_area/settings/continuous_integration", anchor: "maximum-artifacts-size")
Set the maximum file size for each job's artifacts
= link_to icon('question-circle'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'maximum-artifacts-size')
.form-group
= f.label :default_artifacts_expire_in, 'Default artifacts expiration', class: 'control-label col-sm-2'
.col-sm-10
= f.text_field :default_artifacts_expire_in, class: 'form-control'
.help-block
Set the default expiration time for each job's artifacts.
0 for unlimited.
= link_to icon('question-circle'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'default-artifacts-expiration')
- if Gitlab.config.registry.enabled
%fieldset
......
......@@ -14,6 +14,8 @@
= runner.short_sha
%td
= runner.description
%td
= runner.version
%td
- if runner.shared?
n/a
......
......@@ -67,6 +67,7 @@
%th Type
%th Runner token
%th Description
%th Version
%th Projects
%th Jobs
%th Tags
......
......@@ -4,7 +4,7 @@
.nav-block
- if current_user
.controls
= link_to dashboard_projects_path(:atom, { private_token: current_user.private_token }), class: 'btn rss-btn' do
= link_to dashboard_projects_path(:atom, { private_token: current_user.private_token }), class: 'btn rss-btn has-tooltip', title: 'Subscribe' do
%i.fa.fa-rss
= render 'shared/event_filter'
......
......@@ -5,6 +5,8 @@
- page_title "Projects"
- header_title "Projects", dashboard_projects_path
.user-callout{ 'callout-svg' => custom_icon('icon_customization') }
- if @projects.any? || params[:filter_projects]
= render 'dashboard/projects_head'
......
......@@ -8,7 +8,7 @@
= f.text_field :name, class: "form-control top", required: true, title: "This field is required."
.username.form-group
= f.label :username
= f.text_field :username, class: "form-control middle", pattern: Gitlab::Regex::NAMESPACE_REGEX_STR_SIMPLE, required: true, title: 'Please create a username with only alphanumeric characters.'
= f.text_field :username, class: "form-control middle", pattern: Gitlab::Regex::NAMESPACE_REGEX_STR_JS, required: true, title: 'Please create a username with only alphanumeric characters.'
%p.validation-error.hide Username is already taken.
%p.validation-success.hide Username is available.
%p.validation-pending.hide Checking username availability...
......
......@@ -4,7 +4,7 @@
.nav-block
- if current_user
.controls
= link_to group_path(@group, format: :atom, private_token: current_user.private_token), class: 'btn rss-btn' do
= link_to group_path(@group, format: :atom, private_token: current_user.private_token), class: 'btn rss-btn has-tooltip' , title: 'Subscribe' do
%i.fa.fa-rss
= render 'shared/event_filter'
......
......@@ -101,5 +101,3 @@
$("#created-personal-access-token").click(function() {
this.select();
});
$("#created-personal-access-token").effect('highlight', { color: '#ffff99' }, 2000);
......@@ -4,7 +4,7 @@
.nav-block.activity-filter-block
- if current_user
.controls
= link_to namespace_project_path(@project.namespace, @project, format: :atom, private_token: current_user.private_token), title: "Feed", class: 'btn rss-btn' do
= link_to namespace_project_path(@project.namespace, @project, format: :atom, private_token: current_user.private_token), title: "Subscribe", class: 'btn rss-btn has-tooltip' do
= icon('rss')
= render 'shared/event_filter'
......
......@@ -19,10 +19,10 @@
= line_content
- when :parallel
%td.old_line.diff-line-num{ data: { linenumber: line_old } }
= link_to raw(line_old), "##{line_old}"
%a{ href: "##{line_old}", data: { linenumber: line_old } }
= line_content
%td.new_line.diff-line-num{ data: { linenumber: line_new } }
= link_to raw(line_new), "##{line_new}"
%a{ href: "##{line_new}", data: { linenumber: line_new } }
= line_content
- if @form.unfold? && @form.bottom? && @form.to < @blob.lines.size
......
......@@ -9,7 +9,6 @@
%script#js-board-template{ type: "text/x-template" }= render "projects/boards/components/board"
%script#js-board-list-template{ type: "text/x-template" }= render "projects/boards/components/board_list"
%script#js-board-list-card{ type: "text/x-template" }= render "projects/boards/components/card"
= render "projects/issues/head"
......
%li.card{ ":class" => '{ "user-can-drag": !disabled && issue.id, "is-disabled": disabled || !issue.id, "is-active": issueDetailVisible }',
":index" => "index",
":data-issue-id" => "issue.id",
"@mousedown" => "mouseDown",
"@mousemove" => "mouseMove",
"@mouseup" => "showIssue($event)" }
%issue-card-inner{ ":list" => "list",
":issue" => "issue",
":issue-link-base" => "issueLinkBase",
":root-path" => "rootPath" }
......@@ -12,12 +12,16 @@
.form-group
= label_tag :branch_name, nil, class: 'control-label'
.col-sm-10
= text_field_tag :branch_name, params[:branch_name], required: true, tabindex: 1, autofocus: true, class: 'form-control js-branch-name'
= text_field_tag :branch_name, params[:branch_name], required: true, autofocus: true, class: 'form-control js-branch-name'
.help-block.text-danger.js-branch-name-error
.form-group
= label_tag :ref, 'Create from', class: 'control-label'
.col-sm-10
= text_field_tag :ref, params[:ref] || @project.default_branch, required: true, tabindex: 2, class: 'form-control'
= hidden_field_tag :ref, params[:ref] || @project.default_branch
= dropdown_tag(params[:ref] || @project.default_branch,
options: { toggle_class: 'js-branch-select wide',
filter: true, dropdown_class: "dropdown-menu-selectable", placeholder: "Search branches",
data: { selected: params[:ref] || @project.default_branch, field_name: 'ref' } })
.help-block Existing branch name, tag, or commit SHA
.form-actions
= button_tag 'Create branch', class: 'btn btn-create', tabindex: 3
......
.content-block.build-header
.content-block.build-header.top-area
.header-content
= render 'ci/status/badge', status: @build.detailed_status(current_user), link: false
Job
......@@ -16,7 +16,10 @@
- if @build.user
= render "user"
= time_ago_with_tooltip(@build.created_at)
.nav-controls
- if can?(current_user, :create_issue, @project) && @build.failed?
= link_to "New issue", new_namespace_project_issue_path(@project.namespace, @project, issue: build_failed_issue_options), class: 'btn btn-new btn-inverted'
- if can?(current_user, :update_build, @build) && @build.retryable?
= link_to "Retry job", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-inverted-secondary pull-right', method: :post
= link_to "Retry job", retry_namespace_project_build_path(@project.namespace, @project, @build), class: 'btn btn-inverted-secondary', method: :post
%button.btn.btn-default.pull-right.visible-xs-block.visible-sm-block.build-gutter-toggle.js-sidebar-build-toggle{ role: "button", type: "button" }
= icon('angle-double-left')
- status = pipeline.status
- show_commit = local_assigns.fetch(:show_commit, true)
- show_branch = local_assigns.fetch(:show_branch, true)
%tr.commit
%td.commit-link
= render 'ci/status/badge', status: pipeline.detailed_status(current_user)
%td
= link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id) do
%span.pipeline-id ##{pipeline.id}
%span by
- if pipeline.user
= user_avatar(user: pipeline.user, size: 20)
- else
%span.api.monospace API
- if pipeline.latest?
%span.label.label-success.has-tooltip{ title: 'Latest pipeline for this branch' } latest
- if pipeline.triggered?
%span.label.label-primary triggered
- if pipeline.yaml_errors.present?
%span.label.label-danger.has-tooltip{ title: "#{pipeline.yaml_errors}" } yaml invalid
- if pipeline.builds.any?(&:stuck?)
%span.label.label-warning stuck
%td.branch-commit
- if pipeline.ref && show_branch
.icon-container
= pipeline.tag? ? icon('tag') : icon('code-fork')
= link_to pipeline.ref, namespace_project_commits_path(pipeline.project.namespace, pipeline.project, pipeline.ref), class: "monospace branch-name"
- if show_commit
.icon-container.commit-icon
= custom_icon("icon_commit")
= link_to pipeline.short_sha, namespace_project_commit_path(pipeline.project.namespace, pipeline.project, pipeline.sha), class: "commit-id monospace"
%p.commit-title
- if commit = pipeline.commit
= author_avatar(commit, size: 20)
= link_to_gfm truncate(commit.title, length: 60, escape: false), namespace_project_commit_path(pipeline.project.namespace, pipeline.project, commit.id), class: "commit-row-message"
- else
Cant find HEAD commit for this branch
%td
= render 'shared/mini_pipeline_graph', pipeline: pipeline, klass: 'js-mini-pipeline-graph'
%td
- if pipeline.duration
%p.duration
= custom_icon("icon_timer")
= duration_in_numbers(pipeline.duration)
- if pipeline.finished_at
%p.finished-at
= icon("calendar")
#{time_ago_with_tooltip(pipeline.finished_at, short_format: false)}
%td.pipeline-actions.hidden-xs
.controls.pull-right
- artifacts = pipeline.builds.latest.with_artifacts_not_expired
- actions = pipeline.manual_actions
- if artifacts.present? || actions.any?
.btn-group.inline
- if actions.any?
.btn-group
%button.dropdown-toggle.btn.btn-default.has-tooltip.js-pipeline-dropdown-manual-actions{ type: 'button', title: 'Manual pipeline', data: { toggle: 'dropdown', placement: 'top' }, 'aria-label' => 'Manual pipeline' }
= custom_icon('icon_play')
= icon('caret-down', 'aria-hidden' => 'true')
%ul.dropdown-menu.dropdown-menu-align-right
- actions.each do |build|
%li
= link_to play_namespace_project_build_path(pipeline.project.namespace, pipeline.project, build), method: :post, rel: 'nofollow' do
= custom_icon('icon_play')
%span= build.name
- if artifacts.present?
.btn-group
%button.dropdown-toggle.btn.btn-default.build-artifacts.has-tooltip.js-pipeline-dropdown-download{ type: 'button', title: 'Artifacts', data: { toggle: 'dropdown', placement: 'top' }, 'aria-label' => 'Artifacts' }
= icon("download")
= icon('caret-down')
%ul.dropdown-menu.dropdown-menu-align-right
- artifacts.each do |build|
%li
= link_to download_namespace_project_build_artifacts_path(pipeline.project.namespace, pipeline.project, build), rel: 'nofollow', download: '' do
= icon("download")
%span Download '#{build.name}' artifacts
- if can?(current_user, :update_pipeline, pipeline.project)
.cancel-retry-btns.inline
- if pipeline.retryable?
= link_to retry_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: 'btn has-tooltip', title: 'Retry', data: { toggle: 'dropdown', placement: 'top' }, 'aria-label' => 'Retry' , method: :post do
= icon("repeat")
- if pipeline.cancelable?
= link_to cancel_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: 'btn btn-remove has-tooltip', title: 'Cancel', data: { toggle: 'dropdown', placement: 'top' }, 'aria-label' => 'Cancel' , method: :post do
= icon("remove")
......@@ -10,10 +10,10 @@
- if diff_file.renamed_file
- old_path, new_path = mark_inline_diffs(diff_file.old_path, diff_file.new_path)
%strong.file-title-name.has-tooltip{ data: { title: old_path, container: 'body' } }
%strong.file-title-name.has-tooltip{ data: { title: diff_file.old_path, container: 'body' } }
= old_path
&rarr;
%strong.file-title-name.has-tooltip{ data: { title: new_path, container: 'body' } }
%strong.file-title-name.has-tooltip{ data: { title: diff_file.new_path, container: 'body' } }
= new_path
- else
%strong.file-title-name.has-tooltip{ data: { title: diff_file.new_path, container: 'body' } }
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment