diff options
128 files changed, 1412 insertions, 647 deletions
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index 016dac34bf9..917d38ec9f9 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -8.4.3 +8.4.4 @@ -127,7 +127,7 @@ gem 'asciidoctor-plantuml', '0.0.8' gem 'rouge', '~> 3.1' gem 'truncato', '~> 0.7.9' gem 'bootstrap_form', '~> 2.7.0' -gem 'nokogiri', '~> 1.8.4' +gem 'nokogiri', '~> 1.8.5' gem 'escape_utils', '~> 1.1' # Calendar rendering @@ -322,7 +322,7 @@ end group :development, :test do gem 'bootsnap', '~> 1.3' gem 'bullet', '~> 5.5.0', require: !!ENV['ENABLE_BULLET'] - gem 'pry-byebug', '~> 3.4.1', platform: :mri + gem 'pry-byebug', '~> 3.5.1', platform: :mri gem 'pry-rails', '~> 0.3.4' gem 'awesome_print', require: false @@ -337,13 +337,13 @@ group :development, :test do gem 'rspec-parameterized', require: false # Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826) - gem 'minitest', '~> 5.7.0' + gem 'minitest', '~> 5.11.0' # Generate Fake data gem 'ffaker', '~> 2.10' - gem 'capybara', '~> 2.15' - gem 'capybara-screenshot', '~> 1.0.0' + gem 'capybara', '~> 2.16.1' + gem 'capybara-screenshot', '~> 1.0.18' gem 'selenium-webdriver', '~> 3.12' gem 'spring', '~> 2.0.0' diff --git a/Gemfile.lock b/Gemfile.lock index 1f7f58d59e0..4dda0bcad18 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -97,16 +97,16 @@ GEM bundler-audit (0.5.0) bundler (~> 1.2) thor (~> 0.18) - byebug (9.0.6) - capybara (2.15.1) + byebug (9.1.0) + capybara (2.16.1) addressable mini_mime (>= 0.1.3) nokogiri (>= 1.3.3) rack (>= 1.0.0) rack-test (>= 0.5.4) xpath (~> 2.0) - capybara-screenshot (1.0.14) - capybara (>= 1.0, < 3) + capybara-screenshot (1.0.22) + capybara (>= 1.0, < 4) launchy carrierwave (1.3.0) activemodel (>= 4.0.0) @@ -463,7 +463,7 @@ GEM mini_magick (4.8.0) mini_mime (1.0.1) mini_portile2 (2.3.0) - minitest (5.7.0) + minitest (5.11.3) msgpack (1.2.4) multi_json (1.13.1) multi_xml (0.6.0) @@ -595,8 +595,8 @@ GEM pry (0.11.3) coderay (~> 1.1.0) method_source (~> 0.9.0) - pry-byebug (3.4.3) - byebug (>= 9.0, < 9.1) + pry-byebug (3.5.1) + byebug (~> 9.1) pry (~> 0.10) pry-rails (0.3.6) pry (>= 0.10.4) @@ -963,8 +963,8 @@ DEPENDENCIES browser (~> 2.5) bullet (~> 5.5.0) bundler-audit (~> 0.5.0) - capybara (~> 2.15) - capybara-screenshot (~> 1.0.0) + capybara (~> 2.16.1) + capybara-screenshot (~> 1.0.18) carrierwave (~> 1.3) charlock_holmes (~> 0.7.5) chronic (~> 0.10.2) @@ -1054,12 +1054,12 @@ DEPENDENCIES method_source (~> 0.8) mimemagic (~> 0.3.2) mini_magick - minitest (~> 5.7.0) + minitest (~> 5.11.0) mysql2 (~> 0.4.10) nakayoshi_fork (~> 0.0.4) net-ldap net-ssh (~> 5.0) - nokogiri (~> 1.8.4) + nokogiri (~> 1.8.5) oauth2 (~> 1.4) octokit (~> 4.9) omniauth (~> 1.8) @@ -1087,7 +1087,7 @@ DEPENDENCIES pg (~> 0.18.2) premailer-rails (~> 1.9.7) prometheus-client-mmap (~> 0.9.4) - pry-byebug (~> 3.4.1) + pry-byebug (~> 3.5.1) pry-rails (~> 0.3.4) puma (~> 3.12) puma_worker_killer @@ -1171,4 +1171,4 @@ DEPENDENCIES wikicloth (= 0.8.1) BUNDLED WITH - 1.17.1 + 1.17.3 diff --git a/app/assets/javascripts/blob_edit/blob_bundle.js b/app/assets/javascripts/blob_edit/blob_bundle.js index b07f951346e..5f64175362d 100644 --- a/app/assets/javascripts/blob_edit/blob_bundle.js +++ b/app/assets/javascripts/blob_edit/blob_bundle.js @@ -16,6 +16,7 @@ export default () => { const filePath = editBlobForm.data('blobFilename'); const currentAction = $('.js-file-title').data('currentAction'); const projectId = editBlobForm.data('project-id'); + const isMarkdown = editBlobForm.data('is-markdown'); const commitButton = $('.js-commit-button'); const cancelLink = $('.btn.btn-cancel'); @@ -27,7 +28,13 @@ export default () => { window.onbeforeunload = null; }); - new EditBlob(`${urlRoot}${assetsPath}`, filePath, currentAction, projectId); + new EditBlob({ + assetsPath: `${urlRoot}${assetsPath}`, + filePath, + currentAction, + projectId, + isMarkdown, + }); new NewCommitForm(editBlobForm); // returning here blocks page navigation diff --git a/app/assets/javascripts/blob_edit/edit_blob.js b/app/assets/javascripts/blob_edit/edit_blob.js index 6e19548eed2..011898a5e7a 100644 --- a/app/assets/javascripts/blob_edit/edit_blob.js +++ b/app/assets/javascripts/blob_edit/edit_blob.js @@ -6,22 +6,31 @@ import createFlash from '~/flash'; import { __ } from '~/locale'; import TemplateSelectorMediator from '../blob/file_template_mediator'; import getModeByFileExtension from '~/lib/utils/ace_utils'; +import { addEditorMarkdownListeners } from '~/lib/utils/text_markdown'; export default class EditBlob { - constructor(assetsPath, aceMode, currentAction, projectId) { - this.configureAceEditor(aceMode, assetsPath); + // The options object has: + // assetsPath, filePath, currentAction, projectId, isMarkdown + constructor(options) { + this.options = options; + this.configureAceEditor(); this.initModePanesAndLinks(); this.initSoftWrap(); - this.initFileSelectors(currentAction, projectId); + this.initFileSelectors(); } - configureAceEditor(filePath, assetsPath) { + configureAceEditor() { + const { filePath, assetsPath, isMarkdown } = this.options; ace.config.set('modePath', `${assetsPath}/ace`); ace.config.loadModule('ace/ext/searchbox'); ace.config.loadModule('ace/ext/modelist'); this.editor = ace.edit('editor'); + if (isMarkdown) { + addEditorMarkdownListeners(this.editor); + } + // This prevents warnings re: automatic scrolling being logged this.editor.$blockScrolling = Infinity; @@ -32,7 +41,8 @@ export default class EditBlob { } } - initFileSelectors(currentAction, projectId) { + initFileSelectors() { + const { currentAction, projectId } = this.options; this.fileTemplateMediator = new TemplateSelectorMediator({ currentAction, editor: this.editor, diff --git a/app/assets/javascripts/boards/components/modal/empty_state.vue b/app/assets/javascripts/boards/components/modal/empty_state.vue index 08408eb0b52..defd857b92c 100644 --- a/app/assets/javascripts/boards/components/modal/empty_state.vue +++ b/app/assets/javascripts/boards/components/modal/empty_state.vue @@ -45,7 +45,7 @@ export default { <section class="empty-state"> <div class="row"> <div class="col-12 col-md-6 order-md-last"> - <aside class="svg-content"><img :src="emptyStateSvg" /></aside> + <aside class="svg-content d-none d-md-block"><img :src="emptyStateSvg" /></aside> </div> <div class="col-12 col-md-6 order-md-first"> <div class="text-content"> diff --git a/app/assets/javascripts/lib/utils/text_markdown.js b/app/assets/javascripts/lib/utils/text_markdown.js index 1254ec798a6..84a617acb42 100644 --- a/app/assets/javascripts/lib/utils/text_markdown.js +++ b/app/assets/javascripts/lib/utils/text_markdown.js @@ -8,6 +8,10 @@ function selectedText(text, textarea) { return text.substring(textarea.selectionStart, textarea.selectionEnd); } +function addBlockTags(blockTag, selected) { + return `${blockTag}\n${selected}\n${blockTag}`; +} + function lineBefore(text, textarea) { var split; split = text @@ -24,19 +28,45 @@ function lineAfter(text, textarea) { .split('\n')[0]; } +function editorBlockTagText(text, blockTag, selected, editor) { + const lines = text.split('\n'); + const selectionRange = editor.getSelectionRange(); + const shouldRemoveBlock = + lines[selectionRange.start.row - 1] === blockTag && + lines[selectionRange.end.row + 1] === blockTag; + + if (shouldRemoveBlock) { + if (blockTag !== null) { + // ace is globally defined + // eslint-disable-next-line no-undef + const { Range } = ace.require('ace/range'); + const lastLine = lines[selectionRange.end.row + 1]; + const rangeWithBlockTags = new Range( + lines[selectionRange.start.row - 1], + 0, + selectionRange.end.row + 1, + lastLine.length, + ); + editor.getSelection().setSelectionRange(rangeWithBlockTags); + } + return selected; + } + return addBlockTags(blockTag, selected); +} + function blockTagText(text, textArea, blockTag, selected) { - const before = lineBefore(text, textArea); - const after = lineAfter(text, textArea); - if (before === blockTag && after === blockTag) { + const shouldRemoveBlock = + lineBefore(text, textArea) === blockTag && lineAfter(text, textArea) === blockTag; + + if (shouldRemoveBlock) { // To remove the block tag we have to select the line before & after if (blockTag != null) { textArea.selectionStart = textArea.selectionStart - (blockTag.length + 1); textArea.selectionEnd = textArea.selectionEnd + (blockTag.length + 1); } return selected; - } else { - return blockTag + '\n' + selected + '\n' + blockTag; } + return addBlockTags(blockTag, selected); } function moveCursor({ @@ -46,33 +76,48 @@ function moveCursor({ positionBetweenTags, removedLastNewLine, select, + editor, + editorSelectionStart, + editorSelectionEnd, }) { var pos; - if (!textArea.setSelectionRange) { + if (textArea && !textArea.setSelectionRange) { return; } if (select && select.length > 0) { - // calculate the part of the text to be selected - const startPosition = textArea.selectionStart - (tag.length - tag.indexOf(select)); - const endPosition = startPosition + select.length; - return textArea.setSelectionRange(startPosition, endPosition); - } - if (textArea.selectionStart === textArea.selectionEnd) { - if (positionBetweenTags) { - pos = textArea.selectionStart - tag.length; - } else { - pos = textArea.selectionStart; + if (textArea) { + // calculate the part of the text to be selected + const startPosition = textArea.selectionStart - (tag.length - tag.indexOf(select)); + const endPosition = startPosition + select.length; + return textArea.setSelectionRange(startPosition, endPosition); + } else if (editor) { + editor.navigateLeft(tag.length - tag.indexOf(select)); + editor.getSelection().selectAWord(); + return; } + } + if (textArea) { + if (textArea.selectionStart === textArea.selectionEnd) { + if (positionBetweenTags) { + pos = textArea.selectionStart - tag.length; + } else { + pos = textArea.selectionStart; + } - if (removedLastNewLine) { - pos -= 1; - } + if (removedLastNewLine) { + pos -= 1; + } - if (cursorOffset) { - pos -= cursorOffset; - } + if (cursorOffset) { + pos -= cursorOffset; + } - return textArea.setSelectionRange(pos, pos); + return textArea.setSelectionRange(pos, pos); + } + } else if (editor && editorSelectionStart.row === editorSelectionEnd.row) { + if (positionBetweenTags) { + editor.navigateLeft(tag.length); + } } } @@ -85,6 +130,7 @@ export function insertMarkdownText({ selected = '', wrap, select, + editor, }) { var textToInsert, selectedSplit, @@ -92,11 +138,20 @@ export function insertMarkdownText({ removedLastNewLine, removedFirstNewLine, currentLineEmpty, - lastNewLine; + lastNewLine, + editorSelectionStart, + editorSelectionEnd; removedLastNewLine = false; removedFirstNewLine = false; currentLineEmpty = false; + if (editor) { + const selectionRange = editor.getSelectionRange(); + + editorSelectionStart = selectionRange.start; + editorSelectionEnd = selectionRange.end; + } + // check for link pattern and selected text is an URL // if so fill in the url part instead of the text part of the pattern. if (tag === LINK_TAG_PATTERN) { @@ -119,14 +174,27 @@ export function insertMarkdownText({ } // Remove the last newline - if (textArea.selectionEnd - textArea.selectionStart > selected.replace(/\n$/, '').length) { - removedLastNewLine = true; - selected = selected.replace(/\n$/, ''); + if (textArea) { + if (textArea.selectionEnd - textArea.selectionStart > selected.replace(/\n$/, '').length) { + removedLastNewLine = true; + selected = selected.replace(/\n$/, ''); + } + } else if (editor) { + if (editorSelectionStart.row !== editorSelectionEnd.row) { + removedLastNewLine = true; + selected = selected.replace(/\n$/, ''); + } } selectedSplit = selected.split('\n'); - if (!wrap) { + if (editor && !wrap) { + lastNewLine = editor.getValue().split('\n')[editorSelectionStart.row]; + + if (/^\s*$/.test(lastNewLine)) { + currentLineEmpty = true; + } + } else if (textArea && !wrap) { lastNewLine = textArea.value.substr(0, textArea.selectionStart).lastIndexOf('\n'); // Check whether the current line is empty or consists only of spaces(=handle as empty) @@ -135,13 +203,19 @@ export function insertMarkdownText({ } } - startChar = !wrap && !currentLineEmpty && textArea.selectionStart > 0 ? '\n' : ''; + const isBeginning = + (textArea && textArea.selectionStart === 0) || + (editor && editorSelectionStart.column === 0 && editorSelectionStart.row === 0); + + startChar = !wrap && !currentLineEmpty && !isBeginning ? '\n' : ''; const textPlaceholder = '{text}'; if (selectedSplit.length > 1 && (!wrap || (blockTag != null && blockTag !== ''))) { if (blockTag != null && blockTag !== '') { - textToInsert = blockTagText(text, textArea, blockTag, selected); + textToInsert = editor + ? editorBlockTagText(text, blockTag, selected, editor) + : blockTagText(text, textArea, blockTag, selected); } else { textToInsert = selectedSplit .map(function(val) { @@ -170,7 +244,11 @@ export function insertMarkdownText({ textToInsert += '\n'; } - insertText(textArea, textToInsert); + if (editor) { + editor.insert(textToInsert); + } else { + insertText(textArea, textToInsert); + } return moveCursor({ textArea, tag: tag.replace(textPlaceholder, selected), @@ -178,6 +256,9 @@ export function insertMarkdownText({ positionBetweenTags: wrap && selected.length === 0, removedLastNewLine, select, + editor, + editorSelectionStart, + editorSelectionEnd, }); } @@ -217,6 +298,25 @@ export function addMarkdownListeners(form) { }); } +export function addEditorMarkdownListeners(editor) { + $('.js-md') + .off('click') + .on('click', function(e) { + const { mdTag, mdBlock, mdPrepend, mdSelect } = $(e.currentTarget).data(); + + insertMarkdownText({ + tag: mdTag, + blockTag: mdBlock, + wrap: !mdPrepend, + select: mdSelect, + selected: editor.getSelectedText(), + text: editor.getValue(), + editor, + }); + editor.focus(); + }); +} + export function removeMarkdownListeners(form) { return $('.js-md', form).off('click'); } diff --git a/app/assets/javascripts/notes/components/note_form.vue b/app/assets/javascripts/notes/components/note_form.vue index 9b7f3d3588d..e78596f8b52 100644 --- a/app/assets/javascripts/notes/components/note_form.vue +++ b/app/assets/javascripts/notes/components/note_form.vue @@ -226,7 +226,7 @@ export default { <button :disabled="isDisabled" type="button" - class="js-vue-issue-save btn btn-success js-comment-button" + class="js-vue-issue-save btn btn-success js-comment-button qa-reply-comment-button" @click="handleUpdate();" > {{ saveButtonTitle }} diff --git a/app/assets/javascripts/notes/components/toggle_replies_widget.vue b/app/assets/javascripts/notes/components/toggle_replies_widget.vue index 72a8ff28466..f1b0b12bdce 100644 --- a/app/assets/javascripts/notes/components/toggle_replies_widget.vue +++ b/app/assets/javascripts/notes/components/toggle_replies_widget.vue @@ -57,7 +57,7 @@ export default { tooltip-placement="bottom" /> </div> - <button class="btn btn-link js-replies-text" type="button" @click="toggle"> + <button class="btn btn-link js-replies-text qa-expand-replies" type="button" @click="toggle"> {{ replies.length }} {{ n__('reply', 'replies', replies.length) }} </button> {{ __('Last reply by') }} @@ -66,7 +66,11 @@ export default { </a> <time-ago-tooltip :time="lastReply.created_at" tooltip-placement="bottom" /> </template> - <span v-else class="collapse-replies-btn js-collapse-replies" @click="toggle"> + <span + v-else + class="collapse-replies-btn js-collapse-replies qa-collapse-replies" + @click="toggle" + > <icon name="chevron-down" /> {{ s__('Notes|Collapse replies') }} </span> </li> diff --git a/app/assets/javascripts/vue_shared/components/callout.vue b/app/assets/javascripts/vue_shared/components/callout.vue index ddbb14ae812..56bafebf4ce 100644 --- a/app/assets/javascripts/vue_shared/components/callout.vue +++ b/app/assets/javascripts/vue_shared/components/callout.vue @@ -11,13 +11,14 @@ export default { }, message: { type: String, - required: true, + required: false, + default: '', }, }, }; </script> <template> <div :class="`bs-callout bs-callout-${category}`" role="alert" aria-live="assertive"> - {{ message }} + {{ message }} <slot></slot> </div> </template> diff --git a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue index e833a8e0483..95f4395ac13 100644 --- a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue +++ b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue @@ -67,6 +67,7 @@ export default { // In both cases we should render the defaultAvatarUrl sanitizedSource() { let baseSrc = this.imgSrc === '' || this.imgSrc === null ? defaultAvatarUrl : this.imgSrc; + // Only adds the width to the URL if its not a base64 data image if (!baseSrc.startsWith('data:') && !baseSrc.includes('?')) baseSrc += `?width=${this.size}`; return baseSrc; }, diff --git a/app/assets/stylesheets/bootstrap_migration.scss b/app/assets/stylesheets/bootstrap_migration.scss index f0671e36130..587127bb059 100644 --- a/app/assets/stylesheets/bootstrap_migration.scss +++ b/app/assets/stylesheets/bootstrap_migration.scss @@ -70,6 +70,17 @@ h6, margin-bottom: 10px; } +/* Our adjustments to hx & .hx above add unnecessary margins to modal-title + and page-title in modals, so we set them to 0 in order to have properly + formatted modal headers. */ +.modal-header { + .modal-title, + .page-title { + margin-top: 0; + margin-bottom: 0; + } +} + h5, .h5 { font-size: $gl-font-size; @@ -134,7 +145,8 @@ table { pointer-events: none; } -.popover { +.popover, +.popover-header { font-size: 14px; } @@ -142,7 +154,9 @@ table { @include media-breakpoint-up($breakpoint) { $infix: breakpoint-infix($breakpoint, $grid-breakpoints); - .d#{$infix}-table-header-group { display: table-header-group !important; } + .d#{$infix}-table-header-group { + display: table-header-group !important; + } } } diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss index 834e7ffce81..62d471bc30c 100644 --- a/app/assets/stylesheets/framework.scss +++ b/app/assets/stylesheets/framework.scss @@ -31,7 +31,6 @@ @import 'framework/logo'; @import 'framework/markdown_area'; @import 'framework/media_object'; -@import 'framework/mobile'; @import 'framework/modal'; @import 'framework/pagination'; @import 'framework/panels'; diff --git a/app/assets/stylesheets/framework/animations.scss b/app/assets/stylesheets/framework/animations.scss index 549a8730301..43d4044033f 100644 --- a/app/assets/stylesheets/framework/animations.scss +++ b/app/assets/stylesheets/framework/animations.scss @@ -260,3 +260,25 @@ $skeleton-line-widths: ( .slide-down-leave-to { transform: translateY(-30%); } + +@keyframes spin { + 0% { transform: rotate(0deg);} + 100% { transform: rotate(360deg);} +} + +/** COMMON ANIMATION CLASSES **/ +.transform-origin-center { @include webkit-prefix(transform-origin, 50% 50%); } +.animate-n-spin { @include webkit-prefix(animation-name, spin); } +.animate-c-infinite { @include webkit-prefix(animation-iteration-count, infinite); } +.animate-t-linear { @include webkit-prefix(animation-timing-function, linear); } +.animate-d-1 { @include webkit-prefix(animation-duration, 1s); } +.animate-d-2 { @include webkit-prefix(animation-duration, 2s); } + +/** COMPOSITE ANIMATION CLASSES **/ +.gl-spinner { + @include webkit-prefix(animation-name, spin); + @include webkit-prefix(animation-iteration-count, infinite); + @include webkit-prefix(animation-timing-function, linear); + @include webkit-prefix(animation-duration, 1s); + transform-origin: 50% 50%; +} diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 7d283dcfb71..5574873fa22 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -597,3 +597,11 @@ @include emoji-menu-toggle-button; } } + +.nav-links > li > a { + .badge.badge-pill { + @include media-breakpoint-down(xs) { display: none; } + } + + @include media-breakpoint-down(xs) { margin-right: 3px; } +} diff --git a/app/assets/stylesheets/framework/layout.scss b/app/assets/stylesheets/framework/layout.scss index 9218df9b40f..97cb9d90ff0 100644 --- a/app/assets/stylesheets/framework/layout.scss +++ b/app/assets/stylesheets/framework/layout.scss @@ -40,6 +40,14 @@ body { .content { margin: 0; + + @include media-breakpoint-down(xs) { margin-top: 20px; } + } + + @include media-breakpoint-down(xs) { + .container .title { + padding-left: 15px !important; + } } } diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss index 5609a2086e6..ce46d760d7b 100644 --- a/app/assets/stylesheets/framework/markdown_area.scss +++ b/app/assets/stylesheets/framework/markdown_area.scss @@ -173,7 +173,7 @@ svg { width: 14px; height: 14px; - margin-top: 3px; + vertical-align: middle; fill: $gl-text-color-secondary; } @@ -307,4 +307,8 @@ overflow: hidden; text-overflow: ellipsis; } + + .referenced-users { + margin-right: 0; + } } diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss deleted file mode 100644 index 3bb046d0e51..00000000000 --- a/app/assets/stylesheets/framework/mobile.scss +++ /dev/null @@ -1,88 +0,0 @@ -/** Common mobile (screen XS, SM) styles **/ -@include media-breakpoint-down(xs) { - .container .content { - margin-top: 20px; - } - - .nav-links > li > a { - padding: 10px; - font-size: 12px; - margin-right: 3px; - - .badge.badge-pill { - display: none; - } - } - - .referenced-users { - margin-right: 0; - } - - .issues-details-filters:not(.filtered-search-block), - .dash-projects-filters, - .check-all-holder { - display: none; - } - - .rss-btn { - display: none; - } - - .project-home-links { - display: none; - } - - .project-home-panel { - padding-left: 0 !important; - - .project-repo-buttons, - .git-clone-holder { - display: none; - } - } - - .group-buttons { - display: none; - } - - .container .title { - padding-left: 15px !important; - } - - .nav-links, - .nav-links { - li a { - font-size: 14px; - padding: 19px 10px; - } - } - - .activity-filter-block { - display: none; - } - - .projects-search-form { - .btn { - display: none; - } - } -} - -@include media-breakpoint-down(sm) { - .issues-filters { - .milestone-filter { - display: none; - } - } - - .page-title { - .note-created-ago, - .new-issue-link { - display: none; - } - } - - aside:not(.right-sidebar) { - display: none; - } -} diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss index 95291b4a9ad..46d40ea7aa5 100644 --- a/app/assets/stylesheets/framework/modal.scss +++ b/app/assets/stylesheets/framework/modal.scss @@ -29,10 +29,6 @@ padding-right: 28px; } } - - .page-title { - margin-top: 0; - } } .modal-body { diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 7e53f1ec48d..d92d81b2cb5 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -32,6 +32,15 @@ $gray-dark: darken($gray-light, $darken-dark-factor); $gray-darker: #eee; $gray-darkest: #c4c4c4; +$black: #000; +$black-transparent: rgba(0, 0, 0, 0.3); +$almost-black: #242424; + +$t-gray-a-02: rgba($black, 0.02); +$t-gray-a-04: rgba($black, 0.04); +$t-gray-a-06: rgba($black, 0.06); +$t-gray-a-08: rgba($black, 0.08); + $gl-gray-100: #dddddd; $gl-gray-200: #cccccc; $gl-gray-350: #aaaaaa; @@ -170,11 +179,6 @@ $theme-light-red-500: #c24b38; $theme-light-red-600: #b03927; $theme-light-red-700: #a62e21; -$black: #000; -$black-transparent: rgba(0, 0, 0, 0.3); -$shadow-color: rgba($black, 0.1); -$almost-black: #242424; - $border-white-light: darken($white-light, $darken-border-factor); $border-white-normal: darken($white-normal, $darken-border-factor); @@ -187,6 +191,7 @@ $border-gray-dark: darken($white-normal, $darken-border-factor); * UI elements */ $border-color: #e5e5e5; +$shadow-color: $t-gray-a-08; $well-expand-item: #e8f2f7; $well-inner-border: #eef0f2; $well-light-border: #f1f1f1; diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss index f46ff360496..5a988b184b6 100644 --- a/app/assets/stylesheets/pages/editor.scss +++ b/app/assets/stylesheets/pages/editor.scss @@ -128,6 +128,10 @@ width: 100%; } } + + @media(max-width: map-get($grid-breakpoints, md)-1) { + clear: both; + } } .editor-ref { diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb index c0aa39d87c6..30e436365de 100644 --- a/app/controllers/projects/git_http_controller.rb +++ b/app/controllers/projects/git_http_controller.rb @@ -80,9 +80,7 @@ class Projects::GitHttpController < Projects::GitHttpClientController end def access_check - # Use the magic string '_any' to indicate we do not know what the - # changes are. This is also what gitlab-shell does. - access.check(git_command, '_any') + access.check(git_command, Gitlab::GitAccess::ANY) @project ||= access.project end diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 5a7c005fd06..c8e4e2e3df9 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -20,7 +20,7 @@ module ApplicationSettingsHelper def enabled_protocol case Gitlab::CurrentSettings.enabled_git_access_protocol when 'http' - gitlab_config.protocol + Gitlab.config.gitlab.protocol when 'ssh' 'ssh' end @@ -35,7 +35,7 @@ module ApplicationSettingsHelper end def http_enabled? - all_protocols_enabled? || enabled_protocol == 'http' + all_protocols_enabled? || Gitlab::CurrentSettings.enabled_git_access_protocol == 'http' end def enabled_project_button(project, protocol) diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index 3dea0975beb..23d6684a8e6 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -177,7 +177,8 @@ module BlobHelper 'relative-url-root' => Rails.application.config.relative_url_root, 'assets-prefix' => Gitlab::Application.config.assets.prefix, 'blob-filename' => @blob && @blob.path, - 'project-id' => project.id + 'project-id' => project.id, + 'is-markdown' => @blob && @blob.path && Gitlab::MarkupHelper.gitlab_markdown?(@blob.path) } end diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index b128a254b96..aeb35538d67 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -221,6 +221,10 @@ module Ci next unless build.project build.deployment&.drop + end + + after_transition any => [:failed] do |build| + next unless build.project if build.retry_failure? begin diff --git a/app/models/clusters/applications/ingress.rb b/app/models/clusters/applications/ingress.rb index 8f8790585a3..7799f069742 100644 --- a/app/models/clusters/applications/ingress.rb +++ b/app/models/clusters/applications/ingress.rb @@ -23,7 +23,7 @@ module Clusters FETCH_IP_ADDRESS_DELAY = 30.seconds state_machine :status do - before_transition any => [:installed] do |application| + after_transition any => [:installed] do |application| application.run_after_commit do ClusterWaitForIngressIpAddressWorker.perform_in( FETCH_IP_ADDRESS_DELAY, application.name, application.id) diff --git a/app/models/clusters/applications/knative.rb b/app/models/clusters/applications/knative.rb index 0c72d7d8340..0a3168afe68 100644 --- a/app/models/clusters/applications/knative.rb +++ b/app/models/clusters/applications/knative.rb @@ -5,7 +5,7 @@ module Clusters class Knative < ActiveRecord::Base VERSION = '0.2.2'.freeze REPOSITORY = 'https://storage.googleapis.com/triggermesh-charts'.freeze - + METRICS_CONFIG = 'https://storage.googleapis.com/triggermesh-charts/istio-metrics.yaml'.freeze FETCH_IP_ADDRESS_DELAY = 30.seconds self.table_name = 'clusters_applications_knative' @@ -20,7 +20,7 @@ module Clusters self.reactive_cache_key = ->(knative) { [knative.class.model_name.singular, knative.id] } state_machine :status do - before_transition any => [:installed] do |application| + after_transition any => [:installed] do |application| application.run_after_commit do ClusterWaitForIngressIpAddressWorker.perform_in( FETCH_IP_ADDRESS_DELAY, application.name, application.id) @@ -49,7 +49,8 @@ module Clusters rbac: cluster.platform_kubernetes_rbac?, chart: chart, files: files, - repository: REPOSITORY + repository: REPOSITORY, + postinstall: install_knative_metrics ) end @@ -94,6 +95,10 @@ module Clusters rescue Kubeclient::ResourceNotFoundError [] end + + def install_knative_metrics + ["kubectl apply -f #{METRICS_CONFIG}"] if cluster.application_prometheus_available? + end end end end diff --git a/app/models/clusters/applications/prometheus.rb b/app/models/clusters/applications/prometheus.rb index 46d0388a464..e25be522d68 100644 --- a/app/models/clusters/applications/prometheus.rb +++ b/app/models/clusters/applications/prometheus.rb @@ -50,7 +50,8 @@ module Clusters version: VERSION, rbac: cluster.platform_kubernetes_rbac?, chart: chart, - files: files + files: files, + postinstall: install_knative_metrics ) end @@ -74,6 +75,10 @@ module Clusters def kube_client cluster&.kubeclient&.core_client end + + def install_knative_metrics + ["kubectl apply -f #{Clusters::Applications::Knative::METRICS_CONFIG}"] if cluster.application_knative_available? + end end end end diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb index 7fe43cd2de0..6050955fbd8 100644 --- a/app/models/clusters/cluster.rb +++ b/app/models/clusters/cluster.rb @@ -63,6 +63,7 @@ module Clusters delegate :available?, to: :application_helm, prefix: true, allow_nil: true delegate :available?, to: :application_ingress, prefix: true, allow_nil: true delegate :available?, to: :application_prometheus, prefix: true, allow_nil: true + delegate :available?, to: :application_knative, prefix: true, allow_nil: true enum cluster_type: { instance_type: 1, diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index b937bef100b..6092c56b925 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -105,7 +105,9 @@ class MergeRequest < ActiveRecord::Base before_transition any => :opened do |merge_request| merge_request.merge_jid = nil + end + after_transition any => :opened do |merge_request| merge_request.run_after_commit do UpdateHeadPipelineForMergeRequestWorker.perform_async(merge_request.id) end diff --git a/app/models/project.rb b/app/models/project.rb index cd558752080..7a2bc8b78b3 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -912,11 +912,16 @@ class Project < ActiveRecord::Base def new_issuable_address(author, address_type) return unless Gitlab::IncomingEmail.supports_issue_creation? && author + # check since this can come from a request parameter + return unless %w(issue merge_request).include?(address_type) + author.ensure_incoming_email_token! - suffix = address_type == 'merge_request' ? '+merge-request' : '' - Gitlab::IncomingEmail.reply_address( - "#{full_path}#{suffix}+#{author.incoming_email_token}") + suffix = address_type.dasherize + + # example: incoming+h5bp-html5-boilerplate-8-1234567890abcdef123456789-issue@localhost.com + # example: incoming+h5bp-html5-boilerplate-8-1234567890abcdef123456789-merge-request@localhost.com + Gitlab::IncomingEmail.reply_address("#{full_path_slug}-#{project_id}-#{author.incoming_email_token}-#{suffix}") end def build_commit_note(commit) @@ -1928,23 +1933,15 @@ class Project < ActiveRecord::Base .where('project_authorizations.project_id = merge_requests.target_project_id') .limit(1) .select(1) - source_of_merge_requests.opened - .where(allow_collaboration: true) - .where('EXISTS (?)', developer_access_exists) + merge_requests_allowing_collaboration.where('EXISTS (?)', developer_access_exists) end - def branch_allows_collaboration?(user, branch_name) - return false unless user - - cache_key = "user:#{user.id}:#{branch_name}:branch_allows_push" - - memoized_results = strong_memoize(:branch_allows_collaboration) do - Hash.new do |result, cache_key| - result[cache_key] = fetch_branch_allows_collaboration?(user, branch_name) - end - end + def any_branch_allows_collaboration?(user) + fetch_branch_allows_collaboration(user) + end - memoized_results[cache_key] + def branch_allows_collaboration?(user, branch_name) + fetch_branch_allows_collaboration(user, branch_name) end def licensed_features @@ -2018,6 +2015,12 @@ class Project < ActiveRecord::Base private + def merge_requests_allowing_collaboration(source_branch = nil) + relation = source_of_merge_requests.opened.where(allow_collaboration: true) + relation = relation.where(source_branch: source_branch) if source_branch + relation + end + def create_new_pool_repository pool = begin create_pool_repository!(shard: Shard.by_name(repository_storage), source_project: self) @@ -2142,26 +2145,19 @@ class Project < ActiveRecord::Base raise ex end - def fetch_branch_allows_collaboration?(user, branch_name) - check_access = -> do - next false if empty_repo? + def fetch_branch_allows_collaboration(user, branch_name = nil) + return false unless user - merge_requests = source_of_merge_requests.opened - .where(allow_collaboration: true) + Gitlab::SafeRequestStore.fetch("project-#{id}:branch-#{branch_name}:user-#{user.id}:branch_allows_collaboration") do + next false if empty_repo? # Issue for N+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/49322 Gitlab::GitalyClient.allow_n_plus_1_calls do - if branch_name - merge_requests.find_by(source_branch: branch_name)&.can_be_merged_by?(user) - else - merge_requests.any? { |merge_request| merge_request.can_be_merged_by?(user) } + merge_requests_allowing_collaboration(branch_name).any? do |merge_request| + merge_request.can_be_merged_by?(user) end end end - - Gitlab::SafeRequestStore.fetch("project-#{id}:branch-#{branch_name}:user-#{user.id}:branch_allows_collaboration") do - check_access.call - end end def services_templates diff --git a/app/models/project_services/kubernetes_service.rb b/app/models/project_services/kubernetes_service.rb index b801fd84a07..f69edd60003 100644 --- a/app/models/project_services/kubernetes_service.rb +++ b/app/models/project_services/kubernetes_service.rb @@ -53,7 +53,7 @@ class KubernetesService < DeploymentService end def description - 'Kubernetes / Openshift integration' + 'Kubernetes / OpenShift integration' end def help diff --git a/app/views/dashboard/_activities.html.haml b/app/views/dashboard/_activities.html.haml index 9c246e19faa..4359a2c3c2b 100644 --- a/app/views/dashboard/_activities.html.haml +++ b/app/views/dashboard/_activities.html.haml @@ -1,7 +1,7 @@ .nav-block.activities = render 'shared/event_filter' .controls - = link_to dashboard_projects_path(rss_url_options), class: 'btn rss-btn has-tooltip', title: 'Subscribe' do + = link_to dashboard_projects_path(rss_url_options), class: 'btn d-none d-sm-inline-block has-tooltip', title: 'Subscribe' do %i.fa.fa-rss .content_list diff --git a/app/views/groups/_activities.html.haml b/app/views/groups/_activities.html.haml index 82a497289f3..13df1e57125 100644 --- a/app/views/groups/_activities.html.haml +++ b/app/views/groups/_activities.html.haml @@ -1,7 +1,7 @@ .nav-block.activities = render 'shared/event_filter' .controls - = link_to group_path(@group, rss_url_options), class: 'btn rss-btn has-tooltip' , title: 'Subscribe' do + = link_to group_path(@group, rss_url_options), class: 'btn d-none d-sm-inline-block has-tooltip' , title: 'Subscribe' do %i.fa.fa-rss .content_list diff --git a/app/views/groups/_home_panel.html.haml b/app/views/groups/_home_panel.html.haml index 6219da2c715..88e401081f4 100644 --- a/app/views/groups/_home_panel.html.haml +++ b/app/views/groups/_home_panel.html.haml @@ -12,6 +12,6 @@ = markdown_field(@group, :description) - if current_user - .group-buttons + .group-buttons.d-none.d-sm-block = render 'shared/members/access_request_buttons', source: @group = render 'shared/notifications/button', notification_setting: @notification_setting diff --git a/app/views/projects/_activity.html.haml b/app/views/projects/_activity.html.haml index 6bf21570d41..31f1cf560e2 100644 --- a/app/views/projects/_activity.html.haml +++ b/app/views/projects/_activity.html.haml @@ -1,8 +1,8 @@ %div{ class: container_class } - .nav-block.activity-filter-block.activities + .nav-block.d-none.d-sm-block.activities = render 'shared/event_filter' .controls - = link_to project_path(@project, rss_url_options), title: s_("ProjectActivityRSS|Subscribe"), class: 'btn rss-btn has-tooltip' do + = link_to project_path(@project, rss_url_options), title: s_("ProjectActivityRSS|Subscribe"), class: 'btn d-none d-sm-inline-block has-tooltip' do = icon('rss') .content_list.project-activity{ :"data-href" => activity_project_path(@project) } diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml index 0f709c65d0e..03ba1104507 100644 --- a/app/views/projects/_md_preview.html.haml +++ b/app/views/projects/_md_preview.html.haml @@ -18,17 +18,7 @@ Preview %li.md-header-toolbar.active - = markdown_toolbar_button({ icon: "bold", data: { "md-tag" => "**" }, title: s_("MarkdownToolbar|Add bold text") }) - = markdown_toolbar_button({ icon: "italic", data: { "md-tag" => "*" }, title: s_("MarkdownToolbar|Add italic text") }) - = markdown_toolbar_button({ icon: "quote", data: { "md-tag" => "> ", "md-prepend" => true }, title: s_("MarkdownToolbar|Insert a quote") }) - = markdown_toolbar_button({ icon: "code", data: { "md-tag" => "`", "md-block" => "```" }, title: s_("MarkdownToolbar|Insert code") }) - = markdown_toolbar_button({ icon: "link", data: { "md-tag" => "[{text}](url)", "md-select" => "url" }, title: s_("MarkdownToolbar|Add a link") }) - = markdown_toolbar_button({ icon: "list-bulleted", data: { "md-tag" => "* ", "md-prepend" => true }, title: s_("MarkdownToolbar|Add a bullet list") }) - = markdown_toolbar_button({ icon: "list-numbered", data: { "md-tag" => "1. ", "md-prepend" => true }, title: s_("MarkdownToolbar|Add a numbered list") }) - = markdown_toolbar_button({ icon: "task-done", data: { "md-tag" => "* [ ] ", "md-prepend" => true }, title: s_("MarkdownToolbar|Add a task list") }) - = markdown_toolbar_button({ icon: "table", data: { "md-tag" => "| header | header |\n| ------ | ------ |\n| cell | cell |\n| cell | cell |", "md-prepend" => true }, title: s_("MarkdownToolbar|Add a table") }) - %button.toolbar-btn.toolbar-fullscreen-btn.js-zen-enter.has-tooltip{ type: "button", tabindex: -1, "aria-label": "Go full screen", title: s_("MarkdownToolbar|Go full screen"), data: { container: "body" } } - = sprite_icon("screen-full") + = render 'projects/blob/markdown_buttons', show_fullscreen_button: true .md-write-holder = yield diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml index 3c1f33ea95e..a54460f1196 100644 --- a/app/views/projects/blob/_editor.html.haml +++ b/app/views/projects/blob/_editor.html.haml @@ -1,4 +1,6 @@ - action = current_action?(:edit) || current_action?(:update) ? 'edit' : 'create' +- file_name = params[:id].split("/").last ||= "" +- is_markdown = Gitlab::MarkupHelper.gitlab_markdown?(file_name) .file-holder-bottom-radius.file-holder.file.append-bottom-default .js-file-title.file-title.clearfix{ data: { current_action: action } } @@ -17,6 +19,8 @@ required: true, class: 'form-control new-file-name js-file-path-name-input' .file-buttons + - if is_markdown + = render 'projects/blob/markdown_buttons', show_fullscreen_button: false = button_tag class: 'soft-wrap-toggle btn', type: 'button', tabindex: '-1' do %span.no-wrap = custom_icon('icon_no_wrap') diff --git a/app/views/projects/blob/_markdown_buttons.html.haml b/app/views/projects/blob/_markdown_buttons.html.haml new file mode 100644 index 00000000000..1d6acd86108 --- /dev/null +++ b/app/views/projects/blob/_markdown_buttons.html.haml @@ -0,0 +1,13 @@ +.md-header-toolbar.active + = markdown_toolbar_button({ icon: "bold", data: { "md-tag" => "**" }, title: s_("MarkdownToolbar|Add bold text") }) + = markdown_toolbar_button({ icon: "italic", data: { "md-tag" => "*" }, title: s_("MarkdownToolbar|Add italic text") }) + = markdown_toolbar_button({ icon: "quote", data: { "md-tag" => "> ", "md-prepend" => true }, title: s_("MarkdownToolbar|Insert a quote") }) + = markdown_toolbar_button({ icon: "code", data: { "md-tag" => "`", "md-block" => "```" }, title: s_("MarkdownToolbar|Insert code") }) + = markdown_toolbar_button({ icon: "link", data: { "md-tag" => "[{text}](url)", "md-select" => "url" }, title: s_("MarkdownToolbar|Add a link") }) + = markdown_toolbar_button({ icon: "list-bulleted", data: { "md-tag" => "* ", "md-prepend" => true }, title: s_("MarkdownToolbar|Add a bullet list") }) + = markdown_toolbar_button({ icon: "list-numbered", data: { "md-tag" => "1. ", "md-prepend" => true }, title: s_("MarkdownToolbar|Add a numbered list") }) + = markdown_toolbar_button({ icon: "task-done", data: { "md-tag" => "* [ ] ", "md-prepend" => true }, title: s_("MarkdownToolbar|Add a task list") }) + = markdown_toolbar_button({ icon: "table", data: { "md-tag" => "| header | header |\n| ------ | ------ |\n| cell | cell |\n| cell | cell |", "md-prepend" => true }, title: s_("MarkdownToolbar|Add a table") }) + - if show_fullscreen_button + %button.toolbar-btn.toolbar-fullscreen-btn.js-zen-enter.has-tooltip{ type: "button", tabindex: -1, "aria-label": "Go full screen", title: s_("MarkdownToolbar|Go full screen"), data: { container: "body" } } + = sprite_icon("screen-full") diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index 8c2fe2625c7..f048fb91304 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -55,7 +55,7 @@ - if can_report_spam = link_to 'Submit as spam', mark_as_spam_project_issue_path(@project, @issue), method: :post, class: 'd-none d-sm-none d-md-block btn btn-grouped btn-spam', title: 'Submit as spam' - if can_create_issue - = link_to new_project_issue_path(@project), class: 'd-none d-sm-none d-md-block btn btn-grouped new-issue-link btn-success btn-inverted', title: 'New issue', id: 'new_issue_link' do + = link_to new_project_issue_path(@project), class: 'd-none d-sm-none d-md-block btn btn-grouped btn-success btn-inverted', title: 'New issue', id: 'new_issue_link' do New issue .issue-details.issuable-details diff --git a/app/views/projects/services/prometheus/_metrics.html.haml b/app/views/projects/services/prometheus/_metrics.html.haml index 597c029f755..a1d74b91002 100644 --- a/app/views/projects/services/prometheus/_metrics.html.haml +++ b/app/views/projects/services/prometheus/_metrics.html.haml @@ -1,6 +1,6 @@ - project = local_assigns.fetch(:project) -.card.js-panel-monitored-metrics{ data: { active_metrics: active_common_project_prometheus_metrics_path(project, :json), metrics_help_path: help_page_path('user/project/integrations/prometheus_library/metrics') } } +.card.js-panel-monitored-metrics{ data: { active_metrics: active_common_project_prometheus_metrics_path(project, :json), metrics_help_path: help_page_path('user/project/integrations/prometheus_library/index') } } .card-header = s_('PrometheusService|Common metrics') %span.badge.badge-pill.js-monitored-count 0 diff --git a/app/views/projects/services/prometheus/_show.html.haml b/app/views/projects/services/prometheus/_show.html.haml index 1d0b0265bb7..9d4574c4590 100644 --- a/app/views/projects/services/prometheus/_show.html.haml +++ b/app/views/projects/services/prometheus/_show.html.haml @@ -4,7 +4,7 @@ = s_('PrometheusService|Metrics') %p = s_('PrometheusService|Common metrics are automatically monitored based on a library of metrics from popular exporters.') - = link_to s_('PrometheusService|More information'), help_page_path('user/project/integrations/prometheus_library/metrics'), target: '_blank', rel: "noopener noreferrer" + = link_to s_('PrometheusService|More information'), help_page_path('user/project/integrations/prometheus_library/index'), target: '_blank', rel: "noopener noreferrer" .col-lg-9 = render 'projects/services/prometheus/metrics', project: @project diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml index 026bc44a05f..458096f9dd6 100644 --- a/app/views/projects/tags/index.html.haml +++ b/app/views/projects/tags/index.html.haml @@ -27,7 +27,7 @@ - if can?(current_user, :push_code, @project) = link_to new_project_tag_path(@project), class: 'btn btn-success new-tag-btn' do = s_('TagsPage|New tag') - = link_to project_tags_path(@project, rss_url_options), title: _("Tags feed"), class: 'btn rss-btn has-tooltip' do + = link_to project_tags_path(@project, rss_url_options), title: _("Tags feed"), class: 'btn d-none d-sm-inline-block has-tooltip' do = icon("rss") = render_if_exists 'projects/commits/mirror_status' diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index 46634693067..20847378495 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -11,7 +11,7 @@ - if params[:search].present? = hidden_field_tag :search, params[:search] - if @can_bulk_update - .check-all-holder.hidden + .check-all-holder.d-none.d-sm-block.hidden = check_box_tag "check-all-issues", nil, false, class: "check-all-issues left" .issues-other-filters.filtered-search-wrapper .filtered-search-box diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index dd2cd36eac2..8da63a29ca6 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -71,7 +71,7 @@ = icon('twitter-square') - unless @user.website_url.blank? .profile-link-holder.middle-dot-divider - = link_to @user.short_website_url, @user.full_website_url, class: 'text-link', target: '_blank', rel: 'noopener noreferrer nofollow' + = link_to @user.short_website_url, @user.full_website_url, class: 'text-link', target: '_blank', rel: 'me noopener noreferrer nofollow' - unless @user.location.blank? .profile-link-holder.middle-dot-divider = icon('map-marker') diff --git a/changelogs/unreleased/27861-add-markdown-editing-buttons-to-the-file-editor.yml b/changelogs/unreleased/27861-add-markdown-editing-buttons-to-the-file-editor.yml new file mode 100644 index 00000000000..00eb5223d58 --- /dev/null +++ b/changelogs/unreleased/27861-add-markdown-editing-buttons-to-the-file-editor.yml @@ -0,0 +1,5 @@ +--- +title: Add markdown helper buttons to file editor +merge_request: 23480 +author: +type: added diff --git a/changelogs/unreleased/29951-issue-creation-by-email-without-subaddressing.yml b/changelogs/unreleased/29951-issue-creation-by-email-without-subaddressing.yml new file mode 100644 index 00000000000..4139099eac3 --- /dev/null +++ b/changelogs/unreleased/29951-issue-creation-by-email-without-subaddressing.yml @@ -0,0 +1,5 @@ +--- +title: No longer require email subaddressing for issue creation by email +merge_request: 23523 +author: +type: changed diff --git a/changelogs/unreleased/54386-integrate-mobile-css-framework-into-specific-frameworks.yml b/changelogs/unreleased/54386-integrate-mobile-css-framework-into-specific-frameworks.yml new file mode 100644 index 00000000000..e446d2a2781 --- /dev/null +++ b/changelogs/unreleased/54386-integrate-mobile-css-framework-into-specific-frameworks.yml @@ -0,0 +1,5 @@ +--- +title: Remove framework/mobile.scss +merge_request: 23301 +author: Takuya Noguchi +type: other diff --git a/changelogs/unreleased/54844-report-syntax-dep-scan-ado.yml b/changelogs/unreleased/54844-report-syntax-dep-scan-ado.yml new file mode 100644 index 00000000000..95fc5cb804d --- /dev/null +++ b/changelogs/unreleased/54844-report-syntax-dep-scan-ado.yml @@ -0,0 +1,5 @@ +--- +title: Use reports syntax for Dependency scanning in Auto DevOps +merge_request: 24081 +author: +type: added diff --git a/changelogs/unreleased/55883-modal-header-titles-have-an-unnecessary-top-margin.yml b/changelogs/unreleased/55883-modal-header-titles-have-an-unnecessary-top-margin.yml new file mode 100644 index 00000000000..7dc783cc2b8 --- /dev/null +++ b/changelogs/unreleased/55883-modal-header-titles-have-an-unnecessary-top-margin.yml @@ -0,0 +1,5 @@ +--- +title: Remove top margin in modal header titles +merge_request: 24108 +author: +type: fixed diff --git a/changelogs/unreleased/allow-basic-auth-on-go-get-middleware.yml b/changelogs/unreleased/allow-basic-auth-on-go-get-middleware.yml new file mode 100644 index 00000000000..fda3fdc28cf --- /dev/null +++ b/changelogs/unreleased/allow-basic-auth-on-go-get-middleware.yml @@ -0,0 +1,5 @@ +--- +title: Allow basic authentication on go get middleware +merge_request: 23497 +author: Morty Choi @mortyccp +type: changed diff --git a/changelogs/unreleased/deprecated-positional-spec-arguments.yml b/changelogs/unreleased/deprecated-positional-spec-arguments.yml new file mode 100644 index 00000000000..8e541df1ad4 --- /dev/null +++ b/changelogs/unreleased/deprecated-positional-spec-arguments.yml @@ -0,0 +1,5 @@ +--- +title: 'Fix deprecation: Using positional arguments in integration tests' +merge_request: 24110 +author: Jasper Maes +type: other diff --git a/changelogs/unreleased/knative-prometheus.yml b/changelogs/unreleased/knative-prometheus.yml new file mode 100644 index 00000000000..606c5332474 --- /dev/null +++ b/changelogs/unreleased/knative-prometheus.yml @@ -0,0 +1,5 @@ +--- +title: Add Knative metrics to Prometheus +merge_request: 23972 +author: Chris Baumbauer +type: added diff --git a/changelogs/unreleased/sh-drop-webhooks-project-export.yml b/changelogs/unreleased/sh-drop-webhooks-project-export.yml new file mode 100644 index 00000000000..217373bce66 --- /dev/null +++ b/changelogs/unreleased/sh-drop-webhooks-project-export.yml @@ -0,0 +1,5 @@ +--- +title: Drop Webhooks from project import/export config +merge_request: 24121 +author: +type: fixed diff --git a/changelogs/unreleased/sh-fix-clone-url-for-https.yml b/changelogs/unreleased/sh-fix-clone-url-for-https.yml new file mode 100644 index 00000000000..6a17d566685 --- /dev/null +++ b/changelogs/unreleased/sh-fix-clone-url-for-https.yml @@ -0,0 +1,5 @@ +--- +title: Fix clone URL not showing if protocol is HTTPS +merge_request: 24131 +author: +type: fixed diff --git a/changelogs/unreleased/tz-user-popover-follow-up.yml b/changelogs/unreleased/tz-user-popover-follow-up.yml new file mode 100644 index 00000000000..d8f004beaa0 --- /dev/null +++ b/changelogs/unreleased/tz-user-popover-follow-up.yml @@ -0,0 +1,4 @@ +title: Changed Userpopover Fixtures and shadow color +merge_request: 23768 +author: +type: other diff --git a/doc/api/services.md b/doc/api/services.md index f122bac6f1f..c4edaa17815 100644 --- a/doc/api/services.md +++ b/doc/api/services.md @@ -573,7 +573,7 @@ DELETE /projects/:id/services/jira ## Kubernetes -Kubernetes / Openshift integration +Kubernetes / OpenShift integration CAUTION: **Warning:** Kubernetes service integration has been deprecated in GitLab 10.3. API service endpoints will continue to work as long as the Kubernetes service is active, however if the service is inactive API endpoints will automatically return a `400 Bad Request`. Read [GitLab 10.3 release post](https://about.gitlab.com/2017/12/22/gitlab-10-3-released/#kubernetes-integration-service) for more information. diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 5d87a5c0a1f..e94b5211d59 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -1678,9 +1678,9 @@ NOTE: **Note:** `include` requires the external YAML files to have the extensions `.yml` or `.yaml`. The external file will not be included if the extension is missing. -You can define it either as a single string, or, in case you want to include -more than one files, an array of different values . The following examples -are both valid cases: +You can include your extra YAML file either as a single string or +as an array of multiple values. You can also use full paths or +relative URLs. The following examples are both valid: ```yaml # Single string diff --git a/doc/development/documentation/index.md b/doc/development/documentation/index.md index 256e0476c2f..8ffddb9b669 100644 --- a/doc/development/documentation/index.md +++ b/doc/development/documentation/index.md @@ -4,7 +4,7 @@ description: Learn how to contribute to GitLab Documentation. # GitLab Documentation guidelines -GitLab's documentation is [intended as the single source of truth (SSOT)](https://about.gitab.com/handbook/documentation/) for information about how to configure, use, and troubleshoot GitLab. The documentation contains use cases and usage instructions covering every GitLab feature, organized by product area and subject. This includes topics and workflows that span multiple GitLab features, as well as the use of GitLab with other applications. +GitLab's documentation is [intended as the single source of truth (SSOT)](https://about.gitlab.com/handbook/documentation/) for information about how to configure, use, and troubleshoot GitLab. The documentation contains use cases and usage instructions covering every GitLab feature, organized by product area and subject. This includes topics and workflows that span multiple GitLab features, as well as the use of GitLab with other applications. In addition to this page, the following resources to help craft and contribute documentation are available: diff --git a/doc/install/openshift_and_gitlab/index.md b/doc/install/openshift_and_gitlab/index.md index 4c88b6f97fc..25a87dbd9de 100644 --- a/doc/install/openshift_and_gitlab/index.md +++ b/doc/install/openshift_and_gitlab/index.md @@ -18,7 +18,7 @@ In this tutorial, we will see how to deploy GitLab in OpenShift using GitLab's official Docker image while getting familiar with the web interface and CLI tools that will help us achieve our goal. -For a video demonstration on installing GitLab on Openshift, check the article [In 13 minutes from Kubernetes to a complete application development tool](https://about.gitlab.com/2016/11/14/idea-to-production/). +For a video demonstration on installing GitLab on OpenShift, check the article [In 13 minutes from Kubernetes to a complete application development tool](https://about.gitlab.com/2016/11/14/idea-to-production/). --- @@ -518,7 +518,7 @@ PaaS and managing your applications with the ease of containers. [templates]: https://docs.openshift.org/latest/architecture/core_concepts/templates.html "Documentation - OpenShift templates" [old-post]: https://blog.openshift.com/deploy-gitlab-openshift/ "Old post - Deploy GitLab on OpenShift" [line]: https://gitlab.com/gitlab-org/omnibus-gitlab/blob/658c065c8d022ce858dd63eaeeadb0b2ddc8deea/docker/openshift-template.json#L239 "GitLab - OpenShift template" -[oc-gh]: https://github.com/openshift/origin/releases/tag/v1.3.0 "Openshift 1.3.0 release on GitHub" +[oc-gh]: https://github.com/openshift/origin/releases/tag/v1.3.0 "OpenShift Origin 1.3.0 release on GitHub" [ha]: ../../administration/high_availability/gitlab.html "Documentation - GitLab High Availability" [replicas]: https://docs.openshift.org/latest/architecture/core_concepts/deployments.html#replication-controllers "Documentation - Replication controller" [autoscaling]: https://docs.openshift.org/latest/dev_guide/pod_autoscaling.html "Documentation - Autoscale" diff --git a/doc/topics/authentication/index.md b/doc/topics/authentication/index.md index 766a23c419d..a354d3e7884 100644 --- a/doc/topics/authentication/index.md +++ b/doc/topics/authentication/index.md @@ -45,4 +45,4 @@ This page gathers all the resources for the topic **Authentication** within GitL - [Kanboard Plugin GitLab Authentication](https://github.com/kanboard/plugin-gitlab-auth) - [Jenkins GitLab OAuth Plugin](https://wiki.jenkins.io/display/JENKINS/GitLab+OAuth+Plugin) - [How to customize GitLab to support OpenID authentication](http://eric.van-der-vlist.com/blog/2013/11/23/how-to-customize-gitlab-to-support-openid-authentication/) -- [Openshift - Configuring Authentication and User Agent](https://docs.openshift.org/latest/install_config/configuring_authentication.html#GitLab) +- [OKD - Configuring Authentication and User Agent](https://docs.okd.io/latest/install_config/configuring_authentication.html#GitLab) diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md index b41f401e14c..e937d372384 100644 --- a/doc/topics/autodevops/index.md +++ b/doc/topics/autodevops/index.md @@ -340,6 +340,9 @@ Any security warnings are also NOTE: **Note:** The Auto SAST stage will be skipped on licenses other than Ultimate. +NOTE: **Note:** +The Auto SAST job requires GitLab Runner 11.5 or above. + ### Auto Dependency Scanning **[ULTIMATE]** > Introduced in [GitLab Ultimate][ee] 10.7. @@ -356,6 +359,9 @@ Any security warnings are also NOTE: **Note:** The Auto Dependency Scanning stage will be skipped on licenses other than Ultimate. +NOTE: **Note:** +The Auto Dependency Scanning job requires GitLab Runner 11.5 or above. + ### Auto License Management **[ULTIMATE]** > Introduced in [GitLab Ultimate][ee] 11.0. diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md index 7a3ee5a404c..5de8e66e7eb 100644 --- a/doc/user/project/integrations/prometheus.md +++ b/doc/user/project/integrations/prometheus.md @@ -88,7 +88,7 @@ to integrate with. Once configured, GitLab will attempt to retrieve performance metrics for any environment which has had a successful deployment. -GitLab will automatically scan the Prometheus server for metrics from known serves like Kubernetes and NGINX, and attempt to identify individual environment. The supported metrics and scan process is detailed in our [Prometheus Metric Library documentation](prometheus_library/metrics.html). +GitLab will automatically scan the Prometheus server for metrics from known serves like Kubernetes and NGINX, and attempt to identify individual environment. The supported metrics and scan process is detailed in our [Prometheus Metric Library documentation](prometheus_library/index.md). You can view the performance dashboard for an environment by [clicking on the monitoring button](../../../ci/environments.md#monitoring-environments). diff --git a/doc/user/project/settings/import_export.md b/doc/user/project/settings/import_export.md index cb68c9318bc..3bbfa74f4b7 100644 --- a/doc/user/project/settings/import_export.md +++ b/doc/user/project/settings/import_export.md @@ -71,6 +71,7 @@ The following items will NOT be exported: - Build traces and artifacts - Container registry images - CI variables +- Webhooks - Any encrypted tokens ## Exporting a project and its data diff --git a/lib/gitlab/checks/base_checker.rb b/lib/gitlab/checks/base_checker.rb index 7fbcf6a4ff4..09b17b5b76b 100644 --- a/lib/gitlab/checks/base_checker.rb +++ b/lib/gitlab/checks/base_checker.rb @@ -18,12 +18,16 @@ module Gitlab private + def creation? + Gitlab::Git.blank_ref?(oldrev) + end + def deletion? Gitlab::Git.blank_ref?(newrev) end def update? - !Gitlab::Git.blank_ref?(oldrev) && !deletion? + !creation? && !deletion? end def updated_from_web? diff --git a/lib/gitlab/checks/change_access.rb b/lib/gitlab/checks/change_access.rb index 7778d3068cc..8a57a3a6d9a 100644 --- a/lib/gitlab/checks/change_access.rb +++ b/lib/gitlab/checks/change_access.rb @@ -10,7 +10,7 @@ module Gitlab attr_reader(*ATTRIBUTES) def initialize( - change, user_access:, project:, skip_authorization: false, + change, user_access:, project:, skip_lfs_integrity_check: false, protocol:, logger: ) @oldrev, @newrev, @ref = change.values_at(:oldrev, :newrev, :ref) @@ -18,7 +18,6 @@ module Gitlab @tag_name = Gitlab::Git.tag_name(@ref) @user_access = user_access @project = project - @skip_authorization = skip_authorization @skip_lfs_integrity_check = skip_lfs_integrity_check @protocol = protocol @@ -27,8 +26,6 @@ module Gitlab end def exec - return true if skip_authorization - ref_level_checks # Check of commits should happen as the last step # given they're expensive in terms of performance diff --git a/lib/gitlab/checks/diff_check.rb b/lib/gitlab/checks/diff_check.rb index 63da9a3d6b5..ea0d8c85a66 100644 --- a/lib/gitlab/checks/diff_check.rb +++ b/lib/gitlab/checks/diff_check.rb @@ -11,7 +11,7 @@ module Gitlab }.freeze def validate! - return if deletion? || newrev.nil? + return if deletion? return unless should_run_diff_validations? return if commits.empty? diff --git a/lib/gitlab/checks/push_check.rb b/lib/gitlab/checks/push_check.rb index f3a52f09868..91f8d0bdbc8 100644 --- a/lib/gitlab/checks/push_check.rb +++ b/lib/gitlab/checks/push_check.rb @@ -6,7 +6,7 @@ module Gitlab def validate! logger.log_timed("Checking if you are allowed to push...") do unless can_push? - raise GitAccess::UnauthorizedError, 'You are not allowed to push code to this project.' + raise GitAccess::UnauthorizedError, GitAccess::ERROR_MESSAGES[:push_code] end end end @@ -15,7 +15,7 @@ module Gitlab def can_push? user_access.can_do_action?(:push_code) || - user_access.can_push_to_branch?(branch_name) + project.branch_allows_collaboration?(user_access.user, branch_name) end end end diff --git a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml index a9e361b0b32..8f6cf8d2d03 100644 --- a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml @@ -185,7 +185,8 @@ dependency_scanning: - setup_docker - dependency_scanning artifacts: - paths: [gl-dependency-scanning-report.json] + reports: + dependency_scanning: gl-dependency-scanning-report.json only: refs: - branches diff --git a/lib/gitlab/email/handler/base_handler.rb b/lib/gitlab/email/handler/base_handler.rb index 35bb49ad19a..f89d1d15010 100644 --- a/lib/gitlab/email/handler/base_handler.rb +++ b/lib/gitlab/email/handler/base_handler.rb @@ -6,12 +6,14 @@ module Gitlab class BaseHandler attr_reader :mail, :mail_key + HANDLER_ACTION_BASE_REGEX ||= /(?<project_slug>.+)-(?<project_id>\d+)/.freeze + def initialize(mail, mail_key) @mail = mail @mail_key = mail_key end - def can_execute? + def can_handle? raise NotImplementedError end diff --git a/lib/gitlab/email/handler/create_issue_handler.rb b/lib/gitlab/email/handler/create_issue_handler.rb index 69982efbbe6..78a3a9489ac 100644 --- a/lib/gitlab/email/handler/create_issue_handler.rb +++ b/lib/gitlab/email/handler/create_issue_handler.rb @@ -2,21 +2,33 @@ require 'gitlab/email/handler/base_handler' +# handles issue creation emails with these formats: +# incoming+gitlab-org-gitlab-ce-20-Author_Token12345678-issue@incoming.gitlab.com +# incoming+gitlab-org/gitlab-ce+Author_Token12345678@incoming.gitlab.com (legacy) module Gitlab module Email module Handler class CreateIssueHandler < BaseHandler include ReplyProcessing - attr_reader :project_path, :incoming_email_token + + HANDLER_REGEX = /\A#{HANDLER_ACTION_BASE_REGEX}-(?<incoming_email_token>.+)-issue\z/.freeze + HANDLER_REGEX_LEGACY = /\A(?<project_path>[^\+]*)\+(?<incoming_email_token>.*)\z/.freeze def initialize(mail, mail_key) super(mail, mail_key) - @project_path, @incoming_email_token = - mail_key && mail_key.split('+', 2) + + if !mail_key&.include?('/') && (matched = HANDLER_REGEX.match(mail_key.to_s)) + @project_slug = matched[:project_slug] + @project_id = matched[:project_id]&.to_i + @incoming_email_token = matched[:incoming_email_token] + elsif matched = HANDLER_REGEX_LEGACY.match(mail_key.to_s) + @project_path = matched[:project_path] + @incoming_email_token = matched[:incoming_email_token] + end end def can_handle? - !incoming_email_token.nil? && !incoming_email_token.include?("+") && !mail_key.include?(Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX) + incoming_email_token && (project_id || can_handle_legacy_format?) end def execute @@ -36,10 +48,6 @@ module Gitlab end # rubocop: enable CodeReuse/ActiveRecord - def project - @project ||= Project.find_by_full_path(project_path) - end - private def create_issue @@ -50,6 +58,10 @@ module Gitlab description: message_including_reply ).execute end + + def can_handle_legacy_format? + project_path && !incoming_email_token.include?('+') && !mail_key.include?(Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX_LEGACY) + end end end end diff --git a/lib/gitlab/email/handler/create_merge_request_handler.rb b/lib/gitlab/email/handler/create_merge_request_handler.rb index 5772727e855..b3b5063f2ca 100644 --- a/lib/gitlab/email/handler/create_merge_request_handler.rb +++ b/lib/gitlab/email/handler/create_merge_request_handler.rb @@ -3,23 +3,33 @@ require 'gitlab/email/handler/base_handler' require 'gitlab/email/handler/reply_processing' +# handles merge request creation emails with these formats: +# incoming+gitlab-org-gitlab-ce-20-Author_Token12345678-merge-request@incoming.gitlab.com +# incoming+gitlab-org/gitlab-ce+merge-request+Author_Token12345678@incoming.gitlab.com (legacy) module Gitlab module Email module Handler class CreateMergeRequestHandler < BaseHandler include ReplyProcessing - attr_reader :project_path, :incoming_email_token + + HANDLER_REGEX = /\A#{HANDLER_ACTION_BASE_REGEX}-(?<incoming_email_token>.+)-merge-request\z/.freeze + HANDLER_REGEX_LEGACY = /\A(?<project_path>[^\+]*)\+merge-request\+(?<incoming_email_token>.*)/.freeze def initialize(mail, mail_key) super(mail, mail_key) - if m = /\A([^\+]*)\+merge-request\+(.*)/.match(mail_key.to_s) - @project_path, @incoming_email_token = m.captures + if !mail_key&.include?('/') && (matched = HANDLER_REGEX.match(mail_key.to_s)) + @project_slug = matched[:project_slug] + @project_id = matched[:project_id]&.to_i + @incoming_email_token = matched[:incoming_email_token] + elsif matched = HANDLER_REGEX_LEGACY.match(mail_key.to_s) + @project_path = matched[:project_path] + @incoming_email_token = matched[:incoming_email_token] end end def can_handle? - @project_path && @incoming_email_token + incoming_email_token && (project_id || project_path) end def execute @@ -40,10 +50,6 @@ module Gitlab end # rubocop: enable CodeReuse/ActiveRecord - def project - @project ||= Project.find_by_full_path(project_path) - end - def metrics_params super.merge(includes_patches: patch_attachments.any?) end @@ -97,7 +103,7 @@ module Gitlab def remove_patch_attachments patch_attachments.each { |patch| mail.parts.delete(patch) } - # reset the message, so it needs to be reporocessed when the attachments + # reset the message, so it needs to be reprocessed when the attachments # have been modified @message = nil end diff --git a/lib/gitlab/email/handler/create_note_handler.rb b/lib/gitlab/email/handler/create_note_handler.rb index c7c573595fa..b00af15364d 100644 --- a/lib/gitlab/email/handler/create_note_handler.rb +++ b/lib/gitlab/email/handler/create_note_handler.rb @@ -3,6 +3,8 @@ require 'gitlab/email/handler/base_handler' require 'gitlab/email/handler/reply_processing' +# handles note/reply creation emails with these formats: +# incoming+1234567890abcdef1234567890abcdef@incoming.gitlab.com module Gitlab module Email module Handler diff --git a/lib/gitlab/email/handler/reply_processing.rb b/lib/gitlab/email/handler/reply_processing.rb index ff6b2c729b2..ba9730d2685 100644 --- a/lib/gitlab/email/handler/reply_processing.rb +++ b/lib/gitlab/email/handler/reply_processing.rb @@ -6,13 +6,26 @@ module Gitlab module ReplyProcessing private + attr_reader :project_id, :project_slug, :project_path, :incoming_email_token + def author raise NotImplementedError end + # rubocop:disable Gitlab/ModuleWithInstanceVariables def project - raise NotImplementedError + return @project if instance_variable_defined?(:@project) + + if project_id + @project = Project.find_by_id(project_id) + @project = nil unless valid_project_slug?(@project) + else + @project = Project.find_by_full_path(project_path) + end + + @project end + # rubocop:enable Gitlab/ModuleWithInstanceVariables def message @message ||= process_message @@ -58,6 +71,10 @@ module Gitlab raise invalid_exception, msg end + + def valid_project_slug?(found_project) + project_slug == found_project.full_path_slug + end end end end diff --git a/lib/gitlab/email/handler/unsubscribe_handler.rb b/lib/gitlab/email/handler/unsubscribe_handler.rb index d2f617b868a..20e4c125626 100644 --- a/lib/gitlab/email/handler/unsubscribe_handler.rb +++ b/lib/gitlab/email/handler/unsubscribe_handler.rb @@ -2,14 +2,28 @@ require 'gitlab/email/handler/base_handler' +# handles unsubscribe emails with these formats: +# incoming+1234567890abcdef1234567890abcdef-unsubscribe@incoming.gitlab.com +# incoming+1234567890abcdef1234567890abcdef+unsubscribe@incoming.gitlab.com (legacy) module Gitlab module Email module Handler class UnsubscribeHandler < BaseHandler delegate :project, to: :sent_notification, allow_nil: true + HANDLER_REGEX_FOR = -> (suffix) { /\A(?<reply_token>\w+)#{Regexp.escape(suffix)}\z/ }.freeze + HANDLER_REGEX = HANDLER_REGEX_FOR.call(Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX).freeze + HANDLER_REGEX_LEGACY = HANDLER_REGEX_FOR.call(Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX_LEGACY).freeze + + def initialize(mail, mail_key) + super(mail, mail_key) + + matched = HANDLER_REGEX.match(mail_key.to_s) || HANDLER_REGEX_LEGACY.match(mail_key.to_s) + @reply_token = matched[:reply_token] if matched + end + def can_handle? - mail_key =~ /\A\w+#{Regexp.escape(Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX)}\z/ + reply_token.present? end def execute @@ -24,12 +38,10 @@ module Gitlab private - def sent_notification - @sent_notification ||= SentNotification.for(reply_key) - end + attr_reader :reply_token - def reply_key - mail_key.sub(Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX, '') + def sent_notification + @sent_notification ||= SentNotification.for(reply_token) end end end diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index 802fa65dd63..010bd0e520c 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -12,6 +12,10 @@ module Gitlab TimeoutError = Class.new(StandardError) ProjectMovedError = Class.new(NotFoundError) + # Use the magic string '_any' to indicate we do not know what the + # changes are. This is also what gitlab-shell does. + ANY = '_any' + ERROR_MESSAGES = { upload: 'You are not allowed to upload code for this project.', download: 'You are not allowed to download code from this project.', @@ -24,7 +28,8 @@ module Gitlab upload_pack_disabled_over_http: 'Pulling over HTTP is not allowed.', receive_pack_disabled_over_http: 'Pushing over HTTP is not allowed.', read_only: 'The repository is temporarily read-only. Please try again later.', - cannot_push_to_read_only: "You can't push code to a read-only GitLab instance." + cannot_push_to_read_only: "You can't push code to a read-only GitLab instance.", + push_code: 'You are not allowed to push code to this project.' }.freeze INTERNAL_TIMEOUT = 50.seconds.freeze @@ -199,7 +204,7 @@ module Gitlab def ensure_project_on_push!(cmd, changes) return if project || deploy_key? - return unless receive_pack?(cmd) && changes == '_any' && authentication_abilities.include?(:push_code) + return unless receive_pack?(cmd) && changes == ANY && authentication_abilities.include?(:push_code) namespace = Namespace.find_by_full_path(namespace_path) @@ -256,24 +261,34 @@ module Gitlab raise UnauthorizedError, ERROR_MESSAGES[:upload] end - return if changes.blank? # Allow access this is needed for EE. - check_change_access! end def check_change_access! - # If there are worktrees with a HEAD pointing to a non-existent object, - # calls to `git rev-list --all` will fail in git 2.15+. This should also - # clear stale lock files. - project.repository.clean_stale_repository_files - - # Iterate over all changes to find if user allowed all of them to be applied - changes_list.each.with_index do |change, index| - first_change = index == 0 - - # If user does not have access to make at least one change, cancel all - # push by allowing the exception to bubble up - check_single_change_access(change, skip_lfs_integrity_check: !first_change) + # Deploy keys with write access can push anything + return if deploy_key? + + if changes == ANY + can_push = user_access.can_do_action?(:push_code) || + project.any_branch_allows_collaboration?(user_access.user) + + unless can_push + raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:push_code] + end + else + # If there are worktrees with a HEAD pointing to a non-existent object, + # calls to `git rev-list --all` will fail in git 2.15+. This should also + # clear stale lock files. + project.repository.clean_stale_repository_files + + # Iterate over all changes to find if user allowed all of them to be applied + changes_list.each.with_index do |change, index| + first_change = index == 0 + + # If user does not have access to make at least one change, cancel all + # push by allowing the exception to bubble up + check_single_change_access(change, skip_lfs_integrity_check: !first_change) + end end end @@ -282,7 +297,6 @@ module Gitlab change, user_access: user_access, project: project, - skip_authorization: deploy_key?, skip_lfs_integrity_check: skip_lfs_integrity_check, protocol: protocol, logger: logger @@ -348,7 +362,7 @@ module Gitlab protected def changes_list - @changes_list ||= Gitlab::ChangesList.new(changes) + @changes_list ||= Gitlab::ChangesList.new(changes == ANY ? [] : changes) end def user diff --git a/lib/gitlab/git_access_wiki.rb b/lib/gitlab/git_access_wiki.rb index 3f24001e4ee..0af91957fa8 100644 --- a/lib/gitlab/git_access_wiki.rb +++ b/lib/gitlab/git_access_wiki.rb @@ -15,7 +15,7 @@ module Gitlab authentication_abilities.include?(:download_code) && user_access.can_do_action?(:download_wiki_code) end - def check_single_change_access(change, _options = {}) + def check_change_access! unless user_access.can_do_action?(:create_wiki) raise UnauthorizedError, ERROR_MESSAGES[:write_to_wiki] end diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml index 3cd8ede830c..25cdd5ab121 100644 --- a/lib/gitlab/import_export/import_export.yml +++ b/lib/gitlab/import_export/import_export.yml @@ -63,7 +63,6 @@ project_tree: - :triggers - :pipeline_schedules - :services - - :hooks - protected_branches: - :merge_access_levels - :push_access_levels @@ -156,12 +155,6 @@ excluded_attributes: - :reference - :reference_html - :epic_id - hooks: - - :token - - :encrypted_token - - :encrypted_token_iv - - :encrypted_url - - :encrypted_url_iv runners: - :token - :token_encrypted diff --git a/lib/gitlab/incoming_email.rb b/lib/gitlab/incoming_email.rb index 20fc8226611..cc0c633b943 100644 --- a/lib/gitlab/incoming_email.rb +++ b/lib/gitlab/incoming_email.rb @@ -2,8 +2,9 @@ module Gitlab module IncomingEmail - UNSUBSCRIBE_SUFFIX = '+unsubscribe'.freeze - WILDCARD_PLACEHOLDER = '%{key}'.freeze + UNSUBSCRIBE_SUFFIX = '-unsubscribe'.freeze + UNSUBSCRIBE_SUFFIX_LEGACY = '+unsubscribe'.freeze + WILDCARD_PLACEHOLDER = '%{key}'.freeze class << self def enabled? @@ -22,6 +23,7 @@ module Gitlab config.address.sub(WILDCARD_PLACEHOLDER, key) end + # example: incoming+1234567890abcdef1234567890abcdef-unsubscribe@incoming.gitlab.com def unsubscribe_address(key) config.address.sub(WILDCARD_PLACEHOLDER, "#{key}#{UNSUBSCRIBE_SUFFIX}") end diff --git a/lib/gitlab/middleware/go.rb b/lib/gitlab/middleware/go.rb index d1a87c3b3bb..72a788022ef 100644 --- a/lib/gitlab/middleware/go.rb +++ b/lib/gitlab/middleware/go.rb @@ -6,6 +6,7 @@ module Gitlab module Middleware class Go include ActionView::Helpers::TagHelper + include ActionController::HttpAuthentication::Basic PROJECT_PATH_REGEX = %r{\A(#{Gitlab::PathRegex.full_namespace_route_regex}/#{Gitlab::PathRegex.project_route_regex})/}.freeze @@ -14,7 +15,7 @@ module Gitlab end def call(env) - request = Rack::Request.new(env) + request = ActionDispatch::Request.new(env) render_go_doc(request) || @app.call(env) end @@ -110,21 +111,23 @@ module Gitlab def project_for_paths(paths, request) project = Project.where_full_path_in(paths).first - return unless Ability.allowed?(current_user(request), :read_project, project) + return unless Ability.allowed?(current_user(request, project), :read_project, project) project end - def current_user(request) - authenticator = Gitlab::Auth::RequestAuthenticator.new(request) - user = authenticator.find_user_from_access_token || authenticator.find_user_from_warden + def current_user(request, project) + return unless has_basic_credentials?(request) - return unless user&.can?(:access_api) + login, password = user_name_and_password(request) + auth_result = Gitlab::Auth.find_for_git_client(login, password, project: project, ip: request.ip) + return unless auth_result.success? - # Right now, the `api` scope is the only one that should be able to determine private project existence. - return unless authenticator.valid_access_token?(scopes: [:api]) + return unless auth_result.actor&.can?(:access_git) - user + return unless auth_result.authentication_abilities.include?(:read_project) + + auth_result.actor end end end diff --git a/qa/Gemfile b/qa/Gemfile index d69c71003ae..75ad7bd07af 100644 --- a/qa/Gemfile +++ b/qa/Gemfile @@ -5,5 +5,6 @@ gem 'capybara', '~> 2.16.1' gem 'capybara-screenshot', '~> 1.0.18' gem 'rake', '~> 12.3.0' gem 'rspec', '~> 3.7' -gem 'selenium-webdriver', '~> 3.8.0' +gem 'selenium-webdriver', '~> 3.12' gem 'airborne', '~> 0.2.13' +gem 'nokogiri', '~> 1.8.5' diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock index d61ecf8fbb5..55f3211482b 100644 --- a/qa/Gemfile.lock +++ b/qa/Gemfile.lock @@ -25,7 +25,7 @@ GEM capybara-screenshot (1.0.18) capybara (>= 1.0, < 3) launchy - childprocess (0.8.0) + childprocess (0.9.0) ffi (~> 1.0, >= 1.0.11) coderay (1.1.2) concurrent-ruby (1.0.5) @@ -47,7 +47,7 @@ GEM mini_portile2 (2.3.0) minitest (5.11.1) netrc (0.11.0) - nokogiri (1.8.2) + nokogiri (1.8.5) mini_portile2 (~> 2.3.0) pry (0.11.3) coderay (~> 1.1.0) @@ -78,9 +78,9 @@ GEM rspec-support (~> 3.7.0) rspec-support (3.7.0) rubyzip (1.2.2) - selenium-webdriver (3.8.0) + selenium-webdriver (3.141.0) childprocess (~> 0.5) - rubyzip (~> 1.0) + rubyzip (~> 1.2, >= 1.2.2) thread_safe (0.3.6) tzinfo (1.2.4) thread_safe (~> 0.1) @@ -97,10 +97,11 @@ DEPENDENCIES airborne (~> 0.2.13) capybara (~> 2.16.1) capybara-screenshot (~> 1.0.18) + nokogiri (~> 1.8.5) pry-byebug (~> 3.5.1) rake (~> 12.3.0) rspec (~> 3.7) - selenium-webdriver (~> 3.8.0) + selenium-webdriver (~> 3.12) BUNDLED WITH - 1.17.1 + 1.17.3 @@ -283,6 +283,7 @@ module QA autoload :Select2, 'qa/page/component/select2' autoload :DropdownFilter, 'qa/page/component/dropdown_filter' autoload :UsersSelect, 'qa/page/component/users_select' + autoload :Note, 'qa/page/component/note' module Issuable autoload :Common, 'qa/page/component/issuable/common' diff --git a/qa/qa/page/component/note.rb b/qa/qa/page/component/note.rb new file mode 100644 index 00000000000..67d7f114786 --- /dev/null +++ b/qa/qa/page/component/note.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module QA + module Page + module Component + module Note + def self.included(base) + base.view 'app/assets/javascripts/notes/components/comment_form.vue' do + element :note_dropdown + element :discussion_option + end + + base.view 'app/assets/javascripts/notes/components/note_form.vue' do + element :reply_input + element :reply_comment_button + end + + base.view 'app/assets/javascripts/notes/components/noteable_discussion.vue' do + element :discussion_reply + end + + base.view 'app/assets/javascripts/notes/components/toggle_replies_widget.vue' do + element :expand_replies + element :collapse_replies + end + end + + def start_discussion(text) + fill_element :comment_input, text + click_element :note_dropdown + click_element :discussion_option + click_element :comment_button + end + + def reply_to_discussion(reply_text) + all_elements(:discussion_reply).last.click + fill_element :reply_input, reply_text + click_element :reply_comment_button + end + + def collapse_replies + click_element :collapse_replies + end + + def expand_replies + click_element :expand_replies + end + end + end + end +end diff --git a/qa/qa/page/merge_request/show.rb b/qa/qa/page/merge_request/show.rb index 869dc0b9d21..4f21ed602d9 100644 --- a/qa/qa/page/merge_request/show.rb +++ b/qa/qa/page/merge_request/show.rb @@ -4,6 +4,8 @@ module QA module Page module MergeRequest class Show < Page::Base + include Page::Component::Note + view 'app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue' do element :merge_button element :fast_forward_message, 'Fast-forward merge without a merge commit' # rubocop:disable QA/ElementWithPattern @@ -34,19 +36,6 @@ module QA element :diff_comment end - view 'app/assets/javascripts/notes/components/comment_form.vue' do - element :note_dropdown - element :discussion_option - end - - view 'app/assets/javascripts/notes/components/note_form.vue' do - element :reply_input - end - - view 'app/assets/javascripts/notes/components/noteable_discussion.vue' do - element :discussion_reply - end - view 'app/assets/javascripts/diffs/components/inline_diff_table_row.vue' do element :new_diff_line end @@ -163,18 +152,6 @@ module QA fill_element :reply_input, text end - def start_discussion(text) - fill_element :comment_input, text - click_element :note_dropdown - click_element :discussion_option - click_element :comment_button - end - - def reply_to_discussion(reply_text) - all_elements(:discussion_reply).last.click - fill_element :reply_input, reply_text - end - def edit! click_element :edit_button end diff --git a/qa/qa/page/project/issue/show.rb b/qa/qa/page/project/issue/show.rb index 9ec6d90719e..1028cc045a0 100644 --- a/qa/qa/page/project/issue/show.rb +++ b/qa/qa/page/project/issue/show.rb @@ -6,6 +6,7 @@ module QA module Issue class Show < Page::Base include Page::Component::Issuable::Common + include Page::Component::Note view 'app/views/shared/notes/_form.html.haml' do element :new_note_form, 'new-note' # rubocop:disable QA/ElementWithPattern diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb new file mode 100644 index 00000000000..fa779bd1f4e --- /dev/null +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module QA + context 'Plan' do + describe 'collapse comments in issue discussions' do + let(:issue_title) { 'issue title' } + + it 'user collapses reply for comments in an issue' do + Runtime::Browser.visit(:gitlab, Page::Main::Login) + Page::Main::Login.perform(&:sign_in_using_credentials) + + Resource::Issue.fabricate! do |issue| + issue.title = issue_title + end + + expect(page).to have_content(issue_title) + + Page::Project::Issue::Show.perform do |show_page| + show_page.select_all_activities_filter + show_page.start_discussion("My first discussion") + expect(show_page).to have_content("My first discussion") + + show_page.reply_to_discussion("My First Reply") + expect(show_page).to have_content("My First Reply") + + show_page.collapse_replies + expect(show_page).to have_content("1 reply") + expect(show_page).not_to have_content("My First Reply") + + show_page.expand_replies + expect(show_page).to have_content("My First Reply") + expect(show_page).not_to have_content("1 reply") + end + end + end + end +end diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index d46b9ffb3ce..4f4d3ca226f 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -950,7 +950,7 @@ describe Projects::MergeRequestsController do end it 'returns 200' do - get :discussions, namespace_id: project.namespace, project_id: project, id: merge_request.iid + get :discussions, params: { namespace_id: project.namespace, project_id: project, id: merge_request.iid } expect(response.status).to eq(200) end @@ -969,7 +969,7 @@ describe Projects::MergeRequestsController do expect(collection).to receive(:find_by_id).with(note_diff_file.id).and_call_original end - get :discussions, namespace_id: project.namespace, project_id: project, id: merge_request.iid + get :discussions, params: { namespace_id: project.namespace, project_id: project, id: merge_request.iid } end end @@ -986,7 +986,7 @@ describe Projects::MergeRequestsController do expect(collection).to receive(:find_by_id).with(note_diff_file.id).and_call_original end - get :discussions, namespace_id: project.namespace, project_id: project, id: merge_request.iid + get :discussions, params: { namespace_id: project.namespace, project_id: project, id: merge_request.iid } end it 'does not preload highlights when diff note is resolved' do @@ -999,7 +999,7 @@ describe Projects::MergeRequestsController do expect(collection).to receive(:find_by_id).with(note_diff_file.id).and_call_original end - get :discussions, namespace_id: project.namespace, project_id: project, id: merge_request.iid + get :discussions, params: { namespace_id: project.namespace, project_id: project, id: merge_request.iid } end end end diff --git a/spec/controllers/projects/snippets_controller_spec.rb b/spec/controllers/projects/snippets_controller_spec.rb index e4b78aff25d..75c9839dd9b 100644 --- a/spec/controllers/projects/snippets_controller_spec.rb +++ b/spec/controllers/projects/snippets_controller_spec.rb @@ -385,7 +385,7 @@ describe Projects::SnippetsController do before do sign_in(user) - get :show, namespace_id: project.namespace, project_id: project, id: project_snippet.to_param, format: :js + get :show, params: { namespace_id: project.namespace, project_id: project, id: project_snippet.to_param }, format: :js end context 'when snippet is private' do diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index 4747d837273..f84f069f4db 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -667,7 +667,7 @@ describe ProjectsController do project.add_guest(user) sign_in(user) - get :refs, namespace_id: project.namespace, id: project + get :refs, params: { namespace_id: project.namespace, id: project } expect(response).to have_gitlab_http_status(404) end diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb index d762531da7e..5c6858dc7b2 100644 --- a/spec/controllers/snippets_controller_spec.rb +++ b/spec/controllers/snippets_controller_spec.rb @@ -82,7 +82,7 @@ describe SnippetsController do end it 'responds with status 404 when embeddable content is requested' do - get :show, id: personal_snippet.to_param, format: :js + get :show, params: { id: personal_snippet.to_param }, format: :js expect(response).to have_gitlab_http_status(404) end @@ -114,7 +114,7 @@ describe SnippetsController do end it 'responds with status 404 when embeddable content is requested' do - get :show, id: personal_snippet.to_param, format: :js + get :show, params: { id: personal_snippet.to_param }, format: :js expect(response).to have_gitlab_http_status(404) end @@ -145,7 +145,7 @@ describe SnippetsController do end it 'responds with status 200 when embeddable content is requested' do - get :show, id: personal_snippet.to_param, format: :js + get :show, params: { id: personal_snippet.to_param }, format: :js expect(assigns(:snippet)).to eq(personal_snippet) expect(response).to have_gitlab_http_status(200) diff --git a/spec/fixtures/emails/merge_request_multiple_patches.eml b/spec/fixtures/emails/merge_request_multiple_patches.eml index 311b99a525d..7d2e0cd4e50 100644 --- a/spec/fixtures/emails/merge_request_multiple_patches.eml +++ b/spec/fixtures/emails/merge_request_multiple_patches.eml @@ -1,5 +1,5 @@ From: "Jake the Dog" <jake@adventuretime.ooo> -To: incoming+gitlabhq/gitlabhq+merge-request+auth_token@appmail.adventuretime.ooo +To: incoming+gitlabhq-gitlabhq-project_id-auth_token-merge-request@appmail.adventuretime.ooo Subject: new-branch-with-a-patch Date: Wed, 31 Oct 2018 17:27:52 +0100 X-Mailer: MailMate (1.12r5523) diff --git a/spec/fixtures/emails/merge_request_with_conflicting_patch.eml b/spec/fixtures/emails/merge_request_with_conflicting_patch.eml index ddfdfe9e24a..5c9eda640bc 100644 --- a/spec/fixtures/emails/merge_request_with_conflicting_patch.eml +++ b/spec/fixtures/emails/merge_request_with_conflicting_patch.eml @@ -1,5 +1,5 @@ From: "Jake the Dog" <jake@adventuretime.ooo> -To: incoming+gitlabhq/gitlabhq+merge-request+auth_token@appmail.adventuretime.ooo +To: incoming+gitlabhq-gitlabhq-project_id-auth_token-merge-request@appmail.adventuretime.ooo Subject: feature Date: Wed, 31 Oct 2018 17:27:52 +0100 X-Mailer: MailMate (1.12r5523) diff --git a/spec/fixtures/emails/merge_request_with_patch_and_target_branch.eml b/spec/fixtures/emails/merge_request_with_patch_and_target_branch.eml index 965658721cd..9fabfc23e3b 100644 --- a/spec/fixtures/emails/merge_request_with_patch_and_target_branch.eml +++ b/spec/fixtures/emails/merge_request_with_patch_and_target_branch.eml @@ -1,5 +1,5 @@ From: "Jake the Dog" <jake@adventuretime.ooo> -To: incoming+gitlabhq/gitlabhq+merge-request+auth_token@appmail.adventuretime.ooo +To: incoming+gitlabhq-gitlabhq-project_id-auth_token-merge-request@appmail.adventuretime.ooo Subject: new-branch-with-a-patch Date: Wed, 24 Oct 2018 16:39:49 +0200 X-Mailer: MailMate (1.12r5523) diff --git a/spec/fixtures/emails/valid_merge_request_with_patch.eml b/spec/fixtures/emails/valid_merge_request_with_patch.eml index 143fa77d1fa..e0f406639a3 100644 --- a/spec/fixtures/emails/valid_merge_request_with_patch.eml +++ b/spec/fixtures/emails/valid_merge_request_with_patch.eml @@ -1,5 +1,5 @@ From: "Jake the Dog" <jake@adventuretime.ooo> -To: incoming+gitlabhq/gitlabhq+merge-request+auth_token@appmail.adventuretime.ooo +To: incoming+gitlabhq-gitlabhq-project_id-auth_token-merge-request@appmail.adventuretime.ooo Subject: new-branch-with-a-patch Date: Wed, 24 Oct 2018 16:39:49 +0200 X-Mailer: MailMate (1.12r5523) diff --git a/spec/fixtures/emails/valid_new_issue.eml b/spec/fixtures/emails/valid_new_issue.eml index 3cf53a656a5..7d63016ed04 100644 --- a/spec/fixtures/emails/valid_new_issue.eml +++ b/spec/fixtures/emails/valid_new_issue.eml @@ -1,11 +1,11 @@ Return-Path: <jake@adventuretime.ooo> Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400 -Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700 +Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq-gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700 Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 Date: Thu, 13 Jun 2013 17:03:48 -0400 From: Jake the Dog <jake@adventuretime.ooo> -To: incoming+gitlabhq/gitlabhq+auth_token@appmail.adventuretime.ooo +To: incoming+gitlabhq-gitlabhq-project_id-auth_token-issue@appmail.adventuretime.ooo Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com> Subject: New Issue by email Mime-Version: 1.0 diff --git a/spec/fixtures/emails/valid_new_issue_empty.eml b/spec/fixtures/emails/valid_new_issue_empty.eml index fc1d52a3f42..58a6ef29d69 100644 --- a/spec/fixtures/emails/valid_new_issue_empty.eml +++ b/spec/fixtures/emails/valid_new_issue_empty.eml @@ -1,11 +1,11 @@ Return-Path: <jake@adventuretime.ooo> Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400 -Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700 +Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq-gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700 Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 Date: Thu, 13 Jun 2013 17:03:48 -0400 From: Jake the Dog <jake@adventuretime.ooo> -To: incoming+gitlabhq/gitlabhq+auth_token@appmail.adventuretime.ooo +To: incoming+gitlabhq-gitlabhq-project_id-auth_token-issue@appmail.adventuretime.ooo Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com> Subject: New Issue by email Mime-Version: 1.0 diff --git a/spec/fixtures/emails/valid_new_issue_legacy.eml b/spec/fixtures/emails/valid_new_issue_legacy.eml new file mode 100644 index 00000000000..3cf53a656a5 --- /dev/null +++ b/spec/fixtures/emails/valid_new_issue_legacy.eml @@ -0,0 +1,23 @@ +Return-Path: <jake@adventuretime.ooo> +Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 +Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400 +Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700 +Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 +Date: Thu, 13 Jun 2013 17:03:48 -0400 +From: Jake the Dog <jake@adventuretime.ooo> +To: incoming+gitlabhq/gitlabhq+auth_token@appmail.adventuretime.ooo +Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com> +Subject: New Issue by email +Mime-Version: 1.0 +Content-Type: text/plain; + charset=ISO-8859-1 +Content-Transfer-Encoding: 7bit +X-Sieve: CMU Sieve 2.2 +X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, + 13 Jun 2013 14:03:48 -0700 (PDT) +X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 + +The reply by email functionality should be extended to allow creating a new issue by email. + +* Allow an admin to specify which project the issue should be created under by checking the sender domain. +* Possibly allow the use of regular expression matches within the subject/body to specify which project the issue should be created under. diff --git a/spec/fixtures/emails/valid_new_issue_with_quote.eml b/spec/fixtures/emails/valid_new_issue_with_quote.eml index 0caf8ed4e9e..3a9b9dbbba5 100644 --- a/spec/fixtures/emails/valid_new_issue_with_quote.eml +++ b/spec/fixtures/emails/valid_new_issue_with_quote.eml @@ -1,11 +1,11 @@ Return-Path: <jake@adventuretime.ooo> Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400 -Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700 +Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq-gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700 Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 Date: Thu, 13 Jun 2013 17:03:48 -0400 From: Jake the Dog <jake@adventuretime.ooo> -To: incoming+gitlabhq/gitlabhq+auth_token@appmail.adventuretime.ooo +To: incoming+gitlabhq-gitlabhq-project_id-auth_token-issue@appmail.adventuretime.ooo Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com> Subject: New Issue by email Mime-Version: 1.0 diff --git a/spec/fixtures/emails/valid_new_merge_request.eml b/spec/fixtures/emails/valid_new_merge_request.eml index 729df674604..e12843ea76b 100644 --- a/spec/fixtures/emails/valid_new_merge_request.eml +++ b/spec/fixtures/emails/valid_new_merge_request.eml @@ -1,11 +1,11 @@ Return-Path: <jake@adventuretime.ooo> Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 -Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400 -Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700 +Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq-gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400 +Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq-gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700 Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 Date: Thu, 13 Jun 2013 17:03:48 -0400 From: Jake the Dog <jake@adventuretime.ooo> -To: incoming+gitlabhq/gitlabhq+merge-request+auth_token@appmail.adventuretime.ooo +To: incoming+gitlabhq-gitlabhq-project_id-auth_token-merge-request@appmail.adventuretime.ooo Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com> Subject: feature Mime-Version: 1.0 diff --git a/spec/fixtures/emails/valid_new_merge_request_legacy.eml b/spec/fixtures/emails/valid_new_merge_request_legacy.eml new file mode 100644 index 00000000000..b6cf064af19 --- /dev/null +++ b/spec/fixtures/emails/valid_new_merge_request_legacy.eml @@ -0,0 +1,20 @@ +Return-Path: <jake@adventuretime.ooo> +Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 +Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq-gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400 +Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700 +Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 +Date: Thu, 13 Jun 2013 17:03:48 -0400 +From: Jake the Dog <jake@adventuretime.ooo> +To: incoming+gitlabhq/gitlabhq+merge-request+auth_token@appmail.adventuretime.ooo +Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com> +Subject: feature +Mime-Version: 1.0 +Content-Type: text/plain; + charset=ISO-8859-1 +Content-Transfer-Encoding: 7bit +X-Sieve: CMU Sieve 2.2 +X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, + 13 Jun 2013 14:03:48 -0700 (PDT) +X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 + +Merge request description diff --git a/spec/fixtures/emails/valid_new_merge_request_no_description.eml b/spec/fixtures/emails/valid_new_merge_request_no_description.eml index 480675a6d7e..3ac0ea191a9 100644 --- a/spec/fixtures/emails/valid_new_merge_request_no_description.eml +++ b/spec/fixtures/emails/valid_new_merge_request_no_description.eml @@ -1,11 +1,11 @@ Return-Path: <jake@adventuretime.ooo> Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400 -Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700 +Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq-gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700 Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 Date: Thu, 13 Jun 2013 17:03:48 -0400 From: Jake the Dog <jake@adventuretime.ooo> -To: incoming+gitlabhq/gitlabhq+merge-request+auth_token@appmail.adventuretime.ooo +To: incoming+gitlabhq-gitlabhq-project_id-auth_token-merge-request@appmail.adventuretime.ooo Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com> Subject: feature Mime-Version: 1.0 diff --git a/spec/fixtures/emails/valid_new_merge_request_no_subject.eml b/spec/fixtures/emails/valid_new_merge_request_no_subject.eml index 27eb1b7d922..c2735ccb08a 100644 --- a/spec/fixtures/emails/valid_new_merge_request_no_subject.eml +++ b/spec/fixtures/emails/valid_new_merge_request_no_subject.eml @@ -1,11 +1,11 @@ Return-Path: <jake@adventuretime.ooo> Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400 -Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700 +Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq-gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700 Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 Date: Thu, 13 Jun 2013 17:03:48 -0400 From: Jake the Dog <jake@adventuretime.ooo> -To: incoming+gitlabhq/gitlabhq+merge-request+auth_token@appmail.adventuretime.ooo +To: incoming+gitlabhq-gitlabhq-project_id-auth_token-merge-request@appmail.adventuretime.ooo Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com> Subject: Mime-Version: 1.0 diff --git a/spec/fixtures/emails/wrong_incoming_email_token.eml b/spec/fixtures/emails/wrong_issue_incoming_email_token.eml index 0994c2f7775..d3ba6943a90 100644 --- a/spec/fixtures/emails/wrong_incoming_email_token.eml +++ b/spec/fixtures/emails/wrong_issue_incoming_email_token.eml @@ -1,11 +1,11 @@ Return-Path: <jake@adventuretime.ooo> Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400 -Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700 +Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq-gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700 Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 Date: Thu, 13 Jun 2013 17:03:48 -0400 From: Jake the Dog <jake@adventuretime.ooo> -To: incoming+gitlabhq/gitlabhq+bad_token@appmail.adventuretime.ooo +To: incoming+gitlabhq-gitlabhq-project_id-bad_token-issue@appmail.adventuretime.ooo Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com> Subject: New Issue by email Mime-Version: 1.0 diff --git a/spec/fixtures/emails/wrong_merge_request_incoming_email_token.eml b/spec/fixtures/emails/wrong_merge_request_incoming_email_token.eml new file mode 100644 index 00000000000..c7b758b8f1f --- /dev/null +++ b/spec/fixtures/emails/wrong_merge_request_incoming_email_token.eml @@ -0,0 +1,18 @@ +Return-Path: <jake@adventuretime.ooo> +Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 +Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400 +Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq-gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700 +Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 +Date: Thu, 13 Jun 2013 17:03:48 -0400 +From: Jake the Dog <jake@adventuretime.ooo> +To: incoming+gitlabhq-gitlabhq-project_id-bad_token-merge-request@appmail.adventuretime.ooo +Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com> +Subject: New Issue by email +Mime-Version: 1.0 +Content-Type: text/plain; + charset=ISO-8859-1 +Content-Transfer-Encoding: 7bit +X-Sieve: CMU Sieve 2.2 +X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, + 13 Jun 2013 14:03:48 -0700 (PDT) +X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 diff --git a/spec/helpers/application_settings_helper_spec.rb b/spec/helpers/application_settings_helper_spec.rb new file mode 100644 index 00000000000..705523f1110 --- /dev/null +++ b/spec/helpers/application_settings_helper_spec.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ApplicationSettingsHelper do + context 'when all protocols in use' do + before do + stub_application_setting(enabled_git_access_protocol: '') + end + + it { expect(all_protocols_enabled?).to be_truthy } + it { expect(http_enabled?).to be_truthy } + it { expect(ssh_enabled?).to be_truthy } + end + + context 'when SSH is only in use' do + before do + stub_application_setting(enabled_git_access_protocol: 'ssh') + end + + it { expect(all_protocols_enabled?).to be_falsey } + it { expect(http_enabled?).to be_falsey } + it { expect(ssh_enabled?).to be_truthy } + end + + shared_examples 'when HTTP protocol is in use' do |protocol| + before do + allow(Gitlab.config.gitlab).to receive(:protocol).and_return(protocol) + stub_application_setting(enabled_git_access_protocol: 'http') + end + + it { expect(all_protocols_enabled?).to be_falsey } + it { expect(http_enabled?).to be_truthy } + it { expect(ssh_enabled?).to be_falsey } + end + + it_behaves_like 'when HTTP protocol is in use', 'https' + it_behaves_like 'when HTTP protocol is in use', 'http' +end diff --git a/spec/javascripts/blob_edit/blob_bundle_spec.js b/spec/javascripts/blob_edit/blob_bundle_spec.js index 57f60a4a3dd..48af0148e3f 100644 --- a/spec/javascripts/blob_edit/blob_bundle_spec.js +++ b/spec/javascripts/blob_edit/blob_bundle_spec.js @@ -1,18 +1,11 @@ import blobBundle from '~/blob_edit/blob_bundle'; import $ from 'jquery'; -window.ace = { - config: { - set: () => {}, - loadModule: () => {}, - }, - edit: () => ({ focus: () => {} }), -}; - -describe('EditBlob', () => { +describe('BlobBundle', () => { beforeEach(() => { + spyOnDependency(blobBundle, 'EditBlob').and.stub(); setFixtures(` - <div class="js-edit-blob-form"> + <div class="js-edit-blob-form" data-blob-filename="blah"> <button class="js-commit-button"></button> <a class="btn btn-cancel" href="#"></a> </div>`); diff --git a/spec/javascripts/lib/utils/text_markdown_spec.js b/spec/javascripts/lib/utils/text_markdown_spec.js index f71d27eb4e4..df4029555bb 100644 --- a/spec/javascripts/lib/utils/text_markdown_spec.js +++ b/spec/javascripts/lib/utils/text_markdown_spec.js @@ -13,215 +13,296 @@ describe('init markdown', () => { textArea.parentNode.removeChild(textArea); }); - describe('without selection', () => { - it('inserts the tag on an empty line', () => { - const initialValue = ''; + describe('textArea', () => { + describe('without selection', () => { + it('inserts the tag on an empty line', () => { + const initialValue = ''; - textArea.value = initialValue; - textArea.selectionStart = 0; - textArea.selectionEnd = 0; - - insertMarkdownText({ - textArea, - text: textArea.value, - tag: '*', - blockTag: null, - selected: '', - wrap: false, - }); - - expect(textArea.value).toEqual(`${initialValue}* `); - }); - - it('inserts the tag on a new line if the current one is not empty', () => { - const initialValue = 'some text'; + textArea.value = initialValue; + textArea.selectionStart = 0; + textArea.selectionEnd = 0; - textArea.value = initialValue; - textArea.setSelectionRange(initialValue.length, initialValue.length); + insertMarkdownText({ + textArea, + text: textArea.value, + tag: '*', + blockTag: null, + selected: '', + wrap: false, + }); - insertMarkdownText({ - textArea, - text: textArea.value, - tag: '*', - blockTag: null, - selected: '', - wrap: false, + expect(textArea.value).toEqual(`${initialValue}* `); }); - expect(textArea.value).toEqual(`${initialValue}\n* `); - }); + it('inserts the tag on a new line if the current one is not empty', () => { + const initialValue = 'some text'; - it('inserts the tag on the same line if the current line only contains spaces', () => { - const initialValue = ' '; + textArea.value = initialValue; + textArea.setSelectionRange(initialValue.length, initialValue.length); - textArea.value = initialValue; - textArea.setSelectionRange(initialValue.length, initialValue.length); + insertMarkdownText({ + textArea, + text: textArea.value, + tag: '*', + blockTag: null, + selected: '', + wrap: false, + }); - insertMarkdownText({ - textArea, - text: textArea.value, - tag: '*', - blockTag: null, - selected: '', - wrap: false, + expect(textArea.value).toEqual(`${initialValue}\n* `); }); - expect(textArea.value).toEqual(`${initialValue}* `); - }); + it('inserts the tag on the same line if the current line only contains spaces', () => { + const initialValue = ' '; - it('inserts the tag on the same line if the current line only contains tabs', () => { - const initialValue = '\t\t\t'; + textArea.value = initialValue; + textArea.setSelectionRange(initialValue.length, initialValue.length); - textArea.value = initialValue; - textArea.setSelectionRange(initialValue.length, initialValue.length); + insertMarkdownText({ + textArea, + text: textArea.value, + tag: '*', + blockTag: null, + selected: '', + wrap: false, + }); - insertMarkdownText({ - textArea, - text: textArea.value, - tag: '*', - blockTag: null, - selected: '', - wrap: false, + expect(textArea.value).toEqual(`${initialValue}* `); }); - expect(textArea.value).toEqual(`${initialValue}* `); - }); + it('inserts the tag on the same line if the current line only contains tabs', () => { + const initialValue = '\t\t\t'; - it('places the cursor inside the tags', () => { - const start = 'lorem '; - const end = ' ipsum'; - const tag = '*'; + textArea.value = initialValue; + textArea.setSelectionRange(initialValue.length, initialValue.length); - textArea.value = `${start}${end}`; - textArea.setSelectionRange(start.length, start.length); + insertMarkdownText({ + textArea, + text: textArea.value, + tag: '*', + blockTag: null, + selected: '', + wrap: false, + }); - insertMarkdownText({ - textArea, - text: textArea.value, - tag, - blockTag: null, - selected: '', - wrap: true, + expect(textArea.value).toEqual(`${initialValue}* `); }); - expect(textArea.value).toEqual(`${start}**${end}`); + it('places the cursor inside the tags', () => { + const start = 'lorem '; + const end = ' ipsum'; + const tag = '*'; - // cursor placement should be between tags - expect(textArea.selectionStart).toBe(start.length + tag.length); - }); - }); + textArea.value = `${start}${end}`; + textArea.setSelectionRange(start.length, start.length); - describe('with selection', () => { - const text = 'initial selected value'; - const selected = 'selected'; - beforeEach(() => { - textArea.value = text; - const selectedIndex = text.indexOf(selected); - textArea.setSelectionRange(selectedIndex, selectedIndex + selected.length); - }); + insertMarkdownText({ + textArea, + text: textArea.value, + tag, + blockTag: null, + selected: '', + wrap: true, + }); - it('applies the tag to the selected value', () => { - const selectedIndex = text.indexOf(selected); - const tag = '*'; + expect(textArea.value).toEqual(`${start}**${end}`); - insertMarkdownText({ - textArea, - text: textArea.value, - tag, - blockTag: null, - selected, - wrap: true, + // cursor placement should be between tags + expect(textArea.selectionStart).toBe(start.length + tag.length); }); - - expect(textArea.value).toEqual(text.replace(selected, `*${selected}*`)); - - // cursor placement should be after selection + 2 tag lengths - expect(textArea.selectionStart).toBe(selectedIndex + selected.length + 2 * tag.length); }); - it('replaces the placeholder in the tag', () => { - insertMarkdownText({ - textArea, - text: textArea.value, - tag: '[{text}](url)', - blockTag: null, - selected, - wrap: false, + describe('with selection', () => { + const text = 'initial selected value'; + const selected = 'selected'; + beforeEach(() => { + textArea.value = text; + const selectedIndex = text.indexOf(selected); + textArea.setSelectionRange(selectedIndex, selectedIndex + selected.length); }); - expect(textArea.value).toEqual(text.replace(selected, `[${selected}](url)`)); - }); + it('applies the tag to the selected value', () => { + const selectedIndex = text.indexOf(selected); + const tag = '*'; - describe('and text to be selected', () => { - const tag = '[{text}](url)'; - const select = 'url'; - - it('selects the text', () => { insertMarkdownText({ textArea, text: textArea.value, tag, blockTag: null, selected, - wrap: false, - select, + wrap: true, }); - const expectedText = text.replace(selected, `[${selected}](url)`); + expect(textArea.value).toEqual(text.replace(selected, `*${selected}*`)); - expect(textArea.value).toEqual(expectedText); - expect(textArea.selectionStart).toEqual(expectedText.indexOf(select)); - expect(textArea.selectionEnd).toEqual(expectedText.indexOf(select) + select.length); + // cursor placement should be after selection + 2 tag lengths + expect(textArea.selectionStart).toBe(selectedIndex + selected.length + 2 * tag.length); }); - it('selects the right text when multiple tags are present', () => { - const initialValue = `${tag} ${tag} ${selected}`; - textArea.value = initialValue; - const selectedIndex = initialValue.indexOf(selected); - textArea.setSelectionRange(selectedIndex, selectedIndex + selected.length); + it('replaces the placeholder in the tag', () => { insertMarkdownText({ textArea, text: textArea.value, - tag, + tag: '[{text}](url)', blockTag: null, selected, wrap: false, - select, }); - const expectedText = initialValue.replace(selected, `[${selected}](url)`); - - expect(textArea.value).toEqual(expectedText); - expect(textArea.selectionStart).toEqual(expectedText.lastIndexOf(select)); - expect(textArea.selectionEnd).toEqual(expectedText.lastIndexOf(select) + select.length); + expect(textArea.value).toEqual(text.replace(selected, `[${selected}](url)`)); }); - it('should support selected urls', () => { - const expectedUrl = 'http://www.gitlab.com'; - const expectedSelectionText = 'text'; - const expectedText = `text [${expectedSelectionText}](${expectedUrl}) text`; - const initialValue = `text ${expectedUrl} text`; + describe('and text to be selected', () => { + const tag = '[{text}](url)'; + const select = 'url'; + + it('selects the text', () => { + insertMarkdownText({ + textArea, + text: textArea.value, + tag, + blockTag: null, + selected, + wrap: false, + select, + }); + + const expectedText = text.replace(selected, `[${selected}](url)`); + + expect(textArea.value).toEqual(expectedText); + expect(textArea.selectionStart).toEqual(expectedText.indexOf(select)); + expect(textArea.selectionEnd).toEqual(expectedText.indexOf(select) + select.length); + }); - textArea.value = initialValue; - const selectedIndex = initialValue.indexOf(expectedUrl); - textArea.setSelectionRange(selectedIndex, selectedIndex + expectedUrl.length); + it('selects the right text when multiple tags are present', () => { + const initialValue = `${tag} ${tag} ${selected}`; + textArea.value = initialValue; + const selectedIndex = initialValue.indexOf(selected); + textArea.setSelectionRange(selectedIndex, selectedIndex + selected.length); + insertMarkdownText({ + textArea, + text: textArea.value, + tag, + blockTag: null, + selected, + wrap: false, + select, + }); + + const expectedText = initialValue.replace(selected, `[${selected}](url)`); + + expect(textArea.value).toEqual(expectedText); + expect(textArea.selectionStart).toEqual(expectedText.lastIndexOf(select)); + expect(textArea.selectionEnd).toEqual(expectedText.lastIndexOf(select) + select.length); + }); - insertMarkdownText({ - textArea, - text: textArea.value, - tag, - blockTag: null, - selected: expectedUrl, - wrap: false, - select, + it('should support selected urls', () => { + const expectedUrl = 'http://www.gitlab.com'; + const expectedSelectionText = 'text'; + const expectedText = `text [${expectedSelectionText}](${expectedUrl}) text`; + const initialValue = `text ${expectedUrl} text`; + + textArea.value = initialValue; + const selectedIndex = initialValue.indexOf(expectedUrl); + textArea.setSelectionRange(selectedIndex, selectedIndex + expectedUrl.length); + + insertMarkdownText({ + textArea, + text: textArea.value, + tag, + blockTag: null, + selected: expectedUrl, + wrap: false, + select, + }); + + expect(textArea.value).toEqual(expectedText); + expect(textArea.selectionStart).toEqual(expectedText.indexOf(expectedSelectionText, 1)); + expect(textArea.selectionEnd).toEqual( + expectedText.indexOf(expectedSelectionText, 1) + expectedSelectionText.length, + ); }); + }); + }); + }); + + describe('Ace Editor', () => { + let editor; + + beforeEach(() => { + editor = { + getSelectionRange: () => ({ + start: 0, + end: 0, + }), + getValue: () => 'this is text \n in two lines', + insert: () => {}, + navigateLeft: () => {}, + }; + }); + + it('uses ace editor insert text when editor is passed in', () => { + spyOn(editor, 'insert'); - expect(textArea.value).toEqual(expectedText); - expect(textArea.selectionStart).toEqual(expectedText.indexOf(expectedSelectionText, 1)); - expect(textArea.selectionEnd).toEqual( - expectedText.indexOf(expectedSelectionText, 1) + expectedSelectionText.length, - ); + insertMarkdownText({ + text: editor.getValue, + tag: '*', + blockTag: null, + selected: '', + wrap: false, + editor, + }); + + expect(editor.insert).toHaveBeenCalled(); + }); + + it('adds block tags on line above and below selection', () => { + spyOn(editor, 'insert'); + + const selected = 'this text \n is multiple \n lines'; + const text = `before \n ${selected} \n after`; + + insertMarkdownText({ + text, + tag: '', + blockTag: '***', + selected, + wrap: true, + editor, + }); + + expect(editor.insert).toHaveBeenCalledWith(`***\n${selected}\n***`); + }); + + it('uses ace editor to navigate back tag length when nothing is selected', () => { + spyOn(editor, 'navigateLeft'); + + insertMarkdownText({ + text: editor.getValue, + tag: '*', + blockTag: null, + selected: '', + wrap: true, + editor, }); + + expect(editor.navigateLeft).toHaveBeenCalledWith(1); + }); + + it('ace editor does not navigate back when there is selected text', () => { + spyOn(editor, 'navigateLeft'); + + insertMarkdownText({ + text: editor.getValue, + tag: '*', + blockTag: null, + selected: 'foobar', + wrap: true, + editor, + }); + + expect(editor.navigateLeft).not.toHaveBeenCalled(); }); }); }); diff --git a/spec/javascripts/user_popovers_spec.js b/spec/javascripts/user_popovers_spec.js index 6cf8dd81b36..c6537e71850 100644 --- a/spec/javascripts/user_popovers_spec.js +++ b/spec/javascripts/user_popovers_spec.js @@ -2,6 +2,9 @@ import initUserPopovers from '~/user_popovers'; import UsersCache from '~/lib/utils/users_cache'; describe('User Popovers', () => { + const fixtureTemplate = 'merge_requests/diff_comment.html.raw'; + preloadFixtures(fixtureTemplate); + const selector = '.js-user-link'; const dummyUser = { name: 'root' }; @@ -15,11 +18,7 @@ describe('User Popovers', () => { }; beforeEach(() => { - setFixtures(` - <a href="/root" data-user-id="1" class="js-user-link" data-username="root" data-original-title="" title=""> - Root - </a> - `); + loadFixtures(fixtureTemplate); const usersCacheSpy = () => Promise.resolve(dummyUser); spyOn(UsersCache, 'retrieveById').and.callFake(userId => usersCacheSpy(userId)); @@ -39,7 +38,7 @@ describe('User Popovers', () => { expect(shownPopover).not.toBeNull(); expect(shownPopover.innerHTML).toContain(dummyUser.name); - expect(UsersCache.retrieveById).toHaveBeenCalledWith('1'); + expect(UsersCache.retrieveById).toHaveBeenCalledWith('58'); triggerEvent('mouseleave', document.querySelector(selector)); diff --git a/spec/javascripts/vue_shared/components/callout_spec.js b/spec/javascripts/vue_shared/components/callout_spec.js index e62bd86f4ca..91208dfb31a 100644 --- a/spec/javascripts/vue_shared/components/callout_spec.js +++ b/spec/javascripts/vue_shared/components/callout_spec.js @@ -1,45 +1,66 @@ -import Vue from 'vue'; -import callout from '~/vue_shared/components/callout.vue'; -import createComponent from 'spec/helpers/vue_mount_component_helper'; +import { createLocalVue, shallowMount } from '@vue/test-utils'; +import Callout from '~/vue_shared/components/callout.vue'; + +const TEST_MESSAGE = 'This is a callout message!'; +const TEST_SLOT = '<button>This is a callout slot!</button>'; + +const localVue = createLocalVue(); describe('Callout Component', () => { - let CalloutComponent; - let vm; - const exampleMessage = 'This is a callout message!'; + let wrapper; - beforeEach(() => { - CalloutComponent = Vue.extend(callout); - }); + const factory = options => { + wrapper = shallowMount(localVue.extend(Callout), { + localVue, + ...options, + }); + }; afterEach(() => { - vm.$destroy(); + wrapper.destroy(); }); it('should render the appropriate variant of callout', () => { - vm = createComponent(CalloutComponent, { - category: 'info', - message: exampleMessage, + factory({ + propsData: { + category: 'info', + message: TEST_MESSAGE, + }, }); - expect(vm.$el.getAttribute('class')).toEqual('bs-callout bs-callout-info'); + expect(wrapper.classes()).toEqual(['bs-callout', 'bs-callout-info']); - expect(vm.$el.tagName).toEqual('DIV'); + expect(wrapper.element.tagName).toEqual('DIV'); }); it('should render accessibility attributes', () => { - vm = createComponent(CalloutComponent, { - message: exampleMessage, + factory({ + propsData: { + message: TEST_MESSAGE, + }, }); - expect(vm.$el.getAttribute('role')).toEqual('alert'); - expect(vm.$el.getAttribute('aria-live')).toEqual('assertive'); + expect(wrapper.attributes('role')).toEqual('alert'); + expect(wrapper.attributes('aria-live')).toEqual('assertive'); }); it('should render the provided message', () => { - vm = createComponent(CalloutComponent, { - message: exampleMessage, + factory({ + propsData: { + message: TEST_MESSAGE, + }, + }); + + expect(wrapper.element.innerHTML.trim()).toEqual(TEST_MESSAGE); + }); + + it('should render the provided slot', () => { + factory({ + slots: { + default: TEST_SLOT, + }, }); - expect(vm.$el.innerHTML.trim()).toEqual(exampleMessage); + expect(wrapper.element.innerHTML.trim()).toEqual(TEST_SLOT); }); }); diff --git a/spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js b/spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js index 25b6e3b6bc8..de3e0c149de 100644 --- a/spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js +++ b/spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js @@ -17,14 +17,13 @@ const DEFAULT_PROPS = { const UserPopover = Vue.extend(userPopover); describe('User Popover Component', () => { + const fixtureTemplate = 'merge_requests/diff_comment.html.raw'; + preloadFixtures(fixtureTemplate); + let vm; beforeEach(() => { - setFixtures(` - <a href="/root" data-user-id="1" class="js-user-link" title="testuser"> - Root - </a> - `); + loadFixtures(fixtureTemplate); }); afterEach(() => { diff --git a/spec/lib/gitlab/checks/push_check_spec.rb b/spec/lib/gitlab/checks/push_check_spec.rb index 25f0d428cb9..e1bd52d6c0b 100644 --- a/spec/lib/gitlab/checks/push_check_spec.rb +++ b/spec/lib/gitlab/checks/push_check_spec.rb @@ -13,7 +13,7 @@ describe Gitlab::Checks::PushCheck do context 'when the user is not allowed to push to the repo' do it 'raises an error' do expect(user_access).to receive(:can_do_action?).with(:push_code).and_return(false) - expect(user_access).to receive(:can_push_to_branch?).with('master').and_return(false) + expect(project).to receive(:branch_allows_collaboration?).with(user_access.user, 'master').and_return(false) expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to push code to this project.') end diff --git a/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb b/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb index 1d75e8cb5da..48139c2f9dc 100644 --- a/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb +++ b/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb @@ -11,7 +11,7 @@ describe Gitlab::Email::Handler::CreateIssueHandler do stub_config_setting(host: 'localhost') end - let(:email_raw) { fixture_file('emails/valid_new_issue.eml') } + let(:email_raw) { email_fixture('emails/valid_new_issue.eml') } let(:namespace) { create(:namespace, path: 'gitlabhq') } let!(:project) { create(:project, :public, namespace: namespace, path: 'gitlabhq') } @@ -23,21 +23,58 @@ describe Gitlab::Email::Handler::CreateIssueHandler do ) end + context "when email key" do + let(:mail) { Mail::Message.new(email_raw) } + + it "matches the new format" do + handler = described_class.new(mail, "gitlabhq-gitlabhq-#{project.project_id}-#{user.incoming_email_token}-issue") + + expect(handler.instance_variable_get(:@project_id)).to eq project.project_id + expect(handler.instance_variable_get(:@project_slug)).to eq project.full_path_slug + expect(handler.instance_variable_get(:@incoming_email_token)).to eq user.incoming_email_token + expect(handler.can_handle?).to be_truthy + end + + it "matches the legacy format" do + handler = described_class.new(mail, "h5bp/html5-boilerplate+#{user.incoming_email_token}") + + expect(handler.instance_variable_get(:@project_path)).to eq 'h5bp/html5-boilerplate' + expect(handler.instance_variable_get(:@incoming_email_token)).to eq user.incoming_email_token + expect(handler.can_handle?).to be_truthy + end + + it "doesn't match either format" do + handler = described_class.new(mail, "h5bp-html5-boilerplate+something+invalid") + + expect(handler.can_handle?).to be_falsey + end + end + context "when everything is fine" do - it "creates a new issue" do - setup_attachment + shared_examples "a new issue" do + it "creates a new issue" do + setup_attachment - expect { receiver.execute }.to change { project.issues.count }.by(1) - issue = project.issues.last + expect { receiver.execute }.to change { project.issues.count }.by(1) + issue = project.issues.last + + expect(issue.author).to eq(user) + expect(issue.title).to eq('New Issue by email') + expect(issue.description).to include('reply by email') + expect(issue.description).to include(markdown) + end + end + + it_behaves_like "a new issue" - expect(issue.author).to eq(user) - expect(issue.title).to eq('New Issue by email') - expect(issue.description).to include('reply by email') - expect(issue.description).to include(markdown) + context "creates a new issue with legacy email address" do + let(:email_raw) { fixture_file('emails/valid_new_issue_legacy.eml') } + + it_behaves_like "a new issue" end context "when the reply is blank" do - let(:email_raw) { fixture_file("emails/valid_new_issue_empty.eml") } + let(:email_raw) { email_fixture("emails/valid_new_issue_empty.eml") } it "creates a new issue" do expect { receiver.execute }.to change { project.issues.count }.by(1) @@ -50,7 +87,7 @@ describe Gitlab::Email::Handler::CreateIssueHandler do end context "when there are quotes in email" do - let(:email_raw) { fixture_file("emails/valid_new_issue_with_quote.eml") } + let(:email_raw) { email_fixture("emails/valid_new_issue_with_quote.eml") } it "creates a new issue" do expect { receiver.execute }.to change { project.issues.count }.by(1) @@ -76,7 +113,7 @@ describe Gitlab::Email::Handler::CreateIssueHandler do end context "when we can't find the incoming_email_token" do - let(:email_raw) { fixture_file("emails/wrong_incoming_email_token.eml") } + let(:email_raw) { email_fixture("emails/wrong_issue_incoming_email_token.eml") } it "raises an UserNotFoundError" do expect { receiver.execute }.to raise_error(Gitlab::Email::UserNotFoundError) @@ -91,4 +128,8 @@ describe Gitlab::Email::Handler::CreateIssueHandler do end end end + + def email_fixture(path) + fixture_file(path).gsub('project_id', project.project_id.to_s) + end end diff --git a/spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb b/spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb index f276f1a8ddf..2fa86b2b46f 100644 --- a/spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb +++ b/spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb @@ -15,7 +15,7 @@ describe Gitlab::Email::Handler::CreateMergeRequestHandler do TestEnv.clean_test_path end - let(:email_raw) { fixture_file('emails/valid_new_merge_request.eml') } + let(:email_raw) { email_fixture('emails/valid_new_merge_request.eml') } let(:namespace) { create(:namespace, path: 'gitlabhq') } let!(:project) { create(:project, :public, :repository, namespace: namespace, path: 'gitlabhq') } @@ -27,6 +27,33 @@ describe Gitlab::Email::Handler::CreateMergeRequestHandler do ) end + context "when email key" do + let(:mail) { Mail::Message.new(email_raw) } + + it "matches the new format" do + handler = described_class.new(mail, "gitlabhq-gitlabhq-#{project.project_id}-#{user.incoming_email_token}-merge-request") + + expect(handler.instance_variable_get(:@project_id)).to eq project.project_id + expect(handler.instance_variable_get(:@project_slug)).to eq project.full_path_slug + expect(handler.instance_variable_get(:@incoming_email_token)).to eq user.incoming_email_token + expect(handler.can_handle?).to be_truthy + end + + it "matches the legacy format" do + handler = described_class.new(mail, "h5bp/html5-boilerplate+merge-request+#{user.incoming_email_token}") + + expect(handler.instance_variable_get(:@project_path)).to eq 'h5bp/html5-boilerplate' + expect(handler.instance_variable_get(:@incoming_email_token)).to eq user.incoming_email_token + expect(handler.can_handle?).to be_truthy + end + + it "doesn't match either format" do + handler = described_class.new(mail, "h5bp-html5-boilerplate+merge-request") + + expect(handler.can_handle?).to be_falsey + end + end + context "as a non-developer" do before do project.add_guest(user) @@ -43,15 +70,25 @@ describe Gitlab::Email::Handler::CreateMergeRequestHandler do end context "when everything is fine" do - it "creates a new merge request" do - expect { receiver.execute }.to change { project.merge_requests.count }.by(1) - merge_request = project.merge_requests.last - - expect(merge_request.author).to eq(user) - expect(merge_request.source_branch).to eq('feature') - expect(merge_request.title).to eq('Feature added') - expect(merge_request.description).to eq('Merge request description') - expect(merge_request.target_branch).to eq(project.default_branch) + shared_examples "a new merge request" do + it "creates a new merge request" do + expect { receiver.execute }.to change { project.merge_requests.count }.by(1) + merge_request = project.merge_requests.last + + expect(merge_request.author).to eq(user) + expect(merge_request.source_branch).to eq('feature') + expect(merge_request.title).to eq('Feature added') + expect(merge_request.description).to eq('Merge request description') + expect(merge_request.target_branch).to eq(project.default_branch) + end + end + + it_behaves_like "a new merge request" + + context "creates a new merge request with legacy email address" do + let(:email_raw) { fixture_file('emails/valid_new_merge_request_legacy.eml') } + + it_behaves_like "a new merge request" end end @@ -67,7 +104,7 @@ describe Gitlab::Email::Handler::CreateMergeRequestHandler do end context "when we can't find the incoming_email_token" do - let(:email_raw) { fixture_file("emails/wrong_incoming_email_token.eml") } + let(:email_raw) { email_fixture("emails/wrong_merge_request_incoming_email_token.eml") } it "raises an UserNotFoundError" do expect { receiver.execute }.to raise_error(Gitlab::Email::UserNotFoundError) @@ -75,7 +112,7 @@ describe Gitlab::Email::Handler::CreateMergeRequestHandler do end context "when the subject is blank" do - let(:email_raw) { fixture_file("emails/valid_new_merge_request_no_subject.eml") } + let(:email_raw) { email_fixture("emails/valid_new_merge_request_no_subject.eml") } it "raises an InvalidMergeRequestError" do expect { receiver.execute }.to raise_error(Gitlab::Email::InvalidMergeRequestError) @@ -83,7 +120,7 @@ describe Gitlab::Email::Handler::CreateMergeRequestHandler do end context "when the message body is blank" do - let(:email_raw) { fixture_file("emails/valid_new_merge_request_no_description.eml") } + let(:email_raw) { email_fixture("emails/valid_new_merge_request_no_description.eml") } it "creates a new merge request with description set from the last commit" do expect { receiver.execute }.to change { project.merge_requests.count }.by(1) @@ -95,7 +132,7 @@ describe Gitlab::Email::Handler::CreateMergeRequestHandler do end context 'when the email contains patch attachments' do - let(:email_raw) { fixture_file("emails/valid_merge_request_with_patch.eml") } + let(:email_raw) { email_fixture("emails/valid_merge_request_with_patch.eml") } it 'creates the source branch and applies the patches' do receiver.execute @@ -120,7 +157,7 @@ describe Gitlab::Email::Handler::CreateMergeRequestHandler do end context 'when the patch could not be applied' do - let(:email_raw) { fixture_file("emails/merge_request_with_conflicting_patch.eml") } + let(:email_raw) { email_fixture("emails/merge_request_with_conflicting_patch.eml") } it 'raises an error' do expect { receiver.execute }.to raise_error(Gitlab::Email::InvalidAttachment) @@ -128,7 +165,7 @@ describe Gitlab::Email::Handler::CreateMergeRequestHandler do end context 'when specifying the target branch using quick actions' do - let(:email_raw) { fixture_file('emails/merge_request_with_patch_and_target_branch.eml') } + let(:email_raw) { email_fixture('emails/merge_request_with_patch_and_target_branch.eml') } it 'creates the merge request with the correct target branch' do receiver.execute @@ -150,7 +187,7 @@ describe Gitlab::Email::Handler::CreateMergeRequestHandler do end describe '#patch_attachments' do - let(:email_raw) { fixture_file('emails/merge_request_multiple_patches.eml') } + let(:email_raw) { email_fixture('emails/merge_request_multiple_patches.eml') } let(:mail) { Mail::Message.new(email_raw) } subject(:handler) { described_class.new(mail, mail_key) } @@ -163,4 +200,8 @@ describe Gitlab::Email::Handler::CreateMergeRequestHandler do expect(attachments).to eq(expected_filenames) end end + + def email_fixture(path) + fixture_file(path).gsub('project_id', project.project_id.to_s) + end end diff --git a/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb b/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb index b8660b133ec..dcddd00df59 100644 --- a/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb +++ b/spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb @@ -10,13 +10,35 @@ describe Gitlab::Email::Handler::UnsubscribeHandler do stub_config_setting(host: 'localhost') end - let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(mail_key, "#{mail_key}+unsubscribe") } - let(:project) { create(:project, :public) } - let(:user) { create(:user) } - let(:noteable) { create(:issue, project: project) } + let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(mail_key, "#{mail_key}#{Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX}") } + let(:project) { create(:project, :public) } + let(:user) { create(:user) } + let(:noteable) { create(:issue, project: project) } let!(:sent_notification) { SentNotification.record(noteable, user.id, mail_key) } + context "when email key" do + let(:mail) { Mail::Message.new(email_raw) } + + it "matches the new format" do + handler = described_class.new(mail, "#{mail_key}#{Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX}") + + expect(handler.can_handle?).to be_truthy + end + + it "matches the legacy format" do + handler = described_class.new(mail, "#{mail_key}#{Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX_LEGACY}") + + expect(handler.can_handle?).to be_truthy + end + + it "doesn't match either format" do + handler = described_class.new(mail, "+#{mail_key}#{Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX}") + + expect(handler.can_handle?).to be_falsey + end + end + context 'when notification concerns a commit' do let(:commit) { create(:commit, project: project) } let!(:sent_notification) { SentNotification.record(commit, user.id, mail_key) } @@ -40,6 +62,14 @@ describe Gitlab::Email::Handler::UnsubscribeHandler do it 'unsubscribes user from notable' do expect { receiver.execute }.to change { noteable.subscribed?(user) }.from(true).to(false) end + + context 'when using old style unsubscribe link' do + let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(mail_key, "#{mail_key}#{Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX_LEGACY}") } + + it 'unsubscribes user from notable' do + expect { receiver.execute }.to change { noteable.subscribed?(user) }.from(true).to(false) + end + end end context 'when the noteable could not be found' do diff --git a/spec/lib/gitlab/email/handler_spec.rb b/spec/lib/gitlab/email/handler_spec.rb index c651765dc0f..d2920b08956 100644 --- a/spec/lib/gitlab/email/handler_spec.rb +++ b/spec/lib/gitlab/email/handler_spec.rb @@ -19,7 +19,8 @@ describe Gitlab::Email::Handler do describe 'regexps are set properly' do let(:addresses) do - %W(sent_notification_key#{Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX} sent_notification_key path/to/project+merge-request+user_email_token path/to/project+user_email_token) + %W(sent_notification_key#{Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX} sent_notification_key path-to-project-123-user_email_token-merge-request path-to-project-123-user_email_token-issue) + + %W(sent_notification_key#{Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX_LEGACY} sent_notification_key path/to/project+merge-request+user_email_token path/to/project+user_email_token) end it 'picks each handler at least once' do diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index 8d8eb50ad76..3e34dd592f2 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -14,7 +14,7 @@ describe Gitlab::GitAccess do let(:authentication_abilities) { %i[read_project download_code push_code] } let(:redirected_path) { nil } let(:auth_result_type) { nil } - let(:changes) { '_any' } + let(:changes) { Gitlab::GitAccess::ANY } let(:push_access_check) { access.check('git-receive-pack', changes) } let(:pull_access_check) { access.check('git-upload-pack', changes) } @@ -437,7 +437,7 @@ describe Gitlab::GitAccess do let(:project) { nil } context 'when changes is _any' do - let(:changes) { '_any' } + let(:changes) { Gitlab::GitAccess::ANY } context 'when authentication abilities include push code' do let(:authentication_abilities) { [:push_code] } @@ -483,7 +483,7 @@ describe Gitlab::GitAccess do end context 'when project exists' do - let(:changes) { '_any' } + let(:changes) { Gitlab::GitAccess::ANY } let!(:project) { create(:project) } it 'does not create a new project' do @@ -497,7 +497,7 @@ describe Gitlab::GitAccess do let(:project_path) { "nonexistent" } let(:project) { nil } let(:namespace_path) { user.namespace.path } - let(:changes) { '_any' } + let(:changes) { Gitlab::GitAccess::ANY } it 'does not create a new project' do expect { access.send(:ensure_project_on_push!, cmd, changes) }.not_to change { Project.count } @@ -507,7 +507,7 @@ describe Gitlab::GitAccess do context 'when pull' do let(:cmd) { 'git-upload-pack' } - let(:changes) { '_any' } + let(:changes) { Gitlab::GitAccess::ANY } context 'when project does not exist' do let(:project_path) { "new-project" } @@ -736,7 +736,8 @@ describe Gitlab::GitAccess do end let(:changes) do - { push_new_branch: "#{Gitlab::Git::BLANK_SHA} 570e7b2ab refs/heads/wow", + { any: Gitlab::GitAccess::ANY, + push_new_branch: "#{Gitlab::Git::BLANK_SHA} 570e7b2ab refs/heads/wow", push_master: '6f6d7e7ed 570e7b2ab refs/heads/master', push_protected_branch: '6f6d7e7ed 570e7b2ab refs/heads/feature', push_remove_protected_branch: "570e7b2ab #{Gitlab::Git::BLANK_SHA} "\ @@ -798,6 +799,7 @@ describe Gitlab::GitAccess do permissions_matrix = { admin: { + any: true, push_new_branch: true, push_master: true, push_protected_branch: true, @@ -809,6 +811,7 @@ describe Gitlab::GitAccess do }, maintainer: { + any: true, push_new_branch: true, push_master: true, push_protected_branch: true, @@ -820,6 +823,7 @@ describe Gitlab::GitAccess do }, developer: { + any: true, push_new_branch: true, push_master: true, push_protected_branch: false, @@ -831,6 +835,7 @@ describe Gitlab::GitAccess do }, reporter: { + any: false, push_new_branch: false, push_master: false, push_protected_branch: false, @@ -842,6 +847,7 @@ describe Gitlab::GitAccess do }, guest: { + any: false, push_new_branch: false, push_master: false, push_protected_branch: false, diff --git a/spec/lib/gitlab/git_access_wiki_spec.rb b/spec/lib/gitlab/git_access_wiki_spec.rb index 9c6c9fe13bf..6ba65b56618 100644 --- a/spec/lib/gitlab/git_access_wiki_spec.rb +++ b/spec/lib/gitlab/git_access_wiki_spec.rb @@ -38,7 +38,7 @@ describe Gitlab::GitAccessWiki do end describe '#access_check_download!' do - subject { access.check('git-upload-pack', '_any') } + subject { access.check('git-upload-pack', Gitlab::GitAccess::ANY) } before do project.add_developer(user) diff --git a/spec/lib/gitlab/incoming_email_spec.rb b/spec/lib/gitlab/incoming_email_spec.rb index 4c0c3fcbcc7..2db62ab983a 100644 --- a/spec/lib/gitlab/incoming_email_spec.rb +++ b/spec/lib/gitlab/incoming_email_spec.rb @@ -61,7 +61,7 @@ describe Gitlab::IncomingEmail do end it 'returns the address with interpolated reply key and unsubscribe suffix' do - expect(described_class.unsubscribe_address('key')).to eq('replies+key+unsubscribe@example.com') + expect(described_class.unsubscribe_address('key')).to eq("replies+key#{Gitlab::IncomingEmail::UNSUBSCRIBE_SUFFIX}@example.com") end end diff --git a/spec/lib/gitlab/middleware/go_spec.rb b/spec/lib/gitlab/middleware/go_spec.rb index 7a3a9ab875b..f52095bf633 100644 --- a/spec/lib/gitlab/middleware/go_spec.rb +++ b/spec/lib/gitlab/middleware/go_spec.rb @@ -96,43 +96,36 @@ describe Gitlab::Middleware::Go do it_behaves_like 'unauthorized' end - end - - context 'using warden' do - before do - env['warden'] = double(authenticate: current_user) - end - context 'when active' do - it_behaves_like 'authenticated' - end - - context 'when blocked' do + context 'with user is blocked' do before do - current_user.block! + current_user.block end it_behaves_like 'unauthorized' end end - context 'using a personal access token' do - let(:personal_access_token) { create(:personal_access_token, user: current_user) } - - before do - env['HTTP_PRIVATE_TOKEN'] = personal_access_token.token - end - - context 'with api scope' do - it_behaves_like 'authenticated' - end + context 'using basic auth' do + context 'using a personal access token' do + let(:personal_access_token) { create(:personal_access_token, user: current_user) } - context 'with read_user scope' do before do - personal_access_token.update_attribute(:scopes, [:read_user]) + env['REMOTE_ADDR'] = "192.168.0.1" + env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(current_user.username, personal_access_token.token) end - it_behaves_like 'unauthorized' + context 'with api scope' do + it_behaves_like 'authenticated' + end + + context 'with read_user scope' do + before do + personal_access_token.update_attribute(:scopes, [:read_user]) + end + + it_behaves_like 'unauthorized' + end end end end diff --git a/spec/models/clusters/applications/knative_spec.rb b/spec/models/clusters/applications/knative_spec.rb index 809880f5969..8fc755d2a26 100644 --- a/spec/models/clusters/applications/knative_spec.rb +++ b/spec/models/clusters/applications/knative_spec.rb @@ -108,6 +108,23 @@ describe Clusters::Applications::Knative do expect(subject.version).to eq('0.2.2') expect(subject.files).to eq(knative.files) end + + it 'should not install metrics for prometheus' do + expect(subject.postinstall).to be_nil + end + + context 'with prometheus installed' do + let(:prometheus) { create(:clusters_applications_prometheus, :installed) } + let(:knative) { create(:clusters_applications_knative, cluster: prometheus.cluster) } + + subject { knative.install_command } + + it 'should install metrics' do + expect(subject.postinstall).not_to be_nil + expect(subject.postinstall.length).to be(1) + expect(subject.postinstall[0]).to eql("kubectl apply -f #{Clusters::Applications::Knative::METRICS_CONFIG}") + end + end end describe '#files' do diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb index 893ed3e3f64..27143f29350 100644 --- a/spec/models/clusters/applications/prometheus_spec.rb +++ b/spec/models/clusters/applications/prometheus_spec.rb @@ -165,6 +165,10 @@ describe Clusters::Applications::Prometheus do expect(subject.files).to eq(prometheus.files) end + it 'should not install knative metrics' do + expect(subject.postinstall).to be_nil + end + context 'on a rbac enabled cluster' do before do prometheus.cluster.platform_kubernetes.rbac! @@ -180,6 +184,17 @@ describe Clusters::Applications::Prometheus do expect(subject.version).to eq('6.7.3') end end + + context 'with knative installed' do + let(:knative) { create(:clusters_applications_knative, :installed ) } + let(:prometheus) { create(:clusters_applications_prometheus, cluster: knative.cluster) } + + subject { prometheus.install_command } + + it 'should install metrics' do + expect(subject.postinstall).to include("kubectl apply -f #{Clusters::Applications::Knative::METRICS_CONFIG}") + end + end end describe '#files' do diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb index 840f74c9890..f447e64b029 100644 --- a/spec/models/clusters/cluster_spec.rb +++ b/spec/models/clusters/cluster_spec.rb @@ -29,6 +29,7 @@ describe Clusters::Cluster do it { is_expected.to delegate_method(:available?).to(:application_helm).with_prefix } it { is_expected.to delegate_method(:available?).to(:application_ingress).with_prefix } it { is_expected.to delegate_method(:available?).to(:application_prometheus).with_prefix } + it { is_expected.to delegate_method(:available?).to(:application_knative).with_prefix } it { is_expected.to respond_to :project } diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 4b6592020c1..2e95d35e134 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -610,16 +610,20 @@ describe Project do end it 'returns the address to create a new issue' do - address = "p+#{project.full_path}+#{user.incoming_email_token}@gl.ab" + address = "p+#{project.full_path_slug}-#{project.project_id}-#{user.incoming_email_token}-issue@gl.ab" expect(project.new_issuable_address(user, 'issue')).to eq(address) end it 'returns the address to create a new merge request' do - address = "p+#{project.full_path}+merge-request+#{user.incoming_email_token}@gl.ab" + address = "p+#{project.full_path_slug}-#{project.project_id}-#{user.incoming_email_token}-merge-request@gl.ab" expect(project.new_issuable_address(user, 'merge_request')).to eq(address) end + + it 'returns nil with invalid address type' do + expect(project.new_issuable_address(user, 'invalid_param')).to be_nil + end end context 'incoming email disabled' do @@ -3831,6 +3835,16 @@ describe Project do let(:user) { create(:user) } let(:target_project) { create(:project, :repository) } let(:project) { fork_project(target_project, nil, repository: true) } + let!(:local_merge_request) do + create( + :merge_request, + target_project: project, + target_branch: 'target-branch', + source_project: project, + source_branch: 'awesome-feature-1', + allow_collaboration: true + ) + end let!(:merge_request) do create( :merge_request, @@ -3875,14 +3889,23 @@ describe Project do end end - describe '#branch_allows_collaboration_push?' do - it 'allows access if the user can merge the merge request' do - expect(project.branch_allows_collaboration?(user, 'awesome-feature-1')) + describe '#any_branch_allows_collaboration?' do + it 'allows access when there are merge requests open allowing collaboration' do + expect(project.any_branch_allows_collaboration?(user)) .to be_truthy end - it 'allows access when there are merge requests open but no branch name is given' do - expect(project.branch_allows_collaboration?(user, nil)) + it 'does not allow access when there are no merge requests open allowing collaboration' do + merge_request.close! + + expect(project.any_branch_allows_collaboration?(user)) + .to be_falsey + end + end + + describe '#branch_allows_collaboration?' do + it 'allows access if the user can merge the merge request' do + expect(project.branch_allows_collaboration?(user, 'awesome-feature-1')) .to be_truthy end @@ -3913,13 +3936,6 @@ describe Project do .to be_falsy end - it 'caches the result' do - control = ActiveRecord::QueryRecorder.new { project.branch_allows_collaboration?(user, 'awesome-feature-1') } - - expect { 3.times { project.branch_allows_collaboration?(user, 'awesome-feature-1') } } - .not_to exceed_query_limit(control) - end - context 'when the requeststore is active', :request_store do it 'only queries per project across instances' do control = ActiveRecord::QueryRecorder.new { project.branch_allows_collaboration?(user, 'awesome-feature-1') } diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb index cf57776346a..79aa32b29bb 100644 --- a/spec/serializers/pipeline_serializer_spec.rb +++ b/spec/serializers/pipeline_serializer_spec.rb @@ -144,7 +144,7 @@ describe PipelineSerializer do # pipeline. With the same ref this check is cached but if refs are # different then there is an extra query per ref # https://gitlab.com/gitlab-org/gitlab-ce/issues/46368 - expect(recorded.count).to be_within(2).of(34) + expect(recorded.count).to be_within(2).of(38) expect(recorded.cached_count).to eq(0) end end diff --git a/spec/services/ci/retry_pipeline_service_spec.rb b/spec/services/ci/retry_pipeline_service_spec.rb index 55445e71539..75042b29bea 100644 --- a/spec/services/ci/retry_pipeline_service_spec.rb +++ b/spec/services/ci/retry_pipeline_service_spec.rb @@ -285,7 +285,7 @@ describe Ci::RetryPipelineService, '#execute' do end it 'allows to retry failed pipeline' do - allow_any_instance_of(Project).to receive(:fetch_branch_allows_collaboration?).and_return(true) + allow_any_instance_of(Project).to receive(:branch_allows_collaboration?).and_return(true) allow_any_instance_of(Project).to receive(:empty_repo?).and_return(false) service.execute(pipeline) diff --git a/spec/support/shared_contexts/email_shared_blocks.rb b/spec/support/shared_contexts/email_shared_context.rb index 9d806fc524d..9d806fc524d 100644 --- a/spec/support/shared_contexts/email_shared_blocks.rb +++ b/spec/support/shared_contexts/email_shared_context.rb diff --git a/vendor/prometheus/values.yaml b/vendor/prometheus/values.yaml index 02ec3e2d9fe..60241c65202 100644 --- a/vendor/prometheus/values.yaml +++ b/vendor/prometheus/values.yaml @@ -141,3 +141,105 @@ serverFiles: - __meta_kubernetes_pod_name action: replace target_label: kubernetes_pod_name + # Sourced from Knative monitoring config: https://github.com/knative/serving/blob/master/config/monitoring/metrics/prometheus/100-prometheus-scrape-config.yaml + - job_name: autoscaler + scrape_interval: 3s + scrape_timeout: 3s + kubernetes_sd_configs: + - role: pod + relabel_configs: + # Scrape only the the targets matching the following metadata + - source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_pod_label_app, __meta_kubernetes_pod_container_port_name] + action: keep + regex: knative-serving;autoscaler;metrics + # Rename metadata labels to be reader friendly + - source_labels: [__meta_kubernetes_namespace] + target_label: namespace + - source_labels: [__meta_kubernetes_pod_name] + target_label: pod + - source_labels: [__meta_kubernetes_service_name] + target_label: service + - job_name: activator + scrape_interval: 3s + scrape_timeout: 3s + kubernetes_sd_configs: + - role: pod + relabel_configs: + # Scrape only the the targets matching the following metadata + - source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_pod_label_app, __meta_kubernetes_pod_container_port_name] + action: keep + regex: knative-serving;activator;metrics-port + # Rename metadata labels to be reader friendly + - source_labels: [__meta_kubernetes_namespace] + target_label: namespace + - source_labels: [__meta_kubernetes_pod_name] + target_label: pod + - source_labels: [__meta_kubernetes_service_name] + target_label: service + # Istio mesh + - job_name: istio-mesh + scrape_interval: 5s + kubernetes_sd_configs: + - role: endpoints + relabel_configs: + # Scrape only the the targets matching the following metadata + - source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name] + action: keep + regex: istio-system;istio-telemetry;prometheus + # Rename metadata labels to be reader friendly + - source_labels: [__meta_kubernetes_namespace] + target_label: namespace + - source_labels: [__meta_kubernetes_pod_name] + target_label: pod + - source_labels: [__meta_kubernetes_service_name] + target_label: service + - job_name: istio-policy + scrape_interval: 5s + kubernetes_sd_configs: + - role: endpoints + relabel_configs: + # Scrape only the the targets matching the following metadata + - source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name] + action: keep + regex: istio-system;istio-policy;http-monitoring + # Rename metadata labels to be reader friendly + - source_labels: [__meta_kubernetes_namespace] + target_label: namespace + - source_labels: [__meta_kubernetes_pod_name] + target_label: pod + - source_labels: [__meta_kubernetes_service_name] + target_label: service + # Istio telemetry + - job_name: istio-telemetry + scrape_interval: 5s + kubernetes_sd_configs: + - role: endpoints + relabel_configs: + # Scrape only the the targets matching the following metadata + - source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name] + action: keep + regex: istio-system;istio-telemetry;http-monitoring + # Rename metadata labels to be reader friendly + - source_labels: [__meta_kubernetes_namespace] + target_label: namespace + - source_labels: [__meta_kubernetes_pod_name] + target_label: pod + - source_labels: [__meta_kubernetes_service_name] + target_label: service + # Istio pilot + - job_name: istio-pilot + scrape_interval: 5s + kubernetes_sd_configs: + - role: endpoints + relabel_configs: + # Scrape only the the targets matching the following metadata + - source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name] + action: keep + regex: istio-system;istio-pilot;http-monitoring + # Rename metadata labels to be reader friendly + - source_labels: [__meta_kubernetes_namespace] + target_label: namespace + - source_labels: [__meta_kubernetes_pod_name] + target_label: pod + - source_labels: [__meta_kubernetes_service_name] + target_label: service |