Commit 79e1ed24 authored by Zeger-Jan van de Weg's avatar Zeger-Jan van de Weg

Merge branch 'master' into 'zj-format-chat-messages'

# Conflicts:
#   spec/lib/gitlab/chat_commands/command_spec.rb
parents dbda72a7 f27721e8
...@@ -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,22 @@ ...@@ -2,6 +2,22 @@
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.2 (2017-01-25)
- allow issue filter bar to be operated with mouse only. !8681
- Fix CI requests concurrency for newer runners that prevents from picking pending builds (from 1.9.0-rc5). !8760
- Add some basic fixes for IE11/Edge.
- Remove blue border from comment box hover.
- Fixed bug where links in merge dropdown wouldn't work.
## 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
...@@ -395,6 +411,14 @@ entry. ...@@ -395,6 +411,14 @@ entry.
- Whitelist next project names: help, ci, admin, search. !8227 - Whitelist next project names: help, ci, admin, search. !8227
- Adds back CSS for progress-bars. !8237 - Adds back CSS for progress-bars. !8237
## 8.14.8 (2017-01-25)
- Accept environment variables from the `pre-receive` script. !7967
- Milestoneish SQL performance partially improved and memoized. !8146
- Fix N+1 queries on milestone show pages. !8185
- Speed up group milestone index by passing group_id to IssuesFinder. !8363
- Ensure issuable state changes only fire webhooks once.
## 8.14.6 (2017-01-10) ## 8.14.6 (2017-01-10)
- Update the gitlab-markup gem to the version 1.5.1. !8509 - Update the gitlab-markup gem to the version 1.5.1. !8509
......
...@@ -80,11 +80,9 @@ the remaining issues on the GitHub issue tracker. ...@@ -80,11 +80,9 @@ the remaining issues on the GitHub issue tracker.
## I want to contribute! ## I want to contribute!
If you want to contribute to GitLab, but are not sure where to start, If you want to contribute to GitLab, but are not sure where to start,
look for [issues with the label `up-for-grabs`][up-for-grabs]. These issues look for [issues with the label `Accepting Merge Requests` and weight < 5][accepting-mrs-weight].
will be of reasonable size and challenge, for anyone to start contributing to These issues will be of reasonable size and challenge, for anyone to start
GitLab. contributing to GitLab.
This was inspired by [an article by Kent C. Dodds][medium-up-for-grabs].
## Implement design & UI elements ## Implement design & UI elements
...@@ -214,16 +212,19 @@ associated with in the description of the issue. ...@@ -214,16 +212,19 @@ associated with in the description of the issue.
## Merge requests ## Merge requests
We welcome merge requests with fixes and improvements to GitLab code, tests, We welcome merge requests with fixes and improvements to GitLab code, tests,
and/or documentation. The features we would really like a merge request for are and/or documentation. The issues that are specifically suitable for
listed with the label [`Accepting Merge Requests` on our issue tracker for CE][accepting-mrs-ce] community contributions are listed with the label
and [EE][accepting-mrs-ee] but other improvements are also welcome. Please note [`Accepting Merge Requests` on our issue tracker for CE][accepting-mrs-ce]
that if an issue is marked for the current milestone either before or while you and [EE][accepting-mrs-ee], but you are free to contribute to any other issue
are working on it, a team member may take over the merge request in order to you want.
ensure the work is finished before the release date.
Please note that if an issue is marked for the current milestone either before
or while you are working on it, a team member may take over the merge request
in order to ensure the work is finished before the release date.
If you want to add a new feature that is not labeled it is best to first create If you want to add a new feature that is not labeled it is best to first create
a feedback issue (if there isn't one already) and leave a comment asking for it a feedback issue (if there isn't one already) and leave a comment asking for it
to be marked as `Accepting merge requests`. Please include screenshots or to be marked as `Accepting Merge Requests`. Please include screenshots or
wireframes if the feature will also change the UI. wireframes if the feature will also change the UI.
Merge requests should be opened at [GitLab.com][gitlab-mr-tracker]. Merge requests should be opened at [GitLab.com][gitlab-mr-tracker].
...@@ -285,14 +286,6 @@ request is as follows: ...@@ -285,14 +286,6 @@ request is as follows:
1. For tests that use Capybara or PhantomJS, see this [article on how 1. For tests that use Capybara or PhantomJS, see this [article on how
to write reliable asynchronous tests](https://robots.thoughtbot.com/write-reliable-asynchronous-integration-tests-with-capybara). to write reliable asynchronous tests](https://robots.thoughtbot.com/write-reliable-asynchronous-integration-tests-with-capybara).
The **official merge window** is in the beginning of the month from the 1st to
the 7th day of the month. This is the best time to submit an MR and get
feedback fast. Before this time the GitLab Inc. team is still dealing with work
that is created by the monthly release such as regressions requiring patch
releases. After the 7th it is already getting closer to the release date of the
next version. This means there is less time to fix the issues created by
merging large new features.
Please keep the change in a single MR **as small as possible**. If you want to Please keep the change in a single MR **as small as possible**. If you want to
contribute a large feature think very hard what the minimum viable change is. contribute a large feature think very hard what the minimum viable change is.
Can you split the functionality? Can you only submit the backend/API code? Can Can you split the functionality? Can you only submit the backend/API code? Can
...@@ -450,8 +443,7 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor ...@@ -450,8 +443,7 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor
[core team]: https://about.gitlab.com/core-team/ [core team]: https://about.gitlab.com/core-team/
[getting-help]: https://about.gitlab.com/getting-help/ [getting-help]: https://about.gitlab.com/getting-help/
[codetriage]: http://www.codetriage.com/gitlabhq/gitlabhq [codetriage]: http://www.codetriage.com/gitlabhq/gitlabhq
[up-for-grabs]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=up-for-grabs [accepting-mrs-weight]: https://gitlab.com/gitlab-org/gitlab-ce/issues?assignee_id=0&label_name[]=Accepting%20Merge%20Requests&sort=weight_asc
[medium-up-for-grabs]: https://medium.com/@kentcdodds/first-timers-only-78281ea47455
[ce-tracker]: https://gitlab.com/gitlab-org/gitlab-ce/issues [ce-tracker]: https://gitlab.com/gitlab-org/gitlab-ce/issues
[ee-tracker]: https://gitlab.com/gitlab-org/gitlab-ee/issues [ee-tracker]: https://gitlab.com/gitlab-org/gitlab-ee/issues
[google-group]: https://groups.google.com/forum/#!forum/gitlabhq [google-group]: https://groups.google.com/forum/#!forum/gitlabhq
......
...@@ -322,7 +322,7 @@ group :test do ...@@ -322,7 +322,7 @@ group :test do
gem 'email_spec', '~> 1.6.0' gem 'email_spec', '~> 1.6.0'
gem 'json-schema', '~> 2.6.2' gem 'json-schema', '~> 2.6.2'
gem 'webmock', '~> 1.21.0' gem 'webmock', '~> 1.21.0'
gem 'test_after_commit', '~> 0.4.2' gem 'test_after_commit', '~> 1.1'
gem 'sham_rack', '~> 1.3.6' gem 'sham_rack', '~> 1.3.6'
gem 'timecop', '~> 0.8.0' gem 'timecop', '~> 0.8.0'
end end
......
...@@ -760,7 +760,7 @@ GEM ...@@ -760,7 +760,7 @@ GEM
teaspoon-jasmine (2.2.0) teaspoon-jasmine (2.2.0)
teaspoon (>= 1.0.0) teaspoon (>= 1.0.0)
temple (0.7.7) temple (0.7.7)
test_after_commit (0.4.2) test_after_commit (1.1.0)
activerecord (>= 3.2) activerecord (>= 3.2)
thin (1.7.0) thin (1.7.0)
daemons (~> 1.0, >= 1.0.9) daemons (~> 1.0, >= 1.0.9)
...@@ -997,7 +997,7 @@ DEPENDENCIES ...@@ -997,7 +997,7 @@ DEPENDENCIES
sys-filesystem (~> 1.1.6) sys-filesystem (~> 1.1.6)
teaspoon (~> 1.1.0) teaspoon (~> 1.1.0)
teaspoon-jasmine (~> 2.2.0) teaspoon-jasmine (~> 2.2.0)
test_after_commit (~> 0.4.2) test_after_commit (~> 1.1)
thin (~> 1.7.0) thin (~> 1.7.0)
timecop (~> 0.8.0) timecop (~> 0.8.0)
truncato (~> 0.7.8) truncato (~> 0.7.8)
......
This diff is collapsed.
...@@ -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) {
......
...@@ -367,9 +367,14 @@ ...@@ -367,9 +367,14 @@
return $input.trigger('keyup'); return $input.trigger('keyup');
}, },
isLoading(data) { isLoading(data) {
if (!data || !data.length) return false; var dataToInspect = data;
if (Array.isArray(data)) data = data[0]; if (data && data.length > 0) {
return data === this.defaultLoadingData[0] || data.name === this.defaultLoadingData[0]; dataToInspect = data[0];
}
var loadingState = this.defaultLoadingData[0];
return dataToInspect &&
(dataToInspect === loadingState || dataToInspect.name === loadingState);
} }
}; };
}).call(this); }).call(this);
...@@ -651,18 +651,14 @@ ...@@ -651,18 +651,14 @@
isMarking = false; isMarking = false;
el.removeClass(ACTIVE_CLASS); el.removeClass(ACTIVE_CLASS);
if (field && field.length) { if (field && field.length) {
if (isInput) { this.clearField(field, isInput);
field.val('');
} else {
field.remove();
}
} }
} else if (el.hasClass(INDETERMINATE_CLASS)) { } else if (el.hasClass(INDETERMINATE_CLASS)) {
isMarking = true; isMarking = true;
el.addClass(ACTIVE_CLASS); el.addClass(ACTIVE_CLASS);
el.removeClass(INDETERMINATE_CLASS); el.removeClass(INDETERMINATE_CLASS);
if (field && field.length && value == null) { if (field && field.length && value == null) {
field.remove(); this.clearField(field, isInput);
} }
if ((!field || !field.length) && fieldName) { if ((!field || !field.length) && fieldName) {
this.addInput(fieldName, value, selectedObject); this.addInput(fieldName, value, selectedObject);
...@@ -676,7 +672,7 @@ ...@@ -676,7 +672,7 @@
} }
} }
if (field && field.length && value == null) { if (field && field.length && value == null) {
field.remove(); this.clearField(field, isInput);
} }
// Toggle active class for the tick mark // Toggle active class for the tick mark
el.addClass(ACTIVE_CLASS); el.addClass(ACTIVE_CLASS);
...@@ -826,6 +822,10 @@ ...@@ -826,6 +822,10 @@
return $(this.el).find(".dropdown-toggle-text").text(this.options.toggleLabel(selected, el, instance)); return $(this.el).find(".dropdown-toggle-text").text(this.options.toggleLabel(selected, el, instance));
}; };
GitLabDropdown.prototype.clearField = function(field, isInput) {
return isInput ? field.val('') : field.remove();
};
return GitLabDropdown; return GitLabDropdown;
})(); })();
......
...@@ -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
......
...@@ -336,7 +336,11 @@ ...@@ -336,7 +336,11 @@
.removeClass('is-active'); .removeClass('is-active');
} }
if ($dropdown.hasClass('js-filter-bulk-update') || $dropdown.hasClass('js-issuable-form-dropdown')) { if ($dropdown.hasClass('js-issuable-form-dropdown')) {
return;
}
if ($dropdown.hasClass('js-filter-bulk-update')) {
_this.enableBulkLabelDropdown(); _this.enableBulkLabelDropdown();
_this.setDropdownData($dropdown, isMarking, this.id(label)); _this.setDropdownData($dropdown, isMarking, this.id(label));
return; return;
......
...@@ -159,5 +159,75 @@ ...@@ -159,5 +159,75 @@
if (!results[2]) return ''; if (!results[2]) return '';
return decodeURIComponent(results[2].replace(/\+/g, ' ')); return decodeURIComponent(results[2].replace(/\+/g, ' '));
}; };
w.gl.utils.getSelectedFragment = () => {
const selection = window.getSelection();
const documentFragment = selection.getRangeAt(0).cloneContents();
if (documentFragment.textContent.length === 0) return null;
return documentFragment;
};
w.gl.utils.insertText = (target, text) => {
// Firefox doesn't support `document.execCommand('insertText', false, text)` on textareas
const selectionStart = target.selectionStart;
const selectionEnd = target.selectionEnd;
const value = target.value;
const textBefore = value.substring(0, selectionStart);
const textAfter = value.substring(selectionEnd, value.length);
const newText = textBefore + text + textAfter;
target.value = newText;
target.selectionStart = target.selectionEnd = selectionStart + text.length;
// Trigger autosave
$(target).trigger('input');
// Trigger autosize
var event = document.createEvent('Event');
event.initEvent('autosize:update', true, false);
target.dispatchEvent(event);
};
w.gl.utils.nodeMatchesSelector = (node, selector) => {
const matches = Element.prototype.matches ||
Element.prototype.matchesSelector ||
Element.prototype.mozMatchesSelector ||
Element.prototype.msMatchesSelector ||
Element.prototype.oMatchesSelector ||
Element.prototype.webkitMatchesSelector;
if (matches) {
return matches.call(node, selector);
}
// IE11 doesn't support `node.matches(selector)`
let parentNode = node.parentNode;
if (!parentNode) {
parentNode = document.createElement('div');
node = node.cloneNode(true);
parentNode.appendChild(node);
}
const matchingNodes = parentNode.querySelectorAll(selector);
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
*/ */
......
...@@ -69,12 +69,17 @@ ...@@ -69,12 +69,17 @@
search: { search: {
fields: ['text'] fields: ['text']
}, },
id: this.getSearchText,
data: this.getData.bind(this), data: this.getData.bind(this),
selectable: true, selectable: true,
clicked: this.onClick.bind(this) clicked: this.onClick.bind(this)
}); });
} }
getSearchText(selectedObject, el) {
return selectedObject.id ? selectedObject.text : '';
}
getData(term, callback) { getData(term, callback) {
var _this, contents, jqXHR; var _this, contents, jqXHR;
_this = this; _this = this;
...@@ -364,7 +369,7 @@ ...@@ -364,7 +369,7 @@
onClick(item, $el, e) { onClick(item, $el, e) {
if (location.pathname.indexOf(item.url) !== -1) { if (location.pathname.indexOf(item.url) !== -1) {
e.preventDefault(); if (!e.metaKey) e.preventDefault();
if (!this.badgePresent) { if (!this.badgePresent) {
if (item.category === 'Projects') { if (item.category === 'Projects') {
this.projectInputEl.val(item.id); this.projectInputEl.val(item.id);
......
...@@ -39,29 +39,39 @@ ...@@ -39,29 +39,39 @@
} }
ShortcutsIssuable.prototype.replyWithSelectedText = function() { ShortcutsIssuable.prototype.replyWithSelectedText = function() {
var quote, replyField, selected, separator; var quote, replyField, documentFragment, selected, separator;
if (window.getSelection) {
selected = window.getSelection().toString(); documentFragment = window.gl.utils.getSelectedFragment();
if (!documentFragment) return;
// If the documentFragment contains more than just Markdown, don't copy as GFM.
if (documentFragment.querySelector('.md, .wiki')) return;
selected = window.gl.CopyAsGFM.nodeToGFM(documentFragment);
replyField = $('.js-main-target-form #note_note'); replyField = $('.js-main-target-form #note_note');
if (selected.trim() === "") { if (selected.trim() === "") {
return; return;
} }
// Put a '>' character before each non-empty line in the selection
quote = _.map(selected.split("\n"), function(val) { quote = _.map(selected.split("\n"), function(val) {
if (val.trim() !== '') { return ("> " + val).trim() + "\n";
return "> " + val + "\n";
}
}); });
// If replyField already has some content, add a newline before our quote // If replyField already has some content, add a newline before our quote
separator = replyField.val().trim() !== "" && "\n" || ''; separator = replyField.val().trim() !== "" && "\n\n" || '';
replyField.val(function(_, current) { replyField.val(function(_, current) {
return current + separator + quote.join('') + "\n"; return current + separator + quote.join('') + "\n";
}); });
// Trigger autosave for the added text
// Trigger autosave
replyField.trigger('input'); replyField.trigger('input');
// Trigger autosize
var event = document.createEvent('Event');
event.initEvent('autosize:update', true, false);
replyField.get(0).dispatchEvent(event);
// Focus the input field // Focus the input field
return replyField.focus(); return replyField.focus();
}
}; };
ShortcutsIssuable.prototype.editIssue = function() { ShortcutsIssuable.prototype.editIssue = function() {
......
(() => {
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;
} }
......
...@@ -155,7 +155,8 @@ ul.content-list { ...@@ -155,7 +155,8 @@ ul.content-list {
} }
> .btn, > .btn,
> .btn-group { > .btn-group,
> .dropdown.inline {
margin-right: $gl-padding-top; margin-right: $gl-padding-top;
display: inline-block; display: inline-block;
margin-top: 3px; margin-top: 3px;
......
...@@ -13,6 +13,8 @@ $dark-main-bg: #1d1f21; ...@@ -13,6 +13,8 @@ $dark-main-bg: #1d1f21;
$dark-main-color: #1d1f21; $dark-main-color: #1d1f21;
$dark-line-color: #c5c8c6; $dark-line-color: #c5c8c6;
$dark-line-num-color: rgba(255, 255, 255, 0.3); $dark-line-num-color: rgba(255, 255, 255, 0.3);
$dark-line-num-color-new: #627165;
$dark-line-num-color-old: #806565;
$dark-diff-not-empty-bg: #557; $dark-diff-not-empty-bg: #557;
$dark-highlight-bg: #ffe792; $dark-highlight-bg: #ffe792;
$dark-highlight-color: $black; $dark-highlight-color: $black;
...@@ -89,7 +91,6 @@ $dark-il: #de935f; ...@@ -89,7 +91,6 @@ $dark-il: #de935f;
.diff-line-num, .diff-line-num,
.diff-line-num a { .diff-line-num a {
color: $dark-main-color;
color: $dark-line-num-color; color: $dark-line-num-color;
} }
...@@ -121,11 +122,21 @@ $dark-il: #de935f; ...@@ -121,11 +122,21 @@ $dark-il: #de935f;
.diff-line-num.new, .diff-line-num.new,
.line_content.new { .line_content.new {
@include diff_background($dark-new-bg, $dark-new-idiff, $dark-border); @include diff_background($dark-new-bg, $dark-new-idiff, $dark-border);
&::before,
a {
color: $dark-line-num-color-new;
}
} }
.diff-line-num.old, .diff-line-num.old,
.line_content.old { .line_content.old {
@include diff_background($dark-old-bg, $dark-old-idiff, $dark-border); @include diff_background($dark-old-bg, $dark-old-idiff, $dark-border);
&::before,
a {
color: $dark-line-num-color-old;
}
} }
.line_content.match { .line_content.match {
......
...@@ -7,6 +7,8 @@ $monokai-bg: #272822; ...@@ -7,6 +7,8 @@ $monokai-bg: #272822;
$monokai-border: #555; $monokai-border: #555;
$monokai-text-color: #f8f8f2; $monokai-text-color: #f8f8f2;
$monokai-line-num-color: rgba(255, 255, 255, 0.3); $monokai-line-num-color: rgba(255, 255, 255, 0.3);
$monokai-line-num-color-new: #707565;
$monokai-line-num-color-old: #7e736f;
$monokai-line-empty-bg: #49483e; $monokai-line-empty-bg: #49483e;
$monokai-line-empty-border: darken($monokai-line-empty-bg, 15%); $monokai-line-empty-border: darken($monokai-line-empty-bg, 15%);
$monokai-diff-border: #808080; $monokai-diff-border: #808080;
...@@ -120,11 +122,21 @@ $monokai-gi: #a6e22e; ...@@ -120,11 +122,21 @@ $monokai-gi: #a6e22e;
.diff-line-num.new, .diff-line-num.new,
.line_content.new { .line_content.new {
@include diff_background($monokai-new-bg, $monokai-new-idiff, $monokai-diff-border); @include diff_background($monokai-new-bg, $monokai-new-idiff, $monokai-diff-border);
&::before,
a {
color: $monokai-line-num-color-new;
}
} }
.diff-line-num.old, .diff-line-num.old,
.line_content.old { .line_content.old {
@include diff_background($monokai-old-bg, $monokai-old-idiff, $monokai-diff-border); @include diff_background($monokai-old-bg, $monokai-old-idiff, $monokai-diff-border);
&::before,
a {
color: $monokai-line-num-color-old;
}
} }
.line_content.match { .line_content.match {
......
...@@ -13,6 +13,8 @@ $solarized-dark-pre-color: #93a1a1; ...@@ -13,6 +13,8 @@ $solarized-dark-pre-color: #93a1a1;
$solarized-dark-pre-border: #113b46; $solarized-dark-pre-border: #113b46;
$solarized-dark-line-bg: #002b36; $solarized-dark-line-bg: #002b36;
$solarized-dark-line-color: rgba(255, 255, 255, 0.3); $solarized-dark-line-color: rgba(255, 255, 255, 0.3);
$solarized-dark-line-color-new: #5a766c;
$solarized-dark-line-color-old: #7a6c71;
$solarized-dark-highlight: #094554; $solarized-dark-highlight: #094554;
$solarized-dark-hll-bg: #174652; $solarized-dark-hll-bg: #174652;
$solarized-dark-c: #586e75; $solarized-dark-c: #586e75;
...@@ -124,11 +126,21 @@ $solarized-dark-il: #2aa198; ...@@ -124,11 +126,21 @@ $solarized-dark-il: #2aa198;
.diff-line-num.new, .diff-line-num.new,
.line_content.new { .line_content.new {
@include diff_background($solarized-dark-new-bg, $solarized-dark-new-idiff, $solarized-dark-border); @include diff_background($solarized-dark-new-bg, $solarized-dark-new-idiff, $solarized-dark-border);
&::before,
a {
color: $solarized-dark-line-color-new;
}
} }
.diff-line-num.old, .diff-line-num.old,
.line_content.old { .line_content.old {
@include diff_background($solarized-dark-old-bg, $solarized-dark-old-idiff, $solarized-dark-border); @include diff_background($solarized-dark-old-bg, $solarized-dark-old-idiff, $solarized-dark-border);
&::before,
a {
color: $solarized-dark-line-color-old;
}
} }
.line_content.match { .line_content.match {
......
...@@ -13,6 +13,9 @@ $solarized-light-pre-bg: #002b36; ...@@ -13,6 +13,9 @@ $solarized-light-pre-bg: #002b36;
$solarized-light-pre-bg: #fdf6e3; $solarized-light-pre-bg: #fdf6e3;
$solarized-light-pre-color: #586e75; $solarized-light-pre-color: #586e75;
$solarized-light-line-bg: #fdf6e3; $solarized-light-line-bg: #fdf6e3;
$solarized-light-line-color: rgba(0, 0, 0, 0.3);
$solarized-light-line-color-new: #a1a080;
$solarized-light-line-color-old: #ad9186;
$solarized-light-highlight: #eee8d5; $solarized-light-highlight: #eee8d5;
$solarized-light-hll-bg: #ddd8c5; $solarized-light-hll-bg: #ddd8c5;
$solarized-light-c: #93a1a1; $solarized-light-c: #93a1a1;
...@@ -98,7 +101,7 @@ $solarized-light-il: #2aa198; ...@@ -98,7 +101,7 @@ $solarized-light-il: #2aa198;
.diff-line-num, .diff-line-num,
.diff-line-num a { .diff-line-num a {
color: $black-transparent; color: $solarized-light-line-color;
} }
// Code itself // Code itself
...@@ -130,11 +133,21 @@ $solarized-light-il: #2aa198; ...@@ -130,11 +133,21 @@ $solarized-light-il: #2aa198;
.line_content.new { .line_content.new {
@include diff_background($solarized-light-new-bg, @include diff_background($solarized-light-new-bg,
$solarized-light-new-idiff, $solarized-light-border); $solarized-light-new-idiff, $solarized-light-border);
&::before,
a {
color: $solarized-light-line-color-new;
}
} }
.diff-line-num.old, .diff-line-num.old,
.line_content.old { .line_content.old {
@include diff_background($solarized-light-old-bg, $solarized-light-old-idiff, $solarized-light-border); @include diff_background($solarized-light-old-bg, $solarized-light-old-idiff, $solarized-light-border);
&::before,
a {
color: $solarized-light-line-color-old;
}
} }
.line_content.match { .line_content.match {
......
...@@ -108,11 +108,19 @@ $white-gc-bg: #eaf2f5; ...@@ -108,11 +108,19 @@ $white-gc-bg: #eaf2f5;
&.old { &.old {
background-color: $line-number-old; background-color: $line-number-old;
border-color: $line-removed-dark; border-color: $line-removed-dark;
a {
color: scale-color($line-number-old,$red: -30%, $green: -30%, $blue: -30%);
}
} }
&.new { &.new {
background-color: $line-number-new; background-color: $line-number-new;
border-color: $line-added-dark; border-color: $line-added-dark;
a {
color: scale-color($line-number-new,$red: -30%, $green: -30%, $blue: -30%);
}
} }
&.hll:not(.empty-cell) { &.hll:not(.empty-cell) {
...@@ -125,6 +133,10 @@ $white-gc-bg: #eaf2f5; ...@@ -125,6 +133,10 @@ $white-gc-bg: #eaf2f5;
&.old { &.old {
background-color: $line-removed; background-color: $line-removed;
&::before {
color: scale-color($line-number-old,$red: -30%, $green: -30%, $blue: -30%);
}
span.idiff { span.idiff {
background-color: $line-removed-dark; background-color: $line-removed-dark;
} }
...@@ -133,6 +145,10 @@ $white-gc-bg: #eaf2f5; ...@@ -133,6 +145,10 @@ $white-gc-bg: #eaf2f5;
&.new { &.new {
background-color: $line-added; background-color: $line-added;
&::before {
color: scale-color($line-number-new,$red: -30%, $green: -30%, $blue: -30%);
}
span.idiff { span.idiff {
background-color: $line-added-dark; background-color: $line-added-dark;
} }
......
...@@ -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;
}
} }
} }
......
...@@ -32,6 +32,10 @@ ...@@ -32,6 +32,10 @@
.last-commit { .last-commit {
@include str-truncated(506px); @include str-truncated(506px);
.fa-angle-right {
margin-left: 5px;
}
@media (min-width: $screen-md-min) and (max-width: $screen-md-max) { @media (min-width: $screen-md-min) and (max-width: $screen-md-max) {
@include str-truncated(450px); @include str-truncated(450px);
} }
......
...@@ -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
......
...@@ -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)
after_sign_in_path_for(resource)
else
new_session_path(resource_name) new_session_path(resource_name)
end end
end end
end
end end
...@@ -109,12 +109,14 @@ class Projects::GitHttpClientController < Projects::ApplicationController ...@@ -109,12 +109,14 @@ class Projects::GitHttpClientController < Projects::ApplicationController
end end
def repository def repository
_, suffix = project_id_with_suffix wiki? ? project.wiki.repository : project.repository
if suffix == '.wiki.git'
project.wiki.repository
else
project.repository
end end
def wiki?
return @wiki if defined?(@wiki)
_, suffix = project_id_with_suffix
@wiki = suffix == '.wiki.git'
end end
def render_not_found def render_not_found
......
...@@ -84,7 +84,7 @@ class Projects::GitHttpController < Projects::GitHttpClientController ...@@ -84,7 +84,7 @@ class Projects::GitHttpController < Projects::GitHttpClientController
end end
def access def access
@access ||= Gitlab::GitAccess.new(user, project, 'http', authentication_abilities: authentication_abilities) @access ||= access_klass.new(user, project, 'http', authentication_abilities: authentication_abilities)
end end
def access_check def access_check
...@@ -102,4 +102,8 @@ class Projects::GitHttpController < Projects::GitHttpClientController ...@@ -102,4 +102,8 @@ class Projects::GitHttpController < Projects::GitHttpClientController
access_check.allowed? access_check.allowed?
end end
def access_klass
@access_klass ||= wiki? ? Gitlab::GitAccessWiki : Gitlab::GitAccess
end
end end
...@@ -45,6 +45,8 @@ class SearchController < ApplicationController ...@@ -45,6 +45,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
...@@ -59,4 +61,16 @@ class SearchController < ApplicationController ...@@ -59,4 +61,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.
......
...@@ -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
......
...@@ -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)
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
......
...@@ -11,8 +11,8 @@ module Taskable ...@@ -11,8 +11,8 @@ module Taskable
INCOMPLETE = 'incomplete'.freeze INCOMPLETE = 'incomplete'.freeze
ITEM_PATTERN = / ITEM_PATTERN = /
^ ^
(?:\s*[-+*]|(?:\d+\.))? # optional list prefix \s*(?:[-+*]|(?:\d+\.)) # list prefix required - task item has to be always in a list
\s* # optional whitespace prefix \s+ # whitespace prefix has to be always presented for a list item
(\[\s\]|\[[xX]\]) # checkbox (\[\s\]|\[[xX]\]) # checkbox
(\s.+) # followed by whitespace and some text. (\s.+) # followed by whitespace and some text.
/x /x
......
...@@ -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
......
...@@ -68,9 +68,9 @@ class Member < ActiveRecord::Base ...@@ -68,9 +68,9 @@ class Member < ActiveRecord::Base
after_create :send_request, if: :request?, unless: :importing? after_create :send_request, if: :request?, unless: :importing?
after_create :create_notification_setting, unless: [:pending?, :importing?] after_create :create_notification_setting, unless: [:pending?, :importing?]
after_create :post_create_hook, unless: [:pending?, :importing?] after_create :post_create_hook, unless: [:pending?, :importing?]
after_create :refresh_member_authorized_projects, if: :importing?
after_update :post_update_hook, unless: [:pending?, :importing?] after_update :post_update_hook, unless: [:pending?, :importing?]
after_destroy :post_destroy_hook, unless: :pending? after_destroy :post_destroy_hook, unless: :pending?
after_commit :refresh_member_authorized_projects
delegate :name, :username, :email, to: :user, prefix: true delegate :name, :username, :email, to: :user, prefix: true
...@@ -147,8 +147,6 @@ class Member < ActiveRecord::Base ...@@ -147,8 +147,6 @@ class Member < ActiveRecord::Base
member.save member.save
end end
UserProjectAccessChangedService.new(user.id).execute if user.is_a?(User)
member member
end end
...@@ -275,23 +273,27 @@ class Member < ActiveRecord::Base ...@@ -275,23 +273,27 @@ class Member < ActiveRecord::Base
end end
def post_create_hook def post_create_hook
UserProjectAccessChangedService.new(user.id).execute
system_hook_service.execute_hooks_for(self, :create) system_hook_service.execute_hooks_for(self, :create)
end end
def post_update_hook def post_update_hook
UserProjectAccessChangedService.new(user.id).execute if access_level_changed? # override in sub class
end end
def post_destroy_hook def post_destroy_hook
refresh_member_authorized_projects
system_hook_service.execute_hooks_for(self, :destroy) system_hook_service.execute_hooks_for(self, :destroy)
end end
# Refreshes authorizations of the current member.
#
# This method schedules a job using Sidekiq and as such **must not** be called
# in a transaction. Doing so can lead to the job running before the
# transaction has been committed, resulting in the job either throwing an
# error or not doing any meaningful work.
def refresh_member_authorized_projects def refresh_member_authorized_projects
# If user/source is being destroyed, project access are gonna be destroyed eventually # If user/source is being destroyed, project access are going to be
# because of DB foreign keys, so we shouldn't bother with refreshing after each # destroyed eventually because of DB foreign keys, so we shouldn't bother
# member is destroyed through association # with refreshing after each member is destroyed through association
return if destroyed_by_association.present? return if destroyed_by_association.present?
UserProjectAccessChangedService.new(user_id).execute UserProjectAccessChangedService.new(user_id).execute
......
...@@ -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)
......
...@@ -4,6 +4,7 @@ class Namespace < ActiveRecord::Base ...@@ -4,6 +4,7 @@ class Namespace < ActiveRecord::Base
include CacheMarkdownField include CacheMarkdownField
include Sortable include Sortable
include Gitlab::ShellAdapter include Gitlab::ShellAdapter
include Gitlab::CurrentSettings
include Routable include Routable
cache_markdown_field :description, pipeline: :description cache_markdown_field :description, pipeline: :description
...@@ -176,6 +177,10 @@ class Namespace < ActiveRecord::Base ...@@ -176,6 +177,10 @@ class Namespace < ActiveRecord::Base
end end
end end
def shared_runners_enabled?
projects.with_shared_runners.any?
end
def full_name def full_name
@full_name ||= @full_name ||=
if parent if parent
...@@ -185,8 +190,26 @@ class Namespace < ActiveRecord::Base ...@@ -185,8 +190,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
......
...@@ -121,8 +121,6 @@ class Project < ActiveRecord::Base ...@@ -121,8 +121,6 @@ class Project < ActiveRecord::Base
# Merge Requests for target project should be removed with it # Merge Requests for target project should be removed with it
has_many :merge_requests, dependent: :destroy, foreign_key: 'target_project_id' has_many :merge_requests, dependent: :destroy, foreign_key: 'target_project_id'
# Merge requests from source project should be kept when source project was removed
has_many :fork_merge_requests, foreign_key: 'source_project_id', class_name: 'MergeRequest'
has_many :issues, dependent: :destroy has_many :issues, dependent: :destroy
has_many :labels, dependent: :destroy, class_name: 'ProjectLabel' has_many :labels, dependent: :destroy, class_name: 'ProjectLabel'
has_many :services, dependent: :destroy has_many :services, dependent: :destroy
...@@ -226,6 +224,7 @@ class Project < ActiveRecord::Base ...@@ -226,6 +224,7 @@ class Project < ActiveRecord::Base
scope :with_project_feature, -> { joins('LEFT JOIN project_features ON projects.id = project_features.project_id') } scope :with_project_feature, -> { joins('LEFT JOIN project_features ON projects.id = project_features.project_id') }
scope :with_statistics, -> { includes(:statistics) } scope :with_statistics, -> { includes(:statistics) }
scope :with_shared_runners, -> { where(shared_runners_enabled: true) }
# "enabled" here means "not disabled". It includes private features! # "enabled" here means "not disabled". It includes private features!
scope :with_feature_enabled, ->(feature) { scope :with_feature_enabled, ->(feature) {
...@@ -1098,12 +1097,20 @@ class Project < ActiveRecord::Base ...@@ -1098,12 +1097,20 @@ class Project < ActiveRecord::Base
project_feature.update_attribute(:builds_access_level, ProjectFeature::ENABLED) project_feature.update_attribute(:builds_access_level, ProjectFeature::ENABLED)
end end
def shared_runners_available?
shared_runners_enabled?
end
def shared_runners
shared_runners_available? ? Ci::Runner.shared : Ci::Runner.none
end
def any_runners?(&block) def any_runners?(&block)
if runners.active.any?(&block) if runners.active.any?(&block)
return true return true
end end
shared_runners_enabled? && Ci::Runner.shared.active.any?(&block) shared_runners.active.any?(&block)
end end
def valid_runners_token?(token) def valid_runners_token?(token)
......
...@@ -16,8 +16,7 @@ class ProjectGroupLink < ActiveRecord::Base ...@@ -16,8 +16,7 @@ class ProjectGroupLink < ActiveRecord::Base
validates :group_access, inclusion: { in: Gitlab::Access.values }, presence: true validates :group_access, inclusion: { in: Gitlab::Access.values }, presence: true
validate :different_group validate :different_group
after_create :refresh_group_members_authorized_projects after_commit :refresh_group_members_authorized_projects
after_destroy :refresh_group_members_authorized_projects
def self.access_options def self.access_options
Gitlab::Access.options Gitlab::Access.options
......
...@@ -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
...@@ -2,48 +2,72 @@ module Ci ...@@ -2,48 +2,72 @@ module Ci
# This class responsible for assigning # This class responsible for assigning
# proper pending build to runner on runner API request # proper pending build to runner on runner API request
class RegisterBuildService class RegisterBuildService
def execute(current_runner) include Gitlab::CurrentSettings
builds = Ci::Build.pending.unstarted
builds = attr_reader :runner
if current_runner.shared?
builds.
# don't run projects which have not enabled shared runners and builds
joins(:project).where(projects: { shared_runners_enabled: true }).
joins('LEFT JOIN project_features ON ci_builds.gl_project_id = project_features.project_id').
# this returns builds that are ordered by number of running builds Result = Struct.new(:build, :valid?)
# we prefer projects that don't use shared runners at all
joins("LEFT JOIN (#{running_builds_for_shared_runners.to_sql}) AS project_builds ON ci_builds.gl_project_id=project_builds.gl_project_id"). def initialize(runner)
where('project_features.builds_access_level IS NULL or project_features.builds_access_level > 0'). @runner = runner
order('COALESCE(project_builds.running_builds, 0) ASC', 'ci_builds.id ASC') end
def execute
builds =
if runner.shared?
builds_for_shared_runner
else else
# do run projects which are only assigned to this runner (FIFO) builds_for_specific_runner
builds.where(project: current_runner.projects.with_builds_enabled).order('created_at ASC')
end end
build = builds.find do |build| build = builds.find do |build|
current_runner.can_pick?(build) runner.can_pick?(build)
end end
if build if build
# In case when 2 runners try to assign the same build, second runner will be declined # In case when 2 runners try to assign the same build, second runner will be declined
# with StateMachines::InvalidTransition or StaleObjectError when doing run! or save method. # with StateMachines::InvalidTransition or StaleObjectError when doing run! or save method.
build.runner_id = current_runner.id build.runner_id = runner.id
build.run! build.run!
end end
build Result.new(build, true)
rescue StateMachines::InvalidTransition, ActiveRecord::StaleObjectError rescue StateMachines::InvalidTransition, ActiveRecord::StaleObjectError
nil Result.new(build, false)
end end
private private
def builds_for_shared_runner
new_builds.
# don't run projects which have not enabled shared runners and builds
joins(:project).where(projects: { shared_runners_enabled: true }).
joins('LEFT JOIN project_features ON ci_builds.gl_project_id = project_features.project_id').
where('project_features.builds_access_level IS NULL or project_features.builds_access_level > 0').
# Implement fair scheduling
# this returns builds that are ordered by number of running builds
# we prefer projects that don't use shared runners at all
joins("LEFT JOIN (#{running_builds_for_shared_runners.to_sql}) AS project_builds ON ci_builds.gl_project_id=project_builds.gl_project_id").
order('COALESCE(project_builds.running_builds, 0) ASC', 'ci_builds.id ASC')
end
def builds_for_specific_runner
new_builds.where(project: runner.projects.with_builds_enabled).order('created_at ASC')
end
def running_builds_for_shared_runners def running_builds_for_shared_runners
Ci::Build.running.where(runner: Ci::Runner.shared). Ci::Build.running.where(runner: Ci::Runner.shared).
group(:gl_project_id).select(:gl_project_id, 'count(*) AS running_builds') group(:gl_project_id).select(:gl_project_id, 'count(*) AS running_builds')
end end
def new_builds
Ci::Build.pending.unstarted
end
def shared_runner_build_limits_feature_enabled?
ENV['DISABLE_SHARED_RUNNER_BUILD_MINUTES_LIMIT'].to_s != 'true'
end
end end
end end
...@@ -38,15 +38,13 @@ module MergeRequests ...@@ -38,15 +38,13 @@ module MergeRequests
private private
def merge_requests_for(branch) # Returns all origin and fork merge requests from `@project` satisfying passed arguments.
origin_merge_requests = @project.origin_merge_requests def merge_requests_for(source_branch, mr_states: [:opened])
.opened.where(source_branch: branch).to_a MergeRequest
.with_state(mr_states)
fork_merge_requests = @project.fork_merge_requests .where(source_branch: source_branch, source_project_id: @project.id)
.opened.where(source_branch: branch).to_a .preload(:source_project) # we don't need a #includes since we're just preloading for the #select
.select(&:source_project)
(origin_merge_requests + fork_merge_requests)
.uniq.select(&:source_project)
end end
def pipeline_merge_requests(pipeline) def pipeline_merge_requests(pipeline)
......
...@@ -42,7 +42,7 @@ module MergeRequests ...@@ -42,7 +42,7 @@ module MergeRequests
commit_ids.include?(merge_request.diff_head_sha) commit_ids.include?(merge_request.diff_head_sha)
end end
merge_requests.uniq.select(&:source_project).each do |merge_request| filter_merge_requests(merge_requests).each do |merge_request|
MergeRequests::PostMergeService. MergeRequests::PostMergeService.
new(merge_request.target_project, @current_user). new(merge_request.target_project, @current_user).
execute(merge_request) execute(merge_request)
...@@ -58,10 +58,13 @@ module MergeRequests ...@@ -58,10 +58,13 @@ module MergeRequests
def reload_merge_requests def reload_merge_requests
merge_requests = @project.merge_requests.opened. merge_requests = @project.merge_requests.opened.
by_source_or_target_branch(@branch_name).to_a by_source_or_target_branch(@branch_name).to_a
merge_requests += fork_merge_requests
merge_requests = filter_merge_requests(merge_requests)
merge_requests.each do |merge_request| # Fork merge requests
merge_requests += MergeRequest.opened
.where(source_branch: @branch_name, source_project: @project)
.where.not(target_project: @project).to_a
filter_merge_requests(merge_requests).each do |merge_request|
if merge_request.source_branch == @branch_name || force_push? if merge_request.source_branch == @branch_name || force_push?
merge_request.reload_diff merge_request.reload_diff
else else
...@@ -175,16 +178,7 @@ module MergeRequests ...@@ -175,16 +178,7 @@ module MergeRequests
end end
def merge_requests_for_source_branch def merge_requests_for_source_branch
@source_merge_requests ||= begin @source_merge_requests ||= merge_requests_for(@branch_name)
merge_requests = @project.origin_merge_requests.opened.where(source_branch: @branch_name).to_a
merge_requests += fork_merge_requests
filter_merge_requests(merge_requests)
end
end
def fork_merge_requests
@fork_merge_requests ||= @project.fork_merge_requests.opened.
where(source_branch: @branch_name).to_a
end end
def branch_added? def branch_added?
......
...@@ -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
...@@ -22,6 +22,8 @@ module Projects ...@@ -22,6 +22,8 @@ module Projects
if project.update_attributes(params.except(:default_branch)) if project.update_attributes(params.except(:default_branch))
if project.previous_changes.include?('path') if project.previous_changes.include?('path')
project.rename_repo project.rename_repo
else
system_hook_service.execute_hooks_for(project, :update)
end end
success success
......
...@@ -4,6 +4,6 @@ class UserProjectAccessChangedService ...@@ -4,6 +4,6 @@ class UserProjectAccessChangedService
end end
def execute def execute
AuthorizedProjectsWorker.bulk_perform_async(@user_ids.map { |id| [id] }) AuthorizedProjectsWorker.bulk_perform_and_wait(@user_ids.map { |id| [id] })
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)}
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment