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() {
if (this.hidden) {
// debugger // debugger
this.list.style.display = 'block'; this.list.style.display = 'block';
this.currentIndex = 0;
this.hidden = false; this.hidden = false;
}
}, },
hide: function() { hide: function() {
if (!this.hidden) {
// debugger // debugger
this.list.style.display = 'none'; this.list.style.display = 'none';
this.currentIndex = 0;
this.hidden = true; 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;
}
} }
}; };
}); });
......
...@@ -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;
}); });
}, },
......
...@@ -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);
}); });
hiddenCount = matches.filter(function(o) {
return !o.droplab_hidden;
}).length;
if (dataHiddenCount !== hiddenCount) {
list.render(matches); list.render(matches);
list.currentIndex = 0;
}
}, },
init: function init(hookInput) { init: function init(hookInput) {
......
...@@ -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,15 +64,28 @@ ...@@ -64,15 +64,28 @@
} }
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();
if (!activeElements.length) {
// Prevent droplab from opening dropdown // Prevent droplab from opening dropdown
this.dropdownManager.destroyDroplab(); this.dropdownManager.destroyDroplab();
this.search(); this.search();
} }
} }
}
toggleClearSearchButton(e) { toggleClearSearchButton(e) {
if (e.target.value) { if (e.target.value) {
......
...@@ -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" data-toggle="dropdown"
type="button" title="Artifacts"
data-placement="top"
data-toggle="dropdown"
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)
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) 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,11 +865,13 @@ class MergeRequest < ActiveRecord::Base ...@@ -865,11 +865,13 @@ class MergeRequest < ActiveRecord::Base
paths: paths paths: paths
) )
transaction do
active_diff_notes.each do |note| active_diff_notes.each do |note|
service.execute(note) service.execute(note)
Gitlab::Timeless.timeless(note, &:save) Gitlab::Timeless.timeless(note, &:save)
end end
end end
end
def keep_around_commit def keep_around_commit
project.repository.keep_around(self.merge_commit_sha) project.repository.keep_around(self.merge_commit_sha)
......
...@@ -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,7 +3,8 @@ module Notes ...@@ -3,7 +3,8 @@ 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.project = project
note.author = current_user note.author = current_user
note.system = false note.system = false
......
...@@ -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)
unless note.for_personal_snippet?
# Merge project watchers # Merge project watchers
recipients = add_project_watchers(recipients, note.project) 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
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment