summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2019-09-16 12:06:26 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2019-09-16 12:06:26 +0000
commitd2798d607e11e0ebae83ae909404834388733428 (patch)
tree096b7f4d4bdb315d28cdcd4d6db4e80911112e9c
parentd8211a0ed119eada7d292e974a8fc7b0cd982d3c (diff)
downloadgitlab-ce-d2798d607e11e0ebae83ae909404834388733428.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/blob/template_selector.js4
-rw-r--r--app/assets/javascripts/commit/image_file.js19
-rw-r--r--app/assets/javascripts/gl_dropdown.js10
-rw-r--r--app/assets/javascripts/issue.js3
-rw-r--r--app/assets/javascripts/label_manager.js5
-rw-r--r--app/assets/javascripts/labels_select.js25
-rw-r--r--app/assets/javascripts/lib/utils/text_markdown.js4
-rw-r--r--app/assets/javascripts/milestone_select.js8
-rw-r--r--app/assets/javascripts/network/branch_graph.js12
-rw-r--r--app/assets/javascripts/notes.js44
-rw-r--r--app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_util.js4
-rw-r--r--app/assets/javascripts/profile/gl_crop.js13
-rw-r--r--app/assets/javascripts/project_find_file.js5
-rw-r--r--app/assets/javascripts/right_sidebar.js14
-rw-r--r--app/assets/javascripts/search_autocomplete.js9
-rw-r--r--app/assets/javascripts/users_select.js7
-rw-r--r--app/assets/javascripts/zen_mode.js4
-rw-r--r--app/services/ci/create_pipeline_service.rb2
-rw-r--r--app/services/service_response.rb4
-rw-r--r--app/views/projects/protected_branches/shared/_create_protected_branch.html.haml2
-rwxr-xr-xbin/elastic_repo_indexer101
-rw-r--r--changelogs/unreleased/29155-fix-devise-401-responses.yml5
-rw-r--r--config/gitlab.yml.example4
-rw-r--r--config/initializers/8_devise.rb8
-rw-r--r--danger/commit_messages/Dangerfile10
-rw-r--r--db/migrate/20190910212256_add_any_approver_rule_unique_indexes.rb30
-rw-r--r--db/post_migrate/20190905091812_schedule_project_any_approval_rule_migration.rb47
-rw-r--r--db/post_migrate/20190905091831_schedule_merge_request_any_approval_rule_migration.rb47
-rw-r--r--db/schema.rb6
-rw-r--r--doc/api/settings.md1
-rw-r--r--doc/development/elasticsearch.md2
-rw-r--r--doc/development/testing_guide/img/k9s.pngbin0 -> 364038 bytes
-rw-r--r--doc/development/testing_guide/review_apps.md46
-rw-r--r--doc/install/installation.md19
-rw-r--r--doc/user/application_security/dependency_scanning/index.md2
-rw-r--r--doc/workflow/time-tracking/time-tracking-example.pngbin14564 -> 0 bytes
-rw-r--r--doc/workflow/time_tracking.md20
-rw-r--r--doc/workflow/time_tracking/img/time_tracking_example_v12_2.pngbin0 -> 16362 bytes
-rw-r--r--doc/workflow/time_tracking/img/time_tracking_sidebar_v8_16.png (renamed from doc/workflow/time-tracking/time-tracking-sidebar.png)bin9068 -> 9068 bytes
-rw-r--r--lib/gitlab/devise_failure.rb13
-rw-r--r--lib/gitlab/time_tracking_formatter.rb6
-rw-r--r--lib/gitlab_danger.rb2
-rw-r--r--qa/qa.rb1
-rw-r--r--qa/qa/page/main/login.rb9
-rw-r--r--qa/qa/page/main/menu.rb6
-rw-r--r--qa/qa/page/project/settings/protected_branches.rb37
-rw-r--r--qa/qa/resource/group.rb11
-rw-r--r--qa/qa/resource/members.rb28
-rw-r--r--qa/qa/resource/project.rb10
-rw-r--r--qa/qa/resource/protected_branch.rb35
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb20
-rw-r--r--spec/controllers/application_controller_spec.rb38
-rw-r--r--spec/javascripts/boards/boards_store_spec.js4
-rw-r--r--spec/lib/gitlab/popen_spec.rb8
-rw-r--r--spec/lib/gitlab_danger_spec.rb2
-rw-r--r--spec/services/ci/create_pipeline_service_spec.rb5
-rw-r--r--spec/services/service_response_spec.rb14
57 files changed, 446 insertions, 349 deletions
diff --git a/app/assets/javascripts/blob/template_selector.js b/app/assets/javascripts/blob/template_selector.js
index 37e348d93d3..9e69c7d7164 100644
--- a/app/assets/javascripts/blob/template_selector.js
+++ b/app/assets/javascripts/blob/template_selector.js
@@ -1,4 +1,4 @@
-/* eslint-disable class-methods-use-this, no-unused-vars */
+/* eslint-disable class-methods-use-this */
import $ from 'jquery';
@@ -61,7 +61,7 @@ export default class TemplateSelector {
return this.requestFile(item);
}
- requestFile(item) {
+ requestFile() {
// This `requestFile` method is an abstract method that should
// be added by all subclasses.
}
diff --git a/app/assets/javascripts/commit/image_file.js b/app/assets/javascripts/commit/image_file.js
index bc666aef54b..9454f760df8 100644
--- a/app/assets/javascripts/commit/image_file.js
+++ b/app/assets/javascripts/commit/image_file.js
@@ -1,4 +1,4 @@
-/* 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';
@@ -12,11 +12,8 @@ export default class ImageFile {
this.requestImageInfo(
$('.two-up.view .frame.deleted img', this.file),
(function(_this) {
- return function(deletedWidth, deletedHeight) {
- return _this.requestImageInfo($('.two-up.view .frame.added img', _this.file), function(
- width,
- height,
- ) {
+ return function() {
+ return _this.requestImageInfo($('.two-up.view .frame.added img', _this.file), function() {
_this.initViewModes();
// Load two-up view after images are loaded
@@ -112,7 +109,7 @@ export default class ImageFile {
maxHeight = 0;
$('.frame', view)
.each(
- (function(_this) {
+ (function() {
return function(index, frame) {
var height, width;
width = $(frame).width();
@@ -196,13 +193,7 @@ export default class ImageFile {
return $('.onion-skin.view', this.file).each(
(function(_this) {
return function(index, view) {
- var $frame,
- $track,
- $dragger,
- $frameAdded,
- framePadding,
- ref,
- dragging = false;
+ var $frame, $track, $dragger, $frameAdded, framePadding, ref;
(ref = _this.prepareFrames(view)), ([maxWidth, maxHeight] = ref);
$frame = $('.onion-skin-frame', view);
$frameAdded = $('.frame.added', view);
diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js
index 515402fc506..f49246cf07b 100644
--- a/app/assets/javascripts/gl_dropdown.js
+++ b/app/assets/javascripts/gl_dropdown.js
@@ -1,5 +1,4 @@
-/* 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 */
-/* global fuzzaldrinPlus */
+/* 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 */
import $ from 'jquery';
import _ from 'underscore';
@@ -66,12 +65,10 @@ GitLabDropdownInput = (function() {
})();
GitLabDropdownFilter = (function() {
- var ARROW_KEY_CODES, BLUR_KEYCODES, HAS_VALUE_CLASS;
+ var BLUR_KEYCODES, HAS_VALUE_CLASS;
BLUR_KEYCODES = [27, 40];
- ARROW_KEY_CODES = [38, 40];
-
HAS_VALUE_CLASS = 'has-value';
function GitLabDropdownFilter(input, options) {
@@ -877,9 +874,8 @@ GitLabDropdown = (function() {
};
GitLabDropdown.prototype.addArrowKeyEvent = function() {
- var $input, ARROW_KEY_CODES, selector;
+ var ARROW_KEY_CODES, selector;
ARROW_KEY_CODES = [38, 40];
- $input = this.dropdown.find('.dropdown-input-field');
selector = SELECTABLE_CLASSES;
if (this.dropdown.find('.dropdown-toggle-page').length) {
selector = '.dropdown-page-one ' + selector;
diff --git a/app/assets/javascripts/issue.js b/app/assets/javascripts/issue.js
index db4607ca58d..a9e086fade8 100644
--- a/app/assets/javascripts/issue.js
+++ b/app/assets/javascripts/issue.js
@@ -1,10 +1,9 @@
-/* eslint-disable no-var, one-var, no-unused-vars, consistent-return */
+/* eslint-disable no-var, one-var, consistent-return */
import $ from 'jquery';
import axios from './lib/utils/axios_utils';
import { addDelimiter } from './lib/utils/text_utility';
import flash from './flash';
-import TaskList from './task_list';
import CreateMergeRequestDropdown from './create_merge_request_dropdown';
import IssuablesHelper from './helpers/issuables_helper';
import { __ } from './locale';
diff --git a/app/assets/javascripts/label_manager.js b/app/assets/javascripts/label_manager.js
index 7064731a5ea..5dcc719f7c3 100644
--- a/app/assets/javascripts/label_manager.js
+++ b/app/assets/javascripts/label_manager.js
@@ -1,4 +1,4 @@
-/* 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 Sortable from 'sortablejs';
@@ -50,7 +50,7 @@ export default class LabelManager {
$(e.currentTarget).tooltip('hide');
}
- toggleEmptyState($label, $btn, action) {
+ toggleEmptyState() {
this.emptyState.classList.toggle(
'hidden',
Boolean(this.prioritizedLabels[0].querySelector(':scope > li')),
@@ -61,7 +61,6 @@ export default class LabelManager {
if (persistState == null) {
persistState = true;
}
- const _this = this;
const url = $label.find('.js-toggle-priority').data('url');
let $target = this.prioritizedLabels;
let $from = this.otherLabels;
diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js
index 177aa02b8e0..8cc3bc8373f 100644
--- a/app/assets/javascripts/labels_select.js
+++ b/app/assets/javascripts/labels_select.js
@@ -1,4 +1,4 @@
-/* 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 ListLabel */
@@ -26,7 +26,6 @@ export default class LabelsSelect {
$els.each(function(i, dropdown) {
var $block,
- $colorPreview,
$dropdown,
$form,
$loading,
@@ -35,8 +34,6 @@ export default class LabelsSelect {
$value,
abilityName,
defaultLabel,
- enableLabelCreateButton,
- issueURLSplit,
issueUpdateURL,
labelUrl,
namespacePath,
@@ -47,16 +44,11 @@ export default class LabelsSelect {
showNo,
$sidebarLabelTooltip,
initialSelected,
- $toggleText,
fieldName,
- useId,
- propertyName,
showMenuAbove,
- $container,
$dropdownContainer;
$dropdown = $(dropdown);
$dropdownContainer = $dropdown.closest('.labels-filter');
- $toggleText = $dropdown.find('.dropdown-toggle-text');
namespacePath = $dropdown.data('namespacePath');
projectPath = $dropdown.data('projectPath');
issueUpdateURL = $dropdown.data('issueUpdate');
@@ -77,10 +69,6 @@ export default class LabelsSelect {
$value = $block.find('.value');
$loading = $block.find('.block-loading').fadeOut();
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
.find('input[name="' + $dropdown.data('fieldName') + '"]')
.map(function() {
@@ -124,7 +112,7 @@ export default class LabelsSelect {
axios
.put(issueUpdateURL, data)
.then(({ data }) => {
- var labelCount, template, labelTooltipTitle, labelTitles, formattedLabels;
+ var labelCount, template, labelTooltipTitle, labelTitles;
$loading.fadeOut();
$dropdown.trigger('loaded.gl.dropdown');
$selectbox.hide();
@@ -246,12 +234,10 @@ export default class LabelsSelect {
renderRow: function(label) {
var linkEl,
listItemEl,
- color,
colorEl,
indeterminate,
removesAll,
selectedClass,
- spacing,
i,
marked,
dropdownValue;
@@ -378,7 +364,7 @@ export default class LabelsSelect {
}
},
hidden: function() {
- var isIssueIndex, isMRIndex, page, selectedLabels;
+ var isIssueIndex, isMRIndex, page;
page = $('body').attr('data-page');
isIssueIndex = page === 'projects:issues:index';
isMRIndex = page === 'projects:merge_requests:index';
@@ -395,9 +381,6 @@ export default class LabelsSelect {
}
if ($dropdown.hasClass('js-multiselect')) {
if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) {
- selectedLabels = $dropdown
- .closest('form')
- .find("input:hidden[name='" + $dropdown.data('fieldName') + "']");
Issuable.filterResults($dropdown.closest('form'));
} else if ($dropdown.hasClass('js-filter-submit')) {
$dropdown.closest('form').submit();
@@ -495,7 +478,7 @@ export default class LabelsSelect {
}
}
},
- opened: function(e) {
+ opened: function() {
if ($dropdown.hasClass('js-issue-board-sidebar')) {
const previousSelection = $dropdown.attr('data-selected');
this.selected = previousSelection ? previousSelection.split(',') : [];
diff --git a/app/assets/javascripts/lib/utils/text_markdown.js b/app/assets/javascripts/lib/utils/text_markdown.js
index b7922e29bb0..7873eaf059f 100644
--- a/app/assets/javascripts/lib/utils/text_markdown.js
+++ b/app/assets/javascripts/lib/utils/text_markdown.js
@@ -1,4 +1,4 @@
-/* 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 { insertText } from '~/lib/utils/common_utils';
@@ -157,7 +157,7 @@ export function insertMarkdownText({
if (tag === LINK_TAG_PATTERN) {
if (URL) {
try {
- const ignoredUrl = new URL(selected);
+ new URL(selected); // eslint-disable-line no-new
// valid url
tag = '[text]({text})';
select = 'text';
diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js
index 8f077685b07..9c0d55326ee 100644
--- a/app/assets/javascripts/milestone_select.js
+++ b/app/assets/javascripts/milestone_select.js
@@ -1,4 +1,4 @@
-/* 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 ListMilestone */
@@ -37,7 +37,6 @@ export default class MilestoneSelect {
selectedMilestone,
selectedMilestoneDefault;
const $dropdown = $(dropdown);
- const projectId = $dropdown.data('projectId');
const milestonesUrl = $dropdown.data('milestones');
const issueUpdateURL = $dropdown.data('issueUpdate');
const showNo = $dropdown.data('showNo');
@@ -48,7 +47,6 @@ export default class MilestoneSelect {
const useId = $dropdown.data('useId');
const defaultLabel = $dropdown.data('defaultLabel');
const defaultNo = $dropdown.data('defaultNo');
- const issuableId = $dropdown.data('issuableId');
const abilityName = $dropdown.data('abilityName');
const $selectBox = $dropdown.closest('.selectbox');
const $block = $selectBox.closest('.block');
@@ -121,7 +119,7 @@ export default class MilestoneSelect {
fields: ['title'],
},
selectable: true,
- toggleLabel: (selected, el, e) => {
+ toggleLabel: (selected, el) => {
if (selected && 'id' in selected && $(el).hasClass('is-active')) {
return selected.title;
} else {
@@ -153,7 +151,7 @@ export default class MilestoneSelect {
},
vue: $dropdown.hasClass('js-issue-board-sidebar'),
clicked: clickEvent => {
- const { $el, e } = clickEvent;
+ const { e } = clickEvent;
let selected = clickEvent.selectedObj;
let data, modalStoreFilter;
diff --git a/app/assets/javascripts/network/branch_graph.js b/app/assets/javascripts/network/branch_graph.js
index d1fa9f5e2a2..fcfc2570b3d 100644
--- a/app/assets/javascripts/network/branch_graph.js
+++ b/app/assets/javascripts/network/branch_graph.js
@@ -1,9 +1,8 @@
-/* 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 '../locale';
import axios from '../lib/utils/axios_utils';
-import flash from '../flash';
import Raphael from './raphael';
export default (function() {
@@ -104,7 +103,7 @@ export default (function() {
};
BranchGraph.prototype.buildGraph = function() {
- var cuday, cumonth, day, j, len, mm, ref;
+ var cuday, cumonth, day, len, mm, ref;
const { r } = this;
cuday = 0;
cumonth = '';
@@ -178,7 +177,7 @@ export default (function() {
return $(element).scroll(
(function(_this) {
- return function(event) {
+ return function() {
return _this.renderPartialGraph();
};
})(this),
@@ -214,7 +213,7 @@ export default (function() {
};
BranchGraph.prototype.appendLabel = function(x, y, commit) {
- var label, rect, shortrefs, text, textbox, triangle;
+ var label, rect, shortrefs, text, textbox;
if (!commit.refs) {
return;
@@ -239,7 +238,8 @@ export default (function() {
'fill-opacity': 0.5,
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-opacity': 0.5,
stroke: 'none',
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index 9cc31e26648..9cc56b34c75 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -2,10 +2,9 @@
no-unused-expressions, one-var, default-case,
prefer-template, consistent-return, no-alert, no-return-assign,
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 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.
@@ -37,7 +36,6 @@ import {
isMetaKey,
isInMRPage,
} from './lib/utils/common_utils';
-import imageDiffHelper from './image_diff/helpers/index';
import { localTimeAgo } from './lib/utils/datetime_utility';
import { sprintf, s__, __ } from './locale';
@@ -683,7 +681,7 @@ export default class Notes {
);
}
- updateNoteError($parentTimeline) {
+ updateNoteError() {
// eslint-disable-next-line no-new
new Flash(
__('Your comment could not be updated! Please check your network connection and try again.'),
@@ -697,7 +695,6 @@ export default class Notes {
*/
addDiscussionNote($form, note, isNewDiffComment) {
if ($form.attr('data-resolve-all') != null) {
- var projectPath = $form.data('projectPath');
var discussionId = $form.data('discussionId');
var mergeRequestId = $form.data('noteableIid');
@@ -746,7 +743,6 @@ export default class Notes {
if (currentContent === initialContent) {
this.removeNoteEditForm($el);
} else {
- var $buttons = $el.find('.note-form-actions');
var isWidgetVisible = isInViewport($el.get(0));
if (!isWidgetVisible) {
@@ -766,7 +762,7 @@ export default class Notes {
* 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
*/
- showEditForm(e, scrollTo, myLastNote) {
+ showEditForm(e) {
e.preventDefault();
var $target = $(e.target);
@@ -850,16 +846,11 @@ export default class Notes {
* Removes the whole discussion if the last note is being removed.
*/
removeNote(e) {
- var noteElId, noteId, dataNoteId, $note, lineHolder;
+ var noteElId, $note;
$note = $(e.currentTarget).closest('.note');
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(
- (function(_this) {
+ (function() {
// 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,
// where $('#noteId') would return only one.
@@ -1064,25 +1055,8 @@ export default class Notes {
this.setupDiscussionNoteForm($link, newForm);
}
- toggleDiffNote({
- target,
- lineType,
- forceShow,
- showReplyInput = false,
- currentUsername,
- currentUserAvatar,
- currentUserFullname,
- }) {
- var $link,
- addForm,
- hasNotes,
- newForm,
- noteForm,
- replyButton,
- row,
- rowCssToAdd,
- targetContent,
- isDiffCommentAvatar;
+ toggleDiffNote({ target, lineType, forceShow, showReplyInput = false }) {
+ var $link, addForm, hasNotes, newForm, noteForm, replyButton, row, rowCssToAdd;
$link = $(target);
row = $link.closest('tr');
const nextRow = row.next();
@@ -1515,7 +1489,7 @@ export default class Notes {
let tempFormContent;
// Identify executed quick actions from `formContent`
- const executedCommands = availableQuickActions.filter((command, index) => {
+ const executedCommands = availableQuickActions.filter(command => {
const commandRegex = new RegExp(`/${command.name}`);
return commandRegex.test(formContent);
});
@@ -1840,8 +1814,6 @@ export default class Notes {
const $noteBody = $editingNote.find('.js-task-list-container');
const $noteBodyText = $noteBody.find('.note-text');
const { formData, formContent, formAction } = this.getFormData($form);
- const $diffFile = $form.closest('.diff-file');
- const $notesContainer = $form.closest('.notes');
// Cache original comment content
const cachedNoteBodyText = $noteBodyText.html();
diff --git a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_util.js b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_util.js
index 988ae164955..ec3919dd073 100644
--- a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_util.js
+++ b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_util.js
@@ -1,4 +1,4 @@
-/* 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';
export default {
@@ -126,7 +126,7 @@ export default {
_.each(
_.omit(log_entry, 'author_name', 'author_email'),
(function(_this) {
- return function(value, key) {
+ return function(value) {
if (_this.in_range(value.date, date_range)) {
parsed_entry.dates[value.date] = value[field];
parsed_entry.commits += value.commits;
diff --git a/app/assets/javascripts/profile/gl_crop.js b/app/assets/javascripts/profile/gl_crop.js
index befe91c332f..6a07ccc7586 100644
--- a/app/assets/javascripts/profile/gl_crop.js
+++ b/app/assets/javascripts/profile/gl_crop.js
@@ -1,10 +1,10 @@
-/* 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 'cropper';
import _ from 'underscore';
-(global => {
+(() => {
// Matches everything but the file name
const FILENAMEREGEX = /^.*[\\\/]/;
@@ -69,7 +69,7 @@ import _ from 'underscore';
this.modalCrop.on('shown.bs.modal', this.onModalShow);
this.modalCrop.on('hidden.bs.modal', this.onModalHide);
this.uploadImageBtn.on('click', this.onUploadImageBtnClick);
- this.cropActionsBtn.on('click', function(e) {
+ this.cropActionsBtn.on('click', function() {
var btn;
btn = this;
return _this.onActionBtnClick(btn);
@@ -128,10 +128,10 @@ import _ from 'underscore';
}
onActionBtnClick(btn) {
- var data, result;
+ var data;
data = $(btn).data();
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';
}
dataURLtoBlob(dataURL) {
- var array, binary, i, len, v;
+ var array, binary, i, len;
binary = atob(dataURL.split(',')[1]);
array = [];
for (i = 0, len = binary.length; i < len; i += 1) {
- v = binary[i];
array.push(binary.charCodeAt(i));
}
return new Blob([new Uint8Array(array)], {
diff --git a/app/assets/javascripts/project_find_file.js b/app/assets/javascripts/project_find_file.js
index 765cb868f80..e73a828c0ae 100644
--- a/app/assets/javascripts/project_find_file.js
+++ b/app/assets/javascripts/project_find_file.js
@@ -1,4 +1,4 @@
-/* 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 fuzzaldrinPlus from 'fuzzaldrin-plus';
@@ -8,9 +8,8 @@ import { __ } from '~/locale';
// highlight text(awefwbwgtc -> <b>a</b>wefw<b>b</b>wgt<b>c</b> )
const highlighter = function(element, text, matches) {
- var highlightText, j, lastIndex, len, matchIndex, matchedChars, unmatched;
+ var j, lastIndex, len, matchIndex, matchedChars, unmatched;
lastIndex = 0;
- highlightText = '';
matchedChars = [];
for (j = 0, len = matches.length; j < len; j += 1) {
matchIndex = matches[j];
diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js
index 40a2158de78..0cc7a22325b 100644
--- a/app/assets/javascripts/right_sidebar.js
+++ b/app/assets/javascripts/right_sidebar.js
@@ -1,4 +1,4 @@
-/* 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 'underscore';
@@ -7,7 +7,7 @@ import flash from './flash';
import axios from './lib/utils/axios_utils';
import { sprintf, s__, __ } from './locale';
-function Sidebar(currentUser) {
+function Sidebar() {
this.toggleTodo = this.toggleTodo.bind(this);
this.sidebar = $('aside');
@@ -15,9 +15,9 @@ function Sidebar(currentUser) {
this.addEventListeners();
}
-Sidebar.initialize = function(currentUser) {
+Sidebar.initialize = function() {
if (!this.instance) {
- this.instance = new Sidebar(currentUser);
+ this.instance = new Sidebar();
}
};
@@ -77,7 +77,7 @@ Sidebar.prototype.sidebarToggleClicked = function(e, triggered) {
};
Sidebar.prototype.toggleTodo = function(e) {
- var $btnText, $this, $todoLoading, ajaxType, url;
+ var $this, ajaxType, url;
$this = $(e.currentTarget);
ajaxType = $this.data('deletePath') ? 'delete' : 'post';
@@ -140,7 +140,7 @@ Sidebar.prototype.todoUpdateDone = function(data) {
});
};
-Sidebar.prototype.sidebarDropdownLoading = function(e) {
+Sidebar.prototype.sidebarDropdownLoading = function() {
var $loading, $sidebarCollapsedIcon, i, img;
$sidebarCollapsedIcon = $(this)
.closest('.block')
@@ -157,7 +157,7 @@ Sidebar.prototype.sidebarDropdownLoading = function(e) {
}
};
-Sidebar.prototype.sidebarDropdownLoaded = function(e) {
+Sidebar.prototype.sidebarDropdownLoaded = function() {
var $sidebarCollapsedIcon, i, img;
$sidebarCollapsedIcon = $(this)
.closest('.block')
diff --git a/app/assets/javascripts/search_autocomplete.js b/app/assets/javascripts/search_autocomplete.js
index 510a2441924..f02c55c3d5b 100644
--- a/app/assets/javascripts/search_autocomplete.js
+++ b/app/assets/javascripts/search_autocomplete.js
@@ -1,11 +1,10 @@
-/* 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 { escape, throttle } from 'underscore';
-import { s__, __, sprintf } from '~/locale';
+import { s__, __ } from '~/locale';
import { getIdenticonBackgroundClass, getIdenticonTitle } from '~/helpers/avatar_helper';
import axios from './lib/utils/axios_utils';
-import DropdownUtils from './filtered_search/dropdown_utils';
import {
isInGroupsPage,
isInProjectPage,
@@ -142,7 +141,7 @@ export class SearchAutocomplete {
});
}
- getSearchText(selectedObject, el) {
+ getSearchText(selectedObject) {
return selectedObject.id ? selectedObject.text : '';
}
@@ -402,7 +401,7 @@ export class SearchAutocomplete {
return this.searchInput.val('').focus();
}
- onSearchInputBlur(e) {
+ onSearchInputBlur() {
this.isFocused = false;
this.wrap.removeClass('search-active');
// If input is blank then restore state
diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js
index 57efde7f027..4b3c42ae848 100644
--- a/app/assets/javascripts/users_select.js
+++ b/app/assets/javascripts/users_select.js
@@ -1,4 +1,4 @@
-/* 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 emitSidebarEvent */
@@ -405,7 +405,7 @@ function UsersSelect(currentUser, els, options = {}) {
}
},
defaultLabel: defaultLabel,
- hidden: function(e) {
+ hidden: function() {
if ($dropdown.hasClass('js-multiselect')) {
emitSidebarEvent('sidebar.saveAssignees');
}
@@ -442,7 +442,6 @@ function UsersSelect(currentUser, els, options = {}) {
if (user.beforeDivider && user.name.toLowerCase() === 'unassigned') {
// Unassigned selected
previouslySelected.each((index, element) => {
- const id = parseInt(element.value, 10);
element.remove();
});
emitSidebarEvent('sidebar.removeAllAssignees');
@@ -548,7 +547,7 @@ function UsersSelect(currentUser, els, options = {}) {
},
updateLabel: $dropdown.data('dropdownTitle'),
renderRow: function(user) {
- var avatar, img, listClosingTags, listWithName, listWithUserName, username;
+ var avatar, img, username;
username = user.username ? '@' + user.username : '';
avatar = user.avatar_url ? user.avatar_url : gon.default_avatar_url;
diff --git a/app/assets/javascripts/zen_mode.js b/app/assets/javascripts/zen_mode.js
index e98c4d7bf7a..5438572eadf 100644
--- a/app/assets/javascripts/zen_mode.js
+++ b/app/assets/javascripts/zen_mode.js
@@ -1,4 +1,4 @@
-/* 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
//
@@ -62,7 +62,7 @@ export default class ZenMode {
$(document).on(
'zen_mode:leave',
(function(_this) {
- return function(e) {
+ return function() {
return _this.exit();
};
})(this),
diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb
index 539576147f3..3278a87c576 100644
--- a/app/services/ci/create_pipeline_service.rb
+++ b/app/services/ci/create_pipeline_service.rb
@@ -95,7 +95,7 @@ module Ci
# rubocop: disable CodeReuse/ActiveRecord
def auto_cancelable_pipelines
# 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
.where(ref: pipeline.ref)
.where.not(id: pipeline.id)
diff --git a/app/services/service_response.rb b/app/services/service_response.rb
index f3437ba16de..08b7e9d0831 100644
--- a/app/services/service_response.rb
+++ b/app/services/service_response.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
class ServiceResponse
- def self.success(message: nil, payload: {})
- new(status: :success, message: message, payload: payload)
+ def self.success(message: nil, payload: {}, http_status: :ok)
+ new(status: :success, message: message, payload: payload, http_status: http_status)
end
def self.error(message:, payload: {}, http_status: nil)
diff --git a/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml b/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml
index 3644a623d2c..bba4949277d 100644
--- a/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml
+++ b/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml
@@ -29,4 +29,4 @@
= yield :push_access_levels
.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' }
diff --git a/bin/elastic_repo_indexer b/bin/elastic_repo_indexer
deleted file mode 100755
index 3dfe0c4164b..00000000000
--- a/bin/elastic_repo_indexer
+++ /dev/null
@@ -1,101 +0,0 @@
-#!/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
diff --git a/changelogs/unreleased/29155-fix-devise-401-responses.yml b/changelogs/unreleased/29155-fix-devise-401-responses.yml
new file mode 100644
index 00000000000..2c11727e3b1
--- /dev/null
+++ b/changelogs/unreleased/29155-fix-devise-401-responses.yml
@@ -0,0 +1,5 @@
+---
+title: Avoid Devise "401 Unauthorized" responses
+merge_request: 16519
+author:
+type: fixed
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 92674aafa90..814ea551e19 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -982,6 +982,10 @@ production: &base
# Default is '.gitlab_workhorse_secret' relative to Rails.root (i.e. root of the GitLab app).
# secret_file: /home/git/gitlab/.gitlab_workhorse_secret
+ ## GitLab Elasticsearch settings
+ elasticsearch:
+ indexer_path: /home/git/gitlab-elasticsearch-indexer/
+
## Git settings
# CAUTION!
# Use the default values unless you really know what you are doing
diff --git a/config/initializers/8_devise.rb b/config/initializers/8_devise.rb
index 8ef9ff6b7fc..8d4c5fa382c 100644
--- a/config/initializers/8_devise.rb
+++ b/config/initializers/8_devise.rb
@@ -214,11 +214,9 @@ Devise.setup do |config|
# 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.
#
- # config.warden do |manager|
- # manager.failure_app = Gitlab::DeviseFailure
- # manager.intercept_401 = false
- # manager.default_strategies(scope: :user).unshift :some_external_strategy
- # end
+ config.warden do |manager|
+ manager.failure_app = Gitlab::DeviseFailure
+ end
if Gitlab::Auth::LDAP::Config.enabled?
Gitlab::Auth::LDAP::Config.providers.each do |provider|
diff --git a/danger/commit_messages/Dangerfile b/danger/commit_messages/Dangerfile
index d371ade4887..064b8c94805 100644
--- a/danger/commit_messages/Dangerfile
+++ b/danger/commit_messages/Dangerfile
@@ -37,6 +37,10 @@ class EmojiChecker
end
end
+def gitlab_danger
+ @gitlab_danger ||= GitlabDanger.new(helper.gitlab_helper)
+end
+
def fail_commit(commit, message)
fail("#{commit.sha}: #{message}")
end
@@ -56,6 +60,8 @@ def subject_starts_with_capital?(subject)
end
def ce_upstream?
+ return unless gitlab_danger.ci?
+
gitlab.mr_labels.any? { |label| label == 'CE upstream' }
end
@@ -88,8 +94,8 @@ def lint_commit(commit) # rubocop:disable Metrics/AbcSize
# We ignore revert commits as they are well structured by Git already
return false if commit.message.start_with?('Revert "')
- is_squash = gitlab.mr_json['squash']
- is_wip = gitlab.mr_json['work_in_progress']
+ is_squash = gitlab_danger.ci? ? gitlab.mr_json['squash'] : false
+ is_wip = gitlab_danger.ci? ? gitlab.mr_json['work_in_progress'] : false
is_fixup = commit.message.start_with?('fixup!', 'squash!')
if is_fixup
diff --git a/db/migrate/20190910212256_add_any_approver_rule_unique_indexes.rb b/db/migrate/20190910212256_add_any_approver_rule_unique_indexes.rb
new file mode 100644
index 00000000000..e8f5b853d1d
--- /dev/null
+++ b/db/migrate/20190910212256_add_any_approver_rule_unique_indexes.rb
@@ -0,0 +1,30 @@
+# 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
diff --git a/db/post_migrate/20190905091812_schedule_project_any_approval_rule_migration.rb b/db/post_migrate/20190905091812_schedule_project_any_approval_rule_migration.rb
new file mode 100644
index 00000000000..ef1cb452c26
--- /dev/null
+++ b/db/post_migrate/20190905091812_schedule_project_any_approval_rule_migration.rb
@@ -0,0 +1,47 @@
+# 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
diff --git a/db/post_migrate/20190905091831_schedule_merge_request_any_approval_rule_migration.rb b/db/post_migrate/20190905091831_schedule_merge_request_any_approval_rule_migration.rb
new file mode 100644
index 00000000000..4a8398a9eea
--- /dev/null
+++ b/db/post_migrate/20190905091831_schedule_merge_request_any_approval_rule_migration.rb
@@ -0,0 +1,47 @@
+# 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
diff --git a/db/schema.rb b/db/schema.rb
index 64d29d7cc05..b43779f4126 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -319,8 +319,9 @@ ActiveRecord::Schema.define(version: 2019_09_12_061145) do
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: "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", "rule_type"], name: "index_approval_rules_code_owners_rule_type", 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: "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
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
t.integer "approvals_required", limit: 2, default: 0, null: false
t.string "name", 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 ["rule_type"], name: "index_approval_project_rules_on_rule_type"
end
diff --git a/doc/api/settings.md b/doc/api/settings.md
index 4ad4ebdacb6..39fc848b272 100644
--- a/doc/api/settings.md
+++ b/doc/api/settings.md
@@ -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_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_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_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. |
diff --git a/doc/development/elasticsearch.md b/doc/development/elasticsearch.md
index f2412c249c1..f3ea55d3d5d 100644
--- a/doc/development/elasticsearch.md
+++ b/doc/development/elasticsearch.md
@@ -59,7 +59,7 @@ Additionally, if you need large repos or multiple forks for testing, please cons
## 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).
diff --git a/doc/development/testing_guide/img/k9s.png b/doc/development/testing_guide/img/k9s.png
new file mode 100644
index 00000000000..c4b222f0b64
--- /dev/null
+++ b/doc/development/testing_guide/img/k9s.png
Binary files differ
diff --git a/doc/development/testing_guide/review_apps.md b/doc/development/testing_guide/review_apps.md
index 13772cbe015..8698a1e4c2d 100644
--- a/doc/development/testing_guide/review_apps.md
+++ b/doc/development/testing_guide/review_apps.md
@@ -10,24 +10,30 @@ Review Apps are automatically deployed by each pipeline, both in
```mermaid
graph TD
- 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
- review-build-cng -.->|once the `test` stage is done| review-deploy
- review-deploy -.->|once the `review` stage is done| review-qa-smoke
+ build-qa-image -->|once the `prepare` stage is done| gitlab:assets:compile
+ gitlab:assets:compile -->|once the `gitlab:assets:compile` job is done| review-build-cng
+ review-build-cng -.->|triggers a CNG-mirror pipeline and wait for it to be done| CNG-mirror
+ 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"
build-qa-image
end
subgraph "2. gitlab-ce/ee `test` stage"
- gitlab:assets:compile -->|plays dependent job once done| review-build-cng
+ gitlab:assets:compile
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."]
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.]
end
@@ -177,6 +183,25 @@ secure note named **gitlab-{ce,ee} Review App's root password**.
`review-qa-raise-e-12chm0-migrations.1-nqwtx`.
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
#### Finding the problem
@@ -266,6 +291,12 @@ find a way to limit it to only us.**
## Other resources
- [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
[gitlab-pipeline]: https://gitlab.com/gitlab-org/gitlab-ce/pipelines/44362587
@@ -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-k8s-integration]: ../../user/project/clusters/index.md
[password-bug]: https://gitlab.com/gitlab-org/gitlab-ce/issues/53621
+[K9s]: https://github.com/derailed/k9s
---
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 6039ddc45ae..cf084ca74a9 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -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
```
+### 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
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.
diff --git a/doc/user/application_security/dependency_scanning/index.md b/doc/user/application_security/dependency_scanning/index.md
index 166a71b6fbe..5baaa92d3d8 100644
--- a/doc/user/application_security/dependency_scanning/index.md
+++ b/doc/user/application_security/dependency_scanning/index.md
@@ -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) |
| 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) |
-| 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 ([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) |
diff --git a/doc/workflow/time-tracking/time-tracking-example.png b/doc/workflow/time-tracking/time-tracking-example.png
deleted file mode 100644
index a96e4da7f74..00000000000
--- a/doc/workflow/time-tracking/time-tracking-example.png
+++ /dev/null
Binary files differ
diff --git a/doc/workflow/time_tracking.md b/doc/workflow/time_tracking.md
index 3d9f015b1fe..7404d9be210 100644
--- a/doc/workflow/time_tracking.md
+++ b/doc/workflow/time_tracking.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Time Tracking
> Introduced in GitLab 8.14.
@@ -7,7 +11,7 @@ requests within GitLab.
## Overview
-Time Tracking lets you:
+Time Tracking allows you:
- 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
@@ -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
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
@@ -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.
-![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.
@@ -65,11 +69,11 @@ To remove all the time spent at once, use `/remove_time_spent`.
The following time units are available:
-- months (mo)
-- weeks (w)
-- days (d)
-- hours (h)
-- minutes (m)
+- Months (mo)
+- Weeks (w)
+- Days (d)
+- Hours (h)
+- Minutes (m)
Default conversion rates are 1mo = 4w, 1w = 5d and 1d = 8h.
diff --git a/doc/workflow/time_tracking/img/time_tracking_example_v12_2.png b/doc/workflow/time_tracking/img/time_tracking_example_v12_2.png
new file mode 100644
index 00000000000..31d8c490ed1
--- /dev/null
+++ b/doc/workflow/time_tracking/img/time_tracking_example_v12_2.png
Binary files differ
diff --git a/doc/workflow/time-tracking/time-tracking-sidebar.png b/doc/workflow/time_tracking/img/time_tracking_sidebar_v8_16.png
index 22124afed6f..22124afed6f 100644
--- a/doc/workflow/time-tracking/time-tracking-sidebar.png
+++ b/doc/workflow/time_tracking/img/time_tracking_sidebar_v8_16.png
Binary files differ
diff --git a/lib/gitlab/devise_failure.rb b/lib/gitlab/devise_failure.rb
new file mode 100644
index 00000000000..4d27b706e1e
--- /dev/null
+++ b/lib/gitlab/devise_failure.rb
@@ -0,0 +1,13 @@
+# 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
diff --git a/lib/gitlab/time_tracking_formatter.rb b/lib/gitlab/time_tracking_formatter.rb
index 31883527135..b15cb85dde0 100644
--- a/lib/gitlab/time_tracking_formatter.rb
+++ b/lib/gitlab/time_tracking_formatter.rb
@@ -5,7 +5,7 @@ module Gitlab
extend self
# 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)
string = string.sub(/\A-/, '')
@@ -14,7 +14,7 @@ module Gitlab
begin
ChronicDuration.parse(
string,
- CUSTOM_DAY_AND_WEEK_LENGTH.merge(default_unit: 'hours'))
+ CUSTOM_DAY_AND_MONTH_LENGTH.merge(default_unit: 'hours'))
rescue
nil
end
@@ -26,7 +26,7 @@ module Gitlab
def output(seconds)
ChronicDuration.output(
seconds,
- CUSTOM_DAY_AND_WEEK_LENGTH.merge(
+ CUSTOM_DAY_AND_MONTH_LENGTH.merge(
format: :short,
limit_to_hours: limit_to_hours_setting,
weeks: true))
diff --git a/lib/gitlab_danger.rb b/lib/gitlab_danger.rb
index b4768a9546d..cf297c142dc 100644
--- a/lib/gitlab_danger.rb
+++ b/lib/gitlab_danger.rb
@@ -10,13 +10,13 @@ class GitlabDanger
prettier
eslint
database
+ commit_messages
].freeze
CI_ONLY_RULES ||= %w[
metadata
changelog
specs
- commit_messages
roulette
single_codebase
gitlab_ui_wg
diff --git a/qa/qa.rb b/qa/qa.rb
index dc9ef3ac464..fed2dbeade3 100644
--- a/qa/qa.rb
+++ b/qa/qa.rb
@@ -61,6 +61,7 @@ module QA
autoload :KubernetesCluster, 'qa/resource/kubernetes_cluster'
autoload :User, 'qa/resource/user'
autoload :ProjectMilestone, 'qa/resource/project_milestone'
+ autoload :Members, 'qa/resource/members'
autoload :Wiki, 'qa/resource/wiki'
autoload :File, 'qa/resource/file'
autoload :Fork, 'qa/resource/fork'
diff --git a/qa/qa/page/main/login.rb b/qa/qa/page/main/login.rb
index 65d83926f38..ca93663dba2 100644
--- a/qa/qa/page/main/login.rb
+++ b/qa/qa/page/main/login.rb
@@ -44,7 +44,7 @@ module QA
def sign_in_using_credentials(user = nil)
# 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
set_initial_password_if_present
@@ -75,10 +75,7 @@ module QA
end
def sign_in_using_ldap_credentials(user)
- # Log out if already logged in
- Page::Main::Menu.perform do |menu|
- menu.sign_out if menu.has_personal_area?(wait: 0)
- end
+ Page::Main::Menu.perform(&:sign_out_if_signed_in)
using_wait_time 0 do
set_initial_password_if_present
@@ -149,7 +146,7 @@ module QA
end
def sign_out_and_sign_in_as(user:)
- Menu.perform(&:sign_out)
+ Menu.perform(&:sign_out_if_signed_in)
has_sign_in_tab?
sign_in_using_credentials(user)
end
diff --git a/qa/qa/page/main/menu.rb b/qa/qa/page/main/menu.rb
index 4676dc8d077..751b67d7695 100644
--- a/qa/qa/page/main/menu.rb
+++ b/qa/qa/page/main/menu.rb
@@ -55,6 +55,10 @@ module QA
within_top_menu { click_element :admin_area_link }
end
+ def signed_in?
+ has_personal_area?(wait: 0)
+ end
+
def sign_out
within_user_menu do
click_element :sign_out_link
@@ -62,7 +66,7 @@ module QA
end
def sign_out_if_signed_in
- sign_out if has_personal_area?(wait: 0)
+ sign_out if signed_in?
end
def click_settings_link
diff --git a/qa/qa/page/project/settings/protected_branches.rb b/qa/qa/page/project/settings/protected_branches.rb
index 1e707f1d315..d1d2f302013 100644
--- a/qa/qa/page/project/settings/protected_branches.rb
+++ b/qa/qa/page/project/settings/protected_branches.rb
@@ -25,6 +25,10 @@ module QA
element :protected_branches_list
end
+ view 'app/views/projects/protected_branches/shared/_create_protected_branch.html.haml' do
+ element :protect_button
+ end
+
def select_branch(branch_name)
click_element :protected_branch_select
@@ -33,40 +37,31 @@ module QA
end
end
- def allow_no_one_to_push
- go_to_allow(:push, 'No one')
- end
-
- def allow_devs_and_maintainers_to_push
- go_to_allow(:push, 'Developers + Maintainers')
+ def select_allowed_to_merge(allowed)
+ select_allowed(:merge, allowed)
end
- # @deprecated
- alias_method :allow_devs_and_masters_to_push, :allow_devs_and_maintainers_to_push
-
- def allow_no_one_to_merge
- go_to_allow(:merge, 'No one')
+ def select_allowed_to_push(allowed)
+ select_allowed(:push, allowed)
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
- click_on 'Protect'
+ click_element :protect_button
end
private
- def go_to_allow(action, text)
+ def select_allowed(action, allowed)
click_element :"allowed_to_#{action}_select"
+ allowed[:roles] = Resource::ProtectedBranch::Roles::NO_ONE unless allowed.key?(:roles)
+
within_element(:"allowed_to_#{action}_dropdown") do
- click_on text
+ click_on allowed[:roles]
end
+
+ # Click the select element again to close the dropdown
+ click_element :protected_branch_select
end
end
end
diff --git a/qa/qa/resource/group.rb b/qa/qa/resource/group.rb
index b5beba64c61..e11bd5728fb 100644
--- a/qa/qa/resource/group.rb
+++ b/qa/qa/resource/group.rb
@@ -3,6 +3,8 @@
module QA
module Resource
class Group < Base
+ include Members
+
attr_accessor :path, :description
attribute :sandbox do
@@ -48,19 +50,10 @@ module QA
super
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
"/groups/#{CGI.escape("#{sandbox.path}/#{path}")}"
end
- def api_members_path
- "#{api_get_path}/members"
- end
-
def api_post_path
'/groups'
end
diff --git a/qa/qa/resource/members.rb b/qa/qa/resource/members.rb
new file mode 100644
index 00000000000..d70a2907523
--- /dev/null
+++ b/qa/qa/resource/members.rb
@@ -0,0 +1,28 @@
+# 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
diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb
index 2e49f69bd55..a0389390c83 100644
--- a/qa/qa/resource/project.rb
+++ b/qa/qa/resource/project.rb
@@ -6,6 +6,7 @@ module QA
module Resource
class Project < Base
include Events::Project
+ include Members
attr_writer :initialize_with_readme
attr_writer :visibility
@@ -75,11 +76,6 @@ module QA
super
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
"/projects/#{CGI.escape(path_with_namespace)}"
end
@@ -112,6 +108,10 @@ module QA
post_body
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
def transform_api_resource(api_resource)
diff --git a/qa/qa/resource/protected_branch.rb b/qa/qa/resource/protected_branch.rb
index c27647cf3ce..f0cef624e0b 100644
--- a/qa/qa/resource/protected_branch.rb
+++ b/qa/qa/resource/protected_branch.rb
@@ -3,7 +3,7 @@
module QA
module Resource
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
Project.fabricate_via_api! do |resource|
@@ -25,8 +25,12 @@ module QA
def initialize
@branch_name = 'test/branch'
- @allow_to_push = true
- @allow_to_merge = true
+ @allowed_to_push = {
+ roles: Resource::ProtectedBranch::Roles::DEVS_AND_MAINTAINERS
+ }
+ @allowed_to_merge = {
+ roles: Resource::ProtectedBranch::Roles::DEVS_AND_MAINTAINERS
+ }
@protected = false
end
@@ -35,29 +39,14 @@ module QA
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!
Page::Project::Menu.perform(&:go_to_repository_settings)
Page::Project::Settings::Repository.perform do |setting|
setting.expand_protected_branches do |page|
page.select_branch(branch_name)
-
- if allow_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.select_allowed_to_merge(allowed_to_merge)
+ page.select_allowed_to_push(allowed_to_push)
page.wait(reload: false) do
!page.first('.btn-success').disabled?
@@ -79,6 +68,12 @@ module QA
def api_delete_path
"/projects/#{@project.api_resource[:id]}/protected_branches/#{@branch_name}"
end
+
+ class Roles
+ NO_ONE = 'No one'
+ DEVS_AND_MAINTAINERS = 'Developers + Maintainers'
+ MAINTAINERS = 'Maintainers'
+ end
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb
index dd80905d184..5d0f4b215f4 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb
@@ -17,16 +17,11 @@ module QA
Page::Main::Login.perform(&:sign_in_using_credentials)
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
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)
@@ -36,18 +31,19 @@ module QA
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
- 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\)/)
end
end
- def create_protected_branch(allow_to_push:)
+ def create_protected_branch(allowed_to_push:)
Resource::ProtectedBranch.fabricate! do |resource|
resource.branch_name = branch_name
resource.project = project
- resource.allow_to_push = allow_to_push
- resource.protected = true
+ resource.allowed_to_push = allowed_to_push
end
end
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index 0b3833e6515..52a68e987f0 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -171,16 +171,40 @@ describe ApplicationController do
end
describe '#route_not_found' do
+ controller(described_class) do
+ def index
+ route_not_found
+ end
+ end
+
it 'renders 404 if authenticated' do
- allow(controller).to receive(:current_user).and_return(user)
- expect(controller).to receive(:not_found)
- controller.send(:route_not_found)
+ sign_in(user)
+
+ get :index
+
+ expect(response).to have_gitlab_http_status(404)
end
- it 'does redirect to login page via authenticate_user! if not authenticated' do
- allow(controller).to receive(:current_user).and_return(nil)
- expect(controller).to receive(:authenticate_user!)
- controller.send(:route_not_found)
+ it 'redirects to login page via authenticate_user! if not authenticated' do
+ get :index
+
+ 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
diff --git a/spec/javascripts/boards/boards_store_spec.js b/spec/javascripts/boards/boards_store_spec.js
index 36bd7ada4f0..11352140ba4 100644
--- a/spec/javascripts/boards/boards_store_spec.js
+++ b/spec/javascripts/boards/boards_store_spec.js
@@ -1,7 +1,5 @@
-/* eslint-disable no-unused-vars */
/* global ListIssue */
-import Vue from 'vue';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import Cookies from 'js-cookie';
@@ -190,7 +188,7 @@ describe('Store', () => {
it('moves the position of lists', () => {
const listOne = boardsStore.addList(listObj);
- const listTwo = boardsStore.addList(listObjDuplicate);
+ boardsStore.addList(listObjDuplicate);
expect(boardsStore.state.lists.length).toBe(2);
diff --git a/spec/lib/gitlab/popen_spec.rb b/spec/lib/gitlab/popen_spec.rb
index 29afd9df74e..b398381a7e0 100644
--- a/spec/lib/gitlab/popen_spec.rb
+++ b/spec/lib/gitlab/popen_spec.rb
@@ -87,4 +87,12 @@ describe Gitlab::Popen do
it { expect(@status).to be_zero }
it { expect(@output).to eq('hello') }
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
diff --git a/spec/lib/gitlab_danger_spec.rb b/spec/lib/gitlab_danger_spec.rb
index 623ac20fa7c..26bf5d76756 100644
--- a/spec/lib/gitlab_danger_spec.rb
+++ b/spec/lib/gitlab_danger_spec.rb
@@ -9,7 +9,7 @@ describe GitlabDanger do
describe '.local_warning_message' 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
diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb
index fe86982af91..19cc2ddf7db 100644
--- a/spec/services/ci/create_pipeline_service_spec.rb
+++ b/spec/services/ci/create_pipeline_service_spec.rb
@@ -317,9 +317,14 @@ describe Ci::CreatePipelineService do
context 'interruptible builds' do
before do
+ Feature.enable(:ci_support_interruptible_pipelines)
stub_ci_pipeline_yaml_file(YAML.dump(config))
end
+ after do
+ Feature.disable(:ci_support_interruptible_pipelines)
+ end
+
let(:config) do
{
stages: %w[stage1 stage2 stage3 stage4],
diff --git a/spec/services/service_response_spec.rb b/spec/services/service_response_spec.rb
index e790d272e61..a6567f52c6f 100644
--- a/spec/services/service_response_spec.rb
+++ b/spec/services/service_response_spec.rb
@@ -23,6 +23,20 @@ describe ServiceResponse do
expect(response).to be_success
expect(response.payload).to eq(good: 'orange')
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
describe '.error' do