Commit cfa58db8 authored by Stan Hu's avatar Stan Hu

Merge branch 'master' into sh-add-ldap-qa

parents 9638ecbc 7c8e7a8d
...@@ -6,3 +6,5 @@ app/models/concerns/relative_positioning.rb ...@@ -6,3 +6,5 @@ app/models/concerns/relative_positioning.rb
app/workers/stuck_merge_jobs_worker.rb app/workers/stuck_merge_jobs_worker.rb
lib/gitlab/redis/*.rb lib/gitlab/redis/*.rb
lib/gitlab/gitaly_client/operation_service.rb lib/gitlab/gitaly_client/operation_service.rb
lib/gitlab/background_migration/*
app/models/project_services/kubernetes_service.rb
...@@ -627,6 +627,8 @@ codequality: ...@@ -627,6 +627,8 @@ codequality:
sast: sast:
<<: *except-docs <<: *except-docs
image: registry.gitlab.com/gitlab-org/gl-sast:latest image: registry.gitlab.com/gitlab-org/gl-sast:latest
variables:
CONFIDENCE_LEVEL: 2
before_script: [] before_script: []
script: script:
- /app/bin/run . - /app/bin/run .
......
...@@ -50,6 +50,9 @@ _This notice should stay as the first item in the CONTRIBUTING.md file._ ...@@ -50,6 +50,9 @@ _This notice should stay as the first item in the CONTRIBUTING.md file._
## Contribute to GitLab ## Contribute to GitLab
For a first-time step-by-step guide to the contribution process, see
["Contributing to GitLab"](https://about.gitlab.com/contributing/).
Thank you for your interest in contributing to GitLab. This guide details how Thank you for your interest in contributing to GitLab. This guide details how
to contribute to GitLab in a way that is efficient for everyone. to contribute to GitLab in a way that is efficient for everyone.
......
...@@ -69,6 +69,10 @@ gem 'net-ldap' ...@@ -69,6 +69,10 @@ gem 'net-ldap'
# Git Wiki # Git Wiki
# Required manually in config/initializers/gollum.rb to control load order # Required manually in config/initializers/gollum.rb to control load order
# Before updating this gem, check if
# https://github.com/gollum/gollum-lib/pull/292 has been merged.
# If it has, then remove the monkey patch for update_page, rename_page and raw_data_in_committer
# in config/initializers/gollum.rb
gem 'gollum-lib', '~> 4.2', require: false gem 'gollum-lib', '~> 4.2', require: false
# Before updating this gem, check if # Before updating this gem, check if
...@@ -349,7 +353,7 @@ group :development, :test do ...@@ -349,7 +353,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
......
...@@ -211,7 +211,7 @@ GEM ...@@ -211,7 +211,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)
...@@ -588,7 +588,7 @@ GEM ...@@ -588,7 +588,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)
...@@ -1037,7 +1037,7 @@ DEPENDENCIES ...@@ -1037,7 +1037,7 @@ DEPENDENCIES
faraday (~> 0.12) faraday (~> 0.12)
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)
......
{"iconCount":189,"spriteSize":85900,"icons":["abuse","account","admin","angle-double-left","angle-double-right","angle-down","angle-left","angle-right","angle-up","appearance","applications","approval","arrow-down","arrow-right","assignee","bold","book","bookmark","branch","bullhorn","calendar","cancel","chart","chevron-down","chevron-left","chevron-right","chevron-up","clock","close","code","collapse","comment-dots","comment-next","comment","comments","commit","credit-card","cut","dashboard","disk","doc_code","doc_image","doc_text","double-headed-arrow","download","duplicate","earth","ellipsis_v","emoji_slightly_smiling_face","emoji_smile","emoji_smiley","epic","external-link","eye-slash","eye","file-addition","file-deletion","file-modified","filter","folder-o","folder-open","folder","fork","geo-nodes","git-merge","group","history","home","hook","hourglass","image-comment-dark","image-comment-light","import","issue-block","issue-child","issue-close","issue-duplicate","issue-external","issue-new","issue-open-m","issue-open","issue-parent","issues","italic","key-2","key","label","labels","leave","level-up","license","link","list-bulleted","list-numbered","location-dot","location","lock-open","lock","log","mail","menu","merge-request-close","messages","mobile-issue-close","monitor","more","notifications-off","notifications","overview","pencil-square","pencil","pipeline","play","plus-square-o","plus-square","plus","podcast","preferences","profile","project","push-rules","question-o","question","quote","redo","remove","repeat","retry","scale","screen-full","screen-normal","scroll_down","scroll_up","search","settings","shield","slight-frown","slight-smile","smile","smiley","snippet","spam","spinner","staged","star-o","star","status_canceled_borderless","status_canceled","status_closed","status_created_borderless","status_created","status_failed_borderless","status_failed","status_manual_borderless","status_manual","status_notfound_borderless","status_notfound","status_open","status_pending_borderless","status_pending","status_running_borderless","status_running","status_skipped_borderless","status_skipped","status_success_borderless","status_success_solid","status_success","status_warning_borderless","status_warning","stop","task-done","template","terminal","thumb-down","thumb-up","thumbtack","timer","todo-add","todo-done","token","unapproval","unassignee","unlink","unstaged","user","users","volume-up","warning","work"]} {"iconCount":191,"spriteSize":86607,"icons":["abuse","account","admin","angle-double-left","angle-double-right","angle-down","angle-left","angle-right","angle-up","appearance","applications","approval","arrow-down","arrow-right","assignee","bold","book","bookmark","branch","bullhorn","calendar","cancel","chart","chevron-down","chevron-left","chevron-right","chevron-up","clock","close","code","collapse","comment-dots","comment-next","comment","comments","commit","credit-card","cut","dashboard","disk","doc_code","doc_image","doc_text","double-headed-arrow","download","duplicate","earth","ellipsis_v","emoji_slightly_smiling_face","emoji_smile","emoji_smiley","epic","external-link","eye-slash","eye","file-addition","file-deletion","file-modified","filter","folder-o","folder-open","folder","fork","geo-nodes","git-merge","group","history","home","hook","hourglass","image-comment-dark","image-comment-light","import","issue-block","issue-child","issue-close","issue-duplicate","issue-external","issue-new","issue-open-m","issue-open","issue-parent","issues","italic","key-2","key","label","labels","leave","level-up","license","link","list-bulleted","list-numbered","location-dot","location","lock-open","lock","log","mail","menu","merge-request-close","messages","mobile-issue-close","monitor","more","notifications-off","notifications","overview","pencil-square","pencil","pipeline","play","plus-square-o","plus-square","plus","podcast","preferences","profile","project","push-rules","question-o","question","quote","redo","remove","repeat","retry","scale","screen-full","screen-normal","scroll_down","scroll_up","search","settings","shield","slight-frown","slight-smile","smile","smiley","snippet","soft-unwrap","soft-wrap","spam","spinner","staged","star-o","star","status_canceled_borderless","status_canceled","status_closed","status_created_borderless","status_created","status_failed_borderless","status_failed","status_manual_borderless","status_manual","status_notfound_borderless","status_notfound","status_open","status_pending_borderless","status_pending","status_running_borderless","status_running","status_skipped_borderless","status_skipped","status_success_borderless","status_success_solid","status_success","status_warning_borderless","status_warning","stop","task-done","template","terminal","thumb-down","thumb-up","thumbtack","timer","todo-add","todo-done","token","unapproval","unassignee","unlink","unstaged","user","users","volume-up","warning","work"]}
\ No newline at end of file \ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
<svg xmlns="http://www.w3.org/2000/svg" width="142" height="104" viewBox="0 0 142 104"><g fill="none" fill-rule="evenodd"><g transform="translate(112 4)"><path fill="#FFF" d="M8 4a4 4 0 0 0-4 4v14a4 4 0 0 0 4 4h14a4 4 0 0 0 4-4V8a4 4 0 0 0-4-4H8z"/><path fill="#FDC4A8" fill-rule="nonzero" d="M8 4a4 4 0 0 0-4 4v14a4 4 0 0 0 4 4h14a4 4 0 0 0 4-4V8a4 4 0 0 0-4-4H8zm0-4h14a8 8 0 0 1 8 8v14a8 8 0 0 1-8 8H8a8 8 0 0 1-8-8V8a8 8 0 0 1 8-8z"/><rect width="10" height="10" x="10" y="10" fill="#FC6D26" rx="5"/></g><g transform="translate(5 74)"><rect width="30" height="30" fill="#FFF" rx="8"/><path fill="#E1DBF1" fill-rule="nonzero" d="M8 4a4 4 0 0 0-4 4v14a4 4 0 0 0 4 4h14a4 4 0 0 0 4-4V8a4 4 0 0 0-4-4H8zm0-4h14a8 8 0 0 1 8 8v14a8 8 0 0 1-8 8H8a8 8 0 0 1-8-8V8a8 8 0 0 1 8-8z"/><rect width="10" height="10" x="10" y="10" fill="#6B4FBB" rx="5"/></g><path fill="#FFF" d="M6 4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2H6z"/><path fill="#FDC4A8" fill-rule="nonzero" d="M6 4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2H6zm0-4h12a6 6 0 0 1 6 6v12a6 6 0 0 1-6 6H6a6 6 0 0 1-6-6V6a6 6 0 0 1 6-6z"/><rect width="8" height="8" x="8" y="8" fill="#FC6D26" rx="4"/><g transform="translate(112 77)"><rect width="24" height="24" fill="#FFF" rx="6"/><path fill="#E1DBF1" fill-rule="nonzero" d="M6 4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2H6zm0-4h12a6 6 0 0 1 6 6v12a6 6 0 0 1-6 6H6a6 6 0 0 1-6-6V6a6 6 0 0 1 6-6z"/><rect width="8" height="8" x="8" y="8" fill="#6B4FBB" rx="4"/></g><g transform="translate(46 29)"><rect width="46" height="46" y="2" fill="#E1DBF1" rx="10"/><rect width="46" height="46" fill="#E1DBF1" rx="10"/><path fill="#C3B8E3" fill-rule="nonzero" d="M10 4a6 6 0 0 0-6 6v26a6 6 0 0 0 6 6h26a6 6 0 0 0 6-6V10a6 6 0 0 0-6-6H10zm0-4h26c5.523 0 10 4.477 10 10v26c0 5.523-4.477 10-10 10H10C4.477 46 0 41.523 0 36V10C0 4.477 4.477 0 10 0z"/><rect width="14" height="14" x="16" y="16" fill="#6B4FBB" rx="2"/></g><path fill="#E1DBF1" fill-rule="nonzero" d="M98.413 35.682a2 2 0 1 1-2.826-2.83l2.122-2.12a2 2 0 1 1 2.827 2.83l-2.123 2.12z"/><path fill="#C3B8E3" d="M104.78 29.32a2 2 0 0 1-2.826-2.829l2.122-2.12a2 2 0 0 1 2.827 2.83l-2.122 2.12z"/><path fill="#C3B8E3" fill-rule="nonzero" d="M42.413 89.682a2 2 0 1 1-2.826-2.83l2.122-2.12a2 2 0 1 1 2.827 2.83l-2.123 2.12z"/><path fill="#E1DBF1" d="M48.78 83.32a2 2 0 1 1-2.826-2.829l2.122-2.12a2 2 0 1 1 2.827 2.83l-2.122 2.12z"/><path fill="#E1DBF1" fill-rule="nonzero" d="M27.713 26.531a2 2 0 1 1 2.574-3.062l2.296 1.93a2 2 0 1 1-2.573 3.062l-2.297-1.93z"/><path fill="#C3B8E3" d="M34.604 32.321a2 2 0 1 1 2.573-3.062l2.297 1.93A2 2 0 0 1 36.9 34.25l-2.297-1.93z"/><path fill="#C3B8E3" fill-rule="nonzero" d="M93.74 74.553a2 2 0 0 1 2.52-3.106l2.33 1.891a2 2 0 1 1-2.521 3.106l-2.33-1.891z"/><path fill="#E1DBF1" d="M100.727 80.225a2 2 0 1 1 2.521-3.105l2.33 1.89a2 2 0 1 1-2.522 3.106l-2.33-1.89z"/></g></svg>
\ No newline at end of file
...@@ -235,7 +235,7 @@ export default class FileTemplateMediator { ...@@ -235,7 +235,7 @@ export default class FileTemplateMediator {
} }
setFilename(name) { setFilename(name) {
this.$filenameInput.val(name); this.$filenameInput.val(name).trigger('change');
} }
getSelected() { getSelected() {
......
import _ from 'underscore';
import {
getSelector,
togglePopover,
inserted,
mouseenter,
mouseleave,
} from './feature_highlight_helper';
export function setupFeatureHighlightPopover(id, debounceTimeout = 300) {
const $selector = $(getSelector(id));
const $parent = $selector.parent();
const $popoverContent = $parent.siblings('.feature-highlight-popover-content');
const hideOnScroll = togglePopover.bind($selector, false);
const debouncedMouseleave = _.debounce(mouseleave, debounceTimeout);
$selector
// Setup popover
.data('content', $popoverContent.prop('outerHTML'))
.popover({
html: true,
// Override the existing template to add custom CSS classes
template: `
<div class="popover feature-highlight-popover" role="tooltip">
<div class="arrow"></div>
<div class="popover-content"></div>
</div>
`,
})
.on('mouseenter', mouseenter)
.on('mouseleave', debouncedMouseleave)
.on('inserted.bs.popover', inserted)
.on('show.bs.popover', () => {
window.addEventListener('scroll', hideOnScroll);
})
.on('hide.bs.popover', () => {
window.removeEventListener('scroll', hideOnScroll);
})
// Display feature highlight
.removeAttr('disabled');
}
export function findHighestPriorityFeature() {
let priorityFeature;
const sortedFeatureEls = [].slice.call(document.querySelectorAll('.js-feature-highlight')).sort((a, b) =>
(a.dataset.highlightPriority || 0) < (b.dataset.highlightPriority || 0));
const [priorityFeatureEl] = sortedFeatureEls;
if (priorityFeatureEl) {
priorityFeature = priorityFeatureEl.dataset.highlight;
}
return priorityFeature;
}
export function highlightFeatures() {
const priorityFeature = findHighestPriorityFeature();
if (priorityFeature) {
setupFeatureHighlightPopover(priorityFeature);
}
return priorityFeature;
}
import axios from '../lib/utils/axios_utils';
import { __ } from '../locale';
import Flash from '../flash';
import LazyLoader from '../lazy_loader';
export const getSelector = highlightId => `.js-feature-highlight[data-highlight=${highlightId}]`;
export function togglePopover(show) {
const isAlreadyShown = this.hasClass('js-popover-show');
if ((show && isAlreadyShown) || (!show && !isAlreadyShown)) {
return false;
}
this.popover(show ? 'show' : 'hide');
this.toggleClass('disable-animation js-popover-show', show);
return true;
}
export function dismiss(highlightId) {
axios.post(this.attr('data-dismiss-endpoint'), {
feature_name: highlightId,
})
.catch(() => Flash(__('An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again.')));
togglePopover.call(this, false);
this.hide();
}
export function mouseleave() {
if (!$('.popover:hover').length > 0) {
const $featureHighlight = $(this);
togglePopover.call($featureHighlight, false);
}
}
export function mouseenter() {
const $featureHighlight = $(this);
const showedPopover = togglePopover.call($featureHighlight, true);
if (showedPopover) {
$('.popover')
.on('mouseleave', mouseleave.bind($featureHighlight));
}
}
export function inserted() {
const popoverId = this.getAttribute('aria-describedby');
const highlightId = this.dataset.highlight;
const $popover = $(this);
const dismissWrapper = dismiss.bind($popover, highlightId);
$(`#${popoverId} .dismiss-feature-highlight`)
.on('click', dismissWrapper);
const lazyImg = $(`#${popoverId} .feature-highlight-illustration`)[0];
if (lazyImg) {
LazyLoader.loadImage(lazyImg);
}
}
import { highlightFeatures } from './feature_highlight';
import bp from '../breakpoints';
export default function domContentLoaded() {
if (bp.getBreakpointSize() === 'lg') {
highlightFeatures();
return true;
}
return false;
}
document.addEventListener('DOMContentLoaded', domContentLoaded);
...@@ -26,6 +26,7 @@ import './gl_dropdown'; ...@@ -26,6 +26,7 @@ import './gl_dropdown';
import initTodoToggle from './header'; import initTodoToggle from './header';
import initImporterStatus from './importer_status'; import initImporterStatus from './importer_status';
import initLayoutNav from './layout_nav'; import initLayoutNav from './layout_nav';
import './feature_highlight/feature_highlight_options';
import LazyLoader from './lazy_loader'; import LazyLoader from './lazy_loader';
import initLogoAnimation from './logo'; import initLogoAnimation from './logo';
import './milestone_select'; import './milestone_select';
......
import _ from 'underscore'; import _ from 'underscore';
import axios from '~/lib/utils/axios_utils';
import flash from '~/flash';
import { __ } from '~/locale';
export default function initBroadcastMessagesForm() { export default function initBroadcastMessagesForm() {
$('input#broadcast_message_color').on('input', function onMessageColorInput() { $('input#broadcast_message_color').on('input', function onMessageColorInput() {
...@@ -18,13 +21,15 @@ export default function initBroadcastMessagesForm() { ...@@ -18,13 +21,15 @@ export default function initBroadcastMessagesForm() {
if (message === '') { if (message === '') {
$('.js-broadcast-message-preview').text('Your message here'); $('.js-broadcast-message-preview').text('Your message here');
} else { } else {
$.ajax({ axios.post(previewPath, {
url: previewPath, broadcast_message: {
type: 'POST', message,
data: {
broadcast_message: { message },
}, },
}); })
.then(({ data }) => {
$('.js-broadcast-message-preview').html(data.message);
})
.catch(() => flash(__('An error occurred while rendering preview broadcast message')));
} }
}, 250)); }, 250));
} }
/* eslint-disable comma-dangle, consistent-return, class-methods-use-this, arrow-parens, no-param-reassign, max-len */ /* eslint-disable comma-dangle, consistent-return, class-methods-use-this, arrow-parens, no-param-reassign, max-len */
import _ from 'underscore'; import _ from 'underscore';
import axios from '~/lib/utils/axios_utils';
import flash from '~/flash';
import { __ } from '~/locale';
const debounceTimeoutDuration = 1000; const debounceTimeoutDuration = 1000;
const invalidInputClass = 'gl-field-error-outline'; const invalidInputClass = 'gl-field-error-outline';
...@@ -77,12 +80,9 @@ export default class UsernameValidator { ...@@ -77,12 +80,9 @@ export default class UsernameValidator {
this.state.pending = true; this.state.pending = true;
this.state.available = false; this.state.available = false;
this.renderState(); this.renderState();
return $.ajax({ axios.get(`${gon.relative_url_root}/users/${username}/exists`)
type: 'GET', .then(({ data }) => this.setAvailabilityState(data.exists))
url: `${gon.relative_url_root}/users/${username}/exists`, .catch(() => flash(__('An error occurred while validating username')));
dataType: 'json',
success: (res) => this.setAvailabilityState(res.exists)
});
} }
} }
......
...@@ -7,6 +7,10 @@ ...@@ -7,6 +7,10 @@
// more than `x` users are referenced. // more than `x` users are referenced.
// //
import axios from '~/lib/utils/axios_utils';
import flash from '~/flash';
import { __ } from '~/locale';
var lastTextareaPreviewed; var lastTextareaPreviewed;
var lastTextareaHeight = null; var lastTextareaHeight = null;
var markdownPreview; var markdownPreview;
...@@ -62,21 +66,17 @@ MarkdownPreview.prototype.fetchMarkdownPreview = function (text, url, success) { ...@@ -62,21 +66,17 @@ MarkdownPreview.prototype.fetchMarkdownPreview = function (text, url, success) {
success(this.ajaxCache.response); success(this.ajaxCache.response);
return; return;
} }
$.ajax({ axios.post(url, {
type: 'POST', text,
url: url, })
data: { .then(({ data }) => {
text: text this.ajaxCache = {
}, text: text,
dataType: 'json', response: data,
success: (function (response) { };
this.ajaxCache = { success(data);
text: text, })
response: response .catch(() => flash(__('An error occurred while fetching markdown preview')));
};
success(response);
}).bind(this)
});
}; };
MarkdownPreview.prototype.hideReferencedUsers = function ($form) { MarkdownPreview.prototype.hideReferencedUsers = function ($form) {
......
import { __ } from './locale';
import axios from './lib/utils/axios_utils';
import flash from './flash';
export default class ProjectLabelSubscription { export default class ProjectLabelSubscription {
constructor(container) { constructor(container) {
this.$container = $(container); this.$container = $(container);
...@@ -17,10 +21,7 @@ export default class ProjectLabelSubscription { ...@@ -17,10 +21,7 @@ export default class ProjectLabelSubscription {
$btn.addClass('disabled'); $btn.addClass('disabled');
$span.toggleClass('hidden'); $span.toggleClass('hidden');
$.ajax({ axios.post(url).then(() => {
type: 'POST',
url,
}).done(() => {
let newStatus; let newStatus;
let newAction; let newAction;
...@@ -45,6 +46,6 @@ export default class ProjectLabelSubscription { ...@@ -45,6 +46,6 @@ export default class ProjectLabelSubscription {
return button; return button;
}); });
}); }).catch(() => flash(__('There was an error subscribing to this label.')));
} }
} }
import axios from '../lib/utils/axios_utils';
import PANEL_STATE from './constants'; import PANEL_STATE from './constants';
import { backOff } from '../lib/utils/common_utils'; import { backOff } from '../lib/utils/common_utils';
...@@ -81,24 +82,20 @@ export default class PrometheusMetrics { ...@@ -81,24 +82,20 @@ export default class PrometheusMetrics {
loadActiveMetrics() { loadActiveMetrics() {
this.showMonitoringMetricsPanelState(PANEL_STATE.LOADING); this.showMonitoringMetricsPanelState(PANEL_STATE.LOADING);
backOff((next, stop) => { backOff((next, stop) => {
$.ajax({ axios.get(this.activeMetricsEndpoint)
url: this.activeMetricsEndpoint, .then(({ data }) => {
dataType: 'json', if (data && data.success) {
global: false, stop(data);
})
.done((res) => {
if (res && res.success) {
stop(res);
} else { } else {
this.backOffRequestCounter = this.backOffRequestCounter += 1; this.backOffRequestCounter = this.backOffRequestCounter += 1;
if (this.backOffRequestCounter < 3) { if (this.backOffRequestCounter < 3) {
next(); next();
} else { } else {
stop(res); stop(data);
} }
} }
}) })
.fail(stop); .catch(stop);
}) })
.then((res) => { .then((res) => {
if (res && res.data && res.data.length) { if (res && res.data && res.data.length) {
......
/* eslint-disable no-new */ import flash from '../flash';
import Flash from '../flash'; import axios from '../lib/utils/axios_utils';
import ProtectedBranchAccessDropdown from './protected_branch_access_dropdown'; import ProtectedBranchAccessDropdown from './protected_branch_access_dropdown';
export default class ProtectedBranchEdit { export default class ProtectedBranchEdit {
...@@ -38,29 +38,25 @@ export default class ProtectedBranchEdit { ...@@ -38,29 +38,25 @@ export default class ProtectedBranchEdit {
this.$allowedToMergeDropdown.disable(); this.$allowedToMergeDropdown.disable();
this.$allowedToPushDropdown.disable(); this.$allowedToPushDropdown.disable();
$.ajax({ axios.patch(this.$wrap.data('url'), {
type: 'POST', protected_branch: {
url: this.$wrap.data('url'), merge_access_levels_attributes: [{
dataType: 'json', id: this.$allowedToMergeDropdown.data('access-level-id'),
data: { access_level: $allowedToMergeInput.val(),
_method: 'PATCH', }],
protected_branch: { push_access_levels_attributes: [{
merge_access_levels_attributes: [{ id: this.$allowedToPushDropdown.data('access-level-id'),
id: this.$allowedToMergeDropdown.data('access-level-id'), access_level: $allowedToPushInput.val(),
access_level: $allowedToMergeInput.val(), }],
}],
push_access_levels_attributes: [{
id: this.$allowedToPushDropdown.data('access-level-id'),
access_level: $allowedToPushInput.val(),
}],
},
}, },
error() { }).then(() => {
new Flash('Failed to update branch!', 'alert', document.querySelector('.js-protected-branches-list')); this.$allowedToMergeDropdown.enable();
}, this.$allowedToPushDropdown.enable();
}).always(() => { }).catch(() => {
this.$allowedToMergeDropdown.enable(); this.$allowedToMergeDropdown.enable();
this.$allowedToPushDropdown.enable(); this.$allowedToPushDropdown.enable();
flash('Failed to update branch!', 'alert', document.querySelector('.js-protected-branches-list'));
}); });
} }
} }
/* eslint-disable no-new */ import flash from '../flash';
import Flash from '../flash'; import axios from '../lib/utils/axios_utils';
import ProtectedTagAccessDropdown from './protected_tag_access_dropdown'; import ProtectedTagAccessDropdown from './protected_tag_access_dropdown';
export default class ProtectedTagEdit { export default class ProtectedTagEdit {
...@@ -28,24 +28,19 @@ export default class ProtectedTagEdit { ...@@ -28,24 +28,19 @@ export default class ProtectedTagEdit {
this.$allowedToCreateDropdownButton.disable(); this.$allowedToCreateDropdownButton.disable();
$.ajax({ axios.patch(this.$wrap.data('url'), {
type: 'POST', protected_tag: {
url: this.$wrap.data('url'), create_access_levels_attributes: [{
dataType: 'json', id: this.$allowedToCreateDropdownButton.data('access-level-id'),
data: { access_level: $allowedToCreateInput.val(),
_method: 'PATCH', }],
protected_tag: {
create_access_levels_attributes: [{
id: this.$allowedToCreateDropdownButton.data('access-level-id'),
access_level: $allowedToCreateInput.val(),
}],
},
}, },
error() { }).then(() => {
new Flash('Failed to update tag!', 'alert', document.querySelector('.js-protected-tags-list')); this.$allowedToCreateDropdownButton.enable();
}, }).catch(() => {
}).always(() => {
this.$allowedToCreateDropdownButton.enable(); this.$allowedToCreateDropdownButton.enable();
flash('Failed to update tag!', 'alert', document.querySelector('.js-protected-tags-list'));
}); });
} }
} }
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
import _ from 'underscore'; import _ from 'underscore';
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import flash from './flash';
import axios from './lib/utils/axios_utils';
function Sidebar(currentUser) { function Sidebar(currentUser) {
this.toggleTodo = this.toggleTodo.bind(this); this.toggleTodo = this.toggleTodo.bind(this);
...@@ -62,7 +64,7 @@ Sidebar.prototype.sidebarToggleClicked = function (e, triggered) { ...@@ -62,7 +64,7 @@ Sidebar.prototype.sidebarToggleClicked = function (e, triggered) {
Sidebar.prototype.toggleTodo = function(e) { Sidebar.prototype.toggleTodo = function(e) {
var $btnText, $this, $todoLoading, ajaxType, url; var $btnText, $this, $todoLoading, ajaxType, url;
$this = $(e.currentTarget); $this = $(e.currentTarget);
ajaxType = $this.attr('data-delete-path') ? 'DELETE' : 'POST'; ajaxType = $this.attr('data-delete-path') ? 'delete' : 'post';
if ($this.attr('data-delete-path')) { if ($this.attr('data-delete-path')) {
url = "" + ($this.attr('data-delete-path')); url = "" + ($this.attr('data-delete-path'));
} else { } else {
...@@ -71,25 +73,14 @@ Sidebar.prototype.toggleTodo = function(e) { ...@@ -71,25 +73,14 @@ Sidebar.prototype.toggleTodo = function(e) {
$this.tooltip('hide'); $this.tooltip('hide');
return $.ajax({ $('.js-issuable-todo').disable().addClass('is-loading');
url: url,
type: ajaxType, axios[ajaxType](url, {
dataType: 'json', issuable_id: $this.data('issuable-id'),
data: { issuable_type: $this.data('issuable-type'),
issuable_id: $this.data('issuable-id'), }).then(({ data }) => {
issuable_type: $this.data('issuable-type') this.todoUpdateDone(data);
}, }).catch(() => flash(`There was an error ${ajaxType === 'post' ? 'adding a' : 'deleting the'} todo.`));
beforeSend: (function(_this) {
return function() {
$('.js-issuable-todo').disable()
.addClass('is-loading');
};
})(this)
}).done((function(_this) {
return function(data) {
return _this.todoUpdateDone(data);
};
})(this));
}; };
Sidebar.prototype.todoUpdateDone = function(data) { Sidebar.prototype.todoUpdateDone = function(data) {
......
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import Mousetrap from 'mousetrap'; import Mousetrap from 'mousetrap';
import axios from './lib/utils/axios_utils';
import { refreshCurrentPage, visitUrl } from './lib/utils/url_utility'; import { refreshCurrentPage, visitUrl } from './lib/utils/url_utility';
import findAndFollowLink from './shortcuts_dashboard_navigation'; import findAndFollowLink from './shortcuts_dashboard_navigation';
...@@ -85,21 +86,21 @@ export default class Shortcuts { ...@@ -85,21 +86,21 @@ export default class Shortcuts {
$modal.modal('toggle'); $modal.modal('toggle');
} }
$.ajax({ return axios.get(gon.shortcuts_path, {
url: gon.shortcuts_path, responseType: 'text',
dataType: 'script', }).then(({ data }) => {
success() { $.globalEval(data);
if (location && location.length > 0) {
const results = []; if (location && location.length > 0) {
for (let i = 0, len = location.length; i < len; i += 1) { const results = [];
results.push($(location[i]).show()); for (let i = 0, len = location.length; i < len; i += 1) {
} results.push($(location[i]).show());
return results;
} }
return results;
}
$('.hidden-shortcut').show(); $('.hidden-shortcut').show();
return $('.js-more-help-button').remove(); return $('.js-more-help-button').remove();
},
}); });
} }
......
import 'deckar01-task_list'; import 'deckar01-task_list';
import axios from './lib/utils/axios_utils';
import Flash from './flash'; import Flash from './flash';
export default class TaskList { export default class TaskList {
...@@ -7,11 +8,11 @@ export default class TaskList { ...@@ -7,11 +8,11 @@ export default class TaskList {
this.dataType = options.dataType; this.dataType = options.dataType;
this.fieldName = options.fieldName; this.fieldName = options.fieldName;
this.onSuccess = options.onSuccess || (() => {}); this.onSuccess = options.onSuccess || (() => {});
this.onError = function showFlash(response) { this.onError = function showFlash(e) {
let errorMessages = ''; let errorMessages = '';
if (response.responseJSON) { if (e.response.data && typeof e.response.data === 'object') {
errorMessages = response.responseJSON.errors.join(' '); errorMessages = e.response.data.errors.join(' ');
} }
return new Flash(errorMessages || 'Update failed', 'alert'); return new Flash(errorMessages || 'Update failed', 'alert');
...@@ -38,12 +39,9 @@ export default class TaskList { ...@@ -38,12 +39,9 @@ export default class TaskList {
patchData[this.dataType] = { patchData[this.dataType] = {
[this.fieldName]: $target.val(), [this.fieldName]: $target.val(),
}; };
return $.ajax({
type: 'PATCH', return axios.patch($target.data('update-url') || $('form.js-issuable-update').attr('action'), patchData)
url: $target.data('update-url') || $('form.js-issuable-update').attr('action'), .then(({ data }) => this.onSuccess(data))
data: patchData, .catch(err => this.onError(err));
success: this.onSuccess,
error: this.onError,
});
} }
} }
import axios from '../lib/utils/axios_utils';
import Activities from '../activities'; import Activities from '../activities';
import ActivityCalendar from './activity_calendar'; import ActivityCalendar from './activity_calendar';
import { localTimeAgo } from '../lib/utils/datetime_utility'; import { localTimeAgo } from '../lib/utils/datetime_utility';
import { __ } from '../locale';
import flash from '../flash';
/** /**
* UserTabs * UserTabs
...@@ -131,18 +134,20 @@ export default class UserTabs { ...@@ -131,18 +134,20 @@ export default class UserTabs {
} }
loadTab(action, endpoint) { loadTab(action, endpoint) {
return $.ajax({ this.toggleLoading(true);
beforeSend: () => this.toggleLoading(true),
complete: () => this.toggleLoading(false), return axios.get(endpoint)
dataType: 'json', .then(({ data }) => {
url: endpoint,
success: (data) => {
const tabSelector = `div#${action}`; const tabSelector = `div#${action}`;
this.$parentEl.find(tabSelector).html(data.html); this.$parentEl.find(tabSelector).html(data.html);
this.loaded[action] = true; this.loaded[action] = true;
localTimeAgo($('.js-timeago', tabSelector)); localTimeAgo($('.js-timeago', tabSelector));
},
}); this.toggleLoading(false);
})
.catch(() => {
this.toggleLoading(false);
});
} }
loadActivities() { loadActivities() {
...@@ -158,17 +163,15 @@ export default class UserTabs { ...@@ -158,17 +163,15 @@ export default class UserTabs {
utcFormatted = `UTC${utcOffset > 0 ? '+' : ''}${(utcOffset / 3600)}`; utcFormatted = `UTC${utcOffset > 0 ? '+' : ''}${(utcOffset / 3600)}`;
} }
$.ajax({ axios.get(calendarPath)
dataType: 'json', .then(({ data }) => {
url: calendarPath,
success: (activityData) => {
$calendarWrap.html(CALENDAR_TEMPLATE); $calendarWrap.html(CALENDAR_TEMPLATE);
$calendarWrap.find('.calendar-hint').append(`(Timezone: ${utcFormatted})`); $calendarWrap.find('.calendar-hint').append(`(Timezone: ${utcFormatted})`);
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
new ActivityCalendar('.js-contrib-calendar', activityData, calendarActivitiesPath, utcOffset); new ActivityCalendar('.js-contrib-calendar', data, calendarActivitiesPath, utcOffset);
}, })
}); .catch(() => flash(__('There was an error loading users activity calendar.')));
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
new Activities(); new Activities();
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
/* global Issuable */ /* global Issuable */
/* global emitSidebarEvent */ /* global emitSidebarEvent */
import _ from 'underscore'; import _ from 'underscore';
import axios from './lib/utils/axios_utils';
// TODO: remove eventHub hack after code splitting refactor // TODO: remove eventHub hack after code splitting refactor
window.emitSidebarEvent = window.emitSidebarEvent || $.noop; window.emitSidebarEvent = window.emitSidebarEvent || $.noop;
...@@ -177,32 +178,28 @@ function UsersSelect(currentUser, els, options = {}) { ...@@ -177,32 +178,28 @@ function UsersSelect(currentUser, els, options = {}) {
$loading.removeClass('hidden').fadeIn(); $loading.removeClass('hidden').fadeIn();
$dropdown.trigger('loading.gl.dropdown'); $dropdown.trigger('loading.gl.dropdown');
return $.ajax({ return axios.put(issueURL, data)
type: 'PUT', .then(({ data }) => {
dataType: 'json', var user;
url: issueURL, $dropdown.trigger('loaded.gl.dropdown');
data: data $loading.fadeOut();
}).done(function(data) { if (data.assignee) {
var user; user = {
$dropdown.trigger('loaded.gl.dropdown'); name: data.assignee.name,
$loading.fadeOut(); username: data.assignee.username,
if (data.assignee) { avatar: data.assignee.avatar_url
user = { };
name: data.assignee.name, } else {
username: data.assignee.username, user = {
avatar: data.assignee.avatar_url name: 'Unassigned',
}; username: '',
} else { avatar: ''
user = { };
name: 'Unassigned', }
username: '', $value.html(assigneeTemplate(user));
avatar: '' $collapsedSidebar.attr('title', _.escape(user.name)).tooltip('fixTitle');
}; return $collapsedSidebar.html(collapsedAssigneeTemplate(user));
} });
$value.html(assigneeTemplate(user));
$collapsedSidebar.attr('title', _.escape(user.name)).tooltip('fixTitle');
return $collapsedSidebar.html(collapsedAssigneeTemplate(user));
});
}; };
collapsedAssigneeTemplate = _.template('<% if( avatar ) { %> <a class="author_link" href="/<%- username %>"> <img width="24" class="avatar avatar-inline s24" alt="" src="<%- avatar %>"> </a> <% } else { %> <i class="fa fa-user"></i> <% } %>'); collapsedAssigneeTemplate = _.template('<% if( avatar ) { %> <a class="author_link" href="/<%- username %>"> <img width="24" class="avatar avatar-inline s24" alt="" src="<%- avatar %>"> </a> <% } else { %> <i class="fa fa-user"></i> <% } %>');
assigneeTemplate = _.template('<% if (username) { %> <a class="author_link bold" href="/<%- username %>"> <% if( avatar ) { %> <img width="32" class="avatar avatar-inline s32" alt="" src="<%- avatar %>"> <% } %> <span class="author"><%- name %></span> <span class="username"> @<%- username %> </span> </a> <% } else { %> <span class="no-value assign-yourself"> No assignee - <a href="#" class="js-assign-yourself"> assign yourself </a> </span> <% } %>'); assigneeTemplate = _.template('<% if (username) { %> <a class="author_link bold" href="/<%- username %>"> <% if( avatar ) { %> <img width="32" class="avatar avatar-inline s32" alt="" src="<%- avatar %>"> <% } %> <span class="author"><%- name %></span> <span class="username"> @<%- username %> </span> </a> <% } else { %> <span class="no-value assign-yourself"> No assignee - <a href="#" class="js-assign-yourself"> assign yourself </a> </span> <% } %>');
...@@ -660,38 +657,33 @@ UsersSelect.prototype.user = function(user_id, callback) { ...@@ -660,38 +657,33 @@ UsersSelect.prototype.user = function(user_id, callback) {
var url; var url;
url = this.buildUrl(this.userPath); url = this.buildUrl(this.userPath);
url = url.replace(':id', user_id); url = url.replace(':id', user_id);
return $.ajax({ return axios.get(url)
url: url, .then(({ data }) => {
dataType: "json" callback(data);
}).done(function(user) { });
return callback(user);
});
}; };
// Return users list. Filtered by query // Return users list. Filtered by query
// Only active users retrieved // Only active users retrieved
UsersSelect.prototype.users = function(query, options, callback) { UsersSelect.prototype.users = function(query, options, callback) {
var url; const url = this.buildUrl(this.usersPath);
url = this.buildUrl(this.usersPath); const params = {
return $.ajax({ search: query,
url: url, per_page: options.perPage || 20,
data: { active: true,
search: query, project_id: options.projectId || null,
per_page: options.perPage || 20, group_id: options.groupId || null,
active: true, skip_ldap: options.skipLdap || null,
project_id: options.projectId || null, todo_filter: options.todoFilter || null,
group_id: options.groupId || null, todo_state_filter: options.todoStateFilter || null,
skip_ldap: options.skipLdap || null, current_user: options.showCurrentUser || null,
todo_filter: options.todoFilter || null, author_id: options.authorId || null,
todo_state_filter: options.todoStateFilter || null, skip_users: options.skipUsers || null
current_user: options.showCurrentUser || null, };
author_id: options.authorId || null, return axios.get(url, { params })
skip_users: options.skipUsers || null .then(({ data }) => {
}, callback(data);
dataType: "json" });
}).done(function(users) {
return callback(users);
});
}; };
UsersSelect.prototype.buildUrl = function(url) { UsersSelect.prototype.buildUrl = function(url) {
......
...@@ -118,7 +118,7 @@ ...@@ -118,7 +118,7 @@
<template> <template>
<div class="branch-commit"> <div class="branch-commit">
<template v-if="hasCommitRef && showBranch"> <template v-if="hasCommitRef && showBranch">
<div class="icon-container hidden-xs"> <div class="icon-container">
<i <i
v-if="tag" v-if="tag"
class="fa fa-tag" class="fa fa-tag"
...@@ -132,7 +132,7 @@ ...@@ -132,7 +132,7 @@
</div> </div>
<a <a
class="ref-name hidden-xs" class="ref-name"
:href="commitRef.ref_url" :href="commitRef.ref_url"
v-tooltip v-tooltip
data-container="body" data-container="body"
......
...@@ -61,3 +61,4 @@ ...@@ -61,3 +61,4 @@
@import "framework/responsive_tables"; @import "framework/responsive_tables";
@import "framework/stacked-progress-bar"; @import "framework/stacked-progress-bar";
@import "framework/ci_variable_list"; @import "framework/ci_variable_list";
@import "framework/feature_highlight";
.feature-highlight {
position: relative;
margin-left: $gl-padding;
width: 20px;
height: 20px;
cursor: pointer;
&::before {
content: '';
display: block;
position: absolute;
top: 6px;
left: 6px;
width: 8px;
height: 8px;
background-color: $blue-500;
border-radius: 50%;
box-shadow: 0 0 0 rgba($blue-500, 0.4);
animation: pulse-highlight 2s infinite;
}
&:hover::before,
&.disable-animation::before {
animation: none;
}
&[disabled]::before {
display: none;
}
}
.is-showing-fly-out {
.feature-highlight {
display: none;
}
}
.feature-highlight-popover-content {
display: none;
hr {
margin: $gl-padding * 0.5 0;
}
.btn-link {
svg {
@include btn-svg;
path {
fill: currentColor;
}
}
}
.feature-highlight-illustration {
width: 100%;
height: 100px;
padding-top: 12px;
padding-bottom: 12px;
background-color: $indigo-50;
border-top-left-radius: 2px;
border-top-right-radius: 2px;
border-bottom: 1px solid darken($gray-normal, 8%);
}
}
.popover .feature-highlight-popover-content {
display: block;
}
.feature-highlight-popover {
width: 240px;
padding: 0;
border: 1px solid $dropdown-border-color;
box-shadow: 0 2px 4px $dropdown-shadow-color;
&.right > .arrow {
border-right-color: $dropdown-border-color;
}
.popover-content {
padding: 0;
}
}
.feature-highlight-popover-sub-content {
padding: 9px 14px;
}
@include keyframes(pulse-highlight) {
0% {
box-shadow: 0 0 0 0 rgba($blue-200, 0.4);
}
70% {
box-shadow: 0 0 0 10px transparent;
}
100% {
box-shadow: 0 0 0 0 transparent;
}
}
...@@ -121,7 +121,7 @@ ...@@ -121,7 +121,7 @@
.ref-name { .ref-name {
font-weight: $gl-font-weight-bold; font-weight: $gl-font-weight-bold;
max-width: 120px; max-width: 100px;
overflow: hidden; overflow: hidden;
display: inline-block; display: inline-block;
white-space: nowrap; white-space: nowrap;
......
...@@ -6,6 +6,14 @@ ...@@ -6,6 +6,14 @@
} }
} }
.wiki-form {
.edit-wiki-page-slug-tip {
display: inline-block;
max-width: 100%;
margin-top: 5px;
}
}
.title .edit-wiki-header { .title .edit-wiki-header {
width: 780px; width: 780px;
margin-left: auto; margin-left: auto;
......
class Admin::BroadcastMessagesController < Admin::ApplicationController class Admin::BroadcastMessagesController < Admin::ApplicationController
include BroadcastMessagesHelper
before_action :finder, only: [:edit, :update, :destroy] before_action :finder, only: [:edit, :update, :destroy]
def index def index
...@@ -37,7 +39,8 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController ...@@ -37,7 +39,8 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController
end end
def preview def preview
@broadcast_message = BroadcastMessage.new(broadcast_message_params) broadcast_message = BroadcastMessage.new(broadcast_message_params)
render json: { message: render_broadcast_message(broadcast_message) }
end end
protected protected
......
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
...@@ -2,6 +2,7 @@ module Boards ...@@ -2,6 +2,7 @@ module Boards
class IssuesController < Boards::ApplicationController class IssuesController < Boards::ApplicationController
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]
...@@ -92,5 +93,10 @@ module Boards ...@@ -92,5 +93,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
...@@ -70,7 +70,7 @@ module UploadsActions ...@@ -70,7 +70,7 @@ module UploadsActions
end end
def build_uploader_from_params def build_uploader_from_params
uploader = uploader_class.new(model, params[:secret]) uploader = uploader_class.new(model, secret: params[:secret])
uploader.retrieve_from_store!(params[:filename]) uploader.retrieve_from_store!(params[:filename])
uploader uploader
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,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -8,6 +8,7 @@ class Projects::IssuesController < Projects::ApplicationController
prepend_before_action :authenticate_user!, only: [:new] prepend_before_action :authenticate_user!, only: [:new]
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] before_action :issue, except: [:index, :new, :create, :bulk_update]
before_action :set_issuables_index, only: [:index] before_action :set_issuables_index, only: [:index]
...@@ -247,4 +248,13 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -247,4 +248,13 @@ 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
end end
...@@ -4,6 +4,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap ...@@ -4,6 +4,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
include RendersCommits include RendersCommits
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]
...@@ -125,4 +126,8 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap ...@@ -125,4 +126,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
...@@ -7,6 +7,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -7,6 +7,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
include IssuableCollections include IssuableCollections
skip_before_action :merge_request, only: [:index, :bulk_update] skip_before_action :merge_request, only: [:index, :bulk_update]
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]
...@@ -339,4 +340,9 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -339,4 +340,9 @@ 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
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
...@@ -54,8 +54,8 @@ class Projects::WikisController < Projects::ApplicationController ...@@ -54,8 +54,8 @@ class Projects::WikisController < Projects::ApplicationController
else else
render 'edit' render 'edit'
end end
rescue WikiPage::PageChangedError rescue WikiPage::PageChangedError, WikiPage::PageRenameError => e
@conflict = true @error = e
render 'edit' render 'edit'
end end
......
...@@ -3,6 +3,7 @@ class ProjectsController < Projects::ApplicationController ...@@ -3,6 +3,7 @@ class ProjectsController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
include PreviewMarkdown include PreviewMarkdown
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]
...@@ -405,4 +406,8 @@ class ProjectsController < Projects::ApplicationController ...@@ -405,4 +406,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
class RegistrationsController < Devise::RegistrationsController class RegistrationsController < Devise::RegistrationsController
include Recaptcha::Verify include Recaptcha::Verify
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
...@@ -83,4 +85,8 @@ class RegistrationsController < Devise::RegistrationsController ...@@ -83,4 +85,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
class UserCalloutsController < ApplicationController
def create
if ensure_callout.persisted?
respond_to do |format|
format.json { head :ok }
end
else
respond_to do |format|
format.json { head :bad_request }
end
end
end
private
def ensure_callout
current_user.callouts.find_or_create_by(feature_name: UserCallout.feature_names[feature_name])
end
def feature_name
params.require(:feature_name)
end
end
module UserCalloutsHelper
GKE_CLUSTER_INTEGRATION = 'gke_cluster_integration'.freeze
def show_gke_cluster_integration_callout?(project)
can?(current_user, :create_cluster, project) &&
!user_dismissed?(GKE_CLUSTER_INTEGRATION)
end
private
def user_dismissed?(feature_name)
current_user&.callouts&.find_by(feature_name: UserCallout.feature_names[feature_name])
end
end
...@@ -21,4 +21,22 @@ module WikiHelper ...@@ -21,4 +21,22 @@ module WikiHelper
add_to_breadcrumb_dropdown link_to(WikiPage.unhyphenize(dir_or_page).capitalize, project_wiki_path(@project, current_slug)), location: :after add_to_breadcrumb_dropdown link_to(WikiPage.unhyphenize(dir_or_page).capitalize, project_wiki_path(@project, current_slug)), location: :after
end end
end end
def wiki_page_errors(error)
return unless error
content_tag(:div, class: 'alert alert-danger') do
case error
when WikiPage::PageChangedError
page_link = link_to s_("WikiPageConflictMessage|the page"), project_wiki_path(@project, @page), target: "_blank"
concat(
(s_("WikiPageConflictMessage|Someone edited the page the same time you did. Please check out %{page_link} and make sure your changes will not unintentionally remove theirs.") % { page_link: page_link }).html_safe
)
when WikiPage::PageRenameError
s_("WikiEdit|There is already a page with the same title in that path.")
else
error.message
end
end
end
end end
...@@ -17,8 +17,12 @@ module Clusters ...@@ -17,8 +17,12 @@ module Clusters
'stable/nginx-ingress' 'stable/nginx-ingress'
end end
def chart_values_file
"#{Rails.root}/vendor/#{name}/values.yaml"
end
def install_command def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(name, chart: chart) Gitlab::Kubernetes::Helm::InstallCommand.new(name, chart: chart, chart_values_file: chart_values_file)
end end
end end
end end
......
...@@ -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
...@@ -274,12 +274,6 @@ class Group < Namespace ...@@ -274,12 +274,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 }
......
...@@ -221,6 +221,24 @@ class Namespace < ActiveRecord::Base ...@@ -221,6 +221,24 @@ class Namespace < ActiveRecord::Base
has_parent? has_parent?
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
......
...@@ -60,7 +60,7 @@ class Note < ActiveRecord::Base ...@@ -60,7 +60,7 @@ class Note < ActiveRecord::Base
belongs_to :updated_by, class_name: "User" belongs_to :updated_by, class_name: "User"
belongs_to :last_edited_by, class_name: 'User' belongs_to :last_edited_by, class_name: 'User'
has_many :todos, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :todos
has_many :events, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :events, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_one :system_note_metadata has_one :system_note_metadata
......
...@@ -69,6 +69,7 @@ class Project < ActiveRecord::Base ...@@ -69,6 +69,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
...@@ -1525,6 +1526,8 @@ class Project < ActiveRecord::Base ...@@ -1525,6 +1526,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
...@@ -1533,8 +1536,9 @@ class Project < ActiveRecord::Base ...@@ -1533,8 +1536,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
......
...@@ -119,6 +119,8 @@ class ProjectWiki ...@@ -119,6 +119,8 @@ class ProjectWiki
end end
def delete_page(page, message = nil) def delete_page(page, message = nil)
return unless page
wiki.delete_page(page.path, commit_details(:deleted, message, page.title)) wiki.delete_page(page.path, commit_details(:deleted, message, page.title))
update_project_activity update_project_activity
...@@ -131,6 +133,8 @@ class ProjectWiki ...@@ -131,6 +133,8 @@ class ProjectWiki
end end
def page_title_and_dir(title) def page_title_and_dir(title)
return unless title
title_array = title.split("/") title_array = title.split("/")
title = title_array.pop title = title_array.pop
[title, title_array.join("/")] [title, title_array.join("/")]
......
...@@ -713,11 +713,11 @@ class Repository ...@@ -713,11 +713,11 @@ class Repository
end end
def branch_names_contains(sha) def branch_names_contains(sha)
refs_contains_sha('branch', sha) raw_repository.branch_names_contains_sha(sha)
end end
def tag_names_contains(sha) def tag_names_contains(sha)
refs_contains_sha('tag', sha) raw_repository.tag_names_contains_sha(sha)
end end
def local_branches def local_branches
......
...@@ -28,6 +28,7 @@ class Todo < ActiveRecord::Base ...@@ -28,6 +28,7 @@ class Todo < ActiveRecord::Base
delegate :name, :email, to: :author, prefix: true, allow_nil: true delegate :name, :email, to: :author, prefix: true, allow_nil: true
validates :action, :project, :target_type, :user, presence: true validates :action, :project, :target_type, :user, presence: true
validates :author, presence: true
validates :target_id, presence: true, unless: :for_commit? validates :target_id, presence: true, unless: :for_commit?
validates :commit_id, presence: true, if: :for_commit? validates :commit_id, presence: true, if: :for_commit?
......
...@@ -30,7 +30,7 @@ class Upload < ActiveRecord::Base ...@@ -30,7 +30,7 @@ class Upload < ActiveRecord::Base
end end
def build_uploader def build_uploader
uploader_class.new(model).tap do |uploader| uploader_class.new(model, mount_point, **uploader_context).tap do |uploader|
uploader.upload = self uploader.upload = self
uploader.retrieve_from_store!(identifier) uploader.retrieve_from_store!(identifier)
end end
...@@ -40,6 +40,13 @@ class Upload < ActiveRecord::Base ...@@ -40,6 +40,13 @@ class Upload < ActiveRecord::Base
File.exist?(absolute_path) File.exist?(absolute_path)
end end
def uploader_context
{
identifier: identifier,
secret: secret
}.compact
end
private private
def checksummable? def checksummable?
...@@ -62,11 +69,15 @@ class Upload < ActiveRecord::Base ...@@ -62,11 +69,15 @@ class Upload < ActiveRecord::Base
!path.start_with?('/') !path.start_with?('/')
end end
def uploader_class
Object.const_get(uploader)
end
def identifier def identifier
File.basename(path) File.basename(path)
end end
def uploader_class def mount_point
Object.const_get(uploader) super&.to_sym
end end
end end
...@@ -125,7 +125,7 @@ class User < ActiveRecord::Base ...@@ -125,7 +125,7 @@ class User < ActiveRecord::Base
has_many :spam_logs, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :spam_logs, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :builds, dependent: :nullify, class_name: 'Ci::Build' # rubocop:disable Cop/ActiveRecordDependent has_many :builds, dependent: :nullify, class_name: 'Ci::Build' # rubocop:disable Cop/ActiveRecordDependent
has_many :pipelines, dependent: :nullify, class_name: 'Ci::Pipeline' # rubocop:disable Cop/ActiveRecordDependent has_many :pipelines, dependent: :nullify, class_name: 'Ci::Pipeline' # rubocop:disable Cop/ActiveRecordDependent
has_many :todos, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :todos
has_many :notification_settings, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :notification_settings, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :award_emoji, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :award_emoji, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :triggers, dependent: :destroy, class_name: 'Ci::Trigger', foreign_key: :owner_id # rubocop:disable Cop/ActiveRecordDependent has_many :triggers, dependent: :destroy, class_name: 'Ci::Trigger', foreign_key: :owner_id # rubocop:disable Cop/ActiveRecordDependent
...@@ -135,6 +135,7 @@ class User < ActiveRecord::Base ...@@ -135,6 +135,7 @@ class User < ActiveRecord::Base
has_many :assigned_merge_requests, dependent: :nullify, foreign_key: :assignee_id, class_name: "MergeRequest" # rubocop:disable Cop/ActiveRecordDependent has_many :assigned_merge_requests, dependent: :nullify, foreign_key: :assignee_id, class_name: "MergeRequest" # rubocop:disable Cop/ActiveRecordDependent
has_many :custom_attributes, class_name: 'UserCustomAttribute' has_many :custom_attributes, class_name: 'UserCustomAttribute'
has_many :callouts, class_name: 'UserCallout'
has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
# #
......
class UserCallout < ActiveRecord::Base
belongs_to :user
enum feature_name: {
gke_cluster_integration: 1
}
validates :user, presence: true
validates :feature_name,
presence: true,
uniqueness: { scope: :user_id },
inclusion: { in: UserCallout.feature_names.keys }
end
class WikiPage class WikiPage
PageChangedError = Class.new(StandardError) PageChangedError = Class.new(StandardError)
PageRenameError = Class.new(StandardError)
include ActiveModel::Validations include ActiveModel::Validations
include ActiveModel::Conversion include ActiveModel::Conversion
...@@ -102,7 +103,7 @@ class WikiPage ...@@ -102,7 +103,7 @@ class WikiPage
# The hierarchy of the directory this page is contained in. # The hierarchy of the directory this page is contained in.
def directory def directory
wiki.page_title_and_dir(slug).last wiki.page_title_and_dir(slug)&.last.to_s
end end
# The processed/formatted content of this page. # The processed/formatted content of this page.
...@@ -177,7 +178,7 @@ class WikiPage ...@@ -177,7 +178,7 @@ class WikiPage
# Creates a new Wiki Page. # Creates a new Wiki Page.
# #
# attr - Hash of attributes to set on the new page. # attr - Hash of attributes to set on the new page.
# :title - The title for the new page. # :title - The title (optionally including dir) for the new page.
# :content - The raw markup content. # :content - The raw markup content.
# :format - Optional symbol representing the # :format - Optional symbol representing the
# content format. Can be any type # content format. Can be any type
...@@ -189,7 +190,7 @@ class WikiPage ...@@ -189,7 +190,7 @@ class WikiPage
# Returns the String SHA1 of the newly created page # Returns the String SHA1 of the newly created page
# or False if the save was unsuccessful. # or False if the save was unsuccessful.
def create(attrs = {}) def create(attrs = {})
@attributes.merge!(attrs) update_attributes(attrs)
save(page_details: title) do save(page_details: title) do
wiki.create_page(title, content, format, message) wiki.create_page(title, content, format, message)
...@@ -204,24 +205,29 @@ class WikiPage ...@@ -204,24 +205,29 @@ class WikiPage
# See ProjectWiki::MARKUPS Hash for available formats. # See ProjectWiki::MARKUPS Hash for available formats.
# :message - Optional commit message to set on the new version. # :message - Optional commit message to set on the new version.
# :last_commit_sha - Optional last commit sha to validate the page unchanged. # :last_commit_sha - Optional last commit sha to validate the page unchanged.
# :title - The Title to replace existing title # :title - The Title (optionally including dir) to replace existing title
# #
# Returns the String SHA1 of the newly created page # Returns the String SHA1 of the newly created page
# or False if the save was unsuccessful. # or False if the save was unsuccessful.
def update(attrs = {}) def update(attrs = {})
last_commit_sha = attrs.delete(:last_commit_sha) last_commit_sha = attrs.delete(:last_commit_sha)
if last_commit_sha && last_commit_sha != self.last_commit_sha if last_commit_sha && last_commit_sha != self.last_commit_sha
raise PageChangedError.new("You are attempting to update a page that has changed since you started editing it.") raise PageChangedError
end end
attrs.slice!(:content, :format, :message, :title) update_attributes(attrs)
@attributes.merge!(attrs)
page_details = if title_changed?
if title.present? && @page.title != title page_details = title
title
else if wiki.find_page(page_details).present?
@page.url_path @attributes[:title] = @page.url_path
raise PageRenameError
end end
else
page_details = @page.url_path
end
save(page_details: page_details) do save(page_details: page_details) do
wiki.update_page( wiki.update_page(
...@@ -255,8 +261,44 @@ class WikiPage ...@@ -255,8 +261,44 @@ class WikiPage
page.version.to_s page.version.to_s
end end
def title_changed?
title.present? && self.class.unhyphenize(@page.url_path) != title
end
private private
# Process and format the title based on the user input.
def process_title(title)
return if title.blank?
title = deep_title_squish(title)
current_dirname = File.dirname(title)
if @page.present?
return title[1..-1] if current_dirname == '/'
return File.join([directory.presence, title].compact) if current_dirname == '.'
end
title
end
# This method squishes all the filename
# i.e: ' foo / bar / page_name' => 'foo/bar/page_name'
def deep_title_squish(title)
components = title.split(File::SEPARATOR).map(&:squish)
File.join(components)
end
# Updates the current @attributes hash by merging a hash of params
def update_attributes(attrs)
attrs[:title] = process_title(attrs[:title]) if attrs[:title].present?
attrs.slice!(:content, :format, :message, :title)
@attributes.merge!(attrs)
end
def set_attributes def set_attributes
attributes[:slug] = @page.url_path attributes[:slug] = @page.url_path
attributes[:title] = @page.title attributes[:title] = @page.title
......
...@@ -22,8 +22,7 @@ module SystemNoteService ...@@ -22,8 +22,7 @@ module SystemNoteService
commits_text = "#{total_count} commit".pluralize(total_count) commits_text = "#{total_count} commit".pluralize(total_count)
body = "added #{commits_text}\n\n" body = "added #{commits_text}\n\n"
body << existing_commit_summary(noteable, existing_commits, oldrev) body << commits_list(noteable, new_commits, existing_commits, oldrev)
body << new_commit_summary(new_commits).join("\n")
body << "\n\n[Compare with previous version](#{diff_comparison_url(noteable, project, oldrev)})" body << "\n\n[Compare with previous version](#{diff_comparison_url(noteable, project, oldrev)})"
create_note(NoteSummary.new(noteable, project, author, body, action: 'commit', commit_count: total_count)) create_note(NoteSummary.new(noteable, project, author, body, action: 'commit', commit_count: total_count))
...@@ -481,7 +480,7 @@ module SystemNoteService ...@@ -481,7 +480,7 @@ module SystemNoteService
# Returns an Array of Strings # Returns an Array of Strings
def new_commit_summary(new_commits) def new_commit_summary(new_commits)
new_commits.collect do |commit| new_commits.collect do |commit|
"* #{commit.short_id} - #{escape_html(commit.title)}" content_tag('li', "#{commit.short_id} - #{commit.title}")
end end
end end
...@@ -604,6 +603,16 @@ module SystemNoteService ...@@ -604,6 +603,16 @@ module SystemNoteService
"#{cross_reference_note_prefix}#{gfm_reference}" "#{cross_reference_note_prefix}#{gfm_reference}"
end end
# Builds a list of existing and new commits according to existing_commits and
# new_commits methods.
# Returns a String wrapped in `ul` and `li` tags.
def commits_list(noteable, new_commits, existing_commits, oldrev)
existing_commit_summary = existing_commit_summary(noteable, existing_commits, oldrev)
new_commit_summary = new_commit_summary(new_commits).join
content_tag('ul', "#{existing_commit_summary}#{new_commit_summary}".html_safe)
end
# Build a single line summarizing existing commits being added in a merge # Build a single line summarizing existing commits being added in a merge
# request # request
# #
...@@ -640,11 +649,8 @@ module SystemNoteService ...@@ -640,11 +649,8 @@ module SystemNoteService
branch = noteable.target_branch branch = noteable.target_branch
branch = "#{noteable.target_project_namespace}:#{branch}" if noteable.for_fork? branch = "#{noteable.target_project_namespace}:#{branch}" if noteable.for_fork?
"* #{commit_ids} - #{commits_text} from branch `#{branch}`\n" branch_name = content_tag('code', branch)
end content_tag('li', "#{commit_ids} - #{commits_text} from branch #{branch_name}".html_safe)
def escape_html(text)
Rack::Utils.escape_html(text)
end end
def url_helpers def url_helpers
...@@ -661,4 +667,8 @@ module SystemNoteService ...@@ -661,4 +667,8 @@ module SystemNoteService
start_sha: oldrev start_sha: oldrev
) )
end end
def content_tag(*args)
ActionController::Base.helpers.content_tag(*args)
end
end end
...@@ -49,11 +49,11 @@ class FileMover ...@@ -49,11 +49,11 @@ class FileMover
end end
def uploader def uploader
@uploader ||= PersonalFileUploader.new(model, secret) @uploader ||= PersonalFileUploader.new(model, secret: secret)
end end
def temp_file_uploader def temp_file_uploader
@temp_file_uploader ||= PersonalFileUploader.new(nil, secret) @temp_file_uploader ||= PersonalFileUploader.new(nil, secret: secret)
end end
def revert def revert
......
...@@ -62,9 +62,11 @@ class FileUploader < GitlabUploader ...@@ -62,9 +62,11 @@ class FileUploader < GitlabUploader
attr_accessor :model attr_accessor :model
def initialize(model, secret = nil) def initialize(model, mounted_as = nil, **uploader_context)
super(model, nil, **uploader_context)
@model = model @model = model
@secret = secret apply_context!(uploader_context)
end end
def base_dir def base_dir
...@@ -107,15 +109,17 @@ class FileUploader < GitlabUploader ...@@ -107,15 +109,17 @@ class FileUploader < GitlabUploader
self.file.filename self.file.filename
end end
# the upload does not hold the secret, but holds the path
# which contains the secret: extract it
def upload=(value) def upload=(value)
super
return unless value
return if apply_context!(value.uploader_context)
# fallback to the regex based extraction
if matches = DYNAMIC_PATH_PATTERN.match(value.path) if matches = DYNAMIC_PATH_PATTERN.match(value.path)
@secret = matches[:secret] @secret = matches[:secret]
@identifier = matches[:identifier] @identifier = matches[:identifier]
end end
super
end end
def secret def secret
...@@ -124,6 +128,18 @@ class FileUploader < GitlabUploader ...@@ -124,6 +128,18 @@ class FileUploader < GitlabUploader
private private
def apply_context!(uploader_context)
@secret, @identifier = uploader_context.values_at(:secret, :identifier)
!!(@secret && @identifier)
end
def build_upload
super.tap do |upload|
upload.secret = secret
end
end
def markdown_name def markdown_name
(image_or_video? ? File.basename(filename, File.extname(filename)) : filename).gsub("]", "\\]") (image_or_video? ? File.basename(filename, File.extname(filename)) : filename).gsub("]", "\\]")
end end
......
...@@ -29,6 +29,10 @@ class GitlabUploader < CarrierWave::Uploader::Base ...@@ -29,6 +29,10 @@ class GitlabUploader < CarrierWave::Uploader::Base
delegate :base_dir, :file_storage?, to: :class delegate :base_dir, :file_storage?, to: :class
def initialize(model, mounted_as = nil, **uploader_context)
super(model, mounted_as)
end
def file_cache_storage? def file_cache_storage?
cache_storage.is_a?(CarrierWave::Storage::File) cache_storage.is_a?(CarrierWave::Storage::File)
end end
......
...@@ -24,7 +24,7 @@ module RecordsUploads ...@@ -24,7 +24,7 @@ module RecordsUploads
uploads.where(path: upload_path).delete_all uploads.where(path: upload_path).delete_all
upload.destroy! if upload upload.destroy! if upload
self.upload = build_upload_from_uploader(self) self.upload = build_upload
upload.save! upload.save!
end end
end end
...@@ -39,12 +39,13 @@ module RecordsUploads ...@@ -39,12 +39,13 @@ module RecordsUploads
Upload.order(id: :desc).where(uploader: self.class.to_s) Upload.order(id: :desc).where(uploader: self.class.to_s)
end end
def build_upload_from_uploader(uploader) def build_upload
Upload.new( Upload.new(
size: uploader.file.size, uploader: self.class.to_s,
path: uploader.upload_path, size: file.size,
model: uploader.model, path: upload_path,
uploader: uploader.class.to_s model: model,
mount_point: mounted_as
) )
end end
......
$('.js-broadcast-message-preview').html("#{j(render_broadcast_message(@broadcast_message))}");
...@@ -184,10 +184,34 @@ ...@@ -184,10 +184,34 @@
Environments Environments
- if project_nav_tab? :clusters - if project_nav_tab? :clusters
- show_cluster_hint = show_gke_cluster_integration_callout?(@project)
= nav_link(controller: [:clusters, :user, :gcp]) do = nav_link(controller: [:clusters, :user, :gcp]) do
= link_to project_clusters_path(@project), title: 'Cluster', class: 'shortcuts-cluster' do = link_to project_clusters_path(@project), title: 'Cluster', class: 'shortcuts-cluster' do
%span %span
Clusters Clusters
- if show_cluster_hint
.feature-highlight.js-feature-highlight{ disabled: true,
data: { trigger: 'manual',
container: 'body',
toggle: 'popover',
placement: 'right',
highlight: UserCalloutsHelper::GKE_CLUSTER_INTEGRATION,
highlight_priority: UserCallout.feature_names[:GKE_CLUSTER_INTEGRATION],
dismiss_endpoint: user_callouts_path } }
- if show_cluster_hint
.feature-highlight-popover-content
= image_tag 'illustrations/cluster_popover.svg', class: 'feature-highlight-illustration'
.feature-highlight-popover-sub-content
%p= _('Allows you to add and manage Kubernetes clusters.')
%p
= _('Protip:')
= link_to 'Auto DevOps', help_page_path('topics/autodevops/index.md')
%span= _('uses clusters to deploy your code!')
%hr
%button.btn.btn-create.btn-xs.dismiss-feature-highlight{ type: 'button' }
%span= _("Got it!")
= sprite_icon('thumb-up')
- if @project.feature_available?(:builds, current_user) && !@project.empty_repo? - if @project.feature_available?(:builds, current_user) && !@project.empty_repo?
= nav_link(path: 'pipelines#charts') do = nav_link(path: 'pipelines#charts') do
......
...@@ -47,7 +47,7 @@ ...@@ -47,7 +47,7 @@
- if @repository.gitlab_ci_yml - if @repository.gitlab_ci_yml
%li %li
= link_to _('CI configuration'), ci_configuration_path(@project) = link_to _('CI/CD configuration'), ci_configuration_path(@project)
- if current_user && can_push_branch?(@project, @project.default_branch) - if current_user && can_push_branch?(@project, @project.default_branch)
- unless @repository.changelog - unless @repository.changelog
...@@ -65,7 +65,7 @@ ...@@ -65,7 +65,7 @@
- unless @repository.gitlab_ci_yml - unless @repository.gitlab_ci_yml
%li.missing %li.missing
= link_to add_special_file_path(@project, file_name: '.gitlab-ci.yml') do = link_to add_special_file_path(@project, file_name: '.gitlab-ci.yml') do
#{ _('Set up CI') } #{ _('Set up CI/CD') }
- if koding_enabled? && @repository.koding_yml.blank? - if koding_enabled? && @repository.koding_yml.blank?
%li.missing %li.missing
= link_to _('Set up Koding'), add_koding_stack_path(@project) = link_to _('Set up Koding'), add_koding_stack_path(@project)
......
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
.wiki .wiki
= markdown_field(release, :description) = markdown_field(release, :description)
.row-fixed-content.controls .row-fixed-content.controls.flex-row
= render 'projects/buttons/download', project: @project, ref: tag.name, pipeline: @tags_pipelines[tag.name] = render 'projects/buttons/download', project: @project, ref: tag.name, pipeline: @tags_pipelines[tag.name]
- if can?(current_user, :push_code, @project) - if can?(current_user, :push_code, @project)
......
...@@ -9,7 +9,13 @@ ...@@ -9,7 +9,13 @@
.form-group .form-group
.col-sm-12= f.label :title, class: 'control-label-full-width' .col-sm-12= f.label :title, class: 'control-label-full-width'
.col-sm-12= f.text_field :title, class: 'form-control', value: @page.title .col-sm-12
= f.text_field :title, class: 'form-control', value: @page.title
- if @page.persisted?
%span.edit-wiki-page-slug-tip
= icon('lightbulb-o')
= s_("WikiEditPageTip|Tip: You can move this page by adding the path to the beginning of the title.")
= link_to icon('question-circle'), help_page_path('user/project/wiki/index', anchor: 'moving-a-wiki-page'), target: '_blank'
.form-group .form-group
.col-sm-12= f.label :format, class: 'control-label-full-width' .col-sm-12= f.label :format, class: 'control-label-full-width'
.col-sm-12 .col-sm-12
......
- @content_class = "limit-container-width limit-container-width-sm" unless fluid_layout - @content_class = "limit-container-width limit-container-width-sm" unless fluid_layout
- page_title _("Edit"), @page.title.capitalize, _("Wiki") - page_title _("Edit"), @page.title.capitalize, _("Wiki")
- if @conflict = wiki_page_errors(@error)
.alert.alert-danger
- page_link = link_to s_("WikiPageConflictMessage|the page"), project_wiki_path(@project, @page), target: "_blank"
= (s_("WikiPageConflictMessage|Someone edited the page the same time you did. Please check out %{page_link} and make sure your changes will not unintentionally remove theirs.") % { page_link: page_link }).html_safe
.wiki-page-header.has-sidebar-toggle .wiki-page-header.has-sidebar-toggle
%button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" } %button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
......
---
title: Show coverage to two decimal points in coverage badge
merge_request: 10083
author: Jeff Stubler
type: changed
---
title: Add foreign key and NOT NULL constraints to todos table.
merge_request: 16849
author:
type: other
---
title: Enable Prometheus metrics for deployed Ingresses
merge_request: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/16866
author: joshlambert
type: changed
---
title: Add blue dot feature highlight to make GKE Clusters more visible to users
merge_request: 16379
author:
type: added
---
title: Fix export removal for hashed-storage projects within a renamed or deleted
namespace
merge_request: 16658
author:
type: fixed
---
title: Added uploader metadata to the uploads.
merge_request: 16779
author:
type: added
---
title: Fixes different margins between buttons in tag list
merge_request: 16927
author: Jacopo Beschi @jacopo-beschi
type: fixed
---
title: Rename button to enable CI/CD configuration to "Set up CI/CD"
merge_request: 16870
author:
type: changed
---
title: Allow moving wiki pages from the UI
merge_request: 16313
author:
type: fixed
---
title: Trigger change event on filename input when file template is applied
merge_request: 16911
author: Sebastian Klingler
type: fixed
---
title: Bypass commits title markdown on notes
merge_request:
author:
type: fixed
---
title: Add backend for persistently dismissably callouts
merge_request:
author:
type: added
---
title: Track and act upon the number of executed queries
merge_request:
author:
type: added
---
title: Include branch in mobile view for pipelines
merge_request: 16910
author: George Tsiolis
type: other
...@@ -35,6 +35,88 @@ module Gollum ...@@ -35,6 +35,88 @@ module Gollum
[] []
end end
end end
# Remove if https://github.com/gollum/gollum-lib/pull/292 has been merged
def update_page(page, name, format, data, commit = {})
name = name ? ::File.basename(name) : page.name
format ||= page.format
dir = ::File.dirname(page.path)
dir = '' if dir == '.'
filename = (rename = page.name != name) ? Gollum::Page.cname(name) : page.filename_stripped
multi_commit = !!commit[:committer]
committer = multi_commit ? commit[:committer] : Committer.new(self, commit)
if !rename && page.format == format
committer.add(page.path, normalize(data))
else
committer.delete(page.path)
committer.add_to_index(dir, filename, format, data)
end
committer.after_commit do |index, _sha|
@access.refresh
index.update_working_dir(dir, page.filename_stripped, page.format)
index.update_working_dir(dir, filename, format)
end
multi_commit ? committer : committer.commit
end
# Remove if https://github.com/gollum/gollum-lib/pull/292 has been merged
def rename_page(page, rename, commit = {})
return false if page.nil?
return false if rename.nil? || rename.empty?
(target_dir, target_name) = ::File.split(rename)
(source_dir, source_name) = ::File.split(page.path)
source_name = page.filename_stripped
# File.split gives us relative paths with ".", commiter.add_to_index doesn't like that.
target_dir = '' if target_dir == '.'
source_dir = '' if source_dir == '.'
target_dir = target_dir.gsub(/^\//, '') # rubocop:disable Style/RegexpLiteral
# if the rename is a NOOP, abort
if source_dir == target_dir && source_name == target_name
return false
end
multi_commit = !!commit[:committer]
committer = multi_commit ? commit[:committer] : Committer.new(self, commit)
# This piece only works for multi_commit
# If we are in a commit batch and one of the previous operations
# has updated the page, any information we ask to the page can be outdated.
# Therefore, we should ask first to the current committer tree to see if
# there is any updated change.
raw_data = raw_data_in_committer(committer, source_dir, page.filename) ||
raw_data_in_committer(committer, source_dir, "#{target_name}.#{Page.format_to_ext(page.format)}") ||
page.raw_data
committer.delete(page.path)
committer.add_to_index(target_dir, target_name, page.format, raw_data)
committer.after_commit do |index, _sha|
@access.refresh
index.update_working_dir(source_dir, source_name, page.format)
index.update_working_dir(target_dir, target_name, page.format)
end
multi_commit ? committer : committer.commit
end
# Remove if https://github.com/gollum/gollum-lib/pull/292 has been merged
def raw_data_in_committer(committer, dir, filename)
data = nil
[*dir.split(::File::SEPARATOR), filename].each do |key|
data = data ? data[key] : committer.tree[key]
break unless data
end
data
end
end end
module Git module Git
......
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
...@@ -60,6 +60,9 @@ Rails.application.routes.draw do ...@@ -60,6 +60,9 @@ Rails.application.routes.draw do
resources :issues, module: :boards, only: [:index, :update] resources :issues, module: :boards, only: [:index, :update]
end end
# UserCallouts
resources :user_callouts, only: [:create]
end end
# Koding route # Koding route
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class CreateUserCallouts < ActiveRecord::Migration
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
def change
create_table :user_callouts do |t|
t.integer :feature_name, null: false
t.references :user, index: true, foreign_key: { on_delete: :cascade }, null: false
end
add_index :user_callouts, [:user_id, :feature_name], unique: true
end
end
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddUploadsBuilderContext < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
def change
add_column :uploads, :mount_point, :string
add_column :uploads, :secret, :string
end
end
class AddForeignKeysToTodos < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
class Todo < ActiveRecord::Base
self.table_name = 'todos'
include EachBatch
end
BATCH_SIZE = 1000
DOWNTIME = false
disable_ddl_transaction!
def up
Todo.where('NOT EXISTS ( SELECT true FROM users WHERE id=todos.user_id )').each_batch(of: BATCH_SIZE) do |batch|
batch.delete_all
end
Todo.where('NOT EXISTS ( SELECT true FROM users WHERE id=todos.author_id )').each_batch(of: BATCH_SIZE) do |batch|
batch.delete_all
end
Todo.where('note_id IS NOT NULL AND NOT EXISTS ( SELECT true FROM notes WHERE id=todos.note_id )').each_batch(of: BATCH_SIZE) do |batch|
batch.delete_all
end
add_concurrent_foreign_key :todos, :users, column: :user_id, on_delete: :cascade
add_concurrent_foreign_key :todos, :users, column: :author_id, on_delete: :cascade
add_concurrent_foreign_key :todos, :notes, column: :note_id, on_delete: :cascade
end
def down
remove_foreign_key :todos, :users
remove_foreign_key :todos, column: :author_id
remove_foreign_key :todos, :notes
end
end
...@@ -18,12 +18,21 @@ class MigrateRemainingIssuesClosedAt < ActiveRecord::Migration ...@@ -18,12 +18,21 @@ class MigrateRemainingIssuesClosedAt < ActiveRecord::Migration
Gitlab::BackgroundMigration.steal('CopyColumn') Gitlab::BackgroundMigration.steal('CopyColumn')
Gitlab::BackgroundMigration.steal('CleanupConcurrentTypeChange') Gitlab::BackgroundMigration.steal('CleanupConcurrentTypeChange')
# It's possible the cleanup job was killed which means we need to manually if migrate_column_type?
# migrate any remaining rows. if closed_at_for_type_change_exists?
migrate_remaining_rows if migrate_column_type? migrate_remaining_rows
else
# Due to some EE merge problems some environments may not have the
# "closed_at_for_type_change" column. If this is the case we have no
# other option than to migrate the data _right now_.
change_column_type_concurrently(:issues, :closed_at, :datetime_with_timezone)
cleanup_concurrent_column_type_change(:issues, :closed_at)
end
end
end end
def down def down
# Previous migrations already revert the changes made here.
end end
def migrate_remaining_rows def migrate_remaining_rows
...@@ -39,4 +48,8 @@ class MigrateRemainingIssuesClosedAt < ActiveRecord::Migration ...@@ -39,4 +48,8 @@ class MigrateRemainingIssuesClosedAt < ActiveRecord::Migration
# migration, thus we don't need to migrate those environments again. # migration, thus we don't need to migrate those environments again.
column_for('issues', 'closed_at').type == :datetime # rubocop:disable Migration/Datetime column_for('issues', 'closed_at').type == :datetime # rubocop:disable Migration/Datetime
end end
def closed_at_for_type_change_exists?
columns('issues').any? { |col| col.name == 'closed_at_for_type_change' }
end
end end
class ChangeAuthorIdToNotNullInTodos < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
class Todo < ActiveRecord::Base
self.table_name = 'todos'
include EachBatch
end
BATCH_SIZE = 1000
DOWNTIME = false
disable_ddl_transaction!
def up
Todo.where(author_id: nil).each_batch(of: BATCH_SIZE) do |batch|
batch.delete_all
end
change_column_null :todos, :author_id, false
end
def down
change_column_null :todos, :author_id, true
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: 20180202111106) do ActiveRecord::Schema.define(version: 20180204200836) 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"
...@@ -1707,7 +1707,7 @@ ActiveRecord::Schema.define(version: 20180202111106) do ...@@ -1707,7 +1707,7 @@ ActiveRecord::Schema.define(version: 20180202111106) do
t.integer "project_id", null: false t.integer "project_id", null: false
t.integer "target_id" t.integer "target_id"
t.string "target_type", null: false t.string "target_type", null: false
t.integer "author_id" t.integer "author_id", null: false
t.integer "action", null: false t.integer "action", null: false
t.string "state", null: false t.string "state", null: false
t.datetime "created_at" t.datetime "created_at"
...@@ -1751,6 +1751,8 @@ ActiveRecord::Schema.define(version: 20180202111106) do ...@@ -1751,6 +1751,8 @@ ActiveRecord::Schema.define(version: 20180202111106) do
t.string "model_type" t.string "model_type"
t.string "uploader", null: false t.string "uploader", null: false
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.string "mount_point"
t.string "secret"
end end
add_index "uploads", ["checksum"], name: "index_uploads_on_checksum", using: :btree add_index "uploads", ["checksum"], name: "index_uploads_on_checksum", using: :btree
...@@ -1769,6 +1771,14 @@ ActiveRecord::Schema.define(version: 20180202111106) do ...@@ -1769,6 +1771,14 @@ ActiveRecord::Schema.define(version: 20180202111106) do
add_index "user_agent_details", ["subject_id", "subject_type"], name: "index_user_agent_details_on_subject_id_and_subject_type", using: :btree add_index "user_agent_details", ["subject_id", "subject_type"], name: "index_user_agent_details_on_subject_id_and_subject_type", using: :btree
create_table "user_callouts", force: :cascade do |t|
t.integer "feature_name", null: false
t.integer "user_id", null: false
end
add_index "user_callouts", ["user_id", "feature_name"], name: "index_user_callouts_on_user_id_and_feature_name", unique: true, using: :btree
add_index "user_callouts", ["user_id"], name: "index_user_callouts_on_user_id", using: :btree
create_table "user_custom_attributes", force: :cascade do |t| create_table "user_custom_attributes", force: :cascade do |t|
t.datetime_with_timezone "created_at", null: false t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false t.datetime_with_timezone "updated_at", null: false
...@@ -2037,9 +2047,13 @@ ActiveRecord::Schema.define(version: 20180202111106) do ...@@ -2037,9 +2047,13 @@ ActiveRecord::Schema.define(version: 20180202111106) do
add_foreign_key "system_note_metadata", "notes", name: "fk_d83a918cb1", on_delete: :cascade add_foreign_key "system_note_metadata", "notes", name: "fk_d83a918cb1", on_delete: :cascade
add_foreign_key "timelogs", "issues", name: "fk_timelogs_issues_issue_id", on_delete: :cascade add_foreign_key "timelogs", "issues", name: "fk_timelogs_issues_issue_id", on_delete: :cascade
add_foreign_key "timelogs", "merge_requests", name: "fk_timelogs_merge_requests_merge_request_id", on_delete: :cascade add_foreign_key "timelogs", "merge_requests", name: "fk_timelogs_merge_requests_merge_request_id", on_delete: :cascade
add_foreign_key "todos", "notes", name: "fk_91d1f47b13", on_delete: :cascade
add_foreign_key "todos", "projects", name: "fk_45054f9c45", on_delete: :cascade add_foreign_key "todos", "projects", name: "fk_45054f9c45", on_delete: :cascade
add_foreign_key "todos", "users", column: "author_id", name: "fk_ccf0373936", on_delete: :cascade
add_foreign_key "todos", "users", name: "fk_d94154aa95", on_delete: :cascade
add_foreign_key "trending_projects", "projects", on_delete: :cascade add_foreign_key "trending_projects", "projects", on_delete: :cascade
add_foreign_key "u2f_registrations", "users" add_foreign_key "u2f_registrations", "users"
add_foreign_key "user_callouts", "users", on_delete: :cascade
add_foreign_key "user_custom_attributes", "users", on_delete: :cascade add_foreign_key "user_custom_attributes", "users", on_delete: :cascade
add_foreign_key "user_synced_attributes_metadata", "users", on_delete: :cascade add_foreign_key "user_synced_attributes_metadata", "users", on_delete: :cascade
add_foreign_key "users_star_projects", "projects", name: "fk_22cd27ddfc", on_delete: :cascade add_foreign_key "users_star_projects", "projects", name: "fk_22cd27ddfc", on_delete: :cascade
......
...@@ -13,7 +13,7 @@ override certain values. ...@@ -13,7 +13,7 @@ override certain values.
Variable | Type | Description Variable | Type | Description
-------- | ---- | ----------- -------- | ---- | -----------
`GITLAB_CDN_HOST` | string | Sets the hostname for a CDN to serve static assets (e.g. `mycdnsubdomain.fictional-cdn.com`) `GITLAB_CDN_HOST` | string | Sets the base URL for a CDN to serve static assets (e.g. `//mycdnsubdomain.fictional-cdn.com`)
`GITLAB_ROOT_PASSWORD` | string | Sets the password for the `root` user on installation `GITLAB_ROOT_PASSWORD` | string | Sets the password for the `root` user on installation
`GITLAB_HOST` | string | The full URL of the GitLab server (including `http://` or `https://`) `GITLAB_HOST` | string | The full URL of the GitLab server (including `http://` or `https://`)
`RAILS_ENV` | string | The Rails environment; can be one of `production`, `development`, `staging` or `test` `RAILS_ENV` | string | The Rails environment; can be one of `production`, `development`, `staging` or `test`
......
...@@ -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
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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