From d2798d607e11e0ebae83ae909404834388733428 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Mon, 16 Sep 2019 12:06:26 +0000 Subject: Add latest changes from gitlab-org/gitlab@master --- app/assets/javascripts/blob/template_selector.js | 4 +- app/assets/javascripts/commit/image_file.js | 19 +--- app/assets/javascripts/gl_dropdown.js | 10 +- app/assets/javascripts/issue.js | 3 +- app/assets/javascripts/label_manager.js | 5 +- app/assets/javascripts/labels_select.js | 25 +---- app/assets/javascripts/lib/utils/text_markdown.js | 4 +- app/assets/javascripts/milestone_select.js | 8 +- app/assets/javascripts/network/branch_graph.js | 12 +-- app/assets/javascripts/notes.js | 44 ++------- .../graphs/show/stat_graph_contributors_util.js | 4 +- app/assets/javascripts/profile/gl_crop.js | 13 ++- app/assets/javascripts/project_find_file.js | 5 +- app/assets/javascripts/right_sidebar.js | 14 +-- app/assets/javascripts/search_autocomplete.js | 9 +- app/assets/javascripts/users_select.js | 7 +- app/assets/javascripts/zen_mode.js | 4 +- app/services/ci/create_pipeline_service.rb | 2 +- app/services/service_response.rb | 4 +- .../shared/_create_protected_branch.html.haml | 2 +- bin/elastic_repo_indexer | 101 --------------------- .../unreleased/29155-fix-devise-401-responses.yml | 5 + config/gitlab.yml.example | 4 + config/initializers/8_devise.rb | 8 +- danger/commit_messages/Dangerfile | 10 +- ...0212256_add_any_approver_rule_unique_indexes.rb | 30 ++++++ ...schedule_project_any_approval_rule_migration.rb | 47 ++++++++++ ...le_merge_request_any_approval_rule_migration.rb | 47 ++++++++++ db/schema.rb | 6 +- doc/api/settings.md | 1 - doc/development/elasticsearch.md | 2 +- doc/development/testing_guide/img/k9s.png | Bin 0 -> 364038 bytes doc/development/testing_guide/review_apps.md | 46 ++++++++-- doc/install/installation.md | 19 ++++ .../dependency_scanning/index.md | 2 +- .../time-tracking/time-tracking-example.png | Bin 14564 -> 0 bytes .../time-tracking/time-tracking-sidebar.png | Bin 9068 -> 0 bytes doc/workflow/time_tracking.md | 20 ++-- .../img/time_tracking_example_v12_2.png | Bin 0 -> 16362 bytes .../img/time_tracking_sidebar_v8_16.png | Bin 0 -> 9068 bytes lib/gitlab/devise_failure.rb | 13 +++ lib/gitlab/time_tracking_formatter.rb | 6 +- lib/gitlab_danger.rb | 2 +- qa/qa.rb | 1 + qa/qa/page/main/login.rb | 9 +- qa/qa/page/main/menu.rb | 6 +- qa/qa/page/project/settings/protected_branches.rb | 37 ++++---- qa/qa/resource/group.rb | 11 +-- qa/qa/resource/members.rb | 28 ++++++ qa/qa/resource/project.rb | 10 +- qa/qa/resource/protected_branch.rb | 35 +++---- .../repository/push_protected_branch_spec.rb | 20 ++-- spec/controllers/application_controller_spec.rb | 38 ++++++-- spec/javascripts/boards/boards_store_spec.js | 4 +- spec/lib/gitlab/popen_spec.rb | 8 ++ spec/lib/gitlab_danger_spec.rb | 2 +- spec/services/ci/create_pipeline_service_spec.rb | 5 + spec/services/service_response_spec.rb | 14 +++ 58 files changed, 446 insertions(+), 349 deletions(-) delete mode 100755 bin/elastic_repo_indexer create mode 100644 changelogs/unreleased/29155-fix-devise-401-responses.yml create mode 100644 db/migrate/20190910212256_add_any_approver_rule_unique_indexes.rb create mode 100644 db/post_migrate/20190905091812_schedule_project_any_approval_rule_migration.rb create mode 100644 db/post_migrate/20190905091831_schedule_merge_request_any_approval_rule_migration.rb create mode 100644 doc/development/testing_guide/img/k9s.png delete mode 100644 doc/workflow/time-tracking/time-tracking-example.png delete mode 100644 doc/workflow/time-tracking/time-tracking-sidebar.png create mode 100644 doc/workflow/time_tracking/img/time_tracking_example_v12_2.png create mode 100644 doc/workflow/time_tracking/img/time_tracking_sidebar_v8_16.png create mode 100644 lib/gitlab/devise_failure.rb create mode 100644 qa/qa/resource/members.rb 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 -> awefwbwgtc ) 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: | | `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 Binary files /dev/null and b/doc/development/testing_guide/img/k9s.png 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

Helm deploys the Review App using the Cloud
Native images built by the CNG-mirror pipeline.

Cloud Native images are deployed to the `review-apps-ce` or `review-apps-ee`
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

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=` - filters down to all pods for a release. This aids in determining what is having issues in a single deployment + - `-lapp=` - 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 Binary files a/doc/workflow/time-tracking/time-tracking-example.png and /dev/null differ diff --git a/doc/workflow/time-tracking/time-tracking-sidebar.png b/doc/workflow/time-tracking/time-tracking-sidebar.png deleted file mode 100644 index 22124afed6f..00000000000 Binary files a/doc/workflow/time-tracking/time-tracking-sidebar.png and /dev/null 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 Binary files /dev/null and b/doc/workflow/time_tracking/img/time_tracking_example_v12_2.png differ diff --git a/doc/workflow/time_tracking/img/time_tracking_sidebar_v8_16.png b/doc/workflow/time_tracking/img/time_tracking_sidebar_v8_16.png new file mode 100644 index 00000000000..22124afed6f Binary files /dev/null and b/doc/workflow/time_tracking/img/time_tracking_sidebar_v8_16.png 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 -- cgit v1.2.1