Commit d5014d0a authored by Matija Čupić's avatar Matija Čupić

Merge branch 'master' into ee-persistent-callouts

parents 4dec5730 2516ae71
...@@ -13,3 +13,5 @@ lib/gitlab/redis/*.rb ...@@ -13,3 +13,5 @@ lib/gitlab/redis/*.rb
lib/gitlab/gitaly_client/operation_service.rb lib/gitlab/gitaly_client/operation_service.rb
app/models/project_services/packagist_service.rb app/models/project_services/packagist_service.rb
lib/gitlab/background_migration/normalize_ldap_extern_uids_range.rb lib/gitlab/background_migration/normalize_ldap_extern_uids_range.rb
lib/gitlab/background_migration/*
app/models/project_services/kubernetes_service.rb
...@@ -361,7 +361,7 @@ group :development, :test do ...@@ -361,7 +361,7 @@ group :development, :test do
gem 'scss_lint', '~> 0.56.0', require: false gem 'scss_lint', '~> 0.56.0', require: false
gem 'haml_lint', '~> 0.26.0', require: false gem 'haml_lint', '~> 0.26.0', require: false
gem 'simplecov', '~> 0.14.0', require: false gem 'simplecov', '~> 0.14.0', require: false
gem 'flay', '~> 2.8.0', require: false gem 'flay', '~> 2.10.0', require: false
gem 'bundler-audit', '~> 0.5.0', require: false gem 'bundler-audit', '~> 0.5.0', require: false
gem 'benchmark-ips', '~> 2.3.0', require: false gem 'benchmark-ips', '~> 2.3.0', require: false
......
...@@ -235,7 +235,7 @@ GEM ...@@ -235,7 +235,7 @@ GEM
fast_gettext (1.4.0) fast_gettext (1.4.0)
ffaker (2.4.0) ffaker (2.4.0)
ffi (1.9.18) ffi (1.9.18)
flay (2.8.1) flay (2.10.0)
erubis (~> 2.7.0) erubis (~> 2.7.0)
path_expander (~> 1.0) path_expander (~> 1.0)
ruby_parser (~> 3.0) ruby_parser (~> 3.0)
...@@ -617,7 +617,7 @@ GEM ...@@ -617,7 +617,7 @@ GEM
ast (~> 2.3) ast (~> 2.3)
parslet (1.5.0) parslet (1.5.0)
blankslate (~> 2.0) blankslate (~> 2.0)
path_expander (1.0.1) path_expander (1.0.2)
peek (1.0.1) peek (1.0.1)
concurrent-ruby (>= 0.9.0) concurrent-ruby (>= 0.9.0)
concurrent-ruby-ext (>= 0.9.0) concurrent-ruby-ext (>= 0.9.0)
...@@ -1072,7 +1072,7 @@ DEPENDENCIES ...@@ -1072,7 +1072,7 @@ DEPENDENCIES
faraday_middleware-aws-signers-v4 faraday_middleware-aws-signers-v4
fast_blank fast_blank
ffaker (~> 2.4) ffaker (~> 2.4)
flay (~> 2.8.0) flay (~> 2.10.0)
flipper (~> 0.11.0) flipper (~> 0.11.0)
flipper-active_record (~> 0.11.0) flipper-active_record (~> 0.11.0)
flipper-active_support_cache_store (~> 0.11.0) flipper-active_support_cache_store (~> 0.11.0)
......
...@@ -15,11 +15,13 @@ export default class SecretValues { ...@@ -15,11 +15,13 @@ export default class SecretValues {
init() { init() {
this.revealButton = this.container.querySelector('.js-secret-value-reveal-button'); this.revealButton = this.container.querySelector('.js-secret-value-reveal-button');
if (this.revealButton) {
const isRevealed = convertPermissionToBoolean(this.revealButton.dataset.secretRevealStatus); const isRevealed = convertPermissionToBoolean(this.revealButton.dataset.secretRevealStatus);
this.updateDom(isRevealed); this.updateDom(isRevealed);
this.revealButton.addEventListener('click', this.onRevealButtonClicked.bind(this)); this.revealButton.addEventListener('click', this.onRevealButtonClicked.bind(this));
} }
}
onRevealButtonClicked() { onRevealButtonClicked() {
const previousIsRevealed = convertPermissionToBoolean( const previousIsRevealed = convertPermissionToBoolean(
......
import $ from 'jquery';
import { convertPermissionToBoolean } from '../lib/utils/common_utils';
import { s__ } from '../locale';
import setupToggleButtons from '../toggle_buttons';
import CreateItemDropdown from '../create_item_dropdown';
import SecretValues from '../behaviors/secret_values';
const ALL_ENVIRONMENTS_STRING = s__('CiVariable|All environments');
function createEnvironmentItem(value) {
return {
title: value === '*' ? ALL_ENVIRONMENTS_STRING : value,
id: value,
text: value,
};
}
export default class VariableList {
constructor({
container,
formField,
}) {
this.$container = $(container);
this.formField = formField;
this.environmentDropdownMap = new WeakMap();
this.inputMap = {
id: {
selector: '.js-ci-variable-input-id',
default: '',
},
key: {
selector: '.js-ci-variable-input-key',
default: '',
},
value: {
selector: '.js-ci-variable-input-value',
default: '',
},
protected: {
selector: '.js-ci-variable-input-protected',
default: 'true',
},
environment: {
// We can't use a `.js-` class here because
// gl_dropdown replaces the <input> and doesn't copy over the class
// See https://gitlab.com/gitlab-org/gitlab-ce/issues/42458
selector: `input[name="${this.formField}[variables_attributes][][environment]"]`,
default: '*',
},
_destroy: {
selector: '.js-ci-variable-input-destroy',
default: '',
},
};
this.secretValues = new SecretValues({
container: this.$container[0],
valueSelector: '.js-row:not(:last-child) .js-secret-value',
placeholderSelector: '.js-row:not(:last-child) .js-secret-value-placeholder',
});
}
init() {
this.bindEvents();
this.secretValues.init();
}
bindEvents() {
this.$container.find('.js-row').each((index, rowEl) => {
this.initRow(rowEl);
});
this.$container.on('click', '.js-row-remove-button', (e) => {
e.preventDefault();
this.removeRow($(e.currentTarget).closest('.js-row'));
});
const inputSelector = Object.keys(this.inputMap)
.map(name => this.inputMap[name].selector)
.join(',');
// Remove any empty rows except the last row
this.$container.on('blur', inputSelector, (e) => {
const $row = $(e.currentTarget).closest('.js-row');
if ($row.is(':not(:last-child)') && !this.checkIfRowTouched($row)) {
this.removeRow($row);
}
});
// Always make sure there is an empty last row
this.$container.on('input trigger-change', inputSelector, () => {
const $lastRow = this.$container.find('.js-row').last();
if (this.checkIfRowTouched($lastRow)) {
this.insertRow($lastRow);
}
});
}
initRow(rowEl) {
const $row = $(rowEl);
setupToggleButtons($row[0]);
const $environmentSelect = $row.find('.js-variable-environment-toggle');
if ($environmentSelect.length) {
const createItemDropdown = new CreateItemDropdown({
$dropdown: $environmentSelect,
defaultToggleLabel: ALL_ENVIRONMENTS_STRING,
fieldName: `${this.formField}[variables_attributes][][environment]`,
getData: (term, callback) => callback(this.getEnvironmentValues()),
createNewItemFromValue: createEnvironmentItem,
onSelect: () => {
// Refresh the other dropdowns in the variable list
// so they have the new value we just picked
this.refreshDropdownData();
$row.find(this.inputMap.environment.selector).trigger('trigger-change');
},
});
// Clear out any data that might have been left-over from the row clone
createItemDropdown.clearDropdown();
this.environmentDropdownMap.set($row[0], createItemDropdown);
}
}
insertRow($row) {
const $rowClone = $row.clone();
$rowClone.removeAttr('data-is-persisted');
// Reset the inputs to their defaults
Object.keys(this.inputMap).forEach((name) => {
const entry = this.inputMap[name];
$rowClone.find(entry.selector).val(entry.default);
});
this.initRow($rowClone);
$row.after($rowClone);
}
removeRow($row) {
const isPersisted = convertPermissionToBoolean($row.attr('data-is-persisted'));
if (isPersisted) {
$row.hide();
$row
// eslint-disable-next-line no-underscore-dangle
.find(this.inputMap._destroy.selector)
.val(true);
} else {
$row.remove();
}
}
checkIfRowTouched($row) {
return Object.keys(this.inputMap).some((name) => {
const entry = this.inputMap[name];
const $el = $row.find(entry.selector);
return $el.length && $el.val() !== entry.default;
});
}
getAllData() {
// Ignore the last empty row because we don't want to try persist
// a blank variable and run into validation problems.
const validRows = this.$container.find('.js-row').toArray().slice(0, -1);
return validRows.map((rowEl) => {
const resultant = {};
Object.keys(this.inputMap).forEach((name) => {
const entry = this.inputMap[name];
const $input = $(rowEl).find(entry.selector);
if ($input.length) {
resultant[name] = $input.val();
}
});
return resultant;
});
}
getEnvironmentValues() {
const valueMap = this.$container.find(this.inputMap.environment.selector).toArray()
.reduce((prevValueMap, envInput) => ({
...prevValueMap,
[envInput.value]: envInput.value,
}), {});
return Object.keys(valueMap).map(createEnvironmentItem);
}
refreshDropdownData() {
this.$container.find('.js-row').each((index, rowEl) => {
const environmentDropdown = this.environmentDropdownMap.get(rowEl);
if (environmentDropdown) {
environmentDropdown.refreshData();
}
});
}
}
import VariableList from './ci_variable_list';
// Used for the variable list on scheduled pipeline edit page
export default function setupNativeFormVariableList({
container,
formField = 'variables',
}) {
const $container = $(container);
const variableList = new VariableList({
container: $container,
formField,
});
variableList.init();
// Clear out the names in the empty last row so it
// doesn't get submitted and throw validation errors
$container.closest('form').on('submit trigger-submit', () => {
const $lastRow = $container.find('.js-row').last();
const isTouched = variableList.checkIfRowTouched($lastRow);
if (!isTouched) {
$lastRow.find('input, textarea').attr('name', '');
}
});
}
...@@ -8,6 +8,8 @@ import 'core-js/fn/promise'; ...@@ -8,6 +8,8 @@ import 'core-js/fn/promise';
import 'core-js/fn/string/code-point-at'; import 'core-js/fn/string/code-point-at';
import 'core-js/fn/string/from-code-point'; import 'core-js/fn/string/from-code-point';
import 'core-js/fn/symbol'; import 'core-js/fn/symbol';
import 'core-js/es6/map';
import 'core-js/es6/weak-map';
// Browser polyfills // Browser polyfills
import 'classlist-polyfill'; import 'classlist-polyfill';
......
...@@ -22,9 +22,9 @@ import initPathLocks from 'ee/path_locks'; // eslint-disable-line import/first ...@@ -22,9 +22,9 @@ import initPathLocks from 'ee/path_locks'; // eslint-disable-line import/first
import initApprovals from 'ee/approvals'; // eslint-disable-line import/first import initApprovals from 'ee/approvals'; // eslint-disable-line import/first
import initLDAPGroupsSelect from 'ee/ldap_groups_select'; // eslint-disable-line import/first import initLDAPGroupsSelect from 'ee/ldap_groups_select'; // eslint-disable-line import/first
(function() { var Dispatcher;
var Dispatcher;
(function() {
Dispatcher = (function() { Dispatcher = (function() {
function Dispatcher() { function Dispatcher() {
this.initSearch(); this.initSearch();
...@@ -72,46 +72,16 @@ import initLDAPGroupsSelect from 'ee/ldap_groups_select'; // eslint-disable-line ...@@ -72,46 +72,16 @@ import initLDAPGroupsSelect from 'ee/ldap_groups_select'; // eslint-disable-line
} }
switch (page) { switch (page) {
case 'sessions:new':
import('./pages/sessions/new')
.then(callDefault)
.catch(fail);
break;
case 'projects:boards:show':
case 'projects:boards:index':
import('./pages/projects/boards/index')
.then(callDefault)
.catch(fail);
shortcut_handler = true;
break;
case 'projects:environments:metrics': case 'projects:environments:metrics':
import('./pages/projects/environments/metrics') import('./pages/projects/environments/metrics')
.then(callDefault) .then(callDefault)
.catch(fail); .catch(fail);
break; break;
case 'projects:merge_requests:index': case 'projects:merge_requests:index':
import('./pages/projects/merge_requests/index')
.then(callDefault)
.catch(fail);
shortcut_handler = true;
break;
case 'projects:issues:index': case 'projects:issues:index':
import('./pages/projects/issues/index')
.then(callDefault)
.catch(fail);
shortcut_handler = true;
break;
case 'projects:issues:show': case 'projects:issues:show':
import('./pages/projects/issues/show')
.then(callDefault)
.catch(fail);
shortcut_handler = true; shortcut_handler = true;
break; break;
case 'dashboard:milestones:index':
import('./pages/dashboard/milestones/index')
.then(callDefault)
.catch(fail);
break;
case 'projects:milestones:index': case 'projects:milestones:index':
import('./pages/projects/milestones/index') import('./pages/projects/milestones/index')
.then(callDefault) .then(callDefault)
...@@ -352,9 +322,6 @@ import initLDAPGroupsSelect from 'ee/ldap_groups_select'; // eslint-disable-line ...@@ -352,9 +322,6 @@ import initLDAPGroupsSelect from 'ee/ldap_groups_select'; // eslint-disable-line
shortcut_handler = true; shortcut_handler = true;
break; break;
case 'projects:show': case 'projects:show':
import('./pages/projects/show')
.then(callDefault)
.catch(fail);
shortcut_handler = true; shortcut_handler = true;
// ee-start // ee-start
initGeoInfoModal(); initGeoInfoModal();
...@@ -389,9 +356,6 @@ import initLDAPGroupsSelect from 'ee/ldap_groups_select'; // eslint-disable-line ...@@ -389,9 +356,6 @@ import initLDAPGroupsSelect from 'ee/ldap_groups_select'; // eslint-disable-line
.catch(fail); .catch(fail);
break; break;
case 'groups:show': case 'groups:show':
import('./pages/groups/show')
.then(callDefault)
.catch(fail);
shortcut_handler = true; shortcut_handler = true;
break; break;
case 'groups:group_members:index': case 'groups:group_members:index':
...@@ -400,7 +364,7 @@ import initLDAPGroupsSelect from 'ee/ldap_groups_select'; // eslint-disable-line ...@@ -400,7 +364,7 @@ import initLDAPGroupsSelect from 'ee/ldap_groups_select'; // eslint-disable-line
.catch(fail); .catch(fail);
break; break;
case 'projects:project_members:index': case 'projects:project_members:index':
import('./pages/projects/project_members/') import('./pages/projects/project_members')
.then(callDefault) .then(callDefault)
.catch(fail); .catch(fail);
break; break;
...@@ -681,7 +645,7 @@ import initLDAPGroupsSelect from 'ee/ldap_groups_select'; // eslint-disable-line ...@@ -681,7 +645,7 @@ import initLDAPGroupsSelect from 'ee/ldap_groups_select'; // eslint-disable-line
} }
break; break;
case 'profiles': case 'profiles':
import('./pages/profiles/index/') import('./pages/profiles/index')
.then(callDefault) .then(callDefault)
.catch(fail); .catch(fail);
break; break;
...@@ -736,8 +700,8 @@ import initLDAPGroupsSelect from 'ee/ldap_groups_select'; // eslint-disable-line ...@@ -736,8 +700,8 @@ import initLDAPGroupsSelect from 'ee/ldap_groups_select'; // eslint-disable-line
return Dispatcher; return Dispatcher;
})(); })();
})();
$(window).on('load', function() { export default function initDispatcher() {
new Dispatcher(); return new Dispatcher();
}); }
}).call(window);
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, no-underscore-dangle, one-var-declaration-per-line, object-shorthand, no-unused-vars, no-new, comma-dangle, consistent-return, quotes, dot-notation, quote-props, prefer-arrow-callback, max-len */ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, no-underscore-dangle, one-var-declaration-per-line, object-shorthand, no-unused-vars, no-new, comma-dangle, consistent-return, quotes, dot-notation, quote-props, prefer-arrow-callback, max-len */
import 'vendor/jquery.waitforimages'; import 'vendor/jquery.waitforimages';
import axios from './lib/utils/axios_utils';
import { addDelimiter } from './lib/utils/text_utility'; import { addDelimiter } from './lib/utils/text_utility';
import Flash from './flash'; import flash from './flash';
import TaskList from './task_list'; import TaskList from './task_list';
import CreateMergeRequestDropdown from './create_merge_request_dropdown'; import CreateMergeRequestDropdown from './create_merge_request_dropdown';
import IssuablesHelper from './helpers/issuables_helper'; import IssuablesHelper from './helpers/issuables_helper';
...@@ -42,12 +43,8 @@ export default class Issue { ...@@ -42,12 +43,8 @@ export default class Issue {
this.disableCloseReopenButton($button); this.disableCloseReopenButton($button);
url = $button.attr('href'); url = $button.attr('href');
return $.ajax({ return axios.put(url)
type: 'PUT', .then(({ data }) => {
url: url
})
.fail(() => new Flash(issueFailMessage))
.done((data) => {
const isClosedBadge = $('div.status-box-issue-closed'); const isClosedBadge = $('div.status-box-issue-closed');
const isOpenBadge = $('div.status-box-open'); const isOpenBadge = $('div.status-box-open');
const projectIssuesCounter = $('.issue_counter'); const projectIssuesCounter = $('.issue_counter');
...@@ -74,9 +71,10 @@ export default class Issue { ...@@ -74,9 +71,10 @@ export default class Issue {
} }
} }
} else { } else {
new Flash(issueFailMessage); flash(issueFailMessage);
} }
}) })
.catch(() => flash(issueFailMessage))
.then(() => { .then(() => {
this.disableCloseReopenButton($button, false); this.disableCloseReopenButton($button, false);
}); });
...@@ -115,24 +113,22 @@ export default class Issue { ...@@ -115,24 +113,22 @@ export default class Issue {
static initMergeRequests() { static initMergeRequests() {
var $container; var $container;
$container = $('#merge-requests'); $container = $('#merge-requests');
return $.getJSON($container.data('url')).fail(function() { return axios.get($container.data('url'))
return new Flash('Failed to load referenced merge requests'); .then(({ data }) => {
}).done(function(data) {
if ('html' in data) { if ('html' in data) {
return $container.html(data.html); $container.html(data.html);
} }
}); }).catch(() => flash('Failed to load referenced merge requests'));
} }
static initRelatedBranches() { static initRelatedBranches() {
var $container; var $container;
$container = $('#related-branches'); $container = $('#related-branches');
return $.getJSON($container.data('url')).fail(function() { return axios.get($container.data('url'))
return new Flash('Failed to load related branches'); .then(({ data }) => {
}).done(function(data) {
if ('html' in data) { if ('html' in data) {
return $container.html(data.html); $container.html(data.html);
} }
}); }).catch(() => flash('Failed to load related branches'));
} }
} }
import _ from 'underscore'; import _ from 'underscore';
import axios from './lib/utils/axios_utils';
import { visitUrl } from './lib/utils/url_utility'; import { visitUrl } from './lib/utils/url_utility';
import bp from './breakpoints'; import bp from './breakpoints';
import { numberToHumanSize } from './lib/utils/number_utils'; import { numberToHumanSize } from './lib/utils/number_utils';
...@@ -8,6 +9,7 @@ export default class Job { ...@@ -8,6 +9,7 @@ export default class Job {
constructor(options) { constructor(options) {
this.timeout = null; this.timeout = null;
this.state = null; this.state = null;
this.fetchingStatusFavicon = false;
this.options = options || $('.js-build-options').data(); this.options = options || $('.js-build-options').data();
this.pagePath = this.options.pagePath; this.pagePath = this.options.pagePath;
...@@ -171,12 +173,23 @@ export default class Job { ...@@ -171,12 +173,23 @@ export default class Job {
} }
getBuildTrace() { getBuildTrace() {
return $.ajax({ return axios.get(`${this.pagePath}/trace.json`, {
url: `${this.pagePath}/trace.json`, params: { state: this.state },
data: { state: this.state },
}) })
.done((log) => { .then((res) => {
setCiStatusFavicon(`${this.pagePath}/status.json`); const log = res.data;
if (!this.fetchingStatusFavicon) {
this.fetchingStatusFavicon = true;
setCiStatusFavicon(`${this.pagePath}/status.json`)
.then(() => {
this.fetchingStatusFavicon = false;
})
.catch(() => {
this.fetchingStatusFavicon = false;
});
}
if (log.state) { if (log.state) {
this.state = log.state; this.state = log.state;
...@@ -217,7 +230,7 @@ export default class Job { ...@@ -217,7 +230,7 @@ export default class Job {
visitUrl(this.pagePath); visitUrl(this.pagePath);
} }
}) })
.fail(() => { .catch(() => {
this.$buildRefreshAnimation.remove(); this.$buildRefreshAnimation.remove();
}) })
.then(() => { .then(() => {
......
...@@ -2,9 +2,12 @@ ...@@ -2,9 +2,12 @@
/* global Issuable */ /* global Issuable */
/* global ListLabel */ /* global ListLabel */
import _ from 'underscore'; import _ from 'underscore';
import { __ } from './locale';
import axios from './lib/utils/axios_utils';
import IssuableBulkUpdateActions from './issuable_bulk_update_actions'; import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
import DropdownUtils from './filtered_search/dropdown_utils'; import DropdownUtils from './filtered_search/dropdown_utils';
import CreateLabelDropdown from './create_label'; import CreateLabelDropdown from './create_label';
import flash from './flash';
export default class LabelsSelect { export default class LabelsSelect {
constructor(els, options = {}) { constructor(els, options = {}) {
...@@ -82,12 +85,8 @@ export default class LabelsSelect { ...@@ -82,12 +85,8 @@ export default class LabelsSelect {
} }
$loading.removeClass('hidden').fadeIn(); $loading.removeClass('hidden').fadeIn();
$dropdown.trigger('loading.gl.dropdown'); $dropdown.trigger('loading.gl.dropdown');
return $.ajax({ axios.put(issueUpdateURL, data)
type: 'PUT', .then(({ data }) => {
url: issueUpdateURL,
dataType: 'JSON',
data: data
}).done(function(data) {
var labelCount, template, labelTooltipTitle, labelTitles; var labelCount, template, labelTooltipTitle, labelTitles;
$loading.fadeOut(); $loading.fadeOut();
$dropdown.trigger('loaded.gl.dropdown'); $dropdown.trigger('loaded.gl.dropdown');
...@@ -128,15 +127,15 @@ export default class LabelsSelect { ...@@ -128,15 +127,15 @@ export default class LabelsSelect {
$('.has-tooltip', $value).tooltip({ $('.has-tooltip', $value).tooltip({
container: 'body' container: 'body'
}); });
}); })
.catch(() => flash(__('Error saving label update.')));
}; };
$dropdown.glDropdown({ $dropdown.glDropdown({
showMenuAbove: showMenuAbove, showMenuAbove: showMenuAbove,
data: function(term, callback) { data: function(term, callback) {
return $.ajax({ axios.get(labelUrl)
url: labelUrl .then((res) => {
}).done(function(data) { let data = _.chain(res.data).groupBy(function(label) {
data = _.chain(data).groupBy(function(label) {
return label.title; return label.title;
}).map(function(label) { }).map(function(label) {
var color; var color;
...@@ -174,7 +173,8 @@ export default class LabelsSelect { ...@@ -174,7 +173,8 @@ export default class LabelsSelect {
if (showMenuAbove) { if (showMenuAbove) {
$dropdown.data('glDropdown').positionMenuAbove(); $dropdown.data('glDropdown').positionMenuAbove();
} }
}); })
.catch(() => flash(__('Error fetching labels.')));
}, },
renderRow: function(label, instance) { renderRow: function(label, instance) {
var $a, $li, color, colorEl, indeterminate, removesAll, selectedClass, spacing, i, marked, dropdownName, dropdownValue; var $a, $li, color, colorEl, indeterminate, removesAll, selectedClass, spacing, i, marked, dropdownName, dropdownValue;
......
import axios from './axios_utils';
import Cache from './cache'; import Cache from './cache';
class AjaxCache extends Cache { class AjaxCache extends Cache {
...@@ -18,22 +19,15 @@ class AjaxCache extends Cache { ...@@ -18,22 +19,15 @@ class AjaxCache extends Cache {
let pendingRequest = this.pendingRequests[endpoint]; let pendingRequest = this.pendingRequests[endpoint];
if (!pendingRequest) { if (!pendingRequest) {
pendingRequest = new Promise((resolve, reject) => { pendingRequest = axios.get(endpoint)
// jQuery 2 is not Promises/A+ compatible (missing catch) .then(({ data }) => {
$.ajax(endpoint) // eslint-disable-line promise/catch-or-return
.then(data => resolve(data),
(jqXHR, textStatus, errorThrown) => {
const error = new Error(`${endpoint}: ${errorThrown}`);
error.textStatus = textStatus;
reject(error);
},
);
})
.then((data) => {
this.internalStorage[endpoint] = data; this.internalStorage[endpoint] = data;
delete this.pendingRequests[endpoint]; delete this.pendingRequests[endpoint];
}) })
.catch((error) => { .catch((e) => {
const error = new Error(`${endpoint}: ${e.message}`);
error.textStatus = e.message;
delete this.pendingRequests[endpoint]; delete this.pendingRequests[endpoint];
throw error; throw error;
}); });
......
...@@ -19,6 +19,10 @@ axios.interceptors.response.use((config) => { ...@@ -19,6 +19,10 @@ axios.interceptors.response.use((config) => {
window.activeVueResources -= 1; window.activeVueResources -= 1;
return config; return config;
}, (e) => {
window.activeVueResources -= 1;
return Promise.reject(e);
}); });
export default axios; export default axios;
......
import { getLocationHash } from './url_utility';
import axios from './axios_utils'; import axios from './axios_utils';
import { getLocationHash } from './url_utility';
export const getPagePath = (index = 0) => $('body').attr('data-page').split(':')[index]; export const getPagePath = (index = 0) => $('body').attr('data-page').split(':')[index];
...@@ -28,16 +28,11 @@ export const isInIssuePage = () => { ...@@ -28,16 +28,11 @@ export const isInIssuePage = () => {
return page === 'issues' && action === 'show'; return page === 'issues' && action === 'show';
}; };
export const ajaxGet = url => $.ajax({ export const ajaxGet = url => axios.get(url, {
type: 'GET', params: { format: 'js' },
url, responseType: 'text',
dataType: 'script', }).then(({ data }) => {
}); $.globalEval(data);
export const ajaxPost = (url, data) => $.ajax({
type: 'POST',
url,
data,
}); });
export const rstrip = (val) => { export const rstrip = (val) => {
...@@ -412,7 +407,6 @@ window.gl.utils = { ...@@ -412,7 +407,6 @@ window.gl.utils = {
getGroupSlug, getGroupSlug,
isInIssuePage, isInIssuePage,
ajaxGet, ajaxGet,
ajaxPost,
rstrip, rstrip,
updateTooltipTitle, updateTooltipTitle,
disableButtonIfEmptyField, disableButtonIfEmptyField,
......
...@@ -36,7 +36,7 @@ import initBreadcrumbs from './breadcrumb'; ...@@ -36,7 +36,7 @@ import initBreadcrumbs from './breadcrumb';
// EE-only scripts // EE-only scripts
import 'ee/main'; import 'ee/main';
import './dispatcher'; import initDispatcher from './dispatcher';
// eslint-disable-next-line global-require, import/no-commonjs // eslint-disable-next-line global-require, import/no-commonjs
if (process.env.NODE_ENV !== 'production') require('./test_utils/'); if (process.env.NODE_ENV !== 'production') require('./test_utils/');
...@@ -268,4 +268,6 @@ $(() => { ...@@ -268,4 +268,6 @@ $(() => {
removeFlashClickListener(flashEl); removeFlashClickListener(flashEl);
}); });
} }
initDispatcher();
}); });
/* eslint-disable no-param-reassign, comma-dangle */ /* eslint-disable no-param-reassign, comma-dangle */
import axios from '../lib/utils/axios_utils';
((global) => { ((global) => {
global.mergeConflicts = global.mergeConflicts || {}; global.mergeConflicts = global.mergeConflicts || {};
...@@ -10,20 +11,11 @@ ...@@ -10,20 +11,11 @@
} }
fetchConflictsData() { fetchConflictsData() {
return $.ajax({ return axios.get(this.conflictsPath);
dataType: 'json',
url: this.conflictsPath
});
} }
submitResolveConflicts(data) { submitResolveConflicts(data) {
return $.ajax({ return axios.post(this.resolveConflictsPath, data);
url: this.resolveConflictsPath,
data: JSON.stringify(data),
contentType: 'application/json',
dataType: 'json',
method: 'POST'
});
} }
} }
......
...@@ -38,24 +38,23 @@ $(() => { ...@@ -38,24 +38,23 @@ $(() => {
showDiffViewTypeSwitcher() { return mergeConflictsStore.fileTextTypePresent(); } showDiffViewTypeSwitcher() { return mergeConflictsStore.fileTextTypePresent(); }
}, },
created() { created() {
mergeConflictsService mergeConflictsService.fetchConflictsData()
.fetchConflictsData() .then(({ data }) => {
.done((data) => {
if (data.type === 'error') { if (data.type === 'error') {
mergeConflictsStore.setFailedRequest(data.message); mergeConflictsStore.setFailedRequest(data.message);
} else { } else {
mergeConflictsStore.setConflictsData(data); mergeConflictsStore.setConflictsData(data);
} }
})
.error(() => {
mergeConflictsStore.setFailedRequest();
})
.always(() => {
mergeConflictsStore.setLoadingState(false); mergeConflictsStore.setLoadingState(false);
this.$nextTick(() => { this.$nextTick(() => {
syntaxHighlight($('.js-syntax-highlight')); syntaxHighlight($('.js-syntax-highlight'));
}); });
})
.catch(() => {
mergeConflictsStore.setLoadingState(false);
mergeConflictsStore.setFailedRequest();
}); });
}, },
methods: { methods: {
...@@ -82,10 +81,10 @@ $(() => { ...@@ -82,10 +81,10 @@ $(() => {
mergeConflictsService mergeConflictsService
.submitResolveConflicts(mergeConflictsStore.getCommitData()) .submitResolveConflicts(mergeConflictsStore.getCommitData())
.done((data) => { .then(({ data }) => {
window.location.href = data.redirect_to; window.location.href = data.redirect_to;
}) })
.error(() => { .catch(() => {
mergeConflictsStore.setSubmitState(false); mergeConflictsStore.setSubmitState(false);
new Flash('Failed to save merge conflicts resolutions. Please try again!'); new Flash('Failed to save merge conflicts resolutions. Please try again!');
}); });
......
/* eslint-disable no-new, class-methods-use-this */ /* eslint-disable no-new, class-methods-use-this */
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import Flash from './flash'; import axios from './lib/utils/axios_utils';
import flash from './flash';
import BlobForkSuggestion from './blob/blob_fork_suggestion'; import BlobForkSuggestion from './blob/blob_fork_suggestion';
import initChangesDropdown from './init_changes_dropdown'; import initChangesDropdown from './init_changes_dropdown';
import bp from './breakpoints'; import bp from './breakpoints';
...@@ -244,14 +245,21 @@ export default class MergeRequestTabs { ...@@ -244,14 +245,21 @@ export default class MergeRequestTabs {
if (this.commitsLoaded) { if (this.commitsLoaded) {
return; return;
} }
this.ajaxGet({
url: `${source}.json`, this.toggleLoading(true);
success: (data) => {
axios.get(`${source}.json`)
.then(({ data }) => {
document.querySelector('div#commits').innerHTML = data.html; document.querySelector('div#commits').innerHTML = data.html;
localTimeAgo($('.js-timeago', 'div#commits')); localTimeAgo($('.js-timeago', 'div#commits'));
this.commitsLoaded = true; this.commitsLoaded = true;
this.scrollToElement('#commits'); this.scrollToElement('#commits');
},
this.toggleLoading(false);
})
.catch(() => {
this.toggleLoading(false);
flash('An error occurred while fetching this tab.');
}); });
} }
...@@ -283,9 +291,10 @@ export default class MergeRequestTabs { ...@@ -283,9 +291,10 @@ export default class MergeRequestTabs {
// some pages like MergeRequestsController#new has query parameters on that anchor // some pages like MergeRequestsController#new has query parameters on that anchor
const urlPathname = parseUrlPathname(source); const urlPathname = parseUrlPathname(source);
this.ajaxGet({ this.toggleLoading(true);
url: `${urlPathname}.json${location.search}`,
success: (data) => { axios.get(`${urlPathname}.json${location.search}`)
.then(({ data }) => {
const $container = $('#diffs'); const $container = $('#diffs');
$container.html(data.html); $container.html(data.html);
...@@ -335,7 +344,12 @@ export default class MergeRequestTabs { ...@@ -335,7 +344,12 @@ export default class MergeRequestTabs {
// (discussion and diff tabs) and `:target` only applies to the first // (discussion and diff tabs) and `:target` only applies to the first
anchor.addClass('target'); anchor.addClass('target');
} }
},
this.toggleLoading(false);
})
.catch(() => {
this.toggleLoading(false);
flash('An error occurred while fetching this tab.');
}); });
} }
...@@ -346,17 +360,6 @@ export default class MergeRequestTabs { ...@@ -346,17 +360,6 @@ export default class MergeRequestTabs {
$('.mr-loading-status .loading').toggle(status); $('.mr-loading-status .loading').toggle(status);
} }
ajaxGet(options) {
const defaults = {
beforeSend: () => this.toggleLoading(true),
error: () => new Flash('An error occurred while fetching this tab.', 'alert'),
complete: () => this.toggleLoading(false),
dataType: 'json',
type: 'GET',
};
$.ajax($.extend({}, defaults, options));
}
diffViewType() { diffViewType() {
return $('.inline-parallel-buttons a.active').data('view-type'); return $('.inline-parallel-buttons a.active').data('view-type');
} }
......
import Flash from './flash'; import axios from './lib/utils/axios_utils';
import flash from './flash';
export default class Milestone { export default class Milestone {
constructor() { constructor() {
...@@ -33,15 +34,12 @@ export default class Milestone { ...@@ -33,15 +34,12 @@ export default class Milestone {
const tabElId = $target.attr('href'); const tabElId = $target.attr('href');
if (endpoint && !$target.hasClass('is-loaded')) { if (endpoint && !$target.hasClass('is-loaded')) {
$.ajax({ axios.get(endpoint)
url: endpoint, .then(({ data }) => {
dataType: 'JSON',
})
.fail(() => new Flash('Error loading milestone tab'))
.done((data) => {
$(tabElId).html(data.html); $(tabElId).html(data.html);
$target.addClass('is-loaded'); $target.addClass('is-loaded');
}); })
.catch(() => flash('Error loading milestone tab'));
} }
} }
} }
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
/* global Issuable */ /* global Issuable */
/* global ListMilestone */ /* global ListMilestone */
import _ from 'underscore'; import _ from 'underscore';
import axios from './lib/utils/axios_utils';
import { timeFor } from './lib/utils/datetime_utility'; import { timeFor } from './lib/utils/datetime_utility';
export default class MilestoneSelect { export default class MilestoneSelect {
...@@ -52,9 +53,8 @@ export default class MilestoneSelect { ...@@ -52,9 +53,8 @@ export default class MilestoneSelect {
} }
return $dropdown.glDropdown({ return $dropdown.glDropdown({
showMenuAbove: showMenuAbove, showMenuAbove: showMenuAbove,
data: (term, callback) => $.ajax({ data: (term, callback) => axios.get(milestonesUrl)
url: milestonesUrl .then(({ data }) => {
}).done((data) => {
const extraOptions = []; const extraOptions = [];
if (showAny) { if (showAny) {
extraOptions.push({ extraOptions.push({
...@@ -200,11 +200,8 @@ export default class MilestoneSelect { ...@@ -200,11 +200,8 @@ export default class MilestoneSelect {
data[abilityName].milestone_id = selected != null ? selected : null; data[abilityName].milestone_id = selected != null ? selected : null;
$loading.removeClass('hidden').fadeIn(); $loading.removeClass('hidden').fadeIn();
$dropdown.trigger('loading.gl.dropdown'); $dropdown.trigger('loading.gl.dropdown');
return $.ajax({ return axios.put(issueUpdateURL, data)
type: 'PUT', .then(({ data }) => {
url: issueUpdateURL,
data: data
}).done((data) => {
$dropdown.trigger('loaded.gl.dropdown'); $dropdown.trigger('loaded.gl.dropdown');
$loading.fadeOut(); $loading.fadeOut();
$selectBox.hide(); $selectBox.hide();
......
...@@ -24,7 +24,7 @@ import GLForm from './gl_form'; ...@@ -24,7 +24,7 @@ import GLForm from './gl_form';
import loadAwardsHandler from './awards_handler'; import loadAwardsHandler from './awards_handler';
import Autosave from './autosave'; import Autosave from './autosave';
import TaskList from './task_list'; import TaskList from './task_list';
import { ajaxPost, isInViewport, getPagePath, scrollToElement, isMetaKey } from './lib/utils/common_utils'; import { isInViewport, getPagePath, scrollToElement, isMetaKey } from './lib/utils/common_utils';
import imageDiffHelper from './image_diff/helpers/index'; import imageDiffHelper from './image_diff/helpers/index';
import { localTimeAgo } from './lib/utils/datetime_utility'; import { localTimeAgo } from './lib/utils/datetime_utility';
...@@ -1399,7 +1399,7 @@ export default class Notes { ...@@ -1399,7 +1399,7 @@ export default class Notes {
* 2) Identify comment type; a) Main thread b) Discussion thread c) Discussion resolve * 2) Identify comment type; a) Main thread b) Discussion thread c) Discussion resolve
* 3) Build temporary placeholder element (using `createPlaceholderNote`) * 3) Build temporary placeholder element (using `createPlaceholderNote`)
* 4) Show placeholder note on UI * 4) Show placeholder note on UI
* 5) Perform network request to submit the note using `ajaxPost` * 5) Perform network request to submit the note using `axios.post`
* a) If request is successfully completed * a) If request is successfully completed
* 1. Remove placeholder element * 1. Remove placeholder element
* 2. Show submitted Note element * 2. Show submitted Note element
...@@ -1481,8 +1481,10 @@ export default class Notes { ...@@ -1481,8 +1481,10 @@ export default class Notes {
/* eslint-disable promise/catch-or-return */ /* eslint-disable promise/catch-or-return */
// Make request to submit comment on server // Make request to submit comment on server
ajaxPost(formAction, formData) axios.post(formAction, formData)
.then((note) => { .then((res) => {
const note = res.data;
// Submission successful! remove placeholder // Submission successful! remove placeholder
$notesContainer.find(`#${noteUniqueId}`).remove(); $notesContainer.find(`#${noteUniqueId}`).remove();
...@@ -1555,7 +1557,7 @@ export default class Notes { ...@@ -1555,7 +1557,7 @@ export default class Notes {
} }
$form.trigger('ajax:success', [note]); $form.trigger('ajax:success', [note]);
}).fail(() => { }).catch(() => {
// Submission failed, remove placeholder note and show Flash error message // Submission failed, remove placeholder note and show Flash error message
$notesContainer.find(`#${noteUniqueId}`).remove(); $notesContainer.find(`#${noteUniqueId}`).remove();
...@@ -1594,7 +1596,7 @@ export default class Notes { ...@@ -1594,7 +1596,7 @@ export default class Notes {
* *
* 1) Get Form metadata * 1) Get Form metadata
* 2) Update note element with new content * 2) Update note element with new content
* 3) Perform network request to submit the updated note using `ajaxPost` * 3) Perform network request to submit the updated note using `axios.post`
* a) If request is successfully completed * a) If request is successfully completed
* 1. Show submitted Note element * 1. Show submitted Note element
* b) If request failed * b) If request failed
...@@ -1625,12 +1627,12 @@ export default class Notes { ...@@ -1625,12 +1627,12 @@ export default class Notes {
/* eslint-disable promise/catch-or-return */ /* eslint-disable promise/catch-or-return */
// Make request to update comment on server // Make request to update comment on server
ajaxPost(formAction, formData) axios.post(formAction, formData)
.then((note) => { .then(({ data }) => {
// Submission successful! render final note element // Submission successful! render final note element
this.updateNote(note, $editingNote); this.updateNote(data, $editingNote);
}) })
.fail(() => { .catch(() => {
// Submission failed, revert back to original note // Submission failed, revert back to original note
$noteBodyText.html(_.escape(cachedNoteBodyText)); $noteBodyText.html(_.escape(cachedNoteBodyText));
$editingNote.removeClass('being-posted fade-in'); $editingNote.removeClass('being-posted fade-in');
......
import projectSelect from '~/project_select'; import projectSelect from '~/project_select';
export default projectSelect; document.addEventListener('DOMContentLoaded', projectSelect);
...@@ -7,7 +7,7 @@ import ProjectsList from '~/projects_list'; ...@@ -7,7 +7,7 @@ import ProjectsList from '~/projects_list';
import ShortcutsNavigation from '~/shortcuts_navigation'; import ShortcutsNavigation from '~/shortcuts_navigation';
import initGroupsList from '../../../groups'; import initGroupsList from '../../../groups';
export default () => { document.addEventListener('DOMContentLoaded', () => {
const newGroupChildWrapper = document.querySelector('.js-new-project-subgroup'); const newGroupChildWrapper = document.querySelector('.js-new-project-subgroup');
new ShortcutsNavigation(); new ShortcutsNavigation();
new NotificationsForm(); new NotificationsForm();
...@@ -19,4 +19,4 @@ export default () => { ...@@ -19,4 +19,4 @@ export default () => {
} }
initGroupsList(); initGroupsList();
}; });
import UsersSelect from '~/users_select'; import UsersSelect from '~/users_select';
import ShortcutsNavigation from '~/shortcuts_navigation'; import ShortcutsNavigation from '~/shortcuts_navigation';
export default () => { document.addEventListener('DOMContentLoaded', () => {
new UsersSelect(); // eslint-disable-line no-new new UsersSelect(); // eslint-disable-line no-new
new ShortcutsNavigation(); // eslint-disable-line no-new new ShortcutsNavigation(); // eslint-disable-line no-new
}; });
...@@ -7,10 +7,10 @@ import initFilteredSearch from '~/pages/search/init_filtered_search'; ...@@ -7,10 +7,10 @@ import initFilteredSearch from '~/pages/search/init_filtered_search';
import { FILTERED_SEARCH } from '~/pages/constants'; import { FILTERED_SEARCH } from '~/pages/constants';
import { ISSUABLE_INDEX } from '~/pages/projects/constants'; import { ISSUABLE_INDEX } from '~/pages/projects/constants';
export default () => { document.addEventListener('DOMContentLoaded', () => {
initFilteredSearch(FILTERED_SEARCH.ISSUES); initFilteredSearch(FILTERED_SEARCH.ISSUES);
new IssuableIndex(ISSUABLE_INDEX.ISSUE); new IssuableIndex(ISSUABLE_INDEX.ISSUE);
new ShortcutsNavigation(); new ShortcutsNavigation();
new UsersSelect(); new UsersSelect();
}; });
/* eslint-disable no-new */ /* eslint-disable no-new */
import initIssuableSidebar from '~/init_issuable_sidebar'; import initIssuableSidebar from '~/init_issuable_sidebar';
import Issue from '~/issue'; import Issue from '~/issue';
import ShortcutsIssuable from '~/shortcuts_issuable'; import ShortcutsIssuable from '~/shortcuts_issuable';
import ZenMode from '~/zen_mode'; import ZenMode from '~/zen_mode';
export default () => { document.addEventListener('DOMContentLoaded', () => {
new Issue(); new Issue();
new ShortcutsIssuable(); new ShortcutsIssuable();
new ZenMode(); new ZenMode();
initIssuableSidebar(); initIssuableSidebar();
}; });
...@@ -5,9 +5,9 @@ import initFilteredSearch from '~/pages/search/init_filtered_search'; ...@@ -5,9 +5,9 @@ import initFilteredSearch from '~/pages/search/init_filtered_search';
import { FILTERED_SEARCH } from '~/pages/constants'; import { FILTERED_SEARCH } from '~/pages/constants';
import { ISSUABLE_INDEX } from '~/pages/projects/constants'; import { ISSUABLE_INDEX } from '~/pages/projects/constants';
export default () => { document.addEventListener('DOMContentLoaded', () => {
initFilteredSearch(FILTERED_SEARCH.MERGE_REQUESTS); initFilteredSearch(FILTERED_SEARCH.MERGE_REQUESTS);
new IssuableIndex(ISSUABLE_INDEX.MERGE_REQUEST); // eslint-disable-line no-new new IssuableIndex(ISSUABLE_INDEX.MERGE_REQUEST); // eslint-disable-line no-new
new ShortcutsNavigation(); // eslint-disable-line no-new new ShortcutsNavigation(); // eslint-disable-line no-new
new UsersSelect(); // eslint-disable-line no-new new UsersSelect(); // eslint-disable-line no-new
}; });
/* eslint-disable func-names, space-before-function-paren, no-var, consistent-return, no-new, prefer-arrow-callback, no-return-assign, one-var, one-var-declaration-per-line, object-shorthand, no-else-return, newline-per-chained-call, no-shadow, vars-on-top, prefer-template, max-len */ /* eslint-disable func-names, space-before-function-paren, no-var, consistent-return, no-new, prefer-arrow-callback, no-return-assign, one-var, one-var-declaration-per-line, object-shorthand, no-else-return, newline-per-chained-call, no-shadow, vars-on-top, prefer-template, max-len */
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import { visitUrl } from '../../lib/utils/url_utility'; import { __ } from '~/locale';
import { visitUrl } from '~/lib/utils/url_utility';
import axios from '~/lib/utils/axios_utils';
import flash from '~/flash';
import projectSelect from '../../project_select'; import projectSelect from '../../project_select';
export default class Project { export default class Project {
...@@ -79,17 +82,15 @@ export default class Project { ...@@ -79,17 +82,15 @@ export default class Project {
$dropdown = $(this); $dropdown = $(this);
selected = $dropdown.data('selected'); selected = $dropdown.data('selected');
return $dropdown.glDropdown({ return $dropdown.glDropdown({
data: function(term, callback) { data(term, callback) {
return $.ajax({ axios.get($dropdown.data('refs-url'), {
url: $dropdown.data('refs-url'), params: {
data: {
ref: $dropdown.data('ref'), ref: $dropdown.data('ref'),
search: term, search: term,
}, },
dataType: 'json', })
}).done(function(refs) { .then(({ data }) => callback(data))
return callback(refs); .catch(() => flash(__('An error occurred while getting projects')));
});
}, },
selectable: true, selectable: true,
filterable: true, filterable: true,
......
...@@ -8,7 +8,7 @@ import { ajaxGet } from '~/lib/utils/common_utils'; ...@@ -8,7 +8,7 @@ import { ajaxGet } from '~/lib/utils/common_utils';
import Star from '../../../star'; import Star from '../../../star';
import notificationsDropdown from '../../../notifications_dropdown'; import notificationsDropdown from '../../../notifications_dropdown';
export default () => { document.addEventListener('DOMContentLoaded', () => {
new Star(); // eslint-disable-line no-new new Star(); // eslint-disable-line no-new
notificationsDropdown(); notificationsDropdown();
new ShortcutsNavigation(); // eslint-disable-line no-new new ShortcutsNavigation(); // eslint-disable-line no-new
...@@ -24,4 +24,4 @@ export default () => { ...@@ -24,4 +24,4 @@ export default () => {
$('#tree-slider').waitForImages(() => { $('#tree-slider').waitForImages(() => {
ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath); ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath);
}); });
}; });
...@@ -2,10 +2,10 @@ import UsernameValidator from './username_validator'; ...@@ -2,10 +2,10 @@ import UsernameValidator from './username_validator';
import SigninTabsMemoizer from './signin_tabs_memoizer'; import SigninTabsMemoizer from './signin_tabs_memoizer';
import OAuthRememberMe from './oauth_remember_me'; import OAuthRememberMe from './oauth_remember_me';
export default () => { document.addEventListener('DOMContentLoaded', () => {
new UsernameValidator(); // eslint-disable-line no-new new UsernameValidator(); // eslint-disable-line no-new
new SigninTabsMemoizer(); // eslint-disable-line no-new new SigninTabsMemoizer(); // eslint-disable-line no-new
new OAuthRememberMe({ // eslint-disable-line no-new new OAuthRememberMe({ // eslint-disable-line no-new
container: $('.omniauth-container'), container: $('.omniauth-container'),
}).bindEvents(); }).bindEvents();
}; });
...@@ -4,7 +4,7 @@ import GlFieldErrors from '../gl_field_errors'; ...@@ -4,7 +4,7 @@ import GlFieldErrors from '../gl_field_errors';
import intervalPatternInput from './components/interval_pattern_input.vue'; import intervalPatternInput from './components/interval_pattern_input.vue';
import TimezoneDropdown from './components/timezone_dropdown'; import TimezoneDropdown from './components/timezone_dropdown';
import TargetBranchDropdown from './components/target_branch_dropdown'; import TargetBranchDropdown from './components/target_branch_dropdown';
import { setupPipelineVariableList } from './setup_pipeline_variable_list'; import setupNativeFormVariableList from '../ci_variable_list/native_form_variable_list';
Vue.use(Translate); Vue.use(Translate);
...@@ -42,5 +42,8 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -42,5 +42,8 @@ document.addEventListener('DOMContentLoaded', () => {
gl.targetBranchDropdown = new TargetBranchDropdown(); gl.targetBranchDropdown = new TargetBranchDropdown();
gl.pipelineScheduleFieldErrors = new GlFieldErrors(formElement); gl.pipelineScheduleFieldErrors = new GlFieldErrors(formElement);
setupPipelineVariableList($('.js-pipeline-variable-list')); setupNativeFormVariableList({
container: $('.js-ci-variable-list-section'),
formField: 'schedule',
});
}); });
import { convertPermissionToBoolean } from '../lib/utils/common_utils';
function insertRow($row) {
const $rowClone = $row.clone();
$rowClone.removeAttr('data-is-persisted');
$rowClone.find('input, textarea').val('');
$row.after($rowClone);
}
function removeRow($row) {
const isPersisted = convertPermissionToBoolean($row.attr('data-is-persisted'));
if (isPersisted) {
$row.hide();
$row
.find('.js-destroy-input')
.val(1);
} else {
$row.remove();
}
}
function checkIfRowTouched($row) {
return $row.find('.js-user-input').toArray().some(el => $(el).val().length > 0);
}
function setupPipelineVariableList(parent = document) {
const $parent = $(parent);
$parent.on('click', '.js-row-remove-button', (e) => {
const $row = $(e.currentTarget).closest('.js-row');
removeRow($row);
e.preventDefault();
});
// Remove any empty rows except the last r
$parent.on('blur', '.js-user-input', (e) => {
const $row = $(e.currentTarget).closest('.js-row');
const isTouched = checkIfRowTouched($row);
if ($row.is(':not(:last-child)') && !isTouched) {
removeRow($row);
}
});
// Always make sure there is an empty last row
$parent.on('input', '.js-user-input', () => {
const $lastRow = $parent.find('.js-row').last();
const isTouched = checkIfRowTouched($lastRow);
if (isTouched) {
insertRow($lastRow);
}
});
// Clear out the empty last row so it
// doesn't get submitted and throw validation errors
$parent.closest('form').on('submit', () => {
const $lastRow = $parent.find('.js-row').last();
const isTouched = checkIfRowTouched($lastRow);
if (!isTouched) {
$lastRow.find('input, textarea').attr('name', '');
}
});
}
export {
setupPipelineVariableList,
insertRow,
removeRow,
};
...@@ -13,7 +13,7 @@ import { convertPermissionToBoolean } from './lib/utils/common_utils'; ...@@ -13,7 +13,7 @@ import { convertPermissionToBoolean } from './lib/utils/common_utils';
``` ```
*/ */
function updatetoggle(toggle, isOn) { function updateToggle(toggle, isOn) {
toggle.classList.toggle('is-checked', isOn); toggle.classList.toggle('is-checked', isOn);
} }
...@@ -21,7 +21,7 @@ function onToggleClicked(toggle, input, clickCallback) { ...@@ -21,7 +21,7 @@ function onToggleClicked(toggle, input, clickCallback) {
const previousIsOn = convertPermissionToBoolean(input.value); const previousIsOn = convertPermissionToBoolean(input.value);
// Visually change the toggle and start loading // Visually change the toggle and start loading
updatetoggle(toggle, !previousIsOn); updateToggle(toggle, !previousIsOn);
toggle.setAttribute('disabled', true); toggle.setAttribute('disabled', true);
toggle.classList.toggle('is-loading', true); toggle.classList.toggle('is-loading', true);
...@@ -32,7 +32,7 @@ function onToggleClicked(toggle, input, clickCallback) { ...@@ -32,7 +32,7 @@ function onToggleClicked(toggle, input, clickCallback) {
}) })
.catch(() => { .catch(() => {
// Revert the visuals if something goes wrong // Revert the visuals if something goes wrong
updatetoggle(toggle, previousIsOn); updateToggle(toggle, previousIsOn);
}) })
.then(() => { .then(() => {
// Remove the loading indicator in any case // Remove the loading indicator in any case
...@@ -54,7 +54,7 @@ export default function setupToggleButtons(container, clickCallback = () => {}) ...@@ -54,7 +54,7 @@ export default function setupToggleButtons(container, clickCallback = () => {})
const isOn = convertPermissionToBoolean(input.value); const isOn = convertPermissionToBoolean(input.value);
// Get the visible toggle in sync with the hidden input // Get the visible toggle in sync with the hidden input
updatetoggle(toggle, isOn); updateToggle(toggle, isOn);
toggle.addEventListener('click', onToggleClicked.bind(null, toggle, input, clickCallback)); toggle.addEventListener('click', onToggleClicked.bind(null, toggle, input, clickCallback));
}); });
......
...@@ -48,7 +48,7 @@ ...@@ -48,7 +48,7 @@
}; };
</script> </script>
<template> <template>
<ul class="nav-links scrolling-tabs"> <ul class="nav-links scrolling-tabs separator">
<li <li
v-for="(tab, i) in tabs" v-for="(tab, i) in tabs"
:key="i" :key="i"
......
...@@ -60,3 +60,4 @@ ...@@ -60,3 +60,4 @@
@import "framework/responsive_tables"; @import "framework/responsive_tables";
@import "framework/stacked-progress-bar"; @import "framework/stacked-progress-bar";
@import "framework/sortable"; @import "framework/sortable";
@import "framework/ci_variable_list";
...@@ -177,7 +177,8 @@ ...@@ -177,7 +177,8 @@
@include btn-outline($white-light, $red-500, $red-500, $red-500, $white-light, $red-600, $red-600, $red-700); @include btn-outline($white-light, $red-500, $red-500, $red-500, $white-light, $red-600, $red-600, $red-700);
} }
&.btn-primary { &.btn-primary,
&.btn-info {
@include btn-outline($white-light, $blue-500, $blue-500, $blue-500, $white-light, $blue-600, $blue-600, $blue-700); @include btn-outline($white-light, $blue-500, $blue-500, $blue-500, $white-light, $blue-600, $blue-600, $blue-700);
} }
} }
......
.ci-variable-list {
margin-left: 0;
margin-bottom: 0;
padding-left: 0;
list-style: none;
clear: both;
}
.ci-variable-row {
display: flex;
align-items: flex-end;
&:not(:last-child) {
margin-bottom: $gl-btn-padding;
@media (max-width: $screen-xs-max) {
margin-bottom: 3 * $gl-btn-padding;
}
}
&:last-child {
.ci-variable-body-item:last-child {
margin-right: $ci-variable-remove-button-width;
@media (max-width: $screen-xs-max) {
margin-right: 0;
}
}
.ci-variable-row-remove-button {
display: none;
}
@media (max-width: $screen-xs-max) {
.ci-variable-row-body {
margin-right: $ci-variable-remove-button-width;
}
}
}
}
.ci-variable-row-body {
display: flex;
width: 100%;
@media (max-width: $screen-xs-max) {
display: block;
}
}
.ci-variable-body-item {
flex: 1;
&:not(:last-child) {
margin-right: $gl-btn-padding;
@media (max-width: $screen-xs-max) {
margin-right: 0;
margin-bottom: $gl-btn-padding;
}
}
}
.ci-variable-protected-item {
flex: 0 1 auto;
display: flex;
align-items: center;
}
.ci-variable-row-remove-button {
@include transition(color);
flex-shrink: 0;
display: flex;
justify-content: center;
align-items: center;
width: $ci-variable-remove-button-width;
height: $input-height;
padding: 0;
background: transparent;
border: 0;
color: $gl-text-color-secondary;
&:hover,
&:focus {
outline: none;
color: $gl-text-color;
}
}
...@@ -82,6 +82,10 @@ ...@@ -82,6 +82,10 @@
/* Small devices (phones, tablets, 768px and lower) */ /* Small devices (phones, tablets, 768px and lower) */
@media (max-width: $screen-xs-max) { @media (max-width: $screen-xs-max) {
width: 100%; width: 100%;
&.mobile-separator {
border-bottom: 1px solid $border-color;
}
} }
} }
...@@ -168,9 +172,9 @@ ...@@ -168,9 +172,9 @@
display: inline-block; display: inline-block;
} }
// Applies on /dashboard/issues
.project-item-select-holder { .project-item-select-holder {
margin: 0; margin: 0;
width: 100%;
} }
&.inline { &.inline {
...@@ -367,7 +371,6 @@ ...@@ -367,7 +371,6 @@
.project-item-select-holder.btn-group { .project-item-select-holder.btn-group {
display: flex; display: flex;
max-width: 350px;
overflow: hidden; overflow: hidden;
float: right; float: right;
......
...@@ -675,9 +675,9 @@ $pipeline-dropdown-line-height: 20px; ...@@ -675,9 +675,9 @@ $pipeline-dropdown-line-height: 20px;
$pipeline-dropdown-status-icon-size: 18px; $pipeline-dropdown-status-icon-size: 18px;
/* /*
Pipeline Schedules CI variable lists
*/ */
$pipeline-variable-remove-button-width: calc(1em + #{2 * $gl-padding}); $ci-variable-remove-button-width: calc(1em + #{2 * $gl-padding});
/* /*
......
...@@ -78,84 +78,3 @@ ...@@ -78,84 +78,3 @@
margin-right: 3px; margin-right: 3px;
} }
} }
.pipeline-variable-list {
margin-left: 0;
margin-bottom: 0;
padding-left: 0;
list-style: none;
clear: both;
}
.pipeline-variable-row {
display: flex;
align-items: flex-end;
&:not(:last-child) {
margin-bottom: $gl-btn-padding;
}
@media (max-width: $screen-sm-max) {
padding-right: $gl-col-padding;
}
&:last-child {
.pipeline-variable-row-remove-button {
display: none;
}
@media (max-width: $screen-sm-max) {
.pipeline-variable-value-input {
margin-right: $pipeline-variable-remove-button-width;
}
}
@media (max-width: $screen-xs-max) {
.pipeline-variable-row-body {
margin-right: $pipeline-variable-remove-button-width;
}
}
}
}
.pipeline-variable-row-body {
display: flex;
width: calc(75% - #{$gl-col-padding});
padding-left: $gl-col-padding;
@media (max-width: $screen-sm-max) {
width: 100%;
}
@media (max-width: $screen-xs-max) {
display: block;
}
}
.pipeline-variable-key-input {
margin-right: $gl-btn-padding;
@media (max-width: $screen-xs-max) {
margin-bottom: $gl-btn-padding;
}
}
.pipeline-variable-row-remove-button {
@include transition(color);
flex-shrink: 0;
display: flex;
justify-content: center;
align-items: center;
width: $pipeline-variable-remove-button-width;
height: $input-height;
padding: 0;
background: transparent;
border: 0;
color: $gl-text-color-secondary;
&:hover,
&:focus {
outline: none;
color: $gl-text-color;
}
}
class Admin::ServicesController < Admin::ApplicationController class Admin::ServicesController < Admin::ApplicationController
include ServiceParams include ServiceParams
before_action :whitelist_query_limiting, only: [:index]
before_action :service, only: [:edit, :update] before_action :service, only: [:edit, :update]
def index def index
...@@ -37,4 +38,8 @@ class Admin::ServicesController < Admin::ApplicationController ...@@ -37,4 +38,8 @@ class Admin::ServicesController < Admin::ApplicationController
def service def service
@service ||= Service.where(id: params[:id], template: true).first @service ||= Service.where(id: params[:id], template: true).first
end end
def whitelist_query_limiting
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42430')
end
end end
...@@ -4,6 +4,7 @@ module Boards ...@@ -4,6 +4,7 @@ module Boards
prepend EE::Boards::IssuesController prepend EE::Boards::IssuesController
include BoardsResponses include BoardsResponses
before_action :whitelist_query_limiting, only: [:index, :update]
before_action :authorize_read_issue, only: [:index] before_action :authorize_read_issue, only: [:index]
before_action :authorize_create_issue, only: [:create] before_action :authorize_create_issue, only: [:create]
before_action :authorize_update_issue, only: [:update] before_action :authorize_update_issue, only: [:update]
...@@ -94,5 +95,10 @@ module Boards ...@@ -94,5 +95,10 @@ module Boards
} }
) )
end end
def whitelist_query_limiting
# Also see https://gitlab.com/gitlab-org/gitlab-ce/issues/42439
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42428')
end
end end
end end
class Import::GitlabProjectsController < Import::BaseController class Import::GitlabProjectsController < Import::BaseController
before_action :whitelist_query_limiting, only: [:create]
before_action :verify_gitlab_project_import_enabled before_action :verify_gitlab_project_import_enabled
def new def new
...@@ -40,4 +41,8 @@ class Import::GitlabProjectsController < Import::BaseController ...@@ -40,4 +41,8 @@ class Import::GitlabProjectsController < Import::BaseController
:path, :namespace_id, :file :path, :namespace_id, :file
) )
end end
def whitelist_query_limiting
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42437')
end
end end
...@@ -4,6 +4,7 @@ class Projects::CommitsController < Projects::ApplicationController ...@@ -4,6 +4,7 @@ class Projects::CommitsController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
include RendersCommits include RendersCommits
before_action :whitelist_query_limiting
before_action :require_non_empty_project before_action :require_non_empty_project
before_action :assign_ref_vars before_action :assign_ref_vars
before_action :authorize_download_code! before_action :authorize_download_code!
...@@ -65,4 +66,8 @@ class Projects::CommitsController < Projects::ApplicationController ...@@ -65,4 +66,8 @@ class Projects::CommitsController < Projects::ApplicationController
@commits = @commits.with_pipeline_status @commits = @commits.with_pipeline_status
@commits = prepare_commits_for_rendering(@commits) @commits = prepare_commits_for_rendering(@commits)
end end
def whitelist_query_limiting
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42330')
end
end end
...@@ -3,6 +3,7 @@ class Projects::CycleAnalyticsController < Projects::ApplicationController ...@@ -3,6 +3,7 @@ class Projects::CycleAnalyticsController < Projects::ApplicationController
include ActionView::Helpers::TextHelper include ActionView::Helpers::TextHelper
include CycleAnalyticsParams include CycleAnalyticsParams
before_action :whitelist_query_limiting, only: [:show]
before_action :authorize_read_cycle_analytics! before_action :authorize_read_cycle_analytics!
def show def show
...@@ -31,4 +32,8 @@ class Projects::CycleAnalyticsController < Projects::ApplicationController ...@@ -31,4 +32,8 @@ class Projects::CycleAnalyticsController < Projects::ApplicationController
permissions: @cycle_analytics.permissions(user: current_user) permissions: @cycle_analytics.permissions(user: current_user)
} }
end end
def whitelist_query_limiting
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42671')
end
end end
...@@ -2,6 +2,7 @@ class Projects::ForksController < Projects::ApplicationController ...@@ -2,6 +2,7 @@ class Projects::ForksController < Projects::ApplicationController
include ContinueParams include ContinueParams
# Authorize # Authorize
before_action :whitelist_query_limiting, only: [:create]
before_action :require_non_empty_project before_action :require_non_empty_project
before_action :authorize_download_code! before_action :authorize_download_code!
before_action :authenticate_user!, only: [:new, :create] before_action :authenticate_user!, only: [:new, :create]
...@@ -54,4 +55,8 @@ class Projects::ForksController < Projects::ApplicationController ...@@ -54,4 +55,8 @@ class Projects::ForksController < Projects::ApplicationController
render :error render :error
end end
end end
def whitelist_query_limiting
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42335')
end
end end
...@@ -8,6 +8,8 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -8,6 +8,8 @@ class Projects::IssuesController < Projects::ApplicationController
prepend_before_action :authenticate_user!, only: [:new, :export_csv] prepend_before_action :authenticate_user!, only: [:new, :export_csv]
before_action :whitelist_query_limiting_ee, only: [:update]
before_action :whitelist_query_limiting, only: [:create, :create_merge_request, :move, :bulk_update]
before_action :check_issues_available! before_action :check_issues_available!
before_action :issue, except: [:index, :new, :create, :bulk_update, :export_csv] before_action :issue, except: [:index, :new, :create, :bulk_update, :export_csv]
...@@ -250,4 +252,17 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -250,4 +252,17 @@ class Projects::IssuesController < Projects::ApplicationController
@finder_type = IssuesFinder @finder_type = IssuesFinder
super super
end end
def whitelist_query_limiting
# Also see the following issues:
#
# 1. https://gitlab.com/gitlab-org/gitlab-ce/issues/42423
# 2. https://gitlab.com/gitlab-org/gitlab-ce/issues/42424
# 3. https://gitlab.com/gitlab-org/gitlab-ce/issues/42426
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42422')
end
def whitelist_query_limiting_ee
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ee/issues/4794')
end
end end
...@@ -6,6 +6,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap ...@@ -6,6 +6,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
prepend ::EE::Projects::MergeRequests::CreationsController prepend ::EE::Projects::MergeRequests::CreationsController
skip_before_action :merge_request skip_before_action :merge_request
before_action :whitelist_query_limiting, only: [:create]
before_action :authorize_create_merge_request! before_action :authorize_create_merge_request!
before_action :apply_diff_view_cookie!, only: [:diffs, :diff_for_path] before_action :apply_diff_view_cookie!, only: [:diffs, :diff_for_path]
before_action :build_merge_request, except: [:create] before_action :build_merge_request, except: [:create]
...@@ -127,4 +128,8 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap ...@@ -127,4 +128,8 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
@project.forked_from_project @project.forked_from_project
end end
end end
def whitelist_query_limiting
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42384')
end
end end
...@@ -9,6 +9,8 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -9,6 +9,8 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
prepend ::EE::Projects::MergeRequestsController prepend ::EE::Projects::MergeRequestsController
skip_before_action :merge_request, only: [:index, :bulk_update] skip_before_action :merge_request, only: [:index, :bulk_update]
before_action :whitelist_query_limiting_ee, only: [:merge, :show]
before_action :whitelist_query_limiting, only: [:assign_related_issues, :update]
before_action :authorize_update_issuable!, only: [:close, :edit, :update, :remove_wip, :sort] before_action :authorize_update_issuable!, only: [:close, :edit, :update, :remove_wip, :sort]
before_action :set_issuables_index, only: [:index] before_action :set_issuables_index, only: [:index]
before_action :authenticate_user!, only: [:assign_related_issues] before_action :authenticate_user!, only: [:assign_related_issues]
...@@ -348,4 +350,14 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -348,4 +350,14 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
access_denied! unless access_check access_denied! unless access_check
end end
def whitelist_query_limiting
# Also see https://gitlab.com/gitlab-org/gitlab-ce/issues/42441
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42438')
end
def whitelist_query_limiting_ee
# Also see https://gitlab.com/gitlab-org/gitlab-ee/issues/4793
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ee/issues/4792')
end
end end
...@@ -2,6 +2,7 @@ class Projects::NetworkController < Projects::ApplicationController ...@@ -2,6 +2,7 @@ class Projects::NetworkController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
include ApplicationHelper include ApplicationHelper
before_action :whitelist_query_limiting
before_action :require_non_empty_project before_action :require_non_empty_project
before_action :assign_ref_vars before_action :assign_ref_vars
before_action :authorize_download_code! before_action :authorize_download_code!
...@@ -35,4 +36,8 @@ class Projects::NetworkController < Projects::ApplicationController ...@@ -35,4 +36,8 @@ class Projects::NetworkController < Projects::ApplicationController
@options[:extended_sha1] = params[:extended_sha1] @options[:extended_sha1] = params[:extended_sha1]
@commit = @repo.commit(@options[:extended_sha1]) @commit = @repo.commit(@options[:extended_sha1])
end end
def whitelist_query_limiting
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42333')
end
end end
...@@ -2,6 +2,7 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -2,6 +2,7 @@ class Projects::NotesController < Projects::ApplicationController
include NotesActions include NotesActions
include ToggleAwardEmoji include ToggleAwardEmoji
before_action :whitelist_query_limiting, only: [:create]
before_action :authorize_read_note! before_action :authorize_read_note!
before_action :authorize_create_note!, only: [:create] before_action :authorize_create_note!, only: [:create]
before_action :authorize_resolve_note!, only: [:resolve, :unresolve] before_action :authorize_resolve_note!, only: [:resolve, :unresolve]
...@@ -79,4 +80,8 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -79,4 +80,8 @@ class Projects::NotesController < Projects::ApplicationController
access_denied! unless can?(current_user, :create_note, noteable) access_denied! unless can?(current_user, :create_note, noteable)
end end
def whitelist_query_limiting
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42383')
end
end end
class Projects::PipelinesController < Projects::ApplicationController class Projects::PipelinesController < Projects::ApplicationController
before_action :whitelist_query_limiting, only: [:create, :retry]
before_action :pipeline, except: [:index, :new, :create, :charts] before_action :pipeline, except: [:index, :new, :create, :charts]
before_action :commit, only: [:show, :builds, :failures] before_action :commit, only: [:show, :builds, :failures]
before_action :authorize_read_pipeline! before_action :authorize_read_pipeline!
...@@ -166,4 +167,9 @@ class Projects::PipelinesController < Projects::ApplicationController ...@@ -166,4 +167,9 @@ class Projects::PipelinesController < Projects::ApplicationController
def commit def commit
@commit ||= @pipeline.commit @commit ||= @pipeline.commit
end end
def whitelist_query_limiting
# Also see https://gitlab.com/gitlab-org/gitlab-ce/issues/42343
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42339')
end
end end
...@@ -4,6 +4,7 @@ class ProjectsController < Projects::ApplicationController ...@@ -4,6 +4,7 @@ class ProjectsController < Projects::ApplicationController
include PreviewMarkdown include PreviewMarkdown
prepend EE::ProjectsController prepend EE::ProjectsController
before_action :whitelist_query_limiting, only: [:create]
before_action :authenticate_user!, except: [:index, :show, :activity, :refs] before_action :authenticate_user!, except: [:index, :show, :activity, :refs]
before_action :redirect_git_extension, only: [:show] before_action :redirect_git_extension, only: [:show]
before_action :project, except: [:index, :new, :create] before_action :project, except: [:index, :new, :create]
...@@ -415,4 +416,8 @@ class ProjectsController < Projects::ApplicationController ...@@ -415,4 +416,8 @@ class ProjectsController < Projects::ApplicationController
# #
redirect_to request.original_url.sub(%r{\.git/?\Z}, '') if params[:format] == 'git' redirect_to request.original_url.sub(%r{\.git/?\Z}, '') if params[:format] == 'git'
end end
def whitelist_query_limiting
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42440')
end
end end
...@@ -2,6 +2,8 @@ class RegistrationsController < Devise::RegistrationsController ...@@ -2,6 +2,8 @@ class RegistrationsController < Devise::RegistrationsController
include Recaptcha::Verify include Recaptcha::Verify
prepend EE::RegistrationsController prepend EE::RegistrationsController
before_action :whitelist_query_limiting, only: [:destroy]
def new def new
redirect_to(new_user_session_path) redirect_to(new_user_session_path)
end end
...@@ -84,4 +86,8 @@ class RegistrationsController < Devise::RegistrationsController ...@@ -84,4 +86,8 @@ class RegistrationsController < Devise::RegistrationsController
def devise_mapping def devise_mapping
@devise_mapping ||= Devise.mappings[:user] @devise_mapping ||= Devise.mappings[:user]
end end
def whitelist_query_limiting
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42380')
end
end end
...@@ -5,6 +5,24 @@ module WebpackHelper ...@@ -5,6 +5,24 @@ module WebpackHelper
javascript_include_tag(*gitlab_webpack_asset_paths(bundle, force_same_domain: force_same_domain)) javascript_include_tag(*gitlab_webpack_asset_paths(bundle, force_same_domain: force_same_domain))
end end
def webpack_controller_bundle_tags
bundles = []
segments = [*controller.controller_path.split('/'), controller.action_name].compact
until segments.empty?
begin
asset_paths = gitlab_webpack_asset_paths("pages.#{segments.join('.')}", extension: 'js')
bundles.unshift(*asset_paths)
rescue Webpack::Rails::Manifest::EntryPointMissingError
# no bundle exists for this path
end
segments.pop
end
javascript_include_tag(*bundles)
end
# override webpack-rails gem helper until changes can make it upstream # override webpack-rails gem helper until changes can make it upstream
def gitlab_webpack_asset_paths(source, extension: nil, force_same_domain: false) def gitlab_webpack_asset_paths(source, extension: nil, force_same_domain: false)
return "" unless source.present? return "" unless source.present?
......
...@@ -87,20 +87,10 @@ module Storage ...@@ -87,20 +87,10 @@ module Storage
remove_exports! remove_exports!
end end
def remove_exports! def remove_legacy_exports!
Gitlab::Popen.popen(%W(find #{export_path} -not -path #{export_path} -delete)) legacy_export_path = File.join(Gitlab::ImportExport.storage_path, full_path_was)
end
def export_path
File.join(Gitlab::ImportExport.storage_path, full_path_was)
end
def full_path_was FileUtils.rm_rf(legacy_export_path)
if parent
parent.full_path + '/' + path_was
else
path_was
end
end end
end end
end end
...@@ -312,12 +312,6 @@ class Group < Namespace ...@@ -312,12 +312,6 @@ class Group < Namespace
list_of_ids.reverse.map { |group| variables[group.id] }.compact.flatten list_of_ids.reverse.map { |group| variables[group.id] }.compact.flatten
end end
def full_path_was
return path_was unless has_parent?
"#{parent.full_path}/#{path_was}"
end
def group_member(user) def group_member(user)
if group_members.loaded? if group_members.loaded?
group_members.find { |gm| gm.user_id == user.id } group_members.find { |gm| gm.user_id == user.id }
......
...@@ -235,6 +235,24 @@ class Namespace < ActiveRecord::Base ...@@ -235,6 +235,24 @@ class Namespace < ActiveRecord::Base
feature_available?(:multiple_issue_boards) feature_available?(:multiple_issue_boards)
end end
def full_path_was
return path_was unless has_parent?
"#{parent.full_path}/#{path_was}"
end
# Exports belonging to projects with legacy storage are placed in a common
# subdirectory of the namespace, so a simple `rm -rf` is sufficient to remove
# them.
#
# Exports of projects using hashed storage are placed in a location defined
# only by the project ID, so each must be removed individually.
def remove_exports!
remove_legacy_exports!
all_projects.with_storage_feature(:repository).find_each(&:remove_exports)
end
private private
def refresh_access_of_projects_invited_groups def refresh_access_of_projects_invited_groups
......
...@@ -73,6 +73,7 @@ class Project < ActiveRecord::Base ...@@ -73,6 +73,7 @@ class Project < ActiveRecord::Base
before_destroy :remove_private_deploy_keys before_destroy :remove_private_deploy_keys
after_destroy -> { run_after_commit { remove_pages } } after_destroy -> { run_after_commit { remove_pages } }
after_destroy :remove_exports
after_validation :check_pending_delete after_validation :check_pending_delete
...@@ -1537,6 +1538,8 @@ class Project < ActiveRecord::Base ...@@ -1537,6 +1538,8 @@ class Project < ActiveRecord::Base
end end
def export_path def export_path
return nil unless namespace.present? || hashed_storage?(:repository)
File.join(Gitlab::ImportExport.storage_path, disk_path) File.join(Gitlab::ImportExport.storage_path, disk_path)
end end
...@@ -1545,8 +1548,9 @@ class Project < ActiveRecord::Base ...@@ -1545,8 +1548,9 @@ class Project < ActiveRecord::Base
end end
def remove_exports def remove_exports
_, status = Gitlab::Popen.popen(%W(find #{export_path} -not -path #{export_path} -delete)) return nil unless export_path.present?
status.zero?
FileUtils.rm_rf(export_path)
end end
def full_path_slug def full_path_slug
......
...@@ -9,6 +9,8 @@ class Upload < ActiveRecord::Base ...@@ -9,6 +9,8 @@ class Upload < ActiveRecord::Base
validates :model, presence: true validates :model, presence: true
validates :uploader, presence: true validates :uploader, presence: true
scope :with_files_stored_locally, -> { where(store: [nil, ObjectStorage::Store::LOCAL]) }
before_save :calculate_checksum!, if: :foreground_checksummable? before_save :calculate_checksum!, if: :foreground_checksummable?
after_commit :schedule_checksum, if: :checksummable? after_commit :schedule_checksum, if: :checksummable?
......
...@@ -91,6 +91,10 @@ module MergeRequests ...@@ -91,6 +91,10 @@ module MergeRequests
merge_request.mark_as_unchecked merge_request.mark_as_unchecked
UpdateHeadPipelineForMergeRequestWorker.perform_async(merge_request.id) UpdateHeadPipelineForMergeRequestWorker.perform_async(merge_request.id)
end end
# Upcoming method calls need the refreshed version of
# @source_merge_requests diffs (for MergeRequest#commit_shas for instance).
merge_requests_for_source_branch(reload: true)
end end
# Note: Closed merge requests also need approvals reset. # Note: Closed merge requests also need approvals reset.
...@@ -212,7 +216,8 @@ module MergeRequests ...@@ -212,7 +216,8 @@ module MergeRequests
merge_requests.uniq.select(&:source_project) merge_requests.uniq.select(&:source_project)
end end
def merge_requests_for_source_branch def merge_requests_for_source_branch(reload: false)
@source_merge_requests = nil if reload
@source_merge_requests ||= merge_requests_for(@branch_name) @source_merge_requests ||= merge_requests_for(@branch_name)
end end
......
class AttachmentUploader < GitlabUploader class AttachmentUploader < GitlabUploader
include UploaderHelper
include RecordsUploads::Concern include RecordsUploads::Concern
include ObjectStorage::Concern include ObjectStorage::Concern
prepend ObjectStorage::Extension::RecordsUploads prepend ObjectStorage::Extension::RecordsUploads
include UploaderHelper
private private
......
...@@ -8,11 +8,11 @@ class AvatarUploader < GitlabUploader ...@@ -8,11 +8,11 @@ class AvatarUploader < GitlabUploader
model.avatar.file && model.avatar.file.present? model.avatar.file && model.avatar.file.present?
end end
def move_to_store def move_to_cache
false false
end end
def move_to_cache def move_to_store
false false
end end
......
...@@ -15,8 +15,6 @@ class FileUploader < GitlabUploader ...@@ -15,8 +15,6 @@ class FileUploader < GitlabUploader
MARKDOWN_PATTERN = %r{\!?\[.*?\]\(/uploads/(?<secret>[0-9a-f]{32})/(?<file>.*?)\)} MARKDOWN_PATTERN = %r{\!?\[.*?\]\(/uploads/(?<secret>[0-9a-f]{32})/(?<file>.*?)\)}
DYNAMIC_PATH_PATTERN = %r{(?<secret>\h{32})/(?<identifier>.*)} DYNAMIC_PATH_PATTERN = %r{(?<secret>\h{32})/(?<identifier>.*)}
attr_accessor :model
def self.root def self.root
File.join(options.storage_path, 'uploads') File.join(options.storage_path, 'uploads')
end end
...@@ -62,6 +60,8 @@ class FileUploader < GitlabUploader ...@@ -62,6 +60,8 @@ class FileUploader < GitlabUploader
SecureRandom.hex SecureRandom.hex
end end
attr_accessor :model
def initialize(model, secret = nil) def initialize(model, secret = nil)
@model = model @model = model
@secret = secret @secret = secret
......
...@@ -45,6 +45,10 @@ class GitlabUploader < CarrierWave::Uploader::Base ...@@ -45,6 +45,10 @@ class GitlabUploader < CarrierWave::Uploader::Base
file.present? file.present?
end end
def store_dir
File.join(base_dir, dynamic_segment)
end
def cache_dir def cache_dir
File.join(root, base_dir, 'tmp/cache') File.join(root, base_dir, 'tmp/cache')
end end
...@@ -62,10 +66,6 @@ class GitlabUploader < CarrierWave::Uploader::Base ...@@ -62,10 +66,6 @@ class GitlabUploader < CarrierWave::Uploader::Base
# Designed to be overridden by child uploaders that have a dynamic path # Designed to be overridden by child uploaders that have a dynamic path
# segment -- that is, a path that changes based on mutable attributes of its # segment -- that is, a path that changes based on mutable attributes of its
# associated model # associated model
#
# For example, `FileUploader` builds the storage path based on the associated
# project model's `path_with_namespace` value, which can change when the
# project or its containing namespace is moved or renamed.
def dynamic_segment def dynamic_segment
raise(NotImplementedError) raise(NotImplementedError)
end end
......
- form_field = local_assigns.fetch(:form_field, nil)
- variable = local_assigns.fetch(:variable, nil)
- only_key_value = local_assigns.fetch(:only_key_value, false)
- id = variable&.id
- key = variable&.key
- value = variable&.value
- is_protected = variable && !only_key_value ? variable.protected : true
- id_input_name = "#{form_field}[variables_attributes][][id]"
- destroy_input_name = "#{form_field}[variables_attributes][][_destroy]"
- key_input_name = "#{form_field}[variables_attributes][][key]"
- value_input_name = "#{form_field}[variables_attributes][][value]"
- protected_input_name = "#{form_field}[variables_attributes][][protected]"
%li.js-row.ci-variable-row{ data: { is_persisted: "#{!id.nil?}" } }
.ci-variable-row-body
%input.js-ci-variable-input-id{ type: "hidden", name: id_input_name, value: id }
%input.js-ci-variable-input-destroy{ type: "hidden", name: destroy_input_name }
%input.js-ci-variable-input-key.ci-variable-body-item.form-control{ type: "text",
name: key_input_name,
value: key,
placeholder: s_('CiVariables|Input variable key') }
.ci-variable-body-item
.form-control.js-secret-value-placeholder{ class: ('hide' unless id) }
= '*' * 20
%textarea.js-ci-variable-input-value.js-secret-value.form-control{ class: ('hide' if id),
rows: 1,
name: value_input_name,
placeholder: s_('CiVariables|Input variable value') }
= value
- unless only_key_value
.ci-variable-body-item.ci-variable-protected-item
.append-right-default
= s_("CiVariable|Protected")
%button{ type: 'button',
class: "js-project-feature-toggle project-feature-toggle #{'is-checked' if is_protected}",
"aria-label": s_("CiVariable|Toggle protected") }
%input{ type: "hidden",
class: 'js-ci-variable-input-protected js-project-feature-toggle-input',
name: protected_input_name,
value: is_protected }
%span.toggle-icon
= sprite_icon('status_success_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-checked')
= sprite_icon('status_failed_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-unchecked')
-# EE-specific start
= render 'ci/variables/environment_scope', form_field: form_field, variable: variable
-# EE-specific end
%button.js-row-remove-button.ci-variable-row-remove-button{ type: 'button', 'aria-label': s_('CiVariables|Remove variable row') }
= icon('minus-circle')
.top-area .top-area
%ul.nav-links %ul.nav-links.mobile-separator
= nav_link(page: dashboard_groups_path) do = nav_link(page: dashboard_groups_path) do
= link_to dashboard_groups_path, title: _("Your groups") do = link_to dashboard_groups_path, title: _("Your groups") do
Your groups Your groups
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
.top-area.scrolling-tabs-container.inner-page-scroll-tabs .top-area.scrolling-tabs-container.inner-page-scroll-tabs
.fade-left= icon('angle-left') .fade-left= icon('angle-left')
.fade-right= icon('angle-right') .fade-right= icon('angle-right')
%ul.nav-links.scrolling-tabs %ul.nav-links.scrolling-tabs.mobile-separator
= nav_link(page: [dashboard_projects_path, root_path]) do = nav_link(page: [dashboard_projects_path, root_path]) do
= link_to dashboard_projects_path, class: 'shortcuts-activity', data: {placement: 'right'} do = link_to dashboard_projects_path, class: 'shortcuts-activity', data: {placement: 'right'} do
Your projects Your projects
......
.nav-block .nav-block
%ul.nav-links %ul.nav-links.mobile-separator
= nav_link(html_options: { class: ("active" unless params[:personal].present?) }) do = nav_link(html_options: { class: ("active" unless params[:personal].present?) }) do
= link_to s_('DashboardProjects|All'), dashboard_projects_path = link_to s_('DashboardProjects|All'), dashboard_projects_path
= nav_link(html_options: { class: ("active" if params[:personal].present?) }) do = nav_link(html_options: { class: ("active" if params[:personal].present?) }) do
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
- if current_user.todos.any? - if current_user.todos.any?
.top-area .top-area
%ul.nav-links %ul.nav-links.mobile-separator
%li.todos-pending{ class: active_when(params[:state].blank? || params[:state] == 'pending') }> %li.todos-pending{ class: active_when(params[:state].blank? || params[:state] == 'pending') }>
= link_to todos_filter_path(state: 'pending') do = link_to todos_filter_path(state: 'pending') do
%span %span
......
...@@ -47,6 +47,8 @@ ...@@ -47,6 +47,8 @@
- if content_for?(:page_specific_javascripts) - if content_for?(:page_specific_javascripts)
= yield :page_specific_javascripts = yield :page_specific_javascripts
= webpack_controller_bundle_tags
= yield :project_javascripts = yield :project_javascripts
= csrf_meta_tags = csrf_meta_tags
......
...@@ -22,14 +22,20 @@ ...@@ -22,14 +22,20 @@
= f.label :ref, _('Target Branch'), class: 'label-light' = f.label :ref, _('Target Branch'), class: 'label-light'
= dropdown_tag(_("Select target branch"), options: { toggle_class: 'btn js-target-branch-dropdown', dropdown_class: 'git-revision-dropdown', title: _("Select target branch"), filter: true, placeholder: s_("OfSearchInADropdown|Filter"), data: { data: @project.repository.branch_names, default_branch: @project.default_branch } } ) = dropdown_tag(_("Select target branch"), options: { toggle_class: 'btn js-target-branch-dropdown', dropdown_class: 'git-revision-dropdown', title: _("Select target branch"), filter: true, placeholder: s_("OfSearchInADropdown|Filter"), data: { data: @project.repository.branch_names, default_branch: @project.default_branch } } )
= f.text_field :ref, value: @schedule.ref, id: 'schedule_ref', class: 'hidden', name: 'schedule[ref]', required: true = f.text_field :ref, value: @schedule.ref, id: 'schedule_ref', class: 'hidden', name: 'schedule[ref]', required: true
.form-group .form-group.js-ci-variable-list-section
.col-md-9 .col-md-9
%label.label-light %label.label-light
#{ s_('PipelineSchedules|Variables') } #{ s_('PipelineSchedules|Variables') }
%ul.js-pipeline-variable-list.pipeline-variable-list %ul.ci-variable-list
- @schedule.variables.each do |variable| - @schedule.variables.each do |variable|
= render 'variable_row', id: variable.id, key: variable.key, value: variable.value = render 'ci/variables/variable_row', form_field: 'schedule', variable: variable, only_key_value: true
= render 'variable_row' = render 'ci/variables/variable_row', form_field: 'schedule', only_key_value: true
- if @schedule.variables.size > 0
%button.btn.btn-info.btn-inverted.prepend-top-10.js-secret-value-reveal-button{ type: 'button', data: { secret_reveal_status: "#{@schedule.variables.size == 0}" } }
- if @schedule.variables.size == 0
= n_('Hide value', 'Hide values', @schedule.variables.size)
- else
= n_('Reveal value', 'Reveal values', @schedule.variables.size)
.form-group .form-group
.col-md-9 .col-md-9
= f.label :active, s_('PipelineSchedules|Activated'), class: 'label-light' = f.label :active, s_('PipelineSchedules|Activated'), class: 'label-light'
......
%ul.nav-links %ul.nav-links.mobile-separator
%li{ class: active_when(scope.nil?) }> %li{ class: active_when(scope.nil?) }>
= link_to schedule_path_proc.call(nil) do = link_to schedule_path_proc.call(nil) do
= s_("PipelineSchedules|All") = s_("PipelineSchedules|All")
......
- id = local_assigns.fetch(:id, nil)
- key = local_assigns.fetch(:key, "")
- value = local_assigns.fetch(:value, "")
%li.js-row.pipeline-variable-row{ data: { is_persisted: "#{!id.nil?}" } }
.pipeline-variable-row-body
%input{ type: "hidden", name: "schedule[variables_attributes][][id]", value: id }
%input.js-destroy-input{ type: "hidden", name: "schedule[variables_attributes][][_destroy]" }
%input.js-user-input.pipeline-variable-key-input.form-control{ type: "text",
name: "schedule[variables_attributes][][key]",
value: key,
placeholder: s_('PipelineSchedules|Input variable key') }
%textarea.js-user-input.pipeline-variable-value-input.form-control{ rows: 1,
name: "schedule[variables_attributes][][value]",
placeholder: s_('PipelineSchedules|Input variable value') }
= value
%button.js-row-remove-button.pipeline-variable-row-remove-button{ 'aria-label': s_('PipelineSchedules|Remove variable row') }
%i.fa.fa-minus-circle{ 'aria-hidden': "true" }
- failed_builds = @pipeline.statuses.latest.failed - failed_builds = @pipeline.statuses.latest.failed
.tabs-holder .tabs-holder
%ul.pipelines-tabs.nav-links.no-top.no-bottom %ul.pipelines-tabs.nav-links.no-top.no-bottom.mobile-separator
%li.js-pipeline-tab-link %li.js-pipeline-tab-link
= link_to project_pipeline_path(@project, @pipeline), data: { target: 'div#js-tab-pipeline', action: 'pipelines', toggle: 'tab' }, class: 'pipeline-tab' do = link_to project_pipeline_path(@project, @pipeline), data: { target: 'div#js-tab-pipeline', action: 'pipelines', toggle: 'tab' }, class: 'pipeline-tab' do
Pipeline Pipeline
......
%ul.nav-links.event-filter.scrolling-tabs .scrolling-tabs-container.inner-page-scroll-tabs.is-smaller
.fade-left= icon('angle-left')
.fade-right= icon('angle-right')
%ul.nav-links.event-filter.scrolling-tabs
= event_filter_link EventFilter.all, _('All'), s_('EventFilterBy|Filter by all') = event_filter_link EventFilter.all, _('All'), s_('EventFilterBy|Filter by all')
- if event_filter_visible(:repository) - if event_filter_visible(:repository)
= event_filter_link EventFilter.push, _('Push events'), s_('EventFilterBy|Filter by push events') = event_filter_link EventFilter.push, _('Push events'), s_('EventFilterBy|Filter by push events')
......
%ul.nav-links %ul.nav-links.mobile-separator
%li{ class: milestone_class_for_state(params[:state], 'opened', true) }> %li{ class: milestone_class_for_state(params[:state], 'opened', true) }>
= link_to milestones_filter_path(state: 'opened') do = link_to milestones_filter_path(state: 'opened') do
Open Open
......
%ul.nav-links %ul.nav-links.mobile-separator
%li{ class: active_when(scope.nil?) }> %li{ class: active_when(scope.nil?) }>
= link_to build_path_proc.call(nil) do = link_to build_path_proc.call(nil) do
All All
......
- type = local_assigns.fetch(:type, :issues) - type = local_assigns.fetch(:type, :issues)
- page_context_word = type.to_s.humanize(capitalize: false) - page_context_word = type.to_s.humanize(capitalize: false)
%ul.nav-links.issues-state-filters %ul.nav-links.issues-state-filters.mobile-separator
%li{ class: active_when(params[:state] == 'opened') }> %li{ class: active_when(params[:state] == 'opened') }>
= link_to page_filter_path(state: 'opened', label: true), id: 'state-opened', title: "Filter by #{page_context_word} that are currently opened.", data: { state: 'opened' } do = link_to page_filter_path(state: 'opened', label: true), id: 'state-opened', title: "Filter by #{page_context_word} that are currently opened.", data: { state: 'opened' } do
#{issuables_state_counter_text(type, :opened)} #{issuables_state_counter_text(type, :opened)}
......
- subject = local_assigns.fetch(:subject, current_user) - subject = local_assigns.fetch(:subject, current_user)
- include_private = local_assigns.fetch(:include_private, false) - include_private = local_assigns.fetch(:include_private, false)
.nav-links.snippet-scope-menu .nav-links.snippet-scope-menu.mobile-separator
%li{ class: active_when(params[:scope].nil?) } %li{ class: active_when(params[:scope].nil?) }
= link_to subject_snippets_path(subject) do = link_to subject_snippets_path(subject) do
All All
......
---
title: "[Geo] Skip attachments that is stored in the object storage"
merge_request:
author:
type: fixed
---
title: FIx N+1 queries with /api/v4/groups endpoint
merge_request:
author:
type: performance
---
title: Add unique constraint to trending_projects#project_id.
merge_request: 16846
author:
type: other
---
title: Fix export removal for hashed-storage projects within a renamed or deleted
namespace
merge_request: 16658
author:
type: fixed
---
title: Fix GitLab import leaving group_id on ProjectLabel
merge_request: 16877
author:
type: fixed
---
title: Change button group width on mobile
merge_request: 16726
author: George Tsiolis
type: fixed
---
title: Reload MRs memoization after diffs creation
merge_request:
author:
type: fixed
---
title: Track and act upon the number of executed queries
merge_request:
author:
type: added
---
title: Hide variable values on pipeline schedule edit page
merge_request: 16729
author:
type: changed
...@@ -346,7 +346,6 @@ Settings.artifacts['storage_path'] = Settings.absolute(Settings.artifacts.values ...@@ -346,7 +346,6 @@ Settings.artifacts['storage_path'] = Settings.absolute(Settings.artifacts.values
# Settings.artifact['path'] is deprecated, use `storage_path` instead # Settings.artifact['path'] is deprecated, use `storage_path` instead
Settings.artifacts['path'] = Settings.artifacts['storage_path'] Settings.artifacts['path'] = Settings.artifacts['storage_path']
Settings.artifacts['max_size'] ||= 100 # in megabytes Settings.artifacts['max_size'] ||= 100 # in megabytes
Settings.artifacts['object_store'] ||= Settingslogic.new({}) Settings.artifacts['object_store'] ||= Settingslogic.new({})
Settings.artifacts['object_store']['enabled'] ||= false Settings.artifacts['object_store']['enabled'] ||= false
Settings.artifacts['object_store']['remote_directory'] ||= nil Settings.artifacts['object_store']['remote_directory'] ||= nil
...@@ -413,6 +412,13 @@ Settings.uploads['object_store']['background_upload'] ||= true ...@@ -413,6 +412,13 @@ Settings.uploads['object_store']['background_upload'] ||= true
# Convert upload connection settings to use string keys, to make Fog happy # Convert upload connection settings to use string keys, to make Fog happy
Settings.uploads['object_store']['connection']&.deep_stringify_keys! Settings.uploads['object_store']['connection']&.deep_stringify_keys!
#
# Uploads
#
Settings['uploads'] ||= Settingslogic.new({})
Settings.uploads['storage_path'] = Settings.absolute(Settings.uploads['storage_path'] || 'public')
Settings.uploads['base_dir'] = Settings.uploads['base_dir'] || 'uploads/-/system'
# #
# Mattermost # Mattermost
# #
......
if Gitlab::QueryLimiting.enable?
require_dependency 'gitlab/query_limiting/active_support_subscriber'
require_dependency 'gitlab/query_limiting/transaction'
require_dependency 'gitlab/query_limiting/middleware'
Gitlab::Application.configure do |config|
config.middleware.use(Gitlab::QueryLimiting::Middleware)
end
end
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
var crypto = require('crypto'); var crypto = require('crypto');
var fs = require('fs'); var fs = require('fs');
var path = require('path'); var path = require('path');
var glob = require('glob');
var webpack = require('webpack'); var webpack = require('webpack');
var StatsWriterPlugin = require('webpack-stats-plugin').StatsWriterPlugin; var StatsWriterPlugin = require('webpack-stats-plugin').StatsWriterPlugin;
var CopyWebpackPlugin = require('copy-webpack-plugin'); var CopyWebpackPlugin = require('copy-webpack-plugin');
...@@ -20,6 +21,26 @@ var DEV_SERVER_LIVERELOAD = process.env.DEV_SERVER_LIVERELOAD !== 'false'; ...@@ -20,6 +21,26 @@ var DEV_SERVER_LIVERELOAD = process.env.DEV_SERVER_LIVERELOAD !== 'false';
var WEBPACK_REPORT = process.env.WEBPACK_REPORT; var WEBPACK_REPORT = process.env.WEBPACK_REPORT;
var NO_COMPRESSION = process.env.NO_COMPRESSION; var NO_COMPRESSION = process.env.NO_COMPRESSION;
// generate automatic entry points
var autoEntries = {};
var pageEntries = glob.sync('pages/**/index.js', { cwd: path.join(ROOT_PATH, 'app/assets/javascripts') });
// filter out entries currently imported dynamically in dispatcher.js
var dispatcher = fs.readFileSync(path.join(ROOT_PATH, 'app/assets/javascripts/dispatcher.js')).toString();
var dispatcherChunks = dispatcher.match(/(?!import\('.\/)pages\/[^']+/g);
pageEntries.forEach(( path ) => {
let chunkPath = path.replace(/\/index\.js$/, '');
if (!dispatcherChunks.includes(chunkPath)) {
let chunkName = chunkPath.replace(/\//g, '.');
autoEntries[chunkName] = './' + path;
}
});
// report our auto-generated bundle count
var autoEntriesCount = Object.keys(autoEntries).length;
console.log(`${autoEntriesCount} entries from '/pages' automatically added to webpack output.`);
var config = { var config = {
// because sqljs requires fs. // because sqljs requires fs.
node: { node: {
...@@ -323,6 +344,8 @@ var config = { ...@@ -323,6 +344,8 @@ var config = {
} }
} }
config.entry = Object.assign({}, autoEntries, config.entry);
if (IS_PRODUCTION) { if (IS_PRODUCTION) {
config.devtool = 'source-map'; config.devtool = 'source-map';
config.plugins.push( config.plugins.push(
......
class AddUniqueConstraintToTrendingProjectsProjectId < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_index :trending_projects, :project_id, unique: true, name: 'index_trending_projects_on_project_id_unique'
remove_concurrent_index_by_name :trending_projects, 'index_trending_projects_on_project_id'
rename_index :trending_projects, 'index_trending_projects_on_project_id_unique', 'index_trending_projects_on_project_id'
end
def down
rename_index :trending_projects, 'index_trending_projects_on_project_id', 'index_trending_projects_on_project_id_old'
add_concurrent_index :trending_projects, :project_id
remove_concurrent_index_by_name :trending_projects, 'index_trending_projects_on_project_id_old'
end
end
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class RemoveProjectLabelsGroupId < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
update_column_in_batches(:labels, :group_id, nil) do |table, query|
query.where(table[:type].eq('ProjectLabel').and(table[:group_id].not_eq(nil)))
end
end
def down
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20180201145907) do ActiveRecord::Schema.define(version: 20180202111106) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -1214,7 +1214,7 @@ ActiveRecord::Schema.define(version: 20180201145907) do ...@@ -1214,7 +1214,7 @@ ActiveRecord::Schema.define(version: 20180201145907) do
t.datetime "last_edited_at" t.datetime "last_edited_at"
t.integer "last_edited_by_id" t.integer "last_edited_by_id"
t.boolean "discussion_locked" t.boolean "discussion_locked"
t.datetime "closed_at" t.datetime_with_timezone "closed_at"
end end
add_index "issues", ["author_id"], name: "index_issues_on_author_id", using: :btree add_index "issues", ["author_id"], name: "index_issues_on_author_id", using: :btree
...@@ -2228,7 +2228,7 @@ ActiveRecord::Schema.define(version: 20180201145907) do ...@@ -2228,7 +2228,7 @@ ActiveRecord::Schema.define(version: 20180201145907) do
t.integer "project_id", null: false t.integer "project_id", null: false
end end
add_index "trending_projects", ["project_id"], name: "index_trending_projects_on_project_id", using: :btree add_index "trending_projects", ["project_id"], name: "index_trending_projects_on_project_id", unique: true, using: :btree
create_table "u2f_registrations", force: :cascade do |t| create_table "u2f_registrations", force: :cascade do |t|
t.text "certificate" t.text "certificate"
......
...@@ -75,6 +75,7 @@ comments: false ...@@ -75,6 +75,7 @@ comments: false
- [Ordering table columns](ordering_table_columns.md) - [Ordering table columns](ordering_table_columns.md)
- [Verifying database capabilities](verifying_database_capabilities.md) - [Verifying database capabilities](verifying_database_capabilities.md)
- [Database Debugging and Troubleshooting](database_debugging.md) - [Database Debugging and Troubleshooting](database_debugging.md)
- [Query Count Limits](query_count_limits.md)
## Testing guides ## Testing guides
......
# Query Count Limits
Each controller or API endpoint is allowed to execute up to 100 SQL queries. In
a production environment we'll only log an error in case this threshold is
exceeded, but in a test environment we'll raise an error instead.
## Solving Failing Tests
When a test fails because it executes more than 100 SQL queries there are two
solutions to this problem:
1. Reduce the number of SQL queries that are executed.
2. Whitelist the controller or API endpoint.
You should only resort to whitelisting when an existing controller or endpoint
is to blame as in this case reducing the number of SQL queries can take a lot of
effort. Newly added controllers and endpoints are not allowed to execute more
than 100 SQL queries and no exceptions will be made for this rule. _If_ a large
number of SQL queries is necessary to perform certain work it's best to have
this work performed by Sidekiq instead of doing this directly in a web request.
## Whitelisting
In the event that you _have_ to whitelist a controller you'll first need to
create an issue. This issue should (preferably in the title) mention the
controller or endpoint and include the appropriate labels (`database`,
`performance`, and at least a team specific label such as `Discussion`).
Once the issue has been created you can whitelist the code in question. For
Rails controllers it's best to create a `before_action` hook that runs as early
as possible. The called method in turn should call
`Gitlab::QueryLimiting.whitelist('issue URL here')`. For example:
```ruby
class MyController < ApplicationController
before_action :whitelist_query_limiting, only: [:show]
def index
# ...
end
def show
# ...
end
def whitelist_query_limiting
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/...')
end
end
```
By using a `before_action` you don't have to modify the controller method in
question, reducing the likelihood of merge conflicts.
For Grape API endpoints there unfortunately is not a reliable way of running a
hook before a specific endpoint. This means that you have to add the whitelist
call directly into the endpoint like so:
```ruby
get '/projects/:id/foo' do
Gitlab::QueryLimiting.whitelist('...')
# ...
end
```
module Geo module Geo
class AttachmentRegistryFinder < FileRegistryFinder class AttachmentRegistryFinder < FileRegistryFinder
def attachments def attachments
relation =
if selective_sync? if selective_sync?
Upload.where(group_uploads.or(project_uploads).or(other_uploads)) Upload.where(group_uploads.or(project_uploads).or(other_uploads))
else else
Upload.all Upload.all
end end
relation.with_files_stored_locally
end end
def count_attachments def count_attachments
...@@ -114,6 +117,7 @@ module Geo ...@@ -114,6 +117,7 @@ module Geo
fdw_table = Geo::Fdw::Upload.table_name fdw_table = Geo::Fdw::Upload.table_name
Geo::Fdw::Upload.joins("INNER JOIN file_registry ON file_registry.file_id = #{fdw_table}.id") Geo::Fdw::Upload.joins("INNER JOIN file_registry ON file_registry.file_id = #{fdw_table}.id")
.with_files_stored_locally
.merge(Geo::FileRegistry.attachments) .merge(Geo::FileRegistry.attachments)
end end
...@@ -124,6 +128,7 @@ module Geo ...@@ -124,6 +128,7 @@ module Geo
Geo::Fdw::Upload.joins("LEFT OUTER JOIN file_registry Geo::Fdw::Upload.joins("LEFT OUTER JOIN file_registry
ON file_registry.file_id = #{fdw_table}.id ON file_registry.file_id = #{fdw_table}.id
AND file_registry.file_type IN (#{upload_types})") AND file_registry.file_type IN (#{upload_types})")
.with_files_stored_locally
.where(file_registry: { id: nil }) .where(file_registry: { id: nil })
.where.not(id: except_registry_ids) .where.not(id: except_registry_ids)
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.
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