Commit d2798d60 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent d8211a0e
/* eslint-disable class-methods-use-this, no-unused-vars */ /* eslint-disable class-methods-use-this */
import $ from 'jquery'; import $ from 'jquery';
...@@ -61,7 +61,7 @@ export default class TemplateSelector { ...@@ -61,7 +61,7 @@ export default class TemplateSelector {
return this.requestFile(item); return this.requestFile(item);
} }
requestFile(item) { requestFile() {
// This `requestFile` method is an abstract method that should // This `requestFile` method is an abstract method that should
// be added by all subclasses. // be added by all subclasses.
} }
......
/* eslint-disable func-names, no-var, prefer-arrow-callback, no-else-return, consistent-return, prefer-template, one-var, no-unused-vars, no-return-assign, no-unused-expressions, no-sequences */ /* eslint-disable func-names, no-var, prefer-arrow-callback, no-else-return, consistent-return, prefer-template, one-var, no-return-assign, no-unused-expressions, no-sequences */
import $ from 'jquery'; import $ from 'jquery';
...@@ -12,11 +12,8 @@ export default class ImageFile { ...@@ -12,11 +12,8 @@ export default class ImageFile {
this.requestImageInfo( this.requestImageInfo(
$('.two-up.view .frame.deleted img', this.file), $('.two-up.view .frame.deleted img', this.file),
(function(_this) { (function(_this) {
return function(deletedWidth, deletedHeight) { return function() {
return _this.requestImageInfo($('.two-up.view .frame.added img', _this.file), function( return _this.requestImageInfo($('.two-up.view .frame.added img', _this.file), function() {
width,
height,
) {
_this.initViewModes(); _this.initViewModes();
// Load two-up view after images are loaded // Load two-up view after images are loaded
...@@ -112,7 +109,7 @@ export default class ImageFile { ...@@ -112,7 +109,7 @@ export default class ImageFile {
maxHeight = 0; maxHeight = 0;
$('.frame', view) $('.frame', view)
.each( .each(
(function(_this) { (function() {
return function(index, frame) { return function(index, frame) {
var height, width; var height, width;
width = $(frame).width(); width = $(frame).width();
...@@ -196,13 +193,7 @@ export default class ImageFile { ...@@ -196,13 +193,7 @@ export default class ImageFile {
return $('.onion-skin.view', this.file).each( return $('.onion-skin.view', this.file).each(
(function(_this) { (function(_this) {
return function(index, view) { return function(index, view) {
var $frame, var $frame, $track, $dragger, $frameAdded, framePadding, ref;
$track,
$dragger,
$frameAdded,
framePadding,
ref,
dragging = false;
(ref = _this.prepareFrames(view)), ([maxWidth, maxHeight] = ref); (ref = _this.prepareFrames(view)), ([maxWidth, maxHeight] = ref);
$frame = $('.onion-skin-frame', view); $frame = $('.onion-skin-frame', view);
$frameAdded = $('.frame.added', view); $frameAdded = $('.frame.added', view);
......
/* eslint-disable func-names, no-underscore-dangle, no-var, one-var, vars-on-top, no-unused-vars, no-shadow, no-cond-assign, prefer-arrow-callback, no-return-assign, no-else-return, camelcase, no-lonely-if, guard-for-in, no-restricted-syntax, consistent-return, prefer-template, no-param-reassign, no-loop-func */ /* eslint-disable func-names, no-underscore-dangle, no-var, one-var, vars-on-top, no-shadow, no-cond-assign, prefer-arrow-callback, no-return-assign, no-else-return, camelcase, no-lonely-if, guard-for-in, no-restricted-syntax, consistent-return, prefer-template, no-param-reassign, no-loop-func */
/* global fuzzaldrinPlus */
import $ from 'jquery'; import $ from 'jquery';
import _ from 'underscore'; import _ from 'underscore';
...@@ -66,12 +65,10 @@ GitLabDropdownInput = (function() { ...@@ -66,12 +65,10 @@ GitLabDropdownInput = (function() {
})(); })();
GitLabDropdownFilter = (function() { GitLabDropdownFilter = (function() {
var ARROW_KEY_CODES, BLUR_KEYCODES, HAS_VALUE_CLASS; var BLUR_KEYCODES, HAS_VALUE_CLASS;
BLUR_KEYCODES = [27, 40]; BLUR_KEYCODES = [27, 40];
ARROW_KEY_CODES = [38, 40];
HAS_VALUE_CLASS = 'has-value'; HAS_VALUE_CLASS = 'has-value';
function GitLabDropdownFilter(input, options) { function GitLabDropdownFilter(input, options) {
...@@ -877,9 +874,8 @@ GitLabDropdown = (function() { ...@@ -877,9 +874,8 @@ GitLabDropdown = (function() {
}; };
GitLabDropdown.prototype.addArrowKeyEvent = function() { GitLabDropdown.prototype.addArrowKeyEvent = function() {
var $input, ARROW_KEY_CODES, selector; var ARROW_KEY_CODES, selector;
ARROW_KEY_CODES = [38, 40]; ARROW_KEY_CODES = [38, 40];
$input = this.dropdown.find('.dropdown-input-field');
selector = SELECTABLE_CLASSES; selector = SELECTABLE_CLASSES;
if (this.dropdown.find('.dropdown-toggle-page').length) { if (this.dropdown.find('.dropdown-toggle-page').length) {
selector = '.dropdown-page-one ' + selector; selector = '.dropdown-page-one ' + selector;
......
/* eslint-disable no-var, one-var, no-unused-vars, consistent-return */ /* eslint-disable no-var, one-var, consistent-return */
import $ from 'jquery'; import $ from 'jquery';
import axios from './lib/utils/axios_utils'; import axios from './lib/utils/axios_utils';
import { addDelimiter } from './lib/utils/text_utility'; import { addDelimiter } from './lib/utils/text_utility';
import flash from './flash'; import flash from './flash';
import TaskList from './task_list';
import CreateMergeRequestDropdown from './create_merge_request_dropdown'; import CreateMergeRequestDropdown from './create_merge_request_dropdown';
import IssuablesHelper from './helpers/issuables_helper'; import IssuablesHelper from './helpers/issuables_helper';
import { __ } from './locale'; import { __ } from './locale';
......
/* eslint-disable class-methods-use-this, no-underscore-dangle, no-param-reassign, no-unused-vars, func-names */ /* eslint-disable class-methods-use-this, no-underscore-dangle, no-param-reassign, func-names */
import $ from 'jquery'; import $ from 'jquery';
import Sortable from 'sortablejs'; import Sortable from 'sortablejs';
...@@ -50,7 +50,7 @@ export default class LabelManager { ...@@ -50,7 +50,7 @@ export default class LabelManager {
$(e.currentTarget).tooltip('hide'); $(e.currentTarget).tooltip('hide');
} }
toggleEmptyState($label, $btn, action) { toggleEmptyState() {
this.emptyState.classList.toggle( this.emptyState.classList.toggle(
'hidden', 'hidden',
Boolean(this.prioritizedLabels[0].querySelector(':scope > li')), Boolean(this.prioritizedLabels[0].querySelector(':scope > li')),
...@@ -61,7 +61,6 @@ export default class LabelManager { ...@@ -61,7 +61,6 @@ export default class LabelManager {
if (persistState == null) { if (persistState == null) {
persistState = true; persistState = true;
} }
const _this = this;
const url = $label.find('.js-toggle-priority').data('url'); const url = $label.find('.js-toggle-priority').data('url');
let $target = this.prioritizedLabels; let $target = this.prioritizedLabels;
let $from = this.otherLabels; let $from = this.otherLabels;
......
/* eslint-disable no-useless-return, func-names, no-var, no-underscore-dangle, prefer-arrow-callback, one-var, no-unused-vars, prefer-template, no-new, consistent-return, object-shorthand, no-shadow, no-param-reassign, vars-on-top, no-lonely-if, no-else-return, dot-notation, no-empty */ /* eslint-disable no-useless-return, func-names, no-var, no-underscore-dangle, prefer-arrow-callback, one-var, prefer-template, no-new, consistent-return, object-shorthand, no-shadow, no-param-reassign, vars-on-top, no-lonely-if, no-else-return, dot-notation, no-empty */
/* global Issuable */ /* global Issuable */
/* global ListLabel */ /* global ListLabel */
...@@ -26,7 +26,6 @@ export default class LabelsSelect { ...@@ -26,7 +26,6 @@ export default class LabelsSelect {
$els.each(function(i, dropdown) { $els.each(function(i, dropdown) {
var $block, var $block,
$colorPreview,
$dropdown, $dropdown,
$form, $form,
$loading, $loading,
...@@ -35,8 +34,6 @@ export default class LabelsSelect { ...@@ -35,8 +34,6 @@ export default class LabelsSelect {
$value, $value,
abilityName, abilityName,
defaultLabel, defaultLabel,
enableLabelCreateButton,
issueURLSplit,
issueUpdateURL, issueUpdateURL,
labelUrl, labelUrl,
namespacePath, namespacePath,
...@@ -47,16 +44,11 @@ export default class LabelsSelect { ...@@ -47,16 +44,11 @@ export default class LabelsSelect {
showNo, showNo,
$sidebarLabelTooltip, $sidebarLabelTooltip,
initialSelected, initialSelected,
$toggleText,
fieldName, fieldName,
useId,
propertyName,
showMenuAbove, showMenuAbove,
$container,
$dropdownContainer; $dropdownContainer;
$dropdown = $(dropdown); $dropdown = $(dropdown);
$dropdownContainer = $dropdown.closest('.labels-filter'); $dropdownContainer = $dropdown.closest('.labels-filter');
$toggleText = $dropdown.find('.dropdown-toggle-text');
namespacePath = $dropdown.data('namespacePath'); namespacePath = $dropdown.data('namespacePath');
projectPath = $dropdown.data('projectPath'); projectPath = $dropdown.data('projectPath');
issueUpdateURL = $dropdown.data('issueUpdate'); issueUpdateURL = $dropdown.data('issueUpdate');
...@@ -77,10 +69,6 @@ export default class LabelsSelect { ...@@ -77,10 +69,6 @@ export default class LabelsSelect {
$value = $block.find('.value'); $value = $block.find('.value');
$loading = $block.find('.block-loading').fadeOut(); $loading = $block.find('.block-loading').fadeOut();
fieldName = $dropdown.data('fieldName'); fieldName = $dropdown.data('fieldName');
useId = $dropdown.is(
'.js-issuable-form-dropdown, .js-filter-bulk-update, .js-label-sidebar-dropdown',
);
propertyName = useId ? 'id' : 'title';
initialSelected = $selectbox initialSelected = $selectbox
.find('input[name="' + $dropdown.data('fieldName') + '"]') .find('input[name="' + $dropdown.data('fieldName') + '"]')
.map(function() { .map(function() {
...@@ -124,7 +112,7 @@ export default class LabelsSelect { ...@@ -124,7 +112,7 @@ export default class LabelsSelect {
axios axios
.put(issueUpdateURL, data) .put(issueUpdateURL, data)
.then(({ data }) => { .then(({ data }) => {
var labelCount, template, labelTooltipTitle, labelTitles, formattedLabels; var labelCount, template, labelTooltipTitle, labelTitles;
$loading.fadeOut(); $loading.fadeOut();
$dropdown.trigger('loaded.gl.dropdown'); $dropdown.trigger('loaded.gl.dropdown');
$selectbox.hide(); $selectbox.hide();
...@@ -246,12 +234,10 @@ export default class LabelsSelect { ...@@ -246,12 +234,10 @@ export default class LabelsSelect {
renderRow: function(label) { renderRow: function(label) {
var linkEl, var linkEl,
listItemEl, listItemEl,
color,
colorEl, colorEl,
indeterminate, indeterminate,
removesAll, removesAll,
selectedClass, selectedClass,
spacing,
i, i,
marked, marked,
dropdownValue; dropdownValue;
...@@ -378,7 +364,7 @@ export default class LabelsSelect { ...@@ -378,7 +364,7 @@ export default class LabelsSelect {
} }
}, },
hidden: function() { hidden: function() {
var isIssueIndex, isMRIndex, page, selectedLabels; var isIssueIndex, isMRIndex, page;
page = $('body').attr('data-page'); page = $('body').attr('data-page');
isIssueIndex = page === 'projects:issues:index'; isIssueIndex = page === 'projects:issues:index';
isMRIndex = page === 'projects:merge_requests:index'; isMRIndex = page === 'projects:merge_requests:index';
...@@ -395,9 +381,6 @@ export default class LabelsSelect { ...@@ -395,9 +381,6 @@ export default class LabelsSelect {
} }
if ($dropdown.hasClass('js-multiselect')) { if ($dropdown.hasClass('js-multiselect')) {
if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) { if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) {
selectedLabels = $dropdown
.closest('form')
.find("input:hidden[name='" + $dropdown.data('fieldName') + "']");
Issuable.filterResults($dropdown.closest('form')); Issuable.filterResults($dropdown.closest('form'));
} else if ($dropdown.hasClass('js-filter-submit')) { } else if ($dropdown.hasClass('js-filter-submit')) {
$dropdown.closest('form').submit(); $dropdown.closest('form').submit();
...@@ -495,7 +478,7 @@ export default class LabelsSelect { ...@@ -495,7 +478,7 @@ export default class LabelsSelect {
} }
} }
}, },
opened: function(e) { opened: function() {
if ($dropdown.hasClass('js-issue-board-sidebar')) { if ($dropdown.hasClass('js-issue-board-sidebar')) {
const previousSelection = $dropdown.attr('data-selected'); const previousSelection = $dropdown.attr('data-selected');
this.selected = previousSelection ? previousSelection.split(',') : []; this.selected = previousSelection ? previousSelection.split(',') : [];
......
/* eslint-disable func-names, no-var, no-param-reassign, one-var, operator-assignment, no-else-return, prefer-template, prefer-arrow-callback, consistent-return, no-unused-vars */ /* eslint-disable func-names, no-var, no-param-reassign, one-var, operator-assignment, no-else-return, prefer-template, prefer-arrow-callback, consistent-return */
import $ from 'jquery'; import $ from 'jquery';
import { insertText } from '~/lib/utils/common_utils'; import { insertText } from '~/lib/utils/common_utils';
...@@ -157,7 +157,7 @@ export function insertMarkdownText({ ...@@ -157,7 +157,7 @@ export function insertMarkdownText({
if (tag === LINK_TAG_PATTERN) { if (tag === LINK_TAG_PATTERN) {
if (URL) { if (URL) {
try { try {
const ignoredUrl = new URL(selected); new URL(selected); // eslint-disable-line no-new
// valid url // valid url
tag = '[text]({text})'; tag = '[text]({text})';
select = 'text'; select = 'text';
......
/* eslint-disable one-var, no-unused-vars, object-shorthand, no-else-return, no-self-compare, consistent-return, no-param-reassign, no-shadow */ /* eslint-disable one-var, object-shorthand, no-else-return, no-self-compare, consistent-return, no-param-reassign, no-shadow */
/* global Issuable */ /* global Issuable */
/* global ListMilestone */ /* global ListMilestone */
...@@ -37,7 +37,6 @@ export default class MilestoneSelect { ...@@ -37,7 +37,6 @@ export default class MilestoneSelect {
selectedMilestone, selectedMilestone,
selectedMilestoneDefault; selectedMilestoneDefault;
const $dropdown = $(dropdown); const $dropdown = $(dropdown);
const projectId = $dropdown.data('projectId');
const milestonesUrl = $dropdown.data('milestones'); const milestonesUrl = $dropdown.data('milestones');
const issueUpdateURL = $dropdown.data('issueUpdate'); const issueUpdateURL = $dropdown.data('issueUpdate');
const showNo = $dropdown.data('showNo'); const showNo = $dropdown.data('showNo');
...@@ -48,7 +47,6 @@ export default class MilestoneSelect { ...@@ -48,7 +47,6 @@ export default class MilestoneSelect {
const useId = $dropdown.data('useId'); const useId = $dropdown.data('useId');
const defaultLabel = $dropdown.data('defaultLabel'); const defaultLabel = $dropdown.data('defaultLabel');
const defaultNo = $dropdown.data('defaultNo'); const defaultNo = $dropdown.data('defaultNo');
const issuableId = $dropdown.data('issuableId');
const abilityName = $dropdown.data('abilityName'); const abilityName = $dropdown.data('abilityName');
const $selectBox = $dropdown.closest('.selectbox'); const $selectBox = $dropdown.closest('.selectbox');
const $block = $selectBox.closest('.block'); const $block = $selectBox.closest('.block');
...@@ -121,7 +119,7 @@ export default class MilestoneSelect { ...@@ -121,7 +119,7 @@ export default class MilestoneSelect {
fields: ['title'], fields: ['title'],
}, },
selectable: true, selectable: true,
toggleLabel: (selected, el, e) => { toggleLabel: (selected, el) => {
if (selected && 'id' in selected && $(el).hasClass('is-active')) { if (selected && 'id' in selected && $(el).hasClass('is-active')) {
return selected.title; return selected.title;
} else { } else {
...@@ -153,7 +151,7 @@ export default class MilestoneSelect { ...@@ -153,7 +151,7 @@ export default class MilestoneSelect {
}, },
vue: $dropdown.hasClass('js-issue-board-sidebar'), vue: $dropdown.hasClass('js-issue-board-sidebar'),
clicked: clickEvent => { clicked: clickEvent => {
const { $el, e } = clickEvent; const { e } = clickEvent;
let selected = clickEvent.selectedObj; let selected = clickEvent.selectedObj;
let data, modalStoreFilter; let data, modalStoreFilter;
......
/* eslint-disable func-names, no-var, one-var, no-loop-func, consistent-return, no-unused-vars, prefer-template, prefer-arrow-callback, camelcase */ /* eslint-disable func-names, no-var, one-var, no-loop-func, consistent-return, prefer-template, prefer-arrow-callback, camelcase */
import $ from 'jquery'; import $ from 'jquery';
import { __ } from '../locale'; import { __ } from '../locale';
import axios from '../lib/utils/axios_utils'; import axios from '../lib/utils/axios_utils';
import flash from '../flash';
import Raphael from './raphael'; import Raphael from './raphael';
export default (function() { export default (function() {
...@@ -104,7 +103,7 @@ export default (function() { ...@@ -104,7 +103,7 @@ export default (function() {
}; };
BranchGraph.prototype.buildGraph = function() { BranchGraph.prototype.buildGraph = function() {
var cuday, cumonth, day, j, len, mm, ref; var cuday, cumonth, day, len, mm, ref;
const { r } = this; const { r } = this;
cuday = 0; cuday = 0;
cumonth = ''; cumonth = '';
...@@ -178,7 +177,7 @@ export default (function() { ...@@ -178,7 +177,7 @@ export default (function() {
return $(element).scroll( return $(element).scroll(
(function(_this) { (function(_this) {
return function(event) { return function() {
return _this.renderPartialGraph(); return _this.renderPartialGraph();
}; };
})(this), })(this),
...@@ -214,7 +213,7 @@ export default (function() { ...@@ -214,7 +213,7 @@ export default (function() {
}; };
BranchGraph.prototype.appendLabel = function(x, y, commit) { BranchGraph.prototype.appendLabel = function(x, y, commit) {
var label, rect, shortrefs, text, textbox, triangle; var label, rect, shortrefs, text, textbox;
if (!commit.refs) { if (!commit.refs) {
return; return;
...@@ -239,7 +238,8 @@ export default (function() { ...@@ -239,7 +238,8 @@ export default (function() {
'fill-opacity': 0.5, 'fill-opacity': 0.5,
stroke: 'none', stroke: 'none',
}); });
triangle = r.path(['M', x - 5, y, 'L', x - 15, y - 4, 'L', x - 15, y + 4, 'Z']).attr({ // Generate the triangle right of the tag box
r.path(['M', x - 5, y, 'L', x - 15, y - 4, 'L', x - 15, y + 4, 'Z']).attr({
fill: '#000', fill: '#000',
'fill-opacity': 0.5, 'fill-opacity': 0.5,
stroke: 'none', stroke: 'none',
......
...@@ -2,10 +2,9 @@ ...@@ -2,10 +2,9 @@
no-unused-expressions, one-var, default-case, no-unused-expressions, one-var, default-case,
prefer-template, consistent-return, no-alert, no-return-assign, prefer-template, consistent-return, no-alert, no-return-assign,
no-param-reassign, prefer-arrow-callback, no-else-return, vars-on-top, no-param-reassign, prefer-arrow-callback, no-else-return, vars-on-top,
no-unused-vars, no-shadow, no-useless-escape, class-methods-use-this */ no-shadow, no-useless-escape, class-methods-use-this */
/* global ResolveService */ /* global ResolveService */
/* global mrRefreshWidgetUrl */
/* /*
old_notes_spec.js is the spec for the legacy, jQuery notes application. It has nothing to do with the new, fancy Vue notes app. old_notes_spec.js is the spec for the legacy, jQuery notes application. It has nothing to do with the new, fancy Vue notes app.
...@@ -37,7 +36,6 @@ import { ...@@ -37,7 +36,6 @@ import {
isMetaKey, isMetaKey,
isInMRPage, isInMRPage,
} from './lib/utils/common_utils'; } from './lib/utils/common_utils';
import imageDiffHelper from './image_diff/helpers/index';
import { localTimeAgo } from './lib/utils/datetime_utility'; import { localTimeAgo } from './lib/utils/datetime_utility';
import { sprintf, s__, __ } from './locale'; import { sprintf, s__, __ } from './locale';
...@@ -683,7 +681,7 @@ export default class Notes { ...@@ -683,7 +681,7 @@ export default class Notes {
); );
} }
updateNoteError($parentTimeline) { updateNoteError() {
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
new Flash( new Flash(
__('Your comment could not be updated! Please check your network connection and try again.'), __('Your comment could not be updated! Please check your network connection and try again.'),
...@@ -697,7 +695,6 @@ export default class Notes { ...@@ -697,7 +695,6 @@ export default class Notes {
*/ */
addDiscussionNote($form, note, isNewDiffComment) { addDiscussionNote($form, note, isNewDiffComment) {
if ($form.attr('data-resolve-all') != null) { if ($form.attr('data-resolve-all') != null) {
var projectPath = $form.data('projectPath');
var discussionId = $form.data('discussionId'); var discussionId = $form.data('discussionId');
var mergeRequestId = $form.data('noteableIid'); var mergeRequestId = $form.data('noteableIid');
...@@ -746,7 +743,6 @@ export default class Notes { ...@@ -746,7 +743,6 @@ export default class Notes {
if (currentContent === initialContent) { if (currentContent === initialContent) {
this.removeNoteEditForm($el); this.removeNoteEditForm($el);
} else { } else {
var $buttons = $el.find('.note-form-actions');
var isWidgetVisible = isInViewport($el.get(0)); var isWidgetVisible = isInViewport($el.get(0));
if (!isWidgetVisible) { if (!isWidgetVisible) {
...@@ -766,7 +762,7 @@ export default class Notes { ...@@ -766,7 +762,7 @@ export default class Notes {
* Replaces the note text with the note edit form * Replaces the note text with the note edit form
* Adds a data attribute to the form with the original content of the note for cancellations * Adds a data attribute to the form with the original content of the note for cancellations
*/ */
showEditForm(e, scrollTo, myLastNote) { showEditForm(e) {
e.preventDefault(); e.preventDefault();
var $target = $(e.target); var $target = $(e.target);
...@@ -850,16 +846,11 @@ export default class Notes { ...@@ -850,16 +846,11 @@ export default class Notes {
* Removes the whole discussion if the last note is being removed. * Removes the whole discussion if the last note is being removed.
*/ */
removeNote(e) { removeNote(e) {
var noteElId, noteId, dataNoteId, $note, lineHolder; var noteElId, $note;
$note = $(e.currentTarget).closest('.note'); $note = $(e.currentTarget).closest('.note');
noteElId = $note.attr('id'); noteElId = $note.attr('id');
noteId = $note.attr('data-note-id');
lineHolder = $(e.currentTarget)
.closest('.notes[data-discussion-id]')
.closest('.notes_holder')
.prev('.line_holder');
$(`.note[id="${noteElId}"]`).each( $(`.note[id="${noteElId}"]`).each(
(function(_this) { (function() {
// A same note appears in the "Discussion" and in the "Changes" tab, we have // A same note appears in the "Discussion" and in the "Changes" tab, we have
// to remove all. Using $('.note[id='noteId']') ensure we get all the notes, // to remove all. Using $('.note[id='noteId']') ensure we get all the notes,
// where $('#noteId') would return only one. // where $('#noteId') would return only one.
...@@ -1064,25 +1055,8 @@ export default class Notes { ...@@ -1064,25 +1055,8 @@ export default class Notes {
this.setupDiscussionNoteForm($link, newForm); this.setupDiscussionNoteForm($link, newForm);
} }
toggleDiffNote({ toggleDiffNote({ target, lineType, forceShow, showReplyInput = false }) {
target, var $link, addForm, hasNotes, newForm, noteForm, replyButton, row, rowCssToAdd;
lineType,
forceShow,
showReplyInput = false,
currentUsername,
currentUserAvatar,
currentUserFullname,
}) {
var $link,
addForm,
hasNotes,
newForm,
noteForm,
replyButton,
row,
rowCssToAdd,
targetContent,
isDiffCommentAvatar;
$link = $(target); $link = $(target);
row = $link.closest('tr'); row = $link.closest('tr');
const nextRow = row.next(); const nextRow = row.next();
...@@ -1515,7 +1489,7 @@ export default class Notes { ...@@ -1515,7 +1489,7 @@ export default class Notes {
let tempFormContent; let tempFormContent;
// Identify executed quick actions from `formContent` // Identify executed quick actions from `formContent`
const executedCommands = availableQuickActions.filter((command, index) => { const executedCommands = availableQuickActions.filter(command => {
const commandRegex = new RegExp(`/${command.name}`); const commandRegex = new RegExp(`/${command.name}`);
return commandRegex.test(formContent); return commandRegex.test(formContent);
}); });
...@@ -1840,8 +1814,6 @@ export default class Notes { ...@@ -1840,8 +1814,6 @@ export default class Notes {
const $noteBody = $editingNote.find('.js-task-list-container'); const $noteBody = $editingNote.find('.js-task-list-container');
const $noteBodyText = $noteBody.find('.note-text'); const $noteBodyText = $noteBody.find('.note-text');
const { formData, formContent, formAction } = this.getFormData($form); const { formData, formContent, formAction } = this.getFormData($form);
const $diffFile = $form.closest('.diff-file');
const $notesContainer = $form.closest('.notes');
// Cache original comment content // Cache original comment content
const cachedNoteBodyText = $noteBodyText.html(); const cachedNoteBodyText = $noteBodyText.html();
......
/* eslint-disable func-names, object-shorthand, no-var, one-var, camelcase, no-param-reassign, no-return-assign, prefer-arrow-callback, consistent-return, no-unused-vars, no-cond-assign, no-else-return */ /* eslint-disable func-names, object-shorthand, no-var, one-var, camelcase, no-param-reassign, no-return-assign, prefer-arrow-callback, consistent-return, no-cond-assign, no-else-return */
import _ from 'underscore'; import _ from 'underscore';
export default { export default {
...@@ -126,7 +126,7 @@ export default { ...@@ -126,7 +126,7 @@ export default {
_.each( _.each(
_.omit(log_entry, 'author_name', 'author_email'), _.omit(log_entry, 'author_name', 'author_email'),
(function(_this) { (function(_this) {
return function(value, key) { return function(value) {
if (_this.in_range(value.date, date_range)) { if (_this.in_range(value.date, date_range)) {
parsed_entry.dates[value.date] = value[field]; parsed_entry.dates[value.date] = value[field];
parsed_entry.commits += value.commits; parsed_entry.commits += value.commits;
......
/* eslint-disable no-useless-escape, no-var, no-underscore-dangle, func-names, no-unused-vars, no-return-assign, object-shorthand, one-var, consistent-return, class-methods-use-this */ /* eslint-disable no-useless-escape, no-var, no-underscore-dangle, func-names, no-return-assign, object-shorthand, one-var, consistent-return, class-methods-use-this */
import $ from 'jquery'; import $ from 'jquery';
import 'cropper'; import 'cropper';
import _ from 'underscore'; import _ from 'underscore';
(global => { (() => {
// Matches everything but the file name // Matches everything but the file name
const FILENAMEREGEX = /^.*[\\\/]/; const FILENAMEREGEX = /^.*[\\\/]/;
...@@ -69,7 +69,7 @@ import _ from 'underscore'; ...@@ -69,7 +69,7 @@ import _ from 'underscore';
this.modalCrop.on('shown.bs.modal', this.onModalShow); this.modalCrop.on('shown.bs.modal', this.onModalShow);
this.modalCrop.on('hidden.bs.modal', this.onModalHide); this.modalCrop.on('hidden.bs.modal', this.onModalHide);
this.uploadImageBtn.on('click', this.onUploadImageBtnClick); this.uploadImageBtn.on('click', this.onUploadImageBtnClick);
this.cropActionsBtn.on('click', function(e) { this.cropActionsBtn.on('click', function() {
var btn; var btn;
btn = this; btn = this;
return _this.onActionBtnClick(btn); return _this.onActionBtnClick(btn);
...@@ -128,10 +128,10 @@ import _ from 'underscore'; ...@@ -128,10 +128,10 @@ import _ from 'underscore';
} }
onActionBtnClick(btn) { onActionBtnClick(btn) {
var data, result; var data;
data = $(btn).data(); data = $(btn).data();
if (this.modalCropImg.data('cropper') && data.method) { if (this.modalCropImg.data('cropper') && data.method) {
return (result = this.modalCropImg.cropper(data.method, data.option)); return this.modalCropImg.cropper(data.method, data.option);
} }
} }
...@@ -151,12 +151,11 @@ import _ from 'underscore'; ...@@ -151,12 +151,11 @@ import _ from 'underscore';
} }
dataURLtoBlob(dataURL) { dataURLtoBlob(dataURL) {
var array, binary, i, len, v; var array, binary, i, len;
binary = atob(dataURL.split(',')[1]); binary = atob(dataURL.split(',')[1]);
array = []; array = [];
for (i = 0, len = binary.length; i < len; i += 1) { for (i = 0, len = binary.length; i < len; i += 1) {
v = binary[i];
array.push(binary.charCodeAt(i)); array.push(binary.charCodeAt(i));
} }
return new Blob([new Uint8Array(array)], { return new Blob([new Uint8Array(array)], {
......
/* eslint-disable func-names, no-var, consistent-return, one-var, no-cond-assign, prefer-template, no-unused-vars, no-return-assign */ /* eslint-disable func-names, no-var, consistent-return, one-var, no-cond-assign, prefer-template, no-return-assign */
import $ from 'jquery'; import $ from 'jquery';
import fuzzaldrinPlus from 'fuzzaldrin-plus'; import fuzzaldrinPlus from 'fuzzaldrin-plus';
...@@ -8,9 +8,8 @@ import { __ } from '~/locale'; ...@@ -8,9 +8,8 @@ import { __ } from '~/locale';
// highlight text(awefwbwgtc -> <b>a</b>wefw<b>b</b>wgt<b>c</b> ) // highlight text(awefwbwgtc -> <b>a</b>wefw<b>b</b>wgt<b>c</b> )
const highlighter = function(element, text, matches) { const highlighter = function(element, text, matches) {
var highlightText, j, lastIndex, len, matchIndex, matchedChars, unmatched; var j, lastIndex, len, matchIndex, matchedChars, unmatched;
lastIndex = 0; lastIndex = 0;
highlightText = '';
matchedChars = []; matchedChars = [];
for (j = 0, len = matches.length; j < len; j += 1) { for (j = 0, len = matches.length; j < len; j += 1) {
matchIndex = matches[j]; matchIndex = matches[j];
......
/* eslint-disable func-names, no-var, no-unused-vars, consistent-return, one-var, prefer-template, no-else-return, no-param-reassign */ /* eslint-disable func-names, no-var, consistent-return, one-var, prefer-template, no-else-return, no-param-reassign */
import $ from 'jquery'; import $ from 'jquery';
import _ from 'underscore'; import _ from 'underscore';
...@@ -7,7 +7,7 @@ import flash from './flash'; ...@@ -7,7 +7,7 @@ import flash from './flash';
import axios from './lib/utils/axios_utils'; import axios from './lib/utils/axios_utils';
import { sprintf, s__, __ } from './locale'; import { sprintf, s__, __ } from './locale';
function Sidebar(currentUser) { function Sidebar() {
this.toggleTodo = this.toggleTodo.bind(this); this.toggleTodo = this.toggleTodo.bind(this);
this.sidebar = $('aside'); this.sidebar = $('aside');
...@@ -15,9 +15,9 @@ function Sidebar(currentUser) { ...@@ -15,9 +15,9 @@ function Sidebar(currentUser) {
this.addEventListeners(); this.addEventListeners();
} }
Sidebar.initialize = function(currentUser) { Sidebar.initialize = function() {
if (!this.instance) { if (!this.instance) {
this.instance = new Sidebar(currentUser); this.instance = new Sidebar();
} }
}; };
...@@ -77,7 +77,7 @@ Sidebar.prototype.sidebarToggleClicked = function(e, triggered) { ...@@ -77,7 +77,7 @@ Sidebar.prototype.sidebarToggleClicked = function(e, triggered) {
}; };
Sidebar.prototype.toggleTodo = function(e) { Sidebar.prototype.toggleTodo = function(e) {
var $btnText, $this, $todoLoading, ajaxType, url; var $this, ajaxType, url;
$this = $(e.currentTarget); $this = $(e.currentTarget);
ajaxType = $this.data('deletePath') ? 'delete' : 'post'; ajaxType = $this.data('deletePath') ? 'delete' : 'post';
...@@ -140,7 +140,7 @@ Sidebar.prototype.todoUpdateDone = function(data) { ...@@ -140,7 +140,7 @@ Sidebar.prototype.todoUpdateDone = function(data) {
}); });
}; };
Sidebar.prototype.sidebarDropdownLoading = function(e) { Sidebar.prototype.sidebarDropdownLoading = function() {
var $loading, $sidebarCollapsedIcon, i, img; var $loading, $sidebarCollapsedIcon, i, img;
$sidebarCollapsedIcon = $(this) $sidebarCollapsedIcon = $(this)
.closest('.block') .closest('.block')
...@@ -157,7 +157,7 @@ Sidebar.prototype.sidebarDropdownLoading = function(e) { ...@@ -157,7 +157,7 @@ Sidebar.prototype.sidebarDropdownLoading = function(e) {
} }
}; };
Sidebar.prototype.sidebarDropdownLoaded = function(e) { Sidebar.prototype.sidebarDropdownLoaded = function() {
var $sidebarCollapsedIcon, i, img; var $sidebarCollapsedIcon, i, img;
$sidebarCollapsedIcon = $(this) $sidebarCollapsedIcon = $(this)
.closest('.block') .closest('.block')
......
/* eslint-disable no-return-assign, one-var, no-var, no-unused-vars, consistent-return, object-shorthand, prefer-template, class-methods-use-this, no-lonely-if, vars-on-top */ /* eslint-disable no-return-assign, one-var, no-var, consistent-return, object-shorthand, prefer-template, class-methods-use-this, no-lonely-if, vars-on-top */
import $ from 'jquery'; import $ from 'jquery';
import { escape, throttle } from 'underscore'; import { escape, throttle } from 'underscore';
import { s__, __, sprintf } from '~/locale'; import { s__, __ } from '~/locale';
import { getIdenticonBackgroundClass, getIdenticonTitle } from '~/helpers/avatar_helper'; import { getIdenticonBackgroundClass, getIdenticonTitle } from '~/helpers/avatar_helper';
import axios from './lib/utils/axios_utils'; import axios from './lib/utils/axios_utils';
import DropdownUtils from './filtered_search/dropdown_utils';
import { import {
isInGroupsPage, isInGroupsPage,
isInProjectPage, isInProjectPage,
...@@ -142,7 +141,7 @@ export class SearchAutocomplete { ...@@ -142,7 +141,7 @@ export class SearchAutocomplete {
}); });
} }
getSearchText(selectedObject, el) { getSearchText(selectedObject) {
return selectedObject.id ? selectedObject.text : ''; return selectedObject.id ? selectedObject.text : '';
} }
...@@ -402,7 +401,7 @@ export class SearchAutocomplete { ...@@ -402,7 +401,7 @@ export class SearchAutocomplete {
return this.searchInput.val('').focus(); return this.searchInput.val('').focus();
} }
onSearchInputBlur(e) { onSearchInputBlur() {
this.isFocused = false; this.isFocused = false;
this.wrap.removeClass('search-active'); this.wrap.removeClass('search-active');
// If input is blank then restore state // If input is blank then restore state
......
/* eslint-disable func-names, one-var, no-var, prefer-rest-params, vars-on-top, prefer-arrow-callback, consistent-return, object-shorthand, no-shadow, no-unused-vars, no-else-return, no-self-compare, prefer-template, no-unused-expressions, yoda, prefer-spread, camelcase, no-param-reassign */ /* eslint-disable func-names, one-var, no-var, prefer-rest-params, vars-on-top, prefer-arrow-callback, consistent-return, object-shorthand, no-shadow, no-else-return, no-self-compare, prefer-template, no-unused-expressions, yoda, prefer-spread, camelcase, no-param-reassign */
/* global Issuable */ /* global Issuable */
/* global emitSidebarEvent */ /* global emitSidebarEvent */
...@@ -405,7 +405,7 @@ function UsersSelect(currentUser, els, options = {}) { ...@@ -405,7 +405,7 @@ function UsersSelect(currentUser, els, options = {}) {
} }
}, },
defaultLabel: defaultLabel, defaultLabel: defaultLabel,
hidden: function(e) { hidden: function() {
if ($dropdown.hasClass('js-multiselect')) { if ($dropdown.hasClass('js-multiselect')) {
emitSidebarEvent('sidebar.saveAssignees'); emitSidebarEvent('sidebar.saveAssignees');
} }
...@@ -442,7 +442,6 @@ function UsersSelect(currentUser, els, options = {}) { ...@@ -442,7 +442,6 @@ function UsersSelect(currentUser, els, options = {}) {
if (user.beforeDivider && user.name.toLowerCase() === 'unassigned') { if (user.beforeDivider && user.name.toLowerCase() === 'unassigned') {
// Unassigned selected // Unassigned selected
previouslySelected.each((index, element) => { previouslySelected.each((index, element) => {
const id = parseInt(element.value, 10);
element.remove(); element.remove();
}); });
emitSidebarEvent('sidebar.removeAllAssignees'); emitSidebarEvent('sidebar.removeAllAssignees');
...@@ -548,7 +547,7 @@ function UsersSelect(currentUser, els, options = {}) { ...@@ -548,7 +547,7 @@ function UsersSelect(currentUser, els, options = {}) {
}, },
updateLabel: $dropdown.data('dropdownTitle'), updateLabel: $dropdown.data('dropdownTitle'),
renderRow: function(user) { renderRow: function(user) {
var avatar, img, listClosingTags, listWithName, listWithUserName, username; var avatar, img, username;
username = user.username ? '@' + user.username : ''; username = user.username ? '@' + user.username : '';
avatar = user.avatar_url ? user.avatar_url : gon.default_avatar_url; avatar = user.avatar_url ? user.avatar_url : gon.default_avatar_url;
......
/* eslint-disable func-names, prefer-arrow-callback, no-unused-vars, consistent-return, camelcase, class-methods-use-this */ /* eslint-disable func-names, prefer-arrow-callback, consistent-return, camelcase, class-methods-use-this */
// Zen Mode (full screen) textarea // Zen Mode (full screen) textarea
// //
...@@ -62,7 +62,7 @@ export default class ZenMode { ...@@ -62,7 +62,7 @@ export default class ZenMode {
$(document).on( $(document).on(
'zen_mode:leave', 'zen_mode:leave',
(function(_this) { (function(_this) {
return function(e) { return function() {
return _this.exit(); return _this.exit();
}; };
})(this), })(this),
......
...@@ -95,7 +95,7 @@ module Ci ...@@ -95,7 +95,7 @@ module Ci
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def auto_cancelable_pipelines def auto_cancelable_pipelines
# TODO: Introduced by https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/23464 # TODO: Introduced by https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/23464
if Feature.enabled?(:ci_support_interruptible_pipelines, project, default_enabled: true) if Feature.enabled?(:ci_support_interruptible_pipelines, project, default_enabled: false)
project.ci_pipelines project.ci_pipelines
.where(ref: pipeline.ref) .where(ref: pipeline.ref)
.where.not(id: pipeline.id) .where.not(id: pipeline.id)
......
# frozen_string_literal: true # frozen_string_literal: true
class ServiceResponse class ServiceResponse
def self.success(message: nil, payload: {}) def self.success(message: nil, payload: {}, http_status: :ok)
new(status: :success, message: message, payload: payload) new(status: :success, message: message, payload: payload, http_status: http_status)
end end
def self.error(message:, payload: {}, http_status: nil) def self.error(message:, payload: {}, http_status: nil)
......
...@@ -29,4 +29,4 @@ ...@@ -29,4 +29,4 @@
= yield :push_access_levels = yield :push_access_levels
.card-footer .card-footer
= f.submit 'Protect', class: 'btn-success btn', disabled: true = f.submit 'Protect', class: 'btn-success btn', disabled: true, data: { qa_selector: 'protect_button' }
#!/usr/bin/env ruby
require 'rubygems'
require 'bundler/setup'
require 'json'
require 'active_model'
require 'active_support'
require 'active_support/core_ext'
require 'benchmark'
require 'charlock_holmes'
$: << File.expand_path('../lib', __dir__)
$: << File.expand_path('../ee/lib', __dir__)
require 'open3'
require 'rugged'
require 'gitlab/blob_helper'
require 'gitlab/elastic/client'
require 'elasticsearch/model'
require 'elasticsearch/git'
require 'elasticsearch/git/encoder_helper'
require 'elasticsearch/git/lite_blob'
require 'elasticsearch/git/model'
require 'elasticsearch/git/repository'
Thread.abort_on_exception = true
path_to_log_file = File.expand_path('../log/es-indexer.log', __dir__)
LOGGER = Logger.new(path_to_log_file)
PROJECT_ID = ARGV.shift
REPO_PATH = ARGV.shift
FROM_SHA = ENV['FROM_SHA']
TO_SHA = ENV['TO_SHA']
RAILS_ENV = ENV['RAILS_ENV']
# Symbols get stringified when passed through JSON
elastic = {}
JSON.parse(ENV['ELASTIC_CONNECTION_INFO']).each { |k, v| elastic[k.to_sym] = v }
ELASTIC_CONFIG = elastic
LOGGER.info("Has been scheduled for project #{REPO_PATH} with SHA range #{FROM_SHA}:#{TO_SHA}")
class Repository
include Elasticsearch::Git::Repository
index_name ['gitlab', RAILS_ENV].compact.join('-')
def initialize
self.__elasticsearch__.client = ::Gitlab::Elastic::Client.build(ELASTIC_CONFIG)
end
def client_for_indexing
self.__elasticsearch__.client
end
def repository_id
PROJECT_ID
end
def project_id
PROJECT_ID
end
def path_to_repo
REPO_PATH
end
end
repo = Repository.new
params = { from_rev: FROM_SHA, to_rev: TO_SHA }.compact
commit_thr = Thread.new do
LOGGER.info("Indexing commits started")
timings = Benchmark.measure do
indexed = 0
repo.index_commits(params) do |batch, total_count|
indexed += batch.length
LOGGER.info("Indexed #{indexed}/#{total_count} commits")
end
end
LOGGER.info("Commits for #{REPO_PATH} are indexed. Time elapsed: #{timings.real}")
end
LOGGER.info("Indexing blobs started")
timings = Benchmark.measure do
indexed = 0
repo.index_blobs(params) do |batch, total_count|
indexed += batch.length
LOGGER.info("Indexed #{indexed}/#{total_count} blobs")
end
end
LOGGER.info("Blobs for #{REPO_PATH} are indexed. Time elapsed: #{timings.real}")
commit_thr.join
---
title: Avoid Devise "401 Unauthorized" responses
merge_request: 16519
author:
type: fixed
...@@ -982,6 +982,10 @@ production: &base ...@@ -982,6 +982,10 @@ production: &base
# Default is '.gitlab_workhorse_secret' relative to Rails.root (i.e. root of the GitLab app). # Default is '.gitlab_workhorse_secret' relative to Rails.root (i.e. root of the GitLab app).
# secret_file: /home/git/gitlab/.gitlab_workhorse_secret # secret_file: /home/git/gitlab/.gitlab_workhorse_secret
## GitLab Elasticsearch settings
elasticsearch:
indexer_path: /home/git/gitlab-elasticsearch-indexer/
## Git settings ## Git settings
# CAUTION! # CAUTION!
# Use the default values unless you really know what you are doing # Use the default values unless you really know what you are doing
......
...@@ -214,11 +214,9 @@ Devise.setup do |config| ...@@ -214,11 +214,9 @@ Devise.setup do |config|
# If you want to use other strategies, that are not supported by Devise, or # If you want to use other strategies, that are not supported by Devise, or
# change the failure app, you can configure them inside the config.warden block. # change the failure app, you can configure them inside the config.warden block.
# #
# config.warden do |manager| config.warden do |manager|
# manager.failure_app = Gitlab::DeviseFailure manager.failure_app = Gitlab::DeviseFailure
# manager.intercept_401 = false end
# manager.default_strategies(scope: :user).unshift :some_external_strategy
# end
if Gitlab::Auth::LDAP::Config.enabled? if Gitlab::Auth::LDAP::Config.enabled?
Gitlab::Auth::LDAP::Config.providers.each do |provider| Gitlab::Auth::LDAP::Config.providers.each do |provider|
......
...@@ -37,6 +37,10 @@ class EmojiChecker ...@@ -37,6 +37,10 @@ class EmojiChecker
end end
end end
def gitlab_danger
@gitlab_danger ||= GitlabDanger.new(helper.gitlab_helper)
end
def fail_commit(commit, message) def fail_commit(commit, message)
fail("#{commit.sha}: #{message}") fail("#{commit.sha}: #{message}")
end end
...@@ -56,6 +60,8 @@ def subject_starts_with_capital?(subject) ...@@ -56,6 +60,8 @@ def subject_starts_with_capital?(subject)
end end
def ce_upstream? def ce_upstream?
return unless gitlab_danger.ci?
gitlab.mr_labels.any? { |label| label == 'CE upstream' } gitlab.mr_labels.any? { |label| label == 'CE upstream' }
end end
...@@ -88,8 +94,8 @@ def lint_commit(commit) # rubocop:disable Metrics/AbcSize ...@@ -88,8 +94,8 @@ def lint_commit(commit) # rubocop:disable Metrics/AbcSize
# We ignore revert commits as they are well structured by Git already # We ignore revert commits as they are well structured by Git already
return false if commit.message.start_with?('Revert "') return false if commit.message.start_with?('Revert "')
is_squash = gitlab.mr_json['squash'] is_squash = gitlab_danger.ci? ? gitlab.mr_json['squash'] : false
is_wip = gitlab.mr_json['work_in_progress'] is_wip = gitlab_danger.ci? ? gitlab.mr_json['work_in_progress'] : false
is_fixup = commit.message.start_with?('fixup!', 'squash!') is_fixup = commit.message.start_with?('fixup!', 'squash!')
if is_fixup if is_fixup
......
# frozen_string_literal: true
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddAnyApproverRuleUniqueIndexes < ActiveRecord::Migration[5.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
PROJECT_RULE_UNIQUE_INDEX = 'any_approver_project_rule_type_unique_index'
MERGE_REQUEST_RULE_UNIQUE_INDEX = 'any_approver_merge_request_rule_type_unique_index'
disable_ddl_transaction!
def up
add_concurrent_index(:approval_project_rules, [:project_id],
where: "rule_type = 3",
name: PROJECT_RULE_UNIQUE_INDEX, unique: true)
add_concurrent_index(:approval_merge_request_rules, [:merge_request_id, :rule_type],
where: "rule_type = 4",
name: MERGE_REQUEST_RULE_UNIQUE_INDEX, unique: true)
end
def down
remove_concurrent_index_by_name(:approval_project_rules, PROJECT_RULE_UNIQUE_INDEX)
remove_concurrent_index_by_name(:approval_merge_request_rules, MERGE_REQUEST_RULE_UNIQUE_INDEX)
end
end
# frozen_string_literal: true
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class ScheduleProjectAnyApprovalRuleMigration < ActiveRecord::Migration[5.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
BATCH_SIZE = 5_000
MIGRATION = 'PopulateAnyApprovalRuleForProjects'
DELAY_INTERVAL = 8.minutes.to_i
disable_ddl_transaction!
class Project < ActiveRecord::Base
include EachBatch
self.table_name = 'projects'
scope :with_approvals_before_merge, -> { where('approvals_before_merge <> 0') }
end
def up
add_concurrent_index :projects, :id,
name: 'tmp_projects_with_approvals_before_merge',
where: 'approvals_before_merge <> 0'
say "Scheduling `#{MIGRATION}` jobs"
# We currently have ~43k project records with non-zero approvals_before_merge on GitLab.com.
# This means it'll schedule ~9 jobs (5k projects each) with a 8 minutes gap,
# so this should take ~1 hour for all background migrations to complete.
#
# The approximate expected number of affected rows is: 18k
queue_background_migration_jobs_by_range_at_intervals(
ScheduleProjectAnyApprovalRuleMigration::Project.with_approvals_before_merge,
MIGRATION, DELAY_INTERVAL, batch_size: BATCH_SIZE)
remove_concurrent_index_by_name(:projects, 'tmp_projects_with_approvals_before_merge')
end
def down
# no-op
end
end
# frozen_string_literal: true
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class ScheduleMergeRequestAnyApprovalRuleMigration < ActiveRecord::Migration[5.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
BATCH_SIZE = 5_000
MIGRATION = 'PopulateAnyApprovalRuleForMergeRequests'
DELAY_INTERVAL = 8.minutes.to_i
disable_ddl_transaction!
class MergeRequest < ActiveRecord::Base
include EachBatch
self.table_name = 'merge_requests'
scope :with_approvals_before_merge, -> { where('approvals_before_merge <> 0') }
end
def up
add_concurrent_index :merge_requests, :id,
name: 'tmp_merge_requests_with_approvals_before_merge',
where: 'approvals_before_merge <> 0'
say "Scheduling `#{MIGRATION}` jobs"
# We currently have ~440_000 merge request records with non-zero approvals_before_merge on GitLab.com.
# This means it'll schedule ~88 jobs (5k merge requests each) with a 8 minutes gap,
# so this should take ~12 hours for all background migrations to complete.
#
# The approximate expected number of affected rows is: 190k
queue_background_migration_jobs_by_range_at_intervals(
ScheduleMergeRequestAnyApprovalRuleMigration::MergeRequest.with_approvals_before_merge,
MIGRATION, DELAY_INTERVAL, batch_size: BATCH_SIZE)
remove_concurrent_index_by_name(:merge_requests, 'tmp_merge_requests_with_approvals_before_merge')
end
def down
# no-op
end
end
...@@ -319,8 +319,9 @@ ActiveRecord::Schema.define(version: 2019_09_12_061145) do ...@@ -319,8 +319,9 @@ ActiveRecord::Schema.define(version: 2019_09_12_061145) do
t.integer "report_type", limit: 2 t.integer "report_type", limit: 2
t.index ["merge_request_id", "code_owner", "name"], name: "approval_rule_name_index_for_code_owners", unique: true, where: "(code_owner = true)" t.index ["merge_request_id", "code_owner", "name"], name: "approval_rule_name_index_for_code_owners", unique: true, where: "(code_owner = true)"
t.index ["merge_request_id", "code_owner"], name: "index_approval_merge_request_rules_1" t.index ["merge_request_id", "code_owner"], name: "index_approval_merge_request_rules_1"
t.index ["merge_request_id", "rule_type", "name"], name: "index_approval_rule_name_for_code_owners_rule_type", unique: true, where: "(rule_type = 2)" t.index ["merge_request_id", "name"], name: "index_approval_rule_name_for_code_owners_rule_type", unique: true, where: "(rule_type = 2)"
t.index ["merge_request_id", "rule_type"], name: "index_approval_rules_code_owners_rule_type", where: "(rule_type = 2)" t.index ["merge_request_id", "rule_type"], name: "any_approver_merge_request_rule_type_unique_index", unique: true, where: "(rule_type = 4)"
t.index ["merge_request_id"], name: "index_approval_rules_code_owners_rule_type", where: "(rule_type = 2)"
end end
create_table "approval_merge_request_rules_approved_approvers", force: :cascade do |t| create_table "approval_merge_request_rules_approved_approvers", force: :cascade do |t|
...@@ -351,6 +352,7 @@ ActiveRecord::Schema.define(version: 2019_09_12_061145) do ...@@ -351,6 +352,7 @@ ActiveRecord::Schema.define(version: 2019_09_12_061145) do
t.integer "approvals_required", limit: 2, default: 0, null: false t.integer "approvals_required", limit: 2, default: 0, null: false
t.string "name", null: false t.string "name", null: false
t.integer "rule_type", limit: 2, default: 0, null: false t.integer "rule_type", limit: 2, default: 0, null: false
t.index ["project_id"], name: "any_approver_project_rule_type_unique_index", unique: true, where: "(rule_type = 3)"
t.index ["project_id"], name: "index_approval_project_rules_on_project_id" t.index ["project_id"], name: "index_approval_project_rules_on_project_id"
t.index ["rule_type"], name: "index_approval_project_rules_on_rule_type" t.index ["rule_type"], name: "index_approval_project_rules_on_rule_type"
end end
......
...@@ -220,7 +220,6 @@ are listed in the descriptions of the relevant settings. ...@@ -220,7 +220,6 @@ are listed in the descriptions of the relevant settings.
| `elasticsearch_aws` | boolean | no | **(PREMIUM)** Enable the use of AWS hosted Elasticsearch | | `elasticsearch_aws` | boolean | no | **(PREMIUM)** Enable the use of AWS hosted Elasticsearch |
| `elasticsearch_aws_region` | string | no | **(PREMIUM)** The AWS region the elasticsearch domain is configured | | `elasticsearch_aws_region` | string | no | **(PREMIUM)** The AWS region the elasticsearch domain is configured |
| `elasticsearch_aws_secret_access_key` | string | no | **(PREMIUM)** AWS IAM secret access key | | `elasticsearch_aws_secret_access_key` | string | no | **(PREMIUM)** AWS IAM secret access key |
| `elasticsearch_experimental_indexer` | boolean | no | **(PREMIUM)** Use the experimental elasticsearch indexer. More info: <https://gitlab.com/gitlab-org/gitlab-elasticsearch-indexer> |
| `elasticsearch_indexing` | boolean | no | **(PREMIUM)** Enable Elasticsearch indexing | | `elasticsearch_indexing` | boolean | no | **(PREMIUM)** Enable Elasticsearch indexing |
| `elasticsearch_limit_indexing` | boolean | no | **(PREMIUM)** Limit Elasticsearch to index certain namespaces and projects | | `elasticsearch_limit_indexing` | boolean | no | **(PREMIUM)** Limit Elasticsearch to index certain namespaces and projects |
| `elasticsearch_namespace_ids` | array of integers | no | **(PREMIUM)** The namespaces to index via Elasticsearch if `elasticsearch_limit_indexing` is enabled. | | `elasticsearch_namespace_ids` | array of integers | no | **(PREMIUM)** The namespaces to index via Elasticsearch if `elasticsearch_limit_indexing` is enabled. |
......
...@@ -59,7 +59,7 @@ Additionally, if you need large repos or multiple forks for testing, please cons ...@@ -59,7 +59,7 @@ Additionally, if you need large repos or multiple forks for testing, please cons
## How does it work? ## How does it work?
The Elasticsearch integration depends on an external indexer. We ship a [ruby indexer](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/bin/elastic_repo_indexer) by default but are also working on an [indexer written in Go](https://gitlab.com/gitlab-org/gitlab-elasticsearch-indexer). The user must trigger the initial indexing via a rake task, but after this is done GitLab itself will trigger reindexing when required via `after_` callbacks on create, update, and destroy that are inherited from [/ee/app/models/concerns/elastic/application_search.rb](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/app/models/concerns/elastic/application_search.rb). The Elasticsearch integration depends on an external indexer. We ship an [indexer written in Go](https://gitlab.com/gitlab-org/gitlab-elasticsearch-indexer). The user must trigger the initial indexing via a rake task but, after this is done, GitLab itself will trigger reindexing when required via `after_` callbacks on create, update, and destroy that are inherited from [/ee/app/models/concerns/elastic/application_search.rb](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/app/models/concerns/elastic/application_search.rb).
All indexing after the initial one is done via `ElasticIndexerWorker` (sidekiq jobs). All indexing after the initial one is done via `ElasticIndexerWorker` (sidekiq jobs).
......
...@@ -10,24 +10,30 @@ Review Apps are automatically deployed by each pipeline, both in ...@@ -10,24 +10,30 @@ Review Apps are automatically deployed by each pipeline, both in
```mermaid ```mermaid
graph TD graph TD
build-qa-image -.->|once the `prepare` stage is done| gitlab:assets:compile build-qa-image -->|once the `prepare` stage is done| gitlab:assets:compile
review-build-cng -->|triggers a CNG-mirror pipeline and wait for it to be done| CNG-mirror gitlab:assets:compile -->|once the `gitlab:assets:compile` job is done| review-build-cng
review-build-cng -.->|once the `test` stage is done| review-deploy review-build-cng -.->|triggers a CNG-mirror pipeline and wait for it to be done| CNG-mirror
review-deploy -.->|once the `review` stage is done| review-qa-smoke CNG-mirror -.->|polls until completed| review-build-cng
review-build-cng -->|once the `review-build-cng` job is done| review-deploy
review-deploy -->|once the `review-deploy` job is done| review-qa-smoke
subgraph "1. gitlab-ce/ee `prepare` stage" subgraph "1. gitlab-ce/ee `prepare` stage"
build-qa-image build-qa-image
end end
subgraph "2. gitlab-ce/ee `test` stage" subgraph "2. gitlab-ce/ee `test` stage"
gitlab:assets:compile -->|plays dependent job once done| review-build-cng gitlab:assets:compile
end end
subgraph "3. gitlab-ce/ee `review` stage" subgraph "3. gitlab-ce/ee `review-prepare` stage"
review-build-cng
end
subgraph "4. gitlab-ce/ee `review` stage"
review-deploy["review-deploy<br><br>Helm deploys the Review App using the Cloud<br/>Native images built by the CNG-mirror pipeline.<br><br>Cloud Native images are deployed to the `review-apps-ce` or `review-apps-ee`<br>Kubernetes (GKE) cluster, in the GCP `gitlab-review-apps` project."] review-deploy["review-deploy<br><br>Helm deploys the Review App using the Cloud<br/>Native images built by the CNG-mirror pipeline.<br><br>Cloud Native images are deployed to the `review-apps-ce` or `review-apps-ee`<br>Kubernetes (GKE) cluster, in the GCP `gitlab-review-apps` project."]
end end
subgraph "4. gitlab-ce/ee `qa` stage" subgraph "5. gitlab-ce/ee `qa` stage"
review-qa-smoke[review-qa-smoke<br><br>gitlab-qa runs the smoke suite against the Review App.] review-qa-smoke[review-qa-smoke<br><br>gitlab-qa runs the smoke suite against the Review App.]
end end
...@@ -177,6 +183,25 @@ secure note named **gitlab-{ce,ee} Review App's root password**. ...@@ -177,6 +183,25 @@ secure note named **gitlab-{ce,ee} Review App's root password**.
`review-qa-raise-e-12chm0-migrations.1-nqwtx`. `review-qa-raise-e-12chm0-migrations.1-nqwtx`.
1. Click on the `Container logs` link. 1. Click on the `Container logs` link.
### Diagnosing unhealthy review-app releases
If [Review App Stability](https://gitlab.com/gitlab-org/quality/team-tasks/issues/93) dips this may be a signal
that the `review-apps-ce/ee` cluster is unhealthy. Leading indicators may be healthcheck failures leading to restarts or majority failure for Review App deployments.
The following items may help diagnose this:
- [Instance group CPU Utilization in GCP](https://console.cloud.google.com/compute/instanceGroups/details/us-central1-a/gke-review-apps-ce-preemp-n1-standard-a4c9571c-grp?project=gitlab-review-apps&tab=monitoring&graph=GCE_CPU&duration=PT12H) - helpful to identify if nodes are problematic or the entire cluster is trending towards unhealthy
- [Instance Group size in GCP](https://console.cloud.google.com/compute/instanceGroups/details/us-central1-a/gke-review-apps-ce-preemp-n1-standard-a4c9571c-grp?project=gitlab-review-apps&tab=monitoring&graph=GCE_SIZE&duration=PT12H) - aids in identifying load spikes on the cluster. Kubernetes will add nodes up to 220 based on total resource requests.
- `kubectl top nodes --sort-by=cpu` - can identify if node spikes are common or load on specific nodes which may get rebalanced by the Kubernetes scheduler.
- `kubectl top pods --sort-by=cpu` -
- [K9s] - K9s is a powerful command line dashboard which allows you to filter by labels. This can help identify trends with apps exceeding the [review-app resource requests](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/scripts/review_apps/base-config.yaml). Kubernetes will schedule pods to nodes based on resource requests and allow for CPU usage up to the limits.
- In K9s you can sort or add filters by typing the `/` character
- `-lrelease=<review-app-slug>` - filters down to all pods for a release. This aids in determining what is having issues in a single deployment
- `-lapp=<app>` - filters down to all pods for a specific app. This aids in determining resource usage by app.
- You can scroll to a Kubernetes resource and hit `d`(describe), `s`(shell), `l`(logs) for a deeper inspection
![K9s](img/k9s.png)
### Troubleshoot a pending `dns-gitlab-review-app-external-dns` Deployment ### Troubleshoot a pending `dns-gitlab-review-app-external-dns` Deployment
#### Finding the problem #### Finding the problem
...@@ -266,6 +291,12 @@ find a way to limit it to only us.** ...@@ -266,6 +291,12 @@ find a way to limit it to only us.**
## Other resources ## Other resources
- [Review Apps integration for CE/EE (presentation)](https://docs.google.com/presentation/d/1QPLr6FO4LduROU8pQIPkX1yfGvD13GEJIBOenqoKxR8/edit?usp=sharing) - [Review Apps integration for CE/EE (presentation)](https://docs.google.com/presentation/d/1QPLr6FO4LduROU8pQIPkX1yfGvD13GEJIBOenqoKxR8/edit?usp=sharing)
- [Stability issues](https://gitlab.com/gitlab-org/quality/team-tasks/issues/212)
### Helpful command line tools
- [K9s] - enables CLI dashboard across pods and enabling filtering by labels
- [Stern](https://github.com/wercker/stern) - enables cross pod log tailing based on label/field selectors
[charts-1068]: https://gitlab.com/gitlab-org/charts/gitlab/issues/1068 [charts-1068]: https://gitlab.com/gitlab-org/charts/gitlab/issues/1068
[gitlab-pipeline]: https://gitlab.com/gitlab-org/gitlab-ce/pipelines/44362587 [gitlab-pipeline]: https://gitlab.com/gitlab-org/gitlab-ce/pipelines/44362587
...@@ -285,6 +316,7 @@ find a way to limit it to only us.** ...@@ -285,6 +316,7 @@ find a way to limit it to only us.**
[gitlab-ci-yml]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml [gitlab-ci-yml]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml
[gitlab-k8s-integration]: ../../user/project/clusters/index.md [gitlab-k8s-integration]: ../../user/project/clusters/index.md
[password-bug]: https://gitlab.com/gitlab-org/gitlab-ce/issues/53621 [password-bug]: https://gitlab.com/gitlab-org/gitlab-ce/issues/53621
[K9s]: https://github.com/derailed/k9s
--- ---
......
...@@ -585,6 +585,25 @@ You can specify a different Git repository by providing it as an extra parameter ...@@ -585,6 +585,25 @@ You can specify a different Git repository by providing it as an extra parameter
sudo -u git -H bundle exec rake "gitlab:workhorse:install[/home/git/gitlab-workhorse,https://example.com/gitlab-workhorse.git]" RAILS_ENV=production sudo -u git -H bundle exec rake "gitlab:workhorse:install[/home/git/gitlab-workhorse,https://example.com/gitlab-workhorse.git]" RAILS_ENV=production
``` ```
### Install gitlab-elasticsearch-indexer
GitLab-Elasticsearch-Indexer uses [GNU Make](https://www.gnu.org/software/make/). The
following command-line will install GitLab-Elasticsearch-Indexer in `/home/git/gitlab-elasticsearch-indexer`
which is the recommended location.
```sh
sudo -u git -H bundle exec rake "gitlab:indexer:install[/home/git/gitlab-elasticsearch-indexer]" RAILS_ENV=production
```
You can specify a different Git repository by providing it as an extra parameter:
```sh
sudo -u git -H bundle exec rake "gitlab:indexer:install[/home/git/gitlab-elasticsearch-indexer,https://example.com/gitlab-elasticsearch-indexer.git]" RAILS_ENV=production
```
The source code will first be fetched to the path specified by the first parameter. Then a binary will be built under its `bin` directory.
You will then need to update `gitlab.yml`'s `production -> elasticsearch -> indexer_path` setting to point to that binary.
### Install GitLab Pages ### Install GitLab Pages
GitLab Pages uses [GNU Make](https://www.gnu.org/software/make/). This step is optional and only needed if you wish to host static sites from within GitLab. The following commands will install GitLab Pages in `/home/git/gitlab-pages`. For additional setup steps, consult the [administration guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/administration/pages/source.md) for your version of GitLab as the GitLab Pages daemon can be run several different ways. GitLab Pages uses [GNU Make](https://www.gnu.org/software/make/). This step is optional and only needed if you wish to host static sites from within GitLab. The following commands will install GitLab Pages in `/home/git/gitlab-pages`. For additional setup steps, consult the [administration guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/administration/pages/source.md) for your version of GitLab as the GitLab Pages daemon can be run several different ways.
......
...@@ -58,7 +58,7 @@ The following languages and dependency managers are supported. ...@@ -58,7 +58,7 @@ The following languages and dependency managers are supported.
| JavaScript ([npm](https://www.npmjs.com/), [yarn](https://yarnpkg.com/en/)) | yes | [gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium), [Retire.js](https://retirejs.github.io/retire.js) | | JavaScript ([npm](https://www.npmjs.com/), [yarn](https://yarnpkg.com/en/)) | yes | [gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium), [Retire.js](https://retirejs.github.io/retire.js) |
| Go ([Golang](https://golang.org/)) | not currently ([issue](https://gitlab.com/gitlab-org/gitlab-ee/issues/7132 "Dependency Scanning for Go")) | not available | | Go ([Golang](https://golang.org/)) | not currently ([issue](https://gitlab.com/gitlab-org/gitlab-ee/issues/7132 "Dependency Scanning for Go")) | not available |
| PHP ([Composer](https://getcomposer.org/)) | yes | [gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium) | | PHP ([Composer](https://getcomposer.org/)) | yes | [gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium) |
| Python ([pip](https://pip.pypa.io/en/stable/)) (only `requirements.txt` supported) | yes | [gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium) | | Python ([pip](https://pip.pypa.io/en/stable/)) | yes | [gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium) |
| Python ([Pipfile](https://docs.pipenv.org/en/latest/basics/)) | not currently ([issue](https://gitlab.com/gitlab-org/gitlab-ee/issues/11756 "Pipfile.lock support for Dependency Scanning"))| not available | | Python ([Pipfile](https://docs.pipenv.org/en/latest/basics/)) | not currently ([issue](https://gitlab.com/gitlab-org/gitlab-ee/issues/11756 "Pipfile.lock support for Dependency Scanning"))| not available |
| Python ([poetry](https://poetry.eustace.io/)) | not currently ([issue](https://gitlab.com/gitlab-org/gitlab-ee/issues/7006 "Support Poetry in Dependency Scanning")) | not available | | Python ([poetry](https://poetry.eustace.io/)) | not currently ([issue](https://gitlab.com/gitlab-org/gitlab-ee/issues/7006 "Support Poetry in Dependency Scanning")) | not available |
| Ruby ([gem](https://rubygems.org/)) | yes | [gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium), [bundler-audit](https://github.com/rubysec/bundler-audit) | | Ruby ([gem](https://rubygems.org/)) | yes | [gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium), [bundler-audit](https://github.com/rubysec/bundler-audit) |
......
---
type: reference
---
# Time Tracking # Time Tracking
> Introduced in GitLab 8.14. > Introduced in GitLab 8.14.
...@@ -7,7 +11,7 @@ requests within GitLab. ...@@ -7,7 +11,7 @@ requests within GitLab.
## Overview ## Overview
Time Tracking lets you: Time Tracking allows you:
- Record the time spent working on an issue or a merge request. - Record the time spent working on an issue or a merge request.
- Add an estimate of the amount of time needed to complete an issue or a merge - Add an estimate of the amount of time needed to complete an issue or a merge
...@@ -18,7 +22,7 @@ You don't have to indicate an estimate to enter the time spent, and vice versa. ...@@ -18,7 +22,7 @@ You don't have to indicate an estimate to enter the time spent, and vice versa.
Data about time tracking is shown on the issue/merge request sidebar, as shown Data about time tracking is shown on the issue/merge request sidebar, as shown
below. below.
![Time tracking in the sidebar](time-tracking/time-tracking-sidebar.png) ![Time tracking in the sidebar](time_tracking/img/time_tracking_sidebar_v8_16.png)
## How to enter data ## How to enter data
...@@ -30,7 +34,7 @@ in a comment in both an issue or a merge request. ...@@ -30,7 +34,7 @@ in a comment in both an issue or a merge request.
Below is an example of how you can use those new quick actions inside a comment. Below is an example of how you can use those new quick actions inside a comment.
![Time tracking example in a comment](time-tracking/time-tracking-example.png) ![Time tracking example in a comment](time_tracking/img/time_tracking_example_v12_2.png)
Adding time entries (time spent or estimates) is limited to project members. Adding time entries (time spent or estimates) is limited to project members.
...@@ -65,11 +69,11 @@ To remove all the time spent at once, use `/remove_time_spent`. ...@@ -65,11 +69,11 @@ To remove all the time spent at once, use `/remove_time_spent`.
The following time units are available: The following time units are available:
- months (mo) - Months (mo)
- weeks (w) - Weeks (w)
- days (d) - Days (d)
- hours (h) - Hours (h)
- minutes (m) - Minutes (m)
Default conversion rates are 1mo = 4w, 1w = 5d and 1d = 8h. Default conversion rates are 1mo = 4w, 1w = 5d and 1d = 8h.
......
# frozen_string_literal: true
module Gitlab
class DeviseFailure < Devise::FailureApp
# If the request format is not known, send a redirect instead of a 401
# response, since this is the outcome we're most likely to want
def http_auth?
return super unless Feature.enabled?(:devise_redirect_unknown_formats, default_enabled: true)
request_format && super
end
end
end
...@@ -5,7 +5,7 @@ module Gitlab ...@@ -5,7 +5,7 @@ module Gitlab
extend self extend self
# We may want to configure it through project settings in a future version. # We may want to configure it through project settings in a future version.
CUSTOM_DAY_AND_WEEK_LENGTH = { hours_per_day: 8, days_per_month: 20 }.freeze CUSTOM_DAY_AND_MONTH_LENGTH = { hours_per_day: 8, days_per_month: 20 }.freeze
def parse(string) def parse(string)
string = string.sub(/\A-/, '') string = string.sub(/\A-/, '')
...@@ -14,7 +14,7 @@ module Gitlab ...@@ -14,7 +14,7 @@ module Gitlab
begin begin
ChronicDuration.parse( ChronicDuration.parse(
string, string,
CUSTOM_DAY_AND_WEEK_LENGTH.merge(default_unit: 'hours')) CUSTOM_DAY_AND_MONTH_LENGTH.merge(default_unit: 'hours'))
rescue rescue
nil nil
end end
...@@ -26,7 +26,7 @@ module Gitlab ...@@ -26,7 +26,7 @@ module Gitlab
def output(seconds) def output(seconds)
ChronicDuration.output( ChronicDuration.output(
seconds, seconds,
CUSTOM_DAY_AND_WEEK_LENGTH.merge( CUSTOM_DAY_AND_MONTH_LENGTH.merge(
format: :short, format: :short,
limit_to_hours: limit_to_hours_setting, limit_to_hours: limit_to_hours_setting,
weeks: true)) weeks: true))
......
...@@ -10,13 +10,13 @@ class GitlabDanger ...@@ -10,13 +10,13 @@ class GitlabDanger
prettier prettier
eslint eslint
database database
commit_messages
].freeze ].freeze
CI_ONLY_RULES ||= %w[ CI_ONLY_RULES ||= %w[
metadata metadata
changelog changelog
specs specs
commit_messages
roulette roulette
single_codebase single_codebase
gitlab_ui_wg gitlab_ui_wg
......
...@@ -61,6 +61,7 @@ module QA ...@@ -61,6 +61,7 @@ module QA
autoload :KubernetesCluster, 'qa/resource/kubernetes_cluster' autoload :KubernetesCluster, 'qa/resource/kubernetes_cluster'
autoload :User, 'qa/resource/user' autoload :User, 'qa/resource/user'
autoload :ProjectMilestone, 'qa/resource/project_milestone' autoload :ProjectMilestone, 'qa/resource/project_milestone'
autoload :Members, 'qa/resource/members'
autoload :Wiki, 'qa/resource/wiki' autoload :Wiki, 'qa/resource/wiki'
autoload :File, 'qa/resource/file' autoload :File, 'qa/resource/file'
autoload :Fork, 'qa/resource/fork' autoload :Fork, 'qa/resource/fork'
......
...@@ -44,7 +44,7 @@ module QA ...@@ -44,7 +44,7 @@ module QA
def sign_in_using_credentials(user = nil) def sign_in_using_credentials(user = nil)
# Don't try to log-in if we're already logged-in # Don't try to log-in if we're already logged-in
return if Page::Main::Menu.perform { |menu| menu.has_personal_area?(wait: 0) } return if Page::Main::Menu.perform(&:signed_in?)
using_wait_time 0 do using_wait_time 0 do
set_initial_password_if_present set_initial_password_if_present
...@@ -75,10 +75,7 @@ module QA ...@@ -75,10 +75,7 @@ module QA
end end
def sign_in_using_ldap_credentials(user) def sign_in_using_ldap_credentials(user)
# Log out if already logged in Page::Main::Menu.perform(&:sign_out_if_signed_in)
Page::Main::Menu.perform do |menu|
menu.sign_out if menu.has_personal_area?(wait: 0)
end
using_wait_time 0 do using_wait_time 0 do
set_initial_password_if_present set_initial_password_if_present
...@@ -149,7 +146,7 @@ module QA ...@@ -149,7 +146,7 @@ module QA
end end
def sign_out_and_sign_in_as(user:) def sign_out_and_sign_in_as(user:)
Menu.perform(&:sign_out) Menu.perform(&:sign_out_if_signed_in)
has_sign_in_tab? has_sign_in_tab?
sign_in_using_credentials(user) sign_in_using_credentials(user)
end end
......
...@@ -55,6 +55,10 @@ module QA ...@@ -55,6 +55,10 @@ module QA
within_top_menu { click_element :admin_area_link } within_top_menu { click_element :admin_area_link }
end end
def signed_in?
has_personal_area?(wait: 0)
end
def sign_out def sign_out
within_user_menu do within_user_menu do
click_element :sign_out_link click_element :sign_out_link
...@@ -62,7 +66,7 @@ module QA ...@@ -62,7 +66,7 @@ module QA
end end
def sign_out_if_signed_in def sign_out_if_signed_in
sign_out if has_personal_area?(wait: 0) sign_out if signed_in?
end end
def click_settings_link def click_settings_link
......
...@@ -25,6 +25,10 @@ module QA ...@@ -25,6 +25,10 @@ module QA
element :protected_branches_list element :protected_branches_list
end end
view 'app/views/projects/protected_branches/shared/_create_protected_branch.html.haml' do
element :protect_button
end
def select_branch(branch_name) def select_branch(branch_name)
click_element :protected_branch_select click_element :protected_branch_select
...@@ -33,40 +37,31 @@ module QA ...@@ -33,40 +37,31 @@ module QA
end end
end end
def allow_no_one_to_push def select_allowed_to_merge(allowed)
go_to_allow(:push, 'No one') select_allowed(:merge, allowed)
end
def allow_devs_and_maintainers_to_push
go_to_allow(:push, 'Developers + Maintainers')
end end
# @deprecated def select_allowed_to_push(allowed)
alias_method :allow_devs_and_masters_to_push, :allow_devs_and_maintainers_to_push select_allowed(:push, allowed)
def allow_no_one_to_merge
go_to_allow(:merge, 'No one')
end end
def allow_devs_and_maintainers_to_merge
go_to_allow(:merge, 'Developers + Maintainers')
end
# @deprecated
alias_method :allow_devs_and_masters_to_merge, :allow_devs_and_maintainers_to_merge
def protect_branch def protect_branch
click_on 'Protect' click_element :protect_button
end end
private private
def go_to_allow(action, text) def select_allowed(action, allowed)
click_element :"allowed_to_#{action}_select" click_element :"allowed_to_#{action}_select"
allowed[:roles] = Resource::ProtectedBranch::Roles::NO_ONE unless allowed.key?(:roles)
within_element(:"allowed_to_#{action}_dropdown") do within_element(:"allowed_to_#{action}_dropdown") do
click_on text click_on allowed[:roles]
end end
# Click the select element again to close the dropdown
click_element :protected_branch_select
end end
end end
end end
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
module QA module QA
module Resource module Resource
class Group < Base class Group < Base
include Members
attr_accessor :path, :description attr_accessor :path, :description
attribute :sandbox do attribute :sandbox do
...@@ -48,19 +50,10 @@ module QA ...@@ -48,19 +50,10 @@ module QA
super super
end end
def add_member(user, access_level = '30')
# 30 = developer access
post Runtime::API::Request.new(api_client, api_members_path).url, { user_id: user.id, access_level: access_level }
end
def api_get_path def api_get_path
"/groups/#{CGI.escape("#{sandbox.path}/#{path}")}" "/groups/#{CGI.escape("#{sandbox.path}/#{path}")}"
end end
def api_members_path
"#{api_get_path}/members"
end
def api_post_path def api_post_path
'/groups' '/groups'
end end
......
# frozen_string_literal: true
module QA
module Resource
#
# Included in Resource::Project and Resource::Group to allow changes to
# project/group membership
#
module Members
def add_member(user, access_level = AccessLevel::DEVELOPER)
post Runtime::API::Request.new(api_client, api_members_path).url, { user_id: user.id, access_level: access_level }
end
def api_members_path
"#{api_get_path}/members"
end
class AccessLevel
NO_ACCESS = 0
GUEST = 10
REPORTER = 20
DEVELOPER = 30
MAINTAINER = 40
OWNER = 50
end
end
end
end
...@@ -6,6 +6,7 @@ module QA ...@@ -6,6 +6,7 @@ module QA
module Resource module Resource
class Project < Base class Project < Base
include Events::Project include Events::Project
include Members
attr_writer :initialize_with_readme attr_writer :initialize_with_readme
attr_writer :visibility attr_writer :visibility
...@@ -75,11 +76,6 @@ module QA ...@@ -75,11 +76,6 @@ module QA
super super
end end
def add_member(user, access_level = '30')
# 30 = developer access
post Runtime::API::Request.new(api_client, api_members_path).url, { user_id: user.id, access_level: access_level }
end
def api_get_path def api_get_path
"/projects/#{CGI.escape(path_with_namespace)}" "/projects/#{CGI.escape(path_with_namespace)}"
end end
...@@ -112,6 +108,10 @@ module QA ...@@ -112,6 +108,10 @@ module QA
post_body post_body
end end
def share_with_group(invitee, access_level = Resource::Members::AccessLevel::DEVELOPER)
post Runtime::API::Request.new(api_client, "/projects/#{id}/share").url, { group_id: invitee.id, group_access: access_level }
end
private private
def transform_api_resource(api_resource) def transform_api_resource(api_resource)
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
module QA module QA
module Resource module Resource
class ProtectedBranch < Base class ProtectedBranch < Base
attr_accessor :branch_name, :allow_to_push, :allow_to_merge, :protected attr_accessor :branch_name, :allowed_to_push, :allowed_to_merge, :protected
attribute :project do attribute :project do
Project.fabricate_via_api! do |resource| Project.fabricate_via_api! do |resource|
...@@ -25,8 +25,12 @@ module QA ...@@ -25,8 +25,12 @@ module QA
def initialize def initialize
@branch_name = 'test/branch' @branch_name = 'test/branch'
@allow_to_push = true @allowed_to_push = {
@allow_to_merge = true roles: Resource::ProtectedBranch::Roles::DEVS_AND_MAINTAINERS
}
@allowed_to_merge = {
roles: Resource::ProtectedBranch::Roles::DEVS_AND_MAINTAINERS
}
@protected = false @protected = false
end end
...@@ -35,29 +39,14 @@ module QA ...@@ -35,29 +39,14 @@ module QA
project.wait_for_push_new_branch @branch_name project.wait_for_push_new_branch @branch_name
# The upcoming process will make it access the Protected Branches page,
# select the already created branch and protect it according
# to `allow_to_push` variable.
return branch unless @protected
project.visit! project.visit!
Page::Project::Menu.perform(&:go_to_repository_settings) Page::Project::Menu.perform(&:go_to_repository_settings)
Page::Project::Settings::Repository.perform do |setting| Page::Project::Settings::Repository.perform do |setting|
setting.expand_protected_branches do |page| setting.expand_protected_branches do |page|
page.select_branch(branch_name) page.select_branch(branch_name)
page.select_allowed_to_merge(allowed_to_merge)
if allow_to_push page.select_allowed_to_push(allowed_to_push)
page.allow_devs_and_maintainers_to_push
else
page.allow_no_one_to_push
end
if allow_to_merge
page.allow_devs_and_maintainers_to_merge
else
page.allow_no_one_to_merge
end
page.wait(reload: false) do page.wait(reload: false) do
!page.first('.btn-success').disabled? !page.first('.btn-success').disabled?
...@@ -79,6 +68,12 @@ module QA ...@@ -79,6 +68,12 @@ module QA
def api_delete_path def api_delete_path
"/projects/#{@project.api_resource[:id]}/protected_branches/#{@branch_name}" "/projects/#{@project.api_resource[:id]}/protected_branches/#{@branch_name}"
end end
class Roles
NO_ONE = 'No one'
DEVS_AND_MAINTAINERS = 'Developers + Maintainers'
MAINTAINERS = 'Maintainers'
end
end end
end end
end end
...@@ -17,16 +17,11 @@ module QA ...@@ -17,16 +17,11 @@ module QA
Page::Main::Login.perform(&:sign_in_using_credentials) Page::Main::Login.perform(&:sign_in_using_credentials)
end end
after do
# We need to clear localStorage because we're using it for the dropdown,
# and capybara doesn't do this for us.
# https://github.com/teamcapybara/capybara/issues/1702
Capybara.execute_script 'localStorage.clear()'
end
context 'when developers and maintainers are allowed to push to a protected branch' do context 'when developers and maintainers are allowed to push to a protected branch' do
it 'user with push rights successfully pushes to the protected branch' do it 'user with push rights successfully pushes to the protected branch' do
create_protected_branch(allow_to_push: true) create_protected_branch(allowed_to_push: {
roles: Resource::ProtectedBranch::Roles::DEVS_AND_MAINTAINERS
})
push = push_new_file(branch_name) push = push_new_file(branch_name)
...@@ -36,18 +31,19 @@ module QA ...@@ -36,18 +31,19 @@ module QA
context 'when developers and maintainers are not allowed to push to a protected branch' do context 'when developers and maintainers are not allowed to push to a protected branch' do
it 'user without push rights fails to push to the protected branch' do it 'user without push rights fails to push to the protected branch' do
create_protected_branch(allow_to_push: false) create_protected_branch(allowed_to_push: {
roles: Resource::ProtectedBranch::Roles::NO_ONE
})
expect { push_new_file(branch_name) }.to raise_error(QA::Git::Repository::RepositoryCommandError, /remote: GitLab: You are not allowed to push code to protected branches on this project\.([\s\S]+)\[remote rejected\] #{branch_name} -> #{branch_name} \(pre-receive hook declined\)/) expect { push_new_file(branch_name) }.to raise_error(QA::Git::Repository::RepositoryCommandError, /remote: GitLab: You are not allowed to push code to protected branches on this project\.([\s\S]+)\[remote rejected\] #{branch_name} -> #{branch_name} \(pre-receive hook declined\)/)
end end
end end
def create_protected_branch(allow_to_push:) def create_protected_branch(allowed_to_push:)
Resource::ProtectedBranch.fabricate! do |resource| Resource::ProtectedBranch.fabricate! do |resource|
resource.branch_name = branch_name resource.branch_name = branch_name
resource.project = project resource.project = project
resource.allow_to_push = allow_to_push resource.allowed_to_push = allowed_to_push
resource.protected = true
end end
end end
......
...@@ -171,16 +171,40 @@ describe ApplicationController do ...@@ -171,16 +171,40 @@ describe ApplicationController do
end end
describe '#route_not_found' do describe '#route_not_found' do
controller(described_class) do
def index
route_not_found
end
end
it 'renders 404 if authenticated' do it 'renders 404 if authenticated' do
allow(controller).to receive(:current_user).and_return(user) sign_in(user)
expect(controller).to receive(:not_found)
controller.send(:route_not_found) get :index
expect(response).to have_gitlab_http_status(404)
end end
it 'does redirect to login page via authenticate_user! if not authenticated' do it 'redirects to login page via authenticate_user! if not authenticated' do
allow(controller).to receive(:current_user).and_return(nil) get :index
expect(controller).to receive(:authenticate_user!)
controller.send(:route_not_found) expect(response).to redirect_to new_user_session_path
end
context 'request format is unknown' do
it 'redirects if unauthenticated' do
get :index, format: 'unknown'
expect(response).to redirect_to new_user_session_path
end
it 'returns a 401 if the feature flag is disabled' do
stub_feature_flags(devise_redirect_unknown_formats: false)
get :index, format: 'unknown'
expect(response).to have_gitlab_http_status(401)
end
end end
end end
......
/* eslint-disable no-unused-vars */
/* global ListIssue */ /* global ListIssue */
import Vue from 'vue';
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
...@@ -190,7 +188,7 @@ describe('Store', () => { ...@@ -190,7 +188,7 @@ describe('Store', () => {
it('moves the position of lists', () => { it('moves the position of lists', () => {
const listOne = boardsStore.addList(listObj); const listOne = boardsStore.addList(listObj);
const listTwo = boardsStore.addList(listObjDuplicate); boardsStore.addList(listObjDuplicate);
expect(boardsStore.state.lists.length).toBe(2); expect(boardsStore.state.lists.length).toBe(2);
......
...@@ -87,4 +87,12 @@ describe Gitlab::Popen do ...@@ -87,4 +87,12 @@ describe Gitlab::Popen do
it { expect(@status).to be_zero } it { expect(@status).to be_zero }
it { expect(@output).to eq('hello') } it { expect(@output).to eq('hello') }
end end
context 'when binary is absent' do
it 'raises error' do
expect do
@klass.new.popen(%w[foobar])
end.to raise_error
end
end
end end
...@@ -9,7 +9,7 @@ describe GitlabDanger do ...@@ -9,7 +9,7 @@ describe GitlabDanger do
describe '.local_warning_message' do describe '.local_warning_message' do
it 'returns an informational message with rules that can run' do it 'returns an informational message with rules that can run' do
expect(described_class.local_warning_message).to eq('==> Only the following Danger rules can be run locally: changes_size, gemfile, documentation, frozen_string, duplicate_yarn_dependencies, prettier, eslint, database') expect(described_class.local_warning_message).to eq('==> Only the following Danger rules can be run locally: changes_size, gemfile, documentation, frozen_string, duplicate_yarn_dependencies, prettier, eslint, database, commit_messages')
end end
end end
......
...@@ -317,9 +317,14 @@ describe Ci::CreatePipelineService do ...@@ -317,9 +317,14 @@ describe Ci::CreatePipelineService do
context 'interruptible builds' do context 'interruptible builds' do
before do before do
Feature.enable(:ci_support_interruptible_pipelines)
stub_ci_pipeline_yaml_file(YAML.dump(config)) stub_ci_pipeline_yaml_file(YAML.dump(config))
end end
after do
Feature.disable(:ci_support_interruptible_pipelines)
end
let(:config) do let(:config) do
{ {
stages: %w[stage1 stage2 stage3 stage4], stages: %w[stage1 stage2 stage3 stage4],
......
...@@ -23,6 +23,20 @@ describe ServiceResponse do ...@@ -23,6 +23,20 @@ describe ServiceResponse do
expect(response).to be_success expect(response).to be_success
expect(response.payload).to eq(good: 'orange') expect(response.payload).to eq(good: 'orange')
end end
it 'creates a successful response with default HTTP status' do
response = described_class.success
expect(response).to be_success
expect(response.http_status).to eq(:ok)
end
it 'creates a successful response with custom HTTP status' do
response = described_class.success(http_status: 204)
expect(response).to be_success
expect(response.http_status).to eq(204)
end
end end
describe '.error' do describe '.error' do
......
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