Commit 779d6e80 authored by Rémy Coutable's avatar Rémy Coutable

Merge remote-tracking branch 'ce/master' into rc/ce-upstream

Signed-off-by: default avatarRémy Coutable <remy@rymai.me>
parents d5549025 f08d756e
...@@ -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
...@@ -264,6 +264,9 @@ ...@@ -264,6 +264,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
......
...@@ -159,5 +159,19 @@ ...@@ -159,5 +159,19 @@
if (!results[2]) return ''; if (!results[2]) return '';
return decodeURIComponent(results[2].replace(/\+/g, ' ')); return decodeURIComponent(results[2].replace(/\+/g, ' '));
}; };
/**
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;
} }
......
...@@ -288,6 +288,10 @@ ...@@ -288,6 +288,10 @@
} }
} }
} }
.tooltip {
white-space: nowrap;
}
} }
.build-link { .build-link {
......
...@@ -962,8 +962,32 @@ a.allowed-to-push { ...@@ -962,8 +962,32 @@ a.allowed-to-push {
.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;
}
} }
} }
......
...@@ -14,12 +14,8 @@ class ConfirmationsController < Devise::ConfirmationsController ...@@ -14,12 +14,8 @@ class ConfirmationsController < Devise::ConfirmationsController
if signed_in?(resource_name) if signed_in?(resource_name)
after_sign_in_path_for(resource) after_sign_in_path_for(resource)
else else
sign_in(resource) flash[:notice] += " Please sign in."
if signed_in?(resource_name) new_session_path(resource_name)
after_sign_in_path_for(resource)
else
new_session_path(resource_name)
end
end end
end end
end end
...@@ -46,6 +46,8 @@ class SearchController < ApplicationController ...@@ -46,6 +46,8 @@ class SearchController < ApplicationController
end end
@search_objects = @search_results.objects(@scope, params[:page]) @search_objects = @search_results.objects(@scope, params[:page])
check_single_commit_result
end end
def autocomplete def autocomplete
...@@ -60,4 +62,16 @@ class SearchController < ApplicationController ...@@ -60,4 +62,16 @@ class SearchController < ApplicationController
render json: search_autocomplete_opts(term).to_json render json: search_autocomplete_opts(term).to_json
end end
private
def check_single_commit_result
if @search_results.single_commit_result?
only_commit = @search_results.objects('commits').first
query = params[:search].strip.downcase
found_by_commit_sha = Commit.valid_hash?(query) && only_commit.sha.start_with?(query)
redirect_to namespace_project_commit_path(@project.namespace, @project, only_commit) if found_by_commit_sha
end
end
end end
...@@ -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.
......
...@@ -55,6 +55,7 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -55,6 +55,7 @@ class ApplicationSetting < ActiveRecord::Base
user_default_external: false user_default_external: false
} }
<<<<<<< HEAD
DEFAULTS_EE = { DEFAULTS_EE = {
elasticsearch_host: ENV['ELASTIC_HOST'] || 'localhost', elasticsearch_host: ENV['ELASTIC_HOST'] || 'localhost',
elasticsearch_port: ENV['ELASTIC_PORT'] || '9200', elasticsearch_port: ENV['ELASTIC_PORT'] || '9200',
...@@ -62,6 +63,9 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -62,6 +63,9 @@ class ApplicationSetting < ActiveRecord::Base
} }
DEFAULTS = DEFAULTS_CE.merge(DEFAULTS_EE) DEFAULTS = DEFAULTS_CE.merge(DEFAULTS_EE)
=======
DEFAULTS = DEFAULTS_CE
>>>>>>> ce/master
serialize :restricted_visibility_levels serialize :restricted_visibility_levels
serialize :import_sources serialize :import_sources
...@@ -226,10 +230,13 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -226,10 +230,13 @@ class ApplicationSetting < ActiveRecord::Base
def self.create_from_defaults def self.create_from_defaults
create(DEFAULTS) create(DEFAULTS)
<<<<<<< HEAD
end end
def elasticsearch_host def elasticsearch_host
read_attribute(:elasticsearch_host).split(',').map(&:strip) read_attribute(:elasticsearch_host).split(',').map(&:strip)
=======
>>>>>>> ce/master
end end
def home_page_url_column_exist def home_page_url_column_exist
......
...@@ -21,6 +21,9 @@ class Commit ...@@ -21,6 +21,9 @@ class Commit
DIFF_HARD_LIMIT_FILES = 1000 DIFF_HARD_LIMIT_FILES = 1000
DIFF_HARD_LIMIT_LINES = 50000 DIFF_HARD_LIMIT_LINES = 50000
# The SHA can be between 7 and 40 hex characters.
COMMIT_SHA_PATTERN = '\h{7,40}'
class << self class << self
def decorate(commits, project) def decorate(commits, project)
commits.map do |commit| commits.map do |commit|
...@@ -52,6 +55,10 @@ class Commit ...@@ -52,6 +55,10 @@ class Commit
def from_hash(hash, project) def from_hash(hash, project)
new(Gitlab::Git::Commit.new(hash), project) new(Gitlab::Git::Commit.new(hash), project)
end end
def valid_hash?(key)
!!(/\A#{COMMIT_SHA_PATTERN}\z/ =~ key)
end
end end
attr_accessor :raw attr_accessor :raw
...@@ -77,8 +84,6 @@ class Commit ...@@ -77,8 +84,6 @@ class Commit
# Pattern used to extract commit references from text # Pattern used to extract commit references from text
# #
# The SHA can be between 7 and 40 hex characters.
#
# This pattern supports cross-project references. # This pattern supports cross-project references.
def self.reference_pattern def self.reference_pattern
@reference_pattern ||= %r{ @reference_pattern ||= %r{
...@@ -88,7 +93,7 @@ class Commit ...@@ -88,7 +93,7 @@ class Commit
end end
def self.link_reference_pattern def self.link_reference_pattern
@link_reference_pattern ||= super("commit", /(?<commit>\h{7,40})/) @link_reference_pattern ||= super("commit", /(?<commit>#{COMMIT_SHA_PATTERN})/)
end end
def to_reference(from_project = nil, full: false) def to_reference(from_project = nil, full: false)
......
...@@ -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
......
...@@ -11,7 +11,7 @@ module Taskable ...@@ -11,7 +11,7 @@ module Taskable
INCOMPLETE = 'incomplete'.freeze INCOMPLETE = 'incomplete'.freeze
ITEM_PATTERN = / ITEM_PATTERN = /
^ ^
(?:\s*[-+*]|(?:\d+\.))? # optional list prefix \s*(?:[-+*]|(?:\d+\.))? # optional list prefix
\s* # optional whitespace prefix \s* # optional whitespace prefix
(\[\s\]|\[[xX]\]) # checkbox (\[\s\]|\[[xX]\]) # checkbox
(\s.+) # followed by whitespace and some text. (\s.+) # followed by whitespace and some text.
......
...@@ -245,7 +245,7 @@ class Group < Namespace ...@@ -245,7 +245,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
......
...@@ -916,9 +916,11 @@ class MergeRequest < ActiveRecord::Base ...@@ -916,9 +916,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
......
...@@ -196,8 +196,26 @@ class Namespace < ActiveRecord::Base ...@@ -196,8 +196,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
......
...@@ -44,7 +44,8 @@ class Note < ActiveRecord::Base ...@@ -44,7 +44,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 }
...@@ -54,7 +55,7 @@ class Note < ActiveRecord::Base ...@@ -54,7 +55,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
...@@ -85,7 +86,7 @@ class Note < ActiveRecord::Base ...@@ -85,7 +86,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
...@@ -171,6 +172,14 @@ class Note < ActiveRecord::Base ...@@ -171,6 +172,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?
...@@ -226,6 +235,10 @@ class Note < ActiveRecord::Base ...@@ -226,6 +235,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
...@@ -192,8 +192,8 @@ class User < ActiveRecord::Base ...@@ -192,8 +192,8 @@ class User < ActiveRecord::Base
joins(:identities).where(identities: { provider: provider }) joins(:identities).where(identities: { provider: provider })
end end
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").
...@@ -461,6 +461,15 @@ class User < ActiveRecord::Base ...@@ -461,6 +461,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
......
...@@ -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)
......
...@@ -195,8 +195,15 @@ class NotificationService ...@@ -195,8 +195,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)
...@@ -209,11 +216,13 @@ class NotificationService ...@@ -209,11 +216,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)
...@@ -228,8 +237,7 @@ class NotificationService ...@@ -228,8 +237,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
......
...@@ -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
......
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,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
......
...@@ -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
= render 'projects/commits/mirror_status' = render 'projects/commits/mirror_status'
......
...@@ -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
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment