Commit 21755ac9 authored by Douwe Maan's avatar Douwe Maan

Merge branch 'master' into copy-as-md

# Conflicts:
#	app/assets/javascripts/lib/utils/common_utils.js.es6
parents 43dc6263 b55c1bc4
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
"filenames" "filenames"
], ],
"rules": { "rules": {
"filenames/match-regex": [2, "^[a-z0-9_]+(.js)?$"] "filenames/match-regex": [2, "^[a-z0-9_]+(.js)?$"],
"no-multiple-empty-lines": ["error", { "max": 1 }]
} }
} }
...@@ -46,7 +46,7 @@ linters: ...@@ -46,7 +46,7 @@ linters:
max: 80 max: 80
MultilinePipe: MultilinePipe:
enabled: false enabled: true
MultilineScript: MultilineScript:
enabled: true enabled: true
...@@ -77,7 +77,7 @@ linters: ...@@ -77,7 +77,7 @@ linters:
- Style/WhileUntilModifier - Style/WhileUntilModifier
RubyComments: RubyComments:
enabled: false enabled: true
SpaceBeforeScript: SpaceBeforeScript:
enabled: true enabled: true
...@@ -97,7 +97,7 @@ linters: ...@@ -97,7 +97,7 @@ linters:
enabled: true enabled: true
UnnecessaryInterpolation: UnnecessaryInterpolation:
enabled: false enabled: true
UnnecessaryStringOutput: UnnecessaryStringOutput:
enabled: false enabled: true
...@@ -2,6 +2,14 @@ ...@@ -2,6 +2,14 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. entry.
## 8.16.1 (2017-01-23)
- Ensure export files are removed after a namespace is deleted.
- Don't allow project guests to subscribe to merge requests through the API. (Robert Schilling)
- Prevent users from creating notes on resources they can't access.
- Prevent users from deleting system deploy keys via the project deploy key API.
- Upgrade omniauth gem to 1.3.2.
## 8.16.0 (2017-02-22) ## 8.16.0 (2017-02-22)
- Add LDAP Rake task to rename a provider. !2181 - Add LDAP Rake task to rename a provider. !2181
......
...@@ -261,6 +261,9 @@ ...@@ -261,6 +261,9 @@
case 'projects:artifacts:browse': case 'projects:artifacts:browse':
new BuildArtifacts(); new BuildArtifacts();
break; break;
case 'help:index':
gl.VersionCheckImage.bindErrorEvent($('img.js-version-status-badge'));
break;
case 'search:show': case 'search:show':
new Search(); new Search();
break; break;
......
...@@ -58,6 +58,7 @@ var CustomEvent = require('./custom_event_polyfill'); ...@@ -58,6 +58,7 @@ var CustomEvent = require('./custom_event_polyfill');
var utils = require('./utils'); var utils = require('./utils');
var DropDown = function(list) { var DropDown = function(list) {
this.currentIndex = 0;
this.hidden = true; this.hidden = true;
this.list = list; this.list = list;
this.items = []; this.items = [];
...@@ -164,15 +165,21 @@ Object.assign(DropDown.prototype, { ...@@ -164,15 +165,21 @@ Object.assign(DropDown.prototype, {
}, },
show: function() { show: function() {
// debugger if (this.hidden) {
this.list.style.display = 'block'; // debugger
this.hidden = false; this.list.style.display = 'block';
this.currentIndex = 0;
this.hidden = false;
}
}, },
hide: function() { hide: function() {
// debugger if (!this.hidden) {
this.list.style.display = 'none'; // debugger
this.hidden = true; this.list.style.display = 'none';
this.currentIndex = 0;
this.hidden = true;
}
}, },
destroy: function() { destroy: function() {
...@@ -478,6 +485,8 @@ Object.assign(HookInput.prototype, { ...@@ -478,6 +485,8 @@ Object.assign(HookInput.prototype, {
this.input = function input(e) { this.input = function input(e) {
if(self.hasRemovedEvents) return; if(self.hasRemovedEvents) return;
self.list.show();
var inputEvent = new CustomEvent('input.dl', { var inputEvent = new CustomEvent('input.dl', {
detail: { detail: {
hook: self, hook: self,
...@@ -487,7 +496,6 @@ Object.assign(HookInput.prototype, { ...@@ -487,7 +496,6 @@ Object.assign(HookInput.prototype, {
cancelable: true cancelable: true
}); });
e.target.dispatchEvent(inputEvent); e.target.dispatchEvent(inputEvent);
self.list.show();
} }
this.keyup = function keyup(e) { this.keyup = function keyup(e) {
...@@ -503,6 +511,8 @@ Object.assign(HookInput.prototype, { ...@@ -503,6 +511,8 @@ Object.assign(HookInput.prototype, {
} }
function keyEvent(e, keyEventName){ function keyEvent(e, keyEventName){
self.list.show();
var keyEvent = new CustomEvent(keyEventName, { var keyEvent = new CustomEvent(keyEventName, {
detail: { detail: {
hook: self, hook: self,
...@@ -514,7 +524,6 @@ Object.assign(HookInput.prototype, { ...@@ -514,7 +524,6 @@ Object.assign(HookInput.prototype, {
cancelable: true cancelable: true
}); });
e.target.dispatchEvent(keyEvent); e.target.dispatchEvent(keyEvent);
self.list.show();
} }
this.events = this.events || {}; this.events = this.events || {};
...@@ -572,24 +581,43 @@ require('./window')(function(w){ ...@@ -572,24 +581,43 @@ require('./window')(function(w){
module.exports = function(){ module.exports = function(){
var currentKey; var currentKey;
var currentFocus; var currentFocus;
var currentIndex = 0;
var isUpArrow = false; var isUpArrow = false;
var isDownArrow = false; var isDownArrow = false;
var removeHighlight = function removeHighlight(list) { var removeHighlight = function removeHighlight(list) {
var listItems = list.list.querySelectorAll('li'); var listItems = Array.prototype.slice.call(list.list.querySelectorAll('li:not(.divider)'), 0);
var listItemsTmp = [];
for(var i = 0; i < listItems.length; i++) { for(var i = 0; i < listItems.length; i++) {
listItems[i].classList.remove('dropdown-active'); var listItem = listItems[i];
listItem.classList.remove('dropdown-active');
if (listItem.style.display !== 'none') {
listItemsTmp.push(listItem);
}
} }
return listItems; return listItemsTmp;
}; };
var setMenuForArrows = function setMenuForArrows(list) { var setMenuForArrows = function setMenuForArrows(list) {
var listItems = removeHighlight(list); var listItems = removeHighlight(list);
if(currentIndex>0){ if(list.currentIndex>0){
if(!listItems[currentIndex-1]){ if(!listItems[list.currentIndex-1]){
currentIndex = currentIndex-1; list.currentIndex = list.currentIndex-1;
}
if (listItems[list.currentIndex-1]) {
var el = listItems[list.currentIndex-1];
var filterDropdownEl = el.closest('.filter-dropdown');
el.classList.add('dropdown-active');
if (filterDropdownEl) {
var filterDropdownBottom = filterDropdownEl.offsetHeight;
var elOffsetTop = el.offsetTop - 30;
if (elOffsetTop > filterDropdownBottom) {
filterDropdownEl.scrollTop = elOffsetTop - filterDropdownBottom;
}
}
} }
listItems[currentIndex-1].classList.add('dropdown-active');
} }
}; };
...@@ -597,13 +625,13 @@ require('./window')(function(w){ ...@@ -597,13 +625,13 @@ require('./window')(function(w){
var list = e.detail.hook.list; var list = e.detail.hook.list;
removeHighlight(list); removeHighlight(list);
list.show(); list.show();
currentIndex = 0; list.currentIndex = 0;
isUpArrow = false; isUpArrow = false;
isDownArrow = false; isDownArrow = false;
}; };
var selectItem = function selectItem(list) { var selectItem = function selectItem(list) {
var listItems = removeHighlight(list); var listItems = removeHighlight(list);
var currentItem = listItems[currentIndex-1]; var currentItem = listItems[list.currentIndex-1];
var listEvent = new CustomEvent('click.dl', { var listEvent = new CustomEvent('click.dl', {
detail: { detail: {
list: list, list: list,
...@@ -617,6 +645,8 @@ require('./window')(function(w){ ...@@ -617,6 +645,8 @@ require('./window')(function(w){
var keydown = function keydown(e){ var keydown = function keydown(e){
var typedOn = e.target; var typedOn = e.target;
var list = e.detail.hook.list;
var currentIndex = list.currentIndex;
isUpArrow = false; isUpArrow = false;
isDownArrow = false; isDownArrow = false;
...@@ -648,6 +678,7 @@ require('./window')(function(w){ ...@@ -648,6 +678,7 @@ require('./window')(function(w){
if(isUpArrow){ currentIndex--; } if(isUpArrow){ currentIndex--; }
if(isDownArrow){ currentIndex++; } if(isDownArrow){ currentIndex++; }
if(currentIndex < 0){ currentIndex = 0; } if(currentIndex < 0){ currentIndex = 0; }
list.currentIndex = currentIndex;
setMenuForArrows(e.detail.hook.list); setMenuForArrows(e.detail.hook.list);
}; };
......
...@@ -29,6 +29,7 @@ require('../window')(function(w){ ...@@ -29,6 +29,7 @@ require('../window')(function(w){
init: function init(hook) { init: function init(hook) {
var self = this; var self = this;
var config = hook.config.droplabAjax; var config = hook.config.droplabAjax;
this.hook = hook;
if (!config || !config.endpoint || !config.method) { if (!config || !config.endpoint || !config.method) {
return; return;
...@@ -52,19 +53,26 @@ require('../window')(function(w){ ...@@ -52,19 +53,26 @@ require('../window')(function(w){
this._loadUrlData(config.endpoint) this._loadUrlData(config.endpoint)
.then(function(d) { .then(function(d) {
if (config.loadingTemplate) { if (config.loadingTemplate) {
var dataLoadingTemplate = hook.list.list.querySelector('[data-loading-template]'); var dataLoadingTemplate = self.hook.list.list.querySelector('[data-loading-template]');
if (dataLoadingTemplate) { if (dataLoadingTemplate) {
dataLoadingTemplate.outerHTML = self.listTemplate; dataLoadingTemplate.outerHTML = self.listTemplate;
} }
} }
hook.list[config.method].call(hook.list, d);
if (!self.hook.list.hidden) {
self.hook.list[config.method].call(self.hook.list, d);
}
}).catch(function(e) { }).catch(function(e) {
throw new droplabAjaxException(e.message || e); throw new droplabAjaxException(e.message || e);
}); });
}, },
destroy: function() { destroy: function() {
if (this.listTemplate) {
var dynamicList = this.hook.list.list.querySelector('[data-dynamic]');
dynamicList.outerHTML = this.listTemplate;
}
} }
}; };
}); });
...@@ -76,4 +84,4 @@ module.exports = function(callback) { ...@@ -76,4 +84,4 @@ module.exports = function(callback) {
}; };
},{}]},{},[1])(1) },{}]},{},[1])(1)
}); });
\ No newline at end of file
...@@ -93,6 +93,7 @@ require('../window')(function(w){ ...@@ -93,6 +93,7 @@ require('../window')(function(w){
self.hook.list.setData.call(self.hook.list, data); self.hook.list.setData.call(self.hook.list, data);
} }
self.notLoading(); self.notLoading();
self.hook.list.currentIndex = 0;
}); });
}, },
...@@ -142,4 +143,4 @@ module.exports = function(callback) { ...@@ -142,4 +143,4 @@ module.exports = function(callback) {
}; };
},{}]},{},[1])(1) },{}]},{},[1])(1)
}); });
\ No newline at end of file
...@@ -6,6 +6,8 @@ require('../window')(function(w){ ...@@ -6,6 +6,8 @@ require('../window')(function(w){
w.droplabFilter = { w.droplabFilter = {
keydownWrapper: function(e){ keydownWrapper: function(e){
var hiddenCount = 0;
var dataHiddenCount = 0;
var list = e.detail.hook.list; var list = e.detail.hook.list;
var data = list.data; var data = list.data;
var value = e.detail.hook.trigger.value.toLowerCase(); var value = e.detail.hook.trigger.value.toLowerCase();
...@@ -27,10 +29,22 @@ require('../window')(function(w){ ...@@ -27,10 +29,22 @@ require('../window')(function(w){
}; };
} }
dataHiddenCount = data.filter(function(o) {
return !o.droplab_hidden;
}).length;
matches = data.map(function(o) { matches = data.map(function(o) {
return filterFunction(o, value); return filterFunction(o, value);
}); });
list.render(matches);
hiddenCount = matches.filter(function(o) {
return !o.droplab_hidden;
}).length;
if (dataHiddenCount !== hiddenCount) {
list.render(matches);
list.currentIndex = 0;
}
}, },
init: function init(hookInput) { init: function init(hookInput) {
...@@ -57,4 +71,4 @@ module.exports = function(callback) { ...@@ -57,4 +71,4 @@ module.exports = function(callback) {
}; };
},{}]},{},[1])(1) },{}]},{},[1])(1)
}); });
\ No newline at end of file
...@@ -3,7 +3,6 @@ ...@@ -3,7 +3,6 @@
//= require ./components/environment //= require ./components/environment
//= require ./vue_resource_interceptor //= require ./vue_resource_interceptor
$(() => { $(() => {
window.gl = window.gl || {}; window.gl = window.gl || {};
......
...@@ -84,7 +84,7 @@ ...@@ -84,7 +84,7 @@
let inputValue = input.value; let inputValue = input.value;
// Replace all spaces inside quote marks with underscores // Replace all spaces inside quote marks with underscores
// This helps with matching the beginning & end of a token:key // This helps with matching the beginning & end of a token:key
inputValue = inputValue.replace(/"(.*?)"/g, str => str.replace(/\s/g, '_')); inputValue = inputValue.replace(/("(.*?)"|:\s+)/g, str => str.replace(/\s/g, '_'));
// Get the right position for the word selected // Get the right position for the word selected
// Regex matches first space // Regex matches first space
......
...@@ -64,13 +64,26 @@ ...@@ -64,13 +64,26 @@
} }
checkForEnter(e) { checkForEnter(e) {
if (e.keyCode === 38 || e.keyCode === 40) {
const selectionStart = this.filteredSearchInput.selectionStart;
e.preventDefault();
this.filteredSearchInput.setSelectionRange(selectionStart, selectionStart);
}
if (e.keyCode === 13) { if (e.keyCode === 13) {
const dropdown = this.dropdownManager.mapping[this.dropdownManager.currentDropdown];
const dropdownEl = dropdown.element;
const activeElements = dropdownEl.querySelectorAll('.dropdown-active');
e.preventDefault(); e.preventDefault();
// Prevent droplab from opening dropdown if (!activeElements.length) {
this.dropdownManager.destroyDroplab(); // Prevent droplab from opening dropdown
this.dropdownManager.destroyDroplab();
this.search(); this.search();
}
} }
} }
......
...@@ -61,7 +61,6 @@ ...@@ -61,7 +61,6 @@
return labels; return labels;
} }
/** /**
* Will return only labels that were marked previously and the user has unmarked * Will return only labels that were marked previously and the user has unmarked
* @return {Array} Label IDs * @return {Array} Label IDs
...@@ -80,7 +79,6 @@ ...@@ -80,7 +79,6 @@
return result; return result;
} }
/** /**
* Simple form serialization, it will return just what we need * Simple form serialization, it will return just what we need
* Returns key/value pairs from form data * Returns key/value pairs from form data
......
...@@ -215,5 +215,19 @@ ...@@ -215,5 +215,19 @@
const matchingNodes = parentNode.querySelectorAll(selector); const matchingNodes = parentNode.querySelectorAll(selector);
return Array.prototype.indexOf.call(matchingNodes, node) !== -1; return Array.prototype.indexOf.call(matchingNodes, node) !== -1;
}; };
/**
this will take in the headers from an API response and normalize them
this way we don't run into production issues when nginx gives us lowercased header keys
*/
w.gl.utils.normalizeHeaders = (headers) => {
const upperCaseHeaders = {};
Object.keys(headers).forEach((e) => {
upperCaseHeaders[e.toUpperCase()] = headers[e];
});
return upperCaseHeaders;
};
})(window); })(window);
}).call(this); }).call(this);
...@@ -220,7 +220,6 @@ ...@@ -220,7 +220,6 @@
})(this)); })(this));
}; };
/* /*
Increase @pollingInterval up to 120 seconds on every function call, Increase @pollingInterval up to 120 seconds on every function call,
if `shouldReset` has a truthy value, 'null' or 'undefined' the variable if `shouldReset` has a truthy value, 'null' or 'undefined' the variable
...@@ -244,7 +243,6 @@ ...@@ -244,7 +243,6 @@
return this.initRefresh(); return this.initRefresh();
}; };
Notes.prototype.handleCreateChanges = function(note) { Notes.prototype.handleCreateChanges = function(note) {
if (typeof note === 'undefined') { if (typeof note === 'undefined') {
return; return;
...@@ -294,7 +292,6 @@ ...@@ -294,7 +292,6 @@
} }
}; };
/* /*
Check if note does not exists on page Check if note does not exists on page
*/ */
...@@ -307,7 +304,6 @@ ...@@ -307,7 +304,6 @@
return this.view === 'parallel'; return this.view === 'parallel';
}; };
/* /*
Render note in discussion area. Render note in discussion area.
...@@ -358,7 +354,6 @@ ...@@ -358,7 +354,6 @@
return this.updateNotesCount(1); return this.updateNotesCount(1);
}; };
/* /*
Called in response the main target form has been successfully submitted. Called in response the main target form has been successfully submitted.
...@@ -390,7 +385,6 @@ ...@@ -390,7 +385,6 @@
return form.find(".js-note-text").trigger("input"); return form.find(".js-note-text").trigger("input");
}; };
/* /*
Shows the main form and does some setup on it. Shows the main form and does some setup on it.
...@@ -415,7 +409,6 @@ ...@@ -415,7 +409,6 @@
return this.parentTimeline = form.parents('.timeline'); return this.parentTimeline = form.parents('.timeline');
}; };
/* /*
General note form setup. General note form setup.
...@@ -432,7 +425,6 @@ ...@@ -432,7 +425,6 @@
return new Autosave(textarea, ["Note", form.find("#note_noteable_type").val(), form.find("#note_noteable_id").val(), form.find("#note_commit_id").val(), form.find("#note_type").val(), form.find("#note_line_code").val(), form.find("#note_position").val()]); return new Autosave(textarea, ["Note", form.find("#note_noteable_type").val(), form.find("#note_noteable_id").val(), form.find("#note_commit_id").val(), form.find("#note_type").val(), form.find("#note_line_code").val(), form.find("#note_position").val()]);
}; };
/* /*
Called in response to the new note form being submitted Called in response to the new note form being submitted
...@@ -448,7 +440,6 @@ ...@@ -448,7 +440,6 @@
return new Flash('Your comment could not be submitted! Please check your network connection and try again.', 'alert', this.parentTimeline); return new Flash('Your comment could not be submitted! Please check your network connection and try again.', 'alert', this.parentTimeline);
}; };
/* /*
Called in response to the new note form being submitted Called in response to the new note form being submitted
...@@ -473,7 +464,6 @@ ...@@ -473,7 +464,6 @@
this.removeDiscussionNoteForm($form); this.removeDiscussionNoteForm($form);
}; };
/* /*
Called in response to the edit note form being submitted Called in response to the edit note form being submitted
...@@ -498,7 +488,6 @@ ...@@ -498,7 +488,6 @@
} }
}; };
Notes.prototype.checkContentToAllowEditing = function($el) { Notes.prototype.checkContentToAllowEditing = function($el) {
var initialContent = $el.find('.original-note-content').text().trim(); var initialContent = $el.find('.original-note-content').text().trim();
var currentContent = $el.find('.note-textarea').val(); var currentContent = $el.find('.note-textarea').val();
...@@ -522,7 +511,6 @@ ...@@ -522,7 +511,6 @@
return isAllowed; return isAllowed;
}; };
/* /*
Called in response to clicking the edit note link Called in response to clicking the edit note link
...@@ -551,7 +539,6 @@ ...@@ -551,7 +539,6 @@
this.putEditFormInPlace($target); this.putEditFormInPlace($target);
}; };
/* /*
Called in response to clicking the edit note link Called in response to clicking the edit note link
...@@ -596,7 +583,6 @@ ...@@ -596,7 +583,6 @@
return form.find('.js-note-text').val(form.find('form.edit-note').data('original-note')); return form.find('.js-note-text').val(form.find('form.edit-note').data('original-note'));
}; };
/* /*
Called in response to deleting a note of any kind. Called in response to deleting a note of any kind.
...@@ -636,7 +622,6 @@ ...@@ -636,7 +622,6 @@
return this.updateNotesCount(-1); return this.updateNotesCount(-1);
}; };
/* /*
Called in response to clicking the delete attachment link Called in response to clicking the delete attachment link
...@@ -653,7 +638,6 @@ ...@@ -653,7 +638,6 @@
return note.find(".current-note-edit-form").remove(); return note.find(".current-note-edit-form").remove();
}; };
/* /*
Called when clicking on the "reply" button for a diff line. Called when clicking on the "reply" button for a diff line.
...@@ -673,7 +657,6 @@ ...@@ -673,7 +657,6 @@
return this.setupDiscussionNoteForm(replyLink, form); return this.setupDiscussionNoteForm(replyLink, form);
}; };
/* /*
Shows the diff or discussion form and does some setup on it. Shows the diff or discussion form and does some setup on it.
...@@ -715,7 +698,6 @@ ...@@ -715,7 +698,6 @@
.addClass("discussion-form js-discussion-note-form"); .addClass("discussion-form js-discussion-note-form");
}; };
/* /*
Called when clicking on the "add a comment" button on the side of a diff line. Called when clicking on the "add a comment" button on the side of a diff line.
...@@ -772,7 +754,6 @@ ...@@ -772,7 +754,6 @@
} }
}; };
/* /*
Called in response to "cancel" on a diff note form. Called in response to "cancel" on a diff note form.
...@@ -806,7 +787,6 @@ ...@@ -806,7 +787,6 @@
return this.removeDiscussionNoteForm(form); return this.removeDiscussionNoteForm(form);
}; };
/* /*
Called after an attachment file has been selected. Called after an attachment file has been selected.
...@@ -821,7 +801,6 @@ ...@@ -821,7 +801,6 @@
return form.find(".js-attachment-filename").text(filename); return form.find(".js-attachment-filename").text(filename);
}; };
/* /*
Called when the tab visibility changes Called when the tab visibility changes
*/ */
......
(() => {
class VersionCheckImage {
static bindErrorEvent(imageElement) {
imageElement.off('error').on('error', () => imageElement.hide());
}
}
window.gl = window.gl || {};
gl.VersionCheckImage = VersionCheckImage;
})();
...@@ -22,47 +22,51 @@ ...@@ -22,47 +22,51 @@
<div class="controls pull-right"> <div class="controls pull-right">
<div class="btn-group inline"> <div class="btn-group inline">
<div class="btn-group"> <div class="btn-group">
<a <button
v-if='actions' v-if='actions'
class="dropdown-toggle btn btn-default js-pipeline-dropdown-manual-actions" class="dropdown-toggle btn btn-default has-tooltip js-pipeline-dropdown-manual-actions"
data-toggle="dropdown" data-toggle="dropdown"
title="Manual build" title="Manual build"
alt="Manual Build" data-placement="top"
data-toggle="dropdown"
aria-label="Manual build"
> >
<span v-html='svgs.iconPlay'></span> <span v-html='svgs.iconPlay' aria-hidden="true"></span>
<i class="fa fa-caret-down"></i> <i class="fa fa-caret-down" aria-hidden="true"></i>
</a> </button>
<ul class="dropdown-menu dropdown-menu-align-right"> <ul class="dropdown-menu dropdown-menu-align-right">
<li v-for='action in pipeline.details.manual_actions'> <li v-for='action in pipeline.details.manual_actions'>
<a <a
rel="nofollow" rel="nofollow"
data-method="post" data-method="post"
:href='action.path' :href='action.path'
title="Manual build"
> >
<span v-html='svgs.iconPlay'></span> <span v-html='svgs.iconPlay' aria-hidden="true"></span>
<span title="Manual build">{{action.name}}</span> <span>{{action.name}}</span>
</a> </a>
</li> </li>
</ul> </ul>
</div> </div>
<div class="btn-group"> <div class="btn-group">
<a <button
v-if='artifacts' v-if='artifacts'
class="dropdown-toggle btn btn-default build-artifacts js-pipeline-dropdown-download" class="dropdown-toggle btn btn-default build-artifacts has-tooltip js-pipeline-dropdown-download"
data-toggle="dropdown"
title="Artifacts"
data-placement="top"
data-toggle="dropdown" data-toggle="dropdown"
type="button" aria-label="Artifacts"
> >
<i class="fa fa-download"></i> <i class="fa fa-download" aria-hidden="true"></i>
<i class="fa fa-caret-down"></i> <i class="fa fa-caret-down" aria-hidden="true"></i>
</a> </button>
<ul class="dropdown-menu dropdown-menu-align-right"> <ul class="dropdown-menu dropdown-menu-align-right">
<li v-for='artifact in pipeline.details.artifacts'> <li v-for='artifact in pipeline.details.artifacts'>
<a <a
rel="nofollow" rel="nofollow"
:href='artifact.path' :href='artifact.path'
> >
<i class="fa fa-download"></i> <i class="fa fa-download" aria-hidden="true"></i>
<span>{{download(artifact.name)}}</span> <span>{{download(artifact.name)}}</span>
</a> </a>
</li> </li>
...@@ -76,9 +80,12 @@ ...@@ -76,9 +80,12 @@
title="Retry" title="Retry"
rel="nofollow" rel="nofollow"
data-method="post" data-method="post"
data-placement="top"
data-toggle="dropdown"
:href='pipeline.retry_path' :href='pipeline.retry_path'
aria-label="Retry"
> >
<i class="fa fa-repeat"></i> <i class="fa fa-repeat" aria-hidden="true"></i>
</a> </a>
<a <a
v-if='pipeline.flags.cancelable' v-if='pipeline.flags.cancelable'
...@@ -86,10 +93,12 @@ ...@@ -86,10 +93,12 @@
title="Cancel" title="Cancel"
rel="nofollow" rel="nofollow"
data-method="post" data-method="post"
data-placement="top"
data-toggle="dropdown"
:href='pipeline.cancel_path' :href='pipeline.cancel_path'
data-original-title="Cancel" aria-label="Cancel"
> >
<i class="fa fa-remove"></i> <i class="fa fa-remove" aria-hidden="true"></i>
</a> </a>
</div> </div>
</div> </div>
......
...@@ -82,12 +82,13 @@ ...@@ -82,12 +82,13 @@
data-placement="top" data-placement="top"
data-toggle="dropdown" data-toggle="dropdown"
type="button" type="button"
:aria-label='stage.title'
> >
<span v-html="svg"></span> <span v-html="svg" aria-hidden="true"></span>
<i class="fa fa-caret-down "></i> <i class="fa fa-caret-down" aria-hidden="true"></i>
</button> </button>
<ul class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container"> <ul class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container">
<div class="arrow-up"></div> <div class="arrow-up" aria-hidden="true"></div>
<div <div
@click='keepGraph($event)' @click='keepGraph($event)'
:class="dropdownClass" :class="dropdownClass"
......
...@@ -4,19 +4,15 @@ ...@@ -4,19 +4,15 @@
((gl) => { ((gl) => {
const pageValues = (headers) => { const pageValues = (headers) => {
const normalizedHeaders = {}; const normalized = gl.utils.normalizeHeaders(headers);
Object.keys(headers).forEach((e) => {
normalizedHeaders[e.toUpperCase()] = headers[e];
});
const paginationInfo = { const paginationInfo = {
perPage: +normalizedHeaders['X-PER-PAGE'], perPage: +normalized['X-PER-PAGE'],
page: +normalizedHeaders['X-PAGE'], page: +normalized['X-PAGE'],
total: +normalizedHeaders['X-TOTAL'], total: +normalized['X-TOTAL'],
totalPages: +normalizedHeaders['X-TOTAL-PAGES'], totalPages: +normalized['X-TOTAL-PAGES'],
nextPage: +normalizedHeaders['X-NEXT-PAGE'], nextPage: +normalized['X-NEXT-PAGE'],
previousPage: +normalizedHeaders['X-PREV-PAGE'], previousPage: +normalized['X-PREV-PAGE'],
}; };
return paginationInfo; return paginationInfo;
......
...@@ -82,7 +82,12 @@ ...@@ -82,7 +82,12 @@
} }
.block-controls { .block-controls {
float: right; display: -webkit-flex;
display: flex;
-webkit-justify-content: flex-end;
justify-content: flex-end;
-webkit-flex: 1;
flex: 1;
.control { .control {
float: left; float: left;
...@@ -282,3 +287,8 @@ ...@@ -282,3 +287,8 @@
} }
} }
} }
.flex-container-block {
display: -webkit-flex;
display: flex;
}
...@@ -79,6 +79,16 @@ ...@@ -79,6 +79,16 @@
overflow: auto; overflow: auto;
} }
%filter-dropdown-item-btn-hover {
background-color: $dropdown-hover-color;
color: $white-light;
text-decoration: none;
.avatar {
border-color: $white-light;
}
}
.filter-dropdown-item { .filter-dropdown-item {
.btn { .btn {
border: none; border: none;
...@@ -103,13 +113,7 @@ ...@@ -103,13 +113,7 @@
&:hover, &:hover,
&:focus { &:focus {
background-color: $dropdown-hover-color; @extend %filter-dropdown-item-btn-hover;
color: $white-light;
text-decoration: none;
.avatar {
border-color: $white-light;
}
} }
} }
...@@ -131,6 +135,12 @@ ...@@ -131,6 +135,12 @@
} }
} }
.filter-dropdown-item.dropdown-active {
.btn {
@extend %filter-dropdown-item-btn-hover;
}
}
.hint-dropdown { .hint-dropdown {
width: 250px; width: 250px;
} }
......
...@@ -203,6 +203,10 @@ ...@@ -203,6 +203,10 @@
position: relative; position: relative;
margin-right: 6px; margin-right: 6px;
.tooltip {
white-space: nowrap;
}
.tooltip-inner { .tooltip-inner {
padding: 3px 4px; padding: 3px 4px;
} }
...@@ -288,6 +292,10 @@ ...@@ -288,6 +292,10 @@
} }
} }
} }
.tooltip {
white-space: nowrap;
}
} }
.build-link { .build-link {
......
...@@ -929,8 +929,32 @@ pre.light-well { ...@@ -929,8 +929,32 @@ pre.light-well {
.variables-table { .variables-table {
table-layout: fixed; table-layout: fixed;
&.table-responsive {
border: none;
}
.variable-key { .variable-key {
width: 30%; width: 300px;
max-width: 300px;
overflow: hidden;
word-wrap: break-word;
// override bootstrap
white-space: normal!important;
@media (max-width: $screen-sm-max) {
width: 150px;
max-width: 150px;
}
}
.variable-value {
@media(max-width: $screen-xs-max) {
width: 150px;
max-width: 150px;
overflow: hidden;
word-wrap: break-word;
}
} }
} }
......
...@@ -5,7 +5,11 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -5,7 +5,11 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
end end
def update def update
if @application_setting.update_attributes(application_setting_params) successful = ApplicationSettings::UpdateService
.new(@application_setting, current_user, application_setting_params)
.execute
if successful
redirect_to admin_application_settings_path, redirect_to admin_application_settings_path,
notice: 'Application settings saved successfully' notice: 'Application settings saved successfully'
else else
......
...@@ -3,7 +3,7 @@ module CompareHelper ...@@ -3,7 +3,7 @@ module CompareHelper
from.present? && from.present? &&
to.present? && to.present? &&
from != to && from != to &&
project.feature_available?(:merge_requests, current_user) && can?(current_user, :create_merge_request, project) &&
project.repository.branch_names.include?(from) && project.repository.branch_names.include?(from) &&
project.repository.branch_names.include?(to) project.repository.branch_names.include?(to)
end end
......
...@@ -14,7 +14,7 @@ module GroupsHelper ...@@ -14,7 +14,7 @@ module GroupsHelper
def group_title(group, name = nil, url = nil) def group_title(group, name = nil, url = nil)
full_title = '' full_title = ''
group.parents.each do |parent| group.ancestors.each do |parent|
full_title += link_to(simple_sanitize(parent.name), group_path(parent)) full_title += link_to(simple_sanitize(parent.name), group_path(parent))
full_title += ' / '.html_safe full_title += ' / '.html_safe
end end
......
module VersionCheckHelper module VersionCheckHelper
def version_status_badge def version_status_badge
if Rails.env.production? && current_application_settings.version_check_enabled if Rails.env.production? && current_application_settings.version_check_enabled
image_tag VersionCheck.new.url image_url = VersionCheck.new.url
image_tag image_url, class: 'js-version-status-badge'
end end
end end
end end
...@@ -38,6 +38,14 @@ module Emails ...@@ -38,6 +38,14 @@ module Emails
mail_answer_thread(@snippet, note_thread_options(recipient_id)) mail_answer_thread(@snippet, note_thread_options(recipient_id))
end end
def note_personal_snippet_email(recipient_id, note_id)
setup_note_mail(note_id, recipient_id)
@snippet = @note.noteable
@target_url = snippet_url(@note.noteable)
mail_answer_thread(@snippet, note_thread_options(recipient_id))
end
private private
def note_target_url_options def note_target_url_options
......
...@@ -22,6 +22,17 @@ class Ability ...@@ -22,6 +22,17 @@ class Ability
end end
end end
# Given a list of users and a snippet this method returns the users that can
# read the given snippet.
def users_that_can_read_personal_snippet(users, snippet)
case snippet.visibility_level
when Snippet::INTERNAL, Snippet::PUBLIC
users
when Snippet::PRIVATE
users.include?(snippet.author) ? [snippet.author] : []
end
end
# Returns an Array of Issues that can be read by the given user. # Returns an Array of Issues that can be read by the given user.
# #
# issues - The issues to reduce down to those readable by the user. # issues - The issues to reduce down to those readable by the user.
......
...@@ -13,6 +13,49 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -13,6 +13,49 @@ class ApplicationSetting < ActiveRecord::Base
[\r\n] # any number of newline characters [\r\n] # any number of newline characters
}x }x
DEFAULTS_CE = {
after_sign_up_text: nil,
akismet_enabled: false,
container_registry_token_expire_delay: 5,
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'],
default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'],
disabled_oauth_sign_in_sources: [],
domain_whitelist: Settings.gitlab['domain_whitelist'],
gravatar_enabled: Settings.gravatar['enabled'],
help_page_text: nil,
housekeeping_bitmaps_enabled: true,
housekeeping_enabled: true,
housekeeping_full_repack_period: 50,
housekeeping_gc_period: 200,
housekeeping_incremental_repack_period: 10,
import_sources: Gitlab::ImportSources.values,
koding_enabled: false,
koding_url: nil,
max_artifacts_size: Settings.artifacts['max_size'],
max_attachment_size: Settings.gitlab['max_attachment_size'],
plantuml_enabled: false,
plantuml_url: nil,
recaptcha_enabled: false,
repository_checks_enabled: true,
repository_storages: ['default'],
require_two_factor_authentication: false,
restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'],
session_expire_delay: Settings.gitlab['session_expire_delay'],
send_user_confirmation_email: false,
shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'],
shared_runners_text: nil,
sidekiq_throttling_enabled: false,
sign_in_text: nil,
signin_enabled: Settings.gitlab['signin_enabled'],
signup_enabled: Settings.gitlab['signup_enabled'],
two_factor_grace_period: 48,
user_default_external: false
}
DEFAULTS = DEFAULTS_CE
serialize :restricted_visibility_levels serialize :restricted_visibility_levels
serialize :import_sources serialize :import_sources
serialize :disabled_oauth_sign_in_sources, Array serialize :disabled_oauth_sign_in_sources, Array
...@@ -163,46 +206,7 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -163,46 +206,7 @@ class ApplicationSetting < ActiveRecord::Base
end end
def self.create_from_defaults def self.create_from_defaults
create( create(DEFAULTS)
default_projects_limit: Settings.gitlab['default_projects_limit'],
default_branch_protection: Settings.gitlab['default_branch_protection'],
signup_enabled: Settings.gitlab['signup_enabled'],
signin_enabled: Settings.gitlab['signin_enabled'],
gravatar_enabled: Settings.gravatar['enabled'],
sign_in_text: nil,
after_sign_up_text: nil,
help_page_text: nil,
shared_runners_text: nil,
restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'],
max_attachment_size: Settings.gitlab['max_attachment_size'],
session_expire_delay: Settings.gitlab['session_expire_delay'],
default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'],
default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'],
domain_whitelist: Settings.gitlab['domain_whitelist'],
import_sources: Gitlab::ImportSources.values,
shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'],
max_artifacts_size: Settings.artifacts['max_size'],
require_two_factor_authentication: false,
two_factor_grace_period: 48,
recaptcha_enabled: false,
akismet_enabled: false,
koding_enabled: false,
koding_url: nil,
plantuml_enabled: false,
plantuml_url: nil,
repository_checks_enabled: true,
disabled_oauth_sign_in_sources: [],
send_user_confirmation_email: false,
container_registry_token_expire_delay: 5,
repository_storages: ['default'],
user_default_external: false,
sidekiq_throttling_enabled: false,
housekeeping_enabled: true,
housekeeping_bitmaps_enabled: true,
housekeeping_incremental_repack_period: 10,
housekeeping_full_repack_period: 50,
housekeeping_gc_period: 200,
)
end end
def home_page_url_column_exist def home_page_url_column_exist
......
...@@ -51,6 +51,10 @@ module CacheMarkdownField ...@@ -51,6 +51,10 @@ module CacheMarkdownField
CACHING_CLASSES.map(&:constantize) CACHING_CLASSES.map(&:constantize)
end end
def skip_project_check?
false
end
extend ActiveSupport::Concern extend ActiveSupport::Concern
included do included do
...@@ -112,7 +116,8 @@ module CacheMarkdownField ...@@ -112,7 +116,8 @@ module CacheMarkdownField
invalidation_method = "#{html_field}_invalidated?".to_sym invalidation_method = "#{html_field}_invalidated?".to_sym
define_method(cache_method) do define_method(cache_method) do
html = Banzai::Renderer.cacheless_render_field(self, markdown_field) options = { skip_project_check: skip_project_check? }
html = Banzai::Renderer.cacheless_render_field(self, markdown_field, options)
__send__("#{html_field}=", html) __send__("#{html_field}=", html)
true true
end end
......
...@@ -49,7 +49,11 @@ module Mentionable ...@@ -49,7 +49,11 @@ module Mentionable
self.class.mentionable_attrs.each do |attr, options| self.class.mentionable_attrs.each do |attr, options|
text = __send__(attr) text = __send__(attr)
options = options.merge(cache_key: [self, attr], author: author) options = options.merge(
cache_key: [self, attr],
author: author,
skip_project_check: skip_project_check?
)
extractor.analyze(text, options) extractor.analyze(text, options)
end end
...@@ -121,4 +125,8 @@ module Mentionable ...@@ -121,4 +125,8 @@ module Mentionable
def cross_reference_exists?(target) def cross_reference_exists?(target)
SystemNoteService.cross_reference_exists?(target, local_reference) SystemNoteService.cross_reference_exists?(target, local_reference)
end end
def skip_project_check?
false
end
end end
...@@ -96,6 +96,11 @@ module Participable ...@@ -96,6 +96,11 @@ module Participable
participants.merge(ext.users) participants.merge(ext.users)
Ability.users_that_can_read_project(participants.to_a, project) case self
when PersonalSnippet
Ability.users_that_can_read_personal_snippet(participants.to_a, self)
else
Ability.users_that_can_read_project(participants.to_a, project)
end
end end
end end
...@@ -60,6 +60,21 @@ module Routable ...@@ -60,6 +60,21 @@ module Routable
joins(:route).where(wheres.join(' OR ')) joins(:route).where(wheres.join(' OR '))
end end
end end
# Builds a relation to find multiple objects that are nested under user membership
#
# Usage:
#
# Klass.member_descendants(1)
#
# Returns an ActiveRecord::Relation.
def member_descendants(user_id)
joins(:route).
joins("INNER JOIN routes r2 ON routes.path LIKE CONCAT(r2.path, '/%')
INNER JOIN members ON members.source_id = r2.source_id
AND members.source_type = r2.source_type").
where('members.user_id = ?', user_id)
end
end end
private private
......
...@@ -201,7 +201,7 @@ class Group < Namespace ...@@ -201,7 +201,7 @@ class Group < Namespace
end end
def members_with_parents def members_with_parents
GroupMember.where(requested_at: nil, source_id: parents.map(&:id).push(id)) GroupMember.where(requested_at: nil, source_id: ancestors.map(&:id).push(id))
end end
def users_with_parents def users_with_parents
......
...@@ -865,9 +865,11 @@ class MergeRequest < ActiveRecord::Base ...@@ -865,9 +865,11 @@ class MergeRequest < ActiveRecord::Base
paths: paths paths: paths
) )
active_diff_notes.each do |note| transaction do
service.execute(note) active_diff_notes.each do |note|
Gitlab::Timeless.timeless(note, &:save) service.execute(note)
Gitlab::Timeless.timeless(note, &:save)
end
end end
end end
......
...@@ -185,8 +185,26 @@ class Namespace < ActiveRecord::Base ...@@ -185,8 +185,26 @@ class Namespace < ActiveRecord::Base
end end
end end
def parents # Scopes the model on ancestors of the record
@parents ||= parent ? parent.parents + [parent] : [] def ancestors
if parent_id
path = route.path
paths = []
until path.blank?
path = path.rpartition('/').first
paths << path
end
self.class.joins(:route).where('routes.path IN (?)', paths).reorder('routes.path ASC')
else
self.class.none
end
end
# Scopes the model on direct and indirect children of the record
def descendants
self.class.joins(:route).where('routes.path LIKE ?', "#{route.path}/%").reorder('routes.path ASC')
end end
private private
......
...@@ -43,7 +43,8 @@ class Note < ActiveRecord::Base ...@@ -43,7 +43,8 @@ class Note < ActiveRecord::Base
delegate :name, :email, to: :author, prefix: true delegate :name, :email, to: :author, prefix: true
delegate :title, to: :noteable, allow_nil: true delegate :title, to: :noteable, allow_nil: true
validates :note, :project, presence: true validates :note, presence: true
validates :project, presence: true, unless: :for_personal_snippet?
# Attachments are deprecated and are handled by Markdown uploader # Attachments are deprecated and are handled by Markdown uploader
validates :attachment, file_size: { maximum: :max_attachment_size } validates :attachment, file_size: { maximum: :max_attachment_size }
...@@ -53,7 +54,7 @@ class Note < ActiveRecord::Base ...@@ -53,7 +54,7 @@ class Note < ActiveRecord::Base
validates :commit_id, presence: true, if: :for_commit? validates :commit_id, presence: true, if: :for_commit?
validates :author, presence: true validates :author, presence: true
validate unless: [:for_commit?, :importing?] do |note| validate unless: [:for_commit?, :importing?, :for_personal_snippet?] do |note|
unless note.noteable.try(:project) == note.project unless note.noteable.try(:project) == note.project
errors.add(:invalid_project, 'Note and noteable project mismatch') errors.add(:invalid_project, 'Note and noteable project mismatch')
end end
...@@ -83,7 +84,7 @@ class Note < ActiveRecord::Base ...@@ -83,7 +84,7 @@ class Note < ActiveRecord::Base
after_initialize :ensure_discussion_id after_initialize :ensure_discussion_id
before_validation :nullify_blank_type, :nullify_blank_line_code before_validation :nullify_blank_type, :nullify_blank_line_code
before_validation :set_discussion_id before_validation :set_discussion_id
after_save :keep_around_commit after_save :keep_around_commit, unless: :for_personal_snippet?
class << self class << self
def model_name def model_name
...@@ -165,6 +166,14 @@ class Note < ActiveRecord::Base ...@@ -165,6 +166,14 @@ class Note < ActiveRecord::Base
noteable_type == "Snippet" noteable_type == "Snippet"
end end
def for_personal_snippet?
noteable.is_a?(PersonalSnippet)
end
def skip_project_check?
for_personal_snippet?
end
# override to return commits, which are not active record # override to return commits, which are not active record
def noteable def noteable
if for_commit? if for_commit?
...@@ -220,6 +229,10 @@ class Note < ActiveRecord::Base ...@@ -220,6 +229,10 @@ class Note < ActiveRecord::Base
note.match(Banzai::Filter::EmojiFilter.emoji_pattern)[1] note.match(Banzai::Filter::EmojiFilter.emoji_pattern)[1]
end end
def to_ability_name
for_personal_snippet? ? 'personal_snippet' : noteable_type.underscore
end
private private
def keep_around_commit def keep_around_commit
......
...@@ -8,15 +8,16 @@ class Route < ActiveRecord::Base ...@@ -8,15 +8,16 @@ class Route < ActiveRecord::Base
presence: true, presence: true,
uniqueness: { case_sensitive: false } uniqueness: { case_sensitive: false }
after_update :rename_children, if: :path_changed? after_update :rename_descendants, if: :path_changed?
def rename_children def rename_descendants
# We update each row separately because MySQL does not have regexp_replace. # We update each row separately because MySQL does not have regexp_replace.
# rubocop:disable Rails/FindEach # rubocop:disable Rails/FindEach
Route.where('path LIKE ?', "#{path_was}/%").each do |route| Route.where('path LIKE ?', "#{path_was}/%").each do |route|
# Note that update column skips validation and callbacks. # Note that update column skips validation and callbacks.
# We need this to avoid recursive call of rename_children method # We need this to avoid recursive call of rename_descendants method
route.update_column(:path, route.path.sub(path_was, path)) route.update_column(:path, route.path.sub(path_was, path))
end end
# rubocop:enable Rails/FindEach
end end
end end
...@@ -179,8 +179,8 @@ class User < ActiveRecord::Base ...@@ -179,8 +179,8 @@ class User < ActiveRecord::Base
scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all } scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all }
scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM members WHERE user_id IS NOT NULL AND requested_at IS NULL)') } scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM members WHERE user_id IS NOT NULL AND requested_at IS NULL)') }
scope :todo_authors, ->(user_id, state) { where(id: Todo.where(user_id: user_id, state: state).select(:author_id)) } scope :todo_authors, ->(user_id, state) { where(id: Todo.where(user_id: user_id, state: state).select(:author_id)) }
scope :order_recent_sign_in, -> { reorder(last_sign_in_at: :desc) } scope :order_recent_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('last_sign_in_at', 'DESC')) }
scope :order_oldest_sign_in, -> { reorder(last_sign_in_at: :asc) } scope :order_oldest_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('last_sign_in_at', 'ASC')) }
def self.with_two_factor def self.with_two_factor
joins("LEFT OUTER JOIN u2f_registrations AS u2f ON u2f.user_id = users.id"). joins("LEFT OUTER JOIN u2f_registrations AS u2f ON u2f.user_id = users.id").
...@@ -439,6 +439,15 @@ class User < ActiveRecord::Base ...@@ -439,6 +439,15 @@ class User < ActiveRecord::Base
Group.where("namespaces.id IN (#{union.to_sql})") Group.where("namespaces.id IN (#{union.to_sql})")
end end
def nested_groups
Group.member_descendants(id)
end
def nested_projects
Project.joins(:namespace).where('namespaces.parent_id IS NOT NULL').
member_descendants(id)
end
def refresh_authorized_projects def refresh_authorized_projects
Users::RefreshAuthorizedProjectsService.new(self).execute Users::RefreshAuthorizedProjectsService.new(self).execute
end end
......
module ApplicationSettings
class BaseService < ::BaseService
def initialize(application_setting, user, params = {})
@application_setting, @current_user, @params = application_setting, user, params.dup
end
end
end
module ApplicationSettings
class UpdateService < ApplicationSettings::BaseService
def execute
@application_setting.update(@params)
end
end
end
...@@ -3,9 +3,10 @@ module Notes ...@@ -3,9 +3,10 @@ module Notes
def execute def execute
merge_request_diff_head_sha = params.delete(:merge_request_diff_head_sha) merge_request_diff_head_sha = params.delete(:merge_request_diff_head_sha)
note = project.notes.new(params) note = Note.new(params)
note.author = current_user note.project = project
note.system = false note.author = current_user
note.system = false
if note.award_emoji? if note.award_emoji?
noteable = note.noteable noteable = note.noteable
......
...@@ -10,6 +10,9 @@ module Notes ...@@ -10,6 +10,9 @@ module Notes
# Skip system notes, like status changes and cross-references and awards # Skip system notes, like status changes and cross-references and awards
unless @note.system? unless @note.system?
EventCreateService.new.leave_note(@note, @note.author) EventCreateService.new.leave_note(@note, @note.author)
return if @note.for_personal_snippet?
@note.create_cross_references! @note.create_cross_references!
execute_note_hooks execute_note_hooks
end end
......
...@@ -12,7 +12,7 @@ module Notes ...@@ -12,7 +12,7 @@ module Notes
def self.supported?(note, current_user) def self.supported?(note, current_user)
noteable_update_service(note) && noteable_update_service(note) &&
current_user && current_user &&
current_user.can?(:"update_#{note.noteable_type.underscore}", note.noteable) current_user.can?(:"update_#{note.to_ability_name}", note.noteable)
end end
def supported?(note) def supported?(note)
......
...@@ -178,8 +178,15 @@ class NotificationService ...@@ -178,8 +178,15 @@ class NotificationService
recipients = [] recipients = []
mentioned_users = note.mentioned_users mentioned_users = note.mentioned_users
ability, subject = if note.for_personal_snippet?
[:read_personal_snippet, note.noteable]
else
[:read_project, note.project]
end
mentioned_users.select! do |user| mentioned_users.select! do |user|
user.can?(:read_project, note.project) user.can?(ability, subject)
end end
# Add all users participating in the thread (author, assignee, comment authors) # Add all users participating in the thread (author, assignee, comment authors)
...@@ -192,11 +199,13 @@ class NotificationService ...@@ -192,11 +199,13 @@ class NotificationService
recipients = recipients.concat(participants) recipients = recipients.concat(participants)
# Merge project watchers unless note.for_personal_snippet?
recipients = add_project_watchers(recipients, note.project) # Merge project watchers
recipients = add_project_watchers(recipients, note.project)
# Merge project with custom notification # Merge project with custom notification
recipients = add_custom_notifications(recipients, note.project, :new_note) recipients = add_custom_notifications(recipients, note.project, :new_note)
end
# Reject users with Mention notification level, except those mentioned in _this_ note. # Reject users with Mention notification level, except those mentioned in _this_ note.
recipients = reject_mention_users(recipients - mentioned_users, note.project) recipients = reject_mention_users(recipients - mentioned_users, note.project)
...@@ -211,8 +220,7 @@ class NotificationService ...@@ -211,8 +220,7 @@ class NotificationService
recipients.delete(note.author) recipients.delete(note.author)
recipients = recipients.uniq recipients = recipients.uniq
# build notify method like 'note_commit_email' notify_method = "note_#{note.to_ability_name}_email".to_sym
notify_method = "note_#{note.noteable_type.underscore}_email".to_sym
recipients.each do |recipient| recipients.each do |recipient|
mailer.send(notify_method, recipient.id, note.id).deliver_later mailer.send(notify_method, recipient.id, note.id).deliver_later
......
...@@ -22,17 +22,7 @@ module Projects ...@@ -22,17 +22,7 @@ module Projects
return @project return @project
end end
# Set project name from path set_project_name_from_path
if @project.name.present? && @project.path.present?
# if both name and path set - everything is ok
elsif @project.path.present?
# Set project name from path
@project.name = @project.path.dup
elsif @project.name.present?
# For compatibility - set path from name
# TODO: remove this in 8.0
@project.path = @project.name.dup.parameterize
end
# get namespace id # get namespace id
namespace_id = params[:namespace_id] namespace_id = params[:namespace_id]
...@@ -144,5 +134,19 @@ module Projects ...@@ -144,5 +134,19 @@ module Projects
service.save! service.save!
end end
end end
def set_project_name_from_path
# Set project name from path
if @project.name.present? && @project.path.present?
# if both name and path set - everything is ok
elsif @project.path.present?
# Set project name from path
@project.name = @project.path.dup
elsif @project.name.present?
# For compatibility - set path from name
# TODO: remove this in 8.0
@project.path = @project.name.dup.parameterize
end
end
end end
end end
...@@ -118,7 +118,8 @@ module Users ...@@ -118,7 +118,8 @@ module Users
user.personal_projects.select("#{user.id} AS user_id, projects.id AS project_id, #{Gitlab::Access::MASTER} AS access_level"), user.personal_projects.select("#{user.id} AS user_id, projects.id AS project_id, #{Gitlab::Access::MASTER} AS access_level"),
user.groups_projects.select_for_project_authorization, user.groups_projects.select_for_project_authorization,
user.projects.select_for_project_authorization, user.projects.select_for_project_authorization,
user.groups.joins(:shared_projects).select_for_project_authorization user.groups.joins(:shared_projects).select_for_project_authorization,
user.nested_projects.select_for_project_authorization
] ]
Gitlab::SQL::Union.new(relations) Gitlab::SQL::Union.new(relations)
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
- if @broadcast_message.message.present? - if @broadcast_message.message.present?
= render_broadcast_message(@broadcast_message) = render_broadcast_message(@broadcast_message)
- else - else
= "Your message here" Your message here
= form_for [:admin, @broadcast_message], html: { class: 'broadcast-message-form form-horizontal js-quick-submit js-requires-input'} do |f| = form_for [:admin, @broadcast_message], html: { class: 'broadcast-message-form form-horizontal js-quick-submit js-requires-input'} do |f|
= form_errors(@broadcast_message) = form_errors(@broadcast_message)
......
%tr %tr
%td %td
= "#{Gitlab::OAuth::Provider.label_for(identity.provider)} (#{identity.provider})" #{Gitlab::OAuth::Provider.label_for(identity.provider)} (#{identity.provider})
%td %td
= identity.extern_uid = identity.extern_uid
%td %td
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
that for future communication. that for future communication.
%br %br
Registration token is Registration token is
%code{ id: 'runners-token' } #{current_application_settings.runners_registration_token} %code#runners-token= current_application_settings.runners_registration_token
.bs-callout.clearfix .bs-callout.clearfix
.pull-left .pull-left
......
...@@ -100,7 +100,7 @@ ...@@ -100,7 +100,7 @@
%td.build-link %td.build-link
- if project - if project
= link_to ci_status_path(build.pipeline) do = link_to ci_status_path(build.pipeline) do
%strong #{build.pipeline.short_sha} %strong= build.pipeline.short_sha
%td.timestamp %td.timestamp
- if build.finished_at - if build.finished_at
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
%h4 CPU %h4 CPU
.data .data
- if @cpus - if @cpus
%h1= "#{@cpus.length} cores" %h1 #{@cpus.length} cores
- else - else
= icon('warning', class: 'text-warning') = icon('warning', class: 'text-warning')
Unable to collect CPU info Unable to collect CPU info
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
%h4 Memory %h4 Memory
.data .data
- if @memory - if @memory
%h1= "#{number_to_human_size(@memory.active_bytes)} / #{number_to_human_size(@memory.total_bytes)}" %h1 #{number_to_human_size(@memory.active_bytes)} / #{number_to_human_size(@memory.total_bytes)}
- else - else
= icon('warning', class: 'text-warning') = icon('warning', class: 'text-warning')
Unable to collect memory info Unable to collect memory info
...@@ -28,6 +28,6 @@ ...@@ -28,6 +28,6 @@
%h4 Disks %h4 Disks
.data .data
- @disks.each do |disk| - @disks.each do |disk|
%h1= "#{number_to_human_size(disk[:bytes_used])} / #{number_to_human_size(disk[:bytes_total])}" %h1 #{number_to_human_size(disk[:bytes_used])} / #{number_to_human_size(disk[:bytes_total])}
%p= "#{disk[:disk_name]}" %p= disk[:disk_name]
%p= "#{disk[:mount_path]}" %p= disk[:mount_path]
...@@ -186,6 +186,6 @@ ...@@ -186,6 +186,6 @@
- if @user.solo_owned_groups.present? - if @user.solo_owned_groups.present?
%p %p
This user is currently an owner in these groups: This user is currently an owner in these groups:
%strong #{@user.solo_owned_groups.map(&:name).join(', ')} %strong= @user.solo_owned_groups.map(&:name).join(', ')
%p %p
You must transfer ownership or delete these groups before you can delete this user. You must transfer ownership or delete these groups before you can delete this user.
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
.file-holder .file-holder
.file-title.clearfix .file-title.clearfix
Content of .gitlab-ci.yml Content of .gitlab-ci.yml
#ci-editor.ci-editor #{@content} #ci-editor.ci-editor= @content
= text_area_tag(:content, @content, class: 'hidden form-control span1', rows: 7, require: true) = text_area_tag(:content, @content, class: 'hidden form-control span1', rows: 7, require: true)
.col-sm-12 .col-sm-12
.pull-left.prepend-top-10 .pull-left.prepend-top-10
......
- expanded = discussion.expanded? - expanded = discussion.expanded?
%li.note.note-discussion.timeline-entry %li.note.note-discussion.timeline-entry
.timeline-entry-inner .timeline-entry-inner
.timeline-icon
= link_to user_path(discussion.author) do
= image_tag avatar_icon(discussion.author), class: "avatar s40"
.timeline-content .timeline-content
.discussion.js-toggle-container{ class: discussion.id, data: { discussion_id: discussion.id } } .discussion.js-toggle-container{ class: discussion.id, data: { discussion_id: discussion.id } }
.discussion-header .discussion-header
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
.content{ class: ('hide' unless discussion_left.expanded?) } .content{ class: ('hide' unless discussion_left.expanded?) }
= render "discussions/notes", discussion: discussion_left, line_type: 'old' = render "discussions/notes", discussion: discussion_left, line_type: 'old'
- else - else
%td.notes_line.old= "" %td.notes_line.old= ("")
%td.notes_content.parallel.old %td.notes_content.parallel.old
.content .content
...@@ -16,6 +16,6 @@ ...@@ -16,6 +16,6 @@
.content{ class: ('hide' unless discussion_right.expanded?) } .content{ class: ('hide' unless discussion_right.expanded?) }
= render "discussions/notes", discussion: discussion_right, line_type: 'new' = render "discussions/notes", discussion: discussion_right, line_type: 'new'
- else - else
%td.notes_line.new= "" %td.notes_line.new= ("")
%td.notes_content.parallel.new %td.notes_content.parallel.new
.content .content
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
%p %p
= icon("exclamation-triangle fw") = icon("exclamation-triangle fw")
You are an admin, which means granting access to You are an admin, which means granting access to
%strong #{@pre_auth.client.name} %strong= @pre_auth.client.name
will allow them to interact with GitLab as an admin as well. Proceed with caution. will allow them to interact with GitLab as an admin as well. Proceed with caution.
- if @pre_auth.scopes - if @pre_auth.scopes
......
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
.panel.panel-default .panel.panel-default
.panel-heading .panel-heading
Users with access to Users with access to
%strong #{@group.name} %strong= @group.name
%span.badge= @members.total_count %span.badge= @members.total_count
%ul.content-list %ul.content-list
= render partial: 'shared/members/member', collection: @members, as: :member = render partial: 'shared/members/member', collection: @members, as: :member
......
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
.row-content-block.second-block .row-content-block.second-block
Only issues from the Only issues from the
%strong #{@group.name} %strong= @group.name
group are listed here. group are listed here.
- if current_user - if current_user
To see all issues you should visit #{link_to 'dashboard', issues_dashboard_path} page. To see all issues you should visit #{link_to 'dashboard', issues_dashboard_path} page.
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
.row-content-block.second-block .row-content-block.second-block
Only merge requests from Only merge requests from
%strong #{@group.name} %strong= @group.name
group are listed here. group are listed here.
- if current_user - if current_user
To see all merge requests you should visit #{link_to 'dashboard', merge_requests_dashboard_path} page. To see all merge requests you should visit #{link_to 'dashboard', merge_requests_dashboard_path} page.
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
.row-content-block .row-content-block
Only milestones from Only milestones from
%strong #{@group.name} %strong= @group.name
group are listed here. group are listed here.
.milestones .milestones
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
%colgroup.import-jobs-status-col %colgroup.import-jobs-status-col
%thead %thead
%tr %tr
%th= "From #{provider_title}" %th From #{provider_title}
%th To GitLab %th To GitLab
%th Status %th Status
%tbody %tbody
......
...@@ -37,7 +37,7 @@ ...@@ -37,7 +37,7 @@
%tbody %tbody
- @user_map.each do |id, user| - @user_map.each do |id, user|
%tr %tr
%td= id %td= (id)
%td= text_field_tag "users[#{id}][name]", user[:name], class: 'form-control' %td= text_field_tag "users[#{id}][name]", user[:name], class: 'form-control'
%td= text_field_tag "users[#{id}][email]", user[:email], class: 'form-control' %td= text_field_tag "users[#{id}][email]", user[:email], class: 'form-control'
%td %td
......
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
%td %td
= repo.name = repo.name
%td.import-target %td.import-target
= "#{current_user.username}/#{repo.name}" #{current_user.username}/#{repo.name}
%td.import-actions.job-status %td.import-actions.job-status
= button_tag class: "btn btn-import js-add-to-import" do = button_tag class: "btn btn-import js-add-to-import" do
Import Import
......
...@@ -55,7 +55,7 @@ ...@@ -55,7 +55,7 @@
%td %td
= link_to repo.name, "https://code.google.com/p/#{repo.name}", target: "_blank" = link_to repo.name, "https://code.google.com/p/#{repo.name}", target: "_blank"
%td.import-target %td.import-target
= "#{current_user.username}/#{repo.name}" #{current_user.username}/#{repo.name}
%td.import-actions.job-status %td.import-actions.job-status
= button_tag class: "btn btn-import js-add-to-import" do = button_tag class: "btn btn-import js-add-to-import" do
Import Import
......
...@@ -2,9 +2,9 @@ ...@@ -2,9 +2,9 @@
Assignee changed Assignee changed
- if @previous_assignee - if @previous_assignee
from from
%strong #{@previous_assignee.name} %strong= @previous_assignee.name
to to
- if issuable.assignee_id - if issuable.assignee_id
%strong #{issuable.assignee_name} %strong= issuable.assignee_name
- else - else
%strong Unassigned %strong Unassigned
%p %p
= "Issue was closed by #{@updated_by.name}" Issue was closed by #{@updated_by.name}
= "Issue was closed by #{@updated_by.name}" Issue was closed by #{@updated_by.name}
Issue ##{@issue.iid}: #{namespace_project_issue_url(@issue.project.namespace, @issue.project, @issue)} Issue ##{@issue.iid}: #{namespace_project_issue_url(@issue.project.namespace, @issue.project, @issue)}
%p %p
= "Merge Request #{@merge_request.to_reference} was closed by #{@updated_by.name}" Merge Request #{@merge_request.to_reference} was closed by #{@updated_by.name}
= "Merge Request #{@merge_request.to_reference} was closed by #{@updated_by.name}" Merge Request #{@merge_request.to_reference} was closed by #{@updated_by.name}
Merge Request url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)} Merge Request url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)}
......
%p %p
= "Issue was #{@issue_status} by #{@updated_by.name}" Issue was #{@issue_status} by #{@updated_by.name}
%p %p
= "Merge Request #{@merge_request.to_reference} was #{@mr_status} by #{@updated_by.name}" Merge Request #{@merge_request.to_reference} was #{@mr_status} by #{@updated_by.name}
= "Merge Request #{@merge_request.to_reference} was #{@mr_status} by #{@updated_by.name}" Merge Request #{@merge_request.to_reference} was #{@mr_status} by #{@updated_by.name}
Merge Request url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)} Merge Request url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)}
......
%p %p
= "Merge Request #{@merge_request.to_reference} was merged" Merge Request #{@merge_request.to_reference} was merged
= "Merge Request #{@merge_request.to_reference} was merged" Merge Request #{@merge_request.to_reference} was merged
Merge Request url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)} Merge Request url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)}
......
New comment for Snippet <%= @snippet.id %>
<%= url_for(snippet_url(@snippet, anchor: "note_#{@note.id}")) %>
Author: <%= @note.author_name %>
<%= @note.note %>
...@@ -139,7 +139,7 @@ ...@@ -139,7 +139,7 @@
had had
= failed.size = failed.size
failed failed
= "#{'build'.pluralize(failed.size)}." #{'build'.pluralize(failed.size)}.
%tr.warning %tr.warning
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;border:1px solid #ededed;border-bottom:0;border-radius:3px 3px 0 0;overflow:hidden;background-color:#fdf4f6;color:#d22852;font-size:14px;line-height:1.4;text-align:center;padding:8px 15px;" } %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;border:1px solid #ededed;border-bottom:0;border-radius:3px 3px 0 0;overflow:hidden;background-color:#fdf4f6;color:#d22852;font-size:14px;line-height:1.4;text-align:center;padding:8px 15px;" }
Logs may contain sensitive data. Please consider before forwarding this email. Logs may contain sensitive data. Please consider before forwarding this email.
......
...@@ -138,9 +138,9 @@ ...@@ -138,9 +138,9 @@
%a{ href: pipeline_url(@pipeline), style: "color:#3777b0;text-decoration:none;" } %a{ href: pipeline_url(@pipeline), style: "color:#3777b0;text-decoration:none;" }
= "\##{@pipeline.id}" = "\##{@pipeline.id}"
successfully completed successfully completed
= "#{build_count} #{'build'.pluralize(build_count)}" #{build_count} #{'build'.pluralize(build_count)}
in in
= "#{stage_count} #{'stage'.pluralize(stage_count)}." #{stage_count} #{'stage'.pluralize(stage_count)}.
%tr.footer %tr.footer
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" } %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" }
%img{ alt: "GitLab", height: "33", src: image_url('mailers/ci_pipeline_notif_v1/gitlab-logo-full-horizontal.gif'), style: "display:block;margin:0 auto 1em;", width: "90" }/ %img{ alt: "GitLab", height: "33", src: image_url('mailers/ci_pipeline_notif_v1/gitlab-logo-full-horizontal.gif'), style: "display:block;margin:0 auto 1em;", width: "90" }/
......
= "Project #{@project.name} couldn't be exported." Project #{@project.name} couldn't be exported.
= "The errors we encountered were:" The errors we encountered were:
- @errors.each do |error| - @errors.each do |error|
#{error} #{error}
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
%ul %ul
- @message.commits.each do |commit| - @message.commits.each do |commit|
%li %li
%strong #{link_to(commit.short_id, namespace_project_commit_url(@message.project_namespace, @message.project, commit))} %strong= link_to(commit.short_id, namespace_project_commit_url(@message.project_namespace, @message.project, commit))
%div %div
%span by #{commit.author_name} %span by #{commit.author_name}
%i at #{commit.committed_date.to_s(:iso8601)} %i at #{commit.committed_date.to_s(:iso8601)}
......
...@@ -102,7 +102,7 @@ ...@@ -102,7 +102,7 @@
= f.text_field :username, required: true, class: 'form-control' = f.text_field :username, required: true, class: 'form-control'
.help-block .help-block
Current path: Current path:
= "#{root_url}#{current_user.username}" #{root_url}#{current_user.username}
.prepend-top-default .prepend-top-default
= f.button class: "btn btn-warning", type: "submit" do = f.button class: "btn btn-warning", type: "submit" do
= icon "spinner spin", class: "hidden loading-username" = icon "spinner spin", class: "hidden loading-username"
...@@ -128,7 +128,7 @@ ...@@ -128,7 +128,7 @@
- if @user.solo_owned_groups.present? - if @user.solo_owned_groups.present?
%p %p
Your account is currently an owner in these groups: Your account is currently an owner in these groups:
%strong #{@user.solo_owned_groups.map(&:name).join(', ')} %strong= @user.solo_owned_groups.map(&:name).join(', ')
%p %p
You must transfer ownership or delete these groups before you can delete your account. You must transfer ownership or delete these groups before you can delete your account.
.append-bottom-default .append-bottom-default
...@@ -62,7 +62,7 @@ ...@@ -62,7 +62,7 @@
%span.help-block %span.help-block
Please click the link in the confirmation email before continuing. It was sent to Please click the link in the confirmation email before continuing. It was sent to
= succeed "." do = succeed "." do
%strong #{@user.unconfirmed_email} %strong= @user.unconfirmed_email
%p %p
= link_to "Resend confirmation e-mail", user_confirmation_path(user: { email: @user.unconfirmed_email }), method: :post = link_to "Resend confirmation e-mail", user_confirmation_path(user: { email: @user.unconfirmed_email }), method: :post
......
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
= markdown_toolbar_button({ icon: "list-ol fw", data: { "md-tag" => "1. ", "md-prepend" => true }, title: "Add a numbered list" }) = markdown_toolbar_button({ icon: "list-ol fw", data: { "md-tag" => "1. ", "md-prepend" => true }, title: "Add a numbered list" })
= markdown_toolbar_button({ icon: "check-square-o fw", data: { "md-tag" => "* [ ] ", "md-prepend" => true }, title: "Add a task list" }) = markdown_toolbar_button({ icon: "check-square-o fw", data: { "md-tag" => "* [ ] ", "md-prepend" => true }, title: "Add a task list" })
.toolbar-group .toolbar-group
%button.toolbar-btn.js-zen-enter.has-tooltip.hidden-xs{ type: "button", tabindex: -1, aria: { label: "Go full screen" }, title: "Go full screen", data: { container: "body" } } %button.toolbar-btn.js-zen-enter.has-tooltip{ type: "button", tabindex: -1, aria: { label: "Go full screen" }, title: "Go full screen", data: { container: "body" } }
= icon("arrows-alt fw") = icon("arrows-alt fw")
.md-write-holder .md-write-holder
......
...@@ -34,7 +34,7 @@ ...@@ -34,7 +34,7 @@
= select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2' = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2'
.file-editor.code .file-editor.code
%pre.js-edit-mode-pane#editor #{params[:content] || local_assigns[:blob_data]} %pre.js-edit-mode-pane#editor= params[:content] || local_assigns[:blob_data]
- if local_assigns[:path] - if local_assigns[:path]
.js-edit-mode-pane#preview.hide .js-edit-mode-pane#preview.hide
.center .center
......
.file-content.image_file .file-content.image_file
- if blob.svg? - if blob.svg?
- if blob.size_within_svg_limits? - if blob.size_within_svg_limits?
- # We need to scrub SVG but we cannot do so in the RawController: it would -# We need to scrub SVG but we cannot do so in the RawController: it would
- # be wrong/strange if RawController modified the data. -# be wrong/strange if RawController modified the data.
- blob.load_all_data!(@repository) - blob.load_all_data!(@repository)
- blob = sanitize_svg(blob) - blob = sanitize_svg(blob)
%img{ src: "data:#{blob.mime_type};base64,#{Base64.encode64(blob.data)}", alt: "#{blob.name}" } %img{ src: "data:#{blob.mime_type};base64,#{Base64.encode64(blob.data)}", alt: "#{blob.name}" }
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
.modal-content .modal-content
.modal-header .modal-header
%a.close{ href: "#", "data-dismiss" => "modal" } × %a.close{ href: "#", "data-dismiss" => "modal" } ×
%h3.page-title #{title} %h3.page-title= title
.modal-body .modal-body
= form_tag form_path, method: method, class: 'js-quick-submit js-upload-blob-form form-horizontal' do = form_tag form_path, method: method, class: 'js-quick-submit js-upload-blob-form form-horizontal' do
.dropzone .dropzone
......
...@@ -85,7 +85,7 @@ ...@@ -85,7 +85,7 @@
- if build.finished_at - if build.finished_at
%p.finished-at %p.finished-at
= icon("calendar") = icon("calendar")
%span #{time_ago_with_tooltip(build.finished_at)} %span= time_ago_with_tooltip(build.finished_at)
%td.coverage %td.coverage
- if coverage && build.try(:coverage) - if coverage && build.try(:coverage)
......
...@@ -78,7 +78,7 @@ ...@@ -78,7 +78,7 @@
.btn-group.inline .btn-group.inline
- if actions.any? - if actions.any?
.btn-group .btn-group
%button.dropdown-toggle.btn.btn-default.js-pipeline-dropdown-manual-actions{ type: 'button', 'data-toggle' => 'dropdown' } %button.dropdown-toggle.btn.btn-default.has-tooltip.js-pipeline-dropdown-manual-actions{ type: 'button', title: 'Manual build', data: { toggle: 'dropdown', placement: 'top' }, 'aria-label': 'Manual build' }
= custom_icon('icon_play') = custom_icon('icon_play')
= icon('caret-down', 'aria-hidden' => 'true') = icon('caret-down', 'aria-hidden' => 'true')
%ul.dropdown-menu.dropdown-menu-align-right %ul.dropdown-menu.dropdown-menu-align-right
...@@ -89,7 +89,7 @@ ...@@ -89,7 +89,7 @@
%span= build.name %span= build.name
- if artifacts.present? - if artifacts.present?
.btn-group .btn-group
%button.dropdown-toggle.btn.btn-default.build-artifacts.js-pipeline-dropdown-download{ type: 'button', 'data-toggle' => 'dropdown' } %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("download")
= icon('caret-down') = icon('caret-down')
%ul.dropdown-menu.dropdown-menu-align-right %ul.dropdown-menu.dropdown-menu-align-right
...@@ -102,8 +102,8 @@ ...@@ -102,8 +102,8 @@
- if can?(current_user, :update_pipeline, pipeline.project) - if can?(current_user, :update_pipeline, pipeline.project)
.cancel-retry-btns.inline .cancel-retry-btns.inline
- if pipeline.retryable? - if pipeline.retryable?
= link_to retry_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: 'btn has-tooltip', title: "Retry", method: :post do = 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") = icon("repeat")
- if pipeline.cancelable? - 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", method: :post do = 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") = icon("remove")
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
- commits, hidden = limited_commits(@commits) - commits, hidden = limited_commits(@commits)
- commits.chunk { |c| c.committed_date.in_time_zone.to_date }.each do |day, commits| - commits.chunk { |c| c.committed_date.in_time_zone.to_date }.each do |day, commits|
%li.commit-header= "#{day.strftime('%d %b, %Y')} #{pluralize(commits.count, 'commit')}" %li.commit-header #{day.strftime('%d %b, %Y')} #{pluralize(commits.count, 'commit')}
%li.commits-row %li.commits-row
%ul.content-list.commit-list.table-list.table-wide %ul.content-list.commit-list.table-list.table-wide
= render commits, project: project, ref: ref = render commits, project: project, ref: ref
......
...@@ -9,10 +9,13 @@ ...@@ -9,10 +9,13 @@
= render "head" = render "head"
%div{ class: container_class } %div{ class: container_class }
.row-content-block.second-block.content-component-block .row-content-block.second-block.content-component-block.flex-container-block
.tree-ref-holder .tree-ref-holder
= render 'shared/ref_switcher', destination: 'commits' = render 'shared/ref_switcher', destination: 'commits'
%ul.breadcrumb.repo-breadcrumb
= commits_breadcrumbs
.block-controls.hidden-xs.hidden-sm .block-controls.hidden-xs.hidden-sm
- if @merge_request.present? - if @merge_request.present?
.control .control
...@@ -30,8 +33,6 @@ ...@@ -30,8 +33,6 @@
.control .control
= link_to namespace_project_commits_path(@project.namespace, @project, @ref, { format: :atom, private_token: current_user.private_token }), title: "Commits Feed", class: 'btn' do = link_to namespace_project_commits_path(@project.namespace, @project, @ref, { format: :atom, private_token: current_user.private_token }), title: "Commits Feed", class: 'btn' do
= icon("rss") = icon("rss")
%ul.breadcrumb.repo-breadcrumb
= commits_breadcrumbs
%div{ id: dom_id(@project) } %div{ id: dom_id(@project) }
%ol#commits-list.list-unstyled.content_list %ol#commits-list.list-unstyled.content_list
......
...@@ -16,9 +16,9 @@ ...@@ -16,9 +16,9 @@
There isn't anything to compare. There isn't anything to compare.
%p.slead %p.slead
- if params[:to] == params[:from] - if params[:to] == params[:from]
%span.label-branch #{params[:from]} %span.label-branch= params[:from]
and and
%span.label-branch #{params[:to]} %span.label-branch= params[:to]
are the same. are the same.
- else - else
You'll need to use different branch names to get a valid comparison. You'll need to use different branch names to get a valid comparison.
%tr.deployment %tr.deployment
%td %td
%strong= "##{deployment.iid}" %strong ##{deployment.iid}
%td %td
= render 'projects/deployments/commit', deployment: deployment = render 'projects/deployments/commit', deployment: deployment
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
%td.build-column %td.build-column
- if deployment.deployable - if deployment.deployable
= link_to [@project.namespace.becomes(Namespace), @project, deployment.deployable], class: 'build-link' do = link_to [@project.namespace.becomes(Namespace), @project, deployment.deployable], class: 'build-link' do
= "#{deployment.deployable.name} (##{deployment.deployable.id})" #{deployment.deployable.name} (##{deployment.deployable.id})
- if deployment.user - if deployment.user
by by
= user_avatar(user: deployment.user, size: 20) = user_avatar(user: deployment.user, size: 20)
......
.diff-content.diff-wrap-lines .diff-content.diff-wrap-lines
- # Skip all non non-supported blobs -# Skip all non non-supported blobs
- return unless blob.respond_to?(:text?) - return unless blob.respond_to?(:text?)
- if diff_file.too_large? - if diff_file.too_large?
.nothing-here-block This diff could not be displayed because it is too large. .nothing-here-block This diff could not be displayed because it is too large.
......
...@@ -25,4 +25,4 @@ ...@@ -25,4 +25,4 @@
- if diff_file.mode_changed? - if diff_file.mode_changed?
%small %small
= "#{diff_file.a_mode}#{diff_file.b_mode}" #{diff_file.a_mode}#{diff_file.b_mode}
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
%span.wrap %span.wrap
.frame{ class: image_diff_class(diff) } .frame{ class: image_diff_class(diff) }
%img{ src: diff.deleted_file ? old_file_raw_path : file_raw_path, alt: diff.new_path } %img{ src: diff.deleted_file ? old_file_raw_path : file_raw_path, alt: diff.new_path }
%p.image-info= "#{number_to_human_size file.size}" %p.image-info= number_to_human_size(file.size)
- else - else
.image .image
.two-up.view .two-up.view
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
%a{ href: namespace_project_blob_path(@project.namespace, @project, tree_join(diff_file.old_ref, diff.old_path)) } %a{ href: namespace_project_blob_path(@project.namespace, @project, tree_join(diff_file.old_ref, diff.old_path)) }
%img{ src: old_file_raw_path, alt: diff.old_path } %img{ src: old_file_raw_path, alt: diff.old_path }
%p.image-info.hide %p.image-info.hide
%span.meta-filesize= "#{number_to_human_size old_file.size}" %span.meta-filesize= number_to_human_size(old_file.size)
| |
%b W: %b W:
%span.meta-width %span.meta-width
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
%a{ href: namespace_project_blob_path(@project.namespace, @project, tree_join(diff_file.new_ref, diff.new_path)) } %a{ href: namespace_project_blob_path(@project.namespace, @project, tree_join(diff_file.new_ref, diff.new_path)) }
%img{ src: file_raw_path, alt: diff.new_path } %img{ src: file_raw_path, alt: diff.new_path }
%p.image-info.hide %p.image-info.hide
%span.meta-filesize= "#{number_to_human_size file.size}" %span.meta-filesize= number_to_human_size(file.size)
| |
%b W: %b W:
%span.meta-width %span.meta-width
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
.commit-stat-summary .commit-stat-summary
Showing Showing
= link_to '#', class: 'js-toggle-button' do = link_to '#', class: 'js-toggle-button' do
%strong #{pluralize(diff_files.size, "changed file")} %strong= pluralize(diff_files.size, "changed file")
with with
%strong.cgreen #{diff_files.sum(&:added_lines)} additions %strong.cgreen #{diff_files.sum(&:added_lines)} additions
and and
......
.top-area .top-area
.nav-text .nav-text
- full_count_title = "#{@public_forks_count} public and #{@private_forks_count} private" - full_count_title = "#{@public_forks_count} public and #{@private_forks_count} private"
= "#{pluralize(@total_forks_count, 'fork')}: #{full_count_title}" #{pluralize(@total_forks_count, 'fork')}: #{full_count_title}
.nav-controls .nav-controls
= form_tag request.original_url, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f| = form_tag request.original_url, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f|
......
...@@ -77,7 +77,7 @@ ...@@ -77,7 +77,7 @@
- if generic_commit_status.finished_at - if generic_commit_status.finished_at
%p.finished-at %p.finished-at
= icon("calendar") = icon("calendar")
%span #{time_ago_with_tooltip(generic_commit_status.finished_at)} %span= time_ago_with_tooltip(generic_commit_status.finished_at)
%td.coverage %td.coverage
- if coverage && generic_commit_status.try(:coverage) - if coverage && generic_commit_status.try(:coverage)
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
%p.lead %p.lead
Commit statistics for Commit statistics for
%strong #{@ref} %strong= @ref
#{@commits_graph.start_date.strftime('%b %d')} - #{@commits_graph.end_date.strftime('%b %d')} #{@commits_graph.start_date.strftime('%b %d')} - #{@commits_graph.end_date.strftime('%b %d')}
.row .row
...@@ -19,19 +19,19 @@ ...@@ -19,19 +19,19 @@
%ul %ul
%li %li
%p.lead %p.lead
%strong #{@commits_graph.commits.size} %strong= @commits_graph.commits.size
commits during commits during
%strong #{@commits_graph.duration} %strong= @commits_graph.duration
days days
%li %li
%p.lead %p.lead
Average Average
%strong #{@commits_graph.commit_per_day} %strong= @commits_graph.commit_per_day
commits per day commits per day
%li %li
%p.lead %p.lead
Contributed by Contributed by
%strong #{@commits_graph.authors} %strong= @commits_graph.authors
authors authors
.col-md-6 .col-md-6
%div %div
......
...@@ -3,9 +3,9 @@ ...@@ -3,9 +3,9 @@
%p.slead %p.slead
- source_title, target_title = format_mr_branch_names(@merge_request) - source_title, target_title = format_mr_branch_names(@merge_request)
From From
%strong.label-branch #{source_title} %strong.label-branch= source_title
%span into %span into
%strong.label-branch #{target_title} %strong.label-branch= target_title
%span.pull-right %span.pull-right
= link_to 'Change branches', mr_change_branches_path(@merge_request) = link_to 'Change branches', mr_change_branches_path(@merge_request)
...@@ -43,7 +43,7 @@ ...@@ -43,7 +43,7 @@
#commits.commits.tab-pane.active #commits.commits.tab-pane.active
= render "projects/merge_requests/show/commits" = render "projects/merge_requests/show/commits"
#diffs.diffs.tab-pane #diffs.diffs.tab-pane
- # This tab is always loaded via AJAX -# This tab is always loaded via AJAX
- if @pipelines.any? - if @pipelines.any?
#pipelines.pipelines.tab-pane #pipelines.pipelines.tab-pane
= render "projects/merge_requests/show/pipelines" = render "projects/merge_requests/show/pipelines"
......
...@@ -92,11 +92,11 @@ ...@@ -92,11 +92,11 @@
= render "projects/merge_requests/discussion" = render "projects/merge_requests/discussion"
#commits.commits.tab-pane #commits.commits.tab-pane
- # This tab is always loaded via AJAX -# This tab is always loaded via AJAX
#pipelines.pipelines.tab-pane #pipelines.pipelines.tab-pane
- # This tab is always loaded via AJAX -# This tab is always loaded via AJAX
#diffs.diffs.tab-pane #diffs.diffs.tab-pane
- # This tab is always loaded via AJAX -# This tab is always loaded via AJAX
.mr-loading-status .mr-loading-status
= spinner = spinner
......
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
latest version latest version
- else - else
version #{version_index(merge_request_diff)} version #{version_index(merge_request_diff)}
.monospace #{short_sha(merge_request_diff.head_commit_sha)} .monospace= short_sha(merge_request_diff.head_commit_sha)
%small %small
#{number_with_delimiter(merge_request_diff.commits_count)} #{'commit'.pluralize(merge_request_diff.commits_count)}, #{number_with_delimiter(merge_request_diff.commits_count)} #{'commit'.pluralize(merge_request_diff.commits_count)},
= time_ago_with_tooltip(merge_request_diff.created_at) = time_ago_with_tooltip(merge_request_diff.created_at)
...@@ -55,14 +55,14 @@ ...@@ -55,14 +55,14 @@
latest version latest version
- else - else
version #{version_index(merge_request_diff)} version #{version_index(merge_request_diff)}
.monospace #{short_sha(merge_request_diff.head_commit_sha)} .monospace= short_sha(merge_request_diff.head_commit_sha)
%small %small
= time_ago_with_tooltip(merge_request_diff.created_at) = time_ago_with_tooltip(merge_request_diff.created_at)
%li %li
= link_to merge_request_version_path(@project, @merge_request, @merge_request_diff), class: ('is-active' unless @start_sha) do = link_to merge_request_version_path(@project, @merge_request, @merge_request_diff), class: ('is-active' unless @start_sha) do
%strong %strong
#{@merge_request.target_branch} (base) #{@merge_request.target_branch} (base)
.monospace #{short_sha(@merge_request_diff.base_commit_sha)} .monospace= short_sha(@merge_request_diff.base_commit_sha)
- if different_base?(@start_version, @merge_request_diff) - if different_base?(@start_version, @merge_request_diff)
.content-block .content-block
...@@ -72,7 +72,7 @@ ...@@ -72,7 +72,7 @@
= link_to namespace_project_compare_path(@project.namespace, @project, from: @start_version.base_commit_sha, to: @merge_request_diff.base_commit_sha) do = link_to namespace_project_compare_path(@project.namespace, @project, from: @start_version.base_commit_sha, to: @merge_request_diff.base_commit_sha) do
new commits new commits
from from
%code #{@merge_request.target_branch} %code= @merge_request.target_branch
- unless @merge_request_diff.latest? && !@start_sha - unless @merge_request_diff.latest? && !@start_sha
.comments-disabled-notif.content-block .comments-disabled-notif.content-block
......
...@@ -13,8 +13,8 @@ ...@@ -13,8 +13,8 @@
%span.ci-coverage %span.ci-coverage
- elsif @merge_request.has_ci? - elsif @merge_request.has_ci?
- # Compatibility with old CI integrations (ex jenkins) when you request status from CI server via AJAX -# Compatibility with old CI integrations (ex jenkins) when you request status from CI server via AJAX
- # TODO, remove in later versions when services like Jenkins will set CI status via Commit status API -# TODO, remove in later versions when services like Jenkins will set CI status via Commit status API
.mr-widget-heading .mr-widget-heading
- %w[success skipped canceled failed running pending].each do |status| - %w[success skipped canceled failed running pending].each do |status|
.ci_widget{ class: "ci-#{status} ci-status-icon-#{status}", style: "display:none" } .ci_widget{ class: "ci-#{status} ci-status-icon-#{status}", style: "display:none" }
......
...@@ -69,7 +69,7 @@ ...@@ -69,7 +69,7 @@
- if note_editable - if note_editable
.original-note-content.hidden{ data: { post_url: namespace_project_note_path(@project.namespace, @project, note), target_id: note.noteable.id, target_type: note.noteable.class.name.underscore } } .original-note-content.hidden{ data: { post_url: namespace_project_note_path(@project.namespace, @project, note), target_id: note.noteable.id, target_type: note.noteable.class.name.underscore } }
#{note.note} #{note.note}
%textarea.hidden.js-task-list-field.original-task-list #{note.note} %textarea.hidden.js-task-list-field.original-task-list= note.note
.note-awards .note-awards
= render 'award_emoji/awards_block', awardable: note, inline: false = render 'award_emoji/awards_block', awardable: note, inline: false
- if note.system - if note.system
......
.panel.panel-default .panel.panel-default
.panel-heading .panel-heading
Group members with access to Group members with access to
%strong #{@group.name} %strong= @group.name
%span.badge= members.size %span.badge= members.size
- if can?(current_user, :admin_group_member, @group) - if can?(current_user, :admin_group_member, @group)
.controls .controls
......
.panel.panel-default.project-members-groups .panel.panel-default.project-members-groups
.panel-heading .panel-heading
Groups with access to Groups with access to
%strong #{@project.name} %strong= @project.name
%span.badge= group_links.size %span.badge= group_links.size
%ul.content-list %ul.content-list
= render partial: 'shared/members/group', collection: group_links, as: :group_link = render partial: 'shared/members/group', collection: group_links, as: :group_link
...@@ -5,9 +5,9 @@ ...@@ -5,9 +5,9 @@
.panel.panel-default .panel.panel-default
.panel-heading .panel-heading
Shared with Shared with
%strong #{shared_group.name} %strong= shared_group.name
group, members with group, members with
%strong #{group_links.human_access} %strong= group_links.human_access
role (#{shared_group_users_count}) role (#{shared_group_users_count})
- if can?(current_user, :admin_group, shared_group) - if can?(current_user, :admin_group, shared_group)
.panel-head-actions .panel-head-actions
......
.panel.panel-default .panel.panel-default
.panel-heading .panel-heading
Members with access to Members with access to
%strong #{@project.name} %strong= @project.name
%span.badge= @project_members.total_count %span.badge= @project_members.total_count
= form_tag namespace_project_settings_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form' do = form_tag namespace_project_settings_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form' do
.form-group .form-group
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
.oneline .oneline
.title .title
Release notes for tag Release notes for tag
%strong #{@tag.name} %strong= @tag.name
= form_for(@release, method: :put, url: namespace_project_tag_release_path(@project.namespace, @project, @tag.name), html: { class: 'form-horizontal common-note-form release-form js-quick-submit' }) do |f| = form_for(@release, method: :put, url: namespace_project_tag_release_path(@project.namespace, @project, @tag.name), html: { class: 'form-horizontal common-note-form release-form js-quick-submit' }) do |f|
......
...@@ -9,10 +9,10 @@ ...@@ -9,10 +9,10 @@
(checkout the #{link_to 'GitLab Runner section', 'https://about.gitlab.com/gitlab-ci/#gitlab-runner', target: '_blank'} for information on how to install it). (checkout the #{link_to 'GitLab Runner section', 'https://about.gitlab.com/gitlab-ci/#gitlab-runner', target: '_blank'} for information on how to install it).
%li %li
Specify the following URL during the Runner setup: Specify the following URL during the Runner setup:
%code #{ci_root_url(only_path: false)} %code= ci_root_url(only_path: false)
%li %li
Use the following registration token during setup: Use the following registration token during setup:
%code #{@project.runners_token} %code= @project.runners_token
%li %li
Start the Runner! Start the Runner!
......
...@@ -4,4 +4,4 @@ ...@@ -4,4 +4,4 @@
.col-sm-9.col-sm-offset-3 .col-sm-9.col-sm-offset-3
= link_to new_namespace_project_mattermost_path(@project.namespace, @project), class: 'btn btn-lg' do = link_to new_namespace_project_mattermost_path(@project.namespace, @project), class: 'btn btn-lg' do
= custom_icon('mattermost_logo', size: 15) = custom_icon('mattermost_logo', size: 15)
= 'Add to Mattermost' Add to Mattermost
...@@ -3,4 +3,4 @@ ...@@ -3,4 +3,4 @@
%h4 %h4
= icon('search') = icon('search')
We couldn't find any results matching We couldn't find any results matching
%code #{@search_term} %code= @search_term
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
%h4 %h4
= link_to [merge_request.target_project.namespace.becomes(Namespace), merge_request.target_project, merge_request] do = link_to [merge_request.target_project.namespace.becomes(Namespace), merge_request.target_project, merge_request] do
%span.term.str-truncated= merge_request.title %span.term.str-truncated= merge_request.title
.pull-right #{merge_request.to_reference} .pull-right= merge_request.to_reference
- if merge_request.description.present? - if merge_request.description.present?
.description.term .description.term
= preserve do = preserve do
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
= link_to user_snippets_path(snippet.author) do = link_to user_snippets_path(snippet.author) do
= image_tag avatar_icon(snippet.author_email), class: "avatar avatar-inline s16", alt: '' = image_tag avatar_icon(snippet.author_email), class: "avatar avatar-inline s16", alt: ''
= snippet.author_name = snippet.author_name
%span.light #{time_ago_with_tooltip(snippet.created_at)} %span.light= time_ago_with_tooltip(snippet.created_at)
%h4.snippet-title %h4.snippet-title
- snippet_path = reliable_snippet_path(snippet) - snippet_path = reliable_snippet_path(snippet)
= link_to snippet_path do = link_to snippet_path do
......
...@@ -20,4 +20,4 @@ ...@@ -20,4 +20,4 @@
= link_to user_snippets_path(snippet_title.author) do = link_to user_snippets_path(snippet_title.author) do
= image_tag avatar_icon(snippet_title.author_email), class: "avatar avatar-inline s16", alt: '' = image_tag avatar_icon(snippet_title.author_email), class: "avatar avatar-inline s16", alt: ''
= snippet_title.author_name = snippet_title.author_name
%span.light #{time_ago_with_tooltip(snippet_title.created_at)} %span.light= time_ago_with_tooltip(snippet_title.created_at)
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
To prevent accidental actions we ask you to confirm your intention. To prevent accidental actions we ask you to confirm your intention.
%br %br
Please type Please type
%code.js-confirm-danger-match #{phrase} %code.js-confirm-danger-match= phrase
to proceed or close this modal to cancel. to proceed or close this modal to cancel.
.form-group .form-group
......
...@@ -6,14 +6,14 @@ ...@@ -6,14 +6,14 @@
= link_to milestones_filter_path(state: 'opened') do = link_to milestones_filter_path(state: 'opened') do
Open Open
- if @project - if @project
%span.badge #{counts[:opened]} %span.badge= counts[:opened]
%li{ class: milestone_class_for_state(params[:state], 'closed') }> %li{ class: milestone_class_for_state(params[:state], 'closed') }>
= link_to milestones_filter_path(state: 'closed') do = link_to milestones_filter_path(state: 'closed') do
Closed Closed
- if @project - if @project
%span.badge #{counts[:closed]} %span.badge= counts[:closed]
%li{ class: milestone_class_for_state(params[:state], 'all') }> %li{ class: milestone_class_for_state(params[:state], 'all') }>
= link_to milestones_filter_path(state: 'all') do = link_to milestones_filter_path(state: 'all') do
All All
- if @project - if @project
%span.badge #{counts[:all]} %span.badge= counts[:all]
...@@ -32,7 +32,7 @@ ...@@ -32,7 +32,7 @@
- if group_member - if group_member
as as
%span #{group_member.human_access} %span= group_member.human_access
- if group.description.present? - if group.description.present?
.description .description
......
...@@ -68,7 +68,7 @@ ...@@ -68,7 +68,7 @@
- if !issuable.persisted? && !issuable.project.empty_repo? && (guide_url = contribution_guide_path(issuable.project)) - if !issuable.persisted? && !issuable.project.empty_repo? && (guide_url = contribution_guide_path(issuable.project))
.inline.prepend-left-10 .inline.prepend-left-10
Please review the Please review the
%strong #{link_to 'contribution guidelines', guide_url} %strong= link_to('contribution guidelines', guide_url)
for this project. for this project.
- if issuable.new_record? - if issuable.new_record?
......
...@@ -5,5 +5,5 @@ ...@@ -5,5 +5,5 @@
- scopes.each do |scope| - scopes.each do |scope|
%fieldset %fieldset
= check_box_tag "#{prefix}[scopes][]", scope, token.scopes.include?(scope), id: "#{prefix}_scopes_#{scope}" = check_box_tag "#{prefix}[scopes][]", scope, token.scopes.include?(scope), id: "#{prefix}_scopes_#{scope}"
= label_tag "#{prefix}_scopes_#{scope}", scope = label_tag ("#{prefix}_scopes_#{scope}"), scope
%span= "(#{t(scope, scope: [:doorkeeper, :scopes])})" %span= t(scope, scope: [:doorkeeper, :scopes])
%h4.prepend-top-20 %h4.prepend-top-20
Contributions for Contributions for
%strong #{@calendar_date.to_s(:short)} %strong= @calendar_date.to_s(:short)
- if @events.any? - if @events.any?
%ul.bordered-list %ul.bordered-list
......
...@@ -110,16 +110,16 @@ ...@@ -110,16 +110,16 @@
= spinner = spinner
#groups.tab-pane #groups.tab-pane
- # This tab is always loaded via AJAX -# This tab is always loaded via AJAX
#contributed.tab-pane #contributed.tab-pane
- # This tab is always loaded via AJAX -# This tab is always loaded via AJAX
#projects.tab-pane #projects.tab-pane
- # This tab is always loaded via AJAX -# This tab is always loaded via AJAX
#snippets.tab-pane #snippets.tab-pane
- # This tab is always loaded via AJAX -# This tab is always loaded via AJAX
.loading-status .loading-status
= spinner = spinner
......
---
title: Improve button accessibility on pipelines page
merge_request: 8561
author:
---
title: Fix tab index order on branch commits list page
merge_request:
author: Ryan Harris
---
title: Fix Sort by Recent Sign-in in Admin Area
merge_request: 8637
author: Poornima M
---
title: Fix mini-pipeline stage tooltip text wrapping
merge_request:
author:
---
title: Hide version check image if there is no internet connection
merge_request: 8355
author: Ken Ding
---
title: Make MR-review-discussions more reliable
merge_request:
author:
---
title: adds avatar for discussion note
merge_request: 8734
author:
---
title: Flag multiple empty lines in eslint, fix offenses.
merge_request: 8137
author:
---
title: Support notes when a project is not specified (personal snippet notes)
merge_request: 8468
author:
---
title: Display fullscreen button on small screens
merge_request: 5302
author: winniehell
---
title: Only show Merge Request button when user can create a MR
merge_request: 8639
author:
# Autoload all classes that we want to instrument, and instrument the methods we
# need. This takes the Gitlab::Metrics::Instrumentation module as an argument so
# that we can stub it for testing, as it is only called when metrics are
# enabled.
#
# rubocop:disable Metrics/AbcSize
def instrument_classes(instrumentation)
instrumentation.instrument_instance_methods(Gitlab::Shell)
instrumentation.instrument_methods(Gitlab::Git)
Gitlab::Git.constants.each do |name|
const = Gitlab::Git.const_get(name)
next unless const.is_a?(Module)
instrumentation.instrument_methods(const)
instrumentation.instrument_instance_methods(const)
end
# Path to search => prefix to strip from constant
paths_to_instrument = {
['app', 'finders'] => ['app', 'finders'],
['app', 'mailers', 'emails'] => ['app', 'mailers'],
['app', 'services', '**'] => ['app', 'services'],
['lib', 'gitlab', 'conflicts'] => ['lib'],
['lib', 'gitlab', 'diff'] => ['lib'],
['lib', 'gitlab', 'email', 'message'] => ['lib'],
['lib', 'gitlab', 'checks'] => ['lib']
}
paths_to_instrument.each do |(path, prefix)|
prefix = Rails.root.join(*prefix)
Dir[Rails.root.join(*path + ['*.rb'])].each do |file_path|
path = Pathname.new(file_path).relative_path_from(prefix)
const = path.to_s.sub('.rb', '').camelize.constantize
instrumentation.instrument_methods(const)
instrumentation.instrument_instance_methods(const)
end
end
instrumentation.instrument_methods(Premailer::Adapter::Nokogiri)
instrumentation.instrument_instance_methods(Premailer::Adapter::Nokogiri)
[
:Blame, :Branch, :BranchCollection, :Blob, :Commit, :Diff, :Repository,
:Tag, :TagCollection, :Tree
].each do |name|
const = Rugged.const_get(name)
instrumentation.instrument_methods(const)
instrumentation.instrument_instance_methods(const)
end
# Instruments all Banzai filters and reference parsers
{
Filter: Rails.root.join('lib', 'banzai', 'filter', '*.rb'),
ReferenceParser: Rails.root.join('lib', 'banzai', 'reference_parser', '*.rb')
}.each do |const_name, path|
Dir[path].each do |file|
klass = File.basename(file, File.extname(file)).camelize
const = Banzai.const_get(const_name).const_get(klass)
instrumentation.instrument_methods(const)
instrumentation.instrument_instance_methods(const)
end
end
instrumentation.instrument_methods(Banzai::Renderer)
instrumentation.instrument_methods(Banzai::Querying)
instrumentation.instrument_instance_methods(Banzai::ObjectRenderer)
instrumentation.instrument_instance_methods(Banzai::Redactor)
instrumentation.instrument_methods(Banzai::NoteRenderer)
[Issuable, Mentionable, Participable].each do |klass|
instrumentation.instrument_instance_methods(klass)
instrumentation.instrument_instance_methods(klass::ClassMethods)
end
instrumentation.instrument_methods(Gitlab::ReferenceExtractor)
instrumentation.instrument_instance_methods(Gitlab::ReferenceExtractor)
# Instrument the classes used for checking if somebody has push access.
instrumentation.instrument_instance_methods(Gitlab::GitAccess)
instrumentation.instrument_instance_methods(Gitlab::GitAccessWiki)
instrumentation.instrument_instance_methods(API::Helpers)
instrumentation.instrument_instance_methods(RepositoryCheck::SingleRepositoryWorker)
instrumentation.instrument_instance_methods(Rouge::Plugins::Redcarpet)
instrumentation.instrument_instance_methods(Rouge::Formatters::HTMLGitlab)
[:XML, :HTML].each do |namespace|
namespace_mod = Nokogiri.const_get(namespace)
instrumentation.instrument_methods(namespace_mod)
instrumentation.instrument_methods(namespace_mod::Document)
end
instrumentation.instrument_methods(Rinku)
instrumentation.instrument_instance_methods(Repository)
instrumentation.instrument_methods(Gitlab::Highlight)
instrumentation.instrument_instance_methods(Gitlab::Highlight)
# This is a Rails scope so we have to instrument it manually.
instrumentation.instrument_method(Project, :visible_to_user)
end
# rubocop:enable Metrics/AbcSize
if Gitlab::Metrics.enabled? if Gitlab::Metrics.enabled?
require 'pathname' require 'pathname'
require 'influxdb' require 'influxdb'
...@@ -49,110 +163,7 @@ if Gitlab::Metrics.enabled? ...@@ -49,110 +163,7 @@ if Gitlab::Metrics.enabled?
end end
Gitlab::Metrics::Instrumentation.configure do |config| Gitlab::Metrics::Instrumentation.configure do |config|
config.instrument_instance_methods(Gitlab::Shell) instrument_classes(config)
config.instrument_methods(Gitlab::Git)
Gitlab::Git.constants.each do |name|
const = Gitlab::Git.const_get(name)
next unless const.is_a?(Module)
config.instrument_methods(const)
config.instrument_instance_methods(const)
end
# Path to search => prefix to strip from constant
paths_to_instrument = {
['app', 'finders'] => ['app', 'finders'],
['app', 'mailers', 'emails'] => ['app', 'mailers'],
['app', 'services', '**'] => ['app', 'services'],
['lib', 'gitlab', 'conflicts'] => ['lib'],
['lib', 'gitlab', 'diff'] => ['lib'],
['lib', 'gitlab', 'email', 'message'] => ['lib'],
['lib', 'gitlab', 'checks'] => ['lib']
}
paths_to_instrument.each do |(path, prefix)|
prefix = Rails.root.join(*prefix)
Dir[Rails.root.join(*path + ['*.rb'])].each do |file_path|
path = Pathname.new(file_path).relative_path_from(prefix)
const = path.to_s.sub('.rb', '').camelize.constantize
config.instrument_methods(const)
config.instrument_instance_methods(const)
end
end
config.instrument_methods(Premailer::Adapter::Nokogiri)
config.instrument_instance_methods(Premailer::Adapter::Nokogiri)
[
:Blame, :Branch, :BranchCollection, :Blob, :Commit, :Diff, :Repository,
:Tag, :TagCollection, :Tree
].each do |name|
const = Rugged.const_get(name)
config.instrument_methods(const)
config.instrument_instance_methods(const)
end
# Instruments all Banzai filters and reference parsers
{
Filter: Rails.root.join('lib', 'banzai', 'filter', '*.rb'),
ReferenceParser: Rails.root.join('lib', 'banzai', 'reference_parser', '*.rb')
}.each do |const_name, path|
Dir[path].each do |file|
klass = File.basename(file, File.extname(file)).camelize
const = Banzai.const_get(const_name).const_get(klass)
config.instrument_methods(const)
config.instrument_instance_methods(const)
end
end
config.instrument_methods(Banzai::Renderer)
config.instrument_methods(Banzai::Querying)
config.instrument_instance_methods(Banzai::ObjectRenderer)
config.instrument_instance_methods(Banzai::Redactor)
config.instrument_methods(Banzai::NoteRenderer)
[Issuable, Mentionable, Participable].each do |klass|
config.instrument_instance_methods(klass)
config.instrument_instance_methods(klass::ClassMethods)
end
config.instrument_methods(Gitlab::ReferenceExtractor)
config.instrument_instance_methods(Gitlab::ReferenceExtractor)
# Instrument the classes used for checking if somebody has push access.
config.instrument_instance_methods(Gitlab::GitAccess)
config.instrument_instance_methods(Gitlab::GitAccessWiki)
config.instrument_instance_methods(API::Helpers)
config.instrument_instance_methods(RepositoryCheck::SingleRepositoryWorker)
config.instrument_instance_methods(Rouge::Plugins::Redcarpet)
config.instrument_instance_methods(Rouge::Formatters::HTMLGitlab)
[:XML, :HTML].each do |namespace|
namespace_mod = Nokogiri.const_get(namespace)
config.instrument_methods(namespace_mod)
config.instrument_methods(namespace_mod::Document)
end
config.instrument_methods(Rinku)
config.instrument_instance_methods(Repository)
config.instrument_methods(Gitlab::Highlight)
config.instrument_instance_methods(Gitlab::Highlight)
# This is a Rails scope so we have to instrument it manually.
config.instrument_method(Project, :visible_to_user)
end end
GC::Profiler.enable GC::Profiler.enable
......
...@@ -19,13 +19,11 @@ end ...@@ -19,13 +19,11 @@ end
scope(path: 'groups/*id', scope(path: 'groups/*id',
controller: :groups, controller: :groups,
constraints: { id: Gitlab::Regex.namespace_route_regex }) do constraints: { id: Gitlab::Regex.namespace_route_regex, format: /(html|json|atom)/ }) do
get :edit, as: :edit_group get :edit, as: :edit_group
get :issues, as: :issues_group get :issues, as: :issues_group
get :merge_requests, as: :merge_requests_group get :merge_requests, as: :merge_requests_group
get :projects, as: :projects_group get :projects, as: :projects_group
get :activity, as: :activity_group get :activity, as: :activity_group
get '/', action: :show, as: :group_canonical
end end
# Must be last route in this file
get 'groups/*id' => 'groups#show', as: :group_canonical, constraints: { id: Gitlab::Regex.namespace_route_regex }
...@@ -3,7 +3,7 @@ class AddEstimateToIssuablesCe < ActiveRecord::Migration ...@@ -3,7 +3,7 @@ class AddEstimateToIssuablesCe < ActiveRecord::Migration
DOWNTIME = false DOWNTIME = false
def change def up
unless column_exists?(:issues, :time_estimate) unless column_exists?(:issues, :time_estimate)
add_column :issues, :time_estimate, :integer add_column :issues, :time_estimate, :integer
end end
...@@ -12,4 +12,14 @@ class AddEstimateToIssuablesCe < ActiveRecord::Migration ...@@ -12,4 +12,14 @@ class AddEstimateToIssuablesCe < ActiveRecord::Migration
add_column :merge_requests, :time_estimate, :integer add_column :merge_requests, :time_estimate, :integer
end end
end end
def down
if column_exists?(:issues, :time_estimate)
remove_column :issues, :time_estimate
end
if column_exists?(:merge_requests, :time_estimate)
remove_column :merge_requests, :time_estimate
end
end
end end
...@@ -3,7 +3,7 @@ class CreateTimelogsCe < ActiveRecord::Migration ...@@ -3,7 +3,7 @@ class CreateTimelogsCe < ActiveRecord::Migration
DOWNTIME = false DOWNTIME = false
def change def up
unless table_exists?(:timelogs) unless table_exists?(:timelogs)
create_table :timelogs do |t| create_table :timelogs do |t|
t.integer :time_spent, null: false t.integer :time_spent, null: false
...@@ -17,4 +17,8 @@ class CreateTimelogsCe < ActiveRecord::Migration ...@@ -17,4 +17,8 @@ class CreateTimelogsCe < ActiveRecord::Migration
add_index :timelogs, :user_id add_index :timelogs, :user_id
end end
end end
def down
drop_table :timelogs if table_exists?(:timelogs)
end
end end
...@@ -27,6 +27,7 @@ Ruby Version: 2.1.5p273 ...@@ -27,6 +27,7 @@ Ruby Version: 2.1.5p273
Gem Version: 2.4.3 Gem Version: 2.4.3
Bundler Version: 1.7.6 Bundler Version: 1.7.6
Rake Version: 10.3.2 Rake Version: 10.3.2
Redis Version: 3.2.5
Sidekiq Version: 2.17.8 Sidekiq Version: 2.17.8
GitLab information GitLab information
......
...@@ -78,7 +78,7 @@ PUT /projects/:id/environments/:environments_id ...@@ -78,7 +78,7 @@ PUT /projects/:id/environments/:environments_id
| `external_url` | string | no | The new external_url | | `external_url` | string | no | The new external_url |
```bash ```bash
curl --request PUT --data "name=staging&external_url=https://staging.example.gitlab.com" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/environment/1" curl --request PUT --data "name=staging&external_url=https://staging.example.gitlab.com" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/environments/1"
``` ```
Example response: Example response:
...@@ -106,7 +106,7 @@ DELETE /projects/:id/environments/:environment_id ...@@ -106,7 +106,7 @@ DELETE /projects/:id/environments/:environment_id
| `environment_id` | integer | yes | The ID of the environment | | `environment_id` | integer | yes | The ID of the environment |
```bash ```bash
curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/environment/1" curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/environments/1"
``` ```
Example response: Example response:
......
...@@ -129,12 +129,7 @@ module API ...@@ -129,12 +129,7 @@ module API
end end
end end
# Delete all merged branches desc 'Delete all merged branches'
#
# Parameters:
# id (required) - The ID of a project
# Example Request:
# DELETE /projects/:id/repository/branches/delete_merged
delete ":id/repository/merged_branches" do delete ":id/repository/merged_branches" do
DeleteMergedBranchesService.new(user_project, current_user).async_execute DeleteMergedBranchesService.new(user_project, current_user).async_execute
......
...@@ -53,6 +53,10 @@ module Banzai ...@@ -53,6 +53,10 @@ module Banzai
context[:project] context[:project]
end end
def skip_project_check?
context[:skip_project_check]
end
def reference_class(type) def reference_class(type)
"gfm gfm-#{type} has-tooltip" "gfm gfm-#{type} has-tooltip"
end end
......
...@@ -24,7 +24,7 @@ module Banzai ...@@ -24,7 +24,7 @@ module Banzai
end end
def call def call
return doc if project.nil? return doc if project.nil? && !skip_project_check?
ref_pattern = User.reference_pattern ref_pattern = User.reference_pattern
ref_pattern_start = /\A#{ref_pattern}\z/ ref_pattern_start = /\A#{ref_pattern}\z/
...@@ -58,7 +58,7 @@ module Banzai ...@@ -58,7 +58,7 @@ module Banzai
# have `gfm` and `gfm-project_member` class names attached for styling. # have `gfm` and `gfm-project_member` class names attached for styling.
def user_link_filter(text, link_content: nil) def user_link_filter(text, link_content: nil)
self.class.references_in(text) do |match, username| self.class.references_in(text) do |match, username|
if username == 'all' if username == 'all' && !skip_project_check?
link_to_all(link_content: link_content) link_to_all(link_content: link_content)
elsif namespace = namespaces[username] elsif namespace = namespaces[username]
link_to_namespace(namespace, link_content: link_content) || match link_to_namespace(namespace, link_content: link_content) || match
......
...@@ -52,9 +52,9 @@ module Banzai ...@@ -52,9 +52,9 @@ module Banzai
end end
# Same as +render_field+, but without consulting or updating the cache field # Same as +render_field+, but without consulting or updating the cache field
def cacheless_render_field(object, field) def cacheless_render_field(object, field, options = {})
text = object.__send__(field) text = object.__send__(field)
context = object.banzai_render_context(field) context = object.banzai_render_context(field).merge(options)
cacheless_render(text, context) cacheless_render(text, context)
end end
......
...@@ -9,7 +9,9 @@ module Gitlab ...@@ -9,7 +9,9 @@ module Gitlab
end end
def ensure_application_settings! def ensure_application_settings!
if connect_to_db? return fake_application_settings unless connect_to_db?
unless ENV['IN_MEMORY_APPLICATION_SETTINGS'] == 'true'
begin begin
settings = ::ApplicationSetting.current settings = ::ApplicationSetting.current
# In case Redis isn't running or the Redis UNIX socket file is not available # In case Redis isn't running or the Redis UNIX socket file is not available
...@@ -20,43 +22,23 @@ module Gitlab ...@@ -20,43 +22,23 @@ module Gitlab
settings ||= ::ApplicationSetting.create_from_defaults unless ActiveRecord::Migrator.needs_migration? settings ||= ::ApplicationSetting.create_from_defaults unless ActiveRecord::Migrator.needs_migration?
end end
settings || fake_application_settings settings || in_memory_application_settings
end end
def sidekiq_throttling_enabled? def sidekiq_throttling_enabled?
current_application_settings.sidekiq_throttling_enabled? current_application_settings.sidekiq_throttling_enabled?
end end
def in_memory_application_settings
@in_memory_application_settings ||= ::ApplicationSetting.new(::ApplicationSetting::DEFAULTS)
# In case migrations the application_settings table is not created yet,
# we fallback to a simple OpenStruct
rescue ActiveRecord::StatementInvalid, ActiveRecord::UnknownAttributeError
fake_application_settings
end
def fake_application_settings def fake_application_settings
OpenStruct.new( OpenStruct.new(::ApplicationSetting::DEFAULTS)
default_projects_limit: Settings.gitlab['default_projects_limit'],
default_branch_protection: Settings.gitlab['default_branch_protection'],
signup_enabled: Settings.gitlab['signup_enabled'],
signin_enabled: Settings.gitlab['signin_enabled'],
gravatar_enabled: Settings.gravatar['enabled'],
koding_enabled: false,
plantuml_enabled: false,
sign_in_text: nil,
after_sign_up_text: nil,
help_page_text: nil,
shared_runners_text: nil,
restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'],
max_attachment_size: Settings.gitlab['max_attachment_size'],
session_expire_delay: Settings.gitlab['session_expire_delay'],
default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'],
default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'],
domain_whitelist: Settings.gitlab['domain_whitelist'],
import_sources: %w[gitea github bitbucket gitlab google_code fogbugz git gitlab_project],
shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'],
max_artifacts_size: Settings.artifacts['max_size'],
require_two_factor_authentication: false,
two_factor_grace_period: 48,
akismet_enabled: false,
repository_checks_enabled: true,
container_registry_token_expire_delay: 5,
user_default_external: false,
sidekiq_throttling_enabled: false,
)
end end
private private
......
module Gitlab module Gitlab
module GithubImport module GithubImport
class ProjectCreator class ProjectCreator
include Gitlab::CurrentSettings
attr_reader :repo, :name, :namespace, :current_user, :session_data, :type attr_reader :repo, :name, :namespace, :current_user, :session_data, :type
def initialize(repo, name, namespace, current_user, session_data, type: 'github') def initialize(repo, name, namespace, current_user, session_data, type: 'github')
...@@ -34,7 +36,7 @@ module Gitlab ...@@ -34,7 +36,7 @@ module Gitlab
end end
def visibility_level def visibility_level
repo.private ? Gitlab::VisibilityLevel::PRIVATE : ApplicationSetting.current.default_project_visibility repo.private ? Gitlab::VisibilityLevel::PRIVATE : current_application_settings.default_project_visibility
end end
# #
......
...@@ -5,8 +5,6 @@ ...@@ -5,8 +5,6 @@
# #
module Gitlab module Gitlab
module ImportSources module ImportSources
extend CurrentSettings
ImportSource = Struct.new(:name, :title, :importer) ImportSource = Struct.new(:name, :title, :importer)
ImportTable = [ ImportTable = [
......
...@@ -11,8 +11,10 @@ namespace :gitlab do ...@@ -11,8 +11,10 @@ namespace :gitlab do
gem_version = run_command(%W(gem --version)) gem_version = run_command(%W(gem --version))
# check Bundler version # check Bundler version
bunder_version = run_and_match(%W(bundle --version), /[\d\.]+/).try(:to_s) bunder_version = run_and_match(%W(bundle --version), /[\d\.]+/).try(:to_s)
# check Bundler version # check Rake version
rake_version = run_and_match(%W(rake --version), /[\d\.]+/).try(:to_s) rake_version = run_and_match(%W(rake --version), /[\d\.]+/).try(:to_s)
# check redis version
redis_version = run_and_match(%W(redis-cli --version), /redis-cli (\d+\.\d+\.\d+)/).to_a
puts "" puts ""
puts "System information".color(:yellow) puts "System information".color(:yellow)
...@@ -24,6 +26,7 @@ namespace :gitlab do ...@@ -24,6 +26,7 @@ namespace :gitlab do
puts "Gem Version:\t#{gem_version || "unknown".color(:red)}" puts "Gem Version:\t#{gem_version || "unknown".color(:red)}"
puts "Bundler Version:#{bunder_version || "unknown".color(:red)}" puts "Bundler Version:#{bunder_version || "unknown".color(:red)}"
puts "Rake Version:\t#{rake_version || "unknown".color(:red)}" puts "Rake Version:\t#{rake_version || "unknown".color(:red)}"
puts "Redis Version:\t#{redis_version[1] || "unknown".color(:red)}"
puts "Sidekiq Version:#{Sidekiq::VERSION}" puts "Sidekiq Version:#{Sidekiq::VERSION}"
......
require 'spec_helper' require 'spec_helper'
describe HealthCheckController do describe HealthCheckController do
include StubENV
let(:token) { current_application_settings.health_check_access_token } let(:token) { current_application_settings.health_check_access_token }
let(:json_response) { JSON.parse(response.body) } let(:json_response) { JSON.parse(response.body) }
let(:xml_response) { Hash.from_xml(response.body)['hash'] } let(:xml_response) { Hash.from_xml(response.body)['hash'] }
before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
end
describe 'GET #index' do describe 'GET #index' do
context 'when services are up but NO access token' do context 'when services are up but NO access token' do
it 'returns a not found page' do it 'returns a not found page' do
......
...@@ -13,6 +13,7 @@ FactoryGirl.define do ...@@ -13,6 +13,7 @@ FactoryGirl.define do
factory :note_on_issue, traits: [:on_issue], aliases: [:votable_note] factory :note_on_issue, traits: [:on_issue], aliases: [:votable_note]
factory :note_on_merge_request, traits: [:on_merge_request] factory :note_on_merge_request, traits: [:on_merge_request]
factory :note_on_project_snippet, traits: [:on_project_snippet] factory :note_on_project_snippet, traits: [:on_project_snippet]
factory :note_on_personal_snippet, traits: [:on_personal_snippet]
factory :system_note, traits: [:system] factory :system_note, traits: [:system]
factory :legacy_diff_note_on_commit, traits: [:on_commit, :legacy_diff_note], class: LegacyDiffNote factory :legacy_diff_note_on_commit, traits: [:on_commit, :legacy_diff_note], class: LegacyDiffNote
...@@ -70,6 +71,11 @@ FactoryGirl.define do ...@@ -70,6 +71,11 @@ FactoryGirl.define do
noteable { create(:project_snippet, project: project) } noteable { create(:project_snippet, project: project) }
end end
trait :on_personal_snippet do
noteable { create(:personal_snippet) }
project nil
end
trait :system do trait :system do
system true system true
end end
......
require 'rails_helper' require 'rails_helper'
feature 'Admin disables Git access protocol', feature: true do feature 'Admin disables Git access protocol', feature: true do
include StubENV
let(:project) { create(:empty_project, :empty_repo) } let(:project) { create(:empty_project, :empty_repo) }
let(:admin) { create(:admin) } let(:admin) { create(:admin) }
background do background do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
login_as(admin) login_as(admin)
end end
......
require 'spec_helper' require 'spec_helper'
feature "Admin Health Check", feature: true do feature "Admin Health Check", feature: true do
include StubENV
include WaitForAjax include WaitForAjax
before do before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
login_as :admin login_as :admin
end end
...@@ -12,11 +14,12 @@ feature "Admin Health Check", feature: true do ...@@ -12,11 +14,12 @@ feature "Admin Health Check", feature: true do
visit admin_health_check_path visit admin_health_check_path
end end
it { page.has_text? 'Health Check' }
it { page.has_text? 'Health information can be retrieved' }
it 'has a health check access token' do it 'has a health check access token' do
page.has_text? 'Health Check'
page.has_text? 'Health information can be retrieved'
token = current_application_settings.health_check_access_token token = current_application_settings.health_check_access_token
expect(page).to have_content("Access token is #{token}") expect(page).to have_content("Access token is #{token}")
expect(page).to have_selector('#health-check-token', text: token) expect(page).to have_selector('#health-check-token', text: token)
end end
......
require 'spec_helper' require 'spec_helper'
describe "Admin Runners" do describe "Admin Runners" do
include StubENV
before do before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
login_as :admin login_as :admin
end end
......
require 'spec_helper' require 'spec_helper'
feature 'Admin updates settings', feature: true do feature 'Admin updates settings', feature: true do
before(:each) do include StubENV
before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
login_as :admin login_as :admin
visit admin_application_settings_path visit admin_application_settings_path
end end
......
require 'rails_helper' require 'rails_helper'
feature 'Admin uses repository checks', feature: true do feature 'Admin uses repository checks', feature: true do
before { login_as :admin } include StubENV
before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
login_as :admin
end
scenario 'to trigger a single check' do scenario 'to trigger a single check' do
project = create(:empty_project) project = create(:empty_project)
...@@ -29,7 +34,7 @@ feature 'Admin uses repository checks', feature: true do ...@@ -29,7 +34,7 @@ feature 'Admin uses repository checks', feature: true do
scenario 'to clear all repository checks', js: true do scenario 'to clear all repository checks', js: true do
visit admin_application_settings_path visit admin_application_settings_path
expect(RepositoryCheck::ClearWorker).to receive(:perform_async) expect(RepositoryCheck::ClearWorker).to receive(:perform_async)
click_link 'Clear all repository checks' click_link 'Clear all repository checks'
......
...@@ -40,6 +40,16 @@ describe 'Dropdown label', js: true, feature: true do ...@@ -40,6 +40,16 @@ describe 'Dropdown label', js: true, feature: true do
visit namespace_project_issues_path(project.namespace, project) visit namespace_project_issues_path(project.namespace, project)
end end
describe 'keyboard navigation' do
it 'selects label' do
send_keys_to_filtered_search('label:')
filtered_search.native.send_keys(:down, :down, :enter)
expect(filtered_search.value).to eq("label:~#{special_label.name} ")
end
end
describe 'behavior' do describe 'behavior' do
it 'opens when the search bar has label:' do it 'opens when the search bar has label:' do
filtered_search.set('label:') filtered_search.set('label:')
......
...@@ -20,6 +20,22 @@ describe 'Search bar', js: true, feature: true do ...@@ -20,6 +20,22 @@ describe 'Search bar', js: true, feature: true do
left_style.to_s.gsub('left: ', '').to_f left_style.to_s.gsub('left: ', '').to_f
end end
describe 'keyboard navigation' do
it 'makes item active' do
filtered_search.native.send_keys(:down)
page.within '#js-dropdown-hint' do
expect(page).to have_selector('.dropdown-active')
end
end
it 'selects item' do
filtered_search.native.send_keys(:down, :down, :enter)
expect(filtered_search.value).to eq('author:')
end
end
describe 'clear search button' do describe 'clear search button' do
it 'clears text' do it 'clears text' do
search_text = 'search_text' search_text = 'search_text'
......
require 'spec_helper'
feature 'Merge Request button', feature: true do
shared_examples 'Merge Request button only shown when allowed' do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
let(:forked_project) { create(:project, :public, forked_from_project: project) }
context 'not logged in' do
it 'does not show Create Merge Request button' do
visit url
within("#content-body") do
expect(page).not_to have_link(label)
end
end
end
context 'logged in as developer' do
before do
login_as(user)
project.team << [user, :developer]
end
it 'shows Create Merge Request button' do
href = new_namespace_project_merge_request_path(project.namespace,
project,
merge_request: { source_branch: 'feature',
target_branch: 'master' })
visit url
within("#content-body") do
expect(page).to have_link(label, href: href)
end
end
context 'merge requests are disabled' do
before do
project.project_feature.update!(merge_requests_access_level: ProjectFeature::DISABLED)
end
it 'does not show Create Merge Request button' do
visit url
within("#content-body") do
expect(page).not_to have_link(label)
end
end
end
end
context 'logged in as non-member' do
before do
login_as(user)
end
it 'does not show Create Merge Request button' do
visit url
within("#content-body") do
expect(page).not_to have_link(label)
end
end
context 'on own fork of project' do
let(:user) { forked_project.owner }
it 'shows Create Merge Request button' do
href = new_namespace_project_merge_request_path(forked_project.namespace,
forked_project,
merge_request: { source_branch: 'feature',
target_branch: 'master' })
visit fork_url
within("#content-body") do
expect(page).to have_link(label, href: href)
end
end
end
end
end
context 'on branches page' do
it_behaves_like 'Merge Request button only shown when allowed' do
let(:label) { 'Merge Request' }
let(:url) { namespace_project_branches_path(project.namespace, project) }
let(:fork_url) { namespace_project_branches_path(forked_project.namespace, forked_project) }
end
end
context 'on compare page' do
it_behaves_like 'Merge Request button only shown when allowed' do
let(:label) { 'Create Merge Request' }
let(:url) { namespace_project_compare_path(project.namespace, project, from: 'master', to: 'feature') }
let(:fork_url) { namespace_project_compare_path(forked_project.namespace, forked_project, from: 'master', to: 'feature') }
end
end
context 'on commits page' do
it_behaves_like 'Merge Request button only shown when allowed' do
let(:label) { 'Create Merge Request' }
let(:url) { namespace_project_commits_path(project.namespace, project, 'feature') }
let(:fork_url) { namespace_project_commits_path(forked_project.namespace, forked_project, 'feature') }
end
end
end
require 'spec_helper'
require_relative '../../config/initializers/metrics'
describe 'instrument_classes', lib: true do
let(:config) { double(:config) }
before do
allow(config).to receive(:instrument_method)
allow(config).to receive(:instrument_methods)
allow(config).to receive(:instrument_instance_methods)
end
it 'can autoload and instrument all files' do
expect { instrument_classes(config) }.not_to raise_error
end
end
...@@ -21,7 +21,6 @@ ...@@ -21,7 +21,6 @@
messages = $('.abuse-reports .message'); messages = $('.abuse-reports .message');
}); });
it('should truncate long messages', () => { it('should truncate long messages', () => {
const $longMessage = findMessage('LONG MESSAGE'); const $longMessage = findMessage('LONG MESSAGE');
expect($longMessage.data('original-message')).toEqual(jasmine.anything()); expect($longMessage.data('original-message')).toEqual(jasmine.anything());
......
...@@ -33,7 +33,6 @@ describe('Rollback Component', () => { ...@@ -33,7 +33,6 @@ describe('Rollback Component', () => {
expect(component.$el.querySelector('span').textContent).toContain('Re-deploy'); expect(component.$el.querySelector('span').textContent).toContain('Re-deploy');
}); });
it('Should render Rollback label when isLastDeployment is false', () => { it('Should render Rollback label when isLastDeployment is false', () => {
const component = new window.gl.environmentsList.RollbackComponent({ const component = new window.gl.environmentsList.RollbackComponent({
el: document.querySelector('.test-dom-element'), el: document.querySelector('.test-dom-element'),
......
...@@ -52,5 +52,22 @@ ...@@ -52,5 +52,22 @@
expect(value).toBe(null); expect(value).toBe(null);
}); });
}); });
describe('gl.utils.normalizedHeaders', () => {
it('should upperCase all the header keys to keep them consistent', () => {
const apiHeaders = {
'X-Something-Workhorse': { workhorse: 'ok' },
'x-something-nginx': { nginx: 'ok' },
};
const normalized = gl.utils.normalizeHeaders(apiHeaders);
const WORKHORSE = 'X-SOMETHING-WORKHORSE';
const NGINX = 'X-SOMETHING-NGINX';
expect(normalized[WORKHORSE].workhorse).toBe('ok');
expect(normalized[NGINX].nginx).toBe('ok');
});
});
}); });
})(); })();
...@@ -152,6 +152,30 @@ describe Banzai::Filter::UserReferenceFilter, lib: true do ...@@ -152,6 +152,30 @@ describe Banzai::Filter::UserReferenceFilter, lib: true do
end end
end end
context 'when a project is not specified' do
let(:project) { nil }
it 'does not link a User' do
doc = reference_filter("Hey #{reference}")
expect(doc).not_to include('a')
end
context 'when skip_project_check set to true' do
it 'links to a User' do
doc = reference_filter("Hey #{reference}", skip_project_check: true)
expect(doc.css('a').first.attr('href')).to eq urls.user_url(user)
end
it 'does not link users using @all reference' do
doc = reference_filter("Hey #{User.reference_prefix}all", skip_project_check: true)
expect(doc).not_to include('a')
end
end
end
describe '#namespaces' do describe '#namespaces' do
it 'returns a Hash containing all Namespaces' do it 'returns a Hash containing all Namespaces' do
document = Nokogiri::HTML.fragment("<p>#{user.to_reference}</p>") document = Nokogiri::HTML.fragment("<p>#{user.to_reference}</p>")
......
require 'spec_helper' require 'spec_helper'
describe Gitlab::CurrentSettings do describe Gitlab::CurrentSettings do
include StubENV
before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
end
describe '#current_application_settings' do describe '#current_application_settings' do
it 'attempts to use cached values first' do context 'with DB available' do
allow_any_instance_of(Gitlab::CurrentSettings).to receive(:connect_to_db?).and_return(true) before do
expect(ApplicationSetting).to receive(:current).and_return(::ApplicationSetting.create_from_defaults) allow_any_instance_of(Gitlab::CurrentSettings).to receive(:connect_to_db?).and_return(true)
expect(ApplicationSetting).not_to receive(:last) end
expect(current_application_settings).to be_a(ApplicationSetting) it 'attempts to use cached values first' do
end expect(ApplicationSetting).to receive(:current)
expect(ApplicationSetting).not_to receive(:last)
expect(current_application_settings).to be_a(ApplicationSetting)
end
it 'does not attempt to connect to DB or Redis' do it 'falls back to DB if Redis returns an empty value' do
allow_any_instance_of(Gitlab::CurrentSettings).to receive(:connect_to_db?).and_return(false) expect(ApplicationSetting).to receive(:last).and_call_original
expect(ApplicationSetting).not_to receive(:current)
expect(ApplicationSetting).not_to receive(:last)
expect(current_application_settings).to eq fake_application_settings expect(current_application_settings).to be_a(ApplicationSetting)
end
it 'falls back to DB if Redis fails' do
expect(ApplicationSetting).to receive(:current).and_raise(::Redis::BaseError)
expect(ApplicationSetting).to receive(:last).and_call_original
expect(current_application_settings).to be_a(ApplicationSetting)
end
end end
it 'falls back to DB if Redis returns an empty value' do context 'with DB unavailable' do
allow_any_instance_of(Gitlab::CurrentSettings).to receive(:connect_to_db?).and_return(true) before do
expect(ApplicationSetting).to receive(:last).and_call_original allow_any_instance_of(Gitlab::CurrentSettings).to receive(:connect_to_db?).and_return(false)
end
expect(current_application_settings).to be_a(ApplicationSetting) it 'returns an in-memory ApplicationSetting object' do
expect(ApplicationSetting).not_to receive(:current)
expect(ApplicationSetting).not_to receive(:last)
expect(current_application_settings).to be_a(OpenStruct)
end
end end
it 'falls back to DB if Redis fails' do context 'when ENV["IN_MEMORY_APPLICATION_SETTINGS"] is true' do
allow_any_instance_of(Gitlab::CurrentSettings).to receive(:connect_to_db?).and_return(true) before do
expect(ApplicationSetting).to receive(:current).and_raise(::Redis::BaseError) stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'true')
expect(ApplicationSetting).to receive(:last).and_call_original end
it 'returns an in-memory ApplicationSetting object' do
expect(ApplicationSetting).not_to receive(:current)
expect(ApplicationSetting).not_to receive(:last)
expect(current_application_settings).to be_a(ApplicationSetting) expect(current_application_settings).to be_a(ApplicationSetting)
expect(current_application_settings).not_to be_persisted
end
end end
end end
end end
...@@ -171,6 +171,33 @@ describe Ability, lib: true do ...@@ -171,6 +171,33 @@ describe Ability, lib: true do
end end
end end
describe '.users_that_can_read_personal_snippet' do
def users_for_snippet(snippet)
described_class.users_that_can_read_personal_snippet(users, snippet)
end
let(:users) { create_list(:user, 3) }
let(:author) { users[0] }
it 'private snippet is readable only by its author' do
snippet = create(:personal_snippet, :private, author: author)
expect(users_for_snippet(snippet)).to match_array([author])
end
it 'internal snippet is readable by all registered users' do
snippet = create(:personal_snippet, :public, author: author)
expect(users_for_snippet(snippet)).to match_array(users)
end
it 'public snippet is readable by all users' do
snippet = create(:personal_snippet, :public, author: author)
expect(users_for_snippet(snippet)).to match_array(users)
end
end
describe '.issues_readable_by_user' do describe '.issues_readable_by_user' do
context 'with an admin user' do context 'with an admin user' do
it 'returns all given issues' do it 'returns all given issues' do
......
...@@ -30,12 +30,20 @@ describe Issue, "Mentionable" do ...@@ -30,12 +30,20 @@ describe Issue, "Mentionable" do
describe '#mentioned_users' do describe '#mentioned_users' do
let!(:user) { create(:user, username: 'stranger') } let!(:user) { create(:user, username: 'stranger') }
let!(:user2) { create(:user, username: 'john') } let!(:user2) { create(:user, username: 'john') }
let!(:issue) { create(:issue, description: "#{user.to_reference} mentioned") } let!(:user3) { create(:user, username: 'jim') }
let(:issue) { create(:issue, description: "#{user.to_reference} mentioned") }
subject { issue.mentioned_users } subject { issue.mentioned_users }
it { is_expected.to include(user) } it { expect(subject).to contain_exactly(user) }
it { is_expected.not_to include(user2) }
context 'when a note on personal snippet' do
let!(:note) { create(:note_on_personal_snippet, note: "#{user.to_reference} mentioned #{user3.to_reference}") }
subject { note.mentioned_users }
it { expect(subject).to contain_exactly(user, user3) }
end
end end
describe '#referenced_mentionables' do describe '#referenced_mentionables' do
...@@ -138,6 +146,16 @@ describe Issue, "Mentionable" do ...@@ -138,6 +146,16 @@ describe Issue, "Mentionable" do
issue.update_attributes(description: issues[1].to_reference) issue.update_attributes(description: issues[1].to_reference)
issue.create_new_cross_references! issue.create_new_cross_references!
end end
it 'notifies new references from project snippet note' do
snippet = create(:snippet, project: project)
note = create(:note, note: issues[0].to_reference, noteable: snippet, project: project, author: author)
expect(SystemNoteService).to receive(:cross_reference).with(issues[1], any_args)
note.update_attributes(note: issues[1].to_reference)
note.create_new_cross_references!
end
end end
def create_issue(description:) def create_issue(description:)
......
...@@ -68,4 +68,14 @@ describe Group, 'Routable' do ...@@ -68,4 +68,14 @@ describe Group, 'Routable' do
end end
end end
end end
describe '.member_descendants' do
let!(:user) { create(:user) }
let!(:nested_group) { create(:group, parent: group) }
before { group.add_owner(user) }
subject { described_class.member_descendants(user.id) }
it { is_expected.to eq([nested_group]) }
end
end end
...@@ -269,6 +269,12 @@ describe Group, models: true do ...@@ -269,6 +269,12 @@ describe Group, models: true do
it 'returns the canonical URL' do it 'returns the canonical URL' do
expect(group.web_url).to include("groups/#{group.name}") expect(group.web_url).to include("groups/#{group.name}")
end end
context 'nested group' do
let(:nested_group) { create(:group, :nested) }
it { expect(nested_group.web_url).to include("groups/#{nested_group.full_path}") }
end
end end
describe 'nested group' do describe 'nested group' do
......
...@@ -5,6 +5,8 @@ describe Namespace, models: true do ...@@ -5,6 +5,8 @@ describe Namespace, models: true do
it { is_expected.to have_many :projects } it { is_expected.to have_many :projects }
it { is_expected.to have_many :project_statistics } it { is_expected.to have_many :project_statistics }
it { is_expected.to belong_to :parent }
it { is_expected.to have_many :children }
it { is_expected.to validate_presence_of(:name) } it { is_expected.to validate_presence_of(:name) }
it { is_expected.to validate_uniqueness_of(:name).scoped_to(:parent_id) } it { is_expected.to validate_uniqueness_of(:name).scoped_to(:parent_id) }
...@@ -189,17 +191,31 @@ describe Namespace, models: true do ...@@ -189,17 +191,31 @@ describe Namespace, models: true do
it { expect(nested_group.full_name).to eq("#{group.name} / #{nested_group.name}") } it { expect(nested_group.full_name).to eq("#{group.name} / #{nested_group.name}") }
end end
describe '#parents' do describe '#ancestors' do
let(:group) { create(:group) } let(:group) { create(:group) }
let(:nested_group) { create(:group, parent: group) } let(:nested_group) { create(:group, parent: group) }
let(:deep_nested_group) { create(:group, parent: nested_group) } let(:deep_nested_group) { create(:group, parent: nested_group) }
let(:very_deep_nested_group) { create(:group, parent: deep_nested_group) } let(:very_deep_nested_group) { create(:group, parent: deep_nested_group) }
it 'returns the correct parents' do it 'returns the correct ancestors' do
expect(very_deep_nested_group.parents).to eq([group, nested_group, deep_nested_group]) expect(very_deep_nested_group.ancestors).to eq([group, nested_group, deep_nested_group])
expect(deep_nested_group.parents).to eq([group, nested_group]) expect(deep_nested_group.ancestors).to eq([group, nested_group])
expect(nested_group.parents).to eq([group]) expect(nested_group.ancestors).to eq([group])
expect(group.parents).to eq([]) expect(group.ancestors).to eq([])
end
end
describe '#descendants' do
let!(:group) { create(:group) }
let!(:nested_group) { create(:group, parent: group) }
let!(:deep_nested_group) { create(:group, parent: nested_group) }
let!(:very_deep_nested_group) { create(:group, parent: deep_nested_group) }
it 'returns the correct descendants' do
expect(very_deep_nested_group.descendants.to_a).to eq([])
expect(deep_nested_group.descendants.to_a).to eq([very_deep_nested_group])
expect(nested_group.descendants.to_a).to eq([deep_nested_group, very_deep_nested_group])
expect(group.descendants.to_a).to eq([nested_group, deep_nested_group, very_deep_nested_group])
end end
end end
end end
...@@ -52,6 +52,19 @@ describe Note, models: true do ...@@ -52,6 +52,19 @@ describe Note, models: true do
subject { create(:note) } subject { create(:note) }
it { is_expected.to be_valid } it { is_expected.to be_valid }
end end
context 'when project is missing for a project related note' do
subject { build(:note, project: nil, noteable: build_stubbed(:issue)) }
it { is_expected.to be_invalid }
end
context 'when noteable is a personal snippet' do
subject { build(:note_on_personal_snippet) }
it 'is valid without project' do
is_expected.to be_valid
end
end
end end
describe "Commit notes" do describe "Commit notes" do
...@@ -139,6 +152,7 @@ describe Note, models: true do ...@@ -139,6 +152,7 @@ describe Note, models: true do
with([{ with([{
text: note1.note, text: note1.note,
context: { context: {
skip_project_check: false,
pipeline: :note, pipeline: :note,
cache_key: [note1, "note"], cache_key: [note1, "note"],
project: note1.project, project: note1.project,
...@@ -150,6 +164,7 @@ describe Note, models: true do ...@@ -150,6 +164,7 @@ describe Note, models: true do
with([{ with([{
text: note2.note, text: note2.note,
context: { context: {
skip_project_check: false,
pipeline: :note, pipeline: :note,
cache_key: [note2, "note"], cache_key: [note2, "note"],
project: note2.project, project: note2.project,
...@@ -306,4 +321,70 @@ describe Note, models: true do ...@@ -306,4 +321,70 @@ describe Note, models: true do
end end
end end
end end
describe '#for_personal_snippet?' do
it 'returns false for a project snippet note' do
expect(build(:note_on_project_snippet).for_personal_snippet?).to be_falsy
end
it 'returns true for a personal snippet note' do
expect(build(:note_on_personal_snippet).for_personal_snippet?).to be_truthy
end
end
describe '#to_ability_name' do
it 'returns snippet for a project snippet note' do
expect(build(:note_on_project_snippet).to_ability_name).to eq('snippet')
end
it 'returns personal_snippet for a personal snippet note' do
expect(build(:note_on_personal_snippet).to_ability_name).to eq('personal_snippet')
end
it 'returns merge_request for an MR note' do
expect(build(:note_on_merge_request).to_ability_name).to eq('merge_request')
end
it 'returns issue for an issue note' do
expect(build(:note_on_issue).to_ability_name).to eq('issue')
end
it 'returns issue for a commit note' do
expect(build(:note_on_commit).to_ability_name).to eq('commit')
end
end
describe '#cache_markdown_field' do
let(:html) { '<p>some html</p>'}
context 'note for a project snippet' do
let(:note) { build(:note_on_project_snippet) }
before do
expect(Banzai::Renderer).to receive(:cacheless_render_field).
with(note, :note, { skip_project_check: false }).and_return(html)
note.save
end
it 'creates a note' do
expect(note.note_html).to eq(html)
end
end
context 'note for a personal snippet' do
let(:note) { build(:note_on_personal_snippet) }
before do
expect(Banzai::Renderer).to receive(:cacheless_render_field).
with(note, :note, { skip_project_check: true }).and_return(html)
note.save
end
it 'creates a note' do
expect(note.note_html).to eq(html)
end
end
end
end end
...@@ -14,7 +14,7 @@ describe Route, models: true do ...@@ -14,7 +14,7 @@ describe Route, models: true do
it { is_expected.to validate_uniqueness_of(:path) } it { is_expected.to validate_uniqueness_of(:path) }
end end
describe '#rename_children' do describe '#rename_descendants' do
let!(:nested_group) { create(:group, path: "test", parent: group) } let!(:nested_group) { create(:group, path: "test", parent: group) }
let!(:deep_nested_group) { create(:group, path: "foo", parent: nested_group) } let!(:deep_nested_group) { create(:group, path: "foo", parent: nested_group) }
let!(:similar_group) { create(:group, path: 'gitlab-org') } let!(:similar_group) { create(:group, path: 'gitlab-org') }
......
...@@ -797,14 +797,14 @@ describe User, models: true do ...@@ -797,14 +797,14 @@ describe User, models: true do
describe '#avatar_type' do describe '#avatar_type' do
let(:user) { create(:user) } let(:user) { create(:user) }
it "is true if avatar is image" do it 'is true if avatar is image' do
user.update_attribute(:avatar, 'uploads/avatar.png') user.update_attribute(:avatar, 'uploads/avatar.png')
expect(user.avatar_type).to be_truthy expect(user.avatar_type).to be_truthy
end end
it "is false if avatar is html page" do it 'is false if avatar is html page' do
user.update_attribute(:avatar, 'uploads/avatar.html') user.update_attribute(:avatar, 'uploads/avatar.html')
expect(user.avatar_type).to eq(["only images allowed"]) expect(user.avatar_type).to eq(['only images allowed'])
end end
end end
...@@ -926,8 +926,8 @@ describe User, models: true do ...@@ -926,8 +926,8 @@ describe User, models: true do
end end
end end
describe "#starred?" do describe '#starred?' do
it "determines if user starred a project" do it 'determines if user starred a project' do
user = create :user user = create :user
project1 = create(:empty_project, :public) project1 = create(:empty_project, :public)
project2 = create(:empty_project, :public) project2 = create(:empty_project, :public)
...@@ -953,8 +953,8 @@ describe User, models: true do ...@@ -953,8 +953,8 @@ describe User, models: true do
end end
end end
describe "#toggle_star" do describe '#toggle_star' do
it "toggles stars" do it 'toggles stars' do
user = create :user user = create :user
project = create(:empty_project, :public) project = create(:empty_project, :public)
...@@ -966,31 +966,44 @@ describe User, models: true do ...@@ -966,31 +966,44 @@ describe User, models: true do
end end
end end
describe "#sort" do describe '#sort' do
before do before do
User.delete_all User.delete_all
@user = create :user, created_at: Date.today, last_sign_in_at: Date.today, name: 'Alpha' @user = create :user, created_at: Date.today, last_sign_in_at: Date.today, name: 'Alpha'
@user1 = create :user, created_at: Date.today - 1, last_sign_in_at: Date.today - 1, name: 'Omega' @user1 = create :user, created_at: Date.today - 1, last_sign_in_at: Date.today - 1, name: 'Omega'
@user2 = create :user, created_at: Date.today - 2, last_sign_in_at: nil, name: 'Beta'
end end
it "sorts users by the recent sign-in time" do context 'when sort by recent_sign_in' do
expect(User.sort('recent_sign_in').first).to eq(@user) it 'sorts users by the recent sign-in time' do
expect(User.sort('recent_sign_in').first).to eq(@user)
end
it 'pushes users who never signed in to the end' do
expect(User.sort('recent_sign_in').third).to eq(@user2)
end
end end
it "sorts users by the oldest sign-in time" do context 'when sort by oldest_sign_in' do
expect(User.sort('oldest_sign_in').first).to eq(@user1) it 'sorts users by the oldest sign-in time' do
expect(User.sort('oldest_sign_in').first).to eq(@user1)
end
it 'pushes users who never signed in to the end' do
expect(User.sort('oldest_sign_in').third).to eq(@user2)
end
end end
it "sorts users in descending order by their creation time" do it 'sorts users in descending order by their creation time' do
expect(User.sort('created_desc').first).to eq(@user) expect(User.sort('created_desc').first).to eq(@user)
end end
it "sorts users in ascending order by their creation time" do it 'sorts users in ascending order by their creation time' do
expect(User.sort('created_asc').first).to eq(@user1) expect(User.sort('created_asc').first).to eq(@user2)
end end
it "sorts users by id in descending order when nil is passed" do it 'sorts users by id in descending order when nil is passed' do
expect(User.sort(nil).first).to eq(@user1) expect(User.sort(nil).first).to eq(@user2)
end end
end end
...@@ -1350,6 +1363,39 @@ describe User, models: true do ...@@ -1350,6 +1363,39 @@ describe User, models: true do
end end
end end
describe '#nested_groups' do
let!(:user) { create(:user) }
let!(:group) { create(:group) }
let!(:nested_group) { create(:group, parent: group) }
before do
group.add_owner(user)
# Add more data to ensure method does not include wrong groups
create(:group).add_owner(create(:user))
end
it { expect(user.nested_groups).to eq([nested_group]) }
end
describe '#nested_projects' do
let!(:user) { create(:user) }
let!(:group) { create(:group) }
let!(:nested_group) { create(:group, parent: group) }
let!(:project) { create(:project, namespace: group) }
let!(:nested_project) { create(:project, namespace: nested_group) }
before do
group.add_owner(user)
# Add more data to ensure method does not include wrong projects
other_project = create(:project, namespace: create(:group, :nested))
other_project.add_developer(create(:user))
end
it { expect(user.nested_projects).to eq([nested_project]) }
end
describe '#refresh_authorized_projects', redis: true do describe '#refresh_authorized_projects', redis: true do
let(:project1) { create(:empty_project) } let(:project1) { create(:empty_project) }
let(:project2) { create(:empty_project) } let(:project2) { create(:empty_project) }
......
...@@ -337,8 +337,7 @@ describe API::Internal, api: true do ...@@ -337,8 +337,7 @@ describe API::Internal, api: true do
context 'ssh access has been disabled' do context 'ssh access has been disabled' do
before do before do
settings = ::ApplicationSetting.create_from_defaults stub_application_setting(enabled_git_access_protocol: 'http')
settings.update_attribute(:enabled_git_access_protocol, 'http')
end end
it 'rejects the SSH push' do it 'rejects the SSH push' do
...@@ -360,8 +359,7 @@ describe API::Internal, api: true do ...@@ -360,8 +359,7 @@ describe API::Internal, api: true do
context 'http access has been disabled' do context 'http access has been disabled' do
before do before do
settings = ::ApplicationSetting.create_from_defaults stub_application_setting(enabled_git_access_protocol: 'ssh')
settings.update_attribute(:enabled_git_access_protocol, 'ssh')
end end
it 'rejects the HTTP push' do it 'rejects the HTTP push' do
...@@ -383,8 +381,7 @@ describe API::Internal, api: true do ...@@ -383,8 +381,7 @@ describe API::Internal, api: true do
context 'web actions are always allowed' do context 'web actions are always allowed' do
it 'allows WEB push' do it 'allows WEB push' do
settings = ::ApplicationSetting.create_from_defaults stub_application_setting(enabled_git_access_protocol: 'ssh')
settings.update_attribute(:enabled_git_access_protocol, 'ssh')
project.team << [user, :developer] project.team << [user, :developer]
push(key, project, 'web') push(key, project, 'web')
......
...@@ -13,7 +13,12 @@ describe 'cycle analytics events' do ...@@ -13,7 +13,12 @@ describe 'cycle analytics events' do
allow_any_instance_of(Gitlab::ReferenceExtractor).to receive(:issues).and_return([issue]) allow_any_instance_of(Gitlab::ReferenceExtractor).to receive(:issues).and_return([issue])
3.times { create_cycle } 3.times do |count|
Timecop.freeze(Time.now + count.days) do
create_cycle
end
end
deploy_master deploy_master
login_as(user) login_as(user)
......
...@@ -15,39 +15,45 @@ describe Notes::CreateService, services: true do ...@@ -15,39 +15,45 @@ describe Notes::CreateService, services: true do
context "valid params" do context "valid params" do
it 'returns a valid note' do it 'returns a valid note' do
note = Notes::CreateService.new(project, user, opts).execute note = described_class.new(project, user, opts).execute
expect(note).to be_valid expect(note).to be_valid
end end
it 'returns a persisted note' do it 'returns a persisted note' do
note = Notes::CreateService.new(project, user, opts).execute note = described_class.new(project, user, opts).execute
expect(note).to be_persisted expect(note).to be_persisted
end end
it 'note has valid content' do it 'note has valid content' do
note = Notes::CreateService.new(project, user, opts).execute note = described_class.new(project, user, opts).execute
expect(note.note).to eq(opts[:note]) expect(note.note).to eq(opts[:note])
end end
it 'note belongs to the correct project' do
note = described_class.new(project, user, opts).execute
expect(note.project).to eq(project)
end
it 'TodoService#new_note is called' do it 'TodoService#new_note is called' do
note = build(:note) note = build(:note, project: project)
allow(project).to receive_message_chain(:notes, :new).with(opts) { note } allow(Note).to receive(:new).with(opts) { note }
expect_any_instance_of(TodoService).to receive(:new_note).with(note, user) expect_any_instance_of(TodoService).to receive(:new_note).with(note, user)
Notes::CreateService.new(project, user, opts).execute described_class.new(project, user, opts).execute
end end
it 'enqueues NewNoteWorker' do it 'enqueues NewNoteWorker' do
note = build(:note, id: 999) note = build(:note, id: 999, project: project)
allow(project).to receive_message_chain(:notes, :new).with(opts) { note } allow(Note).to receive(:new).with(opts) { note }
expect(NewNoteWorker).to receive(:perform_async).with(note.id) expect(NewNoteWorker).to receive(:perform_async).with(note.id)
Notes::CreateService.new(project, user, opts).execute described_class.new(project, user, opts).execute
end end
end end
...@@ -75,6 +81,27 @@ describe Notes::CreateService, services: true do ...@@ -75,6 +81,27 @@ describe Notes::CreateService, services: true do
end end
end end
end end
describe 'personal snippet note' do
subject { described_class.new(nil, user, params).execute }
let(:snippet) { create(:personal_snippet) }
let(:params) do
{ note: 'comment', noteable_type: 'Snippet', noteable_id: snippet.id }
end
it 'returns a valid note' do
expect(subject).to be_valid
end
it 'returns a persisted note' do
expect(subject).to be_persisted
end
it 'note has valid content' do
expect(subject.note).to eq(params[:note])
end
end
end end
describe "award emoji" do describe "award emoji" do
...@@ -88,7 +115,7 @@ describe Notes::CreateService, services: true do ...@@ -88,7 +115,7 @@ describe Notes::CreateService, services: true do
noteable_type: 'Issue', noteable_type: 'Issue',
noteable_id: issue.id noteable_id: issue.id
} }
note = Notes::CreateService.new(project, user, opts).execute note = described_class.new(project, user, opts).execute
expect(note).to be_valid expect(note).to be_valid
expect(note.name).to eq('smile') expect(note.name).to eq('smile')
...@@ -100,7 +127,7 @@ describe Notes::CreateService, services: true do ...@@ -100,7 +127,7 @@ describe Notes::CreateService, services: true do
noteable_type: 'Issue', noteable_type: 'Issue',
noteable_id: issue.id noteable_id: issue.id
} }
note = Notes::CreateService.new(project, user, opts).execute note = described_class.new(project, user, opts).execute
expect(note).to be_valid expect(note).to be_valid
expect(note.note).to eq(opts[:note]) expect(note.note).to eq(opts[:note])
...@@ -115,7 +142,7 @@ describe Notes::CreateService, services: true do ...@@ -115,7 +142,7 @@ describe Notes::CreateService, services: true do
expect_any_instance_of(TodoService).to receive(:new_award_emoji).with(issue, user) expect_any_instance_of(TodoService).to receive(:new_award_emoji).with(issue, user)
Notes::CreateService.new(project, user, opts).execute described_class.new(project, user, opts).execute
end end
end end
end end
...@@ -269,6 +269,55 @@ describe NotificationService, services: true do ...@@ -269,6 +269,55 @@ describe NotificationService, services: true do
end end
end end
context 'personal snippet note' do
let(:snippet) { create(:personal_snippet, :public, author: @u_snippet_author) }
let(:note) { create(:note_on_personal_snippet, noteable: snippet, note: '@mentioned note', author: @u_note_author) }
before do
@u_watcher = create_global_setting_for(create(:user), :watch)
@u_participant = create_global_setting_for(create(:user), :participating)
@u_disabled = create_global_setting_for(create(:user), :disabled)
@u_mentioned = create_global_setting_for(create(:user, username: 'mentioned'), :mention)
@u_mentioned_level = create_global_setting_for(create(:user, username: 'participator'), :mention)
@u_note_author = create(:user, username: 'note_author')
@u_snippet_author = create(:user, username: 'snippet_author')
@u_not_mentioned = create_global_setting_for(create(:user, username: 'regular'), :participating)
reset_delivered_emails!
end
let!(:notes) do
[
create(:note_on_personal_snippet, noteable: snippet, note: 'note', author: @u_watcher),
create(:note_on_personal_snippet, noteable: snippet, note: 'note', author: @u_participant),
create(:note_on_personal_snippet, noteable: snippet, note: 'note', author: @u_mentioned),
create(:note_on_personal_snippet, noteable: snippet, note: 'note', author: @u_disabled),
create(:note_on_personal_snippet, noteable: snippet, note: 'note', author: @u_note_author),
]
end
describe '#new_note' do
it 'notifies the participants' do
notification.new_note(note)
# it emails participants
should_email(@u_watcher)
should_email(@u_participant)
should_email(@u_watcher)
should_email(@u_snippet_author)
# it emails mentioned users
should_email(@u_mentioned)
# it does not email participants with mention notification level
should_not_email(@u_mentioned_level)
# it does not email note author
should_not_email(@u_note_author)
end
end
end
context 'commit note' do context 'commit note' do
let(:project) { create(:project, :public) } let(:project) { create(:project, :public) }
let(:note) { create(:note_on_commit, project: project) } let(:note) { create(:note_on_commit, project: project) }
......
...@@ -2,6 +2,7 @@ require './spec/simplecov_env' ...@@ -2,6 +2,7 @@ require './spec/simplecov_env'
SimpleCovEnv.start! SimpleCovEnv.start!
ENV["RAILS_ENV"] ||= 'test' ENV["RAILS_ENV"] ||= 'test'
ENV["IN_MEMORY_APPLICATION_SETTINGS"] = 'true'
require File.expand_path("../../config/environment", __FILE__) require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails' require 'rspec/rails'
......
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