Commit 36eaa3de authored by Kamil Trzciński's avatar Kamil Trzciński

Merge branch 'ce-to-ee-2017-06-02' into 'master'

CE upstream - Friday

Closes gitlab-ce#31644, gitlab-ce#32985, gitaly#246, gitaly#242, #2433, #2472, gitlab-qa#45, gitlab-qa#48, and gitlab-ce#31701

See merge request !2018
parents b71b0f48 a3514ce7
---
engines:
brakeman:
enabled: true
bundler-audit:
enabled: true
duplication:
enabled: true
config:
languages:
- ruby
- javascript
eslint:
enabled: true
fixme:
enabled: true
rubocop:
enabled: true
ratings:
paths:
- Gemfile.lock
- "**.erb"
- "**.haml"
- "**.rb"
- "**.rhtml"
- "**.slim"
- "**.inc"
- "**.js"
- "**.jsx"
- "**.module"
exclude_paths:
- config/
- db/
- features/
- node_modules/
- spec/
- vendor/
- lib/api/v3/
...@@ -20,6 +20,12 @@ Please remove this notice if you're confident your issue isn't a duplicate. ...@@ -20,6 +20,12 @@ Please remove this notice if you're confident your issue isn't a duplicate.
(How one can reproduce the issue - this is very important) (How one can reproduce the issue - this is very important)
### Example Project
(If possible, please create an example project here on GitLab.com that exhibits the problematic behaviour, and link to it here in the bug report)
(If you are using an older version of GitLab, this will also determine whether the bug has been fixed in a more recent version)
### What is the current *bug* behavior? ### What is the current *bug* behavior?
(What actually happens) (What actually happens)
......
...@@ -99,6 +99,7 @@ gem 'fog-google', '~> 0.5' ...@@ -99,6 +99,7 @@ gem 'fog-google', '~> 0.5'
gem 'fog-local', '~> 0.3' gem 'fog-local', '~> 0.3'
gem 'fog-openstack', '~> 0.1' gem 'fog-openstack', '~> 0.1'
gem 'fog-rackspace', '~> 0.1.1' gem 'fog-rackspace', '~> 0.1.1'
gem 'fog-aliyun', '~> 0.1.0'
# for Google storage # for Google storage
gem 'google-api-client', '~> 0.8.6' gem 'google-api-client', '~> 0.8.6'
...@@ -118,7 +119,7 @@ gem 'faraday_middleware-aws-signers-v4' ...@@ -118,7 +119,7 @@ gem 'faraday_middleware-aws-signers-v4'
# Markdown and HTML processing # Markdown and HTML processing
gem 'html-pipeline', '~> 1.11.0' gem 'html-pipeline', '~> 1.11.0'
gem 'deckar01-task_list', '1.0.6', require: 'task_list/railtie' gem 'deckar01-task_list', '2.0.0'
gem 'gitlab-markup', '~> 1.5.1' gem 'gitlab-markup', '~> 1.5.1'
gem 'redcarpet', '~> 3.4' gem 'redcarpet', '~> 3.4'
gem 'RedCloth', '~> 4.3.2' gem 'RedCloth', '~> 4.3.2'
...@@ -380,3 +381,7 @@ gem 'sys-filesystem', '~> 1.1.6' ...@@ -380,3 +381,7 @@ gem 'sys-filesystem', '~> 1.1.6'
gem 'gitaly', '~> 0.7.0' gem 'gitaly', '~> 0.7.0'
gem 'toml-rb', '~> 0.3.15', require: false gem 'toml-rb', '~> 0.3.15', require: false
# Feature toggles
gem 'flipper', '~> 0.10.2'
gem 'flipper-active_record', '~> 0.10.2'
...@@ -149,10 +149,8 @@ GEM ...@@ -149,10 +149,8 @@ GEM
database_cleaner (1.5.3) database_cleaner (1.5.3)
debug_inspector (0.0.2) debug_inspector (0.0.2)
debugger-ruby_core_source (1.3.8) debugger-ruby_core_source (1.3.8)
deckar01-task_list (1.0.6) deckar01-task_list (2.0.0)
activesupport (~> 4.0)
html-pipeline html-pipeline
rack (~> 1.0)
default_value_for (3.0.2) default_value_for (3.0.2)
activerecord (>= 3.2.0, < 5.1) activerecord (>= 3.2.0, < 5.1)
descendants_tracker (0.0.4) descendants_tracker (0.0.4)
...@@ -232,9 +230,18 @@ GEM ...@@ -232,9 +230,18 @@ GEM
path_expander (~> 1.0) path_expander (~> 1.0)
ruby_parser (~> 3.0) ruby_parser (~> 3.0)
sexp_processor (~> 4.0) sexp_processor (~> 4.0)
flipper (0.10.2)
flipper-active_record (0.10.2)
activerecord (>= 3.2, < 6)
flipper (~> 0.10.2)
flowdock (0.7.1) flowdock (0.7.1)
httparty (~> 0.7) httparty (~> 0.7)
multi_json multi_json
fog-aliyun (0.1.0)
fog-core (~> 1.27)
fog-json (~> 1.0)
ipaddress (~> 0.8)
xml-simple (~> 1.1)
fog-aws (0.13.0) fog-aws (0.13.0)
fog-core (~> 1.38) fog-core (~> 1.38)
fog-json (~> 1.0) fog-json (~> 1.0)
...@@ -366,7 +373,7 @@ GEM ...@@ -366,7 +373,7 @@ GEM
grape-entity (0.6.0) grape-entity (0.6.0)
activesupport activesupport
multi_json (>= 1.3.2) multi_json (>= 1.3.2)
grpc (1.3.4) grpc (1.2.5)
google-protobuf (~> 3.1) google-protobuf (~> 3.1)
googleauth (~> 0.5.1) googleauth (~> 0.5.1)
gssapi (1.2.0) gssapi (1.2.0)
...@@ -527,11 +534,10 @@ GEM ...@@ -527,11 +534,10 @@ GEM
omniauth (~> 1.0) omniauth (~> 1.0)
omniauth-oauth2 (~> 1.0) omniauth-oauth2 (~> 1.0)
omniauth-google-oauth2 (0.4.1) omniauth-google-oauth2 (0.4.1)
addressable (~> 2.3) jwt (~> 1.5.2)
jwt (~> 1.0)
multi_json (~> 1.3) multi_json (~> 1.3)
omniauth (>= 1.1.1) omniauth (>= 1.1.1)
omniauth-oauth2 (~> 1.3.1) omniauth-oauth2 (>= 1.3.1)
omniauth-kerberos (0.3.0) omniauth-kerberos (0.3.0)
omniauth-multipassword omniauth-multipassword
timfel-krb5-auth (~> 0.8) timfel-krb5-auth (~> 0.8)
...@@ -925,7 +931,7 @@ DEPENDENCIES ...@@ -925,7 +931,7 @@ DEPENDENCIES
creole (~> 0.5.0) creole (~> 0.5.0)
d3_rails (~> 3.5.0) d3_rails (~> 3.5.0)
database_cleaner (~> 1.5.0) database_cleaner (~> 1.5.0)
deckar01-task_list (= 1.0.6) deckar01-task_list (= 2.0.0)
default_value_for (~> 3.0.0) default_value_for (~> 3.0.0)
devise (~> 4.2) devise (~> 4.2)
devise-two-factor (~> 3.0.0) devise-two-factor (~> 3.0.0)
...@@ -943,6 +949,9 @@ DEPENDENCIES ...@@ -943,6 +949,9 @@ DEPENDENCIES
faraday_middleware-aws-signers-v4 faraday_middleware-aws-signers-v4
ffaker (~> 2.4) ffaker (~> 2.4)
flay (~> 2.8.0) flay (~> 2.8.0)
flipper (~> 0.10.2)
flipper-active_record (~> 0.10.2)
fog-aliyun (~> 0.1.0)
fog-aws (~> 0.9) fog-aws (~> 0.9)
fog-core (~> 1.44) fog-core (~> 1.44)
fog-google (~> 0.5) fog-google (~> 0.5)
...@@ -1096,4 +1105,4 @@ DEPENDENCIES ...@@ -1096,4 +1105,4 @@ DEPENDENCIES
wikicloth (= 0.8.1) wikicloth (= 0.8.1)
BUNDLED WITH BUNDLED WITH
1.14.6 1.15.0
...@@ -111,7 +111,7 @@ export default class BlobViewer { ...@@ -111,7 +111,7 @@ export default class BlobViewer {
BlobViewer.loadViewer(newViewer) BlobViewer.loadViewer(newViewer)
.then((viewer) => { .then((viewer) => {
$(viewer).syntaxHighlight(); $(viewer).renderGFM();
this.$fileHolder.trigger('highlight:line'); this.$fileHolder.trigger('highlight:line');
gl.utils.handleLocationHash(); gl.utils.handleLocationHash();
......
This diff is collapsed.
...@@ -16,6 +16,13 @@ const JumpToDiscussion = Vue.extend({ ...@@ -16,6 +16,13 @@ const JumpToDiscussion = Vue.extend({
}; };
}, },
computed: { computed: {
buttonText: function () {
if (this.discussionId) {
return 'Jump to next unresolved discussion';
} else {
return 'Jump to first unresolved discussion';
}
},
allResolved: function () { allResolved: function () {
return this.unresolvedDiscussionCount === 0; return this.unresolvedDiscussionCount === 0;
}, },
......
...@@ -131,9 +131,7 @@ import AuditLogs from './audit_logs'; ...@@ -131,9 +131,7 @@ import AuditLogs from './audit_logs';
case 'projects:merge_requests:index': case 'projects:merge_requests:index':
case 'projects:issues:index': case 'projects:issues:index':
if (gl.FilteredSearchManager && document.querySelector('.filtered-search')) { if (gl.FilteredSearchManager && document.querySelector('.filtered-search')) {
const filteredSearchManager = new gl.FilteredSearchManager( const filteredSearchManager = new gl.FilteredSearchManager(page === 'projects:issues:index' ? 'issues' : 'merge_requests');
page === 'projects:issues:index' ? 'issues' : 'merge_requests',
);
filteredSearchManager.setup(); filteredSearchManager.setup();
} }
const pagePrefix = page === 'projects:merge_requests:index' ? 'merge_request_' : 'issue_'; const pagePrefix = page === 'projects:merge_requests:index' ? 'merge_request_' : 'issue_';
......
...@@ -8,7 +8,7 @@ const Keyboard = function () { ...@@ -8,7 +8,7 @@ const Keyboard = function () {
var isUpArrow = false; var isUpArrow = false;
var isDownArrow = false; var isDownArrow = false;
var removeHighlight = function removeHighlight(list) { var removeHighlight = function removeHighlight(list) {
var itemElements = Array.prototype.slice.call(list.list.querySelectorAll('li:not(.divider)'), 0); var itemElements = Array.prototype.slice.call(list.list.querySelectorAll('li:not(.divider):not(.hidden)'), 0);
var listItems = []; var listItems = [];
for(var i = 0; i < itemElements.length; i++) { for(var i = 0; i < itemElements.length; i++) {
var listItem = itemElements[i]; var listItem = itemElements[i];
......
...@@ -63,6 +63,9 @@ const AjaxFilter = { ...@@ -63,6 +63,9 @@ const AjaxFilter = {
return AjaxCache.retrieve(url) return AjaxCache.retrieve(url)
.then((data) => { .then((data) => {
this._loadData(data, config); this._loadData(data, config);
if (config.onLoadingFinished) {
config.onLoadingFinished(data);
}
}) })
.catch(config.onError); .catch(config.onError);
}, },
......
<script> <script>
/* global Flash */ /* global Flash */
import Visibility from 'visibilityjs';
import EnvironmentsService from '../services/environments_service'; import EnvironmentsService from '../services/environments_service';
import environmentTable from './environments_table.vue'; import environmentTable from './environments_table.vue';
import EnvironmentsStore from '../stores/environments_store'; import EnvironmentsStore from '../stores/environments_store';
...@@ -7,6 +8,8 @@ import loadingIcon from '../../vue_shared/components/loading_icon.vue'; ...@@ -7,6 +8,8 @@ import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import tablePagination from '../../vue_shared/components/table_pagination.vue'; import tablePagination from '../../vue_shared/components/table_pagination.vue';
import '../../lib/utils/common_utils'; import '../../lib/utils/common_utils';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import Poll from '../../lib/utils/poll';
import environmentsMixin from '../mixins/environments_mixin';
export default { export default {
...@@ -16,6 +19,10 @@ export default { ...@@ -16,6 +19,10 @@ export default {
loadingIcon, loadingIcon,
}, },
mixins: [
environmentsMixin,
],
data() { data() {
const environmentsData = document.querySelector('#environments-list-view').dataset; const environmentsData = document.querySelector('#environments-list-view').dataset;
const store = new EnvironmentsStore(); const store = new EnvironmentsStore();
...@@ -36,6 +43,7 @@ export default { ...@@ -36,6 +43,7 @@ export default {
projectStoppedEnvironmentsPath: environmentsData.projectStoppedEnvironmentsPath, projectStoppedEnvironmentsPath: environmentsData.projectStoppedEnvironmentsPath,
newEnvironmentPath: environmentsData.newEnvironmentPath, newEnvironmentPath: environmentsData.newEnvironmentPath,
helpPagePath: environmentsData.helpPagePath, helpPagePath: environmentsData.helpPagePath,
isMakingRequest: false,
// Pagination Properties, // Pagination Properties,
paginationInformation: {}, paginationInformation: {},
...@@ -76,17 +84,43 @@ export default { ...@@ -76,17 +84,43 @@ export default {
* Toggles loading property. * Toggles loading property.
*/ */
created() { created() {
const scope = gl.utils.getParameterByName('scope') || this.visibility;
const page = gl.utils.getParameterByName('page') || this.pageNumber;
this.service = new EnvironmentsService(this.endpoint); this.service = new EnvironmentsService(this.endpoint);
this.fetchEnvironments(); const poll = new Poll({
resource: this.service,
method: 'get',
data: { scope, page },
successCallback: this.successCallback,
errorCallback: this.errorCallback,
notificationCallback: (isMakingRequest) => {
this.isMakingRequest = isMakingRequest;
// We need to verify if any folder is open to also fecth it
this.openFolders = this.store.getOpenFolders();
},
});
if (!Visibility.hidden()) {
this.isLoading = true;
poll.makeRequest();
}
Visibility.change(() => {
if (!Visibility.hidden()) {
poll.restart();
} else {
poll.stop();
}
});
eventHub.$on('refreshEnvironments', this.fetchEnvironments);
eventHub.$on('toggleFolder', this.toggleFolder); eventHub.$on('toggleFolder', this.toggleFolder);
eventHub.$on('postAction', this.postAction); eventHub.$on('postAction', this.postAction);
}, },
beforeDestroyed() { beforeDestroyed() {
eventHub.$off('refreshEnvironments');
eventHub.$off('toggleFolder'); eventHub.$off('toggleFolder');
eventHub.$off('postAction'); eventHub.$off('postAction');
}, },
...@@ -126,29 +160,13 @@ export default { ...@@ -126,29 +160,13 @@ export default {
fetchEnvironments() { fetchEnvironments() {
const scope = gl.utils.getParameterByName('scope') || this.visibility; const scope = gl.utils.getParameterByName('scope') || this.visibility;
const pageNumber = gl.utils.getParameterByName('page') || this.pageNumber; const page = gl.utils.getParameterByName('page') || this.pageNumber;
this.isLoading = true; this.isLoading = true;
return this.service.get(scope, pageNumber) return this.service.get({ scope, page })
.then(resp => ({ .then(this.successCallback)
headers: resp.headers, .catch(this.errorCallback);
body: resp.json(),
}))
.then((response) => {
this.store.storeAvailableCount(response.body.available_count);
this.store.storeStoppedCount(response.body.stopped_count);
this.store.storeEnvironments(response.body.environments);
this.store.setPagination(response.headers);
})
.then(() => {
this.isLoading = false;
})
.catch(() => {
this.isLoading = false;
// eslint-disable-next-line no-new
new Flash('An error occurred while fetching the environments.');
});
}, },
fetchChildEnvironments(folder, folderUrl) { fetchChildEnvironments(folder, folderUrl) {
...@@ -168,9 +186,34 @@ export default { ...@@ -168,9 +186,34 @@ export default {
}, },
postAction(endpoint) { postAction(endpoint) {
this.service.postAction(endpoint) if (!this.isMakingRequest) {
.then(() => this.fetchEnvironments()) this.isLoading = true;
.catch(() => new Flash('An error occured while making the request.'));
this.service.postAction(endpoint)
.then(() => this.fetchEnvironments())
.catch(() => new Flash('An error occured while making the request.'));
}
},
successCallback(resp) {
this.saveData(resp);
// If folders are open while polling we need to open them again
if (this.openFolders.length) {
this.openFolders.map((folder) => {
// TODO - Move this to the backend
const folderUrl = `${window.location.pathname}/folders/${folder.folderName}`;
this.store.updateFolder(folder, 'isOpen', true);
return this.fetchChildEnvironments(folder, folderUrl);
});
}
},
errorCallback() {
this.isLoading = false;
// eslint-disable-next-line no-new
new Flash('An error occurred while fetching the environments.');
}, },
}, },
}; };
......
<script> <script>
/* global Flash */ /* global Flash */
import Visibility from 'visibilityjs';
import EnvironmentsService from '../services/environments_service'; import EnvironmentsService from '../services/environments_service';
import environmentTable from '../components/environments_table.vue'; import environmentTable from '../components/environments_table.vue';
import EnvironmentsStore from '../stores/environments_store'; import EnvironmentsStore from '../stores/environments_store';
import loadingIcon from '../../vue_shared/components/loading_icon.vue'; import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import tablePagination from '../../vue_shared/components/table_pagination.vue'; import tablePagination from '../../vue_shared/components/table_pagination.vue';
import Poll from '../../lib/utils/poll';
import eventHub from '../event_hub';
import environmentsMixin from '../mixins/environments_mixin';
import '../../lib/utils/common_utils'; import '../../lib/utils/common_utils';
import '../../vue_shared/vue_resource_interceptor';
export default { export default {
components: { components: {
...@@ -15,6 +18,10 @@ export default { ...@@ -15,6 +18,10 @@ export default {
loadingIcon, loadingIcon,
}, },
mixins: [
environmentsMixin,
],
data() { data() {
const environmentsData = document.querySelector('#environments-folder-list-view').dataset; const environmentsData = document.querySelector('#environments-folder-list-view').dataset;
const store = new EnvironmentsStore(); const store = new EnvironmentsStore();
...@@ -77,33 +84,39 @@ export default { ...@@ -77,33 +84,39 @@ export default {
*/ */
created() { created() {
const scope = gl.utils.getParameterByName('scope') || this.visibility; const scope = gl.utils.getParameterByName('scope') || this.visibility;
const pageNumber = gl.utils.getParameterByName('page') || this.pageNumber; const page = gl.utils.getParameterByName('page') || this.pageNumber;
const endpoint = `${this.endpoint}?scope=${scope}&page=${pageNumber}`; this.service = new EnvironmentsService(this.endpoint);
this.service = new EnvironmentsService(endpoint); const poll = new Poll({
resource: this.service,
this.isLoading = true; method: 'get',
data: { scope, page },
return this.service.get() successCallback: this.successCallback,
.then(resp => ({ errorCallback: this.errorCallback,
headers: resp.headers, notificationCallback: (isMakingRequest) => {
body: resp.json(), this.isMakingRequest = isMakingRequest;
})) },
.then((response) => { });
this.store.storeAvailableCount(response.body.available_count);
this.store.storeStoppedCount(response.body.stopped_count); if (!Visibility.hidden()) {
this.store.storeEnvironments(response.body.environments); this.isLoading = true;
this.store.setPagination(response.headers); poll.makeRequest();
}) }
.then(() => {
this.isLoading = false; Visibility.change(() => {
}) if (!Visibility.hidden()) {
.catch(() => { poll.restart();
this.isLoading = false; } else {
// eslint-disable-next-line no-new poll.stop();
new Flash('An error occurred while fetching the environments.', 'alert'); }
}); });
eventHub.$on('postAction', this.postAction);
},
beforeDestroyed() {
eventHub.$off('postAction');
}, },
methods: { methods: {
...@@ -129,6 +142,37 @@ export default { ...@@ -129,6 +142,37 @@ export default {
gl.utils.visitUrl(param); gl.utils.visitUrl(param);
return param; return param;
}, },
fetchEnvironments() {
const scope = gl.utils.getParameterByName('scope') || this.visibility;
const page = gl.utils.getParameterByName('page') || this.pageNumber;
this.isLoading = true;
return this.service.get({ scope, page })
.then(this.successCallback)
.catch(this.errorCallback);
},
successCallback(resp) {
this.saveData(resp);
},
errorCallback() {
this.isLoading = false;
// eslint-disable-next-line no-new
new Flash('An error occurred while fetching the environments.');
},
postAction(endpoint) {
if (!this.isMakingRequest) {
this.isLoading = true;
this.service.postAction(endpoint)
.then(() => this.fetchEnvironments())
.catch(() => new Flash('An error occured while making the request.'));
}
},
}, },
}; };
</script> </script>
......
export default {
methods: {
saveData(resp) {
const response = {
headers: resp.headers,
body: resp.json(),
};
this.isLoading = false;
this.store.storeAvailableCount(response.body.available_count);
this.store.storeStoppedCount(response.body.stopped_count);
this.store.storeEnvironments(response.body.environments);
this.store.setPagination(response.headers);
},
},
};
...@@ -10,7 +10,8 @@ export default class EnvironmentsService { ...@@ -10,7 +10,8 @@ export default class EnvironmentsService {
this.folderResults = 3; this.folderResults = 3;
} }
get(scope, page) { get(options = {}) {
const { scope, page } = options;
return this.environments.get({ scope, page }); return this.environments.get({ scope, page });
} }
......
...@@ -223,4 +223,10 @@ export default class EnvironmentsStore { ...@@ -223,4 +223,10 @@ export default class EnvironmentsStore {
return updatedEnvironments; return updatedEnvironments;
} }
getOpenFolders() {
const environments = this.state.environments;
return environments.filter(env => env.isFolder && env.isOpen);
}
} }
...@@ -18,6 +18,9 @@ class DropdownUser extends gl.FilteredSearchDropdown { ...@@ -18,6 +18,9 @@ class DropdownUser extends gl.FilteredSearchDropdown {
}, },
searchValueFunction: this.getSearchInput.bind(this), searchValueFunction: this.getSearchInput.bind(this),
loadingTemplate: this.loadingTemplate, loadingTemplate: this.loadingTemplate,
onLoadingFinished: () => {
this.hideCurrentUser();
},
onError() { onError() {
/* eslint-disable no-new */ /* eslint-disable no-new */
new Flash('An error occured fetching the dropdown data.'); new Flash('An error occured fetching the dropdown data.');
...@@ -28,6 +31,11 @@ class DropdownUser extends gl.FilteredSearchDropdown { ...@@ -28,6 +31,11 @@ class DropdownUser extends gl.FilteredSearchDropdown {
this.tokenKeys = tokenKeys; this.tokenKeys = tokenKeys;
} }
hideCurrentUser() {
const currenUserItem = this.dropdown.querySelector('.js-current-user');
currenUserItem.classList.add('hidden');
}
itemClicked(e) { itemClicked(e) {
super.itemClicked(e, super.itemClicked(e,
selected => selected.querySelector('.dropdown-light-content').innerText.trim()); selected => selected.querySelector('.dropdown-light-content').innerText.trim());
......
...@@ -2,10 +2,10 @@ import './dropdown_hint'; ...@@ -2,10 +2,10 @@ import './dropdown_hint';
import './dropdown_non_user'; import './dropdown_non_user';
import './dropdown_user'; import './dropdown_user';
import './dropdown_utils'; import './dropdown_utils';
import './filtered_search_token_keys';
import './filtered_search_dropdown_manager'; import './filtered_search_dropdown_manager';
import './filtered_search_dropdown'; import './filtered_search_dropdown';
import './filtered_search_manager'; import './filtered_search_manager';
import './filtered_search_token_keys';
import './filtered_search_tokenizer'; import './filtered_search_tokenizer';
import './filtered_search_visual_tokens'; import './filtered_search_visual_tokens';
......
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, one-var-declaration-per-line, consistent-return, prefer-arrow-callback, no-return-assign, object-shorthand, comma-dangle, no-param-reassign, max-len */ /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, one-var-declaration-per-line, consistent-return, prefer-arrow-callback, no-return-assign, object-shorthand, comma-dangle, no-param-reassign, max-len */
(function() { function notificationGranted(message, opts, onclick) {
(function(w) { var notification;
var notificationGranted, notifyMe, notifyPermissions; notification = new Notification(message, opts);
notificationGranted = function(message, opts, onclick) { setTimeout(function() {
var notification; // Hide the notification after X amount of seconds
notification = new Notification(message, opts); return notification.close();
setTimeout(function() { }, 8000);
return notification.close();
// Hide the notification after X amount of seconds return notification.onclick = onclick || notification.close;
}, 8000); }
if (onclick) {
return notification.onclick = onclick;
}
};
notifyPermissions = function() {
if ('Notification' in window) {
return Notification.requestPermission();
}
};
notifyMe = function(message, body, icon, onclick) {
var opts;
opts = {
body: body,
icon: icon
};
// Let's check if the browser supports notifications
if (!('Notification' in window)) {
// do nothing function notifyPermissions() {
} else if (Notification.permission === 'granted') { if ('Notification' in window) {
// If it's okay let's create a notification return Notification.requestPermission();
}
}
function notifyMe(message, body, icon, onclick) {
var opts;
opts = {
body: body,
icon: icon
};
// Let's check if the browser supports notifications
if (!('Notification' in window)) {
// do nothing
} else if (Notification.permission === 'granted') {
// If it's okay let's create a notification
return notificationGranted(message, opts, onclick);
} else if (Notification.permission !== 'denied') {
return Notification.requestPermission(function(permission) {
// If the user accepts, let's create a notification
if (permission === 'granted') {
return notificationGranted(message, opts, onclick); return notificationGranted(message, opts, onclick);
} else if (Notification.permission !== 'denied') {
return Notification.requestPermission(function(permission) {
// If the user accepts, let's create a notification
if (permission === 'granted') {
return notificationGranted(message, opts, onclick);
}
});
} }
}; });
w.notify = notifyMe; }
return w.notifyPermissions = notifyPermissions; }
})(window);
}).call(window); const notify = {
notificationGranted,
notifyPermissions,
notifyMe,
};
export default notify;
...@@ -66,7 +66,8 @@ w.gl.utils.removeParamQueryString = function(url, param) { ...@@ -66,7 +66,8 @@ w.gl.utils.removeParamQueryString = function(url, param) {
})()).join('&'); })()).join('&');
}; };
w.gl.utils.removeParams = (params) => { w.gl.utils.removeParams = (params) => {
const url = new URL(window.location.href); const url = document.createElement('a');
url.href = window.location.href;
params.forEach((param) => { params.forEach((param) => {
url.search = w.gl.utils.removeParamQueryString(url.search, param); url.search = w.gl.utils.removeParamQueryString(url.search, param);
}); });
......
...@@ -56,7 +56,6 @@ import './lib/utils/animate'; ...@@ -56,7 +56,6 @@ import './lib/utils/animate';
import './lib/utils/bootstrap_linked_tabs'; import './lib/utils/bootstrap_linked_tabs';
import './lib/utils/common_utils'; import './lib/utils/common_utils';
import './lib/utils/datetime_utility'; import './lib/utils/datetime_utility';
import './lib/utils/notify';
import './lib/utils/pretty_time'; import './lib/utils/pretty_time';
import './lib/utils/text_utility'; import './lib/utils/text_utility';
import './lib/utils/url_utility'; import './lib/utils/url_utility';
......
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
| |
\\s\\$(?!\\$) \\s\\$(?!\\$)
) )
(.+?) ((.|\\n)+?)
( (
\\s\\\\end{[a-zA-Z]+}$ \\s\\\\end{[a-zA-Z]+}$
| |
...@@ -45,15 +45,25 @@ ...@@ -45,15 +45,25 @@
let inline = false; let inline = false;
if (typeof katex !== 'undefined') { if (typeof katex !== 'undefined') {
const katexString = text.replace(/\\/g, '\\'); const katexString = text.replace(/&amp;/g, '&')
const matches = new RegExp(katexRegexString, 'gi').exec(katexString); .replace(/&=&/g, '\\space=\\space')
.replace(/<(\/?)em>/g, '_');
const regex = new RegExp(katexRegexString, 'gi');
const matchLocation = katexString.search(regex);
const numberOfMatches = katexString.match(regex);
if (matches && matches.length > 0) { if (numberOfMatches && numberOfMatches.length !== 0) {
if (matches[1].trim() === '$' && matches[3].trim() === '$') { if (matchLocation > 0) {
let matches = regex.exec(katexString);
inline = true; inline = true;
text = `${katexString.replace(matches[0], '')} ${katex.renderToString(matches[2])}`; while (matches !== null) {
const renderedKatex = katex.renderToString(matches[0].replace(/\$/g, ''));
text = `${text.replace(matches[0], ` ${renderedKatex}`)}`;
matches = regex.exec(katexString);
}
} else { } else {
const matches = regex.exec(katexString);
text = katex.renderToString(matches[2]); text = katex.renderToString(matches[2]);
} }
} }
...@@ -79,7 +89,7 @@ ...@@ -79,7 +89,7 @@
}, },
computed: { computed: {
markdown() { markdown() {
return marked(this.cell.source.join('')); return marked(this.cell.source.join('').replace(/\\/g, '\\\\'));
}, },
}, },
}; };
......
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
export default {
props: [
'pipeline',
],
computed: {
user() {
return !!this.pipeline.user;
},
},
components: {
userAvatarLink,
},
template: `
<td>
<a
:href="pipeline.path"
class="js-pipeline-url-link">
<span class="pipeline-id">#{{pipeline.id}}</span>
</a>
<span>by</span>
<user-avatar-link
v-if="user"
class="js-pipeline-url-user"
:link-href="pipeline.user.web_url"
:img-src="pipeline.user.avatar_url"
:tooltip-text="pipeline.user.name"
/>
<span
v-if="!user"
class="js-pipeline-url-api api">
API
</span>
<span
v-if="pipeline.flags.latest"
class="js-pipeline-url-lastest label label-success has-tooltip"
title="Latest pipeline for this branch"
data-original-title="Latest pipeline for this branch">
latest
</span>
<span
v-if="pipeline.flags.yaml_errors"
class="js-pipeline-url-yaml label label-danger has-tooltip"
:title="pipeline.yaml_errors"
:data-original-title="pipeline.yaml_errors">
yaml invalid
</span>
<span
v-if="pipeline.flags.stuck"
class="js-pipeline-url-stuck label label-warning">
stuck
</span>
</td>
`,
};
<script>
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import tooltipMixin from '../../vue_shared/mixins/tooltip';
export default {
props: {
pipeline: {
type: Object,
required: true,
},
},
components: {
userAvatarLink,
},
mixins: [
tooltipMixin,
],
computed: {
user() {
return this.pipeline.user;
},
},
};
</script>
<template>
<td>
<a
:href="pipeline.path"
class="js-pipeline-url-link">
<span class="pipeline-id">#{{pipeline.id}}</span>
</a>
<span>by</span>
<user-avatar-link
v-if="user"
class="js-pipeline-url-user"
:link-href="pipeline.user.web_url"
:img-src="pipeline.user.avatar_url"
:tooltip-text="pipeline.user.name"
/>
<span
v-if="!user"
class="js-pipeline-url-api api">
API
</span>
<span
v-if="pipeline.flags.latest"
class="js-pipeline-url-lastest label label-success"
title="Latest pipeline for this branch"
ref="tooltip">
latest
</span>
<span
v-if="pipeline.flags.yaml_errors"
class="js-pipeline-url-yaml label label-danger"
:title="pipeline.yaml_errors"
ref="tooltip">
yaml invalid
</span>
<span
v-if="pipeline.flags.stuck"
class="js-pipeline-url-stuck label label-warning">
stuck
</span>
</td>
</template>
...@@ -55,6 +55,8 @@ import Api from './api'; ...@@ -55,6 +55,8 @@ import Api from './api';
this.includeGroups = $(select).data('include-groups'); this.includeGroups = $(select).data('include-groups');
this.allProjects = $(select).data('allprojects') || false; this.allProjects = $(select).data('allprojects') || false;
this.orderBy = $(select).data('order-by') || 'id'; this.orderBy = $(select).data('order-by') || 'id';
this.withIssuesEnabled = $(select).data('with-issues-enabled');
this.withMergeRequestsEnabled = $(select).data('with-merge-requests-enabled');
idAttribute = $(select).data('idattribute') || 'web_url'; idAttribute = $(select).data('idattribute') || 'web_url';
placeholder = "Search for project"; placeholder = "Search for project";
...@@ -92,6 +94,8 @@ import Api from './api'; ...@@ -92,6 +94,8 @@ import Api from './api';
} else { } else {
return Api.projects(query.term, { return Api.projects(query.term, {
order_by: _this.orderBy, order_by: _this.orderBy,
with_issues_enabled: _this.withIssuesEnabled,
with_merge_requests_enabled: _this.withMergeRequestsEnabled,
membership: !_this.allProjects membership: !_this.allProjects
}, projectsCallback); }, projectsCallback);
} }
......
/* global Flash */ /* global Flash */
import 'vendor/task_list'; import 'deckar01-task_list';
class TaskList { class TaskList {
constructor(options = {}) { constructor(options = {}) {
......
...@@ -41,3 +41,4 @@ export { default as getStateKey } from './ee/stores/get_state_key'; ...@@ -41,3 +41,4 @@ export { default as getStateKey } from './ee/stores/get_state_key';
export { default as mrWidgetOptions } from './ee/mr_widget_options'; export { default as mrWidgetOptions } from './ee/mr_widget_options';
export { default as stateMaps } from './ee/stores/state_maps'; export { default as stateMaps } from './ee/stores/state_maps';
export { default as SquashBeforeMerge } from './ee/components/states/mr_widget_squash_before_merge'; export { default as SquashBeforeMerge } from './ee/components/states/mr_widget_squash_before_merge';
export { default as notify } from '../lib/utils/notify';
...@@ -4,6 +4,8 @@ import { ...@@ -4,6 +4,8 @@ import {
} from './dependencies'; } from './dependencies';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
gl.mrWidgetData.gitlabLogo = gon.gitlab_logo;
const vm = new Vue(mrWidgetOptions); const vm = new Vue(mrWidgetOptions);
window.gl.mrWidget = { window.gl.mrWidget = {
......
...@@ -29,6 +29,7 @@ import { ...@@ -29,6 +29,7 @@ import {
eventHub, eventHub,
stateMaps, stateMaps,
SquashBeforeMerge, SquashBeforeMerge,
notify,
} from './dependencies'; } from './dependencies';
export default { export default {
...@@ -79,6 +80,7 @@ export default { ...@@ -79,6 +80,7 @@ export default {
this.service.checkStatus() this.service.checkStatus()
.then(res => res.json()) .then(res => res.json())
.then((res) => { .then((res) => {
this.handleNotification(res);
this.mr.setData(res); this.mr.setData(res);
this.setFavicon(); this.setFavicon();
...@@ -135,6 +137,15 @@ export default { ...@@ -135,6 +137,15 @@ export default {
}) })
.catch(() => new Flash('Something went wrong. Please try again.')); .catch(() => new Flash('Something went wrong. Please try again.'));
}, },
handleNotification(data) {
if (data.ci_status === this.mr.ciStatus) return;
const label = data.pipeline.details.status.label;
const title = `Pipeline ${label}`;
const message = `Pipeline ${label} for "${data.title}"`;
notify.notifyMe(title, message, this.mr.gitlabLogo);
},
resumePolling() { resumePolling() {
this.pollingInterval.resume(); this.pollingInterval.resume();
}, },
......
...@@ -4,6 +4,7 @@ import { getStateKey } from '../dependencies'; ...@@ -4,6 +4,7 @@ import { getStateKey } from '../dependencies';
export default class MergeRequestStore { export default class MergeRequestStore {
constructor(data) { constructor(data) {
this.sha = data.diff_head_sha; this.sha = data.diff_head_sha;
this.gitlabLogo = data.gitlabLogo;
this.setData(data); this.setData(data);
} }
......
...@@ -4,7 +4,7 @@ import PipelinesActionsComponent from '../../pipelines/components/pipelines_acti ...@@ -4,7 +4,7 @@ import PipelinesActionsComponent from '../../pipelines/components/pipelines_acti
import PipelinesArtifactsComponent from '../../pipelines/components/pipelines_artifacts'; import PipelinesArtifactsComponent from '../../pipelines/components/pipelines_artifacts';
import ciBadge from './ci_badge_link.vue'; import ciBadge from './ci_badge_link.vue';
import PipelinesStageComponent from '../../pipelines/components/stage.vue'; import PipelinesStageComponent from '../../pipelines/components/stage.vue';
import PipelinesUrlComponent from '../../pipelines/components/pipeline_url'; import PipelinesUrlComponent from '../../pipelines/components/pipeline_url.vue';
import PipelinesTimeagoComponent from '../../pipelines/components/time_ago'; import PipelinesTimeagoComponent from '../../pipelines/components/time_ago';
import CommitComponent from './commit'; import CommitComponent from './commit';
......
...@@ -463,4 +463,5 @@ ...@@ -463,4 +463,5 @@
.filter-dropdown-loading { .filter-dropdown-loading {
padding: 8px 16px; padding: 8px 16px;
text-align: center;
} }
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
padding: 0; padding: 0;
&::before { &::before {
@include notes-media('max', $screen-xs-max) { @include notes-media('max', $screen-xs-min) {
background: none; background: none;
} }
} }
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
.timeline-entry-inner { .timeline-entry-inner {
position: relative; position: relative;
@include notes-media('max', $screen-xs-max) { @include notes-media('max', $screen-xs-min) {
.timeline-icon { .timeline-icon {
display: none; display: none;
} }
......
...@@ -297,7 +297,7 @@ $btn-white-active: #848484; ...@@ -297,7 +297,7 @@ $btn-white-active: #848484;
/* /*
* Badges * Badges
*/ */
$badge-bg: #eee; $badge-bg: rgba(0, 0, 0, 0.07);
$badge-color: $gl-text-color-secondary; $badge-color: $gl-text-color-secondary;
/* /*
......
...@@ -29,129 +29,140 @@ ...@@ -29,129 +29,140 @@
} }
} }
.build-page { @keyframes blinking-scroll-button {
pre.trace { 0% { opacity: 0.2; }
background: $builds-trace-bg; 25% { opacity: 0.5; }
color: $white-light; 50% { opacity: 0.7; }
font-family: $monospace_font; 100% { opacity: 1; }
white-space: pre-wrap; }
overflow: auto;
overflow-y: hidden;
font-size: 12px;
.fa-spinner {
font-size: 24px;
margin-left: 20px;
}
}
.environment-information {
background-color: $gray-light;
border: 1px solid $border-color;
padding: 12px $gl-padding;
border-radius: $border-radius-default;
svg { .build-page {
position: relative; .sticky {
top: 1px; position: absolute;
margin-right: 5px; left: 0;
} right: 0;
} }
.truncated-info { .build-trace-container {
text-align: center; position: absolute;
border-bottom: 1px solid; top: 225px;
background-color: $black; left: 15px;
height: 45px; bottom: 10px;
padding: 15px; background: $black;
color: $gray-darkest;
font-family: $monospace_font;
font-size: 12px;
&.affix { &.sidebar-expanded {
top: 0; right: 305px;
} }
// with sidebar &.sidebar-collapsed {
&.affix.sidebar-expanded { right: 16px;
right: 312px;
left: 22px;
} }
// without sidebar code {
&.affix.sidebar-collapsed { background: $black;
right: 20px; color: $gray-darkest;
left: 20px;
} }
&.affix-top { .top-bar {
position: absolute;
top: 0; top: 0;
margin: 0 auto; height: 35px;
right: 5px; display: flex;
left: 5px; justify-content: flex-end;
} border-bottom: 1px outset $white-light;
.truncated-info-size { .truncated-info {
margin: 0 5px; margin: 0 auto;
} align-self: center;
.raw-link { .truncated-info-size {
color: inherit; margin: 0 5px;
margin-left: 5px; }
text-decoration: underline;
.raw-link {
color: inherit;
margin-left: 5px;
text-decoration: underline;
}
}
} }
}
}
.scroll-controls { .controllers {
height: 100%; display: flex;
align-self: center;
font-size: 15px;
.scroll-step { svg {
width: 31px; height: 15px;
margin: 0 0 0 auto; display: block;
} fill: $white-light;
}
.scroll-link, a,
.autoscroll-container { .btn-scroll {
right: 25px; margin: 0 8px;
z-index: 1; color: $white-light;
} }
.scroll-link { .btn-scroll.animate {
position: fixed; .first-triangle {
display: block; animation: blinking-scroll-button 1s ease infinite;
margin-bottom: 10px; animation-delay: .3s;
}
&.scroll-top .gitlab-icon-scroll-up-hover, .second-triangle {
&.scroll-top:hover .gitlab-icon-scroll-up, animation: blinking-scroll-button 1s ease infinite;
&.scroll-bottom .gitlab-icon-scroll-down-hover, animation-delay: .2s;
&.scroll-bottom:hover .gitlab-icon-scroll-down { }
display: none;
}
&.scroll-top:hover .gitlab-icon-scroll-up-hover, .third-triangle {
&.scroll-bottom:hover .gitlab-icon-scroll-down-hover { animation: blinking-scroll-button 1s ease infinite;
display: inline-block; }
}
&.scroll-top { &:disabled {
top: 10px; opacity: 1;
} }
}
&.scroll-bottom { .btn-scroll:disabled {
bottom: -2px; opacity: 0.35;
cursor: not-allowed;
}
} }
} }
.autoscroll-container { .bash {
position: absolute; top: 35px;
left: 10px;
bottom: 0;
overflow-y: hidden;
padding-bottom: 20px;
padding-right: 20px;
} }
&.sidebar-expanded { .environment-information {
background-color: $gray-light;
border: 1px solid $border-color;
padding: 12px $gl-padding;
border-radius: $border-radius-default;
.scroll-link, svg {
.autoscroll-container { position: relative;
right: ($gutter_width + ($gl-padding * 2)); top: 1px;
margin-right: 5px;
} }
} }
.build-loader-animation {
position: relative;
width: 6px;
height: 6px;
margin: auto auto 12px 2px;
border-radius: 50%;
animation: blinking-dots 1s linear infinite;
}
} }
.status-message { .status-message {
...@@ -223,32 +234,6 @@ ...@@ -223,32 +234,6 @@
} }
} }
.build-trace {
background: $black;
color: $gray-darkest;
white-space: pre;
overflow-x: auto;
font-size: 12px;
position: relative;
.fa-spinner {
font-size: 24px;
}
.bash {
display: block;
}
.build-loader-animation {
position: relative;
width: 6px;
height: 6px;
margin: auto auto 12px 2px;
border-radius: 50%;
animation: blinking-dots 1s linear infinite;
}
}
.right-sidebar.build-sidebar { .right-sidebar.build-sidebar {
padding: $gl-padding 0; padding: $gl-padding 0;
...@@ -390,6 +375,10 @@ ...@@ -390,6 +375,10 @@
.container-fluid.container-limited { .container-fluid.container-limited {
max-width: 100%; max-width: 100%;
} }
.content-wrapper {
padding-bottom: 6px;
}
} }
.build-detail-row { .build-detail-row {
......
...@@ -64,6 +64,10 @@ ...@@ -64,6 +64,10 @@
} }
} }
.btn .text-center {
display: inline;
}
.commit-title { .commit-title {
margin: 0; margin: 0;
} }
......
...@@ -520,17 +520,13 @@ ...@@ -520,17 +520,13 @@
position: absolute; position: absolute;
border-top: 2px solid $border-color; border-top: 2px solid $border-color;
height: 1px; height: 1px;
top: 8px; top: 9px;
width: 8px; width: 8px;
left: 0; left: 0;
} }
&:last-child { &:last-child {
margin-bottom: 0; margin-bottom: 0;
&::before {
top: 14px;
}
} }
} }
...@@ -539,7 +535,7 @@ ...@@ -539,7 +535,7 @@
width: 2px; width: 2px;
background: $border-color; background: $border-color;
position: absolute; position: absolute;
top: -5px; top: -9px;
} }
} }
......
...@@ -550,13 +550,13 @@ ul.notes { ...@@ -550,13 +550,13 @@ ul.notes {
position: relative; position: relative;
top: -2px; top: -2px;
display: inline-block; display: inline-block;
padding-left: 4px; padding-left: 7px;
padding-right: 4px; padding-right: 7px;
color: $notes-role-color; color: $notes-role-color;
font-size: 12px; font-size: 12px;
line-height: 20px; line-height: 20px;
border: 1px solid $border-color; border: 1px solid $border-color;
border-radius: $border-radius-base; border-radius: $label-border-radius;
} }
......
...@@ -115,6 +115,10 @@ ...@@ -115,6 +115,10 @@
} }
} }
.btn .text-center {
display: inline;
}
.tooltip { .tooltip {
white-space: nowrap; white-space: nowrap;
} }
......
...@@ -18,7 +18,7 @@ module RendersBlob ...@@ -18,7 +18,7 @@ module RendersBlob
} }
end end
def override_max_blob_size(blob) def conditionally_expand_blob(blob)
blob.override_max_size! if params[:override_max_size] == 'true' blob.expand! if params[:expanded] == 'true'
end end
end end
...@@ -24,7 +24,7 @@ class DashboardController < Dashboard::ApplicationController ...@@ -24,7 +24,7 @@ class DashboardController < Dashboard::ApplicationController
def load_events def load_events
projects = projects =
if params[:filter] == "starred" if params[:filter] == "starred"
current_user.viewable_starred_projects ProjectsFinder.new(current_user: current_user, params: { starred: true }).execute
else else
current_user.authorized_projects current_user.authorized_projects
end end
......
...@@ -64,6 +64,8 @@ class GroupsController < Groups::ApplicationController ...@@ -64,6 +64,8 @@ class GroupsController < Groups::ApplicationController
end end
def subgroups def subgroups
return not_found unless Group.supports_nested_groups?
@nested_groups = GroupsFinder.new(current_user, parent: group).execute @nested_groups = GroupsFinder.new(current_user, parent: group).execute
@nested_groups = @nested_groups.search(params[:filter_groups]) if params[:filter_groups].present? @nested_groups = @nested_groups.search(params[:filter_groups]) if params[:filter_groups].present?
end end
......
...@@ -27,7 +27,7 @@ class Projects::ArtifactsController < Projects::ApplicationController ...@@ -27,7 +27,7 @@ class Projects::ArtifactsController < Projects::ApplicationController
def file def file
blob = @entry.blob blob = @entry.blob
override_max_blob_size(blob) conditionally_expand_blob(blob)
respond_to do |format| respond_to do |format|
format.html do format.html do
......
...@@ -35,7 +35,7 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -35,7 +35,7 @@ class Projects::BlobController < Projects::ApplicationController
end end
def show def show
override_max_blob_size(@blob) conditionally_expand_blob(@blob)
respond_to do |format| respond_to do |format|
format.html do format.html do
......
...@@ -16,6 +16,8 @@ class Projects::EnvironmentsController < Projects::ApplicationController ...@@ -16,6 +16,8 @@ class Projects::EnvironmentsController < Projects::ApplicationController
respond_to do |format| respond_to do |format|
format.html format.html
format.json do format.json do
Gitlab::PollingInterval.set_header(response, interval: 3_000)
render json: { render json: {
environments: EnvironmentSerializer environments: EnvironmentSerializer
.new(project: @project, current_user: @current_user) .new(project: @project, current_user: @current_user)
......
...@@ -58,7 +58,7 @@ class Projects::PipelinesController < Projects::ApplicationController ...@@ -58,7 +58,7 @@ class Projects::PipelinesController < Projects::ApplicationController
def create def create
@pipeline = Ci::CreatePipelineService @pipeline = Ci::CreatePipelineService
.new(project, current_user, create_params) .new(project, current_user, create_params)
.execute(ignore_skip_ci: true, save_on_errors: false) .execute(:web, ignore_skip_ci: true, save_on_errors: false)
if @pipeline.persisted? if @pipeline.persisted?
redirect_to namespace_project_pipeline_path(project.namespace, project, @pipeline) redirect_to namespace_project_pipeline_path(project.namespace, project, @pipeline)
......
...@@ -56,7 +56,7 @@ class Projects::SnippetsController < Projects::ApplicationController ...@@ -56,7 +56,7 @@ class Projects::SnippetsController < Projects::ApplicationController
def show def show
blob = @snippet.blob blob = @snippet.blob
override_max_blob_size(blob) conditionally_expand_blob(blob)
respond_to do |format| respond_to do |format|
format.html do format.html do
......
...@@ -42,6 +42,7 @@ class Projects::VariablesController < Projects::ApplicationController ...@@ -42,6 +42,7 @@ class Projects::VariablesController < Projects::ApplicationController
private private
def project_params def project_params
params.require(:variable).permit([:id, :key, :value, :_destroy]) params.require(:variable)
.permit([:id, :key, :value, :protected, :_destroy])
end end
end end
...@@ -58,7 +58,7 @@ class SnippetsController < ApplicationController ...@@ -58,7 +58,7 @@ class SnippetsController < ApplicationController
def show def show
blob = @snippet.blob blob = @snippet.blob
override_max_blob_size(blob) conditionally_expand_blob(blob)
@note = Note.new(noteable: @snippet) @note = Note.new(noteable: @snippet)
@noteable = @snippet @noteable = @snippet
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
# project_ids_relation: int[] - project ids to use # project_ids_relation: int[] - project ids to use
# params: # params:
# trending: boolean # trending: boolean
# owned: boolean
# non_public: boolean # non_public: boolean
# starred: boolean # starred: boolean
# sort: string # sort: string
...@@ -28,13 +29,17 @@ class ProjectsFinder < UnionFinder ...@@ -28,13 +29,17 @@ class ProjectsFinder < UnionFinder
def execute def execute
items = init_collection items = init_collection
items = by_ids(items) items = items.map do |item|
item = by_ids(item)
item = by_personal(item)
item = by_starred(item)
item = by_trending(item)
item = by_visibilty_level(item)
item = by_tags(item)
item = by_search(item)
by_archived(item)
end
items = union(items) items = union(items)
items = by_personal(items)
items = by_visibilty_level(items)
items = by_tags(items)
items = by_search(items)
items = by_archived(items)
sort(items) sort(items)
end end
...@@ -43,10 +48,8 @@ class ProjectsFinder < UnionFinder ...@@ -43,10 +48,8 @@ class ProjectsFinder < UnionFinder
def init_collection def init_collection
projects = [] projects = []
if params[:trending].present? if params[:owned].present?
projects << Project.trending projects << current_user.owned_projects if current_user
elsif params[:starred].present? && current_user
projects << current_user.viewable_starred_projects
else else
projects << current_user.authorized_projects if current_user projects << current_user.authorized_projects if current_user
projects << Project.unscoped.public_to_user(current_user) unless params[:non_public].present? projects << Project.unscoped.public_to_user(current_user) unless params[:non_public].present?
...@@ -56,7 +59,7 @@ class ProjectsFinder < UnionFinder ...@@ -56,7 +59,7 @@ class ProjectsFinder < UnionFinder
end end
def by_ids(items) def by_ids(items)
project_ids_relation ? items.map { |item| item.where(id: project_ids_relation) } : items project_ids_relation ? items.where(id: project_ids_relation) : items
end end
def union(items) def union(items)
...@@ -67,6 +70,14 @@ class ProjectsFinder < UnionFinder ...@@ -67,6 +70,14 @@ class ProjectsFinder < UnionFinder
(params[:personal].present? && current_user) ? items.personal(current_user) : items (params[:personal].present? && current_user) ? items.personal(current_user) : items
end end
def by_starred(items)
(params[:starred].present? && current_user) ? items.starred_by(current_user) : items
end
def by_trending(items)
params[:trending].present? ? items.trending : items
end
def by_visibilty_level(items) def by_visibilty_level(items)
params[:visibility_level].present? ? items.where(visibility_level: params[:visibility_level]) : items params[:visibility_level].present? ? items.where(visibility_level: params[:visibility_level]) : items
end end
......
...@@ -280,7 +280,7 @@ module ApplicationHelper ...@@ -280,7 +280,7 @@ module ApplicationHelper
end end
def show_user_callout? def show_user_callout?
cookies[:user_callout_dismissed] == 'true' cookies[:user_callout_dismissed].nil?
end end
def linkedin_url(user) def linkedin_url(user)
......
...@@ -8,18 +8,28 @@ module AvatarsHelper ...@@ -8,18 +8,28 @@ module AvatarsHelper
})) }))
end end
def user_avatar(options = {}) def user_avatar_without_link(options = {})
avatar_size = options[:size] || 16 avatar_size = options[:size] || 16
user_name = options[:user].try(:name) || options[:user_name] user_name = options[:user].try(:name) || options[:user_name]
css_class = options[:css_class] || '' css_class = options[:css_class] || ''
avatar_url = options[:url] || avatar_icon(options[:user] || options[:user_email], avatar_size)
avatar = image_tag( data_attributes = { container: 'body' }
avatar_icon(options[:user] || options[:user_email], avatar_size),
if options[:lazy]
data_attributes[:src] = avatar_url
end
image_tag(
options[:lazy] ? '' : avatar_url,
class: "avatar has-tooltip s#{avatar_size} #{css_class}", class: "avatar has-tooltip s#{avatar_size} #{css_class}",
alt: "#{user_name}'s avatar", alt: "#{user_name}'s avatar",
title: user_name, title: user_name,
data: { container: 'body' } data: data_attributes
) )
end
def user_avatar(options = {})
avatar = user_avatar_without_link(options)
if options[:user] if options[:user]
link_to(avatar, user_path(options[:user])) link_to(avatar, user_path(options[:user]))
......
...@@ -240,14 +240,10 @@ module BlobHelper ...@@ -240,14 +240,10 @@ module BlobHelper
def blob_render_error_reason(viewer) def blob_render_error_reason(viewer)
case viewer.render_error case viewer.render_error
when :collapsed
"it is larger than #{number_to_human_size(viewer.collapse_limit)}"
when :too_large when :too_large
max_size = "it is larger than #{number_to_human_size(viewer.size_limit)}"
if viewer.can_override_max_size?
viewer.overridable_max_size
else
viewer.max_size
end
"it is larger than #{number_to_human_size(max_size)}"
when :server_side_but_stored_externally when :server_side_but_stored_externally
case viewer.blob.external_storage case viewer.blob.external_storage
when :lfs when :lfs
...@@ -264,8 +260,8 @@ module BlobHelper ...@@ -264,8 +260,8 @@ module BlobHelper
error = viewer.render_error error = viewer.render_error
options = [] options = []
if error == :too_large && viewer.can_override_max_size? if error == :collapsed
options << link_to('load it anyway', url_for(params.merge(viewer: viewer.type, override_max_size: true, format: nil))) options << link_to('load it anyway', url_for(params.merge(viewer: viewer.type, expanded: true, format: nil)))
end end
# If the error is `:server_side_but_stored_externally`, the simple viewer will show the same error, # If the error is `:server_side_but_stored_externally`, the simple viewer will show the same error,
......
...@@ -56,7 +56,7 @@ module ButtonHelper ...@@ -56,7 +56,7 @@ module ButtonHelper
content_tag (append_link ? :a : :span), protocol, content_tag (append_link ? :a : :span), protocol,
class: klass, class: klass,
href: (project.http_url_to_repo(current_user) if append_link), href: (project.http_url_to_repo if append_link),
data: { data: {
html: true, html: true,
placement: placement, placement: placement,
......
...@@ -8,8 +8,8 @@ module DiffHelper ...@@ -8,8 +8,8 @@ module DiffHelper
[marked_old_line, marked_new_line] [marked_old_line, marked_new_line]
end end
def expand_all_diffs? def diffs_expanded?
params[:expand_all_diffs].present? params[:expanded].present?
end end
def diff_view def diff_view
...@@ -22,10 +22,10 @@ module DiffHelper ...@@ -22,10 +22,10 @@ module DiffHelper
end end
def diff_options def diff_options
options = { ignore_whitespace_change: hide_whitespace?, no_collapse: expand_all_diffs? } options = { ignore_whitespace_change: hide_whitespace?, expanded: diffs_expanded? }
if action_name == 'diff_for_path' if action_name == 'diff_for_path'
options[:no_collapse] = true options[:expanded] = true
options[:paths] = params.values_at(:old_path, :new_path) options[:paths] = params.values_at(:old_path, :new_path)
end end
...@@ -66,12 +66,12 @@ module DiffHelper ...@@ -66,12 +66,12 @@ module DiffHelper
discussions_left = discussions_right = nil discussions_left = discussions_right = nil
if left && (left.unchanged? || left.removed?) if left && (left.unchanged? || left.discussable?)
line_code = diff_file.line_code(left) line_code = diff_file.line_code(left)
discussions_left = @grouped_diff_discussions[line_code] discussions_left = @grouped_diff_discussions[line_code]
end end
if right && right.added? if right&.discussable?
line_code = diff_file.line_code(right) line_code = diff_file.line_code(right)
discussions_right = @grouped_diff_discussions[line_code] discussions_right = @grouped_diff_discussions[line_code]
end end
......
...@@ -50,7 +50,7 @@ module NotesHelper ...@@ -50,7 +50,7 @@ module NotesHelper
def link_to_reply_discussion(discussion, line_type = nil) def link_to_reply_discussion(discussion, line_type = nil)
return unless current_user return unless current_user
data = { discussion_id: discussion.id, line_type: line_type } data = { discussion_id: discussion.reply_id, line_type: line_type }
button_tag 'Reply...', class: 'btn btn-text-field js-discussion-reply-button', button_tag 'Reply...', class: 'btn btn-text-field js-discussion-reply-button',
data: data, title: 'Add a reply' data: data, title: 'Add a reply'
......
...@@ -288,7 +288,7 @@ module ProjectsHelper ...@@ -288,7 +288,7 @@ module ProjectsHelper
when 'ssh' when 'ssh'
project.ssh_url_to_repo project.ssh_url_to_repo
else else
project.http_url_to_repo(current_user) project.http_url_to_repo
end end
end end
......
...@@ -54,6 +54,14 @@ module SelectsHelper ...@@ -54,6 +54,14 @@ module SelectsHelper
end end
end end
with_feature_enabled_data_attribute =
case opts.delete(:with_feature_enabled)
when 'issues' then 'data-with-issues-enabled'
when 'merge_requests' then 'data-with-merge-requests-enabled'
end
opts[with_feature_enabled_data_attribute] = true
hidden_field_tag(id, opts[:selected], opts) hidden_field_tag(id, opts[:selected], opts)
end end
......
...@@ -13,6 +13,7 @@ module SubmoduleHelper ...@@ -13,6 +13,7 @@ module SubmoduleHelper
if url =~ /([^\/:]+)\/([^\/]+(?:\.git)?)\Z/ if url =~ /([^\/:]+)\/([^\/]+(?:\.git)?)\Z/
namespace, project = $1, $2 namespace, project = $1, $2
project.rstrip!
project.sub!(/\.git\z/, '') project.sub!(/\.git\z/, '')
if self_url?(url, namespace, project) if self_url?(url, namespace, project)
......
...@@ -14,13 +14,13 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -14,13 +14,13 @@ class ApplicationSetting < ActiveRecord::Base
[\r\n] # any number of newline characters [\r\n] # any number of newline characters
}x }x
serialize :restricted_visibility_levels serialize :restricted_visibility_levels # rubocop:disable Cop/ActiverecordSerialize
serialize :import_sources serialize :import_sources # rubocop:disable Cop/ActiverecordSerialize
serialize :disabled_oauth_sign_in_sources, Array serialize :disabled_oauth_sign_in_sources, Array # rubocop:disable Cop/ActiverecordSerialize
serialize :domain_whitelist, Array serialize :domain_whitelist, Array # rubocop:disable Cop/ActiverecordSerialize
serialize :domain_blacklist, Array serialize :domain_blacklist, Array # rubocop:disable Cop/ActiverecordSerialize
serialize :repository_storages serialize :repository_storages # rubocop:disable Cop/ActiverecordSerialize
serialize :sidekiq_throttling_queues, Array serialize :sidekiq_throttling_queues, Array # rubocop:disable Cop/ActiverecordSerialize
cache_markdown_field :sign_in_text cache_markdown_field :sign_in_text
cache_markdown_field :help_page_text cache_markdown_field :help_page_text
......
class AuditEvent < ActiveRecord::Base class AuditEvent < ActiveRecord::Base
serialize :details, Hash serialize :details, Hash # rubocop:disable Cop/ActiverecordSerialize
belongs_to :user, foreign_key: :author_id belongs_to :user, foreign_key: :author_id
......
...@@ -102,10 +102,6 @@ class Blob < SimpleDelegator ...@@ -102,10 +102,6 @@ class Blob < SimpleDelegator
raw_size == 0 raw_size == 0
end end
def too_large?
size && truncated?
end
def external_storage_error? def external_storage_error?
if external_storage == :lfs if external_storage == :lfs
!project&.lfs_enabled? !project&.lfs_enabled?
...@@ -160,7 +156,7 @@ class Blob < SimpleDelegator ...@@ -160,7 +156,7 @@ class Blob < SimpleDelegator
end end
def readable_text? def readable_text?
text? && !stored_externally? && !too_large? text? && !stored_externally? && !truncated?
end end
def simple_viewer def simple_viewer
...@@ -187,9 +183,9 @@ class Blob < SimpleDelegator ...@@ -187,9 +183,9 @@ class Blob < SimpleDelegator
rendered_as_text? && rich_viewer rendered_as_text? && rich_viewer
end end
def override_max_size! def expand!
simple_viewer&.override_max_size = true simple_viewer&.expanded = true
rich_viewer&.override_max_size = true rich_viewer&.expanded = true
end end
private private
......
...@@ -7,8 +7,8 @@ module BlobViewer ...@@ -7,8 +7,8 @@ module BlobViewer
included do included do
self.loading_partial_name = 'loading_auxiliary' self.loading_partial_name = 'loading_auxiliary'
self.type = :auxiliary self.type = :auxiliary
self.overridable_max_size = 100.kilobytes self.collapse_limit = 100.kilobytes
self.max_size = 100.kilobytes self.size_limit = 100.kilobytes
end end
def visible_to?(current_user) def visible_to?(current_user)
......
...@@ -2,14 +2,14 @@ module BlobViewer ...@@ -2,14 +2,14 @@ module BlobViewer
class Base class Base
PARTIAL_PATH_PREFIX = 'projects/blob/viewers'.freeze PARTIAL_PATH_PREFIX = 'projects/blob/viewers'.freeze
class_attribute :partial_name, :loading_partial_name, :type, :extensions, :file_types, :load_async, :binary, :switcher_icon, :switcher_title, :overridable_max_size, :max_size class_attribute :partial_name, :loading_partial_name, :type, :extensions, :file_types, :load_async, :binary, :switcher_icon, :switcher_title, :collapse_limit, :size_limit
self.loading_partial_name = 'loading' self.loading_partial_name = 'loading'
delegate :partial_path, :loading_partial_path, :rich?, :simple?, :text?, :binary?, to: :class delegate :partial_path, :loading_partial_path, :rich?, :simple?, :text?, :binary?, to: :class
attr_reader :blob attr_reader :blob
attr_accessor :override_max_size attr_accessor :expanded
delegate :project, to: :blob delegate :project, to: :blob
...@@ -61,24 +61,16 @@ module BlobViewer ...@@ -61,24 +61,16 @@ module BlobViewer
self.class.load_async? && render_error.nil? self.class.load_async? && render_error.nil?
end end
def exceeds_overridable_max_size? def collapsed?
overridable_max_size && blob.raw_size > overridable_max_size return @collapsed if defined?(@collapsed)
end
def exceeds_max_size?
max_size && blob.raw_size > max_size
end
def can_override_max_size? @collapsed = !expanded && collapse_limit && blob.raw_size > collapse_limit
exceeds_overridable_max_size? && !exceeds_max_size?
end end
def too_large? def too_large?
if override_max_size return @too_large if defined?(@too_large)
exceeds_max_size?
else @too_large = size_limit && blob.raw_size > size_limit
exceeds_overridable_max_size?
end
end end
# This method is used on the server side to check whether we can attempt to # This method is used on the server side to check whether we can attempt to
...@@ -95,6 +87,8 @@ module BlobViewer ...@@ -95,6 +87,8 @@ module BlobViewer
def render_error def render_error
if too_large? if too_large?
:too_large :too_large
elsif collapsed?
:collapsed
end end
end end
......
...@@ -4,8 +4,8 @@ module BlobViewer ...@@ -4,8 +4,8 @@ module BlobViewer
included do included do
self.load_async = false self.load_async = false
self.overridable_max_size = 10.megabytes self.collapse_limit = 10.megabytes
self.max_size = 50.megabytes self.size_limit = 50.megabytes
end end
end end
end end
...@@ -4,8 +4,8 @@ module BlobViewer ...@@ -4,8 +4,8 @@ module BlobViewer
included do included do
self.load_async = true self.load_async = true
self.overridable_max_size = 2.megabytes self.collapse_limit = 2.megabytes
self.max_size = 5.megabytes self.size_limit = 5.megabytes
end end
def prepare! def prepare!
......
...@@ -5,7 +5,7 @@ module BlobViewer ...@@ -5,7 +5,7 @@ module BlobViewer
self.partial_name = 'text' self.partial_name = 'text'
self.binary = false self.binary = false
self.overridable_max_size = 1.megabyte self.collapse_limit = 1.megabyte
self.max_size = 10.megabytes self.size_limit = 10.megabytes
end end
end end
...@@ -20,8 +20,8 @@ module Ci ...@@ -20,8 +20,8 @@ module Ci
) )
end end
serialize :options serialize :options # rubocop:disable Cop/ActiverecordSerialize
serialize :yaml_variables, Gitlab::Serializer::Ci::Variables serialize :yaml_variables, Gitlab::Serializer::Ci::Variables # rubocop:disable Cop/ActiverecordSerialize
delegate :name, to: :project, prefix: true delegate :name, to: :project, prefix: true
...@@ -193,7 +193,7 @@ module Ci ...@@ -193,7 +193,7 @@ module Ci
variables += project.deployment_variables if has_environment? variables += project.deployment_variables if has_environment?
variables += yaml_variables variables += yaml_variables
variables += user_variables variables += user_variables
variables += project.secret_variables variables += project.secret_variables_for(ref).map(&:to_runner_variable)
variables += trigger_request.user_variables if trigger_request variables += trigger_request.user_variables if trigger_request
variables variables
end end
...@@ -257,38 +257,6 @@ module Ci ...@@ -257,38 +257,6 @@ module Ci
Time.now - updated_at > 15.minutes.to_i Time.now - updated_at > 15.minutes.to_i
end end
##
# Deprecated
#
# This contains a hotfix for CI build data integrity, see #4246
#
# This method is used by `ArtifactUploader` to create a store_dir.
# Warning: Uploader uses it after AND before file has been stored.
#
# This method returns old path to artifacts only if it already exists.
#
def artifacts_path
# We need the project even if it's soft deleted, because whenever
# we're really deleting the project, we'll also delete the builds,
# and in order to delete the builds, we need to know where to find
# the artifacts, which is depending on the data of the project.
# We need to retain the project in this case.
the_project = project || unscoped_project
old = File.join(created_at.utc.strftime('%Y_%m'),
the_project.ci_id.to_s,
id.to_s)
old_store = File.join(ArtifactUploader.artifacts_path, old)
return old if the_project.ci_id && File.directory?(old_store)
File.join(
created_at.utc.strftime('%Y_%m'),
the_project.id.to_s,
id.to_s
)
end
def valid_token?(token) def valid_token?(token)
self.token && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.token) self.token && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.token)
end end
......
...@@ -30,6 +30,7 @@ module Ci ...@@ -30,6 +30,7 @@ module Ci
delegate :id, to: :project, prefix: true delegate :id, to: :project, prefix: true
validates :source, exclusion: { in: %w(unknown), unless: :importing? }, on: :create
validates :sha, presence: { unless: :importing? } validates :sha, presence: { unless: :importing? }
validates :ref, presence: { unless: :importing? } validates :ref, presence: { unless: :importing? }
validates :status, presence: { unless: :importing? } validates :status, presence: { unless: :importing? }
...@@ -37,6 +38,16 @@ module Ci ...@@ -37,6 +38,16 @@ module Ci
after_create :keep_around_commits, unless: :importing? after_create :keep_around_commits, unless: :importing?
enum source: {
unknown: nil,
push: 1,
web: 2,
trigger: 3,
schedule: 4,
api: 5,
external: 6
}
state_machine :status, initial: :created do state_machine :status, initial: :created do
event :enqueue do event :enqueue do
transition created: :pending transition created: :pending
...@@ -269,10 +280,6 @@ module Ci ...@@ -269,10 +280,6 @@ module Ci
commit.sha == sha commit.sha == sha
end end
def triggered?
trigger_requests.any?
end
def retried def retried
@retried ||= (statuses.order(id: :desc) - statuses.latest) @retried ||= (statuses.order(id: :desc) - statuses.latest)
end end
......
...@@ -10,9 +10,9 @@ module Ci ...@@ -10,9 +10,9 @@ module Ci
has_one :last_pipeline, -> { order(id: :desc) }, class_name: 'Ci::Pipeline' has_one :last_pipeline, -> { order(id: :desc) }, class_name: 'Ci::Pipeline'
has_many :pipelines has_many :pipelines
validates :cron, unless: :importing_or_inactive?, cron: true, presence: { unless: :importing_or_inactive? } validates :cron, unless: :importing?, cron: true, presence: { unless: :importing? }
validates :cron_timezone, cron_timezone: true, presence: { unless: :importing_or_inactive? } validates :cron_timezone, cron_timezone: true, presence: { unless: :importing? }
validates :ref, presence: { unless: :importing_or_inactive? } validates :ref, presence: { unless: :importing? }
validates :description, presence: true validates :description, presence: true
before_save :set_next_run_at before_save :set_next_run_at
...@@ -24,6 +24,10 @@ module Ci ...@@ -24,6 +24,10 @@ module Ci
owner == current_user owner == current_user
end end
def own!(user)
update(owner: user)
end
def inactive? def inactive?
!active? !active?
end end
...@@ -32,10 +36,6 @@ module Ci ...@@ -32,10 +36,6 @@ module Ci
update_attribute(:active, false) update_attribute(:active, false)
end end
def importing_or_inactive?
importing? || inactive?
end
def runnable_by_owner? def runnable_by_owner?
Ability.allowed?(owner, :create_pipeline, project) Ability.allowed?(owner, :create_pipeline, project)
end end
......
...@@ -6,7 +6,7 @@ module Ci ...@@ -6,7 +6,7 @@ module Ci
belongs_to :pipeline, foreign_key: :commit_id belongs_to :pipeline, foreign_key: :commit_id
has_many :builds has_many :builds
serialize :variables serialize :variables # rubocop:disable Cop/ActiverecordSerialize
def user_variables def user_variables
return [] unless variables return [] unless variables
......
...@@ -12,11 +12,16 @@ module Ci ...@@ -12,11 +12,16 @@ module Ci
message: "can contain only letters, digits and '_'." } message: "can contain only letters, digits and '_'." }
scope :order_key_asc, -> { reorder(key: :asc) } scope :order_key_asc, -> { reorder(key: :asc) }
scope :unprotected, -> { where(protected: false) }
attr_encrypted :value, attr_encrypted :value,
mode: :per_attribute_iv_and_salt, mode: :per_attribute_iv_and_salt,
insecure_mode: true, insecure_mode: true,
key: Gitlab::Application.secrets.db_key_base, key: Gitlab::Application.secrets.db_key_base,
algorithm: 'aes-256-cbc' algorithm: 'aes-256-cbc'
def to_runner_variable
{ key: key, value: value, public: false }
end
end end
end end
...@@ -43,7 +43,12 @@ module Noteable ...@@ -43,7 +43,12 @@ module Noteable
end end
def resolvable_discussions def resolvable_discussions
@resolvable_discussions ||= discussion_notes.resolvable.discussions(self) @resolvable_discussions ||=
if defined?(@discussions)
@discussions.select(&:resolvable?)
else
discussion_notes.resolvable.discussions(self)
end
end end
def discussions_resolvable? def discussions_resolvable?
......
...@@ -84,89 +84,6 @@ module Routable ...@@ -84,89 +84,6 @@ 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
# Builds a relation to find multiple objects that are nested under user
# membership. Includes the parent, as opposed to `#member_descendants`
# which only includes the descendants.
#
# Usage:
#
# Klass.member_self_and_descendants(1)
#
# Returns an ActiveRecord::Relation.
def member_self_and_descendants(user_id)
joins(:route).
joins("INNER JOIN routes r2 ON routes.path LIKE CONCAT(r2.path, '/%')
OR routes.path = 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
# Returns all objects in a hierarchy, where any node in the hierarchy is
# under the user membership.
#
# Usage:
#
# Klass.member_hierarchy(1)
#
# Examples:
#
# Given the following group tree...
#
# _______group_1_______
# | |
# | |
# nested_group_1 nested_group_2
# | |
# | |
# nested_group_1_1 nested_group_2_1
#
#
# ... the following results are returned:
#
# * the user is a member of group 1
# => 'group_1',
# 'nested_group_1', nested_group_1_1',
# 'nested_group_2', 'nested_group_2_1'
#
# * the user is a member of nested_group_2
# => 'group1',
# 'nested_group_2', 'nested_group_2_1'
#
# * the user is a member of nested_group_2_1
# => 'group1',
# 'nested_group_2', 'nested_group_2_1'
#
# Returns an ActiveRecord::Relation.
def member_hierarchy(user_id)
paths = member_self_and_descendants(user_id).pluck('routes.path')
return none if paths.empty?
wheres = paths.map do |path|
"#{connection.quote(path)} = routes.path
OR
#{connection.quote(path)} LIKE CONCAT(routes.path, '/%')"
end
joins(:route).where(wheres.join(' OR '))
end
end end
def full_name def full_name
......
...@@ -3,7 +3,11 @@ module SelectForProjectAuthorization ...@@ -3,7 +3,11 @@ module SelectForProjectAuthorization
module ClassMethods module ClassMethods
def select_for_project_authorization def select_for_project_authorization
select("members.user_id, projects.id AS project_id, members.access_level") select("projects.id AS project_id, members.access_level")
end
def select_as_master_for_project_authorization
select(["projects.id AS project_id", "#{Gitlab::Access::MASTER} AS access_level"])
end end
end end
end end
...@@ -12,6 +12,7 @@ class Deployment < ActiveRecord::Base ...@@ -12,6 +12,7 @@ class Deployment < ActiveRecord::Base
delegate :name, to: :environment, prefix: true delegate :name, to: :environment, prefix: true
after_create :create_ref after_create :create_ref
after_create :invalidate_cache
def commit def commit
project.commit(sha) project.commit(sha)
...@@ -33,6 +34,10 @@ class Deployment < ActiveRecord::Base ...@@ -33,6 +34,10 @@ class Deployment < ActiveRecord::Base
project.repository.create_ref(ref, ref_path) project.repository.create_ref(ref, ref_path)
end end
def invalidate_cache
environment.expire_etag_cache
end
def manual_actions def manual_actions
@manual_actions ||= deployable.try(:other_actions) @manual_actions ||= deployable.try(:other_actions)
end end
......
...@@ -10,6 +10,7 @@ class DiffDiscussion < Discussion ...@@ -10,6 +10,7 @@ class DiffDiscussion < Discussion
delegate :position, delegate :position,
:original_position, :original_position,
:change_position,
to: :first_note to: :first_note
......
...@@ -11,9 +11,9 @@ class DiffNote < Note ...@@ -11,9 +11,9 @@ class DiffNote < Note
NOTEABLE_TYPES = %w(MergeRequest Commit).freeze NOTEABLE_TYPES = %w(MergeRequest Commit).freeze
serialize :original_position, Gitlab::Diff::Position serialize :original_position, Gitlab::Diff::Position # rubocop:disable Cop/ActiverecordSerialize
serialize :position, Gitlab::Diff::Position serialize :position, Gitlab::Diff::Position # rubocop:disable Cop/ActiverecordSerialize
serialize :change_position, Gitlab::Diff::Position serialize :change_position, Gitlab::Diff::Position # rubocop:disable Cop/ActiverecordSerialize
validates :original_position, presence: true validates :original_position, presence: true
validates :position, presence: true validates :position, presence: true
...@@ -100,13 +100,21 @@ class DiffNote < Note ...@@ -100,13 +100,21 @@ class DiffNote < Note
return if active? return if active?
Notes::DiffPositionUpdateService.new( tracer = Gitlab::Diff::PositionTracer.new(
self.project, project: self.project,
nil,
old_diff_refs: self.position.diff_refs, old_diff_refs: self.position.diff_refs,
new_diff_refs: noteable.diff_refs, new_diff_refs: self.noteable.diff_refs,
paths: self.position.paths paths: self.position.paths
).execute(self) )
result = tracer.trace(self.position)
return unless result
if result[:outdated]
self.change_position = result[:position]
else
self.position = result[:position]
end
end end
def verify_supported def verify_supported
......
...@@ -21,7 +21,8 @@ class Discussion ...@@ -21,7 +21,8 @@ class Discussion
end end
def self.build_collection(notes, context_noteable = nil) def self.build_collection(notes, context_noteable = nil)
notes.group_by { |n| n.discussion_id(context_noteable) }.values.map { |notes| build(notes, context_noteable) } grouped_notes = notes.group_by { |n| n.discussion_id(context_noteable) }
grouped_notes.values.map { |notes| build(notes, context_noteable) }
end end
# Returns an alphanumeric discussion ID based on `build_discussion_id` # Returns an alphanumeric discussion ID based on `build_discussion_id`
...@@ -84,6 +85,12 @@ class Discussion ...@@ -84,6 +85,12 @@ class Discussion
first_note.discussion_id(context_noteable) first_note.discussion_id(context_noteable)
end end
def reply_id
# To reply to this discussion, we need the actual discussion_id from the database,
# not the potentially overwritten one based on the noteable.
first_note.discussion_id
end
alias_method :to_param, :id alias_method :to_param, :id
def diff_discussion? def diff_discussion?
......
...@@ -57,6 +57,10 @@ class Environment < ActiveRecord::Base ...@@ -57,6 +57,10 @@ class Environment < ActiveRecord::Base
state :available state :available
state :stopped state :stopped
after_transition do |environment|
environment.expire_etag_cache
end
end end
def predefined_variables def predefined_variables
...@@ -200,6 +204,18 @@ class Environment < ActiveRecord::Base ...@@ -200,6 +204,18 @@ class Environment < ActiveRecord::Base
[external_url, public_path].join('/') [external_url, public_path].join('/')
end end
def expire_etag_cache
Gitlab::EtagCaching::Store.new.tap do |store|
store.touch(etag_cache_key)
end
end
def etag_cache_key
Gitlab::Routing.url_helpers.namespace_project_environments_path(
project.namespace,
project)
end
private private
# Slugifying a name may remove the uniqueness guarantee afforded by it being # Slugifying a name may remove the uniqueness guarantee afforded by it being
......
...@@ -26,7 +26,7 @@ class Event < ActiveRecord::Base ...@@ -26,7 +26,7 @@ class Event < ActiveRecord::Base
belongs_to :target, polymorphic: true belongs_to :target, polymorphic: true
# For Hash only # For Hash only
serialize :data serialize :data # rubocop:disable Cop/ActiverecordSerialize
# Callbacks # Callbacks
after_create :reset_project_activity after_create :reset_project_activity
......
...@@ -54,6 +54,10 @@ class Group < Namespace ...@@ -54,6 +54,10 @@ class Group < Namespace
end end
class << self class << self
def supports_nested_groups?
Gitlab::Database.postgresql?
end
# Searches for groups matching the given query. # Searches for groups matching the given query.
# #
# This method uses ILIKE on PostgreSQL and LIKE on MySQL. # This method uses ILIKE on PostgreSQL and LIKE on MySQL.
...@@ -94,7 +98,7 @@ class Group < Namespace ...@@ -94,7 +98,7 @@ class Group < Namespace
if current_scope.joins_values.include?(:shared_projects) if current_scope.joins_values.include?(:shared_projects)
joins('INNER JOIN namespaces project_namespace ON project_namespace.id = projects.namespace_id') joins('INNER JOIN namespaces project_namespace ON project_namespace.id = projects.namespace_id')
.where('project_namespace.share_with_group_lock = ?', false) .where('project_namespace.share_with_group_lock = ?', false)
.select("members.user_id, projects.id AS project_id, LEAST(project_group_links.group_access, members.access_level) AS access_level") .select("projects.id AS project_id, LEAST(project_group_links.group_access, members.access_level) AS access_level")
else else
super super
end end
......
class WebHookLog < ActiveRecord::Base class WebHookLog < ActiveRecord::Base
belongs_to :web_hook belongs_to :web_hook
serialize :request_headers, Hash serialize :request_headers, Hash # rubocop:disable Cop/ActiverecordSerialize
serialize :request_data, Hash serialize :request_data, Hash # rubocop:disable Cop/ActiverecordSerialize
serialize :response_headers, Hash serialize :response_headers, Hash # rubocop:disable Cop/ActiverecordSerialize
validates :web_hook, presence: true validates :web_hook, presence: true
......
...@@ -12,7 +12,7 @@ class LegacyDiffNote < Note ...@@ -12,7 +12,7 @@ class LegacyDiffNote < Note
include NoteOnDiff include NoteOnDiff
serialize :st_diff serialize :st_diff # rubocop:disable Cop/ActiverecordSerialize
validates :line_code, presence: true, line_code: true validates :line_code, presence: true, line_code: true
......
...@@ -26,7 +26,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -26,7 +26,7 @@ class MergeRequest < ActiveRecord::Base
belongs_to :assignee, class_name: "User" belongs_to :assignee, class_name: "User"
serialize :merge_params, Hash serialize :merge_params, Hash # rubocop:disable Cop/ActiverecordSerialize
after_create :ensure_merge_request_diff, unless: :importing? after_create :ensure_merge_request_diff, unless: :importing?
after_update :reload_diff_if_branch_changed after_update :reload_diff_if_branch_changed
...@@ -229,10 +229,10 @@ class MergeRequest < ActiveRecord::Base ...@@ -229,10 +229,10 @@ class MergeRequest < ActiveRecord::Base
def diffs(diff_options = {}) def diffs(diff_options = {})
if compare if compare
# When saving MR diffs, `no_collapse` is implicitly added (because we need # When saving MR diffs, `expanded` is implicitly added (because we need
# to save the entire contents to the DB), so add that here for # to save the entire contents to the DB), so add that here for
# consistency. # consistency.
compare.diffs(diff_options.merge(no_collapse: true)) compare.diffs(diff_options.merge(expanded: true))
else else
merge_request_diff.diffs(diff_options) merge_request_diff.diffs(diff_options)
end end
...@@ -446,7 +446,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -446,7 +446,7 @@ class MergeRequest < ActiveRecord::Base
MergeRequests::MergeRequestDiffCacheService.new.execute(self) MergeRequests::MergeRequestDiffCacheService.new.execute(self)
new_diff_refs = self.diff_refs new_diff_refs = self.diff_refs
update_diff_notes_positions( update_diff_discussion_positions(
old_diff_refs: old_diff_refs, old_diff_refs: old_diff_refs,
new_diff_refs: new_diff_refs, new_diff_refs: new_diff_refs,
current_user: current_user current_user: current_user
...@@ -923,19 +923,18 @@ class MergeRequest < ActiveRecord::Base ...@@ -923,19 +923,18 @@ class MergeRequest < ActiveRecord::Base
diff_refs && diff_refs.complete? diff_refs && diff_refs.complete?
end end
def update_diff_notes_positions(old_diff_refs:, new_diff_refs:, current_user: nil) def update_diff_discussion_positions(old_diff_refs:, new_diff_refs:, current_user: nil)
return unless has_complete_diff_refs? return unless has_complete_diff_refs?
return if new_diff_refs == old_diff_refs return if new_diff_refs == old_diff_refs
active_diff_notes = self.notes.new_diff_notes.select do |note| active_diff_discussions = self.notes.new_diff_notes.discussions.select do |discussion|
note.active?(old_diff_refs) discussion.active?(old_diff_refs)
end end
return if active_diff_discussions.empty?
return if active_diff_notes.empty? paths = active_diff_discussions.flat_map { |n| n.diff_file.paths }.uniq
paths = active_diff_notes.flat_map { |n| n.diff_file.paths }.uniq service = Discussions::UpdateDiffPositionService.new(
service = Notes::DiffPositionUpdateService.new(
self.project, self.project,
current_user, current_user,
old_diff_refs: old_diff_refs, old_diff_refs: old_diff_refs,
...@@ -943,11 +942,8 @@ class MergeRequest < ActiveRecord::Base ...@@ -943,11 +942,8 @@ class MergeRequest < ActiveRecord::Base
paths: paths paths: paths
) )
transaction do active_diff_discussions.each do |discussion|
active_diff_notes.each do |note| service.execute(discussion)
service.execute(note)
Gitlab::Timeless.timeless(note, &:save)
end
end end
end end
......
class MergeRequestDiff < ActiveRecord::Base class MergeRequestDiff < ActiveRecord::Base
include Sortable include Sortable
include Importable include Importable
include Gitlab::Git::EncodingHelper include Gitlab::EncodingHelper
# Prevent store of diff if commits amount more then 500 # Prevent store of diff if commits amount more then 500
COMMITS_SAFE_SIZE = 100 COMMITS_SAFE_SIZE = 100
...@@ -11,8 +11,8 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -11,8 +11,8 @@ class MergeRequestDiff < ActiveRecord::Base
belongs_to :merge_request belongs_to :merge_request
serialize :st_commits serialize :st_commits # rubocop:disable Cop/ActiverecordSerialize
serialize :st_diffs serialize :st_diffs # rubocop:disable Cop/ActiverecordSerialize
state_machine :state, initial: :empty do state_machine :state, initial: :empty do
state :collected state :collected
......
...@@ -109,7 +109,7 @@ class Milestone < ActiveRecord::Base ...@@ -109,7 +109,7 @@ class Milestone < ActiveRecord::Base
end end
def participants def participants
User.joins(assigned_issues: :milestone).where("milestones.id = ?", id) User.joins(assigned_issues: :milestone).where("milestones.id = ?", id).uniq
end end
def self.sort(method) def self.sort(method)
......
...@@ -182,26 +182,20 @@ class Namespace < ActiveRecord::Base ...@@ -182,26 +182,20 @@ class Namespace < ActiveRecord::Base
projects.with_shared_runners.any? projects.with_shared_runners.any?
end end
# Scopes the model on ancestors of the record # Returns all the ancestors of the current namespaces.
def ancestors def ancestors
if parent_id return self.class.none unless parent_id
path = route ? route.path : full_path
paths = []
until path.blank? Gitlab::GroupHierarchy.
path = path.rpartition('/').first new(self.class.where(id: parent_id)).
paths << path base_and_ancestors
end
self.class.joins(:route).where('routes.path IN (?)', paths).reorder('routes.path ASC')
else
self.class.none
end
end end
# Scopes the model on direct and indirect children of the record # Returns all the descendants of the current namespace.
def descendants def descendants
self.class.joins(:route).merge(Route.inside_path(route.path)).reorder('routes.path ASC') Gitlab::GroupHierarchy.
new(self.class.where(parent_id: id)).
base_and_descendants
end end
def user_ids_for_project_authorizations def user_ids_for_project_authorizations
......
...@@ -112,7 +112,7 @@ class Note < ActiveRecord::Base ...@@ -112,7 +112,7 @@ class Note < ActiveRecord::Base
end end
def discussions(context_noteable = nil) def discussions(context_noteable = nil)
Discussion.build_collection(fresh, context_noteable) Discussion.build_collection(all.includes(:noteable).fresh, context_noteable)
end end
def find_discussion(discussion_id) def find_discussion(discussion_id)
......
...@@ -3,7 +3,7 @@ class PersonalAccessToken < ActiveRecord::Base ...@@ -3,7 +3,7 @@ class PersonalAccessToken < ActiveRecord::Base
include TokenAuthenticatable include TokenAuthenticatable
add_authentication_token_field :token add_authentication_token_field :token
serialize :scopes, Array serialize :scopes, Array # rubocop:disable Cop/ActiverecordSerialize
belongs_to :user belongs_to :user
......
...@@ -262,6 +262,7 @@ class Project < ActiveRecord::Base ...@@ -262,6 +262,7 @@ class Project < ActiveRecord::Base
scope :in_namespace, ->(namespace_ids) { where(namespace_id: namespace_ids) } scope :in_namespace, ->(namespace_ids) { where(namespace_id: namespace_ids) }
scope :personal, ->(user) { where(namespace_id: user.namespace_id) } scope :personal, ->(user) { where(namespace_id: user.namespace_id) }
scope :joined, ->(user) { where('namespace_id != ?', user.namespace_id) } scope :joined, ->(user) { where('namespace_id != ?', user.namespace_id) }
scope :starred_by, ->(user) { joins(:users_star_projects).where('users_star_projects.user_id': user.id) }
scope :visible_to_user, ->(user) { where(id: user.authorized_projects.select(:id).reorder(nil)) } scope :visible_to_user, ->(user) { where(id: user.authorized_projects.select(:id).reorder(nil)) }
scope :non_archived, -> { where(archived: false) } scope :non_archived, -> { where(archived: false) }
scope :mirror, -> { where(mirror: true) } scope :mirror, -> { where(mirror: true) }
...@@ -292,6 +293,9 @@ class Project < ActiveRecord::Base ...@@ -292,6 +293,9 @@ class Project < ActiveRecord::Base
scope :with_builds_enabled, -> { with_feature_enabled(:builds) } scope :with_builds_enabled, -> { with_feature_enabled(:builds) }
scope :with_issues_enabled, -> { with_feature_enabled(:issues) } scope :with_issues_enabled, -> { with_feature_enabled(:issues) }
scope :with_merge_requests_enabled, -> { with_feature_enabled(:merge_requests) }
# EE
scope :with_wiki_enabled, -> { with_feature_enabled(:wiki) } scope :with_wiki_enabled, -> { with_feature_enabled(:wiki) }
enum auto_cancel_pending_pipelines: { disabled: 0, enabled: 1 } enum auto_cancel_pending_pipelines: { disabled: 0, enabled: 1 }
...@@ -430,10 +434,6 @@ class Project < ActiveRecord::Base ...@@ -430,10 +434,6 @@ class Project < ActiveRecord::Base
where("projects.id IN (#{union.to_sql})") where("projects.id IN (#{union.to_sql})")
end end
def search_by_visibility(level)
where(visibility_level: Gitlab::VisibilityLevel.string_options[level])
end
def search_by_title(query) def search_by_title(query)
pattern = "%#{query}%" pattern = "%#{query}%"
table = Project.arel_table table = Project.arel_table
...@@ -1032,10 +1032,8 @@ class Project < ActiveRecord::Base ...@@ -1032,10 +1032,8 @@ class Project < ActiveRecord::Base
url_to_repo url_to_repo
end end
def http_url_to_repo(user = nil) def http_url_to_repo
credentials = Gitlab::UrlSanitizer.http_credentials_for_user(user) "#{web_url}.git"
Gitlab::UrlSanitizer.new("#{web_url}.git", credentials: credentials).full_url
end end
# No need to have a Kerberos Web url. Kerberos URL will be used only to clone # No need to have a Kerberos Web url. Kerberos URL will be used only to clone
...@@ -1257,11 +1255,6 @@ class Project < ActiveRecord::Base ...@@ -1257,11 +1255,6 @@ class Project < ActiveRecord::Base
pipelines.order(id: :desc).find_by(sha: sha, ref: ref) pipelines.order(id: :desc).find_by(sha: sha, ref: ref)
end end
def ensure_pipeline(ref, sha, current_user = nil)
pipeline_for(ref, sha) ||
pipelines.create(sha: sha, ref: ref, user: current_user)
end
def enable_ci def enable_ci
project_feature.update_attribute(:builds_access_level, ProjectFeature::ENABLED) project_feature.update_attribute(:builds_access_level, ProjectFeature::ENABLED)
end end
...@@ -1505,12 +1498,19 @@ class Project < ActiveRecord::Base ...@@ -1505,12 +1498,19 @@ class Project < ActiveRecord::Base
variables variables
end end
def secret_variables def secret_variables_for(ref)
variables.map do |variable| if protected_for?(ref)
{ key: variable.key, value: variable.value, public: false } variables
else
variables.unprotected
end end
end end
def protected_for?(ref)
ProtectedBranch.protected?(self, ref) ||
ProtectedTag.protected?(self, ref)
end
def deployment_variables def deployment_variables
return [] unless deployment_service return [] unless deployment_service
......
...@@ -6,6 +6,12 @@ class ProjectAuthorization < ActiveRecord::Base ...@@ -6,6 +6,12 @@ class ProjectAuthorization < ActiveRecord::Base
validates :access_level, inclusion: { in: Gitlab::Access.all_values }, presence: true validates :access_level, inclusion: { in: Gitlab::Access.all_values }, presence: true
validates :user, uniqueness: { scope: [:project, :access_level] }, presence: true validates :user, uniqueness: { scope: [:project, :access_level] }, presence: true
def self.select_from_union(union)
select(['project_id', 'MAX(access_level) AS access_level']).
from("(#{union.to_sql}) #{ProjectAuthorization.table_name}").
group(:project_id)
end
def self.insert_authorizations(rows, per_batch = 1000) def self.insert_authorizations(rows, per_batch = 1000)
rows.each_slice(per_batch) do |slice| rows.each_slice(per_batch) do |slice|
tuples = slice.map do |tuple| tuples = slice.map do |tuple|
......
...@@ -10,7 +10,7 @@ class ProjectImportData < ActiveRecord::Base ...@@ -10,7 +10,7 @@ class ProjectImportData < ActiveRecord::Base
insecure_mode: true, insecure_mode: true,
algorithm: 'aes-256-cbc' algorithm: 'aes-256-cbc'
serialize :data, JSON serialize :data, JSON # rubocop:disable Cop/ActiverecordSerialize
validates :project, presence: true validates :project, presence: true
......
...@@ -239,17 +239,26 @@ class JiraService < IssueTrackerService ...@@ -239,17 +239,26 @@ class JiraService < IssueTrackerService
return unless client_url.present? return unless client_url.present?
jira_request do jira_request do
if issue.comments.build.save!(body: message) remote_link = find_remote_link(issue, remote_link_props[:object][:url])
remote_link = issue.remotelink.build if remote_link
remote_link.save!(remote_link_props) remote_link.save!(remote_link_props)
result_message = "#{self.class.name} SUCCESS: Successfully posted to #{client_url}." elsif issue.comments.build.save!(body: message)
new_remote_link = issue.remotelink.build
new_remote_link.save!(remote_link_props)
end end
result_message = "#{self.class.name} SUCCESS: Successfully posted to #{client_url}."
Rails.logger.info(result_message) Rails.logger.info(result_message)
result_message result_message
end end
end end
def find_remote_link(issue, url)
links = jira_request { issue.remotelink.all }
links.find { |link| link.object["url"] == url }
end
def build_remote_link_props(url:, title:, resolved: false) def build_remote_link_props(url:, title:, resolved: false)
status = { status = {
resolved: resolved resolved: resolved
......
...@@ -169,7 +169,7 @@ class ProjectTeam ...@@ -169,7 +169,7 @@ class ProjectTeam
access = RequestStore.store[key] access = RequestStore.store[key]
end end
# Lookup only the IDs we need # Look up only the IDs we need
user_ids = user_ids - access.keys user_ids = user_ids - access.keys
return access if user_ids.empty? return access if user_ids.empty?
...@@ -180,6 +180,13 @@ class ProjectTeam ...@@ -180,6 +180,13 @@ class ProjectTeam
maximum(:access_level) maximum(:access_level)
access.merge!(users_access) access.merge!(users_access)
missing_user_ids = user_ids - users_access.keys
missing_user_ids.each do |user_id|
access[user_id] = Gitlab::Access::NO_ACCESS
end
access access
end end
......
...@@ -44,11 +44,8 @@ class ProjectWiki ...@@ -44,11 +44,8 @@ class ProjectWiki
url_to_repo url_to_repo
end end
def http_url_to_repo(user = nil) def http_url_to_repo
url = "#{Gitlab.config.gitlab.url}/#{path_with_namespace}.git" "#{Gitlab.config.gitlab.url}/#{path_with_namespace}.git"
credentials = Gitlab::UrlSanitizer.http_credentials_for_user(user)
Gitlab::UrlSanitizer.new(url, credentials: credentials).full_url
end end
# No need to have a Kerberos Web url. Kerberos URL will be used only to clone # No need to have a Kerberos Web url. Kerberos URL will be used only to clone
......
class SentNotification < ActiveRecord::Base class SentNotification < ActiveRecord::Base
serialize :position, Gitlab::Diff::Position serialize :position, Gitlab::Diff::Position # rubocop:disable Cop/ActiverecordSerialize
belongs_to :project belongs_to :project
belongs_to :noteable, polymorphic: true belongs_to :noteable, polymorphic: true
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
# and implement a set of methods # and implement a set of methods
class Service < ActiveRecord::Base class Service < ActiveRecord::Base
include Sortable include Sortable
serialize :properties, JSON serialize :properties, JSON # rubocop:disable Cop/ActiverecordSerialize
default_value_for :active, false default_value_for :active, false
default_value_for :push_events, true default_value_for :push_events, true
......
...@@ -10,11 +10,14 @@ class User < ActiveRecord::Base ...@@ -10,11 +10,14 @@ class User < ActiveRecord::Base
include Sortable include Sortable
include CaseSensitivity include CaseSensitivity
include TokenAuthenticatable include TokenAuthenticatable
include IgnorableColumn
prepend EE::GeoAwareAvatar prepend EE::GeoAwareAvatar
prepend EE::User prepend EE::User
DEFAULT_NOTIFICATION_LEVEL = :participating DEFAULT_NOTIFICATION_LEVEL = :participating
ignore_column :authorized_projects_populated
add_authentication_token_field :authentication_token add_authentication_token_field :authentication_token
add_authentication_token_field :incoming_email_token add_authentication_token_field :incoming_email_token
add_authentication_token_field :rss_token add_authentication_token_field :rss_token
...@@ -39,7 +42,7 @@ class User < ActiveRecord::Base ...@@ -39,7 +42,7 @@ class User < ActiveRecord::Base
otp_secret_encryption_key: Gitlab::Application.secrets.otp_key_base otp_secret_encryption_key: Gitlab::Application.secrets.otp_key_base
devise :two_factor_backupable, otp_number_of_backup_codes: 10 devise :two_factor_backupable, otp_number_of_backup_codes: 10
serialize :otp_backup_codes, JSON serialize :otp_backup_codes, JSON # rubocop:disable Cop/ActiverecordSerialize
devise :lockable, :recoverable, :rememberable, :trackable, devise :lockable, :recoverable, :rememberable, :trackable,
:validatable, :omniauthable, :confirmable, :registerable :validatable, :omniauthable, :confirmable, :registerable
...@@ -231,7 +234,6 @@ class User < ActiveRecord::Base ...@@ -231,7 +234,6 @@ class User < ActiveRecord::Base
scope :blocked, -> { with_states(:blocked, :ldap_blocked) } scope :blocked, -> { with_states(:blocked, :ldap_blocked) }
scope :external, -> { where(external: true) } scope :external, -> { where(external: true) }
scope :active, -> { with_state(:active).non_internal } scope :active, -> { with_state(:active).non_internal }
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 :subscribed_for_admin_email, -> { where(admin_email_unsubscribed_at: nil) } scope :subscribed_for_admin_email, -> { where(admin_email_unsubscribed_at: nil) }
scope :ldap, -> { joins(:identities).where('identities.provider LIKE ?', 'ldap%') } scope :ldap, -> { joins(:identities).where('identities.provider LIKE ?', 'ldap%') }
...@@ -394,6 +396,7 @@ class User < ActiveRecord::Base ...@@ -394,6 +396,7 @@ class User < ActiveRecord::Base
# Pattern used to extract `@user` user references from text # Pattern used to extract `@user` user references from text
def reference_pattern def reference_pattern
%r{ %r{
(?<!\w)
#{Regexp.escape(reference_prefix)} #{Regexp.escape(reference_prefix)}
(?<user>#{Gitlab::PathRegex::FULL_NAMESPACE_FORMAT_REGEX}) (?<user>#{Gitlab::PathRegex::FULL_NAMESPACE_FORMAT_REGEX})
}x }x
...@@ -537,23 +540,16 @@ class User < ActiveRecord::Base ...@@ -537,23 +540,16 @@ 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 # Returns a relation of groups the user has access to, including their parent
Group.member_descendants(id) # and child groups (recursively).
end
def all_expanded_groups def all_expanded_groups
Group.member_hierarchy(id) Gitlab::GroupHierarchy.new(groups).all_groups
end end
def expanded_groups_requiring_two_factor_authentication def expanded_groups_requiring_two_factor_authentication
all_expanded_groups.where(require_two_factor_authentication: true) all_expanded_groups.where(require_two_factor_authentication: true)
end end
def nested_groups_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
...@@ -562,18 +558,15 @@ class User < ActiveRecord::Base ...@@ -562,18 +558,15 @@ class User < ActiveRecord::Base
project_authorizations.where(project_id: project_ids).delete_all project_authorizations.where(project_id: project_ids).delete_all
end end
def set_authorized_projects_column
unless authorized_projects_populated
update_column(:authorized_projects_populated, true)
end
end
def authorized_projects(min_access_level = nil) def authorized_projects(min_access_level = nil)
refresh_authorized_projects unless authorized_projects_populated # We're overriding an association, so explicitly call super with no
# arguments or it would be passed as `force_reload` to the association
# We're overriding an association, so explicitly call super with no arguments or it would be passed as `force_reload` to the association
projects = super() projects = super()
projects = projects.where('project_authorizations.access_level >= ?', min_access_level) if min_access_level
if min_access_level
projects = projects.
where('project_authorizations.access_level >= ?', min_access_level)
end
projects projects
end end
...@@ -592,12 +585,6 @@ class User < ActiveRecord::Base ...@@ -592,12 +585,6 @@ class User < ActiveRecord::Base
authorized_projects(Gitlab::Access::REPORTER).where(id: projects) authorized_projects(Gitlab::Access::REPORTER).where(id: projects)
end end
def viewable_starred_projects
starred_projects.where("projects.visibility_level IN (?) OR projects.id IN (?)",
[Project::PUBLIC, Project::INTERNAL],
authorized_projects.select(:project_id))
end
def owned_projects def owned_projects
@owned_projects ||= @owned_projects ||=
Project.where('namespace_id IN (?) OR namespace_id = ?', Project.where('namespace_id IN (?) OR namespace_id = ?',
...@@ -821,7 +808,7 @@ class User < ActiveRecord::Base ...@@ -821,7 +808,7 @@ class User < ActiveRecord::Base
def avatar_url(size: nil, scale: 2, **args) def avatar_url(size: nil, scale: 2, **args)
# We use avatar_path instead of overriding avatar_url because of carrierwave. # We use avatar_path instead of overriding avatar_url because of carrierwave.
# See https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11001/diffs#note_28659864 # See https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11001/diffs#note_28659864
avatar_path(args) || GravatarService.new.execute(email, size, scale) avatar_path(args) || GravatarService.new.execute(email, size, scale, username: username)
end end
def all_emails def all_emails
......
...@@ -23,7 +23,7 @@ module Ci ...@@ -23,7 +23,7 @@ module Ci
!::Gitlab::UserAccess !::Gitlab::UserAccess
.new(user, project: build.project) .new(user, project: build.project)
.can_push_to_branch?(build.ref) .can_merge_to_branch?(build.ref)
end end
end end
end end
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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