diff options
1350 files changed, 13537 insertions, 4499 deletions
diff --git a/.eslintignore b/.eslintignore index f78840e67be..9a5e15c86ae 100644 --- a/.eslintignore +++ b/.eslintignore @@ -9,5 +9,6 @@ /scripts/ /tmp/ /vendor/ +jest.config.js karma.config.js webpack.config.js diff --git a/.eslintrc.yml b/.eslintrc.yml index b0794bb7434..98a497aa12a 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -9,9 +9,6 @@ plugins: - import - html settings: - html/html-extensions: - - '.html' - - '.html.raw' import/resolver: webpack: config: './config/webpack.config.js' diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 33559812872..ab38c87039e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -249,8 +249,8 @@ package-and-qa: - ./scripts/trigger-build omnibus when: manual only: - - //@gitlab-org/gitlab-ce - - //@gitlab-org/gitlab-ee + - /.+/@gitlab-org/gitlab-ce + - /.+/@gitlab-org/gitlab-ee # Review docs base .review-docs: &review-docs @@ -320,7 +320,7 @@ cloud-native-image: variables: GIT_DEPTH: "1" cache: {} - when: always + when: manual script: - gem install gitlab --no-document - CNG_PROJECT_PATH="gitlab-org/build/CNG" BUILD_TRIGGER_TOKEN=$CI_JOB_TOKEN ./scripts/trigger-build cng @@ -685,10 +685,10 @@ gitlab:assets:compile: - public/assets/ <<: *assets-compile-cache only: - - //@gitlab-org/gitlab-ce - - //@gitlab-org/gitlab-ee - - //@gitlab/gitlabhq - - //@gitlab/gitlab-ee + - /.+/@gitlab-org/gitlab-ce + - /.+/@gitlab-org/gitlab-ee + - /.+/@gitlab/gitlabhq + - /.+/@gitlab/gitlab-ee tags: - docker - gitlab-org @@ -990,7 +990,7 @@ no_ee_check: script: - scripts/no-ee-check only: - - //@gitlab-org/gitlab-ce + - /.+/@gitlab-org/gitlab-ce # GitLab Review apps .review-build-cng-base: &review-build-cng-base diff --git a/.gitlab/issue_templates/Documentation.md b/.gitlab/issue_templates/Documentation.md index c0919aeeda4..67602b7b2df 100644 --- a/.gitlab/issue_templates/Documentation.md +++ b/.gitlab/issue_templates/Documentation.md @@ -9,7 +9,7 @@ * For information about documentation content and process, see https://docs.gitlab.com/ee/development/documentation/ --> -### Type of issue +<!-- Type of issue --> <!-- Un-comment the line for the applicable doc issue type to add its label. Note that all text on that line is deleted upon issue creation. --> diff --git a/.gitlab/merge_request_templates/Security Release.md b/.gitlab/merge_request_templates/Security Release.md index 246f2dae009..42314f9b2dd 100644 --- a/.gitlab/merge_request_templates/Security Release.md +++ b/.gitlab/merge_request_templates/Security Release.md @@ -7,6 +7,10 @@ See [the general developer security release guidelines](https://gitlab.com/gitla This merge request _must not_ close the corresponding security issue _unless_ it targets master. +When submitting a merge request for CE, a corresponding EE merge request is +always required. This makes it easier to merge security merge requests, as +manually merging CE into EE is no longer required. + --> ## Related issues @@ -20,8 +24,8 @@ targets master. - [ ] Title of this MR is the same as for all backports - [ ] A [CHANGELOG entry](https://docs.gitlab.com/ee/development/changelog.html) is added without a `merge_request` value, with `type` set to `security` - [ ] Add a link to this MR in the `links` section of related issue -- [ ] Add a link to an EE MR if required -- [ ] Assign to a reviewer +- [ ] Set up an EE MR (always required for CE merge requests): EE_MR_LINK_HERE +- [ ] Assign to a reviewer (that is not a release manager) ## Reviewer checklist diff --git a/.rubocop.yml b/.rubocop.yml index 9143966b864..648d59e8062 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -146,6 +146,20 @@ Naming/FileName: - XSS - GRPC +Rails/ApplicationRecord: + Enabled: true + Exclude: + # Models in database migrations should not subclass from ApplicationRecord + # as they need to be as decoupled from application code as possible + - db/**/*.rb + - lib/gitlab/background_migration/**/*.rb + - lib/gitlab/database/**/*.rb + - spec/**/*.rb + - ee/db/**/*.rb + - ee/lib/gitlab/background_migration/**/*.rb + - ee/lib/ee/gitlab/background_migration/**/*.rb + - ee/spec/**/*.rb + # GitLab ################################################################### Gitlab/ModuleWithInstanceVariables: diff --git a/.scss-lint.yml b/.scss-lint.yml index 3df66033fa8..2a1fa27fdb5 100644 --- a/.scss-lint.yml +++ b/.scss-lint.yml @@ -1,7 +1,9 @@ # Linter Documentation: # https://github.com/brigade/scss-lint/blob/master/lib/scss_lint/linter/README.md -scss_files: 'app/assets/stylesheets/**/*.scss' +scss_files: + - 'app/assets/stylesheets/**/*.scss' + - 'ee/app/assets/stylesheets/**/*.scss' exclude: - 'app/assets/stylesheets/pages/emojis.scss' diff --git a/.stylelintrc b/.stylelintrc index c0f21aed292..241d2c94a88 100644 --- a/.stylelintrc +++ b/.stylelintrc @@ -1,6 +1,8 @@ { "plugins":[ - "stylelint-scss" + "./scripts/frontend/stylelint/stylelint-duplicate-selectors.js", + "./scripts/frontend/stylelint/stylelint-utility-classes.js", + "stylelint-scss", ], "rules":{ "at-rule-blacklist":[ @@ -95,13 +97,15 @@ }, ], "selector-list-comma-newline-after":"always", - "selector-max-compound-selectors":[5, { "severity": "warning" }], + "selector-max-compound-selectors":[3, { "severity": "warning" }], "selector-max-id":1, "selector-no-vendor-prefix":true, "selector-pseudo-element-colon-notation":"double", "selector-pseudo-element-no-unknown":true, "shorthand-property-no-redundant-values":true, "string-quotes":"single", - "value-no-vendor-prefix":[true, { ignoreValues: ["sticky"] }] + "value-no-vendor-prefix":[true, { ignoreValues: ["sticky"] }], + "stylelint-gitlab/duplicate-selectors":[true,{ "severity": "warning" }], + "stylelint-gitlab/utility-classes":[true,{ "severity": "warning" }], } } diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 5e57fb89558..034552a83ee 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -1.29.0 +1.30.0 @@ -116,7 +116,7 @@ gem 'seed-fu', '~> 2.3.7' # Markdown and HTML processing gem 'html-pipeline', '~> 2.8' gem 'deckar01-task_list', '2.2.0' -gem 'gitlab-markup', '~> 1.6.5' +gem 'gitlab-markup', '~> 1.7.0' gem 'github-markup', '~> 1.7.0', require: 'github/markup' gem 'commonmarker', '~> 0.17' gem 'RedCloth', '~> 4.3.2' @@ -128,7 +128,7 @@ gem 'asciidoctor', '~> 1.5.8' gem 'asciidoctor-plantuml', '0.0.8' gem 'rouge', '~> 3.1' gem 'truncato', '~> 0.7.11' -gem 'bootstrap_form', '~> 2.7.0' +gem 'bootstrap_form', '~> 4.2.0' gem 'nokogiri', '~> 1.10.1' gem 'escape_utils', '~> 1.1' diff --git a/Gemfile.lock b/Gemfile.lock index 66e12efa4b3..4ebcc6c81b2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -87,7 +87,9 @@ GEM debug_inspector (>= 0.0.1) bootsnap (1.4.1) msgpack (~> 1.0) - bootstrap_form (2.7.0) + bootstrap_form (4.2.0) + actionpack (>= 5.0) + activemodel (>= 5.0) brakeman (4.2.1) browser (2.5.3) builder (3.2.3) @@ -260,7 +262,7 @@ GEM foreman (0.84.0) thor (~> 0.19.1) formatador (0.2.5) - fugit (1.1.7) + fugit (1.1.9) et-orbi (~> 1.1, >= 1.1.7) raabro (~> 1.1) fuubar (2.2.0) @@ -284,7 +286,7 @@ GEM github-markup (1.7.0) gitlab-default_value_for (3.1.1) activerecord (>= 3.2.0, < 6.0) - gitlab-markup (1.6.5) + gitlab-markup (1.7.0) gitlab-sidekiq-fetcher (0.4.0) sidekiq (~> 5) gitlab-styles (2.5.1) @@ -784,7 +786,7 @@ GEM rubyntlm (0.6.2) rubypants (0.2.0) rubyzip (1.2.2) - rugged (0.28.0) + rugged (0.28.1) safe_yaml (1.0.4) sanitize (4.6.6) crass (~> 1.0.2) @@ -964,7 +966,7 @@ DEPENDENCIES better_errors (~> 2.5.0) binding_of_caller (~> 0.8.0) bootsnap (~> 1.4) - bootstrap_form (~> 2.7.0) + bootstrap_form (~> 4.2.0) brakeman (~> 4.2) browser (~> 2.5) bullet (~> 5.5.0) @@ -1018,7 +1020,7 @@ DEPENDENCIES gitaly-proto (~> 1.13.0) github-markup (~> 1.7.0) gitlab-default_value_for (~> 3.1.1) - gitlab-markup (~> 1.6.5) + gitlab-markup (~> 1.7.0) gitlab-sidekiq-fetcher (~> 0.4.0) gitlab-styles (~> 2.4) gitlab_omniauth-ldap (~> 2.1.1) diff --git a/Pipfile b/Pipfile new file mode 100644 index 00000000000..2afbe5fc3f2 --- /dev/null +++ b/Pipfile @@ -0,0 +1,12 @@ +[[source]] +name = "pypi" +url = "https://pypi.org/simple" +verify_ssl = true + +[dev-packages] + +[packages] +docutils = "==0.13.1" + +[requires] +python_version = "3.4" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 00000000000..dc23ca89ed2 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,30 @@ +{ + "_meta": { + "hash": { + "sha256": "ec82d5e7c10fd591aeebbc9b7b62d730f7fd70dc52e4e4818834891aa4194c73" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.4" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "docutils": { + "hashes": [ + "sha256:718c0f5fb677be0f34b781e04241c4067cbd9327b66bdd8e763201130f5175be", + "sha256:cb3ebcb09242804f84bdbf0b26504077a054da6772c6f4d625f335cc53ebf94d", + "sha256:de454f1015958450b72641165c08afe7023cd7e3944396448f2fb1b0ccba9d77" + ], + "index": "pypi", + "version": "==0.13.1" + } + }, + "develop": {} +} diff --git a/app/assets/images/select2-spinner.gif b/app/assets/images/select2-spinner.gif Binary files differnew file mode 100644 index 00000000000..5b33f7e54f4 --- /dev/null +++ b/app/assets/images/select2-spinner.gif diff --git a/app/assets/images/select2.png b/app/assets/images/select2.png Binary files differnew file mode 100644 index 00000000000..1d804ffb996 --- /dev/null +++ b/app/assets/images/select2.png diff --git a/app/assets/images/select2x2.png b/app/assets/images/select2x2.png Binary files differnew file mode 100644 index 00000000000..4bdd5c961d4 --- /dev/null +++ b/app/assets/images/select2x2.png diff --git a/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js b/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js index 58cf057b2c2..318b7f77c7b 100644 --- a/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js +++ b/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js @@ -10,10 +10,10 @@ export class CopyAsGFM { const isIOS = /\b(iPad|iPhone|iPod)(?=;)/.test(userAgent); if (isIOS) return; - $(document).on('copy', '.md, .wiki', e => { + $(document).on('copy', '.md', e => { CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformGFMSelection); }); - $(document).on('copy', 'pre.code.highlight, .diff-content .line_content', e => { + $(document).on('copy', 'pre.code.highlight, table.code td.line_content', e => { CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformCodeSelection); }); $(document).on('paste', '.js-gfm-input', CopyAsGFM.pasteGFM); @@ -99,7 +99,7 @@ export class CopyAsGFM { } static transformGFMSelection(documentFragment) { - const gfmElements = documentFragment.querySelectorAll('.md, .wiki'); + const gfmElements = documentFragment.querySelectorAll('.md'); switch (gfmElements.length) { case 0: { return documentFragment; diff --git a/app/assets/javascripts/behaviors/markdown/render_gfm.js b/app/assets/javascripts/behaviors/markdown/render_gfm.js index fc9286d15e6..bfb073fdcdc 100644 --- a/app/assets/javascripts/behaviors/markdown/render_gfm.js +++ b/app/assets/javascripts/behaviors/markdown/render_gfm.js @@ -4,6 +4,7 @@ import renderMath from './render_math'; import renderMermaid from './render_mermaid'; import highlightCurrentUser from './highlight_current_user'; import initUserPopovers from '../../user_popovers'; +import initMRPopovers from '../../mr_popover'; // Render GitLab flavoured Markdown // @@ -15,6 +16,7 @@ $.fn.renderGFM = function renderGFM() { renderMermaid(this.find('.js-render-mermaid')); highlightCurrentUser(this.find('.gfm-project_member').get()); initUserPopovers(this.find('.gfm-project_member').get()); + initMRPopovers(this.find('.gfm-merge_request').get()); return this; }; diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js index 680f2031409..670f66b005e 100644 --- a/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js +++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js @@ -37,7 +37,7 @@ export default class ShortcutsIssuable extends Shortcuts { } // Sanity check: Make sure the selected text comes from a discussion : it can either contain a message... - let foundMessage = !!documentFragment.querySelector('.md, .wiki'); + let foundMessage = !!documentFragment.querySelector('.md'); // ... Or come from a message if (!foundMessage) { @@ -46,7 +46,7 @@ export default class ShortcutsIssuable extends Shortcuts { let node = e; do { // Text nodes don't define the `matches` method - if (node.matches && node.matches('.md, .wiki')) { + if (node.matches && node.matches('.md')) { foundMessage = true; } node = node.parentNode; diff --git a/app/assets/javascripts/clusters/components/applications.vue b/app/assets/javascripts/clusters/components/applications.vue index 5b206b82fe0..d54f9ce552c 100644 --- a/app/assets/javascripts/clusters/components/applications.vue +++ b/app/assets/javascripts/clusters/components/applications.vue @@ -393,7 +393,6 @@ export default { <div slot="description" v-html="prometheusDescription"></div> </application-row> <application-row - v-if="isProjectCluster" id="runner" :logo-url="gitlabLogo" :title="applications.runner.title" @@ -409,9 +408,9 @@ export default { > <div slot="description"> {{ - s__(`ClusterIntegration|GitLab Runner connects to this - project's repository and executes CI/CD jobs, - pushing results back and deploying, + s__(`ClusterIntegration|GitLab Runner connects to the + repository and executes CI/CD jobs, + pushing results back and deploying applications to production.`) }} </div> diff --git a/app/assets/javascripts/commons/bootstrap.js b/app/assets/javascripts/commons/bootstrap.js index fba30aea9ae..e5e1cbb1e62 100644 --- a/app/assets/javascripts/commons/bootstrap.js +++ b/app/assets/javascripts/commons/bootstrap.js @@ -16,3 +16,63 @@ $.fn.extend({ .removeClass('disabled'); }, }); + +/* + Starting with bootstrap 4.3.1, bootstrap sanitizes html used for tooltips / popovers. + This extends the default whitelists with more elements / attributes: + https://getbootstrap.com/docs/4.3/getting-started/javascript/#sanitizer + */ +const whitelist = $.fn.tooltip.Constructor.Default.whiteList; + +const inputAttributes = ['value', 'type']; + +const dataAttributes = [ + 'data-toggle', + 'data-placement', + 'data-container', + 'data-title', + 'data-class', + 'data-clipboard-text', + 'data-placement', +]; + +// Whitelisting data attributes +whitelist['*'] = [ + ...whitelist['*'], + ...dataAttributes, + 'title', + 'width height', + 'abbr', + 'datetime', + 'name', + 'width', + 'height', +]; + +// Whitelist missing elements: +whitelist.label = ['for']; +whitelist.button = [...inputAttributes]; +whitelist.input = [...inputAttributes]; + +whitelist.tt = []; +whitelist.samp = []; +whitelist.kbd = []; +whitelist.var = []; +whitelist.dfn = []; +whitelist.cite = []; +whitelist.big = []; +whitelist.address = []; +whitelist.dl = []; +whitelist.dt = []; +whitelist.dd = []; +whitelist.abbr = []; +whitelist.acronym = []; +whitelist.blockquote = []; +whitelist.del = []; +whitelist.ins = []; +whitelist['gl-emoji'] = []; + +// Whitelisting SVG tags and attributes +whitelist.svg = ['viewBox']; +whitelist.use = ['xlink:href']; +whitelist.path = ['d']; diff --git a/app/assets/javascripts/diffs/components/diff_file.vue b/app/assets/javascripts/diffs/components/diff_file.vue index 0e779e1be9a..58a9605c181 100644 --- a/app/assets/javascripts/diffs/components/diff_file.vue +++ b/app/assets/javascripts/diffs/components/diff_file.vue @@ -170,21 +170,23 @@ export default { </div> <gl-loading-icon v-if="showLoadingIcon" class="diff-content loading" /> <template v-else> - <div v-if="errorMessage" class="diff-viewer"> - <div class="nothing-here-block" v-html="errorMessage"></div> + <div :id="`diff-content-${file.file_hash}`"> + <div v-if="errorMessage" class="diff-viewer"> + <div class="nothing-here-block" v-html="errorMessage"></div> + </div> + <div v-else-if="isCollapsed" class="nothing-here-block diff-collapsed"> + {{ __('This diff is collapsed.') }} + <a class="click-to-expand js-click-to-expand" href="#" @click.prevent="handleToggle">{{ + __('Click to expand it.') + }}</a> + </div> + <diff-content + v-else + :class="{ hidden: isCollapsed || isFileTooLarge }" + :diff-file="file" + :help-page-path="helpPagePath" + /> </div> - <div v-else-if="isCollapsed" class="nothing-here-block diff-collapsed"> - {{ __('This diff is collapsed.') }} - <a class="click-to-expand js-click-to-expand" href="#" @click.prevent="handleToggle">{{ - __('Click to expand it.') - }}</a> - </div> - <diff-content - v-else - :class="{ hidden: isCollapsed || isFileTooLarge }" - :diff-file="file" - :help-page-path="helpPagePath" - /> </template> <div v-if="isFileTooLarge" class="nothing-here-block diff-collapsed js-too-large-diff"> {{ __('This source diff could not be displayed because it is too large.') }} diff --git a/app/assets/javascripts/diffs/components/diff_file_header.vue b/app/assets/javascripts/diffs/components/diff_file_header.vue index a5125c3d077..fda7b7c5fd9 100644 --- a/app/assets/javascripts/diffs/components/diff_file_header.vue +++ b/app/assets/javascripts/diffs/components/diff_file_header.vue @@ -11,6 +11,7 @@ import { __, s__, sprintf } from '~/locale'; import { diffViewerModes } from '~/ide/constants'; import EditButton from './edit_button.vue'; import DiffStats from './diff_stats.vue'; +import { scrollToElement } from '~/lib/utils/common_utils'; export default { components: { @@ -66,6 +67,9 @@ export default { hasExpandedDiscussions() { return this.diffHasExpandedDiscussions(this.diffFile); }, + diffContentIDSelector() { + return `#diff-content-${this.diffFile.file_hash}`; + }, icon() { if (this.diffFile.submodule) { return 'archive'; @@ -77,6 +81,11 @@ export default { if (this.diffFile.submodule) { return this.diffFile.submodule_tree_url || this.diffFile.submodule_link; } + + if (!this.discussionPath) { + return this.diffContentIDSelector; + } + return this.discussionPath; }, filePath() { @@ -128,9 +137,6 @@ export default { isModeChanged() { return this.diffFile.viewer.name === diffViewerModes.mode_changed; }, - showExpandDiffToFullFileEnabled() { - return gon.features.expandDiffFullFile && !this.diffFile.is_fully_expanded; - }, }, mounted() { polyfillSticky(this.$refs.header); @@ -152,6 +158,18 @@ export default { handleToggleDiscussions() { this.toggleFileDiscussions(this.diffFile); }, + handleFileNameClick(e) { + const isLinkToOtherPage = + this.diffFile.submodule_tree_url || this.diffFile.submodule_link || this.discussionPath; + + if (!isLinkToOtherPage) { + e.preventDefault(); + const selector = this.diffContentIDSelector; + + scrollToElement(document.querySelector(selector)); + window.location.hash = selector; + } + }, }, }; </script> @@ -171,7 +189,14 @@ export default { class="diff-toggle-caret append-right-5" @click.stop="handleToggle" /> - <a v-once ref="titleWrapper" :href="titleLink" class="append-right-4 js-title-wrapper"> + <a + v-once + id="diffFile.file_path" + ref="titleWrapper" + class="append-right-4 js-title-wrapper" + :href="titleLink" + @click="handleFileNameClick" + > <file-icon :file-name="filePath" :size="18" @@ -258,7 +283,7 @@ export default { <icon name="external-link" /> </gl-button> <gl-button - v-if="showExpandDiffToFullFileEnabled" + v-if="!diffFile.is_fully_expanded" class="expand-file js-expand-file" @click="toggleFullDiff(diffFile.file_path)" > diff --git a/app/assets/javascripts/diffs/components/parallel_diff_view.vue b/app/assets/javascripts/diffs/components/parallel_diff_view.vue index 93e754fa896..41a80d99850 100644 --- a/app/assets/javascripts/diffs/components/parallel_diff_view.vue +++ b/app/assets/javascripts/diffs/components/parallel_diff_view.vue @@ -38,36 +38,34 @@ export default { </script> <template> - <div + <table :class="$options.userColorScheme" :data-commit-id="commitId" class="code diff-wrap-lines js-syntax-highlight text-file" > - <table> - <tbody> - <template v-for="(line, index) in diffLines"> - <parallel-diff-table-row - :key="line.line_code" - :file-hash="diffFile.file_hash" - :context-lines-path="diffFile.context_lines_path" - :line="line" - :is-bottom="index + 1 === diffLinesLength" - /> - <parallel-diff-comment-row - :key="`dcr-${line.line_code || index}`" - :line="line" - :diff-file-hash="diffFile.file_hash" - :line-index="index" - :help-page-path="helpPagePath" - /> - <parallel-draft-comment-row - v-if="shouldRenderParallelDraftRow(diffFile.file_hash, line)" - :key="`drafts-${index}`" - :line="line" - :diff-file-content-sha="diffFile.file_hash" - /> - </template> - </tbody> - </table> - </div> + <tbody> + <template v-for="(line, index) in diffLines"> + <parallel-diff-table-row + :key="line.line_code" + :file-hash="diffFile.file_hash" + :context-lines-path="diffFile.context_lines_path" + :line="line" + :is-bottom="index + 1 === diffLinesLength" + /> + <parallel-diff-comment-row + :key="`dcr-${line.line_code || index}`" + :line="line" + :diff-file-hash="diffFile.file_hash" + :line-index="index" + :help-page-path="helpPagePath" + /> + <parallel-draft-comment-row + v-if="shouldRenderParallelDraftRow(diffFile.file_hash, line)" + :key="`drafts-${index}`" + :line="line" + :diff-file-content-sha="diffFile.file_hash" + /> + </template> + </tbody> + </table> </template> diff --git a/app/assets/javascripts/environments/components/environment_item.vue b/app/assets/javascripts/environments/components/environment_item.vue index a092bdfbc6c..c541ea3445b 100644 --- a/app/assets/javascripts/environments/components/environment_item.vue +++ b/app/assets/javascripts/environments/components/environment_item.vue @@ -459,12 +459,7 @@ export default { class="gl-responsive-table-row" role="row" > - <div - v-gl-tooltip - :title="model.name" - class="table-section section-wrap section-15 text-truncate" - role="gridcell" - > + <div class="table-section section-wrap section-15 text-truncate" role="gridcell"> <div v-if="!model.isFolder" class="table-mobile-header" role="rowheader"> {{ s__('Environments|Environment') }} </div> @@ -473,14 +468,28 @@ export default { <icon :name="deployIconName" /> </span> - <span v-if="!model.isFolder" class="environment-name table-mobile-content"> - <a class="qa-environment-link" :href="environmentPath"> {{ model.name }} </a> + <span + v-if="!model.isFolder" + v-gl-tooltip + :title="model.name" + class="environment-name table-mobile-content" + > + <a class="qa-environment-link" :href="environmentPath"> + <span v-if="model.size === 1">{{ model.name }}</span> + <span v-else>{{ model.name_without_type }}</span> + </a> <span v-if="isProtected" class="badge badge-success"> {{ s__('Environments|protected') }} </span> </span> - - <span v-else class="folder-name" role="button" @click="onClickFolder"> + <span + v-else + v-gl-tooltip + :title="model.folderName" + class="folder-name" + role="button" + @click="onClickFolder" + > <icon :name="folderIconName" class="folder-icon" /> <icon name="folder" class="folder-icon" /> diff --git a/app/assets/javascripts/environments/components/environments_table.vue b/app/assets/javascripts/environments/components/environments_table.vue index ff4e16178e8..55613d815ce 100644 --- a/app/assets/javascripts/environments/components/environments_table.vue +++ b/app/assets/javascripts/environments/components/environments_table.vue @@ -99,7 +99,7 @@ export default { /> <div - v-if="shouldRenderDeployBoard" + v-if="shouldRenderDeployBoard(model)" :key="`deploy-board-row-${i}`" class="js-deploy-board-row" > diff --git a/app/assets/javascripts/error_tracking/store/actions.js b/app/assets/javascripts/error_tracking/store/actions.js index d42e4f145dc..1e754a4f54f 100644 --- a/app/assets/javascripts/error_tracking/store/actions.js +++ b/app/assets/javascripts/error_tracking/store/actions.js @@ -20,7 +20,7 @@ export function startPolling({ commit, dispatch }, endpoint) { commit(types.SET_LOADING, false); dispatch('stopPolling'); }, - errorCallback: response => { + errorCallback: ({ response }) => { let errorMessage = ''; if (response && response.data && response.data.message) { errorMessage = response.data.message; diff --git a/app/assets/javascripts/issue_show/components/description.vue b/app/assets/javascripts/issue_show/components/description.vue index 58f14bac8c8..732184dc782 100644 --- a/app/assets/javascripts/issue_show/components/description.vue +++ b/app/assets/javascripts/issue_show/components/description.vue @@ -140,7 +140,7 @@ export default { 'issue-realtime-pre-pulse': preAnimation, 'issue-realtime-trigger-pulse': pulseAnimation, }" - class="wiki" + class="md" v-html="descriptionHtml" ></div> <textarea diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js index 1af6b63efc9..59930f8d4a3 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js +++ b/app/assets/javascripts/lib/utils/common_utils.js @@ -7,6 +7,7 @@ import axios from './axios_utils'; import { getLocationHash } from './url_utility'; import { convertToCamelCase } from './text_utility'; import { isObject } from './type_utility'; +import BreakpointInstance from '../../breakpoints'; export const getPagePath = (index = 0) => { const page = $('body').attr('data-page') || ''; @@ -193,16 +194,24 @@ export const isMetaKey = e => e.metaKey || e.ctrlKey || e.altKey || e.shiftKey; export const isMetaClick = e => e.metaKey || e.ctrlKey || e.which === 2; export const contentTop = () => { - const perfBar = $('#js-peek').height() || 0; - const mrTabsHeight = $('.merge-request-tabs').height() || 0; - const headerHeight = $('.navbar-gitlab').height() || 0; - const diffFilesChanged = $('.js-diff-files-changed').height() || 0; - const diffFileLargeEnoughScreen = - 'matchMedia' in window ? window.matchMedia('min-width: 768') : true; + const perfBar = $('#js-peek').outerHeight() || 0; + const mrTabsHeight = $('.merge-request-tabs').outerHeight() || 0; + const headerHeight = $('.navbar-gitlab').outerHeight() || 0; + const diffFilesChanged = $('.js-diff-files-changed').outerHeight() || 0; + const mdScreenOrBigger = ['lg', 'md'].includes(BreakpointInstance.getBreakpointSize()); const diffFileTitleBar = - (diffFileLargeEnoughScreen && $('.diff-file .file-title-flex-parent:visible').height()) || 0; + (mdScreenOrBigger && $('.diff-file .file-title-flex-parent:visible').outerHeight()) || 0; + const compareVersionsHeaderHeight = + (mdScreenOrBigger && $('.mr-version-controls').outerHeight()) || 0; - return perfBar + mrTabsHeight + headerHeight + diffFilesChanged + diffFileTitleBar; + return ( + perfBar + + mrTabsHeight + + headerHeight + + diffFilesChanged + + diffFileTitleBar + + compareVersionsHeaderHeight + ); }; export const scrollToElement = element => { diff --git a/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js b/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js index c2de0379d23..3cb406b819d 100644 --- a/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js +++ b/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js @@ -16,7 +16,7 @@ import utilsMixin from '../mixins/line_conflict_utils'; }, }, template: ` - <table> + <table class="diff-wrap-lines code js-syntax-highlight"> <tr class="line_holder parallel" v-for="section in file.parallelLines"> <template v-for="line in section"> <td class="diff-line-num header" :class="lineCssClass(line)" v-if="line.isHeader"></td> diff --git a/app/assets/javascripts/monitoring/components/charts/area.vue b/app/assets/javascripts/monitoring/components/charts/area.vue index 41783d311ef..d453dc1fdb7 100644 --- a/app/assets/javascripts/monitoring/components/charts/area.vue +++ b/app/assets/javascripts/monitoring/components/charts/area.vue @@ -69,17 +69,17 @@ export default { appearance && appearance.line && appearance.line.type ? appearance.line.type : lineTypes.default; - const lineColor = lineType === lineTypes.threshold ? this.primaryColor : undefined; + const lineWidth = + appearance && appearance.line && appearance.line.width + ? appearance.line.width + : undefined; return { name: this.formatLegendLabel(query), data: this.concatenateResults(query.result), lineStyle: { type: lineType, - color: lineColor, - }, - itemStyle: { - color: lineColor, + width: lineWidth, }, areaStyle: { opacity: diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue index 895a57785bc..ba6a17827f7 100644 --- a/app/assets/javascripts/monitoring/components/dashboard.vue +++ b/app/assets/javascripts/monitoring/components/dashboard.vue @@ -1,6 +1,8 @@ <script> +import { GlDropdown, GlDropdownItem } from '@gitlab/ui'; import { s__ } from '~/locale'; import Icon from '~/vue_shared/components/icon.vue'; +import '~/vue_shared/mixins/is_ee'; import Flash from '../../flash'; import MonitoringService from '../services/monitoring_service'; import MonitorAreaChart from './charts/area.vue'; @@ -17,7 +19,10 @@ export default { GraphGroup, EmptyState, Icon, + GlDropdown, + GlDropdownItem, }, + props: { hasMetrics: { type: Boolean, @@ -104,9 +109,29 @@ export default { } }, mounted() { + this.servicePromises = [ + this.service + .getGraphsData() + .then(data => this.store.storeMetrics(data)) + .catch(() => Flash(s__('Metrics|There was an error while retrieving metrics'))), + this.service + .getDeploymentData() + .then(data => this.store.storeDeploymentData(data)) + .catch(() => Flash(s__('Metrics|There was an error getting deployment information.'))), + ]; if (!this.hasMetrics) { this.state = 'gettingStarted'; } else { + if (this.environmentsEndpoint) { + this.servicePromises.push( + this.service + .getEnvironmentsData() + .then(data => this.store.storeEnvironmentsData(data)) + .catch(() => + Flash(s__('Metrics|There was an error getting environments information.')), + ), + ); + } this.getGraphsData(); sidebarMutationObserver = new MutationObserver(this.onSidebarMutation); sidebarMutationObserver.observe(document.querySelector('.layout-page'), { @@ -122,17 +147,7 @@ export default { }, getGraphsData() { this.state = 'loading'; - Promise.all([ - this.service.getGraphsData().then(data => this.store.storeMetrics(data)), - this.service - .getDeploymentData() - .then(data => this.store.storeDeploymentData(data)) - .catch(() => Flash(s__('Metrics|There was an error getting deployment information.'))), - this.service - .getEnvironmentsData() - .then(data => this.store.storeEnvironmentsData(data)) - .catch(() => Flash(s__('Metrics|There was an error getting environments information.'))), - ]) + Promise.all(this.servicePromises) .then(() => { if (this.store.groups.length < 1) { this.state = 'noData'; @@ -156,29 +171,22 @@ export default { <template> <div v-if="!showEmptyState" class="prometheus-graphs prepend-top-default"> - <div class="environments d-flex align-items-center"> - {{ s__('Metrics|Environment') }} - <div class="dropdown prepend-left-10"> - <button class="dropdown-menu-toggle" data-toggle="dropdown" type="button"> - <span>{{ currentEnvironmentName }}</span> - <icon name="chevron-down" /> - </button> - <div - v-if="store.environmentsData.length > 0" - class="dropdown-menu dropdown-menu-selectable dropdown-menu-drop-up" + <div v-if="environmentsEndpoint" class="environments d-flex align-items-center"> + <strong>{{ s__('Metrics|Environment') }}</strong> + <gl-dropdown + class="prepend-left-10 js-environments-dropdown" + toggle-class="dropdown-menu-toggle" + :text="currentEnvironmentName" + :disabled="store.environmentsData.length === 0" + > + <gl-dropdown-item + v-for="environment in store.environmentsData" + :key="environment.id" + :active="environment.name === currentEnvironmentName" + active-class="is-active" + >{{ environment.name }}</gl-dropdown-item > - <ul> - <li v-for="environment in store.environmentsData" :key="environment.id"> - <a - :href="environment.metrics_path" - :class="{ 'is-active': environment.name == currentEnvironmentName }" - class="dropdown-item" - >{{ environment.name }}</a - > - </li> - </ul> - </div> - </div> + </gl-dropdown> </div> <graph-group v-for="(groupData, index) in store.groups" @@ -194,7 +202,17 @@ export default { :alert-data="getGraphAlerts(graphData.id)" :container-width="elWidth" group-id="monitor-area-chart" - /> + > + <alert-widget + v-if="isEE && prometheusAlertsAvailable && alertsEndpoint && graphData.id" + :alerts-endpoint="alertsEndpoint" + :label="getGraphLabel(graphData)" + :current-alerts="getQueryAlerts(graphData)" + :custom-metric-id="graphData.id" + :alert-data="alertData[graphData.id]" + @setAlerts="setAlerts" + /> + </monitor-area-chart> </graph-group> </div> <empty-state diff --git a/app/assets/javascripts/monitoring/constants.js b/app/assets/javascripts/monitoring/constants.js index 869173b6572..55ecf3b5334 100644 --- a/app/assets/javascripts/monitoring/constants.js +++ b/app/assets/javascripts/monitoring/constants.js @@ -6,5 +6,4 @@ export const graphTypes = { export const lineTypes = { default: 'solid', - threshold: 'dashed', }; diff --git a/app/assets/javascripts/mr_popover/components/mr_popover.vue b/app/assets/javascripts/mr_popover/components/mr_popover.vue new file mode 100644 index 00000000000..8e2d8fa816a --- /dev/null +++ b/app/assets/javascripts/mr_popover/components/mr_popover.vue @@ -0,0 +1,110 @@ +<script> +import { GlPopover, GlSkeletonLoading } from '@gitlab/ui'; +import Icon from '../../vue_shared/components/icon.vue'; +import CiIcon from '../../vue_shared/components/ci_icon.vue'; +import timeagoMixin from '../../vue_shared/mixins/timeago'; +import query from '../queries/merge_request.graphql'; +import { mrStates, humanMRStates } from '../constants'; + +export default { + name: 'MRPopover', + components: { + GlPopover, + GlSkeletonLoading, + Icon, + CiIcon, + }, + mixins: [timeagoMixin], + props: { + target: { + type: HTMLAnchorElement, + required: true, + }, + projectPath: { + type: String, + required: true, + }, + mergeRequestIID: { + type: String, + required: true, + }, + mergeRequestTitle: { + type: String, + required: true, + }, + }, + data() { + return { + mergeRequest: {}, + }; + }, + computed: { + detailedStatus() { + return this.mergeRequest.headPipeline && this.mergeRequest.headPipeline.detailedStatus; + }, + formattedTime() { + return this.timeFormated(this.mergeRequest.createdAt); + }, + statusBoxClass() { + switch (this.mergeRequest.state) { + case mrStates.merged: + return 'status-box-mr-merged'; + case mrStates.closed: + return 'status-box-closed'; + default: + return 'status-box-open'; + } + }, + stateHumanName() { + switch (this.mergeRequest.state) { + case mrStates.merged: + return humanMRStates.merged; + case mrStates.closed: + return humanMRStates.closed; + default: + return humanMRStates.open; + } + }, + showDetails() { + return Object.keys(this.mergeRequest).length > 0; + }, + }, + apollo: { + mergeRequest: { + query, + update: data => data.project.mergeRequest, + variables() { + const { projectPath, mergeRequestIID } = this; + + return { + projectPath, + mergeRequestIID, + }; + }, + }, + }, +}; +</script> + +<template> + <gl-popover :target="target" boundary="viewport" placement="top" show> + <div class="mr-popover"> + <div v-if="$apollo.loading"> + <gl-skeleton-loading :lines="1" class="animation-container-small mt-1" /> + </div> + <div v-else-if="showDetails" class="d-flex align-items-center justify-content-between"> + <div class="d-inline-flex align-items-center"> + <div :class="`issuable-status-box status-box ${statusBoxClass}`"> + {{ stateHumanName }} + </div> + <span class="text-secondary">Opened <time v-text="formattedTime"></time></span> + </div> + <ci-icon v-if="detailedStatus" :status="detailedStatus" /> + </div> + <h5 class="my-2">{{ mergeRequestTitle }}</h5> + <div class="text-secondary"> + {{ `${projectPath}!${mergeRequestIID}` }} + </div> + </div> + </gl-popover> +</template> diff --git a/app/assets/javascripts/mr_popover/constants.js b/app/assets/javascripts/mr_popover/constants.js new file mode 100644 index 00000000000..433df844c80 --- /dev/null +++ b/app/assets/javascripts/mr_popover/constants.js @@ -0,0 +1,10 @@ +export const mrStates = { + merged: 'merged', + closed: 'closed', +}; + +export const humanMRStates = { + merged: 'Merged', + closed: 'Closed', + open: 'Open', +}; diff --git a/app/assets/javascripts/mr_popover/index.js b/app/assets/javascripts/mr_popover/index.js new file mode 100644 index 00000000000..cc686b401d2 --- /dev/null +++ b/app/assets/javascripts/mr_popover/index.js @@ -0,0 +1,62 @@ +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import MRPopover from './components/mr_popover.vue'; +import createDefaultClient from '~/lib/graphql'; + +let renderedPopover; +let renderFn; + +const handleUserPopoverMouseOut = ({ target }) => { + target.removeEventListener('mouseleave', handleUserPopoverMouseOut); + + if (renderFn) { + clearTimeout(renderFn); + } + if (renderedPopover) { + renderedPopover.$destroy(); + renderedPopover = null; + } +}; + +/** + * Adds a MergeRequestPopover component to the body, hands over as much data as the target element has in data attributes. + * loads based on data-project-path and data-iid more data about an MR from the API and sets it on the popover + */ +const handleMRPopoverMount = apolloProvider => ({ target }) => { + // Add listener to actually remove it again + target.addEventListener('mouseleave', handleUserPopoverMouseOut); + + const { projectPath, mrTitle, iid } = target.dataset; + const mergeRequest = {}; + + renderFn = setTimeout(() => { + const MRPopoverComponent = Vue.extend(MRPopover); + renderedPopover = new MRPopoverComponent({ + propsData: { + target, + projectPath, + mergeRequestIID: iid, + mergeRequest, + mergeRequestTitle: mrTitle, + }, + apolloProvider, + }); + + renderedPopover.$mount(); + }, 200); // 200ms delay so not every mouseover triggers Popover + API Call +}; + +export default elements => { + const mrLinks = elements || [...document.querySelectorAll('.gfm-merge_request')]; + if (mrLinks.length > 0) { + Vue.use(VueApollo); + + const apolloProvider = new VueApollo({ + defaultClient: createDefaultClient(), + }); + + mrLinks.forEach(el => { + el.addEventListener('mouseenter', handleMRPopoverMount(apolloProvider)); + }); + } +}; diff --git a/app/assets/javascripts/mr_popover/queries/merge_request.graphql b/app/assets/javascripts/mr_popover/queries/merge_request.graphql new file mode 100644 index 00000000000..0bb9bc03bc7 --- /dev/null +++ b/app/assets/javascripts/mr_popover/queries/merge_request.graphql @@ -0,0 +1,14 @@ +query mergeRequest($projectPath: ID!, $mergeRequestIID: ID!) { + project(fullPath: $projectPath) { + mergeRequest(iid: $mergeRequestIID) { + createdAt + state + headPipeline { + detailedStatus { + icon + group + } + } + } + } +} diff --git a/app/assets/javascripts/notebook/cells/output/index.vue b/app/assets/javascripts/notebook/cells/output/index.vue index c5ae7e7ee10..b59ddd0d57a 100644 --- a/app/assets/javascripts/notebook/cells/output/index.vue +++ b/app/assets/javascripts/notebook/cells/output/index.vue @@ -20,12 +20,20 @@ export default { required: true, }, }, - data() { - return { - outputType: '', - }; - }, methods: { + outputType(output) { + if (output.text) { + return 'text/plain'; + } else if (output.data['image/png']) { + return 'image/png'; + } else if (output.data['text/html']) { + return 'text/html'; + } else if (output.data['image/svg+xml']) { + return 'image/svg+xml'; + } + + return 'text/plain'; + }, dataForType(output, type) { let data = output.data[type]; @@ -39,20 +47,13 @@ export default { if (output.text) { return CodeOutput; } else if (output.data['image/png']) { - this.outputType = 'image/png'; - return ImageOutput; } else if (output.data['text/html']) { - this.outputType = 'text/html'; - return HtmlOutput; } else if (output.data['image/svg+xml']) { - this.outputType = 'image/svg+xml'; - return HtmlOutput; } - this.outputType = 'text/plain'; return CodeOutput; }, rawCode(output) { @@ -60,7 +61,7 @@ export default { return output.text.join(''); } - return this.dataForType(output, this.outputType); + return this.dataForType(output, this.outputType(output)); }, }, }; @@ -73,7 +74,7 @@ export default { v-for="(output, index) in outputs" :key="index" type="output" - :output-type="outputType" + :output-type="outputType(output)" :count="count" :index="index" :raw-code="rawCode(output)" diff --git a/app/assets/javascripts/notes/components/diff_with_note.vue b/app/assets/javascripts/notes/components/diff_with_note.vue index d8947e8ca50..ab758a9e922 100644 --- a/app/assets/javascripts/notes/components/diff_with_note.vue +++ b/app/assets/javascripts/notes/components/diff_with_note.vue @@ -72,8 +72,8 @@ export default { :can-current-user-fork="false" :expanded="!discussion.diff_file.viewer.collapsed" /> - <div v-if="isTextFile" :class="$options.userColorSchemeClass" class="diff-content code"> - <table> + <div v-if="isTextFile" class="diff-content"> + <table class="code js-syntax-highlight" :class="$options.userColorSchemeClass"> <template v-if="hasTruncatedDiffLines"> <tr v-for="line in discussion.truncated_diff_lines" @@ -81,8 +81,8 @@ export default { :key="line.line_code" class="line_holder" > - <td class="diff-line-num old_line">{{ line.old_line }}</td> - <td class="diff-line-num new_line">{{ line.new_line }}</td> + <td :class="line.type" class="diff-line-num old_line">{{ line.old_line }}</td> + <td :class="line.type" class="diff-line-num new_line">{{ line.new_line }}</td> <td :class="line.type" class="line_content" v-html="line.rich_text"></td> </tr> </template> diff --git a/app/assets/javascripts/notes/components/note_body.vue b/app/assets/javascripts/notes/components/note_body.vue index fb1d98355b3..ff303d0f55a 100644 --- a/app/assets/javascripts/notes/components/note_body.vue +++ b/app/assets/javascripts/notes/components/note_body.vue @@ -95,7 +95,6 @@ export default { <div ref="note-body" :class="{ 'js-task-list-container': canEdit }" class="note-body"> <suggestions v-if="hasSuggestion && !isEditing" - class="note-text md" :suggestions="note.suggestions" :note-html="note.note_html" :line-type="lineType" diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue index 0fabbfb06b5..a3d664a738f 100644 --- a/app/assets/javascripts/notes/components/noteable_discussion.vue +++ b/app/assets/javascripts/notes/components/noteable_discussion.vue @@ -87,13 +87,10 @@ export default { }, }, data() { - const { diff_discussion: isDiffDiscussion, resolved } = this.discussion; - return { isReplying: false, isResolving: false, resolveAsThread: true, - isRepliesCollapsed: Boolean(!isDiffDiscussion && resolved), }; }, computed: { @@ -178,11 +175,11 @@ export default { return ''; }, - shouldShowDiscussions() { - const { expanded, resolved } = this.discussion; - const isResolvedNonDiffDiscussion = !this.discussion.diff_discussion && resolved; - - return expanded || this.alwaysExpanded || isResolvedNonDiffDiscussion; + isExpanded() { + return this.discussion.expanded || this.alwaysExpanded; + }, + shouldHideDiscussionBody() { + return this.shouldRenderDiffs && !this.isExpanded; }, actionText() { const linkStart = `<a href="${_.escape(this.discussion.discussion_path)}">`; @@ -282,9 +279,6 @@ export default { toggleDiscussionHandler() { this.toggleDiscussion({ discussionId: this.discussion.id }); }, - toggleReplies() { - this.isRepliesCollapsed = !this.isRepliesCollapsed; - }, showReplyForm() { this.isReplying = true; }, @@ -405,7 +399,7 @@ Please check your network connection and try again.`; /> </div> </div> - <div v-if="shouldShowDiscussions" class="discussion-body"> + <div v-if="!shouldHideDiscussionBody" class="discussion-body"> <component :is="wrapperComponent" v-bind="wrapperComponentProps" @@ -436,11 +430,11 @@ Please check your network connection and try again.`; </component> <toggle-replies-widget v-if="hasReplies" - :collapsed="isRepliesCollapsed" + :collapsed="!isExpanded" :replies="replies" - @toggle="toggleReplies" + @toggle="toggleDiscussionHandler" /> - <template v-if="!isRepliesCollapsed"> + <template v-if="isExpanded"> <component :is="componentName(note)" v-for="note in replies" @@ -467,7 +461,7 @@ Please check your network connection and try again.`; </template> </ul> <div - v-if="!isRepliesCollapsed || !hasReplies" + v-if="isExpanded || !hasReplies" :class="{ 'is-replying': isReplying }" class="discussion-reply-holder" > diff --git a/app/assets/javascripts/notifications_dropdown.js b/app/assets/javascripts/notifications_dropdown.js index e7fa05faa8a..6aed2492084 100644 --- a/app/assets/javascripts/notifications_dropdown.js +++ b/app/assets/javascripts/notifications_dropdown.js @@ -4,6 +4,7 @@ import Flash from './flash'; export default function notificationsDropdown() { $(document).on('click', '.update-notification', function updateNotificationCallback(e) { e.preventDefault(); + if ($(this).is('.is-active') && $(this).data('notificationLevel') === 'custom') { return; } diff --git a/app/assets/javascripts/pipelines/mixins/graph_pipeline_bundle_mixin.js b/app/assets/javascripts/pipelines/mixins/graph_pipeline_bundle_mixin.js index 9177943f88a..dd79ade5bc9 100644 --- a/app/assets/javascripts/pipelines/mixins/graph_pipeline_bundle_mixin.js +++ b/app/assets/javascripts/pipelines/mixins/graph_pipeline_bundle_mixin.js @@ -1,6 +1,16 @@ +import Flash from '~/flash'; +import { __ } from '~/locale'; + export default { methods: { clickTriggeredByPipeline() {}, clickTriggeredPipeline() {}, + requestRefreshPipelineGraph() { + // When an action is clicked + // (wether in the dropdown or in the main nodes, we refresh the big graph) + this.mediator + .refreshPipeline() + .catch(() => Flash(__('An error occurred while making the request.'))); + }, }, }; diff --git a/app/assets/javascripts/pipelines/pipeline_details_bundle.js b/app/assets/javascripts/pipelines/pipeline_details_bundle.js index 8adbd39edd4..6660f8120f8 100644 --- a/app/assets/javascripts/pipelines/pipeline_details_bundle.js +++ b/app/assets/javascripts/pipelines/pipeline_details_bundle.js @@ -29,15 +29,6 @@ export default () => { mediator, }; }, - methods: { - requestRefreshPipelineGraph() { - // When an action is clicked - // (wether in the dropdown or in the main nodes, we refresh the big graph) - this.mediator - .refreshPipeline() - .catch(() => Flash(__('An error occurred while making the request.'))); - }, - }, render(createElement) { return createElement('pipeline-graph', { props: { diff --git a/app/assets/javascripts/pipelines/stores/pipeline_store.js b/app/assets/javascripts/pipelines/stores/pipeline_store.js index 052e34a8aef..259278b6410 100644 --- a/app/assets/javascripts/pipelines/stores/pipeline_store.js +++ b/app/assets/javascripts/pipelines/stores/pipeline_store.js @@ -1,7 +1,6 @@ export default class PipelineStore { constructor() { this.state = {}; - this.state.pipeline = {}; } diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.vue index 2a4dff71d9b..11bc8c73ee9 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.vue @@ -80,7 +80,7 @@ export default { <status-icon :show-disabled-button="true" status="warning" /> <div class="media-body space-children"> <span class="bold"> - <span v-if="mr.mergeError" class="has-error-message"> {{ mergeError }}. </span> + <span v-if="mr.mergeError" class="has-error-message"> {{ mergeError }} </span> <span v-else> {{ s__('mrWidget|Merge failed.') }} </span> <span :class="{ 'has-custom-error': mr.mergeError }"> {{ timerText }} </span> </span> diff --git a/app/assets/javascripts/vue_shared/components/ci_icon.vue b/app/assets/javascripts/vue_shared/components/ci_icon.vue index e6f0a1c69cd..25f80219993 100644 --- a/app/assets/javascripts/vue_shared/components/ci_icon.vue +++ b/app/assets/javascripts/vue_shared/components/ci_icon.vue @@ -22,6 +22,7 @@ import Icon from '../../vue_shared/components/icon.vue'; * - Jobs show view header * - Jobs show view sidebar * - Linked pipelines + * - Extended MR Popover */ const validSizes = [8, 12, 16, 18, 24, 32, 48, 72]; diff --git a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue index c9915f7d685..5fdc915fffb 100644 --- a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue +++ b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue @@ -78,8 +78,8 @@ export default { </script> <template> - <div ref="markdown-preview" class="md md-previewer"> + <div ref="markdown-preview" class="md-previewer"> <gl-skeleton-loading v-if="isLoading" /> - <div v-else v-html="previewContent"></div> + <div v-else class="md" v-html="previewContent"></div> </div> </template> diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue index 3f607aa2a0a..eccf73e227c 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/field.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue @@ -189,7 +189,7 @@ export default { <div ref="gl-form" :class="{ 'prepend-top-default append-bottom-default': addSpacingClasses }" - class="md-area js-vue-markdown-field" + class="js-vue-markdown-field md-area position-relative" > <markdown-header :preview-markdown="previewMarkdown" @@ -215,7 +215,7 @@ export default { <div v-show="previewMarkdown" ref="markdown-preview" - class="md-preview js-vue-md-preview md md-preview-holder" + class="js-vue-md-preview md-preview-holder" > <suggestions v-if="hasSuggestion" @@ -233,7 +233,7 @@ export default { <div v-show="previewMarkdown" ref="markdown-preview" - class="md-preview js-vue-md-preview md md-preview-holder" + class="js-vue-md-preview md md-preview-holder" v-html="markdownPreview" ></div> </template> diff --git a/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue b/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue index dcda701f049..177d78cb904 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue @@ -130,6 +130,6 @@ export default { <template> <div> <div class="flash-container js-suggestions-flash"></div> - <div v-show="isRendered" ref="container" v-html="noteHtml"></div> + <div v-show="isRendered" ref="container" class="md" v-html="noteHtml"></div> </div> </template> diff --git a/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue b/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue index 8d3a3009c55..a50f49c1279 100644 --- a/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue +++ b/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue @@ -57,7 +57,7 @@ export default { </div> </div> <div class="note-body"> - <div class="note-text"> + <div class="note-text md"> <p>{{ note.body }}</p> </div> </div> diff --git a/app/assets/javascripts/vue_shared/components/notes/system_note.vue b/app/assets/javascripts/vue_shared/components/notes/system_note.vue index b0af8399955..acc179b3834 100644 --- a/app/assets/javascripts/vue_shared/components/notes/system_note.vue +++ b/app/assets/javascripts/vue_shared/components/notes/system_note.vue @@ -93,7 +93,7 @@ export default { 'system-note-commit-list': hasMoreCommits, 'hide-shade': expanded, }" - class="note-text" + class="note-text md" v-html="note.note_html" ></div> <div v-if="hasMoreCommits" class="flex-list"> diff --git a/app/assets/javascripts/vue_shared/components/svg_gradient.vue b/app/assets/javascripts/vue_shared/components/svg_gradient.vue index cca90af275e..5ce45d492f9 100644 --- a/app/assets/javascripts/vue_shared/components/svg_gradient.vue +++ b/app/assets/javascripts/vue_shared/components/svg_gradient.vue @@ -4,10 +4,16 @@ export default { colors: { type: Array, required: true, + validator(value) { + return value.length === 2; + }, }, opacity: { type: Array, required: true, + validator(value) { + return value.length === 2; + }, }, identifierName: { type: String, diff --git a/app/assets/stylesheets/bootstrap_migration.scss b/app/assets/stylesheets/bootstrap_migration.scss index c8357f7751c..93377b8dd91 100644 --- a/app/assets/stylesheets/bootstrap_migration.scss +++ b/app/assets/stylesheets/bootstrap_migration.scss @@ -343,16 +343,6 @@ input[type=color].form-control { } } -// Bootstrap 3 compatibility because bootstrap_form Gem is not updated yet -.input-group-btn:first-child { - @extend .input-group-prepend; -} - -// Bootstrap 3 compatibility because bootstrap_form Gem is not updated yet -.input-group-btn:last-child { - @extend .input-group-append; -} - /* Bootstrap 4.1.2 introduced a new default vertical alignment which breaks our icons, so we need to reset the vertical alignment to the default value. See: diff --git a/app/assets/stylesheets/components/popover.scss b/app/assets/stylesheets/components/popover.scss index 2f4d30fe923..7d46b262a69 100644 --- a/app/assets/stylesheets/components/popover.scss +++ b/app/assets/stylesheets/components/popover.scss @@ -7,3 +7,10 @@ line-height: $gl-line-height; } } + +.mr-popover { + .text-secondary { + font-size: 12px; + line-height: 1.33; + } +} diff --git a/app/assets/stylesheets/components/related_items_list.scss b/app/assets/stylesheets/components/related_items_list.scss index edf7b26ebaa..5a5601f2fa3 100644 --- a/app/assets/stylesheets/components/related_items_list.scss +++ b/app/assets/stylesheets/components/related_items_list.scss @@ -307,6 +307,7 @@ $item-weight-max-width: 48px; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; + line-height: 1.3; } .issue-token-state-icon-open, diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss index 216877a4461..ab9047c54e4 100644 --- a/app/assets/stylesheets/framework.scss +++ b/app/assets/stylesheets/framework.scss @@ -60,6 +60,7 @@ @import 'framework/memory_graph'; @import 'framework/responsive_tables'; @import 'framework/stacked_progress_bar'; +@import 'framework/sortable'; @import 'framework/ci_variable_list'; @import 'framework/feature_highlight'; @import 'framework/terms'; diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index 97a9a55c968..d72597a6147 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -415,6 +415,12 @@ img.emoji { .ws-normal { white-space: normal; } .overflow-auto { overflow: auto; } +.d-flex-center { + display: flex; + align-items: center; + justify-content: center; +} + /** COMMON SIZING CLASSES **/ .w-0 { width: 0; } @@ -456,10 +462,10 @@ img.emoji { } /** COMMON POSITIONING CLASSES */ -.position-bottom-0 { bottom: 0; } -.position-left-0 { left: 0; } -.position-right-0 { right: 0; } -.position-top-0 { top: 0; } +.position-bottom-0 { bottom: 0 !important; } +.position-left-0 { left: 0 !important; } +.position-right-0 { right: 0 !important; } +.position-top-0 { top: 0 !important; } .drag-handle { width: 4px; diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index 6108eaa1ad0..8d38310e8e6 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -25,10 +25,6 @@ } } - table { - @extend .table; - } - .file-title { position: relative; background-color: $gray-light; @@ -123,7 +119,7 @@ } } - &.wiki { + &.md { padding: $gl-padding; @include media-breakpoint-up(md) { diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss index 3b1d1d67509..c36c15a85be 100644 --- a/app/assets/stylesheets/framework/forms.scss +++ b/app/assets/stylesheets/framework/forms.scss @@ -128,7 +128,7 @@ label { .form-control { @include box-shadow(none); - border-radius: 2px; + border-radius: $border-radius-default; padding: $gl-vert-padding $gl-input-padding; &.input-short { diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss index de2cd600623..7c10de828cd 100644 --- a/app/assets/stylesheets/framework/markdown_area.scss +++ b/app/assets/stylesheets/framework/markdown_area.scss @@ -50,10 +50,6 @@ transition: opacity 200ms ease-in-out; } -.md-area { - position: relative; -} - .md-header { .nav-links { a { @@ -135,30 +131,6 @@ width: 100%; } -.md:not(.use-csslab) { - &.md-preview-holder { - // Reset ul style types since we're nested inside a ul already - @include bulleted-list; - } - - // On diffs code should wrap nicely and not overflow - code { - white-space: pre-wrap; - word-break: keep-all; - } - - hr { - // Darken 'whitesmoke' a bit to make it more visible in note bodies - border-color: darken($gray-normal, 8%); - margin: 10px 0; - } - - - table { - @include markdown-table; - } -} - .toolbar-btn { float: left; padding: 0 7px; diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss index 9e192cbe3fc..18eb10c1f23 100644 --- a/app/assets/stylesheets/framework/mixins.scss +++ b/app/assets/stylesheets/framework/mixins.scss @@ -22,26 +22,6 @@ } /* - * Mixin for markdown tables - */ -@mixin markdown-table { - width: auto; - display: block; - overflow-x: auto; - border: 0; - - tr { - th { - border-bottom: solid 2px $gl-gray-200; - } - - td { - border-color: $gl-gray-200; - } - } -} - -/* * Base mixin for lists in GitLab */ @mixin basic-list { @@ -93,20 +73,6 @@ } } -@mixin bulleted-list { - > ul { - list-style-type: disc; - - ul { - list-style-type: circle; - - ul { - list-style-type: square; - } - } - } -} - @mixin webkit-prefix($property, $value) { #{'-webkit-' + $property}: $value; #{$property}: $value; diff --git a/app/assets/stylesheets/framework/page_title.scss b/app/assets/stylesheets/framework/page_title.scss index e8302953a63..ad6575761b5 100644 --- a/app/assets/stylesheets/framework/page_title.scss +++ b/app/assets/stylesheets/framework/page_title.scss @@ -2,7 +2,6 @@ @extend .d-flex; @extend .align-items-center; - padding-top: $gl-padding-top; border-bottom: 1px solid $border-color; .page-title { diff --git a/app/assets/stylesheets/framework/sortable.scss b/app/assets/stylesheets/framework/sortable.scss new file mode 100644 index 00000000000..8c070200135 --- /dev/null +++ b/app/assets/stylesheets/framework/sortable.scss @@ -0,0 +1,92 @@ +.sortable-container { + background-color: $gray-light; + + .flex-list { + padding: 5px; + margin-bottom: 0; + } +} + +.sortable-row { + .flex-row { + display: flex; + + &.issuable-info-container { + padding-right: 0; + } + } + + .sortable-link { + color: $black; + } +} + +.gl-sortable { + .header { + user-select: none; + + &:hover { + cursor: pointer; + background-color: $gray-100; + } + + &:focus { + outline: 1px solid $blue-300; + } + } +} + +.related-issues-list-item { + .card-body, + .issuable-info-container { + padding: $gl-padding-4 $gl-padding-4 $gl-padding-4 $gl-padding; + + .block-truncated { + padding: $gl-padding-8 0; + line-height: $gl-btn-line-height; + } + + @include media-breakpoint-down(md) { + padding-left: $gl-padding; + + .block-truncated { + flex-direction: column-reverse; + padding: $gl-padding-4 0; + + .text-secondary { + margin-top: $gl-padding-4; + } + + .issue-token-title-text { + display: block; + } + } + + .issue-item-remove-button { + align-self: baseline; + } + } + + @include media-breakpoint-only(md) { + .block-truncated .issue-token-title-text { + white-space: nowrap; + } + + .issue-item-remove-button { + align-self: center; + } + } + + @include media-breakpoint-down(sm) { + padding-left: $gl-padding-8; + + .block-truncated .issue-token-title-text { + white-space: normal; + } + } + } + + &.is-dragging { + padding: 0; + } +} diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index 5e5e8bcc3d6..244b414d334 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -1,4 +1,8 @@ -@mixin md-typography { +/** + * Apply Markdown typography + * + */ +.md:not(.use-csslab) { color: $gl-text-color; word-wrap: break-word; @@ -6,8 +10,35 @@ text-align: initial; } + *:first-child { + margin-top: 0; + } + + > :last-child { + margin-bottom: 0; + } + + p { + color: $gl-text-color; + margin: 0 0 16px; + + > code { + font-weight: inherit; + } + + a:not(.no-attachment-icon) img { + // Remove bottom padding because + // <p> already has $gl-padding bottom + margin-bottom: 0; + } + } + a { color: $blue-600; + + > code { + color: $blue-600; + } } img:not(.emoji) { @@ -28,18 +59,12 @@ max-width: 100%; } - p a:not(.no-attachment-icon) img { - // Remove bottom padding because - // <p> already has $gl-padding bottom - margin-bottom: 0; - } - - *:first-child { - margin-top: 0; - } - - > :last-child { - margin-bottom: 0; + &:not(.md-file) img:not(.emoji) { + border: 1px solid $white-normal; + padding: 5px; + margin: 5px 0; + // Ensure that image does not exceed viewport + max-height: calc(100vh - 100px); } // Single code lines should wrap @@ -47,6 +72,7 @@ font-family: $monospace-font; white-space: pre-wrap; word-wrap: normal; + word-break: keep-all; } kbd { @@ -131,17 +157,25 @@ } } - p { - color: $gl-text-color; - margin: 0 0 16px; + hr { + // Darken 'whitesmoke' a bit to make it more visible in note bodies + border-color: darken($gray-normal, 8%); + margin: 10px 0; } - table { + table:not(.code) { @extend .table; @extend .table-bordered; margin: 16px 0; color: $gl-text-color; border: 0; + width: auto; + display: block; + overflow-x: auto; + + tbody { + background-color: $white-light; + } tr { th { @@ -179,14 +213,6 @@ } } - p > code { - font-weight: inherit; - } - - a > code { - color: $blue-600; - } - dd { margin-left: $gl-padding; } @@ -202,6 +228,18 @@ margin: 3px 28px 3px 0 !important; } + > ul { + list-style-type: disc; + + ul { + list-style-type: circle; + + ul { + list-style-type: square; + } + } + } + li { line-height: 1.6em; margin-left: 25px; @@ -245,11 +283,11 @@ &:hover::before { text-decoration: none; } - } - a.no-attachment-icon { - &::before { - display: none; + &.no-attachment-icon { + &::before { + display: none; + } } } @@ -368,28 +406,6 @@ code { } /** - * Apply Markdown typography - * - */ -.wiki:not(.use-csslab) { - @include md-typography; -} - -.md:not(.use-csslab) { - @include md-typography; - - &:not(.wiki) { - img:not(.emoji) { - border: 1px solid $white-normal; - padding: 5px; - margin: 5px 0; - // Ensure that image does not exceed viewport - max-height: calc(100vh - 100px); - } - } -} - -/** * Textareas intended for GFM * */ diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index efebbd124d0..5d4c84c494d 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -711,3 +711,11 @@ $mr-version-controls-height: 56px; Compare Branches */ $compare-branches-sticky-header-height: 68px; + +/** + Bootstrap 4.2.0 introduced new icons for validating forms. + Our design system does not use those, so we are disabling them for now: + - Docs: https://getbootstrap.com/docs/4.3/components/forms/#server-side + - Issue: https://gitlab.com/gitlab-org/design.gitlab.com/issues/242 + */ +$enable-validation-icons: false; diff --git a/app/assets/stylesheets/framework/variables_overrides.scss b/app/assets/stylesheets/framework/variables_overrides.scss index efcc437bd7f..fb4d3f23cd9 100644 --- a/app/assets/stylesheets/framework/variables_overrides.scss +++ b/app/assets/stylesheets/framework/variables_overrides.scss @@ -9,7 +9,6 @@ $input-border-color: $gray-200; $input-color: $gl-text-color; $font-family-sans-serif: $regular-font; $font-family-monospace: $monospace-font; -$input-line-height: 20px; $btn-line-height: 20px; $table-accent-bg: $gray-light; $card-border-color: $border-color; diff --git a/app/assets/stylesheets/framework/wells.scss b/app/assets/stylesheets/framework/wells.scss index 161943766d4..434cbd6d21c 100644 --- a/app/assets/stylesheets/framework/wells.scss +++ b/app/assets/stylesheets/framework/wells.scss @@ -12,6 +12,10 @@ border-bottom: 1px solid $well-inner-border; } + &.borderless { + border-bottom: 0; + } + &.branch-info { .commit-sha, .commit-info { diff --git a/app/assets/stylesheets/notify.scss b/app/assets/stylesheets/notify.scss index f24c80bd81c..d77b7dfad68 100644 --- a/app/assets/stylesheets/notify.scss +++ b/app/assets/stylesheets/notify.scss @@ -1,4 +1,4 @@ -@import "framework/variables"; +@import 'framework/variables'; img { max-width: 100%; diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index 81216b2b98e..ed0e9db035b 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -19,6 +19,7 @@ .is-ghost { opacity: 0.3; + pointer-events: none; } .dropdown-projects { @@ -50,6 +51,19 @@ .content-wrapper { padding-bottom: 0; } + + .issues-details-filters { + display: flex; + } + + .filter-form { + width: 100%; + } +} + +.board-extra-actions { + font-size: 0; + white-space: nowrap; } .boards-app { @@ -236,7 +250,8 @@ } } -.board-blank-state { +.board-blank-state, +.board-promotion-state { padding: $gl-padding; background-color: $white-light; flex: 1; diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index c88922ae5ea..1fedbd8bddb 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -89,138 +89,6 @@ } } - .note-text { - table { - font-family: $font-family-sans-serif; - } - } - - table { - width: 100%; - font-family: $monospace-font; - border: 0; - border-collapse: separate; - margin: 0; - padding: 0; - table-layout: fixed; - border-radius: 0 0 $border-radius-default $border-radius-default; - - .diff-line-num { - width: 50px; - position: relative; - - a { - transition: none; - } - } - - .line_holder td { - line-height: $code-line-height; - font-size: $code-font-size; - vertical-align: top; - - &.noteable_line { - position: relative; - } - - span { - white-space: pre-wrap; - - &.context-cell { - display: inline-block; - width: 100%; - height: 100%; - } - } - - .line { - word-wrap: break-word; - } - } - - &.left-side-selected { - td.line_content.parallel.right-side { - user-select: none; - } - } - - &.right-side-selected { - td.line_content.parallel.left-side { - user-select: none; - } - } - } - - tr.line_holder.parallel { - td.line_content.parallel { - width: 46%; - } - - .add-diff-note { - margin-left: -55px; - } - } - - .old_line, - .new_line { - user-select: none; - margin: 0; - border: 0; - padding: 0 5px; - border-right: 1px solid; - text-align: right; - min-width: 35px; - max-width: 50px; - width: 35px; - - a { - float: left; - width: 35px; - font-weight: $gl-font-weight-normal; - - &[disabled] { - cursor: default; - - &:hover, - &:active { - text-decoration: none; - } - } - } - } - - .line_content { - display: block; - margin: 0; - padding: 0 1.5em; - border: 0; - position: relative; - - &.parallel { - display: table-cell; - - span { - word-break: break-all; - } - } - - &.old { - &::before { - content: '-'; - position: absolute; - left: 0.5em; - } - } - - &.new { - &::before { - content: '+'; - position: absolute; - left: 0.5em; - } - } - } - .diff-loading-error-block { padding: $gl-padding * 2 $gl-padding; text-align: center; @@ -443,10 +311,6 @@ } } - .line_content { - white-space: pre-wrap; - } - .diff-file-container { .frame.deleted { border: 1px solid $deleted; @@ -508,6 +372,114 @@ } } +table.code { + width: 100%; + font-family: $monospace-font; + border: 0; + border-collapse: separate; + margin: 0; + padding: 0; + table-layout: fixed; + border-radius: 0 0 $border-radius-default $border-radius-default; + + tr.line_holder td { + line-height: $code-line-height; + font-size: $code-font-size; + vertical-align: top; + + span { + white-space: pre-wrap; + + &.context-cell { + display: inline-block; + width: 100%; + height: 100%; + } + + &.line { + word-wrap: break-word; + } + } + + &.diff-line-num { + user-select: none; + margin: 0; + border: 0; + padding: 0 10px 0 5px; + border-right: 1px solid; + text-align: right; + width: 50px; + position: relative; + + a { + transition: none; + float: left; + width: 100%; + font-weight: $gl-font-weight-normal; + + &[disabled] { + cursor: default; + + &:hover, + &:active { + text-decoration: none; + } + } + } + + &:not(.js-unfold-bottom) a::before { + content: attr(data-linenumber); + } + } + + &.line_content { + display: block; + margin: 0; + padding: 0 1.5em; + border: 0; + position: relative; + white-space: pre-wrap; + + &.parallel { + display: table-cell; + width: 46%; + + span { + word-break: break-all; + } + } + + &.old { + &::before { + content: '-'; + position: absolute; + left: 0.5em; + } + } + + &.new { + &::before { + content: '+'; + position: absolute; + left: 0.5em; + } + } + } + } + + &.left-side-selected { + td.line_content.parallel.right-side { + user-select: none; + } + } + + &.right-side-selected { + td.line_content.parallel.left-side { + user-select: none; + } + } +} + .diff-stats { align-items: center; padding: 0 0.25rem; @@ -608,16 +580,6 @@ } } -.file-holder { - .diff-line-num:not(.js-unfold-bottom) { - a { - &::before { - content: attr(data-linenumber); - } - } - } -} - .diff-comment-avatar-holders { position: absolute; height: 19px; diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss index 61ecf133b02..0eb854ecf98 100644 --- a/app/assets/stylesheets/pages/environments.scss +++ b/app/assets/stylesheets/pages/environments.scss @@ -346,7 +346,9 @@ } > .popover-title, - > .popover-content { + > .popover-content, + > .popover-header, + > .popover-body { padding: 8px; font-size: 12px; white-space: nowrap; diff --git a/app/assets/stylesheets/pages/help.scss b/app/assets/stylesheets/pages/help.scss index 161d4dbfb22..7610c5cf6f3 100644 --- a/app/assets/stylesheets/pages/help.scss +++ b/app/assets/stylesheets/pages/help.scss @@ -37,12 +37,4 @@ .documentation { padding: 7px; - - // Border around images in the help pages. - img:not(.emoji) { - border: 1px solid $white-normal; - padding: 5px; - margin: 5px; - max-height: calc(100vh - 100px); - } } diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 623fa485ba6..6415d902ca6 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -72,14 +72,6 @@ height: $gl-padding * 2; } - // Border around images in issue and MR descriptions. - .description img:not(.emoji) { - border: 1px solid $white-normal; - padding: 5px; - max-height: calc(100vh - 100px); - max-width: 100%; - } - .emoji-block { padding: 10px 0; } @@ -264,6 +256,10 @@ .selectbox { display: none; + + &.show { + display: block; + } } .btn-clipboard:hover { @@ -317,6 +313,7 @@ } .no-value, + .btn-default-hover-link, .btn-secondary-hover-link { color: $gl-text-color-secondary; } @@ -600,7 +597,6 @@ margin: -7px; } - .user-list { display: flex; flex-wrap: wrap; @@ -724,6 +720,7 @@ .issuable-main-info { flex: 1 auto; margin-right: 10px; + min-width: 0; .issue-weight-icon { vertical-align: sub; @@ -785,6 +782,7 @@ @media(max-width: map-get($grid-breakpoints, lg)-1) { .task-status, .issuable-due-date, + .issuable-weight, .project-ref-path { display: none; } diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index 9f30495a7ef..c7d2369a6b8 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -145,6 +145,11 @@ ul.related-merge-requests > li { } } +.issues-footer { + padding-top: $gl-padding; + padding-bottom: 37px; +} + .issues-nav-controls { font-size: 0; diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss index eb32beb0972..e0b84e0f92d 100644 --- a/app/assets/stylesheets/pages/members.scss +++ b/app/assets/stylesheets/pages/members.scss @@ -14,6 +14,12 @@ } .member { + &.is-overriden { + .btn-ldap-override { + display: none !important; + } + } + .list-item-name { @include media-breakpoint-up(sm) { float: left; @@ -122,6 +128,49 @@ outline: 0; } +.members-ldap { + align-self: center; + height: 100%; + margin-right: 10px; + margin-left: -49px; +} + +.alert-member-ldap { + background-color: $orange-50; + + @include media-breakpoint-up(sm) { + line-height: 40px; + } + + > p { + float: left; + margin-bottom: 10px; + color: $orange-600; + + @include media-breakpoint-up(sm) { + padding-left: 55px; + margin-bottom: 0; + } + } + + .controls { + width: 100%; + + @include media-breakpoint-up(sm) { + width: auto; + } + } +} + +.btn-ldap-override { + width: 100%; + + @include media-breakpoint-up(sm) { + margin-left: 10px; + width: auto; + } +} + .flex-project-members-panel { display: flex; flex-direction: row; diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss index 3ca8e943a3a..49608a3964f 100644 --- a/app/assets/stylesheets/pages/milestone.scss +++ b/app/assets/stylesheets/pages/milestone.scss @@ -235,6 +235,7 @@ $status-box-line-height: 26px; padding: 0; } + .popover-body, .popover-content { padding: 0; } diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 9c72dcbc54c..0c334e919de 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -225,8 +225,6 @@ $note-form-margin-left: 72px; overflow-y: hidden; .note-text { - // Reset ul style types since we're nested inside a ul already - @include bulleted-list; word-wrap: break-word; } } @@ -738,7 +736,7 @@ $note-form-margin-left: 72px; .add-diff-note { @include btn-comment-icon; opacity: 0; - margin-left: -55px; + margin-left: -50px; position: absolute; top: 50%; transform: translateY(-50%); diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss index 8e933b62dd9..87cef43b923 100644 --- a/app/assets/stylesheets/pages/profile.scss +++ b/app/assets/stylesheets/pages/profile.scss @@ -305,6 +305,7 @@ table.u2f-registrations { margin: 20px -5px 0; .bordered-box { + padding: 32px; border: 1px solid $blue-300; border-radius: $border-radius-default; background-color: $blue-50; @@ -438,8 +439,17 @@ table.u2f-registrations { } } + .form-group > label { + font-weight: $gl-font-weight-bold; + } + + .form-group > .form-text { + font-size: $gl-font-size; + } + .emoji-menu-toggle-button { @include emoji-menu-toggle-button; + padding: 6px 10px; .no-emoji-placeholder { position: relative; @@ -461,3 +471,41 @@ table.u2f-registrations { .help-block { color: $gl-text-color-secondary; } + +.gitlab-slack-gif { + width: 100%; + max-width: $add-to-slack-gif-max-width; +} + +.gitlab-slack-well { + background-color: $white-light; + box-shadow: none; + max-width: $add-to-slack-well-max-width; +} + +.gitlab-slack-logo { + width: $add-to-slack-logo-size; + height: $add-to-slack-logo-size; +} + +.gitlab-slack-popup { + width: 100%; + max-width: $add-to-slack-popup-max-width; +} + +.gitlab-slack-right-arrow svg { + fill: $white-dark; + width: $right-arrow-size; + height: $right-arrow-size; + vertical-align: text-bottom; +} + +.gitlab-slack-double-headed-arrow { + vertical-align: text-top; + + svg { + fill: $gray-darker; + width: $double-headed-arrow-width; + height: $double-headed-arrow-height; + } +} diff --git a/app/assets/stylesheets/pages/wiki.scss b/app/assets/stylesheets/pages/wiki.scss index 82e887aa62a..3260aed143e 100644 --- a/app/assets/stylesheets/pages/wiki.scss +++ b/app/assets/stylesheets/pages/wiki.scss @@ -179,9 +179,3 @@ ul.wiki-pages-list.content-list { } } } - -.wiki:not(.use-csslab) { - table { - @include markdown-table; - } -} diff --git a/app/assets/stylesheets/print.scss b/app/assets/stylesheets/print.scss index bb10928a037..9ed1600419d 100644 --- a/app/assets/stylesheets/print.scss +++ b/app/assets/stylesheets/print.scss @@ -1,21 +1,21 @@ -.wiki h1, -.wiki h2, -.wiki h3, -.wiki h4, -.wiki h5, -.wiki h6 { +.md h1, +.md h2, +.md h3, +.md h4, +.md h5, +.md h6 { margin-top: 17px; } -.wiki h1 { +.md h1 { font-size: 30px; } -.wiki h2 { +.md h2 { font-size: 22px; } -.wiki h3 { +.md h3 { font-size: 18px; font-weight: 600; } diff --git a/app/controllers/clusters/clusters_controller.rb b/app/controllers/clusters/clusters_controller.rb index 68a2a83f0de..e82756e4643 100644 --- a/app/controllers/clusters/clusters_controller.rb +++ b/app/controllers/clusters/clusters_controller.rb @@ -123,25 +123,25 @@ class Clusters::ClustersController < Clusters::BaseController private def update_params - if cluster.managed? + if cluster.provided_by_user? params.require(:cluster).permit( :enabled, + :name, :environment_scope, :base_domain, platform_kubernetes_attributes: [ + :api_url, + :token, + :ca_cert, :namespace ] ) else params.require(:cluster).permit( :enabled, - :name, :environment_scope, :base_domain, platform_kubernetes_attributes: [ - :api_url, - :token, - :ca_cert, :namespace ] ) diff --git a/app/controllers/concerns/authenticates_with_two_factor.rb b/app/controllers/concerns/authenticates_with_two_factor.rb index 5507328f8ae..d5c4712bd78 100644 --- a/app/controllers/concerns/authenticates_with_two_factor.rb +++ b/app/controllers/concerns/authenticates_with_two_factor.rb @@ -36,7 +36,7 @@ module AuthenticatesWithTwoFactor end def locked_user_redirect(user) - flash.now[:alert] = 'Invalid Login or password' + flash.now[:alert] = _('Invalid Login or password') render 'devise/sessions/new' end @@ -66,7 +66,7 @@ module AuthenticatesWithTwoFactor else user.increment_failed_attempts! Gitlab::AppLogger.info("Failed Login: user=#{user.username} ip=#{request.remote_ip} method=OTP") - flash.now[:alert] = 'Invalid two-factor code.' + flash.now[:alert] = _('Invalid two-factor code.') prompt_for_two_factor(user) end end @@ -83,7 +83,7 @@ module AuthenticatesWithTwoFactor else user.increment_failed_attempts! Gitlab::AppLogger.info("Failed Login: user=#{user.username} ip=#{request.remote_ip} method=U2F") - flash.now[:alert] = 'Authentication via U2F device failed.' + flash.now[:alert] = _('Authentication via U2F device failed.') prompt_for_two_factor(user) end end diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb index b3777fd2b0f..e8e681ce649 100644 --- a/app/controllers/concerns/creates_commit.rb +++ b/app/controllers/concerns/creates_commit.rb @@ -31,7 +31,7 @@ module CreatesCommit respond_to do |format| format.html { redirect_to success_path } - format.json { render json: { message: "success", filePath: success_path } } + format.json { render json: { message: _("success"), filePath: success_path } } end else flash[:alert] = result[:message] @@ -45,7 +45,7 @@ module CreatesCommit redirect_to failure_path end end - format.json { render json: { message: "failed", filePath: failure_path } } + format.json { render json: { message: _("failed"), filePath: failure_path } } end end end @@ -60,15 +60,22 @@ module CreatesCommit private def update_flash_notice(success_notice) - flash[:notice] = success_notice || "Your changes have been successfully committed." + flash[:notice] = success_notice || _("Your changes have been successfully committed.") if create_merge_request? - if merge_request_exists? - flash[:notice] = nil - else - target = different_project? ? "project" : "branch" - flash[:notice] = flash[:notice] + " You can now submit a merge request to get this change into the original #{target}." - end + flash[:notice] = + if merge_request_exists? + nil + else + mr_message = + if different_project? + _("You can now submit a merge request to get this change into the original project.") + else + _("You can now submit a merge request to get this change into the original branch.") + end + + flash[:notice] += " " + mr_message + end end end diff --git a/app/controllers/concerns/lfs_request.rb b/app/controllers/concerns/lfs_request.rb index 57e444319e0..f7137a04437 100644 --- a/app/controllers/concerns/lfs_request.rb +++ b/app/controllers/concerns/lfs_request.rb @@ -26,7 +26,7 @@ module LfsRequest render( json: { - message: 'Git LFS is not enabled on this GitLab server, contact your admin.', + message: _('Git LFS is not enabled on this GitLab server, contact your admin.'), documentation_url: help_url }, status: :not_implemented @@ -51,7 +51,7 @@ module LfsRequest def render_lfs_forbidden render( json: { - message: 'Access forbidden. Check your access level.', + message: _('Access forbidden. Check your access level.'), documentation_url: help_url }, content_type: CONTENT_TYPE, @@ -62,7 +62,7 @@ module LfsRequest def render_lfs_not_found render( json: { - message: 'Not found.', + message: _('Not found.'), documentation_url: help_url }, content_type: CONTENT_TYPE, diff --git a/app/controllers/concerns/membership_actions.rb b/app/controllers/concerns/membership_actions.rb index 6402e01ddc0..0b2756c0c6a 100644 --- a/app/controllers/concerns/membership_actions.rb +++ b/app/controllers/concerns/membership_actions.rb @@ -9,7 +9,7 @@ module MembershipActions result = Members::CreateService.new(current_user, create_params).execute(membershipable) if result[:status] == :success - redirect_to members_page_url, notice: 'Users were successfully added.' + redirect_to members_page_url, notice: _('Users were successfully added.') else redirect_to members_page_url, alert: result[:message] end @@ -35,9 +35,16 @@ module MembershipActions respond_to do |format| format.html do - source = source_type == 'group' ? 'group and any subresources' : source_type + message = + begin + case membershipable + when Namespace + _("User was successfully removed from group and any subresources.") + else + _("User was successfully removed from project.") + end + end - message = "User was successfully removed from #{source}." redirect_to members_page_url, notice: message end @@ -49,7 +56,7 @@ module MembershipActions membershipable.request_access(current_user) redirect_to polymorphic_path(membershipable), - notice: 'Your request for access has been queued for review.' + notice: _('Your request for access has been queued for review.') end def approve_access_request @@ -68,9 +75,9 @@ module MembershipActions notice = if member.request? - "Your access request to the #{source_type} has been withdrawn." + _("Your access request to the %{source_type} has been withdrawn.") % { source_type: source_type } else - "You left the \"#{membershipable.human_name}\" #{source_type}." + _("You left the \"%{membershipable_human_name}\" %{source_type}.") % { membershipable_human_name: membershipable.human_name, source_type: source_type } end respond_to do |format| @@ -90,9 +97,9 @@ module MembershipActions if member.invite? member.resend_invite - redirect_to members_page_url, notice: 'The invitation was successfully resent.' + redirect_to members_page_url, notice: _('The invitation was successfully resent.') else - redirect_to members_page_url, alert: 'The invitation has already been accepted.' + redirect_to members_page_url, alert: _('The invitation has already been accepted.') end end @@ -125,6 +132,16 @@ module MembershipActions end def source_type - @source_type ||= membershipable.class.to_s.humanize(capitalize: false) + @source_type ||= + begin + case membershipable + when Namespace + _("group") + when Project + _("project") + else + raise "Unknown membershipable type: #{membershipable}!" + end + end end end diff --git a/app/controllers/concerns/spammable_actions.rb b/app/controllers/concerns/spammable_actions.rb index c3a1b12af84..a8ffa33f1c7 100644 --- a/app/controllers/concerns/spammable_actions.rb +++ b/app/controllers/concerns/spammable_actions.rb @@ -12,9 +12,9 @@ module SpammableActions def mark_as_spam if SpamService.new(spammable).mark_as_spam! - redirect_to spammable_path, notice: "#{spammable.spammable_entity_type.titlecase} was submitted to Akismet successfully." + redirect_to spammable_path, notice: _("%{spammable_titlecase} was submitted to Akismet successfully.") % { spammable_titlecase: spammable.spammable_entity_type.titlecase } else - redirect_to spammable_path, alert: 'Error with Akismet. Please check the logs for more info.' + redirect_to spammable_path, alert: _('Error with Akismet. Please check the logs for more info.') end end @@ -33,7 +33,7 @@ module SpammableActions ensure_spam_config_loaded! if params[:recaptcha_verification] - flash[:alert] = 'There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.' + flash[:alert] = _('There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.') end respond_to do |format| diff --git a/app/controllers/concerns/uploads_actions.rb b/app/controllers/concerns/uploads_actions.rb index 4ec0e94df9a..59f6d3452a3 100644 --- a/app/controllers/concerns/uploads_actions.rb +++ b/app/controllers/concerns/uploads_actions.rb @@ -16,7 +16,7 @@ module UploadsActions end else format.json do - render json: 'Invalid file.', status: :unprocessable_entity + render json: _('Invalid file.'), status: :unprocessable_entity end end end @@ -57,7 +57,7 @@ module UploadsActions render json: authorized rescue SocketError - render json: "Error uploading file", status: :internal_server_error + render json: _("Error uploading file"), status: :internal_server_error end private diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb index 3fa582cf25b..f173c263474 100644 --- a/app/controllers/dashboard/todos_controller.rb +++ b/app/controllers/dashboard/todos_controller.rb @@ -21,7 +21,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController format.html do redirect_to dashboard_todos_path, status: 302, - notice: 'Todo was successfully marked as done.' + notice: _('Todo was successfully marked as done.') end format.js { head :ok } format.json { render json: todos_counts } @@ -32,7 +32,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController updated_ids = TodoService.new.mark_todos_as_done(@todos, current_user) respond_to do |format| - format.html { redirect_to dashboard_todos_path, status: 302, notice: 'All todos were marked as done.' } + format.html { redirect_to dashboard_todos_path, status: 302, notice: _('All todos were marked as done.') } format.js { head :ok } format.json { render json: todos_counts.merge(updated_ids: updated_ids) } end diff --git a/app/controllers/graphql_controller.rb b/app/controllers/graphql_controller.rb index e147d32be2e..7b5dc22815c 100644 --- a/app/controllers/graphql_controller.rb +++ b/app/controllers/graphql_controller.rb @@ -12,6 +12,7 @@ class GraphqlController < ApplicationController protect_from_forgery with: :null_session, only: :execute before_action :check_graphql_feature_flag! + before_action :authorize_access_api! before_action(only: [:execute]) { authenticate_sessionless_user!(:api) } def execute @@ -37,6 +38,10 @@ class GraphqlController < ApplicationController private + def authorize_access_api! + access_denied!("API not accessible for user.") unless can?(current_user, :access_api) + end + # Overridden from the ApplicationController to make the response look like # a GraphQL response. That is nicely picked up in Graphiql. def render_404 diff --git a/app/controllers/groups/runners_controller.rb b/app/controllers/groups/runners_controller.rb index dd8fbf7a029..f8e32451b02 100644 --- a/app/controllers/groups/runners_controller.rb +++ b/app/controllers/groups/runners_controller.rb @@ -16,7 +16,7 @@ class Groups::RunnersController < Groups::ApplicationController def update if Ci::UpdateRunnerService.new(@runner).update(runner_params) - redirect_to group_runner_path(@group, @runner), notice: 'Runner was successfully updated.' + redirect_to group_runner_path(@group, @runner), notice: _('Runner was successfully updated.') else render 'edit' end @@ -30,17 +30,17 @@ class Groups::RunnersController < Groups::ApplicationController def resume if Ci::UpdateRunnerService.new(@runner).update(active: true) - redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), notice: 'Runner was successfully updated.' + redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), notice: _('Runner was successfully updated.') else - redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), alert: 'Runner was not updated.' + redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), alert: _('Runner was not updated.') end end def pause if Ci::UpdateRunnerService.new(@runner).update(active: false) - redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), notice: 'Runner was successfully updated.' + redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), notice: _('Runner was successfully updated.') else - redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), alert: 'Runner was not updated.' + redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), alert: _('Runner was not updated.') end end diff --git a/app/controllers/groups/settings/ci_cd_controller.rb b/app/controllers/groups/settings/ci_cd_controller.rb index f378f7ac79a..c465e622de0 100644 --- a/app/controllers/groups/settings/ci_cd_controller.rb +++ b/app/controllers/groups/settings/ci_cd_controller.rb @@ -13,7 +13,7 @@ module Groups def reset_registration_token @group.reset_runners_token! - flash[:notice] = 'New runners registration token has been generated!' + flash[:notice] = _('GroupSettings|New runners registration token has been generated!') redirect_to group_settings_ci_cd_path end diff --git a/app/controllers/import/bitbucket_controller.rb b/app/controllers/import/bitbucket_controller.rb index 2b1395f364f..293d76ea765 100644 --- a/app/controllers/import/bitbucket_controller.rb +++ b/app/controllers/import/bitbucket_controller.rb @@ -62,7 +62,7 @@ class Import::BitbucketController < Import::BaseController render json: { errors: project_save_error(project) }, status: :unprocessable_entity end else - render json: { errors: 'This namespace has already been taken! Please choose another one.' }, status: :unprocessable_entity + render json: { errors: _('This namespace has already been taken! Please choose another one.') }, status: :unprocessable_entity end end diff --git a/app/controllers/import/bitbucket_server_controller.rb b/app/controllers/import/bitbucket_server_controller.rb index f333e43b892..643a3bfed1f 100644 --- a/app/controllers/import/bitbucket_server_controller.rb +++ b/app/controllers/import/bitbucket_server_controller.rb @@ -25,7 +25,7 @@ class Import::BitbucketServerController < Import::BaseController repo = bitbucket_client.repo(@project_key, @repo_slug) unless repo - return render json: { errors: "Project #{@project_key}/#{@repo_slug} could not be found" }, status: :unprocessable_entity + return render json: { errors: _("Project %{project_repo} could not be found") % { project_repo: "#{@project_key}/#{@repo_slug}" } }, status: :unprocessable_entity end project_name = params[:new_name].presence || repo.name @@ -41,10 +41,10 @@ class Import::BitbucketServerController < Import::BaseController render json: { errors: project_save_error(project) }, status: :unprocessable_entity end else - render json: { errors: 'This namespace has already been taken! Please choose another one.' }, status: :unprocessable_entity + render json: { errors: _('This namespace has already been taken! Please choose another one.') }, status: :unprocessable_entity end - rescue BitbucketServer::Connection::ConnectionError => e - render json: { errors: "Unable to connect to server: #{e}" }, status: :unprocessable_entity + rescue BitbucketServer::Connection::ConnectionError => error + render json: { errors: _("Unable to connect to server: %{error}") % { error: error } }, status: :unprocessable_entity end def configure @@ -65,8 +65,8 @@ class Import::BitbucketServerController < Import::BaseController already_added_projects_names = @already_added_projects.pluck(:import_source) @repos.reject! { |repo| already_added_projects_names.include?(repo.browse_url) } - rescue BitbucketServer::Connection::ConnectionError => e - flash[:alert] = "Unable to connect to server: #{e}" + rescue BitbucketServer::Connection::ConnectionError => error + flash[:alert] = _("Unable to connect to server: %{error}") % { error: error } clear_session_data redirect_to new_import_bitbucket_server_path end diff --git a/app/controllers/import/fogbugz_controller.rb b/app/controllers/import/fogbugz_controller.rb index 5a439e6de78..a37ba682b91 100644 --- a/app/controllers/import/fogbugz_controller.rb +++ b/app/controllers/import/fogbugz_controller.rb @@ -14,7 +14,7 @@ class Import::FogbugzController < Import::BaseController res = Gitlab::FogbugzImport::Client.new(import_params.symbolize_keys) rescue # If the URI is invalid various errors can occur - return redirect_to new_import_fogbugz_path, alert: 'Could not connect to FogBugz, check your URL' + return redirect_to new_import_fogbugz_path, alert: _('Could not connect to FogBugz, check your URL') end session[:fogbugz_token] = res.get_token session[:fogbugz_uri] = params[:uri] @@ -29,14 +29,14 @@ class Import::FogbugzController < Import::BaseController user_map = params[:users] unless user_map.is_a?(Hash) && user_map.all? { |k, v| !v[:name].blank? } - flash.now[:alert] = 'All users must have a name.' + flash.now[:alert] = _('All users must have a name.') return render 'new_user_map' end session[:fogbugz_user_map] = user_map - flash[:notice] = 'The user map has been saved. Continue by selecting the projects you want to import.' + flash[:notice] = _('The user map has been saved. Continue by selecting the projects you want to import.') redirect_to status_import_fogbugz_path end diff --git a/app/controllers/import/gitea_controller.rb b/app/controllers/import/gitea_controller.rb index 68ad8650dba..a23b2f8139e 100644 --- a/app/controllers/import/gitea_controller.rb +++ b/app/controllers/import/gitea_controller.rb @@ -46,7 +46,7 @@ class Import::GiteaController < Import::GithubController def provider_auth if session[access_token_key].blank? || provider_url.blank? redirect_to new_import_gitea_url, - alert: 'You need to specify both an Access Token and a Host URL.' + alert: _('You need to specify both an Access Token and a Host URL.') end end diff --git a/app/controllers/import/gitlab_controller.rb b/app/controllers/import/gitlab_controller.rb index 498de0b07b8..5ec8e9e6fc5 100644 --- a/app/controllers/import/gitlab_controller.rb +++ b/app/controllers/import/gitlab_controller.rb @@ -42,7 +42,7 @@ class Import::GitlabController < Import::BaseController render json: { errors: project_save_error(project) }, status: :unprocessable_entity end else - render json: { errors: 'This namespace has already been taken! Please choose another one.' }, status: :unprocessable_entity + render json: { errors: _('This namespace has already been taken! Please choose another one.') }, status: :unprocessable_entity end end diff --git a/app/controllers/import/gitlab_projects_controller.rb b/app/controllers/import/gitlab_projects_controller.rb index 354fba5d204..89889141be6 100644 --- a/app/controllers/import/gitlab_projects_controller.rb +++ b/app/controllers/import/gitlab_projects_controller.rb @@ -13,7 +13,7 @@ class Import::GitlabProjectsController < Import::BaseController def create unless file_is_valid? - return redirect_back_or_default(options: { alert: "You need to upload a GitLab project export archive (ending in .gz)." }) + return redirect_back_or_default(options: { alert: _("You need to upload a GitLab project export archive (ending in .gz).") }) end @project = ::Projects::GitlabProjectsImportService.new(current_user, project_params).execute @@ -21,7 +21,7 @@ class Import::GitlabProjectsController < Import::BaseController if @project.saved? redirect_to( project_path(@project), - notice: "Project '#{@project.name}' is being imported." + notice: _("Project '%{project_name}' is being imported.") % { project_name: @project.name } ) else redirect_back_or_default(options: { alert: "Project could not be imported: #{@project.errors.full_messages.join(', ')}" }) diff --git a/app/controllers/import/google_code_controller.rb b/app/controllers/import/google_code_controller.rb index 331f06c3dd6..4dddfbcd20d 100644 --- a/app/controllers/import/google_code_controller.rb +++ b/app/controllers/import/google_code_controller.rb @@ -11,18 +11,18 @@ class Import::GoogleCodeController < Import::BaseController dump_file = params[:dump_file] unless dump_file.respond_to?(:read) - return redirect_back_or_default(options: { alert: "You need to upload a Google Takeout archive." }) + return redirect_back_or_default(options: { alert: _("You need to upload a Google Takeout archive.") }) end begin dump = JSON.parse(dump_file.read) rescue - return redirect_back_or_default(options: { alert: "The uploaded file is not a valid Google Takeout archive." }) + return redirect_back_or_default(options: { alert: _("The uploaded file is not a valid Google Takeout archive.") }) end client = Gitlab::GoogleCodeImport::Client.new(dump) unless client.valid? - return redirect_back_or_default(options: { alert: "The uploaded file is not a valid Google Takeout archive." }) + return redirect_back_or_default(options: { alert: _("The uploaded file is not a valid Google Takeout archive.") }) end session[:google_code_dump] = dump @@ -44,13 +44,13 @@ class Import::GoogleCodeController < Import::BaseController begin user_map = JSON.parse(user_map_json) rescue - flash.now[:alert] = "The entered user map is not a valid JSON user map." + flash.now[:alert] = _("The entered user map is not a valid JSON user map.") return render "new_user_map" end unless user_map.is_a?(Hash) && user_map.all? { |k, v| k.is_a?(String) && v.is_a?(String) } - flash.now[:alert] = "The entered user map is not a valid JSON user map." + flash.now[:alert] = _("The entered user map is not a valid JSON user map.") return render "new_user_map" end @@ -62,7 +62,7 @@ class Import::GoogleCodeController < Import::BaseController session[:google_code_user_map] = user_map - flash[:notice] = "The user map has been saved. Continue by selecting the projects you want to import." + flash[:notice] = _("The user map has been saved. Continue by selecting the projects you want to import.") redirect_to status_import_google_code_path end diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index 0a33856a8d3..7e072788fc9 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -29,7 +29,7 @@ class Projects::BlobController < Projects::ApplicationController end def create - create_commit(Files::CreateService, success_notice: "The file has been successfully created.", + create_commit(Files::CreateService, success_notice: _("The file has been successfully created."), success_path: -> { project_blob_path(@project, File.join(@branch_name, @file_path)) }, failure_view: :new, failure_path: project_new_blob_path(@project, @ref)) @@ -81,7 +81,7 @@ class Projects::BlobController < Projects::ApplicationController end def destroy - create_commit(Files::DeleteService, success_notice: "The file has been successfully deleted.", + create_commit(Files::DeleteService, success_notice: _("The file has been successfully deleted."), success_path: -> { after_delete_path }, failure_view: :show, failure_path: project_blob_path(@project, @id)) @@ -172,8 +172,7 @@ class Projects::BlobController < Projects::ApplicationController end if params[:file].present? - params[:content] = Base64.encode64(params[:file].read) - params[:encoding] = 'base64' + params[:content] = params[:file] end @commit_params = { diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb index 32b7f3207ef..6ff2e222489 100644 --- a/app/controllers/projects/branches_controller.rb +++ b/app/controllers/projects/branches_controller.rb @@ -115,7 +115,7 @@ class Projects::BranchesController < Projects::ApplicationController DeleteMergedBranchesService.new(@project, current_user).async_execute redirect_to project_branches_path(@project), - notice: 'Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes.' + notice: _('Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes.') end private @@ -143,7 +143,7 @@ class Projects::BranchesController < Projects::ApplicationController def redirect_for_legacy_index_sort_or_search # Normalize a legacy URL with redirect if request.format != :json && !params[:state].presence && [:sort, :search, :page].any? { |key| params[key].presence } - redirect_to project_branches_filtered_path(@project, state: 'all'), notice: 'Update your bookmarked URLs as filtered/sorted branches URL has been changed.' + redirect_to project_branches_filtered_path(@project, state: 'all'), notice: _('Update your bookmarked URLs as filtered/sorted branches URL has been changed.') end end diff --git a/app/controllers/projects/deploy_keys_controller.rb b/app/controllers/projects/deploy_keys_controller.rb index 6824a07dc76..514b03e23b5 100644 --- a/app/controllers/projects/deploy_keys_controller.rb +++ b/app/controllers/projects/deploy_keys_controller.rb @@ -38,7 +38,7 @@ class Projects::DeployKeysController < Projects::ApplicationController def update if deploy_key.update(update_params) - flash[:notice] = 'Deploy key was successfully updated.' + flash[:notice] = _('Deploy key was successfully updated.') redirect_to_repository_settings(@project, anchor: 'js-deploy-keys-settings') else render 'edit' diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb index d439db97252..55d5fce9214 100644 --- a/app/controllers/projects/git_http_client_controller.rb +++ b/app/controllers/projects/git_http_client_controller.rb @@ -78,7 +78,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController end def parse_repo_path - @project, @wiki, @redirected_path = Gitlab::RepoPath.parse("#{params[:namespace_id]}/#{params[:project_id]}") + @project, @repo_type, @redirected_path = Gitlab::RepoPath.parse("#{params[:namespace_id]}/#{params[:project_id]}") end def render_missing_personal_access_token @@ -89,13 +89,19 @@ class Projects::GitHttpClientController < Projects::ApplicationController end def repository - wiki? ? project.wiki.repository : project.repository + repo_type.repository_for(project) end def wiki? - parse_repo_path unless defined?(@wiki) + repo_type.wiki? + end - @wiki + def repo_type + parse_repo_path unless defined?(@repo_type) + # When there a project did not exist, the parsed repo_type would be empty. + # In that case, we want to continue with a regular project repository. As we + # could create the project if the user pushing is allowed to do so. + @repo_type || Gitlab::GlRepository::PROJECT end def handle_basic_authentication(login, password) diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb index f28af42d1b7..e519cc1f158 100644 --- a/app/controllers/projects/git_http_controller.rb +++ b/app/controllers/projects/git_http_controller.rb @@ -55,7 +55,7 @@ class Projects::GitHttpController < Projects::GitHttpClientController def render_ok set_workhorse_internal_api_content_type - render json: Gitlab::Workhorse.git_http_ok(repository, wiki?, user, action_name) + render json: Gitlab::Workhorse.git_http_ok(repository, repo_type, user, action_name) end def render_403(exception) @@ -99,7 +99,7 @@ class Projects::GitHttpController < Projects::GitHttpClientController end def access_klass - @access_klass ||= wiki? ? Gitlab::GitAccessWiki : Gitlab::GitAccess + @access_klass ||= repo_type.access_checker_class end def project_path diff --git a/app/controllers/projects/group_links_controller.rb b/app/controllers/projects/group_links_controller.rb index bc942ba9288..dc65f9959db 100644 --- a/app/controllers/projects/group_links_controller.rb +++ b/app/controllers/projects/group_links_controller.rb @@ -18,7 +18,7 @@ class Projects::GroupLinksController < Projects::ApplicationController flash[:alert] = result[:message] if result[:http_status] == 409 else - flash[:alert] = 'Please select a group.' + flash[:alert] = _('Please select a group.') end redirect_to project_project_members_path(project) diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb index bc84418b79f..5fa0339f44d 100644 --- a/app/controllers/projects/hooks_controller.rb +++ b/app/controllers/projects/hooks_controller.rb @@ -32,7 +32,7 @@ class Projects::HooksController < Projects::ApplicationController def update if hook.update(hook_params) - flash[:notice] = 'Hook was successfully updated.' + flash[:notice] = _('Hook was successfully updated.') redirect_to project_settings_integrations_path(@project) else render 'edit' diff --git a/app/controllers/projects/imports_controller.rb b/app/controllers/projects/imports_controller.rb index 8b33fa85c1e..8ee0bd26daf 100644 --- a/app/controllers/projects/imports_controller.rb +++ b/app/controllers/projects/imports_controller.rb @@ -42,9 +42,9 @@ class Projects::ImportsController < Projects::ApplicationController def finished_notice if @project.forked? - 'The project was successfully forked.' + _('The project was successfully forked.') else - 'The project was successfully imported.' + _('The project was successfully imported.') end end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index b9d02a62fc3..e9ed5554ab4 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -95,9 +95,9 @@ class Projects::IssuesController < Projects::ApplicationController if service.discussions_to_resolve.count(&:resolved?) > 0 flash[:notice] = if service.discussion_to_resolve_id - "Resolved 1 discussion." + _("Resolved 1 discussion.") else - "Resolved all discussions." + _("Resolved all discussions.") end end diff --git a/app/controllers/projects/jobs_controller.rb b/app/controllers/projects/jobs_controller.rb index d5ce790e2d9..35cc32d3e63 100644 --- a/app/controllers/projects/jobs_controller.rb +++ b/app/controllers/projects/jobs_controller.rb @@ -122,7 +122,7 @@ class Projects::JobsController < Projects::ApplicationController def erase if @build.erase(erased_by: current_user) redirect_to project_job_path(project, @build), - notice: "Job has been successfully erased!" + notice: _("Job has been successfully erased!") else respond_422 end diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb index 640038818f2..386a1f00bd2 100644 --- a/app/controllers/projects/labels_controller.rb +++ b/app/controllers/projects/labels_controller.rb @@ -132,7 +132,7 @@ class Projects::LabelsController < Projects::ApplicationController respond_to do |format| format.html do redirect_to(project_labels_path(@project), - notice: 'Failed to promote label due to internal error. Please contact administrators.') + notice: _('Failed to promote label due to internal error. Please contact administrators.')) end format.js end diff --git a/app/controllers/projects/lfs_api_controller.rb b/app/controllers/projects/lfs_api_controller.rb index be40077d389..42c415757f9 100644 --- a/app/controllers/projects/lfs_api_controller.rb +++ b/app/controllers/projects/lfs_api_controller.rb @@ -26,7 +26,7 @@ class Projects::LfsApiController < Projects::GitHttpClientController def deprecated render( json: { - message: 'Server supports batch API only, please update your Git LFS client to version 1.0.1 and up.', + message: _('Server supports batch API only, please update your Git LFS client to version 1.0.1 and up.'), documentation_url: "#{Gitlab.config.gitlab.url}/help" }, status: :not_implemented @@ -62,7 +62,7 @@ class Projects::LfsApiController < Projects::GitHttpClientController else object[:error] = { code: 404, - message: "Object does not exist on the server or you don't have permissions to access it" + message: _("Object does not exist on the server or you don't have permissions to access it") } end end diff --git a/app/controllers/projects/merge_requests/conflicts_controller.rb b/app/controllers/projects/merge_requests/conflicts_controller.rb index 045a4e974fe..011ac9a42f8 100644 --- a/app/controllers/projects/merge_requests/conflicts_controller.rb +++ b/app/controllers/projects/merge_requests/conflicts_controller.rb @@ -16,12 +16,12 @@ class Projects::MergeRequests::ConflictsController < Projects::MergeRequests::Ap render json: @conflicts_list elsif @merge_request.can_be_merged? render json: { - message: 'The merge conflicts for this merge request have already been resolved. Please return to the merge request.', + message: _('The merge conflicts for this merge request have already been resolved. Please return to the merge request.'), type: 'error' } else render json: { - message: 'The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally.', + message: _('The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally.'), type: 'error' } end @@ -43,7 +43,7 @@ class Projects::MergeRequests::ConflictsController < Projects::MergeRequests::Ap return render_404 unless @conflicts_list.can_be_resolved_in_ui? if @merge_request.can_be_merged? - render status: :bad_request, json: { message: 'The merge conflicts for this merge request have already been resolved.' } + render status: :bad_request, json: { message: _('The merge conflicts for this merge request have already been resolved.') } return end @@ -52,7 +52,7 @@ class Projects::MergeRequests::ConflictsController < Projects::MergeRequests::Ap .new(merge_request) .execute(current_user, params) - flash[:notice] = 'All merge conflicts were resolved. The merge request can now be merged.' + flash[:notice] = _('All merge conflicts were resolved. The merge request can now be merged.') render json: { redirect_to: project_merge_request_url(@project, @merge_request, resolved_conflicts: true) } rescue Gitlab::Git::Conflict::Resolver::ResolutionError => e diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 2b78abc66df..34cb0416965 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -16,10 +16,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo before_action :authenticate_user!, only: [:assign_related_issues] before_action :check_user_can_push_to_source_branch!, only: [:rebase] - before_action only: [:show] do - push_frontend_feature_flag(:expand_diff_full_file) - end - def index @merge_requests = @issuables @@ -319,7 +315,9 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo end def serializer - MergeRequestSerializer.new(current_user: current_user, project: merge_request.project) + ::Gitlab::GitalyClient.allow_ref_name_caching do + MergeRequestSerializer.new(current_user: current_user, project: merge_request.project) + end end def define_edit_vars diff --git a/app/controllers/projects/mirrors_controller.rb b/app/controllers/projects/mirrors_controller.rb index ab7ab13657a..ef330ae00f4 100644 --- a/app/controllers/projects/mirrors_controller.rb +++ b/app/controllers/projects/mirrors_controller.rb @@ -18,7 +18,7 @@ class Projects::MirrorsController < Projects::ApplicationController result = ::Projects::UpdateService.new(project, current_user, mirror_params).execute if result[:status] == :success - flash[:notice] = 'Mirroring settings were successfully updated.' + flash[:notice] = _('Mirroring settings were successfully updated.') else flash[:alert] = project.errors.full_messages.join(', ').html_safe end @@ -38,7 +38,7 @@ class Projects::MirrorsController < Projects::ApplicationController def update_now if params[:sync_remote] project.update_remote_mirrors - flash[:notice] = "The remote repository is being updated..." + flash[:notice] = _("The remote repository is being updated...") end redirect_to_repository_settings(project, anchor: 'js-push-remote-settings') diff --git a/app/controllers/projects/pipeline_schedules_controller.rb b/app/controllers/projects/pipeline_schedules_controller.rb index acf56f0eb6a..fd5b89298a7 100644 --- a/app/controllers/projects/pipeline_schedules_controller.rb +++ b/app/controllers/projects/pipeline_schedules_controller.rb @@ -50,9 +50,11 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController job_id = RunPipelineScheduleWorker.perform_async(schedule.id, current_user.id) if job_id - flash[:notice] = "Successfully scheduled a pipeline to run. Go to the <a href=\"#{project_pipelines_path(@project)}\">Pipelines page</a> for details.".html_safe + link_to_pipelines = view_context.link_to(_('Pipelines page'), project_pipelines_path(@project)) + message = _("Successfully scheduled a pipeline to run. Go to the %{link_to_pipelines} for details.").html_safe % { link_to_pipelines: link_to_pipelines } + flash[:notice] = message.html_safe else - flash[:alert] = 'Unable to schedule a pipeline to run immediately' + flash[:alert] = _('Unable to schedule a pipeline to run immediately') end redirect_to pipeline_schedules_path(@project) @@ -85,7 +87,7 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController return unless limiter.throttled?([current_user, schedule], 1) - flash[:alert] = 'You cannot play this scheduled pipeline at the moment. Please wait a minute.' + flash[:alert] = _('You cannot play this scheduled pipeline at the moment. Please wait a minute.') redirect_to pipeline_schedules_path(@project) end diff --git a/app/controllers/projects/runners_controller.rb b/app/controllers/projects/runners_controller.rb index 91f40b90aa8..ca62f54813b 100644 --- a/app/controllers/projects/runners_controller.rb +++ b/app/controllers/projects/runners_controller.rb @@ -15,7 +15,7 @@ class Projects::RunnersController < Projects::ApplicationController def update if Ci::UpdateRunnerService.new(@runner).update(runner_params) - redirect_to project_runner_path(@project, @runner), notice: 'Runner was successfully updated.' + redirect_to project_runner_path(@project, @runner), notice: _('Runner was successfully updated.') else render 'edit' end @@ -31,17 +31,17 @@ class Projects::RunnersController < Projects::ApplicationController def resume if Ci::UpdateRunnerService.new(@runner).update(active: true) - redirect_to project_runners_path(@project), notice: 'Runner was successfully updated.' + redirect_to project_runners_path(@project), notice: _('Runner was successfully updated.') else - redirect_to project_runners_path(@project), alert: 'Runner was not updated.' + redirect_to project_runners_path(@project), alert: _('Runner was not updated.') end end def pause if Ci::UpdateRunnerService.new(@runner).update(active: false) - redirect_to project_runners_path(@project), notice: 'Runner was successfully updated.' + redirect_to project_runners_path(@project), notice: _('Runner was successfully updated.') else - redirect_to project_runners_path(@project), alert: 'Runner was not updated.' + redirect_to project_runners_path(@project), alert: _('Runner was not updated.') end end diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb index f1c9d0d0f77..e0df51590ae 100644 --- a/app/controllers/projects/services_controller.rb +++ b/app/controllers/projects/services_controller.rb @@ -43,20 +43,20 @@ class Projects::ServicesController < Projects::ApplicationController if outcome[:success] {} else - { error: true, message: 'Test failed.', service_response: outcome[:result].to_s, test_failed: true } + { error: true, message: _('Test failed.'), service_response: outcome[:result].to_s, test_failed: true } end else - { error: true, message: 'Validations failed.', service_response: @service.errors.full_messages.join(','), test_failed: false } + { error: true, message: _('Validations failed.'), service_response: @service.errors.full_messages.join(','), test_failed: false } end rescue Gitlab::HTTP::BlockedUrlError => e - { error: true, message: 'Test failed.', service_response: e.message, test_failed: true } + { error: true, message: _('Test failed.'), service_response: e.message, test_failed: true } end def success_message if @service.active? - "#{@service.title} activated." + _("%{service_title} activated.") % { service_title: @service.title } else - "#{@service.title} settings saved, but not activated." + _("%{service_title} settings saved, but not activated.") % { service_title: @service.title } end end diff --git a/app/controllers/projects/settings/ci_cd_controller.rb b/app/controllers/projects/settings/ci_cd_controller.rb index f2f63e986bb..d1c5cef76fa 100644 --- a/app/controllers/projects/settings/ci_cd_controller.rb +++ b/app/controllers/projects/settings/ci_cd_controller.rb @@ -13,7 +13,7 @@ module Projects Projects::UpdateService.new(project, current_user, update_params).tap do |service| result = service.execute if result[:status] == :success - flash[:notice] = "Pipelines settings for '#{@project.name}' were successfully updated." + flash[:notice] = _("Pipelines settings for '%{project_name}' were successfully updated.") % { project_name: @project.name } run_autodevops_pipeline(service) @@ -39,7 +39,7 @@ module Projects def reset_registration_token @project.reset_runners_token! - flash[:notice] = 'New runners registration token has been generated!' + flash[:notice] = _('New runners registration token has been generated!') redirect_to namespace_project_settings_ci_cd_path end @@ -58,7 +58,7 @@ module Projects return unless service.run_auto_devops_pipeline? if @project.empty_repo? - flash[:warning] = "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch." + flash[:warning] = _("This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch.") return end diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb index edebfc55c17..90d53aa08ea 100644 --- a/app/controllers/projects/tree_controller.rb +++ b/app/controllers/projects/tree_controller.rb @@ -37,7 +37,7 @@ class Projects::TreeController < Projects::ApplicationController def create_dir return render_404 unless @commit_params.values.all? - create_commit(Files::CreateDirService, success_notice: "The directory has been successfully created.", + create_commit(Files::CreateDirService, success_notice: _("The directory has been successfully created."), success_path: project_tree_path(@project, File.join(@branch_name, @dir_name)), failure_path: project_tree_path(@project, @ref)) end diff --git a/app/controllers/projects/triggers_controller.rb b/app/controllers/projects/triggers_controller.rb index c7b4ebb2b24..284e119ca06 100644 --- a/app/controllers/projects/triggers_controller.rb +++ b/app/controllers/projects/triggers_controller.rb @@ -16,9 +16,9 @@ class Projects::TriggersController < Projects::ApplicationController @trigger = project.triggers.create(trigger_params.merge(owner: current_user)) if @trigger.valid? - flash[:notice] = 'Trigger was created successfully.' + flash[:notice] = _('Trigger was created successfully.') else - flash[:alert] = 'You could not create a new trigger.' + flash[:alert] = _('You could not create a new trigger.') end redirect_to project_settings_ci_cd_path(@project, anchor: 'js-pipeline-triggers') @@ -26,9 +26,9 @@ class Projects::TriggersController < Projects::ApplicationController def take_ownership if trigger.update(owner: current_user) - flash[:notice] = 'Trigger was re-assigned.' + flash[:notice] = _('Trigger was re-assigned.') else - flash[:alert] = 'You could not take ownership of trigger.' + flash[:alert] = _('You could not take ownership of trigger.') end redirect_to project_settings_ci_cd_path(@project, anchor: 'js-pipeline-triggers') @@ -39,7 +39,7 @@ class Projects::TriggersController < Projects::ApplicationController def update if trigger.update(trigger_params) - redirect_to project_settings_ci_cd_path(@project, anchor: 'js-pipeline-triggers'), notice: 'Trigger was successfully updated.' + redirect_to project_settings_ci_cd_path(@project, anchor: 'js-pipeline-triggers'), notice: _('Trigger was successfully updated.') else render action: "edit" end @@ -47,9 +47,9 @@ class Projects::TriggersController < Projects::ApplicationController def destroy if trigger.destroy - flash[:notice] = "Trigger removed." + flash[:notice] = _("Trigger removed.") else - flash[:alert] = "Could not remove the trigger." + flash[:alert] = _("Could not remove the trigger.") end redirect_to project_settings_ci_cd_path(@project, anchor: 'js-pipeline-triggers'), status: :found diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb index 88dd111132b..da2420633ef 100644 --- a/app/controllers/projects/wikis_controller.rb +++ b/app/controllers/projects/wikis_controller.rb @@ -49,7 +49,7 @@ class Projects::WikisController < Projects::ApplicationController if @page.valid? redirect_to( project_wiki_path(@project, @page), - notice: 'Wiki was successfully updated.' + notice: _('Wiki was successfully updated.') ) else render 'edit' @@ -65,7 +65,7 @@ class Projects::WikisController < Projects::ApplicationController if @page.persisted? redirect_to( project_wiki_path(@project, @page), - notice: 'Wiki was successfully updated.' + notice: _('Wiki was successfully updated.') ) else render action: "edit" @@ -85,7 +85,7 @@ class Projects::WikisController < Projects::ApplicationController else redirect_to( project_wiki_path(@project, :home), - notice: "Page not found" + notice: _("Page not found") ) end end @@ -95,7 +95,7 @@ class Projects::WikisController < Projects::ApplicationController redirect_to project_wiki_path(@project, :home), status: 302, - notice: "Page was successfully deleted" + notice: _("Page was successfully deleted") rescue Gitlab::Git::Wiki::OperationError => e @error = e render 'edit' @@ -118,7 +118,7 @@ class Projects::WikisController < Projects::ApplicationController @sidebar_wiki_entries = WikiPage.group_by_directory(@project_wiki.pages(limit: 15)) end rescue ProjectWiki::CouldNotCreateWikiError - flash[:notice] = "Could not create Wiki Repository at this time. Please try again later." + flash[:notice] = _("Could not create Wiki Repository at this time. Please try again later.") redirect_to project_path(@project) false end @@ -155,7 +155,7 @@ class Projects::WikisController < Projects::ApplicationController end def set_encoding_error - flash.now[:notice] = "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository." + flash.now[:notice] = _("The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository.") end def file_blob diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 90d4bc674d9..a80ab3bcd28 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -14,8 +14,6 @@ class SearchController < ApplicationController layout 'search' def show - search_service = SearchService.new(current_user, params) - @project = search_service.project @group = search_service.group diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb index 93d3c991846..0319e95d439 100644 --- a/app/finders/projects_finder.rb +++ b/app/finders/projects_finder.rb @@ -81,7 +81,7 @@ class ProjectsFinder < UnionFinder if private_only? current_user.authorized_projects else - Project.public_or_visible_to_user(current_user) + Project.public_or_visible_to_user(current_user, params[:visibility_level]) end end end diff --git a/app/graphql/types/ci/detailed_status_type.rb b/app/graphql/types/ci/detailed_status_type.rb new file mode 100644 index 00000000000..2987354b556 --- /dev/null +++ b/app/graphql/types/ci/detailed_status_type.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true +module Types + module Ci + class DetailedStatusType < BaseObject + graphql_name 'DetailedStatus' + + field :group, GraphQL::STRING_TYPE, null: false + field :icon, GraphQL::STRING_TYPE, null: false + field :favicon, GraphQL::STRING_TYPE, null: false + field :details_path, GraphQL::STRING_TYPE, null: false + field :has_details, GraphQL::BOOLEAN_TYPE, null: false, method: :has_details? + field :label, GraphQL::STRING_TYPE, null: false + field :text, GraphQL::STRING_TYPE, null: false + field :tooltip, GraphQL::STRING_TYPE, null: false, method: :status_tooltip + end + end +end diff --git a/app/graphql/types/ci/pipeline_type.rb b/app/graphql/types/ci/pipeline_type.rb index 2bbffad4563..18696293b97 100644 --- a/app/graphql/types/ci/pipeline_type.rb +++ b/app/graphql/types/ci/pipeline_type.rb @@ -13,6 +13,10 @@ module Types field :sha, GraphQL::STRING_TYPE, null: false field :before_sha, GraphQL::STRING_TYPE, null: true field :status, PipelineStatusEnum, null: false + field :detailed_status, + Types::Ci::DetailedStatusType, + null: false, + resolve: -> (obj, _args, ctx) { obj.detailed_status(ctx[:current_user]) } field :duration, GraphQL::INT_TYPE, null: true, diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index 8110377850b..69520e33774 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -33,10 +33,15 @@ module SearchHelper "Showing #{from} - #{to} of #{count} #{scope.humanize(capitalize: false)} for \"#{term}\"" end - def find_project_for_result_blob(result) + def find_project_for_result_blob(projects, result) @project end + # Used in EE + def blob_projects(results) + nil + end + def parse_search_result(result) result end @@ -45,6 +50,10 @@ module SearchHelper filename end + def search_service + @search_service ||= ::SearchService.new(current_user, params) + end + private # Autocomplete results for various settings pages diff --git a/app/models/abuse_report.rb b/app/models/abuse_report.rb index 1b78fd04ebb..a3a1748142f 100644 --- a/app/models/abuse_report.rb +++ b/app/models/abuse_report.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class AbuseReport < ActiveRecord::Base +class AbuseReport < ApplicationRecord include CacheMarkdownField cache_markdown_field :message, pipeline: :single_line diff --git a/app/models/appearance.rb b/app/models/appearance.rb index bdee9b2b73c..2815a117f7f 100644 --- a/app/models/appearance.rb +++ b/app/models/appearance.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Appearance < ActiveRecord::Base +class Appearance < ApplicationRecord include CacheableAttributes include CacheMarkdownField include ObjectStorage::BackgroundMove diff --git a/app/models/application_record.rb b/app/models/application_record.rb index a3d662d8250..6976185264e 100644 --- a/app/models/application_record.rb +++ b/app/models/application_record.rb @@ -7,6 +7,14 @@ class ApplicationRecord < ActiveRecord::Base where(id: ids) end + def self.id_not_in(ids) + where.not(id: ids) + end + + def self.pluck_primary_key + where(nil).pluck(self.primary_key) + end + def self.safe_find_or_create_by!(*args) safe_find_or_create_by(*args).tap do |record| record.validate! unless record.persisted? diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 9cc7c0a1b97..9e91e4ab4b9 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class ApplicationSetting < ActiveRecord::Base +class ApplicationSetting < ApplicationRecord include CacheableAttributes include CacheMarkdownField include TokenAuthenticatable diff --git a/app/models/application_setting/term.rb b/app/models/application_setting/term.rb index 498701ba22b..723540c9b91 100644 --- a/app/models/application_setting/term.rb +++ b/app/models/application_setting/term.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class ApplicationSetting - class Term < ActiveRecord::Base + class Term < ApplicationRecord include CacheMarkdownField has_many :term_agreements diff --git a/app/models/audit_event.rb b/app/models/audit_event.rb index 8508c88d406..6ef2914ac11 100644 --- a/app/models/audit_event.rb +++ b/app/models/audit_event.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class AuditEvent < ActiveRecord::Base +class AuditEvent < ApplicationRecord serialize :details, Hash # rubocop:disable Cop/ActiveRecordSerialize belongs_to :user, foreign_key: :author_id diff --git a/app/models/award_emoji.rb b/app/models/award_emoji.rb index ddc516ccb60..e26162f6151 100644 --- a/app/models/award_emoji.rb +++ b/app/models/award_emoji.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class AwardEmoji < ActiveRecord::Base +class AwardEmoji < ApplicationRecord DOWNVOTE_NAME = "thumbsdown".freeze UPVOTE_NAME = "thumbsup".freeze diff --git a/app/models/badge.rb b/app/models/badge.rb index f016654206b..a244ed473de 100644 --- a/app/models/badge.rb +++ b/app/models/badge.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Badge < ActiveRecord::Base +class Badge < ApplicationRecord include FromUnion # This structure sets the placeholders that the urls diff --git a/app/models/board.rb b/app/models/board.rb index 758a71d6903..e08db764f65 100644 --- a/app/models/board.rb +++ b/app/models/board.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Board < ActiveRecord::Base +class Board < ApplicationRecord belongs_to :group belongs_to :project diff --git a/app/models/board_group_recent_visit.rb b/app/models/board_group_recent_visit.rb index f5b75270595..2f1cd830791 100644 --- a/app/models/board_group_recent_visit.rb +++ b/app/models/board_group_recent_visit.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Tracks which boards in a specific group a user has visited -class BoardGroupRecentVisit < ActiveRecord::Base +class BoardGroupRecentVisit < ApplicationRecord belongs_to :user belongs_to :group belongs_to :board diff --git a/app/models/board_project_recent_visit.rb b/app/models/board_project_recent_visit.rb index 2a1b14b3ae0..236d88e909c 100644 --- a/app/models/board_project_recent_visit.rb +++ b/app/models/board_project_recent_visit.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Tracks which boards in a specific project a user has visited -class BoardProjectRecentVisit < ActiveRecord::Base +class BoardProjectRecentVisit < ApplicationRecord belongs_to :user belongs_to :project belongs_to :board diff --git a/app/models/broadcast_message.rb b/app/models/broadcast_message.rb index 1c95abdd9ee..18fe2a9624f 100644 --- a/app/models/broadcast_message.rb +++ b/app/models/broadcast_message.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class BroadcastMessage < ActiveRecord::Base +class BroadcastMessage < ApplicationRecord include CacheMarkdownField include Sortable diff --git a/app/models/chat_name.rb b/app/models/chat_name.rb index 03b0af53046..0041595baba 100644 --- a/app/models/chat_name.rb +++ b/app/models/chat_name.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class ChatName < ActiveRecord::Base +class ChatName < ApplicationRecord LAST_USED_AT_INTERVAL = 1.hour belongs_to :service diff --git a/app/models/chat_team.rb b/app/models/chat_team.rb index 4e724f9adf7..52b5a7b4a91 100644 --- a/app/models/chat_team.rb +++ b/app/models/chat_team.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class ChatTeam < ActiveRecord::Base +class ChatTeam < ApplicationRecord validates :team_id, presence: true validates :namespace, uniqueness: true diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 59f47effff7..1bd517641ac 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -26,7 +26,8 @@ module Ci belongs_to :erased_by, class_name: 'User' RUNNER_FEATURES = { - upload_multiple_artifacts: -> (build) { build.publishes_artifacts_reports? } + upload_multiple_artifacts: -> (build) { build.publishes_artifacts_reports? }, + refspecs: -> (build) { build.merge_request_ref? } }.freeze has_one :deployment, as: :deployable, class_name: 'Deployment' @@ -47,7 +48,8 @@ module Ci delegate :terminal_specification, to: :runner_session, allow_nil: true delegate :gitlab_deploy_token, to: :project delegate :trigger_short_token, to: :trigger_request, allow_nil: true - delegate :merge_request_event?, to: :pipeline + delegate :merge_request_event?, :merge_request_ref?, + :legacy_detached_merge_request_pipeline?, to: :pipeline ## # Since Gitlab 11.5, deployments records started being created right after diff --git a/app/models/ci/build_metadata.rb b/app/models/ci/build_metadata.rb index cd8eb774cf5..f281cbd1d6f 100644 --- a/app/models/ci/build_metadata.rb +++ b/app/models/ci/build_metadata.rb @@ -3,7 +3,7 @@ module Ci # The purpose of this class is to store Build related data that can be disposed. # Data that should be persisted forever, should be stored with Ci::Build model. - class BuildMetadata < ActiveRecord::Base + class BuildMetadata < ApplicationRecord extend Gitlab::Ci::Model include Presentable include ChronicDurationAttribute diff --git a/app/models/ci/build_runner_session.rb b/app/models/ci/build_runner_session.rb index 457d7eeab6a..061eff090f5 100644 --- a/app/models/ci/build_runner_session.rb +++ b/app/models/ci/build_runner_session.rb @@ -3,7 +3,7 @@ module Ci # The purpose of this class is to store Build related runner session. # Data will be removed after transitioning from running to any state. - class BuildRunnerSession < ActiveRecord::Base + class BuildRunnerSession < ApplicationRecord extend Gitlab::Ci::Model self.table_name = 'ci_builds_runner_session' diff --git a/app/models/ci/build_trace_chunk.rb b/app/models/ci/build_trace_chunk.rb index 75017f224a0..0a7a0e0772b 100644 --- a/app/models/ci/build_trace_chunk.rb +++ b/app/models/ci/build_trace_chunk.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Ci - class BuildTraceChunk < ActiveRecord::Base + class BuildTraceChunk < ApplicationRecord include FastDestroyAll include ::Gitlab::ExclusiveLeaseHelpers extend Gitlab::Ci::Model diff --git a/app/models/ci/build_trace_section.rb b/app/models/ci/build_trace_section.rb index a4bee59c83b..8be42eb48d6 100644 --- a/app/models/ci/build_trace_section.rb +++ b/app/models/ci/build_trace_section.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Ci - class BuildTraceSection < ActiveRecord::Base + class BuildTraceSection < ApplicationRecord extend Gitlab::Ci::Model belongs_to :build, class_name: 'Ci::Build' diff --git a/app/models/ci/build_trace_section_name.rb b/app/models/ci/build_trace_section_name.rb index cbdf3c4b673..c065cfea14e 100644 --- a/app/models/ci/build_trace_section_name.rb +++ b/app/models/ci/build_trace_section_name.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Ci - class BuildTraceSectionName < ActiveRecord::Base + class BuildTraceSectionName < ApplicationRecord extend Gitlab::Ci::Model belongs_to :project diff --git a/app/models/ci/group_variable.rb b/app/models/ci/group_variable.rb index 323ff560564..0e50265c7ba 100644 --- a/app/models/ci/group_variable.rb +++ b/app/models/ci/group_variable.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Ci - class GroupVariable < ActiveRecord::Base + class GroupVariable < ApplicationRecord extend Gitlab::Ci::Model include HasVariable include Presentable diff --git a/app/models/ci/job_artifact.rb b/app/models/ci/job_artifact.rb index 789bb293811..99512a7c1dd 100644 --- a/app/models/ci/job_artifact.rb +++ b/app/models/ci/job_artifact.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Ci - class JobArtifact < ActiveRecord::Base + class JobArtifact < ApplicationRecord include AfterCommitQueue include ObjectStorage::BackgroundMove extend Gitlab::Ci::Model diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 826b3f82bbf..abac16a138d 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Ci - class Pipeline < ActiveRecord::Base + class Pipeline < ApplicationRecord extend Gitlab::Ci::Model include HasStatus include Importable @@ -184,7 +184,7 @@ module Ci scope :sort_by_merge_request_pipelines, -> do sql = 'CASE ci_pipelines.source WHEN (?) THEN 0 ELSE 1 END, ci_pipelines.id DESC' - query = ActiveRecord::Base.send(:sanitize_sql_array, [sql, sources[:merge_request_event]]) # rubocop:disable GitlabSecurity/PublicSend + query = ApplicationRecord.send(:sanitize_sql_array, [sql, sources[:merge_request_event]]) # rubocop:disable GitlabSecurity/PublicSend order(query) end @@ -465,9 +465,9 @@ module Ci end def latest? - return false unless ref && commit.present? + return false unless git_ref && commit.present? - project.commit(ref) == commit + project.commit(git_ref) == commit end def retried @@ -738,6 +738,10 @@ module Ci triggered_by_merge_request? && target_sha.nil? end + def legacy_detached_merge_request_pipeline? + detached_merge_request_pipeline? && !merge_request_ref? + end + def merge_request_pipeline? triggered_by_merge_request? && target_sha.present? end @@ -746,6 +750,10 @@ module Ci triggered_by_merge_request? && target_sha == merge_request.target_branch_sha end + def merge_request_ref? + MergeRequest.merge_request_ref?(ref) + end + def matches_sha_or_source_sha?(sha) self.sha == sha || self.source_sha == sha end @@ -781,16 +789,18 @@ module Ci end def git_ref - if merge_request_event? - ## - # In the future, we're going to change this ref to - # merge request's merged reference, such as "refs/merge-requests/:iid/merge". - # In order to do that, we have to update GitLab-Runner's source pulling - # logic. - # See https://gitlab.com/gitlab-org/gitlab-runner/merge_requests/1092 - Gitlab::Git::BRANCH_REF_PREFIX + ref.to_s - else - super + strong_memoize(:git_ref) do + if merge_request_event? + ## + # In the future, we're going to change this ref to + # merge request's merged reference, such as "refs/merge-requests/:iid/merge". + # In order to do that, we have to update GitLab-Runner's source pulling + # logic. + # See https://gitlab.com/gitlab-org/gitlab-runner/merge_requests/1092 + Gitlab::Git::BRANCH_REF_PREFIX + ref.to_s + else + super + end end end diff --git a/app/models/ci/pipeline_chat_data.rb b/app/models/ci/pipeline_chat_data.rb index 8d37500fec5..65466a8c6f8 100644 --- a/app/models/ci/pipeline_chat_data.rb +++ b/app/models/ci/pipeline_chat_data.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Ci - class PipelineChatData < ActiveRecord::Base + class PipelineChatData < ApplicationRecord self.table_name = 'ci_pipeline_chat_data' belongs_to :chat_name diff --git a/app/models/ci/pipeline_schedule.rb b/app/models/ci/pipeline_schedule.rb index 1c1f203bdb2..1454b2dfb39 100644 --- a/app/models/ci/pipeline_schedule.rb +++ b/app/models/ci/pipeline_schedule.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Ci - class PipelineSchedule < ActiveRecord::Base + class PipelineSchedule < ApplicationRecord extend Gitlab::Ci::Model include Importable include IgnorableColumn diff --git a/app/models/ci/pipeline_schedule_variable.rb b/app/models/ci/pipeline_schedule_variable.rb index fbb9987cab2..be6e5e76c31 100644 --- a/app/models/ci/pipeline_schedule_variable.rb +++ b/app/models/ci/pipeline_schedule_variable.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Ci - class PipelineScheduleVariable < ActiveRecord::Base + class PipelineScheduleVariable < ApplicationRecord extend Gitlab::Ci::Model include HasVariable diff --git a/app/models/ci/pipeline_variable.rb b/app/models/ci/pipeline_variable.rb index 08514d6af4e..51a6272e1ff 100644 --- a/app/models/ci/pipeline_variable.rb +++ b/app/models/ci/pipeline_variable.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Ci - class PipelineVariable < ActiveRecord::Base + class PipelineVariable < ApplicationRecord extend Gitlab::Ci::Model include HasVariable diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index 43f040a91ae..07d00503861 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Ci - class Runner < ActiveRecord::Base + class Runner < ApplicationRecord extend Gitlab::Ci::Model include Gitlab::SQL::Pattern include IgnorableColumn diff --git a/app/models/ci/runner_namespace.rb b/app/models/ci/runner_namespace.rb index 22b80b98551..6903e8a21a1 100644 --- a/app/models/ci/runner_namespace.rb +++ b/app/models/ci/runner_namespace.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Ci - class RunnerNamespace < ActiveRecord::Base + class RunnerNamespace < ApplicationRecord extend Gitlab::Ci::Model belongs_to :runner, inverse_of: :runner_namespaces, validate: true diff --git a/app/models/ci/runner_project.rb b/app/models/ci/runner_project.rb index 1a718d24141..f5bd50dc5a3 100644 --- a/app/models/ci/runner_project.rb +++ b/app/models/ci/runner_project.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Ci - class RunnerProject < ActiveRecord::Base + class RunnerProject < ApplicationRecord extend Gitlab::Ci::Model belongs_to :runner, inverse_of: :runner_projects diff --git a/app/models/ci/stage.rb b/app/models/ci/stage.rb index 098f5189517..b25b0369666 100644 --- a/app/models/ci/stage.rb +++ b/app/models/ci/stage.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Ci - class Stage < ActiveRecord::Base + class Stage < ApplicationRecord extend Gitlab::Ci::Model include Importable include HasStatus diff --git a/app/models/ci/trigger.rb b/app/models/ci/trigger.rb index 637148c4ce4..8927bb9bc18 100644 --- a/app/models/ci/trigger.rb +++ b/app/models/ci/trigger.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Ci - class Trigger < ActiveRecord::Base + class Trigger < ApplicationRecord extend Gitlab::Ci::Model include IgnorableColumn include Presentable diff --git a/app/models/ci/trigger_request.rb b/app/models/ci/trigger_request.rb index 0b52c690e93..5daf3dd192d 100644 --- a/app/models/ci/trigger_request.rb +++ b/app/models/ci/trigger_request.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Ci - class TriggerRequest < ActiveRecord::Base + class TriggerRequest < ApplicationRecord extend Gitlab::Ci::Model belongs_to :trigger diff --git a/app/models/ci/variable.rb b/app/models/ci/variable.rb index 64836ea4fa4..a77bbef0fca 100644 --- a/app/models/ci/variable.rb +++ b/app/models/ci/variable.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Ci - class Variable < ActiveRecord::Base + class Variable < ApplicationRecord extend Gitlab::Ci::Model include HasVariable include Presentable diff --git a/app/models/clusters/applications/cert_manager.rb b/app/models/clusters/applications/cert_manager.rb index c758577815a..ac0e7eb03bc 100644 --- a/app/models/clusters/applications/cert_manager.rb +++ b/app/models/clusters/applications/cert_manager.rb @@ -2,7 +2,7 @@ module Clusters module Applications - class CertManager < ActiveRecord::Base + class CertManager < ApplicationRecord VERSION = 'v0.5.2'.freeze self.table_name = 'clusters_applications_cert_managers' diff --git a/app/models/clusters/applications/helm.rb b/app/models/clusters/applications/helm.rb index 423071ec024..71aff00077d 100644 --- a/app/models/clusters/applications/helm.rb +++ b/app/models/clusters/applications/helm.rb @@ -4,7 +4,7 @@ require 'openssl' module Clusters module Applications - class Helm < ActiveRecord::Base + class Helm < ApplicationRecord self.table_name = 'clusters_applications_helm' attr_encrypted :ca_key, diff --git a/app/models/clusters/applications/ingress.rb b/app/models/clusters/applications/ingress.rb index 567f1a2267f..376d54aab2c 100644 --- a/app/models/clusters/applications/ingress.rb +++ b/app/models/clusters/applications/ingress.rb @@ -2,7 +2,7 @@ module Clusters module Applications - class Ingress < ActiveRecord::Base + class Ingress < ApplicationRecord VERSION = '1.1.2'.freeze self.table_name = 'clusters_applications_ingress' diff --git a/app/models/clusters/applications/jupyter.rb b/app/models/clusters/applications/jupyter.rb index 7efcc175f9f..f86ff3551a1 100644 --- a/app/models/clusters/applications/jupyter.rb +++ b/app/models/clusters/applications/jupyter.rb @@ -2,7 +2,7 @@ module Clusters module Applications - class Jupyter < ActiveRecord::Base + class Jupyter < ApplicationRecord VERSION = '0.9-174bbd5'.freeze self.table_name = 'clusters_applications_jupyter' diff --git a/app/models/clusters/applications/knative.rb b/app/models/clusters/applications/knative.rb index 347c3c8c37f..f7e54833296 100644 --- a/app/models/clusters/applications/knative.rb +++ b/app/models/clusters/applications/knative.rb @@ -2,8 +2,8 @@ module Clusters module Applications - class Knative < ActiveRecord::Base - VERSION = '0.2.2'.freeze + class Knative < ApplicationRecord + VERSION = '0.3.0'.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 @@ -86,7 +86,7 @@ module Clusters end def ingress_service - cluster.kubeclient.get_service('knative-ingressgateway', 'istio-system') + cluster.kubeclient.get_service('istio-ingressgateway', 'istio-system') end def services_for(ns: namespace) diff --git a/app/models/clusters/applications/prometheus.rb b/app/models/clusters/applications/prometheus.rb index fa7ce363531..954c29da196 100644 --- a/app/models/clusters/applications/prometheus.rb +++ b/app/models/clusters/applications/prometheus.rb @@ -2,7 +2,7 @@ module Clusters module Applications - class Prometheus < ActiveRecord::Base + class Prometheus < ApplicationRecord include PrometheusAdapter VERSION = '6.7.3' diff --git a/app/models/clusters/applications/runner.rb b/app/models/clusters/applications/runner.rb index ef9cc4bd6d6..8cb81bfcbe4 100644 --- a/app/models/clusters/applications/runner.rb +++ b/app/models/clusters/applications/runner.rb @@ -2,7 +2,7 @@ module Clusters module Applications - class Runner < ActiveRecord::Base + class Runner < ApplicationRecord VERSION = '0.3.0'.freeze self.table_name = 'clusters_applications_runners' @@ -13,7 +13,7 @@ module Clusters include ::Clusters::Concerns::ApplicationData belongs_to :runner, class_name: 'Ci::Runner', foreign_key: :runner_id - delegate :project, to: :cluster + delegate :project, :group, to: :cluster default_value_for :version, VERSION @@ -55,12 +55,17 @@ module Clusters end def runner_create_params - { + attributes = { name: 'kubernetes-cluster', - runner_type: :project_type, - tag_list: %w(kubernetes cluster), - projects: [project] + runner_type: cluster.cluster_type, + tag_list: %w[kubernetes cluster] } + + if cluster.group_type? + attributes.merge(groups: [group]) + elsif cluster.project_type? + attributes.merge(projects: [project]) + end end def gitlab_url diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb index 5156c7d7514..4262c03498d 100644 --- a/app/models/clusters/cluster.rb +++ b/app/models/clusters/cluster.rb @@ -1,22 +1,24 @@ # frozen_string_literal: true module Clusters - class Cluster < ActiveRecord::Base + class Cluster < ApplicationRecord include Presentable include Gitlab::Utils::StrongMemoize include FromUnion self.table_name = 'clusters' + PROJECT_ONLY_APPLICATIONS = { + Applications::Jupyter.application_name => Applications::Jupyter, + Applications::Knative.application_name => Applications::Knative, + Applications::Prometheus.application_name => Applications::Prometheus + }.freeze APPLICATIONS = { Applications::Helm.application_name => Applications::Helm, Applications::Ingress.application_name => Applications::Ingress, Applications::CertManager.application_name => Applications::CertManager, - Applications::Prometheus.application_name => Applications::Prometheus, - Applications::Runner.application_name => Applications::Runner, - Applications::Jupyter.application_name => Applications::Jupyter, - Applications::Knative.application_name => Applications::Knative - }.freeze + Applications::Runner.application_name => Applications::Runner + }.merge(PROJECT_ONLY_APPLICATIONS).freeze DEFAULT_ENVIRONMENT = '*'.freeze KUBE_INGRESS_BASE_DOMAIN = 'KUBE_INGRESS_BASE_DOMAIN'.freeze @@ -70,6 +72,7 @@ module Clusters delegate :external_hostname, to: :application_ingress, prefix: true, allow_nil: true alias_attribute :base_domain, :domain + alias_attribute :provided_by_user?, :user? enum cluster_type: { instance_type: 1, @@ -149,10 +152,6 @@ module Clusters return platform_kubernetes if kubernetes? end - def managed? - !user? - end - def all_projects if project_type? projects diff --git a/app/models/clusters/group.rb b/app/models/clusters/group.rb index 2b08a9e47f0..27f39b53579 100644 --- a/app/models/clusters/group.rb +++ b/app/models/clusters/group.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Clusters - class Group < ActiveRecord::Base + class Group < ApplicationRecord self.table_name = 'cluster_groups' belongs_to :cluster, class_name: 'Clusters::Cluster' diff --git a/app/models/clusters/kubernetes_namespace.rb b/app/models/clusters/kubernetes_namespace.rb index 7fc75e00cd0..b0c4900546e 100644 --- a/app/models/clusters/kubernetes_namespace.rb +++ b/app/models/clusters/kubernetes_namespace.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Clusters - class KubernetesNamespace < ActiveRecord::Base + class KubernetesNamespace < ApplicationRecord include Gitlab::Kubernetes self.table_name = 'clusters_kubernetes_namespaces' diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb index 7786b48429c..2ae141190a8 100644 --- a/app/models/clusters/platforms/kubernetes.rb +++ b/app/models/clusters/platforms/kubernetes.rb @@ -2,7 +2,7 @@ module Clusters module Platforms - class Kubernetes < ActiveRecord::Base + class Kubernetes < ApplicationRecord include Gitlab::Kubernetes include ReactiveCaching include EnumWithNil @@ -54,7 +54,7 @@ module Clusters delegate :project, to: :cluster, allow_nil: true delegate :enabled?, to: :cluster, allow_nil: true - delegate :managed?, to: :cluster, allow_nil: true + delegate :provided_by_user?, to: :cluster, allow_nil: true delegate :allow_user_defined_namespace?, to: :cluster, allow_nil: true delegate :kubernetes_namespace, to: :cluster @@ -219,7 +219,7 @@ module Clusters end def prevent_modification - return unless managed? + return if provided_by_user? if api_url_changed? || token_changed? || ca_pem_changed? errors.add(:base, _('Cannot modify managed Kubernetes cluster')) diff --git a/app/models/clusters/project.rb b/app/models/clusters/project.rb index 15092b1c9d2..d2b68b3f117 100644 --- a/app/models/clusters/project.rb +++ b/app/models/clusters/project.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Clusters - class Project < ActiveRecord::Base + class Project < ApplicationRecord self.table_name = 'cluster_projects' belongs_to :cluster, class_name: 'Clusters::Cluster' diff --git a/app/models/clusters/providers/gcp.rb b/app/models/clusters/providers/gcp.rb index 16b59cd9d14..390748bf252 100644 --- a/app/models/clusters/providers/gcp.rb +++ b/app/models/clusters/providers/gcp.rb @@ -2,7 +2,7 @@ module Clusters module Providers - class Gcp < ActiveRecord::Base + class Gcp < ApplicationRecord self.table_name = 'cluster_providers_gcp' belongs_to :cluster, inverse_of: :provider_gcp, class_name: 'Clusters::Cluster' diff --git a/app/models/commit_collection.rb b/app/models/commit_collection.rb index a9a2e9c81eb..52524456439 100644 --- a/app/models/commit_collection.rb +++ b/app/models/commit_collection.rb @@ -28,10 +28,43 @@ class CommitCollection def without_merge_commits strong_memoize(:without_merge_commits) do - commits.reject(&:merge_commit?) + # `#enrich!` the collection to ensure all commits contain + # the necessary parent data + enrich!.commits.reject(&:merge_commit?) end end + def unenriched + commits.reject(&:gitaly_commit?) + end + + def fully_enriched? + unenriched.empty? + end + + # Batch load any commits that are not backed by full gitaly data, and + # replace them in the collection. + def enrich! + # A project is needed in order to fetch data from gitaly. Projects + # can be absent from commits in certain rare situations (like when + # viewing a MR of a deleted fork). In these cases, assume that the + # enriched data is not needed. + return self if project.blank? || fully_enriched? + + # Batch load full Commits from the repository + # and map to a Hash of id => Commit + replacements = Hash[unenriched.map do |c| + [c.id, Commit.lazy(project, c.id)] + end.compact] + + # Replace the commits, keeping the same order + @commits = @commits.map do |c| + replacements.fetch(c.id, c) + end + + self + end + # Sets the pipeline status for every commit. # # Setting this status ahead of time removes the need for running a query for diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 5f66a661324..f97dc38dab7 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class CommitStatus < ActiveRecord::Base +class CommitStatus < ApplicationRecord include HasStatus include Importable include AfterCommitQueue diff --git a/app/models/concerns/atomic_internal_id.rb b/app/models/concerns/atomic_internal_id.rb index 4e15b60ccd1..ab3d9e923c0 100644 --- a/app/models/concerns/atomic_internal_id.rb +++ b/app/models/concerns/atomic_internal_id.rb @@ -7,7 +7,7 @@ # # For example, let's generate internal ids for Issue per Project: # ``` -# class Issue < ActiveRecord::Base +# class Issue < ApplicationRecord # has_internal_id :iid, scope: :project, init: ->(s) { s.project.issues.maximum(:iid) } # end # ``` diff --git a/app/models/concerns/cache_markdown_field.rb b/app/models/concerns/cache_markdown_field.rb index 15d8d58b9b5..28ea51d6769 100644 --- a/app/models/concerns/cache_markdown_field.rb +++ b/app/models/concerns/cache_markdown_field.rb @@ -15,7 +15,7 @@ module CacheMarkdownField # Increment this number every time the renderer changes its output CACHE_COMMONMARK_VERSION_START = 10 - CACHE_COMMONMARK_VERSION = 14 + CACHE_COMMONMARK_VERSION = 15 # changes to these attributes cause the cache to be invalidates INVALIDATED_BY = %w[author project].freeze diff --git a/app/models/concerns/ignorable_column.rb b/app/models/concerns/ignorable_column.rb index 5c1f7dfcd2a..3bec44dc79b 100644 --- a/app/models/concerns/ignorable_column.rb +++ b/app/models/concerns/ignorable_column.rb @@ -5,7 +5,7 @@ # # Example: # -# class User < ActiveRecord::Base +# class User < ApplicationRecord # include IgnorableColumn # # ignore_column :updated_at diff --git a/app/models/concerns/participable.rb b/app/models/concerns/participable.rb index 614c3242874..b140fca9b83 100644 --- a/app/models/concerns/participable.rb +++ b/app/models/concerns/participable.rb @@ -7,7 +7,7 @@ # # Usage: # -# class Issue < ActiveRecord::Base +# class Issue < ApplicationRecord # include Participable # # # ... diff --git a/app/models/concerns/reactive_caching.rb b/app/models/concerns/reactive_caching.rb index d2ead7130e5..1ab3b3ddc46 100644 --- a/app/models/concerns/reactive_caching.rb +++ b/app/models/concerns/reactive_caching.rb @@ -7,7 +7,7 @@ # # Example of use: # -# class Foo < ActiveRecord::Base +# class Foo < ApplicationRecord # include ReactiveCaching # # self.reactive_cache_key = ->(thing) { ["foo", thing.id] } diff --git a/app/models/concerns/sha_attribute.rb b/app/models/concerns/sha_attribute.rb index a479bef993c..70ac873a030 100644 --- a/app/models/concerns/sha_attribute.rb +++ b/app/models/concerns/sha_attribute.rb @@ -39,7 +39,7 @@ module ShaAttribute end def database_exists? - ActiveRecord::Base.connection + ApplicationRecord.connection true rescue diff --git a/app/models/concerns/strip_attribute.rb b/app/models/concerns/strip_attribute.rb index c9f5ba7793d..8f6a6244dd3 100644 --- a/app/models/concerns/strip_attribute.rb +++ b/app/models/concerns/strip_attribute.rb @@ -6,7 +6,7 @@ # # Usage: # -# class Milestone < ActiveRecord::Base +# class Milestone < ApplicationRecord # strip_attributes :title # end # diff --git a/app/models/container_repository.rb b/app/models/container_repository.rb index cf057d774cf..39e12ac2b06 100644 --- a/app/models/container_repository.rb +++ b/app/models/container_repository.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class ContainerRepository < ActiveRecord::Base +class ContainerRepository < ApplicationRecord include Gitlab::Utils::StrongMemoize belongs_to :project diff --git a/app/models/conversational_development_index/metric.rb b/app/models/conversational_development_index/metric.rb index c54537572d6..b91123be87e 100644 --- a/app/models/conversational_development_index/metric.rb +++ b/app/models/conversational_development_index/metric.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module ConversationalDevelopmentIndex - class Metric < ActiveRecord::Base + class Metric < ApplicationRecord include Presentable self.table_name = 'conversational_development_index_metrics' diff --git a/app/models/deploy_keys_project.rb b/app/models/deploy_keys_project.rb index 71fd02fac86..15906ed8e06 100644 --- a/app/models/deploy_keys_project.rb +++ b/app/models/deploy_keys_project.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class DeployKeysProject < ActiveRecord::Base +class DeployKeysProject < ApplicationRecord belongs_to :project belongs_to :deploy_key, inverse_of: :deploy_keys_projects diff --git a/app/models/deploy_token.rb b/app/models/deploy_token.rb index e3524305346..b0e570f52ba 100644 --- a/app/models/deploy_token.rb +++ b/app/models/deploy_token.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class DeployToken < ActiveRecord::Base +class DeployToken < ApplicationRecord include Expirable include TokenAuthenticatable include PolicyActor diff --git a/app/models/deployment.rb b/app/models/deployment.rb index 428edfd88de..d847a0a11e4 100644 --- a/app/models/deployment.rb +++ b/app/models/deployment.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Deployment < ActiveRecord::Base +class Deployment < ApplicationRecord include AtomicInternalId include IidRoutes include AfterCommitQueue diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb index 87755cf3f3d..feabea9b8ba 100644 --- a/app/models/diff_note.rb +++ b/app/models/diff_note.rb @@ -77,8 +77,8 @@ class DiffNote < Note def supports_suggestion? return false unless noteable.supports_suggestion? && on_text? # We don't want to trigger side-effects of `diff_file` call. - return false unless file = fetch_diff_file - return false unless line = file.line_for_position(self.original_position) + return false unless file = latest_diff_file + return false unless line = file.line_for_position(self.position) line&.suggestible? end diff --git a/app/models/email.rb b/app/models/email.rb index 7c33c5c7e64..0ddaa049c3b 100644 --- a/app/models/email.rb +++ b/app/models/email.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Email < ActiveRecord::Base +class Email < ApplicationRecord include Sortable include Gitlab::SQL::Pattern diff --git a/app/models/environment.rb b/app/models/environment.rb index 3d909cc8e5c..25373c7a1f7 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Environment < ActiveRecord::Base +class Environment < ApplicationRecord include Gitlab::Utils::StrongMemoize # Used to generate random suffixes for the slug LETTERS = 'a'..'z' diff --git a/app/models/epic.rb b/app/models/epic.rb index ccd10593434..3693db1de33 100644 --- a/app/models/epic.rb +++ b/app/models/epic.rb @@ -2,7 +2,7 @@ # Placeholder class for model that is implemented in EE # It reserves '&' as a reference prefix, but the table does not exists in CE -class Epic < ActiveRecord::Base +class Epic < ApplicationRecord def self.link_reference_pattern nil end diff --git a/app/models/error_tracking/project_error_tracking_setting.rb b/app/models/error_tracking/project_error_tracking_setting.rb index 1e2bd3bda7f..70954bf8b05 100644 --- a/app/models/error_tracking/project_error_tracking_setting.rb +++ b/app/models/error_tracking/project_error_tracking_setting.rb @@ -1,10 +1,13 @@ # frozen_string_literal: true module ErrorTracking - class ProjectErrorTrackingSetting < ActiveRecord::Base + class ProjectErrorTrackingSetting < ApplicationRecord include Gitlab::Utils::StrongMemoize include ReactiveCaching + SENTRY_API_ERROR_TYPE_MISSING_KEYS = 'missing_keys_in_sentry_response' + SENTRY_API_ERROR_TYPE_NON_20X_RESPONSE = 'non_20x_response_from_sentry' + API_URL_PATH_REGEXP = %r{ \A (?<prefix>/api/0/projects/+) @@ -90,7 +93,9 @@ module ErrorTracking { issues: sentry_client.list_issues(**opts.symbolize_keys) } end rescue Sentry::Client::Error => e - { error: e.message } + { error: e.message, error_type: SENTRY_API_ERROR_TYPE_NON_20X_RESPONSE } + rescue Sentry::Client::MissingKeysError => e + { error: e.message, error_type: SENTRY_API_ERROR_TYPE_MISSING_KEYS } end # http://HOST/api/0/projects/ORG/PROJECT diff --git a/app/models/event.rb b/app/models/event.rb index 6a35bca72c5..593acf5edfe 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Event < ActiveRecord::Base +class Event < ApplicationRecord include Sortable include IgnorableColumn include FromUnion diff --git a/app/models/fork_network.rb b/app/models/fork_network.rb index 1b9bf93cbbc..0323a8d222a 100644 --- a/app/models/fork_network.rb +++ b/app/models/fork_network.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class ForkNetwork < ActiveRecord::Base +class ForkNetwork < ApplicationRecord belongs_to :root_project, class_name: 'Project' has_many :fork_network_members has_many :projects, through: :fork_network_members diff --git a/app/models/fork_network_member.rb b/app/models/fork_network_member.rb index 36c66f21b0b..f18c306cf91 100644 --- a/app/models/fork_network_member.rb +++ b/app/models/fork_network_member.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class ForkNetworkMember < ActiveRecord::Base +class ForkNetworkMember < ApplicationRecord belongs_to :fork_network belongs_to :project belongs_to :forked_from_project, class_name: 'Project' diff --git a/app/models/gpg_key.rb b/app/models/gpg_key.rb index 077afffd358..116beac5c2a 100644 --- a/app/models/gpg_key.rb +++ b/app/models/gpg_key.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class GpgKey < ActiveRecord::Base +class GpgKey < ApplicationRecord KEY_PREFIX = '-----BEGIN PGP PUBLIC KEY BLOCK-----'.freeze KEY_SUFFIX = '-----END PGP PUBLIC KEY BLOCK-----'.freeze diff --git a/app/models/gpg_key_subkey.rb b/app/models/gpg_key_subkey.rb index 440b588bc78..110bf451136 100644 --- a/app/models/gpg_key_subkey.rb +++ b/app/models/gpg_key_subkey.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class GpgKeySubkey < ActiveRecord::Base +class GpgKeySubkey < ApplicationRecord include ShaAttribute sha_attribute :keyid diff --git a/app/models/group_custom_attribute.rb b/app/models/group_custom_attribute.rb index 22f14885657..5ac6e5f2550 100644 --- a/app/models/group_custom_attribute.rb +++ b/app/models/group_custom_attribute.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class GroupCustomAttribute < ActiveRecord::Base +class GroupCustomAttribute < ApplicationRecord belongs_to :group validates :group, :key, :value, presence: true diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb index 1a8662db9fb..daf7ff4b771 100644 --- a/app/models/hooks/web_hook.rb +++ b/app/models/hooks/web_hook.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class WebHook < ActiveRecord::Base +class WebHook < ApplicationRecord include Sortable attr_encrypted :token, diff --git a/app/models/hooks/web_hook_log.rb b/app/models/hooks/web_hook_log.rb index 2d9f7594e8c..cfb1f3ec63b 100644 --- a/app/models/hooks/web_hook_log.rb +++ b/app/models/hooks/web_hook_log.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class WebHookLog < ActiveRecord::Base +class WebHookLog < ApplicationRecord belongs_to :web_hook serialize :request_headers, Hash # rubocop:disable Cop/ActiveRecordSerialize diff --git a/app/models/identity.rb b/app/models/identity.rb index acdde4f296b..8322b9bf35f 100644 --- a/app/models/identity.rb +++ b/app/models/identity.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Identity < ActiveRecord::Base +class Identity < ApplicationRecord include Sortable include CaseSensitivity diff --git a/app/models/identity/uniqueness_scopes.rb b/app/models/identity/uniqueness_scopes.rb index 674b735903f..ce68371ae87 100644 --- a/app/models/identity/uniqueness_scopes.rb +++ b/app/models/identity/uniqueness_scopes.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Identity < ActiveRecord::Base +class Identity < ApplicationRecord # This module and method are defined in a separate file to allow EE to # redefine the `scopes` method before it is used in the `Identity` model. module UniquenessScopes diff --git a/app/models/import_export_upload.rb b/app/models/import_export_upload.rb index f0cc5aafcd4..60f5491849a 100644 --- a/app/models/import_export_upload.rb +++ b/app/models/import_export_upload.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class ImportExportUpload < ActiveRecord::Base +class ImportExportUpload < ApplicationRecord include WithUploads include ObjectStorage::BackgroundMove diff --git a/app/models/internal_id.rb b/app/models/internal_id.rb index e75c6eb2331..3f2d368a3f2 100644 --- a/app/models/internal_id.rb +++ b/app/models/internal_id.rb @@ -15,7 +15,7 @@ # In order to leverage InternalId for other usages, the idea is to # * Add `usage` value to enum # * (Optionally) add columns to `internal_ids` if needed for scope. -class InternalId < ActiveRecord::Base +class InternalId < ApplicationRecord belongs_to :project belongs_to :namespace diff --git a/app/models/issue.rb b/app/models/issue.rb index deab53d25e7..97c6dcc4745 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -2,7 +2,7 @@ require 'carrierwave/orm/activerecord' -class Issue < ActiveRecord::Base +class Issue < ApplicationRecord include AtomicInternalId include IidRoutes include Issuable diff --git a/app/models/issue/metrics.rb b/app/models/issue/metrics.rb index 0f5ee957ec9..8010cbc3d78 100644 --- a/app/models/issue/metrics.rb +++ b/app/models/issue/metrics.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Issue::Metrics < ActiveRecord::Base +class Issue::Metrics < ApplicationRecord belongs_to :issue def record! diff --git a/app/models/issue_assignee.rb b/app/models/issue_assignee.rb index 400c0256945..fbd9be1fb43 100644 --- a/app/models/issue_assignee.rb +++ b/app/models/issue_assignee.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class IssueAssignee < ActiveRecord::Base +class IssueAssignee < ApplicationRecord belongs_to :issue belongs_to :assignee, class_name: "User", foreign_key: :user_id end diff --git a/app/models/key.rb b/app/models/key.rb index 8f93418b88b..b097be8cc89 100644 --- a/app/models/key.rb +++ b/app/models/key.rb @@ -2,7 +2,7 @@ require 'digest/md5' -class Key < ActiveRecord::Base +class Key < ApplicationRecord include AfterCommitQueue include Sortable diff --git a/app/models/label.rb b/app/models/label.rb index 96bdb7f17c5..024daeb4fae 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Label < ActiveRecord::Base +class Label < ApplicationRecord include CacheMarkdownField include Referable include Subscribable diff --git a/app/models/label_link.rb b/app/models/label_link.rb index 1d93a55e8e9..ffc0afd8e85 100644 --- a/app/models/label_link.rb +++ b/app/models/label_link.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class LabelLink < ActiveRecord::Base +class LabelLink < ApplicationRecord include Importable belongs_to :target, polymorphic: true, inverse_of: :label_links # rubocop:disable Cop/PolymorphicAssociations diff --git a/app/models/label_priority.rb b/app/models/label_priority.rb index 8ed8bb7577f..8f8f36efbfe 100644 --- a/app/models/label_priority.rb +++ b/app/models/label_priority.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class LabelPriority < ActiveRecord::Base +class LabelPriority < ApplicationRecord belongs_to :project belongs_to :label diff --git a/app/models/lfs_file_lock.rb b/app/models/lfs_file_lock.rb index 431d37e12e9..624b1d02e1a 100644 --- a/app/models/lfs_file_lock.rb +++ b/app/models/lfs_file_lock.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class LfsFileLock < ActiveRecord::Base +class LfsFileLock < ApplicationRecord belongs_to :project belongs_to :user diff --git a/app/models/lfs_object.rb b/app/models/lfs_object.rb index 69c563545bb..e1aac691a64 100644 --- a/app/models/lfs_object.rb +++ b/app/models/lfs_object.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class LfsObject < ActiveRecord::Base +class LfsObject < ApplicationRecord include AfterCommitQueue include ObjectStorage::BackgroundMove diff --git a/app/models/lfs_objects_project.rb b/app/models/lfs_objects_project.rb index 353602800d7..f9afb18c1d7 100644 --- a/app/models/lfs_objects_project.rb +++ b/app/models/lfs_objects_project.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class LfsObjectsProject < ActiveRecord::Base +class LfsObjectsProject < ApplicationRecord belongs_to :project belongs_to :lfs_object diff --git a/app/models/list.rb b/app/models/list.rb index 682af761ba0..17b1a8510cf 100644 --- a/app/models/list.rb +++ b/app/models/list.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class List < ActiveRecord::Base +class List < ApplicationRecord belongs_to :board belongs_to :label diff --git a/app/models/member.rb b/app/models/member.rb index 5dbc0c2eec9..8a06bff51b5 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Member < ActiveRecord::Base +class Member < ApplicationRecord include AfterCommitQueue include Sortable include Importable diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb index 2c9e1ba1d80..510f856087d 100644 --- a/app/models/members/group_member.rb +++ b/app/models/members/group_member.rb @@ -14,6 +14,8 @@ class GroupMember < Member scope :in_groups, ->(groups) { where(source_id: groups.select(:id)) } + scope :count_users_by_group_id, -> { joins(:user).group(:source_id).count } + after_create :update_two_factor_requirement, unless: :invite? after_destroy :update_two_factor_requirement, unless: :invite? diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 5f6d5095bcc..f6b83453c2a 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class MergeRequest < ActiveRecord::Base +class MergeRequest < ApplicationRecord include AtomicInternalId include IidRoutes include Issuable @@ -66,7 +66,10 @@ class MergeRequest < ActiveRecord::Base has_many :cached_closes_issues, through: :merge_requests_closing_issues, source: :issue has_many :merge_request_pipelines, foreign_key: 'merge_request_id', class_name: 'Ci::Pipeline' + has_many :suggestions, through: :notes + has_many :merge_request_assignees + # Will be deprecated at https://gitlab.com/gitlab-org/gitlab-ce/issues/59457 belongs_to :assignee, class_name: "User" serialize :merge_params, Hash # rubocop:disable Cop/ActiveRecordSerialize @@ -76,6 +79,10 @@ class MergeRequest < ActiveRecord::Base after_update :reload_diff_if_branch_changed after_save :ensure_metrics + # Required until the codebase starts using this relation for single or multiple assignees. + # TODO: Remove at gitlab-ee#2004 implementation. + after_save :refresh_merge_request_assignees, if: :assignee_id_changed? + # When this attribute is true some MR validation is ignored # It allows us to close or modify broken merge requests attr_accessor :allow_broken @@ -675,6 +682,15 @@ class MergeRequest < ActiveRecord::Base merge_request_diff || create_merge_request_diff end + def refresh_merge_request_assignees + transaction do + # Using it instead relation.delete_all in order to avoid adding a + # dependent: :delete_all (we already have foreign key cascade deletion). + MergeRequestAssignee.where(merge_request_id: self).delete_all + merge_request_assignees.create(user_id: assignee_id) if assignee_id + end + end + def create_merge_request_diff fetch_ref! @@ -791,8 +807,7 @@ class MergeRequest < ActiveRecord::Base end def mergeable_to_ref? - return false if merged? - return false if broken? + return false unless mergeable_state?(skip_ci_check: true, skip_discussions_check: true) # Given the `merge_ref_path` will have the same # state the `target_branch` would have. Ideally @@ -1113,6 +1128,10 @@ class MergeRequest < ActiveRecord::Base "refs/#{Repository::REF_MERGE_REQUEST}/#{iid}/merge" end + def self.merge_request_ref?(ref) + ref.start_with?("refs/#{Repository::REF_MERGE_REQUEST}/") + end + def in_locked_state begin lock_mr @@ -1323,7 +1342,7 @@ class MergeRequest < ActiveRecord::Base end def has_commits? - merge_request_diff && commits_count > 0 + merge_request_diff && commits_count.to_i > 0 end def has_no_commits? diff --git a/app/models/merge_request/metrics.rb b/app/models/merge_request/metrics.rb index 65e94a97b0a..05f8e18a2c1 100644 --- a/app/models/merge_request/metrics.rb +++ b/app/models/merge_request/metrics.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class MergeRequest::Metrics < ActiveRecord::Base +class MergeRequest::Metrics < ApplicationRecord belongs_to :merge_request belongs_to :pipeline, class_name: 'Ci::Pipeline', foreign_key: :pipeline_id belongs_to :latest_closed_by, class_name: 'User' diff --git a/app/models/merge_request_assignee.rb b/app/models/merge_request_assignee.rb new file mode 100644 index 00000000000..f0e6be51b7f --- /dev/null +++ b/app/models/merge_request_assignee.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +class MergeRequestAssignee < ApplicationRecord + belongs_to :merge_request + belongs_to :assignee, class_name: "User", foreign_key: :user_id +end diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 98db1bf7de7..ac8d3b98266 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class MergeRequestDiff < ActiveRecord::Base +class MergeRequestDiff < ApplicationRecord include Sortable include Importable include ManualInverseAssociation @@ -12,6 +12,10 @@ class MergeRequestDiff < ActiveRecord::Base # Don't display more than 100 commits at once COMMITS_SAFE_SIZE = 100 + # Applies to closed or merged MRs when determining whether to migrate their + # diffs to external storage + EXTERNAL_DIFF_CUTOFF = 7.days.freeze + belongs_to :merge_request manual_inverse_association :merge_request, :merge_request_diff @@ -48,6 +52,81 @@ class MergeRequestDiff < ActiveRecord::Base end scope :recent, -> { order(id: :desc).limit(100) } + scope :files_in_database, -> { where(stored_externally: [false, nil]) } + + scope :not_latest_diffs, -> do + merge_requests = MergeRequest.arel_table + mr_diffs = arel_table + + join_condition = merge_requests[:id].eq(mr_diffs[:merge_request_id]) + .and(mr_diffs[:id].not_eq(merge_requests[:latest_merge_request_diff_id])) + + arel_join = mr_diffs.join(merge_requests).on(join_condition) + joins(arel_join.join_sources) + end + + scope :old_merged_diffs, -> (before) do + merge_requests = MergeRequest.arel_table + mr_metrics = MergeRequest::Metrics.arel_table + mr_diffs = arel_table + + mr_join = mr_diffs + .join(merge_requests) + .on(mr_diffs[:merge_request_id].eq(merge_requests[:id])) + + metrics_join_condition = mr_diffs[:merge_request_id] + .eq(mr_metrics[:merge_request_id]) + .and(mr_metrics[:merged_at].not_eq(nil)) + + metrics_join = mr_diffs.join(mr_metrics).on(metrics_join_condition) + + condition = MergeRequest.arel_table[:state].eq(:merged) + .and(MergeRequest::Metrics.arel_table[:merged_at].lteq(before)) + .and(MergeRequest::Metrics.arel_table[:merged_at].not_eq(nil)) + + joins(metrics_join.join_sources, mr_join.join_sources).where(condition) + end + + scope :old_closed_diffs, -> (before) do + condition = MergeRequest.arel_table[:state].eq(:closed) + .and(MergeRequest::Metrics.arel_table[:latest_closed_at].lteq(before)) + + joins(merge_request: :metrics).where(condition) + end + + def self.ids_for_external_storage_migration(limit:) + # No point doing any work unless the feature is enabled + return [] unless Gitlab.config.external_diffs.enabled + + case Gitlab.config.external_diffs.when + when 'always' + files_in_database.limit(limit).pluck(:id) + when 'outdated' + # Outdated is too complex to be a single SQL query, so split into three + before = EXTERNAL_DIFF_CUTOFF.ago + + ids = files_in_database + .old_merged_diffs(before) + .limit(limit) + .pluck(:id) + + return ids if ids.size >= limit + + ids += files_in_database + .old_closed_diffs(before) + .limit(limit - ids.size) + .pluck(:id) + + return ids if ids.size >= limit + + ids + files_in_database + .not_latest_diffs + .limit(limit - ids.size) + .pluck(:id) + else + [] + end + end mount_uploader :external_diff, ExternalDiffUploader @@ -55,7 +134,7 @@ class MergeRequestDiff < ActiveRecord::Base # It allows you to override variables like head_commit_sha before getting diff. after_create :save_git_content, unless: :importing? - after_save :update_external_diff_store, if: :external_diff_changed? + after_save :update_external_diff_store, if: -> { !importing? && external_diff_changed? } def self.find_by_diff_refs(diff_refs) find_by(start_commit_sha: diff_refs.start_sha, head_commit_sha: diff_refs.head_sha, base_commit_sha: diff_refs.base_sha) @@ -294,6 +373,23 @@ class MergeRequestDiff < ActiveRecord::Base end end + # Transactionally migrate the current merge_request_diff_files entries to + # external storage. If external storage isn't an option for this diff, the + # method is a no-op. + def migrate_files_to_external_storage! + return if stored_externally? || !use_external_diff? || merge_request_diff_files.count == 0 + + rows = build_merge_request_diff_files(merge_request_diff_files) + + transaction do + MergeRequestDiffFile.where(merge_request_diff_id: id).delete_all + create_merge_request_diff_files(rows) + save! + end + + merge_request_diff_files.reload + end + private def encode_in_base64?(diff_text) @@ -301,20 +397,7 @@ class MergeRequestDiff < ActiveRecord::Base diff_text.include?("\0") end - def create_merge_request_diff_files(diffs) - rows = - if has_attribute?(:external_diff) && Gitlab.config.external_diffs.enabled - build_external_merge_request_diff_files(diffs) - else - build_merge_request_diff_files(diffs) - end - - # Faster inserts - Gitlab::Database.bulk_insert('merge_request_diff_files', rows) - end - - def build_external_merge_request_diff_files(diffs) - rows = build_merge_request_diff_files(diffs) + def build_external_merge_request_diff_files(rows) tempfile = build_external_diff_tempfile(rows) self.external_diff = tempfile @@ -325,16 +408,21 @@ class MergeRequestDiff < ActiveRecord::Base tempfile&.unlink end + def create_merge_request_diff_files(rows) + rows = build_external_merge_request_diff_files(rows) if use_external_diff? + + # Faster inserts + Gitlab::Database.bulk_insert('merge_request_diff_files', rows) + end + def build_external_diff_tempfile(rows) Tempfile.open(external_diff.filename) do |file| - rows.inject(0) do |offset, row| + rows.each do |row| data = row.delete(:diff) - row[:external_diff_offset] = offset - row[:external_diff_size] = data.size + row[:external_diff_offset] = file.pos + row[:external_diff_size] = data.bytesize file.write(data) - - offset + data.size end file @@ -361,6 +449,47 @@ class MergeRequestDiff < ActiveRecord::Base end end + def use_external_diff? + return false unless has_attribute?(:external_diff) + return false unless Gitlab.config.external_diffs.enabled + + case Gitlab.config.external_diffs.when + when 'always' + true + when 'outdated' + outdated_by_merge? || outdated_by_closure? || old_version? + else + false # Disable external diffs if misconfigured + end + end + + def outdated_by_merge? + return false unless merge_request&.metrics&.merged_at + + merge_request.merged? && merge_request.metrics.merged_at < EXTERNAL_DIFF_CUTOFF.ago + end + + def outdated_by_closure? + return false unless merge_request&.metrics&.latest_closed_at + + merge_request.closed? && merge_request.metrics.latest_closed_at < EXTERNAL_DIFF_CUTOFF.ago + end + + # We can't rely on `merge_request.latest_merge_request_diff_id` because that + # may have been changed in `save_git_content` without being reflected in + # the association's instance. This query is always subject to races, but + # the worst case is that we *don't* make a diff external when we could. The + # background worker will make it external at a later date. + def old_version? + latest_id = MergeRequest + .where(id: merge_request_id) + .limit(1) + .pluck(:latest_merge_request_diff_id) + .first + + self.id != latest_id + end + def load_diffs(options) # Ensure all diff files operate on the same external diff file instance if # present. This reduces file open/close overhead. @@ -394,7 +523,8 @@ class MergeRequestDiff < ActiveRecord::Base if diff_collection.any? new_attributes[:state] = :collected - create_merge_request_diff_files(diff_collection) + rows = build_merge_request_diff_files(diff_collection) + create_merge_request_diff_files(rows) end # Set our state to 'overflow' to make the #empty? and #collected? diff --git a/app/models/merge_request_diff_commit.rb b/app/models/merge_request_diff_commit.rb index 4ad3690512d..b897bbc8cf5 100644 --- a/app/models/merge_request_diff_commit.rb +++ b/app/models/merge_request_diff_commit.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class MergeRequestDiffCommit < ActiveRecord::Base +class MergeRequestDiffCommit < ApplicationRecord include ShaAttribute belongs_to :merge_request_diff diff --git a/app/models/merge_request_diff_file.rb b/app/models/merge_request_diff_file.rb index 16ec4ed470f..01ee82ae398 100644 --- a/app/models/merge_request_diff_file.rb +++ b/app/models/merge_request_diff_file.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class MergeRequestDiffFile < ActiveRecord::Base +class MergeRequestDiffFile < ApplicationRecord include Gitlab::EncodingHelper include DiffFile diff --git a/app/models/merge_requests_closing_issues.rb b/app/models/merge_requests_closing_issues.rb index 242b65bedc0..61af50841ee 100644 --- a/app/models/merge_requests_closing_issues.rb +++ b/app/models/merge_requests_closing_issues.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class MergeRequestsClosingIssues < ActiveRecord::Base +class MergeRequestsClosingIssues < ApplicationRecord belongs_to :merge_request belongs_to :issue diff --git a/app/models/milestone.rb b/app/models/milestone.rb index a3831ae3fa8..b4aad9e512e 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Milestone < ActiveRecord::Base +class Milestone < ApplicationRecord # Represents a "No Milestone" state used for filtering Issues and Merge # Requests that have no milestone assigned. MilestoneStruct = Struct.new(:title, :name, :id) diff --git a/app/models/note.rb b/app/models/note.rb index 2c9980b1a0d..081d6f91230 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -3,7 +3,7 @@ # A note on the root of an issue, merge request, commit, or snippet. # # A note of this type is never resolvable. -class Note < ActiveRecord::Base +class Note < ApplicationRecord extend ActiveModel::Naming include Participable include Mentionable diff --git a/app/models/note_diff_file.rb b/app/models/note_diff_file.rb index e369122003e..9afb94c869a 100644 --- a/app/models/note_diff_file.rb +++ b/app/models/note_diff_file.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class NoteDiffFile < ActiveRecord::Base +class NoteDiffFile < ApplicationRecord include DiffFile scope :for_commit_or_unresolved, -> do diff --git a/app/models/notification_setting.rb b/app/models/notification_setting.rb index e82eaf4e069..61af5c09ae4 100644 --- a/app/models/notification_setting.rb +++ b/app/models/notification_setting.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class NotificationSetting < ActiveRecord::Base +class NotificationSetting < ApplicationRecord include IgnorableColumn ignore_column :events diff --git a/app/models/pages_domain.rb b/app/models/pages_domain.rb index 7a33ade826b..82901ceec01 100644 --- a/app/models/pages_domain.rb +++ b/app/models/pages_domain.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class PagesDomain < ActiveRecord::Base +class PagesDomain < ApplicationRecord VERIFICATION_KEY = 'gitlab-pages-verification-code'.freeze VERIFICATION_THRESHOLD = 3.days.freeze diff --git a/app/models/personal_access_token.rb b/app/models/personal_access_token.rb index ed78a46eaf3..570112b63b7 100644 --- a/app/models/personal_access_token.rb +++ b/app/models/personal_access_token.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class PersonalAccessToken < ActiveRecord::Base +class PersonalAccessToken < ApplicationRecord include Expirable include IgnorableColumn include TokenAuthenticatable diff --git a/app/models/pool_repository.rb b/app/models/pool_repository.rb index 4635fc72dc7..35c718365b4 100644 --- a/app/models/pool_repository.rb +++ b/app/models/pool_repository.rb @@ -3,7 +3,7 @@ # The PoolRepository model is the database equivalent of an ObjectPool for Gitaly # That is; PoolRepository is the record in the database, ObjectPool is the # repository on disk -class PoolRepository < ActiveRecord::Base +class PoolRepository < ApplicationRecord include Shardable include AfterCommitQueue diff --git a/app/models/postgresql/replication_slot.rb b/app/models/postgresql/replication_slot.rb index e264fe88e47..74ccf23cf69 100644 --- a/app/models/postgresql/replication_slot.rb +++ b/app/models/postgresql/replication_slot.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Postgresql - class ReplicationSlot < ActiveRecord::Base + class ReplicationSlot < ApplicationRecord self.table_name = 'pg_replication_slots' # Returns true if there are any replication slots in use. diff --git a/app/models/programming_language.rb b/app/models/programming_language.rb index 5f0f313b7f9..375fbe9b5a9 100644 --- a/app/models/programming_language.rb +++ b/app/models/programming_language.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class ProgrammingLanguage < ActiveRecord::Base +class ProgrammingLanguage < ApplicationRecord validates :name, presence: true validates :color, allow_blank: false, color: true diff --git a/app/models/project.rb b/app/models/project.rb index 611c64c8f49..82c2f9090c8 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -2,7 +2,7 @@ require 'carrierwave/orm/activerecord' -class Project < ActiveRecord::Base +class Project < ApplicationRecord include Gitlab::ConfigHelper include Gitlab::ShellAdapter include Gitlab::VisibilityLevel @@ -459,14 +459,41 @@ class Project < ActiveRecord::Base # Returns a collection of projects that is either public or visible to the # logged in user. - def self.public_or_visible_to_user(user = nil) - if user - where('EXISTS (?) OR projects.visibility_level IN (?)', - user.authorizations_for_projects, - Gitlab::VisibilityLevel.levels_for_user(user)) - else - public_to_user - end + # + # requested_visiblity_levels: Normally all projects that are visible + # to the user (e.g. internal and public) are queried, but this + # parameter allows the caller to narrow the search space to optimize + # database queries. For instance, a caller may only want to see + # internal projects. Instead of querying for internal and public + # projects and throwing away public projects, this parameter allows + # the query to be targeted for only internal projects. + def self.public_or_visible_to_user(user = nil, requested_visibility_levels = []) + return public_to_user unless user + + visible_levels = Gitlab::VisibilityLevel.levels_for_user(user) + include_private = true + requested_visibility_levels = Array(requested_visibility_levels) + + if requested_visibility_levels.present? + visible_levels &= requested_visibility_levels + include_private = requested_visibility_levels.include?(Gitlab::VisibilityLevel::PRIVATE) + end + + public_or_internal_rel = + if visible_levels.present? + where('projects.visibility_level IN (?)', visible_levels) + else + Project.none + end + + private_rel = + if include_private + where('EXISTS (?)', user.authorizations_for_projects) + else + Project.none + end + + public_or_internal_rel.or(private_rel) end # project features may be "disabled", "internal", "enabled" or "public". If "internal", @@ -2003,12 +2030,8 @@ class Project < ActiveRecord::Base @storage = nil if storage_version_changed? end - def gl_repository(is_wiki:) - Gitlab::GlRepository.gl_repository(self, is_wiki) - end - - def reference_counter(wiki: false) - Gitlab::ReferenceCounter.new(gl_repository(is_wiki: wiki)) + def reference_counter(type: Gitlab::GlRepository::PROJECT) + Gitlab::ReferenceCounter.new(type.identifier_for_subject(self)) end def badges @@ -2152,7 +2175,7 @@ class Project < ActiveRecord::Base end def wiki_reference_count - reference_counter(wiki: true).value + reference_counter(type: Gitlab::GlRepository::WIKI).value end def check_repository_absence! diff --git a/app/models/project_authorization.rb b/app/models/project_authorization.rb index 2c590008db2..f95d3ab54e2 100644 --- a/app/models/project_authorization.rb +++ b/app/models/project_authorization.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class ProjectAuthorization < ActiveRecord::Base +class ProjectAuthorization < ApplicationRecord include FromUnion belongs_to :user diff --git a/app/models/project_auto_devops.rb b/app/models/project_auto_devops.rb index e353a6443c4..f972c40f317 100644 --- a/app/models/project_auto_devops.rb +++ b/app/models/project_auto_devops.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class ProjectAutoDevops < ActiveRecord::Base +class ProjectAutoDevops < ApplicationRecord belongs_to :project enum deploy_strategy: { diff --git a/app/models/project_ci_cd_setting.rb b/app/models/project_ci_cd_setting.rb index 1dad235cc2b..1414164b703 100644 --- a/app/models/project_ci_cd_setting.rb +++ b/app/models/project_ci_cd_setting.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class ProjectCiCdSetting < ActiveRecord::Base +class ProjectCiCdSetting < ApplicationRecord belongs_to :project, inverse_of: :ci_cd_settings # The version of the schema that first introduced this model/table. diff --git a/app/models/project_custom_attribute.rb b/app/models/project_custom_attribute.rb index 4e767cb3b26..b0da586988a 100644 --- a/app/models/project_custom_attribute.rb +++ b/app/models/project_custom_attribute.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class ProjectCustomAttribute < ActiveRecord::Base +class ProjectCustomAttribute < ApplicationRecord belongs_to :project validates :project, :key, :value, presence: true diff --git a/app/models/project_daily_statistic.rb b/app/models/project_daily_statistic.rb index ff115dd010f..5ee11ab186e 100644 --- a/app/models/project_daily_statistic.rb +++ b/app/models/project_daily_statistic.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class ProjectDailyStatistic < ActiveRecord::Base +class ProjectDailyStatistic < ApplicationRecord belongs_to :project scope :of_project, -> (project) { where(project: project) } diff --git a/app/models/project_deploy_token.rb b/app/models/project_deploy_token.rb index 719c492a1ff..a55667496fb 100644 --- a/app/models/project_deploy_token.rb +++ b/app/models/project_deploy_token.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class ProjectDeployToken < ActiveRecord::Base +class ProjectDeployToken < ApplicationRecord belongs_to :project belongs_to :deploy_token, inverse_of: :project_deploy_tokens diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb index e6787236c4e..0542581c6e0 100644 --- a/app/models/project_feature.rb +++ b/app/models/project_feature.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class ProjectFeature < ActiveRecord::Base +class ProjectFeature < ApplicationRecord # == Project features permissions # # Grants access level to project tools diff --git a/app/models/project_group_link.rb b/app/models/project_group_link.rb index bc3759142ae..58b555c3581 100644 --- a/app/models/project_group_link.rb +++ b/app/models/project_group_link.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class ProjectGroupLink < ActiveRecord::Base +class ProjectGroupLink < ApplicationRecord include Expirable GUEST = 10 diff --git a/app/models/project_import_data.rb b/app/models/project_import_data.rb index aa0c121fe99..580e8dfd833 100644 --- a/app/models/project_import_data.rb +++ b/app/models/project_import_data.rb @@ -2,7 +2,7 @@ require 'carrierwave/orm/activerecord' -class ProjectImportData < ActiveRecord::Base +class ProjectImportData < ApplicationRecord belongs_to :project, inverse_of: :import_data attr_encrypted :credentials, key: Settings.attr_encrypted_db_key_base, diff --git a/app/models/project_import_state.rb b/app/models/project_import_state.rb index 488f0cb5971..1605345efd5 100644 --- a/app/models/project_import_state.rb +++ b/app/models/project_import_state.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class ProjectImportState < ActiveRecord::Base +class ProjectImportState < ApplicationRecord include AfterCommitQueue self.table_name = "project_mirror_data" diff --git a/app/models/project_repository.rb b/app/models/project_repository.rb index 38913f3f2f5..092efabd73f 100644 --- a/app/models/project_repository.rb +++ b/app/models/project_repository.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class ProjectRepository < ActiveRecord::Base +class ProjectRepository < ApplicationRecord include Shardable belongs_to :project, inverse_of: :project_repository diff --git a/app/models/project_statistics.rb b/app/models/project_statistics.rb index 781a197d56f..c020e72908c 100644 --- a/app/models/project_statistics.rb +++ b/app/models/project_statistics.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class ProjectStatistics < ActiveRecord::Base +class ProjectStatistics < ApplicationRecord belongs_to :project belongs_to :namespace diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb index 6ea0716c192..268706a6aea 100644 --- a/app/models/project_wiki.rb +++ b/app/models/project_wiki.rb @@ -59,7 +59,7 @@ class ProjectWiki # Returns the Gitlab::Git::Wiki object. def wiki @wiki ||= begin - gl_repository = Gitlab::GlRepository.gl_repository(project, true) + gl_repository = Gitlab::GlRepository::WIKI.identifier_for_subject(project) raw_repository = Gitlab::Git::Repository.new(project.repository_storage, disk_path + '.git', gl_repository, full_path) create_repo!(raw_repository) unless raw_repository.exists? @@ -151,7 +151,7 @@ class ProjectWiki end def repository - @repository ||= Repository.new(full_path, @project, disk_path: disk_path, is_wiki: true) + @repository ||= Repository.new(full_path, @project, disk_path: disk_path, repo_type: Gitlab::GlRepository::WIKI) end def default_branch diff --git a/app/models/prometheus_metric.rb b/app/models/prometheus_metric.rb index 5594594a48d..62090444f79 100644 --- a/app/models/prometheus_metric.rb +++ b/app/models/prometheus_metric.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class PrometheusMetric < ActiveRecord::Base +class PrometheusMetric < ApplicationRecord belongs_to :project, validate: true, inverse_of: :prometheus_metrics enum group: { diff --git a/app/models/protected_branch.rb b/app/models/protected_branch.rb index 597431be65a..ee0c94c20af 100644 --- a/app/models/protected_branch.rb +++ b/app/models/protected_branch.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class ProtectedBranch < ActiveRecord::Base +class ProtectedBranch < ApplicationRecord include ProtectedRef protected_ref_access_levels :merge, :push diff --git a/app/models/protected_branch/merge_access_level.rb b/app/models/protected_branch/merge_access_level.rb index b0d5c64e931..de240e40316 100644 --- a/app/models/protected_branch/merge_access_level.rb +++ b/app/models/protected_branch/merge_access_level.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -class ProtectedBranch::MergeAccessLevel < ActiveRecord::Base +class ProtectedBranch::MergeAccessLevel < ApplicationRecord include ProtectedBranchAccess end diff --git a/app/models/protected_branch/push_access_level.rb b/app/models/protected_branch/push_access_level.rb index b2a88229853..bde1d29ad7f 100644 --- a/app/models/protected_branch/push_access_level.rb +++ b/app/models/protected_branch/push_access_level.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -class ProtectedBranch::PushAccessLevel < ActiveRecord::Base +class ProtectedBranch::PushAccessLevel < ApplicationRecord include ProtectedBranchAccess end diff --git a/app/models/protected_tag.rb b/app/models/protected_tag.rb index d28ebabfe49..6b507429e57 100644 --- a/app/models/protected_tag.rb +++ b/app/models/protected_tag.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class ProtectedTag < ActiveRecord::Base +class ProtectedTag < ApplicationRecord include ProtectedRef validates :name, uniqueness: { scope: :project_id } diff --git a/app/models/protected_tag/create_access_level.rb b/app/models/protected_tag/create_access_level.rb index b06e55fb5dd..9fcfa7646a2 100644 --- a/app/models/protected_tag/create_access_level.rb +++ b/app/models/protected_tag/create_access_level.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class ProtectedTag::CreateAccessLevel < ActiveRecord::Base +class ProtectedTag::CreateAccessLevel < ApplicationRecord include ProtectedTagAccess def check_access(user) diff --git a/app/models/push_event_payload.rb b/app/models/push_event_payload.rb index c7769edf055..537859ec7b7 100644 --- a/app/models/push_event_payload.rb +++ b/app/models/push_event_payload.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class PushEventPayload < ActiveRecord::Base +class PushEventPayload < ApplicationRecord include ShaAttribute belongs_to :event, inverse_of: :push_event_payload diff --git a/app/models/redirect_route.rb b/app/models/redirect_route.rb index c6bd4bb6dfa..2e4769364c6 100644 --- a/app/models/redirect_route.rb +++ b/app/models/redirect_route.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class RedirectRoute < ActiveRecord::Base +class RedirectRoute < ApplicationRecord belongs_to :source, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations validates :source, presence: true diff --git a/app/models/release.rb b/app/models/release.rb index 0dae5c90394..746fc31a038 100644 --- a/app/models/release.rb +++ b/app/models/release.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Release < ActiveRecord::Base +class Release < ApplicationRecord include CacheMarkdownField include Gitlab::Utils::StrongMemoize diff --git a/app/models/releases/link.rb b/app/models/releases/link.rb index 6c507c47752..36ec33d3e3e 100644 --- a/app/models/releases/link.rb +++ b/app/models/releases/link.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Releases - class Link < ActiveRecord::Base + class Link < ApplicationRecord self.table_name = 'release_links' belongs_to :release diff --git a/app/models/remote_mirror.rb b/app/models/remote_mirror.rb index 5eba7ddd75c..5610cfe0f24 100644 --- a/app/models/remote_mirror.rb +++ b/app/models/remote_mirror.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class RemoteMirror < ActiveRecord::Base +class RemoteMirror < ApplicationRecord include AfterCommitQueue include MirrorAuthentication diff --git a/app/models/repository.rb b/app/models/repository.rb index ff355295862..574ce12b309 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -19,7 +19,7 @@ class Repository include Gitlab::RepositoryCacheAdapter - attr_accessor :full_path, :disk_path, :project, :is_wiki + attr_accessor :full_path, :disk_path, :project, :repo_type delegate :ref_name_for_sha, to: :raw_repository delegate :bundle_to_disk, to: :raw_repository @@ -60,12 +60,12 @@ class Repository xcode_config: :xcode_project? }.freeze - def initialize(full_path, project, disk_path: nil, is_wiki: false) + def initialize(full_path, project, disk_path: nil, repo_type: Gitlab::GlRepository::PROJECT) @full_path = full_path @disk_path = disk_path || full_path @project = project @commit_cache = {} - @is_wiki = is_wiki + @repo_type = repo_type end def ==(other) @@ -1112,7 +1112,7 @@ class Repository def initialize_raw_repository Gitlab::Git::Repository.new(project.repository_storage, disk_path + '.git', - Gitlab::GlRepository.gl_repository(project, is_wiki), + repo_type.identifier_for_subject(project), project.full_path) end end diff --git a/app/models/repository_language.rb b/app/models/repository_language.rb index b18142a2ac4..e6867f905e2 100644 --- a/app/models/repository_language.rb +++ b/app/models/repository_language.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class RepositoryLanguage < ActiveRecord::Base +class RepositoryLanguage < ApplicationRecord belongs_to :project belongs_to :programming_language diff --git a/app/models/resource_label_event.rb b/app/models/resource_label_event.rb index 3fd96b9dc18..f2c7cb6a65d 100644 --- a/app/models/resource_label_event.rb +++ b/app/models/resource_label_event.rb @@ -2,7 +2,7 @@ # This model is not used yet, it will be used for: # https://gitlab.com/gitlab-org/gitlab-ce/issues/48483 -class ResourceLabelEvent < ActiveRecord::Base +class ResourceLabelEvent < ApplicationRecord include Importable include Gitlab::Utils::StrongMemoize include CacheMarkdownField diff --git a/app/models/route.rb b/app/models/route.rb index 4b23dfa5778..7e3db54d4fe 100644 --- a/app/models/route.rb +++ b/app/models/route.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Route < ActiveRecord::Base +class Route < ApplicationRecord include CaseSensitivity belongs_to :source, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations diff --git a/app/models/sent_notification.rb b/app/models/sent_notification.rb index 6caab24143b..0427d5b9ca7 100644 --- a/app/models/sent_notification.rb +++ b/app/models/sent_notification.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class SentNotification < ActiveRecord::Base +class SentNotification < ApplicationRecord serialize :position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize belongs_to :project diff --git a/app/models/service.rb b/app/models/service.rb index da523bfa426..c6d5eb353dc 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -2,7 +2,7 @@ # To add new service you should build a class inherited from Service # and implement a set of methods -class Service < ActiveRecord::Base +class Service < ApplicationRecord include Sortable include Importable include ProjectServicesLoggable diff --git a/app/models/shard.rb b/app/models/shard.rb index e39d4232486..335a279c6aa 100644 --- a/app/models/shard.rb +++ b/app/models/shard.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Shard < ActiveRecord::Base +class Shard < ApplicationRecord # Store shard names from the configuration file in the database. This is not a # list of active shards - we just want to assign an immutable, unique ID to # every shard name for easy indexing / referencing. diff --git a/app/models/snippet.rb b/app/models/snippet.rb index f23ddd64fe3..f4fdac2558c 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Snippet < ActiveRecord::Base +class Snippet < ApplicationRecord include Gitlab::VisibilityLevel include Redactable include CacheMarkdownField diff --git a/app/models/spam_log.rb b/app/models/spam_log.rb index ef3f974b959..5b9ece8373f 100644 --- a/app/models/spam_log.rb +++ b/app/models/spam_log.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class SpamLog < ActiveRecord::Base +class SpamLog < ApplicationRecord belongs_to :user validates :user, presence: true diff --git a/app/models/subscription.rb b/app/models/subscription.rb index 0f6ee0ddf7e..24a2b8b5167 100644 --- a/app/models/subscription.rb +++ b/app/models/subscription.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Subscription < ActiveRecord::Base +class Subscription < ApplicationRecord belongs_to :user belongs_to :project belongs_to :subscribable, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations diff --git a/app/models/suggestion.rb b/app/models/suggestion.rb index 09034646bff..22e2f11230d 100644 --- a/app/models/suggestion.rb +++ b/app/models/suggestion.rb @@ -1,11 +1,19 @@ # frozen_string_literal: true class Suggestion < ApplicationRecord + include Suggestible + belongs_to :note, inverse_of: :suggestions validates :note, presence: true validates :commit_id, presence: true, if: :applied? - delegate :original_position, :position, :noteable, to: :note + delegate :position, :noteable, to: :note + + scope :active, -> { where(outdated: false) } + + def diff_file + note.latest_diff_file + end def project noteable.source_project @@ -19,37 +27,37 @@ class Suggestion < ApplicationRecord position.file_path end - # For now, suggestions only serve as a way to send patches that - # will change a single line (being able to apply multiple in the same place), - # which explains `from_line` and `to_line` being the same line. - # We'll iterate on that in https://gitlab.com/gitlab-org/gitlab-ce/issues/53310 - # when allowing multi-line suggestions. - def from_line - position.new_line - end - alias_method :to_line, :from_line - - def from_original_line - original_position.new_line - end - alias_method :to_original_line, :from_original_line - # `from_line_index` and `to_line_index` represents diff/blob line numbers in # index-like way (N-1). def from_line_index from_line - 1 end - alias_method :to_line_index, :from_line_index - def appliable? - return false unless note.supports_suggestion? + def to_line_index + to_line - 1 + end + def appliable?(cached: true) !applied? && noteable.opened? && + !outdated?(cached: cached) && + note.supports_suggestion? && different_content? && note.active? end + # Overwrites outdated column + def outdated?(cached: true) + return super() if cached + return true unless diff_file + + from_content != fetch_from_content + end + + def target_line + position.new_line + end + private def different_content? diff --git a/app/models/system_note_metadata.rb b/app/models/system_note_metadata.rb index d555ebe5322..55da37c9545 100644 --- a/app/models/system_note_metadata.rb +++ b/app/models/system_note_metadata.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class SystemNoteMetadata < ActiveRecord::Base +class SystemNoteMetadata < ApplicationRecord # These notes's action text might contain a reference that is external. # We should always force a deep validation upon references that are found # in this note type. diff --git a/app/models/term_agreement.rb b/app/models/term_agreement.rb index 9b3c8ac68bd..a4a9dc10282 100644 --- a/app/models/term_agreement.rb +++ b/app/models/term_agreement.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class TermAgreement < ActiveRecord::Base +class TermAgreement < ApplicationRecord belongs_to :term, class_name: 'ApplicationSetting::Term' belongs_to :user diff --git a/app/models/timelog.rb b/app/models/timelog.rb index e04c644a53a..048134fbf04 100644 --- a/app/models/timelog.rb +++ b/app/models/timelog.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Timelog < ActiveRecord::Base +class Timelog < ApplicationRecord validates :time_spent, :user, presence: true validate :issuable_id_is_present diff --git a/app/models/todo.rb b/app/models/todo.rb index 2b0dee875a3..5dcc3e9945a 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Todo < ActiveRecord::Base +class Todo < ApplicationRecord include Sortable include FromUnion diff --git a/app/models/trending_project.rb b/app/models/trending_project.rb index 7b22e8cb760..810dee672b2 100644 --- a/app/models/trending_project.rb +++ b/app/models/trending_project.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class TrendingProject < ActiveRecord::Base +class TrendingProject < ApplicationRecord belongs_to :project # The number of months to include in the trending calculation. diff --git a/app/models/u2f_registration.rb b/app/models/u2f_registration.rb index 37598173fd1..b4645462314 100644 --- a/app/models/u2f_registration.rb +++ b/app/models/u2f_registration.rb @@ -2,7 +2,7 @@ # Registration information for U2F (universal 2nd factor) devices, like Yubikeys -class U2fRegistration < ActiveRecord::Base +class U2fRegistration < ApplicationRecord belongs_to :user def self.register(user, app_id, params, challenges) diff --git a/app/models/upload.rb b/app/models/upload.rb index 20860f14b83..9bffdcdb2e7 100644 --- a/app/models/upload.rb +++ b/app/models/upload.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Upload < ActiveRecord::Base +class Upload < ApplicationRecord # Upper limit for foreground checksum processing CHECKSUM_THRESHOLD = 100.megabytes diff --git a/app/models/user.rb b/app/models/user.rb index d2be26370ff..e0c518a9b75 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -432,7 +432,7 @@ class User < ApplicationRecord fuzzy_arel_match(:name, query, lower_exact_match: true) .or(fuzzy_arel_match(:username, query, lower_exact_match: true)) .or(arel_table[:email].eq(query)) - ).reorder(order % { query: ActiveRecord::Base.connection.quote(query) }, :name) + ).reorder(order % { query: ApplicationRecord.connection.quote(query) }, :name) end # Limits the result set to users _not_ in the given query/list of IDs. diff --git a/app/models/user_agent_detail.rb b/app/models/user_agent_detail.rb index e2b2e7f1df9..fea1fce3c8d 100644 --- a/app/models/user_agent_detail.rb +++ b/app/models/user_agent_detail.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class UserAgentDetail < ActiveRecord::Base +class UserAgentDetail < ApplicationRecord belongs_to :subject, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations validates :user_agent, :ip_address, :subject_id, :subject_type, presence: true diff --git a/app/models/user_callout.rb b/app/models/user_callout.rb index 76e7bc06b4e..027ee44c6a9 100644 --- a/app/models/user_callout.rb +++ b/app/models/user_callout.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class UserCallout < ActiveRecord::Base +class UserCallout < ApplicationRecord belongs_to :user # We use `UserCalloutEnums.feature_names` here so that EE can more easily diff --git a/app/models/user_custom_attribute.rb b/app/models/user_custom_attribute.rb index e0ffe8ebbfd..727975c3f6e 100644 --- a/app/models/user_custom_attribute.rb +++ b/app/models/user_custom_attribute.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class UserCustomAttribute < ActiveRecord::Base +class UserCustomAttribute < ApplicationRecord belongs_to :user validates :user_id, :key, :value, presence: true diff --git a/app/models/user_interacted_project.rb b/app/models/user_interacted_project.rb index 5fc59b274f5..f6f72f4b77a 100644 --- a/app/models/user_interacted_project.rb +++ b/app/models/user_interacted_project.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class UserInteractedProject < ActiveRecord::Base +class UserInteractedProject < ApplicationRecord belongs_to :user belongs_to :project diff --git a/app/models/user_preference.rb b/app/models/user_preference.rb index 32d0407800f..282b192167f 100644 --- a/app/models/user_preference.rb +++ b/app/models/user_preference.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class UserPreference < ActiveRecord::Base +class UserPreference < ApplicationRecord # We could use enums, but Rails 4 doesn't support multiple # enum options with same name for multiple fields, also it creates # extra methods that aren't really needed here. diff --git a/app/models/user_status.rb b/app/models/user_status.rb index 2bbb0c59ac1..6ced4f56823 100644 --- a/app/models/user_status.rb +++ b/app/models/user_status.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class UserStatus < ActiveRecord::Base +class UserStatus < ApplicationRecord include CacheMarkdownField self.primary_key = :user_id diff --git a/app/models/user_synced_attributes_metadata.rb b/app/models/user_synced_attributes_metadata.rb index 7115262942d..5aacf11b1cb 100644 --- a/app/models/user_synced_attributes_metadata.rb +++ b/app/models/user_synced_attributes_metadata.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class UserSyncedAttributesMetadata < ActiveRecord::Base +class UserSyncedAttributesMetadata < ApplicationRecord belongs_to :user validates :user, presence: true diff --git a/app/models/users_star_project.rb b/app/models/users_star_project.rb index bdaf58ae1c1..9be6bd2e6f3 100644 --- a/app/models/users_star_project.rb +++ b/app/models/users_star_project.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class UsersStarProject < ActiveRecord::Base +class UsersStarProject < ApplicationRecord belongs_to :project, counter_cache: :star_count, touch: true belongs_to :user diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index 9f9f5230040..dfbad4627eb 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -187,6 +187,7 @@ class ProjectPolicy < BasePolicy rule { can?(:reporter_access) }.policy do enable :download_code + enable :read_statistics enable :download_wiki_code enable :fork_project enable :create_project_snippet diff --git a/app/presenters/ci/build_runner_presenter.rb b/app/presenters/ci/build_runner_presenter.rb index 29656b17183..ed3daf6585b 100644 --- a/app/presenters/ci/build_runner_presenter.rb +++ b/app/presenters/ci/build_runner_presenter.rb @@ -4,6 +4,7 @@ module Ci class BuildRunnerPresenter < SimpleDelegator include Gitlab::Utils::StrongMemoize + DEFAULT_GIT_DEPTH_MERGE_REQUEST = 10 RUNNER_REMOTE_TAG_PREFIX = 'refs/tags/'.freeze RUNNER_REMOTE_BRANCH_PREFIX = 'refs/remotes/origin/'.freeze @@ -27,6 +28,7 @@ module Ci def git_depth strong_memoize(:git_depth) do git_depth = variables&.find { |variable| variable[:key] == 'GIT_DEPTH' }&.dig(:value) + git_depth ||= DEFAULT_GIT_DEPTH_MERGE_REQUEST if merge_request_ref? git_depth.to_i end end @@ -35,8 +37,9 @@ module Ci specs = [] if git_depth > 0 - specs << refspec_for_branch(ref) if branch? || merge_request_event? + specs << refspec_for_branch(ref) if branch? || legacy_detached_merge_request_pipeline? specs << refspec_for_tag(ref) if tag? + specs << refspec_for_merge_request_ref if merge_request_ref? else specs << refspec_for_branch specs << refspec_for_tag @@ -83,5 +86,9 @@ module Ci def refspec_for_tag(ref = '*') "+#{Gitlab::Git::TAG_REF_PREFIX}#{ref}:#{RUNNER_REMOTE_TAG_PREFIX}#{ref}" end + + def refspec_for_merge_request_ref + "+#{ref}:#{ref}" + end end end diff --git a/app/presenters/clusters/cluster_presenter.rb b/app/presenters/clusters/cluster_presenter.rb index 7a5b68f9a4b..81994bbce7d 100644 --- a/app/presenters/clusters/cluster_presenter.rb +++ b/app/presenters/clusters/cluster_presenter.rb @@ -48,6 +48,10 @@ module Clusters end end + def read_only_kubernetes_platform_fields? + !cluster.provided_by_user? + end + private def clusterable diff --git a/app/serializers/diff_file_entity.rb b/app/serializers/diff_file_entity.rb index 066e30cd3bb..d3d5883e46b 100644 --- a/app/serializers/diff_file_entity.rb +++ b/app/serializers/diff_file_entity.rb @@ -57,7 +57,7 @@ class DiffFileEntity < DiffFileBaseEntity diff_file.diff_lines_for_serializer end - expose :is_fully_expanded, if: -> (diff_file, _) { Feature.enabled?(:expand_diff_full_file, default_enabled: true) && diff_file.text? } do |diff_file| + expose :is_fully_expanded, if: -> (diff_file, _) { diff_file.text? } do |diff_file| diff_file.fully_expanded? end diff --git a/app/services/after_branch_delete_service.rb b/app/services/after_branch_delete_service.rb index e7eb74d3e7d..ece9fbbef43 100644 --- a/app/services/after_branch_delete_service.rb +++ b/app/services/after_branch_delete_service.rb @@ -1,9 +1,6 @@ # frozen_string_literal: true -## -# Branch can be deleted either by DeleteBranchService -# or by GitPushService. -# +# Branch can be deleted either by DeleteBranchService or by Git::BranchPushService. class AfterBranchDeleteService < BaseService attr_reader :branch_name diff --git a/app/services/ci/destroy_pipeline_service.rb b/app/services/ci/destroy_pipeline_service.rb index 5c4a34043c1..2292ec42b16 100644 --- a/app/services/ci/destroy_pipeline_service.rb +++ b/app/services/ci/destroy_pipeline_service.rb @@ -6,6 +6,8 @@ module Ci raise Gitlab::Access::AccessDeniedError unless can?(current_user, :destroy_pipeline, pipeline) pipeline.destroy! + + Gitlab::Cache::Ci::ProjectPipelineStatus.new(pipeline.project).delete_from_cache end end end diff --git a/app/services/clusters/applications/base_service.rb b/app/services/clusters/applications/base_service.rb index cbd1cf03ae1..14a45437287 100644 --- a/app/services/clusters/applications/base_service.rb +++ b/app/services/clusters/applications/base_service.rb @@ -41,7 +41,7 @@ module Clusters raise NotImplementedError end - def builders + def builder raise NotImplementedError end @@ -50,11 +50,27 @@ module Clusters end def instantiate_application - builder.call(@cluster) || raise(InvalidApplicationError, "invalid application: #{application_name}") + raise_invalid_application_error if invalid_application? + + builder || raise(InvalidApplicationError, "invalid application: #{application_name}") end - def builder - builders[application_name] || raise(InvalidApplicationError, "invalid application: #{application_name}") + def raise_invalid_application_error + raise(InvalidApplicationError, "invalid application: #{application_name}") + end + + def invalid_application? + unknown_application? || (!cluster.project_type? && project_only_application?) + end + + def unknown_application? + Clusters::Cluster::APPLICATIONS.keys.exclude?(application_name) + end + + # These applications will need extra configuration to enable them to work + # with groups of projects + def project_only_application? + Clusters::Cluster::PROJECT_ONLY_APPLICATIONS.include?(application_name) end def application_name diff --git a/app/services/clusters/applications/create_service.rb b/app/services/clusters/applications/create_service.rb index bd7c31bb981..ae36da7b3dd 100644 --- a/app/services/clusters/applications/create_service.rb +++ b/app/services/clusters/applications/create_service.rb @@ -9,25 +9,9 @@ module Clusters application.updateable? ? ClusterUpgradeAppWorker : ClusterInstallAppWorker end - def builders - { - "helm" => -> (cluster) { cluster.application_helm || cluster.build_application_helm }, - "ingress" => -> (cluster) { cluster.application_ingress || cluster.build_application_ingress }, - "cert_manager" => -> (cluster) { cluster.application_cert_manager || cluster.build_application_cert_manager } - }.tap do |hash| - hash.merge!(project_builders) if cluster.project_type? - end - end - - # These applications will need extra configuration to enable them to work - # with groups of projects - def project_builders - { - "prometheus" => -> (cluster) { cluster.application_prometheus || cluster.build_application_prometheus }, - "runner" => -> (cluster) { cluster.application_runner || cluster.build_application_runner }, - "jupyter" => -> (cluster) { cluster.application_jupyter || cluster.build_application_jupyter }, - "knative" => -> (cluster) { cluster.application_knative || cluster.build_application_knative } - } + def builder + cluster.method("application_#{application_name}").call || + cluster.method("build_application_#{application_name}").call end end end diff --git a/app/services/clusters/applications/update_service.rb b/app/services/clusters/applications/update_service.rb index a9d4e609992..5071c31839c 100644 --- a/app/services/clusters/applications/update_service.rb +++ b/app/services/clusters/applications/update_service.rb @@ -9,25 +9,8 @@ module Clusters ClusterPatchAppWorker end - def builders - { - "helm" => -> (cluster) { cluster.application_helm }, - "ingress" => -> (cluster) { cluster.application_ingress }, - "cert_manager" => -> (cluster) { cluster.application_cert_manager } - }.tap do |hash| - hash.merge!(project_builders) if cluster.project_type? - end - end - - # These applications will need extra configuration to enable them to work - # with groups of projects - def project_builders - { - "prometheus" => -> (cluster) { cluster.application_prometheus }, - "runner" => -> (cluster) { cluster.application_runner }, - "jupyter" => -> (cluster) { cluster.application_jupyter }, - "knative" => -> (cluster) { cluster.application_knative } - } + def builder + cluster.method("application_#{application_name}").call end end end diff --git a/app/services/concerns/suggestible.rb b/app/services/concerns/suggestible.rb new file mode 100644 index 00000000000..0b9822b1909 --- /dev/null +++ b/app/services/concerns/suggestible.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module Suggestible + extend ActiveSupport::Concern + + # This translates into limiting suggestion changes to `suggestion:-100+100`. + MAX_LINES_CONTEXT = 100.freeze + + def fetch_from_content + diff_file.new_blob_lines_between(from_line, to_line).join + end + + def from_line + real_above = [lines_above, MAX_LINES_CONTEXT].min + [target_line - real_above, 1].max + end + + def to_line + real_below = [lines_below, MAX_LINES_CONTEXT].min + target_line + real_below + end + + def diff_file + raise NotImplementedError + end + + def target_line + raise NotImplementedError + end +end diff --git a/app/services/concerns/users/participable_service.rb b/app/services/concerns/users/participable_service.rb index 6713b6617ae..a3cc6014fd3 100644 --- a/app/services/concerns/users/participable_service.rb +++ b/app/services/concerns/users/participable_service.rb @@ -28,19 +28,35 @@ module Users end def groups - current_user.authorized_groups.sort_by(&:path).map do |group| - group_as_hash(group) + group_counts = GroupMember + .in_groups(current_user.authorized_groups) + .non_request + .count_users_by_group_id + + current_user.authorized_groups.with_route.sort_by(&:path).map do |group| + group_as_hash(group, group_counts) end end private def user_as_hash(user) - { type: user.class.name, username: user.username, name: user.name, avatar_url: user.avatar_url } + { + type: user.class.name, + username: user.username, + name: user.name, + avatar_url: user.avatar_url + } end - def group_as_hash(group) - { type: group.class.name, username: group.full_path, name: group.full_name, avatar_url: group.avatar_url, count: group.users.count } + def group_as_hash(group, group_counts) + { + type: group.class.name, + username: group.full_path, + name: group.full_name, + avatar_url: group.avatar_url, + count: group_counts.fetch(group.id, 0) + } end end end diff --git a/app/services/error_tracking/list_issues_service.rb b/app/services/error_tracking/list_issues_service.rb index a6c6bec9598..86ab21fa865 100644 --- a/app/services/error_tracking/list_issues_service.rb +++ b/app/services/error_tracking/list_issues_service.rb @@ -18,7 +18,7 @@ module ErrorTracking end if result[:error].present? - return error(result[:error], :bad_request) + return error(result[:error], http_status_from_error_type(result[:error_type])) end success(issues: result[:issues]) @@ -30,6 +30,15 @@ module ErrorTracking private + def http_status_from_error_type(error_type) + case error_type + when ErrorTracking::ProjectErrorTrackingSetting::SENTRY_API_ERROR_TYPE_MISSING_KEYS + :internal_server_error + else + :bad_request + end + end + def project_error_tracking_setting project.error_tracking_setting end diff --git a/app/services/git/branch_push_service.rb b/app/services/git/branch_push_service.rb new file mode 100644 index 00000000000..b55aeb5f2b9 --- /dev/null +++ b/app/services/git/branch_push_service.rb @@ -0,0 +1,244 @@ +# frozen_string_literal: true + +module Git + class BranchPushService < BaseService + attr_accessor :push_data, :push_commits + include Gitlab::Access + include Gitlab::Utils::StrongMemoize + + # The N most recent commits to process in a single push payload. + PROCESS_COMMIT_LIMIT = 100 + + # This method will be called after each git update + # and only if the provided user and project are present in GitLab. + # + # All callbacks for post receive action should be placed here. + # + # Next, this method: + # 1. Creates the push event + # 2. Updates merge requests + # 3. Recognizes cross-references from commit messages + # 4. Executes the project's webhooks + # 5. Executes the project's services + # 6. Checks if the project's main language has changed + # + def execute + update_commits + execute_related_hooks + perform_housekeeping + + update_remote_mirrors + update_caches + + update_signatures + end + + def update_commits + project.repository.after_create if project.empty_repo? + project.repository.after_push_commit(branch_name) + + if push_remove_branch? + project.repository.after_remove_branch + @push_commits = [] + elsif push_to_new_branch? + project.repository.after_create_branch + + # Re-find the pushed commits. + if default_branch? + # Initial push to the default branch. Take the full history of that branch as "newly pushed". + process_default_branch + else + # Use the pushed commits that aren't reachable by the default branch + # as a heuristic. This may include more commits than are actually pushed, but + # that shouldn't matter because we check for existing cross-references later. + @push_commits = project.repository.commits_between(project.default_branch, params[:newrev]) + + # don't process commits for the initial push to the default branch + process_commit_messages + end + elsif push_to_existing_branch? + # Collect data for this git push + @push_commits = project.repository.commits_between(params[:oldrev], params[:newrev]) + + process_commit_messages + + # Update the bare repositories info/attributes file using the contents of the default branches + # .gitattributes file + update_gitattributes if default_branch? + end + end + + def update_gitattributes + project.repository.copy_gitattributes(params[:ref]) + end + + def update_caches + if default_branch? + if push_to_new_branch? + # If this is the initial push into the default branch, the file type caches + # will already be reset as a result of `Project#change_head`. + types = [] + else + paths = Set.new + + last_pushed_commits.each do |commit| + commit.raw_deltas.each do |diff| + paths << diff.new_path + end + end + + types = Gitlab::FileDetector.types_in_paths(paths.to_a) + end + + DetectRepositoryLanguagesWorker.perform_async(@project.id, current_user.id) + else + types = [] + end + + ProjectCacheWorker.perform_async(project.id, types, [:commit_count, :repository_size]) + end + + # rubocop: disable CodeReuse/ActiveRecord + def update_signatures + commit_shas = last_pushed_commits.map(&:sha) + + return if commit_shas.empty? + + shas_with_cached_signatures = GpgSignature.where(commit_sha: commit_shas).pluck(:commit_sha) + commit_shas -= shas_with_cached_signatures + + return if commit_shas.empty? + + commit_shas = Gitlab::Git::Commit.shas_with_signatures(project.repository, commit_shas) + + CreateGpgSignatureWorker.perform_async(commit_shas, project.id) + end + # rubocop: enable CodeReuse/ActiveRecord + + # Schedules processing of commit messages. + def process_commit_messages + default = default_branch? + + last_pushed_commits.each do |commit| + if commit.matches_cross_reference_regex? + ProcessCommitWorker + .perform_async(project.id, current_user.id, commit.to_hash, default) + end + end + end + + def update_remote_mirrors + return unless project.has_remote_mirror? + + project.mark_stuck_remote_mirrors_as_failed! + project.update_remote_mirrors + end + + def execute_related_hooks + # Update merge requests that may be affected by this push. A new branch + # could cause the last commit of a merge request to change. + # + UpdateMergeRequestsWorker + .perform_async(project.id, current_user.id, params[:oldrev], params[:newrev], params[:ref]) + + EventCreateService.new.push(project, current_user, build_push_data) + Ci::CreatePipelineService.new(project, current_user, build_push_data).execute(:push, pipeline_options) + + project.execute_hooks(build_push_data.dup, :push_hooks) + project.execute_services(build_push_data.dup, :push_hooks) + + if push_remove_branch? + AfterBranchDeleteService + .new(project, current_user) + .execute(branch_name) + end + end + + def perform_housekeeping + housekeeping = Projects::HousekeepingService.new(project) + housekeeping.increment! + housekeeping.execute if housekeeping.needed? + rescue Projects::HousekeepingService::LeaseTaken + end + + def process_default_branch + offset = [push_commits_count_for_ref - PROCESS_COMMIT_LIMIT, 0].max + @push_commits = project.repository.commits(params[:newrev], offset: offset, limit: PROCESS_COMMIT_LIMIT) + + project.after_create_default_branch + end + + def build_push_data + @push_data ||= Gitlab::DataBuilder::Push.build( + project, + current_user, + params[:oldrev], + params[:newrev], + params[:ref], + @push_commits, + commits_count: commits_count, + push_options: params[:push_options] || [] + ) + end + + def push_to_existing_branch? + # Return if this is not a push to a branch (e.g. new commits) + branch_ref? && !Gitlab::Git.blank_ref?(params[:oldrev]) + end + + def push_to_new_branch? + strong_memoize(:push_to_new_branch) do + branch_ref? && Gitlab::Git.blank_ref?(params[:oldrev]) + end + end + + def push_remove_branch? + strong_memoize(:push_remove_branch) do + branch_ref? && Gitlab::Git.blank_ref?(params[:newrev]) + end + end + + def default_branch? + branch_ref? && + (branch_name == project.default_branch || project.default_branch.nil?) + end + + def commit_user(commit) + commit.author || current_user + end + + def branch_name + strong_memoize(:branch_name) do + Gitlab::Git.ref_name(params[:ref]) + end + end + + def branch_ref? + strong_memoize(:branch_ref) do + Gitlab::Git.branch_ref?(params[:ref]) + end + end + + def commits_count + return push_commits_count_for_ref if default_branch? && push_to_new_branch? + + Array(@push_commits).size + end + + def push_commits_count_for_ref + strong_memoize(:push_commits_count_for_ref) do + project.repository.commit_count_for_ref(params[:ref]) + end + end + + def last_pushed_commits + @last_pushed_commits ||= @push_commits.last(PROCESS_COMMIT_LIMIT) + end + + private + + def pipeline_options + {} # to be overridden in EE + end + end +end diff --git a/app/services/git/tag_push_service.rb b/app/services/git/tag_push_service.rb new file mode 100644 index 00000000000..318dfd4f886 --- /dev/null +++ b/app/services/git/tag_push_service.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +module Git + class TagPushService < BaseService + attr_accessor :push_data + + def execute + project.repository.after_create if project.empty_repo? + project.repository.before_push_tag + + @push_data = build_push_data + + EventCreateService.new.push(project, current_user, push_data) + Ci::CreatePipelineService.new(project, current_user, push_data).execute(:push, pipeline_options) + + SystemHooksService.new.execute_hooks(build_system_push_data, :tag_push_hooks) + project.execute_hooks(push_data.dup, :tag_push_hooks) + project.execute_services(push_data.dup, :tag_push_hooks) + + ProjectCacheWorker.perform_async(project.id, [], [:commit_count, :repository_size]) + + true + end + + private + + def build_push_data + commits = [] + message = nil + + unless Gitlab::Git.blank_ref?(params[:newrev]) + tag_name = Gitlab::Git.ref_name(params[:ref]) + tag = project.repository.find_tag(tag_name) + + if tag && tag.target == params[:newrev] + commit = project.commit(tag.dereferenced_target) + commits = [commit].compact + message = tag.message + end + end + + Gitlab::DataBuilder::Push.build( + project, + current_user, + params[:oldrev], + params[:newrev], + params[:ref], + commits, + message, + push_options: params[:push_options] || []) + end + + def build_system_push_data + Gitlab::DataBuilder::Push.build( + project, + current_user, + params[:oldrev], + params[:newrev], + params[:ref], + [], + '') + end + + def pipeline_options + {} # to be overridden in EE + end + end +end diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb deleted file mode 100644 index f387c749a21..00000000000 --- a/app/services/git_push_service.rb +++ /dev/null @@ -1,240 +0,0 @@ -# frozen_string_literal: true - -class GitPushService < BaseService - attr_accessor :push_data, :push_commits - include Gitlab::Access - include Gitlab::Utils::StrongMemoize - - # The N most recent commits to process in a single push payload. - PROCESS_COMMIT_LIMIT = 100 - - # This method will be called after each git update - # and only if the provided user and project are present in GitLab. - # - # All callbacks for post receive action should be placed here. - # - # Next, this method: - # 1. Creates the push event - # 2. Updates merge requests - # 3. Recognizes cross-references from commit messages - # 4. Executes the project's webhooks - # 5. Executes the project's services - # 6. Checks if the project's main language has changed - # - def execute - project.repository.after_create if project.empty_repo? - project.repository.after_push_commit(branch_name) - - if push_remove_branch? - project.repository.after_remove_branch - @push_commits = [] - elsif push_to_new_branch? - project.repository.after_create_branch - - # Re-find the pushed commits. - if default_branch? - # Initial push to the default branch. Take the full history of that branch as "newly pushed". - process_default_branch - else - # Use the pushed commits that aren't reachable by the default branch - # as a heuristic. This may include more commits than are actually pushed, but - # that shouldn't matter because we check for existing cross-references later. - @push_commits = project.repository.commits_between(project.default_branch, params[:newrev]) - - # don't process commits for the initial push to the default branch - process_commit_messages - end - elsif push_to_existing_branch? - # Collect data for this git push - @push_commits = project.repository.commits_between(params[:oldrev], params[:newrev]) - - process_commit_messages - - # Update the bare repositories info/attributes file using the contents of the default branches - # .gitattributes file - update_gitattributes if default_branch? - end - - execute_related_hooks - perform_housekeeping - - update_remote_mirrors - update_caches - - update_signatures - end - - def update_gitattributes - project.repository.copy_gitattributes(params[:ref]) - end - - def update_caches - if default_branch? - if push_to_new_branch? - # If this is the initial push into the default branch, the file type caches - # will already be reset as a result of `Project#change_head`. - types = [] - else - paths = Set.new - - last_pushed_commits.each do |commit| - commit.raw_deltas.each do |diff| - paths << diff.new_path - end - end - - types = Gitlab::FileDetector.types_in_paths(paths.to_a) - end - - DetectRepositoryLanguagesWorker.perform_async(@project.id, current_user.id) - else - types = [] - end - - ProjectCacheWorker.perform_async(project.id, types, [:commit_count, :repository_size]) - end - - # rubocop: disable CodeReuse/ActiveRecord - def update_signatures - commit_shas = last_pushed_commits.map(&:sha) - - return if commit_shas.empty? - - shas_with_cached_signatures = GpgSignature.where(commit_sha: commit_shas).pluck(:commit_sha) - commit_shas -= shas_with_cached_signatures - - return if commit_shas.empty? - - commit_shas = Gitlab::Git::Commit.shas_with_signatures(project.repository, commit_shas) - - CreateGpgSignatureWorker.perform_async(commit_shas, project.id) - end - # rubocop: enable CodeReuse/ActiveRecord - - # Schedules processing of commit messages. - def process_commit_messages - default = default_branch? - - last_pushed_commits.each do |commit| - if commit.matches_cross_reference_regex? - ProcessCommitWorker - .perform_async(project.id, current_user.id, commit.to_hash, default) - end - end - end - - protected - - def update_remote_mirrors - return unless project.has_remote_mirror? - - project.mark_stuck_remote_mirrors_as_failed! - project.update_remote_mirrors - end - - def execute_related_hooks - # Update merge requests that may be affected by this push. A new branch - # could cause the last commit of a merge request to change. - # - UpdateMergeRequestsWorker - .perform_async(project.id, current_user.id, params[:oldrev], params[:newrev], params[:ref]) - - EventCreateService.new.push(project, current_user, build_push_data) - Ci::CreatePipelineService.new(project, current_user, build_push_data).execute(:push, pipeline_options) - - project.execute_hooks(build_push_data.dup, :push_hooks) - project.execute_services(build_push_data.dup, :push_hooks) - - if push_remove_branch? - AfterBranchDeleteService - .new(project, current_user) - .execute(branch_name) - end - end - - def perform_housekeeping - housekeeping = Projects::HousekeepingService.new(project) - housekeeping.increment! - housekeeping.execute if housekeeping.needed? - rescue Projects::HousekeepingService::LeaseTaken - end - - def process_default_branch - offset = [push_commits_count_for_ref - PROCESS_COMMIT_LIMIT, 0].max - @push_commits = project.repository.commits(params[:newrev], offset: offset, limit: PROCESS_COMMIT_LIMIT) - - project.after_create_default_branch - end - - def build_push_data - @push_data ||= Gitlab::DataBuilder::Push.build( - project, - current_user, - params[:oldrev], - params[:newrev], - params[:ref], - @push_commits, - commits_count: commits_count, - push_options: params[:push_options] || []) - end - - def push_to_existing_branch? - # Return if this is not a push to a branch (e.g. new commits) - branch_ref? && !Gitlab::Git.blank_ref?(params[:oldrev]) - end - - def push_to_new_branch? - strong_memoize(:push_to_new_branch) do - branch_ref? && Gitlab::Git.blank_ref?(params[:oldrev]) - end - end - - def push_remove_branch? - strong_memoize(:push_remove_branch) do - branch_ref? && Gitlab::Git.blank_ref?(params[:newrev]) - end - end - - def default_branch? - branch_ref? && - (branch_name == project.default_branch || project.default_branch.nil?) - end - - def commit_user(commit) - commit.author || current_user - end - - def branch_name - strong_memoize(:branch_name) do - Gitlab::Git.ref_name(params[:ref]) - end - end - - def branch_ref? - strong_memoize(:branch_ref) do - Gitlab::Git.branch_ref?(params[:ref]) - end - end - - def commits_count - return push_commits_count_for_ref if default_branch? && push_to_new_branch? - - Array(@push_commits).size - end - - def push_commits_count_for_ref - strong_memoize(:push_commits_count_for_ref) do - project.repository.commit_count_for_ref(params[:ref]) - end - end - - def last_pushed_commits - @last_pushed_commits ||= @push_commits.last(PROCESS_COMMIT_LIMIT) - end - - private - - def pipeline_options - {} # to be overridden in EE - end -end diff --git a/app/services/git_tag_push_service.rb b/app/services/git_tag_push_service.rb deleted file mode 100644 index e39b3603c6c..00000000000 --- a/app/services/git_tag_push_service.rb +++ /dev/null @@ -1,66 +0,0 @@ -# frozen_string_literal: true - -class GitTagPushService < BaseService - attr_accessor :push_data - - def execute - project.repository.after_create if project.empty_repo? - project.repository.before_push_tag - - @push_data = build_push_data - - EventCreateService.new.push(project, current_user, push_data) - Ci::CreatePipelineService.new(project, current_user, push_data).execute(:push, pipeline_options) - - SystemHooksService.new.execute_hooks(build_system_push_data, :tag_push_hooks) - project.execute_hooks(push_data.dup, :tag_push_hooks) - project.execute_services(push_data.dup, :tag_push_hooks) - - ProjectCacheWorker.perform_async(project.id, [], [:commit_count, :repository_size]) - - true - end - - private - - def build_push_data - commits = [] - message = nil - - unless Gitlab::Git.blank_ref?(params[:newrev]) - tag_name = Gitlab::Git.ref_name(params[:ref]) - tag = project.repository.find_tag(tag_name) - - if tag && tag.target == params[:newrev] - commit = project.commit(tag.dereferenced_target) - commits = [commit].compact - message = tag.message - end - end - - Gitlab::DataBuilder::Push.build( - project, - current_user, - params[:oldrev], - params[:newrev], - params[:ref], - commits, - message, - push_options: params[:push_options] || []) - end - - def build_system_push_data - Gitlab::DataBuilder::Push.build( - project, - current_user, - params[:oldrev], - params[:newrev], - params[:ref], - [], - '') - end - - def pipeline_options - {} # to be overridden in EE - end -end diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index f35ad2a9d8b..95bd83bb03c 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -76,13 +76,11 @@ class IssuableBaseService < BaseService find_or_create_label_ids end - # rubocop: disable CodeReuse/ActiveRecord def filter_labels_in_param(key) return if params[key].to_a.empty? - params[key] = available_labels.where(id: params[key]).pluck(:id) + params[key] = available_labels.id_in(params[key]).pluck_primary_key end - # rubocop: enable CodeReuse/ActiveRecord def find_or_create_label_ids labels = params.delete(:labels) @@ -115,7 +113,7 @@ class IssuableBaseService < BaseService new_label_ids -= remove_label_ids if remove_label_ids end - new_label_ids + new_label_ids.uniq end def available_labels diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index 11ede5223e5..3e208241da5 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -54,7 +54,7 @@ module MergeRequests merge_request, merge_request.project, current_user, merge_request.assignee) end - def create_merge_request_pipeline(merge_request, user) + def create_pipeline_for(merge_request, user) return unless Feature.enabled?(:ci_merge_request_pipeline, merge_request.source_project, default_enabled: true) @@ -65,12 +65,24 @@ module MergeRequests return if merge_request.merge_request_pipeline_exists? return if merge_request.has_no_commits? - Ci::CreatePipelineService - .new(merge_request.source_project, user, ref: merge_request.source_branch) - .execute(:merge_request_event, - ignore_skip_ci: true, - save_on_errors: false, - merge_request: merge_request) + create_detached_merge_request_pipeline(merge_request, user) + end + + def create_detached_merge_request_pipeline(merge_request, user) + if can_use_merge_request_ref?(merge_request) + Ci::CreatePipelineService.new(merge_request.source_project, user, + ref: merge_request.ref_path) + .execute(:merge_request_event, merge_request: merge_request) + else + Ci::CreatePipelineService.new(merge_request.source_project, user, + ref: merge_request.source_branch) + .execute(:merge_request_event, merge_request: merge_request) + end + end + + def can_use_merge_request_ref?(merge_request) + Feature.enabled?(:ci_use_merge_request_ref, project, default_enabled: true) && + !merge_request.for_fork? end # Returns all origin and fork merge requests from `@project` satisfying passed arguments. diff --git a/app/services/merge_requests/create_service.rb b/app/services/merge_requests/create_service.rb index 02c2388c05c..06e46595b95 100644 --- a/app/services/merge_requests/create_service.rb +++ b/app/services/merge_requests/create_service.rb @@ -25,7 +25,7 @@ module MergeRequests def after_create(issuable) todo_service.new_merge_request(issuable, current_user) issuable.cache_merge_request_closes_issues!(current_user) - create_merge_request_pipeline(issuable, current_user) + create_pipeline_for(issuable, current_user) issuable.update_head_pipeline super diff --git a/app/services/merge_requests/delete_non_latest_diffs_service.rb b/app/services/merge_requests/delete_non_latest_diffs_service.rb index d5929446122..bdb7ec8a7c2 100644 --- a/app/services/merge_requests/delete_non_latest_diffs_service.rb +++ b/app/services/merge_requests/delete_non_latest_diffs_service.rb @@ -8,15 +8,13 @@ module MergeRequests @merge_request = merge_request end - # rubocop: disable CodeReuse/ActiveRecord def execute diffs = @merge_request.non_latest_diffs.with_files diffs.each_batch(of: BATCH_SIZE) do |relation, index| - ids = relation.pluck(:id).map { |id| [id] } + ids = relation.pluck_primary_key.map { |id| [id] } DeleteDiffFilesWorker.bulk_perform_in(index * 5.minutes, ids) end end - # rubocop: enable CodeReuse/ActiveRecord end end diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb index 8241e408ce5..d8a78001b79 100644 --- a/app/services/merge_requests/merge_service.rb +++ b/app/services/merge_requests/merge_service.rb @@ -76,8 +76,8 @@ module MergeRequests def try_merge repository.merge(current_user, source, merge_request, commit_message) rescue Gitlab::Git::PreReceiveError => e - handle_merge_error(log_message: e.message) - raise_error('Something went wrong during merge pre-receive hook') + raise MergeError, + "Something went wrong during merge pre-receive hook. #{e.message}".strip rescue => e handle_merge_error(log_message: e.message) raise_error('Something went wrong during merge') diff --git a/app/services/merge_requests/merge_to_ref_service.rb b/app/services/merge_requests/merge_to_ref_service.rb index 69cc441f1bb..87147d90c32 100644 --- a/app/services/merge_requests/merge_to_ref_service.rb +++ b/app/services/merge_requests/merge_to_ref_service.rb @@ -41,9 +41,7 @@ module MergeRequests super error = - if Feature.disabled?(:merge_to_tmp_merge_ref_path, project) - 'Feature is not enabled' - elsif !hooks_validation_pass?(merge_request) + if !hooks_validation_pass?(merge_request) hooks_validation_error(merge_request) elsif !@merge_request.mergeable_to_ref? "Merge request is not mergeable to #{target_ref}" diff --git a/app/services/merge_requests/migrate_external_diffs_service.rb b/app/services/merge_requests/migrate_external_diffs_service.rb new file mode 100644 index 00000000000..16050244637 --- /dev/null +++ b/app/services/merge_requests/migrate_external_diffs_service.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module MergeRequests + class MigrateExternalDiffsService < ::BaseService + MAX_JOBS = 1000.freeze + + attr_reader :diff + + def self.enqueue! + ids = MergeRequestDiff.ids_for_external_storage_migration(limit: MAX_JOBS) + + MigrateExternalDiffsWorker.bulk_perform_async(ids.map { |id| [id] }) + end + + def initialize(merge_request_diff) + @diff = merge_request_diff + end + + def execute + diff.migrate_files_to_external_storage! + end + end +end diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb index f712b8863cd..51d27673787 100644 --- a/app/services/merge_requests/refresh_service.rb +++ b/app/services/merge_requests/refresh_service.rb @@ -20,6 +20,7 @@ module MergeRequests close_upon_missing_source_branch_ref post_merge_manually_merged reload_merge_requests + outdate_suggestions reset_merge_when_pipeline_succeeds mark_pending_todos_done cache_merge_requests_closing_issues @@ -106,7 +107,7 @@ module MergeRequests end merge_request.mark_as_unchecked - create_merge_request_pipeline(merge_request, current_user) + create_pipeline_for(merge_request, current_user) UpdateHeadPipelineForMergeRequestWorker.perform_async(merge_request.id) end @@ -125,6 +126,14 @@ module MergeRequests merge_request.source_branch == @push.branch_name end + def outdate_suggestions + outdate_service = Suggestions::OutdateService.new + + merge_requests_for_source_branch.each do |merge_request| + outdate_service.execute(merge_request) + end + end + def reset_merge_when_pipeline_succeeds merge_requests_for_source_branch.each(&:reset_merge_when_pipeline_succeeds) end diff --git a/app/services/milestones/promote_service.rb b/app/services/milestones/promote_service.rb index cbe5996e8ca..f6c04a7bae2 100644 --- a/app/services/milestones/promote_service.rb +++ b/app/services/milestones/promote_service.rb @@ -26,17 +26,15 @@ module Milestones private - # rubocop: disable CodeReuse/ActiveRecord def milestone_ids_for_merge(group_milestone) # Pluck need to be used here instead of select so the array of ids # is persistent after old milestones gets deleted. @milestone_ids_for_merge ||= begin search_params = { title: group_milestone.title, project_ids: group_project_ids, state: 'all' } milestones = MilestonesFinder.new(search_params).execute - milestones.pluck(:id) + milestones.pluck_primary_key end end - # rubocop: enable CodeReuse/ActiveRecord def move_children_to_group_milestone(group_milestone) milestone_ids_for_merge(group_milestone).in_groups_of(100, false) do |milestone_ids| diff --git a/app/services/projects/after_import_service.rb b/app/services/projects/after_import_service.rb index bbdde4408d2..afb9048e87b 100644 --- a/app/services/projects/after_import_service.rb +++ b/app/services/projects/after_import_service.rb @@ -9,7 +9,7 @@ module Projects end def execute - Projects::HousekeepingService.new(@project).execute do + Projects::HousekeepingService.new(@project, :gc).execute do repository.delete_all_refs_except(RESERVED_REF_PREFIXES) end rescue Projects::HousekeepingService::LeaseTaken => e diff --git a/app/services/projects/housekeeping_service.rb b/app/services/projects/housekeeping_service.rb index 2f6dc4207dd..10bd5363b51 100644 --- a/app/services/projects/housekeeping_service.rb +++ b/app/services/projects/housekeeping_service.rb @@ -18,8 +18,9 @@ module Projects end end - def initialize(project) + def initialize(project, task = nil) @project = project + @task = task end def execute @@ -69,6 +70,8 @@ module Projects end def task + return @task if @task + if pushes_since_gc % gc_period == 0 :gc elsif pushes_since_gc % full_repack_period == 0 diff --git a/app/services/quick_actions/interpret_service.rb b/app/services/quick_actions/interpret_service.rb index 131efb8925e..f463e08ee7e 100644 --- a/app/services/quick_actions/interpret_service.rb +++ b/app/services/quick_actions/interpret_service.rb @@ -4,21 +4,24 @@ module QuickActions class InterpretService < BaseService include Gitlab::Utils::StrongMemoize include Gitlab::QuickActions::Dsl + include Gitlab::QuickActions::IssueActions + include Gitlab::QuickActions::IssueAndMergeRequestActions + include Gitlab::QuickActions::IssuableActions + include Gitlab::QuickActions::MergeRequestActions + include Gitlab::QuickActions::CommitActions + include Gitlab::QuickActions::CommonActions - attr_reader :issuable + attr_reader :quick_action_target # Counts how many commands have been executed. # Used to display relevant feedback on UI when a note # with only commands has been processed. attr_accessor :commands_executed_count - SHRUG = '¯\\_(ツ)_/¯'.freeze - TABLEFLIP = '(╯°□°)╯︵ ┻━┻'.freeze - - # Takes an issuable and returns an array of all the available commands + # Takes an quick_action_target and returns an array of all the available commands # represented with .to_h - def available_commands(issuable) - @issuable = issuable + def available_commands(quick_action_target) + @quick_action_target = quick_action_target self.class.command_definitions.map do |definition| next unless definition.available?(self) @@ -29,10 +32,10 @@ module QuickActions # Takes a text and interprets the commands that are extracted from it. # Returns the content without commands, and hash of changes to be applied to a record. - def execute(content, issuable, only: nil) + def execute(content, quick_action_target, only: nil) return [content, {}] unless current_user.can?(:use_quick_actions) - @issuable = issuable + @quick_action_target = quick_action_target @updates = {} content, commands = extractor.extract_commands(content, only: only) @@ -43,10 +46,10 @@ module QuickActions # Takes a text and interprets the commands that are extracted from it. # Returns the content without commands, and array of changes explained. - def explain(content, issuable) + def explain(content, quick_action_target) return [content, []] unless current_user.can?(:use_quick_actions) - @issuable = issuable + @quick_action_target = quick_action_target content, commands = extractor.extract_commands(content) commands = explain_commands(commands) @@ -59,598 +62,6 @@ module QuickActions Gitlab::QuickActions::Extractor.new(self.class.command_definitions) end - desc do - "Close this #{issuable.to_ability_name.humanize(capitalize: false)}" - end - explanation do - "Closes this #{issuable.to_ability_name.humanize(capitalize: false)}." - end - condition do - issuable.is_a?(Issuable) && - issuable.persisted? && - issuable.open? && - current_user.can?(:"update_#{issuable.to_ability_name}", issuable) - end - command :close do - @updates[:state_event] = 'close' - end - - desc do - "Reopen this #{issuable.to_ability_name.humanize(capitalize: false)}" - end - explanation do - "Reopens this #{issuable.to_ability_name.humanize(capitalize: false)}." - end - condition do - issuable.is_a?(Issuable) && - issuable.persisted? && - issuable.closed? && - current_user.can?(:"update_#{issuable.to_ability_name}", issuable) - end - command :reopen do - @updates[:state_event] = 'reopen' - end - - desc 'Merge (when the pipeline succeeds)' - explanation 'Merges this merge request when the pipeline succeeds.' - condition do - last_diff_sha = params && params[:merge_request_diff_head_sha] - issuable.is_a?(MergeRequest) && - issuable.persisted? && - issuable.mergeable_with_quick_action?(current_user, autocomplete_precheck: !last_diff_sha, last_diff_sha: last_diff_sha) - end - command :merge do - @updates[:merge] = params[:merge_request_diff_head_sha] - end - - desc 'Change title' - explanation do |title_param| - "Changes the title to \"#{title_param}\"." - end - params '<New title>' - condition do - issuable.persisted? && - current_user.can?(:"update_#{issuable.to_ability_name}", issuable) - end - command :title do |title_param| - @updates[:title] = title_param - end - - desc 'Assign' - # rubocop: disable CodeReuse/ActiveRecord - explanation do |users| - users = issuable.allows_multiple_assignees? ? users : users.take(1) - "Assigns #{users.map(&:to_reference).to_sentence}." - end - # rubocop: enable CodeReuse/ActiveRecord - params do - issuable.allows_multiple_assignees? ? '@user1 @user2' : '@user' - end - condition do - current_user.can?(:"admin_#{issuable.to_ability_name}", project) - end - parse_params do |assignee_param| - extract_users(assignee_param) - end - command :assign do |users| - next if users.empty? - - if issuable.allows_multiple_assignees? - @updates[:assignee_ids] ||= issuable.assignees.map(&:id) - @updates[:assignee_ids] += users.map(&:id) - else - @updates[:assignee_ids] = [users.first.id] - end - end - - desc do - if issuable.allows_multiple_assignees? - 'Remove all or specific assignee(s)' - else - 'Remove assignee' - end - end - explanation do |users = nil| - assignees = issuable.assignees - assignees &= users if users.present? && issuable.allows_multiple_assignees? - "Removes #{'assignee'.pluralize(assignees.size)} #{assignees.map(&:to_reference).to_sentence}." - end - params do - issuable.allows_multiple_assignees? ? '@user1 @user2' : '' - end - condition do - issuable.is_a?(Issuable) && - issuable.persisted? && - issuable.assignees.any? && - current_user.can?(:"admin_#{issuable.to_ability_name}", project) - end - parse_params do |unassign_param| - # When multiple users are assigned, all will be unassigned if multiple assignees are no longer allowed - extract_users(unassign_param) if issuable.allows_multiple_assignees? - end - command :unassign do |users = nil| - if issuable.allows_multiple_assignees? && users&.any? - @updates[:assignee_ids] ||= issuable.assignees.map(&:id) - @updates[:assignee_ids] -= users.map(&:id) - else - @updates[:assignee_ids] = [] - end - end - - desc 'Set milestone' - explanation do |milestone| - "Sets the milestone to #{milestone.to_reference}." if milestone - end - params '%"milestone"' - condition do - current_user.can?(:"admin_#{issuable.to_ability_name}", project) && - find_milestones(project, state: 'active').any? - end - parse_params do |milestone_param| - extract_references(milestone_param, :milestone).first || - find_milestones(project, title: milestone_param.strip).first - end - command :milestone do |milestone| - @updates[:milestone_id] = milestone.id if milestone - end - - desc 'Remove milestone' - explanation do - "Removes #{issuable.milestone.to_reference(format: :name)} milestone." - end - condition do - issuable.is_a?(Issuable) && - issuable.persisted? && - issuable.milestone_id? && - current_user.can?(:"admin_#{issuable.to_ability_name}", project) - end - command :remove_milestone do - @updates[:milestone_id] = nil - end - - desc 'Add label(s)' - explanation do |labels_param| - labels = find_label_references(labels_param) - - "Adds #{labels.join(' ')} #{'label'.pluralize(labels.count)}." if labels.any? - end - params '~label1 ~"label 2"' - condition do - parent && - current_user.can?(:"admin_#{issuable.to_ability_name}", parent) && - find_labels.any? - end - command :label do |labels_param| - label_ids = find_label_ids(labels_param) - - if label_ids.any? - @updates[:add_label_ids] ||= [] - @updates[:add_label_ids] += label_ids - - @updates[:add_label_ids].uniq! - end - end - - desc 'Remove all or specific label(s)' - explanation do |labels_param = nil| - if labels_param.present? - labels = find_label_references(labels_param) - "Removes #{labels.join(' ')} #{'label'.pluralize(labels.count)}." if labels.any? - else - 'Removes all labels.' - end - end - params '~label1 ~"label 2"' - condition do - issuable.is_a?(Issuable) && - issuable.persisted? && - issuable.labels.any? && - current_user.can?(:"admin_#{issuable.to_ability_name}", parent) - end - command :unlabel do |labels_param = nil| - if labels_param.present? - label_ids = find_label_ids(labels_param) - - if label_ids.any? - @updates[:remove_label_ids] ||= [] - @updates[:remove_label_ids] += label_ids - - @updates[:remove_label_ids].uniq! - end - else - @updates[:label_ids] = [] - end - end - - desc 'Replace all label(s)' - explanation do |labels_param| - labels = find_label_references(labels_param) - "Replaces all labels with #{labels.join(' ')} #{'label'.pluralize(labels.count)}." if labels.any? - end - params '~label1 ~"label 2"' - condition do - issuable.is_a?(Issuable) && - issuable.persisted? && - issuable.labels.any? && - current_user.can?(:"admin_#{issuable.to_ability_name}", project) - end - command :relabel do |labels_param| - label_ids = find_label_ids(labels_param) - - if label_ids.any? - @updates[:label_ids] ||= [] - @updates[:label_ids] += label_ids - - @updates[:label_ids].uniq! - end - end - - desc 'Copy labels and milestone from other issue or merge request' - explanation do |source_issuable| - "Copy labels and milestone from #{source_issuable.to_reference}." - end - params '#issue | !merge_request' - condition do - [MergeRequest, Issue].include?(issuable.class) && - current_user.can?(:"update_#{issuable.to_ability_name}", issuable) - end - parse_params do |issuable_param| - extract_references(issuable_param, :issue).first || - extract_references(issuable_param, :merge_request).first - end - command :copy_metadata do |source_issuable| - if source_issuable.present? && source_issuable.project.id == issuable.project.id - @updates[:add_label_ids] = source_issuable.labels.map(&:id) - @updates[:milestone_id] = source_issuable.milestone.id if source_issuable.milestone - end - end - - desc 'Add a todo' - explanation 'Adds a todo.' - condition do - issuable.is_a?(Issuable) && - issuable.persisted? && - !TodoService.new.todo_exist?(issuable, current_user) - end - command :todo do - @updates[:todo_event] = 'add' - end - - desc 'Mark todo as done' - explanation 'Marks todo as done.' - condition do - issuable.persisted? && - TodoService.new.todo_exist?(issuable, current_user) - end - command :done do - @updates[:todo_event] = 'done' - end - - desc 'Subscribe' - explanation do - "Subscribes to this #{issuable.to_ability_name.humanize(capitalize: false)}." - end - condition do - issuable.is_a?(Issuable) && - issuable.persisted? && - !issuable.subscribed?(current_user, project) - end - command :subscribe do - @updates[:subscription_event] = 'subscribe' - end - - desc 'Unsubscribe' - explanation do - "Unsubscribes from this #{issuable.to_ability_name.humanize(capitalize: false)}." - end - condition do - issuable.is_a?(Issuable) && - issuable.persisted? && - issuable.subscribed?(current_user, project) - end - command :unsubscribe do - @updates[:subscription_event] = 'unsubscribe' - end - - desc 'Set due date' - explanation do |due_date| - "Sets the due date to #{due_date.to_s(:medium)}." if due_date - end - params '<in 2 days | this Friday | December 31st>' - condition do - issuable.respond_to?(:due_date) && - current_user.can?(:"admin_#{issuable.to_ability_name}", project) - end - parse_params do |due_date_param| - Chronic.parse(due_date_param).try(:to_date) - end - command :due do |due_date| - @updates[:due_date] = due_date if due_date - end - - desc 'Remove due date' - explanation 'Removes the due date.' - condition do - issuable.persisted? && - issuable.respond_to?(:due_date) && - issuable.due_date? && - current_user.can?(:"admin_#{issuable.to_ability_name}", project) - end - command :remove_due_date do - @updates[:due_date] = nil - end - - desc 'Toggle the Work In Progress status' - explanation do - verb = issuable.work_in_progress? ? 'Unmarks' : 'Marks' - noun = issuable.to_ability_name.humanize(capitalize: false) - "#{verb} this #{noun} as Work In Progress." - end - condition do - issuable.respond_to?(:work_in_progress?) && - # Allow it to mark as WIP on MR creation page _or_ through MR notes. - (issuable.new_record? || current_user.can?(:"update_#{issuable.to_ability_name}", issuable)) - end - command :wip do - @updates[:wip_event] = issuable.work_in_progress? ? 'unwip' : 'wip' - end - - desc 'Toggle emoji award' - explanation do |name| - "Toggles :#{name}: emoji award." if name - end - params ':emoji:' - condition do - issuable.is_a?(Issuable) && - issuable.persisted? - end - parse_params do |emoji_param| - match = emoji_param.match(Banzai::Filter::EmojiFilter.emoji_pattern) - match[1] if match - end - command :award do |name| - if name && issuable.user_can_award?(current_user) - @updates[:emoji_award] = name - end - end - - desc 'Set time estimate' - explanation do |time_estimate| - time_estimate = Gitlab::TimeTrackingFormatter.output(time_estimate) - - "Sets time estimate to #{time_estimate}." if time_estimate - end - params '<1w 3d 2h 14m>' - condition do - current_user.can?(:"admin_#{issuable.to_ability_name}", project) - end - parse_params do |raw_duration| - Gitlab::TimeTrackingFormatter.parse(raw_duration) - end - command :estimate do |time_estimate| - if time_estimate - @updates[:time_estimate] = time_estimate - end - end - - desc 'Add or subtract spent time' - explanation do |time_spent, time_spent_date| - if time_spent - if time_spent > 0 - verb = 'Adds' - value = time_spent - else - verb = 'Subtracts' - value = -time_spent - end - - "#{verb} #{Gitlab::TimeTrackingFormatter.output(value)} spent time." - end - end - params '<time(1h30m | -1h30m)> <date(YYYY-MM-DD)>' - condition do - issuable.is_a?(TimeTrackable) && - current_user.can?(:"admin_#{issuable.to_ability_name}", issuable) - end - parse_params do |raw_time_date| - Gitlab::QuickActions::SpendTimeAndDateSeparator.new(raw_time_date).execute - end - command :spend do |time_spent, time_spent_date| - if time_spent - @updates[:spend_time] = { - duration: time_spent, - user_id: current_user.id, - spent_at: time_spent_date - } - end - end - - desc 'Remove time estimate' - explanation 'Removes time estimate.' - condition do - issuable.persisted? && - current_user.can?(:"admin_#{issuable.to_ability_name}", project) - end - command :remove_estimate do - @updates[:time_estimate] = 0 - end - - desc 'Remove spent time' - explanation 'Removes spent time.' - condition do - issuable.persisted? && - current_user.can?(:"admin_#{issuable.to_ability_name}", project) - end - command :remove_time_spent do - @updates[:spend_time] = { duration: :reset, user_id: current_user.id } - end - - desc "Append the comment with #{SHRUG}" - params '<Comment>' - substitution :shrug do |comment| - "#{comment} #{SHRUG}" - end - - desc "Append the comment with #{TABLEFLIP}" - params '<Comment>' - substitution :tableflip do |comment| - "#{comment} #{TABLEFLIP}" - end - - desc "Lock the discussion" - explanation "Locks the discussion" - condition do - [MergeRequest, Issue].include?(issuable.class) && - issuable.persisted? && - !issuable.discussion_locked? && - current_user.can?(:"admin_#{issuable.to_ability_name}", issuable) - end - command :lock do - @updates[:discussion_locked] = true - end - - desc "Unlock the discussion" - explanation "Unlocks the discussion" - condition do - [MergeRequest, Issue].include?(issuable.class) && - issuable.persisted? && - issuable.discussion_locked? && - current_user.can?(:"admin_#{issuable.to_ability_name}", issuable) - end - command :unlock do - @updates[:discussion_locked] = false - end - - # This is a dummy command, so that it appears in the autocomplete commands - desc 'CC' - params '@user' - command :cc - - desc 'Set target branch' - explanation do |branch_name| - "Sets target branch to #{branch_name}." - end - params '<Local branch name>' - condition do - issuable.respond_to?(:target_branch) && - (current_user.can?(:"update_#{issuable.to_ability_name}", issuable) || - issuable.new_record?) - end - parse_params do |target_branch_param| - target_branch_param.strip - end - command :target_branch do |branch_name| - @updates[:target_branch] = branch_name if project.repository.branch_exists?(branch_name) - end - - desc 'Move issue from one column of the board to another' - explanation do |target_list_name| - label = find_label_references(target_list_name).first - "Moves issue to #{label} column in the board." if label - end - params '~"Target column"' - condition do - issuable.is_a?(Issue) && - current_user.can?(:"update_#{issuable.to_ability_name}", issuable) && - issuable.project.boards.count == 1 - end - # rubocop: disable CodeReuse/ActiveRecord - command :board_move do |target_list_name| - label_ids = find_label_ids(target_list_name) - - if label_ids.size == 1 - label_id = label_ids.first - - # Ensure this label corresponds to a list on the board - next unless Label.on_project_boards(issuable.project_id).where(id: label_id).exists? - - @updates[:remove_label_ids] = - issuable.labels.on_project_boards(issuable.project_id).where.not(id: label_id).pluck(:id) - @updates[:add_label_ids] = [label_id] - end - end - # rubocop: enable CodeReuse/ActiveRecord - - desc 'Mark this issue as a duplicate of another issue' - explanation do |duplicate_reference| - "Marks this issue as a duplicate of #{duplicate_reference}." - end - params '#issue' - condition do - issuable.is_a?(Issue) && - issuable.persisted? && - current_user.can?(:"update_#{issuable.to_ability_name}", issuable) - end - command :duplicate do |duplicate_param| - canonical_issue = extract_references(duplicate_param, :issue).first - - if canonical_issue.present? - @updates[:canonical_issue_id] = canonical_issue.id - end - end - - desc 'Move this issue to another project.' - explanation do |path_to_project| - "Moves this issue to #{path_to_project}." - end - params 'path/to/project' - condition do - issuable.is_a?(Issue) && - issuable.persisted? && - current_user.can?(:"admin_#{issuable.to_ability_name}", project) - end - command :move do |target_project_path| - target_project = Project.find_by_full_path(target_project_path) - - if target_project.present? - @updates[:target_project] = target_project - end - end - - desc 'Make issue confidential.' - explanation do - 'Makes this issue confidential' - end - condition do - issuable.is_a?(Issue) && current_user.can?(:"admin_#{issuable.to_ability_name}", issuable) - end - command :confidential do - @updates[:confidential] = true - end - - desc 'Tag this commit.' - explanation do |tag_name, message| - with_message = %{ with "#{message}"} if message.present? - "Tags this commit to #{tag_name}#{with_message}." - end - params 'v1.2.3 <message>' - parse_params do |tag_name_and_message| - tag_name_and_message.split(' ', 2) - end - condition do - issuable.is_a?(Commit) && current_user.can?(:push_code, project) - end - command :tag do |tag_name, message| - @updates[:tag_name] = tag_name - @updates[:tag_message] = message - end - - desc 'Create a merge request.' - explanation do |branch_name = nil| - branch_text = branch_name ? "branch '#{branch_name}'" : 'a branch' - "Creates #{branch_text} and a merge request to resolve this issue" - end - params "<branch name>" - condition do - issuable.is_a?(Issue) && current_user.can?(:create_merge_request_in, project) && current_user.can?(:push_code, project) - end - command :create_merge_request do |branch_name = nil| - @updates[:create_merge_request] = { - branch_name: branch_name, - issue_iid: issuable.iid - } - end - # rubocop: disable CodeReuse/ActiveRecord def extract_users(params) return [] if params.nil? @@ -680,7 +91,7 @@ module QuickActions def group strong_memoize(:group) do - issuable.group if issuable.respond_to?(:group) + quick_action_target.group if quick_action_target.respond_to?(:group) end end diff --git a/app/services/releases/destroy_service.rb b/app/services/releases/destroy_service.rb index 8c2bc3b4e6e..f9f6101abdd 100644 --- a/app/services/releases/destroy_service.rb +++ b/app/services/releases/destroy_service.rb @@ -5,7 +5,6 @@ module Releases include Releases::Concerns def execute - return error('Tag does not exist', 404) unless existing_tag return error('Release does not exist', 404) unless release return error('Access Denied', 403) unless allowed? diff --git a/app/services/suggestions/apply_service.rb b/app/services/suggestions/apply_service.rb index f778c5aa5f5..8ba50e22b09 100644 --- a/app/services/suggestions/apply_service.rb +++ b/app/services/suggestions/apply_service.rb @@ -7,7 +7,7 @@ module Suggestions end def execute(suggestion) - unless suggestion.appliable? + unless suggestion.appliable?(cached: false) return error('Suggestion is not appliable') end @@ -15,7 +15,7 @@ module Suggestions return error('The file has been changed') end - diff_file = suggestion.note.latest_diff_file + diff_file = suggestion.diff_file unless diff_file return error('The file was not found') diff --git a/app/services/suggestions/create_service.rb b/app/services/suggestions/create_service.rb index c7ac2452c53..1d3338c1b45 100644 --- a/app/services/suggestions/create_service.rb +++ b/app/services/suggestions/create_service.rb @@ -9,52 +9,24 @@ module Suggestions def execute return unless @note.supports_suggestion? - diff_file = @note.latest_diff_file - - return unless diff_file - - suggestions = Banzai::SuggestionsParser.parse(@note.note) - - # For single line suggestion we're only looking forward to - # change the line receiving the comment. Though, in - # https://gitlab.com/gitlab-org/gitlab-ce/issues/53310 - # we'll introduce a ```suggestion:L<x>-<y>, so this will - # slightly change. - comment_line = @note.position.new_line + suggestions = Gitlab::Diff::SuggestionsParser.parse(@note.note, + project: @note.project, + position: @note.position) rows = suggestions.map.with_index do |suggestion, index| - from_content = changing_lines(diff_file, comment_line, comment_line) + creation_params = + suggestion.to_hash.slice(:from_content, + :to_content, + :lines_above, + :lines_below) - # The parsed suggestion doesn't have information about the correct - # ending characters (we may have a line break, or not), so we take - # this information from the last line being changed (last - # characters). - endline_chars = line_break_chars(from_content.lines.last) - to_content = "#{suggestion}#{endline_chars}" - - { - note_id: @note.id, - from_content: from_content, - to_content: to_content, - relative_order: index - } + creation_params.merge!(note_id: @note.id, relative_order: index) end rows.in_groups_of(100, false) do |rows| Gitlab::Database.bulk_insert('suggestions', rows) end end - - private - - def changing_lines(diff_file, from_line, to_line) - diff_file.new_blob_lines_between(from_line, to_line).join - end - - def line_break_chars(line) - match = /\r\n|\r|\n/.match(line) - match[0] if match - end end end diff --git a/app/services/suggestions/outdate_service.rb b/app/services/suggestions/outdate_service.rb new file mode 100644 index 00000000000..a33aac9f6b5 --- /dev/null +++ b/app/services/suggestions/outdate_service.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Suggestions + class OutdateService + def execute(merge_request) + # rubocop: disable CodeReuse/ActiveRecord + suggestions = merge_request.suggestions.active.includes(:note) + + suggestions.find_in_batches(batch_size: 100) do |group| + outdatable_suggestion_ids = group.select do |suggestion| + suggestion.outdated?(cached: false) + end.map(&:id) + + Suggestion.where(id: outdatable_suggestion_ids).update_all(outdated: true) + end + # rubocop: enable CodeReuse/ActiveRecord + end + end +end diff --git a/app/uploaders/object_storage.rb b/app/uploaders/object_storage.rb index dad6e85fb56..5f8b89f2a24 100644 --- a/app/uploaders/object_storage.rb +++ b/app/uploaders/object_storage.rb @@ -278,8 +278,12 @@ module ObjectStorage self.class.object_store_credentials end + # Set ACL of uploaded objects to not-public (fog-aws)[1] or no ACL at all + # (fog-google). Value is ignored by other supported backends (fog-aliyun, + # fog-openstack, fog-rackspace) + # [1]: https://github.com/fog/fog-aws/blob/daa50bb3717a462baf4d04d0e0cbfc18baacb541/lib/fog/aws/models/storage/file.rb#L152-L159 def fog_public - false + nil end def delete_migrated_file(migrated_file) diff --git a/app/validators/cluster_name_validator.rb b/app/validators/cluster_name_validator.rb index 85fd63f08e5..79c9c67ae58 100644 --- a/app/validators/cluster_name_validator.rb +++ b/app/validators/cluster_name_validator.rb @@ -5,7 +5,9 @@ # Custom validator for ClusterName. class ClusterNameValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) - if record.managed? + if record.provided_by_user? + record.errors.add(attribute, " has to be present") unless value.present? + else if record.persisted? && record.name_changed? record.errors.add(attribute, " can not be changed because it's synchronized with provider") end @@ -17,10 +19,6 @@ class ClusterNameValidator < ActiveModel::EachValidator unless value =~ Gitlab::Regex.kubernetes_namespace_regex record.errors.add(attribute, Gitlab::Regex.kubernetes_namespace_regex_message) end - else - unless value.present? - record.errors.add(attribute, " has to be present") - end end end end diff --git a/app/views/admin/runners/_runner.html.haml b/app/views/admin/runners/_runner.html.haml index ecf2b1d60ba..423472324fe 100644 --- a/app/views/admin/runners/_runner.html.haml +++ b/app/views/admin/runners/_runner.html.haml @@ -50,7 +50,7 @@ .table-mobile-header{ role: 'rowheader' }= _('Tags') .table-mobile-content - runner.tags.map(&:name).sort.each do |tag| - %span.badge.badge-primary + %span.badge.badge-primary.str-truncated.has-tooltip{ title: tag } = tag .table-section.section-10 diff --git a/app/views/clusters/clusters/_advanced_settings.html.haml b/app/views/clusters/clusters/_advanced_settings.html.haml index 7037c80aa6b..8005dcbf65f 100644 --- a/app/views/clusters/clusters/_advanced_settings.html.haml +++ b/app/views/clusters/clusters/_advanced_settings.html.haml @@ -1,5 +1,5 @@ - if can?(current_user, :admin_cluster, @cluster) - - if @cluster.managed? + - unless @cluster.provided_by_user? .append-bottom-20 %label.append-bottom-10 = s_('ClusterIntegration|Google Kubernetes Engine') diff --git a/app/views/clusters/clusters/show.html.haml b/app/views/clusters/clusters/show.html.haml index 61188c6fa0b..62b947ca40d 100644 --- a/app/views/clusters/clusters/show.html.haml +++ b/app/views/clusters/clusters/show.html.haml @@ -53,5 +53,5 @@ %button.btn.js-settings-toggle{ type: 'button' } = expanded ? _('Collapse') : _('Expand') %p= s_("ClusterIntegration|Advanced options on this Kubernetes cluster's integration") - .settings-content + .settings-content#advanced-settings-section = render 'advanced_settings' diff --git a/app/views/clusters/platforms/kubernetes/_form.html.haml b/app/views/clusters/platforms/kubernetes/_form.html.haml index 4a452b83112..b5ddca7ccb9 100644 --- a/app/views/clusters/platforms/kubernetes/_form.html.haml +++ b/app/views/clusters/platforms/kubernetes/_form.html.haml @@ -2,7 +2,7 @@ = form_errors(cluster) .form-group - - if cluster.managed? + - if cluster.read_only_kubernetes_platform_fields? %label.append-bottom-10{ for: 'cluster-name' } = s_('ClusterIntegration|Kubernetes cluster name') .input-group @@ -18,27 +18,27 @@ .form-group = platform_field.label :api_url, s_('ClusterIntegration|API URL') .input-group - = platform_field.text_field :api_url, class: 'form-control js-select-on-focus', placeholder: s_('ClusterIntegration|API URL'), readonly: cluster.managed? - - if cluster.managed? + = platform_field.text_field :api_url, class: 'form-control js-select-on-focus', placeholder: s_('ClusterIntegration|API URL'), readonly: cluster.read_only_kubernetes_platform_fields? + - if cluster.read_only_kubernetes_platform_fields? %span.input-group-append = clipboard_button(text: platform.api_url, title: s_('ClusterIntegration|Copy API URL'), class: 'input-group-text btn-default') .form-group = platform_field.label :ca_cert, s_('ClusterIntegration|CA Certificate') .input-group - = platform_field.text_area :ca_cert, class: 'form-control js-select-on-focus', placeholder: s_('ClusterIntegration|Certificate Authority bundle (PEM format)'), readonly: cluster.managed? - - if cluster.managed? + = platform_field.text_area :ca_cert, class: 'form-control js-select-on-focus', placeholder: s_('ClusterIntegration|Certificate Authority bundle (PEM format)'), readonly: cluster.read_only_kubernetes_platform_fields? + - if cluster.read_only_kubernetes_platform_fields? %span.input-group-append.clipboard-addon = clipboard_button(text: platform.ca_cert, title: s_('ClusterIntegration|Copy CA Certificate'), class: 'input-group-text btn-blank') .form-group = platform_field.label :token, s_('ClusterIntegration|Token') .input-group - = platform_field.text_field :token, class: 'form-control js-cluster-token js-select-on-focus', type: 'password', placeholder: s_('ClusterIntegration|Token'), readonly: cluster.managed? + = platform_field.text_field :token, class: 'form-control js-cluster-token js-select-on-focus', type: 'password', placeholder: s_('ClusterIntegration|Token'), readonly: cluster.read_only_kubernetes_platform_fields? %span.input-group-append %button.btn.btn-default.input-group-text.js-show-cluster-token{ type: 'button' } = s_('ClusterIntegration|Show') - - if cluster.managed? + - if cluster.read_only_kubernetes_platform_fields? = clipboard_button(text: platform.token, title: s_('ClusterIntegration|Copy Token'), class: 'btn-default') - if cluster.allow_user_defined_namespace? diff --git a/app/views/discussions/_diff_with_notes.html.haml b/app/views/discussions/_diff_with_notes.html.haml index 44c898e0fac..8a3c841de0b 100644 --- a/app/views/discussions/_diff_with_notes.html.haml +++ b/app/views/discussions/_diff_with_notes.html.haml @@ -11,8 +11,8 @@ = render "projects/diffs/file_header", diff_file: diff_file, url: discussion_path(discussion), show_toggle: false - if diff_file.text? - .diff-content.code.js-syntax-highlight - %table + .diff-content + %table.code.js-syntax-highlight - if expanded - discussions = { discussion.original_line_code => [discussion] } = render partial: "projects/diffs/line", diff --git a/app/views/help/index.html.haml b/app/views/help/index.html.haml index dfa5d820ce9..f40fdb0b86b 100644 --- a/app/views/help/index.html.haml +++ b/app/views/help/index.html.haml @@ -29,7 +29,7 @@ .row.prepend-top-default .col-md-8 - .documentation-index.wiki + .documentation-index.md = markdown(@help_index) .col-md-4 .card diff --git a/app/views/help/instance_configuration.html.haml b/app/views/help/instance_configuration.html.haml index f09e3825a4b..99576d45f76 100644 --- a/app/views/help/instance_configuration.html.haml +++ b/app/views/help/instance_configuration.html.haml @@ -1,5 +1,5 @@ - page_title 'Instance Configuration' -.wiki.documentation +.documentation.md %h1 Instance Configuration %p diff --git a/app/views/help/show.html.haml b/app/views/help/show.html.haml index c07c148a12a..dce27dee9be 100644 --- a/app/views/help/show.html.haml +++ b/app/views/help/show.html.haml @@ -1,3 +1,3 @@ - page_title @path.split("/").reverse.map(&:humanize) -.documentation.wiki.prepend-top-default +.documentation.md.prepend-top-default = markdown @markdown diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml index 506f580b246..969df69aafb 100644 --- a/app/views/help/ui.html.haml +++ b/app/views/help/ui.html.haml @@ -513,7 +513,7 @@ %h2#markdown Markdown %h4 - %code .md or .wiki and others + %code .md Markdown rendering has a bit different css and presented in next UI elements: diff --git a/app/views/layouts/nav/sidebar/_group.html.haml b/app/views/layouts/nav/sidebar/_group.html.haml index eefe86eb6b4..b950e53639a 100644 --- a/app/views/layouts/nav/sidebar/_group.html.haml +++ b/app/views/layouts/nav/sidebar/_group.html.haml @@ -45,6 +45,8 @@ %span = _('Contribution Analytics') + = render_if_exists 'layouts/nav/group_insights_link' + = render_if_exists "layouts/nav/ee/epic_link", group: @group - if group_sidebar_link?(:issues) diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml index 7b492efeb09..3a0c2b9c284 100644 --- a/app/views/layouts/nav/sidebar/_project.html.haml +++ b/app/views/layouts/nav/sidebar/_project.html.haml @@ -41,6 +41,8 @@ = link_to project_cycle_analytics_path(@project), title: _('Cycle Analytics'), class: 'shortcuts-project-cycle-analytics' do %span= _('Cycle Analytics') + = render_if_exists 'layouts/nav/project_insights_link' + - if project_nav_tab? :files = nav_link(controller: sidebar_repository_paths) do = link_to project_tree_path(@project), class: 'shortcuts-tree qa-project-menu-repo' do @@ -355,12 +357,12 @@ = link_to project_settings_repository_path(@project), title: _('Repository') do %span = _('Repository') - - if @project.feature_available?(:builds, current_user) + - if !@project.archived? && @project.feature_available?(:builds, current_user) = nav_link(controller: :ci_cd) do = link_to project_settings_ci_cd_path(@project), title: _('CI / CD') do %span = _('CI / CD') - - if settings_operations_available? + - if !@project.archived? && settings_operations_available? = nav_link(controller: [:operations]) do = link_to project_settings_operations_path(@project), title: _('Operations') do = _('Operations') diff --git a/app/views/notify/_note_email.text.erb b/app/views/notify/_note_email.text.erb index 5a67214059c..fae8fa3ccf3 100644 --- a/app/views/notify/_note_email.text.erb +++ b/app/views/notify/_note_email.text.erb @@ -1,5 +1,6 @@ <% note = local_assigns.fetch(:note, @note) -%> <% diff_limit = local_assigns.fetch(:diff_limit, nil) -%> +<% target_url = local_assigns.fetch(:target_url, @target_url) -%> <% discussion = note.discussion if note.part_of_discussion? -%> <% if discussion && !discussion.individual_note? -%> @@ -13,6 +14,9 @@ <%= " on #{discussion.file_path}" -%> <% end -%> <%= ":" -%> +<% if discussion.diff_discussion? || !discussion.new_discussion? -%> +<%= " #{target_url}" -%> +<% end -%> <% elsif Gitlab::CurrentSettings.email_author_in_body -%> diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index 1fffea08dae..02c750a92c3 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -99,10 +99,10 @@ .checkbox-icon-inline-wrapper - private_profile_label = capture do = s_("Profiles|Don't display activity-related personal information on your profiles") - = f.check_box :private_profile, label: private_profile_label + = f.check_box :private_profile, label: private_profile_label, inline: true, wrapper_class: 'mr-0' = link_to icon('question-circle'), help_page_path('user/profile/index.md', anchor: 'private-profile') %h5= s_("Profiles|Private contributions") - = f.check_box :include_private_contributions, label: 'Include private contributions on my profile' + = f.check_box :include_private_contributions, label: 'Include private contributions on my profile', wrapper_class: 'mb-2', inline: true .help-block = s_("Profiles|Choose to show contributions of private projects on your public profile without any project, repository or organization information") .prepend-top-default.append-bottom-default diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml index 830cfa80d58..10575aa68b1 100644 --- a/app/views/projects/_md_preview.html.haml +++ b/app/views/projects/_md_preview.html.haml @@ -7,7 +7,7 @@ = _('This merge request is locked.') = _('Only project members can comment.') -.md-area +.md-area.position-relative .md-header %ul.nav.nav-tabs.nav-links.clearfix %li.md-header-tab.active @@ -22,7 +22,7 @@ .md-write-holder = yield - .md.md-preview-holder.js-md-preview.hide.md-preview{ data: { url: url } } + .md.md-preview-holder.js-md-preview.hide{ data: { url: url } } .referenced-commands.hide - if referenced_users diff --git a/app/views/projects/_wiki.html.haml b/app/views/projects/_wiki.html.haml index de4653dad2c..6103d86bf5a 100644 --- a/app/views/projects/_wiki.html.haml +++ b/app/views/projects/_wiki.html.haml @@ -1,8 +1,7 @@ - if @wiki_home.present? %div{ class: container_class } - .prepend-top-default.append-bottom-default - .wiki - = render_wiki_content(@wiki_home) + .md.md-file.prepend-top-default.append-bottom-default + = render_wiki_content(@wiki_home) - else - can_create_wiki = can?(current_user, :create_wiki, @project) .landing{ class: [('row-content-block row p-0 align-items-center' if can_create_wiki), ('content-block' unless can_create_wiki)] } diff --git a/app/views/projects/artifacts/browse.html.haml b/app/views/projects/artifacts/browse.html.haml index 09295940529..6a7cb1499c5 100644 --- a/app/views/projects/artifacts/browse.html.haml +++ b/app/views/projects/artifacts/browse.html.haml @@ -4,7 +4,7 @@ = render "projects/jobs/header" - add_to_breadcrumbs(s_('CICD|Jobs'), project_jobs_path(@project)) -- add_to_breadcrumbs("##{@build.id}", project_jobs_path(@project)) +- add_to_breadcrumbs("##{@build.id}", project_job_path(@project, @build)) .tree-holder .nav-block diff --git a/app/views/projects/blob/diff.html.haml b/app/views/projects/blob/diff.html.haml index ea7a71792a3..4f3db61f688 100644 --- a/app/views/projects/blob/diff.html.haml +++ b/app/views/projects/blob/diff.html.haml @@ -15,14 +15,14 @@ %a{ href: "#", data: { linenumber: line_number_old }, disabled: true } %td.new_line.diff-line-num{ data: { linenumber: line_number_new } } %a{ href: "#", data: { linenumber: line_number_new }, disabled: true } - %td.line_content.noteable_line{ class: line_class }= line + %td.line_content{ class: line_class }= line - when :parallel %td.old_line.diff-line-num{ data: { linenumber: line_number_old } } %a{ href: "##{line_number_old}", data: { linenumber: line_number_old }, disabled: true } - %td.line_content.noteable_line.left-side{ class: line_class }= line + %td.line_content.left-side{ class: line_class }= line %td.new_line.diff-line-num{ data: { linenumber: line_number_new } } %a{ href: "##{line_number_new}", data: { linenumber: line_number_new }, disabled: true } - %td.line_content.noteable_line.right-side{ class: line_class }= line + %td.line_content.right-side{ class: line_class }= line - if @form.unfold? && @form.bottom? && @form.to < @blob.lines.size %tr.line_holder{ id: @form.to, class: line_class } diff --git a/app/views/projects/blob/preview.html.haml b/app/views/projects/blob/preview.html.haml index 66687f087ff..3e893343165 100644 --- a/app/views/projects/blob/preview.html.haml +++ b/app/views/projects/blob/preview.html.haml @@ -1,21 +1,20 @@ -.diff-file.file-holder - .diff-content - - if markup?(@blob.name) - .file-content.wiki.md{ class: ('use-csslab' if Feature.enabled?(:csslab)) } - = markup(@blob.name, @content) - - else - .file-content.code.js-syntax-highlight - - unless @diff_lines.empty? - %table.text-file - - @diff_lines.each do |line| - %tr.line_holder{ class: "#{line.type}" } - - if line.type == "match" - %td.old_line.diff-line-num= "..." - %td.new_line.diff-line-num= "..." - %td.line_content.match= line.text - - else - %td.old_line.diff-line-num - %td.new_line.diff-line-num - %td.line_content{ class: "#{line.type}" }= diff_line_content(line.text) - - else - .nothing-here-block No changes. +- if markup?(@blob.name) + .file-content.md.md-file{ class: ('use-csslab' if Feature.enabled?(:csslab)) } + = markup(@blob.name, @content) +- else + .diff-file + .diff-content + - unless @diff_lines.empty? + %table.text-file.code.js-syntax-highlight + - @diff_lines.each do |line| + %tr.line_holder{ class: line.type } + - if line.type == "match" + %td.old_line.diff-line-num.match= "..." + %td.new_line.diff-line-num.match= "..." + %td.line_content.match= line.text + - else + %td.old_line.diff-line-num{ class: line.type } + %td.new_line.diff-line-num{ class: line.type } + %td.line_content{ class: line.type }= diff_line_content(line.text) + - else + .nothing-here-block No changes. diff --git a/app/views/projects/blob/viewers/_markup.html.haml b/app/views/projects/blob/viewers/_markup.html.haml index 1a77eb078be..abc74b66e90 100644 --- a/app/views/projects/blob/viewers/_markup.html.haml +++ b/app/views/projects/blob/viewers/_markup.html.haml @@ -1,4 +1,4 @@ - blob = viewer.blob - context = blob.respond_to?(:rendered_markup) ? { rendered: blob.rendered_markup } : {} -.file-content.wiki.md{ class: ('use-csslab' if Feature.enabled?(:csslab)) } +.file-content.md.md-file{ class: ('use-csslab' if Feature.enabled?(:csslab)) } = markup(blob.name, blob.data, context) diff --git a/app/views/projects/diffs/_line.html.haml b/app/views/projects/diffs/_line.html.haml index ffdca500abe..d35443cca1e 100644 --- a/app/views/projects/diffs/_line.html.haml +++ b/app/views/projects/diffs/_line.html.haml @@ -30,7 +30,7 @@ = link_text - else %a{ href: "##{line_code}", data: { linenumber: link_text } } - %td.line_content.noteable_line{ class: type }< + %td.line_content{ class: type }< - if email %pre= line.rich_text - else diff --git a/app/views/projects/diffs/_parallel_view.html.haml b/app/views/projects/diffs/_parallel_view.html.haml index 4b1d4b3ea17..311b0be19ab 100644 --- a/app/views/projects/diffs/_parallel_view.html.haml +++ b/app/views/projects/diffs/_parallel_view.html.haml @@ -1,7 +1,7 @@ / Side-by-side diff view -.text-file.diff-wrap-lines.code.js-syntax-highlight{ data: diff_view_data } - %table +.text-file{ data: diff_view_data } + %table.diff-wrap-lines.code.js-syntax-highlight - diff_file.parallel_diff_lines.each do |line| - left = line[:left] - right = line[:right] @@ -23,7 +23,7 @@ - discussion_left = discussions_left.try(:first) - if discussion_left && discussion_left.resolvable? %diff-note-avatars{ "discussion-id" => discussion_left.id } - %td.line_content.parallel.noteable_line.left-side{ id: left_line_code, class: left.type }= diff_line_content(left.rich_text) + %td.line_content.parallel.left-side{ id: left_line_code, class: left.type }= diff_line_content(left.rich_text) - else %td.old_line.diff-line-num.empty-cell %td.line_content.parallel.left-side @@ -44,7 +44,7 @@ - discussion_right = discussions_right.try(:first) - if discussion_right && discussion_right.resolvable? %diff-note-avatars{ "discussion-id" => discussion_right.id } - %td.line_content.parallel.noteable_line.right-side{ id: right_line_code, class: right.type }= diff_line_content(right.rich_text) + %td.line_content.parallel.right-side{ id: right_line_code, class: right.type }= diff_line_content(right.rich_text) - else %td.old_line.diff-line-num.empty-cell %td.line_content.parallel.right-side diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index 3a674da6e87..00fbfd80ce5 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -72,7 +72,7 @@ %h2.title= markdown_field(@issue, :title) - if @issue.description.present? .description{ class: can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : '' } - .wiki= markdown_field(@issue, :description) + .md= markdown_field(@issue, :description) %textarea.hidden.js-task-list-field= @issue.description = edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue-edited-ago js-issue-edited-ago') diff --git a/app/views/projects/merge_requests/_mr_box.html.haml b/app/views/projects/merge_requests/_mr_box.html.haml index 1a9ab288683..7f2c9dcacfd 100644 --- a/app/views/projects/merge_requests/_mr_box.html.haml +++ b/app/views/projects/merge_requests/_mr_box.html.haml @@ -5,7 +5,7 @@ %div - if @merge_request.description.present? .description{ class: can?(current_user, :update_merge_request, @merge_request) ? 'js-task-list-container' : '' } - .wiki + .md = markdown_field(@merge_request, :description) %textarea.hidden.js-task-list-field = @merge_request.description diff --git a/app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml b/app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml index d828cb6cf9e..03226de120d 100644 --- a/app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml +++ b/app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml @@ -1,5 +1,5 @@ %inline-conflict-lines{ "inline-template" => "true", ":file" => "file" } - %table + %table.diff-wrap-lines.code.js-syntax-highlight %tr.line_holder.diff-inline{ "v-for" => "line in file.inlineLines" } %td.diff-line-num.new_line{ ":class" => "lineCssClass(line)", "v-if" => "!line.isHeader" } %a {{line.new_line}} diff --git a/app/views/projects/merge_requests/conflicts/show.html.haml b/app/views/projects/merge_requests/conflicts/show.html.haml index 09aeb81671a..f48390aa046 100644 --- a/app/views/projects/merge_requests/conflicts/show.html.haml +++ b/app/views/projects/merge_requests/conflicts/show.html.haml @@ -26,9 +26,9 @@ %strong {{file.filePath}} = render partial: 'projects/merge_requests/conflicts/file_actions' .diff-content.diff-wrap-lines - .diff-wrap-lines.code.file-content.js-syntax-highlight{ "v-show" => "!isParallel && file.resolveMode === 'interactive' && file.type === 'text'" } + .file-content{ "v-show" => "!isParallel && file.resolveMode === 'interactive' && file.type === 'text'" } = render partial: "projects/merge_requests/conflicts/components/inline_conflict_lines" - .diff-wrap-lines.code.file-content.js-syntax-highlight{ "v-show" => "isParallel && file.resolveMode === 'interactive' && file.type === 'text'" } + .file-content{ "v-show" => "isParallel && file.resolveMode === 'interactive' && file.type === 'text'" } %parallel-conflict-lines{ ":file" => "file" } %div{ "v-show" => "file.resolveMode === 'edit' || file.type === 'text-editor'" } = render partial: "projects/merge_requests/conflicts/components/diff_file_editor" diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml index 0542b349e44..78b416edd5c 100644 --- a/app/views/projects/milestones/show.html.haml +++ b/app/views/projects/milestones/show.html.haml @@ -54,9 +54,8 @@ %div - if @milestone.description.present? - .description - .wiki - = markdown_field(@milestone, :description) + .description.md + = markdown_field(@milestone, :description) = render_if_exists 'shared/milestones/burndown', milestone: @milestone, project: @project diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml index cc203cfad86..8bfface3f5a 100644 --- a/app/views/projects/tags/_tag.html.haml +++ b/app/views/projects/tags/_tag.html.haml @@ -20,9 +20,8 @@ %p = s_("TagsPage|Can't find HEAD commit for this tag") - if release && release.description.present? - .description.prepend-top-default - .wiki - = markdown_field(release, :description) + .description.md.prepend-top-default + = markdown_field(release, :description) .row-fixed-content.controls.flex-row = render 'projects/buttons/download', project: @project, ref: tag.name, pipeline: @tags_pipelines[tag.name] diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml index 8ee1407d9d9..0be62bc5612 100644 --- a/app/views/projects/tags/show.html.haml +++ b/app/views/projects/tags/show.html.haml @@ -39,8 +39,7 @@ .append-bottom-default.prepend-top-default - if @release.description.present? - .description - .wiki - = markdown_field(@release, :description) + .description.md + = markdown_field(@release, :description) - else = s_('TagsPage|This tag has no release notes.') diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml index 8e1c054b50c..40d674f3fec 100644 --- a/app/views/projects/wikis/show.html.haml +++ b/app/views/projects/wikis/show.html.haml @@ -26,7 +26,7 @@ = (s_("WikiHistoricalPage|You can view the %{most_recent_link} or browse the %{history_link}.") % { most_recent_link: most_recent_link, history_link: history_link }).html_safe .prepend-top-default.append-bottom-default - .wiki.md{ class: ('use-csslab' if Feature.enabled?(:csslab)) } + .md.md-file{ class: ('use-csslab' if Feature.enabled?(:csslab)) } = render_wiki_content(@page) = render 'sidebar' diff --git a/app/views/search/_results.html.haml b/app/views/search/_results.html.haml index 2e62039b90a..5b25a67bc87 100644 --- a/app/views/search/_results.html.haml +++ b/app/views/search/_results.html.haml @@ -21,6 +21,8 @@ - if @scope == 'projects' .term = render 'shared/projects/list', projects: @search_objects, pipeline_status: false + - elsif %w[blobs wiki_blobs].include?(@scope) + = render partial: 'search/results/blob', collection: @search_objects, locals: { projects: blob_projects(@search_objects) } - else = render partial: "search/results/#{@scope.singularize}", collection: @search_objects diff --git a/app/views/search/results/_blob.html.haml b/app/views/search/results/_blob.html.haml index 2a602095845..bdad07f36d1 100644 --- a/app/views/search/results/_blob.html.haml +++ b/app/views/search/results/_blob.html.haml @@ -1,4 +1,4 @@ -- project = find_project_for_result_blob(blob) +- project = find_project_for_result_blob(projects, blob) - return unless project - blob = parse_search_result(blob) diff --git a/app/views/search/results/_snippet_blob.html.haml b/app/views/search/results/_snippet_blob.html.haml index a60a4501557..f17dae0a94c 100644 --- a/app/views/search/results/_snippet_blob.html.haml +++ b/app/views/search/results/_snippet_blob.html.haml @@ -18,7 +18,7 @@ %i.fa.fa-file %strong= snippet.file_name - if markup?(snippet.file_name) - .file-content.wiki + .file-content.md.md-file - snippet_chunks.each do |chunk| - unless chunk[:data].empty? = markup(snippet.file_name, chunk[:data]) diff --git a/app/views/search/results/_wiki_blob.html.haml b/app/views/search/results/_wiki_blob.html.haml index 389e4cc75b9..b351ecd4edf 100644 --- a/app/views/search/results/_wiki_blob.html.haml +++ b/app/views/search/results/_wiki_blob.html.haml @@ -1,4 +1,4 @@ -- project = find_project_for_result_blob(wiki_blob) +- project = find_project_for_result_blob(projects, wiki_blob) - wiki_blob = parse_search_result(wiki_blob) - wiki_blob_link = project_wiki_path(project, wiki_blob.basename) diff --git a/app/views/shared/milestones/_milestone.html.haml b/app/views/shared/milestones/_milestone.html.haml index e75f0a184ea..e99aa3f1ee4 100644 --- a/app/views/shared/milestones/_milestone.html.haml +++ b/app/views/shared/milestones/_milestone.html.haml @@ -52,7 +52,7 @@ = link_to 'Close Milestone', project_milestone_path(@project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-sm btn-close btn-grouped" - unless milestone.active? - = link_to 'Reopen Milestone', project_milestone_path(@project, milestone, {state_event: :activate }), method: :put, class: "btn btn-grouped btn-reopen" + = link_to 'Reopen Milestone', project_milestone_path(@project, milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-grouped btn-reopen" - if @group - if can?(current_user, :admin_milestone, @group) - if milestone.closed? diff --git a/app/views/shared/milestones/_top.html.haml b/app/views/shared/milestones/_top.html.haml index 55b1c14022f..edaeff782de 100644 --- a/app/views/shared/milestones/_top.html.haml +++ b/app/views/shared/milestones/_top.html.haml @@ -42,9 +42,8 @@ = markdown_field(milestone, :title) - if milestone.group_milestone? && milestone.description.present? %div - .description - .wiki - = markdown_field(milestone, :description) + .description.md + = markdown_field(milestone, :description) - if milestone.complete?(current_user) && milestone.active? .alert.alert-success.prepend-top-default diff --git a/app/views/shared/snippets/_header.html.haml b/app/views/shared/snippets/_header.html.haml index 0c07eae8643..ebb634fe75f 100644 --- a/app/views/shared/snippets/_header.html.haml +++ b/app/views/shared/snippets/_header.html.haml @@ -22,7 +22,7 @@ - if @snippet.description.present? .description.qa-snippet-description - .wiki + .md = markdown_field(@snippet, :description) %textarea.hidden.js-task-list-field = @snippet.description diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index 6ebd756d3da..3e8c2a1209a 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -21,6 +21,7 @@ - cronjob:trending_projects - cronjob:issue_due_scheduler - cronjob:prune_web_hook_logs +- cronjob:schedule_migrate_external_diffs - gcp_cluster:cluster_install_app - gcp_cluster:cluster_patch_app @@ -119,6 +120,7 @@ - invalid_gpg_signature_update - irker - merge +- migrate_external_diffs - namespaceless_project_destroy - new_issue - new_merge_request diff --git a/app/workers/create_gpg_signature_worker.rb b/app/workers/create_gpg_signature_worker.rb index 2827529cc1c..7fac7822cf7 100644 --- a/app/workers/create_gpg_signature_worker.rb +++ b/app/workers/create_gpg_signature_worker.rb @@ -5,8 +5,8 @@ class CreateGpgSignatureWorker # rubocop: disable CodeReuse/ActiveRecord def perform(commit_shas, project_id) - # Older versions of GitPushService may push a single commit ID on the stack. - # We need this to be backwards compatible. + # Older versions of Git::BranchPushService may push a single commit ID on + # the stack. We need this to be backwards compatible. commit_shas = Array(commit_shas) return if commit_shas.empty? diff --git a/app/workers/migrate_external_diffs_worker.rb b/app/workers/migrate_external_diffs_worker.rb new file mode 100644 index 00000000000..debac97af2c --- /dev/null +++ b/app/workers/migrate_external_diffs_worker.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: false + +class MigrateExternalDiffsWorker + include ApplicationWorker + + def perform(merge_request_diff_id) + diff = MergeRequestDiff.find_by_id(merge_request_diff_id) + return unless diff + + MergeRequests::MigrateExternalDiffsService.new(diff).execute + end +end diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb index bbd4ab159e4..396f44396a3 100644 --- a/app/workers/post_receive.rb +++ b/app/workers/post_receive.rb @@ -4,7 +4,7 @@ class PostReceive include ApplicationWorker def perform(gl_repository, identifier, changes, push_options = []) - project, is_wiki = Gitlab::GlRepository.parse(gl_repository) + project, repo_type = Gitlab::GlRepository.parse(gl_repository) if project.nil? log("Triggered hook for non-existing project with gl_repository \"#{gl_repository}\"") @@ -17,7 +17,7 @@ class PostReceive Sidekiq.logger.info "changes: #{changes.inspect}" if ENV['SIDEKIQ_LOG_ARGUMENTS'] post_received = Gitlab::GitPostReceive.new(project, identifier, changes, push_options) - if is_wiki + if repo_type.wiki? process_wiki_changes(post_received) else process_project_changes(post_received) @@ -38,7 +38,7 @@ class PostReceive post_received.changes_refs do |oldrev, newrev, ref| if Gitlab::Git.tag_ref?(ref) - GitTagPushService.new( + Git::TagPushService.new( post_received.project, @user, oldrev: oldrev, @@ -46,7 +46,7 @@ class PostReceive ref: ref, push_options: post_received.push_options).execute elsif Gitlab::Git.branch_ref?(ref) - GitPushService.new( + Git::BranchPushService.new( post_received.project, @user, oldrev: oldrev, diff --git a/app/workers/schedule_migrate_external_diffs_worker.rb b/app/workers/schedule_migrate_external_diffs_worker.rb new file mode 100644 index 00000000000..70910f7ca04 --- /dev/null +++ b/app/workers/schedule_migrate_external_diffs_worker.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: false + +class ScheduleMigrateExternalDiffsWorker + include ApplicationWorker + include CronjobQueue + include Gitlab::ExclusiveLeaseHelpers + + def perform + in_lock(self.class.name.underscore, ttl: 2.hours, retries: 0) do + MergeRequests::MigrateExternalDiffsService.enqueue! + end + rescue Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError + end +end diff --git a/changelogs/unreleased/10735-geo-gitlab-revision-can-return-not-consistent-results.yml b/changelogs/unreleased/10735-geo-gitlab-revision-can-return-not-consistent-results.yml new file mode 100644 index 00000000000..2360295b022 --- /dev/null +++ b/changelogs/unreleased/10735-geo-gitlab-revision-can-return-not-consistent-results.yml @@ -0,0 +1,5 @@ +--- +title: Use a fixed git abbrev parameter when we fetch a git revision +merge_request: 26707 +author: +type: fixed diff --git a/changelogs/unreleased/24936-remove-type-from-review-app-name.yml b/changelogs/unreleased/24936-remove-type-from-review-app-name.yml new file mode 100644 index 00000000000..639333264f6 --- /dev/null +++ b/changelogs/unreleased/24936-remove-type-from-review-app-name.yml @@ -0,0 +1,5 @@ +--- +title: Remove unnecessary folder prefix from environment name +merge_request: 25600 +author: +type: changed diff --git a/changelogs/unreleased/48132-display-output-from-pre-receive-scripts.yml b/changelogs/unreleased/48132-display-output-from-pre-receive-scripts.yml new file mode 100644 index 00000000000..e06a4d5ee75 --- /dev/null +++ b/changelogs/unreleased/48132-display-output-from-pre-receive-scripts.yml @@ -0,0 +1,5 @@ +--- +title: "Allow failed custom hook script errors to safely appear in GitLab UI by filtering error messages by the prefix GL-HOOK-ERR:" +merge_request: 25625 +author: +type: changed diff --git a/changelogs/unreleased/49856-upgrade-bootstrap-form-gem.yml b/changelogs/unreleased/49856-upgrade-bootstrap-form-gem.yml new file mode 100644 index 00000000000..1dc8d5b4179 --- /dev/null +++ b/changelogs/unreleased/49856-upgrade-bootstrap-form-gem.yml @@ -0,0 +1,5 @@ +--- +title: Upgrade bootstrap_form Gem +merge_request: 26568 +author: +type: other diff --git a/changelogs/unreleased/49910-reopening-a-closed-milestone-from-the-closed-milestones-page-fails2.yml b/changelogs/unreleased/49910-reopening-a-closed-milestone-from-the-closed-milestones-page-fails2.yml new file mode 100644 index 00000000000..68d38cd56c5 --- /dev/null +++ b/changelogs/unreleased/49910-reopening-a-closed-milestone-from-the-closed-milestones-page-fails2.yml @@ -0,0 +1,5 @@ +--- +title: Fix bug when reopening milestone from index page +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/51988-install-group-runner-on-group-cluster.yml b/changelogs/unreleased/51988-install-group-runner-on-group-cluster.yml new file mode 100644 index 00000000000..86f08dd1798 --- /dev/null +++ b/changelogs/unreleased/51988-install-group-runner-on-group-cluster.yml @@ -0,0 +1,5 @@ +--- +title: Support installing Group runner on group-level cluster +merge_request: 26260 +author: +type: added diff --git a/changelogs/unreleased/53210-add-uniq-constraints-on-issues-and-mrs-labels.yml b/changelogs/unreleased/53210-add-uniq-constraints-on-issues-and-mrs-labels.yml new file mode 100644 index 00000000000..f9cd8716b92 --- /dev/null +++ b/changelogs/unreleased/53210-add-uniq-constraints-on-issues-and-mrs-labels.yml @@ -0,0 +1,5 @@ +--- +title: add a uniq constraints on issues and mrs labels +merge_request: 25435 +author: Antoine Huret +type: fixed diff --git a/changelogs/unreleased/54670-external-diffs-when-outdated.yml b/changelogs/unreleased/54670-external-diffs-when-outdated.yml new file mode 100644 index 00000000000..2a0b9e75cb4 --- /dev/null +++ b/changelogs/unreleased/54670-external-diffs-when-outdated.yml @@ -0,0 +1,5 @@ +--- +title: 'Allow external diffs to be used conditionally' +merge_request: 25432 +author: +type: added diff --git a/changelogs/unreleased/54916-extended-tooltip-for-merge-request-links.yml b/changelogs/unreleased/54916-extended-tooltip-for-merge-request-links.yml new file mode 100644 index 00000000000..7fd0bcd1c00 --- /dev/null +++ b/changelogs/unreleased/54916-extended-tooltip-for-merge-request-links.yml @@ -0,0 +1,5 @@ +--- +title: Add extended merge request tooltip +merge_request: !25221 +author: +type: added diff --git a/changelogs/unreleased/57669-fix-bug-clicking-file-header-refreshes-page.yml b/changelogs/unreleased/57669-fix-bug-clicking-file-header-refreshes-page.yml new file mode 100644 index 00000000000..c6161870096 --- /dev/null +++ b/changelogs/unreleased/57669-fix-bug-clicking-file-header-refreshes-page.yml @@ -0,0 +1,6 @@ +--- +title: Scroll to diff file content when clicking on file header name and it is not + a link to other page +merge_request: !26422 +author: +type: fixed diff --git a/changelogs/unreleased/58793-fix-nav-links-archived-project.yml b/changelogs/unreleased/58793-fix-nav-links-archived-project.yml new file mode 100644 index 00000000000..a8250804c34 --- /dev/null +++ b/changelogs/unreleased/58793-fix-nav-links-archived-project.yml @@ -0,0 +1,5 @@ +--- +title: "Disable inaccessible navigation links upon archiving a project" +merge_request: 26020 +author: Elias Werberich +type: fixed diff --git a/changelogs/unreleased/58805-allow-incomplete-commit-data-to-be-fetched-from-collection.yml b/changelogs/unreleased/58805-allow-incomplete-commit-data-to-be-fetched-from-collection.yml new file mode 100644 index 00000000000..4377ebfdbdf --- /dev/null +++ b/changelogs/unreleased/58805-allow-incomplete-commit-data-to-be-fetched-from-collection.yml @@ -0,0 +1,5 @@ +--- +title: Fix merge commits being used as default squash commit messages +merge_request: 26445 +author: +type: fixed diff --git a/changelogs/unreleased/58971-sentry-api-keyerror.yml b/changelogs/unreleased/58971-sentry-api-keyerror.yml new file mode 100644 index 00000000000..0f195c4b4f7 --- /dev/null +++ b/changelogs/unreleased/58971-sentry-api-keyerror.yml @@ -0,0 +1,5 @@ +--- +title: Handle missing keys in sentry api response +merge_request: 26264 +author: +type: fixed diff --git a/changelogs/unreleased/59062-update-gitlab-markup-python-3.yml b/changelogs/unreleased/59062-update-gitlab-markup-python-3.yml new file mode 100644 index 00000000000..265a7e36841 --- /dev/null +++ b/changelogs/unreleased/59062-update-gitlab-markup-python-3.yml @@ -0,0 +1,5 @@ +--- +title: Update gitlab-markup to 1.7.0 which requies python3 +merge_request: 26246 +author: +type: changed diff --git a/changelogs/unreleased/59079-fix-jupyter-render-loop.yml b/changelogs/unreleased/59079-fix-jupyter-render-loop.yml new file mode 100644 index 00000000000..29264b33dfa --- /dev/null +++ b/changelogs/unreleased/59079-fix-jupyter-render-loop.yml @@ -0,0 +1,5 @@ +--- +title: Fix jupyter rendering bug that ended in an infinite loop +merge_request: 26656 +author: ROSPARS Benoit +type: fixed diff --git a/changelogs/unreleased/59273-update-fugit.yml b/changelogs/unreleased/59273-update-fugit.yml new file mode 100644 index 00000000000..3a1c64d87ef --- /dev/null +++ b/changelogs/unreleased/59273-update-fugit.yml @@ -0,0 +1,5 @@ +--- +title: Update fugit which fixes a potential infinite loop +merge_request: 26579 +author: +type: fixed diff --git a/changelogs/unreleased/59296-add-filter-by-title-milestones-api.yml b/changelogs/unreleased/59296-add-filter-by-title-milestones-api.yml new file mode 100644 index 00000000000..440b24a548c --- /dev/null +++ b/changelogs/unreleased/59296-add-filter-by-title-milestones-api.yml @@ -0,0 +1,5 @@ +--- +title: Add select by title to milestones API +merge_request: 26573 +author: +type: added diff --git a/changelogs/unreleased/59352-fix-mr-discussion-expansion.yml b/changelogs/unreleased/59352-fix-mr-discussion-expansion.yml new file mode 100644 index 00000000000..ab9ad53835c --- /dev/null +++ b/changelogs/unreleased/59352-fix-mr-discussion-expansion.yml @@ -0,0 +1,5 @@ +--- +title: Expand resolved discussion when linking to a comment in the discussion +merge_request: 26483 +author: +type: fixed diff --git a/changelogs/unreleased/59441-add-base-domain-to-cluster-api.yml b/changelogs/unreleased/59441-add-base-domain-to-cluster-api.yml new file mode 100644 index 00000000000..cb5cbba4e92 --- /dev/null +++ b/changelogs/unreleased/59441-add-base-domain-to-cluster-api.yml @@ -0,0 +1,5 @@ +--- +title: Add cluster domain to Project Cluster API +merge_request: 26735 +author: +type: other diff --git a/changelogs/unreleased/59502-fix-breadcrumb-artifacts.yml b/changelogs/unreleased/59502-fix-breadcrumb-artifacts.yml new file mode 100644 index 00000000000..da65c3bc870 --- /dev/null +++ b/changelogs/unreleased/59502-fix-breadcrumb-artifacts.yml @@ -0,0 +1,5 @@ +--- +title: Fixes job link in artifacts page breadcrumb +merge_request: 26592 +author: +type: fixed diff --git a/changelogs/unreleased/59546-fix-error-handling-for-missing-domain.yml b/changelogs/unreleased/59546-fix-error-handling-for-missing-domain.yml new file mode 100644 index 00000000000..8f0ce5d57c5 --- /dev/null +++ b/changelogs/unreleased/59546-fix-error-handling-for-missing-domain.yml @@ -0,0 +1,5 @@ +--- +title: Fix Auto DevOps missing domain error handling +merge_request: 26627 +author: +type: fixed diff --git a/changelogs/unreleased/check-mergeability-in-merge-to-ref-service.yml b/changelogs/unreleased/check-mergeability-in-merge-to-ref-service.yml new file mode 100644 index 00000000000..9f615bbb54a --- /dev/null +++ b/changelogs/unreleased/check-mergeability-in-merge-to-ref-service.yml @@ -0,0 +1,5 @@ +--- +title: Check mergeability in MergeToRefService +merge_request: 26757 +author: +type: changed diff --git a/changelogs/unreleased/delete-release-when-delete-tag.yml b/changelogs/unreleased/delete-release-when-delete-tag.yml new file mode 100644 index 00000000000..58acd449bf1 --- /dev/null +++ b/changelogs/unreleased/delete-release-when-delete-tag.yml @@ -0,0 +1,5 @@ +--- +title: Releases will now be automatically deleted when deleting corresponding tag +merge_request: 26530 +author: +type: fixed diff --git a/changelogs/unreleased/fix-container-scanning-on-k8s.yml b/changelogs/unreleased/fix-container-scanning-on-k8s.yml new file mode 100644 index 00000000000..f4500370a0b --- /dev/null +++ b/changelogs/unreleased/fix-container-scanning-on-k8s.yml @@ -0,0 +1,5 @@ +--- +title: Fix Container Scanning in Kubernetes Runners +merge_request: 26793 +author: +type: changed diff --git a/changelogs/unreleased/fix-hidden-statistics.yml b/changelogs/unreleased/fix-hidden-statistics.yml new file mode 100644 index 00000000000..4d99bd00136 --- /dev/null +++ b/changelogs/unreleased/fix-hidden-statistics.yml @@ -0,0 +1,5 @@ +--- +title: Show statistics also when repository is disabled +merge_request: 26509 +author: Peter Marko +type: fixed diff --git a/changelogs/unreleased/fix-routes-n-plus-one-in-user-autocomplete.yml b/changelogs/unreleased/fix-routes-n-plus-one-in-user-autocomplete.yml new file mode 100644 index 00000000000..ae097e859d9 --- /dev/null +++ b/changelogs/unreleased/fix-routes-n-plus-one-in-user-autocomplete.yml @@ -0,0 +1,5 @@ +--- +title: Fix some N+1s in loading routes and counting members for groups in @-autocomplete +merge_request: 26491 +author: +type: performance diff --git a/changelogs/unreleased/issue_58547.yml b/changelogs/unreleased/issue_58547.yml new file mode 100644 index 00000000000..553c752e72d --- /dev/null +++ b/changelogs/unreleased/issue_58547.yml @@ -0,0 +1,5 @@ +--- +title: Add API access check to Graphql +merge_request: 26570 +author: +type: other diff --git a/changelogs/unreleased/knative-update.yml b/changelogs/unreleased/knative-update.yml new file mode 100644 index 00000000000..e84940ae7e0 --- /dev/null +++ b/changelogs/unreleased/knative-update.yml @@ -0,0 +1,5 @@ +--- +title: Knative version bump 0.2.2 -> 0.3.0 +merge_request: 26459 +author: Chris Baumbauer +type: changed diff --git a/changelogs/unreleased/nfriend-css-updates-for-gitlab-design-system-compliance.yml b/changelogs/unreleased/nfriend-css-updates-for-gitlab-design-system-compliance.yml new file mode 100644 index 00000000000..8cde0958f7a --- /dev/null +++ b/changelogs/unreleased/nfriend-css-updates-for-gitlab-design-system-compliance.yml @@ -0,0 +1,5 @@ +--- +title: Update `border-radius` of form controls and remove extra space above page titles +merge_request: 24497 +author: +type: fixed diff --git a/changelogs/unreleased/osw-multi-line-suggestions-creation-strategy.yml b/changelogs/unreleased/osw-multi-line-suggestions-creation-strategy.yml new file mode 100644 index 00000000000..01bd7ede270 --- /dev/null +++ b/changelogs/unreleased/osw-multi-line-suggestions-creation-strategy.yml @@ -0,0 +1,5 @@ +--- +title: Implements the creation strategy for multi-line suggestions +merge_request: 26057 +author: +type: changed diff --git a/changelogs/unreleased/persist-fulll-ref-path-for-mr-pipelines.yml b/changelogs/unreleased/persist-fulll-ref-path-for-mr-pipelines.yml new file mode 100644 index 00000000000..ca42a26e8ff --- /dev/null +++ b/changelogs/unreleased/persist-fulll-ref-path-for-mr-pipelines.yml @@ -0,0 +1,5 @@ +--- +title: Create MR pipelines with `refs/merge-requests/:iid/head` +merge_request: 25504 +author: +type: changed diff --git a/changelogs/unreleased/recreate-all-diffs-on-import.yml b/changelogs/unreleased/recreate-all-diffs-on-import.yml new file mode 100644 index 00000000000..fd9124372f3 --- /dev/null +++ b/changelogs/unreleased/recreate-all-diffs-on-import.yml @@ -0,0 +1,5 @@ +--- +title: Force to recreate all MR diffs on import +merge_request: 26480 +author: +type: fixed diff --git a/changelogs/unreleased/related-mr-link-cutoff.yml b/changelogs/unreleased/related-mr-link-cutoff.yml new file mode 100644 index 00000000000..8cf77b6231a --- /dev/null +++ b/changelogs/unreleased/related-mr-link-cutoff.yml @@ -0,0 +1,5 @@ +--- +title: Don't cutoff letters in MR and Issue links +merge_request: 25910 +author: gfyoung +type: fixed diff --git a/changelogs/unreleased/sh-add-gitaly-duration-logs.yml b/changelogs/unreleased/sh-add-gitaly-duration-logs.yml new file mode 100644 index 00000000000..eea50384278 --- /dev/null +++ b/changelogs/unreleased/sh-add-gitaly-duration-logs.yml @@ -0,0 +1,5 @@ +--- +title: Log Gitaly RPC duration to api_json.log and production_json.log +merge_request: 26652 +author: +type: other diff --git a/changelogs/unreleased/sh-clear-pipeline-status-cache-upon-destroy.yml b/changelogs/unreleased/sh-clear-pipeline-status-cache-upon-destroy.yml new file mode 100644 index 00000000000..55779f0f9d3 --- /dev/null +++ b/changelogs/unreleased/sh-clear-pipeline-status-cache-upon-destroy.yml @@ -0,0 +1,5 @@ +--- +title: Clear pipeline status cache after destruction of pipeline +merge_request: 26575 +author: +type: fixed diff --git a/changelogs/unreleased/sh-fix-gitaly-find-commit-caching.yml b/changelogs/unreleased/sh-fix-gitaly-find-commit-caching.yml new file mode 100644 index 00000000000..16d349c407c --- /dev/null +++ b/changelogs/unreleased/sh-fix-gitaly-find-commit-caching.yml @@ -0,0 +1,5 @@ +--- +title: Allow ref name caching CommitService#find_commit +merge_request: 26248 +author: +type: performance diff --git a/changelogs/unreleased/sh-fix-project-branches-merge-status.yml b/changelogs/unreleased/sh-fix-project-branches-merge-status.yml new file mode 100644 index 00000000000..65f41b3faf9 --- /dev/null +++ b/changelogs/unreleased/sh-fix-project-branches-merge-status.yml @@ -0,0 +1,5 @@ +--- +title: Fix API /project/:id/branches not returning correct merge status +merge_request: 26785 +author: +type: fixed diff --git a/changelogs/unreleased/sh-force-gc-after-import.yml b/changelogs/unreleased/sh-force-gc-after-import.yml new file mode 100644 index 00000000000..755d66c1607 --- /dev/null +++ b/changelogs/unreleased/sh-force-gc-after-import.yml @@ -0,0 +1,5 @@ +--- +title: Force a full GC after importing a project +merge_request: 26803 +author: +type: performance diff --git a/changelogs/unreleased/sh-optimize-projects-api.yml b/changelogs/unreleased/sh-optimize-projects-api.yml new file mode 100644 index 00000000000..2f2459be77f --- /dev/null +++ b/changelogs/unreleased/sh-optimize-projects-api.yml @@ -0,0 +1,5 @@ +--- +title: Optimize /api/v4/projects endpoint for visibility level +merge_request: 26481 +author: +type: performance diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index eba7d2b9fb7..8d9b6624995 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -301,6 +301,10 @@ production: &base pages_domain_verification_cron_worker: cron: "*/15 * * * *" + # Periodically migrate diffs from the database to external storage + schedule_migrate_external_diffs_worker: + cron: "15 * * * *" + registry: # enabled: true # host: registry.example.com @@ -787,6 +791,10 @@ test: enabled: true external_diffs: enabled: false + # Diffs may be `always` external (the default), or they can be made external + # after they have become `outdated` (i.e., the MR is closed or a new version + # has been pushed). + # when: always # The location where external diffs are stored (default: shared/external-diffs). # storage_path: shared/external-diffs object_store: diff --git a/config/helpers/is_ee_env.js b/config/helpers/is_ee_env.js new file mode 100644 index 00000000000..1fdbca591c0 --- /dev/null +++ b/config/helpers/is_ee_env.js @@ -0,0 +1,9 @@ +const fs = require('fs'); +const path = require('path'); + +const ROOT_PATH = path.resolve(__dirname, '../..'); + +module.exports = + process.env.EE !== undefined + ? JSON.parse(process.env.EE) + : fs.existsSync(path.join(ROOT_PATH, 'ee')); diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 99bdf5a95c2..01ffcade931 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -238,6 +238,7 @@ Settings.pages.admin['certificate'] ||= '' # Settings['external_diffs'] ||= Settingslogic.new({}) Settings.external_diffs['enabled'] = false if Settings.external_diffs['enabled'].nil? +Settings.external_diffs['when'] = 'always' if Settings.external_diffs['when'].nil? Settings.external_diffs['storage_path'] = Settings.absolute(Settings.external_diffs['storage_path'] || File.join(Settings.shared['path'], 'external-diffs')) Settings.external_diffs['object_store'] = ObjectStoreSettings.parse(Settings.external_diffs['object_store']) @@ -344,6 +345,10 @@ Settings.cron_jobs['prune_web_hook_logs_worker'] ||= Settingslogic.new({}) Settings.cron_jobs['prune_web_hook_logs_worker']['cron'] ||= '0 */1 * * *' Settings.cron_jobs['prune_web_hook_logs_worker']['job_class'] = 'PruneWebHookLogsWorker' +Settings.cron_jobs['schedule_migrate_external_diffs_worker'] ||= Settingslogic.new({}) +Settings.cron_jobs['schedule_migrate_external_diffs_worker']['cron'] ||= '15 * * * *' +Settings.cron_jobs['schedule_migrate_external_diffs_worker']['job_class'] = 'ScheduleMigrateExternalDiffsWorker' + # # Sidekiq # diff --git a/config/initializers/jira.rb b/config/initializers/jira.rb new file mode 100644 index 00000000000..05f784a6a2a --- /dev/null +++ b/config/initializers/jira.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +# Changes JIRA DVCS user agent requests in order to be successfully handled +# by our API. +# +# Gitlab::Jira::Middleware is only defined on EE +# +# Use safe_constantize because the class may exist but has not been loaded yet +if "Gitlab::Jira::Middleware".safe_constantize + Rails.application.config.middleware.use(Gitlab::Jira::Middleware) +end diff --git a/config/initializers/lograge.rb b/config/initializers/lograge.rb index 164954d1293..5e790a9eccb 100644 --- a/config/initializers/lograge.rb +++ b/config/initializers/lograge.rb @@ -28,7 +28,12 @@ unless Sidekiq.server? } gitaly_calls = Gitlab::GitalyClient.get_request_count - payload[:gitaly_calls] = gitaly_calls if gitaly_calls > 0 + + if gitaly_calls > 0 + payload[:gitaly_calls] = gitaly_calls + payload[:gitaly_duration] = Gitlab::GitalyClient.query_time_ms + end + payload[:response] = event.payload[:response] if event.payload[:response] payload[Gitlab::CorrelationId::LOG_KEY] = Gitlab::CorrelationId.current_id diff --git a/config/karma.config.js b/config/karma.config.js index c30c58edc6f..7e1e89f3c10 100644 --- a/config/karma.config.js +++ b/config/karma.config.js @@ -110,7 +110,7 @@ module.exports = function(config) { frameworks: ['jasmine'], files: [ { pattern: 'spec/javascripts/test_bundle.js', watched: false }, - { pattern: 'spec/javascripts/fixtures/**/*@(.json|.html|.html.raw|.png)', included: false }, + { pattern: 'spec/javascripts/fixtures/**/*@(.json|.html|.png)', included: false }, ], preprocessors: { 'spec/javascripts/**/*.js': ['webpack', 'sourcemap'], diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml index cef123b86ae..2dc0da00919 100644 --- a/config/sidekiq_queues.yml +++ b/config/sidekiq_queues.yml @@ -89,3 +89,4 @@ - [project_daily_statistics, 1] - [import_issues_csv, 2] - [chat_notification, 2] + - [migrate_external_diffs, 1] diff --git a/config/webpack.config.js b/config/webpack.config.js index 20b3f4c0264..9a37856a99e 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -1,4 +1,3 @@ -const fs = require('fs'); const path = require('path'); const glob = require('glob'); const webpack = require('webpack'); @@ -12,10 +11,7 @@ const ROOT_PATH = path.resolve(__dirname, '..'); const CACHE_PATH = process.env.WEBPACK_CACHE_PATH || path.join(ROOT_PATH, 'tmp/cache'); const IS_PRODUCTION = process.env.NODE_ENV === 'production'; const IS_DEV_SERVER = process.argv.join(' ').indexOf('webpack-dev-server') !== -1; -const IS_EE = - process.env.EE !== undefined - ? JSON.parse(process.env.EE) - : fs.existsSync(path.join(ROOT_PATH, 'ee')); +const IS_EE = require('./helpers/is_ee_env'); const DEV_SERVER_HOST = process.env.DEV_SERVER_HOST || 'localhost'; const DEV_SERVER_PORT = parseInt(process.env.DEV_SERVER_PORT, 10) || 3808; const DEV_SERVER_LIVERELOAD = IS_DEV_SERVER && process.env.DEV_SERVER_LIVERELOAD !== 'false'; diff --git a/db/fixtures/development/25_api_personal_access_token.rb b/db/fixtures/development/25_api_personal_access_token.rb new file mode 100644 index 00000000000..a2e6c674c1f --- /dev/null +++ b/db/fixtures/development/25_api_personal_access_token.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require './spec/support/sidekiq' + +# Create an api access token for root user with the value: ypCa3Dzb23o5nvsixwPA +Gitlab::Seeder.quiet do + PersonalAccessToken.create!( + user_id: User.find_by(username: 'root').id, + name: "seeded-api-token", + scopes: ["api"], + token_digest: "/O0jfLERYT/L5gG8nfByQxqTj43TeLlRzOtJGTzRsbQ=" + ) + + print '.' +end diff --git a/db/migrate/20190222051615_add_indexes_for_merge_request_diffs_query.rb b/db/migrate/20190222051615_add_indexes_for_merge_request_diffs_query.rb new file mode 100644 index 00000000000..0048268ca6f --- /dev/null +++ b/db/migrate/20190222051615_add_indexes_for_merge_request_diffs_query.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +class AddIndexesForMergeRequestDiffsQuery < ActiveRecord::Migration[5.0] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + INDEX_SPECS = [ + [ + :merge_request_metrics, + :latest_closed_at, + { where: 'latest_closed_at IS NOT NULL' } + ], + [ + :merge_request_metrics, + [:merge_request_id, :merged_at], + { where: 'merged_at IS NOT NULL' } + ], + [ + :merge_request_diffs, + [:merge_request_id, :id], + { + name: 'index_merge_request_diffs_on_merge_request_id_and_id_partial', + where: 'NOT stored_externally OR stored_externally IS NULL' + } + ] + ].freeze + + disable_ddl_transaction! + + def up + INDEX_SPECS.each do |spec| + add_concurrent_index(*spec) + end + end + + def down + INDEX_SPECS.reverse.each do |spec| + remove_concurrent_index(*spec) + end + end +end diff --git a/db/migrate/20190228192410_add_multi_line_attributes_to_suggestion.rb b/db/migrate/20190228192410_add_multi_line_attributes_to_suggestion.rb new file mode 100644 index 00000000000..856dfc89fa3 --- /dev/null +++ b/db/migrate/20190228192410_add_multi_line_attributes_to_suggestion.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +class AddMultiLineAttributesToSuggestion < ActiveRecord::Migration[5.0] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_column_with_default :suggestions, :lines_above, :integer, default: 0, allow_null: false + add_column_with_default :suggestions, :lines_below, :integer, default: 0, allow_null: false + add_column_with_default :suggestions, :outdated, :boolean, default: false, allow_null: false + end + + def down + remove_columns :suggestions, :outdated, :lines_above, :lines_below + end +end diff --git a/db/migrate/20190315191339_create_merge_request_assignees_table.rb b/db/migrate/20190315191339_create_merge_request_assignees_table.rb new file mode 100644 index 00000000000..6fc4463f281 --- /dev/null +++ b/db/migrate/20190315191339_create_merge_request_assignees_table.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class CreateMergeRequestAssigneesTable < ActiveRecord::Migration[5.0] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + INDEX_NAME = 'index_merge_request_assignees_on_merge_request_id_and_user_id' + + def up + create_table :merge_request_assignees do |t| + t.references :user, foreign_key: { on_delete: :cascade }, index: true, null: false + t.references :merge_request, foreign_key: { on_delete: :cascade }, null: false + end + + add_index :merge_request_assignees, [:merge_request_id, :user_id], unique: true, name: INDEX_NAME + end + + def down + drop_table :merge_request_assignees + end +end diff --git a/db/post_migrate/20190322132835_schedule_populate_merge_request_assignees_table.rb b/db/post_migrate/20190322132835_schedule_populate_merge_request_assignees_table.rb new file mode 100644 index 00000000000..1ecb38e1a86 --- /dev/null +++ b/db/post_migrate/20190322132835_schedule_populate_merge_request_assignees_table.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class SchedulePopulateMergeRequestAssigneesTable < ActiveRecord::Migration[5.0] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + BATCH_SIZE = 10_000 + MIGRATION = 'PopulateMergeRequestAssigneesTable' + DELAY_INTERVAL = 8.minutes.to_i + + disable_ddl_transaction! + + def up + say 'Scheduling `PopulateMergeRequestAssigneesTable` jobs' + # We currently have ~4_500_000 merge request records on GitLab.com. + # This means it'll schedule ~450 jobs (10k MRs each) with a 8 minutes gap, + # so this should take ~60 hours for all background migrations to complete. + queue_background_migration_jobs_by_range_at_intervals(MergeRequest, MIGRATION, DELAY_INTERVAL, batch_size: BATCH_SIZE) + end +end diff --git a/db/schema.rb b/db/schema.rb index dda0445e3f2..7cc09e56285 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20190301182457) do +ActiveRecord::Schema.define(version: 20190322132835) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -1201,6 +1201,14 @@ ActiveRecord::Schema.define(version: 20190301182457) do t.index ["user_id"], name: "index_members_on_user_id", using: :btree end + create_table "merge_request_assignees", force: :cascade do |t| + t.integer "user_id", null: false + t.integer "merge_request_id", null: false + t.index ["merge_request_id", "user_id"], name: "index_merge_request_assignees_on_merge_request_id_and_user_id", unique: true, using: :btree + t.index ["merge_request_id"], name: "index_merge_request_assignees_on_merge_request_id", using: :btree + t.index ["user_id"], name: "index_merge_request_assignees_on_user_id", using: :btree + end + create_table "merge_request_diff_commits", id: false, force: :cascade do |t| t.datetime_with_timezone "authored_date" t.datetime_with_timezone "committed_date" @@ -1248,6 +1256,7 @@ ActiveRecord::Schema.define(version: 20190301182457) do t.integer "external_diff_store" t.boolean "stored_externally" t.index ["merge_request_id", "id"], name: "index_merge_request_diffs_on_merge_request_id_and_id", using: :btree + t.index ["merge_request_id", "id"], name: "index_merge_request_diffs_on_merge_request_id_and_id_partial", where: "((NOT stored_externally) OR (stored_externally IS NULL))", using: :btree end create_table "merge_request_metrics", force: :cascade do |t| @@ -1263,7 +1272,9 @@ ActiveRecord::Schema.define(version: 20190301182457) do t.integer "latest_closed_by_id" t.datetime_with_timezone "latest_closed_at" t.index ["first_deployed_to_production_at"], name: "index_merge_request_metrics_on_first_deployed_to_production_at", using: :btree + t.index ["latest_closed_at"], name: "index_merge_request_metrics_on_latest_closed_at", where: "(latest_closed_at IS NOT NULL)", using: :btree t.index ["latest_closed_by_id"], name: "index_merge_request_metrics_on_latest_closed_by_id", using: :btree + t.index ["merge_request_id", "merged_at"], name: "index_merge_request_metrics_on_merge_request_id_and_merged_at", where: "(merged_at IS NOT NULL)", using: :btree t.index ["merge_request_id"], name: "index_merge_request_metrics", using: :btree t.index ["merged_by_id"], name: "index_merge_request_metrics_on_merged_by_id", using: :btree t.index ["pipeline_id"], name: "index_merge_request_metrics_on_pipeline_id", using: :btree @@ -2031,6 +2042,9 @@ ActiveRecord::Schema.define(version: 20190301182457) do t.string "commit_id" t.text "from_content", null: false t.text "to_content", null: false + t.integer "lines_above", default: 0, null: false + t.integer "lines_below", default: 0, null: false + t.boolean "outdated", default: false, null: false t.index ["note_id", "relative_order"], name: "index_suggestions_on_note_id_and_relative_order", unique: true, using: :btree end @@ -2454,6 +2468,8 @@ ActiveRecord::Schema.define(version: 20190301182457) do add_foreign_key "lists", "boards", name: "fk_0d3f677137", on_delete: :cascade add_foreign_key "lists", "labels", name: "fk_7a5553d60f", on_delete: :cascade add_foreign_key "members", "users", name: "fk_2e88fb7ce9", on_delete: :cascade + add_foreign_key "merge_request_assignees", "merge_requests", on_delete: :cascade + add_foreign_key "merge_request_assignees", "users", on_delete: :cascade add_foreign_key "merge_request_diff_commits", "merge_request_diffs", on_delete: :cascade add_foreign_key "merge_request_diff_files", "merge_request_diffs", on_delete: :cascade add_foreign_key "merge_request_diffs", "merge_requests", name: "fk_8483f3258f", on_delete: :cascade diff --git a/doc/README.md b/doc/README.md index ecc214d97c8..aead50ea97e 100644 --- a/doc/README.md +++ b/doc/README.md @@ -222,7 +222,7 @@ The following documentation relates to the DevOps **Verify** stage: |:---------------------------------------------------|:-----------------------------------------------------------------------------| | [GitLab CI/CD](ci/README.md) | Explore the features and capabilities of Continuous Integration with GitLab. | | [JUnit test reports](ci/junit_test_reports.md) | Display JUnit test reports on merge requests. | -| [Pipeline Graphs](ci/pipelines.md#pipeline-graphs) | Visualize builds. | +| [Pipeline Graphs](ci/pipelines.md#visualizing-pipelines) | Visualize builds. | | [Review Apps](ci/review_apps/index.md) | Preview changes to your application right from a merge request. | <div align="right"> diff --git a/doc/administration/custom_hooks.md b/doc/administration/custom_hooks.md index 60ad4bf4e8f..28afaf84f5a 100644 --- a/doc/administration/custom_hooks.md +++ b/doc/administration/custom_hooks.md @@ -51,7 +51,7 @@ Hooks can be also placed in `hooks/<hook_name>.d` (global) or execution of the hooks. NOTE: **Note:** `<hook_name>.d` would need to be either `pre-receive.d`, -`post-receive.d`, or `update.d` to work properly. Any other names will be ignored. +`post-receive.d`, or `update.d` to work properly. Any other names will be ignored. To look in a different directory for the global custom hooks (those in `hooks/<hook_name.d>`), set `custom_hooks_dir` in gitlab-shell config. For @@ -76,9 +76,21 @@ first script exiting with a non-zero value. > [Introduced][5073] in GitLab 8.10. -If the commit is declined or an error occurs during the Git hook check, -the STDERR or STDOUT message of the hook will be present in GitLab's UI. -STDERR takes precedence over STDOUT. +To have custom error messages appear in GitLab's UI when the commit is +declined or an error occurs during the Git hook, your script should: + +- Send the custom error messages to either the script's `stdout` or `stderr`. +- Prefix each message with `GL-HOOK-ERR:` with no characters appearing before the prefix. + +### Example custom error message + +This hook script written in bash will generate the following message in GitLab's UI: + +```bash +#!/bin/sh +echo "GL-HOOK-ERR: My custom error message."; +exit 1 +``` ![Custom message from custom Git hook](img/custom_hooks_error_msg.png) diff --git a/doc/administration/gitaly/index.md b/doc/administration/gitaly/index.md index 2d4b5c65c46..f1cedb85455 100644 --- a/doc/administration/gitaly/index.md +++ b/doc/administration/gitaly/index.md @@ -241,12 +241,24 @@ repository from your GitLab server over HTTP. > [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22602) in GitLab 11.8. -Gitaly supports TLS credentials for GRPC authentication. To be able to communicate +Gitaly supports TLS encryption. To be able to communicate with a Gitaly instance that listens for secure connections you will need to use `tls://` url scheme in the `gitaly_address` of the corresponding storage entry in the gitlab configuration. The admin needs to bring their own certificate as we do not provide that automatically. -The certificate to be used needs to be installed on all Gitaly nodes and on all client nodes that communicate with it following procedures described in [GitLab custom certificate configuration](https://docs.gitlab.com/omnibus/settings/ssl.html#install-custom-public-certificates) +The certificate to be used needs to be installed on all Gitaly nodes and on all client nodes that communicate with it following procedures described in [GitLab custom certificate configuration](https://docs.gitlab.com/omnibus/settings/ssl.html#install-custom-public-certificates). + +Note that it is possible to configure Gitaly servers with both an +unencrypted listening address `listen_addr` and an encrypted listening +address `tls_listen_addr` at the same time. This allows you to do a +gradual transition from unencrypted to encrypted traffic, if necessary. + +To observe what type of connections are actually being used in a +production environment you can use the following Prometheus query: + +``` +sum(rate(gitaly_connections_total[5m])) by (type) +``` ### Example TLS configuration @@ -303,6 +315,66 @@ certificate_path = '/path/to/cert.pem' key_path = '/path/to/key.pem' ``` +## Gitaly-ruby + +Gitaly was developed to replace Ruby application code in gitlab-ce/ee. +In order to save time and/or avoid the risk of rewriting existing +application logic, in some cases we chose to copy some application code +from gitlab-ce into Gitaly almost as-is. To be able to run that code, we +made gitaly-ruby, which is a sidecar process for the main Gitaly Go +process. Some examples of things that are implemented in gitaly-ruby are +RPC's that deal with wiki's, and RPC's that create commits on behalf of +a user, such as merge commits. + +### Number of gitaly-ruby workers + +Gitaly-ruby has much less capacity than Gitaly itself. If your Gitaly +server has to handle a lot of request, the default setting of having +just 1 active gitaly-ruby sidecar might not be enough. If you see +ResourceExhausted errors from Gitaly it's very likely that you have not +enough gitaly-ruby capacity. + +You can increase the number of gitaly-ruby processes on your Gitaly +server with the following settings. + +Omnibus: + +```ruby +# /etc/gitlab/gitlab.rb +# Default is 2 workers. The minimum is 2; 1 worker is always reserved as +# a passive stand-by. +gitaly['ruby_num_workers'] = 4 +``` + +Source: + +```toml +# /home/git/gitaly/config.toml +[gitaly-ruby] +num_workers = 4 +``` + +### Observing gitaly-ruby traffic + +Gitaly-ruby is a somewhat hidden, internal implementation detail of +Gitaly. There is not that much visibility into what goes on inside +gitaly-ruby processes. + +If you have Prometheus set up to scrape your Gitaly process, you can see +request rates and error codes for individual RPC's in gitaly-ruby by +querying `grpc_client_handled_total`. Strictly speaking this metric does +not differentiate between gitaly-ruby and other RPC's, but in practice +(as of GitLab 11.9), all gRPC calls made by Gitaly itself are internal +calls from the main Gitaly process to one of its gitaly-ruby sidecars. + +Assuming your `grpc_client_handled_total` counter only observes Gitaly, +the following query shows you RPC's are (most likely) internally +implemented as calls to gitaly-ruby. + +``` +sum(rate(grpc_client_handled_total[5m])) by (grpc_method) > 0 +``` + ## Disabling or enabling the Gitaly service in a cluster environment If you are running Gitaly [as a remote diff --git a/doc/administration/high_availability/nfs.md b/doc/administration/high_availability/nfs.md index b1aaa3bca13..f406163aea0 100644 --- a/doc/administration/high_availability/nfs.md +++ b/doc/administration/high_availability/nfs.md @@ -71,9 +71,8 @@ bug](https://bugzilla.redhat.com/show_bug.cgi?id=1552203) that may be fixed in [more recent kernels with this commit](https://github.com/torvalds/linux/commit/95da1b3a5aded124dd1bda1e3cdb876184813140). -Users encountering a similar issue may be advised to disable the NFS server -delegation feature, which is an optimization to reduce the number of network -round-trips needed to read or write files. To disable NFS server delegations +GitLab recommends all NFS users disable the NFS server +delegation feature. To disable NFS server delegations on an Linux NFS server, do the following: 1. On the NFS server, run: diff --git a/doc/administration/img/custom_hooks_error_msg.png b/doc/administration/img/custom_hooks_error_msg.png Binary files differindex 845f0de19ce..4f25c471908 100644 --- a/doc/administration/img/custom_hooks_error_msg.png +++ b/doc/administration/img/custom_hooks_error_msg.png diff --git a/doc/administration/logs.md b/doc/administration/logs.md index 36dee75bd44..3d40cda491a 100644 --- a/doc/administration/logs.md +++ b/doc/administration/logs.md @@ -23,16 +23,19 @@ requests from the API are logged to a separate file in `api_json.log`. Each line contains a JSON line that can be ingested by Elasticsearch, Splunk, etc. For example: ```json -{"method":"GET","path":"/gitlab/gitlab-ce/issues/1234","format":"html","controller":"Projects::IssuesController","action":"show","status":200,"duration":229.03,"view":174.07,"db":13.24,"time":"2017-08-08T20:15:54.821Z","params":[{"key":"param_key","value":"param_value"}],"remote_ip":"18.245.0.1","user_id":1,"username":"admin","gitaly_calls":76,"queue_duration": 112.47} +{"method":"GET","path":"/gitlab/gitlab-ce/issues/1234","format":"html","controller":"Projects::IssuesController","action":"show","status":200,"duration":229.03,"view":174.07,"db":13.24,"time":"2017-08-08T20:15:54.821Z","params":[{"key":"param_key","value":"param_value"}],"remote_ip":"18.245.0.1","user_id":1,"username":"admin","gitaly_calls":76,"gitaly_duration":7.41,"queue_duration": 112.47} ``` -In this example, you can see this was a GET request for a specific issue. Notice each line also contains performance data: +In this example, you can see this was a GET request for a specific +issue. Notice each line also contains performance data. All times are in +milliseconds: -1. `duration`: total time in milliseconds taken to retrieve the request -1. `queue_duration`: total time in milliseconds that the request was queued inside GitLab Workhorse +1. `duration`: total time taken to retrieve the request +1. `queue_duration`: total time that the request was queued inside GitLab Workhorse 1. `view`: total time taken inside the Rails views 1. `db`: total time to retrieve data from the database 1. `gitaly_calls`: total number of calls made to Gitaly +1. `gitaly_duration`: total time taken by Gitaly calls User clone/fetch activity using http transport appears in this log as `action: git_upload_pack`. @@ -85,7 +88,7 @@ Introduced in GitLab 10.0, this file lives in It helps you see requests made directly to the API. For example: ```json -{"time":"2018-10-29T12:49:42.123Z","severity":"INFO","duration":709.08,"db":14.59,"view":694.49,"status":200,"method":"GET","path":"/api/v4/projects","params":[{"key":"action","value":"git-upload-pack"},{"key":"changes","value":"_any"},{"key":"key_id","value":"secret"},{"key":"secret_token","value":"[FILTERED]"}],"host":"localhost","ip":"::1","ua":"Ruby","route":"/api/:version/projects","user_id":1,"username":"root","queue_duration":100.31,"gitaly_calls":30} +{"time":"2018-10-29T12:49:42.123Z","severity":"INFO","duration":709.08,"db":14.59,"view":694.49,"status":200,"method":"GET","path":"/api/v4/projects","params":[{"key":"action","value":"git-upload-pack"},{"key":"changes","value":"_any"},{"key":"key_id","value":"secret"},{"key":"secret_token","value":"[FILTERED]"}],"host":"localhost","ip":"::1","ua":"Ruby","route":"/api/:version/projects","user_id":1,"username":"root","queue_duration":100.31,"gitaly_calls":30,"gitaly_duration":5.36} ``` This entry above shows an access to an internal endpoint to check whether an diff --git a/doc/administration/merge_request_diffs.md b/doc/administration/merge_request_diffs.md index c34a9519ace..5e9ba4f640f 100644 --- a/doc/administration/merge_request_diffs.md +++ b/doc/administration/merge_request_diffs.md @@ -145,3 +145,47 @@ The connection settings match those provided by [Fog](https://github.com/fog), a ``` 1. Save the file and [restart GitLab](restart_gitlab.md#installations-from-source) for the changes to take effect. + +### Alternative in-database storage + +Enabling external diffs may reduce the performance of merge requests, as they +must be retrieved in a separate operation to other data. A compromise may be +reached by only storing outdated diffs externally, while keeping current diffs +in the database. + +To enable this feature, perform the following steps: + +**In Omnibus installations:** + +1. Edit `/etc/gitlab/gitlab.rb` and add the following line: + + ```ruby + gitlab_rails['external_diffs_when'] = 'outdated' + ``` + +1. Save the file and [reconfigure GitLab](restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect. + +**In installations from source:** + +1. Edit `/home/git/gitlab/config/gitlab.yml` and add or amend the following + lines: + + ```yaml + external_diffs: + enabled: true + when: outdated + ``` + +1. Save the file and [restart GitLab](restart_gitlab.md#installations-from-source) for the changes to take effect. + +With this feature enabled, diffs will initially stored in the database, rather +than externally. They will be moved to external storage once any of these +conditions become true: + +- A newer version of the merge request diff exists +- The merge request was merged more than seven days ago +- The merge request was closed more than seven day ago + +These rules strike a balance between space and performance by only storing +frequently-accessed diffs in the database. Diffs that are less likely to be +accessed are moved to external storage instead. diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md index 288ce376687..17d72b96a51 100644 --- a/doc/administration/pages/index.md +++ b/doc/administration/pages/index.md @@ -125,7 +125,7 @@ The Pages daemon doesn't listen to the outside world. pages_external_url 'http://example.io' ``` -1. [Reconfigure GitLab][reconfigure] +1. [Reconfigure GitLab][reconfigure]. Watch the [video tutorial][video-admin] for this configuration. @@ -157,7 +157,22 @@ outside world. where `pages-nginx.crt` and `pages-nginx.key` are the SSL cert and key, respectively. -1. [Reconfigure GitLab][reconfigure] +1. [Reconfigure GitLab][reconfigure]. + +### Additional configuration for Docker container + +The GitLab Pages daemon will not have permissions to bind mounts when it runs +in a Docker container. To overcome this issue you'll need to change the chroot +behavior: + +1. Edit `/etc/gitlab/gitlab.rb`. +1. Set the `inplace_chroot` to `true` for GitLab Pages: + + ```shell + gitlab_pages['inplace_chroot'] = true + ``` + +1. [Reconfigure GitLab][reconfigure]. ## Advanced configuration @@ -195,7 +210,7 @@ world. Custom domains are supported, but no TLS. `192.0.2.2` and `2001::2` are the secondary IPs the GitLab Pages daemon listens on. If you don't have IPv6, you can omit the IPv6 address. -1. [Reconfigure GitLab][reconfigure] +1. [Reconfigure GitLab][reconfigure]. ### Custom domains with TLS support @@ -229,7 +244,7 @@ world. Custom domains and TLS are supported. `192.0.2.2` and `2001::2` are the secondary IPs where the GitLab Pages daemon listens on. If you don't have IPv6, you can omit the IPv6 address. -1. [Reconfigure GitLab][reconfigure] +1. [Reconfigure GitLab][reconfigure]. ### Custom domain verification @@ -287,7 +302,7 @@ Follow the steps below to configure verbose logging of GitLab Pages daemon. gitlab_pages['log_verbose'] = true ``` -1. [Reconfigure GitLab][reconfigure] +1. [Reconfigure GitLab][reconfigure]. ## Change storage path @@ -302,7 +317,7 @@ are stored. gitlab_rails['pages_path'] = "/mnt/storage/pages" ``` -1. [Reconfigure GitLab][reconfigure] +1. [Reconfigure GitLab][reconfigure]. ## Configure listener for reverse proxy requests @@ -325,7 +340,7 @@ Omnibus GitLab 11.1. gitlab_pages['listen_proxy'] = "localhost:10080" ``` -1. [Reconfigure GitLab][reconfigure] +1. [Reconfigure GitLab][reconfigure]. ## Set maximum pages size diff --git a/doc/administration/repository_storage_paths.md b/doc/administration/repository_storage_paths.md index 7f25423171f..1689b0a57d6 100644 --- a/doc/administration/repository_storage_paths.md +++ b/doc/administration/repository_storage_paths.md @@ -62,6 +62,8 @@ files and add the full paths of the alternative repository storage paths. In the example below, we add two more mountpoints that are named `nfs` and `cephfs` respectively. +NOTE: **Note:** This example uses NFS and CephFS. We do not recommend using EFS for storage as it may impact GitLab's performance. See the [relevant documentation](./high_availability/nfs.md#avoid-using-awss-elastic-file-system-efs) for more details. + **For installations from source** 1. Edit `gitlab.yml` and add the storage paths: diff --git a/doc/administration/repository_storage_types.md b/doc/administration/repository_storage_types.md index 25c3d564560..4e1e363888d 100644 --- a/doc/administration/repository_storage_types.md +++ b/doc/administration/repository_storage_types.md @@ -142,7 +142,7 @@ projects: 2. Uncheck the **Use hashed storage paths for newly created and renamed projects** checkbox. To schedule a complete rollback, see the -[rake task documentation for storage rollback][rake/rollback-to-legacy] for instructions. +[rake task documentation for storage rollback](raketasks/storage.md#rollback-from-hashed-storage-to-legacy-storage) for instructions. The rollback task also supports specifying a range of Project IDs. Here is an example of limiting the rollout to Project IDs 50 to 100, in an Omnibus Gitlab installation: @@ -205,6 +205,5 @@ They are also S3 compatible since **10.0** (GitLab Premium), and available in Gi [ce-2821]: https://gitlab.com/gitlab-com/infrastructure/issues/2821 [ce-28283]: https://gitlab.com/gitlab-org/gitlab-ce/issues/28283 [rake/migrate-to-hashed]: raketasks/storage.md#migrate-existing-projects-to-hashed-storage -[rake/rollback-to-legacy]: raketasks/storage.md#rollback [storage-paths]: repository_storage_types.md [gitaly]: gitaly/index.md diff --git a/doc/api/build_triggers.md b/doc/api/build_triggers.md index 20d924ab35e..bad7a655d08 100644 --- a/doc/api/build_triggers.md +++ b/doc/api/build_triggers.md @@ -1 +1,5 @@ -This document was moved to [Pipeline Triggers](pipeline_triggers.md). +--- +redirect_to: 'pipeline_triggers.md' +--- + +This document was moved to [another location](pipeline_triggers.md). diff --git a/doc/api/builds.md b/doc/api/builds.md index a6edda68bc4..0154d35cab6 100644 --- a/doc/api/builds.md +++ b/doc/api/builds.md @@ -1 +1,5 @@ +--- +redirect_to: 'jobs.md' +--- + This document was moved to [another location](jobs.md). diff --git a/doc/api/commits.md b/doc/api/commits.md index 09546fcac3f..c8c282a71d9 100644 --- a/doc/api/commits.md +++ b/doc/api/commits.md @@ -155,6 +155,32 @@ Example response: } ``` +GitLab supports [form encoding](../README.md#encoding-api-parameters-of-array-and-hash-types). The following is an example using Commit API with form encoding: + +```bash +curl --request POST \ + --form "branch=master" \ + --form "commit_message=some commit message" \ + --form "start_branch=master" \ + --form "actions[][action]=create" \ + --form "actions[][file_path]=foo/bar" \ + --form "actions[][content]=</path/to/local.file" \ + --form "actions[][action]=delete" \ + --form "actions[][file_path]=foo/bar2" \ + --form "actions[][action]=move" \ + --form "actions[][file_path]=foo/bar3" \ + --form "actions[][previous_path]=foo/bar4" \ + --form "actions[][content]=</path/to/local1.file" \ + --form "actions[][action]=update" \ + --form "actions[][file_path]=foo/bar5" \ + --form "actions[][content]=</path/to/local2.file" \ + --form "actions[][action]=chmod" \ + --form "actions[][file_path]=foo/bar5" \ + --form "actions[][execute_filemode]=true" \ + --header "PRIVATE-TOKEN: <your_access_token>" \ + "https://gitlab.example.com/api/v4/projects/1/repository/commits" +``` + ## Get a single commit Get a specific commit identified by the commit hash or name of a branch or tag. @@ -196,9 +222,9 @@ Example response: "last_pipeline" : { "id": 8, "ref": "master", - "sha": "2dc6aa325a317eda67812f05600bdf0fcdc70ab0" + "sha": "2dc6aa325a317eda67812f05600bdf0fcdc70ab0", "status": "created" - } + }, "stats": { "additions": 15, "deletions": 10, diff --git a/doc/api/group_milestones.md b/doc/api/group_milestones.md index 1c2f56581eb..260eb09cc38 100644 --- a/doc/api/group_milestones.md +++ b/doc/api/group_milestones.md @@ -12,6 +12,7 @@ GET /groups/:id/milestones?iids[]=42 GET /groups/:id/milestones?iids[]=42&iids[]=43 GET /groups/:id/milestones?state=active GET /groups/:id/milestones?state=closed +GET /groups/:id/milestones?title=1.0 GET /groups/:id/milestones?search=version ``` @@ -22,6 +23,7 @@ Parameters: | `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user | | `iids[]` | Array[integer] | optional | Return only the milestones having the given `iid` | | `state` | string | optional | Return only `active` or `closed` milestones | +| `title` | string | optional | Return only the milestones having the given `title` | | `search` | string | optional | Return only milestones with a title or description matching the provided string | ```bash diff --git a/doc/api/milestones.md b/doc/api/milestones.md index 897184d51af..3b76c19dc07 100644 --- a/doc/api/milestones.md +++ b/doc/api/milestones.md @@ -10,6 +10,7 @@ GET /projects/:id/milestones?iids[]=42 GET /projects/:id/milestones?iids[]=42&iids[]=43 GET /projects/:id/milestones?state=active GET /projects/:id/milestones?state=closed +GET /projects/:id/milestones?title=1.0 GET /projects/:id/milestones?search=version ``` @@ -20,6 +21,7 @@ Parameters: | `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | | `iids[]` | Array[integer] | optional | Return only the milestones having the given `iid` | | `state` | string | optional | Return only `active` or `closed` milestones | +| `title` | string | optional | Return only the milestones having the given `title` | | `search` | string | optional | Return only milestones with a title or description matching the provided string | ```bash diff --git a/doc/api/project_clusters.md b/doc/api/project_clusters.md index 8efb98fe1fc..02334f0298e 100644 --- a/doc/api/project_clusters.md +++ b/doc/api/project_clusters.md @@ -33,6 +33,7 @@ Example response: { "id":18, "name":"cluster-1", + "domain":"example.com", "created_at":"2019-01-02T20:18:12.563Z", "provider_type":"user", "platform_type":"kubernetes", @@ -90,6 +91,7 @@ Example response: { "id":18, "name":"cluster-1", + "domain":"example.com", "created_at":"2019-01-02T20:18:12.563Z", "provider_type":"user", "platform_type":"kubernetes", @@ -157,6 +159,7 @@ Parameters: | --------- | ---- | -------- | ----------- | | `id` | integer | yes | The ID of the project owned by the authenticated user | | `name` | String | yes | The name of the cluster | +| `domain` | String | no | The [base domain](../user/project/clusters/index.md#base_domain) of the cluster | | `enabled` | Boolean | no | Determines if cluster is active or not, defaults to true | | `platform_kubernetes_attributes[api_url]` | String | yes | The URL to access the Kubernetes API | | `platform_kubernetes_attributes[token]` | String | yes | The token to authenticate against Kubernetes | @@ -247,6 +250,7 @@ Parameters: | `id` | integer | yes | The ID of the project owned by the authenticated user | | `cluster_id` | integer | yes | The ID of the cluster | | `name` | String | no | The name of the cluster | +| `domain` | String | no | The [base domain](../user/project/clusters/index.md#base_domain) of the cluster | | `platform_kubernetes_attributes[api_url]` | String | no | The URL to access the Kubernetes API | | `platform_kubernetes_attributes[token]` | String | no | The token to authenticate against Kubernetes | | `platform_kubernetes_attributes[ca_cert]` | String | no | TLS certificate (needed if API is using a self-signed TLS certificate | @@ -262,7 +266,7 @@ Example request: ```bash curl --header 'Private-Token: <your_access_token>' https://gitlab.example.com/api/v4/projects/26/clusters/24 \ -H "Content-Type:application/json" \ --X PUT --data '{"name":"new-cluster-name","api_url":"https://new-api-url.com"}' +-X PUT --data '{"name":"new-cluster-name","domain":"new-domain.com","api_url":"https://new-api-url.com"}' ``` Example response: @@ -271,6 +275,7 @@ Example response: { "id":24, "name":"new-cluster-name", + "domain":"new-domain.com", "created_at":"2019-01-03T21:53:40.610Z", "provider_type":"user", "platform_type":"kubernetes", diff --git a/doc/api/services.md b/doc/api/services.md index 03d0a80aa64..1f84e2de7de 100644 --- a/doc/api/services.md +++ b/doc/api/services.md @@ -522,7 +522,7 @@ Parameters: | `username` | string | yes | The username of the user created to be used with GitLab/JIRA. | | `password` | string | yes | The password of the user created to be used with GitLab/JIRA. | | `active` | boolean | no | Activates or deactivates the service. Defaults to false (deactivated). | -| `jira_issue_transition_id` | integer | no | The ID of a transition that moves issues to a closed state. You can find this number under the JIRA workflow administration (**Administration > Issues > Workflows**) by selecting **View** under **Operations** of the desired workflow of your project. The ID of each state can be found inside the parenthesis of each transition name under the **Transitions (id)** column ([see screenshot][trans]). By default, this ID is set to `2`. | +| `jira_issue_transition_id` | string | no | The ID of a transition that moves issues to a closed state. You can find this number under the JIRA workflow administration (**Administration > Issues > Workflows**) by selecting **View** under **Operations** of the desired workflow of your project. The ID of each state can be found inside the parenthesis of each transition name under the **Transitions (id)** column ([see screenshot][trans]). By default, this ID is set to `2`. | ### Delete JIRA service diff --git a/doc/api/suggestions.md b/doc/api/suggestions.md index e88d536282a..188989bc94e 100644 --- a/doc/api/suggestions.md +++ b/doc/api/suggestions.md @@ -24,8 +24,6 @@ Example response: ```json { "id": 36, - "from_original_line": 10, - "to_original_line": 10, "from_line": 10, "to_line": 10, "appliable": false, diff --git a/doc/articles/artifactory_and_gitlab/index.md b/doc/articles/artifactory_and_gitlab/index.md index 6a590b53727..ed9fd135e7c 100644 --- a/doc/articles/artifactory_and_gitlab/index.md +++ b/doc/articles/artifactory_and_gitlab/index.md @@ -1 +1,5 @@ +--- +redirect_to: '../../ci/examples/artifactory_and_gitlab/index.md' +--- + This document was moved to [another location](../../ci/examples/artifactory_and_gitlab/index.md) diff --git a/doc/articles/how_to_configure_ldap_gitlab_ce/index.md b/doc/articles/how_to_configure_ldap_gitlab_ce/index.md index a8320c12e6b..8e2e54711e7 100644 --- a/doc/articles/how_to_configure_ldap_gitlab_ce/index.md +++ b/doc/articles/how_to_configure_ldap_gitlab_ce/index.md @@ -1 +1,5 @@ +--- +redirect_to: '../../administration/auth/how_to_configure_ldap_gitlab_ce/index.md' +--- + This document was moved to [another location](../../administration/auth/how_to_configure_ldap_gitlab_ce/index.md). diff --git a/doc/articles/how_to_install_git/index.md b/doc/articles/how_to_install_git/index.md index 3e6003a33b7..62598101895 100644 --- a/doc/articles/how_to_install_git/index.md +++ b/doc/articles/how_to_install_git/index.md @@ -1 +1,5 @@ +--- +redirect_to: '../../topics/git/how_to_install_git/index.md' +--- + This document was moved to [another location](../../topics/git/how_to_install_git/index.md). diff --git a/doc/articles/laravel_with_gitlab_and_envoy/index.md b/doc/articles/laravel_with_gitlab_and_envoy/index.md index b092cdb0f7a..fa4f6243410 100644 --- a/doc/articles/laravel_with_gitlab_and_envoy/index.md +++ b/doc/articles/laravel_with_gitlab_and_envoy/index.md @@ -1 +1,5 @@ +--- +redirect_to: '../../ci/examples/laravel_with_gitlab_and_envoy/index.md' +--- + This document was moved to [another location](../../ci/examples/laravel_with_gitlab_and_envoy/index.md). diff --git a/doc/articles/numerous_undo_possibilities_in_git/index.md b/doc/articles/numerous_undo_possibilities_in_git/index.md index 3f46ee9a5e6..83aac82db4e 100644 --- a/doc/articles/numerous_undo_possibilities_in_git/index.md +++ b/doc/articles/numerous_undo_possibilities_in_git/index.md @@ -1 +1,5 @@ +--- +redirect_to: '../../topics/git/numerous_undo_possibilities_in_git/index.md' +--- + This document was moved to [another location](../../topics/git/numerous_undo_possibilities_in_git/index.md). diff --git a/doc/articles/runner_autoscale_aws/index.md b/doc/articles/runner_autoscale_aws/index.md index e2667aebc5f..fb769731256 100644 --- a/doc/articles/runner_autoscale_aws/index.md +++ b/doc/articles/runner_autoscale_aws/index.md @@ -1 +1,5 @@ +--- +redirect_to: 'https://docs.gitlab.com/runner/configuration/runner_autoscale_aws/index.html' +--- + This document was moved to [another location](https://docs.gitlab.com/runner/configuration/runner_autoscale_aws/index.html). diff --git a/doc/ci/introduction/img/gitlab_workflow_example.png b/doc/ci/introduction/img/gitlab_workflow_example.png Binary files differdeleted file mode 100644 index 94e7753c3b2..00000000000 --- a/doc/ci/introduction/img/gitlab_workflow_example.png +++ /dev/null diff --git a/doc/ci/introduction/img/gitlab_workflow_example_11_9.png b/doc/ci/introduction/img/gitlab_workflow_example_11_9.png Binary files differnew file mode 100644 index 00000000000..204e9c462e5 --- /dev/null +++ b/doc/ci/introduction/img/gitlab_workflow_example_11_9.png diff --git a/doc/ci/introduction/index.md b/doc/ci/introduction/index.md index 1b423a4696e..d505f2ae4ce 100644 --- a/doc/ci/introduction/index.md +++ b/doc/ci/introduction/index.md @@ -154,7 +154,7 @@ Once you're happy with your implementation: - GitLab CI/CD deploys your changes automatically to a production environment. - And finally, you and your team can easily roll it back if something goes wrong. -<img src="img/gitlab_workflow_example.png" alt="GitLab workflow example" class="image-noshadow"> +<img src="img/gitlab_workflow_example_11_9.png" alt="GitLab workflow example" class="image-noshadow"> GitLab CI/CD is capable of a doing a lot more, but this workflow exemplifies GitLab's ability to track the entire process, diff --git a/doc/ci/pipelines.md b/doc/ci/pipelines.md index c509c341d1e..38cd58f11ac 100644 --- a/doc/ci/pipelines.md +++ b/doc/ci/pipelines.md @@ -45,12 +45,12 @@ Pipeline graphs can be displayed in two different ways, depending on the page yo access the graph from. NOTE: **Note:** -GitLab capitalizes the stages' names when shown in the [pipeline graphs](#pipeline-graphs). +GitLab capitalizes the stages' names when shown in the pipeline graphs (below). ### Regular pipeline graphs Regular pipeline graphs show the names of the jobs of each stage. Regular pipeline graphs can -be found when you are on a [single pipeline page](#seeing-pipeline-status). For example: +be found when you are on a [single pipeline page](#accessing-pipelines). For example: ![Pipelines example](img/pipelines.png) diff --git a/doc/ci/quick_start/README.md b/doc/ci/quick_start/README.md index 9684cb6ed98..65886400c64 100644 --- a/doc/ci/quick_start/README.md +++ b/doc/ci/quick_start/README.md @@ -105,7 +105,7 @@ Jobs are used to create jobs, which are then picked by What is important is that each job is run independently from each other. If you want to check whether the `.gitlab-ci.yml` of your project is valid, there is a -Lint tool under the page `/ci/lint` of your project namespace. You can also find +Lint tool under the page `/-/ci/lint` of your project namespace. You can also find a "CI Lint" button to go to this page under **CI/CD ➔ Pipelines** and **Pipelines ➔ Jobs** in your project. diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index 4d15d58cb40..8827961d86c 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -45,10 +45,11 @@ version of [GitLab Runner](https://docs.gitlab.com/runner/) is used. Consult the version of Runner required. NOTE: **Note:** -Starting with GitLab 9.0, we have deprecated some variables. Read the -[9.0 Renaming](#gitlab-90-renaming) section to find out their replacements. **You are -strongly advised to use the new variables as we will remove the old ones in -future GitLab releases.** +Starting with GitLab 9.0, we have deprecated some variables. +Read the [deprecated variables](deprecated_variables.md) +document to find out their replacements. **You are strongly advised +to use the new variables as we will remove the old ones in future +GitLab releases.** | Variable | GitLab | Runner | Description | |-------------------------------------------|--------|--------|-------------| @@ -141,32 +142,6 @@ future GitLab releases.** | **GITLAB_USER_NAME** | 10.0 | all | The real name of the user who started the job | | **RESTORE_CACHE_ATTEMPTS** | 8.15 | 1.9 | Number of attempts to restore the cache running a job | -## GitLab 9.0 renaming - -To follow conventions of naming across GitLab, and to further move away from the -`build` term and toward `job` CI variables have been renamed for the 9.0 -release. - -NOTE: **Note:** -Starting with GitLab 9.0, we have deprecated the `$CI_BUILD_*` variables. **You are -strongly advised to use the new variables as we will remove the old ones in -future GitLab releases.** - -| 8.x name | 9.0+ name | -| --------------------- |------------------------ | -| `CI_BUILD_ID` | `CI_JOB_ID` | -| `CI_BUILD_REF` | `CI_COMMIT_SHA` | -| `CI_BUILD_TAG` | `CI_COMMIT_TAG` | -| `CI_BUILD_BEFORE_SHA` | `CI_COMMIT_BEFORE_SHA` | -| `CI_BUILD_REF_NAME` | `CI_COMMIT_REF_NAME` | -| `CI_BUILD_REF_SLUG` | `CI_COMMIT_REF_SLUG` | -| `CI_BUILD_NAME` | `CI_JOB_NAME` | -| `CI_BUILD_STAGE` | `CI_JOB_STAGE` | -| `CI_BUILD_REPO` | `CI_REPOSITORY_URL` | -| `CI_BUILD_TRIGGERED` | `CI_PIPELINE_TRIGGERED` | -| `CI_BUILD_MANUAL` | `CI_JOB_MANUAL` | -| `CI_BUILD_TOKEN` | `CI_JOB_TOKEN` | - ## `.gitlab-ci.yml` defined variables NOTE **Note:** @@ -311,7 +286,7 @@ variables that were set, etc. Before enabling this, you should ensure jobs are visible to [team members only](../../user/permissions.md#project-features). You should -also [erase](../pipelines.md#seeing-job-status) all generated job traces +also [erase](../pipelines.md#accessing-individual-jobs) all generated job traces before making them visible again. To enable debug traces, set the `CI_DEBUG_TRACE` variable to `true`: diff --git a/doc/ci/variables/deprecated_variables.md b/doc/ci/variables/deprecated_variables.md new file mode 100644 index 00000000000..2642c9b0eb4 --- /dev/null +++ b/doc/ci/variables/deprecated_variables.md @@ -0,0 +1,27 @@ +# Deprecated GitLab CI/CD variables + +## GitLab 9.0 renamed variables + +To follow conventions of naming across GitLab, and to further move away from the +`build` term and toward `job`, some [CI/CD environment variables](README.md#predefined-environment-variables) were renamed for GitLab 9.0 +release. + +NOTE: **Note:** +Starting with GitLab 9.0, we have deprecated the `$CI_BUILD_*` variables. **You are +strongly advised to use the new variables as we will remove the old ones in +future GitLab releases.** + +| 8.x name | 9.0+ name | +| --------------------- |------------------------ | +| `CI_BUILD_ID` | `CI_JOB_ID` | +| `CI_BUILD_REF` | `CI_COMMIT_SHA` | +| `CI_BUILD_TAG` | `CI_COMMIT_TAG` | +| `CI_BUILD_BEFORE_SHA` | `CI_COMMIT_BEFORE_SHA` | +| `CI_BUILD_REF_NAME` | `CI_COMMIT_REF_NAME` | +| `CI_BUILD_REF_SLUG` | `CI_COMMIT_REF_SLUG` | +| `CI_BUILD_NAME` | `CI_JOB_NAME` | +| `CI_BUILD_STAGE` | `CI_JOB_STAGE` | +| `CI_BUILD_REPO` | `CI_REPOSITORY_URL` | +| `CI_BUILD_TRIGGERED` | `CI_PIPELINE_TRIGGERED` | +| `CI_BUILD_MANUAL` | `CI_JOB_MANUAL` | +| `CI_BUILD_TOKEN` | `CI_JOB_TOKEN` | diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 9eb694a2c64..3e6e89052f8 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -499,9 +499,9 @@ docker build: - more_scripts/*.{rb,py,sh} ``` -In the scenario above, if you are pushing multiple commits to GitLab to an -existing branch, GitLab creates and triggers the `docker build` job, provided that -one of the commits contains changes to either: +In the scenario above, when pushing multiple commits to GitLab to an existing +branch, GitLab creates and triggers `docker build` job, provided that one of the +commits contains changes to either: - The `Dockerfile` file. - Any of the files inside `docker/scripts/` directory. @@ -514,21 +514,20 @@ the section below. ##### Using `changes` with new branches and tags -If you are pushing a **new** branch or a **new** tag to GitLab, the policy -always evaluates to true and GitLab will create a job. This feature is not -connected with merge requests yet, and because GitLab is creating pipelines -before an user can create a merge request we don't know a target branch at -this point. +When pushing a **new** branch or a **new** tag to GitLab, the policy always +evaluates to true and GitLab will create a job. This feature is not connected +with merge requests yet and, because GitLab is creating pipelines before a user +can create a merge request, it is unknown what the target branch is at this point. ##### Using `changes` with `merge_requests` With [pipelines for merge requests](../merge_request_pipelines/index.md), -make it possible to define if a job should be created base on files modified +it is possible to define a job to be created based on files modified in a merge request. For example: -``` +```yaml docker build service one: script: docker build -t my-service-one-image:$CI_COMMIT_REF_SLUG . only: @@ -539,9 +538,9 @@ docker build service one: - service-one/**/* ``` -In the scenario above, if you create or update a merge request that changes -either files in `service-one` folder or `Dockerfile`, GitLab creates and triggers -the `docker build service one` job. +In the scenario above, if a merge request is created or updated that changes +either files in `service-one` directory or the `Dockerfile`, GitLab creates +and triggers the `docker build service one` job. ### `tags` diff --git a/doc/customization/welcome_message.md b/doc/customization/welcome_message.md index 9194f847cdf..45f1fed355e 100644 --- a/doc/customization/welcome_message.md +++ b/doc/customization/welcome_message.md @@ -1 +1,5 @@ +--- +redirect_to: 'branded_login_page.md' +--- + This document was moved to [another location](branded_login_page.md). diff --git a/doc/development/contributing/index.md b/doc/development/contributing/index.md index bfe1ef75914..b39c302453b 100644 --- a/doc/development/contributing/index.md +++ b/doc/development/contributing/index.md @@ -52,9 +52,12 @@ for audiences of all ages. If a contributor is no longer actively working on a submitted merge request we can decide that the merge request will be finished by one of our [Merge request coaches][team] or close the merge request. We make this decision -based on how important the change is for our product vision. If a Merge request +based on how important the change is for our product vision. If a merge request coach is going to finish the merge request we assign the -~"coach will finish" label. +~"coach will finish" label. When a team member picks up a community contribution, +we credit the original author by adding a changelog entry crediting the author +and optionally include the original author on at least one of the commits +within the MR. ## Helping others diff --git a/doc/development/contributing/style_guides.md b/doc/development/contributing/style_guides.md index 0eedef5e14f..45104a1f91d 100644 --- a/doc/development/contributing/style_guides.md +++ b/doc/development/contributing/style_guides.md @@ -22,6 +22,7 @@ text should be _sorry, we could not create your account because:_ 1. Code should be written in [US English][us-english] 1. [Go](../go_guide/index.md) +1. [Python](../python_guide/index.md) This is also the style used by linting tools such as [RuboCop](https://github.com/bbatsov/rubocop) and [Hound CI](https://houndci.com). diff --git a/doc/development/ee_features.md b/doc/development/ee_features.md index 41a64044c68..c2e05b2d065 100644 --- a/doc/development/ee_features.md +++ b/doc/development/ee_features.md @@ -909,14 +909,15 @@ export default { - Since we [can't async load a mixin](https://github.com/vuejs/vue-loader/issues/418#issuecomment-254032223) we will use the [`ee_else_ce`](https://docs.gitlab.com/ee/development/ee_features.html#javascript-code-in-assetsjavascripts) alias we already have for webpack. - This means all the EE specific props, computed properties, methods, etc that are EE only should be in a mixin in the `ee/` folder and we need to create a CE counterpart of the mixin - ##### Example: - ```javascript - import mixin from 'ee_else_ce/path/mixin'; +##### Example: +```javascript +import mixin from 'ee_else_ce/path/mixin'; - { +{ mixins: [mixin] - } - ``` +} +``` + - Computed Properties/methods and getters only used in the child import still need a counterpart in CE - For store modules, we will need a CE counterpart too. diff --git a/doc/development/fe_guide/testing.md b/doc/development/fe_guide/testing.md index 98e499b8c0f..b23e37d1eef 100644 --- a/doc/development/fe_guide/testing.md +++ b/doc/development/fe_guide/testing.md @@ -1 +1,5 @@ -This document was moved to [../testing_guide/frontend_testing.md](../testing_guide/frontend_testing.md). +--- +redirect_to: '../testing_guide/frontend_testing.md' +--- + +This document was moved to [another location](../testing_guide/frontend_testing.md). diff --git a/doc/development/gitaly.md b/doc/development/gitaly.md index 1a4b1743a00..27b69ba8278 100644 --- a/doc/development/gitaly.md +++ b/doc/development/gitaly.md @@ -197,3 +197,82 @@ as a [CI environment variable](../ci/variables/README.md#variables). --- [Return to Development documentation](README.md) + +## Wrapping RPCs in Feature Flags + +Here are the steps to gate a new feature in Gitaly behind a feature flag. + +### Gitaly + +1. Create a package scoped flag name: + + ```go + var findAllTagsFeatureFlag = "go-find-all-tags" + ``` + +1. Create a switch in the code using the `featureflag` package: + + ```go + if featureflag.IsEnabled(ctx, findAllTagsFeatureFlag) { + // go implementation + } else { + // ruby implementation + } + ``` + +1. Create prometheus metrics: + + ```go + var findAllTagsRequests = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "gitaly_find_all_tags_requests_total", + Help: "Counter of go vs ruby implementation of FindAllTags", + }, + []string{"implementation"}, + ) + ) + + func init() { + prometheus.Register(findAllTagsRequests) + } + + if featureflag.IsEnabled(ctx, findAllTagsFeatureFlag) { + findAllTagsRequests.WithLabelValues("go").Inc() + // go implementation + } else { + findAllTagsRequests.WithLabelValues("ruby").Inc() + // ruby impelmentation + } + ``` + +1. Set headers in tests: + + ```go + import ( + "google.golang.org/grpc/metadata" + + "gitlab.com/gitlab-org/gitaly/internal/featureflag" + ) + + //... + + md := metadata.New(map[string]string{featureflag.HeaderKey(findAllTagsFeatureFlag): "true"}) + ctx = metadata.NewOutgoingContext(context.Background(), md) + + c, err = client.FindAllTags(ctx, rpcRequest) + require.NoError(t, err) + ``` + +### Gitlab-Rails + +1. Add feature flag to `lib/gitlab/gitaly_client.rb` (in gitlab-rails): + + ```ruby + SERVER_FEATURE_FLAGS = %w[go-find-all-tags].freeze + ``` + +1. Test in rails console by setting feature flag: + + ```ruby + Feature.enable('gitaly_go-find-all-tags') + ``` diff --git a/doc/development/go_guide/index.md b/doc/development/go_guide/index.md index 7fd41c5e01f..6dcade3bb51 100644 --- a/doc/development/go_guide/index.md +++ b/doc/development/go_guide/index.md @@ -26,7 +26,7 @@ Reviewers and maintainers should pay attention to: - `defer` functions: ensure the presence when needed, and after `err` check. - Inject dependencies as parameters. -- Void structs when marshalling to JSON (generates `null` instead of `[]`). +- Void structs when marshaling to JSON (generates `null` instead of `[]`). ### Security @@ -185,7 +185,7 @@ There are a few guidelines one should follow when using the - When printing an error use [WithError](https://godoc.org/github.com/sirupsen/logrus#WithError). For - exmaple, `logrus.WithError(err).Error("Failed to do something")`. + example, `logrus.WithError(err).Error("Failed to do something")`. - Since we use [structured logging](#structured-json-logging) we can log fields in the context of that code path, such as the URI of the request using [`WithField`](https://godoc.org/github.com/sirupsen/logrus#WithField) or diff --git a/doc/development/i18n/proofreader.md b/doc/development/i18n/proofreader.md index dd338f6f67d..131e3edf35e 100644 --- a/doc/development/i18n/proofreader.md +++ b/doc/development/i18n/proofreader.md @@ -126,5 +126,8 @@ are very appreciative of the work done by translators and proofreaders! your previous translations by [GitLab team members](https://about.gitlab.com/team/) or [Core team members](https://about.gitlab.com/core-team/) who are fluent in the language or current proofreaders. + - When a request is made for the first proofreader for a lanuguage and there are no [GitLab team members](https://about.gitlab.com/team/) + or [Core team members](https://about.gitlab.com/core-team/) who speak the language, we will request links to previous translation work in other communities or projects. + [proofreader-src]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/development/i18n/proofreader.md diff --git a/doc/development/i18n_guide.md b/doc/development/i18n_guide.md index f6e949b5fd8..e588b47e203 100644 --- a/doc/development/i18n_guide.md +++ b/doc/development/i18n_guide.md @@ -1 +1,5 @@ -This document was moved to [a new location](i18n/index.md). +--- +redirect_to: 'i18n/index.md' +--- + +This document was moved to [another location](i18n/index.md). diff --git a/doc/development/python_guide/index.md b/doc/development/python_guide/index.md new file mode 100644 index 00000000000..6025dc9ebf2 --- /dev/null +++ b/doc/development/python_guide/index.md @@ -0,0 +1,79 @@ +# Python Development Guidelines + +GitLab requires Python as a dependency for [reStructuredText](http://docutils.sourceforge.net/rst.html) +markup rendering. + +As of GitLab 11.10, we require Python 3. + +## Installation + +There are several ways of installing python on your system. To be able to use the same version we use in production, +we suggest you use [pyenv](https://github.com/pyenv/pyenv). It works and behave similar to its counterpart in the +ruby world: [rbenv](https://github.com/rbenv/rbenv). + +### macOS + +To install `pyenv` on macOS, you can use [Homebrew](https://brew.sh/) with: + +```bash +brew install pyenv +``` + +### Linux + +To install `pyenv` on Linux, you can run the command below: + +```bash +curl https://pyenv.run | bash +``` + +Alternatively, you may find `pypenv` available as a system package via your distro package manager. + +You can read more about it in: <https://github.com/pyenv/pyenv-installer#prerequisites>. + +### Shell integration + +Pyenv installation will add required changes to Bash. If you use a different shell, +check for any additional steps required for it. + +For Fish, you can install a plugin for [Fisherman](https://github.com/fisherman/fisherman): + +```bash +fisher add fisherman/pyenv +``` + +Or for [Oh My Fish](https://github.com/oh-my-fish/oh-my-fish): + +```bash +omf install pyenv +``` + +## Dependency management + +While GitLab doesn't directly contain any Python scripts, because we depend on Python to render +[reStructuredText](http://docutils.sourceforge.net/rst.html) markup, we need to keep track on dependencies +on the main project level, so we can run that on our development machines. + +Recently, an equivalent to the `Gemfile` and the [Bundler](https://bundler.io/) project has been introduced to Python: +`Pipfile` and [Pipenv](https://pipenv.readthedocs.io/en/latest/). + +You will now find a `Pipfile` with the dependencies in the root folder. To install them, run: + +```bash +pipenv install +``` + +Running this command will install both the required Python version as well as required pip dependencies. + +## Use instructions + +To run any python code under the Pipenv environment, you need to first start a `virtualenv` based on the dependencies +of the application. With Pipenv, this is a simple as running: + +```bash +pipenv shell +``` + +After running that command, you can run GitLab on the same shell and it will be using the Python and dependencies +installed from the `pipenv install` command. + diff --git a/doc/development/sql.md b/doc/development/sql.md index 47519d39e74..edeca7fb298 100644 --- a/doc/development/sql.md +++ b/doc/development/sql.md @@ -155,6 +155,21 @@ The _only_ time you should use `pluck` is when you actually need to operate on the values in Ruby itself (e.g. write them to a file). In almost all other cases you should ask yourself "Can I not just use a sub-query?". +In line with our `CodeReuse/ActiveRecord` cop, you should only use forms like +`pluck(:id)` or `pluck(:user_id)` within model code. In the former case, you can +use the `ApplicationRecord`-provided `.pluck_primary_key` helper method instead. +In the latter, you should add a small helper method to the relevant model. + +## Inherit from ApplicationRecord + +Most models in the GitLab codebase should inherit from `ApplicationRecord`, +rather than from `ActiveRecord::Base`. This allows helper methods to be easily +added. + +An exception to this rule exists for models created in database migrations. As +these should be isolated from application code, they should continue to subclass +from `ActiveRecord::Base`. + ## Use UNIONs UNIONs aren't very commonly used in most Rails applications but they're very diff --git a/doc/development/testing.md b/doc/development/testing.md index 45b1519ece8..79ef8e75432 100644 --- a/doc/development/testing.md +++ b/doc/development/testing.md @@ -1 +1,5 @@ -This document was moved to [testing_guide/index.md](testing_guide/index.md). +--- +redirect_to: 'testing_guide/index.md' +--- + +This document was moved to [another location](testing_guide/index.md). diff --git a/doc/development/testing_guide/best_practices.md b/doc/development/testing_guide/best_practices.md index cfe0e6f70fc..7262c04d746 100644 --- a/doc/development/testing_guide/best_practices.md +++ b/doc/development/testing_guide/best_practices.md @@ -1,5 +1,20 @@ # Testing best practices +## Test Design + +Testing at GitLab is a first class citizen, not an afterthought. It's important we consider the design of our tests +as we do the design of our features. + +When implementing a feature, we think about developing the right capabilities the right way, which helps us +narrow our scope to a manageable level. When implementing tests for a feature, we must think about developing +the right tests, but then cover _all_ the important ways the test may fail, which can quickly widen our scope to +a level that is difficult to manage. + +Test heuristics can help solve this problem. They concisely address many of the common ways bugs +manifest themselves within our code. When designing our tests, take time to review known test heuristics to inform +our test design. We can find some helpful heuristics documented in the Handbook in the +[Test Design](https://about.gitlab.com/handbook/engineering/quality/guidelines/test-engineering/test-design/) section. + ## Test speed GitLab has a massive test suite that, without [parallelization], can take hours diff --git a/doc/development/testing_guide/end_to_end_tests.md b/doc/development/testing_guide/end_to_end_tests.md index 9837ea515a3..51fe19c3d9e 100644 --- a/doc/development/testing_guide/end_to_end_tests.md +++ b/doc/development/testing_guide/end_to_end_tests.md @@ -7,6 +7,24 @@ as expected across the entire software stack and architecture, including integration of all micro-services and components that are supposed to work together. +## Branch naming + +If your contribution contains **only** changes under the +[`qa/` folder](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/qa), you can +speed up the CI process by following some branch naming conventions. You have +three choices: + +| Branch name | Valid example | +|:----------------------|:-----------------------------| +| Starting with `qa/` | `qa/new-oauth-login-test` | +| Starting with `qa-` | `qa-new-oauth-login-test` | +| Ending in `-qa` | `123-new-oauth-login-test-qa` | + +If your branch name matches any of the above, it will run only the QA-related +jobs. +If it does not, the whole application test suite will run (including QA-related +jobs). + ## How do we test GitLab? We use [Omnibus GitLab][omnibus-gitlab] to build GitLab packages and then we diff --git a/doc/development/testing_guide/index.md b/doc/development/testing_guide/index.md index 67e4cfeda0e..ecad9ba48a3 100644 --- a/doc/development/testing_guide/index.md +++ b/doc/development/testing_guide/index.md @@ -33,7 +33,7 @@ changes should be tested. ## [Testing best practices](best_practices.md) -Everything you should know about how to write good tests: RSpec, FactoryBot, +Everything you should know about how to write good tests: Test Design, RSpec, FactoryBot, system tests, parameterized tests etc. --- diff --git a/doc/gitlab-basics/add-merge-request.md b/doc/gitlab-basics/add-merge-request.md index 5cc014419ad..7bb2e014738 100644 --- a/doc/gitlab-basics/add-merge-request.md +++ b/doc/gitlab-basics/add-merge-request.md @@ -8,7 +8,7 @@ request. For more information, check the --- 1. Before you start, you should have already [created a branch](create-branch.md) - and [pushed your changes](basic-git-commands.md) to GitLab. + and [pushed your changes](start-using-git.md#send-changes-to-gitlabcom) to GitLab. 1. Go to the project where you'd like to merge your changes and click on the **Merge requests** tab. 1. Click on **New merge request** on the right side of the screen. diff --git a/doc/gitlab-basics/create-group.md b/doc/gitlab-basics/create-group.md index 985a52d88f5..8c0d3561882 100644 --- a/doc/gitlab-basics/create-group.md +++ b/doc/gitlab-basics/create-group.md @@ -1,2 +1,5 @@ +--- +redirect_to: '../user/group/index.md#create-a-new-group' +--- This document was moved to [another location](../user/group/index.md#create-a-new-group). diff --git a/doc/gitlab-basics/create-issue.md b/doc/gitlab-basics/create-issue.md index abb163dbf18..818a03a6f02 100644 --- a/doc/gitlab-basics/create-issue.md +++ b/doc/gitlab-basics/create-issue.md @@ -1,2 +1,5 @@ +--- +redirect_to: '../user/project/issues/index.md#new-issue' +--- This document was moved to [another location](../user/project/issues/index.md#new-issue). diff --git a/doc/gitlab-basics/create-project.md b/doc/gitlab-basics/create-project.md index 3d4259ab7d3..3e99496d531 100644 --- a/doc/gitlab-basics/create-project.md +++ b/doc/gitlab-basics/create-project.md @@ -42,7 +42,9 @@ Project templates can pre-populate your project with necessary files to get you There are two types of project templates: -- [Built-in templates](#built-in-templates), sourced from the [`project-templates`](https://gitlab.com/gitlab-org/project-templates) group. +- [Built-in templates](#built-in-templates), sourced from the following groups: + - [`project-templates`](https://gitlab.com/gitlab-org/project-templates) + - [`pages`](https://gitlab.com/pages) - [Custom project templates](#custom-project-templates-premium-only), for custom templates configured by GitLab administrators and users. ### Built-in templates @@ -50,7 +52,7 @@ There are two types of project templates: Built-in templates are project templates that are: - Developed and maintained in the - [`project-templates`](https://gitlab.com/gitlab-org/project-templates) group. + [`project-templates`](https://gitlab.com/gitlab-org/project-templates) and [`pages`](https://gitlab.com/pages) groups. - Released with GitLab. To use a built-in template on the **New project** page: @@ -64,7 +66,7 @@ To use a built-in template on the **New project** page: TIP: **Tip:** You can improve the existing built-in templates or contribute new ones on the -[`project-templates`](https://gitlab.com/gitlab-org/project-templates) group. +[`project-templates`](https://gitlab.com/gitlab-org/project-templates) and [`pages`](https://gitlab.com/pages) groups. ### Custom project templates **[PREMIUM ONLY]** diff --git a/doc/install/aws/index.md b/doc/install/aws/index.md index 2fcc9b90157..0000e03f1d7 100644 --- a/doc/install/aws/index.md +++ b/doc/install/aws/index.md @@ -1,11 +1,11 @@ -# Installing GitLab on Amazon Web Services (AWS) - -To install GitLab on AWS, you can use the Amazon Machine Images (AMIs) that GitLab -provides with [each release](https://about.gitlab.com/releases/). +# Installing GitLab HA on Amazon Web Services (AWS) This page offers a walkthrough of a common HA (Highly Available) configuration for GitLab on AWS. You should customize it to accommodate your needs. +NOTE: **Note** +For organizations with 300 users or less, the recommended AWS installation method is to launch an EC2 single box [Omnibus Installation](https://about.gitlab.com/install/) and implement a snapshot strategy for backing up the data. + ## Introduction GitLab on AWS can leverage many of the services that are already @@ -55,6 +55,8 @@ Here's a list of the AWS services we will use, with links to pricing information - **ElastiCache**: An in-memory cache environment will be used to provide a High Availability Redis configuration. See the [Amazon ElastiCache pricing](https://aws.amazon.com/elasticache/pricing/). + +NOTE: **Note:** Please note that while we will be using EBS for storage, we do not recommend using EFS as it may negatively impact GitLab's performance. You can review the [relevant documentation](../../administration/high_availability/nfs.md#avoid-using-awss-elastic-file-system-efs) for more details. ## Creating an IAM EC2 instance role and profile To minimize the permissions of the user, we'll create a new [IAM](https://docs.aws.amazon.com/IAM/latest/UserGuide/introduction.html) diff --git a/doc/markdown/markdown.md b/doc/markdown/markdown.md index 4ac81ab3ee7..29cb6ac9164 100644 --- a/doc/markdown/markdown.md +++ b/doc/markdown/markdown.md @@ -1 +1,5 @@ -This document was moved to [user/markdown.md](../user/markdown.md). +--- +redirect_to: '../user/markdown.md' +--- + +This document was moved to [another location](../user/markdown.md). diff --git a/doc/operations/README.md b/doc/operations/README.md index d7a83948b87..319e5f48d38 100644 --- a/doc/operations/README.md +++ b/doc/operations/README.md @@ -1 +1,5 @@ +--- +redirect_to: '../administration/operations/index.md' +--- + This document was moved to [another location](../administration/operations/index.md). diff --git a/doc/operations/cleaning_up_redis_sessions.md b/doc/operations/cleaning_up_redis_sessions.md index 2a1d0a8c8eb..bde7fffd090 100644 --- a/doc/operations/cleaning_up_redis_sessions.md +++ b/doc/operations/cleaning_up_redis_sessions.md @@ -1 +1,5 @@ -This document was moved to [administration/operations/cleaning_up_redis_sessions](../administration/operations/cleaning_up_redis_sessions.md). +--- +redirect_to: '../administration/operations/cleaning_up_redis_sessions.md' +--- + +This document was moved to [another location](../administration/operations/cleaning_up_redis_sessions.md). diff --git a/doc/operations/moving_repositories.md b/doc/operations/moving_repositories.md index c54bca324a5..57d47e3e9ea 100644 --- a/doc/operations/moving_repositories.md +++ b/doc/operations/moving_repositories.md @@ -1 +1,5 @@ -This document was moved to [administration/operations/moving_repositories](../administration/operations/moving_repositories.md). +--- +redirect_to: '../administration/operations/moving_repositories.md' +--- + +This document was moved to [another location](../administration/operations/moving_repositories.md). diff --git a/doc/operations/sidekiq_memory_killer.md b/doc/operations/sidekiq_memory_killer.md index cf7c3b2e2ed..2df4a6e5648 100644 --- a/doc/operations/sidekiq_memory_killer.md +++ b/doc/operations/sidekiq_memory_killer.md @@ -1 +1,5 @@ -This document was moved to [administration/operations/sidekiq_memory_killer](../administration/operations/sidekiq_memory_killer.md). +--- +redirect_to: '../administration/operations/sidekiq_memory_killer.md' +--- + +This document was moved to [another location](../administration/operations/sidekiq_memory_killer.md). diff --git a/doc/operations/unicorn.md b/doc/operations/unicorn.md index fbc9697b755..949f4a66c9d 100644 --- a/doc/operations/unicorn.md +++ b/doc/operations/unicorn.md @@ -1 +1,5 @@ -This document was moved to [administration/operations/unicorn](../administration/operations/unicorn.md). +--- +redirect_to: '../administration/operations/unicorn.md' +--- + +This document was moved to [another location](../administration/operations/unicorn.md). diff --git a/doc/pages/README.md b/doc/pages/README.md index 7878bce3f10..c67847f1a83 100644 --- a/doc/pages/README.md +++ b/doc/pages/README.md @@ -1 +1,5 @@ -This document was moved to [pages/index.md](../user/project/pages/index.md). +--- +redirect_to: '../user/project/pages/index.md' +--- + +This document was moved to [another location](../user/project/pages/index.md). diff --git a/doc/pages/administration.md b/doc/pages/administration.md index 4eb3bb32c77..015dd54ec7f 100644 --- a/doc/pages/administration.md +++ b/doc/pages/administration.md @@ -1 +1,5 @@ -This document was moved to [administration/pages](../administration/pages/index.md). +--- +redirect_to: '../administration/pages/index.md' +--- + +This document was moved to [another location](../administration/pages/index.md). diff --git a/doc/pages/getting_started_part_one.md b/doc/pages/getting_started_part_one.md index 1d63ccb4d2f..a0feed0b477 100644 --- a/doc/pages/getting_started_part_one.md +++ b/doc/pages/getting_started_part_one.md @@ -1 +1,5 @@ +--- +redirect_to: '../user/project/pages/getting_started_part_one.md' +--- + This document was moved to [another location](../user/project/pages/getting_started_part_one.md). diff --git a/doc/pages/getting_started_part_three.md b/doc/pages/getting_started_part_three.md index 1697b5cd6b4..b65247ff7b7 100644 --- a/doc/pages/getting_started_part_three.md +++ b/doc/pages/getting_started_part_three.md @@ -1 +1,5 @@ +--- +redirect_to: '../user/project/pages/getting_started_part_three.md' +--- + This document was moved to [another location](../user/project/pages/getting_started_part_three.md). diff --git a/doc/pages/getting_started_part_two.md b/doc/pages/getting_started_part_two.md index a58affec73d..05353c171fc 100644 --- a/doc/pages/getting_started_part_two.md +++ b/doc/pages/getting_started_part_two.md @@ -1 +1,5 @@ +--- +redirect_to: '../user/project/pages/getting_started_part_two.md' +--- + This document was moved to [another location](../user/project/pages/getting_started_part_two.md). diff --git a/doc/raketasks/check.md b/doc/raketasks/check.md index f7f6a40cd04..ceb089e80c0 100644 --- a/doc/raketasks/check.md +++ b/doc/raketasks/check.md @@ -1,3 +1,5 @@ -# Check Rake Tasks +--- +redirect_to: '../administration/raketasks/check.md' +--- -This document was moved to [administration/raketasks/check](../administration/raketasks/check.md). +This document was moved to [another location](../administration/raketasks/check.md). diff --git a/doc/raketasks/maintenance.md b/doc/raketasks/maintenance.md index 266aeb7d60e..f554a09d94d 100644 --- a/doc/raketasks/maintenance.md +++ b/doc/raketasks/maintenance.md @@ -1,3 +1,5 @@ -# Maintenance Rake Tasks +--- +redirect_to: '../administration/raketasks/maintenance.md' +--- -This document was moved to [administration/raketasks/maintenance](../administration/raketasks/maintenance.md). +This document was moved to [another location](../administration/raketasks/maintenance.md). diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md index fb3f9711711..b9e3e6aea69 100644 --- a/doc/topics/autodevops/index.md +++ b/doc/topics/autodevops/index.md @@ -652,7 +652,7 @@ repo or by specifying a project variable: ### Custom Helm chart per environment **[PREMIUM]** -You can specify the use of a custom Helm chart per environment by scoping the environment variable +You can specify the use of a custom Helm chart per environment by scoping the environment variable to the desired environment. See [Limiting environment scopes of variables](https://docs.gitlab.com/ee/ci/variables/#limiting-environment-scopes-of-variables-premium). ### Customizing `.gitlab-ci.yml` @@ -901,7 +901,7 @@ increasing the rollout up to 100%. If `INCREMENTAL_ROLLOUT_MODE` is set to `manual` in your project, then instead of the standard `production` job, 4 different -[manual jobs](../../ci/pipelines.md#manual-actions-from-the-pipeline-graph) +[manual jobs](../../ci/pipelines.md#manual-actions-from-pipeline-graphs) will be created: 1. `rollout 10%` @@ -1022,10 +1022,9 @@ planned for a subsequent release. buildpack](#custom-buildpacks). - Auto Test may fail because of a mismatch between testing frameworks. In this case, you may need to customize your `.gitlab-ci.yml` with your test commands. -- Auto Deploy may fail if it is unable to create a Kubernetes namespace and - service account for your project. See the - [troubleshooting failed deployments](../../user/project/clusters/index.md#troubleshooting-failed-deployment-jobs) - section to debug why these resources were not created. +- Auto Deploy will fail if GitLab can not create a Kubernetes namespace and + service account for your project. For help debugging this issue, see + [Troubleshooting failed deployment jobs](../../user/project/clusters/index.md#troubleshooting-failed-deployment-jobs). ### Disable the banner instance wide diff --git a/doc/user/gitlab_com/index.md b/doc/user/gitlab_com/index.md index 5dc798fd8d3..2fb6cec55fa 100644 --- a/doc/user/gitlab_com/index.md +++ b/doc/user/gitlab_com/index.md @@ -71,6 +71,14 @@ or over the size limit, you can [reduce your repository size with Git](../projec | ----------- | ----------------- | ------------- | | Repository size including LFS | 10G | Unlimited | +## IP range + +GitLab.com, CI/CD, and related services are deployed into Google Cloud Platform (GCP). Any +IP based firewall can be configured by looking up all +[IP address ranges or CIDR blocks for GCP](https://cloud.google.com/compute/docs/faq#where_can_i_find_product_name_short_ip_ranges). + +[Static endpoints](https://gitlab.com/gitlab-com/gl-infra/infrastructure/issues/5071) are being considered. + ## Shared Runners Shared Runners on GitLab.com run in [autoscale mode] and powered by diff --git a/doc/user/group/clusters/index.md b/doc/user/group/clusters/index.md index e67795b9bae..984881ef26c 100644 --- a/doc/user/group/clusters/index.md +++ b/doc/user/group/clusters/index.md @@ -28,6 +28,7 @@ deployments. | [Helm Tiller](https://docs.helm.sh) | 11.6+ | Helm is a package manager for Kubernetes and is required to install all the other applications. It is installed in its own pod inside the cluster which can run the `helm` CLI in a safe environment. | n/a | | [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress) | 11.6+ | Ingress can provide load balancing, SSL termination, and name-based virtual hosting. It acts as a web proxy for your applications and is useful if you want to use [Auto DevOps](../../../topics/autodevops/index.md) or deploy your own web apps. | [stable/nginx-ingress](https://github.com/helm/charts/tree/master/stable/nginx-ingress) | | [Cert-Manager](https://docs.cert-manager.io/en/latest/) | 11.6+ | Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by [Let's Encrypt](https://letsencrypt.org/) and ensure that certificates are valid and up-to-date. | [stable/cert-manager](https://github.com/helm/charts/tree/master/stable/cert-manager) | +| [GitLab Runner](https://docs.gitlab.com/runner/) | 11.10+ | GitLab Runner is the open source project that is used to run your jobs and send the results back to GitLab. It is used in conjunction with [GitLab CI/CD](../../../ci/README.md), the open-source continuous integration service included with GitLab that coordinates the jobs. When installing the GitLab Runner via the applications, it will run in **privileged mode** by default. Make sure you read the [security implications](../../project/clusters/index.md#security-implications) before doing so. | [runner/gitlab-runner](https://gitlab.com/charts/gitlab-runner) | NOTE: **Note:** Some [cluster @@ -35,8 +36,6 @@ applications](../../project/clusters/index.md#installing-applications) are installable only for a project-level cluster. Support for installing these applications in a group-level cluster is planned for future releases. For updates, see: -- Support installing [Runner in group-level - clusters](https://gitlab.com/gitlab-org/gitlab-ce/issues/51988) - Support installing [JupyterHub in group-level clusters](https://gitlab.com/gitlab-org/gitlab-ce/issues/51989) - Support installing [Prometheus in group-level diff --git a/doc/user/markdown.md b/doc/user/markdown.md index d8bc3a9187e..8239742969a 100644 --- a/doc/user/markdown.md +++ b/doc/user/markdown.md @@ -267,10 +267,7 @@ However the wrapping tags cannot be mixed as such: ### Emoji -> If this is not rendered correctly, see -https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#emoji - -``` +```md Sometimes you want to :monkey: around a bit and add some :star2: to your :speech_balloon:. Well we have a gift for you: :zap: You can use emoji anywhere GFM is supported. :v: @@ -288,15 +285,15 @@ On Linux, you can download [Noto Color Emoji](https://www.google.com/get/noto/he Ubuntu 18.04 (like many modern Linux distros) has this font installed by default. ``` -Sometimes you want to <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/monkey.png" width="20px" height="20px"> around a bit and add some <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/star2.png" width="20px" height="20px"> to your <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/speech_balloon.png" width="20px" height="20px">. Well we have a gift for you: +Sometimes you want to <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/monkey.png" width="20px" height="20px" style="display:inline;margin:0"> around a bit and add some <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/star2.png" width="20px" height="20px" style="display:inline;margin:0"> to your <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/speech_balloon.png" width="20px" height="20px" style="display:inline;margin:0">. Well we have a gift for you: -<img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/zap.png" width="20px" height="20px">You can use emoji anywhere GFM is supported. <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/v.png" width="20px" height="20px"> +<img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/zap.png" width="20px" height="20px" style="display:inline;margin:0">You can use emoji anywhere GFM is supported. <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/v.png" width="20px" height="20px" style="display:inline;margin:0"> -You can use it to point out a <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/bug.png" width="20px" height="20px"> or warn about <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/speak_no_evil.png" width="20px" height="20px"> patches. And if someone improves your really <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/snail.png" width="20px" height="20px"> code, send them some <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/birthday.png" width="20px" height="20px">. People will <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/heart.png" width="20px" height="20px"> you for that. +You can use it to point out a <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/bug.png" width="20px" height="20px" style="display:inline;margin:0"> or warn about <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/speak_no_evil.png" width="20px" height="20px" style="display:inline;margin:0"> patches. And if someone improves your really <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/snail.png" width="20px" height="20px" style="display:inline;margin:0"> code, send them some <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/birthday.png" width="20px" height="20px" style="display:inline;margin:0">. People will <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/heart.png" width="20px" height="20px" style="display:inline;margin:0"> you for that. -If you are new to this, don't be <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/fearful.png" width="20px" height="20px">. You can easily join the emoji <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/family.png" width="20px" height="20px">. All you need to do is to look up one of the supported codes. +If you are new to this, don't be <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/fearful.png" width="20px" height="20px" style="display:inline;margin:0">. You can easily join the emoji <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/family.png" width="20px" height="20px" style="display:inline;margin:0">. All you need to do is to look up one of the supported codes. -Consult the [Emoji Cheat Sheet](https://www.webfx.com/tools/emoji-cheat-sheet/) for a list of all supported emoji codes. <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/thumbsup.png" width="20px" height="20px"> +Consult the [Emoji Cheat Sheet](https://www.webfx.com/tools/emoji-cheat-sheet/) for a list of all supported emoji codes. <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/thumbsup.png" width="20px" height="20px" style="display:inline;margin:0"> Most emoji are natively supported on macOS, Windows, iOS, Android and will fallback to image-based emoji where there is lack of support. diff --git a/doc/user/permissions.md b/doc/user/permissions.md index adc0f4d568b..a340dd063e4 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -119,7 +119,7 @@ The following table depicts the various user permission levels in a project. | Force push to protected branches [^4] | | | | | | | Remove protected branches [^4] | | | | | | | View project Audit Events | | | | ✓ | ✓ | -| View project statistics | | | | ✓ | ✓ | +| View project statistics | | ✓ | ✓ | ✓ | ✓ | ## Project features permissions diff --git a/doc/user/profile/account/two_factor_authentication.md b/doc/user/profile/account/two_factor_authentication.md index b74bd81d467..8b3ae19b544 100644 --- a/doc/user/profile/account/two_factor_authentication.md +++ b/doc/user/profile/account/two_factor_authentication.md @@ -18,9 +18,10 @@ the second factor of authentication. Once enabled, in addition to supplying your password to login, you'll be prompted to activate your U2F device (usually by pressing a button on it), and it will perform secure authentication on your behalf. -The U2F workflow is only [supported by](https://caniuse.com/#search=U2F) Google Chrome, Opera and Firefox at this point, so we _strongly_ recommend -that you set up both methods of two-factor authentication, so you can still access your account -from other browsers. +The U2F workflow is [supported by](https://caniuse.com/#search=U2F) Google Chrome, Opera, and Firefox. + +We recommend that you set up 2FA with both a [one-time password authenticator](#enable-2fa-via-one-time-password-authenticator) and a [U2F device](#enable-2fa-via-u2f-device), so you can still access your account +if you lose your U2F device. ## Enabling 2FA diff --git a/doc/user/profile/personal_access_tokens.md b/doc/user/profile/personal_access_tokens.md index 3a4d09c35d9..52b4a72e688 100644 --- a/doc/user/profile/personal_access_tokens.md +++ b/doc/user/profile/personal_access_tokens.md @@ -5,12 +5,9 @@ Personal access tokens are the preferred way for third party applications and scripts to authenticate with the [GitLab API][api], if using [OAuth2](../../api/oauth2.md) is not practical. -You can also use them to authenticate against Git over HTTP. They are the only -accepted method of authentication when you have -[Two-Factor Authentication (2FA)][2fa] enabled. +You can also use personal access tokens to authenticate against Git over HTTP or SSH. They must be used when you have [Two-Factor Authentication (2FA)][2fa] enabled. Authenticate with a token in place of your password. -Once you have your token, [pass it to the API][usage] using either the -`private_token` parameter or the `Private-Token` header. +To make [authenticated requests to the API][usage], use either the `private_token` parameter or the `Private-Token` header. The expiration of personal access tokens happens on the date you define, at midnight UTC. diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md index 5a74ac96e83..3a9a3b4a423 100644 --- a/doc/user/project/clusters/index.md +++ b/doc/user/project/clusters/index.md @@ -101,14 +101,20 @@ To add an existing Kubernetes cluster to your project: It's the URL that GitLab uses to access the Kubernetes API. Kubernetes exposes several APIs, we want the "base" URL that is common to all of them, e.g., `https://kubernetes.example.com` rather than `https://kubernetes.example.com/api/v1`. + + Get the API URL by running this command: + + ```sh + kubectl cluster-info | grep 'Kubernetes master' | awk '/http/ {print $NF}' + ``` - **CA certificate** (required) - A valid Kubernetes certificate is needed to authenticate to the EKS cluster. We will use the certificate created by default. - - List the secrets with `kubectl get secrets`, and one should named similar to + - List the secrets with `kubectl get secrets`, and one should named similar to `default-token-xxxxx`. Copy that token name for use below. - - Get the certificate by running this command: + - Get the certificate by running this command: - ```sh - kubectl get secret <secret name> -o jsonpath="{['data']['ca\.crt']}" | base64 --decode - ``` + ```sh + kubectl get secret <secret name> -o jsonpath="{['data']['ca\.crt']}" | base64 --decode + ``` - **Token** - GitLab authenticates against Kubernetes using service tokens, which are scoped to a particular `namespace`. @@ -124,23 +130,7 @@ To add an existing Kubernetes cluster to your project: metadata: name: gitlab-admin namespace: kube-system - ``` - - 2. Apply the service account to your cluster: - - ```bash - kubectl apply -f gitlab-admin-service-account.yaml - ``` - - Output: - - ```bash - serviceaccount "gitlab-admin" created - ``` - - 3. Create a file called `gitlab-admin-cluster-role-binding.yaml` with contents: - - ```yaml + --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRoleBinding metadata: @@ -155,41 +145,42 @@ To add an existing Kubernetes cluster to your project: namespace: kube-system ``` - 4. Apply the cluster role binding to your cluster: + 1. Apply the service account and cluster role binding to your cluster: ```bash - kubectl apply -f gitlab-admin-cluster-role-binding.yaml + kubectl apply -f gitlab-admin-service-account.yaml ``` Output: ```bash + serviceaccount "gitlab-admin" created clusterrolebinding "gitlab-admin" created ``` - 5. Retrieve the token for the `gitlab-admin` service account: + 1. Retrieve the token for the `gitlab-admin` service account: ```bash kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep gitlab-admin | awk '{print $1}') ``` - Copy the `<authentication_token>` value from the output: + Copy the `<authentication_token>` value from the output: - ```yaml - Name: gitlab-admin-token-b5zv4 - Namespace: kube-system - Labels: <none> - Annotations: kubernetes.io/service-account.name=gitlab-admin - kubernetes.io/service-account.uid=bcfe66ac-39be-11e8-97e8-026dce96b6e8 + ```yaml + Name: gitlab-admin-token-b5zv4 + Namespace: kube-system + Labels: <none> + Annotations: kubernetes.io/service-account.name=gitlab-admin + kubernetes.io/service-account.uid=bcfe66ac-39be-11e8-97e8-026dce96b6e8 - Type: kubernetes.io/service-account-token + Type: kubernetes.io/service-account-token - Data - ==== - ca.crt: 1025 bytes - namespace: 11 bytes - token: <authentication_token> - ``` + Data + ==== + ca.crt: 1025 bytes + namespace: 11 bytes + token: <authentication_token> + ``` NOTE: **Note:** For GKE clusters, you will need the @@ -212,14 +203,6 @@ To add an existing Kubernetes cluster to your project: After a couple of minutes, your cluster will be ready to go. You can now proceed to install some [pre-defined applications](#installing-applications). -To determine the: - -- API URL, run `kubectl cluster-info | grep 'Kubernetes master' | awk '/http/ {print $NF}'`. -- Token: - 1. List the secrets by running: `kubectl get secrets`. Note the name of the secret you need the token for. - 1. Get the token for the appropriate secret by running: `kubectl get secret <SECRET_NAME> -o jsonpath="{['data']['token']}" | base64 --decode`. -- CA certificate, run `kubectl get secret <secret name> -o jsonpath="{['data']['ca\.crt']}" | base64 --decode`. - ## Security implications CAUTION: **Important:** @@ -360,8 +343,8 @@ by GitLab before installing any of the applications. | [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) | 10.2+ | Ingress can provide load balancing, SSL termination, and name-based virtual hosting. It acts as a web proxy for your applications and is useful if you want to use [Auto DevOps] or deploy your own web apps. | [stable/nginx-ingress](https://github.com/helm/charts/tree/master/stable/nginx-ingress) | | [Cert-Manager](https://docs.cert-manager.io/en/latest/) | 11.6+ | Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by [Let's Encrypt](https://letsencrypt.org/) and ensure that certificates are valid and up-to-date. | [stable/cert-manager](https://github.com/helm/charts/tree/master/stable/cert-manager) | | [Prometheus](https://prometheus.io/docs/introduction/overview/) | 10.4+ | Prometheus is an open-source monitoring and alerting system useful to supervise your deployed applications. | [stable/prometheus](https://github.com/helm/charts/tree/master/stable/prometheus) | -| [GitLab Runner](https://docs.gitlab.com/runner/) | 10.6+ | GitLab Runner is the open source project that is used to run your jobs and send the results back to GitLab. It is used in conjunction with [GitLab CI/CD](https://about.gitlab.com/features/gitlab-ci-cd/), the open-source continuous integration service included with GitLab that coordinates the jobs. When installing the GitLab Runner via the applications, it will run in **privileged mode** by default. Make sure you read the [security implications](#security-implications) before doing so. | [runner/gitlab-runner](https://gitlab.com/charts/gitlab-runner) | -| [JupyterHub](http://jupyter.org/) | 11.0+ | [JupyterHub](https://jupyterhub.readthedocs.io/en/stable/) is a multi-user service for managing notebooks across a team. [Jupyter Notebooks](https://jupyter-notebook.readthedocs.io/en/latest/) provide a web-based interactive programming environment used for data analysis, visualization, and machine learning. We use a [custom Jupyter image](https://gitlab.com/gitlab-org/jupyterhub-user-image/blob/master/Dockerfile) that installs additional useful packages on top of the base Jupyter. Authentication will be enabled only for [project members](../members/index.md) with [Developer or higher](../../permissions.md) access to the project. You will also see ready-to-use DevOps Runbooks built with Nurtch's [Rubix library](https://github.com/amit1rrr/rubix). More information on creating executable runbooks can be found in [our Nurtch documentation](runbooks/index.md#nurtch-executable-runbooks). | [jupyter/jupyterhub](https://jupyterhub.github.io/helm-chart/) | +| [GitLab Runner](https://docs.gitlab.com/runner/) | 10.6+ | GitLab Runner is the open source project that is used to run your jobs and send the results back to GitLab. It is used in conjunction with [GitLab CI/CD](../../../ci/README.md), the open-source continuous integration service included with GitLab that coordinates the jobs. When installing the GitLab Runner via the applications, it will run in **privileged mode** by default. Make sure you read the [security implications](#security-implications) before doing so. | [runner/gitlab-runner](https://gitlab.com/charts/gitlab-runner) | +| [JupyterHub](http://jupyter.org/) | 11.0+ | [JupyterHub](https://jupyterhub.readthedocs.io/en/stable/) is a multi-user service for managing notebooks across a team. [Jupyter Notebooks](https://jupyter-notebook.readthedocs.io/en/latest/) provide a web-based interactive programming environment used for data analysis, visualization, and machine learning. We use a [custom Jupyter image](https://gitlab.com/gitlab-org/jupyterhub-user-image/blob/master/Dockerfile) that installs additional useful packages on top of the base Jupyter. Authentication will be enabled only for [project members](../members/index.md) with [Developer or higher](../../permissions.md) access to the project. You will also see ready-to-use DevOps Runbooks built with Nurtch's [Rubix library](https://github.com/amit1rrr/rubix). More information on creating executable runbooks can be found in [our Nurtch documentation](runbooks/index.md#nurtch-executable-runbooks). Note that Ingress must be installed and have an IP address assigned before JupyterHub can be installed. | [jupyter/jupyterhub](https://jupyterhub.github.io/helm-chart/) | | [Knative](https://cloud.google.com/knative) | 11.5+ | Knative provides a platform to create, deploy, and manage serverless workloads from a Kubernetes cluster. It is used in conjunction with, and includes [Istio](https://istio.io) to provide an external IP address for all programs hosted by Knative. You will be prompted to enter a wildcard domain where your applications will be exposed. Configure your DNS server to use the external IP address for that domain. For any application created and installed, they will be accessible as `<program_name>.<kubernetes_namespace>.<domain_name>`. This will require your kubernetes cluster to have [RBAC enabled](#role-based-access-control-rbac). | [knative/knative](https://storage.googleapis.com/triggermesh-charts) With the exception of Knative, the applications will be installed in a dedicated @@ -406,14 +389,20 @@ to obtain the endpoint. You can use either In order to publish your web application, you first need to find the endpoint which will be either an IP address or a hostname associated with your load balancer. -### Let GitLab fetch the external endpoint +### Automatically determining the external endpoint > [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17052) in GitLab 10.6. -If you [installed Ingress or Knative](#installing-applications), -you should see the Ingress Endpoint on this same page within a few minutes. -If you don't see this, GitLab might not be able to determine the external endpoint of -your ingress application in which case you should manually determine it. +After you install [Ingress or Knative](#installing-applications), Gitlab attempts to determine the external endpoint +and it should be available within a few minutes. If the endpoint doesn't appear +and your cluster runs on Google Kubernetes Engine: + +1. Check your [Kubernetes cluster on Google Kubernetes Engine](https://console.cloud.google.com/kubernetes) to ensure there are no errors on its nodes. +1. Ensure you have enough [Quotas](https://console.cloud.google.com/iam-admin/quotas) on Google Kubernetes Engine. For more information, see [Resource Quotas](https://cloud.google.com/compute/quotas). +1. Check [Google Cloud's Status](https://status.cloud.google.com/) to ensure they are not having any disruptions. + +If GitLab is still unable to determine the endpoint of your Ingress or Knative application, you can +manually determine it by following the steps below. ### Manually determining the external endpoint @@ -559,25 +548,23 @@ service account of the cluster integration. ### Troubleshooting failed deployment jobs GitLab will create a namespace and service account specifically for your -deployment jobs. These resources are created just before the deployment -job starts. Sometimes there may be errors that cause their creation to fail. +deployment jobs, immediately before the jobs starts. -In such instances, your job will fail with the message: +However, sometimes GitLab can not create them. In such instances, your job will fail with the message: -```The job failed to complete prerequisite tasks``` +```text +The job failed to complete prerequisite tasks +``` -You will need to check the [logs](../../../administration/logs.md) to debug -why the namespace and service account creation failed. +To find the cause of this error when creating a namespace and service account, check the [logs](../../../administration/logs.md#sidekiqlog). -A common reason for failure is that the token you gave GitLab did not have -[`cluster-admin`](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles) -privileges as GitLab expects. +Common reasons for failure include: -Another common problem is caused by a missing `KUBECONFIG` or `KUBE_TOKEN`. -To be passed to your job, it must have a matching -[`environment:name`](../../../ci/environments.md#defining-environments). If -your job has no `environment:name` set, it will not be passed the Kubernetes -credentials. +- The token you gave GitLab did not have [`cluster-admin`](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles) + privileges required by GitLab. +- Missing `KUBECONFIG` or `KUBE_TOKEN` variables. To be passed to your job, they must have a matching + [`environment:name`](../../../ci/environments.md#defining-environments). If your job has no + `environment:name` set, it will not be passed the Kubernetes credentials. ## Monitoring your Kubernetes cluster **[ULTIMATE]** diff --git a/doc/user/project/index.md b/doc/user/project/index.md index 4d19464cb7a..64139f9dbe9 100644 --- a/doc/user/project/index.md +++ b/doc/user/project/index.md @@ -55,11 +55,11 @@ When you create a project in GitLab, you'll have access to a large number of - [Auto Deploy](../../ci/autodeploy/index.md): Configure GitLab CI/CD to automatically set up your app's deployment - [Enable and disable GitLab CI](../../ci/enable_or_disable_ci.md) - - [Pipelines](../../ci/pipelines.md#pipelines): Configure and visualize + - [Pipelines](../../ci/pipelines.md): Configure and visualize your GitLab CI/CD pipelines from the UI - [Scheduled Pipelines](pipelines/schedules.md): Schedule a pipeline to start at a chosen time - - [Pipeline Graphs](../../ci/pipelines.md#pipeline-graphs): View your + - [Pipeline Graphs](../../ci/pipelines.md#visualizing-pipelines): View your entire pipeline from the UI - [Job artifacts](pipelines/job_artifacts.md): Define, browse, and download job artifacts diff --git a/doc/user/project/integrations/webhooks.md b/doc/user/project/integrations/webhooks.md index d324e0de0cc..8e1603f9ec9 100644 --- a/doc/user/project/integrations/webhooks.md +++ b/doc/user/project/integrations/webhooks.md @@ -48,7 +48,7 @@ Navigate to the webhooks page by going to your project's ## Use-cases - You can set up a webhook in GitLab to send a notification to - [Slack](https://api.slack.com/incoming-webhooks) every time a build fails, for example + [Slack](https://api.slack.com/incoming-webhooks) every time a job fails. - You can [integrate with Twilio to be notified via SMS](https://www.datadoghq.com/blog/send-alerts-sms-customizable-webhooks-twilio/) every time an issue is created for a specific project or group within GitLab - You can use them to [automatically assign labels to merge requests](https://about.gitlab.com/2016/08/19/applying-gitlab-labels-automatically/). @@ -1004,7 +1004,7 @@ X-Gitlab-Event: Pipeline Hook "email": "user@gitlab.com" } }, - "builds":[ + "jobs":[ { "id": 380, "stage": "deploy", @@ -1129,34 +1129,34 @@ X-Gitlab-Event: Pipeline Hook } ``` -### Build events +### Job events -Triggered on status change of a Build. +Triggered on status change of a job. **Request Header**: ``` -X-Gitlab-Event: Build Hook +X-Gitlab-Event: Job Hook ``` **Request Body**: ```json { - "object_kind": "build", + "object_kind": "job", "ref": "gitlab-script-trigger", "tag": false, "before_sha": "2293ada6b400935a1378653304eaf6221e0fdb8f", "sha": "2293ada6b400935a1378653304eaf6221e0fdb8f", - "build_id": 1977, - "build_name": "test", - "build_stage": "test", - "build_status": "created", - "build_started_at": null, - "build_finished_at": null, - "build_duration": null, - "build_allow_failure": false, - "build_failure_reason": "script_failure", + "job_id": 1977, + "job_name": "test", + "job_stage": "test", + "job_status": "created", + "job_started_at": null, + "job_finished_at": null, + "job_duration": null, + "job_allow_failure": false, + "job_failure_reason": "script_failure", "project_id": 380, "project_name": "gitlab-org/gitlab-test", "user": { diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md index 593eb80e044..01a3a5bbbe1 100644 --- a/doc/user/project/merge_requests/index.md +++ b/doc/user/project/merge_requests/index.md @@ -18,7 +18,7 @@ With GitLab merge requests, you can: - Live preview the changes when [Review Apps](../../../ci/review_apps/index.md) is configured for your project - Build, test, and deploy your code in a per-branch basis with built-in [GitLab CI/CD](../../../ci/README.md) - Prevent the merge request from being merged before it's ready with [WIP MRs](#work-in-progress-merge-requests) -- View the deployment process through [Pipeline Graphs](../../../ci/pipelines.md#pipeline-graphs) +- View the deployment process through [Pipeline Graphs](../../../ci/pipelines.md#visualizing-pipelines) - [Automatically close the issue(s)](../../project/issues/closing_issues.md#via-merge-request) that originated the implementation proposed in the merge request - Assign it to any registered user, and change the assignee how many times you need - Assign a [milestone](../../project/milestones/index.md) and track the development of a broader implementation diff --git a/doc/user/project/pipelines/schedules.md b/doc/user/project/pipelines/schedules.md index 2911a56cf67..89f1beb6d1f 100644 --- a/doc/user/project/pipelines/schedules.md +++ b/doc/user/project/pipelines/schedules.md @@ -2,29 +2,33 @@ > **Notes**: > -> - This feature was introduced in 9.1 as [Trigger Schedule][ce-10533]. -> - In 9.2, the feature was [renamed to Pipeline Schedule][ce-10853]. +> - Introduced in GitLab 9.1 as [Trigger Schedule](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10533). +> - [Renamed to Pipeline Schedule](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10853) in GitLab 9.2. > - Cron notation is parsed by [Fugit](https://github.com/floraison/fugit). -Pipeline schedules can be used to run a pipeline at specific intervals, for example every -month on the 22nd for a certain branch. +Pipelines are normally run based on certain conditions being met. For example, when a branch is pushed to repository. -## Using Pipeline schedules +Pipeline schedules can be used to also run [pipelines](../../../ci/pipelines.md) at specific intervals. For example: -In order to schedule a pipeline: +- Every month on the 22nd for a certain branch. +- Once every day. -1. Navigate to your project's **CI / CD ➔ Schedules** and click the - **New Schedule** button. -1. Fill in the form -1. Hit **Save pipeline schedule** for the changes to take effect. +In addition to using the GitLab UI, pipeline schedules can be maintained using the +[Pipeline schedules API](../../../api/pipeline_schedules.md). + +## Configuring pipeline schedules + +To schedule a pipeline for project: + +1. Navigate to the project's **CI / CD > Schedules** page. +1. Click the **New schedule** button. +1. Fill in the **Schedule a new pipeline** form. +1. Click the **Save pipeline schedule** button. ![New Schedule Form](img/pipeline_schedules_new_form.png) -> **Attention:** -The pipelines won't be executed precisely, because schedules are handled by -Sidekiq, which runs according to its interval. -See [advanced admin configuration](#advanced-admin-configuration) for more -information. +NOTE: **Note:** +Pipelines execution [timing is dependent](#advanced-configuration) on Sidekiq's own schedule. In the **Schedules** index page you can see a list of the pipelines that are scheduled to run. The next run is automatically calculated by the server GitLab @@ -32,36 +36,24 @@ is installed on. ![Schedules list](img/pipeline_schedules_list.png) -### Running a scheduled pipeline manually - -> [Introduced][ce-15700] in GitLab 10.4. - -To trigger a pipeline schedule manually, click the "Play" button: - -![Play Pipeline Schedule](img/pipeline_schedule_play.png) - -This will schedule a background job to run the pipeline schedule. A flash -message will provide a link to the CI/CD Pipeline index page. - -To help avoid abuse, users are rate limited to triggering a pipeline once per -minute. - -### Making use of scheduled pipeline variables +### Using variables -> [Introduced][ce-12328] in GitLab 9.4. +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12328) in GitLab 9.4. You can pass any number of arbitrary variables and they will be available in -GitLab CI so that they can be used in your `.gitlab-ci.yml` file. +GitLab CI so that they can be used in your [`.gitlab-ci.yml` file](../../../ci/yaml/README.md). ![Scheduled pipeline variables](img/pipeline_schedule_variables.png) -## Using only and except +### Using only and except To configure that a job can be executed only when the pipeline has been scheduled (or the opposite), you can use [only and except](../../../ci/yaml/README.md#onlyexcept-basic) configuration keywords. -``` +For example: + +```yaml job:on-schedule: only: - schedules @@ -75,11 +67,47 @@ job: - make build ``` -## Taking ownership +### Advanced configuration + +The pipelines won't be executed exactly on schedule because schedules are handled by +Sidekiq, which runs according to its interval. + +For example, only two pipelines will be created per day if: -Pipelines are executed as a user, who owns a schedule. This influences what -projects and other resources the pipeline has access to. If a user does not own -a pipeline, you can take ownership by clicking the **Take ownership** button. +- You set a schedule to create a pipeline every minute (`* * * * *`). +- The Sidekiq worker runs on 00:00 and 12:00 every day (`0 */12 * * *`). + +To change the Sidekiq worker's frequency: + +1. Edit the `pipeline_schedule_worker_cron` value in your instance's `gitlab.rb` file. +1. [Restart GitLab](../../../administration/restart_gitlab.md). + +For GitLab.com, refer to the [dedicated settings page](../../gitlab_com/index.md#cron-jobs). + +## Working with scheduled pipelines + +Once configured, GitLab supports many functions for working with scheduled pipelines. + +### Running manually + +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15700) in GitLab 10.4. + +To trigger a pipeline schedule manually, click the "Play" button: + +![Play Pipeline Schedule](img/pipeline_schedule_play.png) + +This will schedule a background job to run the pipeline schedule. A flash +message will provide a link to the CI/CD Pipeline index page. + +NOTE: **Note:** +To help avoid abuse, users are rate limited to triggering a pipeline once per +minute. + +### Taking ownership + +Pipelines are executed as a user, who owns a schedule. This influences what projects and other resources the pipeline has access to. + +If a user does not own a pipeline, you can take ownership by clicking the **Take ownership** button. The next time a pipeline is scheduled, your credentials will be used. ![Schedules list](img/pipeline_schedules_ownership.png) @@ -90,20 +118,3 @@ on the target branch, the schedule will stop creating new pipelines. This can happen if, for example, the owner is blocked or removed from the project, or the target branch or tag is protected. In this case, someone with sufficient privileges must take ownership of the schedule. - -## Advanced admin configuration - -The pipelines won't be executed precisely, because schedules are handled by -Sidekiq, which runs according to its interval. For example, if you set a -schedule to create a pipeline every minute (`* * * * *`) and the Sidekiq worker -runs on 00:00 and 12:00 every day (`0 */12 * * *`), only 2 pipelines will be -created per day. To change the Sidekiq worker's frequency, you have to edit the -`pipeline_schedule_worker_cron` value in your `gitlab.rb` and restart GitLab. -For GitLab.com, you can check the [dedicated settings page][settings]. If you -don't have admin access to the server, ask your administrator. - -[ce-10533]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10533 -[ce-10853]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10853 -[ce-12328]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12328 -[ce-15700]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15700 -[settings]: https://about.gitlab.com/gitlab-com/settings/#cron-jobs diff --git a/doc/user/project/wiki/index.md b/doc/user/project/wiki/index.md index 532247a98cd..95f606fd786 100644 --- a/doc/user/project/wiki/index.md +++ b/doc/user/project/wiki/index.md @@ -25,7 +25,7 @@ message. ## Creating a new wiki page NOTE: **Note:** -A [permission level][permissions] of **Developer** is needed to create Wiki pages. +Requires Developer [permissions](../../permissions.md). Create a new page by clicking the **New page** button that can be found in all wiki pages. You will be asked to fill in the page name from which GitLab @@ -58,7 +58,7 @@ repository, you will have to upload them again. ## Editing a wiki page NOTE: **Note:** -A [permission level][permissions] of **Developer** is needed to edit Wiki pages. +Requires Developer [permissions](../../permissions.md). To edit a page, simply click on the **Edit** button. From there on, you can change its content. When done, click **Save changes** for the changes to take @@ -67,7 +67,7 @@ effect. ## Deleting a wiki page NOTE: **Note:** -A [permission level][permissions] of **Maintainer** is needed to delete Wiki pages. +Requires Maintainer [permissions](../../permissions.md). You can find the **Delete** button only when editing a page. Click on it and confirm you want the page to be deleted. @@ -114,8 +114,6 @@ them like you would do with every other Git repository. On the right sidebar, click on **Clone repository** and follow the on-screen instructions. -[permissions]: ../../permissions.md - ## Customizing sidebar By default, the wiki would render a sidebar which lists all the pages for the diff --git a/doc/workflow/add-user/add-user.md b/doc/workflow/add-user/add-user.md index 35cc080d2b7..f1ec771dd9a 100644 --- a/doc/workflow/add-user/add-user.md +++ b/doc/workflow/add-user/add-user.md @@ -1 +1,5 @@ +--- +redirect_to: '../../user/project/members/index.md' +--- + This document was moved to [../../user/project/members/index.md](../../user/project/members/index.md) diff --git a/doc/workflow/authorization_for_merge_requests.md b/doc/workflow/authorization_for_merge_requests.md index 7bf80a3ad0d..8e43d340613 100644 --- a/doc/workflow/authorization_for_merge_requests.md +++ b/doc/workflow/authorization_for_merge_requests.md @@ -1 +1,5 @@ +--- +redirect_to: '../user/project/merge_requests/authorization_for_merge_requests.md' +--- + This document was moved to [user/project/merge_requests/authorization_for_merge_requests](../user/project/merge_requests/authorization_for_merge_requests.md) diff --git a/doc/workflow/award_emoji.md b/doc/workflow/award_emoji.md index d74378cc564..02db97b8dd6 100644 --- a/doc/workflow/award_emoji.md +++ b/doc/workflow/award_emoji.md @@ -1 +1,5 @@ +--- +redirect_to: '../user/award_emojis.md' +--- + This document was moved to [another location](../user/award_emojis.md). diff --git a/doc/workflow/cherry_pick_changes.md b/doc/workflow/cherry_pick_changes.md index 663ffd3f746..29c4f854416 100644 --- a/doc/workflow/cherry_pick_changes.md +++ b/doc/workflow/cherry_pick_changes.md @@ -1 +1,5 @@ -This document was moved to [user/project/merge_requests/cherry_pick_changes](../user/project/merge_requests/cherry_pick_changes.md). +--- +redirect_to: '../user/project/merge_requests/cherry_pick_changes.md' +--- + +This document was moved to [another location](../user/project/merge_requests/cherry_pick_changes.md). diff --git a/doc/workflow/importing/README.md b/doc/workflow/importing/README.md index e0a445920b4..29da321ba46 100644 --- a/doc/workflow/importing/README.md +++ b/doc/workflow/importing/README.md @@ -1 +1,5 @@ +---
+redirect_to: '../../user/project/import/index.md'
+---
+
This document was moved to [another location](../../user/project/import/index.md).
diff --git a/doc/workflow/importing/import_projects_from_bitbucket.md b/doc/workflow/importing/import_projects_from_bitbucket.md index ec9a11f390e..a42ba7d4518 100644 --- a/doc/workflow/importing/import_projects_from_bitbucket.md +++ b/doc/workflow/importing/import_projects_from_bitbucket.md @@ -1 +1,5 @@ +---
+redirect_to: '../../user/project/import/bitbucket.md'
+---
+
This document was moved to [another location](../../user/project/import/bitbucket.md).
diff --git a/doc/workflow/importing/import_projects_from_fogbugz.md b/doc/workflow/importing/import_projects_from_fogbugz.md index 876eb0434f0..f5c791dc6de 100644 --- a/doc/workflow/importing/import_projects_from_fogbugz.md +++ b/doc/workflow/importing/import_projects_from_fogbugz.md @@ -1 +1,5 @@ +--- +redirect_to: '../../user/project/import/fogbugz.md' +--- + This document was moved to [another location](../../user/project/import/fogbugz.md). diff --git a/doc/workflow/importing/import_projects_from_gitea.md b/doc/workflow/importing/import_projects_from_gitea.md index 8b55b6c23eb..df053835b44 100644 --- a/doc/workflow/importing/import_projects_from_gitea.md +++ b/doc/workflow/importing/import_projects_from_gitea.md @@ -1 +1,5 @@ +--- +redirect_to: '../../user/project/import/gitea.md' +--- + This document was moved to [another location](../../user/project/import/gitea.md). diff --git a/doc/workflow/importing/import_projects_from_github.md b/doc/workflow/importing/import_projects_from_github.md index 72dfe5403c3..6397fcc74b8 100644 --- a/doc/workflow/importing/import_projects_from_github.md +++ b/doc/workflow/importing/import_projects_from_github.md @@ -1 +1,5 @@ +---
+redirect_to: '../../user/project/import/github.md'
+---
+
This document was moved to [another location](../../user/project/import/github.md).
diff --git a/doc/workflow/importing/import_projects_from_gitlab_com.md b/doc/workflow/importing/import_projects_from_gitlab_com.md index 3256088c014..135b9704df9 100644 --- a/doc/workflow/importing/import_projects_from_gitlab_com.md +++ b/doc/workflow/importing/import_projects_from_gitlab_com.md @@ -1 +1,5 @@ +--- +redirect_to: '../../user/project/import/gitlab_com.md' +--- + This document was moved to [another location](../../user/project/import/gitlab_com.md). diff --git a/doc/workflow/importing/migrating_from_svn.md b/doc/workflow/importing/migrating_from_svn.md index 32a75a6c6af..99f13d6354c 100644 --- a/doc/workflow/importing/migrating_from_svn.md +++ b/doc/workflow/importing/migrating_from_svn.md @@ -1 +1,5 @@ +--- +redirect_to: '../../user/project/import/svn.md' +--- + This document was moved to [another location](../../user/project/import/svn.md). diff --git a/doc/workflow/labels.md b/doc/workflow/labels.md index 5c09891dfdd..3d07d411dd4 100644 --- a/doc/workflow/labels.md +++ b/doc/workflow/labels.md @@ -1,3 +1,7 @@ +--- +redirect_to: '../user/project/labels.md' +--- + # Labels This document was moved to [user/project/labels.md](../user/project/labels.md). diff --git a/doc/workflow/lfs/lfs_administration.md b/doc/workflow/lfs/lfs_administration.md index 3fb553280d0..6d941135bf2 100644 --- a/doc/workflow/lfs/lfs_administration.md +++ b/doc/workflow/lfs/lfs_administration.md @@ -108,6 +108,18 @@ Here is a configuration example with GCS. _NOTE: The service account must have permission to access the bucket. [See more](https://cloud.google.com/storage/docs/authentication)_ +Here is a configuration example with Rackspace Cloud Files. + +| Setting | Description | example | +|---------|-------------|---------| +| `provider` | The provider name | `Rackspace` | +| `rackspace_username` | The username of the Rackspace account with access to the container | `joe.smith` | +| `rackspace_api_key` | The API key of the Rackspace account with access to the container | `ABC123DEF456ABC123DEF456ABC123DE` | +| `rackspace_region` | The Rackspace storage region to use, a three letter code from the [list of service access endpoints](https://developer.rackspace.com/docs/cloud-files/v1/general-api-info/service-access/) | `iad` | +| `rackspace_temp_url_key` | The private key you have set in the Rackspace API for temporary URLs. Read more [here](https://developer.rackspace.com/docs/cloud-files/v1/use-cases/public-access-to-your-cloud-files-account/#tempurl) | `ABC123DEF456ABC123DEF456ABC123DE` | + +_NOTES: Regardless of whether the container has public access enabled or disabled, Fog will use the TempURL method to grant access to LFS objects. If you see errors in logs referencing instantiating storage with a temp-url-key, ensure that you have set they key properly on the Rackspace API and in gitlab.rb. You can verify the value of the key Rackspace has set by sending a GET request with token header to the service access endpoint URL and comparing the output of the returned headers._ + ### Manual uploading to an object storage There are two ways to manually do the same thing as automatic uploading (described above). diff --git a/doc/workflow/merge_requests.md b/doc/workflow/merge_requests.md index dc6da1938f3..fd9f9b81bc9 100644 --- a/doc/workflow/merge_requests.md +++ b/doc/workflow/merge_requests.md @@ -1 +1,5 @@ +--- +redirect_to: '../user/project/merge_requests/index.md' +--- + This document was moved to [user/project/merge_requests/index.md](../user/project/merge_requests/index.md). diff --git a/doc/workflow/merge_when_build_succeeds.md b/doc/workflow/merge_when_build_succeeds.md index b4f6d6117de..41e6ff0cdd6 100644 --- a/doc/workflow/merge_when_build_succeeds.md +++ b/doc/workflow/merge_when_build_succeeds.md @@ -1 +1,5 @@ +--- +redirect_to: '../user/project/merge_requests/merge_when_pipeline_succeeds.md' +--- + This document was moved to [merge_when_pipeline_succeeds](../user/project/merge_requests/merge_when_pipeline_succeeds.md). diff --git a/doc/workflow/milestones.md b/doc/workflow/milestones.md index 69eb6b286b0..18dc15f7327 100644 --- a/doc/workflow/milestones.md +++ b/doc/workflow/milestones.md @@ -1 +1,5 @@ +--- +redirect_to: '../user/project/milestones/index.md' +--- + This document was moved to [another location](../user/project/milestones/index.md). diff --git a/doc/workflow/project_features.md b/doc/workflow/project_features.md index feb88712f5a..f54afb768a1 100644 --- a/doc/workflow/project_features.md +++ b/doc/workflow/project_features.md @@ -1 +1,5 @@ +--- +redirect_to: '../user/project/index.md' +--- + This document was moved to [../user/project/index.md](../user/project/index.md) diff --git a/doc/workflow/protected_branches.md b/doc/workflow/protected_branches.md index ced7d391ace..1bcac4a2de5 100644 --- a/doc/workflow/protected_branches.md +++ b/doc/workflow/protected_branches.md @@ -1 +1,5 @@ +--- +redirect_to: '../user/project/protected_branches.md' +--- + This document was moved to [another location](../user/project/protected_branches.md). diff --git a/doc/workflow/revert_changes.md b/doc/workflow/revert_changes.md index cf1292253fc..15f199af703 100644 --- a/doc/workflow/revert_changes.md +++ b/doc/workflow/revert_changes.md @@ -1 +1,5 @@ +--- +redirect_to: '../user/project/merge_requests/revert_changes.md' +--- + This document was moved to [user/project/merge_requests/revert_changes](../user/project/merge_requests/revert_changes.md). diff --git a/doc/workflow/share_projects_with_other_groups.md b/doc/workflow/share_projects_with_other_groups.md index 2eb4d24958a..c39cd78f32d 100644 --- a/doc/workflow/share_projects_with_other_groups.md +++ b/doc/workflow/share_projects_with_other_groups.md @@ -1 +1,5 @@ +--- +redirect_to: '../user/project/members/share_project_with_groups.md' +--- + This document was moved to [../user/project/members/share_project_with_groups.md](../user/project/members/share_project_with_groups.md) diff --git a/doc/workflow/share_with_group.md b/doc/workflow/share_with_group.md index 2eb4d24958a..c39cd78f32d 100644 --- a/doc/workflow/share_with_group.md +++ b/doc/workflow/share_with_group.md @@ -1 +1,5 @@ +--- +redirect_to: '../user/project/members/share_project_with_groups.md' +--- + This document was moved to [../user/project/members/share_project_with_groups.md](../user/project/members/share_project_with_groups.md) diff --git a/doc/workflow/web_editor.md b/doc/workflow/web_editor.md index 595c7da155b..2366372d984 100644 --- a/doc/workflow/web_editor.md +++ b/doc/workflow/web_editor.md @@ -1 +1,5 @@ +--- +redirect_to: '../user/project/repository/web_editor.md' +--- + This document was moved to [user/project/repository/web_editor](../user/project/repository/web_editor.md). diff --git a/doc/workflow/wip_merge_requests.md b/doc/workflow/wip_merge_requests.md index abb8002f442..020455dcbdc 100644 --- a/doc/workflow/wip_merge_requests.md +++ b/doc/workflow/wip_merge_requests.md @@ -1 +1,5 @@ +--- +redirect_to: '../user/project/merge_requests/work_in_progress_merge_requests.md' +--- + This document was moved to [user/project/merge_requests/work_in_progress_merge_requests](../user/project/merge_requests/work_in_progress_merge_requests.md). diff --git a/jest.config.js b/jest.config.js index 1f6e04390ae..c7518be9e96 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,4 +1,4 @@ -/* eslint-disable filenames/match-regex */ +const IS_EE = require('./config/helpers/is_ee_env'); const reporters = ['default']; @@ -37,4 +37,8 @@ module.exports = { }, transformIgnorePatterns: ['node_modules/(?!(@gitlab/ui)/)'], timers: 'fake', + testEnvironment: '<rootDir>/spec/frontend/environment.js', + testEnvironmentOptions: { + IS_EE, + }, }; diff --git a/lib/api/branches.rb b/lib/api/branches.rb index 07f529b01bb..5c98b0ad56c 100644 --- a/lib/api/branches.rb +++ b/lib/api/branches.rb @@ -34,11 +34,11 @@ module API repository = user_project.repository branches = BranchesFinder.new(repository, declared_params(include_missing: false)).execute - branches = ::Kaminari.paginate_array(branches) + branches = paginate(::Kaminari.paginate_array(branches)) merged_branch_names = repository.merged_branch_names(branches.map(&:name)) present( - paginate(branches), + branches, with: Entities::Branch, current_user: current_user, project: user_project, diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 611523a2444..4533305bfd3 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -275,7 +275,7 @@ module API expose :printing_merge_request_link_enabled expose :merge_method expose :statistics, using: 'API::Entities::ProjectStatistics', if: -> (project, options) { - options[:statistics] && Ability.allowed?(options[:current_user], :download_code, project) + options[:statistics] && Ability.allowed?(options[:current_user], :read_statistics, project) } # rubocop: disable CodeReuse/ActiveRecord @@ -1558,8 +1558,6 @@ module API class Suggestion < Grape::Entity expose :id - expose :from_original_line - expose :to_original_line expose :from_line expose :to_line expose :appliable?, as: :appliable @@ -1590,7 +1588,7 @@ module API end class Cluster < Grape::Entity - expose :id, :name, :created_at + expose :id, :name, :created_at, :domain expose :provider_type, :platform_type, :environment_scope, :cluster_type expose :user, using: Entities::UserBasic expose :platform_kubernetes, using: Entities::Platform::Kubernetes diff --git a/lib/api/groups.rb b/lib/api/groups.rb index cb0d6d96f29..9fcf476f537 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -20,8 +20,19 @@ module API optional :share_with_group_lock, type: Boolean, desc: 'Prevent sharing a project with another group within this group' end + if Gitlab.ee? + params :optional_params_ee do + optional :membership_lock, type: Boolean, desc: 'Prevent adding new members to project membership within this group' + optional :ldap_cn, type: String, desc: 'LDAP Common Name' + optional :ldap_access, type: Integer, desc: 'A valid access level' + optional :shared_runners_minutes_limit, type: Integer, desc: '(admin-only) Pipeline minutes quota for this group' + all_or_none_of :ldap_cn, :ldap_access + end + end + params :optional_params do use :optional_params_ce + use :optional_params_ee if Gitlab.ee? end params :statistics_params do @@ -164,6 +175,10 @@ module API optional :name, type: String, desc: 'The name of the group' optional :path, type: String, desc: 'The path of the group' use :optional_params + + if Gitlab.ee? + optional :file_template_project_id, type: Integer, desc: 'The ID of a project to use for custom templates in this group' + end end put ':id' do group = find_group!(params[:id]) diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index b8bd180bdc1..8a21d44b4bf 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -302,6 +302,12 @@ module API end # rubocop: enable CodeReuse/ActiveRecord + # rubocop: disable CodeReuse/ActiveRecord + def filter_by_title(items, title) + items.where(title: title) + end + # rubocop: enable CodeReuse/ActiveRecord + def filter_by_search(items, text) items.search(text) end diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb index fe78049af87..3fd824877ae 100644 --- a/lib/api/helpers/internal_helpers.rb +++ b/lib/api/helpers/internal_helpers.rb @@ -5,9 +5,11 @@ module API module InternalHelpers attr_reader :redirected_path - def wiki? - set_project unless defined?(@wiki) # rubocop:disable Gitlab/ModuleWithInstanceVariables - @wiki # rubocop:disable Gitlab/ModuleWithInstanceVariables + delegate :wiki?, to: :repo_type + + def repo_type + set_project unless defined?(@repo_type) # rubocop:disable Gitlab/ModuleWithInstanceVariables + @repo_type # rubocop:disable Gitlab/ModuleWithInstanceVariables end def project @@ -67,10 +69,10 @@ module API # rubocop:disable Gitlab/ModuleWithInstanceVariables def set_project if params[:gl_repository] - @project, @wiki = Gitlab::GlRepository.parse(params[:gl_repository]) + @project, @repo_type = Gitlab::GlRepository.parse(params[:gl_repository]) @redirected_path = nil else - @project, @wiki, @redirected_path = Gitlab::RepoPath.parse(params[:project]) + @project, @repo_type, @redirected_path = Gitlab::RepoPath.parse(params[:project]) end end # rubocop:enable Gitlab/ModuleWithInstanceVariables @@ -78,7 +80,7 @@ module API # Project id to pass between components that don't share/don't have # access to the same filesystem mounts def gl_repository - Gitlab::GlRepository.gl_repository(project, wiki?) + repo_type.identifier_for_subject(project) end def gl_project_path @@ -92,7 +94,7 @@ module API # Return the repository depending on whether we want the wiki or the # regular repository def repository - if wiki? + if repo_type.wiki? project.wiki.repository else project.repository diff --git a/lib/api/helpers/issues_helpers.rb b/lib/api/helpers/issues_helpers.rb new file mode 100644 index 00000000000..f6762910b0c --- /dev/null +++ b/lib/api/helpers/issues_helpers.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module API + module Helpers + module IssuesHelpers + def self.update_params_at_least_one_of + [ + :assignee_id, + :assignee_ids, + :confidential, + :created_at, + :description, + :discussion_locked, + :due_date, + :labels, + :milestone_id, + :state_event, + :title + ] + end + end + end +end diff --git a/lib/api/helpers/projects_helpers.rb b/lib/api/helpers/projects_helpers.rb index e6a72b949f9..7b858dc2e72 100644 --- a/lib/api/helpers/projects_helpers.rb +++ b/lib/api/helpers/projects_helpers.rb @@ -31,11 +31,50 @@ module API optional :initialize_with_readme, type: Boolean, desc: "Initialize a project with a README.md" end + if Gitlab.ee? + params :optional_project_params_ee do + optional :repository_storage, type: String, desc: 'Which storage shard the repository is on. Available only to admins' + optional :approvals_before_merge, type: Integer, desc: 'How many approvers should approve merge request by default' + optional :external_authorization_classification_label, type: String, desc: 'The classification label for the project' + optional :mirror, type: Boolean, desc: 'Enables pull mirroring in a project' + optional :mirror_trigger_builds, type: Boolean, desc: 'Pull mirroring triggers builds' + end + end + params :optional_project_params do use :optional_project_params_ce + use :optional_project_params_ee if Gitlab.ee? end end end + + def self.update_params_at_least_one_of + [ + :jobs_enabled, + :resolve_outdated_diff_discussions, + :ci_config_path, + :container_registry_enabled, + :default_branch, + :description, + :issues_enabled, + :lfs_enabled, + :merge_requests_enabled, + :merge_method, + :name, + :only_allow_merge_if_all_discussions_are_resolved, + :only_allow_merge_if_pipeline_succeeds, + :path, + :printing_merge_request_link_enabled, + :public_builds, + :request_access_enabled, + :shared_runners_enabled, + :snippets_enabled, + :tag_list, + :visibility, + :wiki_enabled, + :avatar + ] + end end end end diff --git a/lib/api/internal.rb b/lib/api/internal.rb index 7f4a00f1389..cb9aa849eeb 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -59,7 +59,7 @@ module API actor end - access_checker_klass = wiki? ? Gitlab::GitAccessWiki : Gitlab::GitAccess + access_checker_klass = repo_type.access_checker_class access_checker = access_checker_klass.new(actor, project, protocol, authentication_abilities: ssh_authentication_abilities, namespace_path: namespace_path, project_path: project_path, diff --git a/lib/api/issues.rb b/lib/api/issues.rb index b2ec4ed898e..fae20e45bf9 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -8,15 +8,6 @@ module API helpers ::Gitlab::IssuableMetadata - # EE::API::Issues would override the following helpers - helpers do - params :issues_params_ee do - end - - params :issue_params_ee do - end - end - helpers do # rubocop: disable CodeReuse/ActiveRecord def find_issues(args = {}) @@ -33,6 +24,16 @@ module API end # rubocop: enable CodeReuse/ActiveRecord + if Gitlab.ee? + params :issues_params_ee do + optional :weight, types: [Integer, String], integer_none_any: true, desc: 'The weight of the issue' + end + + params :issue_params_ee do + optional :weight, type: Integer, desc: 'The weight of the issue' + end + end + params :issues_params do optional :labels, type: Array[String], coerce_with: Validations::Types::LabelsList.coerce, desc: 'Comma-separated list of label names' optional :milestone, type: String, desc: 'Milestone title' @@ -57,7 +58,7 @@ module API optional :confidential, type: Boolean, desc: 'Filter confidential or public issues' use :pagination - use :issues_params_ee + use :issues_params_ee if Gitlab.ee? end params :issue_params do @@ -70,7 +71,7 @@ module API optional :confidential, type: Boolean, desc: 'Boolean parameter if the issue should be confidential' optional :discussion_locked, type: Boolean, desc: " Boolean parameter indicating if the issue's discussion is locked" - use :issue_params_ee + use :issue_params_ee if Gitlab.ee? end end @@ -219,8 +220,8 @@ module API desc: 'Date time when the issue was updated. Available only for admins and project owners.' optional :state_event, type: String, values: %w[reopen close], desc: 'State of the issue' use :issue_params - at_least_one_of :title, :description, :assignee_ids, :assignee_id, :milestone_id, :discussion_locked, - :labels, :created_at, :due_date, :confidential, :state_event + + at_least_one_of(*Helpers::IssuesHelpers.update_params_at_least_one_of) end # rubocop: disable CodeReuse/ActiveRecord put ':id/issues/:issue_iid' do diff --git a/lib/api/milestone_responses.rb b/lib/api/milestone_responses.rb index a0ca39b69d4..62e159ab003 100644 --- a/lib/api/milestone_responses.rb +++ b/lib/api/milestone_responses.rb @@ -16,6 +16,7 @@ module API optional :state, type: String, values: %w[active closed all], default: 'all', desc: 'Return "active", "closed", or "all" milestones' optional :iids, type: Array[Integer], desc: 'The IIDs of the milestones' + optional :title, type: String, desc: 'The title of the milestones' optional :search, type: String, desc: 'The search criteria for the title or description of the milestone' use :pagination end @@ -33,6 +34,7 @@ module API milestones = parent.milestones.order_id_desc milestones = Milestone.filter_by_state(milestones, params[:state]) milestones = filter_by_iid(milestones, params[:iids]) if params[:iids].present? + milestones = filter_by_title(milestones, params[:title]) if params[:title] milestones = filter_by_search(milestones, params[:search]) if params[:search] present paginate(milestones), with: Entities::Milestone diff --git a/lib/api/project_clusters.rb b/lib/api/project_clusters.rb index c96261a7b57..b62ec887183 100644 --- a/lib/api/project_clusters.rb +++ b/lib/api/project_clusters.rb @@ -53,6 +53,7 @@ module API params do requires :name, type: String, desc: 'Cluster name' optional :enabled, type: Boolean, default: true, desc: 'Determines if cluster is active or not, defaults to true' + optional :domain, type: String, desc: 'Cluster base domain' requires :platform_kubernetes_attributes, type: Hash, desc: %q(Platform Kubernetes data) do requires :api_url, type: String, allow_blank: false, desc: 'URL to access the Kubernetes API' requires :token, type: String, desc: 'Token to authenticate against Kubernetes' @@ -83,6 +84,7 @@ module API params do requires :cluster_id, type: Integer, desc: 'The cluster ID' optional :name, type: String, desc: 'Cluster name' + optional :domain, type: String, desc: 'Cluster base domain' optional :platform_kubernetes_attributes, type: Hash, desc: %q(Platform Kubernetes data) do optional :api_url, type: String, desc: 'URL to access the Kubernetes API' optional :token, type: String, desc: 'Token to authenticate against Kubernetes' diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 91501ba4d36..0f4a47677d9 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -11,12 +11,20 @@ module API before { authenticate_non_get! } helpers do - params :optional_filter_params_ee do - # EE::API::Projects would override this helper - end + if Gitlab.ee? + params :optional_filter_params_ee do + optional :wiki_checksum_failed, type: Grape::API::Boolean, default: false, desc: 'Limit by projects where wiki checksum is failed' + optional :repository_checksum_failed, type: Grape::API::Boolean, default: false, desc: 'Limit by projects where repository checksum is failed' + end - params :optional_update_params_ee do - # EE::API::Projects would override this helper + params :optional_update_params_ee do + optional :mirror_user_id, type: Integer, desc: 'User responsible for all the activity surrounding a pull mirror event' + optional :only_mirror_protected_branches, type: Grape::API::Boolean, desc: 'Only mirror protected branches' + optional :mirror_overwrites_diverged_branches, type: Grape::API::Boolean, desc: 'Pull mirror overwrites diverged branches' + optional :import_url, type: String, desc: 'URL from which the project is imported' + optional :packages_enabled, type: Grape::API::Boolean, desc: 'Enable project packages feature' + optional :fallback_approvals_required, type: Integer, desc: 'Overall approvals required when no rule is present' + end end # EE::API::Projects would override this method @@ -35,34 +43,6 @@ module API end end - def self.update_params_at_least_one_of - [ - :jobs_enabled, - :resolve_outdated_diff_discussions, - :ci_config_path, - :container_registry_enabled, - :default_branch, - :description, - :issues_enabled, - :lfs_enabled, - :merge_requests_enabled, - :merge_method, - :name, - :only_allow_merge_if_all_discussions_are_resolved, - :only_allow_merge_if_pipeline_succeeds, - :path, - :printing_merge_request_link_enabled, - :public_builds, - :request_access_enabled, - :shared_runners_enabled, - :snippets_enabled, - :tag_list, - :visibility, - :wiki_enabled, - :avatar - ] - end - helpers do params :statistics_params do optional :statistics, type: Boolean, default: false, desc: 'Include project statistics' @@ -97,7 +77,7 @@ module API optional :with_programming_language, type: String, desc: 'Limit to repositories which use the given programming language' optional :min_access_level, type: Integer, values: Gitlab::Access.all_values, desc: 'Limit by minimum access level of authenticated user' - use :optional_filter_params_ee + use :optional_filter_params_ee if Gitlab.ee? end params :create_params do @@ -316,8 +296,9 @@ module API optional :path, type: String, desc: 'The path of the repository' use :optional_project_params + use :optional_update_params_ee if Gitlab.ee? - at_least_one_of(*::API::Projects.update_params_at_least_one_of) + at_least_one_of(*Helpers::ProjectsHelpers.update_params_at_least_one_of) end put ':id' do authorize_admin_project diff --git a/lib/api/protected_branches.rb b/lib/api/protected_branches.rb index 5af43448727..f8cce1ed784 100644 --- a/lib/api/protected_branches.rb +++ b/lib/api/protected_branches.rb @@ -51,6 +51,30 @@ module API optional :merge_access_level, type: Integer, values: ProtectedBranch::MergeAccessLevel.allowed_access_levels, desc: 'Access levels allowed to merge (defaults: `40`, maintainer access level)' + + if Gitlab.ee? + optional :unprotect_access_level, type: Integer, + values: ProtectedBranch::UnprotectAccessLevel.allowed_access_levels, + desc: 'Access levels allowed to unprotect (defaults: `40`, maintainer access level)' + + optional :allowed_to_push, type: Array, desc: 'An array of users/groups allowed to push' do + optional :access_level, type: Integer, values: ProtectedBranch::PushAccessLevel.allowed_access_levels + optional :user_id, type: Integer + optional :group_id, type: Integer + end + + optional :allowed_to_merge, type: Array, desc: 'An array of users/groups allowed to merge' do + optional :access_level, type: Integer, values: ProtectedBranch::MergeAccessLevel.allowed_access_levels + optional :user_id, type: Integer + optional :group_id, type: Integer + end + + optional :allowed_to_unprotect, type: Array, desc: 'An array of users/groups allowed to unprotect' do + optional :access_level, type: Integer, values: ProtectedBranch::UnprotectAccessLevel.allowed_access_levels + optional :user_id, type: Integer + optional :group_id, type: Integer + end + end end # rubocop: disable CodeReuse/ActiveRecord post ':id/protected_branches' do diff --git a/lib/api/settings.rb b/lib/api/settings.rb index 3cb2f69c4ef..d742c6c97c1 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -135,8 +135,44 @@ module API desc: "Restrictions on the complexity of uploaded #{type.upcase} keys. A value of #{ApplicationSetting::FORBIDDEN_KEY_VALUE} disables all #{type.upcase} keys." end + if Gitlab.ee? + optional :elasticsearch_aws, type: Boolean, desc: 'Enable support for AWS hosted elasticsearch' + + given elasticsearch_aws: ->(val) { val } do + optional :elasticsearch_aws_access_key, type: String, desc: 'AWS IAM access key' + requires :elasticsearch_aws_region, type: String, desc: 'The AWS region the elasticsearch domain is configured' + optional :elasticsearch_aws_secret_access_key, type: String, desc: 'AWS IAM secret access key' + end + + optional :elasticsearch_indexing, type: Boolean, desc: 'Enable Elasticsearch indexing' + + given elasticsearch_indexing: ->(val) { val } do + optional :elasticsearch_search, type: Boolean, desc: 'Enable Elasticsearch search' + requires :elasticsearch_url, type: String, desc: 'The url to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., "http://localhost:9200, http://localhost:9201")' + end + + optional :email_additional_text, type: String, desc: 'Additional text added to the bottom of every email for legal/auditing/compliance reasons' + optional :help_text, type: String, desc: 'GitLab server administrator information' + optional :repository_size_limit, type: Integer, desc: 'Size limit per repository (MB)' + optional :file_template_project_id, type: Integer, desc: 'ID of project where instance-level file templates are stored.' + optional :repository_storages, type: Array[String], desc: 'A list of names of enabled storage paths, taken from `gitlab.yml`. New projects will be created in one of these stores, chosen at random.' + optional :snowplow_enabled, type: Boolean, desc: 'Enable Snowplow' + + given snowplow_enabled: ->(val) { val } do + requires :snowplow_collector_uri, type: String, desc: 'Snowplow Collector URI' + optional :snowplow_cookie_domain, type: String, desc: 'Snowplow cookie domain' + optional :snowplow_site_id, type: String, desc: 'Snowplow Site/Application ID' + end + + optional :usage_ping_enabled, type: Boolean, desc: 'Every week GitLab will report license usage back to GitLab, Inc.' + end + optional_attributes = ::ApplicationSettingsHelper.visible_attributes << :performance_bar_allowed_group_id + if Gitlab.ee? + optional_attributes += EE::ApplicationSettingsHelper.possible_licensed_attributes + end + optional(*optional_attributes) at_least_one_of(*optional_attributes) end diff --git a/lib/api/users.rb b/lib/api/users.rb index a3d4acc63d5..776329622e2 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -51,6 +51,10 @@ module API optional :avatar, type: File, desc: 'Avatar image for user' optional :private_profile, type: Boolean, desc: 'Flag indicating the user has a private profile' all_or_none_of :extern_uid, :provider + + if Gitlab.ee? + optional :shared_runners_minutes_limit, type: Integer, desc: 'Pipeline minutes quota for this user' + end end params :sort_params do @@ -80,6 +84,10 @@ module API use :sort_params use :pagination use :with_custom_attributes + + if Gitlab.ee? + optional :skip_ldap, type: Boolean, default: false, desc: 'Skip LDAP users' + end end # rubocop: disable CodeReuse/ActiveRecord get do diff --git a/lib/api/variables.rb b/lib/api/variables.rb index d0d81ebc870..3489ba827e4 100644 --- a/lib/api/variables.rb +++ b/lib/api/variables.rb @@ -55,6 +55,10 @@ module API requires :key, type: String, desc: 'The key of the variable' requires :value, type: String, desc: 'The value of the variable' optional :protected, type: String, desc: 'Whether the variable is protected' + + if Gitlab.ee? + optional :environment_scope, type: String, desc: 'The environment_scope of the variable' + end end post ':id/variables' do variable_params = declared_params(include_missing: false) @@ -76,6 +80,10 @@ module API optional :key, type: String, desc: 'The key of the variable' optional :value, type: String, desc: 'The value of the variable' optional :protected, type: String, desc: 'Whether the variable is protected' + + if Gitlab.ee? + optional :environment_scope, type: String, desc: 'The environment_scope of the variable' + end end # rubocop: disable CodeReuse/ActiveRecord put ':id/variables/:key' do diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb index 4764f8e1e19..5f8aca104aa 100644 --- a/lib/banzai/filter/abstract_reference_filter.rb +++ b/lib/banzai/filter/abstract_reference_filter.rb @@ -181,9 +181,10 @@ module Banzai title = object_link_title(object, matches) klass = reference_class(object_sym) - data = data_attributes_for(link_content || match, parent, object, - link_content: !!link_content, - link_reference: link_reference) + data_attributes = data_attributes_for(link_content || match, parent, object, + link_content: !!link_content, + link_reference: link_reference) + data = data_attribute(data_attributes) url = if matches.names.include?("url") && matches[:url] @@ -206,13 +207,13 @@ module Banzai def data_attributes_for(text, parent, object, link_content: false, link_reference: false) object_parent_type = parent.is_a?(Group) ? :group : :project - data_attribute( + { original: text, link: link_content, link_reference: link_reference, object_parent_type => parent.id, object_sym => object.id - ) + } end def object_link_text_extras(object, matches) diff --git a/lib/banzai/filter/merge_request_reference_filter.rb b/lib/banzai/filter/merge_request_reference_filter.rb index 7098767b583..f05902078dc 100644 --- a/lib/banzai/filter/merge_request_reference_filter.rb +++ b/lib/banzai/filter/merge_request_reference_filter.rb @@ -20,7 +20,9 @@ module Banzai end def object_link_title(object, matches) - object_link_commit_title(object, matches) || super + # The method will return `nil` if object is not a commit + # allowing for properly handling the extended MR Tooltip + object_link_commit_title(object, matches) end def object_link_text_extras(object, matches) @@ -53,6 +55,14 @@ module Banzai .includes(target_project: :namespace) end + def reference_class(object_sym, options = {}) + super(object_sym, tooltip: false) + end + + def data_attributes_for(text, parent, object, data = {}) + super.merge(project_path: parent.full_path, iid: object.iid, mr_title: object.title) + end + private def object_link_commit_title(object, matches) diff --git a/lib/banzai/suggestions_parser.rb b/lib/banzai/suggestions_parser.rb index 09f36635020..0d7f751bfc1 100644 --- a/lib/banzai/suggestions_parser.rb +++ b/lib/banzai/suggestions_parser.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# TODO: Delete when https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/26107 +# exchange this parser by `Gitlab::Diff::SuggestionsParser`. module Banzai module SuggestionsParser # Returns the content of each suggestion code block. diff --git a/lib/gitlab.rb b/lib/gitlab.rb index f42ca5a9cd6..1204e53ee2e 100644 --- a/lib/gitlab.rb +++ b/lib/gitlab.rb @@ -24,7 +24,7 @@ module Gitlab if File.exist?(root.join("REVISION")) File.read(root.join("REVISION")).strip.freeze else - result = Gitlab::Popen.popen_with_detail(%W[#{config.git.bin_path} log --pretty=format:%h -n 1]) + result = Gitlab::Popen.popen_with_detail(%W[#{config.git.bin_path} log --pretty=format:%h --abbrev=11 -n 1]) if result.status.success? result.stdout.chomp.freeze diff --git a/lib/gitlab/background_migration/populate_merge_request_assignees_table.rb b/lib/gitlab/background_migration/populate_merge_request_assignees_table.rb new file mode 100644 index 00000000000..a4c6540c61b --- /dev/null +++ b/lib/gitlab/background_migration/populate_merge_request_assignees_table.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module Gitlab + module BackgroundMigration + # This background migration creates records on merge_request_assignees according + # to the given merge request IDs range. A _single_ INSERT is issued for the given range. + # This is required for supporting multiple assignees on merge requests. + class PopulateMergeRequestAssigneesTable + def perform(from_id, to_id) + select_sql = + MergeRequest + .where(merge_request_assignees_not_exists_clause) + .where(id: from_id..to_id) + .where('assignee_id IS NOT NULL') + .select(:id, :assignee_id) + .to_sql + + execute("INSERT INTO merge_request_assignees (merge_request_id, user_id) #{select_sql}") + end + + private + + def merge_request_assignees_not_exists_clause + <<~SQL + NOT EXISTS (SELECT 1 FROM merge_request_assignees + WHERE merge_request_assignees.merge_request_id = merge_requests.id) + SQL + end + + def execute(sql) + @connection ||= ActiveRecord::Base.connection + @connection.execute(sql) + end + end + end +end diff --git a/lib/gitlab/ci/pipeline/chain/command.rb b/lib/gitlab/ci/pipeline/chain/command.rb index bf9f03f6134..03af99ba9a5 100644 --- a/lib/gitlab/ci/pipeline/chain/command.rb +++ b/lib/gitlab/ci/pipeline/chain/command.rb @@ -33,6 +33,13 @@ module Gitlab end end + def merge_request_ref_exists? + strong_memoize(:merge_request_ref_exists) do + MergeRequest.merge_request_ref?(origin_ref) && + project.repository.ref_exists?(origin_ref) + end + end + def ref strong_memoize(:ref) do Gitlab::Git.ref_name(origin_ref) diff --git a/lib/gitlab/ci/pipeline/chain/validate/abilities.rb b/lib/gitlab/ci/pipeline/chain/validate/abilities.rb index ebd7e6e8289..aaa3daddcc5 100644 --- a/lib/gitlab/ci/pipeline/chain/validate/abilities.rb +++ b/lib/gitlab/ci/pipeline/chain/validate/abilities.rb @@ -44,6 +44,8 @@ module Gitlab access.can_update_branch?(@command.ref) elsif @command.tag_exists? access.can_create_tag?(@command.ref) + elsif @command.merge_request_ref_exists? + access.can_update_branch?(@command.merge_request.source_branch) else true # Allow it for now and we'll reject when we check ref existence end diff --git a/lib/gitlab/ci/pipeline/chain/validate/repository.rb b/lib/gitlab/ci/pipeline/chain/validate/repository.rb index 9c6c2bc8e25..8f5445850d7 100644 --- a/lib/gitlab/ci/pipeline/chain/validate/repository.rb +++ b/lib/gitlab/ci/pipeline/chain/validate/repository.rb @@ -9,7 +9,7 @@ module Gitlab include Chain::Helpers def perform! - unless @command.branch_exists? || @command.tag_exists? + unless @command.branch_exists? || @command.tag_exists? || @command.merge_request_ref_exists? return error('Reference not found') end diff --git a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml index 7ec786b6d5d..3116f1a136b 100644 --- a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml @@ -531,8 +531,8 @@ rollout 100%: touch clair-whitelist.yml retries=0 echo "Waiting for clair daemon to start" - while( ! wget -T 10 -q -O /dev/null http://docker:6060/v1/namespaces ) ; do sleep 1 ; echo -n "." ; if [ $retries -eq 10 ] ; then echo " Timeout, aborting." ; exit 1 ; fi ; retries=$(($retries+1)) ; done - ./clair-scanner -c http://docker:6060 --ip $(hostname -i) -r gl-container-scanning-report.json -l clair.log -w clair-whitelist.yml ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG} || true + while( ! wget -T 10 -q -O /dev/null http://${DOCKER_SERVICE}:6060/v1/namespaces ) ; do sleep 1 ; echo -n "." ; if [ $retries -eq 10 ] ; then echo " Timeout, aborting." ; exit 1 ; fi ; retries=$(($retries+1)) ; done + ./clair-scanner -c http://${DOCKER_SERVICE}:6060 --ip $(hostname -i) -r gl-container-scanning-report.json -l clair.log -w clair-whitelist.yml ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG} || true } function code_quality() { @@ -800,10 +800,15 @@ rollout 100%: kubectl version --client } + # With the Kubernetes executor, 'localhost' must be used instead + # https://docs.gitlab.com/runner/executors/kubernetes.html function setup_docker() { if ! docker info &>/dev/null; then if [ -z "$DOCKER_HOST" -a "$KUBERNETES_PORT" ]; then export DOCKER_HOST='tcp://localhost:2375' + export DOCKER_SERVICE="localhost" + else + export DOCKER_SERVICE="docker" fi fi } @@ -856,7 +861,7 @@ rollout 100%: function check_kube_domain() { ensure_kube_ingress_base_domain - if [ -z ${KUBE_INGRESS_BASE_DOMAIN+x} ]; then + if [[ -z "$KUBE_INGRESS_BASE_DOMAIN" ]]; then echo "In order to deploy or use Review Apps," echo "AUTO_DEVOPS_DOMAIN or KUBE_INGRESS_BASE_DOMAIN variables must be set" echo "From 11.8, you can set KUBE_INGRESS_BASE_DOMAIN in cluster settings" diff --git a/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml index ea1e6ae5fdc..0b7a531682b 100644 --- a/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml @@ -17,23 +17,30 @@ container_scanning: # # Container Scanning deals with Docker images only so no need to import the project's Git repository: GIT_STRATEGY: none + # Services and containers running in the same Kubernetes pod are all sharing the same localhost address + # https://docs.gitlab.com/runner/executors/kubernetes.html + DOCKER_SERVICE: docker + DOCKER_HOST: tcp://${DOCKER_SERVICE}:2375/ + # https://hub.docker.com/r/arminc/clair-local-scan/tags + CLAIR_LOCAL_SCAN_VERSION: v2.0.6 allow_failure: true services: - docker:stable-dind script: + - if [ -z "$DOCKER_HOST" -a "$KUBERNETES_PORT" ]; then { export DOCKER_SERVICE="localhost" ; export DOCKER_HOST="tcp://${DOCKER_SERVICE}:2375" ; } fi - docker run -d --name db arminc/clair-db:latest - - docker run -p 6060:6060 --link db:postgres -d --name clair --restart on-failure arminc/clair-local-scan:v2.0.6 + - docker run -p 6060:6060 --link db:postgres -d --name clair --restart on-failure arminc/clair-local-scan:${CLAIR_LOCAL_SCAN_VERSION} - apk add -U wget ca-certificates - docker pull ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG} - wget https://github.com/arminc/clair-scanner/releases/download/v8/clair-scanner_linux_amd64 - mv clair-scanner_linux_amd64 clair-scanner - chmod +x clair-scanner - touch clair-whitelist.yml - - while( ! wget -q -O /dev/null http://docker:6060/v1/namespaces ) ; do sleep 1 ; done + - while( ! wget -q -O /dev/null http://${DOCKER_SERVICE}:6060/v1/namespaces ) ; do sleep 1 ; done - retries=0 - echo "Waiting for clair daemon to start" - - while( ! wget -T 10 -q -O /dev/null http://docker:6060/v1/namespaces ) ; do sleep 1 ; echo -n "." ; if [ $retries -eq 10 ] ; then echo " Timeout, aborting." ; exit 1 ; fi ; retries=$(($retries+1)) ; done - - ./clair-scanner -c http://docker:6060 --ip $(hostname -i) -r gl-container-scanning-report.json -l clair.log -w clair-whitelist.yml ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG} || true + - while( ! wget -T 10 -q -O /dev/null http://${DOCKER_SERVICE}:6060/v1/namespaces ) ; do sleep 1 ; echo -n "." ; if [ $retries -eq 10 ] ; then echo " Timeout, aborting." ; exit 1 ; fi ; retries=$(($retries+1)) ; done + - ./clair-scanner -c http://${DOCKER_SERVICE}:6060 --ip $(hostname -i) -r gl-container-scanning-report.json -l clair.log -w clair-whitelist.yml ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG} || true artifacts: reports: container_scanning: gl-container-scanning-report.json diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb index 552aad83dd4..469a7fd9f7b 100644 --- a/lib/gitlab/current_settings.rb +++ b/lib/gitlab/current_settings.rb @@ -47,7 +47,7 @@ module Gitlab # defaults for missing columns. if ActiveRecord::Migrator.needs_migration? db_attributes = current_settings&.attributes || {} - ::ApplicationSetting.build_from_defaults(db_attributes) + fake_application_settings(db_attributes) elsif current_settings.present? current_settings else diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb index dce80bf21de..c46087e65de 100644 --- a/lib/gitlab/diff/file.rb +++ b/lib/gitlab/diff/file.rb @@ -133,11 +133,15 @@ module Gitlab end def new_blob - new_blob_lazy&.itself + strong_memoize(:new_blob) do + new_blob_lazy&.itself + end end def old_blob - old_blob_lazy&.itself + strong_memoize(:old_blob) do + old_blob_lazy&.itself + end end def new_blob_lines_between(from_line, to_line) diff --git a/lib/gitlab/diff/suggestion.rb b/lib/gitlab/diff/suggestion.rb new file mode 100644 index 00000000000..027c7a31bcf --- /dev/null +++ b/lib/gitlab/diff/suggestion.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module Gitlab + module Diff + class Suggestion + include Suggestible + include Gitlab::Utils::StrongMemoize + + attr_reader :diff_file, :lines_above, :lines_below, + :target_line + + def initialize(text, line:, above:, below:, diff_file:) + @text = text + @target_line = line + @lines_above = above.to_i + @lines_below = below.to_i + @diff_file = diff_file + end + + def to_hash + { + from_content: from_content, + to_content: to_content, + lines_above: @lines_above, + lines_below: @lines_below + } + end + + def from_content + strong_memoize(:from_content) do + fetch_from_content + end + end + + def to_content + # The parsed suggestion doesn't have information about the correct + # ending characters (we may have a line break, or not), so we take + # this information from the last line being changed (last + # characters). + endline_chars = line_break_chars(from_content.lines.last) + "#{@text}#{endline_chars}" + end + + private + + def line_break_chars(line) + match = /\r\n|\r|\n/.match(line) + match[0] if match + end + end + end +end diff --git a/lib/gitlab/diff/suggestions_parser.rb b/lib/gitlab/diff/suggestions_parser.rb index 043bd9a4bcb..c8c03d5d001 100644 --- a/lib/gitlab/diff/suggestions_parser.rb +++ b/lib/gitlab/diff/suggestions_parser.rb @@ -5,6 +5,47 @@ module Gitlab class SuggestionsParser # Matches for instance "-1", "+1" or "-1+2". SUGGESTION_CONTEXT = /^(\-(?<above>\d+))?(\+(?<below>\d+))?$/.freeze + + class << self + # Returns an array of Gitlab::Diff::Suggestion which represents each + # suggestion in the given text. + # + def parse(text, position:, project:) + return [] unless position.complete? + + html = Banzai.render(text, project: nil, no_original_data: true) + doc = Nokogiri::HTML(html) + suggestion_nodes = doc.search('pre.suggestion') + + return [] if suggestion_nodes.empty? + + diff_file = position.diff_file(project.repository) + + suggestion_nodes.map do |node| + lang_param = node['data-lang-params'] + + lines_above, lines_below = nil + + if lang_param && suggestion_params = fetch_suggestion_params(lang_param) + lines_above, lines_below = + suggestion_params[:above], + suggestion_params[:below] + end + + Gitlab::Diff::Suggestion.new(node.text, + line: position.new_line, + above: lines_above.to_i, + below: lines_below.to_i, + diff_file: diff_file) + end + end + + private + + def fetch_suggestion_params(lang_param) + lang_param.match(SUGGESTION_CONTEXT) + end + end end end end diff --git a/lib/gitlab/encoding_helper.rb b/lib/gitlab/encoding_helper.rb index a4a154c80f7..5a61a7f5d60 100644 --- a/lib/gitlab/encoding_helper.rb +++ b/lib/gitlab/encoding_helper.rb @@ -76,8 +76,11 @@ module Gitlab str.dup.force_encoding(Encoding::ASCII_8BIT) end - def binary_stringio(str) - StringIO.new(str.freeze || '').tap { |io| io.set_encoding(Encoding::ASCII_8BIT) } + def binary_io(str_or_io) + io = str_or_io.to_io.dup if str_or_io.respond_to?(:to_io) + io ||= StringIO.new(str_or_io.to_s.freeze) + + io.tap { |io| io.set_encoding(Encoding::ASCII_8BIT) } end private diff --git a/lib/gitlab/git/commit.rb b/lib/gitlab/git/commit.rb index 88ff9fbceb4..8fac3621df9 100644 --- a/lib/gitlab/git/commit.rb +++ b/lib/gitlab/git/commit.rb @@ -318,6 +318,10 @@ module Gitlab parent_ids.size > 1 end + def gitaly_commit? + raw_commit.is_a?(Gitaly::GitCommit) + end + def tree_entry(path) return unless path.present? @@ -340,7 +344,7 @@ module Gitlab end def to_gitaly_commit - return raw_commit if raw_commit.is_a?(Gitaly::GitCommit) + return raw_commit if gitaly_commit? message_split = raw_commit.message.split("\n", 2) Gitaly::GitCommit.new( diff --git a/lib/gitlab/git/pre_receive_error.rb b/lib/gitlab/git/pre_receive_error.rb index 03caace6fce..b46d4ba0b02 100644 --- a/lib/gitlab/git/pre_receive_error.rb +++ b/lib/gitlab/git/pre_receive_error.rb @@ -4,19 +4,38 @@ module Gitlab module Git # # PreReceiveError is special because its message gets displayed to users - # in the web UI. To prevent XSS we sanitize the message on - # initialization. + # in the web UI. Because of this, we: + # - Only display errors that have been marked as safe with a prefix. + # This is to prevent leaking of stacktraces, or other sensitive info. + # - Sanitize the string of any XSS class PreReceiveError < StandardError - def initialize(msg = '') - super(nlbr(msg)) + SAFE_MESSAGE_PREFIXES = [ + 'GitLab:', # Messages from gitlab-shell + 'GL-HOOK-ERR:' # Messages marked as safe by user + ].freeze + + SAFE_MESSAGE_REGEX = /^(#{SAFE_MESSAGE_PREFIXES.join('|')})\s*(?<safe_message>.+)/ + + def initialize(message = '') + super(sanitize(message)) end private # In gitaly-ruby we override this method to do nothing, so that # sanitization happens in gitlab-rails only. - def nlbr(str) - Gitlab::Utils.nlbr(str) + def sanitize(message) + return message if message.blank? + + safe_messages = message.split("\n").map do |msg| + if (match = msg.match(SAFE_MESSAGE_REGEX)) + match[:safe_message].presence + end + end + + safe_messages = safe_messages.compact.join("\n") + + Gitlab::Utils.nlbr(safe_messages) end end end diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb index e3b9a7a1a89..35565b68388 100644 --- a/lib/gitlab/gitaly_client.rb +++ b/lib/gitlab/gitaly_client.rb @@ -33,12 +33,6 @@ module Gitlab MUTEX = Mutex.new - class << self - attr_accessor :query_time - end - - self.query_time = 0 - define_histogram :gitaly_controller_action_duration_seconds do docstring "Gitaly endpoint histogram by controller and action combination" base_labels Gitlab::Metrics::Transaction::BASE_LABELS.merge(gitaly_service: nil, rpc: nil) @@ -174,6 +168,18 @@ module Gitlab add_call_details(feature: "#{service}##{rpc}", duration: duration, request: request_hash, rpc: rpc) end + def self.query_time + SafeRequestStore[:gitaly_query_time] ||= 0 + end + + def self.query_time=(duration) + SafeRequestStore[:gitaly_query_time] = duration + end + + def self.query_time_ms + (self.query_time * 1000).round(2) + end + def self.current_transaction_labels Gitlab::Metrics::Transaction.current&.labels || {} end @@ -226,7 +232,7 @@ module Gitlab result end - SERVER_FEATURE_FLAGS = %w[go-find-all-tags].freeze + SERVER_FEATURE_FLAGS = %w[].freeze def self.server_feature_flags SERVER_FEATURE_FLAGS.map do |f| @@ -296,6 +302,26 @@ module Gitlab end end + # Normally a FindCommit RPC will cache the commit with its SHA + # instead of a ref name, since it's possible the branch is mutated + # afterwards. However, for read-only requests that never mutate the + # branch, this method allows caching of the ref name directly. + def self.allow_ref_name_caching + return yield unless Gitlab::SafeRequestStore.active? + return yield if ref_name_caching_allowed? + + begin + Gitlab::SafeRequestStore[:allow_ref_name_caching] = true + yield + ensure + Gitlab::SafeRequestStore[:allow_ref_name_caching] = false + end + end + + def self.ref_name_caching_allowed? + Gitlab::SafeRequestStore[:allow_ref_name_caching] + end + def self.get_call_count(key) Gitlab::SafeRequestStore[key] || 0 end diff --git a/lib/gitlab/gitaly_client/commit_service.rb b/lib/gitlab/gitaly_client/commit_service.rb index ea12424eb4a..0d5debfcd01 100644 --- a/lib/gitlab/gitaly_client/commit_service.rb +++ b/lib/gitlab/gitaly_client/commit_service.rb @@ -286,7 +286,7 @@ module Gitlab commit = call_find_commit(revision) return unless commit - key[:commit_id] = commit.id + key[:commit_id] = commit.id unless GitalyClient.ref_name_caching_allowed? Gitlab::SafeRequestStore[key] = commit else call_find_commit(revision) diff --git a/lib/gitlab/gitaly_client/conflicts_service.rb b/lib/gitlab/gitaly_client/conflicts_service.rb index 6304f998563..077b63205a8 100644 --- a/lib/gitlab/gitaly_client/conflicts_service.rb +++ b/lib/gitlab/gitaly_client/conflicts_service.rb @@ -37,7 +37,7 @@ module Gitlab end def resolve_conflicts(target_repository, resolution, source_branch, target_branch) - reader = binary_stringio(resolution.files.to_json) + reader = binary_io(resolution.files.to_json) req_enum = Enumerator.new do |y| header = resolve_conflicts_request_header(target_repository, resolution, source_branch, target_branch) diff --git a/lib/gitlab/gitaly_client/operation_service.rb b/lib/gitlab/gitaly_client/operation_service.rb index 2528208440e..b0f328ce3d4 100644 --- a/lib/gitlab/gitaly_client/operation_service.rb +++ b/lib/gitlab/gitaly_client/operation_service.rb @@ -294,7 +294,7 @@ module Gitlab action: Gitaly::UserCommitFilesAction.new(header: action_header) ) - reader = binary_stringio(action[:content]) + reader = binary_io(action[:content]) until reader.eof? chunk = reader.read(MAX_MSG_SIZE) @@ -327,7 +327,7 @@ module Gitlab user: Gitlab::Git::User.from_gitlab(user).to_gitaly, target_branch: encode_binary(branch_name) ) - reader = binary_stringio(patches) + reader = binary_io(patches) chunks = Enumerator.new do |chunk| chunk.yield Gitaly::UserApplyPatchRequest.new(header: header) diff --git a/lib/gitlab/gitaly_client/wiki_service.rb b/lib/gitlab/gitaly_client/wiki_service.rb index 2b3d622af4d..15c9463e2f2 100644 --- a/lib/gitlab/gitaly_client/wiki_service.rb +++ b/lib/gitlab/gitaly_client/wiki_service.rb @@ -22,7 +22,7 @@ module Gitlab commit_details: gitaly_commit_details(commit_details) ) - strio = binary_stringio(content) + strio = binary_io(content) enum = Enumerator.new do |y| until strio.eof? @@ -49,7 +49,7 @@ module Gitlab commit_details: gitaly_commit_details(commit_details) ) - strio = binary_stringio(content) + strio = binary_io(content) enum = Enumerator.new do |y| until strio.eof? diff --git a/lib/gitlab/gl_repository.rb b/lib/gitlab/gl_repository.rb index 435b74806e7..c2be7f3d63a 100644 --- a/lib/gitlab/gl_repository.rb +++ b/lib/gitlab/gl_repository.rb @@ -2,23 +2,38 @@ module Gitlab module GlRepository - def self.gl_repository(project, is_wiki) - "#{is_wiki ? 'wiki' : 'project'}-#{project.id}" + PROJECT = RepoType.new( + name: :project, + access_checker_class: Gitlab::GitAccess, + repository_accessor: -> (project) { project.repository } + ).freeze + WIKI = RepoType.new( + name: :wiki, + access_checker_class: Gitlab::GitAccessWiki, + repository_accessor: -> (project) { project.wiki.repository } + ).freeze + + TYPES = { + PROJECT.name.to_s => PROJECT, + WIKI.name.to_s => WIKI + }.freeze + + def self.types + TYPES end - # rubocop: disable CodeReuse/ActiveRecord def self.parse(gl_repository) - match_data = /\A(project|wiki)-([1-9][0-9]*)\z/.match(gl_repository) - unless match_data + type_name, _id = gl_repository.split('-').first + type = types[type_name] + subject_id = type&.fetch_id(gl_repository) + + unless subject_id raise ArgumentError, "Invalid GL Repository \"#{gl_repository}\"" end - type, id = match_data.captures - project = Project.find_by(id: id) - wiki = type == 'wiki' + project = Project.find_by_id(subject_id) - [project, wiki] + [project, type] end - # rubocop: enable CodeReuse/ActiveRecord end end diff --git a/lib/gitlab/gl_repository/repo_type.rb b/lib/gitlab/gl_repository/repo_type.rb new file mode 100644 index 00000000000..7abe6c29a25 --- /dev/null +++ b/lib/gitlab/gl_repository/repo_type.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +module Gitlab + module GlRepository + class RepoType + attr_reader :name, + :access_checker_class, + :repository_accessor + + def initialize(name:, access_checker_class:, repository_accessor:) + @name = name + @access_checker_class = access_checker_class + @repository_accessor = repository_accessor + end + + def identifier_for_subject(subject) + "#{name}-#{subject.id}" + end + + def fetch_id(identifier) + match = /\A#{name}-(?<id>\d+)\z/.match(identifier) + match[:id] if match + end + + def wiki? + self == WIKI + end + + def project? + self == PROJECT + end + + def path_suffix + project? ? "" : ".#{name}" + end + + def repository_for(subject) + repository_accessor.call(subject) + end + end + end +end diff --git a/lib/gitlab/grape_logging/loggers/perf_logger.rb b/lib/gitlab/grape_logging/loggers/perf_logger.rb index e3b9c59bd6e..18ea3a8d2f3 100644 --- a/lib/gitlab/grape_logging/loggers/perf_logger.rb +++ b/lib/gitlab/grape_logging/loggers/perf_logger.rb @@ -6,7 +6,10 @@ module Gitlab module Loggers class PerfLogger < ::GrapeLogging::Loggers::Base def parameters(_, _) - { gitaly_calls: Gitlab::GitalyClient.get_request_count } + { + gitaly_calls: Gitlab::GitalyClient.get_request_count, + gitaly_duration: Gitlab::GitalyClient.query_time_ms + } end end end diff --git a/lib/gitlab/import/merge_request_helpers.rb b/lib/gitlab/import/merge_request_helpers.rb index fa3ff6c3f12..b3fe1fc0685 100644 --- a/lib/gitlab/import/merge_request_helpers.rb +++ b/lib/gitlab/import/merge_request_helpers.rb @@ -38,7 +38,6 @@ module Gitlab end # rubocop: enable CodeReuse/ActiveRecord - # rubocop: disable CodeReuse/ActiveRecord def insert_or_replace_git_data(merge_request, source_branch_sha, target_branch_sha, already_exists = false) # These fields are set so we can create the correct merge request # diffs. @@ -47,24 +46,21 @@ module Gitlab merge_request.keep_around_commit + # We force to recreate all diffs to replace all existing data + # We use `.all` as otherwise `dependent: :nullify` (the default) + # takes an effect + merge_request.merge_request_diffs.all.delete_all if already_exists + # MR diffs normally use an "after_save" hook to pull data from Git. # All of this happens in the transaction started by calling # create/save/etc. This in turn can lead to these transactions being # held open for much longer than necessary. To work around this we # first save the diff, then populate it. - diff = - if already_exists - merge_request.merge_request_diffs.take || - merge_request.merge_request_diffs.build - else - merge_request.merge_request_diffs.build - end - + diff = merge_request.merge_request_diffs.build diff.importing = true diff.save diff.save_git_content end - # rubocop: enable CodeReuse/ActiveRecord end end end diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml index a0aab9fcbaf..89667976217 100644 --- a/lib/gitlab/import_export/import_export.yml +++ b/lib/gitlab/import_export/import_export.yml @@ -33,6 +33,7 @@ project_tree: - :user - merge_requests: - :metrics + - :suggestions - notes: - :author - events: diff --git a/lib/gitlab/quick_actions/command_definition.rb b/lib/gitlab/quick_actions/command_definition.rb index e7bfcb16582..93030fd454e 100644 --- a/lib/gitlab/quick_actions/command_definition.rb +++ b/lib/gitlab/quick_actions/command_definition.rb @@ -4,7 +4,7 @@ module Gitlab module QuickActions class CommandDefinition attr_accessor :name, :aliases, :description, :explanation, :params, - :condition_block, :parse_params_block, :action_block, :warning + :condition_block, :parse_params_block, :action_block, :warning, :types def initialize(name, attributes = {}) @name = name @@ -17,6 +17,7 @@ module Gitlab @condition_block = attributes[:condition_block] @parse_params_block = attributes[:parse_params_block] @action_block = attributes[:action_block] + @types = attributes[:types] || [] end def all_names @@ -28,6 +29,7 @@ module Gitlab end def available?(context) + return false unless valid_type?(context) return true unless condition_block context.instance_exec(&condition_block) @@ -96,6 +98,10 @@ module Gitlab context.instance_exec(arg, &parse_params_block) end + + def valid_type?(context) + types.blank? || types.any? { |type| context.quick_action_target.is_a?(type) } + end end end end diff --git a/lib/gitlab/quick_actions/commit_actions.rb b/lib/gitlab/quick_actions/commit_actions.rb new file mode 100644 index 00000000000..62c0fbb5afd --- /dev/null +++ b/lib/gitlab/quick_actions/commit_actions.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module Gitlab + module QuickActions + module CommitActions + extend ActiveSupport::Concern + include Gitlab::QuickActions::Dsl + + included do + # Commit only quick actions definitions + desc 'Tag this commit.' + explanation do |tag_name, message| + with_message = %{ with "#{message}"} if message.present? + "Tags this commit to #{tag_name}#{with_message}." + end + params 'v1.2.3 <message>' + parse_params do |tag_name_and_message| + tag_name_and_message.split(' ', 2) + end + types Commit + condition do + current_user.can?(:push_code, project) + end + command :tag do |tag_name, message| + @updates[:tag_name] = tag_name + @updates[:tag_message] = message + end + end + end + end +end diff --git a/lib/gitlab/quick_actions/common_actions.rb b/lib/gitlab/quick_actions/common_actions.rb new file mode 100644 index 00000000000..5d2732d4826 --- /dev/null +++ b/lib/gitlab/quick_actions/common_actions.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Gitlab + module QuickActions + module CommonActions + extend ActiveSupport::Concern + include Gitlab::QuickActions::Dsl + + included do + # This is a dummy command, so that it appears in the autocomplete commands + desc 'CC' + params '@user' + command :cc + end + end + end +end diff --git a/lib/gitlab/quick_actions/dsl.rb b/lib/gitlab/quick_actions/dsl.rb index a3aab92061b..ecb2169151e 100644 --- a/lib/gitlab/quick_actions/dsl.rb +++ b/lib/gitlab/quick_actions/dsl.rb @@ -24,7 +24,7 @@ module Gitlab # Example: # # desc do - # "This is a dynamic description for #{noteable.to_ability_name}" + # "This is a dynamic description for #{quick_action_target.to_ability_name}" # end # command :command_key do |arguments| # # Awesome code block @@ -66,6 +66,23 @@ module Gitlab @explanation = block_given? ? block : text end + # Allows to define type(s) that must be met in order for the command + # to be returned by `.command_names` & `.command_definitions`. + # + # It is being evaluated before the conditions block is being evaluated + # + # If no types are passed then any type is allowed as the check is simply skipped. + # + # Example: + # + # types Commit, Issue, MergeRequest + # command :command_key do |arguments| + # # Awesome code block + # end + def types(*types_list) + @types = types_list + end + # Allows to define conditions that must be met in order for the command # to be returned by `.command_names` & `.command_definitions`. # It accepts a block that will be evaluated with the context @@ -144,7 +161,8 @@ module Gitlab params: @params, condition_block: @condition_block, parse_params_block: @parse_params_block, - action_block: block + action_block: block, + types: @types ) self.command_definitions << definition @@ -159,6 +177,7 @@ module Gitlab @condition_block = nil @warning = nil @parse_params_block = nil + @types = nil end end end diff --git a/lib/gitlab/quick_actions/issuable_actions.rb b/lib/gitlab/quick_actions/issuable_actions.rb new file mode 100644 index 00000000000..ad2e15d19fa --- /dev/null +++ b/lib/gitlab/quick_actions/issuable_actions.rb @@ -0,0 +1,221 @@ +# frozen_string_literal: true + +module Gitlab + module QuickActions + module IssuableActions + extend ActiveSupport::Concern + include Gitlab::QuickActions::Dsl + + SHRUG = '¯\\_(ツ)_/¯'.freeze + TABLEFLIP = '(╯°□°)╯︵ ┻━┻'.freeze + + included do + # Issue, MergeRequest, Epic: quick actions definitions + desc do + "Close this #{quick_action_target.to_ability_name.humanize(capitalize: false)}" + end + explanation do + "Closes this #{quick_action_target.to_ability_name.humanize(capitalize: false)}." + end + types Issuable + condition do + quick_action_target.persisted? && + quick_action_target.open? && + current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target) + end + command :close do + @updates[:state_event] = 'close' + end + + desc do + "Reopen this #{quick_action_target.to_ability_name.humanize(capitalize: false)}" + end + explanation do + "Reopens this #{quick_action_target.to_ability_name.humanize(capitalize: false)}." + end + types Issuable + condition do + quick_action_target.persisted? && + quick_action_target.closed? && + current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target) + end + command :reopen do + @updates[:state_event] = 'reopen' + end + + desc 'Change title' + explanation do |title_param| + "Changes the title to \"#{title_param}\"." + end + params '<New title>' + types Issuable + condition do + quick_action_target.persisted? && + current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target) + end + command :title do |title_param| + @updates[:title] = title_param + end + + desc 'Add label(s)' + explanation do |labels_param| + labels = find_label_references(labels_param) + + "Adds #{labels.join(' ')} #{'label'.pluralize(labels.count)}." if labels.any? + end + params '~label1 ~"label 2"' + types Issuable + condition do + parent && + current_user.can?(:"admin_#{quick_action_target.to_ability_name}", parent) && + find_labels.any? + end + command :label do |labels_param| + label_ids = find_label_ids(labels_param) + + if label_ids.any? + @updates[:add_label_ids] ||= [] + @updates[:add_label_ids] += label_ids + + @updates[:add_label_ids].uniq! + end + end + + desc 'Remove all or specific label(s)' + explanation do |labels_param = nil| + if labels_param.present? + labels = find_label_references(labels_param) + "Removes #{labels.join(' ')} #{'label'.pluralize(labels.count)}." if labels.any? + else + 'Removes all labels.' + end + end + params '~label1 ~"label 2"' + types Issuable + condition do + quick_action_target.persisted? && + quick_action_target.labels.any? && + current_user.can?(:"admin_#{quick_action_target.to_ability_name}", parent) + end + command :unlabel do |labels_param = nil| + if labels_param.present? + label_ids = find_label_ids(labels_param) + + if label_ids.any? + @updates[:remove_label_ids] ||= [] + @updates[:remove_label_ids] += label_ids + + @updates[:remove_label_ids].uniq! + end + else + @updates[:label_ids] = [] + end + end + + desc 'Replace all label(s)' + explanation do |labels_param| + labels = find_label_references(labels_param) + "Replaces all labels with #{labels.join(' ')} #{'label'.pluralize(labels.count)}." if labels.any? + end + params '~label1 ~"label 2"' + types Issuable + condition do + quick_action_target.persisted? && + quick_action_target.labels.any? && + current_user.can?(:"admin_#{quick_action_target.to_ability_name}", parent) + end + command :relabel do |labels_param| + label_ids = find_label_ids(labels_param) + + if label_ids.any? + @updates[:label_ids] ||= [] + @updates[:label_ids] += label_ids + + @updates[:label_ids].uniq! + end + end + + desc 'Add a todo' + explanation 'Adds a todo.' + types Issuable + condition do + quick_action_target.persisted? && + !TodoService.new.todo_exist?(quick_action_target, current_user) + end + command :todo do + @updates[:todo_event] = 'add' + end + + desc 'Mark todo as done' + explanation 'Marks todo as done.' + types Issuable + condition do + quick_action_target.persisted? && + TodoService.new.todo_exist?(quick_action_target, current_user) + end + command :done do + @updates[:todo_event] = 'done' + end + + desc 'Subscribe' + explanation do + "Subscribes to this #{quick_action_target.to_ability_name.humanize(capitalize: false)}." + end + types Issuable + condition do + quick_action_target.persisted? && + !quick_action_target.subscribed?(current_user, project) + end + command :subscribe do + @updates[:subscription_event] = 'subscribe' + end + + desc 'Unsubscribe' + explanation do + "Unsubscribes from this #{quick_action_target.to_ability_name.humanize(capitalize: false)}." + end + types Issuable + condition do + quick_action_target.persisted? && + quick_action_target.subscribed?(current_user, project) + end + command :unsubscribe do + @updates[:subscription_event] = 'unsubscribe' + end + + desc 'Toggle emoji award' + explanation do |name| + "Toggles :#{name}: emoji award." if name + end + params ':emoji:' + types Issuable + condition do + quick_action_target.persisted? + end + parse_params do |emoji_param| + match = emoji_param.match(Banzai::Filter::EmojiFilter.emoji_pattern) + match[1] if match + end + command :award do |name| + if name && quick_action_target.user_can_award?(current_user) + @updates[:emoji_award] = name + end + end + + desc "Append the comment with #{SHRUG}" + params '<Comment>' + types Issuable + substitution :shrug do |comment| + "#{comment} #{SHRUG}" + end + + desc "Append the comment with #{TABLEFLIP}" + params '<Comment>' + types Issuable + substitution :tableflip do |comment| + "#{comment} #{TABLEFLIP}" + end + end + end + end +end diff --git a/lib/gitlab/quick_actions/issue_actions.rb b/lib/gitlab/quick_actions/issue_actions.rb new file mode 100644 index 00000000000..1f08e8740a2 --- /dev/null +++ b/lib/gitlab/quick_actions/issue_actions.rb @@ -0,0 +1,136 @@ +# frozen_string_literal: true + +module Gitlab + module QuickActions + module IssueActions + extend ActiveSupport::Concern + include Gitlab::QuickActions::Dsl + + included do + # Issue only quick actions definition + desc 'Set due date' + explanation do |due_date| + "Sets the due date to #{due_date.to_s(:medium)}." if due_date + end + params '<in 2 days | this Friday | December 31st>' + types Issue + condition do + quick_action_target.respond_to?(:due_date) && + current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project) + end + parse_params do |due_date_param| + Chronic.parse(due_date_param).try(:to_date) + end + command :due do |due_date| + @updates[:due_date] = due_date if due_date + end + + desc 'Remove due date' + explanation 'Removes the due date.' + types Issue + condition do + quick_action_target.persisted? && + quick_action_target.respond_to?(:due_date) && + quick_action_target.due_date? && + current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project) + end + command :remove_due_date do + @updates[:due_date] = nil + end + + desc 'Move issue from one column of the board to another' + explanation do |target_list_name| + label = find_label_references(target_list_name).first + "Moves issue to #{label} column in the board." if label + end + params '~"Target column"' + types Issue + condition do + current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target) && + quick_action_target.project.boards.count == 1 + end + # rubocop: disable CodeReuse/ActiveRecord + command :board_move do |target_list_name| + label_ids = find_label_ids(target_list_name) + + if label_ids.size == 1 + label_id = label_ids.first + + # Ensure this label corresponds to a list on the board + next unless Label.on_project_boards(quick_action_target.project_id).where(id: label_id).exists? + + @updates[:remove_label_ids] = + quick_action_target.labels.on_project_boards(quick_action_target.project_id).where.not(id: label_id).pluck(:id) + @updates[:add_label_ids] = [label_id] + end + end + # rubocop: enable CodeReuse/ActiveRecord + + desc 'Mark this issue as a duplicate of another issue' + explanation do |duplicate_reference| + "Marks this issue as a duplicate of #{duplicate_reference}." + end + params '#issue' + types Issue + condition do + quick_action_target.persisted? && + current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target) + end + command :duplicate do |duplicate_param| + canonical_issue = extract_references(duplicate_param, :issue).first + + if canonical_issue.present? + @updates[:canonical_issue_id] = canonical_issue.id + end + end + + desc 'Move this issue to another project.' + explanation do |path_to_project| + "Moves this issue to #{path_to_project}." + end + params 'path/to/project' + types Issue + condition do + quick_action_target.persisted? && + current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project) + end + command :move do |target_project_path| + target_project = Project.find_by_full_path(target_project_path) + + if target_project.present? + @updates[:target_project] = target_project + end + end + + desc 'Make issue confidential.' + explanation do + 'Makes this issue confidential' + end + types Issue + condition do + current_user.can?(:"admin_#{quick_action_target.to_ability_name}", quick_action_target) + end + command :confidential do + @updates[:confidential] = true + end + + desc 'Create a merge request.' + explanation do |branch_name = nil| + branch_text = branch_name ? "branch '#{branch_name}'" : 'a branch' + "Creates #{branch_text} and a merge request to resolve this issue" + end + params "<branch name>" + types Issue + condition do + current_user.can?(:create_merge_request_in, project) && current_user.can?(:push_code, project) + end + command :create_merge_request do |branch_name = nil| + @updates[:create_merge_request] = { + branch_name: branch_name, + issue_iid: quick_action_target.iid + } + end + end + end + end +end diff --git a/lib/gitlab/quick_actions/issue_and_merge_request_actions.rb b/lib/gitlab/quick_actions/issue_and_merge_request_actions.rb new file mode 100644 index 00000000000..08872eda410 --- /dev/null +++ b/lib/gitlab/quick_actions/issue_and_merge_request_actions.rb @@ -0,0 +1,225 @@ +# frozen_string_literal: true + +module Gitlab + module QuickActions + module IssueAndMergeRequestActions + extend ActiveSupport::Concern + include Gitlab::QuickActions::Dsl + + included do + # Issue, MergeRequest: quick actions definitions + desc 'Assign' + # rubocop: disable CodeReuse/ActiveRecord + explanation do |users| + users = quick_action_target.allows_multiple_assignees? ? users : users.take(1) + "Assigns #{users.map(&:to_reference).to_sentence}." + end + # rubocop: enable CodeReuse/ActiveRecord + params do + quick_action_target.allows_multiple_assignees? ? '@user1 @user2' : '@user' + end + types Issue, MergeRequest + condition do + current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project) + end + parse_params do |assignee_param| + extract_users(assignee_param) + end + command :assign do |users| + next if users.empty? + + if quick_action_target.allows_multiple_assignees? + @updates[:assignee_ids] ||= quick_action_target.assignees.map(&:id) + @updates[:assignee_ids] += users.map(&:id) + else + @updates[:assignee_ids] = [users.first.id] + end + end + + desc do + if quick_action_target.allows_multiple_assignees? + 'Remove all or specific assignee(s)' + else + 'Remove assignee' + end + end + explanation do |users = nil| + assignees = quick_action_target.assignees + assignees &= users if users.present? && quick_action_target.allows_multiple_assignees? + "Removes #{'assignee'.pluralize(assignees.size)} #{assignees.map(&:to_reference).to_sentence}." + end + params do + quick_action_target.allows_multiple_assignees? ? '@user1 @user2' : '' + end + types Issue, MergeRequest + condition do + quick_action_target.persisted? && + quick_action_target.assignees.any? && + current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project) + end + parse_params do |unassign_param| + # When multiple users are assigned, all will be unassigned if multiple assignees are no longer allowed + extract_users(unassign_param) if quick_action_target.allows_multiple_assignees? + end + command :unassign do |users = nil| + if quick_action_target.allows_multiple_assignees? && users&.any? + @updates[:assignee_ids] ||= quick_action_target.assignees.map(&:id) + @updates[:assignee_ids] -= users.map(&:id) + else + @updates[:assignee_ids] = [] + end + end + + desc 'Set milestone' + explanation do |milestone| + "Sets the milestone to #{milestone.to_reference}." if milestone + end + params '%"milestone"' + types Issue, MergeRequest + condition do + current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project) && + find_milestones(project, state: 'active').any? + end + parse_params do |milestone_param| + extract_references(milestone_param, :milestone).first || + find_milestones(project, title: milestone_param.strip).first + end + command :milestone do |milestone| + @updates[:milestone_id] = milestone.id if milestone + end + + desc 'Remove milestone' + explanation do + "Removes #{quick_action_target.milestone.to_reference(format: :name)} milestone." + end + types Issue, MergeRequest + condition do + quick_action_target.persisted? && + quick_action_target.milestone_id? && + current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project) + end + command :remove_milestone do + @updates[:milestone_id] = nil + end + + desc 'Copy labels and milestone from other issue or merge request' + explanation do |source_issuable| + "Copy labels and milestone from #{source_issuable.to_reference}." + end + params '#issue | !merge_request' + types Issue, MergeRequest + condition do + current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target) + end + parse_params do |issuable_param| + extract_references(issuable_param, :issue).first || + extract_references(issuable_param, :merge_request).first + end + command :copy_metadata do |source_issuable| + if source_issuable.present? && source_issuable.project.id == quick_action_target.project.id + @updates[:add_label_ids] = source_issuable.labels.map(&:id) + @updates[:milestone_id] = source_issuable.milestone.id if source_issuable.milestone + end + end + + desc 'Set time estimate' + explanation do |time_estimate| + time_estimate = Gitlab::TimeTrackingFormatter.output(time_estimate) + + "Sets time estimate to #{time_estimate}." if time_estimate + end + params '<1w 3d 2h 14m>' + types Issue, MergeRequest + condition do + current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project) + end + parse_params do |raw_duration| + Gitlab::TimeTrackingFormatter.parse(raw_duration) + end + command :estimate do |time_estimate| + if time_estimate + @updates[:time_estimate] = time_estimate + end + end + + desc 'Add or subtract spent time' + explanation do |time_spent, time_spent_date| + if time_spent + if time_spent > 0 + verb = 'Adds' + value = time_spent + else + verb = 'Subtracts' + value = -time_spent + end + + "#{verb} #{Gitlab::TimeTrackingFormatter.output(value)} spent time." + end + end + params '<time(1h30m | -1h30m)> <date(YYYY-MM-DD)>' + types Issue, MergeRequest + condition do + current_user.can?(:"admin_#{quick_action_target.to_ability_name}", quick_action_target) + end + parse_params do |raw_time_date| + Gitlab::QuickActions::SpendTimeAndDateSeparator.new(raw_time_date).execute + end + command :spend do |time_spent, time_spent_date| + if time_spent + @updates[:spend_time] = { + duration: time_spent, + user_id: current_user.id, + spent_at: time_spent_date + } + end + end + + desc 'Remove time estimate' + explanation 'Removes time estimate.' + types Issue, MergeRequest + condition do + quick_action_target.persisted? && + current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project) + end + command :remove_estimate do + @updates[:time_estimate] = 0 + end + + desc 'Remove spent time' + explanation 'Removes spent time.' + condition do + quick_action_target.persisted? && + current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project) + end + types Issue, MergeRequest + command :remove_time_spent do + @updates[:spend_time] = { duration: :reset, user_id: current_user.id } + end + + desc "Lock the discussion" + explanation "Locks the discussion" + types Issue, MergeRequest + condition do + quick_action_target.persisted? && + !quick_action_target.discussion_locked? && + current_user.can?(:"admin_#{quick_action_target.to_ability_name}", quick_action_target) + end + command :lock do + @updates[:discussion_locked] = true + end + + desc "Unlock the discussion" + explanation "Unlocks the discussion" + types Issue, MergeRequest + condition do + quick_action_target.persisted? && + quick_action_target.discussion_locked? && + current_user.can?(:"admin_#{quick_action_target.to_ability_name}", quick_action_target) + end + command :unlock do + @updates[:discussion_locked] = false + end + end + end + end +end diff --git a/lib/gitlab/quick_actions/merge_request_actions.rb b/lib/gitlab/quick_actions/merge_request_actions.rb new file mode 100644 index 00000000000..bade59182a1 --- /dev/null +++ b/lib/gitlab/quick_actions/merge_request_actions.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +module Gitlab + module QuickActions + module MergeRequestActions + extend ActiveSupport::Concern + include Gitlab::QuickActions::Dsl + + included do + # MergeRequest only quick actions definitions + desc 'Merge (when the pipeline succeeds)' + explanation 'Merges this merge request when the pipeline succeeds.' + types MergeRequest + condition do + last_diff_sha = params && params[:merge_request_diff_head_sha] + quick_action_target.persisted? && + quick_action_target.mergeable_with_quick_action?(current_user, autocomplete_precheck: !last_diff_sha, last_diff_sha: last_diff_sha) + end + command :merge do + @updates[:merge] = params[:merge_request_diff_head_sha] + end + + desc 'Toggle the Work In Progress status' + explanation do + verb = quick_action_target.work_in_progress? ? 'Unmarks' : 'Marks' + noun = quick_action_target.to_ability_name.humanize(capitalize: false) + "#{verb} this #{noun} as Work In Progress." + end + types MergeRequest + condition do + quick_action_target.respond_to?(:work_in_progress?) && + # Allow it to mark as WIP on MR creation page _or_ through MR notes. + (quick_action_target.new_record? || current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target)) + end + command :wip do + @updates[:wip_event] = quick_action_target.work_in_progress? ? 'unwip' : 'wip' + end + + desc 'Set target branch' + explanation do |branch_name| + "Sets target branch to #{branch_name}." + end + params '<Local branch name>' + types MergeRequest + condition do + quick_action_target.respond_to?(:target_branch) && + (current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target) || + quick_action_target.new_record?) + end + parse_params do |target_branch_param| + target_branch_param.strip + end + command :target_branch do |branch_name| + @updates[:target_branch] = branch_name if project.repository.branch_exists?(branch_name) + end + end + end + end +end diff --git a/lib/gitlab/repo_path.rb b/lib/gitlab/repo_path.rb index 202d310e237..207a80b7db2 100644 --- a/lib/gitlab/repo_path.rb +++ b/lib/gitlab/repo_path.rb @@ -5,19 +5,26 @@ module Gitlab NotFoundError = Class.new(StandardError) def self.parse(repo_path) - wiki = false project_path = repo_path.sub(/\.git\z/, '').sub(%r{\A/}, '') - project, was_redirected = find_project(project_path) - - if project_path.end_with?('.wiki') && project.nil? - project, was_redirected = find_project(project_path.chomp('.wiki')) - wiki = true + # Detect the repo type based on the path, the first one tried is the project + # type, which does not have a suffix. + Gitlab::GlRepository.types.each do |_name, type| + # If the project path does not end with the defined suffix, try the next + # type. + # We'll always try to find a project with an empty suffix (for the + # `Gitlab::GlRepository::PROJECT` type. + next unless project_path.end_with?(type.path_suffix) + + project, was_redirected = find_project(project_path.chomp(type.path_suffix)) + redirected_path = project_path if was_redirected + + # If we found a matching project, then the type was matched, no need to + # continue looking. + return [project, type, redirected_path] if project end - redirected_path = project_path if was_redirected - - [project, wiki, redirected_path] + nil end def self.find_project(project_path) diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb index 265f6213a99..5d5a867c9ab 100644 --- a/lib/gitlab/workhorse.rb +++ b/lib/gitlab/workhorse.rb @@ -20,14 +20,14 @@ module Gitlab SECRET_LENGTH = 32 class << self - def git_http_ok(repository, is_wiki, user, action, show_all_refs: false) + def git_http_ok(repository, repo_type, user, action, show_all_refs: false) raise "Unsupported action: #{action}" unless ALLOWED_GIT_HTTP_ACTIONS.include?(action.to_s) project = repository.project attrs = { GL_ID: Gitlab::GlId.gl_id(user), - GL_REPOSITORY: Gitlab::GlRepository.gl_repository(project, is_wiki), + GL_REPOSITORY: repo_type.identifier_for_subject(project), GL_USERNAME: user&.username, ShowAllRefs: show_all_refs, Repository: repository.gitaly_repository.to_h, diff --git a/lib/sentry/client.rb b/lib/sentry/client.rb index 5e0c9101de5..bb1aa2a7a10 100644 --- a/lib/sentry/client.rb +++ b/lib/sentry/client.rb @@ -3,7 +3,7 @@ module Sentry class Client Error = Class.new(StandardError) - SentryError = Class.new(StandardError) + MissingKeysError = Class.new(StandardError) attr_accessor :url, :token @@ -14,18 +14,29 @@ module Sentry def list_issues(issue_status:, limit:) issues = get_issues(issue_status: issue_status, limit: limit) - map_to_errors(issues) + + handle_mapping_exceptions do + map_to_errors(issues) + end end def list_projects projects = get_projects - map_to_projects(projects) - rescue KeyError => e - raise Client::SentryError, "Sentry API response is missing keys. #{e.message}" + + handle_mapping_exceptions do + map_to_projects(projects) + end end private + def handle_mapping_exceptions(&block) + yield + rescue KeyError => e + Gitlab::Sentry.track_acceptable_exception(e) + raise Client::MissingKeysError, "Sentry API response is missing keys. #{e.message}" + end + def request_params { headers: { @@ -94,7 +105,6 @@ module Sentry def map_to_error(issue) id = issue.fetch('id') - project = issue.fetch('project') count = issue.fetch('count', nil) @@ -117,9 +127,9 @@ module Sentry short_id: issue.fetch('shortId', nil), status: issue.fetch('status', nil), frequency: frequency, - project_id: project.fetch('id'), - project_name: project.fetch('name', nil), - project_slug: project.fetch('slug', nil) + project_id: issue.dig('project', 'id'), + project_name: issue.dig('project', 'name'), + project_slug: issue.dig('project', 'slug') ) end @@ -127,12 +137,12 @@ module Sentry organization = project.fetch('organization') Gitlab::ErrorTracking::Project.new( - id: project.fetch('id'), + id: project.fetch('id', nil), name: project.fetch('name'), slug: project.fetch('slug'), status: project.dig('status'), organization_name: organization.fetch('name'), - organization_id: organization.fetch('id'), + organization_id: organization.fetch('id', nil), organization_slug: organization.fetch('slug') ) end diff --git a/lib/tasks/tokens.rake b/lib/tasks/tokens.rake index eec024f9bbb..46635cd7c8f 100644 --- a/lib/tasks/tokens.rake +++ b/lib/tasks/tokens.rake @@ -24,7 +24,7 @@ namespace :tokens do end end -class TmpUser < ActiveRecord::Base +class TmpUser < ActiveRecord::Base # rubocop:disable Rails/ApplicationRecord include TokenAuthenticatable self.table_name = 'users' diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 177bd189817..1e6f04c7815 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -144,6 +144,15 @@ msgstr "" msgid "%{percent}%% complete" msgstr "" +msgid "%{service_title} activated." +msgstr "" + +msgid "%{service_title} settings saved, but not activated." +msgstr "" + +msgid "%{spammable_titlecase} was submitted to Akismet successfully." +msgstr "" + msgid "%{strong_start}%{branch_count}%{strong_end} Branch" msgid_plural "%{strong_start}%{branch_count}%{strong_end} Branches" msgstr[0] "" @@ -372,6 +381,9 @@ msgstr "" msgid "Access expiration date" msgstr "" +msgid "Access forbidden. Check your access level." +msgstr "" + msgid "Account" msgstr "" @@ -609,9 +621,18 @@ msgstr "" msgid "All issues for this milestone are closed. You may close this milestone now." msgstr "" +msgid "All merge conflicts were resolved. The merge request can now be merged." +msgstr "" + +msgid "All todos were marked as done." +msgstr "" + msgid "All users" msgstr "" +msgid "All users must have a name." +msgstr "" + msgid "Allow commits from members who can merge to the target branch." msgstr "" @@ -954,6 +975,9 @@ msgstr "" msgid "Authentication method" msgstr "" +msgid "Authentication via U2F device failed." +msgstr "" + msgid "Author" msgstr "" @@ -1815,7 +1839,7 @@ msgstr "" msgid "ClusterIntegration|GitLab Runner" msgstr "" -msgid "ClusterIntegration|GitLab Runner connects to this project's repository and executes CI/CD jobs, pushing results back and deploying, applications to production." +msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production." msgstr "" msgid "ClusterIntegration|Google Cloud Platform project" @@ -2447,6 +2471,15 @@ msgstr "" msgid "Copy token to clipboard" msgstr "" +msgid "Could not connect to FogBugz, check your URL" +msgstr "" + +msgid "Could not create Wiki Repository at this time. Please try again later." +msgstr "" + +msgid "Could not remove the trigger." +msgstr "" + msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}" msgstr "" @@ -3370,6 +3403,9 @@ msgstr "" msgid "Error updating todo status." msgstr "" +msgid "Error uploading file" +msgstr "" + msgid "Error while loading the merge request. Please try again." msgstr "" @@ -3541,6 +3577,9 @@ msgstr "" msgid "Failed to load errors from Sentry. Error message: %{errorMessage}" msgstr "" +msgid "Failed to promote label due to internal error. Please contact administrators." +msgstr "" + msgid "Failed to remove issue from board, please try again." msgstr "" @@ -3786,6 +3825,9 @@ msgstr "" msgid "Git" msgstr "" +msgid "Git LFS is not enabled on this GitLab server, contact your admin." +msgstr "" + msgid "Git global setup" msgstr "" @@ -3966,6 +4008,9 @@ msgstr "" msgid "GroupSettings|Learn more about badges." msgstr "" +msgid "GroupSettings|New runners registration token has been generated!" +msgstr "" + msgid "GroupSettings|Prevent sharing a project within %{group} with other groups" msgstr "" @@ -4130,6 +4175,9 @@ msgstr "" msgid "Hook was successfully created." msgstr "" +msgid "Hook was successfully updated." +msgstr "" + msgid "Housekeeping successfully started" msgstr "" @@ -4409,12 +4457,21 @@ msgstr "" msgid "Introducing Your Conversational Development Index" msgstr "" +msgid "Invalid Login or password" +msgstr "" + +msgid "Invalid file." +msgstr "" + msgid "Invalid input, please avoid emojis" msgstr "" msgid "Invalid pin code" msgstr "" +msgid "Invalid two-factor code." +msgstr "" + msgid "Invitation" msgstr "" @@ -4478,6 +4535,9 @@ msgstr "" msgid "Job has been erased" msgstr "" +msgid "Job has been successfully erased!" +msgstr "" + msgid "Job is stuck. Check runners." msgstr "" @@ -4986,6 +5046,9 @@ msgstr "" msgid "Merged" msgstr "" +msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes." +msgstr "" + msgid "Messages" msgstr "" @@ -5025,6 +5088,9 @@ msgstr "" msgid "Metrics|There was an error getting environments information." msgstr "" +msgid "Metrics|There was an error while retrieving metrics" +msgstr "" + msgid "Metrics|Unexpected deployment data response from prometheus endpoint" msgstr "" @@ -5079,6 +5145,9 @@ msgstr "" msgid "Mirroring repositories" msgstr "" +msgid "Mirroring settings were successfully updated." +msgstr "" + msgid "MissingSSHKeyWarningLink|add an SSH key" msgstr "" @@ -5384,6 +5453,9 @@ msgstr "" msgid "Not enough data" msgstr "" +msgid "Not found." +msgstr "" + msgid "Not now" msgstr "" @@ -5489,6 +5561,9 @@ msgstr "" msgid "November" msgstr "" +msgid "Object does not exist on the server or you don't have permissions to access it" +msgstr "" + msgid "Oct" msgstr "" @@ -5590,6 +5665,12 @@ msgstr "" msgid "Owner" msgstr "" +msgid "Page not found" +msgstr "" + +msgid "Page was successfully deleted" +msgstr "" + msgid "Pages" msgstr "" @@ -5752,6 +5833,12 @@ msgstr "" msgid "Pipelines for last year" msgstr "" +msgid "Pipelines page" +msgstr "" + +msgid "Pipelines settings for '%{project_name}' were successfully updated." +msgstr "" + msgid "Pipelines|Build with confidence" msgstr "" @@ -5896,6 +5983,9 @@ msgstr "" msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access." msgstr "" +msgid "Please select a group." +msgstr "" + msgid "Please select at least one filter to see results" msgstr "" @@ -6217,6 +6307,12 @@ msgstr "" msgid "Project \"%{name}\" is no longer available. Select another project to continue." msgstr "" +msgid "Project %{project_repo} could not be found" +msgstr "" + +msgid "Project '%{project_name}' is being imported." +msgstr "" + msgid "Project '%{project_name}' is in the process of being deleted." msgstr "" @@ -6696,6 +6792,12 @@ msgstr "" msgid "Resolved" msgstr "" +msgid "Resolved 1 discussion." +msgstr "" + +msgid "Resolved all discussions." +msgstr "" + msgid "Response metrics (AWS ELB)" msgstr "" @@ -7019,6 +7121,9 @@ msgstr "" msgid "September" msgstr "" +msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up." +msgstr "" + msgid "Server version" msgstr "" @@ -7603,6 +7708,9 @@ msgstr "" msgid "Successfully removed email." msgstr "" +msgid "Successfully scheduled a pipeline to run. Go to the %{link_to_pipelines} for details." +msgstr "" + msgid "Successfully unblocked" msgstr "" @@ -7756,6 +7864,9 @@ msgstr "" msgid "Test coverage parsing" msgstr "" +msgid "Test failed." +msgstr "" + msgid "The Git LFS objects will <strong>not</strong> be synced." msgstr "" @@ -7774,9 +7885,24 @@ msgstr "" msgid "The collection of events added to the data gathered for that stage." msgstr "" +msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository." +msgstr "" + msgid "The deployment of this job to %{environmentLink} did not succeed." msgstr "" +msgid "The directory has been successfully created." +msgstr "" + +msgid "The entered user map is not a valid JSON user map." +msgstr "" + +msgid "The file has been successfully created." +msgstr "" + +msgid "The file has been successfully deleted." +msgstr "" + msgid "The fork relationship has been removed." msgstr "" @@ -7789,6 +7915,12 @@ msgstr "" msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination." msgstr "" +msgid "The invitation has already been accepted." +msgstr "" + +msgid "The invitation was successfully resent." +msgstr "" + msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage." msgstr "" @@ -7798,6 +7930,15 @@ msgstr "" msgid "The maximum file size allowed is 200KB." msgstr "" +msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally." +msgstr "" + +msgid "The merge conflicts for this merge request have already been resolved." +msgstr "" + +msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request." +msgstr "" + msgid "The name %{entryName} is already taken in this directory." msgstr "" @@ -7822,6 +7963,15 @@ msgstr "" msgid "The project can be accessed without any authentication." msgstr "" +msgid "The project was successfully forked." +msgstr "" + +msgid "The project was successfully imported." +msgstr "" + +msgid "The remote repository is being updated..." +msgstr "" + msgid "The repository for this project does not exist." msgstr "" @@ -7852,12 +8002,18 @@ msgstr "" msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination." msgstr "" +msgid "The uploaded file is not a valid Google Takeout archive." +msgstr "" + msgid "The usage ping is disabled, and cannot be configured through this form." msgstr "" msgid "The user is being deleted." msgstr "" +msgid "The user map has been saved. Continue by selecting the projects you want to import." +msgstr "" + msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side." msgstr "" @@ -7921,6 +8077,9 @@ msgstr "" msgid "There was an error when unsubscribing from this label." msgstr "" +msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again." +msgstr "" + msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue." msgstr "" @@ -8071,6 +8230,9 @@ msgstr "" msgid "This merge request is locked." msgstr "" +msgid "This namespace has already been taken! Please choose another one." +msgstr "" + msgid "This option is disabled as you don't have write permissions for the current branch" msgstr "" @@ -8107,6 +8269,9 @@ msgstr "" msgid "This repository" msgstr "" +msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch." +msgstr "" + msgid "This runner will only run on pipelines triggered on protected branches" msgstr "" @@ -8393,6 +8558,9 @@ msgstr "" msgid "Todo" msgstr "" +msgid "Todo was successfully marked as done." +msgstr "" + msgid "Todos" msgstr "" @@ -8450,6 +8618,9 @@ msgstr "" msgid "Trending" msgstr "" +msgid "Trigger removed." +msgstr "" + msgid "Trigger this manual action" msgstr "" @@ -8459,6 +8630,15 @@ msgstr "" msgid "Trigger variables:" msgstr "" +msgid "Trigger was created successfully." +msgstr "" + +msgid "Trigger was re-assigned." +msgstr "" + +msgid "Trigger was successfully updated." +msgstr "" + msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions." msgstr "" @@ -8483,9 +8663,15 @@ msgstr "" msgid "Type" msgstr "" +msgid "Unable to connect to server: %{error}" +msgstr "" + msgid "Unable to load the diff. %{button_try_again}" msgstr "" +msgid "Unable to schedule a pipeline to run immediately" +msgstr "" + msgid "Unblock" msgstr "" @@ -8561,6 +8747,9 @@ msgstr "" msgid "Update now" msgstr "" +msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed." +msgstr "" + msgid "Update your group name, description, avatar, and visibility." msgstr "" @@ -8657,6 +8846,12 @@ msgstr "" msgid "User was successfully created." msgstr "" +msgid "User was successfully removed from group and any subresources." +msgstr "" + +msgid "User was successfully removed from project." +msgstr "" + msgid "User was successfully updated." msgstr "" @@ -8750,6 +8945,9 @@ msgstr "" msgid "Validate your GitLab CI configuration file" msgstr "" +msgid "Validations failed." +msgstr "" + msgid "Value" msgstr "" @@ -8888,6 +9086,9 @@ msgstr "" msgid "Wiki" msgstr "" +msgid "Wiki was successfully updated." +msgstr "" + msgid "WikiClone|Clone your wiki" msgstr "" @@ -9107,6 +9308,12 @@ msgstr "" msgid "You can move around the graph by using the arrow keys." msgstr "" +msgid "You can now submit a merge request to get this change into the original branch." +msgstr "" + +msgid "You can now submit a merge request to get this change into the original project." +msgstr "" + msgid "You can only add files when you are on a branch" msgstr "" @@ -9134,9 +9341,18 @@ msgstr "" msgid "You cannot impersonate an internal user" msgstr "" +msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute." +msgstr "" + msgid "You cannot write to this read-only GitLab instance." msgstr "" +msgid "You could not create a new trigger." +msgstr "" + +msgid "You could not take ownership of trigger." +msgstr "" + msgid "You do not have any subscriptions yet" msgstr "" @@ -9155,6 +9371,9 @@ msgstr "" msgid "You have reached your project limit" msgstr "" +msgid "You left the \"%{membershipable_human_name}\" %{source_type}." +msgstr "" + msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>." msgstr "" @@ -9170,6 +9389,15 @@ msgstr "" msgid "You need to register a two-factor authentication app before you can set up a U2F device." msgstr "" +msgid "You need to specify both an Access Token and a Host URL." +msgstr "" + +msgid "You need to upload a GitLab project export archive (ending in .gz)." +msgstr "" + +msgid "You need to upload a Google Takeout archive." +msgstr "" + msgid "You will lose all changes you've made to this file. This action cannot be undone." msgstr "" @@ -9242,6 +9470,9 @@ msgstr "" msgid "Your U2F device was registered!" msgstr "" +msgid "Your access request to the %{source_type} has been withdrawn." +msgstr "" + msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO." msgstr "" @@ -9263,6 +9494,9 @@ msgstr "" msgid "Your changes have been saved" msgstr "" +msgid "Your changes have been successfully committed." +msgstr "" + msgid "Your comment will not be visible to the public." msgstr "" @@ -9287,6 +9521,9 @@ msgstr "" msgid "Your projects" msgstr "" +msgid "Your request for access has been queued for review." +msgstr "" + msgid "a deleted user" msgstr "" @@ -9366,6 +9603,9 @@ msgstr "" msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command." msgstr "" +msgid "failed" +msgstr "" + msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}" msgstr "" @@ -9743,6 +9983,9 @@ msgstr "" msgid "stuck" msgstr "" +msgid "success" +msgstr "" + msgid "syntax is correct" msgstr "" diff --git a/package.json b/package.json index 1154d57cad2..f80f9b3690b 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,8 @@ "prettier-all": "node ./scripts/frontend/prettier.js check-all", "prettier-all-save": "node ./scripts/frontend/prettier.js save-all", "stylelint": "node node_modules/stylelint/bin/stylelint.js app/assets/stylesheets/**/*.* --custom-formatter node_modules/stylelint-error-string-formatter", + "stylelint-file": "node node_modules/stylelint/bin/stylelint.js", + "stylelint-create-utility-map": "node scripts/frontend/stylelint/stylelint-utility-map.js", "test": "yarn jest && yarn karma", "webpack": "webpack --config config/webpack.config.js", "webpack-prod": "NODE_ENV=production webpack --config config/webpack.config.js" @@ -29,8 +31,8 @@ "@babel/plugin-syntax-dynamic-import": "^7.2.0", "@babel/plugin-syntax-import-meta": "^7.2.0", "@babel/preset-env": "^7.3.1", - "@gitlab/csslab": "^1.8.0", - "@gitlab/svgs": "^1.54.0", + "@gitlab/csslab": "^1.9.0", + "@gitlab/svgs": "^1.57.0", "@gitlab/ui": "^3.0.0", "apollo-boost": "^0.3.1", "apollo-client": "^2.5.1", @@ -38,7 +40,7 @@ "autosize": "^4.0.0", "axios": "^0.17.1", "babel-loader": "^8.0.5", - "bootstrap": "4.1.3", + "bootstrap": "4.3.1", "brace-expansion": "^1.1.8", "cache-loader": "^2.0.1", "chart.js": "2.7.2", @@ -89,7 +91,7 @@ "monaco-editor-webpack-plugin": "^1.7.0", "mousetrap": "^1.4.6", "pikaday": "^1.6.1", - "popper.js": "^1.14.3", + "popper.js": "^1.14.7", "prismjs": "^1.6.0", "prosemirror-markdown": "^1.3.0", "prosemirror-model": "^1.6.4", @@ -116,14 +118,14 @@ "underscore": "^1.9.0", "url-loader": "^1.1.2", "visibilityjs": "^1.2.4", - "vue": "^2.5.21", + "vue": "^2.6.10", "vue-apollo": "^3.0.0-beta.28", - "vue-loader": "^15.4.2", + "vue-loader": "^15.7.0", "vue-resource": "^1.5.1", "vue-router": "^3.0.2", - "vue-template-compiler": "^2.5.21", - "vue-virtual-scroll-list": "^1.2.5", - "vuex": "^3.0.1", + "vue-template-compiler": "^2.6.10", + "vue-virtual-scroll-list": "^1.3.1", + "vuex": "^3.1.0", "webpack": "^4.29.0", "webpack-bundle-analyzer": "^3.0.3", "webpack-cli": "^3.2.1", @@ -142,6 +144,7 @@ "babel-plugin-rewire": "^1.2.0", "chalk": "^2.4.1", "commander": "^2.18.0", + "custom-jquery-matchers": "^2.1.0", "docdash": "^1.0.2", "eslint": "~5.9.0", "eslint-import-resolver-jest": "^2.1.1", @@ -150,15 +153,17 @@ "eslint-plugin-import": "^2.14.0", "eslint-plugin-jasmine": "^2.10.1", "eslint-plugin-jest": "^22.3.0", - "gettext-extractor": "^3.3.2", - "gettext-extractor-vue": "^4.0.1", + "gettext-extractor": "^3.4.3", + "gettext-extractor-vue": "^4.0.2", "graphql-tag": "^2.10.0", "istanbul": "^0.4.5", "jasmine-core": "^2.9.0", "jasmine-diff": "^0.1.3", "jasmine-jquery": "^2.1.1", "jest": "^24.1.0", + "jest-environment-jsdom": "^24.0.0", "jest-junit": "^6.3.0", + "jest-util": "^24.0.0", "jsdoc": "^3.5.5", "jsdoc-vue": "^1.0.0", "karma": "^3.0.0", @@ -169,13 +174,15 @@ "karma-mocha-reporter": "^2.2.5", "karma-sourcemap-loader": "^0.3.7", "karma-webpack": "^4.0.0-beta.0", + "md5": "^2.2.1", + "node-sass": "^4.11.0", "nodemon": "^1.18.9", "pixelmatch": "^4.0.2", "postcss": "^7.0.14", "prettier": "1.16.4", "stylelint": "^9.10.1", "stylelint-config-recommended": "^2.1.0", - "stylelint-scss": "^3.5.3", + "stylelint-scss": "^3.5.4", "vue-jest": "^4.0.0-beta.2", "webpack-dev-server": "^3.1.14", "yarn-deduplicate": "^1.1.1" diff --git a/qa/README.md b/qa/README.md index 735868e7640..7d66f7d5abc 100644 --- a/qa/README.md +++ b/qa/README.md @@ -55,16 +55,19 @@ You can also supply specific tests to run as another parameter. For example, to run the repository-related specs, you can execute: ``` -bin/qa Test::Instance::All http://localhost qa/specs/features/repository/ +bin/qa Test::Instance::All http://localhost -- qa/specs/features/browser_ui/3_create/repository ``` Since the arguments would be passed to `rspec`, you could use all `rspec` options there. For example, passing `--backtrace` and also line number: ``` -bin/qa Test::Instance::All http://localhost qa/specs/features/project/create_spec.rb:3 --backtrace +bin/qa Test::Instance::All http://localhost -- qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb:6 --backtrace ``` +Note that the separator `--` is required; all subsequent options will be +ignored by the QA framework and passed to `rspec`. + ### Overriding the authenticated user Unless told otherwise, the QA tests will run as the default `root` user seeded @@ -117,7 +120,7 @@ tests that are expected to fail while a fix is in progress (similar to how can be used). ``` -bin/qa Test::Instance::All http://localhost --tag quarantine +bin/qa Test::Instance::All http://localhost -- --tag quarantine ``` If `quarantine` is used with other tags, tests will only be run if they have at @@ -128,3 +131,25 @@ For example, suppose one test has `:smoke` and `:quarantine` metadata, and another test has `:ldap` and `:quarantine` metadata. If the tests are run with `--tag smoke --tag quarantine`, only the first test will run. The test with `:ldap` will not run even though it also has `:quarantine`. + +### Running tests with a feature flag enabled + +Tests can be run with with a feature flag enabled by using the command-line +option `--enable-feature FEATURE_FLAG`. For example, to enable the feature flag +that enforces Gitaly request limits, you would use the command: + +``` +bin/qa Test::Instance::All http://localhost --enable-feature gitaly_enforce_requests_limits +``` + +This will instruct the QA framework to enable the `gitaly_enforce_requests_limits` +feature flag ([via the API](https://docs.gitlab.com/ee/api/features.html)), run +all the tests in the `Test::Instance::All` scenario, and then disable the +feature flag again. + +Note: the QA framework doesn't currently allow you to easily toggle a feature +flag during a single test, [as you can in unit tests](https://docs.gitlab.com/ee/development/feature_flags.html#specs), +but [that capability is planned](https://gitlab.com/gitlab-org/quality/team-tasks/issues/77). + +Note also that the `--` separator isn't used because `--enable-feature` is a QA +framework option, not an `rspec` option.
\ No newline at end of file diff --git a/qa/Rakefile b/qa/Rakefile index d0101740f1a..7ac018f7286 100644 --- a/qa/Rakefile +++ b/qa/Rakefile @@ -27,9 +27,9 @@ task :run_artillery_load_tests do end urls = YAML.safe_load(File.read(urls_file)) - ENV['HOST_URL'] = urls[:host] - ENV['LARGE_ISSUE_URL'] = urls[:large_issue] - ENV['LARGE_MR_URL'] = urls[:large_mr] + ENV['HOST_URL'] = urls["host"] + ENV['LARGE_ISSUE_URL'] = urls["large_issue"] + ENV['LARGE_MR_URL'] = urls["large_mr"] end sh('artillery run load/artillery.yml -o report.json') diff --git a/qa/load/artillery.yml b/qa/load/artillery.yml index e2c3c293d8b..17d253ec480 100644 --- a/qa/load/artillery.yml +++ b/qa/load/artillery.yml @@ -1,15 +1,18 @@ config: target: "{{ $processEnvironment.HOST_URL }}" + http: + pool: 10 # All HTTP requests from all virtual users will be sent over the same <pool> connections. + # This also means that there is a limit on the number of requests sent per second. phases: - - duration: 60 - arrivalRate: 1 + - duration: 30 + arrivalRate: 10 name: "Warm up" - - duration: 120 - arrivalRate: 1 - rampTo: 50 + - duration: 90 + arrivalRate: 10 + rampTo: 100 name: "Gradual ramp up" - - duration: 60 - arrivalRate: 50 + - duration: 90 + arrivalRate: 100 name: "Sustained max load" scenarios: - name: "Visit large issue url" @@ -17,6 +17,7 @@ module QA autoload :Env, 'qa/runtime/env' autoload :Address, 'qa/runtime/address' autoload :Path, 'qa/runtime/path' + autoload :Feature, 'qa/runtime/feature' autoload :Fixtures, 'qa/runtime/fixtures' autoload :Logger, 'qa/runtime/logger' @@ -89,6 +90,7 @@ module QA autoload :Bootable, 'qa/scenario/bootable' autoload :Actable, 'qa/scenario/actable' autoload :Template, 'qa/scenario/template' + autoload :SharedAttributes, 'qa/scenario/shared_attributes' ## # Test scenario entrypoints. diff --git a/qa/qa/page/dashboard/groups.rb b/qa/qa/page/dashboard/groups.rb index 7a07515de62..52853376f17 100644 --- a/qa/qa/page/dashboard/groups.rb +++ b/qa/qa/page/dashboard/groups.rb @@ -19,11 +19,13 @@ module QA has_filtered_group?(name) end - def go_to_group(name) + def click_group(name) + raise "Group with name #{name} not found!" unless has_group?(name) + click_link name end - def go_to_new_group + def click_new_group click_on 'New group' end end diff --git a/qa/qa/page/dashboard/snippet/new.rb b/qa/qa/page/dashboard/snippet/new.rb index a637b869d2f..4a8e65e20af 100644 --- a/qa/qa/page/dashboard/snippet/new.rb +++ b/qa/qa/page/dashboard/snippet/new.rb @@ -37,7 +37,7 @@ module QA text_area.set content end - def create_snippet + def click_create_snippet_button click_element :create_snippet_button end diff --git a/qa/qa/page/group/show.rb b/qa/qa/page/group/show.rb index 41716326685..d4c4be0d6ca 100644 --- a/qa/qa/page/group/show.rb +++ b/qa/qa/page/group/show.rb @@ -18,7 +18,7 @@ module QA element :no_result_text, 'No groups or projects matched your search' # rubocop:disable QA/ElementWithPattern end - def go_to_subgroup(name) + def click_subgroup(name) click_link name end diff --git a/qa/qa/page/label/index.rb b/qa/qa/page/label/index.rb index de0cfa9f293..e73d40b37ac 100644 --- a/qa/qa/page/label/index.rb +++ b/qa/qa/page/label/index.rb @@ -18,7 +18,7 @@ module QA element :label_svg end - def go_to_new_label + def click_new_label_button # The 'labels.svg' takes a fraction of a second to load after which the "New label" button shifts up a bit # This can cause webdriver to miss the hit so we wait for the svg to load (implicitly with has_element?) # before clicking the button. diff --git a/qa/qa/page/label/new.rb b/qa/qa/page/label/new.rb index b5422dc9400..9c8cf5f07e6 100644 --- a/qa/qa/page/label/new.rb +++ b/qa/qa/page/label/new.rb @@ -9,7 +9,7 @@ module QA element :label_create_button end - def create_label + def click_label_create_button click_element :label_create_button end diff --git a/qa/qa/page/main/menu.rb b/qa/qa/page/main/menu.rb index 1b3445b0064..e98d531c86e 100644 --- a/qa/qa/page/main/menu.rb +++ b/qa/qa/page/main/menu.rb @@ -47,7 +47,7 @@ module QA end end - def go_to_admin_area + def click_admin_area within_top_menu { click_element :admin_area_link } end @@ -57,7 +57,7 @@ module QA end end - def go_to_profile_settings + def click_settings_link retry_until(reload: false) do within_user_menu do click_link 'Settings' @@ -67,7 +67,7 @@ module QA end end - def go_to_snippets + def click_snippets_link click_element :snippets_link end diff --git a/qa/qa/page/merge_request/show.rb b/qa/qa/page/merge_request/show.rb index c0411db6505..5aef868a805 100644 --- a/qa/qa/page/merge_request/show.rb +++ b/qa/qa/page/merge_request/show.rb @@ -145,11 +145,11 @@ module QA click_element :squash_checkbox end - def go_to_discussions_tab + def click_discussions_tab click_element :notes_tab end - def go_to_diffs_tab + def click_diffs_tab click_element :diffs_tab end diff --git a/qa/qa/page/profile/personal_access_tokens.rb b/qa/qa/page/profile/personal_access_tokens.rb index 8c12eff5cf1..c9a5a1b3ca0 100644 --- a/qa/qa/page/profile/personal_access_tokens.rb +++ b/qa/qa/page/profile/personal_access_tokens.rb @@ -26,7 +26,7 @@ module QA check_element(:api_radio) end - def create_token + def click_create_token_button click_element(:create_token_button) end diff --git a/qa/qa/page/project/issue/index.rb b/qa/qa/page/project/issue/index.rb index 1035bf74a43..e987d279e21 100644 --- a/qa/qa/page/project/issue/index.rb +++ b/qa/qa/page/project/issue/index.rb @@ -7,7 +7,7 @@ module QA element :issue_link, 'link_to issue.title' # rubocop:disable QA/ElementWithPattern end - def go_to_issue(title) + def click_issue_link(title) click_link(title) end end diff --git a/qa/qa/page/project/job/show.rb b/qa/qa/page/project/job/show.rb index 9c218f4ed8b..5853f487f0b 100644 --- a/qa/qa/page/project/job/show.rb +++ b/qa/qa/page/project/job/show.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module QA::Page module Project::Job class Show < QA::Page::Base @@ -31,7 +33,9 @@ module QA::Page private def loaded?(wait: 60) - has_element?(:build_trace, wait: wait) + wait(reload: true, max: wait, interval: 1) do + has_element?(:build_trace, wait: 1) + end end def completed?(timeout: 60) diff --git a/qa/qa/page/project/milestone/new.rb b/qa/qa/page/project/milestone/new.rb index 992ef89004b..0db8d2cf766 100644 --- a/qa/qa/page/project/milestone/new.rb +++ b/qa/qa/page/project/milestone/new.rb @@ -17,7 +17,7 @@ module QA fill_element :milestone_description, description end - def create_new_milestone + def click_milestone_create_button click_element :milestone_create_button end end diff --git a/qa/qa/page/project/new.rb b/qa/qa/page/project/new.rb index 552b2293115..f841afbbffd 100644 --- a/qa/qa/page/project/new.rb +++ b/qa/qa/page/project/new.rb @@ -35,7 +35,7 @@ module QA end end - def go_to_import_project + def click_import_project click_on 'Import project' end @@ -51,7 +51,7 @@ module QA click_on 'Create project' end - def go_to_create_from_template + def click_create_from_template_tab click_element(:project_create_from_template_tab) end @@ -59,7 +59,7 @@ module QA choose visibility end - def go_to_github_import + def click_github_link click_link 'GitHub' end end diff --git a/qa/qa/page/project/operations/environments/index.rb b/qa/qa/page/project/operations/environments/index.rb index 63965a57edd..610a34385b1 100644 --- a/qa/qa/page/project/operations/environments/index.rb +++ b/qa/qa/page/project/operations/environments/index.rb @@ -10,7 +10,7 @@ module QA element :environment_link end - def go_to_environment(environment_name) + def click_environment_link(environment_name) wait(reload: false) do find(element_selector_css(:environment_link), text: environment_name).click end diff --git a/qa/qa/page/project/pipeline/index.rb b/qa/qa/page/project/pipeline/index.rb index 19d83ecc4f4..0373a12f597 100644 --- a/qa/qa/page/project/pipeline/index.rb +++ b/qa/qa/page/project/pipeline/index.rb @@ -5,7 +5,7 @@ module QA::Page element :pipeline_link, 'class="js-pipeline-url-link"' # rubocop:disable QA/ElementWithPattern end - def go_to_latest_pipeline + def click_on_latest_pipeline css = '.js-pipeline-url-link' link = wait(reload: false) do diff --git a/qa/qa/page/project/pipeline/show.rb b/qa/qa/page/project/pipeline/show.rb index 6f8a66bf527..9c0b55b1c4c 100644 --- a/qa/qa/page/project/pipeline/show.rb +++ b/qa/qa/page/project/pipeline/show.rb @@ -42,11 +42,11 @@ module QA::Page end end - def go_to_job(job_name) + def click_job(job_name) find_element(:job_link, text: job_name).click end - def go_to_first_job + def click_on_first_job css = '.js-pipeline-graph-job-link' wait(reload: false) do diff --git a/qa/qa/page/project/settings/protected_branches.rb b/qa/qa/page/project/settings/protected_branches.rb index 76591a4e3fe..399a49d2420 100644 --- a/qa/qa/page/project/settings/protected_branches.rb +++ b/qa/qa/page/project/settings/protected_branches.rb @@ -32,22 +32,22 @@ module QA end def allow_no_one_to_push - click_allow(:push, 'No one') + go_to_allow(:push, 'No one') end def allow_devs_and_maintainers_to_push - click_allow(:push, 'Developers + Maintainers') + go_to_allow(:push, 'Developers + Maintainers') end # @deprecated alias_method :allow_devs_and_masters_to_push, :allow_devs_and_maintainers_to_push def allow_no_one_to_merge - click_allow(:merge, 'No one') + go_to_allow(:merge, 'No one') end def allow_devs_and_maintainers_to_merge - click_allow(:merge, 'Developers + Maintainers') + go_to_allow(:merge, 'Developers + Maintainers') end # @deprecated @@ -59,7 +59,7 @@ module QA private - def click_allow(action, text) + def go_to_allow(action, text) click_element :"allowed_to_#{action}_select" within_element(:"allowed_to_#{action}_dropdown") do diff --git a/qa/qa/page/project/show.rb b/qa/qa/page/project/show.rb index 9c21d9ddbfa..1a9a2fd413f 100644 --- a/qa/qa/page/project/show.rb +++ b/qa/qa/page/project/show.rb @@ -76,13 +76,13 @@ module QA click_on 'Fork' end - def go_to_file(filename) + def click_file(filename) within_element(:file_tree) do click_on filename end end - def go_to_commit(commit_msg) + def click_commit(commit_msg) within_element(:file_tree) do click_on commit_msg end diff --git a/qa/qa/page/project/sub_menus/operations.rb b/qa/qa/page/project/sub_menus/operations.rb index cf9fc453565..24a99a9464c 100644 --- a/qa/qa/page/project/sub_menus/operations.rb +++ b/qa/qa/page/project/sub_menus/operations.rb @@ -14,7 +14,7 @@ module QA end end - def click_operations_environments + def go_to_operations_environments hover_operations do within_submenu do click_element(:operations_environments_link) @@ -22,7 +22,7 @@ module QA end end - def click_operations_kubernetes + def go_to_operations_kubernetes hover_operations do within_submenu do click_link('Kubernetes') diff --git a/qa/qa/page/project/sub_menus/repository.rb b/qa/qa/page/project/sub_menus/repository.rb index 29eaa9a74de..4cc73a6b25a 100644 --- a/qa/qa/page/project/sub_menus/repository.rb +++ b/qa/qa/page/project/sub_menus/repository.rb @@ -20,7 +20,7 @@ module QA end end - def click_repository_branches + def go_to_repository_branches hover_repository do within_submenu do click_element(:branches_link) diff --git a/qa/qa/page/project/sub_menus/settings.rb b/qa/qa/page/project/sub_menus/settings.rb index 62c594c0210..22743ebd0a1 100644 --- a/qa/qa/page/project/sub_menus/settings.rb +++ b/qa/qa/page/project/sub_menus/settings.rb @@ -14,7 +14,7 @@ module QA end end - def click_ci_cd_settings + def go_to_ci_cd_settings hover_settings do within_submenu do click_link('CI / CD') @@ -22,7 +22,7 @@ module QA end end - def click_members_settings + def go_to_members_settings hover_settings do within_submenu do click_element :link_members_settings @@ -30,7 +30,7 @@ module QA end end - def click_repository_settings + def go_to_repository_settings hover_settings do within_submenu do click_link('Repository') @@ -38,7 +38,7 @@ module QA end end - def go_to_settings + def click_settings within_sidebar do click_on 'Settings' end diff --git a/qa/qa/page/project/wiki/edit.rb b/qa/qa/page/project/wiki/edit.rb index 8d0eafa1818..1ccd67349c3 100644 --- a/qa/qa/page/project/wiki/edit.rb +++ b/qa/qa/page/project/wiki/edit.rb @@ -9,15 +9,7 @@ module QA element :edit_page_link, 'Edit' # rubocop:disable QA/ElementWithPattern end - def go_to_new_page - click_on 'New page' - end - - def got_to_view_history_page - click_on 'Page history' - end - - def go_to_edit_page + def click_edit click_on 'Edit' end end diff --git a/qa/qa/page/project/wiki/new.rb b/qa/qa/page/project/wiki/new.rb index b90e03be36a..792eba4bab7 100644 --- a/qa/qa/page/project/wiki/new.rb +++ b/qa/qa/page/project/wiki/new.rb @@ -23,7 +23,7 @@ module QA element :svg_content end - def go_to_create_first_page + def click_create_your_first_page_button # The svg takes a fraction of a second to load after which the # "Create your first page" button shifts up a bit. This can cause # webdriver to miss the hit so we wait for the svg to load before diff --git a/qa/qa/page/project/wiki/show.rb b/qa/qa/page/project/wiki/show.rb index dffbc5d60a2..f79ad510084 100644 --- a/qa/qa/page/project/wiki/show.rb +++ b/qa/qa/page/project/wiki/show.rb @@ -11,7 +11,7 @@ module QA element :clone_repository_link, 'Clone repository' # rubocop:disable QA/ElementWithPattern end - def go_to_clone_repository + def click_clone_repository click_on 'Clone repository' end end diff --git a/qa/qa/resource/api_fabricator.rb b/qa/qa/resource/api_fabricator.rb index 98eebac0880..de04467ff5b 100644 --- a/qa/qa/resource/api_fabricator.rb +++ b/qa/qa/resource/api_fabricator.rb @@ -8,9 +8,6 @@ module QA module ApiFabricator include Capybara::DSL - HTTP_STATUS_OK = 200 - HTTP_STATUS_CREATED = 201 - ResourceNotFoundError = Class.new(RuntimeError) ResourceFabricationFailedError = Class.new(RuntimeError) ResourceURLMissingError = Class.new(RuntimeError) diff --git a/qa/qa/resource/branch.rb b/qa/qa/resource/branch.rb index bd52c4abe02..a45dd030625 100644 --- a/qa/qa/resource/branch.rb +++ b/qa/qa/resource/branch.rb @@ -46,7 +46,7 @@ module QA # to `allow_to_push` variable. return branch unless @protected - Page::Project::Menu.perform(&:click_repository_settings) + Page::Project::Menu.perform(&:go_to_repository_settings) Page::Project::Settings::Repository.perform do |setting| setting.expand_protected_branches do |page| diff --git a/qa/qa/resource/ci_variable.rb b/qa/qa/resource/ci_variable.rb index 0570c47d41c..d82de4cb816 100644 --- a/qa/qa/resource/ci_variable.rb +++ b/qa/qa/resource/ci_variable.rb @@ -15,7 +15,7 @@ module QA def fabricate! project.visit! - Page::Project::Menu.perform(&:click_ci_cd_settings) + Page::Project::Menu.perform(&:go_to_ci_cd_settings) Page::Project::Settings::CICD.perform do |setting| setting.expand_ci_variables do |page| diff --git a/qa/qa/resource/deploy_key.rb b/qa/qa/resource/deploy_key.rb index 9565598efb0..869e2a71e47 100644 --- a/qa/qa/resource/deploy_key.rb +++ b/qa/qa/resource/deploy_key.rb @@ -23,7 +23,7 @@ module QA def fabricate! project.visit! - Page::Project::Menu.perform(&:click_repository_settings) + Page::Project::Menu.perform(&:go_to_repository_settings) Page::Project::Settings::Repository.perform do |setting| setting.expand_deploy_keys do |page| diff --git a/qa/qa/resource/deploy_token.rb b/qa/qa/resource/deploy_token.rb index cee4422f6b4..fca5ed83c87 100644 --- a/qa/qa/resource/deploy_token.rb +++ b/qa/qa/resource/deploy_token.rb @@ -32,7 +32,7 @@ module QA project.visit! Page::Project::Menu.act do - click_repository_settings + go_to_repository_settings end Page::Project::Settings::Repository.perform do |setting| diff --git a/qa/qa/resource/group.rb b/qa/qa/resource/group.rb index d7f9ec6a836..0b567a474c8 100644 --- a/qa/qa/resource/group.rb +++ b/qa/qa/resource/group.rb @@ -21,7 +21,7 @@ module QA Page::Group::Show.perform do |group_show| if group_show.has_subgroup?(path) - group_show.go_to_subgroup(path) + group_show.click_subgroup(path) else group_show.go_to_new_subgroup diff --git a/qa/qa/resource/kubernetes_cluster.rb b/qa/qa/resource/kubernetes_cluster.rb index 93a06be6818..27ab7b60211 100644 --- a/qa/qa/resource/kubernetes_cluster.rb +++ b/qa/qa/resource/kubernetes_cluster.rb @@ -16,7 +16,7 @@ module QA @project.visit! Page::Project::Menu.perform( - &:click_operations_kubernetes) + &:go_to_operations_kubernetes) Page::Project::Operations::Kubernetes::Index.perform( &:add_kubernetes_cluster) diff --git a/qa/qa/resource/label.rb b/qa/qa/resource/label.rb index c0869cb1f2a..7c899db31f3 100644 --- a/qa/qa/resource/label.rb +++ b/qa/qa/resource/label.rb @@ -25,13 +25,13 @@ module QA project.visit! Page::Project::Menu.perform(&:go_to_labels) - Page::Label::Index.perform(&:go_to_new_label) + Page::Label::Index.perform(&:click_new_label_button) Page::Label::New.perform do |page| page.fill_title(@title) page.fill_description(@description) page.fill_color(@color) - page.create_label + page.click_label_create_button end end end diff --git a/qa/qa/resource/personal_access_token.rb b/qa/qa/resource/personal_access_token.rb index b8dd0a3562f..f5c632cd8d2 100644 --- a/qa/qa/resource/personal_access_token.rb +++ b/qa/qa/resource/personal_access_token.rb @@ -13,13 +13,13 @@ module QA end def fabricate! - Page::Main::Menu.perform(&:go_to_profile_settings) + Page::Main::Menu.perform(&:click_settings_link) Page::Profile::Menu.perform(&:click_access_tokens) Page::Profile::PersonalAccessTokens.perform do |page| page.fill_token_name(name || 'api-test-token') page.check_api - page.create_token + page.click_create_token_button end end end diff --git a/qa/qa/resource/project_imported_from_github.rb b/qa/qa/resource/project_imported_from_github.rb index 0d25e7dd842..a160cdb3273 100644 --- a/qa/qa/resource/project_imported_from_github.rb +++ b/qa/qa/resource/project_imported_from_github.rb @@ -18,11 +18,11 @@ module QA Page::Group::Show.perform(&:go_to_new_project) Page::Project::New.perform do |page| - page.go_to_import_project + page.click_import_project end Page::Project::New.perform do |page| - page.go_to_github_import + page.click_github_link end Page::Project::Import::Github.perform do |page| diff --git a/qa/qa/resource/project_milestone.rb b/qa/qa/resource/project_milestone.rb index a4d6657caff..8ace75f695a 100644 --- a/qa/qa/resource/project_milestone.rb +++ b/qa/qa/resource/project_milestone.rb @@ -28,7 +28,7 @@ module QA Page::Project::Milestone::New.perform do |milestone_new| milestone_new.set_title(@title) milestone_new.set_description(@description) - milestone_new.create_new_milestone + milestone_new.click_milestone_create_button end end end diff --git a/qa/qa/resource/repository/wiki_push.rb b/qa/qa/resource/repository/wiki_push.rb index 77c4c8a514d..95712300854 100644 --- a/qa/qa/resource/repository/wiki_push.rb +++ b/qa/qa/resource/repository/wiki_push.rb @@ -24,7 +24,7 @@ module QA @repository_http_uri ||= begin wiki.visit! Page::Project::Wiki::Show.act do - go_to_clone_repository + click_clone_repository choose_repository_clone_http repository_location.uri end diff --git a/qa/qa/resource/runner.rb b/qa/qa/resource/runner.rb index 08ae3f22117..3344ad3360a 100644 --- a/qa/qa/resource/runner.rb +++ b/qa/qa/resource/runner.rb @@ -29,7 +29,7 @@ module QA def fabricate! project.visit! - Page::Project::Menu.perform(&:click_ci_cd_settings) + Page::Project::Menu.perform(&:go_to_ci_cd_settings) Service::Runner.new(name).tap do |runner| Page::Project::Settings::CICD.perform do |settings| diff --git a/qa/qa/resource/sandbox.rb b/qa/qa/resource/sandbox.rb index 41ce857a8b8..942eea5cc40 100644 --- a/qa/qa/resource/sandbox.rb +++ b/qa/qa/resource/sandbox.rb @@ -20,9 +20,9 @@ module QA Page::Dashboard::Groups.perform do |page| if page.has_group?(path) - page.go_to_group(path) + page.click_group(path) else - page.go_to_new_group + page.click_new_group Page::Group::New.perform do |group| group.set_path(path) diff --git a/qa/qa/resource/settings/hashed_storage.rb b/qa/qa/resource/settings/hashed_storage.rb index 40c06768ffe..08bb95cfd4b 100644 --- a/qa/qa/resource/settings/hashed_storage.rb +++ b/qa/qa/resource/settings/hashed_storage.rb @@ -8,7 +8,7 @@ module QA raise ArgumentError unless traits.include?(:enabled) Page::Main::Login.perform(&:sign_in_using_credentials) - Page::Main::Menu.perform(&:go_to_admin_area) + Page::Main::Menu.perform(&:click_admin_area) Page::Admin::Menu.perform(&:go_to_repository_settings) Page::Admin::Settings::Repository.perform do |setting| diff --git a/qa/qa/resource/snippet.rb b/qa/qa/resource/snippet.rb index 1478f197570..f58d7b5113f 100644 --- a/qa/qa/resource/snippet.rb +++ b/qa/qa/resource/snippet.rb @@ -22,7 +22,7 @@ module QA page.set_visibility(@visibility) page.fill_file_name(@file_name) page.fill_file_content(@file_content) - page.create_snippet + page.click_create_snippet_button end end end diff --git a/qa/qa/resource/ssh_key.rb b/qa/qa/resource/ssh_key.rb index c6c97c8532f..4e399ae730e 100644 --- a/qa/qa/resource/ssh_key.rb +++ b/qa/qa/resource/ssh_key.rb @@ -14,7 +14,7 @@ module QA end def fabricate! - Page::Main::Menu.perform(&:go_to_profile_settings) + Page::Main::Menu.perform(&:click_settings_link) Page::Profile::Menu.perform(&:click_ssh_keys) Page::Profile::SSHKeys.perform do |page| diff --git a/qa/qa/resource/wiki.rb b/qa/qa/resource/wiki.rb index e942e9718a0..0a776f22e25 100644 --- a/qa/qa/resource/wiki.rb +++ b/qa/qa/resource/wiki.rb @@ -18,7 +18,7 @@ module QA Page::Project::Menu.perform { |menu_side| menu_side.click_wiki } Page::Project::Wiki::New.perform do |wiki_new| - wiki_new.go_to_create_first_page + wiki_new.click_create_your_first_page_button wiki_new.set_title(@title) wiki_new.set_content(@content) wiki_new.set_message(@message) diff --git a/qa/qa/runtime/address.rb b/qa/qa/runtime/address.rb index ffad3974b02..af0537dc17c 100644 --- a/qa/qa/runtime/address.rb +++ b/qa/qa/runtime/address.rb @@ -15,6 +15,13 @@ module QA @instance.to_s end end + + def self.valid?(value) + uri = URI.parse(value) + uri.is_a?(URI::HTTP) && !uri.host.nil? + rescue URI::InvalidURIError + false + end end end end diff --git a/qa/qa/runtime/api/client.rb b/qa/qa/runtime/api/client.rb index aff84c89f0e..d3327b49339 100644 --- a/qa/qa/runtime/api/client.rb +++ b/qa/qa/runtime/api/client.rb @@ -14,7 +14,7 @@ module QA def personal_access_token @personal_access_token ||= begin - # you can set the environment variable PERSONAL_ACCESS_TOKEN + # you can set the environment variable GITLAB_QA_ACCESS_TOKEN # to use a specific access token rather than create one from the UI Runtime::Env.personal_access_token ||= create_personal_access_token end diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb index dd0ddbdbd6b..03cae3c1fe6 100644 --- a/qa/qa/runtime/env.rb +++ b/qa/qa/runtime/env.rb @@ -53,7 +53,7 @@ module QA # specifies token that can be used for the api def personal_access_token - @personal_access_token ||= ENV['PERSONAL_ACCESS_TOKEN'] + @personal_access_token ||= ENV['GITLAB_QA_ACCESS_TOKEN'] end def remote_grid diff --git a/qa/qa/runtime/feature.rb b/qa/qa/runtime/feature.rb new file mode 100644 index 00000000000..1b4ae7adbbe --- /dev/null +++ b/qa/qa/runtime/feature.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module QA + module Runtime + module Feature + extend self + extend Support::Api + + SetFeatureError = Class.new(RuntimeError) + + def enable(key) + QA::Runtime::Logger.info("Enabling feature: #{key}") + set_feature(key, true) + end + + def disable(key) + QA::Runtime::Logger.info("Disabling feature: #{key}") + set_feature(key, false) + end + + private + + def api_client + @api_client ||= Runtime::API::Client.new(:gitlab) + end + + def set_feature(key, value) + request = Runtime::API::Request.new(api_client, "/features/#{key}") + response = post(request.url, { value: value }) + unless response.code == QA::Support::Api::HTTP_STATUS_CREATED + raise SetFeatureError, "Setting feature flag #{key} to #{value} failed with `#{response}`." + end + end + end + end +end diff --git a/qa/qa/scenario/bootable.rb b/qa/qa/scenario/bootable.rb index dd12ea6d492..038418be023 100644 --- a/qa/qa/scenario/bootable.rb +++ b/qa/qa/scenario/bootable.rb @@ -23,7 +23,7 @@ module QA arguments.parse!(argv) - self.perform(Runtime::Scenario.attributes, *arguments.default_argv) + self.perform(Runtime::Scenario.attributes, *argv) end private @@ -33,7 +33,13 @@ module QA end def options - @options ||= [] + # Scenario options/attributes are global. There's only ever one + # scenario at a time, but they can be inherited and we want scenarios + # to share the attributes of their ancestors. For example, `Mattermost` + # inherits from `Test::Instance::All` but if this were an instance + # variable then `Mattermost` wouldn't have access to the attributes + # in `All` + @@options ||= [] # rubocop:disable Style/ClassVars end def has_attributes? diff --git a/qa/qa/scenario/shared_attributes.rb b/qa/qa/scenario/shared_attributes.rb new file mode 100644 index 00000000000..40d5c6b1ff1 --- /dev/null +++ b/qa/qa/scenario/shared_attributes.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module QA + module Scenario + module SharedAttributes + include Bootable + + attribute :gitlab_address, '--address URL', 'Address of the instance to test' + attribute :enable_feature, '--enable-feature FEATURE_FLAG', 'Enable a feature before running tests' + end + end +end diff --git a/qa/qa/scenario/template.rb b/qa/qa/scenario/template.rb index cb1a1de6b9a..b8ea26e805e 100644 --- a/qa/qa/scenario/template.rb +++ b/qa/qa/scenario/template.rb @@ -18,19 +18,44 @@ module QA end end - def perform(address, *rspec_options) - Runtime::Scenario.define(:gitlab_address, address) + def perform(options, *args) + extract_address(:gitlab_address, options, args) ## # Perform before hooks, which are different for CE and EE # Runtime::Release.perform_before_hooks + Runtime::Feature.enable(options[:enable_feature]) if options.key?(:enable_feature) + Specs::Runner.perform do |specs| specs.tty = true specs.tags = self.class.focus - specs.options = rspec_options if rspec_options.any? + specs.options = args if args.any? end + ensure + Runtime::Feature.disable(options[:enable_feature]) if options.key?(:enable_feature) + end + + def extract_option(name, options, args) + option = if options.key?(name) + options[name] + else + args.shift + end + + Runtime::Scenario.define(name, option) + + option + end + + # For backwards-compatibility, if the gitlab instance address is not + # specified as an option parsed by OptionParser, it can be specified as + # the first argument + def extract_address(name, options, args) + address = extract_option(name, options, args) + + raise ::ArgumentError, "The address provided for `#{name}` is not valid: #{address}" unless Runtime::Address.valid?(address) end end end diff --git a/qa/qa/scenario/test/instance/all.rb b/qa/qa/scenario/test/instance/all.rb index a07c26431bd..168ac4c09a1 100644 --- a/qa/qa/scenario/test/instance/all.rb +++ b/qa/qa/scenario/test/instance/all.rb @@ -8,6 +8,7 @@ module QA module Instance class All < Template include Bootable + include SharedAttributes end end end diff --git a/qa/qa/scenario/test/instance/smoke.rb b/qa/qa/scenario/test/instance/smoke.rb index a7d2cb27f27..43f0623483e 100644 --- a/qa/qa/scenario/test/instance/smoke.rb +++ b/qa/qa/scenario/test/instance/smoke.rb @@ -8,6 +8,7 @@ module QA # class Smoke < Template include Bootable + include SharedAttributes tags :smoke end diff --git a/qa/qa/scenario/test/integration/mattermost.rb b/qa/qa/scenario/test/integration/mattermost.rb index ece6fba75c9..f5072ee227c 100644 --- a/qa/qa/scenario/test/integration/mattermost.rb +++ b/qa/qa/scenario/test/integration/mattermost.rb @@ -9,10 +9,13 @@ module QA class Mattermost < Test::Instance::All tags :mattermost - def perform(address, mattermost, *rspec_options) - Runtime::Scenario.define(:mattermost_address, mattermost) + attribute :mattermost_address, '--mattermost-address URL', 'Address of the Mattermost server' - super(address, *rspec_options) + def perform(options, *args) + extract_address(:gitlab_address, options, args) + extract_address(:mattermost_address, options, args) + + super(options, *args) end end end diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb index d8609aa037a..d1747280227 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb @@ -14,7 +14,7 @@ module QA end project.visit! - Page::Project::Menu.perform(&:click_members_settings) + Page::Project::Menu.perform(&:go_to_members_settings) Page::Project::Settings::Members.perform do |page| page.add_member(user.username) end diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb index 3fbcd77dac6..7a36f9ea420 100644 --- a/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb @@ -12,7 +12,7 @@ module QA end project.visit! - Page::Project::Menu.perform(&:go_to_settings) + Page::Project::Menu.perform(&:click_settings) Page::Project::Settings::MergeRequest.perform(&:enable_ff_only) merge_request = Resource::MergeRequest.fabricate! do |merge_request| diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb index 8e181eb28c6..daeee665c93 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb @@ -57,7 +57,7 @@ module QA end it 'branches are correctly listed after CRUD operations' do - Page::Project::Menu.perform(&:click_repository_branches) + Page::Project::Menu.perform(&:go_to_repository_branches) expect(page).to have_content(master_branch) expect(page).to have_content(second_branch) diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb index ff879fdeb16..f41240b7605 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb @@ -16,7 +16,7 @@ module QA expect(page).to have_content("Title: #{key_title}") expect(page).to have_content(key.fingerprint) - Page::Main::Menu.act { go_to_profile_settings } + Page::Main::Menu.act { click_settings_link } Page::Profile::Menu.act { click_ssh_keys } Page::Profile::SSHKeys.perform do |ssh_keys| diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb index eb59b54e9ab..dc42191b448 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb @@ -31,7 +31,7 @@ module QA # Remove the SSH key login - Page::Main::Menu.perform(&:go_to_profile_settings) + Page::Main::Menu.perform(&:click_settings_link) Page::Profile::Menu.perform(&:click_ssh_keys) Page::Profile::SSHKeys.perform do |ssh_keys| ssh_keys.remove_key(key_title) diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb index 99601e3d230..23008a58af8 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb @@ -21,7 +21,7 @@ module QA end source_project_push.project.visit! - Page::Project::Menu.perform(&:click_repository_settings) + Page::Project::Menu.perform(&:go_to_repository_settings) Page::Project::Settings::Repository.perform do |settings| settings.expand_mirroring_repositories do |mirror_settings| # Configure the source project to push to the target project diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb index 243f0b83b77..a544efb35ee 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb @@ -40,12 +40,12 @@ module QA set_file_size_limit(1) expect(page).to have_content("Application settings saved successfully") - push = push_new_file('oversize_file_2.bin', wait_for_push: false) - expect(push.output).to have_content 'remote: fatal: pack exceeds maximum allowed size' + expect { push_new_file('oversize_file_2.bin', wait_for_push: false) } + .to raise_error(QA::Git::Repository::RepositoryCommandError, /remote: fatal: pack exceeds maximum allowed size/) end def set_file_size_limit(limit) - Page::Main::Menu.perform(&:go_to_admin_area) + Page::Main::Menu.perform(&:click_admin_area) Page::Admin::Menu.perform(&:go_to_general_settings) Page::Admin::Settings::General.perform do |setting| @@ -65,6 +65,7 @@ module QA p.file_content = SecureRandom.random_bytes(2000000) p.commit_message = 'Adding a new file' p.wait_for_push = wait_for_push + p.new_branch = false end end end diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb index 7223831d96f..a0251e1c385 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb @@ -28,7 +28,7 @@ module QA expect(page).to have_content('README.md') expect(page).to have_content('Test Use SSH Key') - Page::Main::Menu.perform(&:go_to_profile_settings) + Page::Main::Menu.perform(&:click_settings_link) Page::Profile::Menu.perform(&:click_ssh_keys) Page::Profile::SSHKeys.perform do |ssh_keys| diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb index b862a7bd1ed..b7400cdca97 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb @@ -33,7 +33,7 @@ module QA def view_commit @project.visit! Page::Project::Show.perform do |page| - page.go_to_commit(@commit_message) + page.click_commit(@commit_message) end end diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb index ab53dff464e..b643468a664 100644 --- a/qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb @@ -7,7 +7,7 @@ module QA Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.perform(&:sign_in_using_credentials) - Page::Main::Menu.perform(&:go_to_snippets) + Page::Main::Menu.perform(&:click_snippets_link) Resource::Snippet.fabricate_via_browser_ui! do |snippet| snippet.title = 'Snippet title' diff --git a/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb b/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb index 29589ec870a..309ae6cd986 100644 --- a/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb @@ -20,7 +20,7 @@ module QA validate_content('My First Wiki Content') - Page::Project::Wiki::Edit.perform(&:go_to_edit_page) + Page::Project::Wiki::Edit.perform(&:click_edit) Page::Project::Wiki::New.perform do |page| page.set_content("My Second Wiki Content") page.save_changes diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb index 2238d6c382e..7c1d4489c47 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb @@ -70,7 +70,7 @@ module QA puts 'Waiting for the runner to process the pipeline' sleep 15 # Runner should process all jobs within 15 seconds. - Page::Project::Pipeline::Index.perform(&:go_to_latest_pipeline) + Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline) Page::Project::Pipeline::Show.perform do |pipeline| expect(pipeline).to be_running diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb index 3f65eabc756..609155da855 100644 --- a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb +++ b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb @@ -91,8 +91,8 @@ module QA sha1sum = Digest::SHA1.hexdigest(gitlab_ci) Page::Project::Menu.perform(&:click_ci_cd_pipelines) - Page::Project::Pipeline::Index.perform(&:go_to_latest_pipeline) - Page::Project::Pipeline::Show.perform(&:go_to_first_job) + Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline) + Page::Project::Pipeline::Show.perform(&:click_on_first_job) Page::Project::Job::Show.perform do |job| expect(job).to be_successful diff --git a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb index 0b92ea29ca4..bb1f775da75 100644 --- a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb +++ b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb @@ -63,10 +63,10 @@ module QA end Page::Project::Menu.perform(&:click_ci_cd_pipelines) - Page::Project::Pipeline::Index.perform(&:go_to_latest_pipeline) + Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline) Page::Project::Pipeline::Show.perform do |pipeline| - pipeline.go_to_job('build') + pipeline.click_job('build') end Page::Project::Job::Show.perform do |job| expect(job).to be_successful(timeout: 600) @@ -75,7 +75,7 @@ module QA end Page::Project::Pipeline::Show.perform do |pipeline| - pipeline.go_to_job('test') + pipeline.click_job('test') end Page::Project::Job::Show.perform do |job| expect(job).to be_successful(timeout: 600) @@ -84,7 +84,7 @@ module QA end Page::Project::Pipeline::Show.perform do |pipeline| - pipeline.go_to_job('production') + pipeline.click_job('production') end Page::Project::Job::Show.perform do |job| expect(job).to be_successful(timeout: 1200) @@ -92,9 +92,9 @@ module QA job.click_element(:pipeline_path) end - Page::Project::Menu.perform(&:click_operations_environments) + Page::Project::Menu.perform(&:go_to_operations_environments) Page::Project::Operations::Environments::Index.perform do |index| - index.go_to_environment('production') + index.click_environment_link('production') end Page::Project::Operations::Environments::Show.perform do |show| show.view_deployment do @@ -106,8 +106,7 @@ module QA end end - # Failure issue: https://gitlab.com/gitlab-org/quality/nightly/issues/87 - describe 'Auto DevOps', :smoke, :quarantine do + describe 'Auto DevOps', :smoke do it 'enables AutoDevOps by default' do login @@ -126,7 +125,7 @@ module QA end Page::Project::Menu.perform(&:click_ci_cd_pipelines) - Page::Project::Pipeline::Index.perform(&:go_to_latest_pipeline) + Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline) Page::Project::Pipeline::Show.perform do |pipeline| expect(pipeline).to have_tag('Auto DevOps') diff --git a/qa/qa/specs/features/browser_ui/7_configure/mattermost/create_group_with_mattermost_team_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/mattermost/create_group_with_mattermost_team_spec.rb index 7096864e011..8383dcdb983 100644 --- a/qa/qa/specs/features/browser_ui/7_configure/mattermost/create_group_with_mattermost_team_spec.rb +++ b/qa/qa/specs/features/browser_ui/7_configure/mattermost/create_group_with_mattermost_team_spec.rb @@ -9,7 +9,7 @@ module QA Page::Main::Menu.act { go_to_groups } Page::Dashboard::Groups.perform do |page| - page.go_to_new_group + page.click_new_group expect(page).to have_content( /Create a Mattermost team for this group/ diff --git a/qa/qa/support/api.rb b/qa/qa/support/api.rb index 229bfb44fa5..31cff5a241c 100644 --- a/qa/qa/support/api.rb +++ b/qa/qa/support/api.rb @@ -1,6 +1,9 @@ module QA module Support module Api + HTTP_STATUS_OK = 200 + HTTP_STATUS_CREATED = 201 + def post(url, payload) RestClient::Request.execute( method: :post, diff --git a/qa/qa/tools/delete_subgroups.rb b/qa/qa/tools/delete_subgroups.rb index c5c48e77ade..3f752adbe6f 100644 --- a/qa/qa/tools/delete_subgroups.rb +++ b/qa/qa/tools/delete_subgroups.rb @@ -3,7 +3,7 @@ require_relative '../../qa' # This script deletes all subgroups of a group specified by ENV['GROUP_NAME_OR_PATH'] -# Required environment variables: PERSONAL_ACCESS_TOKEN and GITLAB_ADDRESS +# Required environment variables: GITLAB_QA_ACCESS_TOKEN and GITLAB_ADDRESS # Optional environment variable: GROUP_NAME_OR_PATH (defaults to 'gitlab-qa-sandbox-group') # Run `rake delete_subgroups` @@ -14,9 +14,9 @@ module QA def initialize raise ArgumentError, "Please provide GITLAB_ADDRESS" unless ENV['GITLAB_ADDRESS'] - raise ArgumentError, "Please provide PERSONAL_ACCESS_TOKEN" unless ENV['PERSONAL_ACCESS_TOKEN'] + raise ArgumentError, "Please provide GITLAB_QA_ACCESS_TOKEN" unless ENV['GITLAB_QA_ACCESS_TOKEN'] - @api_client = Runtime::API::Client.new(ENV['GITLAB_ADDRESS'], personal_access_token: ENV['PERSONAL_ACCESS_TOKEN']) + @api_client = Runtime::API::Client.new(ENV['GITLAB_ADDRESS'], personal_access_token: ENV['GITLAB_QA_ACCESS_TOKEN']) end def run diff --git a/qa/qa/tools/generate_perf_testdata.rb b/qa/qa/tools/generate_perf_testdata.rb index 0a0dbdf5b15..49a1af8e9f0 100644 --- a/qa/qa/tools/generate_perf_testdata.rb +++ b/qa/qa/tools/generate_perf_testdata.rb @@ -5,7 +5,7 @@ require 'faker' require 'yaml' require_relative '../../qa' # This script generates testdata for Performance Testing. -# Required environment variables: PERSONAL_ACCESS_TOKEN and GITLAB_ADDRESS +# Required environment variables: GITLAB_QA_ACCESS_TOKEN and GITLAB_ADDRESS # This job creates a urls.txt which contains a hash of all the URLs needed for Performance Testing # Run `rake generate_perf_testdata` @@ -16,9 +16,9 @@ module QA def initialize raise ArgumentError, "Please provide GITLAB_ADDRESS" unless ENV['GITLAB_ADDRESS'] - raise ArgumentError, "Please provide PERSONAL_ACCESS_TOKEN" unless ENV['PERSONAL_ACCESS_TOKEN'] + raise ArgumentError, "Please provide GITLAB_QA_ACCESS_TOKEN" unless ENV['GITLAB_QA_ACCESS_TOKEN'] - @api_client = Runtime::API::Client.new(ENV['GITLAB_ADDRESS'], personal_access_token: ENV['PERSONAL_ACCESS_TOKEN']) + @api_client = Runtime::API::Client.new(ENV['GITLAB_ADDRESS'], personal_access_token: ENV['GITLAB_QA_ACCESS_TOKEN']) @group_name = "gitlab-qa-perf-sandbox-#{SecureRandom.hex(8)}" @project_name = "my-test-project-#{SecureRandom.hex(8)}" @visibility = "public" @@ -46,7 +46,7 @@ module QA threads_arr.each(&:join) STDOUT.puts "\nURLs: #{@urls}" - File.open("urls.yml", "w") { |file| file.puts @urls.to_yaml } + File.open("urls.yml", "w") { |file| file.puts @urls.stringify_keys.to_yaml } STDOUT.puts "\nDone" end diff --git a/qa/qa/tools/revoke_all_personal_access_tokens.rb b/qa/qa/tools/revoke_all_personal_access_tokens.rb index 7484b633bf6..8a74f52324d 100644 --- a/qa/qa/tools/revoke_all_personal_access_tokens.rb +++ b/qa/qa/tools/revoke_all_personal_access_tokens.rb @@ -27,7 +27,7 @@ module QA Runtime::Browser.visit(ENV['GITLAB_ADDRESS'], Page::Main::Login) Page::Main::Login.perform(&:sign_in_using_credentials) - Page::Main::Menu.perform(&:go_to_profile_settings) + Page::Main::Menu.perform(&:click_settings_link) Page::Profile::Menu.perform(&:click_access_tokens) token_name = 'api-test-token' diff --git a/qa/spec/runtime/env_spec.rb b/qa/spec/runtime/env_spec.rb index fc51f45c3a1..04085efe2ce 100644 --- a/qa/spec/runtime/env_spec.rb +++ b/qa/spec/runtime/env_spec.rb @@ -90,13 +90,13 @@ describe QA::Runtime::Env do described_class.instance_variable_set(:@personal_access_token, nil) end - context 'when PERSONAL_ACCESS_TOKEN is set' do + context 'when GITLAB_QA_ACCESS_TOKEN is set' do before do - stub_env('PERSONAL_ACCESS_TOKEN', 'a_token') + stub_env('GITLAB_QA_ACCESS_TOKEN', 'a_token_too') end it 'returns specified token from env' do - expect(described_class.personal_access_token).to eq 'a_token' + expect(described_class.personal_access_token).to eq 'a_token_too' end end diff --git a/qa/spec/runtime/feature_spec.rb b/qa/spec/runtime/feature_spec.rb new file mode 100644 index 00000000000..192299b7857 --- /dev/null +++ b/qa/spec/runtime/feature_spec.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +describe QA::Runtime::Feature do + let(:api_client) { double('QA::Runtime::API::Client') } + let(:request) { Struct.new(:url).new('http://api') } + let(:response) { Struct.new(:code).new(201) } + + before do + allow(described_class).to receive(:api_client).and_return(api_client) + end + + describe '.enable' do + it 'enables a feature flag' do + expect(QA::Runtime::API::Request) + .to receive(:new) + .with(api_client, "/features/a-flag") + .and_return(request) + expect(described_class) + .to receive(:post) + .with(request.url, { value: true }) + .and_return(response) + + subject.enable('a-flag') + end + end + + describe '.disable' do + it 'disables a feature flag' do + expect(QA::Runtime::API::Request) + .to receive(:new) + .with(api_client, "/features/a-flag") + .and_return(request) + expect(described_class) + .to receive(:post) + .with(request.url, { value: false }) + .and_return(response) + + subject.disable('a-flag') + end + end +end diff --git a/qa/spec/runtime/scenario_spec.rb b/qa/spec/runtime/scenario_spec.rb index 7009192bcc0..70fc71ffc02 100644 --- a/qa/spec/runtime/scenario_spec.rb +++ b/qa/spec/runtime/scenario_spec.rb @@ -13,6 +13,14 @@ describe QA::Runtime::Scenario do .to eq(my_attribute: 'some-value', another_attribute: 'another-value') end + it 'replaces an existing attribute' do + subject.define(:my_attribute, 'some-value') + subject.define(:my_attribute, 'another-value') + + expect(subject.my_attribute).to eq 'another-value' + expect(subject.attributes).to eq(my_attribute: 'another-value') + end + it 'raises error when attribute is not known' do expect { subject.invalid_accessor } .to raise_error ArgumentError, /invalid_accessor/ diff --git a/qa/spec/scenario/bootable_spec.rb b/qa/spec/scenario/bootable_spec.rb index 273aac7677e..bd89b21f7fb 100644 --- a/qa/spec/scenario/bootable_spec.rb +++ b/qa/spec/scenario/bootable_spec.rb @@ -4,14 +4,21 @@ describe QA::Scenario::Bootable do .include(described_class) end + before do + allow(subject).to receive(:options).and_return([]) + allow(QA::Runtime::Scenario).to receive(:attributes).and_return({}) + end + it 'makes it possible to define the scenario attribute' do subject.class_eval do attribute :something, '--something SOMETHING', 'Some attribute' attribute :another, '--another ANOTHER', 'Some other attribute' end + # If we run just this test from the command line it fails unless + # we include the command line args that we use to select this test. expect(subject).to receive(:perform) - .with(something: 'test', another: 'other') + .with({ something: 'test', another: 'other' }) subject.launch!(%w[--another other --something test]) end diff --git a/qa/spec/scenario/template_spec.rb b/qa/spec/scenario/template_spec.rb new file mode 100644 index 00000000000..f97fc22daf9 --- /dev/null +++ b/qa/spec/scenario/template_spec.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +describe QA::Scenario::Template do + let(:feature) { spy('Runtime::Feature') } + let(:release) { spy('Runtime::Release') } + + before do + stub_const('QA::Runtime::Release', release) + stub_const('QA::Runtime::Feature', feature) + allow(QA::Specs::Runner).to receive(:perform) + allow(QA::Runtime::Address).to receive(:valid?).and_return(true) + end + + it 'allows a feature to be enabled' do + subject.perform({ enable_feature: 'a-feature' }) + + expect(feature).to have_received(:enable).with('a-feature') + end + + it 'ensures an enabled feature is disabled afterwards' do + allow(QA::Specs::Runner).to receive(:perform).and_raise('failed test') + + expect { subject.perform({ enable_feature: 'a-feature' }) }.to raise_error('failed test') + + expect(feature).to have_received(:enable).with('a-feature') + expect(feature).to have_received(:disable).with('a-feature') + end +end diff --git a/qa/spec/scenario/test/integration/github_spec.rb b/qa/spec/scenario/test/integration/github_spec.rb index c2aeb1ded1d..6112ba7c694 100644 --- a/qa/spec/scenario/test/integration/github_spec.rb +++ b/qa/spec/scenario/test/integration/github_spec.rb @@ -12,7 +12,7 @@ describe QA::Scenario::Test::Integration::Github do let(:tags) { [:github] } it 'requires a GitHub access token' do - subject.perform('gitlab_address') + subject.perform(args) expect(env).to have_received(:require_github_access_token!) end diff --git a/qa/spec/scenario/test/integration/mattermost_spec.rb b/qa/spec/scenario/test/integration/mattermost_spec.rb index 59caf2ba2cd..4e75e72f4d2 100644 --- a/qa/spec/scenario/test/integration/mattermost_spec.rb +++ b/qa/spec/scenario/test/integration/mattermost_spec.rb @@ -4,14 +4,21 @@ describe QA::Scenario::Test::Integration::Mattermost do context '#perform' do it_behaves_like 'a QA scenario class' do let(:args) { %w[gitlab_address mattermost_address] } + let(:args) do + { + gitlab_address: 'http://gitlab_address', + mattermost_address: 'http://mattermost_address' + } + end + let(:named_options) { %w[--address http://gitlab_address --mattermost-address http://mattermost_address] } let(:tags) { [:mattermost] } let(:options) { ['path1']} it 'requires a GitHub access token' do - subject.perform(*args) + subject.perform(args) expect(attributes).to have_received(:define) - .with(:mattermost_address, 'mattermost_address') + .with(:mattermost_address, 'http://mattermost_address') end end end diff --git a/qa/spec/shared_examples/scenario_shared_examples.rb b/qa/spec/shared_examples/scenario_shared_examples.rb index 5fd55d7d96b..697e6cb39c8 100644 --- a/qa/spec/shared_examples/scenario_shared_examples.rb +++ b/qa/spec/shared_examples/scenario_shared_examples.rb @@ -2,19 +2,23 @@ shared_examples 'a QA scenario class' do let(:attributes) { spy('Runtime::Scenario') } - let(:release) { spy('Runtime::Release') } let(:runner) { spy('Specs::Runner') } + let(:release) { spy('Runtime::Release') } + let(:feature) { spy('Runtime::Feature') } - let(:args) { ['gitlab_address'] } + let(:args) { { gitlab_address: 'http://gitlab_address' } } + let(:named_options) { %w[--address http://gitlab_address] } let(:tags) { [] } let(:options) { %w[path1 path2] } before do + stub_const('QA::Specs::Runner', runner) stub_const('QA::Runtime::Release', release) stub_const('QA::Runtime::Scenario', attributes) - stub_const('QA::Specs::Runner', runner) + stub_const('QA::Runtime::Feature', feature) allow(runner).to receive(:perform).and_yield(runner) + allow(QA::Runtime::Address).to receive(:valid?).and_return(true) end it 'responds to perform' do @@ -22,28 +26,48 @@ shared_examples 'a QA scenario class' do end it 'sets an address of the subject' do - subject.perform(*args) + subject.perform(args) - expect(attributes).to have_received(:define).with(:gitlab_address, 'gitlab_address') + expect(attributes).to have_received(:define).with(:gitlab_address, 'http://gitlab_address').at_least(:once) end it 'performs before hooks' do - subject.perform(*args) + subject.perform(args) expect(release).to have_received(:perform_before_hooks) end it 'sets tags on runner' do - subject.perform(*args) + subject.perform(args) expect(runner).to have_received(:tags=).with(tags) end context 'specifying RSpec options' do it 'sets options on runner' do - subject.perform(*args, *options) + subject.perform(args, *options) expect(runner).to have_received(:options=).with(options) end end + + context 'with named command-line options' do + it 'converts options to attributes' do + described_class.launch!(named_options) + + args do |k, v| + expect(attributes).to have_received(:define).with(k, v) + end + end + + it 'raises an error if the option is invalid' do + expect { described_class.launch!(['--foo']) }.to raise_error(OptionParser::InvalidOption) + end + + it 'passes on options after --' do + expect(described_class).to receive(:perform).with(attributes, *%w[--tag quarantine]) + + described_class.launch!(named_options.push(*%w[-- --tag quarantine])) + end + end end diff --git a/rubocop/cop/migration/update_column_in_batches.rb b/rubocop/cop/migration/update_column_in_batches.rb index 5461abc5ee0..b1c43393f6a 100644 --- a/rubocop/cop/migration/update_column_in_batches.rb +++ b/rubocop/cop/migration/update_column_in_batches.rb @@ -28,7 +28,7 @@ module RuboCop source_name = node.location.expression.source_buffer.name path = Pathname.new(source_name).relative_path_from(rails_root) dirname = File.dirname(path) - .sub(%r{\Adb/(migrate|post_migrate)}, 'spec/migrations') + .sub(%r{db/(migrate|post_migrate)}, 'spec/migrations') filename = File.basename(source_name, '.rb').sub(/\A\d+_/, '') File.join(dirname, "#{filename}_spec.rb") diff --git a/scripts/frontend/stylelint/stylelint-duplicate-selectors.js b/scripts/frontend/stylelint/stylelint-duplicate-selectors.js new file mode 100644 index 00000000000..4b46ee21a7a --- /dev/null +++ b/scripts/frontend/stylelint/stylelint-duplicate-selectors.js @@ -0,0 +1,23 @@ +const stylelint = require('stylelint'); +const utils = require('./stylelint-utils'); +const ruleName = 'stylelint-gitlab/duplicate-selectors'; + +const messages = stylelint.utils.ruleMessages(ruleName, { + expected: (selector1, selector2) => { + return `"${selector1}" and "${selector2}" have the same properties.`; + }, +}); + +module.exports = stylelint.createPlugin(ruleName, function(enabled) { + if (!enabled) { + return; + } + + return function(root, result) { + const selectorGroups = {}; + utils.createPropertiesHashmap(root, result, ruleName, messages, selectorGroups, true); + }; +}); + +module.exports.ruleName = ruleName; +module.exports.messages = messages; diff --git a/scripts/frontend/stylelint/stylelint-utility-classes.js b/scripts/frontend/stylelint/stylelint-utility-classes.js new file mode 100644 index 00000000000..8a1cfdbf302 --- /dev/null +++ b/scripts/frontend/stylelint/stylelint-utility-classes.js @@ -0,0 +1,24 @@ +const stylelint = require('stylelint'); +const utils = require('./stylelint-utils'); +const utilityClasses = require('./utility-classes-map.js'); + +const ruleName = 'stylelint-gitlab/utility-classes'; + +const messages = stylelint.utils.ruleMessages(ruleName, { + expected: (selector1, selector2) => { + return `"${selector1}" has the same properties as our BS4 utility class "${selector2}" so please use that instead.`; + }, +}); + +module.exports = stylelint.createPlugin(ruleName, function(enabled) { + if (!enabled) { + return; + } + + return function(root, result) { + utils.createPropertiesHashmap(root, result, ruleName, messages, utilityClasses, false); + }; +}); + +module.exports.ruleName = ruleName; +module.exports.messages = messages; diff --git a/scripts/frontend/stylelint/stylelint-utility-map.js b/scripts/frontend/stylelint/stylelint-utility-map.js new file mode 100644 index 00000000000..7e012b157b3 --- /dev/null +++ b/scripts/frontend/stylelint/stylelint-utility-map.js @@ -0,0 +1,54 @@ +const sass = require('node-sass'); +const postcss = require('postcss'); +const fs = require('fs'); +const path = require('path'); +const prettier = require('prettier'); + +const utils = require('./stylelint-utils'); +const ROOT_PATH = path.resolve(__dirname, '../../..'); +const hashMapPath = path.resolve(__dirname, './utility-classes-map.js'); + +// +// This creates a JS based hash map (saved in utility-classes-map.js) of the different values in the utility classes +// +sass.render( + { + data: ` + @import './functions'; + @import './variables'; + @import './mixins'; + @import './utilities'; + `, + includePaths: [path.resolve(ROOT_PATH, 'node_modules/bootstrap/scss')], + }, + (err, result) => { + if (err) console.error('Error ', err); + + const cssResult = result.css.toString(); + + // We just use postcss to create a CSS tree + postcss([]) + .process(cssResult, { + // This supresses a postcss warning + from: undefined, + }) + .then(result => { + const selectorGroups = {}; + utils.createPropertiesHashmap(result.root, result, null, null, selectorGroups, true); + + const prettierOptions = prettier.resolveConfig.sync(hashMapPath); + const prettyHashmap = prettier.format( + `module.exports = ${JSON.stringify(selectorGroups)};`, + prettierOptions, + ); + + fs.writeFile(hashMapPath, prettyHashmap, function(err) { + if (err) { + return console.log(err); + } + + console.log('The file was saved!'); + }); + }); + }, +); diff --git a/scripts/frontend/stylelint/stylelint-utils.js b/scripts/frontend/stylelint/stylelint-utils.js new file mode 100644 index 00000000000..2d931c1c4c2 --- /dev/null +++ b/scripts/frontend/stylelint/stylelint-utils.js @@ -0,0 +1,77 @@ +const stylelint = require('stylelint'); +const md5 = require('md5'); + +module.exports.createPropertiesHashmap = ( + ruleRoot, + result, + ruleName, + messages, + selectorGroups, + addSelectors, +) => { + ruleRoot.walkRules(rule => { + const selector = rule.selector.replace(/(?:\r\n|\r|\n)/g, ' '); + + if ( + rule && + rule.parent && + rule.parent.type != 'atrule' && + !( + selector.includes('-webkit-') || + selector.includes('-moz-') || + selector.includes('-o-') || + selector.includes('-ms-') || + selector.includes(':') + ) + ) { + let cssArray = []; + rule.nodes.forEach(function(property) { + const { prop, value } = property; + if (property && value) { + const propval = `${prop}${value}${property.important ? '!important' : ''}`; + cssArray.push(propval); + } + }); + + cssArray = cssArray.sort(); + const cssContent = cssArray.toString(); + + if (cssContent) { + const hashValue = md5(cssContent); + const selObj = selectorGroups[hashValue]; + + const selectorLine = `${selector} (${ + rule.source.input.file ? rule.source.input.file + ' -' : '' + }${rule.source.start.line}:${rule.source.start.column})`; + + if (selObj) { + if (selectorGroups[hashValue].selectors.indexOf(selector) == -1) { + let lastSelector = + selectorGroups[hashValue].selectors[selectorGroups[hashValue].selectors.length - 1]; + + // So we have nicer formatting if it is the same file, we remove the filename + lastSelector = lastSelector.replace(`${rule.source.input.file} - `, ''); + + if (messages) { + stylelint.utils.report({ + result, + ruleName, + message: messages.expected(selector, lastSelector), + node: rule, + word: rule.node, + }); + } + + if (addSelectors) { + selectorGroups[hashValue].selectors.push(selectorLine); + } + } + } else if (addSelectors) { + selectorGroups[hashValue] = { + selectors: [selectorLine], + }; + } + } + } + }); +}; diff --git a/scripts/frontend/stylelint/utility-classes-map.js b/scripts/frontend/stylelint/utility-classes-map.js new file mode 100644 index 00000000000..1929f950f6c --- /dev/null +++ b/scripts/frontend/stylelint/utility-classes-map.js @@ -0,0 +1,216 @@ +module.exports = { + '99097f29a9473b56eacdb9ff0681c366': { selectors: ['.align-baseline (1:1)'] }, + d969b318bb994e104e8c965006d71cb7: { selectors: ['.align-top (4:1)'] }, + '8cd54ab97b9cc43cb9d13d2ea7c601c7': { selectors: ['.align-middle (7:1)'] }, + dd06eb6c49e979b7a9fdaa7119aa0a0b: { selectors: ['.align-bottom (10:1)'] }, + '0af1e90cbc468615e299ec9f49e97c4a': { selectors: ['.align-text-bottom (13:1)'] }, + '50af706df238cf59bdc634fc684ba0c9': { selectors: ['.align-text-top (16:1)'] }, + c968922e6e47445362129a684b5913c0: { selectors: ['.bg-primary (19:1)'] }, + '3c397f9786c24cff4779a11cf5b3d7e7': { selectors: ['.bg-secondary (27:1)'] }, + '659677469a4477267fabc1788f7cad4e': { selectors: ['.bg-success (35:1)'] }, + '56d246d5b6a708a4c6f78dbd2444106c': { selectors: ['.bg-info (43:1)'] }, + '6bec0a33df3a6380c30103db5c273455': { selectors: ['.bg-warning (51:1)'] }, + '0ce5d074c8667ce6c32360658f428d5d': { selectors: ['.bg-danger (59:1)'] }, + '0d0269c62a01e97caa9039d227a25d12': { selectors: ['.bg-light (67:1)'] }, + '3a56309ad8c5b46ebcc3b13fe1987ac1': { selectors: ['.bg-dark (75:1)'] }, + '0e252f8dd392a33343d3d5efc1e3194a': { selectors: ['.bg-white (83:1)'] }, + '3af6f52f0ed4f98e797d5f10a35ca6bc': { selectors: ['.bg-transparent (86:1)'] }, + '16da7fdce74577ceab356509db565612': { selectors: ['.border (89:1)'] }, + '929622517ca05efde3b51e5f1a57064e': { selectors: ['.border-top (92:1)'] }, + '7283090353df54f1d515a6ceddfb9693': { selectors: ['.border-right (95:1)'] }, + bd5670d71332c652b46db82949042e31: { selectors: ['.border-bottom (98:1)'] }, + fa71e003d04734a898a85cc5285e3cbb: { selectors: ['.border-left (101:1)'] }, + ed482cea071e316f29d78fd93c3f3644: { selectors: ['.border-0 (104:1)'] }, + '90cb661baf21e10be6e479cb0544b1a7': { selectors: ['.border-top-0 (107:1)'] }, + '8a32707eaa09fc998bf8cc915710b60c': { selectors: ['.border-right-0 (110:1)'] }, + a6f01957e142a000e7742b31ac6c2331: { selectors: ['.border-bottom-0 (113:1)'] }, + c740fe952cc1985ee14f7d1c7a359a29: { selectors: ['.border-left-0 (116:1)'] }, + af9dd93e9780306ffa4bb25a6384902f: { selectors: ['.border-primary (119:1)'] }, + afa290dfe58cca06be5924ceae1b019b: { selectors: ['.border-secondary (122:1)'] }, + '9b1ac460bdddf1e0164d7bf988cc2da8': { selectors: ['.border-success (125:1)'] }, + '091cbf41d6be12061382fa571ee1ce82': { selectors: ['.border-info (128:1)'] }, + '3ada321d4a387901dad6d80e1b6be3fd': { selectors: ['.border-warning (131:1)'] }, + '13b4713dd52c1e359d1b43dd658cb249': { selectors: ['.border-danger (134:1)'] }, + '0048e110875ea22b04104d55e764a367': { selectors: ['.border-light (137:1)'] }, + a900b6b567c9a911326cdd0e19f40f8e: { selectors: ['.border-dark (140:1)'] }, + '78bcd867ac9677c743c2bc33b872f27b': { selectors: ['.border-white (143:1)'] }, + e0fc10c49c7b7f4d1924336d21a4f64e: { selectors: ['.rounded (146:1)'] }, + '1b74b9d0a7d6a59281b5b5cae43c859a': { selectors: ['.rounded-top (149:1)'] }, + '20b75f55f39e662e038d51a6442c03df': { selectors: ['.rounded-right (153:1)'] }, + '83ea6db794873239c21f44af25618677': { selectors: ['.rounded-bottom (157:1)'] }, + '8464e9e8001e65dfc06397436a5eebd7': { selectors: ['.rounded-left (161:1)'] }, + '59c2f788287fa43caf5891adfc5c796e': { selectors: ['.rounded-circle (165:1)'] }, + '31a632ba94f8c41558bd6044458f1459': { selectors: ['.rounded-0 (168:1)'] }, + '16aaf53ab29d6b248b0257f2fa413914': { selectors: ['.d-none (176:1)'] }, + '4f42736ac9217039ed791b4306e60aeb': { selectors: ['.d-inline (179:1)'] }, + '067efa04b76649e8afcdceb9f5f7e870': { selectors: ['.d-inline-block (182:1)'] }, + de54f49149fb9b512aa79ad9ada838f2: { selectors: ['.d-block (185:1)'] }, + '80fc32acbc0c28ee890a160c23529d26': { selectors: ['.d-table (188:1)'] }, + '6a87b1db48298ca94cbe5dee79a6eed1': { selectors: ['.d-table-row (191:1)'] }, + b9896f0d94760bf5920f47904e9f7512: { selectors: ['.d-table-cell (194:1)'] }, + d25c51f38c4d057209b96c664de68c44: { selectors: ['.d-flex (197:1)'] }, + e72d46b636d5b8e17e771daa95793f33: { selectors: ['.d-inline-flex (200:1)'] }, + '2c433b7c14a5ae32cfa8ec7867ee8526': { selectors: ['.embed-responsive (303:1)'] }, + '56b318b8d8eb845b769d60cefcd131bb': { + selectors: [ + '.embed-responsive .embed-responsive-item, .embed-responsive iframe, .embed-responsive embed, .embed-responsive object, .embed-responsive video (312:3)', + ], + }, + c5009af89633c4d2f71a0a9fa333630d: { selectors: ['.flex-row (337:1)'] }, + '7b06a3d956579cd64b6f5b1a57255369': { selectors: ['.flex-column (340:1)'] }, + '21744a8c4dc6ed1519903b4236b00af4': { selectors: ['.flex-row-reverse (343:1)'] }, + '18d903735f9c71070b6d8166aa1112f1': { selectors: ['.flex-column-reverse (346:1)'] }, + e2a57aa8196347d4da84f33a4f551325: { selectors: ['.flex-wrap (349:1)'] }, + b6b29f75b174b5609f9e7d5eef457b70: { selectors: ['.flex-nowrap (352:1)'] }, + '839230fc7c0abacb6418b49d8f10b27f': { selectors: ['.flex-wrap-reverse (355:1)'] }, + '924d9b261944a8e8ff684d5b519062bb': { selectors: ['.flex-fill (358:1)'] }, + '5ed396aeb08464b7df8fc37d29455319': { selectors: ['.flex-grow-0 (361:1)'] }, + '94251dc4d012339a3e37df6196fc79bb': { selectors: ['.flex-grow-1 (364:1)'] }, + '0eeed7dabca0452a46574776a4485e6e': { selectors: ['.flex-shrink-0 (367:1)'] }, + c69e60d5e51a1b74d22b172ab98ef9d5: { selectors: ['.flex-shrink-1 (370:1)'] }, + '03401c1a81eb6d4639f020f76dd60176': { selectors: ['.justify-content-start (373:1)'] }, + '39e3d2c344e78869c98ef099249e717d': { selectors: ['.justify-content-end (376:1)'] }, + '3b2d00c0bea857ab78a71b0872842980': { selectors: ['.justify-content-center (379:1)'] }, + b1c3c9edd20ed7c08b43863d38ebee40: { selectors: ['.justify-content-between (382:1)'] }, + a11b4b1d983c5fa75777f273e998f73e: { selectors: ['.justify-content-around (385:1)'] }, + '50e33f29f65bfffa6a3591fcb6045ca9': { selectors: ['.align-items-start (388:1)'] }, + e44b276e47415ec19b74cc16740ada1d: { selectors: ['.align-items-end (391:1)'] }, + '4a9d2716bca651758564059dceed1271': { selectors: ['.align-items-center (394:1)'] }, + fd970b017f7f558f30cb273bf71ede7d: { selectors: ['.align-items-baseline (397:1)'] }, + '7204b6b00b69f8af1e4a24c9b6e7f7f9': { selectors: ['.align-items-stretch (400:1)'] }, + '350fbb74eb70bd05f9438067c3990e9f': { selectors: ['.align-content-start (403:1)'] }, + '74d61397b4fcbf608f3dba39ab3b2a1b': { selectors: ['.align-content-end (406:1)'] }, + eed1ab4ee9e5b327a434512176741548: { selectors: ['.align-content-center (409:1)'] }, + '19878ab832978ef7e1746ac2fe4084b2': { selectors: ['.align-content-between (412:1)'] }, + dded333d0522692809517039f5a727c1: { selectors: ['.align-content-around (415:1)'] }, + '5cbb83ea2af7e9db8ef13f4c7d6db875': { selectors: ['.align-content-stretch (418:1)'] }, + dc3df46d3c023184d375a63a71916646: { selectors: ['.align-self-auto (421:1)'] }, + '0c6d2d8c9732c571f9cf61a4b1d2877f': { selectors: ['.align-self-start (424:1)'] }, + fe7c6071e3e17214df1bdd38850d9ff0: { selectors: ['.align-self-end (427:1)'] }, + '9611bbad74d72d50cf238088576a5089': { selectors: ['.align-self-center (430:1)'] }, + '4bc5edddf5981866946175bfedb7247f': { selectors: ['.align-self-baseline (433:1)'] }, + '4fffdd27ec60120ec9ed16fd7feef801': { selectors: ['.align-self-stretch (436:1)'] }, + '39e658501f5502b35919f02fa9591360': { selectors: ['.float-left (719:1)'] }, + b51436c537ffc4269b1533e44d7c3467: { selectors: ['.float-right (722:1)'] }, + c4597a87d2c793a6d696cfe06f6c95ce: { selectors: ['.float-none (725:1)'] }, + aaf8dc6e0768e59f3098a98a5c144d66: { selectors: ['.position-static (760:1)'] }, + '79592de2383045d15ab57d35aa1dab95': { selectors: ['.position-relative (763:1)'] }, + a7c272f53d0368730bde4c2740ffb5c3: { selectors: ['.position-absolute (766:1)'] }, + dad0bb92d53f17cf8affc10f77824b7f: { selectors: ['.position-fixed (769:1)'] }, + '6da0f6a7354a75fe6c95b08a4cabc06f': { selectors: ['.position-sticky (772:1)'] }, + '66602e21eea7b673883155c8f42b9590': { selectors: ['.fixed-top (775:1)'] }, + '33ea70eb6db7f6ab3469680f182abb19': { selectors: ['.fixed-bottom (782:1)'] }, + '405e9dd7c9919943af14658313fd31e4': { selectors: ['.sr-only (795:1)'] }, + '9220ad156a70c2586b15fe2b9b6108b2': { selectors: ['.shadow-sm (813:1)'] }, + b1b8c0ff70767ca2160811a026766982: { selectors: ['.shadow (816:1)'] }, + da0792abe99964acb6692a03c61d6dd8: { selectors: ['.shadow-lg (819:1)'] }, + bb2173057af1cf20e687469b2d9cbb3c: { selectors: ['.shadow-none (822:1)'] }, + '6d8abb6519a186483b25429ab8b9765e': { selectors: ['.w-25 (825:1)'] }, + a087c1ffdf8ead76cdd37445b414d63e: { selectors: ['.w-50 (828:1)'] }, + '28180742013a90275be5633e6ec0dd51': { selectors: ['.w-75 (831:1)'] }, + '195a03bc95a0af0ba6c8824db97a0b2f': { selectors: ['.w-100 (834:1)'] }, + e67c74b650d6236b03be9dfc10c78e32: { selectors: ['.w-auto (837:1)'] }, + c1b6262b3ee069addc1fbe46f64aac4e: { selectors: ['.h-25 (840:1)'] }, + a520396ae349bef86145e0761aa0699e: { selectors: ['.h-50 (843:1)'] }, + '7c53b57d54beb087fd7ab8b669c5fe60': { selectors: ['.h-75 (846:1)'] }, + ad74f1972cb745b7a78b03e16a387f21: { selectors: ['.h-100 (849:1)'] }, + '2cd49c3d63d260ba4f0b23c559ad05e0': { selectors: ['.h-auto (852:1)'] }, + '0b43071a67efc45ee1735fdc2491313c': { selectors: ['.mw-100 (855:1)'] }, + eac31a6f08e5c935e24b97df0fdad579: { selectors: ['.mh-100 (858:1)'] }, + cfdb4f497b16074959bfd3deb7ea9c42: { selectors: ['.m-0 (861:1)'] }, + '4d666c270ba50524312d97c4b937d153': { selectors: ['.mt-0, .my-0 (864:1)'] }, + eccf47ccd76ceffb4b139cb6c080b5ac: { selectors: ['.mr-0, .mx-0 (868:1)'] }, + '9bc513e73c0bdc6efdf170cb31de16d1': { selectors: ['.mb-0, .my-0 (872:1)'] }, + e99cfc55b03f0e67f11628b19889ad7b: { selectors: ['.ml-0, .mx-0 (876:1)'] }, + e1c39484d90d2acaa00973531f47f738: { selectors: ['.m-1 (880:1)'] }, + '63791bc02eccfdfa2c01621a801e565f': { selectors: ['.mt-1, .my-1 (883:1)'] }, + bcb27ab9d7dcfdd0d7cacad02709966c: { selectors: ['.mr-1, .mx-1 (887:1)'] }, + cb5d1c4328e25b5bc93be9a252973690: { selectors: ['.mb-1, .my-1 (891:1)'] }, + b80b1010c7dcfbb30bed9015c4f2e969: { selectors: ['.ml-1, .mx-1 (895:1)'] }, + ecab1c9cdf8a562e3c0f70307aeafa89: { selectors: ['.m-2 (899:1)'] }, + '6505fe17fbbd88b1884113a754aa82ab': { selectors: ['.mt-2, .my-2 (902:1)'] }, + '6f0c7d09d1e729f332c4671ccc2b48c0': { selectors: ['.mr-2, .mx-2 (906:1)'] }, + '70ef7b668b382b3c747b2d73e08cdbed': { selectors: ['.mb-2, .my-2 (910:1)'] }, + '2d7f277cc78ed324a8fc1f71ab281e1f': { selectors: ['.ml-2, .mx-2 (914:1)'] }, + '8ebcfe52fd4024861082ffb1735747a7': { selectors: ['.m-3 (918:1)'] }, + '9965fb516bdb72b87023a533123a8035': { selectors: ['.mt-3, .my-3 (921:1)'] }, + b1fcbbb1dc6226f6da6000830088e051: { selectors: ['.mr-3, .mx-3 (925:1)'] }, + '02204826cfbe3da98535c0d802870940': { selectors: ['.mb-3, .my-3 (929:1)'] }, + '0259859060250ae6b730218733e7a437': { selectors: ['.ml-3, .mx-3 (933:1)'] }, + '8cf300dab2a4994a105eeddda826f2e6': { selectors: ['.m-4 (937:1)'] }, + '1ba62fdddd3349f52a452050688905c7': { selectors: ['.mt-4, .my-4 (940:1)'] }, + '66a104129fa13db5a0829567fba6ee41': { selectors: ['.mr-4, .mx-4 (944:1)'] }, + eefcc4c10b79e70e8e8a5a66fb2b7aa1: { selectors: ['.mb-4, .my-4 (948:1)'] }, + eb1503656dc920d15a31116956fdffa4: { selectors: ['.ml-4, .mx-4 (952:1)'] }, + '79cbb6e5c9b73fd0be29d4fc5733a099': { selectors: ['.m-5 (956:1)'] }, + '67d8671699df706a428e7da42a7141cb': { selectors: ['.mt-5, .my-5 (959:1)'] }, + e9cb4a0a8a60ff018c87a0b7efa9de29: { selectors: ['.mr-5, .mx-5 (963:1)'] }, + '93f579214354dbd8cb60209c068f0086': { selectors: ['.mb-5, .my-5 (967:1)'] }, + '2a789d4af97d2b87fd0bf2b4626120cd': { selectors: ['.ml-5, .mx-5 (971:1)'] }, + '64a89d28e8287c1a0ac153001082644c': { selectors: ['.p-0 (975:1)'] }, + b03aa6db5ddf110bbdbefbbec43fda30: { selectors: ['.pt-0, .py-0 (978:1)'] }, + e38192ca32a98888d4c4876880f4fece: { selectors: ['.pr-0, .px-0 (982:1)'] }, + '70fe8ef50e999ddd29506f672c107069': { selectors: ['.pb-0, .py-0 (986:1)'] }, + '9355e8cd9109049726475ba356661bcf': { selectors: ['.pl-0, .px-0 (990:1)'] }, + '0d4c53468c2658c5324b9ec7a8ca6de2': { selectors: ['.p-1 (994:1)'] }, + d74e430b2a56b3a4e20065c972b7fa3f: { selectors: ['.pt-1, .py-1 (997:1)'] }, + '21e4644967aedd19888b6f4a700b629b': { selectors: ['.pr-1, .px-1 (1001:1)'] }, + e315a7b9b7a1d0df3ea7d95af5203a0b: { selectors: ['.pb-1, .py-1 (1005:1)'] }, + '14630ca122e1d9830a9ef5591c4097d0': { selectors: ['.pl-1, .px-1 (1009:1)'] }, + '5b1c65e5139e86e5f4755824f8b77d13': { selectors: ['.p-2 (1013:1)'] }, + '244af70950a1e200d3849f75ce51d707': { selectors: ['.pt-2, .py-2 (1016:1)'] }, + b583832738cad724c7c23e5c14ac9bfb: { selectors: ['.pr-2, .px-2 (1020:1)'] }, + e1e633c4f1375e8276154192d8899e39: { selectors: ['.pb-2, .py-2 (1024:1)'] }, + '676b01e25f0dbb3f7d2f2529231cda08': { selectors: ['.pl-2, .px-2 (1028:1)'] }, + '9b5165e3333b22801f2287f7983d7516': { selectors: ['.p-3 (1032:1)'] }, + '5bcaa9df87a507f6cd14659ea176bdc5': { selectors: ['.pt-3, .py-3 (1035:1)'] }, + f706637180776c5589385599705a2409: { selectors: ['.pr-3, .px-3 (1039:1)'] }, + '41157cfbcf47990b383b5b0379386ab2': { selectors: ['.pb-3, .py-3 (1043:1)'] }, + cac1e7a204bb6a1f42707b684ad46238: { selectors: ['.pl-3, .px-3 (1047:1)'] }, + '43e0671cd41a4b7590284888b607a134': { selectors: ['.p-4 (1051:1)'] }, + '116b0f95ebde1ff8907e488413a88854': { selectors: ['.pt-4, .py-4 (1054:1)'] }, + ecb06765fe691d892df000eebbb23dcc: { selectors: ['.pr-4, .px-4 (1058:1)'] }, + '1331503a48d36025c861e660bc615048': { selectors: ['.pb-4, .py-4 (1062:1)'] }, + f8665f7e547e499abd7ac63813b274f5: { selectors: ['.pl-4, .px-4 (1066:1)'] }, + '4160a315459f1b5a98255863f42136fe': { selectors: ['.p-5 (1070:1)'] }, + f55a6b2de6a434ec7b4375f06f4fad75: { selectors: ['.pt-5, .py-5 (1073:1)'] }, + '19391dc45c8d7730a86d521c28f52c3f': { selectors: ['.pr-5, .px-5 (1077:1)'] }, + '15898bcb7ff74a60006f9931422b4ad3': { selectors: ['.pb-5, .py-5 (1081:1)'] }, + '6290bdc6355aed1e9b27379003aa4828': { selectors: ['.pl-5, .px-5 (1085:1)'] }, + e57ec4fe9e8ed36e38f1c50041fc9f47: { selectors: ['.m-auto (1089:1)'] }, + f10380665932186d1effe0674a74ba12: { selectors: ['.mt-auto, .my-auto (1092:1)'] }, + '2ce71a27023eb50a47c24a99399faa28': { selectors: ['.mr-auto, .mx-auto (1096:1)'] }, + '196c77d357d314678cd3a99cfacbea96': { selectors: ['.mb-auto, .my-auto (1100:1)'] }, + ca007ce268b463a6bf42145cf5ce3685: { selectors: ['.ml-auto, .mx-auto (1104:1)'] }, + cb431b84034f2e710509c7656b3c6f16: { selectors: ['.text-monospace (1844:1)'] }, + a8fc5ca823f51d72673577064387a029: { selectors: ['.text-justify (1847:1)'] }, + '0bb94dfab7ca2c9892ebbd993b2baf0f': { selectors: ['.text-nowrap (1850:1)'] }, + aea4958ce85ddc0cbffca1015c3a7eba: { selectors: ['.text-truncate (1853:1)'] }, + '52b9443947b6b94a5c7e1b837da115e2': { selectors: ['.text-left (1858:1)'] }, + baaf5136fc6e1c54ba29b6040f166d5f: { selectors: ['.text-right (1861:1)'] }, + '282aa4319bee75af06cc2632b7124e26': { selectors: ['.text-center (1864:1)'] }, + '1cb1c8ad9b560eca25ebcefe95c1b7fa': { selectors: ['.text-lowercase (1899:1)'] }, + '45234533eac658ba2857e9c4d3bc78a5': { selectors: ['.text-uppercase (1902:1)'] }, + f9e3f64237f2e81b6aed84223a0ceb1d: { selectors: ['.text-capitalize (1905:1)'] }, + '09caca3d36aa9f3ef815e0da7e1a16b4': { selectors: ['.font-weight-light (1908:1)'] }, + '25189f4fad18eaeef19e349c6680834c': { selectors: ['.font-weight-normal (1911:1)'] }, + b2a9507678ec557603eb8ec077f0eb1f: { selectors: ['.font-weight-bold (1914:1)'] }, + '7d2da06b621a98a8599e5ec82e39eac8': { selectors: ['.font-italic (1917:1)'] }, + '0020d10e4fce033b418aace7c3143b82': { selectors: ['.text-white (1920:1)'] }, + '34ad81e372a038e6f78ae4f22bd4813d': { selectors: ['.text-primary (1923:1)'] }, + '9fde9a179d24755438ace2a874dda817': { + selectors: ['.text-secondary (1929:1)', '.text-muted (1974:1)'], + }, + '9ffcb1532b3fb397c0e818850683da29': { selectors: ['.text-success (1935:1)'] }, + f28fd089809bcd15d5684b158a0af98d: { selectors: ['.text-info (1941:1)'] }, + '6cac1cb5ee5149e91e45d15d0bdae310': { selectors: ['.text-warning (1947:1)'] }, + '2faab1e0abf22b20fdf05b9b01fff29b': { selectors: ['.text-danger (1953:1)'] }, + '46b52fea531aaaf29b63c40be2356849': { selectors: ['.text-light (1959:1)'] }, + '78f31d1ab6529decf28e0366a8ee81aa': { selectors: ['.text-dark (1965:1)'] }, + '45330b41b77e8880ad7680c51e0f61c4': { selectors: ['.text-body (1971:1)'] }, + '60d93588f62b5e85eb4f11dfd3461897': { selectors: ['.text-black-50 (1977:1)'] }, + '7dea35658553032ff7b7cc0287613b7c': { selectors: ['.text-white-50 (1980:1)'] }, + '61bf92980cac3d51d0cf1ba24c948fa1': { selectors: ['.text-hide (1983:1)'] }, + '7dcad258820769677bc60871fafe9b93': { selectors: ['.visible (1990:1)'] }, + '0f8833af4e2f4a6fc785bd7edc1e75b3': { selectors: ['.invisible (1993:1)'] }, +}; diff --git a/scripts/insert-rspec-profiling-data b/scripts/insert-rspec-profiling-data index 10e337b9972..b34379764e0 100755 --- a/scripts/insert-rspec-profiling-data +++ b/scripts/insert-rspec-profiling-data @@ -22,7 +22,7 @@ module RspecProfiling ENV['RSPEC_PROFILING_POSTGRES_URL'] end - class Result < ActiveRecord::Base + class Result < ActiveRecord::Base # rubocop:disable Rails/ApplicationRecord acts_as_copy_target end end diff --git a/scripts/review_apps/review-apps.sh b/scripts/review_apps/review-apps.sh index 32fce946c17..e324f972640 100755 --- a/scripts/review_apps/review-apps.sh +++ b/scripts/review_apps/review-apps.sh @@ -1,12 +1,115 @@ [[ "$TRACE" ]] && set -x export TILLER_NAMESPACE="$KUBE_NAMESPACE" -function echoerr() { printf "\033[0;31m%s\n\033[0m" "$*" >&2; } -function echoinfo() { printf "\033[0;33m%s\n\033[0m" "$*" >&2; } +function echoerr() { + local header="${2}" + + if [ -n "${header}" ]; then + printf "\n\033[0;31m** %s **\n\033[0m" "${1}" >&2; + else + printf "\033[0;31m%s\n\033[0m" "${1}" >&2; + fi +} + +function echoinfo() { + local header="${2}" + + if [ -n "${header}" ]; then + printf "\n\033[0;33m** %s **\n\033[0m" "${1}" >&2; + else + printf "\033[0;33m%s\n\033[0m" "${1}" >&2; + fi +} + +function deployExists() { + local namespace="${1}" + local deploy="${2}" + echoinfo "Checking if ${deploy} exists in the ${namespace} namespace..." true + + helm status --tiller-namespace "${namespace}" "${deploy}" >/dev/null 2>&1 + local deploy_exists=$? + + echoinfo "Deployment status for ${deploy} is ${deploy_exists}" + return $deploy_exists +} + +function previousDeployFailed() { + set +e + local deploy="${1}" + echoinfo "Checking for previous deployment of ${deploy}" true + + helm status ${deploy} >/dev/null 2>&1 + local status=$? + + # if `status` is `0`, deployment exists, has a status + if [ $status -eq 0 ]; then + echoinfo "Previous deployment found, checking status..." + deployment_status=$(helm status ${deploy} | grep ^STATUS | cut -d' ' -f2) + echoinfo "Previous deployment state: ${deployment_status}" + if [[ "$deployment_status" == "FAILED" || "$deployment_status" == "PENDING_UPGRADE" || "$deployment_status" == "PENDING_INSTALL" ]]; then + status=0; + else + status=1; + fi + else + echoerr "Previous deployment NOT found." + fi + set -e + return $status +} + +function delete() { + if [ -z "$CI_ENVIRONMENT_SLUG" ]; then + echoerr "No release given, aborting the delete!" + return + fi + + local track="${1-stable}" + local name="$CI_ENVIRONMENT_SLUG" + + if [[ "$track" != "stable" ]]; then + name="$name-$track" + fi + + echoinfo "Deleting release '$name'..." true + + helm delete --purge "$name" || true +} + +function cleanup() { + if [ -z "$CI_ENVIRONMENT_SLUG" ]; then + echoerr "No release given, aborting the delete!" + return + fi + + echoinfo "Cleaning up '$CI_ENVIRONMENT_SLUG'..." true + + kubectl -n "$KUBE_NAMESPACE" delete \ + ingress,svc,pdb,hpa,deploy,statefulset,job,pod,secret,configmap,pvc,secret,clusterrole,clusterrolebinding,role,rolebinding,sa \ + -l release="$CI_ENVIRONMENT_SLUG" \ + || true +} + +function get_pod() { + local app_name="${1}" + local status="${2-Running}" + get_pod_cmd="kubectl get pods -n ${KUBE_NAMESPACE} --field-selector=status.phase=${status} -lapp=${app_name},release=${CI_ENVIRONMENT_SLUG} --no-headers -o=custom-columns=NAME:.metadata.name" + echoinfo "Running '${get_pod_cmd}'" true + + while true; do + local pod_name="$(eval $get_pod_cmd)" + [[ "${pod_name}" == "" ]] || break + + echoinfo "Waiting till '${app_name}' pod is ready"; + sleep 5; + done + + echoinfo "The pod name is '${pod_name}'." + echo "${pod_name}" +} function perform_review_app_deployment() { check_kube_domain - download_gitlab_chart ensure_namespace install_tiller install_external_dns @@ -15,6 +118,8 @@ function perform_review_app_deployment() { } function check_kube_domain() { + echoinfo "Checking that Kube domain exists..." true + if [ -z ${REVIEW_APPS_DOMAIN+x} ]; then echo "In order to deploy or use Review Apps, REVIEW_APPS_DOMAIN variable must be set" echo "You can do it in Auto DevOps project settings or defining a variable at group or project level" @@ -25,36 +130,56 @@ function check_kube_domain() { fi } -function download_gitlab_chart() { - curl -o gitlab.tar.bz2 https://gitlab.com/charts/gitlab/-/archive/$GITLAB_HELM_CHART_REF/gitlab-$GITLAB_HELM_CHART_REF.tar.bz2 - tar -xjf gitlab.tar.bz2 - cd gitlab-$GITLAB_HELM_CHART_REF - - helm init --client-only - helm repo add gitlab https://charts.gitlab.io - helm dependency update - helm dependency build -} - function ensure_namespace() { + echoinfo "Ensuring the ${KUBE_NAMESPACE} namespace exists..." true + kubectl describe namespace "$KUBE_NAMESPACE" || kubectl create namespace "$KUBE_NAMESPACE" } function install_tiller() { - echo "Checking Tiller..." + echoinfo "Checking deployment/tiller-deploy status in the ${TILLER_NAMESPACE} namespace..." true + + echoinfo "Initiating the Helm client..." + helm init --client-only + helm init \ --upgrade \ --replicas 2 + kubectl rollout status -n "$TILLER_NAMESPACE" -w "deployment/tiller-deploy" + if ! helm version --debug; then echo "Failed to init Tiller." return 1 fi - echo "" +} + +function install_external_dns() { + local release_name="dns-gitlab-review-app" + local domain=$(echo "${REVIEW_APPS_DOMAIN}" | awk -F. '{printf "%s.%s", $(NF-1), $NF}') + echoinfo "Installing external DNS for domain ${domain}..." true + + if ! deployExists "${KUBE_NAMESPACE}" "${release_name}" || previousDeployFailed "${release_name}" ; then + echoinfo "Installing external-dns Helm chart" + helm repo update + helm install stable/external-dns \ + -n "${release_name}" \ + --namespace "${KUBE_NAMESPACE}" \ + --set provider="aws" \ + --set aws.secretKey="${REVIEW_APPS_AWS_SECRET_KEY}" \ + --set aws.accessKey="${REVIEW_APPS_AWS_ACCESS_KEY}" \ + --set aws.zoneType="public" \ + --set domainFilters[0]="${domain}" \ + --set txtOwnerId="${KUBE_NAMESPACE}" \ + --set rbac.create="true" \ + --set policy="sync" + else + echoinfo "The external-dns Helm chart is already successfully deployed." + fi } function create_secret() { - echo "Create secret..." + echoinfo "Creating the ${CI_ENVIRONMENT_SLUG}-gitlab-initial-root-password secret in the ${KUBE_NAMESPACE} namespace..." true kubectl create secret generic -n "$KUBE_NAMESPACE" \ $CI_ENVIRONMENT_SLUG-gitlab-initial-root-password \ @@ -62,43 +187,28 @@ function create_secret() { --dry-run -o json | kubectl apply -f - } -function deployExists() { - local namespace="${1}" - local deploy="${2}" - helm status --tiller-namespace "${namespace}" "${deploy}" >/dev/null 2>&1 - return $? -} +function download_gitlab_chart() { + echoinfo "Downloading the GitLab chart..." true -function previousDeployFailed() { - set +e - deploy="${1}" - echo "Checking for previous deployment of ${deploy}" - deployment_status=$(helm status ${deploy} >/dev/null 2>&1) - status=$? - # if `status` is `0`, deployment exists, has a status - if [ $status -eq 0 ]; then - echo "Previous deployment found, checking status" - deployment_status=$(helm status ${deploy} | grep ^STATUS | cut -d' ' -f2) - echo "Previous deployment state: $deployment_status" - if [[ "$deployment_status" == "FAILED" || "$deployment_status" == "PENDING_UPGRADE" || "$deployment_status" == "PENDING_INSTALL" ]]; then - status=0; - else - status=1; - fi - else - echo "Previous deployment NOT found." - fi - set -e - return $status + curl -o gitlab.tar.bz2 https://gitlab.com/charts/gitlab/-/archive/$GITLAB_HELM_CHART_REF/gitlab-$GITLAB_HELM_CHART_REF.tar.bz2 + tar -xjf gitlab.tar.bz2 + cd gitlab-$GITLAB_HELM_CHART_REF + + echoinfo "Adding the gitlab repo to Helm..." + helm repo add gitlab https://charts.gitlab.io + + echoinfo "Building the gitlab chart's dependencies..." + helm dependency build . } function deploy() { - track="${1-stable}" - name="$CI_ENVIRONMENT_SLUG" + local track="${1-stable}" + local name="$CI_ENVIRONMENT_SLUG" if [[ "$track" != "stable" ]]; then name="$name-$track" fi + echoinfo "Deploying ${name}..." true replicas="1" service_enabled="false" @@ -140,9 +250,7 @@ function deploy() { fi create_secret - - helm repo add gitlab https://charts.gitlab.io/ - helm dep update . + download_gitlab_chart HELM_CMD=$(cat << EOF helm upgrade --install \ @@ -188,92 +296,20 @@ HELM_CMD=$(cat << EOF EOF ) - echo "Deploying with:" - echo $HELM_CMD + echoinfo "Deploying with:" + echoinfo "${HELM_CMD}" eval $HELM_CMD } -function delete() { - track="${1-stable}" - name="$CI_ENVIRONMENT_SLUG" - - if [ -z "$CI_ENVIRONMENT_SLUG" ]; then - echo "No release given, aborting the delete!" - return - fi - - if [[ "$track" != "stable" ]]; then - name="$name-$track" - fi - - if ! deployExists "${KUBE_NAMESPACE}" "${name}"; then - echo "The release $name doesn't exist, aborting the cleanup!" - return - fi - - echo "Deleting release '$name'..." - helm delete --purge "$name" || true -} - -function cleanup() { - if [ -z "$CI_ENVIRONMENT_SLUG" ]; then - echo "No release given, aborting the delete!" - return - fi - - echo "Cleaning up '$CI_ENVIRONMENT_SLUG'..." - kubectl -n "$KUBE_NAMESPACE" delete \ - ingress,svc,pdb,hpa,deploy,statefulset,job,pod,secret,configmap,pvc,secret,clusterrole,clusterrolebinding,role,rolebinding,sa \ - -l release="$CI_ENVIRONMENT_SLUG" \ - || true -} - -function install_external_dns() { - local release_name="dns-gitlab-review-app" - local domain=$(echo "${REVIEW_APPS_DOMAIN}" | awk -F. '{printf "%s.%s", $(NF-1), $NF}') - - if ! deployExists "${KUBE_NAMESPACE}" "${release_name}" || previousDeployFailed "${release_name}" ; then - echo "Installing external-dns helm chart" - helm repo update - helm install stable/external-dns \ - -n "${release_name}" \ - --namespace "${KUBE_NAMESPACE}" \ - --set provider="aws" \ - --set aws.secretKey="${REVIEW_APPS_AWS_SECRET_KEY}" \ - --set aws.accessKey="${REVIEW_APPS_AWS_ACCESS_KEY}" \ - --set aws.zoneType="public" \ - --set domainFilters[0]="${domain}" \ - --set txtOwnerId="${KUBE_NAMESPACE}" \ - --set rbac.create="true" \ - --set policy="sync" - fi -} - -function get_pod() { - local app_name="${1}" - local status="${2-Running}" - get_pod_cmd="kubectl get pods -n ${KUBE_NAMESPACE} --field-selector=status.phase=${status} -lapp=${app_name},release=${CI_ENVIRONMENT_SLUG} --no-headers -o=custom-columns=NAME:.metadata.name" - echoinfo "Running '${get_pod_cmd}'" - - while true; do - local pod_name="$(eval $get_pod_cmd)" - [[ "${pod_name}" == "" ]] || break - - echoinfo "Waiting till '${app_name}' pod is ready"; - sleep 5; - done - - echoinfo "The pod name is '${pod_name}'." - echo "${pod_name}" -} - function add_license() { if [ -z "${REVIEW_APPS_EE_LICENSE}" ]; then echo "License not found" && return; fi task_runner_pod=$(get_pod "task-runner"); if [ -z "${task_runner_pod}" ]; then echo "Task runner pod not found" && return; fi + echoinfo "Installing license..." true + echo "${REVIEW_APPS_EE_LICENSE}" > /tmp/license.gitlab kubectl -n "$KUBE_NAMESPACE" cp /tmp/license.gitlab ${task_runner_pod}:/tmp/license.gitlab rm /tmp/license.gitlab diff --git a/spec/controllers/graphql_controller_spec.rb b/spec/controllers/graphql_controller_spec.rb new file mode 100644 index 00000000000..c19a752b07b --- /dev/null +++ b/spec/controllers/graphql_controller_spec.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe GraphqlController do + before do + stub_feature_flags(graphql: true) + end + + describe 'POST #execute' do + context 'when user is logged in' do + let(:user) { create(:user) } + + before do + sign_in(user) + end + + it 'returns 200 when user can access API' do + post :execute + + expect(response).to have_gitlab_http_status(200) + end + + it 'returns access denied template when user cannot access API' do + # User cannot access API in a couple of cases + # * When user is internal(like ghost users) + # * When user is blocked + expect(Ability).to receive(:allowed?).with(user, :access_api, :global).and_return(false) + + post :execute + + expect(response.status).to eq(403) + expect(response).to render_template('errors/access_denied') + end + end + + context 'when user is not logged in' do + it 'returns 200' do + post :execute + + expect(response).to have_gitlab_http_status(200) + 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 79f97aa4170..c8fa93a74ee 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -86,6 +86,10 @@ describe Projects::MergeRequestsController do end describe 'as json' do + before do + expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original + end + context 'with basic serializer param' do it 'renders basic MR entity as json' do go(serializer: 'basic', format: :json) diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb index a73f330a7a9..abf0e6bccb7 100644 --- a/spec/factories/merge_requests.rb +++ b/spec/factories/merge_requests.rb @@ -46,10 +46,26 @@ FactoryBot.define do target_branch "improve/awesome" end + trait :merged_last_month do + merged + + after(:build) do |merge_request| + merge_request.build_metrics.merged_at = 1.month.ago + end + end + trait :closed do state :closed end + trait :closed_last_month do + closed + + after(:build) do |merge_request| + merge_request.build_metrics.latest_closed_at = 1.month.ago + end + end + trait :opened do state :opened end @@ -101,9 +117,20 @@ FactoryBot.define do end end + trait :with_legacy_detached_merge_request_pipeline do + after(:create) do |merge_request| + merge_request.merge_request_pipelines << create(:ci_pipeline, + source: :merge_request_event, + merge_request: merge_request, + project: merge_request.source_project, + ref: merge_request.source_branch, + sha: merge_request.source_branch_sha) + end + end + trait :with_detached_merge_request_pipeline do - after(:build) do |merge_request| - merge_request.merge_request_pipelines << build(:ci_pipeline, + after(:create) do |merge_request| + merge_request.merge_request_pipelines << create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, project: merge_request.source_project, @@ -119,7 +146,7 @@ FactoryBot.define do target_sha { target_branch_sha } end - after(:build) do |merge_request, evaluator| + after(:create) do |merge_request, evaluator| merge_request.merge_request_pipelines << create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, diff --git a/spec/factories/suggestions.rb b/spec/factories/suggestions.rb index 307523cc061..b1427e0211f 100644 --- a/spec/factories/suggestions.rb +++ b/spec/factories/suggestions.rb @@ -16,5 +16,11 @@ FactoryBot.define do applied true commit_id { RepoHelpers.sample_commit.id } end + + trait :content_from_repo do + after(:build) do |suggestion, evaluator| + suggestion.from_content = suggestion.fetch_from_content + end + end end end diff --git a/spec/features/clusters/cluster_detail_page_spec.rb b/spec/features/clusters/cluster_detail_page_spec.rb index b9fc52d0dce..d2e46d15730 100644 --- a/spec/features/clusters/cluster_detail_page_spec.rb +++ b/spec/features/clusters/cluster_detail_page_spec.rb @@ -53,12 +53,80 @@ describe 'Clusterable > Show page' do end end + shared_examples 'editing a GCP cluster' do + before do + clusterable.add_maintainer(current_user) + visit cluster_path + end + + it 'is not able to edit the name, API url, CA certificate nor token' do + within('#js-cluster-details') do + cluster_name_field = find('.cluster-name') + api_url_field = find('#cluster_platform_kubernetes_attributes_api_url') + ca_certificate_field = find('#cluster_platform_kubernetes_attributes_ca_cert') + token_field = find('#cluster_platform_kubernetes_attributes_token') + + expect(cluster_name_field).to be_readonly + expect(api_url_field).to be_readonly + expect(ca_certificate_field).to be_readonly + expect(token_field).to be_readonly + end + end + + it 'displays GKE information' do + within('#advanced-settings-section') do + expect(page).to have_content('Google Kubernetes Engine') + expect(page).to have_content('Manage your Kubernetes cluster by visiting') + end + end + end + + shared_examples 'editing a user-provided cluster' do + before do + clusterable.add_maintainer(current_user) + visit cluster_path + end + + it 'is able to edit the name, API url, CA certificate and token' do + within('#js-cluster-details') do + cluster_name_field = find('#cluster_name') + api_url_field = find('#cluster_platform_kubernetes_attributes_api_url') + ca_certificate_field = find('#cluster_platform_kubernetes_attributes_ca_cert') + token_field = find('#cluster_platform_kubernetes_attributes_token') + + expect(cluster_name_field).not_to be_readonly + expect(api_url_field).not_to be_readonly + expect(ca_certificate_field).not_to be_readonly + expect(token_field).not_to be_readonly + end + end + + it 'does not display GKE information' do + within('#advanced-settings-section') do + expect(page).not_to have_content('Google Kubernetes Engine') + expect(page).not_to have_content('Manage your Kubernetes cluster by visiting') + end + end + end + context 'when clusterable is a project' do it_behaves_like 'editing domain' do let(:clusterable) { create(:project) } let(:cluster) { create(:cluster, :provided_by_gcp, :project, projects: [clusterable]) } let(:cluster_path) { project_cluster_path(clusterable, cluster) } end + + it_behaves_like 'editing a GCP cluster' do + let(:clusterable) { create(:project) } + let(:cluster) { create(:cluster, :provided_by_gcp, :project, projects: [clusterable]) } + let(:cluster_path) { project_cluster_path(clusterable, cluster) } + end + + it_behaves_like 'editing a user-provided cluster' do + let(:clusterable) { create(:project) } + let(:cluster) { create(:cluster, :provided_by_user, :project, projects: [clusterable]) } + let(:cluster_path) { project_cluster_path(clusterable, cluster) } + end end context 'when clusterable is a group' do @@ -67,5 +135,17 @@ describe 'Clusterable > Show page' do let(:cluster) { create(:cluster, :provided_by_gcp, :group, groups: [clusterable]) } let(:cluster_path) { group_cluster_path(clusterable, cluster) } end + + it_behaves_like 'editing a GCP cluster' do + let(:clusterable) { create(:group) } + let(:cluster) { create(:cluster, :provided_by_gcp, :group, groups: [clusterable]) } + let(:cluster_path) { group_cluster_path(clusterable, cluster) } + end + + it_behaves_like 'editing a user-provided cluster' do + let(:clusterable) { create(:group) } + let(:cluster) { create(:cluster, :provided_by_user, :group, groups: [clusterable]) } + let(:cluster_path) { group_cluster_path(clusterable, cluster) } + end end end diff --git a/spec/features/issues/user_uses_quick_actions_spec.rb b/spec/features/issues/user_uses_quick_actions_spec.rb index b5e7c3954e2..362f8a468ec 100644 --- a/spec/features/issues/user_uses_quick_actions_spec.rb +++ b/spec/features/issues/user_uses_quick_actions_spec.rb @@ -3,8 +3,41 @@ require 'rails_helper' describe 'Issues > User uses quick actions', :js do include Spec::Support::Helpers::Features::NotesHelpers - it_behaves_like 'issuable record that supports quick actions in its description and notes', :issue do + context "issuable common quick actions" do + let(:new_url_opts) { {} } + let(:maintainer) { create(:user) } + let(:project) { create(:project, :public) } + let!(:label_bug) { create(:label, project: project, title: 'bug') } + let!(:label_feature) { create(:label, project: project, title: 'feature') } + let!(:milestone) { create(:milestone, project: project, title: 'ASAP') } let(:issuable) { create(:issue, project: project) } + let(:source_issuable) { create(:issue, project: project, milestone: milestone, labels: [label_bug, label_feature])} + + it_behaves_like 'assign quick action', :issue + it_behaves_like 'unassign quick action', :issue + it_behaves_like 'close quick action', :issue + it_behaves_like 'reopen quick action', :issue + it_behaves_like 'title quick action', :issue + it_behaves_like 'todo quick action', :issue + it_behaves_like 'done quick action', :issue + it_behaves_like 'subscribe quick action', :issue + it_behaves_like 'unsubscribe quick action', :issue + it_behaves_like 'lock quick action', :issue + it_behaves_like 'unlock quick action', :issue + it_behaves_like 'milestone quick action', :issue + it_behaves_like 'remove_milestone quick action', :issue + it_behaves_like 'label quick action', :issue + it_behaves_like 'unlabel quick action', :issue + it_behaves_like 'relabel quick action', :issue + it_behaves_like 'award quick action', :issue + it_behaves_like 'estimate quick action', :issue + it_behaves_like 'remove_estimate quick action', :issue + it_behaves_like 'spend quick action', :issue + it_behaves_like 'remove_time_spent quick action', :issue + it_behaves_like 'shrug quick action', :issue + it_behaves_like 'tableflip quick action', :issue + it_behaves_like 'copy_metadata quick action', :issue + it_behaves_like 'issuable time tracker', :issue end describe 'issue-only commands' do @@ -15,37 +48,17 @@ describe 'Issues > User uses quick actions', :js do project.add_maintainer(user) sign_in(user) visit project_issue_path(project, issue) + wait_for_all_requests end after do wait_for_requests end - describe 'time tracking' do - let(:issue) { create(:issue, project: project) } - - before do - visit project_issue_path(project, issue) - end - - it_behaves_like 'issuable time tracker' - end - describe 'adding a due date from note' do let(:issue) { create(:issue, project: project) } - context 'when the current user can update the due date' do - it 'does not create a note, and sets the due date accordingly' do - add_note("/due 2016-08-28") - - expect(page).not_to have_content '/due 2016-08-28' - expect(page).to have_content 'Commands applied' - - issue.reload - - expect(issue.due_date).to eq Date.new(2016, 8, 28) - end - end + it_behaves_like 'due quick action available and date can be added' context 'when the current user cannot update the due date' do let(:guest) { create(:user) } @@ -56,35 +69,14 @@ describe 'Issues > User uses quick actions', :js do visit project_issue_path(project, issue) end - it 'does not create a note, and sets the due date accordingly' do - add_note("/due 2016-08-28") - - expect(page).not_to have_content 'Commands applied' - - issue.reload - - expect(issue.due_date).to be_nil - end + it_behaves_like 'due quick action not available' end end describe 'removing a due date from note' do let(:issue) { create(:issue, project: project, due_date: Date.new(2016, 8, 28)) } - context 'when the current user can update the due date' do - it 'does not create a note, and removes the due date accordingly' do - expect(issue.due_date).to eq Date.new(2016, 8, 28) - - add_note("/remove_due_date") - - expect(page).not_to have_content '/remove_due_date' - expect(page).to have_content 'Commands applied' - - issue.reload - - expect(issue.due_date).to be_nil - end - end + it_behaves_like 'remove_due_date action available and due date can be removed' context 'when the current user cannot update the due date' do let(:guest) { create(:user) } @@ -95,15 +87,7 @@ describe 'Issues > User uses quick actions', :js do visit project_issue_path(project, issue) end - it 'does not create a note, and sets the due date accordingly' do - add_note("/remove_due_date") - - expect(page).not_to have_content 'Commands applied' - - issue.reload - - expect(issue.due_date).to eq Date.new(2016, 8, 28) - end + it_behaves_like 'remove_due_date action not available' end end @@ -200,6 +184,7 @@ describe 'Issues > User uses quick actions', :js do gitlab_sign_out sign_in(user) visit project_issue_path(project, issue) + wait_for_requests end it 'moves the issue' do @@ -221,6 +206,7 @@ describe 'Issues > User uses quick actions', :js do gitlab_sign_out sign_in(user) visit project_issue_path(project, issue) + wait_for_requests end it 'does not move the issue' do @@ -238,6 +224,7 @@ describe 'Issues > User uses quick actions', :js do gitlab_sign_out sign_in(user) visit project_issue_path(project, issue) + wait_for_requests end it 'does not move the issue' do diff --git a/spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb b/spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb index 8c2599615cb..2f7d359575e 100644 --- a/spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb +++ b/spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb @@ -5,9 +5,7 @@ describe 'Merge request > User scrolls to note on load', :js do let(:user) { project.creator } let(:merge_request) { create(:merge_request, source_project: project, author: user) } let(:note) { create(:diff_note_on_merge_request, noteable: merge_request, project: project) } - let(:resolved_note) { create(:diff_note_on_merge_request, :resolved, noteable: merge_request, project: project) } let(:fragment_id) { "#note_#{note.id}" } - let(:collapsed_fragment_id) { "#note_#{resolved_note.id}" } before do sign_in(user) @@ -45,13 +43,35 @@ describe 'Merge request > User scrolls to note on load', :js do end end - # TODO: https://gitlab.com/gitlab-org/gitlab-ce/issues/48034 - xit 'expands collapsed notes' do - visit "#{project_merge_request_path(project, merge_request)}#{collapsed_fragment_id}" - note_element = find(collapsed_fragment_id) - note_container = note_element.ancestor('.timeline-content') + context 'resolved notes' do + let(:collapsed_fragment_id) { "#note_#{resolved_note.id}" } - expect(note_element.visible?).to eq true - expect(note_container.find('.line_content.noteable_line.old', match: :first).visible?).to eq true + context 'when diff note' do + let(:resolved_note) { create(:diff_note_on_merge_request, :resolved, noteable: merge_request, project: project) } + + it 'expands collapsed notes' do + visit "#{project_merge_request_path(project, merge_request)}#{collapsed_fragment_id}" + + note_element = find(collapsed_fragment_id) + diff_container = note_element.ancestor('.diff-content') + + expect(note_element.visible?).to eq(true) + expect(diff_container.visible?).to eq(true) + end + end + + context 'when non-diff note' do + let(:non_diff_discussion) { create(:discussion_note_on_merge_request, :resolved, noteable: merge_request, project: project) } + let(:resolved_note) { create(:discussion_note_on_merge_request, :resolved, noteable: merge_request, project: project, in_reply_to: non_diff_discussion) } + + it 'expands collapsed replies' do + visit "#{project_merge_request_path(project, merge_request)}#{collapsed_fragment_id}" + + note_element = find(collapsed_fragment_id) + + expect(note_element.visible?).to eq(true) + expect(note_element.sibling('.replies-toggle')[:class]).to include('expanded') + end + end end end diff --git a/spec/features/merge_request/user_uses_quick_actions_spec.rb b/spec/features/merge_request/user_uses_quick_actions_spec.rb index b81478a481f..a2b5859bd1e 100644 --- a/spec/features/merge_request/user_uses_quick_actions_spec.rb +++ b/spec/features/merge_request/user_uses_quick_actions_spec.rb @@ -9,9 +9,41 @@ describe 'Merge request > User uses quick actions', :js do let(:merge_request) { create(:merge_request, source_project: project) } let!(:milestone) { create(:milestone, project: project, title: 'ASAP') } - it_behaves_like 'issuable record that supports quick actions in its description and notes', :merge_request do + context "issuable common quick actions" do + let!(:new_url_opts) { { merge_request: { source_branch: 'feature', target_branch: 'master' } } } + let(:maintainer) { create(:user) } + let(:project) { create(:project, :public, :repository) } + let!(:label_bug) { create(:label, project: project, title: 'bug') } + let!(:label_feature) { create(:label, project: project, title: 'feature') } + let!(:milestone) { create(:milestone, project: project, title: 'ASAP') } let(:issuable) { create(:merge_request, source_project: project) } - let(:new_url_opts) { { merge_request: { source_branch: 'feature', target_branch: 'master' } } } + let(:source_issuable) { create(:issue, project: project, milestone: milestone, labels: [label_bug, label_feature])} + + it_behaves_like 'assign quick action', :merge_request + it_behaves_like 'unassign quick action', :merge_request + it_behaves_like 'close quick action', :merge_request + it_behaves_like 'reopen quick action', :merge_request + it_behaves_like 'title quick action', :merge_request + it_behaves_like 'todo quick action', :merge_request + it_behaves_like 'done quick action', :merge_request + it_behaves_like 'subscribe quick action', :merge_request + it_behaves_like 'unsubscribe quick action', :merge_request + it_behaves_like 'lock quick action', :merge_request + it_behaves_like 'unlock quick action', :merge_request + it_behaves_like 'milestone quick action', :merge_request + it_behaves_like 'remove_milestone quick action', :merge_request + it_behaves_like 'label quick action', :merge_request + it_behaves_like 'unlabel quick action', :merge_request + it_behaves_like 'relabel quick action', :merge_request + it_behaves_like 'award quick action', :merge_request + it_behaves_like 'estimate quick action', :merge_request + it_behaves_like 'remove_estimate quick action', :merge_request + it_behaves_like 'spend quick action', :merge_request + it_behaves_like 'remove_time_spent quick action', :merge_request + it_behaves_like 'shrug quick action', :merge_request + it_behaves_like 'tableflip quick action', :merge_request + it_behaves_like 'copy_metadata quick action', :merge_request + it_behaves_like 'issuable time tracker', :merge_request end describe 'merge-request-only commands' do @@ -24,20 +56,12 @@ describe 'Merge request > User uses quick actions', :js do project.add_maintainer(user) end - describe 'time tracking' do - before do - sign_in(user) - visit project_merge_request_path(project, merge_request) - end - - it_behaves_like 'issuable time tracker' - end - describe 'toggling the WIP prefix in the title from note' do context 'when the current user can toggle the WIP prefix' do before do sign_in(user) visit project_merge_request_path(project, merge_request) + wait_for_requests end it 'adds the WIP: prefix to the title' do @@ -135,11 +159,16 @@ describe 'Merge request > User uses quick actions', :js do visit project_merge_request_path(project, merge_request) end - it 'does not recognize the command nor create a note' do - add_note('/due 2016-08-28') + it_behaves_like 'due quick action not available' + end - expect(page).not_to have_content '/due 2016-08-28' + describe 'removing a due date from note' do + before do + sign_in(user) + visit project_merge_request_path(project, merge_request) end + + it_behaves_like 'remove_due_date action not available' end describe '/target_branch command in merge request' do diff --git a/spec/features/merge_request/user_views_open_merge_request_spec.rb b/spec/features/merge_request/user_views_open_merge_request_spec.rb index 71022c6bb08..849fab62fc6 100644 --- a/spec/features/merge_request/user_views_open_merge_request_spec.rb +++ b/spec/features/merge_request/user_views_open_merge_request_spec.rb @@ -13,7 +13,7 @@ describe 'User views an open merge request' do end it 'renders both the title and the description' do - node = find('.wiki h1 a#user-content-description-header') + node = find('.md h1 a#user-content-description-header') expect(node[:href]).to end_with('#description-header') # Work around a weird Capybara behavior where calling `parent` on a node diff --git a/spec/features/milestone_spec.rb b/spec/features/milestone_spec.rb index 6e349395017..adac59b89ef 100644 --- a/spec/features/milestone_spec.rb +++ b/spec/features/milestone_spec.rb @@ -122,4 +122,32 @@ describe 'Milestone' do expect(page).to have_selector('.popover') end end + + describe 'reopen closed milestones' do + before do + create(:milestone, :closed, project: project) + end + + describe 'group milestones page' do + it 'reopens the milestone' do + visit group_milestones_path(group, { state: 'closed' }) + + click_link 'Reopen Milestone' + + expect(page).not_to have_selector('.status-box-closed') + expect(page).to have_selector('.status-box-open') + end + end + + describe 'project milestones page' do + it 'reopens the milestone' do + visit project_milestones_path(project, { state: 'closed' }) + + click_link 'Reopen Milestone' + + expect(page).not_to have_selector('.status-box-closed') + expect(page).to have_selector('.status-box-open') + end + end + end end diff --git a/spec/features/projects/artifacts/user_browses_artifacts_spec.rb b/spec/features/projects/artifacts/user_browses_artifacts_spec.rb index 5f630c9ffa4..a1fcd4024c0 100644 --- a/spec/features/projects/artifacts/user_browses_artifacts_spec.rb +++ b/spec/features/projects/artifacts/user_browses_artifacts_spec.rb @@ -19,6 +19,12 @@ describe "User browses artifacts" do visit(browse_project_job_artifacts_path(project, job)) end + it "renders a link to the job in the breadcrumbs" do + page.within('.js-breadcrumbs-list') do + expect(page).to have_link("##{job.id}", href: project_job_path(project, job)) + end + end + it "shows artifacts" do expect(page).not_to have_selector(".build-sidebar") diff --git a/spec/features/projects/environments/environment_spec.rb b/spec/features/projects/environments/environment_spec.rb index 3090f1a2131..fe71cb7661a 100644 --- a/spec/features/projects/environments/environment_spec.rb +++ b/spec/features/projects/environments/environment_spec.rb @@ -319,7 +319,7 @@ describe 'Environment' do yield - GitPushService.new(project, user, params).execute + Git::BranchPushService.new(project, user, params).execute end end diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index 9fdf78baa1e..b197557039d 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -59,11 +59,11 @@ describe 'Pipeline', :js do let(:project) { create(:project, :repository) } let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id, user: user) } - before do - visit project_pipeline_path(project, pipeline) - end + subject(:visit_pipeline) { visit project_pipeline_path(project, pipeline) } it 'shows the pipeline graph' do + visit_pipeline + expect(page).to have_selector('.pipeline-visualization') expect(page).to have_content('Build') expect(page).to have_content('Test') @@ -73,14 +73,20 @@ describe 'Pipeline', :js do end it 'shows Pipeline tab pane as active' do + visit_pipeline + expect(page).to have_css('#js-tab-pipeline.active') end it 'shows link to the pipeline ref' do + visit_pipeline + expect(page).to have_link(pipeline.ref) end it 'shows the pipeline information' do + visit_pipeline + within '.pipeline-info' do expect(page).to have_content("#{pipeline.statuses.count} jobs " \ "for #{pipeline.ref} ") @@ -96,6 +102,10 @@ describe 'Pipeline', :js do end describe 'pipeline graph' do + before do + visit_pipeline + end + context 'when pipeline has running builds' do it 'shows a running icon and a cancel action for the running build' do page.within('#ci-badge-deploy') do @@ -227,6 +237,10 @@ describe 'Pipeline', :js do end context 'page tabs' do + before do + visit_pipeline + end + it 'shows Pipeline, Jobs and Failed Jobs tabs with link' do expect(page).to have_link('Pipeline') expect(page).to have_link('Jobs') @@ -253,6 +267,10 @@ describe 'Pipeline', :js do end context 'retrying jobs' do + before do + visit_pipeline + end + it { expect(page).not_to have_content('retried') } context 'when retrying' do @@ -265,6 +283,10 @@ describe 'Pipeline', :js do end context 'canceling jobs' do + before do + visit_pipeline + end + it { expect(page).not_to have_selector('.ci-canceled') } context 'when canceling' do @@ -284,6 +306,10 @@ describe 'Pipeline', :js do user: user) end + before do + visit_pipeline + end + it 'does not render link to the pipeline ref' do expect(page).not_to have_link(pipeline.ref) expect(page).to have_content(pipeline.ref) @@ -305,6 +331,10 @@ describe 'Pipeline', :js do merge_request.all_pipelines.last end + before do + visit_pipeline + end + it 'shows the pipeline information' do within '.pipeline-info' do expect(page).to have_content("#{pipeline.statuses.count} jobs " \ @@ -356,6 +386,8 @@ describe 'Pipeline', :js do before do pipeline.update(user: user) + + visit_pipeline end it 'shows the pipeline information' do diff --git a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb index b1a7f167977..efb7b01f5ad 100644 --- a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb +++ b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb @@ -136,7 +136,7 @@ describe "User creates wiki page" do click_button("Create page") end - page.within ".wiki" do + page.within ".md" do expect(page).to have_selector(".katex", count: 3).and have_content("2+2 is 4") end end diff --git a/spec/features/snippets/notes_on_personal_snippets_spec.rb b/spec/features/snippets/notes_on_personal_snippets_spec.rb index eeacaf5f72a..fc6726985ae 100644 --- a/spec/features/snippets/notes_on_personal_snippets_spec.rb +++ b/spec/features/snippets/notes_on_personal_snippets_spec.rb @@ -70,7 +70,7 @@ describe 'Comments on personal snippets', :js do fill_in 'note[note]', with: 'This is **awesome**!' find('.js-md-preview-button').click - page.within('.new-note .md-preview') do + page.within('.new-note .md-preview-holder') do expect(page).to have_content('This is awesome!') expect(page).to have_selector('strong') end diff --git a/spec/features/snippets/user_creates_snippet_spec.rb b/spec/features/snippets/user_creates_snippet_spec.rb index 879c46d7c4e..1c97d5ec5b4 100644 --- a/spec/features/snippets/user_creates_snippet_spec.rb +++ b/spec/features/snippets/user_creates_snippet_spec.rb @@ -37,7 +37,7 @@ describe 'User creates snippet', :js do dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif') find('.js-md-preview-button').click - page.within('#new_personal_snippet .md-preview') do + page.within('#new_personal_snippet .md-preview-holder') do expect(page).to have_content('My Snippet') link = find('a.no-attachment-icon img[alt="banana_sample"]')['src'] diff --git a/spec/features/tags/master_deletes_tag_spec.rb b/spec/features/tags/master_deletes_tag_spec.rb index 8d567e925ef..bdbbe645779 100644 --- a/spec/features/tags/master_deletes_tag_spec.rb +++ b/spec/features/tags/master_deletes_tag_spec.rb @@ -37,7 +37,7 @@ describe 'Maintainer deletes tag' do context 'when pre-receive hook fails', :js do before do allow_any_instance_of(Gitlab::GitalyClient::OperationService).to receive(:rm_tag) - .and_raise(Gitlab::Git::PreReceiveError, 'Do not delete tags') + .and_raise(Gitlab::Git::PreReceiveError, 'GitLab: Do not delete tags') end it 'shows the error message' do diff --git a/spec/features/task_lists_spec.rb b/spec/features/task_lists_spec.rb index 6fe840dccf6..33d9c10f5e8 100644 --- a/spec/features/task_lists_spec.rb +++ b/spec/features/task_lists_spec.rb @@ -79,7 +79,7 @@ describe 'Task Lists' do visit_issue(project, issue) wait_for_requests - expect(page).to have_selector(".wiki .task-list .task-list-item .task-list-item-checkbox") + expect(page).to have_selector(".md .task-list .task-list-item .task-list-item-checkbox") expect(page).to have_selector('a.btn-close') end @@ -87,14 +87,14 @@ describe 'Task Lists' do visit_issue(project, issue) wait_for_requests - expect(page).to have_selector(".wiki .task-list .task-list-item .task-list-item-checkbox") + expect(page).to have_selector(".md .task-list .task-list-item .task-list-item-checkbox") logout(:user) login_as(user2) visit current_path wait_for_requests - expect(page).to have_selector(".wiki .task-list .task-list-item .task-list-item-checkbox") + expect(page).to have_selector(".md .task-list .task-list-item .task-list-item-checkbox") end it 'provides a summary on Issues#index' do @@ -231,7 +231,7 @@ describe 'Task Lists' do container = '.detail-page-description .description.js-task-list-container' expect(page).to have_selector(container) - expect(page).to have_selector("#{container} .wiki .task-list .task-list-item .task-list-item-checkbox") + expect(page).to have_selector("#{container} .md .task-list .task-list-item .task-list-item-checkbox") expect(page).to have_selector("#{container} .js-task-list-field") expect(page).to have_selector('form.js-issuable-update') expect(page).to have_selector('a.btn-close') diff --git a/spec/features/users/login_spec.rb b/spec/features/users/login_spec.rb index 368a814874f..9d5780d29b0 100644 --- a/spec/features/users/login_spec.rb +++ b/spec/features/users/login_spec.rb @@ -447,7 +447,7 @@ describe 'Login' do 'You can leave Group 1 and leave Group 2. '\ 'You need to do this '\ 'before '\ - "#{(Time.zone.now + 2.days).strftime("%a, %-d %b %Y %H:%M:%S %z")}" + "#{(Time.zone.now + 2.days).strftime("%a, %d %b %Y %H:%M:%S %z")}" ) end end diff --git a/spec/frontend/.eslintrc.yml b/spec/frontend/.eslintrc.yml index 046215e4c93..054dc27cda6 100644 --- a/spec/frontend/.eslintrc.yml +++ b/spec/frontend/.eslintrc.yml @@ -2,8 +2,13 @@ env: jest/globals: true plugins: -- jest + - jest settings: import/resolver: jest: - jestConfigFile: "jest.config.js" + jestConfigFile: 'jest.config.js' +globals: + getJSONFixture: false + loadFixtures: false + preloadFixtures: false + setFixtures: false diff --git a/spec/frontend/environment.js b/spec/frontend/environment.js new file mode 100644 index 00000000000..34df8019a2e --- /dev/null +++ b/spec/frontend/environment.js @@ -0,0 +1,52 @@ +/* eslint-disable import/no-commonjs */ + +const { ErrorWithStack } = require('jest-util'); +const JSDOMEnvironment = require('jest-environment-jsdom'); + +class CustomEnvironment extends JSDOMEnvironment { + constructor(config, context) { + super(config, context); + + Object.assign(context.console, { + error(...args) { + throw new ErrorWithStack( + `Unexpected call of console.error() with:\n\n${args.join(', ')}`, + this.error, + ); + }, + + warn(...args) { + throw new ErrorWithStack( + `Unexpected call of console.warn() with:\n\n${args.join(', ')}`, + this.warn, + ); + }, + }); + + const { testEnvironmentOptions } = config; + this.global.gon = { + ee: testEnvironmentOptions.IS_EE, + }; + + this.rejectedPromises = []; + + this.global.promiseRejectionHandler = error => { + this.rejectedPromises.push(error); + }; + } + + async teardown() { + await new Promise(setImmediate); + + if (this.rejectedPromises.length > 0) { + throw new ErrorWithStack( + `Unhandled Promise rejections: ${this.rejectedPromises.join(', ')}`, + this.teardown, + ); + } + + await super.teardown(); + } +} + +module.exports = CustomEnvironment; diff --git a/spec/frontend/helpers/class_spec_helper.js b/spec/frontend/helpers/class_spec_helper.js new file mode 100644 index 00000000000..7a60d33b471 --- /dev/null +++ b/spec/frontend/helpers/class_spec_helper.js @@ -0,0 +1,9 @@ +export default class ClassSpecHelper { + static itShouldBeAStaticMethod(base, method) { + return it('should be a static method', () => { + expect(Object.prototype.hasOwnProperty.call(base, method)).toBeTruthy(); + }); + } +} + +window.ClassSpecHelper = ClassSpecHelper; diff --git a/spec/frontend/helpers/fixtures.js b/spec/frontend/helpers/fixtures.js index f96f27c4d80..f0351aa31c6 100644 --- a/spec/frontend/helpers/fixtures.js +++ b/spec/frontend/helpers/fixtures.js @@ -1,24 +1,36 @@ -/* eslint-disable import/prefer-default-export, global-require, import/no-dynamic-require */ - import fs from 'fs'; import path from 'path'; -// jest-util is part of Jest -// eslint-disable-next-line import/no-extraneous-dependencies import { ErrorWithStack } from 'jest-util'; const fixturesBasePath = path.join(process.cwd(), 'spec', 'javascripts', 'fixtures'); -export function getJSONFixture(relativePath, ee = false) { - const absolutePath = path.join(fixturesBasePath, ee ? 'ee' : '', relativePath); +export function getFixture(relativePath) { + const absolutePath = path.join(fixturesBasePath, relativePath); if (!fs.existsSync(absolutePath)) { throw new ErrorWithStack( `Fixture file ${relativePath} does not exist. Did you run bin/rake karma:fixtures?`, - getJSONFixture, + getFixture, ); } - return require(absolutePath); + return fs.readFileSync(absolutePath, 'utf8'); } + +export const getJSONFixture = relativePath => JSON.parse(getFixture(relativePath)); + +export const resetHTMLFixture = () => { + document.body.textContent = ''; +}; + +export const setHTMLFixture = (htmlContent, resetHook = afterEach) => { + document.body.outerHTML = htmlContent; + resetHook(resetHTMLFixture); +}; + +export const loadHTMLFixture = (relativePath, resetHook = afterEach) => { + const fileContent = getFixture(relativePath); + setHTMLFixture(fileContent, resetHook); +}; diff --git a/spec/frontend/helpers/locale_helper.js b/spec/frontend/helpers/locale_helper.js new file mode 100644 index 00000000000..80047b06003 --- /dev/null +++ b/spec/frontend/helpers/locale_helper.js @@ -0,0 +1,11 @@ +/* eslint-disable import/prefer-default-export */ + +export const setLanguage = languageCode => { + const htmlElement = document.querySelector('html'); + + if (languageCode) { + htmlElement.setAttribute('lang', languageCode); + } else { + htmlElement.removeAttribute('lang'); + } +}; diff --git a/spec/frontend/helpers/scroll_into_view_promise.js b/spec/frontend/helpers/scroll_into_view_promise.js new file mode 100644 index 00000000000..0edea2103da --- /dev/null +++ b/spec/frontend/helpers/scroll_into_view_promise.js @@ -0,0 +1,28 @@ +export default function scrollIntoViewPromise(intersectionTarget, timeout = 100, maxTries = 5) { + return new Promise((resolve, reject) => { + let intersectionObserver; + let retry = 0; + + const intervalId = setInterval(() => { + if (retry >= maxTries) { + intersectionObserver.disconnect(); + clearInterval(intervalId); + reject(new Error(`Could not scroll target into viewPort within ${timeout * maxTries} ms`)); + } + retry += 1; + intersectionTarget.scrollIntoView(); + }, timeout); + + intersectionObserver = new IntersectionObserver(entries => { + if (entries[0].isIntersecting) { + intersectionObserver.disconnect(); + clearInterval(intervalId); + resolve(); + } + }); + + intersectionObserver.observe(intersectionTarget); + + intersectionTarget.scrollIntoView(); + }); +} diff --git a/spec/frontend/helpers/set_timeout_promise_helper.js b/spec/frontend/helpers/set_timeout_promise_helper.js new file mode 100644 index 00000000000..47087619187 --- /dev/null +++ b/spec/frontend/helpers/set_timeout_promise_helper.js @@ -0,0 +1,4 @@ +export default (time = 0) => + new Promise(resolve => { + setTimeout(resolve, time); + }); diff --git a/spec/frontend/helpers/user_mock_data_helper.js b/spec/frontend/helpers/user_mock_data_helper.js new file mode 100644 index 00000000000..6999fa1f8a1 --- /dev/null +++ b/spec/frontend/helpers/user_mock_data_helper.js @@ -0,0 +1,14 @@ +export default { + createNumberRandomUsers(numberUsers) { + const users = []; + for (let i = 0; i < numberUsers; i += 1) { + users.push({ + avatar: 'https://gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + id: i + 1, + name: `GitLab User ${i}`, + username: `gitlab${i}`, + }); + } + return users; + }, +}; diff --git a/spec/frontend/helpers/vue_component_helper.js b/spec/frontend/helpers/vue_component_helper.js new file mode 100644 index 00000000000..e0fe18e5560 --- /dev/null +++ b/spec/frontend/helpers/vue_component_helper.js @@ -0,0 +1,18 @@ +/** + * Replaces line break with an empty space + * @param {*} data + */ +export const removeBreakLine = data => data.replace(/\r?\n|\r/g, ' '); + +/** + * Removes line breaks, spaces and trims the given text + * @param {String} str + * @returns {String} + */ +export const trimText = str => + str + .replace(/\r?\n|\r/g, '') + .replace(/\s\s+/g, ' ') + .trim(); + +export const removeWhitespace = str => str.replace(/\s\s+/g, ' '); diff --git a/spec/frontend/helpers/vue_resource_helper.js b/spec/frontend/helpers/vue_resource_helper.js new file mode 100644 index 00000000000..0f58af09933 --- /dev/null +++ b/spec/frontend/helpers/vue_resource_helper.js @@ -0,0 +1,11 @@ +// eslint-disable-next-line import/prefer-default-export +export const headersInterceptor = (request, next) => { + next(response => { + const headers = {}; + response.headers.forEach((value, key) => { + headers[key] = value; + }); + // eslint-disable-next-line no-param-reassign + response.headers = headers; + }); +}; diff --git a/spec/frontend/helpers/vue_test_utils_helper.js b/spec/frontend/helpers/vue_test_utils_helper.js new file mode 100644 index 00000000000..19e27388eeb --- /dev/null +++ b/spec/frontend/helpers/vue_test_utils_helper.js @@ -0,0 +1,19 @@ +/* eslint-disable import/prefer-default-export */ + +const vNodeContainsText = (vnode, text) => + (vnode.text && vnode.text.includes(text)) || + (vnode.children && vnode.children.filter(child => vNodeContainsText(child, text)).length); + +/** + * Determines whether a `shallowMount` Wrapper contains text + * within one of it's slots. This will also work on Wrappers + * acquired with `find()`, but only if it's parent Wrapper + * was shallowMounted. + * NOTE: Prefer checking the rendered output of a component + * wherever possible using something like `text()` instead. + * @param {Wrapper} shallowWrapper - Vue test utils wrapper (shallowMounted) + * @param {String} slotName + * @param {String} text + */ +export const shallowWrapperContainsSlotText = (shallowWrapper, slotName, text) => + !!shallowWrapper.vm.$slots[slotName].filter(vnode => vNodeContainsText(vnode, text)).length; diff --git a/spec/frontend/helpers/vuex_action_helper.js b/spec/frontend/helpers/vuex_action_helper.js new file mode 100644 index 00000000000..88652202a8e --- /dev/null +++ b/spec/frontend/helpers/vuex_action_helper.js @@ -0,0 +1,104 @@ +const noop = () => {}; + +/** + * Helper for testing action with expected mutations inspired in + * https://vuex.vuejs.org/en/testing.html + * + * @param {Function} action to be tested + * @param {Object} payload will be provided to the action + * @param {Object} state will be provided to the action + * @param {Array} [expectedMutations=[]] mutations expected to be committed + * @param {Array} [expectedActions=[]] actions expected to be dispatched + * @param {Function} [done=noop] to be executed after the tests + * @return {Promise} + * + * @example + * testAction( + * actions.actionName, // action + * { }, // mocked payload + * state, //state + * // expected mutations + * [ + * { type: types.MUTATION} + * { type: types.MUTATION_1, payload: jasmine.any(Number)} + * ], + * // expected actions + * [ + * { type: 'actionName', payload: {param: 'foobar'}}, + * { type: 'actionName1'} + * ] + * done, + * ); + * + * @example + * testAction( + * actions.actionName, // action + * { }, // mocked payload + * state, //state + * [ { type: types.MUTATION} ], // expected mutations + * [], // expected actions + * ).then(done) + * .catch(done.fail); + */ +export default ( + action, + payload, + state, + expectedMutations = [], + expectedActions = [], + done = noop, +) => { + const mutations = []; + const actions = []; + + // mock commit + const commit = (type, mutationPayload) => { + const mutation = { type }; + + if (typeof mutationPayload !== 'undefined') { + mutation.payload = mutationPayload; + } + + mutations.push(mutation); + }; + + // mock dispatch + const dispatch = (type, actionPayload) => { + const dispatchedAction = { type }; + + if (typeof actionPayload !== 'undefined') { + dispatchedAction.payload = actionPayload; + } + + actions.push(dispatchedAction); + }; + + const validateResults = () => { + expect({ + mutations, + actions, + }).toEqual({ + mutations: expectedMutations, + actions: expectedActions, + }); + done(); + }; + + const result = action( + { commit, state, dispatch, rootState: state, rootGetters: state, getters: state }, + payload, + ); + + return new Promise(resolve => { + setImmediate(resolve); + }) + .then(() => result) + .catch(error => { + validateResults(); + throw error; + }) + .then(data => { + validateResults(); + return data; + }); +}; diff --git a/spec/frontend/helpers/wait_for_attribute_change.js b/spec/frontend/helpers/wait_for_attribute_change.js new file mode 100644 index 00000000000..8f22d569222 --- /dev/null +++ b/spec/frontend/helpers/wait_for_attribute_change.js @@ -0,0 +1,16 @@ +export default (domElement, attributes, timeout = 1500) => + new Promise((resolve, reject) => { + let observer; + const timeoutId = setTimeout(() => { + observer.disconnect(); + reject(new Error(`Could not see an attribute update within ${timeout} ms`)); + }, timeout); + + observer = new MutationObserver(() => { + clearTimeout(timeoutId); + observer.disconnect(); + resolve(); + }); + + observer.observe(domElement, { attributes: true, attributeFilter: attributes }); + }); diff --git a/spec/frontend/helpers/wait_for_promises.js b/spec/frontend/helpers/wait_for_promises.js new file mode 100644 index 00000000000..1d2b53fc770 --- /dev/null +++ b/spec/frontend/helpers/wait_for_promises.js @@ -0,0 +1 @@ +export default () => new Promise(resolve => requestAnimationFrame(resolve)); diff --git a/spec/frontend/mr_popover/__snapshots__/mr_popover_spec.js.snap b/spec/frontend/mr_popover/__snapshots__/mr_popover_spec.js.snap new file mode 100644 index 00000000000..5f9f13d591d --- /dev/null +++ b/spec/frontend/mr_popover/__snapshots__/mr_popover_spec.js.snap @@ -0,0 +1,93 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`MR Popover loaded state matches the snapshot 1`] = ` +<glpopover-stub + boundary="viewport" + placement="top" + show="" + target="" +> + <div + class="mr-popover" + > + <div + class="d-flex align-items-center justify-content-between" + > + <div + class="d-inline-flex align-items-center" + > + <div + class="issuable-status-box status-box status-box-open" + > + + Open + + </div> + + <span + class="text-secondary" + > + Opened + <time> + just now + </time> + </span> + </div> + + <ciicon-stub + cssclasses="" + size="16" + status="[object Object]" + /> + </div> + + <h5 + class="my-2" + > + MR Title + </h5> + + <div + class="text-secondary" + > + + foo/bar!1 + + </div> + </div> +</glpopover-stub> +`; + +exports[`MR Popover shows skeleton-loader while apollo is loading 1`] = ` +<glpopover-stub + boundary="viewport" + placement="top" + show="" + target="" +> + <div + class="mr-popover" + > + <div> + <glskeletonloading-stub + class="animation-container-small mt-1" + lines="1" + /> + </div> + + <h5 + class="my-2" + > + MR Title + </h5> + + <div + class="text-secondary" + > + + foo/bar!1 + + </div> + </div> +</glpopover-stub> +`; diff --git a/spec/frontend/mr_popover/mr_popover_spec.js b/spec/frontend/mr_popover/mr_popover_spec.js new file mode 100644 index 00000000000..79ed4163010 --- /dev/null +++ b/spec/frontend/mr_popover/mr_popover_spec.js @@ -0,0 +1,61 @@ +import MRPopover from '~/mr_popover/components/mr_popover'; +import { shallowMount } from '@vue/test-utils'; + +describe('MR Popover', () => { + let wrapper; + + beforeEach(() => { + wrapper = shallowMount(MRPopover, { + propsData: { + target: document.createElement('a'), + projectPath: 'foo/bar', + mergeRequestIID: '1', + mergeRequestTitle: 'MR Title', + }, + mocks: { + $apollo: { + loading: false, + }, + }, + }); + }); + + it('shows skeleton-loader while apollo is loading', () => { + wrapper.vm.$apollo.loading = true; + + expect(wrapper.element).toMatchSnapshot(); + }); + + describe('loaded state', () => { + it('matches the snapshot', () => { + wrapper.setData({ + mergeRequest: { + state: 'opened', + createdAt: new Date(), + headPipeline: { + detailedStatus: { + group: 'success', + status: 'status_success', + }, + }, + }, + }); + + expect(wrapper.element).toMatchSnapshot(); + }); + + it('does not show CI Icon if there is no pipeline data', () => { + wrapper.setData({ + mergeRequest: { + state: 'opened', + headPipeline: null, + stateHumanName: 'Open', + title: 'Merge Request Title', + createdAt: new Date(), + }, + }); + + expect(wrapper.contains('ciicon-stub')).toBe(false); + }); + }); +}); diff --git a/spec/javascripts/pages/admin/abuse_reports/abuse_reports_spec.js b/spec/frontend/pages/admin/abuse_reports/abuse_reports_spec.js index 23d07056925..1e0bc708c31 100644 --- a/spec/javascripts/pages/admin/abuse_reports/abuse_reports_spec.js +++ b/spec/frontend/pages/admin/abuse_reports/abuse_reports_spec.js @@ -3,7 +3,7 @@ import '~/lib/utils/text_utility'; import AbuseReports from '~/pages/admin/abuse_reports/abuse_reports'; describe('Abuse Reports', () => { - const FIXTURE = 'abuse_reports/abuse_reports_list.html.raw'; + const FIXTURE = 'abuse_reports/abuse_reports_list.html'; const MAX_MESSAGE_LENGTH = 500; let $messages; @@ -16,9 +16,9 @@ describe('Abuse Reports', () => { preloadFixtures(FIXTURE); - beforeEach(function() { + beforeEach(() => { loadFixtures(FIXTURE); - this.abuseReports = new AbuseReports(); + new AbuseReports(); // eslint-disable-line no-new $messages = $('.abuse-reports .message'); }); diff --git a/spec/javascripts/reports/components/report_section_spec.js b/spec/frontend/reports/components/report_section_spec.js index b02af8baaec..3b609484b9e 100644 --- a/spec/javascripts/reports/components/report_section_spec.js +++ b/spec/frontend/reports/components/report_section_spec.js @@ -1,6 +1,6 @@ import Vue from 'vue'; import reportSection from '~/reports/components/report_section.vue'; -import mountComponent, { mountComponentWithSlots } from 'spec/helpers/vue_mount_component_helper'; +import mountComponent, { mountComponentWithSlots } from 'helpers/vue_mount_component_helper'; describe('Report section', () => { let vm; diff --git a/spec/frontend/test_setup.js b/spec/frontend/test_setup.js index 006fc60ef57..c57e0e7cfc6 100644 --- a/spec/frontend/test_setup.js +++ b/spec/frontend/test_setup.js @@ -1,12 +1,19 @@ import Vue from 'vue'; +import * as jqueryMatchers from 'custom-jquery-matchers'; import Translate from '~/vue_shared/translate'; import axios from '~/lib/utils/axios_utils'; import { initializeTestTimeout } from './helpers/timeout'; +import { getJSONFixture, loadHTMLFixture, setHTMLFixture } from './helpers/fixtures'; -// wait for pending setTimeout()s -afterEach(() => { - jest.runAllTimers(); -}); +process.on('unhandledRejection', global.promiseRejectionHandler); + +afterEach(() => + // give Promises a bit more time so they fail the right test + new Promise(setImmediate).then(() => { + // wait for pending setTimeout()s + jest.runAllTimers(); + }), +); initializeTestTimeout(300); @@ -22,4 +29,31 @@ beforeEach(done => { done(); }); +Vue.config.devtools = false; +Vue.config.productionTip = false; + Vue.use(Translate); + +// workaround for JSDOM not supporting innerText +// see https://github.com/jsdom/jsdom/issues/1245 +Object.defineProperty(global.Element.prototype, 'innerText', { + get() { + return this.textContent; + }, + configurable: true, // make it so that it doesn't blow chunks on re-running tests with things like --watch +}); + +// convenience wrapper for migration from Karma +Object.assign(global, { + loadFixtures: loadHTMLFixture, + loadJSONFixtures: getJSONFixture, + preloadFixtures() {}, + setFixtures: setHTMLFixture, +}); + +// custom-jquery-matchers was written for an old Jest version, we need to make it compatible +Object.entries(jqueryMatchers).forEach(([matcherName, matcherFactory]) => { + expect.extend({ + [matcherName]: matcherFactory().compare, + }); +}); diff --git a/spec/graphql/types/ci/detailed_status_type_spec.rb b/spec/graphql/types/ci/detailed_status_type_spec.rb new file mode 100644 index 00000000000..a21162adb42 --- /dev/null +++ b/spec/graphql/types/ci/detailed_status_type_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe Types::Ci::DetailedStatusType do + it { expect(described_class.graphql_name).to eq('DetailedStatus') } + + it "has all fields" do + expect(described_class).to have_graphql_fields(:group, :icon, :favicon, + :details_path, :has_details, + :label, :text, :tooltip) + end +end diff --git a/spec/graphql/types/permission_types/issue_spec.rb b/spec/graphql/types/permission_types/issue_spec.rb index c3f84629aa2..f0fbeda202f 100644 --- a/spec/graphql/types/permission_types/issue_spec.rb +++ b/spec/graphql/types/permission_types/issue_spec.rb @@ -7,6 +7,8 @@ describe Types::PermissionTypes::Issue do :create_note, :reopen_issue ] - expect(described_class).to have_graphql_fields(expected_permissions) + expected_permissions.each do |permission| + expect(described_class).to have_graphql_field(permission) + end end end diff --git a/spec/graphql/types/permission_types/project_spec.rb b/spec/graphql/types/permission_types/project_spec.rb index 4288412eda3..4974995b587 100644 --- a/spec/graphql/types/permission_types/project_spec.rb +++ b/spec/graphql/types/permission_types/project_spec.rb @@ -13,6 +13,8 @@ describe Types::PermissionTypes::Project do :update_wiki, :destroy_wiki, :create_pages, :destroy_pages, :read_pages_content ] - expect(described_class).to have_graphql_fields(expected_permissions) + expected_permissions.each do |permission| + expect(described_class).to have_graphql_field(permission) + end end end diff --git a/spec/helpers/appearances_helper_spec.rb b/spec/helpers/appearances_helper_spec.rb index 8d717b968dd..a3511e078ce 100644 --- a/spec/helpers/appearances_helper_spec.rb +++ b/spec/helpers/appearances_helper_spec.rb @@ -65,12 +65,10 @@ describe AppearancesHelper do end describe '#brand_title' do - it 'returns the default CE title when no appearance is present' do - allow(helper) - .to receive(:current_appearance) - .and_return(nil) + it 'returns the default title when no appearance is present' do + allow(helper).to receive(:current_appearance).and_return(nil) - expect(helper.brand_title).to eq('GitLab Community Edition') + expect(helper.brand_title).to eq(helper.default_brand_title) end end end diff --git a/spec/helpers/auth_helper_spec.rb b/spec/helpers/auth_helper_spec.rb index 2ba8b3dbf22..aae515def0c 100644 --- a/spec/helpers/auth_helper_spec.rb +++ b/spec/helpers/auth_helper_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe AuthHelper do diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb index 540a8674ec2..91541a16c13 100644 --- a/spec/helpers/groups_helper_spec.rb +++ b/spec/helpers/groups_helper_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe GroupsHelper do diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb index 8b82dea2524..1d1446eaa30 100644 --- a/spec/helpers/issuables_helper_spec.rb +++ b/spec/helpers/issuables_helper_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe IssuablesHelper do @@ -176,7 +178,7 @@ describe IssuablesHelper do stub_commonmark_sourcepos_disabled end - it 'returns the correct json for an issue' do + it 'returns the correct data for an issue' do issue = create(:issue, author: user, description: 'issue text') @project = issue.project @@ -198,7 +200,7 @@ describe IssuablesHelper do initialDescriptionText: 'issue text', initialTaskStatus: '0 of 0 tasks completed' } - expect(helper.issuable_initial_data(issue)).to eq(expected_data) + expect(helper.issuable_initial_data(issue)).to match(hash_including(expected_data)) end end end diff --git a/spec/helpers/merge_requests_helper_spec.rb b/spec/helpers/merge_requests_helper_spec.rb index 885204062fe..193390d2f2c 100644 --- a/spec/helpers/merge_requests_helper_spec.rb +++ b/spec/helpers/merge_requests_helper_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe MergeRequestsHelper do diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index 291eafece94..37c63807c82 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProjectsHelper do diff --git a/spec/javascripts/activities_spec.js b/spec/javascripts/activities_spec.js index 068b8eb65bc..23b6de7e4e0 100644 --- a/spec/javascripts/activities_spec.js +++ b/spec/javascripts/activities_spec.js @@ -7,7 +7,7 @@ import Pager from '~/pager'; describe('Activities', () => { window.gon || (window.gon = {}); - const fixtureTemplate = 'static/event_filter.html.raw'; + const fixtureTemplate = 'static/event_filter.html'; const filters = [ { id: 'all', diff --git a/spec/javascripts/ajax_loading_spinner_spec.js b/spec/javascripts/ajax_loading_spinner_spec.js index 9389fc94f17..89195a4397f 100644 --- a/spec/javascripts/ajax_loading_spinner_spec.js +++ b/spec/javascripts/ajax_loading_spinner_spec.js @@ -2,7 +2,7 @@ import $ from 'jquery'; import AjaxLoadingSpinner from '~/ajax_loading_spinner'; describe('Ajax Loading Spinner', () => { - const fixtureTemplate = 'static/ajax_loading_spinner.html.raw'; + const fixtureTemplate = 'static/ajax_loading_spinner.html'; preloadFixtures(fixtureTemplate); beforeEach(() => { diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js index e5b5707dcef..e10df1b45e7 100644 --- a/spec/javascripts/awards_handler_spec.js +++ b/spec/javascripts/awards_handler_spec.js @@ -24,13 +24,13 @@ const lazyAssert = function(done, assertFn) { describe('AwardsHandler', function() { const emojiData = getJSONFixture('emojis/emojis.json'); - preloadFixtures('snippets/show.html.raw'); + preloadFixtures('snippets/show.html'); beforeEach(function(done) { mock = new MockAdapter(axios); mock.onGet(`/-/emojis/${EMOJI_VERSION}/emojis.json`).reply(200, emojiData); - loadFixtures('snippets/show.html.raw'); + loadFixtures('snippets/show.html'); loadAwardsHandler(true) .then(obj => { awardsHandler = obj; diff --git a/spec/javascripts/behaviors/quick_submit_spec.js b/spec/javascripts/behaviors/quick_submit_spec.js index 681463aab66..7af8c984841 100644 --- a/spec/javascripts/behaviors/quick_submit_spec.js +++ b/spec/javascripts/behaviors/quick_submit_spec.js @@ -4,10 +4,10 @@ import '~/behaviors/quick_submit'; describe('Quick Submit behavior', function() { const keydownEvent = (options = { keyCode: 13, metaKey: true }) => $.Event('keydown', options); - preloadFixtures('snippets/show.html.raw'); + preloadFixtures('snippets/show.html'); beforeEach(() => { - loadFixtures('snippets/show.html.raw'); + loadFixtures('snippets/show.html'); $('form').submit(e => { // Prevent a form submit from moving us off the testing page e.preventDefault(); diff --git a/spec/javascripts/behaviors/requires_input_spec.js b/spec/javascripts/behaviors/requires_input_spec.js index 1bde2bb3024..617fe49b059 100644 --- a/spec/javascripts/behaviors/requires_input_spec.js +++ b/spec/javascripts/behaviors/requires_input_spec.js @@ -3,10 +3,10 @@ import '~/behaviors/requires_input'; describe('requiresInput', () => { let submitButton; - preloadFixtures('branches/new_branch.html.raw'); + preloadFixtures('branches/new_branch.html'); beforeEach(() => { - loadFixtures('branches/new_branch.html.raw'); + loadFixtures('branches/new_branch.html'); submitButton = $('button[type="submit"]'); }); diff --git a/spec/javascripts/behaviors/shortcuts/shortcuts_issuable_spec.js b/spec/javascripts/behaviors/shortcuts/shortcuts_issuable_spec.js index 4843a0386b5..5e457a4e823 100644 --- a/spec/javascripts/behaviors/shortcuts/shortcuts_issuable_spec.js +++ b/spec/javascripts/behaviors/shortcuts/shortcuts_issuable_spec.js @@ -9,7 +9,7 @@ import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable'; const FORM_SELECTOR = '.js-main-target-form .js-vue-comment-form'; describe('ShortcutsIssuable', function() { - const fixtureName = 'snippets/show.html.raw'; + const fixtureName = 'snippets/show.html'; preloadFixtures(fixtureName); beforeAll(done => { diff --git a/spec/javascripts/blob/balsamiq/balsamiq_viewer_integration_spec.js b/spec/javascripts/blob/balsamiq/balsamiq_viewer_integration_spec.js index 5f027f59fcf..68b4f261617 100644 --- a/spec/javascripts/blob/balsamiq/balsamiq_viewer_integration_spec.js +++ b/spec/javascripts/blob/balsamiq/balsamiq_viewer_integration_spec.js @@ -6,10 +6,10 @@ describe('Balsamiq integration spec', () => { let endpoint; let balsamiqViewer; - preloadFixtures('static/balsamiq_viewer.html.raw'); + preloadFixtures('static/balsamiq_viewer.html'); beforeEach(() => { - loadFixtures('static/balsamiq_viewer.html.raw'); + loadFixtures('static/balsamiq_viewer.html'); container = document.getElementById('js-balsamiq-viewer'); balsamiqViewer = new BalsamiqViewer(container); diff --git a/spec/javascripts/blob/blob_file_dropzone_spec.js b/spec/javascripts/blob/blob_file_dropzone_spec.js index 432d8a65b0a..cab06a0a9be 100644 --- a/spec/javascripts/blob/blob_file_dropzone_spec.js +++ b/spec/javascripts/blob/blob_file_dropzone_spec.js @@ -2,10 +2,10 @@ import $ from 'jquery'; import BlobFileDropzone from '~/blob/blob_file_dropzone'; describe('BlobFileDropzone', function() { - preloadFixtures('blob/show.html.raw'); + preloadFixtures('blob/show.html'); beforeEach(() => { - loadFixtures('blob/show.html.raw'); + loadFixtures('blob/show.html'); const form = $('.js-upload-blob-form'); this.blobFileDropzone = new BlobFileDropzone(form, 'POST'); this.dropzone = $('.js-upload-blob-form .dropzone').get(0).dropzone; diff --git a/spec/javascripts/blob/notebook/index_spec.js b/spec/javascripts/blob/notebook/index_spec.js index 28d3b2f5ea3..6bb5bac007f 100644 --- a/spec/javascripts/blob/notebook/index_spec.js +++ b/spec/javascripts/blob/notebook/index_spec.js @@ -3,10 +3,10 @@ import axios from '~/lib/utils/axios_utils'; import renderNotebook from '~/blob/notebook'; describe('iPython notebook renderer', () => { - preloadFixtures('static/notebook_viewer.html.raw'); + preloadFixtures('static/notebook_viewer.html'); beforeEach(() => { - loadFixtures('static/notebook_viewer.html.raw'); + loadFixtures('static/notebook_viewer.html'); }); it('shows loading icon', () => { diff --git a/spec/javascripts/blob/pdf/index_spec.js b/spec/javascripts/blob/pdf/index_spec.js index be917a0613f..acf87580777 100644 --- a/spec/javascripts/blob/pdf/index_spec.js +++ b/spec/javascripts/blob/pdf/index_spec.js @@ -15,10 +15,10 @@ describe('PDF renderer', () => { } }; - preloadFixtures('static/pdf_viewer.html.raw'); + preloadFixtures('static/pdf_viewer.html'); beforeEach(() => { - loadFixtures('static/pdf_viewer.html.raw'); + loadFixtures('static/pdf_viewer.html'); viewer = document.getElementById('js-pdf-viewer'); viewer.dataset.endpoint = testPDF; }); diff --git a/spec/javascripts/blob/sketch/index_spec.js b/spec/javascripts/blob/sketch/index_spec.js index 2b1e81e9cbc..3d3129e10da 100644 --- a/spec/javascripts/blob/sketch/index_spec.js +++ b/spec/javascripts/blob/sketch/index_spec.js @@ -13,10 +13,10 @@ describe('Sketch viewer', () => { }); }; - preloadFixtures('static/sketch_viewer.html.raw'); + preloadFixtures('static/sketch_viewer.html'); beforeEach(() => { - loadFixtures('static/sketch_viewer.html.raw'); + loadFixtures('static/sketch_viewer.html'); }); describe('with error message', () => { diff --git a/spec/javascripts/blob/viewer/index_spec.js b/spec/javascripts/blob/viewer/index_spec.js index 93a942fe8d4..4ac15ca5aa2 100644 --- a/spec/javascripts/blob/viewer/index_spec.js +++ b/spec/javascripts/blob/viewer/index_spec.js @@ -9,12 +9,12 @@ describe('Blob viewer', () => { let blob; let mock; - preloadFixtures('snippets/show.html.raw'); + preloadFixtures('snippets/show.html'); beforeEach(() => { mock = new MockAdapter(axios); - loadFixtures('snippets/show.html.raw'); + loadFixtures('snippets/show.html'); $('#modal-upload-blob').remove(); blob = new BlobViewer(); diff --git a/spec/javascripts/boards/components/board_spec.js b/spec/javascripts/boards/components/board_spec.js index dee7841c088..6e6b3e6950b 100644 --- a/spec/javascripts/boards/components/board_spec.js +++ b/spec/javascripts/boards/components/board_spec.js @@ -9,7 +9,7 @@ describe('Board component', () => { let el; beforeEach(done => { - loadFixtures('boards/show.html.raw'); + loadFixtures('boards/show.html'); el = document.createElement('div'); document.body.appendChild(el); diff --git a/spec/javascripts/bootstrap_linked_tabs_spec.js b/spec/javascripts/bootstrap_linked_tabs_spec.js index c3e3d78ff63..1d21637ceae 100644 --- a/spec/javascripts/bootstrap_linked_tabs_spec.js +++ b/spec/javascripts/bootstrap_linked_tabs_spec.js @@ -1,10 +1,10 @@ import LinkedTabs from '~/lib/utils/bootstrap_linked_tabs'; describe('Linked Tabs', () => { - preloadFixtures('static/linked_tabs.html.raw'); + preloadFixtures('static/linked_tabs.html'); beforeEach(() => { - loadFixtures('static/linked_tabs.html.raw'); + loadFixtures('static/linked_tabs.html'); }); describe('when is initialized', () => { diff --git a/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js b/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js index 1fc0e206d5e..481b1a4d4b0 100644 --- a/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js +++ b/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js @@ -7,8 +7,8 @@ const VARIABLE_PATCH_ENDPOINT = 'http://test.host/frontend-fixtures/builds-proje const HIDE_CLASS = 'hide'; describe('AjaxFormVariableList', () => { - preloadFixtures('projects/ci_cd_settings.html.raw'); - preloadFixtures('projects/ci_cd_settings_with_variables.html.raw'); + preloadFixtures('projects/ci_cd_settings.html'); + preloadFixtures('projects/ci_cd_settings_with_variables.html'); let container; let saveButton; @@ -18,7 +18,7 @@ describe('AjaxFormVariableList', () => { let ajaxVariableList; beforeEach(() => { - loadFixtures('projects/ci_cd_settings.html.raw'); + loadFixtures('projects/ci_cd_settings.html'); container = document.querySelector('.js-ci-variable-list-section'); mock = new MockAdapter(axios); @@ -168,7 +168,7 @@ describe('AjaxFormVariableList', () => { describe('updateRowsWithPersistedVariables', () => { beforeEach(() => { - loadFixtures('projects/ci_cd_settings_with_variables.html.raw'); + loadFixtures('projects/ci_cd_settings_with_variables.html'); container = document.querySelector('.js-ci-variable-list-section'); const ajaxVariableListEl = document.querySelector('.js-ci-variable-list-section'); diff --git a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js index bef59b86d0c..70f49469300 100644 --- a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js +++ b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js @@ -5,9 +5,9 @@ import getSetTimeoutPromise from 'spec/helpers/set_timeout_promise_helper'; const HIDE_CLASS = 'hide'; describe('VariableList', () => { - preloadFixtures('pipeline_schedules/edit.html.raw'); - preloadFixtures('pipeline_schedules/edit_with_variables.html.raw'); - preloadFixtures('projects/ci_cd_settings.html.raw'); + preloadFixtures('pipeline_schedules/edit.html'); + preloadFixtures('pipeline_schedules/edit_with_variables.html'); + preloadFixtures('projects/ci_cd_settings.html'); let $wrapper; let variableList; @@ -15,7 +15,7 @@ describe('VariableList', () => { describe('with only key/value inputs', () => { describe('with no variables', () => { beforeEach(() => { - loadFixtures('pipeline_schedules/edit.html.raw'); + loadFixtures('pipeline_schedules/edit.html'); $wrapper = $('.js-ci-variable-list-section'); variableList = new VariableList({ @@ -82,7 +82,7 @@ describe('VariableList', () => { describe('with persisted variables', () => { beforeEach(() => { - loadFixtures('pipeline_schedules/edit_with_variables.html.raw'); + loadFixtures('pipeline_schedules/edit_with_variables.html'); $wrapper = $('.js-ci-variable-list-section'); variableList = new VariableList({ @@ -115,7 +115,7 @@ describe('VariableList', () => { describe('with all inputs(key, value, protected)', () => { beforeEach(() => { - loadFixtures('projects/ci_cd_settings.html.raw'); + loadFixtures('projects/ci_cd_settings.html'); $wrapper = $('.js-ci-variable-list-section'); $wrapper.find('.js-ci-variable-input-protected').attr('data-default', 'false'); @@ -149,7 +149,7 @@ describe('VariableList', () => { describe('toggleEnableRow method', () => { beforeEach(() => { - loadFixtures('pipeline_schedules/edit_with_variables.html.raw'); + loadFixtures('pipeline_schedules/edit_with_variables.html'); $wrapper = $('.js-ci-variable-list-section'); variableList = new VariableList({ @@ -198,7 +198,7 @@ describe('VariableList', () => { describe('hideValues', () => { beforeEach(() => { - loadFixtures('projects/ci_cd_settings.html.raw'); + loadFixtures('projects/ci_cd_settings.html'); $wrapper = $('.js-ci-variable-list-section'); variableList = new VariableList({ diff --git a/spec/javascripts/ci_variable_list/native_form_variable_list_spec.js b/spec/javascripts/ci_variable_list/native_form_variable_list_spec.js index 997d0d54d79..4982b68fa81 100644 --- a/spec/javascripts/ci_variable_list/native_form_variable_list_spec.js +++ b/spec/javascripts/ci_variable_list/native_form_variable_list_spec.js @@ -2,12 +2,12 @@ import $ from 'jquery'; import setupNativeFormVariableList from '~/ci_variable_list/native_form_variable_list'; describe('NativeFormVariableList', () => { - preloadFixtures('pipeline_schedules/edit.html.raw'); + preloadFixtures('pipeline_schedules/edit.html'); let $wrapper; beforeEach(() => { - loadFixtures('pipeline_schedules/edit.html.raw'); + loadFixtures('pipeline_schedules/edit.html'); $wrapper = $('.js-ci-variable-list-section'); setupNativeFormVariableList({ diff --git a/spec/javascripts/clusters/clusters_bundle_spec.js b/spec/javascripts/clusters/clusters_bundle_spec.js index 71f16dc259e..0d3dcc29f22 100644 --- a/spec/javascripts/clusters/clusters_bundle_spec.js +++ b/spec/javascripts/clusters/clusters_bundle_spec.js @@ -9,10 +9,10 @@ import getSetTimeoutPromise from 'spec/helpers/set_timeout_promise_helper'; describe('Clusters', () => { let cluster; - preloadFixtures('clusters/show_cluster.html.raw'); + preloadFixtures('clusters/show_cluster.html'); beforeEach(() => { - loadFixtures('clusters/show_cluster.html.raw'); + loadFixtures('clusters/show_cluster.html'); cluster = new Clusters(); }); diff --git a/spec/javascripts/clusters/components/applications_spec.js b/spec/javascripts/clusters/components/applications_spec.js index 790e4b9602c..0f8153ad493 100644 --- a/spec/javascripts/clusters/components/applications_spec.js +++ b/spec/javascripts/clusters/components/applications_spec.js @@ -79,7 +79,7 @@ describe('Applications', () => { }); it('renders a row for GitLab Runner', () => { - expect(vm.$el.querySelector('.js-cluster-application-row-runner')).toBeNull(); + expect(vm.$el.querySelector('.js-cluster-application-row-runner')).not.toBeNull(); }); it('renders a row for Jupyter', () => { diff --git a/spec/javascripts/collapsed_sidebar_todo_spec.js b/spec/javascripts/collapsed_sidebar_todo_spec.js index dc5737558c0..bb90e53e525 100644 --- a/spec/javascripts/collapsed_sidebar_todo_spec.js +++ b/spec/javascripts/collapsed_sidebar_todo_spec.js @@ -6,7 +6,7 @@ import Sidebar from '~/right_sidebar'; import timeoutPromise from './helpers/set_timeout_promise_helper'; describe('Issuable right sidebar collapsed todo toggle', () => { - const fixtureName = 'issues/open-issue.html.raw'; + const fixtureName = 'issues/open-issue.html'; const jsonFixtureName = 'todos/todos.json'; let mock; diff --git a/spec/javascripts/create_item_dropdown_spec.js b/spec/javascripts/create_item_dropdown_spec.js index 9cf72d7c55b..a814952faab 100644 --- a/spec/javascripts/create_item_dropdown_spec.js +++ b/spec/javascripts/create_item_dropdown_spec.js @@ -20,7 +20,7 @@ const DROPDOWN_ITEM_DATA = [ ]; describe('CreateItemDropdown', () => { - preloadFixtures('static/create_item_dropdown.html.raw'); + preloadFixtures('static/create_item_dropdown.html'); let $wrapperEl; let createItemDropdown; @@ -44,7 +44,7 @@ describe('CreateItemDropdown', () => { } beforeEach(() => { - loadFixtures('static/create_item_dropdown.html.raw'); + loadFixtures('static/create_item_dropdown.html'); $wrapperEl = $('.js-create-item-dropdown-fixture-root'); }); diff --git a/spec/javascripts/diffs/components/diff_file_header_spec.js b/spec/javascripts/diffs/components/diff_file_header_spec.js index 66c5b17b825..6614069f44d 100644 --- a/spec/javascripts/diffs/components/diff_file_header_spec.js +++ b/spec/javascripts/diffs/components/diff_file_header_spec.js @@ -3,7 +3,7 @@ import Vuex from 'vuex'; import diffsModule from '~/diffs/store/modules'; import notesModule from '~/notes/stores/modules'; import DiffFileHeader from '~/diffs/components/diff_file_header.vue'; -import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; +import mountComponent, { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import diffDiscussionsMockData from '../mock_data/diff_discussions'; import { diffViewerModes } from '~/ide/constants'; @@ -23,9 +23,6 @@ describe('diff_file_header', () => { }); beforeEach(() => { - gon.features = { - expandDiffFullFile: true, - }; const diffFile = diffDiscussionMock.diff_file; diffFile.added_lines = 2; @@ -252,6 +249,75 @@ describe('diff_file_header', () => { expect(vm.$emit).not.toHaveBeenCalled(); }); }); + + describe('handleFileNameClick', () => { + let e; + + beforeEach(() => { + e = { preventDefault: () => {} }; + spyOn(e, 'preventDefault'); + }); + + describe('when file name links to other page', () => { + it('does not call preventDefault if submodule tree url exists', () => { + vm = mountComponent(Component, { + ...props, + diffFile: { ...props.diffFile, submodule_tree_url: 'foobar.com' }, + }); + + vm.handleFileNameClick(e); + + expect(e.preventDefault).not.toHaveBeenCalled(); + }); + + it('does not call preventDefault if submodule_link exists', () => { + vm = mountComponent(Component, { + ...props, + diffFile: { ...props.diffFile, submodule_link: 'foobar.com' }, + }); + vm.handleFileNameClick(e); + + expect(e.preventDefault).not.toHaveBeenCalled(); + }); + + it('does not call preventDefault if discussionPath exists', () => { + vm = mountComponent(Component, { + ...props, + discussionPath: 'Foo bar', + }); + + vm.handleFileNameClick(e); + + expect(e.preventDefault).not.toHaveBeenCalled(); + }); + }); + + describe('scrolling to diff', () => { + let scrollToElement; + let el; + + beforeEach(() => { + el = document.createElement('div'); + spyOn(document, 'querySelector').and.returnValue(el); + scrollToElement = spyOnDependency(DiffFileHeader, 'scrollToElement'); + vm = mountComponent(Component, props); + + vm.handleFileNameClick(e); + }); + + it('calls scrollToElement with file content', () => { + expect(scrollToElement).toHaveBeenCalledWith(el); + }); + + it('element adds the content id to the window location', () => { + expect(window.location.hash).toContain(props.diffFile.file_hash); + }); + + it('calls preventDefault when button does not link to other page', () => { + expect(e.preventDefault).toHaveBeenCalled(); + }); + }); + }); }); describe('template', () => { diff --git a/spec/javascripts/diffs/mock_data/diff_discussions.js b/spec/javascripts/diffs/mock_data/diff_discussions.js index fd5dd611383..711ab543411 100644 --- a/spec/javascripts/diffs/mock_data/diff_discussions.js +++ b/spec/javascripts/diffs/mock_data/diff_discussions.js @@ -496,7 +496,7 @@ export default { { text: 'line', rich_text: - '<tr class="line_holder new" id="">\n<td class="diff-line-num new old_line" data-linenumber="1">\n \n</td>\n<td class="diff-line-num new new_line" data-linenumber="1">\n1\n</td>\n<td class="line_content new noteable_line"><span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n</td>\n</tr>\n<tr class="line_holder new" id="">\n<td class="diff-line-num new old_line" data-linenumber="1">\n \n</td>\n<td class="diff-line-num new new_line" data-linenumber="2">\n2\n</td>\n<td class="line_content new noteable_line"><span id="LC2" class="line" lang="plaintext"></span>\n</td>\n</tr>\n', + '<tr class="line_holder new" id="">\n<td class="diff-line-num new old_line" data-linenumber="1">\n \n</td>\n<td class="diff-line-num new new_line" data-linenumber="1">\n1\n</td>\n<td class="line_content new"><span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n</td>\n</tr>\n<tr class="line_holder new" id="">\n<td class="diff-line-num new old_line" data-linenumber="1">\n \n</td>\n<td class="diff-line-num new new_line" data-linenumber="2">\n2\n</td>\n<td class="line_content new"><span id="LC2" class="line" lang="plaintext"></span>\n</td>\n</tr>\n', can_receive_suggestion: true, line_code: '6f209374f7e565f771b95720abf46024c41d1885_1_1', type: 'new', diff --git a/spec/javascripts/filtered_search/dropdown_user_spec.js b/spec/javascripts/filtered_search/dropdown_user_spec.js index e8fcc8592eb..f764800fff0 100644 --- a/spec/javascripts/filtered_search/dropdown_user_spec.js +++ b/spec/javascripts/filtered_search/dropdown_user_spec.js @@ -72,7 +72,7 @@ describe('Dropdown User', () => { }); describe('hideCurrentUser', () => { - const fixtureTemplate = 'issues/issue_list.html.raw'; + const fixtureTemplate = 'issues/issue_list.html'; preloadFixtures(fixtureTemplate); let dropdown; diff --git a/spec/javascripts/filtered_search/dropdown_utils_spec.js b/spec/javascripts/filtered_search/dropdown_utils_spec.js index cfd0b96ec43..62d1bd69635 100644 --- a/spec/javascripts/filtered_search/dropdown_utils_spec.js +++ b/spec/javascripts/filtered_search/dropdown_utils_spec.js @@ -4,7 +4,7 @@ import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered import FilteredSearchSpecHelper from '../helpers/filtered_search_spec_helper'; describe('Dropdown Utils', () => { - const issueListFixture = 'issues/issue_list.html.raw'; + const issueListFixture = 'issues/issue_list.html'; preloadFixtures(issueListFixture); describe('getEscapedText', () => { diff --git a/spec/javascripts/fixtures/.gitignore b/spec/javascripts/fixtures/.gitignore index 0c35cdd778e..2507c8e7263 100644 --- a/spec/javascripts/fixtures/.gitignore +++ b/spec/javascripts/fixtures/.gitignore @@ -1,2 +1,3 @@ *.html.raw +*.html *.json diff --git a/spec/javascripts/fixtures/abuse_reports.rb b/spec/javascripts/fixtures/abuse_reports.rb index 387858cba77..54b6419bcdb 100644 --- a/spec/javascripts/fixtures/abuse_reports.rb +++ b/spec/javascripts/fixtures/abuse_reports.rb @@ -18,7 +18,7 @@ describe Admin::AbuseReportsController, '(JavaScript fixtures)', type: :controll sign_in(admin) end - it 'abuse_reports/abuse_reports_list.html.raw' do |example| + it 'abuse_reports/abuse_reports_list.html' do |example| get :index expect(response).to be_success diff --git a/spec/javascripts/fixtures/admin_users.rb b/spec/javascripts/fixtures/admin_users.rb index 9989ac4fff2..76dbdf603da 100644 --- a/spec/javascripts/fixtures/admin_users.rb +++ b/spec/javascripts/fixtures/admin_users.rb @@ -17,7 +17,7 @@ describe Admin::UsersController, '(JavaScript fixtures)', type: :controller do clean_frontend_fixtures('admin/users') end - it 'admin/users/new_with_internal_user_regex.html.raw' do |example| + it 'admin/users/new_with_internal_user_regex.html' do |example| stub_application_setting(user_default_external: true) stub_application_setting(user_default_internal_regex: '^(?:(?!\.ext@).)*$\r?') diff --git a/spec/javascripts/fixtures/application_settings.rb b/spec/javascripts/fixtures/application_settings.rb index a9d3043f73d..c535e598e12 100644 --- a/spec/javascripts/fixtures/application_settings.rb +++ b/spec/javascripts/fixtures/application_settings.rb @@ -23,7 +23,7 @@ describe Admin::ApplicationSettingsController, '(JavaScript fixtures)', type: :c remove_repository(project) end - it 'application_settings/accounts_and_limit.html.raw' do |example| + it 'application_settings/accounts_and_limit.html' do |example| stub_application_setting(user_default_external: false) get :show diff --git a/spec/javascripts/fixtures/blob.rb b/spec/javascripts/fixtures/blob.rb index cd66d98f92a..db7749bc000 100644 --- a/spec/javascripts/fixtures/blob.rb +++ b/spec/javascripts/fixtures/blob.rb @@ -22,7 +22,7 @@ describe Projects::BlobController, '(JavaScript fixtures)', type: :controller do remove_repository(project) end - it 'blob/show.html.raw' do |example| + it 'blob/show.html' do |example| get(:show, params: { namespace_id: project.namespace, project_id: project, diff --git a/spec/javascripts/fixtures/boards.rb b/spec/javascripts/fixtures/boards.rb index 1d675e008ba..c4390e89578 100644 --- a/spec/javascripts/fixtures/boards.rb +++ b/spec/javascripts/fixtures/boards.rb @@ -17,7 +17,7 @@ describe Projects::BoardsController, '(JavaScript fixtures)', type: :controller sign_in(admin) end - it 'boards/show.html.raw' do |example| + it 'boards/show.html' do |example| get(:index, params: { namespace_id: project.namespace, project_id: project diff --git a/spec/javascripts/fixtures/branches.rb b/spec/javascripts/fixtures/branches.rb index 3cc713ef90f..5d2d6c7ec0e 100644 --- a/spec/javascripts/fixtures/branches.rb +++ b/spec/javascripts/fixtures/branches.rb @@ -21,7 +21,7 @@ describe Projects::BranchesController, '(JavaScript fixtures)', type: :controlle remove_repository(project) end - it 'branches/new_branch.html.raw' do |example| + it 'branches/new_branch.html' do |example| get :new, params: { namespace_id: project.namespace.to_param, project_id: project diff --git a/spec/javascripts/fixtures/clusters.rb b/spec/javascripts/fixtures/clusters.rb index 69dbe54ffc2..8ebd8a41366 100644 --- a/spec/javascripts/fixtures/clusters.rb +++ b/spec/javascripts/fixtures/clusters.rb @@ -22,7 +22,7 @@ describe Projects::ClustersController, '(JavaScript fixtures)', type: :controlle remove_repository(project) end - it 'clusters/show_cluster.html.raw' do |example| + it 'clusters/show_cluster.html' do |example| get :show, params: { namespace_id: project.namespace.to_param, project_id: project, diff --git a/spec/javascripts/fixtures/commit.rb b/spec/javascripts/fixtures/commit.rb index 295f13b34a4..ab10f559e4b 100644 --- a/spec/javascripts/fixtures/commit.rb +++ b/spec/javascripts/fixtures/commit.rb @@ -19,7 +19,7 @@ describe Projects::CommitController, '(JavaScript fixtures)', type: :controller allow(SecureRandom).to receive(:hex).and_return('securerandomhex:thereisnospoon') end - it 'commit/show.html.raw' do |example| + it 'commit/show.html' do |example| params = { namespace_id: project.namespace, project_id: project, diff --git a/spec/javascripts/fixtures/groups.rb b/spec/javascripts/fixtures/groups.rb index 03136f4e661..16e31028b05 100644 --- a/spec/javascripts/fixtures/groups.rb +++ b/spec/javascripts/fixtures/groups.rb @@ -18,7 +18,7 @@ describe 'Groups (JavaScript fixtures)', type: :controller do end describe GroupsController, '(JavaScript fixtures)', type: :controller do - it 'groups/edit.html.raw' do |example| + it 'groups/edit.html' do |example| get :edit, params: { id: group } expect(response).to be_success @@ -27,7 +27,7 @@ describe 'Groups (JavaScript fixtures)', type: :controller do end describe Groups::Settings::CiCdController, '(JavaScript fixtures)', type: :controller do - it 'groups/ci_cd_settings.html.raw' do |example| + it 'groups/ci_cd_settings.html' do |example| get :show, params: { group_id: group } expect(response).to be_success diff --git a/spec/javascripts/fixtures/issues.rb b/spec/javascripts/fixtures/issues.rb index 9b8e90c2a43..645b3aa788a 100644 --- a/spec/javascripts/fixtures/issues.rb +++ b/spec/javascripts/fixtures/issues.rb @@ -21,26 +21,26 @@ describe Projects::IssuesController, '(JavaScript fixtures)', type: :controller remove_repository(project) end - it 'issues/open-issue.html.raw' do |example| + it 'issues/open-issue.html' do |example| render_issue(example.description, create(:issue, project: project)) end - it 'issues/closed-issue.html.raw' do |example| + it 'issues/closed-issue.html' do |example| render_issue(example.description, create(:closed_issue, project: project)) end - it 'issues/issue-with-task-list.html.raw' do |example| + it 'issues/issue-with-task-list.html' do |example| issue = create(:issue, project: project, description: '- [ ] Task List Item') render_issue(example.description, issue) end - it 'issues/issue_with_comment.html.raw' do |example| + it 'issues/issue_with_comment.html' do |example| issue = create(:issue, project: project) create(:note, project: project, noteable: issue, note: '- [ ] Task List Item').save render_issue(example.description, issue) end - it 'issues/issue_list.html.raw' do |example| + it 'issues/issue_list.html' do |example| create(:issue, project: project) get :index, params: { diff --git a/spec/javascripts/fixtures/jobs.rb b/spec/javascripts/fixtures/jobs.rb index 433bb690a1c..941235190b5 100644 --- a/spec/javascripts/fixtures/jobs.rb +++ b/spec/javascripts/fixtures/jobs.rb @@ -32,7 +32,7 @@ describe Projects::JobsController, '(JavaScript fixtures)', type: :controller do remove_repository(project) end - it 'builds/build-with-artifacts.html.raw' do |example| + it 'builds/build-with-artifacts.html' do |example| get :show, params: { namespace_id: project.namespace.to_param, project_id: project, diff --git a/spec/javascripts/fixtures/merge_requests.rb b/spec/javascripts/fixtures/merge_requests.rb index eb37be87e1d..7df1e5cb512 100644 --- a/spec/javascripts/fixtures/merge_requests.rb +++ b/spec/javascripts/fixtures/merge_requests.rb @@ -42,19 +42,19 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont remove_repository(project) end - it 'merge_requests/merge_request_of_current_user.html.raw' do |example| + it 'merge_requests/merge_request_of_current_user.html' do |example| merge_request.update(author: admin) render_merge_request(example.description, merge_request) end - it 'merge_requests/merge_request_with_task_list.html.raw' do |example| + it 'merge_requests/merge_request_with_task_list.html' do |example| create(:ci_build, :pending, pipeline: pipeline) render_merge_request(example.description, merge_request) end - it 'merge_requests/merged_merge_request.html.raw' do |example| + it 'merge_requests/merged_merge_request.html' do |example| expect_next_instance_of(MergeRequest) do |merge_request| allow(merge_request).to receive(:source_branch_exists?).and_return(true) allow(merge_request).to receive(:can_remove_source_branch?).and_return(true) @@ -62,13 +62,13 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont render_merge_request(example.description, merged_merge_request) end - it 'merge_requests/diff_comment.html.raw' do |example| + it 'merge_requests/diff_comment.html' do |example| create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request) create(:note_on_merge_request, author: admin, project: project, noteable: merge_request) render_merge_request(example.description, merge_request) end - it 'merge_requests/merge_request_with_comment.html.raw' do |example| + it 'merge_requests/merge_request_with_comment.html' do |example| create(:note_on_merge_request, author: admin, project: project, noteable: merge_request, note: '- [ ] Task List Item') render_merge_request(example.description, merge_request) end diff --git a/spec/javascripts/fixtures/pipeline_schedules.rb b/spec/javascripts/fixtures/pipeline_schedules.rb index 05d79ec8de9..e5176a58273 100644 --- a/spec/javascripts/fixtures/pipeline_schedules.rb +++ b/spec/javascripts/fixtures/pipeline_schedules.rb @@ -21,7 +21,7 @@ describe Projects::PipelineSchedulesController, '(JavaScript fixtures)', type: : sign_in(admin) end - it 'pipeline_schedules/edit.html.raw' do |example| + it 'pipeline_schedules/edit.html' do |example| get :edit, params: { namespace_id: project.namespace.to_param, project_id: project, @@ -32,7 +32,7 @@ describe Projects::PipelineSchedulesController, '(JavaScript fixtures)', type: : store_frontend_fixture(response, example.description) end - it 'pipeline_schedules/edit_with_variables.html.raw' do |example| + it 'pipeline_schedules/edit_with_variables.html' do |example| get :edit, params: { namespace_id: project.namespace.to_param, project_id: project, diff --git a/spec/javascripts/fixtures/projects.rb b/spec/javascripts/fixtures/projects.rb index 85f02923804..446da83a7f9 100644 --- a/spec/javascripts/fixtures/projects.rb +++ b/spec/javascripts/fixtures/projects.rb @@ -28,7 +28,7 @@ describe 'Projects (JavaScript fixtures)', type: :controller do end describe ProjectsController, '(JavaScript fixtures)', type: :controller do - it 'projects/dashboard.html.raw' do |example| + it 'projects/dashboard.html' do |example| get :show, params: { namespace_id: project.namespace.to_param, id: project @@ -38,7 +38,7 @@ describe 'Projects (JavaScript fixtures)', type: :controller do store_frontend_fixture(response, example.description) end - it 'projects/overview.html.raw' do |example| + it 'projects/overview.html' do |example| get :show, params: { namespace_id: project_with_repo.namespace.to_param, id: project_with_repo @@ -48,7 +48,7 @@ describe 'Projects (JavaScript fixtures)', type: :controller do store_frontend_fixture(response, example.description) end - it 'projects/edit.html.raw' do |example| + it 'projects/edit.html' do |example| get :edit, params: { namespace_id: project.namespace.to_param, id: project @@ -60,7 +60,7 @@ describe 'Projects (JavaScript fixtures)', type: :controller do end describe Projects::Settings::CiCdController, '(JavaScript fixtures)', type: :controller do - it 'projects/ci_cd_settings.html.raw' do |example| + it 'projects/ci_cd_settings.html' do |example| get :show, params: { namespace_id: project.namespace.to_param, project_id: project @@ -70,7 +70,7 @@ describe 'Projects (JavaScript fixtures)', type: :controller do store_frontend_fixture(response, example.description) end - it 'projects/ci_cd_settings_with_variables.html.raw' do |example| + it 'projects/ci_cd_settings_with_variables.html' do |example| create(:ci_variable, project: project_variable_populated) create(:ci_variable, project: project_variable_populated) diff --git a/spec/javascripts/fixtures/prometheus_service.rb b/spec/javascripts/fixtures/prometheus_service.rb index 746fbfd66dd..29dc95305b7 100644 --- a/spec/javascripts/fixtures/prometheus_service.rb +++ b/spec/javascripts/fixtures/prometheus_service.rb @@ -22,7 +22,7 @@ describe Projects::ServicesController, '(JavaScript fixtures)', type: :controlle remove_repository(project) end - it 'services/prometheus/prometheus_service.html.raw' do |example| + it 'services/prometheus/prometheus_service.html' do |example| get :edit, params: { namespace_id: namespace, project_id: project, diff --git a/spec/javascripts/fixtures/search.rb b/spec/javascripts/fixtures/search.rb index 703cd3d49fa..5f5b4d4e60d 100644 --- a/spec/javascripts/fixtures/search.rb +++ b/spec/javascripts/fixtures/search.rb @@ -9,7 +9,7 @@ describe SearchController, '(JavaScript fixtures)', type: :controller do clean_frontend_fixtures('search/') end - it 'search/show.html.raw' do |example| + it 'search/show.html' do |example| get :show expect(response).to be_success diff --git a/spec/javascripts/fixtures/services.rb b/spec/javascripts/fixtures/services.rb index 6ccd74a07ff..dc7ee484c22 100644 --- a/spec/javascripts/fixtures/services.rb +++ b/spec/javascripts/fixtures/services.rb @@ -22,7 +22,7 @@ describe Projects::ServicesController, '(JavaScript fixtures)', type: :controlle remove_repository(project) end - it 'services/edit_service.html.raw' do |example| + it 'services/edit_service.html' do |example| get :edit, params: { namespace_id: namespace, project_id: project, diff --git a/spec/javascripts/fixtures/sessions.rb b/spec/javascripts/fixtures/sessions.rb index e90a58e8c54..8656dea696a 100644 --- a/spec/javascripts/fixtures/sessions.rb +++ b/spec/javascripts/fixtures/sessions.rb @@ -16,7 +16,7 @@ describe 'Sessions (JavaScript fixtures)' do set_devise_mapping(context: @request) end - it 'sessions/new.html.raw' do |example| + it 'sessions/new.html' do |example| get :new expect(response).to be_success diff --git a/spec/javascripts/fixtures/snippet.rb b/spec/javascripts/fixtures/snippet.rb index bcd6546f3df..ebc5b793166 100644 --- a/spec/javascripts/fixtures/snippet.rb +++ b/spec/javascripts/fixtures/snippet.rb @@ -23,7 +23,7 @@ describe SnippetsController, '(JavaScript fixtures)', type: :controller do remove_repository(project) end - it 'snippets/show.html.raw' do |example| + it 'snippets/show.html' do |example| create(:discussion_note_on_snippet, noteable: snippet, project: project, author: admin, note: '- [ ] Task List Item') get(:show, params: { id: snippet.to_param }) diff --git a/spec/javascripts/fixtures/static/ajax_loading_spinner.html.raw b/spec/javascripts/fixtures/static/ajax_loading_spinner.html index 0e1ebb32b1c..0e1ebb32b1c 100644 --- a/spec/javascripts/fixtures/static/ajax_loading_spinner.html.raw +++ b/spec/javascripts/fixtures/static/ajax_loading_spinner.html diff --git a/spec/javascripts/fixtures/static/balsamiq_viewer.html.raw b/spec/javascripts/fixtures/static/balsamiq_viewer.html index cdd723d1a84..cdd723d1a84 100644 --- a/spec/javascripts/fixtures/static/balsamiq_viewer.html.raw +++ b/spec/javascripts/fixtures/static/balsamiq_viewer.html diff --git a/spec/javascripts/fixtures/static/create_item_dropdown.html.raw b/spec/javascripts/fixtures/static/create_item_dropdown.html index d2d38370092..d2d38370092 100644 --- a/spec/javascripts/fixtures/static/create_item_dropdown.html.raw +++ b/spec/javascripts/fixtures/static/create_item_dropdown.html diff --git a/spec/javascripts/fixtures/static/event_filter.html.raw b/spec/javascripts/fixtures/static/event_filter.html index 8e9b6fb1b5c..8e9b6fb1b5c 100644 --- a/spec/javascripts/fixtures/static/event_filter.html.raw +++ b/spec/javascripts/fixtures/static/event_filter.html diff --git a/spec/javascripts/fixtures/static/gl_dropdown.html.raw b/spec/javascripts/fixtures/static/gl_dropdown.html index 08f6738414e..08f6738414e 100644 --- a/spec/javascripts/fixtures/static/gl_dropdown.html.raw +++ b/spec/javascripts/fixtures/static/gl_dropdown.html diff --git a/spec/javascripts/fixtures/static/gl_field_errors.html.raw b/spec/javascripts/fixtures/static/gl_field_errors.html index f8470e02b7c..f8470e02b7c 100644 --- a/spec/javascripts/fixtures/static/gl_field_errors.html.raw +++ b/spec/javascripts/fixtures/static/gl_field_errors.html diff --git a/spec/javascripts/fixtures/static/issuable_filter.html.raw b/spec/javascripts/fixtures/static/issuable_filter.html index 06b70fb43f1..06b70fb43f1 100644 --- a/spec/javascripts/fixtures/static/issuable_filter.html.raw +++ b/spec/javascripts/fixtures/static/issuable_filter.html diff --git a/spec/javascripts/fixtures/static/issue_sidebar_label.html.raw b/spec/javascripts/fixtures/static/issue_sidebar_label.html index ec8fb30f219..ec8fb30f219 100644 --- a/spec/javascripts/fixtures/static/issue_sidebar_label.html.raw +++ b/spec/javascripts/fixtures/static/issue_sidebar_label.html diff --git a/spec/javascripts/fixtures/static/line_highlighter.html.raw b/spec/javascripts/fixtures/static/line_highlighter.html index 897a25d6760..897a25d6760 100644 --- a/spec/javascripts/fixtures/static/line_highlighter.html.raw +++ b/spec/javascripts/fixtures/static/line_highlighter.html diff --git a/spec/javascripts/fixtures/static/linked_tabs.html.raw b/spec/javascripts/fixtures/static/linked_tabs.html index c25463bf1db..c25463bf1db 100644 --- a/spec/javascripts/fixtures/static/linked_tabs.html.raw +++ b/spec/javascripts/fixtures/static/linked_tabs.html diff --git a/spec/javascripts/fixtures/static/merge_requests_show.html.raw b/spec/javascripts/fixtures/static/merge_requests_show.html index e219d9462aa..87e36c9f315 100644 --- a/spec/javascripts/fixtures/static/merge_requests_show.html.raw +++ b/spec/javascripts/fixtures/static/merge_requests_show.html @@ -1,7 +1,7 @@ <a class="btn-close"></a> <div class="detail-page-description"> <div class="description js-task-list-container"> -<div class="wiki"> +<div class="md"> <ul class="task-list"> <li class="task-list-item"> <input class="task-list-item-checkbox" type="checkbox"> diff --git a/spec/javascripts/fixtures/static/mini_dropdown_graph.html.raw b/spec/javascripts/fixtures/static/mini_dropdown_graph.html index cd0b8dec3fc..cd0b8dec3fc 100644 --- a/spec/javascripts/fixtures/static/mini_dropdown_graph.html.raw +++ b/spec/javascripts/fixtures/static/mini_dropdown_graph.html diff --git a/spec/javascripts/fixtures/static/notebook_viewer.html.raw b/spec/javascripts/fixtures/static/notebook_viewer.html index 4bbb7bf1094..4bbb7bf1094 100644 --- a/spec/javascripts/fixtures/static/notebook_viewer.html.raw +++ b/spec/javascripts/fixtures/static/notebook_viewer.html diff --git a/spec/javascripts/fixtures/static/oauth_remember_me.html.raw b/spec/javascripts/fixtures/static/oauth_remember_me.html index 9ba1ffc72fe..9ba1ffc72fe 100644 --- a/spec/javascripts/fixtures/static/oauth_remember_me.html.raw +++ b/spec/javascripts/fixtures/static/oauth_remember_me.html diff --git a/spec/javascripts/fixtures/static/pdf_viewer.html.raw b/spec/javascripts/fixtures/static/pdf_viewer.html index 350d35a262f..350d35a262f 100644 --- a/spec/javascripts/fixtures/static/pdf_viewer.html.raw +++ b/spec/javascripts/fixtures/static/pdf_viewer.html diff --git a/spec/javascripts/fixtures/static/pipeline_graph.html.raw b/spec/javascripts/fixtures/static/pipeline_graph.html index 422372bb7d5..422372bb7d5 100644 --- a/spec/javascripts/fixtures/static/pipeline_graph.html.raw +++ b/spec/javascripts/fixtures/static/pipeline_graph.html diff --git a/spec/javascripts/fixtures/static/pipelines.html.raw b/spec/javascripts/fixtures/static/pipelines.html index 42333f94f2f..42333f94f2f 100644 --- a/spec/javascripts/fixtures/static/pipelines.html.raw +++ b/spec/javascripts/fixtures/static/pipelines.html diff --git a/spec/javascripts/fixtures/static/project_select_combo_button.html.raw b/spec/javascripts/fixtures/static/project_select_combo_button.html index 50c826051c0..50c826051c0 100644 --- a/spec/javascripts/fixtures/static/project_select_combo_button.html.raw +++ b/spec/javascripts/fixtures/static/project_select_combo_button.html diff --git a/spec/javascripts/fixtures/static/search_autocomplete.html.raw b/spec/javascripts/fixtures/static/search_autocomplete.html index 29db9020424..29db9020424 100644 --- a/spec/javascripts/fixtures/static/search_autocomplete.html.raw +++ b/spec/javascripts/fixtures/static/search_autocomplete.html diff --git a/spec/javascripts/fixtures/static/signin_tabs.html.raw b/spec/javascripts/fixtures/static/signin_tabs.html index 7e66ab9394b..7e66ab9394b 100644 --- a/spec/javascripts/fixtures/static/signin_tabs.html.raw +++ b/spec/javascripts/fixtures/static/signin_tabs.html diff --git a/spec/javascripts/fixtures/static/sketch_viewer.html.raw b/spec/javascripts/fixtures/static/sketch_viewer.html index e25e554e568..e25e554e568 100644 --- a/spec/javascripts/fixtures/static/sketch_viewer.html.raw +++ b/spec/javascripts/fixtures/static/sketch_viewer.html diff --git a/spec/javascripts/fixtures/static_fixtures.rb b/spec/javascripts/fixtures/static_fixtures.rb index b5188eeb994..cb4b90cdca5 100644 --- a/spec/javascripts/fixtures/static_fixtures.rb +++ b/spec/javascripts/fixtures/static_fixtures.rb @@ -4,7 +4,7 @@ describe ApplicationController, '(Static JavaScript fixtures)', type: :controlle include JavaScriptFixturesHelpers Dir.glob('{,ee/}spec/javascripts/fixtures/**/*.haml').map do |file_path| - it "static/#{file_path.sub(%r{\A(ee/)?spec/javascripts/fixtures/}, '').sub(/\.haml\z/, '.raw')}" do |example| + it "static/#{file_path.sub(%r{\A(ee/)?spec/javascripts/fixtures/}, '').sub(/\.haml\z/, '')}" do |example| store_frontend_fixture(render_template(file_path), example.description) end end diff --git a/spec/javascripts/fixtures/todos.rb b/spec/javascripts/fixtures/todos.rb index b5f6620873b..6e37a2e5a4c 100644 --- a/spec/javascripts/fixtures/todos.rb +++ b/spec/javascripts/fixtures/todos.rb @@ -26,7 +26,7 @@ describe 'Todos (JavaScript fixtures)' do sign_in(admin) end - it 'todos/todos.html.raw' do |example| + it 'todos/todos.html' do |example| get :index expect(response).to be_success diff --git a/spec/javascripts/fixtures/u2f.rb b/spec/javascripts/fixtures/u2f.rb index 5cdbadef639..15866d65a4f 100644 --- a/spec/javascripts/fixtures/u2f.rb +++ b/spec/javascripts/fixtures/u2f.rb @@ -18,7 +18,7 @@ context 'U2F' do set_devise_mapping(context: @request) end - it 'u2f/authenticate.html.raw' do |example| + it 'u2f/authenticate.html' do |example| allow(controller).to receive(:find_user).and_return(user) post :create, params: { user: { login: user.username, password: user.password } } @@ -36,7 +36,7 @@ context 'U2F' do allow_any_instance_of(Profiles::TwoFactorAuthsController).to receive(:build_qr_code).and_return('qrcode:blackandwhitesquares') end - it 'u2f/register.html.raw' do |example| + it 'u2f/register.html' do |example| get :show expect(response).to be_success diff --git a/spec/javascripts/gl_dropdown_spec.js b/spec/javascripts/gl_dropdown_spec.js index 85083653db8..57e31d933ca 100644 --- a/spec/javascripts/gl_dropdown_spec.js +++ b/spec/javascripts/gl_dropdown_spec.js @@ -5,7 +5,7 @@ import GLDropdown from '~/gl_dropdown'; import '~/lib/utils/common_utils'; describe('glDropdown', function describeDropdown() { - preloadFixtures('static/gl_dropdown.html.raw'); + preloadFixtures('static/gl_dropdown.html'); loadJSONFixtures('projects.json'); const NON_SELECTABLE_CLASSES = @@ -64,7 +64,7 @@ describe('glDropdown', function describeDropdown() { } beforeEach(() => { - loadFixtures('static/gl_dropdown.html.raw'); + loadFixtures('static/gl_dropdown.html'); this.dropdownContainerElement = $('.dropdown.inline'); this.$dropdownMenuElement = $('.dropdown-menu', this.dropdownContainerElement); this.projectsData = getJSONFixture('projects.json'); diff --git a/spec/javascripts/gl_field_errors_spec.js b/spec/javascripts/gl_field_errors_spec.js index b463c9afbee..294f219d6fe 100644 --- a/spec/javascripts/gl_field_errors_spec.js +++ b/spec/javascripts/gl_field_errors_spec.js @@ -4,10 +4,10 @@ import $ from 'jquery'; import GlFieldErrors from '~/gl_field_errors'; describe('GL Style Field Errors', function() { - preloadFixtures('static/gl_field_errors.html.raw'); + preloadFixtures('static/gl_field_errors.html'); beforeEach(function() { - loadFixtures('static/gl_field_errors.html.raw'); + loadFixtures('static/gl_field_errors.html'); const $form = $('form.gl-show-field-errors'); this.$form = $form; diff --git a/spec/javascripts/header_spec.js b/spec/javascripts/header_spec.js index 2fe34e5a76f..0ddf589f368 100644 --- a/spec/javascripts/header_spec.js +++ b/spec/javascripts/header_spec.js @@ -3,7 +3,7 @@ import initTodoToggle from '~/header'; describe('Header', function() { const todosPendingCount = '.todos-count'; - const fixtureTemplate = 'issues/open-issue.html.raw'; + const fixtureTemplate = 'issues/open-issue.html'; function isTodosCountHidden() { return $(todosPendingCount).hasClass('hidden'); diff --git a/spec/javascripts/ide/components/error_message_spec.js b/spec/javascripts/ide/components/error_message_spec.js index 430e8e2baa3..80d6c7fd564 100644 --- a/spec/javascripts/ide/components/error_message_spec.js +++ b/spec/javascripts/ide/components/error_message_spec.js @@ -84,7 +84,7 @@ describe('IDE error message component', () => { expect(vm.isLoading).toBe(true); - vm.$nextTick(() => { + setTimeout(() => { expect(vm.isLoading).toBe(false); done(); diff --git a/spec/javascripts/ide/stores/actions/file_spec.js b/spec/javascripts/ide/stores/actions/file_spec.js index 7ddc734ff56..1e5b55af4ba 100644 --- a/spec/javascripts/ide/stores/actions/file_spec.js +++ b/spec/javascripts/ide/stores/actions/file_spec.js @@ -121,68 +121,48 @@ describe('IDE store file actions', () => { store._actions.scrollToTab = oldScrollToTab; // eslint-disable-line }); - it('calls scrollToTab', done => { - store - .dispatch('setFileActive', localFile.path) - .then(() => { - expect(scrollToTabSpy).toHaveBeenCalled(); - - done(); - }) - .catch(done.fail); - }); + it('calls scrollToTab', () => { + const dispatch = jasmine.createSpy(); - it('sets the file active', done => { - store - .dispatch('setFileActive', localFile.path) - .then(() => { - expect(localFile.active).toBeTruthy(); + actions.setFileActive( + { commit() {}, state: store.state, getters: store.getters, dispatch }, + localFile.path, + ); - done(); - }) - .catch(done.fail); + expect(dispatch).toHaveBeenCalledWith('scrollToTab'); }); - it('returns early if file is already active', done => { - localFile.active = true; + it('commits SET_FILE_ACTIVE', () => { + const commit = jasmine.createSpy(); - store - .dispatch('setFileActive', localFile.path) - .then(() => { - expect(scrollToTabSpy).not.toHaveBeenCalled(); + actions.setFileActive( + { commit, state: store.state, getters: store.getters, dispatch() {} }, + localFile.path, + ); - done(); - }) - .catch(done.fail); + expect(commit).toHaveBeenCalledWith('SET_FILE_ACTIVE', { + path: localFile.path, + active: true, + }); }); - it('sets current active file to not active', done => { + it('sets current active file to not active', () => { const f = file('newActive'); store.state.entries[f.path] = f; localFile.active = true; store.state.openFiles.push(localFile); - store - .dispatch('setFileActive', f.path) - .then(() => { - expect(localFile.active).toBeFalsy(); + const commit = jasmine.createSpy(); - done(); - }) - .catch(done.fail); - }); - - it('resets location.hash for line highlighting', done => { - window.location.hash = 'test'; - - store - .dispatch('setFileActive', localFile.path) - .then(() => { - expect(window.location.hash).not.toBe('test'); + actions.setFileActive( + { commit, state: store.state, getters: store.getters, dispatch() {} }, + f.path, + ); - done(); - }) - .catch(done.fail); + expect(commit).toHaveBeenCalledWith('SET_FILE_ACTIVE', { + path: localFile.path, + active: false, + }); }); }); diff --git a/spec/javascripts/integrations/integration_settings_form_spec.js b/spec/javascripts/integrations/integration_settings_form_spec.js index 4f4c9a7b463..069e2cb07b5 100644 --- a/spec/javascripts/integrations/integration_settings_form_spec.js +++ b/spec/javascripts/integrations/integration_settings_form_spec.js @@ -4,7 +4,7 @@ import axios from '~/lib/utils/axios_utils'; import IntegrationSettingsForm from '~/integrations/integration_settings_form'; describe('IntegrationSettingsForm', () => { - const FIXTURE = 'services/edit_service.html.raw'; + const FIXTURE = 'services/edit_service.html'; preloadFixtures(FIXTURE); beforeEach(() => { diff --git a/spec/javascripts/issue_show/components/app_spec.js b/spec/javascripts/issue_show/components/app_spec.js index 0ccf771c7ef..dfc889773c1 100644 --- a/spec/javascripts/issue_show/components/app_spec.js +++ b/spec/javascripts/issue_show/components/app_spec.js @@ -75,7 +75,7 @@ describe('Issuable output', () => { .then(() => { expect(document.querySelector('title').innerText).toContain('this is a title (#1)'); expect(vm.$el.querySelector('.title').innerHTML).toContain('<p>this is a title</p>'); - expect(vm.$el.querySelector('.wiki').innerHTML).toContain('<p>this is a description!</p>'); + expect(vm.$el.querySelector('.md').innerHTML).toContain('<p>this is a description!</p>'); expect(vm.$el.querySelector('.js-task-list-field').value).toContain( 'this is a description', ); @@ -92,7 +92,7 @@ describe('Issuable output', () => { .then(() => { expect(document.querySelector('title').innerText).toContain('2 (#1)'); expect(vm.$el.querySelector('.title').innerHTML).toContain('<p>2</p>'); - expect(vm.$el.querySelector('.wiki').innerHTML).toContain('<p>42</p>'); + expect(vm.$el.querySelector('.md').innerHTML).toContain('<p>42</p>'); expect(vm.$el.querySelector('.js-task-list-field').value).toContain('42'); expect(vm.$el.querySelector('.edited-text')).toBeTruthy(); expect(formatText(vm.$el.querySelector('.edited-text').innerText)).toMatch( diff --git a/spec/javascripts/issue_show/components/description_spec.js b/spec/javascripts/issue_show/components/description_spec.js index 2eeed6770be..7e00fbf2745 100644 --- a/spec/javascripts/issue_show/components/description_spec.js +++ b/spec/javascripts/issue_show/components/description_spec.js @@ -43,12 +43,12 @@ describe('Description component', () => { Vue.nextTick(() => { expect( - vm.$el.querySelector('.wiki').classList.contains('issue-realtime-pre-pulse'), + vm.$el.querySelector('.md').classList.contains('issue-realtime-pre-pulse'), ).toBeTruthy(); setTimeout(() => { expect( - vm.$el.querySelector('.wiki').classList.contains('issue-realtime-trigger-pulse'), + vm.$el.querySelector('.md').classList.contains('issue-realtime-trigger-pulse'), ).toBeTruthy(); done(); diff --git a/spec/javascripts/issue_spec.js b/spec/javascripts/issue_spec.js index 7be495d1d35..11ab6c38a55 100644 --- a/spec/javascripts/issue_spec.js +++ b/spec/javascripts/issue_spec.js @@ -9,9 +9,9 @@ import '~/lib/utils/text_utility'; describe('Issue', function() { let $boxClosed, $boxOpen, $btn; - preloadFixtures('issues/closed-issue.html.raw'); - preloadFixtures('issues/issue-with-task-list.html.raw'); - preloadFixtures('issues/open-issue.html.raw'); + preloadFixtures('issues/closed-issue.html'); + preloadFixtures('issues/issue-with-task-list.html'); + preloadFixtures('issues/open-issue.html'); function expectErrorMessage() { const $flashMessage = $('div.flash-alert'); @@ -105,9 +105,9 @@ describe('Issue', function() { beforeEach(function() { if (isIssueInitiallyOpen) { - loadFixtures('issues/open-issue.html.raw'); + loadFixtures('issues/open-issue.html'); } else { - loadFixtures('issues/closed-issue.html.raw'); + loadFixtures('issues/closed-issue.html'); } mock = new MockAdapter(axios); diff --git a/spec/javascripts/labels_issue_sidebar_spec.js b/spec/javascripts/labels_issue_sidebar_spec.js index e5678ee5379..ccf439aac74 100644 --- a/spec/javascripts/labels_issue_sidebar_spec.js +++ b/spec/javascripts/labels_issue_sidebar_spec.js @@ -16,10 +16,10 @@ let saveLabelCount = 0; let mock; describe('Issue dropdown sidebar', () => { - preloadFixtures('static/issue_sidebar_label.html.raw'); + preloadFixtures('static/issue_sidebar_label.html'); beforeEach(() => { - loadFixtures('static/issue_sidebar_label.html.raw'); + loadFixtures('static/issue_sidebar_label.html'); mock = new MockAdapter(axios); diff --git a/spec/javascripts/lazy_loader_spec.js b/spec/javascripts/lazy_loader_spec.js index cbdc1644430..f3fb792c62d 100644 --- a/spec/javascripts/lazy_loader_spec.js +++ b/spec/javascripts/lazy_loader_spec.js @@ -11,11 +11,11 @@ const execImmediately = callback => { describe('LazyLoader', function() { let lazyLoader = null; - preloadFixtures('issues/issue_with_comment.html.raw'); + preloadFixtures('issues/issue_with_comment.html'); describe('without IntersectionObserver', () => { beforeEach(function() { - loadFixtures('issues/issue_with_comment.html.raw'); + loadFixtures('issues/issue_with_comment.html'); lazyLoader = new LazyLoader({ observerNode: 'foobar', @@ -131,7 +131,7 @@ describe('LazyLoader', function() { describe('with IntersectionObserver', () => { beforeEach(function() { - loadFixtures('issues/issue_with_comment.html.raw'); + loadFixtures('issues/issue_with_comment.html'); lazyLoader = new LazyLoader({ observerNode: 'foobar', diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js index 0bb43c94f6a..2084c36e484 100644 --- a/spec/javascripts/lib/utils/common_utils_spec.js +++ b/spec/javascripts/lib/utils/common_utils_spec.js @@ -2,6 +2,7 @@ import axios from '~/lib/utils/axios_utils'; import * as commonUtils from '~/lib/utils/common_utils'; import MockAdapter from 'axios-mock-adapter'; import { faviconDataUrl, overlayDataUrl, faviconWithOverlayDataUrl } from './mock_data'; +import BreakpointInstance from '~/breakpoints'; const PIXEL_TOLERANCE = 0.2; @@ -380,6 +381,38 @@ describe('common_utils', () => { }); }); + describe('contentTop', () => { + it('does not add height for fileTitle or compareVersionsHeader if screen is too small', () => { + spyOn(BreakpointInstance, 'getBreakpointSize').and.returnValue('sm'); + + setFixtures(` + <div class="diff-file file-title-flex-parent"> + blah blah blah + </div> + <div class="mr-version-controls"> + more blah blah blah + </div> + `); + + expect(commonUtils.contentTop()).toBe(0); + }); + + it('adds height for fileTitle and compareVersionsHeader screen is large enough', () => { + spyOn(BreakpointInstance, 'getBreakpointSize').and.returnValue('lg'); + + setFixtures(` + <div class="diff-file file-title-flex-parent"> + blah blah blah + </div> + <div class="mr-version-controls"> + more blah blah blah + </div> + `); + + expect(commonUtils.contentTop()).toBe(18); + }); + }); + describe('parseBoolean', () => { const { parseBoolean } = commonUtils; diff --git a/spec/javascripts/line_highlighter_spec.js b/spec/javascripts/line_highlighter_spec.js index 4eea364bd69..a75470b4db8 100644 --- a/spec/javascripts/line_highlighter_spec.js +++ b/spec/javascripts/line_highlighter_spec.js @@ -5,7 +5,7 @@ import LineHighlighter from '~/line_highlighter'; describe('LineHighlighter', function() { var clickLine; - preloadFixtures('static/line_highlighter.html.raw'); + preloadFixtures('static/line_highlighter.html'); clickLine = function(number, eventData = {}) { if ($.isEmptyObject(eventData)) { return $('#L' + number).click(); @@ -15,7 +15,7 @@ describe('LineHighlighter', function() { } }; beforeEach(function() { - loadFixtures('static/line_highlighter.html.raw'); + loadFixtures('static/line_highlighter.html'); this['class'] = new LineHighlighter(); this.css = this['class'].highlightLineClass; return (this.spies = { diff --git a/spec/javascripts/merge_request_spec.js b/spec/javascripts/merge_request_spec.js index ab809930804..431798c6ec3 100644 --- a/spec/javascripts/merge_request_spec.js +++ b/spec/javascripts/merge_request_spec.js @@ -11,9 +11,9 @@ describe('MergeRequest', function() { describe('task lists', function() { let mock; - preloadFixtures('merge_requests/merge_request_with_task_list.html.raw'); + preloadFixtures('merge_requests/merge_request_with_task_list.html'); beforeEach(function() { - loadFixtures('merge_requests/merge_request_with_task_list.html.raw'); + loadFixtures('merge_requests/merge_request_with_task_list.html'); spyOn(axios, 'patch').and.callThrough(); mock = new MockAdapter(axios); @@ -125,7 +125,7 @@ describe('MergeRequest', function() { describe('hideCloseButton', () => { describe('merge request of another user', () => { beforeEach(() => { - loadFixtures('merge_requests/merge_request_with_task_list.html.raw'); + loadFixtures('merge_requests/merge_request_with_task_list.html'); this.el = document.querySelector('.js-issuable-actions'); new MergeRequest(); // eslint-disable-line no-new MergeRequest.hideCloseButton(); @@ -145,7 +145,7 @@ describe('MergeRequest', function() { describe('merge request of current_user', () => { beforeEach(() => { - loadFixtures('merge_requests/merge_request_of_current_user.html.raw'); + loadFixtures('merge_requests/merge_request_of_current_user.html'); this.el = document.querySelector('.js-issuable-actions'); MergeRequest.hideCloseButton(); }); diff --git a/spec/javascripts/merge_request_tabs_spec.js b/spec/javascripts/merge_request_tabs_spec.js index c8df05eccf5..1295d900de7 100644 --- a/spec/javascripts/merge_request_tabs_spec.js +++ b/spec/javascripts/merge_request_tabs_spec.js @@ -22,8 +22,8 @@ describe('MergeRequestTabs', function() { }; preloadFixtures( - 'merge_requests/merge_request_with_task_list.html.raw', - 'merge_requests/diff_comment.html.raw', + 'merge_requests/merge_request_with_task_list.html', + 'merge_requests/diff_comment.html', ); beforeEach(function() { @@ -48,7 +48,7 @@ describe('MergeRequestTabs', function() { var windowTarget = '_blank'; beforeEach(function() { - loadFixtures('merge_requests/merge_request_with_task_list.html.raw'); + loadFixtures('merge_requests/merge_request_with_task_list.html'); tabUrl = $('.commits-tab a').attr('href'); }); diff --git a/spec/javascripts/mini_pipeline_graph_dropdown_spec.js b/spec/javascripts/mini_pipeline_graph_dropdown_spec.js index 092ca9e1dab..aa4a376caf7 100644 --- a/spec/javascripts/mini_pipeline_graph_dropdown_spec.js +++ b/spec/javascripts/mini_pipeline_graph_dropdown_spec.js @@ -5,10 +5,10 @@ import MiniPipelineGraph from '~/mini_pipeline_graph_dropdown'; import timeoutPromise from './helpers/set_timeout_promise_helper'; describe('Mini Pipeline Graph Dropdown', () => { - preloadFixtures('static/mini_dropdown_graph.html.raw'); + preloadFixtures('static/mini_dropdown_graph.html'); beforeEach(() => { - loadFixtures('static/mini_dropdown_graph.html.raw'); + loadFixtures('static/mini_dropdown_graph.html'); }); describe('When is initialized', () => { diff --git a/spec/javascripts/monitoring/charts/area_spec.js b/spec/javascripts/monitoring/charts/area_spec.js index fb49290be19..549a7935c0f 100644 --- a/spec/javascripts/monitoring/charts/area_spec.js +++ b/spec/javascripts/monitoring/charts/area_spec.js @@ -203,6 +203,10 @@ describe('Area component', () => { .length, ).toBe(data.length); }); + + it('formats line width correctly', () => { + expect(chartData[0].lineStyle.width).toBe(2); + }); }); describe('scatterSeries', () => { diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js index b1778029a77..454777fa912 100644 --- a/spec/javascripts/monitoring/dashboard_spec.js +++ b/spec/javascripts/monitoring/dashboard_spec.js @@ -33,6 +33,11 @@ describe('Dashboard', () => { <div class="layout-page"></div> `); + window.gon = { + ...window.gon, + ee: false, + }; + mock = new MockAdapter(axios); DashboardComponent = Vue.extend(Dashboard); }); @@ -98,7 +103,7 @@ describe('Dashboard', () => { }); }); - it('renders the dropdown with a number of environments', done => { + it('renders the environments dropdown with a number of environments', done => { const component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), propsData: { ...propsData, hasMetrics: true, showPanels: false }, @@ -107,14 +112,16 @@ describe('Dashboard', () => { component.store.storeEnvironmentsData(environmentData); setTimeout(() => { - const dropdownMenuEnvironments = component.$el.querySelectorAll('.dropdown-menu ul li a'); + const dropdownMenuEnvironments = component.$el.querySelectorAll( + '.js-environments-dropdown .dropdown-item', + ); expect(dropdownMenuEnvironments.length).toEqual(component.store.environmentsData.length); done(); }); }); - it('hides the dropdown list when there is no environments', done => { + it('hides the environments dropdown list when there is no environments', done => { const component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), propsData: { ...propsData, hasMetrics: true, showPanels: false }, @@ -123,14 +130,16 @@ describe('Dashboard', () => { component.store.storeEnvironmentsData([]); setTimeout(() => { - const dropdownMenuEnvironments = component.$el.querySelectorAll('.dropdown-menu ul'); + const dropdownMenuEnvironments = component.$el.querySelectorAll( + '.js-environments-dropdown .dropdown-item', + ); expect(dropdownMenuEnvironments.length).toEqual(0); done(); }); }); - it('renders the dropdown with a single is-active element', done => { + it('renders the environments dropdown with a single is-active element', done => { const component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), propsData: { ...propsData, hasMetrics: true, showPanels: false }, @@ -139,14 +148,31 @@ describe('Dashboard', () => { component.store.storeEnvironmentsData(environmentData); setTimeout(() => { - const dropdownIsActiveElement = component.$el.querySelectorAll( - '.dropdown-menu ul li a.is-active', + const dropdownItems = component.$el.querySelectorAll( + '.js-environments-dropdown .dropdown-item[active="true"]', ); - expect(dropdownIsActiveElement.length).toEqual(1); - expect(dropdownIsActiveElement[0].textContent.trim()).toEqual( - component.currentEnvironmentName, - ); + expect(dropdownItems.length).toEqual(1); + expect(dropdownItems[0].textContent.trim()).toEqual(component.currentEnvironmentName); + done(); + }); + }); + + it('hides the dropdown', done => { + const component = new DashboardComponent({ + el: document.querySelector('.prometheus-graphs'), + propsData: { + ...propsData, + hasMetrics: true, + showPanels: false, + environmentsEndpoint: '', + }, + }); + + Vue.nextTick(() => { + const dropdownIsActiveElement = component.$el.querySelectorAll('.environments'); + + expect(dropdownIsActiveElement.length).toEqual(0); done(); }); }); diff --git a/spec/javascripts/monitoring/mock_data.js b/spec/javascripts/monitoring/mock_data.js index ffc7148fde2..6d4ef960c1a 100644 --- a/spec/javascripts/monitoring/mock_data.js +++ b/spec/javascripts/monitoring/mock_data.js @@ -330,6 +330,11 @@ export const metricsGroupsAPIResponse = { weight: 1, queries: [ { + appearance: { + line: { + width: 2, + }, + }, query_range: 'avg(rate(container_cpu_usage_seconds_total{%{environment_filter}}[2m])) * 100', label: 'Core Usage', diff --git a/spec/javascripts/new_branch_spec.js b/spec/javascripts/new_branch_spec.js index 1d7b885e64f..4e3140ce4f1 100644 --- a/spec/javascripts/new_branch_spec.js +++ b/spec/javascripts/new_branch_spec.js @@ -3,7 +3,7 @@ import NewBranchForm from '~/new_branch_form'; describe('Branch', function() { describe('create a new branch', function() { - preloadFixtures('branches/new_branch.html.raw'); + preloadFixtures('branches/new_branch.html'); function fillNameWith(value) { $('.js-branch-name') @@ -16,7 +16,7 @@ describe('Branch', function() { } beforeEach(function() { - loadFixtures('branches/new_branch.html.raw'); + loadFixtures('branches/new_branch.html'); $('form').on('submit', function(e) { return e.preventDefault(); }); diff --git a/spec/javascripts/notes/components/note_app_spec.js b/spec/javascripts/notes/components/note_app_spec.js index d716ece3766..ef876dc2941 100644 --- a/spec/javascripts/notes/components/note_app_spec.js +++ b/spec/javascripts/notes/components/note_app_spec.js @@ -192,9 +192,9 @@ describe('note_app', () => { expect(service.updateNote).toHaveBeenCalled(); // Wait for the requests to finish before destroying - Vue.nextTick() - .then(done) - .catch(done.fail); + setTimeout(() => { + done(); + }); }); }); @@ -227,9 +227,9 @@ describe('note_app', () => { expect(service.updateNote).toHaveBeenCalled(); // Wait for the requests to finish before destroying - Vue.nextTick() - .then(done) - .catch(done.fail); + setTimeout(() => { + done(); + }); }); }); }); diff --git a/spec/javascripts/notes/components/note_form_spec.js b/spec/javascripts/notes/components/note_form_spec.js index c48f8188105..b632ee6736d 100644 --- a/spec/javascripts/notes/components/note_form_spec.js +++ b/spec/javascripts/notes/components/note_form_spec.js @@ -41,8 +41,6 @@ describe('issue_note_form component', () => { noteBody: 'Magni suscipit eius consectetur enim et ex et commodi.', noteId: '545', }; - - wrapper = createComponentWrapper(); }); afterEach(() => { @@ -50,6 +48,10 @@ describe('issue_note_form component', () => { }); describe('noteHash', () => { + beforeEach(() => { + wrapper = createComponentWrapper(); + }); + it('returns note hash string based on `noteId`', () => { expect(wrapper.vm.noteHash).toBe(`#note_${props.noteId}`); }); @@ -71,6 +73,10 @@ describe('issue_note_form component', () => { }); describe('conflicts editing', () => { + beforeEach(() => { + wrapper = createComponentWrapper(); + }); + it('should show conflict message if note changes outside the component', done => { wrapper.setProps({ ...props, @@ -100,6 +106,10 @@ describe('issue_note_form component', () => { }); describe('form', () => { + beforeEach(() => { + wrapper = createComponentWrapper(); + }); + it('should render text area with placeholder', () => { const textarea = wrapper.find('textarea'); @@ -198,10 +208,6 @@ describe('issue_note_form component', () => { }); describe('with autosaveKey', () => { - beforeEach(() => { - wrapper.destroy(); - }); - describe('with draft', () => { beforeEach(done => { Object.assign(props, { diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js index 7c869d4c326..3d2c617e479 100644 --- a/spec/javascripts/notes_spec.js +++ b/spec/javascripts/notes_spec.js @@ -34,7 +34,7 @@ const htmlEscape = comment => { describe('Notes', function() { const FLASH_TYPE_ALERT = 'alert'; const NOTES_POST_PATH = /(.*)\/notes\?html=true$/; - var fixture = 'snippets/show.html.raw'; + var fixture = 'snippets/show.html'; preloadFixtures(fixture); beforeEach(function() { diff --git a/spec/javascripts/oauth_remember_me_spec.js b/spec/javascripts/oauth_remember_me_spec.js index 4125706a407..381be82697e 100644 --- a/spec/javascripts/oauth_remember_me_spec.js +++ b/spec/javascripts/oauth_remember_me_spec.js @@ -2,10 +2,10 @@ import $ from 'jquery'; import OAuthRememberMe from '~/pages/sessions/new/oauth_remember_me'; describe('OAuthRememberMe', () => { - preloadFixtures('static/oauth_remember_me.html.raw'); + preloadFixtures('static/oauth_remember_me.html'); beforeEach(() => { - loadFixtures('static/oauth_remember_me.html.raw'); + loadFixtures('static/oauth_remember_me.html'); new OAuthRememberMe({ container: $('#oauth-container') }).bindEvents(); }); diff --git a/spec/javascripts/pages/admin/application_settings/account_and_limits_spec.js b/spec/javascripts/pages/admin/application_settings/account_and_limits_spec.js index 561bd2c96cb..6a239e307e9 100644 --- a/spec/javascripts/pages/admin/application_settings/account_and_limits_spec.js +++ b/spec/javascripts/pages/admin/application_settings/account_and_limits_spec.js @@ -5,7 +5,7 @@ import initUserInternalRegexPlaceholder, { } from '~/pages/admin/application_settings/account_and_limits'; describe('AccountAndLimits', () => { - const FIXTURE = 'application_settings/accounts_and_limit.html.raw'; + const FIXTURE = 'application_settings/accounts_and_limit.html'; let $userDefaultExternal; let $userInternalRegex; preloadFixtures(FIXTURE); diff --git a/spec/javascripts/pages/admin/users/new/index_spec.js b/spec/javascripts/pages/admin/users/new/index_spec.js index 5a849f34bc3..3896323eef7 100644 --- a/spec/javascripts/pages/admin/users/new/index_spec.js +++ b/spec/javascripts/pages/admin/users/new/index_spec.js @@ -2,7 +2,7 @@ import $ from 'jquery'; import UserInternalRegexHandler from '~/pages/admin/users/new/index'; describe('UserInternalRegexHandler', () => { - const FIXTURE = 'admin/users/new_with_internal_user_regex.html.raw'; + const FIXTURE = 'admin/users/new_with_internal_user_regex.html'; let $userExternal; let $userEmail; let $warningMessage; diff --git a/spec/javascripts/pages/sessions/new/preserve_url_fragment_spec.js b/spec/javascripts/pages/sessions/new/preserve_url_fragment_spec.js index 7a8227479d4..1809e92e1d9 100644 --- a/spec/javascripts/pages/sessions/new/preserve_url_fragment_spec.js +++ b/spec/javascripts/pages/sessions/new/preserve_url_fragment_spec.js @@ -2,10 +2,10 @@ import $ from 'jquery'; import preserveUrlFragment from '~/pages/sessions/new/preserve_url_fragment'; describe('preserve_url_fragment', () => { - preloadFixtures('sessions/new.html.raw'); + preloadFixtures('sessions/new.html'); beforeEach(() => { - loadFixtures('sessions/new.html.raw'); + loadFixtures('sessions/new.html'); }); it('adds the url fragment to all login and sign up form actions', () => { diff --git a/spec/javascripts/pipelines/graph/action_component_spec.js b/spec/javascripts/pipelines/graph/action_component_spec.js index 3d2232ff239..95717d659b8 100644 --- a/spec/javascripts/pipelines/graph/action_component_spec.js +++ b/spec/javascripts/pipelines/graph/action_component_spec.js @@ -55,13 +55,16 @@ describe('pipeline graph action component', () => { component.$el.click(); - component - .$nextTick() - .then(() => { - expect(component.$emit).toHaveBeenCalledWith('pipelineActionRequestComplete'); - }) - .then(done) - .catch(done.fail); + setTimeout(() => { + component + .$nextTick() + .then(() => { + expect(component.$emit).toHaveBeenCalledWith('pipelineActionRequestComplete'); + }) + .catch(done.fail); + + done(); + }, 0); }); }); }); diff --git a/spec/javascripts/pipelines/graph/stage_column_component_spec.js b/spec/javascripts/pipelines/graph/stage_column_component_spec.js index dafb892da43..3240e8e4c1b 100644 --- a/spec/javascripts/pipelines/graph/stage_column_component_spec.js +++ b/spec/javascripts/pipelines/graph/stage_column_component_spec.js @@ -55,7 +55,7 @@ describe('stage column component', () => { id: 4259, name: '<img src=x onerror=alert(document.domain)>', status: { - icon: 'icon_status_success', + icon: 'status_success', label: 'success', tooltip: '<img src=x onerror=alert(document.domain)>', }, diff --git a/spec/javascripts/pipelines/stage_spec.js b/spec/javascripts/pipelines/stage_spec.js index 3c8b8032de8..19ae7860333 100644 --- a/spec/javascripts/pipelines/stage_spec.js +++ b/spec/javascripts/pipelines/stage_spec.js @@ -120,13 +120,15 @@ describe('Pipelines stage component', () => { setTimeout(() => { component.$el.querySelector('.js-ci-action').click(); - component - .$nextTick() - .then(() => { - expect(eventHub.$emit).toHaveBeenCalledWith('refreshPipelinesTable'); - }) - .then(done) - .catch(done.fail); + setTimeout(() => { + component + .$nextTick() + .then(() => { + expect(eventHub.$emit).toHaveBeenCalledWith('refreshPipelinesTable'); + }) + .then(done) + .catch(done.fail); + }, 0); }, 0); }); }); diff --git a/spec/javascripts/pipelines_spec.js b/spec/javascripts/pipelines_spec.js index 6b86f9ea437..6d4d634c575 100644 --- a/spec/javascripts/pipelines_spec.js +++ b/spec/javascripts/pipelines_spec.js @@ -1,10 +1,10 @@ import Pipelines from '~/pipelines'; describe('Pipelines', () => { - preloadFixtures('static/pipeline_graph.html.raw'); + preloadFixtures('static/pipeline_graph.html'); beforeEach(() => { - loadFixtures('static/pipeline_graph.html.raw'); + loadFixtures('static/pipeline_graph.html'); }); it('should be defined', () => { diff --git a/spec/javascripts/project_select_combo_button_spec.js b/spec/javascripts/project_select_combo_button_spec.js index 109a5000f5d..dc85292c23e 100644 --- a/spec/javascripts/project_select_combo_button_spec.js +++ b/spec/javascripts/project_select_combo_button_spec.js @@ -1,7 +1,7 @@ import $ from 'jquery'; import ProjectSelectComboButton from '~/project_select_combo_button'; -const fixturePath = 'static/project_select_combo_button.html.raw'; +const fixturePath = 'static/project_select_combo_button.html'; describe('Project Select Combo Button', function() { preloadFixtures(fixturePath); diff --git a/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown_spec.js b/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown_spec.js index 030662b4d90..1eb7cb4bd5b 100644 --- a/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown_spec.js +++ b/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown_spec.js @@ -53,36 +53,32 @@ describe('GkeProjectIdDropdown', () => { }); it('returns default toggle text', done => - vm - .$nextTick() - .then(() => { - vm.setItem(emptyProjectMock); + setTimeout(() => { + vm.setItem(emptyProjectMock); - expect(vm.toggleText).toBe(LABELS.DEFAULT); - done(); - }) - .catch(done.fail)); + expect(vm.toggleText).toBe(LABELS.DEFAULT); + + done(); + })); it('returns project name if project selected', done => - vm - .$nextTick() - .then(() => { - expect(vm.toggleText).toBe(selectedProjectMock.name); - done(); - }) - .catch(done.fail)); + setTimeout(() => { + vm.isLoading = false; + + expect(vm.toggleText).toBe(selectedProjectMock.name); + + done(); + })); it('returns empty toggle text', done => - vm - .$nextTick() - .then(() => { - vm.$store.commit(SET_PROJECTS, null); - vm.setItem(emptyProjectMock); + setTimeout(() => { + vm.$store.commit(SET_PROJECTS, null); + vm.setItem(emptyProjectMock); - expect(vm.toggleText).toBe(LABELS.EMPTY); - done(); - }) - .catch(done.fail)); + expect(vm.toggleText).toBe(LABELS.EMPTY); + + done(); + })); }); describe('selectItem', () => { diff --git a/spec/javascripts/prometheus_metrics/prometheus_metrics_spec.js b/spec/javascripts/prometheus_metrics/prometheus_metrics_spec.js index 94e2f959d46..dca3e1553b9 100644 --- a/spec/javascripts/prometheus_metrics/prometheus_metrics_spec.js +++ b/spec/javascripts/prometheus_metrics/prometheus_metrics_spec.js @@ -5,7 +5,7 @@ import PANEL_STATE from '~/prometheus_metrics/constants'; import { metrics, missingVarMetrics } from './mock_data'; describe('PrometheusMetrics', () => { - const FIXTURE = 'services/prometheus/prometheus_service.html.raw'; + const FIXTURE = 'services/prometheus/prometheus_service.html'; preloadFixtures(FIXTURE); beforeEach(() => { diff --git a/spec/javascripts/read_more_spec.js b/spec/javascripts/read_more_spec.js index b1af0f80a50..d1d01272403 100644 --- a/spec/javascripts/read_more_spec.js +++ b/spec/javascripts/read_more_spec.js @@ -1,7 +1,7 @@ import initReadMore from '~/read_more'; describe('Read more click-to-expand functionality', () => { - const fixtureName = 'projects/overview.html.raw'; + const fixtureName = 'projects/overview.html'; preloadFixtures(fixtureName); diff --git a/spec/javascripts/right_sidebar_spec.js b/spec/javascripts/right_sidebar_spec.js index 992e17978c1..9565e3ce546 100644 --- a/spec/javascripts/right_sidebar_spec.js +++ b/spec/javascripts/right_sidebar_spec.js @@ -23,7 +23,7 @@ const assertSidebarState = function(state) { describe('RightSidebar', function() { describe('fixture tests', () => { - const fixtureName = 'issues/open-issue.html.raw'; + const fixtureName = 'issues/open-issue.html'; preloadFixtures(fixtureName); loadJSONFixtures('todos/todos.json'); let mock; diff --git a/spec/javascripts/search_autocomplete_spec.js b/spec/javascripts/search_autocomplete_spec.js index 7a4ca587313..ce7fa7a52ae 100644 --- a/spec/javascripts/search_autocomplete_spec.js +++ b/spec/javascripts/search_autocomplete_spec.js @@ -126,9 +126,9 @@ describe('Search autocomplete dropdown', () => { expect(list.find(mrsIHaveCreatedLink).text()).toBe("Merge requests I've created"); }; - preloadFixtures('static/search_autocomplete.html.raw'); + preloadFixtures('static/search_autocomplete.html'); beforeEach(function() { - loadFixtures('static/search_autocomplete.html.raw'); + loadFixtures('static/search_autocomplete.html'); window.gon = {}; window.gon.current_user_id = userId; diff --git a/spec/javascripts/search_spec.js b/spec/javascripts/search_spec.js index 40bdbac7451..32f60508fa3 100644 --- a/spec/javascripts/search_spec.js +++ b/spec/javascripts/search_spec.js @@ -3,7 +3,7 @@ import Api from '~/api'; import Search from '~/pages/search/show/search'; describe('Search', () => { - const fixturePath = 'search/show.html.raw'; + const fixturePath = 'search/show.html'; const searchTerm = 'some search'; const fillDropdownInput = dropdownSelector => { const dropdownElement = document.querySelector(dropdownSelector).parentNode; diff --git a/spec/javascripts/settings_panels_spec.js b/spec/javascripts/settings_panels_spec.js index 3b681a9ff28..2c5d91a45bc 100644 --- a/spec/javascripts/settings_panels_spec.js +++ b/spec/javascripts/settings_panels_spec.js @@ -2,10 +2,10 @@ import $ from 'jquery'; import initSettingsPanels from '~/settings_panels'; describe('Settings Panels', () => { - preloadFixtures('groups/edit.html.raw'); + preloadFixtures('groups/edit.html'); beforeEach(() => { - loadFixtures('groups/edit.html.raw'); + loadFixtures('groups/edit.html'); }); describe('initSettingsPane', () => { diff --git a/spec/javascripts/shortcuts_spec.js b/spec/javascripts/shortcuts_spec.js index 3ca6ecaa938..df7012bb659 100644 --- a/spec/javascripts/shortcuts_spec.js +++ b/spec/javascripts/shortcuts_spec.js @@ -2,7 +2,7 @@ import $ from 'jquery'; import Shortcuts from '~/behaviors/shortcuts/shortcuts'; describe('Shortcuts', () => { - const fixtureName = 'snippets/show.html.raw'; + const fixtureName = 'snippets/show.html'; const createEvent = (type, target) => $.Event(type, { target, diff --git a/spec/javascripts/sidebar/assignees_spec.js b/spec/javascripts/sidebar/assignees_spec.js index eced4925489..57b16b12cb0 100644 --- a/spec/javascripts/sidebar/assignees_spec.js +++ b/spec/javascripts/sidebar/assignees_spec.js @@ -210,6 +210,19 @@ describe('Assignee component', () => { expect(component.$el.querySelector('.user-list-more')).toBe(null); }); + it('sets tooltip container to body', () => { + const users = UsersMockHelper.createNumberRandomUsers(2); + component = new AssigneeComponent({ + propsData: { + rootPath: 'http://localhost:3000', + users, + editable: true, + }, + }).$mount(); + + expect(component.$el.querySelector('.user-link').getAttribute('data-container')).toBe('body'); + }); + it('Shows the "show-less" assignees label', done => { const users = UsersMockHelper.createNumberRandomUsers(6); component = new AssigneeComponent({ diff --git a/spec/javascripts/sidebar/sidebar_assignees_spec.js b/spec/javascripts/sidebar/sidebar_assignees_spec.js index 3f0f67d71ca..016f5e033a5 100644 --- a/spec/javascripts/sidebar/sidebar_assignees_spec.js +++ b/spec/javascripts/sidebar/sidebar_assignees_spec.js @@ -11,12 +11,12 @@ describe('sidebar assignees', () => { let vm; let mediator; let sidebarAssigneesEl; - preloadFixtures('issues/open-issue.html.raw'); + preloadFixtures('issues/open-issue.html'); beforeEach(() => { Vue.http.interceptors.push(Mock.sidebarMockInterceptor); - loadFixtures('issues/open-issue.html.raw'); + loadFixtures('issues/open-issue.html'); mediator = new SidebarMediator(Mock.mediator); spyOn(mediator, 'saveAssignees').and.callThrough(); diff --git a/spec/javascripts/signin_tabs_memoizer_spec.js b/spec/javascripts/signin_tabs_memoizer_spec.js index 52da6a79939..ef5c774736b 100644 --- a/spec/javascripts/signin_tabs_memoizer_spec.js +++ b/spec/javascripts/signin_tabs_memoizer_spec.js @@ -2,7 +2,7 @@ import AccessorUtilities from '~/lib/utils/accessor'; import SigninTabsMemoizer from '~/pages/sessions/new/signin_tabs_memoizer'; describe('SigninTabsMemoizer', () => { - const fixtureTemplate = 'static/signin_tabs.html.raw'; + const fixtureTemplate = 'static/signin_tabs.html'; const tabSelector = 'ul.new-session-tabs'; const currentTabKey = 'current_signin_tab'; let memo; diff --git a/spec/javascripts/todos_spec.js b/spec/javascripts/todos_spec.js index 69e43274250..802f54f6a7e 100644 --- a/spec/javascripts/todos_spec.js +++ b/spec/javascripts/todos_spec.js @@ -3,11 +3,11 @@ import Todos from '~/pages/dashboard/todos/index/todos'; import '~/lib/utils/common_utils'; describe('Todos', () => { - preloadFixtures('todos/todos.html.raw'); + preloadFixtures('todos/todos.html'); let todoItem; beforeEach(() => { - loadFixtures('todos/todos.html.raw'); + loadFixtures('todos/todos.html'); todoItem = document.querySelector('.todos-list .todo'); return new Todos(); diff --git a/spec/javascripts/u2f/authenticate_spec.js b/spec/javascripts/u2f/authenticate_spec.js index ddb09811dda..8f9cb270729 100644 --- a/spec/javascripts/u2f/authenticate_spec.js +++ b/spec/javascripts/u2f/authenticate_spec.js @@ -4,10 +4,10 @@ import 'vendor/u2f'; import MockU2FDevice from './mock_u2f_device'; describe('U2FAuthenticate', function() { - preloadFixtures('u2f/authenticate.html.raw'); + preloadFixtures('u2f/authenticate.html'); beforeEach(() => { - loadFixtures('u2f/authenticate.html.raw'); + loadFixtures('u2f/authenticate.html'); this.u2fDevice = new MockU2FDevice(); this.container = $('#js-authenticate-u2f'); this.component = new U2FAuthenticate( diff --git a/spec/javascripts/u2f/register_spec.js b/spec/javascripts/u2f/register_spec.js index 261db3d66d7..a75ceca9f4c 100644 --- a/spec/javascripts/u2f/register_spec.js +++ b/spec/javascripts/u2f/register_spec.js @@ -4,10 +4,10 @@ import 'vendor/u2f'; import MockU2FDevice from './mock_u2f_device'; describe('U2FRegister', function() { - preloadFixtures('u2f/register.html.raw'); + preloadFixtures('u2f/register.html'); beforeEach(done => { - loadFixtures('u2f/register.html.raw'); + loadFixtures('u2f/register.html'); this.u2fDevice = new MockU2FDevice(); this.container = $('#js-register-u2f'); this.component = new U2FRegister(this.container, $('#js-register-u2f-templates'), {}, 'token'); diff --git a/spec/javascripts/user_popovers_spec.js b/spec/javascripts/user_popovers_spec.js index b174a51c1a0..c0d5ee9c446 100644 --- a/spec/javascripts/user_popovers_spec.js +++ b/spec/javascripts/user_popovers_spec.js @@ -2,7 +2,7 @@ import initUserPopovers from '~/user_popovers'; import UsersCache from '~/lib/utils/users_cache'; describe('User Popovers', () => { - const fixtureTemplate = 'merge_requests/diff_comment.html.raw'; + const fixtureTemplate = 'merge_requests/diff_comment.html'; preloadFixtures(fixtureTemplate); const selector = '.js-user-link'; diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_failed_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_failed_to_merge_spec.js index 3229ddd5e27..780bed1bf69 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_failed_to_merge_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_failed_to_merge_spec.js @@ -120,7 +120,7 @@ describe('MRWidgetFailedToMerge', () => { it('renders given error', () => { expect(vm.$el.querySelector('.has-error-message').textContent.trim()).toEqual( - 'Merge error happened.', + 'Merge error happened', ); }); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js index 30659ad16f3..368c997d318 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js @@ -415,7 +415,7 @@ describe('ReadyToMerge', () => { }); beforeEach(() => { - loadFixtures('merge_requests/merge_request_of_current_user.html.raw'); + loadFixtures('merge_requests/merge_request_of_current_user.html'); }); it('should call start and stop polling when MR merged', done => { diff --git a/spec/javascripts/vue_shared/components/markdown/field_spec.js b/spec/javascripts/vue_shared/components/markdown/field_spec.js index 79e0e756a7a..02d73e1849a 100644 --- a/spec/javascripts/vue_shared/components/markdown/field_spec.js +++ b/spec/javascripts/vue_shared/components/markdown/field_spec.js @@ -5,7 +5,7 @@ import fieldComponent from '~/vue_shared/components/markdown/field.vue'; function assertMarkdownTabs(isWrite, writeLink, previewLink, vm) { expect(writeLink.parentNode.classList.contains('active')).toEqual(isWrite); expect(previewLink.parentNode.classList.contains('active')).toEqual(!isWrite); - expect(vm.$el.querySelector('.md-preview').style.display).toEqual(isWrite ? 'none' : ''); + expect(vm.$el.querySelector('.md-preview-holder').style.display).toEqual(isWrite ? 'none' : ''); } describe('Markdown field component', () => { @@ -80,7 +80,9 @@ describe('Markdown field component', () => { previewLink.click(); Vue.nextTick(() => { - expect(vm.$el.querySelector('.md-preview').textContent.trim()).toContain('Loading…'); + expect(vm.$el.querySelector('.md-preview-holder').textContent.trim()).toContain( + 'Loading…', + ); done(); }); @@ -90,7 +92,7 @@ describe('Markdown field component', () => { previewLink.click(); setTimeout(() => { - expect(vm.$el.querySelector('.md-preview').innerHTML).toContain( + expect(vm.$el.querySelector('.md-preview-holder').innerHTML).toContain( '<p>markdown preview</p>', ); 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 e8b41e8eeff..852558a83bc 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,7 +17,7 @@ const DEFAULT_PROPS = { const UserPopover = Vue.extend(userPopover); describe('User Popover Component', () => { - const fixtureTemplate = 'merge_requests/diff_comment.html.raw'; + const fixtureTemplate = 'merge_requests/diff_comment.html'; preloadFixtures(fixtureTemplate); let vm; diff --git a/spec/javascripts/zen_mode_spec.js b/spec/javascripts/zen_mode_spec.js index e5f1e6ae937..8f662c71c7a 100644 --- a/spec/javascripts/zen_mode_spec.js +++ b/spec/javascripts/zen_mode_spec.js @@ -6,7 +6,7 @@ import ZenMode from '~/zen_mode'; describe('ZenMode', () => { let zen; let dropzoneForElementSpy; - const fixtureName = 'snippets/show.html.raw'; + const fixtureName = 'snippets/show.html'; preloadFixtures(fixtureName); diff --git a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb index 55c41e55437..72dfd6ff9ea 100644 --- a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb @@ -30,6 +30,23 @@ describe Banzai::Filter::MergeRequestReferenceFilter do end end + describe 'all references' do + let(:doc) { reference_filter(merge.to_reference) } + let(:tag_el) { doc.css('a').first } + + it 'adds merge request iid' do + expect(tag_el["data-iid"]).to eq(merge.iid.to_s) + end + + it 'adds project data attribute with project id' do + expect(tag_el["data-project-path"]).to eq(project.full_path) + end + + it 'does not add `has-tooltip` class' do + expect(tag_el["class"]).not_to include('has-tooltip') + end + end + context 'internal reference' do let(:reference) { merge.to_reference } @@ -57,9 +74,9 @@ describe Banzai::Filter::MergeRequestReferenceFilter do expect(reference_filter(act).to_html).to eq exp end - it 'includes a title attribute' do + it 'has no title' do doc = reference_filter("Merge #{reference}") - expect(doc.css('a').first.attr('title')).to eq merge.title + expect(doc.css('a').first.attr('title')).to eq "" end it 'escapes the title attribute' do @@ -69,9 +86,9 @@ describe Banzai::Filter::MergeRequestReferenceFilter do expect(doc.text).to eq "Merge #{reference}" end - it 'includes default classes' do + it 'includes default classes, without tooltip' do doc = reference_filter("Merge #{reference}") - expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-merge_request has-tooltip' + expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-merge_request' end it 'includes a data-project attribute' do diff --git a/spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb b/spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb new file mode 100644 index 00000000000..4a81a37d341 --- /dev/null +++ b/spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Gitlab::BackgroundMigration::PopulateMergeRequestAssigneesTable, :migration, schema: 20190315191339 do + let(:namespaces) { table(:namespaces) } + let(:projects) { table(:projects) } + let(:users) { table(:users) } + + let(:user) { users.create!(email: 'test@example.com', projects_limit: 100, username: 'test') } + let(:user_2) { users.create!(email: 'test2@example.com', projects_limit: 100, username: 'test') } + let(:user_3) { users.create!(email: 'test3@example.com', projects_limit: 100, username: 'test') } + + let(:namespace) { namespaces.create(name: 'gitlab', path: 'gitlab-org') } + let(:project) { projects.create(namespace_id: namespace.id, name: 'foo') } + let(:merge_requests) { table(:merge_requests) } + let(:merge_request_assignees) { table(:merge_request_assignees) } + + def create_merge_request(id, params = {}) + params.merge!(id: id, + target_project_id: project.id, + target_branch: 'master', + source_project_id: project.id, + source_branch: 'mr name', + title: "mr name#{id}") + + merge_requests.create(params) + end + + describe '#perform' do + it 'creates merge_request_assignees rows according to merge_requests' do + create_merge_request(2, assignee_id: user.id) + create_merge_request(3, assignee_id: user_2.id) + create_merge_request(4, assignee_id: user_3.id) + # Test filtering already migrated row + merge_request_assignees.create!(merge_request_id: 2, user_id: user_3.id) + + subject.perform(1, 4) + + rows = merge_request_assignees.order(:id).map { |row| row.attributes.slice('merge_request_id', 'user_id') } + existing_rows = [ + { 'merge_request_id' => 2, 'user_id' => user_3.id } + ] + created_rows = [ + { 'merge_request_id' => 3, 'user_id' => user_2.id }, + { 'merge_request_id' => 4, 'user_id' => user_3.id } + ] + expected_rows = existing_rows + created_rows + + expect(rows.size).to eq(expected_rows.size) + expected_rows.each do |expected_row| + expect(rows).to include(expected_row) + end + end + end +end diff --git a/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb index dab0fb51bcc..5181e9c1583 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb @@ -48,6 +48,24 @@ describe Gitlab::Ci::Pipeline::Chain::Command do end end + describe '#merge_request_ref_exists?' do + subject { command.merge_request_ref_exists? } + + let!(:merge_request) { create(:merge_request, source_project: project, target_project: project) } + + context 'for existing merge request ref' do + let(:origin_ref) { merge_request.ref_path } + + it { is_expected.to eq(true) } + end + + context 'for branch ref' do + let(:origin_ref) { merge_request.source_branch } + + it { is_expected.to eq(false) } + end + end + describe '#ref' do subject { command.ref } diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb index 8ba56d73838..7d750877d09 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb @@ -10,12 +10,33 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities do let(:command) do Gitlab::Ci::Pipeline::Chain::Command.new( - project: project, current_user: user, origin_ref: ref) + project: project, current_user: user, origin_ref: origin_ref, merge_request: merge_request) end let(:step) { described_class.new(pipeline, command) } let(:ref) { 'master' } + let(:origin_ref) { ref } + let(:merge_request) { nil } + + shared_context 'detached merge request pipeline' do + let(:merge_request) do + create(:merge_request, + source_project: project, + source_branch: ref, + target_project: project, + target_branch: 'feature') + end + + let(:pipeline) do + build(:ci_pipeline, + source: :merge_request_event, + merge_request: merge_request, + project: project) + end + + let(:origin_ref) { merge_request.ref_path } + end context 'when users has no ability to run a pipeline' do before do @@ -58,6 +79,12 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities do it { is_expected.to be_truthy } + context 'when pipeline is a detached merge request pipeline' do + include_context 'detached merge request pipeline' + + it { is_expected.to be_truthy } + end + context 'when the branch is protected' do let!(:protected_branch) do create(:protected_branch, project: project, name: ref) @@ -65,6 +92,12 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities do it { is_expected.to be_falsey } + context 'when pipeline is a detached merge request pipeline' do + include_context 'detached merge request pipeline' + + it { is_expected.to be_falsey } + end + context 'when developers are allowed to merge' do let!(:protected_branch) do create(:protected_branch, @@ -74,6 +107,12 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities do end it { is_expected.to be_truthy } + + context 'when pipeline is a detached merge request pipeline' do + include_context 'detached merge request pipeline' + + it { is_expected.to be_truthy } + end end end @@ -112,6 +151,12 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities do end it { is_expected.to be_truthy } + + context 'when pipeline is a detached merge request pipeline' do + include_context 'detached merge request pipeline' + + it { is_expected.to be_truthy } + end end context 'when the tag is protected' do diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb index a7cad423d09..2e8c9d70098 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb @@ -42,6 +42,25 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Repository do end end + context 'when origin ref is a merge request ref' do + let(:command) do + Gitlab::Ci::Pipeline::Chain::Command.new( + project: project, current_user: user, origin_ref: origin_ref, checkout_sha: checkout_sha) + end + + let(:merge_request) { create(:merge_request, source_project: project, target_project: project) } + let(:origin_ref) { merge_request.ref_path } + let(:checkout_sha) { project.repository.commit(merge_request.ref_path).id } + + it 'does not break the chain' do + expect(step.break?).to be false + end + + it 'does not append pipeline errors' do + expect(pipeline.errors).to be_empty + end + end + context 'when ref is ambiguous' do let(:project) do create(:project, :repository).tap do |proj| diff --git a/spec/lib/gitlab/current_settings_spec.rb b/spec/lib/gitlab/current_settings_spec.rb index 17d5eae24f5..909dbffa38f 100644 --- a/spec/lib/gitlab/current_settings_spec.rb +++ b/spec/lib/gitlab/current_settings_spec.rb @@ -115,9 +115,8 @@ describe Gitlab::CurrentSettings do shared_examples 'a non-persisted ApplicationSetting object' do let(:current_settings) { described_class.current_application_settings } - it 'returns a non-persisted ApplicationSetting object' do - expect(current_settings).to be_a(ApplicationSetting) - expect(current_settings).not_to be_persisted + it 'returns a FakeApplicationSettings object' do + expect(current_settings).to be_a(Gitlab::FakeApplicationSettings) end it 'uses the default value from ApplicationSetting.defaults' do @@ -146,6 +145,16 @@ describe Gitlab::CurrentSettings do it 'uses the value from the DB attribute if present and not overridden by an accessor' do expect(current_settings.home_page_url).to eq(db_settings.home_page_url) end + + context 'when a new column is used before being migrated' do + before do + allow(ApplicationSetting).to receive(:defaults).and_return({ foo: 'bar' }) + end + + it 'uses the default value if present' do + expect(current_settings.foo).to eq('bar') + end + end end end diff --git a/spec/lib/gitlab/diff/suggestion_spec.rb b/spec/lib/gitlab/diff/suggestion_spec.rb new file mode 100644 index 00000000000..71fd25df698 --- /dev/null +++ b/spec/lib/gitlab/diff/suggestion_spec.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Diff::Suggestion do + shared_examples 'correct suggestion raw content' do + it 'returns correct raw data' do + expect(suggestion.to_hash).to include(from_content: expected_lines.join, + to_content: "#{text}\n", + lines_above: above, + lines_below: below) + end + end + + let(:merge_request) { create(:merge_request) } + let(:project) { merge_request.project } + let(:position) do + Gitlab::Diff::Position.new(old_path: "files/ruby/popen.rb", + new_path: "files/ruby/popen.rb", + old_line: nil, + new_line: 9, + diff_refs: merge_request.diff_refs) + end + let(:diff_file) do + position.diff_file(project.repository) + end + let(:text) { "# parsed suggestion content\n# with comments" } + + def blob_lines_data(from_line, to_line) + diff_file.new_blob_lines_between(from_line, to_line) + end + + def blob_data + blob = diff_file.new_blob + blob.load_all_data! + blob.data + end + + let(:suggestion) do + described_class.new(text, line: line, above: above, below: below, diff_file: diff_file) + end + + describe '#to_hash' do + context 'when changing content surpasses the top limit' do + let(:line) { 4 } + let(:above) { 5 } + let(:below) { 2 } + let(:expected_above) { line - 1 } + let(:expected_below) { below } + let(:expected_lines) { blob_lines_data(line - expected_above, line + expected_below) } + + it_behaves_like 'correct suggestion raw content' + end + + context 'when changing content surpasses the amount of lines in the blob (bottom)' do + let(:line) { 5 } + let(:above) { 1 } + let(:below) { blob_data.lines.size + 10 } + let(:expected_below) { below } + let(:expected_above) { above } + let(:expected_lines) { blob_lines_data(line - expected_above, line + expected_below) } + + it_behaves_like 'correct suggestion raw content' + end + + context 'when lines are within blob lines boundary' do + let(:line) { 5 } + let(:above) { 2 } + let(:below) { 3 } + let(:expected_below) { below } + let(:expected_above) { above } + let(:expected_lines) { blob_lines_data(line - expected_above, line + expected_below) } + + it_behaves_like 'correct suggestion raw content' + end + + context 'when no extra lines (single-line suggestion)' do + let(:line) { 5 } + let(:above) { 0 } + let(:below) { 0 } + let(:expected_below) { below } + let(:expected_above) { above } + let(:expected_lines) { blob_lines_data(line - expected_above, line + expected_below) } + + it_behaves_like 'correct suggestion raw content' + end + end +end diff --git a/spec/lib/gitlab/diff/suggestions_parser_spec.rb b/spec/lib/gitlab/diff/suggestions_parser_spec.rb new file mode 100644 index 00000000000..1119ea04995 --- /dev/null +++ b/spec/lib/gitlab/diff/suggestions_parser_spec.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Diff::SuggestionsParser do + describe '.parse' do + let(:merge_request) { create(:merge_request) } + let(:project) { merge_request.project } + let(:position) do + Gitlab::Diff::Position.new(old_path: "files/ruby/popen.rb", + new_path: "files/ruby/popen.rb", + old_line: nil, + new_line: 9, + diff_refs: merge_request.diff_refs) + end + + let(:diff_file) do + position.diff_file(project.repository) + end + + subject do + described_class.parse(markdown, project: merge_request.project, + position: position) + end + + def blob_lines_data(from_line, to_line) + diff_file.new_blob_lines_between(from_line, to_line).join + end + + context 'single-line suggestions' do + let(:markdown) do + <<-MARKDOWN.strip_heredoc + ```suggestion + foo + bar + ``` + + ``` + nothing + ``` + + ```suggestion + xpto + baz + ``` + + ```thing + this is not a suggestion, it's a thing + ``` + MARKDOWN + end + + it 'returns a list of Gitlab::Diff::Suggestion' do + expect(subject).to all(be_a(Gitlab::Diff::Suggestion)) + expect(subject.size).to eq(2) + end + + it 'parsed suggestion has correct data' do + from_line, to_line = position.new_line, position.new_line + + expect(subject.first.to_hash).to include(from_content: blob_lines_data(from_line, to_line), + to_content: " foo\n bar\n", + lines_above: 0, + lines_below: 0) + + expect(subject.second.to_hash).to include(from_content: blob_lines_data(from_line, to_line), + to_content: " xpto\n baz\n", + lines_above: 0, + lines_below: 0) + end + end + end +end diff --git a/spec/lib/gitlab/encoding_helper_spec.rb b/spec/lib/gitlab/encoding_helper_spec.rb index 429816efec3..88ea98eb1e1 100644 --- a/spec/lib/gitlab/encoding_helper_spec.rb +++ b/spec/lib/gitlab/encoding_helper_spec.rb @@ -189,14 +189,23 @@ describe Gitlab::EncodingHelper do end end - describe '#binary_stringio' do + describe '#binary_io' do it 'does not mutate the original string encoding' do test = 'my-test' - io_stream = ext_class.binary_stringio(test) + io_stream = ext_class.binary_io(test) expect(io_stream.external_encoding.name).to eq('ASCII-8BIT') expect(test.encoding.name).to eq('UTF-8') end + + it 'returns a copy of the IO with the correct encoding' do + test = fixture_file_upload('spec/fixtures/doc_sample.txt').to_io + + io_stream = ext_class.binary_io(test) + + expect(io_stream.external_encoding.name).to eq('ASCII-8BIT') + expect(test).not_to eq(io_stream) + end end end diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb index 3fb41a626b2..4a4ac833e39 100644 --- a/spec/lib/gitlab/git/commit_spec.rb +++ b/spec/lib/gitlab/git/commit_spec.rb @@ -537,6 +537,18 @@ describe Gitlab::Git::Commit, :seed_helper do end end + describe '#gitaly_commit?' do + context 'when the commit data comes from gitaly' do + it { expect(commit.gitaly_commit?).to eq(true) } + end + + context 'when the commit data comes from a Hash' do + let(:commit) { described_class.new(repository, sample_commit_hash) } + + it { expect(commit.gitaly_commit?).to eq(false) } + end + end + describe '#has_zero_stats?' do it { expect(commit.has_zero_stats?).to eq(false) } end diff --git a/spec/lib/gitlab/git/pre_receive_error_spec.rb b/spec/lib/gitlab/git/pre_receive_error_spec.rb index 1b8be62dec6..cb030e38032 100644 --- a/spec/lib/gitlab/git/pre_receive_error_spec.rb +++ b/spec/lib/gitlab/git/pre_receive_error_spec.rb @@ -1,9 +1,19 @@ require 'spec_helper' describe Gitlab::Git::PreReceiveError do - it 'makes its message HTML-friendly' do - ex = described_class.new("hello\nworld\n") + Gitlab::Git::PreReceiveError::SAFE_MESSAGE_PREFIXES.each do |prefix| + context "error messages prefixed with #{prefix}" do + it 'accepts only errors lines with the prefix' do + ex = described_class.new("#{prefix} Hello,\nworld!") - expect(ex.message).to eq('hello<br>world<br>') + expect(ex.message).to eq('Hello,') + end + + it 'makes its message HTML-friendly' do + ex = described_class.new("#{prefix} Hello,\n#{prefix} world!\n") + + expect(ex.message).to eq('Hello,<br>world!') + end + end end end diff --git a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb index d7bd757149d..6d6107ca3e7 100644 --- a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb @@ -221,6 +221,21 @@ describe Gitlab::GitalyClient::CommitService do expect(commit).to eq(commit_dbl) end end + + context 'when caching of the ref name is enabled' do + it 'returns a cached commit' do + expect_any_instance_of(Gitaly::CommitService::Stub).to receive(:find_commit).once.and_return(double(commit: commit_dbl)) + + commit = nil + 2.times do + ::Gitlab::GitalyClient.allow_ref_name_caching do + commit = described_class.new(repository).find_commit('master') + end + end + + expect(commit).to eq(commit_dbl) + end + end end end diff --git a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb index b37fe2686b6..7579a6577b9 100644 --- a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb @@ -39,7 +39,7 @@ describe Gitlab::GitalyClient::OperationService do context "when pre_receive_error is present" do let(:response) do - Gitaly::UserCreateBranchResponse.new(pre_receive_error: "something failed") + Gitaly::UserCreateBranchResponse.new(pre_receive_error: "GitLab: something failed") end it "throws a PreReceive exception" do @@ -80,7 +80,7 @@ describe Gitlab::GitalyClient::OperationService do context "when pre_receive_error is present" do let(:response) do - Gitaly::UserUpdateBranchResponse.new(pre_receive_error: "something failed") + Gitaly::UserUpdateBranchResponse.new(pre_receive_error: "GitLab: something failed") end it "throws a PreReceive exception" do @@ -117,7 +117,7 @@ describe Gitlab::GitalyClient::OperationService do context "when pre_receive_error is present" do let(:response) do - Gitaly::UserDeleteBranchResponse.new(pre_receive_error: "something failed") + Gitaly::UserDeleteBranchResponse.new(pre_receive_error: "GitLab: something failed") end it "throws a PreReceive exception" do @@ -175,7 +175,7 @@ describe Gitlab::GitalyClient::OperationService do shared_examples 'cherry pick and revert errors' do context 'when a pre_receive_error is present' do - let(:response) { response_class.new(pre_receive_error: "something failed") } + let(:response) { response_class.new(pre_receive_error: "GitLab: something failed") } it 'raises a PreReceiveError' do expect { subject }.to raise_error(Gitlab::Git::PreReceiveError, "something failed") @@ -313,7 +313,7 @@ describe Gitlab::GitalyClient::OperationService do end context 'when a pre_receive_error is present' do - let(:response) { Gitaly::UserCommitFilesResponse.new(pre_receive_error: "something failed") } + let(:response) { Gitaly::UserCommitFilesResponse.new(pre_receive_error: "GitLab: something failed") } it 'raises a PreReceiveError' do expect { subject }.to raise_error(Gitlab::Git::PreReceiveError, "something failed") diff --git a/spec/lib/gitlab/gitaly_client/ref_service_spec.rb b/spec/lib/gitlab/gitaly_client/ref_service_spec.rb index 400d426c949..0dab39575b9 100644 --- a/spec/lib/gitlab/gitaly_client/ref_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/ref_service_spec.rb @@ -89,6 +89,16 @@ describe Gitlab::GitalyClient::RefService do end end + describe '#list_new_blobs' do + it 'raises DeadlineExceeded when timeout is too small' do + newrev = '54fcc214b94e78d7a41a9a8fe6d87a5e59500e51' + + expect do + client.list_new_blobs(newrev, dynamic_timeout: 0.001) + end.to raise_error(GRPC::DeadlineExceeded) + end + end + describe '#local_branches' do it 'sends a find_local_branches message' do expect_any_instance_of(Gitaly::RefService::Stub) diff --git a/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb b/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb index 680de47de2b..2e4a7c36fb8 100644 --- a/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb @@ -11,6 +11,7 @@ describe Gitlab::GithubImport::Importer::PullRequestImporter, :clean_gitlab_redi let(:source_commit) { project.repository.commit('feature') } let(:target_commit) { project.repository.commit('master') } let(:milestone) { create(:milestone, project: project) } + let(:state) { :closed } let(:pull_request) do alice = Gitlab::GithubImport::Representation::User.new(id: 4, login: 'alice') @@ -26,13 +27,13 @@ describe Gitlab::GithubImport::Importer::PullRequestImporter, :clean_gitlab_redi source_repository_id: 400, target_repository_id: 200, source_repository_owner: 'alice', - state: :closed, + state: state, milestone_number: milestone.iid, author: alice, assignee: alice, created_at: created_at, updated_at: updated_at, - merged_at: merged_at + merged_at: state == :closed && merged_at ) end @@ -260,58 +261,63 @@ describe Gitlab::GithubImport::Importer::PullRequestImporter, :clean_gitlab_redi end it 'does not create the source branch if merge request is merged' do - mr, exists = importer.create_merge_request - - importer.insert_git_data(mr, exists) + mr = insert_git_data expect(project.repository.branch_exists?(mr.source_branch)).to be_falsey expect(project.repository.branch_exists?(mr.target_branch)).to be_truthy end - it 'creates the source branch if merge request is open' do - mr, exists = importer.create_merge_request - mr.state = 'opened' - mr.save + context 'when merge request is open' do + let(:state) { :opened } - # Ensure the project creator is creating the branches because the - # merge request author may not have access to push to this - # repository. The project owner may also be a group. - allow(project.repository).to receive(:add_branch).with(project.creator, anything, anything).and_call_original + it 'creates the source branch' do + # Ensure the project creator is creating the branches because the + # merge request author may not have access to push to this + # repository. The project owner may also be a group. + allow(project.repository).to receive(:add_branch).with(project.creator, anything, anything).and_call_original - importer.insert_git_data(mr, exists) + mr = insert_git_data - expect(project.repository.branch_exists?(mr.source_branch)).to be_truthy - expect(project.repository.branch_exists?(mr.target_branch)).to be_truthy - end + expect(project.repository.branch_exists?(mr.source_branch)).to be_truthy + expect(project.repository.branch_exists?(mr.target_branch)).to be_truthy + end - it 'ignores Git errors when creating a branch' do - mr, exists = importer.create_merge_request - mr.state = 'opened' - mr.save + it 'is able to retry on pre-receive errors' do + expect(importer).to receive(:insert_or_replace_git_data).twice.and_call_original + expect(project.repository).to receive(:add_branch).and_raise('exception') - expect(project.repository).to receive(:add_branch).and_raise(Gitlab::Git::CommandError) - expect(Gitlab::Sentry).to receive(:track_acceptable_exception).and_call_original + expect { insert_git_data }.to raise_error('exception') - importer.insert_git_data(mr, exists) + expect(project.repository).to receive(:add_branch).with(project.creator, anything, anything).and_call_original - expect(project.repository.branch_exists?(mr.source_branch)).to be_falsey - expect(project.repository.branch_exists?(mr.target_branch)).to be_truthy + mr = insert_git_data + + expect(project.repository.branch_exists?(mr.source_branch)).to be_truthy + expect(project.repository.branch_exists?(mr.target_branch)).to be_truthy + expect(mr.merge_request_diffs).to be_one + end + + it 'ignores Git command errors when creating a branch' do + expect(project.repository).to receive(:add_branch).and_raise(Gitlab::Git::CommandError) + expect(Gitlab::Sentry).to receive(:track_acceptable_exception).and_call_original + + mr = insert_git_data + + expect(project.repository.branch_exists?(mr.source_branch)).to be_falsey + expect(project.repository.branch_exists?(mr.target_branch)).to be_truthy + end end it 'creates the merge request diffs' do - mr, exists = importer.create_merge_request - - importer.insert_git_data(mr, exists) + mr = insert_git_data expect(mr.merge_request_diffs.exists?).to eq(true) end it 'creates the merge request diff commits' do - mr, exists = importer.create_merge_request - - importer.insert_git_data(mr, exists) + mr = insert_git_data - diff = mr.merge_request_diffs.take + diff = mr.merge_request_diffs.reload.first expect(diff.merge_request_diff_commits.exists?).to eq(true) end @@ -327,5 +333,11 @@ describe Gitlab::GithubImport::Importer::PullRequestImporter, :clean_gitlab_redi expect(mr.merge_request_diffs.exists?).to eq(true) end end + + def insert_git_data + mr, exists = importer.create_merge_request + importer.insert_git_data(mr, exists) + mr + end end end diff --git a/spec/lib/gitlab/gl_repository/repo_type_spec.rb b/spec/lib/gitlab/gl_repository/repo_type_spec.rb new file mode 100644 index 00000000000..f06a2448ff7 --- /dev/null +++ b/spec/lib/gitlab/gl_repository/repo_type_spec.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe Gitlab::GlRepository::RepoType do + set(:project) { create(:project) } + + shared_examples 'a repo type' do + describe "#identifier_for_subject" do + subject { described_class.identifier_for_subject(project) } + + it { is_expected.to eq(expected_identifier) } + end + + describe "#fetch_id" do + it "finds an id match in the identifier" do + expect(described_class.fetch_id(expected_identifier)).to eq(expected_id) + end + + it 'does not break on other identifiers' do + expect(described_class.fetch_id("wiki-noid")).to eq(nil) + end + end + + describe "#path_suffix" do + subject { described_class.path_suffix } + + it { is_expected.to eq(expected_suffix) } + end + + describe "#repository_for" do + it "finds the repository for the repo type" do + expect(described_class.repository_for(project)).to eq(expected_repository) + end + end + end + + describe Gitlab::GlRepository::PROJECT do + it_behaves_like 'a repo type' do + let(:expected_identifier) { "project-#{project.id}" } + let(:expected_id) { project.id.to_s } + let(:expected_suffix) { "" } + let(:expected_repository) { project.repository } + end + + it "knows its type" do + expect(described_class).not_to be_wiki + expect(described_class).to be_project + end + end + + describe Gitlab::GlRepository::WIKI do + it_behaves_like 'a repo type' do + let(:expected_identifier) { "wiki-#{project.id}" } + let(:expected_id) { project.id.to_s } + let(:expected_suffix) { ".wiki" } + let(:expected_repository) { project.wiki.repository } + end + + it "knows its type" do + expect(described_class).to be_wiki + expect(described_class).not_to be_project + end + end +end diff --git a/spec/lib/gitlab/gl_repository_spec.rb b/spec/lib/gitlab/gl_repository_spec.rb index 4e09020471b..d4b6c629659 100644 --- a/spec/lib/gitlab/gl_repository_spec.rb +++ b/spec/lib/gitlab/gl_repository_spec.rb @@ -5,11 +5,11 @@ describe ::Gitlab::GlRepository do set(:project) { create(:project, :repository) } it 'parses a project gl_repository' do - expect(described_class.parse("project-#{project.id}")).to eq([project, false]) + expect(described_class.parse("project-#{project.id}")).to eq([project, Gitlab::GlRepository::PROJECT]) end it 'parses a wiki gl_repository' do - expect(described_class.parse("wiki-#{project.id}")).to eq([project, true]) + expect(described_class.parse("wiki-#{project.id}")).to eq([project, Gitlab::GlRepository::WIKI]) end it 'throws an argument error on an invalid gl_repository' do diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 01da3ea7081..e418516569a 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -100,6 +100,8 @@ merge_requests: - head_pipeline - latest_merge_request_diff - merge_request_pipelines +- merge_request_assignees +- suggestions merge_request_diff: - merge_request - merge_request_diff_commits @@ -351,3 +353,5 @@ resource_label_events: - label error_tracking_setting: - project +suggestions: +- note diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index ee96e5c4d42..496567b0036 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -608,3 +608,14 @@ ErrorTracking::ProjectErrorTrackingSetting: - project_id - project_name - organization_name +Suggestion: +- id +- note_id +- relative_order +- applied +- commit_id +- from_content +- to_content +- outdated +- lines_above +- lines_below diff --git a/spec/lib/gitlab/quick_actions/command_definition_spec.rb b/spec/lib/gitlab/quick_actions/command_definition_spec.rb index 136cfb5bcc5..b6e0adbc1c2 100644 --- a/spec/lib/gitlab/quick_actions/command_definition_spec.rb +++ b/spec/lib/gitlab/quick_actions/command_definition_spec.rb @@ -69,6 +69,36 @@ describe Gitlab::QuickActions::CommandDefinition do expect(subject.available?(opts)).to be true end end + + context "when the command has types" do + before do + subject.types = [Issue, Commit] + end + + context "when the command target type is allowed" do + it "returns true" do + opts[:quick_action_target] = Issue.new + expect(subject.available?(opts)).to be true + end + end + + context "when the command target type is not allowed" do + it "returns true" do + opts[:quick_action_target] = MergeRequest.new + expect(subject.available?(opts)).to be false + end + end + end + + context "when the command has no types" do + it "any target type is allowed" do + opts[:quick_action_target] = Issue.new + expect(subject.available?(opts)).to be true + + opts[:quick_action_target] = MergeRequest.new + expect(subject.available?(opts)).to be true + end + end end describe "#execute" do diff --git a/spec/lib/gitlab/quick_actions/dsl_spec.rb b/spec/lib/gitlab/quick_actions/dsl_spec.rb index fd4df8694ba..185adab1ff6 100644 --- a/spec/lib/gitlab/quick_actions/dsl_spec.rb +++ b/spec/lib/gitlab/quick_actions/dsl_spec.rb @@ -48,13 +48,19 @@ describe Gitlab::QuickActions::Dsl do substitution :something do |text| "#{text} Some complicated thing you want in here" end + + desc 'A command with types' + types Issue, Commit + command :has_types do + "Has Issue and Commit types" + end end end describe '.command_definitions' do it 'returns an array with commands definitions' do no_args_def, explanation_with_aliases_def, dynamic_description_def, - cc_def, cond_action_def, with_params_parsing_def, substitution_def = + cc_def, cond_action_def, with_params_parsing_def, substitution_def, has_types = DummyClass.command_definitions expect(no_args_def.name).to eq(:no_args) @@ -63,6 +69,7 @@ describe Gitlab::QuickActions::Dsl do expect(no_args_def.explanation).to eq('') expect(no_args_def.params).to eq([]) expect(no_args_def.condition_block).to be_nil + expect(no_args_def.types).to eq([]) expect(no_args_def.action_block).to be_a_kind_of(Proc) expect(no_args_def.parse_params_block).to be_nil expect(no_args_def.warning).to eq('') @@ -73,6 +80,7 @@ describe Gitlab::QuickActions::Dsl do expect(explanation_with_aliases_def.explanation).to eq('Static explanation') expect(explanation_with_aliases_def.params).to eq(['The first argument']) expect(explanation_with_aliases_def.condition_block).to be_nil + expect(explanation_with_aliases_def.types).to eq([]) expect(explanation_with_aliases_def.action_block).to be_a_kind_of(Proc) expect(explanation_with_aliases_def.parse_params_block).to be_nil expect(explanation_with_aliases_def.warning).to eq('Possible problem!') @@ -83,6 +91,7 @@ describe Gitlab::QuickActions::Dsl do expect(dynamic_description_def.explanation).to eq('') expect(dynamic_description_def.params).to eq(['The first argument', 'The second argument']) expect(dynamic_description_def.condition_block).to be_nil + expect(dynamic_description_def.types).to eq([]) expect(dynamic_description_def.action_block).to be_a_kind_of(Proc) expect(dynamic_description_def.parse_params_block).to be_nil expect(dynamic_description_def.warning).to eq('') @@ -93,6 +102,7 @@ describe Gitlab::QuickActions::Dsl do expect(cc_def.explanation).to eq('') expect(cc_def.params).to eq([]) expect(cc_def.condition_block).to be_nil + expect(cc_def.types).to eq([]) expect(cc_def.action_block).to be_nil expect(cc_def.parse_params_block).to be_nil expect(cc_def.warning).to eq('') @@ -103,6 +113,7 @@ describe Gitlab::QuickActions::Dsl do expect(cond_action_def.explanation).to be_a_kind_of(Proc) expect(cond_action_def.params).to eq([]) expect(cond_action_def.condition_block).to be_a_kind_of(Proc) + expect(cond_action_def.types).to eq([]) expect(cond_action_def.action_block).to be_a_kind_of(Proc) expect(cond_action_def.parse_params_block).to be_nil expect(cond_action_def.warning).to eq('') @@ -113,6 +124,7 @@ describe Gitlab::QuickActions::Dsl do expect(with_params_parsing_def.explanation).to eq('') expect(with_params_parsing_def.params).to eq([]) expect(with_params_parsing_def.condition_block).to be_nil + expect(with_params_parsing_def.types).to eq([]) expect(with_params_parsing_def.action_block).to be_a_kind_of(Proc) expect(with_params_parsing_def.parse_params_block).to be_a_kind_of(Proc) expect(with_params_parsing_def.warning).to eq('') @@ -123,9 +135,21 @@ describe Gitlab::QuickActions::Dsl do expect(substitution_def.explanation).to eq('') expect(substitution_def.params).to eq(['<Comment>']) expect(substitution_def.condition_block).to be_nil + expect(substitution_def.types).to eq([]) expect(substitution_def.action_block.call('text')).to eq('text Some complicated thing you want in here') expect(substitution_def.parse_params_block).to be_nil expect(substitution_def.warning).to eq('') + + expect(has_types.name).to eq(:has_types) + expect(has_types.aliases).to eq([]) + expect(has_types.description).to eq('A command with types') + expect(has_types.explanation).to eq('') + expect(has_types.params).to eq([]) + expect(has_types.condition_block).to be_nil + expect(has_types.types).to eq([Issue, Commit]) + expect(has_types.action_block).to be_a_kind_of(Proc) + expect(has_types.parse_params_block).to be_nil + expect(has_types.warning).to eq('') end end end diff --git a/spec/lib/gitlab/repo_path_spec.rb b/spec/lib/gitlab/repo_path_spec.rb index 13940713dfc..4c7ca4e2b57 100644 --- a/spec/lib/gitlab/repo_path_spec.rb +++ b/spec/lib/gitlab/repo_path_spec.rb @@ -6,43 +6,47 @@ describe ::Gitlab::RepoPath do context 'a repository storage path' do it 'parses a full repository path' do - expect(described_class.parse(project.repository.full_path)).to eq([project, false, nil]) + expect(described_class.parse(project.repository.full_path)).to eq([project, Gitlab::GlRepository::PROJECT, nil]) end it 'parses a full wiki path' do - expect(described_class.parse(project.wiki.repository.full_path)).to eq([project, true, nil]) + expect(described_class.parse(project.wiki.repository.full_path)).to eq([project, Gitlab::GlRepository::WIKI, nil]) end end context 'a relative path' do it 'parses a relative repository path' do - expect(described_class.parse(project.full_path + '.git')).to eq([project, false, nil]) + expect(described_class.parse(project.full_path + '.git')).to eq([project, Gitlab::GlRepository::PROJECT, nil]) end it 'parses a relative wiki path' do - expect(described_class.parse(project.full_path + '.wiki.git')).to eq([project, true, nil]) + expect(described_class.parse(project.full_path + '.wiki.git')).to eq([project, Gitlab::GlRepository::WIKI, nil]) end it 'parses a relative path starting with /' do - expect(described_class.parse('/' + project.full_path + '.git')).to eq([project, false, nil]) + expect(described_class.parse('/' + project.full_path + '.git')).to eq([project, Gitlab::GlRepository::PROJECT, nil]) end context 'of a redirected project' do let(:redirect) { project.route.create_redirect('foo/bar') } it 'parses a relative repository path' do - expect(described_class.parse(redirect.path + '.git')).to eq([project, false, 'foo/bar']) + expect(described_class.parse(redirect.path + '.git')).to eq([project, Gitlab::GlRepository::PROJECT, 'foo/bar']) end it 'parses a relative wiki path' do - expect(described_class.parse(redirect.path + '.wiki.git')).to eq([project, true, 'foo/bar.wiki']) + expect(described_class.parse(redirect.path + '.wiki.git')).to eq([project, Gitlab::GlRepository::WIKI, 'foo/bar.wiki']) end it 'parses a relative path starting with /' do - expect(described_class.parse('/' + redirect.path + '.git')).to eq([project, false, 'foo/bar']) + expect(described_class.parse('/' + redirect.path + '.git')).to eq([project, Gitlab::GlRepository::PROJECT, 'foo/bar']) end end end + + it "returns nil for non existent paths" do + expect(described_class.parse("path/non-existent.git")).to eq(nil) + end end describe '.find_project' do diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb index 7213eee5675..d88086b01b1 100644 --- a/spec/lib/gitlab/workhorse_spec.rb +++ b/spec/lib/gitlab/workhorse_spec.rb @@ -250,11 +250,11 @@ describe Gitlab::Workhorse do } end - subject { described_class.git_http_ok(repository, false, user, action) } + subject { described_class.git_http_ok(repository, Gitlab::GlRepository::PROJECT, user, action) } it { expect(subject).to include(params) } - context 'when is_wiki' do + context 'when the repo_type is a wiki' do let(:params) do { GL_ID: "user-#{user.id}", @@ -264,7 +264,7 @@ describe Gitlab::Workhorse do } end - subject { described_class.git_http_ok(repository, true, user, action) } + subject { described_class.git_http_ok(repository, Gitlab::GlRepository::WIKI, user, action) } it { expect(subject).to include(params) } end @@ -304,7 +304,7 @@ describe Gitlab::Workhorse do end context 'show_all_refs enabled' do - subject { described_class.git_http_ok(repository, false, user, action, show_all_refs: true) } + subject { described_class.git_http_ok(repository, Gitlab::GlRepository::PROJECT, user, action, show_all_refs: true) } it { is_expected.to include(ShowAllRefs: true) } end @@ -322,7 +322,7 @@ describe Gitlab::Workhorse do it { expect(subject).to include(gitaly_params) } context 'show_all_refs enabled' do - subject { described_class.git_http_ok(repository, false, user, action, show_all_refs: true) } + subject { described_class.git_http_ok(repository, Gitlab::GlRepository::PROJECT, user, action, show_all_refs: true) } it { is_expected.to include(ShowAllRefs: true) } end diff --git a/spec/lib/gitlab_spec.rb b/spec/lib/gitlab_spec.rb index 8232715d00e..767b5779a79 100644 --- a/spec/lib/gitlab_spec.rb +++ b/spec/lib/gitlab_spec.rb @@ -10,7 +10,7 @@ describe Gitlab do end describe '.revision' do - let(:cmd) { %W[#{described_class.config.git.bin_path} log --pretty=format:%h -n 1] } + let(:cmd) { %W[#{described_class.config.git.bin_path} log --pretty=format:%h --abbrev=11 -n 1] } around do |example| described_class.instance_variable_set(:@_revision, nil) diff --git a/spec/lib/sentry/client_spec.rb b/spec/lib/sentry/client_spec.rb index 88e7e2e5ebb..3333f8307ae 100644 --- a/spec/lib/sentry/client_spec.rb +++ b/spec/lib/sentry/client_spec.rb @@ -65,7 +65,9 @@ describe Sentry::Client do let(:issue_status) { 'unresolved' } let(:limit) { 20 } - let!(:sentry_api_request) { stub_sentry_request(sentry_url + '/issues/?limit=20&query=is:unresolved', body: issues_sample_response) } + let(:sentry_api_response) { issues_sample_response } + + let!(:sentry_api_request) { stub_sentry_request(sentry_url + '/issues/?limit=20&query=is:unresolved', body: sentry_api_response) } subject { client.list_issues(issue_status: issue_status, limit: limit) } @@ -74,6 +76,14 @@ describe Sentry::Client do it_behaves_like 'has correct return type', Gitlab::ErrorTracking::Error it_behaves_like 'has correct length', 1 + shared_examples 'has correct external_url' do + context 'external_url' do + it 'is constructed correctly' do + expect(subject[0].external_url).to eq('https://sentrytest.gitlab.com/sentry-org/sentry-project/issues/11') + end + end + end + context 'error object created from sentry response' do using RSpec::Parameterized::TableSyntax @@ -96,14 +106,10 @@ describe Sentry::Client do end with_them do - it { expect(subject[0].public_send(error_object)).to eq(issues_sample_response[0].dig(*sentry_response)) } + it { expect(subject[0].public_send(error_object)).to eq(sentry_api_response[0].dig(*sentry_response)) } end - context 'external_url' do - it 'is constructed correctly' do - expect(subject[0].external_url).to eq('https://sentrytest.gitlab.com/sentry-org/sentry-project/issues/11') - end - end + it_behaves_like 'has correct external_url' end context 'redirects' do @@ -135,12 +141,42 @@ describe Sentry::Client do expect(valid_req_stub).to have_been_requested end end + + context 'Older sentry versions where keys are not present' do + let(:sentry_api_response) do + issues_sample_response[0...1].map do |issue| + issue[:project].delete(:id) + issue + end + end + + it_behaves_like 'calls sentry api' + + it_behaves_like 'has correct return type', Gitlab::ErrorTracking::Error + it_behaves_like 'has correct length', 1 + + it_behaves_like 'has correct external_url' + end + + context 'essential keys missing in API response' do + let(:sentry_api_response) do + issues_sample_response[0...1].map do |issue| + issue.except(:id) + end + end + + it 'raises exception' do + expect { subject }.to raise_error(Sentry::Client::MissingKeysError, 'Sentry API response is missing keys. key not found: "id"') + end + end end describe '#list_projects' do let(:sentry_list_projects_url) { 'https://sentrytest.gitlab.com/api/0/projects/' } - let!(:sentry_api_request) { stub_sentry_request(sentry_list_projects_url, body: projects_sample_response) } + let(:sentry_api_response) { projects_sample_response } + + let!(:sentry_api_request) { stub_sentry_request(sentry_list_projects_url, body: sentry_api_response) } subject { client.list_projects } @@ -149,14 +185,31 @@ describe Sentry::Client do it_behaves_like 'has correct return type', Gitlab::ErrorTracking::Project it_behaves_like 'has correct length', 2 - context 'keys missing in API response' do - it 'raises exception' do - projects_sample_response[0].delete(:slug) + context 'essential keys missing in API response' do + let(:sentry_api_response) do + projects_sample_response[0...1].map do |project| + project.except(:slug) + end + end - stub_sentry_request(sentry_list_projects_url, body: projects_sample_response) + it 'raises exception' do + expect { subject }.to raise_error(Sentry::Client::MissingKeysError, 'Sentry API response is missing keys. key not found: "slug"') + end + end - expect { subject }.to raise_error(Sentry::Client::SentryError, 'Sentry API response is missing keys. key not found: "slug"') + context 'optional keys missing in sentry response' do + let(:sentry_api_response) do + projects_sample_response[0...1].map do |project| + project[:organization].delete(:id) + project.delete(:id) + project.except(:status) + end end + + it_behaves_like 'calls sentry api' + + it_behaves_like 'has correct return type', Gitlab::ErrorTracking::Project + it_behaves_like 'has correct length', 1 end context 'error object created from sentry response' do @@ -173,7 +226,11 @@ describe Sentry::Client do end with_them do - it { expect(subject[0].public_send(sentry_project_object)).to eq(projects_sample_response[0].dig(*sentry_response)) } + it do + expect(subject[0].public_send(sentry_project_object)).to( + eq(sentry_api_response[0].dig(*sentry_response)) + ) + end end end diff --git a/spec/migrations/migrate_cluster_configure_worker_sidekiq_queue_spec.rb b/spec/migrations/migrate_cluster_configure_worker_sidekiq_queue_spec.rb index b2d8f476bb2..a1f243651b5 100644 --- a/spec/migrations/migrate_cluster_configure_worker_sidekiq_queue_spec.rb +++ b/spec/migrations/migrate_cluster_configure_worker_sidekiq_queue_spec.rb @@ -3,12 +3,13 @@ require Rails.root.join('db', 'post_migrate', '20181219145520_migrate_cluster_co describe MigrateClusterConfigureWorkerSidekiqQueue, :sidekiq, :redis do include Gitlab::Database::MigrationHelpers + include StubWorker context 'when there are jobs in the queue' do it 'correctly migrates queue when migrating up' do Sidekiq::Testing.disable! do - stubbed_worker(queue: 'gcp_cluster:cluster_platform_configure').perform_async('Something', [1]) - stubbed_worker(queue: 'gcp_cluster:cluster_configure').perform_async('Something', [1]) + stub_worker(queue: 'gcp_cluster:cluster_platform_configure').perform_async('Something', [1]) + stub_worker(queue: 'gcp_cluster:cluster_configure').perform_async('Something', [1]) described_class.new.up @@ -19,12 +20,12 @@ describe MigrateClusterConfigureWorkerSidekiqQueue, :sidekiq, :redis do it 'does not affect other queues under the same namespace' do Sidekiq::Testing.disable! do - stubbed_worker(queue: 'gcp_cluster:cluster_install_app').perform_async('Something', [1]) - stubbed_worker(queue: 'gcp_cluster:cluster_provision').perform_async('Something', [1]) - stubbed_worker(queue: 'gcp_cluster:cluster_wait_for_app_installation').perform_async('Something', [1]) - stubbed_worker(queue: 'gcp_cluster:wait_for_cluster_creation').perform_async('Something', [1]) - stubbed_worker(queue: 'gcp_cluster:cluster_wait_for_ingress_ip_address').perform_async('Something', [1]) - stubbed_worker(queue: 'gcp_cluster:cluster_project_configure').perform_async('Something', [1]) + stub_worker(queue: 'gcp_cluster:cluster_install_app').perform_async('Something', [1]) + stub_worker(queue: 'gcp_cluster:cluster_provision').perform_async('Something', [1]) + stub_worker(queue: 'gcp_cluster:cluster_wait_for_app_installation').perform_async('Something', [1]) + stub_worker(queue: 'gcp_cluster:wait_for_cluster_creation').perform_async('Something', [1]) + stub_worker(queue: 'gcp_cluster:cluster_wait_for_ingress_ip_address').perform_async('Something', [1]) + stub_worker(queue: 'gcp_cluster:cluster_project_configure').perform_async('Something', [1]) described_class.new.up @@ -39,7 +40,7 @@ describe MigrateClusterConfigureWorkerSidekiqQueue, :sidekiq, :redis do it 'correctly migrates queue when migrating down' do Sidekiq::Testing.disable! do - stubbed_worker(queue: 'gcp_cluster:cluster_configure').perform_async('Something', [1]) + stub_worker(queue: 'gcp_cluster:cluster_configure').perform_async('Something', [1]) described_class.new.down @@ -58,11 +59,4 @@ describe MigrateClusterConfigureWorkerSidekiqQueue, :sidekiq, :redis do expect { described_class.new.down }.not_to raise_error end end - - def stubbed_worker(queue:) - Class.new do - include Sidekiq::Worker - sidekiq_options queue: queue - end - end end diff --git a/spec/migrations/migrate_create_trace_artifact_sidekiq_queue_spec.rb b/spec/migrations/migrate_create_trace_artifact_sidekiq_queue_spec.rb index c18ae3b76d3..66555118a43 100644 --- a/spec/migrations/migrate_create_trace_artifact_sidekiq_queue_spec.rb +++ b/spec/migrations/migrate_create_trace_artifact_sidekiq_queue_spec.rb @@ -3,12 +3,13 @@ require Rails.root.join('db', 'post_migrate', '20180306074045_migrate_create_tra describe MigrateCreateTraceArtifactSidekiqQueue, :sidekiq, :redis do include Gitlab::Database::MigrationHelpers + include StubWorker context 'when there are jobs in the queues' do it 'correctly migrates queue when migrating up' do Sidekiq::Testing.disable! do - stubbed_worker(queue: 'pipeline_default:create_trace_artifact').perform_async('Something', [1]) - stubbed_worker(queue: 'pipeline_background:archive_trace').perform_async('Something', [1]) + stub_worker(queue: 'pipeline_default:create_trace_artifact').perform_async('Something', [1]) + stub_worker(queue: 'pipeline_background:archive_trace').perform_async('Something', [1]) described_class.new.up @@ -19,11 +20,11 @@ describe MigrateCreateTraceArtifactSidekiqQueue, :sidekiq, :redis do it 'does not affect other queues under the same namespace' do Sidekiq::Testing.disable! do - stubbed_worker(queue: 'pipeline_default:build_coverage').perform_async('Something', [1]) - stubbed_worker(queue: 'pipeline_default:build_trace_sections').perform_async('Something', [1]) - stubbed_worker(queue: 'pipeline_default:pipeline_metrics').perform_async('Something', [1]) - stubbed_worker(queue: 'pipeline_default:pipeline_notification').perform_async('Something', [1]) - stubbed_worker(queue: 'pipeline_default:update_head_pipeline_for_merge_request').perform_async('Something', [1]) + stub_worker(queue: 'pipeline_default:build_coverage').perform_async('Something', [1]) + stub_worker(queue: 'pipeline_default:build_trace_sections').perform_async('Something', [1]) + stub_worker(queue: 'pipeline_default:pipeline_metrics').perform_async('Something', [1]) + stub_worker(queue: 'pipeline_default:pipeline_notification').perform_async('Something', [1]) + stub_worker(queue: 'pipeline_default:update_head_pipeline_for_merge_request').perform_async('Something', [1]) described_class.new.up @@ -37,7 +38,7 @@ describe MigrateCreateTraceArtifactSidekiqQueue, :sidekiq, :redis do it 'correctly migrates queue when migrating down' do Sidekiq::Testing.disable! do - stubbed_worker(queue: 'pipeline_background:archive_trace').perform_async('Something', [1]) + stub_worker(queue: 'pipeline_background:archive_trace').perform_async('Something', [1]) described_class.new.down @@ -56,11 +57,4 @@ describe MigrateCreateTraceArtifactSidekiqQueue, :sidekiq, :redis do expect { described_class.new.down }.not_to raise_error end end - - def stubbed_worker(queue:) - Class.new do - include Sidekiq::Worker - sidekiq_options queue: queue - end - end end diff --git a/spec/migrations/migrate_object_storage_upload_sidekiq_queue_spec.rb b/spec/migrations/migrate_object_storage_upload_sidekiq_queue_spec.rb index 1ee6c440cf4..6ce04805e5d 100644 --- a/spec/migrations/migrate_object_storage_upload_sidekiq_queue_spec.rb +++ b/spec/migrations/migrate_object_storage_upload_sidekiq_queue_spec.rb @@ -3,12 +3,13 @@ require Rails.root.join('db', 'post_migrate', '20180603190921_migrate_object_sto describe MigrateObjectStorageUploadSidekiqQueue, :sidekiq, :redis do include Gitlab::Database::MigrationHelpers + include StubWorker context 'when there are jobs in the queue' do it 'correctly migrates queue when migrating up' do Sidekiq::Testing.disable! do - stubbed_worker(queue: 'object_storage_upload').perform_async('Something', [1]) - stubbed_worker(queue: 'object_storage:object_storage_background_move').perform_async('Something', [1]) + stub_worker(queue: 'object_storage_upload').perform_async('Something', [1]) + stub_worker(queue: 'object_storage:object_storage_background_move').perform_async('Something', [1]) described_class.new.up @@ -23,11 +24,4 @@ describe MigrateObjectStorageUploadSidekiqQueue, :sidekiq, :redis do expect { described_class.new.up }.not_to raise_error end end - - def stubbed_worker(queue:) - Class.new do - include Sidekiq::Worker - sidekiq_options queue: queue - end - end end diff --git a/spec/migrations/migrate_pipeline_sidekiq_queues_spec.rb b/spec/migrations/migrate_pipeline_sidekiq_queues_spec.rb index e02bcd2f4da..e38044ccceb 100644 --- a/spec/migrations/migrate_pipeline_sidekiq_queues_spec.rb +++ b/spec/migrations/migrate_pipeline_sidekiq_queues_spec.rb @@ -3,12 +3,13 @@ require Rails.root.join('db', 'post_migrate', '20170822101017_migrate_pipeline_s describe MigratePipelineSidekiqQueues, :sidekiq, :redis do include Gitlab::Database::MigrationHelpers + include StubWorker context 'when there are jobs in the queues' do it 'correctly migrates queue when migrating up' do Sidekiq::Testing.disable! do - stubbed_worker(queue: :pipeline).perform_async('Something', [1]) - stubbed_worker(queue: :build).perform_async('Something', [1]) + stub_worker(queue: :pipeline).perform_async('Something', [1]) + stub_worker(queue: :build).perform_async('Something', [1]) described_class.new.up @@ -20,10 +21,10 @@ describe MigratePipelineSidekiqQueues, :sidekiq, :redis do it 'correctly migrates queue when migrating down' do Sidekiq::Testing.disable! do - stubbed_worker(queue: :pipeline_default).perform_async('Class', [1]) - stubbed_worker(queue: :pipeline_processing).perform_async('Class', [2]) - stubbed_worker(queue: :pipeline_hooks).perform_async('Class', [3]) - stubbed_worker(queue: :pipeline_cache).perform_async('Class', [4]) + stub_worker(queue: :pipeline_default).perform_async('Class', [1]) + stub_worker(queue: :pipeline_processing).perform_async('Class', [2]) + stub_worker(queue: :pipeline_hooks).perform_async('Class', [3]) + stub_worker(queue: :pipeline_cache).perform_async('Class', [4]) described_class.new.down @@ -45,11 +46,4 @@ describe MigratePipelineSidekiqQueues, :sidekiq, :redis do expect { described_class.new.down }.not_to raise_error end end - - def stubbed_worker(queue:) - Class.new do - include Sidekiq::Worker - sidekiq_options queue: queue - end - end end diff --git a/spec/migrations/migrate_storage_migrator_sidekiq_queue_spec.rb b/spec/migrations/migrate_storage_migrator_sidekiq_queue_spec.rb index f8cf76cb339..94de208e53e 100644 --- a/spec/migrations/migrate_storage_migrator_sidekiq_queue_spec.rb +++ b/spec/migrations/migrate_storage_migrator_sidekiq_queue_spec.rb @@ -3,11 +3,12 @@ require Rails.root.join('db', 'post_migrate', '20190124200344_migrate_storage_mi describe MigrateStorageMigratorSidekiqQueue, :sidekiq, :redis do include Gitlab::Database::MigrationHelpers + include StubWorker context 'when there are jobs in the queues' do it 'correctly migrates queue when migrating up' do Sidekiq::Testing.disable! do - stubbed_worker(queue: :storage_migrator).perform_async(1, 5) + stub_worker(queue: :storage_migrator).perform_async(1, 5) described_class.new.up @@ -18,7 +19,7 @@ describe MigrateStorageMigratorSidekiqQueue, :sidekiq, :redis do it 'correctly migrates queue when migrating down' do Sidekiq::Testing.disable! do - stubbed_worker(queue: :'hashed_storage:hashed_storage_migrator').perform_async(1, 5) + stub_worker(queue: :'hashed_storage:hashed_storage_migrator').perform_async(1, 5) described_class.new.down @@ -37,11 +38,4 @@ describe MigrateStorageMigratorSidekiqQueue, :sidekiq, :redis do expect { described_class.new.down }.not_to raise_error end end - - def stubbed_worker(queue:) - Class.new do - include Sidekiq::Worker - sidekiq_options queue: queue - end - end end diff --git a/spec/migrations/migrate_update_head_pipeline_for_merge_request_sidekiq_queue_spec.rb b/spec/migrations/migrate_update_head_pipeline_for_merge_request_sidekiq_queue_spec.rb index 5e3b20ab4a8..976f3ce07d7 100644 --- a/spec/migrations/migrate_update_head_pipeline_for_merge_request_sidekiq_queue_spec.rb +++ b/spec/migrations/migrate_update_head_pipeline_for_merge_request_sidekiq_queue_spec.rb @@ -3,12 +3,13 @@ require Rails.root.join('db', 'post_migrate', '20180307012445_migrate_update_hea describe MigrateUpdateHeadPipelineForMergeRequestSidekiqQueue, :sidekiq, :redis do include Gitlab::Database::MigrationHelpers + include StubWorker context 'when there are jobs in the queues' do it 'correctly migrates queue when migrating up' do Sidekiq::Testing.disable! do - stubbed_worker(queue: 'pipeline_default:update_head_pipeline_for_merge_request').perform_async('Something', [1]) - stubbed_worker(queue: 'pipeline_processing:update_head_pipeline_for_merge_request').perform_async('Something', [1]) + stub_worker(queue: 'pipeline_default:update_head_pipeline_for_merge_request').perform_async('Something', [1]) + stub_worker(queue: 'pipeline_processing:update_head_pipeline_for_merge_request').perform_async('Something', [1]) described_class.new.up @@ -19,10 +20,10 @@ describe MigrateUpdateHeadPipelineForMergeRequestSidekiqQueue, :sidekiq, :redis it 'does not affect other queues under the same namespace' do Sidekiq::Testing.disable! do - stubbed_worker(queue: 'pipeline_default:build_coverage').perform_async('Something', [1]) - stubbed_worker(queue: 'pipeline_default:build_trace_sections').perform_async('Something', [1]) - stubbed_worker(queue: 'pipeline_default:pipeline_metrics').perform_async('Something', [1]) - stubbed_worker(queue: 'pipeline_default:pipeline_notification').perform_async('Something', [1]) + stub_worker(queue: 'pipeline_default:build_coverage').perform_async('Something', [1]) + stub_worker(queue: 'pipeline_default:build_trace_sections').perform_async('Something', [1]) + stub_worker(queue: 'pipeline_default:pipeline_metrics').perform_async('Something', [1]) + stub_worker(queue: 'pipeline_default:pipeline_notification').perform_async('Something', [1]) described_class.new.up @@ -35,7 +36,7 @@ describe MigrateUpdateHeadPipelineForMergeRequestSidekiqQueue, :sidekiq, :redis it 'correctly migrates queue when migrating down' do Sidekiq::Testing.disable! do - stubbed_worker(queue: 'pipeline_processing:update_head_pipeline_for_merge_request').perform_async('Something', [1]) + stub_worker(queue: 'pipeline_processing:update_head_pipeline_for_merge_request').perform_async('Something', [1]) described_class.new.down @@ -54,11 +55,4 @@ describe MigrateUpdateHeadPipelineForMergeRequestSidekiqQueue, :sidekiq, :redis expect { described_class.new.down }.not_to raise_error end end - - def stubbed_worker(queue:) - Class.new do - include Sidekiq::Worker - sidekiq_options queue: queue - end - end end diff --git a/spec/migrations/schedule_populate_merge_request_assignees_table_spec.rb b/spec/migrations/schedule_populate_merge_request_assignees_table_spec.rb new file mode 100644 index 00000000000..e397fbb7138 --- /dev/null +++ b/spec/migrations/schedule_populate_merge_request_assignees_table_spec.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20190322132835_schedule_populate_merge_request_assignees_table.rb') + +describe SchedulePopulateMergeRequestAssigneesTable, :migration, :sidekiq do + let(:namespaces) { table(:namespaces) } + let(:projects) { table(:projects) } + let(:namespace) { namespaces.create(name: 'gitlab', path: 'gitlab-org') } + let(:project) { projects.create(namespace_id: namespace.id, name: 'foo') } + let(:merge_requests) { table(:merge_requests) } + + def create_merge_request(id) + params = { + id: id, + target_project_id: project.id, + target_branch: 'master', + source_project_id: project.id, + source_branch: 'mr name', + title: "mr name#{id}" + } + + merge_requests.create!(params) + end + + it 'correctly schedules background migrations' do + create_merge_request(1) + create_merge_request(2) + create_merge_request(3) + + stub_const("#{described_class.name}::BATCH_SIZE", 2) + + Sidekiq::Testing.fake! do + Timecop.freeze do + migrate! + + expect(described_class::MIGRATION) + .to be_scheduled_delayed_migration(8.minutes, 1, 2) + + expect(described_class::MIGRATION) + .to be_scheduled_delayed_migration(16.minutes, 3, 3) + + expect(BackgroundMigrationWorker.jobs.size).to eq(2) + end + end + end +end diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb index eee80e9bad7..d9d60e02a97 100644 --- a/spec/models/ability_spec.rb +++ b/spec/models/ability_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ability do diff --git a/spec/models/abuse_report_spec.rb b/spec/models/abuse_report_spec.rb index f49a61062c1..a5f8e999d5d 100644 --- a/spec/models/abuse_report_spec.rb +++ b/spec/models/abuse_report_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe AbuseReport do diff --git a/spec/models/active_session_spec.rb b/spec/models/active_session_spec.rb index e128fe8a4b7..b523f393ece 100644 --- a/spec/models/active_session_spec.rb +++ b/spec/models/active_session_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe ActiveSession, :clean_gitlab_redis_shared_state do diff --git a/spec/models/appearance_spec.rb b/spec/models/appearance_spec.rb index 3e95aa2b5dd..209d138f956 100644 --- a/spec/models/appearance_spec.rb +++ b/spec/models/appearance_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe Appearance do diff --git a/spec/models/application_setting/term_spec.rb b/spec/models/application_setting/term_spec.rb index aa49594f4d1..dd263335b81 100644 --- a/spec/models/application_setting/term_spec.rb +++ b/spec/models/application_setting/term_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ApplicationSetting::Term do diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index 314f0728b8e..c5579dafb4a 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ApplicationSetting do diff --git a/spec/models/award_emoji_spec.rb b/spec/models/award_emoji_spec.rb index 3f52091698c..8452ac69734 100644 --- a/spec/models/award_emoji_spec.rb +++ b/spec/models/award_emoji_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe AwardEmoji do diff --git a/spec/models/badge_spec.rb b/spec/models/badge_spec.rb index 33dc19e3432..314d7d1e9f4 100644 --- a/spec/models/badge_spec.rb +++ b/spec/models/badge_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Badge do diff --git a/spec/models/badges/group_badge_spec.rb b/spec/models/badges/group_badge_spec.rb index ed7f83d0489..c297bc957ea 100644 --- a/spec/models/badges/group_badge_spec.rb +++ b/spec/models/badges/group_badge_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe GroupBadge do diff --git a/spec/models/badges/project_badge_spec.rb b/spec/models/badges/project_badge_spec.rb index 0e1a8159cb6..e683d110252 100644 --- a/spec/models/badges/project_badge_spec.rb +++ b/spec/models/badges/project_badge_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProjectBadge do diff --git a/spec/models/blob_spec.rb b/spec/models/blob_spec.rb index 05cf242e84d..d0e1688cce3 100644 --- a/spec/models/blob_spec.rb +++ b/spec/models/blob_spec.rb @@ -1,4 +1,6 @@ # encoding: utf-8 +# frozen_string_literal: true + require 'rails_helper' describe Blob do diff --git a/spec/models/blob_viewer/base_spec.rb b/spec/models/blob_viewer/base_spec.rb index 7ba28f72215..39c7a34f052 100644 --- a/spec/models/blob_viewer/base_spec.rb +++ b/spec/models/blob_viewer/base_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe BlobViewer::Base do diff --git a/spec/models/blob_viewer/changelog_spec.rb b/spec/models/blob_viewer/changelog_spec.rb index db41eca0fc8..0fcc94182af 100644 --- a/spec/models/blob_viewer/changelog_spec.rb +++ b/spec/models/blob_viewer/changelog_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe BlobViewer::Changelog do diff --git a/spec/models/blob_viewer/composer_json_spec.rb b/spec/models/blob_viewer/composer_json_spec.rb index 85b0d9668a0..eda34779679 100644 --- a/spec/models/blob_viewer/composer_json_spec.rb +++ b/spec/models/blob_viewer/composer_json_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe BlobViewer::ComposerJson do diff --git a/spec/models/blob_viewer/gemspec_spec.rb b/spec/models/blob_viewer/gemspec_spec.rb index d8c4490637f..b6cc82c03ba 100644 --- a/spec/models/blob_viewer/gemspec_spec.rb +++ b/spec/models/blob_viewer/gemspec_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe BlobViewer::Gemspec do diff --git a/spec/models/blob_viewer/gitlab_ci_yml_spec.rb b/spec/models/blob_viewer/gitlab_ci_yml_spec.rb index 16bf947b493..db405ceb4f1 100644 --- a/spec/models/blob_viewer/gitlab_ci_yml_spec.rb +++ b/spec/models/blob_viewer/gitlab_ci_yml_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe BlobViewer::GitlabCiYml do diff --git a/spec/models/blob_viewer/license_spec.rb b/spec/models/blob_viewer/license_spec.rb index 222ed166ee0..e02bfae3829 100644 --- a/spec/models/blob_viewer/license_spec.rb +++ b/spec/models/blob_viewer/license_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe BlobViewer::License do diff --git a/spec/models/blob_viewer/package_json_spec.rb b/spec/models/blob_viewer/package_json_spec.rb index fbaa8d47a71..b317278f3c8 100644 --- a/spec/models/blob_viewer/package_json_spec.rb +++ b/spec/models/blob_viewer/package_json_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe BlobViewer::PackageJson do diff --git a/spec/models/blob_viewer/podspec_json_spec.rb b/spec/models/blob_viewer/podspec_json_spec.rb index 9a23877b23f..7f1fb8666fd 100644 --- a/spec/models/blob_viewer/podspec_json_spec.rb +++ b/spec/models/blob_viewer/podspec_json_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe BlobViewer::PodspecJson do diff --git a/spec/models/blob_viewer/podspec_spec.rb b/spec/models/blob_viewer/podspec_spec.rb index 02d06ea24d6..527ae79d766 100644 --- a/spec/models/blob_viewer/podspec_spec.rb +++ b/spec/models/blob_viewer/podspec_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe BlobViewer::Podspec do diff --git a/spec/models/blob_viewer/readme_spec.rb b/spec/models/blob_viewer/readme_spec.rb index 8d11d58cfca..958927bddb4 100644 --- a/spec/models/blob_viewer/readme_spec.rb +++ b/spec/models/blob_viewer/readme_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe BlobViewer::Readme do diff --git a/spec/models/blob_viewer/route_map_spec.rb b/spec/models/blob_viewer/route_map_spec.rb index c13662427b0..f7ce873c9d1 100644 --- a/spec/models/blob_viewer/route_map_spec.rb +++ b/spec/models/blob_viewer/route_map_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe BlobViewer::RouteMap do diff --git a/spec/models/blob_viewer/server_side_spec.rb b/spec/models/blob_viewer/server_side_spec.rb index 63790486200..f95305abe78 100644 --- a/spec/models/blob_viewer/server_side_spec.rb +++ b/spec/models/blob_viewer/server_side_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe BlobViewer::ServerSide do diff --git a/spec/models/board_spec.rb b/spec/models/board_spec.rb index 12d29540137..54452faa0e1 100644 --- a/spec/models/board_spec.rb +++ b/spec/models/board_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe Board do diff --git a/spec/models/broadcast_message_spec.rb b/spec/models/broadcast_message_spec.rb index 30ca07d5d2c..3ab013ddc0e 100644 --- a/spec/models/broadcast_message_spec.rb +++ b/spec/models/broadcast_message_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe BroadcastMessage do diff --git a/spec/models/chat_name_spec.rb b/spec/models/chat_name_spec.rb index 504bc710b25..82991937644 100644 --- a/spec/models/chat_name_spec.rb +++ b/spec/models/chat_name_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ChatName do diff --git a/spec/models/chat_team_spec.rb b/spec/models/chat_team_spec.rb index 70a9a206faa..76beb3d506b 100644 --- a/spec/models/chat_team_spec.rb +++ b/spec/models/chat_team_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ChatTeam do diff --git a/spec/models/ci/artifact_blob_spec.rb b/spec/models/ci/artifact_blob_spec.rb index 0014bbcf9f5..f63816fd92a 100644 --- a/spec/models/ci/artifact_blob_spec.rb +++ b/spec/models/ci/artifact_blob_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::ArtifactBlob do diff --git a/spec/models/ci/bridge_spec.rb b/spec/models/ci/bridge_spec.rb index b5ec8991720..aacfbe3f180 100644 --- a/spec/models/ci/bridge_spec.rb +++ b/spec/models/ci/bridge_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::Bridge do diff --git a/spec/models/ci/build_metadata_spec.rb b/spec/models/ci/build_metadata_spec.rb index 016a5899eef..917a65ddf21 100644 --- a/spec/models/ci/build_metadata_spec.rb +++ b/spec/models/ci/build_metadata_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::BuildMetadata do diff --git a/spec/models/ci/build_runner_session_spec.rb b/spec/models/ci/build_runner_session_spec.rb index 35622366829..a52c10019e6 100644 --- a/spec/models/ci/build_runner_session_spec.rb +++ b/spec/models/ci/build_runner_session_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::BuildRunnerSession, model: true do diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 7500e6ae5b1..2c41dfa65cd 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::Build do @@ -24,6 +26,8 @@ describe Ci::Build do it { is_expected.to respond_to(:has_trace?) } it { is_expected.to respond_to(:trace) } it { is_expected.to delegate_method(:merge_request_event?).to(:pipeline) } + it { is_expected.to delegate_method(:merge_request_ref?).to(:pipeline) } + it { is_expected.to delegate_method(:legacy_detached_merge_request_pipeline?).to(:pipeline) } it { is_expected.to be_a(ArtifactMigratable) } @@ -3626,6 +3630,24 @@ describe Ci::Build do it { is_expected.to be_falsey } end end + + context 'when refspecs feature is required by build' do + before do + allow(build).to receive(:merge_request_ref?) { true } + end + + context 'when runner provides given feature' do + let(:runner_features) { { refspecs: true } } + + it { is_expected.to be_truthy } + end + + context 'when runner does not provide given feature' do + let(:runner_features) { {} } + + it { is_expected.to be_falsey } + end + end end describe '#deployment_status' do diff --git a/spec/models/ci/build_trace_chunks/database_spec.rb b/spec/models/ci/build_trace_chunks/database_spec.rb index d8fc9d57e95..eb94d7dae38 100644 --- a/spec/models/ci/build_trace_chunks/database_spec.rb +++ b/spec/models/ci/build_trace_chunks/database_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::BuildTraceChunks::Database do diff --git a/spec/models/ci/build_trace_chunks/fog_spec.rb b/spec/models/ci/build_trace_chunks/fog_spec.rb index 8f49190af13..b8d78bcd069 100644 --- a/spec/models/ci/build_trace_chunks/fog_spec.rb +++ b/spec/models/ci/build_trace_chunks/fog_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::BuildTraceChunks::Fog do diff --git a/spec/models/ci/build_trace_chunks/redis_spec.rb b/spec/models/ci/build_trace_chunks/redis_spec.rb index 9da1e6a95ee..6cff33d24fa 100644 --- a/spec/models/ci/build_trace_chunks/redis_spec.rb +++ b/spec/models/ci/build_trace_chunks/redis_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::BuildTraceChunks::Redis, :clean_gitlab_redis_shared_state do diff --git a/spec/models/ci/build_trace_section_name_spec.rb b/spec/models/ci/build_trace_section_name_spec.rb index 386ee6880cb..11e2d27ff79 100644 --- a/spec/models/ci/build_trace_section_name_spec.rb +++ b/spec/models/ci/build_trace_section_name_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::BuildTraceSectionName, model: true do diff --git a/spec/models/ci/build_trace_section_spec.rb b/spec/models/ci/build_trace_section_spec.rb index 541a9a36fb8..5bd3a953ec0 100644 --- a/spec/models/ci/build_trace_section_spec.rb +++ b/spec/models/ci/build_trace_section_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::BuildTraceSection, model: true do diff --git a/spec/models/ci/group_spec.rb b/spec/models/ci/group_spec.rb index 838fa63cb1f..36c65d92840 100644 --- a/spec/models/ci/group_spec.rb +++ b/spec/models/ci/group_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::Group do diff --git a/spec/models/ci/group_variable_spec.rb b/spec/models/ci/group_variable_spec.rb index 21d96bf3454..b3999765e5f 100644 --- a/spec/models/ci/group_variable_spec.rb +++ b/spec/models/ci/group_variable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::GroupVariable do diff --git a/spec/models/ci/job_artifact_spec.rb b/spec/models/ci/job_artifact_spec.rb index c68ba02b8de..d7abd54eec1 100644 --- a/spec/models/ci/job_artifact_spec.rb +++ b/spec/models/ci/job_artifact_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::JobArtifact do diff --git a/spec/models/ci/legacy_stage_spec.rb b/spec/models/ci/legacy_stage_spec.rb index 0c33c1466b7..be0307518eb 100644 --- a/spec/models/ci/legacy_stage_spec.rb +++ b/spec/models/ci/legacy_stage_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::LegacyStage do diff --git a/spec/models/ci/pipeline_schedule_spec.rb b/spec/models/ci/pipeline_schedule_spec.rb index 8ee15f0e734..81913f4a3b6 100644 --- a/spec/models/ci/pipeline_schedule_spec.rb +++ b/spec/models/ci/pipeline_schedule_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::PipelineSchedule do diff --git a/spec/models/ci/pipeline_schedule_variable_spec.rb b/spec/models/ci/pipeline_schedule_variable_spec.rb index dc8427f28bc..3c9379ecb0d 100644 --- a/spec/models/ci/pipeline_schedule_variable_spec.rb +++ b/spec/models/ci/pipeline_schedule_variable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::PipelineScheduleVariable do diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 5b8097621e0..e76cf376ed1 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::Pipeline, :mailer do @@ -362,6 +364,42 @@ describe Ci::Pipeline, :mailer do end end + describe '#merge_request_ref?' do + subject { pipeline.merge_request_ref? } + + it 'calls MergeRequest#merge_request_ref?' do + expect(MergeRequest).to receive(:merge_request_ref?).with(pipeline.ref) + + subject + end + end + + describe '#legacy_detached_merge_request_pipeline?' do + subject { pipeline.legacy_detached_merge_request_pipeline? } + + set(:merge_request) { create(:merge_request) } + let(:ref) { 'feature' } + let(:target_sha) { nil } + + let(:pipeline) do + build(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, ref: ref, target_sha: target_sha) + end + + it { is_expected.to be_truthy } + + context 'when pipeline ref is a merge request ref' do + let(:ref) { 'refs/merge-requests/1/head' } + + it { is_expected.to be_falsy } + end + + context 'when target sha is set' do + let(:target_sha) { 'target-sha' } + + it { is_expected.to be_falsy } + end + end + describe '#matches_sha_or_source_sha?' do subject { pipeline.matches_sha_or_source_sha?(sample_sha) } @@ -1460,6 +1498,14 @@ describe Ci::Pipeline, :mailer do end end + context 'with a branch name as the ref' do + it 'looks up commit with the full ref name' do + expect(pipeline.project).to receive(:commit).with('refs/heads/master').and_call_original + + expect(pipeline).to be_latest + end + end + context 'with not latest sha' do before do pipeline.update( diff --git a/spec/models/ci/pipeline_variable_spec.rb b/spec/models/ci/pipeline_variable_spec.rb index 03d09cb31d6..2ecb688299a 100644 --- a/spec/models/ci/pipeline_variable_spec.rb +++ b/spec/models/ci/pipeline_variable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::PipelineVariable do diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb index eb2daed7f32..b3ab63925dd 100644 --- a/spec/models/ci/runner_spec.rb +++ b/spec/models/ci/runner_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::Runner do diff --git a/spec/models/ci/stage_spec.rb b/spec/models/ci/stage_spec.rb index 3228c400155..661958390e2 100644 --- a/spec/models/ci/stage_spec.rb +++ b/spec/models/ci/stage_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::Stage, :models do diff --git a/spec/models/ci/trigger_request_spec.rb b/spec/models/ci/trigger_request_spec.rb index 7dcf3528f73..d04349bec92 100644 --- a/spec/models/ci/trigger_request_spec.rb +++ b/spec/models/ci/trigger_request_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::TriggerRequest do diff --git a/spec/models/ci/trigger_spec.rb b/spec/models/ci/trigger_spec.rb index d4b72205203..fde8375f2a5 100644 --- a/spec/models/ci/trigger_spec.rb +++ b/spec/models/ci/trigger_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::Trigger do diff --git a/spec/models/ci/variable_spec.rb b/spec/models/ci/variable_spec.rb index 02c07a2bd83..d2df6b3344e 100644 --- a/spec/models/ci/variable_spec.rb +++ b/spec/models/ci/variable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::Variable do diff --git a/spec/models/clusters/applications/cert_manager_spec.rb b/spec/models/clusters/applications/cert_manager_spec.rb index cf5cbf8ec5c..af7eadfc74c 100644 --- a/spec/models/clusters/applications/cert_manager_spec.rb +++ b/spec/models/clusters/applications/cert_manager_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe Clusters::Applications::CertManager do @@ -9,7 +11,27 @@ describe Clusters::Applications::CertManager do include_examples 'cluster application initial status specs' describe '#install_command' do - let(:cluster_issuer_file) { { "cluster_issuer.yaml": "---\napiVersion: certmanager.k8s.io/v1alpha1\nkind: ClusterIssuer\nmetadata:\n name: letsencrypt-prod\nspec:\n acme:\n server: https://acme-v02.api.letsencrypt.org/directory\n email: admin@example.com\n privateKeySecretRef:\n name: letsencrypt-prod\n http01: {}\n" } } + let(:cert_email) { 'admin@example.com' } + + let(:cluster_issuer_file) do + file_contents = <<~EOF + --- + apiVersion: certmanager.k8s.io/v1alpha1 + kind: ClusterIssuer + metadata: + name: letsencrypt-prod + spec: + acme: + server: https://acme-v02.api.letsencrypt.org/directory + email: #{cert_email} + privateKeySecretRef: + name: letsencrypt-prod + http01: {} + EOF + + { "cluster_issuer.yaml": file_contents } + end + subject { cert_manager.install_command } it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand) } @@ -24,9 +46,10 @@ describe Clusters::Applications::CertManager do end context 'for a specific user' do + let(:cert_email) { 'abc@xyz.com' } + before do - cert_manager.email = 'abc@xyz.com' - cluster_issuer_file[:'cluster_issuer.yaml'].gsub! 'admin@example.com', 'abc@xyz.com' + cert_manager.email = cert_email end it 'should use his/her email to register issuer with certificate provider' do diff --git a/spec/models/clusters/applications/helm_spec.rb b/spec/models/clusters/applications/helm_spec.rb index f16eff92167..f97d126d918 100644 --- a/spec/models/clusters/applications/helm_spec.rb +++ b/spec/models/clusters/applications/helm_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe Clusters::Applications::Helm do diff --git a/spec/models/clusters/applications/ingress_spec.rb b/spec/models/clusters/applications/ingress_spec.rb index a40fa988287..09e60b9a206 100644 --- a/spec/models/clusters/applications/ingress_spec.rb +++ b/spec/models/clusters/applications/ingress_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe Clusters::Applications::Ingress do diff --git a/spec/models/clusters/applications/jupyter_spec.rb b/spec/models/clusters/applications/jupyter_spec.rb index 2967c4076c6..5970a1959b5 100644 --- a/spec/models/clusters/applications/jupyter_spec.rb +++ b/spec/models/clusters/applications/jupyter_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe Clusters::Applications::Jupyter do diff --git a/spec/models/clusters/applications/knative_spec.rb b/spec/models/clusters/applications/knative_spec.rb index bf425a2617c..25493689fbc 100644 --- a/spec/models/clusters/applications/knative_spec.rb +++ b/spec/models/clusters/applications/knative_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe Clusters::Applications::Knative do @@ -107,7 +109,7 @@ describe Clusters::Applications::Knative do subject { knative.install_command } it 'should be initialized with latest version' do - expect(subject.version).to eq('0.2.2') + expect(subject.version).to eq('0.3.0') end it_behaves_like 'a command' diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb index 81708b0c2ed..82a502addd4 100644 --- a/spec/models/clusters/applications/prometheus_spec.rb +++ b/spec/models/clusters/applications/prometheus_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe Clusters::Applications::Prometheus do diff --git a/spec/models/clusters/applications/runner_spec.rb b/spec/models/clusters/applications/runner_spec.rb index 06d9bc076cd..7e2f5835279 100644 --- a/spec/models/clusters/applications/runner_spec.rb +++ b/spec/models/clusters/applications/runner_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe Clusters::Applications::Runner do @@ -64,24 +66,45 @@ describe Clusters::Applications::Runner do end context 'without a runner' do - let(:project) { create(:project) } - let(:cluster) { create(:cluster, :with_installed_helm, projects: [project]) } let(:application) { create(:clusters_applications_runner, runner: nil, cluster: cluster) } + let(:runner) { application.runner } - it 'creates a runner' do - expect do - subject - end.to change { Ci::Runner.count }.by(1) + shared_examples 'runner creation' do + it 'creates a runner' do + expect { subject }.to change { Ci::Runner.count }.by(1) + end + + it 'uses the new runner token' do + expect(values).to match(/runnerToken: '?#{runner.token}/) + end end - it 'uses the new runner token' do - expect(values).to match(/runnerToken: '?#{application.reload.runner.token}/) + context 'project cluster' do + let(:project) { create(:project) } + let(:cluster) { create(:cluster, :with_installed_helm, projects: [project]) } + + include_examples 'runner creation' + + it 'creates a project runner' do + subject + + expect(runner).to be_project_type + expect(runner.projects).to eq [project] + end end - it 'assigns the new runner to runner' do - subject + context 'group cluster' do + let(:group) { create(:group) } + let(:cluster) { create(:cluster, :with_installed_helm, cluster_type: :group_type, groups: [group]) } + + include_examples 'runner creation' + + it 'creates a group runner' do + subject - expect(application.reload.runner).to be_project_type + expect(runner).to be_group_type + expect(runner.groups).to eq [group] + end end end diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb index acbcdc7d170..fabd2806d9a 100644 --- a/spec/models/clusters/cluster_spec.rb +++ b/spec/models/clusters/cluster_spec.rb @@ -620,4 +620,20 @@ describe Clusters::Cluster do end end end + + describe '#provided_by_user?' do + subject { cluster.provided_by_user? } + + context 'with a GCP provider' do + let(:cluster) { create(:cluster, :provided_by_gcp) } + + it { is_expected.to be_falsy } + end + + context 'with an user provider' do + let(:cluster) { create(:cluster, :provided_by_user) } + + it { is_expected.to be_truthy } + end + end end diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb index af65530e663..14bec17a2bd 100644 --- a/spec/models/clusters/platforms/kubernetes_spec.rb +++ b/spec/models/clusters/platforms/kubernetes_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching do @@ -15,7 +17,7 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching it { is_expected.to delegate_method(:project).to(:cluster) } it { is_expected.to delegate_method(:enabled?).to(:cluster) } - it { is_expected.to delegate_method(:managed?).to(:cluster) } + it { is_expected.to delegate_method(:provided_by_user?).to(:cluster) } it { is_expected.to delegate_method(:kubernetes_namespace).to(:cluster) } it_behaves_like 'having unique enum values' diff --git a/spec/models/clusters/project_spec.rb b/spec/models/clusters/project_spec.rb index 82ef5a23c18..2f017e69251 100644 --- a/spec/models/clusters/project_spec.rb +++ b/spec/models/clusters/project_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Clusters::Project do diff --git a/spec/models/clusters/providers/gcp_spec.rb b/spec/models/clusters/providers/gcp_spec.rb index 5012e6f15c6..785db4febe0 100644 --- a/spec/models/clusters/providers/gcp_spec.rb +++ b/spec/models/clusters/providers/gcp_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Clusters::Providers::Gcp do diff --git a/spec/models/commit_collection_spec.rb b/spec/models/commit_collection_spec.rb index 0f5d03ff458..8a7fcb39fe2 100644 --- a/spec/models/commit_collection_spec.rb +++ b/spec/models/commit_collection_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe CommitCollection do @@ -37,12 +39,92 @@ describe CommitCollection do describe '#without_merge_commits' do it 'returns all commits except merge commits' do + merge_commit = project.commit("60ecb67744cb56576c30214ff52294f8ce2def98") + expect(merge_commit).to receive(:merge_commit?).and_return(true) + collection = described_class.new(project, [ - build(:commit), - build(:commit, :merge_commit) + commit, + merge_commit ]) - expect(collection.without_merge_commits.size).to eq(1) + expect(collection.without_merge_commits).to contain_exactly(commit) + end + end + + describe 'enrichment methods' do + let(:gitaly_commit) { commit } + let(:hash_commit) { Commit.from_hash(gitaly_commit.to_hash, project) } + + describe '#unenriched' do + it 'returns all commits that are not backed by gitaly data' do + collection = described_class.new(project, [gitaly_commit, hash_commit]) + + expect(collection.unenriched).to contain_exactly(hash_commit) + end + end + + describe '#fully_enriched?' do + it 'returns true when all commits are backed by gitaly data' do + collection = described_class.new(project, [gitaly_commit, gitaly_commit]) + + expect(collection.fully_enriched?).to eq(true) + end + + it 'returns false when any commits are not backed by gitaly data' do + collection = described_class.new(project, [gitaly_commit, hash_commit]) + + expect(collection.fully_enriched?).to eq(false) + end + + it 'returns true when the collection is empty' do + collection = described_class.new(project, []) + + expect(collection.fully_enriched?).to eq(true) + end + end + + describe '#enrich!' do + it 'replaces commits in the collection with those backed by gitaly data' do + collection = described_class.new(project, [hash_commit]) + + collection.enrich! + + new_commit = collection.commits.first + expect(new_commit.id).to eq(hash_commit.id) + expect(hash_commit.gitaly_commit?).to eq(false) + expect(new_commit.gitaly_commit?).to eq(true) + end + + it 'maintains the original order of the commits' do + gitaly_commits = [gitaly_commit] * 3 + hash_commits = [hash_commit] * 3 + # Interleave the gitaly and hash commits together + original_commits = gitaly_commits.zip(hash_commits).flatten + collection = described_class.new(project, original_commits) + + collection.enrich! + + original_commits.each_with_index do |original_commit, i| + new_commit = collection.commits[i] + expect(original_commit.id).to eq(new_commit.id) + end + end + + it 'fetches data if there are unenriched commits' do + collection = described_class.new(project, [hash_commit]) + + expect(Commit).to receive(:lazy).exactly(:once) + + collection.enrich! + end + + it 'does not fetch data if all commits are enriched' do + collection = described_class.new(project, [gitaly_commit]) + + expect(Commit).not_to receive(:lazy) + + collection.enrich! + end end end diff --git a/spec/models/commit_range_spec.rb b/spec/models/commit_range_spec.rb index f2efcd9d0e9..b96ca89c893 100644 --- a/spec/models/commit_range_spec.rb +++ b/spec/models/commit_range_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe CommitRange do diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index 9d4e18534ae..14f4b4d692f 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Commit do diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb index e2b7f5c6ee2..ca2f9e36c98 100644 --- a/spec/models/commit_status_spec.rb +++ b/spec/models/commit_status_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe CommitStatus do diff --git a/spec/models/compare_spec.rb b/spec/models/compare_spec.rb index 0bc3ee014e6..43c3580bed2 100644 --- a/spec/models/compare_spec.rb +++ b/spec/models/compare_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Compare do diff --git a/spec/models/concerns/access_requestable_spec.rb b/spec/models/concerns/access_requestable_spec.rb index 04d6cfa2c02..de2bc3a387b 100644 --- a/spec/models/concerns/access_requestable_spec.rb +++ b/spec/models/concerns/access_requestable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe AccessRequestable do diff --git a/spec/models/concerns/avatarable_spec.rb b/spec/models/concerns/avatarable_spec.rb index 1ea7f2b9985..c750be6b75c 100644 --- a/spec/models/concerns/avatarable_spec.rb +++ b/spec/models/concerns/avatarable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Avatarable do diff --git a/spec/models/concerns/awardable_spec.rb b/spec/models/concerns/awardable_spec.rb index 5713106418d..9e7106281ee 100644 --- a/spec/models/concerns/awardable_spec.rb +++ b/spec/models/concerns/awardable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Awardable do diff --git a/spec/models/concerns/batch_destroy_dependent_associations_spec.rb b/spec/models/concerns/batch_destroy_dependent_associations_spec.rb index e5392fe0462..1fe90d3cc9a 100644 --- a/spec/models/concerns/batch_destroy_dependent_associations_spec.rb +++ b/spec/models/concerns/batch_destroy_dependent_associations_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe BatchDestroyDependentAssociations do diff --git a/spec/models/concerns/blocks_json_serialization_spec.rb b/spec/models/concerns/blocks_json_serialization_spec.rb index 5906b588d0e..e8657c45a93 100644 --- a/spec/models/concerns/blocks_json_serialization_spec.rb +++ b/spec/models/concerns/blocks_json_serialization_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe BlocksJsonSerialization do diff --git a/spec/models/concerns/cache_markdown_field_spec.rb b/spec/models/concerns/cache_markdown_field_spec.rb index 7d555f15e39..78637ff10c6 100644 --- a/spec/models/concerns/cache_markdown_field_spec.rb +++ b/spec/models/concerns/cache_markdown_field_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe CacheMarkdownField do diff --git a/spec/models/concerns/cacheable_attributes_spec.rb b/spec/models/concerns/cacheable_attributes_spec.rb index 43a544cfe26..394fac52aa7 100644 --- a/spec/models/concerns/cacheable_attributes_spec.rb +++ b/spec/models/concerns/cacheable_attributes_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe CacheableAttributes do diff --git a/spec/models/concerns/case_sensitivity_spec.rb b/spec/models/concerns/case_sensitivity_spec.rb index 1bf6c9b3404..d6d41a25eac 100644 --- a/spec/models/concerns/case_sensitivity_spec.rb +++ b/spec/models/concerns/case_sensitivity_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe CaseSensitivity do diff --git a/spec/models/concerns/chronic_duration_attribute_spec.rb b/spec/models/concerns/chronic_duration_attribute_spec.rb index 51221e07ca3..e41d75568f7 100644 --- a/spec/models/concerns/chronic_duration_attribute_spec.rb +++ b/spec/models/concerns/chronic_duration_attribute_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' shared_examples 'ChronicDurationAttribute reader' do diff --git a/spec/models/concerns/deployable_spec.rb b/spec/models/concerns/deployable_spec.rb index 6951be903fe..42bed9434f5 100644 --- a/spec/models/concerns/deployable_spec.rb +++ b/spec/models/concerns/deployable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe Deployable do diff --git a/spec/models/concerns/deployment_platform_spec.rb b/spec/models/concerns/deployment_platform_spec.rb index 19ab4382b53..0e34d8fccf3 100644 --- a/spec/models/concerns/deployment_platform_spec.rb +++ b/spec/models/concerns/deployment_platform_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe DeploymentPlatform do diff --git a/spec/models/concerns/discussion_on_diff_spec.rb b/spec/models/concerns/discussion_on_diff_spec.rb index 64bf04071e8..baddca47dfa 100644 --- a/spec/models/concerns/discussion_on_diff_spec.rb +++ b/spec/models/concerns/discussion_on_diff_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe DiscussionOnDiff do diff --git a/spec/models/concerns/each_batch_spec.rb b/spec/models/concerns/each_batch_spec.rb index 17224c09693..c4cf8e80f7a 100644 --- a/spec/models/concerns/each_batch_spec.rb +++ b/spec/models/concerns/each_batch_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe EachBatch do diff --git a/spec/models/concerns/editable_spec.rb b/spec/models/concerns/editable_spec.rb index 49a9a8ebcbc..4a4a3ca5687 100644 --- a/spec/models/concerns/editable_spec.rb +++ b/spec/models/concerns/editable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Editable do diff --git a/spec/models/concerns/expirable_spec.rb b/spec/models/concerns/expirable_spec.rb index f7b436f32e6..f4f5eab5b86 100644 --- a/spec/models/concerns/expirable_spec.rb +++ b/spec/models/concerns/expirable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Expirable do diff --git a/spec/models/concerns/faster_cache_keys_spec.rb b/spec/models/concerns/faster_cache_keys_spec.rb index 8d3f94267fa..7830acbae3d 100644 --- a/spec/models/concerns/faster_cache_keys_spec.rb +++ b/spec/models/concerns/faster_cache_keys_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe FasterCacheKeys do diff --git a/spec/models/concerns/feature_gate_spec.rb b/spec/models/concerns/feature_gate_spec.rb index 3f601243245..276d3d9e1d5 100644 --- a/spec/models/concerns/feature_gate_spec.rb +++ b/spec/models/concerns/feature_gate_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe FeatureGate do diff --git a/spec/models/concerns/group_descendant_spec.rb b/spec/models/concerns/group_descendant_spec.rb index 28352d8c961..194caac3fce 100644 --- a/spec/models/concerns/group_descendant_spec.rb +++ b/spec/models/concerns/group_descendant_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe GroupDescendant, :nested_groups do diff --git a/spec/models/concerns/has_status_spec.rb b/spec/models/concerns/has_status_spec.rb index e8b1eba67cc..a217dc42537 100644 --- a/spec/models/concerns/has_status_spec.rb +++ b/spec/models/concerns/has_status_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe HasStatus do diff --git a/spec/models/concerns/has_variable_spec.rb b/spec/models/concerns/has_variable_spec.rb index bff96e12ffa..2bb21d7934e 100644 --- a/spec/models/concerns/has_variable_spec.rb +++ b/spec/models/concerns/has_variable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe HasVariable do diff --git a/spec/models/concerns/ignorable_column_spec.rb b/spec/models/concerns/ignorable_column_spec.rb index b70f2331a0e..6b82825d2cc 100644 --- a/spec/models/concerns/ignorable_column_spec.rb +++ b/spec/models/concerns/ignorable_column_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe IgnorableColumn do diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index 72c6161424b..259ac6852a8 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Issuable do diff --git a/spec/models/concerns/loaded_in_group_list_spec.rb b/spec/models/concerns/loaded_in_group_list_spec.rb index 7a279547a3a..7c97b580779 100644 --- a/spec/models/concerns/loaded_in_group_list_spec.rb +++ b/spec/models/concerns/loaded_in_group_list_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe LoadedInGroupList do diff --git a/spec/models/concerns/manual_inverse_association_spec.rb b/spec/models/concerns/manual_inverse_association_spec.rb index ff4a04ea573..ee32e3b165b 100644 --- a/spec/models/concerns/manual_inverse_association_spec.rb +++ b/spec/models/concerns/manual_inverse_association_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ManualInverseAssociation do diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb index a9b237fa9ea..f31e3e8821d 100644 --- a/spec/models/concerns/mentionable_spec.rb +++ b/spec/models/concerns/mentionable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Mentionable do diff --git a/spec/models/concerns/milestoneish_spec.rb b/spec/models/concerns/milestoneish_spec.rb index 81ca5b638fe..22d4b2cc517 100644 --- a/spec/models/concerns/milestoneish_spec.rb +++ b/spec/models/concerns/milestoneish_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Milestone, 'Milestoneish' do diff --git a/spec/models/concerns/noteable_spec.rb b/spec/models/concerns/noteable_spec.rb index 485a6e165a1..ee613b199ad 100644 --- a/spec/models/concerns/noteable_spec.rb +++ b/spec/models/concerns/noteable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Noteable do diff --git a/spec/models/concerns/participable_spec.rb b/spec/models/concerns/participable_spec.rb index 431f1482615..3d5937c4fc6 100644 --- a/spec/models/concerns/participable_spec.rb +++ b/spec/models/concerns/participable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Participable do diff --git a/spec/models/concerns/presentable_spec.rb b/spec/models/concerns/presentable_spec.rb index 941647a79fb..9db868dd348 100644 --- a/spec/models/concerns/presentable_spec.rb +++ b/spec/models/concerns/presentable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Presentable do diff --git a/spec/models/concerns/project_features_compatibility_spec.rb b/spec/models/concerns/project_features_compatibility_spec.rb index 9041690023f..5aa43b58217 100644 --- a/spec/models/concerns/project_features_compatibility_spec.rb +++ b/spec/models/concerns/project_features_compatibility_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProjectFeaturesCompatibility do diff --git a/spec/models/concerns/prometheus_adapter_spec.rb b/spec/models/concerns/prometheus_adapter_spec.rb index f4b9c57e71a..7148261b1e4 100644 --- a/spec/models/concerns/prometheus_adapter_spec.rb +++ b/spec/models/concerns/prometheus_adapter_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PrometheusAdapter, :use_clean_rails_memory_store_caching do diff --git a/spec/models/concerns/protected_ref_access_spec.rb b/spec/models/concerns/protected_ref_access_spec.rb index ce602337647..94798f0590d 100644 --- a/spec/models/concerns/protected_ref_access_spec.rb +++ b/spec/models/concerns/protected_ref_access_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProtectedRefAccess do diff --git a/spec/models/concerns/reactive_caching_spec.rb b/spec/models/concerns/reactive_caching_spec.rb index 03ae45e6b17..32e13d5abed 100644 --- a/spec/models/concerns/reactive_caching_spec.rb +++ b/spec/models/concerns/reactive_caching_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ReactiveCaching, :use_clean_rails_memory_store_caching do diff --git a/spec/models/concerns/redactable_spec.rb b/spec/models/concerns/redactable_spec.rb index 7feeaa54069..57c7d2cb767 100644 --- a/spec/models/concerns/redactable_spec.rb +++ b/spec/models/concerns/redactable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Redactable do diff --git a/spec/models/concerns/redis_cacheable_spec.rb b/spec/models/concerns/redis_cacheable_spec.rb index 23c6c6233e9..a9dca27f258 100644 --- a/spec/models/concerns/redis_cacheable_spec.rb +++ b/spec/models/concerns/redis_cacheable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe RedisCacheable do diff --git a/spec/models/concerns/relative_positioning_spec.rb b/spec/models/concerns/relative_positioning_spec.rb index ac8da30b6c9..d0ae45f7871 100644 --- a/spec/models/concerns/relative_positioning_spec.rb +++ b/spec/models/concerns/relative_positioning_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe RelativePositioning do diff --git a/spec/models/concerns/resolvable_discussion_spec.rb b/spec/models/concerns/resolvable_discussion_spec.rb index 97b046b0f21..9ea01ca9002 100644 --- a/spec/models/concerns/resolvable_discussion_spec.rb +++ b/spec/models/concerns/resolvable_discussion_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Discussion, ResolvableDiscussion do diff --git a/spec/models/concerns/resolvable_note_spec.rb b/spec/models/concerns/resolvable_note_spec.rb index fcb5250278e..4f46252a044 100644 --- a/spec/models/concerns/resolvable_note_spec.rb +++ b/spec/models/concerns/resolvable_note_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Note, ResolvableNote do diff --git a/spec/models/concerns/routable_spec.rb b/spec/models/concerns/routable_spec.rb index 565266321d3..1fb0dd5030c 100644 --- a/spec/models/concerns/routable_spec.rb +++ b/spec/models/concerns/routable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Group, 'Routable' do diff --git a/spec/models/concerns/sha_attribute_spec.rb b/spec/models/concerns/sha_attribute_spec.rb index 0d3beb6a6e3..a4a81ae126d 100644 --- a/spec/models/concerns/sha_attribute_spec.rb +++ b/spec/models/concerns/sha_attribute_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ShaAttribute do diff --git a/spec/models/concerns/sortable_spec.rb b/spec/models/concerns/sortable_spec.rb index 0a9d2021a19..184f7986a6f 100644 --- a/spec/models/concerns/sortable_spec.rb +++ b/spec/models/concerns/sortable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Sortable do diff --git a/spec/models/concerns/spammable_spec.rb b/spec/models/concerns/spammable_spec.rb index e698207166c..650d49e41a1 100644 --- a/spec/models/concerns/spammable_spec.rb +++ b/spec/models/concerns/spammable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Spammable do diff --git a/spec/models/concerns/strip_attribute_spec.rb b/spec/models/concerns/strip_attribute_spec.rb index 8c945686b66..5c0d1042e06 100644 --- a/spec/models/concerns/strip_attribute_spec.rb +++ b/spec/models/concerns/strip_attribute_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe StripAttribute do diff --git a/spec/models/concerns/subscribable_spec.rb b/spec/models/concerns/subscribable_spec.rb index 45dfb136aea..2f88adf08dd 100644 --- a/spec/models/concerns/subscribable_spec.rb +++ b/spec/models/concerns/subscribable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Subscribable, 'Subscribable' do diff --git a/spec/models/concerns/token_authenticatable_spec.rb b/spec/models/concerns/token_authenticatable_spec.rb index 40cb4eef60a..51e28974ae0 100644 --- a/spec/models/concerns/token_authenticatable_spec.rb +++ b/spec/models/concerns/token_authenticatable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' shared_examples 'TokenAuthenticatable' do diff --git a/spec/models/concerns/token_authenticatable_strategies/base_spec.rb b/spec/models/concerns/token_authenticatable_strategies/base_spec.rb index 2a0182b4294..7332da309d5 100644 --- a/spec/models/concerns/token_authenticatable_strategies/base_spec.rb +++ b/spec/models/concerns/token_authenticatable_strategies/base_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe TokenAuthenticatableStrategies::Base do diff --git a/spec/models/concerns/token_authenticatable_strategies/encrypted_spec.rb b/spec/models/concerns/token_authenticatable_strategies/encrypted_spec.rb index ca38f86c5ab..70f41981b3b 100644 --- a/spec/models/concerns/token_authenticatable_strategies/encrypted_spec.rb +++ b/spec/models/concerns/token_authenticatable_strategies/encrypted_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe TokenAuthenticatableStrategies::Encrypted do diff --git a/spec/models/concerns/triggerable_hooks_spec.rb b/spec/models/concerns/triggerable_hooks_spec.rb index 265abd6bd72..f28e5f56411 100644 --- a/spec/models/concerns/triggerable_hooks_spec.rb +++ b/spec/models/concerns/triggerable_hooks_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe TriggerableHooks do diff --git a/spec/models/concerns/uniquify_spec.rb b/spec/models/concerns/uniquify_spec.rb index 6cd2de6dcce..9ba35702ba6 100644 --- a/spec/models/concerns/uniquify_spec.rb +++ b/spec/models/concerns/uniquify_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Uniquify do diff --git a/spec/models/container_repository_spec.rb b/spec/models/container_repository_spec.rb index e46945e301e..013112d1d51 100644 --- a/spec/models/container_repository_spec.rb +++ b/spec/models/container_repository_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ContainerRepository do diff --git a/spec/models/conversational_development_index/metric_spec.rb b/spec/models/conversational_development_index/metric_spec.rb index b3193619503..60b1a860dfd 100644 --- a/spec/models/conversational_development_index/metric_spec.rb +++ b/spec/models/conversational_development_index/metric_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe ConversationalDevelopmentIndex::Metric do diff --git a/spec/models/cycle_analytics/code_spec.rb b/spec/models/cycle_analytics/code_spec.rb index 6a6b58fb52b..b22a0340015 100644 --- a/spec/models/cycle_analytics/code_spec.rb +++ b/spec/models/cycle_analytics/code_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'CycleAnalytics#code' do diff --git a/spec/models/cycle_analytics/issue_spec.rb b/spec/models/cycle_analytics/issue_spec.rb index 45f1b4fe8a3..07d60be091a 100644 --- a/spec/models/cycle_analytics/issue_spec.rb +++ b/spec/models/cycle_analytics/issue_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'CycleAnalytics#issue' do diff --git a/spec/models/cycle_analytics/plan_spec.rb b/spec/models/cycle_analytics/plan_spec.rb index d366e2b723a..3d22a284264 100644 --- a/spec/models/cycle_analytics/plan_spec.rb +++ b/spec/models/cycle_analytics/plan_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'CycleAnalytics#plan' do diff --git a/spec/models/cycle_analytics/production_spec.rb b/spec/models/cycle_analytics/production_spec.rb index 156eb96cfce..383727cd8f7 100644 --- a/spec/models/cycle_analytics/production_spec.rb +++ b/spec/models/cycle_analytics/production_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'CycleAnalytics#production' do diff --git a/spec/models/cycle_analytics/review_spec.rb b/spec/models/cycle_analytics/review_spec.rb index 0aedfb49cb5..1af5f9cc1f4 100644 --- a/spec/models/cycle_analytics/review_spec.rb +++ b/spec/models/cycle_analytics/review_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'CycleAnalytics#review' do diff --git a/spec/models/cycle_analytics/staging_spec.rb b/spec/models/cycle_analytics/staging_spec.rb index 0cbda50c688..8375944f03c 100644 --- a/spec/models/cycle_analytics/staging_spec.rb +++ b/spec/models/cycle_analytics/staging_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'CycleAnalytics#staging' do diff --git a/spec/models/cycle_analytics/test_spec.rb b/spec/models/cycle_analytics/test_spec.rb index e58b8fdff58..b78258df564 100644 --- a/spec/models/cycle_analytics/test_spec.rb +++ b/spec/models/cycle_analytics/test_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'CycleAnalytics#test' do diff --git a/spec/models/cycle_analytics_spec.rb b/spec/models/cycle_analytics_spec.rb index 0fe24870f02..5d8b5b573cf 100644 --- a/spec/models/cycle_analytics_spec.rb +++ b/spec/models/cycle_analytics_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe CycleAnalytics do diff --git a/spec/models/deploy_key_spec.rb b/spec/models/deploy_key_spec.rb index 41440c6d288..ec6cfb6b826 100644 --- a/spec/models/deploy_key_spec.rb +++ b/spec/models/deploy_key_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe DeployKey, :mailer do diff --git a/spec/models/deploy_keys_project_spec.rb b/spec/models/deploy_keys_project_spec.rb index fca3090ff4a..c137444763b 100644 --- a/spec/models/deploy_keys_project_spec.rb +++ b/spec/models/deploy_keys_project_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe DeployKeysProject do diff --git a/spec/models/deploy_token_spec.rb b/spec/models/deploy_token_spec.rb index 3435f93c999..05320703e25 100644 --- a/spec/models/deploy_token_spec.rb +++ b/spec/models/deploy_token_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe DeployToken do diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb index 5fce9504334..d9170d5fa07 100644 --- a/spec/models/deployment_spec.rb +++ b/spec/models/deployment_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Deployment do diff --git a/spec/models/diff_discussion_spec.rb b/spec/models/diff_discussion_spec.rb index 50b19000799..cfeb4382927 100644 --- a/spec/models/diff_discussion_spec.rb +++ b/spec/models/diff_discussion_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe DiffDiscussion do diff --git a/spec/models/diff_note_spec.rb b/spec/models/diff_note_spec.rb index 67e5f4f7e41..fa19cb47a0d 100644 --- a/spec/models/diff_note_spec.rb +++ b/spec/models/diff_note_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe DiffNote do diff --git a/spec/models/diff_viewer/base_spec.rb b/spec/models/diff_viewer/base_spec.rb index f4efe5a7b3a..b8bdeb781dc 100644 --- a/spec/models/diff_viewer/base_spec.rb +++ b/spec/models/diff_viewer/base_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe DiffViewer::Base do diff --git a/spec/models/diff_viewer/server_side_spec.rb b/spec/models/diff_viewer/server_side_spec.rb index 86b14b6ebf3..27de0584b8a 100644 --- a/spec/models/diff_viewer/server_side_spec.rb +++ b/spec/models/diff_viewer/server_side_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe DiffViewer::ServerSide do diff --git a/spec/models/discussion_spec.rb b/spec/models/discussion_spec.rb index a46f7ed6507..0d02165787a 100644 --- a/spec/models/discussion_spec.rb +++ b/spec/models/discussion_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Discussion do diff --git a/spec/models/email_spec.rb b/spec/models/email_spec.rb index 47eb0717c0c..cae88f39660 100644 --- a/spec/models/email_spec.rb +++ b/spec/models/email_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Email do diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index ab1b306e597..448ed35cb1e 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Environment do diff --git a/spec/models/environment_status_spec.rb b/spec/models/environment_status_spec.rb index 2576a9aba06..c503c35305f 100644 --- a/spec/models/environment_status_spec.rb +++ b/spec/models/environment_status_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe EnvironmentStatus do diff --git a/spec/models/error_tracking/project_error_tracking_setting_spec.rb b/spec/models/error_tracking/project_error_tracking_setting_spec.rb index cbde13a2c7a..21e381d9fb7 100644 --- a/spec/models/error_tracking/project_error_tracking_setting_spec.rb +++ b/spec/models/error_tracking/project_error_tracking_setting_spec.rb @@ -167,7 +167,7 @@ describe ErrorTracking::ProjectErrorTrackingSetting do end end - context 'when sentry client raises exception' do + context 'when sentry client raises Sentry::Client::Error' do let(:sentry_client) { spy(:sentry_client) } before do @@ -179,7 +179,31 @@ describe ErrorTracking::ProjectErrorTrackingSetting do end it 'returns error' do - expect(result).to eq(error: 'error message') + expect(result).to eq( + error: 'error message', + error_type: ErrorTracking::ProjectErrorTrackingSetting::SENTRY_API_ERROR_TYPE_NON_20X_RESPONSE + ) + expect(subject).to have_received(:sentry_client) + expect(sentry_client).to have_received(:list_issues) + end + end + + context 'when sentry client raises Sentry::Client::MissingKeysError' do + let(:sentry_client) { spy(:sentry_client) } + + before do + synchronous_reactive_cache(subject) + + allow(subject).to receive(:sentry_client).and_return(sentry_client) + allow(sentry_client).to receive(:list_issues).with(opts) + .and_raise(Sentry::Client::MissingKeysError, 'Sentry API response is missing keys. key not found: "id"') + end + + it 'returns error' do + expect(result).to eq( + error: 'Sentry API response is missing keys. key not found: "id"', + error_type: ErrorTracking::ProjectErrorTrackingSetting::SENTRY_API_ERROR_TYPE_MISSING_KEYS + ) expect(subject).to have_received(:sentry_client) expect(sentry_client).to have_received(:list_issues) end diff --git a/spec/models/event_collection_spec.rb b/spec/models/event_collection_spec.rb index 6078f429bdc..efe511042c3 100644 --- a/spec/models/event_collection_spec.rb +++ b/spec/models/event_collection_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe EventCollection do diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index ce4f8ee4705..d192fe70506 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Event do diff --git a/spec/models/external_issue_spec.rb b/spec/models/external_issue_spec.rb index 83ba22caa03..9d064d458f0 100644 --- a/spec/models/external_issue_spec.rb +++ b/spec/models/external_issue_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ExternalIssue do diff --git a/spec/models/fork_network_member_spec.rb b/spec/models/fork_network_member_spec.rb index 60d04562e6c..eab758248de 100644 --- a/spec/models/fork_network_member_spec.rb +++ b/spec/models/fork_network_member_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ForkNetworkMember do diff --git a/spec/models/fork_network_spec.rb b/spec/models/fork_network_spec.rb index a43baf1820a..5ec0f8d6b02 100644 --- a/spec/models/fork_network_spec.rb +++ b/spec/models/fork_network_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ForkNetwork do diff --git a/spec/models/generic_commit_status_spec.rb b/spec/models/generic_commit_status_spec.rb index a3e68d2e646..c851810ffb3 100644 --- a/spec/models/generic_commit_status_spec.rb +++ b/spec/models/generic_commit_status_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe GenericCommitStatus do diff --git a/spec/models/global_milestone_spec.rb b/spec/models/global_milestone_spec.rb index f93904065c7..9d901d01a52 100644 --- a/spec/models/global_milestone_spec.rb +++ b/spec/models/global_milestone_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe GlobalMilestone do diff --git a/spec/models/gpg_key_spec.rb b/spec/models/gpg_key_spec.rb index 58a1d2e4ea2..479b39cd139 100644 --- a/spec/models/gpg_key_spec.rb +++ b/spec/models/gpg_key_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe GpgKey do diff --git a/spec/models/gpg_key_subkey_spec.rb b/spec/models/gpg_key_subkey_spec.rb index 3c86837f47f..51d2f9cb9ac 100644 --- a/spec/models/gpg_key_subkey_spec.rb +++ b/spec/models/gpg_key_subkey_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe GpgKeySubkey do diff --git a/spec/models/gpg_signature_spec.rb b/spec/models/gpg_signature_spec.rb index e90319c39b1..47c343edf0e 100644 --- a/spec/models/gpg_signature_spec.rb +++ b/spec/models/gpg_signature_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe GpgSignature do diff --git a/spec/models/group_custom_attribute_spec.rb b/spec/models/group_custom_attribute_spec.rb index 7ecb2022567..7d60c74b62b 100644 --- a/spec/models/group_custom_attribute_spec.rb +++ b/spec/models/group_custom_attribute_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe GroupCustomAttribute do diff --git a/spec/models/group_label_spec.rb b/spec/models/group_label_spec.rb index d0fc1eaa3ec..a3a5c631c3d 100644 --- a/spec/models/group_label_spec.rb +++ b/spec/models/group_label_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe GroupLabel do diff --git a/spec/models/group_milestone_spec.rb b/spec/models/group_milestone_spec.rb index fcc33cd95fe..01856870fe0 100644 --- a/spec/models/group_milestone_spec.rb +++ b/spec/models/group_milestone_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe GroupMilestone do diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 16624ce47d0..2c6abddca17 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Group do diff --git a/spec/models/guest_spec.rb b/spec/models/guest_spec.rb index fc30f3056e5..93862e98172 100644 --- a/spec/models/guest_spec.rb +++ b/spec/models/guest_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Guest do diff --git a/spec/models/hooks/active_hook_filter_spec.rb b/spec/models/hooks/active_hook_filter_spec.rb index df7edda2213..1249c793f7f 100644 --- a/spec/models/hooks/active_hook_filter_spec.rb +++ b/spec/models/hooks/active_hook_filter_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ActiveHookFilter do diff --git a/spec/models/hooks/project_hook_spec.rb b/spec/models/hooks/project_hook_spec.rb index 5dd31b1b5de..a945f0d1516 100644 --- a/spec/models/hooks/project_hook_spec.rb +++ b/spec/models/hooks/project_hook_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProjectHook do diff --git a/spec/models/hooks/service_hook_spec.rb b/spec/models/hooks/service_hook_spec.rb index e32eaafc13f..936c2fbad27 100644 --- a/spec/models/hooks/service_hook_spec.rb +++ b/spec/models/hooks/service_hook_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ServiceHook do diff --git a/spec/models/hooks/system_hook_spec.rb b/spec/models/hooks/system_hook_spec.rb index edd1cb455af..e0d4d2e4858 100644 --- a/spec/models/hooks/system_hook_spec.rb +++ b/spec/models/hooks/system_hook_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe SystemHook do diff --git a/spec/models/hooks/web_hook_log_spec.rb b/spec/models/hooks/web_hook_log_spec.rb index 744a6ccae8b..f812149c9be 100644 --- a/spec/models/hooks/web_hook_log_spec.rb +++ b/spec/models/hooks/web_hook_log_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe WebHookLog do diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb index a308ac6e33a..fe08dc4f5e6 100644 --- a/spec/models/hooks/web_hook_spec.rb +++ b/spec/models/hooks/web_hook_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe WebHook do diff --git a/spec/models/identity_spec.rb b/spec/models/identity_spec.rb index e1a7a59dfd1..74ddc2d6284 100644 --- a/spec/models/identity_spec.rb +++ b/spec/models/identity_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Identity do diff --git a/spec/models/import_export_upload_spec.rb b/spec/models/import_export_upload_spec.rb index 58af84b8a08..18a714f4d98 100644 --- a/spec/models/import_export_upload_spec.rb +++ b/spec/models/import_export_upload_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ImportExportUpload do diff --git a/spec/models/instance_configuration_spec.rb b/spec/models/instance_configuration_spec.rb index cb3d6c7cda2..e65f97df3c3 100644 --- a/spec/models/instance_configuration_spec.rb +++ b/spec/models/instance_configuration_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe InstanceConfiguration do @@ -30,8 +32,8 @@ describe InstanceConfiguration do end def stub_pub_file(exist: true) - path = 'spec/fixtures/ssh_host_example_key.pub' - path << 'random' unless exist + path = exist ? 'spec/fixtures/ssh_host_example_key.pub' : 'spec/fixtures/ssh_host_example_key.pub.random' + allow(subject).to receive(:ssh_algorithm_file).and_return(Rails.root.join(path)) end end diff --git a/spec/models/internal_id_spec.rb b/spec/models/internal_id_spec.rb index d32f163f05b..ff2382838ae 100644 --- a/spec/models/internal_id_spec.rb +++ b/spec/models/internal_id_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe InternalId do diff --git a/spec/models/issue/metrics_spec.rb b/spec/models/issue/metrics_spec.rb index b7291eebe64..07858fe8a70 100644 --- a/spec/models/issue/metrics_spec.rb +++ b/spec/models/issue/metrics_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Issue::Metrics do diff --git a/spec/models/issue_collection_spec.rb b/spec/models/issue_collection_spec.rb index 580a98193af..7fc635f100f 100644 --- a/spec/models/issue_collection_spec.rb +++ b/spec/models/issue_collection_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe IssueCollection do diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 6101df2e099..892dd053e39 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Issue do diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb index 06d26ef89f1..a0b6eff88d5 100644 --- a/spec/models/key_spec.rb +++ b/spec/models/key_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Key, :mailer do diff --git a/spec/models/label_link_spec.rb b/spec/models/label_link_spec.rb index e2b49bc2de7..b160e72e759 100644 --- a/spec/models/label_link_spec.rb +++ b/spec/models/label_link_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe LabelLink do diff --git a/spec/models/label_priority_spec.rb b/spec/models/label_priority_spec.rb index 9dcb0f06b20..1a93468290f 100644 --- a/spec/models/label_priority_spec.rb +++ b/spec/models/label_priority_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe LabelPriority do diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb index 3fc6c06b7fa..5174c590a10 100644 --- a/spec/models/label_spec.rb +++ b/spec/models/label_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Label do diff --git a/spec/models/legacy_diff_discussion_spec.rb b/spec/models/legacy_diff_discussion_spec.rb index dae97b69c84..49ea319fbd1 100644 --- a/spec/models/legacy_diff_discussion_spec.rb +++ b/spec/models/legacy_diff_discussion_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe LegacyDiffDiscussion do diff --git a/spec/models/lfs_download_object_spec.rb b/spec/models/lfs_download_object_spec.rb index 88838b127d2..effd8b08124 100644 --- a/spec/models/lfs_download_object_spec.rb +++ b/spec/models/lfs_download_object_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe LfsDownloadObject do diff --git a/spec/models/lfs_file_lock_spec.rb b/spec/models/lfs_file_lock_spec.rb index 41ca1578b94..aa64d66944b 100644 --- a/spec/models/lfs_file_lock_spec.rb +++ b/spec/models/lfs_file_lock_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe LfsFileLock do diff --git a/spec/models/lfs_object_spec.rb b/spec/models/lfs_object_spec.rb index 3f929710862..3d4d4b7d795 100644 --- a/spec/models/lfs_object_spec.rb +++ b/spec/models/lfs_object_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe LfsObject do diff --git a/spec/models/lfs_objects_project_spec.rb b/spec/models/lfs_objects_project_spec.rb index 0a3180f43e8..3e86ee38566 100644 --- a/spec/models/lfs_objects_project_spec.rb +++ b/spec/models/lfs_objects_project_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe LfsObjectsProject do diff --git a/spec/models/license_template_spec.rb b/spec/models/license_template_spec.rb index dd912eefac1..7037277e580 100644 --- a/spec/models/license_template_spec.rb +++ b/spec/models/license_template_spec.rb @@ -1,16 +1,19 @@ +# frozen_string_literal: true + require 'spec_helper' describe LicenseTemplate do describe '#content' do it 'calls a proc exactly once if provided' do - lazy = build_template(-> { 'bar' }) - content = lazy.content + content_proc = -> { 'bar' } + expect(content_proc).to receive(:call).once.and_call_original + + lazy = build_template(content_proc) - expect(content).to eq('bar') - expect(content.object_id).to eq(lazy.content.object_id) + expect(lazy.content).to eq('bar') - content.replace('foo') - expect(lazy.content).to eq('foo') + # Subsequent calls should not call proc again + expect(lazy.content).to eq('bar') end it 'returns a string if provided' do diff --git a/spec/models/list_spec.rb b/spec/models/list_spec.rb index a51580f8292..18d4549977c 100644 --- a/spec/models/list_spec.rb +++ b/spec/models/list_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe List do diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb index 188beac1582..c68c3ce2abe 100644 --- a/spec/models/member_spec.rb +++ b/spec/models/member_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Member do diff --git a/spec/models/members/group_member_spec.rb b/spec/models/members/group_member_spec.rb index a3451c67bd8..f227abd3dae 100644 --- a/spec/models/members/group_member_spec.rb +++ b/spec/models/members/group_member_spec.rb @@ -1,6 +1,24 @@ +# frozen_string_literal: true + require 'spec_helper' describe GroupMember do + describe '.count_users_by_group_id' do + it 'counts users by group ID' do + user_1 = create(:user) + user_2 = create(:user) + group_1 = create(:group) + group_2 = create(:group) + + group_1.add_owner(user_1) + group_1.add_owner(user_2) + group_2.add_owner(user_1) + + expect(described_class.count_users_by_group_id).to eq(group_1.id => 2, + group_2.id => 1) + end + end + describe '.access_level_roles' do it 'returns Gitlab::Access.options_with_owner' do expect(described_class.access_level_roles).to eq(Gitlab::Access.options_with_owner) diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb index 36bfff2c339..497764b6825 100644 --- a/spec/models/members/project_member_spec.rb +++ b/spec/models/members/project_member_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProjectMember do diff --git a/spec/models/merge_request/metrics_spec.rb b/spec/models/merge_request/metrics_spec.rb index 02ff7839739..49573af0fed 100644 --- a/spec/models/merge_request/metrics_spec.rb +++ b/spec/models/merge_request/metrics_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe MergeRequest::Metrics do diff --git a/spec/models/merge_request_diff_commit_spec.rb b/spec/models/merge_request_diff_commit_spec.rb index 10487190a44..ab2aadf7d88 100644 --- a/spec/models/merge_request_diff_commit_spec.rb +++ b/spec/models/merge_request_diff_commit_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe MergeRequestDiffCommit do diff --git a/spec/models/merge_request_diff_file_spec.rb b/spec/models/merge_request_diff_file_spec.rb index faa47660a74..66957c24fdc 100644 --- a/spec/models/merge_request_diff_file_spec.rb +++ b/spec/models/merge_request_diff_file_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe MergeRequestDiffFile do diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb index 53f5307ea0b..a53add67066 100644 --- a/spec/models/merge_request_diff_spec.rb +++ b/spec/models/merge_request_diff_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe MergeRequestDiff do @@ -51,7 +53,104 @@ describe MergeRequestDiff do end end - describe '#latest' do + describe '.ids_for_external_storage_migration' do + set(:merge_request) { create(:merge_request) } + set(:outdated) { merge_request.merge_request_diff } + set(:latest) { merge_request.create_merge_request_diff } + + set(:closed_mr) { create(:merge_request, :closed_last_month) } + let(:closed) { closed_mr.merge_request_diff } + + set(:merged_mr) { create(:merge_request, :merged_last_month) } + let(:merged) { merged_mr.merge_request_diff } + + set(:recently_closed_mr) { create(:merge_request, :closed) } + let(:closed_recently) { recently_closed_mr.merge_request_diff } + + set(:recently_merged_mr) { create(:merge_request, :merged) } + let(:merged_recently) { recently_merged_mr.merge_request_diff } + + before do + merge_request.update!(latest_merge_request_diff: latest) + end + + subject { described_class.ids_for_external_storage_migration(limit: 1000) } + + context 'external diffs are disabled' do + before do + stub_external_diffs_setting(enabled: false) + end + + it { is_expected.to be_empty } + end + + context 'external diffs are misconfigured' do + before do + stub_external_diffs_setting(enabled: true, when: 'every second tuesday') + end + + it { is_expected.to be_empty } + end + + context 'external diffs are enabled unconditionally' do + before do + stub_external_diffs_setting(enabled: true) + end + + it { is_expected.to contain_exactly(outdated.id, latest.id, closed.id, merged.id, closed_recently.id, merged_recently.id) } + end + + context 'external diffs are enabled for outdated diffs' do + before do + stub_external_diffs_setting(enabled: true, when: 'outdated') + end + + it 'returns records for outdated merge request versions' do + is_expected.to contain_exactly(outdated.id, closed.id, merged.id) + end + end + + context 'with limit' do + it 'respects the limit' do + stub_external_diffs_setting(enabled: true) + + expect(described_class.ids_for_external_storage_migration(limit: 3).count).to eq(3) + end + end + end + + describe '#migrate_files_to_external_storage!' do + let(:diff) { create(:merge_request).merge_request_diff } + + it 'converts from in-database to external storage' do + expect(diff).not_to be_stored_externally + + stub_external_diffs_setting(enabled: true) + expect(diff).to receive(:save!) + + diff.migrate_files_to_external_storage! + + expect(diff).to be_stored_externally + end + + it 'does nothing with an external diff' do + stub_external_diffs_setting(enabled: true) + + expect(diff).to be_stored_externally + expect(diff).not_to receive(:save!) + + diff.migrate_files_to_external_storage! + end + + it 'does nothing if external diffs are disabled' do + expect(diff).not_to be_stored_externally + expect(diff).not_to receive(:save!) + + diff.migrate_files_to_external_storage! + end + end + + describe '#latest?' do let!(:mr) { create(:merge_request, :with_diffs) } let!(:first_diff) { mr.merge_request_diff } let!(:last_diff) { mr.create_merge_request_diff } @@ -222,12 +321,56 @@ describe MergeRequestDiff do include_examples 'merge request diffs' end - describe 'external diffs configured' do + describe 'external diffs always enabled' do before do - stub_external_diffs_setting(enabled: true) + stub_external_diffs_setting(enabled: true, when: 'always') + end + + include_examples 'merge request diffs' + end + + describe 'exernal diffs enabled for outdated diffs' do + before do + stub_external_diffs_setting(enabled: true, when: 'outdated') end include_examples 'merge request diffs' + + it 'stores up-to-date diffs in the database' do + expect(diff).not_to be_stored_externally + end + + it 'stores diffs for recently closed MRs in the database' do + mr = create(:merge_request, :closed) + + expect(mr.merge_request_diff).not_to be_stored_externally + end + + it 'stores diffs for recently merged MRs in the database' do + mr = create(:merge_request, :merged) + + expect(mr.merge_request_diff).not_to be_stored_externally + end + + it 'stores diffs for old MR versions in external storage' do + old_diff = diff + merge_request.create_merge_request_diff + old_diff.migrate_files_to_external_storage! + + expect(old_diff).to be_stored_externally + end + + it 'stores diffs for old closed MRs in external storage' do + mr = create(:merge_request, :closed_last_month) + + expect(mr.merge_request_diff).to be_stored_externally + end + + it 'stores diffs for old merged MRs in external storage' do + mr = create(:merge_request, :merged_last_month) + + expect(mr.merge_request_diff).to be_stored_externally + end end describe '#commit_shas' do diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 22998bc5b6a..fb32f72e2de 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe MergeRequest do @@ -84,32 +86,27 @@ describe MergeRequest do describe '#default_squash_commit_message' do let(:project) { subject.project } - - def commit_collection(commit_hashes) - raw_commits = commit_hashes.map { |raw| Commit.from_hash(raw, project) } - - CommitCollection.new(project, raw_commits) - end + let(:is_multiline) { -> (c) { c.description.present? } } + let(:multiline_commits) { subject.commits.select(&is_multiline) } + let(:singleline_commits) { subject.commits.reject(&is_multiline) } it 'returns the oldest multiline commit message' do - commits = commit_collection([ - { message: 'Singleline', parent_ids: [] }, - { message: "Second multiline\nCommit message", parent_ids: [] }, - { message: "First multiline\nCommit message", parent_ids: [] } - ]) - - expect(subject).to receive(:commits).and_return(commits) - - expect(subject.default_squash_commit_message).to eq("First multiline\nCommit message") + expect(subject.default_squash_commit_message).to eq(multiline_commits.last.message) end it 'returns the merge request title if there are no multiline commits' do - commits = commit_collection([ - { message: 'Singleline', parent_ids: [] } - ]) + expect(subject).to receive(:commits).and_return( + CommitCollection.new(project, singleline_commits) + ) + + expect(subject.default_squash_commit_message).to eq(subject.title) + end - expect(subject).to receive(:commits).and_return(commits) + it 'does not return commit messages from multiline merge commits' do + collection = CommitCollection.new(project, multiline_commits).enrich! + expect(collection.commits).to all( receive(:merge_commit?).and_return(true) ) + expect(subject).to receive(:commits).and_return(collection) expect(subject.default_squash_commit_message).to eq(subject.title) end end @@ -184,6 +181,31 @@ describe MergeRequest do expect(MergeRequest::Metrics.count).to eq(1) end end + + describe '#refresh_merge_request_assignees' do + set(:user) { create(:user) } + + it 'creates merge request assignees relation upon MR creation' do + merge_request = create(:merge_request, assignee: nil) + + expect(merge_request.merge_request_assignees).to be_empty + + expect { merge_request.update!(assignee: user) } + .to change { merge_request.reload.merge_request_assignees.count } + .from(0).to(1) + end + + it 'updates merge request assignees relation upon MR assignee change' do + another_user = create(:user) + merge_request = create(:merge_request, assignee: user) + + expect { merge_request.update!(assignee: another_user) } + .to change { merge_request.reload.merge_request_assignees.first.assignee } + .from(user).to(another_user) + + expect(merge_request.merge_request_assignees.count).to eq(1) + end + end end describe 'respond to' do @@ -1045,7 +1067,7 @@ describe MergeRequest do describe '#commit_authors' do it 'returns all the authors of every commit in the merge request' do - users = subject.commits.map(&:author_email).uniq.map do |email| + users = subject.commits.without_merge_commits.map(&:author_email).uniq.map do |email| create(:user, email: email) end @@ -1059,7 +1081,7 @@ describe MergeRequest do describe '#authors' do it 'returns a list with all the commit authors in the merge request and author' do - users = subject.commits.map(&:author_email).uniq.map do |email| + users = subject.commits.without_merge_commits.map(&:author_email).uniq.map do |email| create(:user, email: email) end @@ -2692,14 +2714,21 @@ describe MergeRequest do end describe '#has_commits?' do - before do + it 'returns true when merge request diff has commits' do allow(subject.merge_request_diff).to receive(:commits_count) .and_return(2) - end - it 'returns true when merge request diff has commits' do expect(subject.has_commits?).to be_truthy end + + context 'when commits_count is nil' do + it 'returns false' do + allow(subject.merge_request_diff).to receive(:commits_count) + .and_return(nil) + + expect(subject.has_commits?).to be_falsey + end + end end describe '#has_no_commits?' do @@ -3061,6 +3090,38 @@ describe MergeRequest do end end + describe '#mergeable_to_ref?' do + it 'returns true when merge request is mergeable' do + subject = create(:merge_request) + + expect(subject.mergeable_to_ref?).to be(true) + end + + it 'returns false when merge request is already merged' do + subject = create(:merge_request, :merged) + + expect(subject.mergeable_to_ref?).to be(false) + end + + it 'returns false when merge request is closed' do + subject = create(:merge_request, :closed) + + expect(subject.mergeable_to_ref?).to be(false) + end + + it 'returns false when merge request is work in progress' do + subject = create(:merge_request, title: 'WIP: The feature') + + expect(subject.mergeable_to_ref?).to be(false) + end + + it 'returns false when merge request has no commits' do + subject = create(:merge_request, source_branch: 'empty-branch', target_branch: 'master') + + expect(subject.mergeable_to_ref?).to be(false) + end + end + describe '#merge_participants' do it 'contains author' do expect(subject.merge_participants).to eq([subject.author]) @@ -3095,4 +3156,32 @@ describe MergeRequest do end end end + + describe '.merge_request_ref?' do + subject { described_class.merge_request_ref?(ref) } + + context 'when ref is ref name of a branch' do + let(:ref) { 'feature' } + + it { is_expected.to be_falsey } + end + + context 'when ref is HEAD ref path of a branch' do + let(:ref) { 'refs/heads/feature' } + + it { is_expected.to be_falsey } + end + + context 'when ref is HEAD ref path of a merge request' do + let(:ref) { 'refs/merge-requests/1/head' } + + it { is_expected.to be_truthy } + end + + context 'when ref is merge ref path of a merge request' do + let(:ref) { 'refs/merge-requests/1/merge' } + + it { is_expected.to be_truthy } + end + end end diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb index 717d4ae4ec0..b82368318f2 100644 --- a/spec/models/milestone_spec.rb +++ b/spec/models/milestone_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Milestone do diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index aadc298ae0b..62e7dd3231b 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Namespace do diff --git a/spec/models/network/graph_spec.rb b/spec/models/network/graph_spec.rb index c364dd6643b..d1a2bedf542 100644 --- a/spec/models/network/graph_spec.rb +++ b/spec/models/network/graph_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Network::Graph do diff --git a/spec/models/note_diff_file_spec.rb b/spec/models/note_diff_file_spec.rb index 591c1a89748..99eeac8d778 100644 --- a/spec/models/note_diff_file_spec.rb +++ b/spec/models/note_diff_file_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe NoteDiffFile do diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index eb6f6ff5faf..7a1ab20186a 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Note do diff --git a/spec/models/notification_recipient_spec.rb b/spec/models/notification_recipient_spec.rb index 13fe47799ed..3710f2be287 100644 --- a/spec/models/notification_recipient_spec.rb +++ b/spec/models/notification_recipient_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe NotificationRecipient do diff --git a/spec/models/notification_setting_spec.rb b/spec/models/notification_setting_spec.rb index c8ab564e3bc..85128456918 100644 --- a/spec/models/notification_setting_spec.rb +++ b/spec/models/notification_setting_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe NotificationSetting do diff --git a/spec/models/pages_domain_spec.rb b/spec/models/pages_domain_spec.rb index 4b85c5e8720..142ddebbbf8 100644 --- a/spec/models/pages_domain_spec.rb +++ b/spec/models/pages_domain_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PagesDomain do diff --git a/spec/models/personal_access_token_spec.rb b/spec/models/personal_access_token_spec.rb index c82ab9c9e62..e0e1101ffc6 100644 --- a/spec/models/personal_access_token_spec.rb +++ b/spec/models/personal_access_token_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PersonalAccessToken do diff --git a/spec/models/programming_language_spec.rb b/spec/models/programming_language_spec.rb index 99cd358f863..b327d360461 100644 --- a/spec/models/programming_language_spec.rb +++ b/spec/models/programming_language_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProgrammingLanguage do diff --git a/spec/models/project_authorization_spec.rb b/spec/models/project_authorization_spec.rb index c289ee0859a..6f06fe4e55a 100644 --- a/spec/models/project_authorization_spec.rb +++ b/spec/models/project_authorization_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProjectAuthorization do diff --git a/spec/models/project_auto_devops_spec.rb b/spec/models/project_auto_devops_spec.rb index 7ff64c76e37..8ad28ce68cc 100644 --- a/spec/models/project_auto_devops_spec.rb +++ b/spec/models/project_auto_devops_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProjectAutoDevops do diff --git a/spec/models/project_custom_attribute_spec.rb b/spec/models/project_custom_attribute_spec.rb index 669de5506bc..80638676b49 100644 --- a/spec/models/project_custom_attribute_spec.rb +++ b/spec/models/project_custom_attribute_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProjectCustomAttribute do diff --git a/spec/models/project_deploy_token_spec.rb b/spec/models/project_deploy_token_spec.rb index 9e2e40c2e8f..2a5fefc1ab0 100644 --- a/spec/models/project_deploy_token_spec.rb +++ b/spec/models/project_deploy_token_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe ProjectDeployToken, type: :model do diff --git a/spec/models/project_feature_spec.rb b/spec/models/project_feature_spec.rb index fee7d65c217..50c9d5968ac 100644 --- a/spec/models/project_feature_spec.rb +++ b/spec/models/project_feature_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProjectFeature do diff --git a/spec/models/project_group_link_spec.rb b/spec/models/project_group_link_spec.rb index 5bea21427d4..dad5506900b 100644 --- a/spec/models/project_group_link_spec.rb +++ b/spec/models/project_group_link_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProjectGroupLink do diff --git a/spec/models/project_import_state_spec.rb b/spec/models/project_import_state_spec.rb index e3b2d971419..472bf8f9713 100644 --- a/spec/models/project_import_state_spec.rb +++ b/spec/models/project_import_state_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe ProjectImportState, type: :model do diff --git a/spec/models/project_label_spec.rb b/spec/models/project_label_spec.rb index 689d4e505e5..330aab9f856 100644 --- a/spec/models/project_label_spec.rb +++ b/spec/models/project_label_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProjectLabel do diff --git a/spec/models/project_services/asana_service_spec.rb b/spec/models/project_services/asana_service_spec.rb index e66109fd98f..18e839bcc64 100644 --- a/spec/models/project_services/asana_service_spec.rb +++ b/spec/models/project_services/asana_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe AsanaService do diff --git a/spec/models/project_services/assembla_service_spec.rb b/spec/models/project_services/assembla_service_spec.rb index 5cb6d63659e..7742e33e901 100644 --- a/spec/models/project_services/assembla_service_spec.rb +++ b/spec/models/project_services/assembla_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe AssemblaService do diff --git a/spec/models/project_services/bamboo_service_spec.rb b/spec/models/project_services/bamboo_service_spec.rb index b880d90d28f..08c510f09df 100644 --- a/spec/models/project_services/bamboo_service_spec.rb +++ b/spec/models/project_services/bamboo_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe BambooService, :use_clean_rails_memory_store_caching do diff --git a/spec/models/project_services/bugzilla_service_spec.rb b/spec/models/project_services/bugzilla_service_spec.rb index 43f7bcb1a19..6818db48fee 100644 --- a/spec/models/project_services/bugzilla_service_spec.rb +++ b/spec/models/project_services/bugzilla_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe BugzillaService do diff --git a/spec/models/project_services/buildkite_service_spec.rb b/spec/models/project_services/buildkite_service_spec.rb index 1615a93a4ca..091d4d8f695 100644 --- a/spec/models/project_services/buildkite_service_spec.rb +++ b/spec/models/project_services/buildkite_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe BuildkiteService, :use_clean_rails_memory_store_caching do diff --git a/spec/models/project_services/campfire_service_spec.rb b/spec/models/project_services/campfire_service_spec.rb index ed8347edffd..bf4c52fc7ab 100644 --- a/spec/models/project_services/campfire_service_spec.rb +++ b/spec/models/project_services/campfire_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe CampfireService do diff --git a/spec/models/project_services/chat_message/issue_message_spec.rb b/spec/models/project_services/chat_message/issue_message_spec.rb index f7a35fdc88a..d3adc62c38e 100644 --- a/spec/models/project_services/chat_message/issue_message_spec.rb +++ b/spec/models/project_services/chat_message/issue_message_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ChatMessage::IssueMessage do diff --git a/spec/models/project_services/chat_message/merge_message_spec.rb b/spec/models/project_services/chat_message/merge_message_spec.rb index 7997b5bb6b9..b56eb19dd55 100644 --- a/spec/models/project_services/chat_message/merge_message_spec.rb +++ b/spec/models/project_services/chat_message/merge_message_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ChatMessage::MergeMessage do diff --git a/spec/models/project_services/chat_message/note_message_spec.rb b/spec/models/project_services/chat_message/note_message_spec.rb index 5abbd7bec18..5e7987dc0f6 100644 --- a/spec/models/project_services/chat_message/note_message_spec.rb +++ b/spec/models/project_services/chat_message/note_message_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ChatMessage::NoteMessage do diff --git a/spec/models/project_services/chat_message/pipeline_message_spec.rb b/spec/models/project_services/chat_message/pipeline_message_spec.rb index 0ff20400999..8f9fa310ad4 100644 --- a/spec/models/project_services/chat_message/pipeline_message_spec.rb +++ b/spec/models/project_services/chat_message/pipeline_message_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ChatMessage::PipelineMessage do diff --git a/spec/models/project_services/chat_message/push_message_spec.rb b/spec/models/project_services/chat_message/push_message_spec.rb index 973d6bdb2a0..a89645a3ea8 100644 --- a/spec/models/project_services/chat_message/push_message_spec.rb +++ b/spec/models/project_services/chat_message/push_message_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ChatMessage::PushMessage do diff --git a/spec/models/project_services/chat_message/wiki_page_message_spec.rb b/spec/models/project_services/chat_message/wiki_page_message_spec.rb index 7efcba9bcfd..c3db516f253 100644 --- a/spec/models/project_services/chat_message/wiki_page_message_spec.rb +++ b/spec/models/project_services/chat_message/wiki_page_message_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ChatMessage::WikiPageMessage do diff --git a/spec/models/project_services/chat_notification_service_spec.rb b/spec/models/project_services/chat_notification_service_spec.rb index 46713df77da..6f4ddd223f6 100644 --- a/spec/models/project_services/chat_notification_service_spec.rb +++ b/spec/models/project_services/chat_notification_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ChatNotificationService do diff --git a/spec/models/project_services/custom_issue_tracker_service_spec.rb b/spec/models/project_services/custom_issue_tracker_service_spec.rb index 7e1b1a4f2af..f0e7551693d 100644 --- a/spec/models/project_services/custom_issue_tracker_service_spec.rb +++ b/spec/models/project_services/custom_issue_tracker_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe CustomIssueTrackerService do diff --git a/spec/models/project_services/drone_ci_service_spec.rb b/spec/models/project_services/drone_ci_service_spec.rb index 26597d9b83c..22df19d943f 100644 --- a/spec/models/project_services/drone_ci_service_spec.rb +++ b/spec/models/project_services/drone_ci_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe DroneCiService, :use_clean_rails_memory_store_caching do diff --git a/spec/models/project_services/emails_on_push_service_spec.rb b/spec/models/project_services/emails_on_push_service_spec.rb index d9b7010e5e5..0a58eb367e3 100644 --- a/spec/models/project_services/emails_on_push_service_spec.rb +++ b/spec/models/project_services/emails_on_push_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe EmailsOnPushService do diff --git a/spec/models/project_services/external_wiki_service_spec.rb b/spec/models/project_services/external_wiki_service_spec.rb index 62fd97b038b..bdd8605436f 100644 --- a/spec/models/project_services/external_wiki_service_spec.rb +++ b/spec/models/project_services/external_wiki_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ExternalWikiService do diff --git a/spec/models/project_services/flowdock_service_spec.rb b/spec/models/project_services/flowdock_service_spec.rb index fabcb142858..c1ebe69ee66 100644 --- a/spec/models/project_services/flowdock_service_spec.rb +++ b/spec/models/project_services/flowdock_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe FlowdockService do diff --git a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb index 3237b660a16..11f96c03d46 100644 --- a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb +++ b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe GitlabIssueTrackerService do diff --git a/spec/models/project_services/irker_service_spec.rb b/spec/models/project_services/irker_service_spec.rb index cb9ca76fc3f..2e1f6964692 100644 --- a/spec/models/project_services/irker_service_spec.rb +++ b/spec/models/project_services/irker_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' require 'socket' require 'json' diff --git a/spec/models/project_services/issue_tracker_service_spec.rb b/spec/models/project_services/issue_tracker_service_spec.rb index e6a1752576b..2fc4d69c2db 100644 --- a/spec/models/project_services/issue_tracker_service_spec.rb +++ b/spec/models/project_services/issue_tracker_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe IssueTrackerService do diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb index 5428fcb1271..4a7eee1fbf3 100644 --- a/spec/models/project_services/jira_service_spec.rb +++ b/spec/models/project_services/jira_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe JiraService do diff --git a/spec/models/project_services/kubernetes_service_spec.rb b/spec/models/project_services/kubernetes_service_spec.rb index 56e587262ef..7bf093b71e7 100644 --- a/spec/models/project_services/kubernetes_service_spec.rb +++ b/spec/models/project_services/kubernetes_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe KubernetesService, :use_clean_rails_memory_store_caching do diff --git a/spec/models/project_services/mattermost_service_spec.rb b/spec/models/project_services/mattermost_service_spec.rb index 10c62ca55a7..6261c70f266 100644 --- a/spec/models/project_services/mattermost_service_spec.rb +++ b/spec/models/project_services/mattermost_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe MattermostService do diff --git a/spec/models/project_services/mattermost_slash_commands_service_spec.rb b/spec/models/project_services/mattermost_slash_commands_service_spec.rb index 1983e0cc967..87e482059f2 100644 --- a/spec/models/project_services/mattermost_slash_commands_service_spec.rb +++ b/spec/models/project_services/mattermost_slash_commands_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe MattermostSlashCommandsService do diff --git a/spec/models/project_services/microsoft_teams_service_spec.rb b/spec/models/project_services/microsoft_teams_service_spec.rb index 3351c6280b4..521d5265753 100644 --- a/spec/models/project_services/microsoft_teams_service_spec.rb +++ b/spec/models/project_services/microsoft_teams_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe MicrosoftTeamsService do diff --git a/spec/models/project_services/packagist_service_spec.rb b/spec/models/project_services/packagist_service_spec.rb index 6acee311700..53f18a1bdd9 100644 --- a/spec/models/project_services/packagist_service_spec.rb +++ b/spec/models/project_services/packagist_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PackagistService do diff --git a/spec/models/project_services/pipelines_email_service_spec.rb b/spec/models/project_services/pipelines_email_service_spec.rb index 75ae2207910..ca17e7453b8 100644 --- a/spec/models/project_services/pipelines_email_service_spec.rb +++ b/spec/models/project_services/pipelines_email_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PipelinesEmailService, :mailer do diff --git a/spec/models/project_services/pivotaltracker_service_spec.rb b/spec/models/project_services/pivotaltracker_service_spec.rb index f7d2372eca2..de2c8790405 100644 --- a/spec/models/project_services/pivotaltracker_service_spec.rb +++ b/spec/models/project_services/pivotaltracker_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PivotaltrackerService do diff --git a/spec/models/project_services/pushover_service_spec.rb b/spec/models/project_services/pushover_service_spec.rb index 54b8c658ff6..d2a45f48705 100644 --- a/spec/models/project_services/pushover_service_spec.rb +++ b/spec/models/project_services/pushover_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PushoverService do diff --git a/spec/models/project_services/redmine_service_spec.rb b/spec/models/project_services/redmine_service_spec.rb index 2ac14eab5e1..ac570ac27e1 100644 --- a/spec/models/project_services/redmine_service_spec.rb +++ b/spec/models/project_services/redmine_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe RedmineService do diff --git a/spec/models/project_services/slack_service_spec.rb b/spec/models/project_services/slack_service_spec.rb index 13cf4d1915e..01f580c5d01 100644 --- a/spec/models/project_services/slack_service_spec.rb +++ b/spec/models/project_services/slack_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe SlackService do diff --git a/spec/models/project_services/slack_slash_commands_service_spec.rb b/spec/models/project_services/slack_slash_commands_service_spec.rb index 5c4bce90ace..8c57907d064 100644 --- a/spec/models/project_services/slack_slash_commands_service_spec.rb +++ b/spec/models/project_services/slack_slash_commands_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe SlackSlashCommandsService do diff --git a/spec/models/project_services/teamcity_service_spec.rb b/spec/models/project_services/teamcity_service_spec.rb index 64b4efca43a..96dccae733b 100644 --- a/spec/models/project_services/teamcity_service_spec.rb +++ b/spec/models/project_services/teamcity_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe TeamcityService, :use_clean_rails_memory_store_caching do diff --git a/spec/models/project_services/youtrack_service_spec.rb b/spec/models/project_services/youtrack_service_spec.rb index 9524b526a46..bf9d892f66c 100644 --- a/spec/models/project_services/youtrack_service_spec.rb +++ b/spec/models/project_services/youtrack_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe YoutrackService do diff --git a/spec/models/project_snippet_spec.rb b/spec/models/project_snippet_spec.rb index 1b439bcfad1..e87b4f41f4d 100644 --- a/spec/models/project_snippet_spec.rb +++ b/spec/models/project_snippet_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProjectSnippet do diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 1ea54eeb4f7..2158d3cf3e6 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Project do @@ -2710,7 +2712,7 @@ describe Project do end describe '#any_lfs_file_locks?', :request_store do - set(:project) { create(:project) } + let!(:project) { create(:project) } it 'returns false when there are no LFS file locks' do expect(project.any_lfs_file_locks?).to be_falsey @@ -3148,6 +3150,53 @@ describe Project do expect(projects).to eq([public_project]) end end + + context 'with requested visibility levels' do + set(:internal_project) { create(:project, :internal, :repository) } + set(:private_project_2) { create(:project, :private) } + + context 'with admin user' do + set(:admin) { create(:admin) } + + it 'returns all projects' do + projects = described_class.all.public_or_visible_to_user(admin, []) + + expect(projects).to match_array([public_project, private_project, private_project_2, internal_project]) + end + + it 'returns all public and private projects' do + projects = described_class.all.public_or_visible_to_user(admin, [Gitlab::VisibilityLevel::PUBLIC, Gitlab::VisibilityLevel::PRIVATE]) + + expect(projects).to match_array([public_project, private_project, private_project_2]) + end + + it 'returns all private projects' do + projects = described_class.all.public_or_visible_to_user(admin, [Gitlab::VisibilityLevel::PRIVATE]) + + expect(projects).to match_array([private_project, private_project_2]) + end + end + + context 'with regular user' do + it 'returns authorized projects' do + projects = described_class.all.public_or_visible_to_user(user, []) + + expect(projects).to match_array([public_project, private_project, internal_project]) + end + + it "returns user's public and private projects" do + projects = described_class.all.public_or_visible_to_user(user, [Gitlab::VisibilityLevel::PUBLIC, Gitlab::VisibilityLevel::PRIVATE]) + + expect(projects).to match_array([public_project, private_project]) + end + + it 'returns one private project' do + projects = described_class.all.public_or_visible_to_user(user, [Gitlab::VisibilityLevel::PRIVATE]) + + expect(projects).to eq([private_project]) + end + end + end end describe '.with_feature_available_for_user' do @@ -3428,7 +3477,7 @@ describe Project do end it 'schedules HashedStorage::ProjectMigrateWorker with delayed start when the project repo is in use' do - Gitlab::ReferenceCounter.new(project.gl_repository(is_wiki: false)).increase + Gitlab::ReferenceCounter.new(Gitlab::GlRepository::PROJECT.identifier_for_subject(project)).increase expect(HashedStorage::ProjectMigrateWorker).to receive(:perform_in) @@ -3436,7 +3485,7 @@ describe Project do end it 'schedules HashedStorage::ProjectMigrateWorker with delayed start when the wiki repo is in use' do - Gitlab::ReferenceCounter.new(project.gl_repository(is_wiki: true)).increase + Gitlab::ReferenceCounter.new(Gitlab::GlRepository::WIKI.identifier_for_subject(project)).increase expect(HashedStorage::ProjectMigrateWorker).to receive(:perform_in) @@ -3569,16 +3618,6 @@ describe Project do end end - describe '#gl_repository' do - let(:project) { create(:project) } - - it 'delegates to Gitlab::GlRepository.gl_repository' do - expect(Gitlab::GlRepository).to receive(:gl_repository).with(project, true) - - project.gl_repository(is_wiki: true) - end - end - describe '#has_ci?' do set(:project) { create(:project) } let(:repository) { double } diff --git a/spec/models/project_statistics_spec.rb b/spec/models/project_statistics_spec.rb index 64c39f09e33..c670b6aac56 100644 --- a/spec/models/project_statistics_spec.rb +++ b/spec/models/project_statistics_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe ProjectStatistics do diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb index 3537dead5d1..a2d4fad9292 100644 --- a/spec/models/project_team_spec.rb +++ b/spec/models/project_team_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe ProjectTeam do diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb index 7be8d67ba9e..7ea67f31534 100644 --- a/spec/models/project_wiki_spec.rb +++ b/spec/models/project_wiki_spec.rb @@ -1,4 +1,6 @@ # coding: utf-8 +# frozen_string_literal: true + require "spec_helper" describe ProjectWiki do diff --git a/spec/models/protectable_dropdown_spec.rb b/spec/models/protectable_dropdown_spec.rb index d4433a88a15..aca3df9fdde 100644 --- a/spec/models/protectable_dropdown_spec.rb +++ b/spec/models/protectable_dropdown_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProtectableDropdown do diff --git a/spec/models/protected_branch/merge_access_level_spec.rb b/spec/models/protected_branch/merge_access_level_spec.rb index 612e4a0e332..39dd586b157 100644 --- a/spec/models/protected_branch/merge_access_level_spec.rb +++ b/spec/models/protected_branch/merge_access_level_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProtectedBranch::MergeAccessLevel do diff --git a/spec/models/protected_branch/push_access_level_spec.rb b/spec/models/protected_branch/push_access_level_spec.rb index 9ccdc22fd41..628c8d29ecd 100644 --- a/spec/models/protected_branch/push_access_level_spec.rb +++ b/spec/models/protected_branch/push_access_level_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProtectedBranch::PushAccessLevel do diff --git a/spec/models/protected_branch_spec.rb b/spec/models/protected_branch_spec.rb index dafe7646366..267434a4148 100644 --- a/spec/models/protected_branch_spec.rb +++ b/spec/models/protected_branch_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProtectedBranch do diff --git a/spec/models/protected_tag_spec.rb b/spec/models/protected_tag_spec.rb index e5a0f6ec23f..79120d17d39 100644 --- a/spec/models/protected_tag_spec.rb +++ b/spec/models/protected_tag_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProtectedTag do diff --git a/spec/models/push_event_payload_spec.rb b/spec/models/push_event_payload_spec.rb index 69a4922b6fd..6b59ee5ee57 100644 --- a/spec/models/push_event_payload_spec.rb +++ b/spec/models/push_event_payload_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PushEventPayload do diff --git a/spec/models/push_event_spec.rb b/spec/models/push_event_spec.rb index bfe7a30b96a..f86500f91cd 100644 --- a/spec/models/push_event_spec.rb +++ b/spec/models/push_event_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PushEvent do diff --git a/spec/models/redirect_route_spec.rb b/spec/models/redirect_route_spec.rb index 106ae59af29..6ecb5c31c7e 100644 --- a/spec/models/redirect_route_spec.rb +++ b/spec/models/redirect_route_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe RedirectRoute do diff --git a/spec/models/release_spec.rb b/spec/models/release_spec.rb index 157c96c1f65..b4b32c95dee 100644 --- a/spec/models/release_spec.rb +++ b/spec/models/release_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe Release do diff --git a/spec/models/remote_mirror_spec.rb b/spec/models/remote_mirror_spec.rb index c06e9a08ab4..0478094034a 100644 --- a/spec/models/remote_mirror_spec.rb +++ b/spec/models/remote_mirror_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe RemoteMirror, :mailer do diff --git a/spec/models/repository_language_spec.rb b/spec/models/repository_language_spec.rb index e2e4beb512f..13a4cd1e7cf 100644 --- a/spec/models/repository_language_spec.rb +++ b/spec/models/repository_language_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe RepositoryLanguage do diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 6599b4e765a..2578208659a 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Repository do diff --git a/spec/models/route_spec.rb b/spec/models/route_spec.rb index 48799781b87..20289afbeb5 100644 --- a/spec/models/route_spec.rb +++ b/spec/models/route_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Route do diff --git a/spec/models/sent_notification_spec.rb b/spec/models/sent_notification_spec.rb index 6c35ed8f649..09be90b82ed 100644 --- a/spec/models/sent_notification_spec.rb +++ b/spec/models/sent_notification_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe SentNotification do diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb index 25eecb3f909..2f025038bab 100644 --- a/spec/models/service_spec.rb +++ b/spec/models/service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Service do diff --git a/spec/models/snippet_blob_spec.rb b/spec/models/snippet_blob_spec.rb index 7c71c458fcc..88441e39d45 100644 --- a/spec/models/snippet_blob_spec.rb +++ b/spec/models/snippet_blob_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe SnippetBlob do diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb index 664dc3fa145..3524cdae3b8 100644 --- a/spec/models/snippet_spec.rb +++ b/spec/models/snippet_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Snippet do diff --git a/spec/models/spam_log_spec.rb b/spec/models/spam_log_spec.rb index 90a2caaeb88..e9ea234f75d 100644 --- a/spec/models/spam_log_spec.rb +++ b/spec/models/spam_log_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe SpamLog do diff --git a/spec/models/ssh_host_key_spec.rb b/spec/models/ssh_host_key_spec.rb index 4c677569561..a17cd8ba345 100644 --- a/spec/models/ssh_host_key_spec.rb +++ b/spec/models/ssh_host_key_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe SshHostKey do diff --git a/spec/models/subscription_spec.rb b/spec/models/subscription_spec.rb index 9e4c2620d82..41bd48810b2 100644 --- a/spec/models/subscription_spec.rb +++ b/spec/models/subscription_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Subscription do diff --git a/spec/models/system_note_metadata_spec.rb b/spec/models/system_note_metadata_spec.rb index 1e3f587e460..bcd3c03f947 100644 --- a/spec/models/system_note_metadata_spec.rb +++ b/spec/models/system_note_metadata_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe SystemNoteMetadata do diff --git a/spec/models/term_agreement_spec.rb b/spec/models/term_agreement_spec.rb index 950dfa09a6a..42a48048b67 100644 --- a/spec/models/term_agreement_spec.rb +++ b/spec/models/term_agreement_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe TermAgreement do diff --git a/spec/models/timelog_spec.rb b/spec/models/timelog_spec.rb index a0c93c531ea..9d69a0ab148 100644 --- a/spec/models/timelog_spec.rb +++ b/spec/models/timelog_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe Timelog do diff --git a/spec/models/todo_spec.rb b/spec/models/todo_spec.rb index 3682e21ca40..b5bf294790a 100644 --- a/spec/models/todo_spec.rb +++ b/spec/models/todo_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Todo do diff --git a/spec/models/tree_spec.rb b/spec/models/tree_spec.rb index 6bdb62a0864..c2d5dfdf9c4 100644 --- a/spec/models/tree_spec.rb +++ b/spec/models/tree_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Tree do diff --git a/spec/models/trending_project_spec.rb b/spec/models/trending_project_spec.rb index 3b5e7ca0d39..619fc8e7d38 100644 --- a/spec/models/trending_project_spec.rb +++ b/spec/models/trending_project_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe TrendingProject do diff --git a/spec/models/upload_spec.rb b/spec/models/upload_spec.rb index 5a0df9fbbb0..02702cb2497 100644 --- a/spec/models/upload_spec.rb +++ b/spec/models/upload_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe Upload do diff --git a/spec/models/user_agent_detail_spec.rb b/spec/models/user_agent_detail_spec.rb index b4669f8c1c2..f191d245045 100644 --- a/spec/models/user_agent_detail_spec.rb +++ b/spec/models/user_agent_detail_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe UserAgentDetail do diff --git a/spec/models/user_callout_spec.rb b/spec/models/user_callout_spec.rb index d54355afe12..b87f6f03d6f 100644 --- a/spec/models/user_callout_spec.rb +++ b/spec/models/user_callout_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe UserCallout do diff --git a/spec/models/user_custom_attribute_spec.rb b/spec/models/user_custom_attribute_spec.rb index 37fc3cb64f0..d0981b2d771 100644 --- a/spec/models/user_custom_attribute_spec.rb +++ b/spec/models/user_custom_attribute_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe UserCustomAttribute do diff --git a/spec/models/user_interacted_project_spec.rb b/spec/models/user_interacted_project_spec.rb index cb4bb3372d4..47d919c1d12 100644 --- a/spec/models/user_interacted_project_spec.rb +++ b/spec/models/user_interacted_project_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe UserInteractedProject do diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 1be29d039a7..b7e36748fa2 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe User do diff --git a/spec/models/wiki_directory_spec.rb b/spec/models/wiki_directory_spec.rb index fb8575cfe2b..5fbcccf897e 100644 --- a/spec/models/wiki_directory_spec.rb +++ b/spec/models/wiki_directory_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' RSpec.describe WikiDirectory do diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb index cba22b2cc4e..e68da67818a 100644 --- a/spec/models/wiki_page_spec.rb +++ b/spec/models/wiki_page_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe WikiPage do diff --git a/spec/policies/board_policy_spec.rb b/spec/policies/board_policy_spec.rb index 4b76d65ef69..52c23951e37 100644 --- a/spec/policies/board_policy_spec.rb +++ b/spec/policies/board_policy_spec.rb @@ -17,14 +17,6 @@ describe BoardPolicy do ] end - def expect_allowed(*permissions) - permissions.each { |p| is_expected.to be_allowed(p) } - end - - def expect_disallowed(*permissions) - permissions.each { |p| is_expected.not_to be_allowed(p) } - end - context 'group board' do subject { described_class.new(user, group_board) } diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb index 92bdaa8b8b8..dc98baca6dc 100644 --- a/spec/policies/group_policy_spec.rb +++ b/spec/policies/group_policy_spec.rb @@ -1,63 +1,7 @@ require 'spec_helper' describe GroupPolicy do - let(:guest) { create(:user) } - let(:reporter) { create(:user) } - let(:developer) { create(:user) } - let(:maintainer) { create(:user) } - let(:owner) { create(:user) } - let(:admin) { create(:admin) } - let(:group) { create(:group, :private) } - - let(:guest_permissions) do - [:read_label, :read_group, :upload_file, :read_namespace, :read_group_activity, - :read_group_issues, :read_group_boards, :read_group_labels, :read_group_milestones, - :read_group_merge_requests] - end - - let(:reporter_permissions) { [:admin_label] } - - let(:developer_permissions) { [:admin_milestone] } - - let(:maintainer_permissions) do - [ - :create_projects, - :read_cluster, - :create_cluster, - :update_cluster, - :admin_cluster, - :add_cluster - ] - end - - let(:owner_permissions) do - [ - :admin_group, - :admin_namespace, - :admin_group_member, - :change_visibility_level, - :set_note_created_at, - (Gitlab::Database.postgresql? ? :create_subgroup : nil) - ].compact - end - - before do - group.add_guest(guest) - group.add_reporter(reporter) - group.add_developer(developer) - group.add_maintainer(maintainer) - group.add_owner(owner) - end - - subject { described_class.new(current_user, group) } - - def expect_allowed(*permissions) - permissions.each { |p| is_expected.to be_allowed(p) } - end - - def expect_disallowed(*permissions) - permissions.each { |p| is_expected.not_to be_allowed(p) } - end + include_context 'GroupPolicy context' context 'with no user' do let(:group) { create(:group, :public) } diff --git a/spec/policies/namespace_policy_spec.rb b/spec/policies/namespace_policy_spec.rb index 1fdf95ad716..99fa8b1fe44 100644 --- a/spec/policies/namespace_policy_spec.rb +++ b/spec/policies/namespace_policy_spec.rb @@ -30,7 +30,7 @@ describe NamespacePolicy do context 'user who has exceeded project limit' do let(:owner) { create(:user, projects_limit: 0) } - it { is_expected.not_to be_allowed(:create_projects) } + it { is_expected.to be_disallowed(:create_projects) } end end diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index 772d1fbee2b..726ccba8807 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -1,96 +1,7 @@ require 'spec_helper' describe ProjectPolicy do - set(:guest) { create(:user) } - set(:reporter) { create(:user) } - set(:developer) { create(:user) } - set(:maintainer) { create(:user) } - set(:owner) { create(:user) } - set(:admin) { create(:admin) } - let(:project) { create(:project, :public, namespace: owner.namespace) } - - let(:base_guest_permissions) do - %i[ - read_project read_board read_list read_wiki read_issue - read_project_for_iids read_issue_iid read_label - read_milestone read_project_snippet read_project_member read_note - create_project create_issue create_note upload_file create_merge_request_in - award_emoji read_release - ] - end - - let(:base_reporter_permissions) do - %i[ - download_code fork_project create_project_snippet update_issue - admin_issue admin_label admin_list read_commit_status read_build - read_container_image read_pipeline read_environment read_deployment - read_merge_request download_wiki_code read_sentry_issue - ] - end - - let(:team_member_reporter_permissions) do - %i[build_download_code build_read_container_image] - end - - let(:developer_permissions) do - %i[ - admin_milestone admin_merge_request update_merge_request create_commit_status - update_commit_status create_build update_build create_pipeline - update_pipeline create_merge_request_from create_wiki push_code - resolve_note create_container_image update_container_image - create_environment create_deployment create_release update_release - ] - end - - let(:base_maintainer_permissions) do - %i[ - push_to_delete_protected_branch update_project_snippet update_environment - update_deployment admin_project_snippet admin_project_member admin_note admin_wiki admin_project - admin_commit_status admin_build admin_container_image - admin_pipeline admin_environment admin_deployment destroy_release add_cluster - daily_statistics - ] - end - - let(:public_permissions) do - %i[ - download_code fork_project read_commit_status read_pipeline - read_container_image build_download_code build_read_container_image - download_wiki_code read_release - ] - end - - let(:owner_permissions) do - %i[ - change_namespace change_visibility_level rename_project remove_project - archive_project remove_fork_project destroy_merge_request destroy_issue - set_issue_iid set_issue_created_at set_note_created_at - ] - end - - # Used in EE specs - let(:additional_guest_permissions) { [] } - let(:additional_reporter_permissions) { [] } - let(:additional_maintainer_permissions) { [] } - - let(:guest_permissions) { base_guest_permissions + additional_guest_permissions } - let(:reporter_permissions) { base_reporter_permissions + additional_reporter_permissions } - let(:maintainer_permissions) { base_maintainer_permissions + additional_maintainer_permissions } - - before do - project.add_guest(guest) - project.add_maintainer(maintainer) - project.add_developer(developer) - project.add_reporter(reporter) - end - - def expect_allowed(*permissions) - permissions.each { |p| is_expected.to be_allowed(p) } - end - - def expect_disallowed(*permissions) - permissions.each { |p| is_expected.not_to be_allowed(p) } - end + include_context 'ProjectPolicy context' it 'does not include the read_issue permission when the issue author is not a member of the private project' do project = create(:project, :private) @@ -140,7 +51,7 @@ describe ProjectPolicy do end it 'disables boards and lists permissions' do - expect_disallowed :read_board, :create_board, :update_board, :admin_board + expect_disallowed :read_board, :create_board, :update_board expect_disallowed :read_list, :create_list, :update_list, :admin_list end @@ -237,237 +148,6 @@ describe ProjectPolicy do end end - shared_examples 'archived project policies' do - let(:feature_write_abilities) do - described_class::READONLY_FEATURES_WHEN_ARCHIVED.flat_map do |feature| - described_class.create_update_admin_destroy(feature) - end - end - - let(:other_write_abilities) do - %i[ - create_merge_request_in - create_merge_request_from - push_to_delete_protected_branch - push_code - request_access - upload_file - resolve_note - award_emoji - ] - end - - context 'when the project is archived' do - before do - project.archived = true - end - - it 'disables write actions on all relevant project features' do - expect_disallowed(*feature_write_abilities) - end - - it 'disables some other important write actions' do - expect_disallowed(*other_write_abilities) - end - - it 'does not disable other abilities' do - expect_allowed(*(regular_abilities - feature_write_abilities - other_write_abilities)) - end - end - end - - shared_examples 'project policies as anonymous' do - context 'abilities for public projects' do - context 'when a project has pending invites' do - let(:group) { create(:group, :public) } - let(:project) { create(:project, :public, namespace: group) } - let(:user_permissions) { [:create_merge_request_in, :create_project, :create_issue, :create_note, :upload_file, :award_emoji] } - let(:anonymous_permissions) { guest_permissions - user_permissions } - - subject { described_class.new(nil, project) } - - before do - create(:group_member, :invited, group: group) - end - - it 'does not grant owner access' do - expect_allowed(*anonymous_permissions) - expect_disallowed(*user_permissions) - end - - it_behaves_like 'archived project policies' do - let(:regular_abilities) { anonymous_permissions } - end - end - end - - context 'abilities for non-public projects' do - let(:project) { create(:project, namespace: owner.namespace) } - - subject { described_class.new(nil, project) } - - it { is_expected.to be_banned } - end - end - - shared_examples 'project policies as guest' do - subject { described_class.new(guest, project) } - - context 'abilities for non-public projects' do - let(:project) { create(:project, namespace: owner.namespace) } - let(:reporter_public_build_permissions) do - reporter_permissions - [:read_build, :read_pipeline] - end - - it do - expect_allowed(*guest_permissions) - expect_disallowed(*reporter_public_build_permissions) - expect_disallowed(*team_member_reporter_permissions) - expect_disallowed(*developer_permissions) - expect_disallowed(*maintainer_permissions) - expect_disallowed(*owner_permissions) - end - - it_behaves_like 'archived project policies' do - let(:regular_abilities) { guest_permissions } - end - - context 'public builds enabled' do - it do - expect_allowed(*guest_permissions) - expect_allowed(:read_build, :read_pipeline) - end - end - - context 'when public builds disabled' do - before do - project.update(public_builds: false) - end - - it do - expect_allowed(*guest_permissions) - expect_disallowed(:read_build, :read_pipeline) - end - end - - context 'when builds are disabled' do - before do - project.project_feature.update(builds_access_level: ProjectFeature::DISABLED) - end - - it do - expect_disallowed(:read_build) - expect_allowed(:read_pipeline) - end - end - end - end - - shared_examples 'project policies as reporter' do - context 'abilities for non-public projects' do - let(:project) { create(:project, namespace: owner.namespace) } - - subject { described_class.new(reporter, project) } - - it do - expect_allowed(*guest_permissions) - expect_allowed(*reporter_permissions) - expect_allowed(*team_member_reporter_permissions) - expect_disallowed(*developer_permissions) - expect_disallowed(*maintainer_permissions) - expect_disallowed(*owner_permissions) - end - - it_behaves_like 'archived project policies' do - let(:regular_abilities) { reporter_permissions } - end - end - end - - shared_examples 'project policies as developer' do - context 'abilities for non-public projects' do - let(:project) { create(:project, namespace: owner.namespace) } - - subject { described_class.new(developer, project) } - - it do - expect_allowed(*guest_permissions) - expect_allowed(*reporter_permissions) - expect_allowed(*team_member_reporter_permissions) - expect_allowed(*developer_permissions) - expect_disallowed(*maintainer_permissions) - expect_disallowed(*owner_permissions) - end - - it_behaves_like 'archived project policies' do - let(:regular_abilities) { developer_permissions } - end - end - end - - shared_examples 'project policies as maintainer' do - context 'abilities for non-public projects' do - let(:project) { create(:project, namespace: owner.namespace) } - - subject { described_class.new(maintainer, project) } - - it do - expect_allowed(*guest_permissions) - expect_allowed(*reporter_permissions) - expect_allowed(*team_member_reporter_permissions) - expect_allowed(*developer_permissions) - expect_allowed(*maintainer_permissions) - expect_disallowed(*owner_permissions) - end - - it_behaves_like 'archived project policies' do - let(:regular_abilities) { maintainer_permissions } - end - end - end - - shared_examples 'project policies as owner' do - context 'abilities for non-public projects' do - let(:project) { create(:project, namespace: owner.namespace) } - - subject { described_class.new(owner, project) } - - it do - expect_allowed(*guest_permissions) - expect_allowed(*reporter_permissions) - expect_allowed(*team_member_reporter_permissions) - expect_allowed(*developer_permissions) - expect_allowed(*maintainer_permissions) - expect_allowed(*owner_permissions) - end - - it_behaves_like 'archived project policies' do - let(:regular_abilities) { owner_permissions } - end - end - end - - shared_examples 'project policies as admin' do - context 'abilities for non-public projects' do - let(:project) { create(:project, namespace: owner.namespace) } - - subject { described_class.new(admin, project) } - - it do - expect_allowed(*guest_permissions) - expect_allowed(*reporter_permissions) - expect_disallowed(*team_member_reporter_permissions) - expect_allowed(*developer_permissions) - expect_allowed(*maintainer_permissions) - expect_allowed(*owner_permissions) - end - - it_behaves_like 'archived project policies' do - let(:regular_abilities) { owner_permissions } - end - end - end - it_behaves_like 'project policies as anonymous' it_behaves_like 'project policies as guest' it_behaves_like 'project policies as reporter' diff --git a/spec/policies/project_snippet_policy_spec.rb b/spec/policies/project_snippet_policy_spec.rb index d6329e84579..2e9ef1e89fd 100644 --- a/spec/policies/project_snippet_policy_spec.rb +++ b/spec/policies/project_snippet_policy_spec.rb @@ -5,7 +5,7 @@ describe ProjectSnippetPolicy do let(:regular_user) { create(:user) } let(:external_user) { create(:user, :external) } let(:project) { create(:project, :public) } - + let(:snippet) { create(:project_snippet, snippet_visibility, project: project) } let(:author_permissions) do [ :update_project_snippet, @@ -13,23 +13,13 @@ describe ProjectSnippetPolicy do ] end - def abilities(user, snippet_visibility) - snippet = create(:project_snippet, snippet_visibility, project: project) - - described_class.new(user, snippet) - end - - def expect_allowed(*permissions) - permissions.each { |p| is_expected.to be_allowed(p) } - end - - def expect_disallowed(*permissions) - permissions.each { |p| is_expected.not_to be_allowed(p) } - end + subject { described_class.new(current_user, snippet) } context 'public snippet' do + let(:snippet_visibility) { :public } + context 'no user' do - subject { abilities(nil, :public) } + let(:current_user) { nil } it do expect_allowed(:read_project_snippet) @@ -38,7 +28,7 @@ describe ProjectSnippetPolicy do end context 'regular user' do - subject { abilities(regular_user, :public) } + let(:current_user) { regular_user } it do expect_allowed(:read_project_snippet, :create_note) @@ -47,7 +37,7 @@ describe ProjectSnippetPolicy do end context 'external user' do - subject { abilities(external_user, :public) } + let(:current_user) { external_user } it do expect_allowed(:read_project_snippet, :create_note) @@ -57,8 +47,10 @@ describe ProjectSnippetPolicy do end context 'internal snippet' do + let(:snippet_visibility) { :internal } + context 'no user' do - subject { abilities(nil, :internal) } + let(:current_user) { nil } it do expect_disallowed(:read_project_snippet) @@ -67,7 +59,7 @@ describe ProjectSnippetPolicy do end context 'regular user' do - subject { abilities(regular_user, :internal) } + let(:current_user) { regular_user } it do expect_allowed(:read_project_snippet, :create_note) @@ -76,31 +68,31 @@ describe ProjectSnippetPolicy do end context 'external user' do - subject { abilities(external_user, :internal) } + let(:current_user) { external_user } it do expect_disallowed(:read_project_snippet, :create_note) expect_disallowed(*author_permissions) end - end - context 'project team member external user' do - subject { abilities(external_user, :internal) } - - before do - project.add_developer(external_user) - end + context 'project team member' do + before do + project.add_developer(external_user) + end - it do - expect_allowed(:read_project_snippet, :create_note) - expect_disallowed(*author_permissions) + it do + expect_allowed(:read_project_snippet, :create_note) + expect_disallowed(*author_permissions) + end end end end context 'private snippet' do + let(:snippet_visibility) { :private } + context 'no user' do - subject { abilities(nil, :private) } + let(:current_user) { nil } it do expect_disallowed(:read_project_snippet) @@ -109,53 +101,52 @@ describe ProjectSnippetPolicy do end context 'regular user' do - subject { abilities(regular_user, :private) } + let(:current_user) { regular_user } it do expect_disallowed(:read_project_snippet, :create_note) expect_disallowed(*author_permissions) end - end - - context 'snippet author' do - let(:snippet) { create(:project_snippet, :private, author: regular_user, project: project) } - subject { described_class.new(regular_user, snippet) } + context 'snippet author' do + let(:snippet) { create(:project_snippet, :private, author: regular_user, project: project) } - it do - expect_allowed(:read_project_snippet, :create_note) - expect_allowed(*author_permissions) + it do + expect_allowed(:read_project_snippet, :create_note) + expect_allowed(*author_permissions) + end end - end - context 'project team member normal user' do - subject { abilities(regular_user, :private) } - - before do - project.add_developer(regular_user) - end + context 'project team member normal user' do + before do + project.add_developer(regular_user) + end - it do - expect_allowed(:read_project_snippet, :create_note) - expect_disallowed(*author_permissions) + it do + expect_allowed(:read_project_snippet, :create_note) + expect_disallowed(*author_permissions) + end end end - context 'project team member external user' do - subject { abilities(external_user, :private) } + context 'external user' do + context 'project team member' do + let(:current_user) { external_user } - before do - project.add_developer(external_user) - end + before do + project.add_developer(external_user) + end - it do - expect_allowed(:read_project_snippet, :create_note) - expect_disallowed(*author_permissions) + it do + expect_allowed(:read_project_snippet, :create_note) + expect_disallowed(*author_permissions) + end end end context 'admin user' do - subject { abilities(create(:admin), :private) } + let(:snippet_visibility) { :private } + let(:current_user) { create(:admin) } it do expect_allowed(:read_project_snippet, :create_note) diff --git a/spec/presenters/ci/build_runner_presenter_spec.rb b/spec/presenters/ci/build_runner_presenter_spec.rb index f50bcf54b46..ad6cb012d0b 100644 --- a/spec/presenters/ci/build_runner_presenter_spec.rb +++ b/spec/presenters/ci/build_runner_presenter_spec.rb @@ -136,6 +136,24 @@ describe Ci::BuildRunnerPresenter do is_expected.to eq(1) end end + + context 'when pipeline is detached merge request pipeline' do + let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) } + let(:pipeline) { merge_request.all_pipelines.first } + let(:build) { create(:ci_build, ref: pipeline.ref, pipeline: pipeline) } + + it 'returns the default git depth for pipelines for merge requests' do + is_expected.to eq(described_class::DEFAULT_GIT_DEPTH_MERGE_REQUEST) + end + + context 'when pipeline is legacy detached merge request pipeline' do + let(:merge_request) { create(:merge_request, :with_legacy_detached_merge_request_pipeline) } + + it 'behaves as branch pipeline' do + is_expected.to eq(0) + end + end + end end describe '#refspecs' do @@ -165,5 +183,25 @@ describe Ci::BuildRunnerPresenter do end end end + + context 'when pipeline is detached merge request pipeline' do + let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) } + let(:pipeline) { merge_request.all_pipelines.first } + let(:build) { create(:ci_build, ref: pipeline.ref, pipeline: pipeline) } + + it 'returns the correct refspecs' do + is_expected + .to contain_exactly('+refs/merge-requests/1/head:refs/merge-requests/1/head') + end + + context 'when pipeline is legacy detached merge request pipeline' do + let(:merge_request) { create(:merge_request, :with_legacy_detached_merge_request_pipeline) } + + it 'returns the correct refspecs' do + is_expected.to contain_exactly('+refs/tags/*:refs/tags/*', + '+refs/heads/*:refs/remotes/origin/*') + end + end + end end end diff --git a/spec/presenters/clusters/cluster_presenter_spec.rb b/spec/presenters/clusters/cluster_presenter_spec.rb index 754ba0a594c..a9d786bc872 100644 --- a/spec/presenters/clusters/cluster_presenter_spec.rb +++ b/spec/presenters/clusters/cluster_presenter_spec.rb @@ -228,4 +228,20 @@ describe Clusters::ClusterPresenter do it { is_expected.to eq(group_cluster_path(group, cluster)) } end end + + describe '#read_only_kubernetes_platform_fields?' do + subject { described_class.new(cluster).read_only_kubernetes_platform_fields? } + + context 'with a user-provided cluster' do + let(:cluster) { build_stubbed(:cluster, :provided_by_user) } + + it { is_expected.to be_falsy } + end + + context 'with a GCP-provided cluster' do + let(:cluster) { build_stubbed(:cluster, :provided_by_gcp) } + + it { is_expected.to be_truthy } + end + end end diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb index b38cd66986f..8b503777443 100644 --- a/spec/requests/api/branches_spec.rb +++ b/spec/requests/api/branches_spec.rb @@ -20,9 +20,9 @@ describe API::Branches do let(:route) { "/projects/#{project_id}/repository/branches" } shared_examples_for 'repository branches' do - RSpec::Matchers.define :has_merged_branch_names_count do |expected| + RSpec::Matchers.define :has_up_to_merged_branch_names_count do |expected| match do |actual| - actual[:merged_branch_names].count == expected + expected >= actual[:merged_branch_names].count end end @@ -36,10 +36,30 @@ describe API::Branches do expect(branch_names).to match_array(project.repository.branch_names) end + def check_merge_status(json_response) + merged, unmerged = json_response.partition { |branch| branch['merged'] } + merged_branches = merged.map { |branch| branch['name'] } + unmerged_branches = unmerged.map { |branch| branch['name'] } + expect(Set.new(merged_branches)).to eq(project.repository.merged_branch_names(merged_branches + unmerged_branches)) + expect(project.repository.merged_branch_names(unmerged_branches)).to be_empty + end + it 'determines only a limited number of merged branch names' do - expect(API::Entities::Branch).to receive(:represent).with(anything, has_merged_branch_names_count(2)) + expect(API::Entities::Branch).to receive(:represent).with(anything, has_up_to_merged_branch_names_count(2)).and_call_original get api(route, current_user), params: { per_page: 2 } + + expect(response).to have_gitlab_http_status(200) + + check_merge_status(json_response) + end + + it 'merge status matches reality on paginated input' do + get api(route, current_user), params: { per_page: 20, page: 2 } + + expect(response).to have_gitlab_http_status(200) + + check_merge_status(json_response) end context 'when repository is disabled' do diff --git a/spec/requests/api/graphql/project/merge_request_spec.rb b/spec/requests/api/graphql/project/merge_request_spec.rb index deb6abbc026..74820d39102 100644 --- a/spec/requests/api/graphql/project/merge_request_spec.rb +++ b/spec/requests/api/graphql/project/merge_request_spec.rb @@ -70,13 +70,13 @@ describe 'getting merge request information nested in a project' do context 'when there are pipelines' do before do - pipeline = create( + create( :ci_pipeline, project: merge_request.source_project, ref: merge_request.source_branch, sha: merge_request.diff_head_sha ) - merge_request.update!(head_pipeline: pipeline) + merge_request.update_head_pipeline end it 'has a head pipeline' do diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index b184c92824a..537194b8e11 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -321,7 +321,7 @@ describe API::Internal do end context 'with env passed as a JSON' do - let(:gl_repository) { project.gl_repository(is_wiki: true) } + let(:gl_repository) { Gitlab::GlRepository::WIKI.identifier_for_subject(project) } it 'sets env in RequestStore' do obj_dir_relative = './objects' @@ -975,9 +975,9 @@ describe API::Internal do def gl_repository_for(project_or_wiki) case project_or_wiki when ProjectWiki - project_or_wiki.project.gl_repository(is_wiki: true) + Gitlab::GlRepository::WIKI.identifier_for_subject(project_or_wiki.project) when Project - project_or_wiki.gl_repository(is_wiki: false) + Gitlab::GlRepository::PROJECT.identifier_for_subject(project_or_wiki) else nil end diff --git a/spec/requests/api/project_clusters_spec.rb b/spec/requests/api/project_clusters_spec.rb index 4e42e233b4c..81442125a1c 100644 --- a/spec/requests/api/project_clusters_spec.rb +++ b/spec/requests/api/project_clusters_spec.rb @@ -60,7 +60,7 @@ describe API::ProjectClusters do end let(:cluster) do - create(:cluster, :project, :provided_by_gcp, + create(:cluster, :project, :provided_by_gcp, :with_domain, platform_kubernetes: platform_kubernetes, user: current_user, projects: [project]) @@ -88,6 +88,7 @@ describe API::ProjectClusters do expect(json_response['platform_type']).to eq('kubernetes') expect(json_response['environment_scope']).to eq('*') expect(json_response['cluster_type']).to eq('project_type') + expect(json_response['domain']).to eq('example.com') end it 'returns project information' do @@ -187,6 +188,7 @@ describe API::ProjectClusters do let(:cluster_params) do { name: 'test-cluster', + domain: 'domain.example.com', platform_kubernetes_attributes: platform_kubernetes_attributes } end @@ -217,6 +219,7 @@ describe API::ProjectClusters do expect(cluster_result).to be_kubernetes expect(cluster_result.project).to eq(project) expect(cluster_result.name).to eq('test-cluster') + expect(cluster_result.domain).to eq('domain.example.com') expect(platform_kubernetes.rbac?).to be_truthy expect(platform_kubernetes.api_url).to eq(api_url) expect(platform_kubernetes.namespace).to eq(namespace) @@ -294,6 +297,7 @@ describe API::ProjectClusters do let(:update_params) do { + domain: 'new-domain.com', platform_kubernetes_attributes: platform_kubernetes_attributes } end @@ -330,6 +334,7 @@ describe API::ProjectClusters do end it 'should update cluster attributes' do + expect(cluster.domain).to eq('new-domain.com') expect(cluster.platform_kubernetes.namespace).to eq('new-namespace') end end @@ -342,6 +347,7 @@ describe API::ProjectClusters do end it 'should not update cluster attributes' do + expect(cluster.domain).not_to eq('new_domain.com') expect(cluster.platform_kubernetes.namespace).not_to eq('invalid_namespace') expect(cluster.kubernetes_namespace.namespace).not_to eq('invalid_namespace') end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 60d9d7fed13..4c3c088b307 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -1183,6 +1183,16 @@ describe API::Projects do expect(response).to have_gitlab_http_status(200) expect(json_response).to include 'statistics' end + + it "includes statistics also when repository is disabled" do + project.add_developer(user) + project.project_feature.update_attribute(:repository_access_level, ProjectFeature::DISABLED) + + get api("/projects/#{project.id}", user), params: { statistics: true } + + expect(response).to have_gitlab_http_status(200) + expect(json_response).to include 'statistics' + end end it "includes import_error if user can admin project" do diff --git a/spec/requests/api/suggestions_spec.rb b/spec/requests/api/suggestions_spec.rb index 3c2842e5725..5b07e598b8d 100644 --- a/spec/requests/api/suggestions_spec.rb +++ b/spec/requests/api/suggestions_spec.rb @@ -42,8 +42,7 @@ describe API::Suggestions do expect(response).to have_gitlab_http_status(200) expect(json_response) - .to include('id', 'from_original_line', 'to_original_line', - 'from_line', 'to_line', 'appliable', 'applied', + .to include('id', 'from_line', 'to_line', 'appliable', 'applied', 'from_content', 'to_content') end end diff --git a/spec/rubocop/cop/migration/update_column_in_batches_spec.rb b/spec/rubocop/cop/migration/update_column_in_batches_spec.rb index 1c8ab0ad5d2..cba01400d85 100644 --- a/spec/rubocop/cop/migration/update_column_in_batches_spec.rb +++ b/spec/rubocop/cop/migration/update_column_in_batches_spec.rb @@ -93,4 +93,22 @@ describe RuboCop::Cop::Migration::UpdateColumnInBatches do it_behaves_like 'a migration file with no spec file' it_behaves_like 'a migration file with a spec file' end + + context 'EE migrations' do + let(:spec_filepath) { tmp_rails_root.join('ee', 'spec', 'migrations', 'my_super_migration_spec.rb') } + + context 'in a migration' do + let(:migration_filepath) { tmp_rails_root.join('ee', 'db', 'migrate', '20121220064453_my_super_migration.rb') } + + it_behaves_like 'a migration file with no spec file' + it_behaves_like 'a migration file with a spec file' + end + + context 'in a post migration' do + let(:migration_filepath) { tmp_rails_root.join('ee', 'db', 'post_migrate', '20121220064453_my_super_migration.rb') } + + it_behaves_like 'a migration file with no spec file' + it_behaves_like 'a migration file with a spec file' + end + end end diff --git a/spec/serializers/merge_request_widget_entity_spec.rb b/spec/serializers/merge_request_widget_entity_spec.rb index 4dbd79f2fc0..727fd8951f2 100644 --- a/spec/serializers/merge_request_widget_entity_spec.rb +++ b/spec/serializers/merge_request_widget_entity_spec.rb @@ -279,13 +279,18 @@ describe MergeRequestWidgetEntity do end describe 'commits_without_merge_commits' do + def find_matching_commit(short_id) + resource.commits.find { |c| c.short_id == short_id } + end + it 'should not include merge commits' do - # Mock all but the first 5 commits to be merge commits - resource.commits.each_with_index do |commit, i| - expect(commit).to receive(:merge_commit?).at_least(:once).and_return(i > 4) - end + commits_in_widget = subject[:commits_without_merge_commits] - expect(subject[:commits_without_merge_commits].size).to eq(5) + expect(commits_in_widget.length).to be < resource.commits.length + expect(commits_in_widget.length).to eq(resource.commits.without_merge_commits.length) + commits_in_widget.each do |c| + expect(find_matching_commit(c[:short_id]).merge_commit?).to eq(false) + end end end end diff --git a/spec/serializers/suggestion_entity_spec.rb b/spec/serializers/suggestion_entity_spec.rb index 047571f161c..d38fc2b132b 100644 --- a/spec/serializers/suggestion_entity_spec.rb +++ b/spec/serializers/suggestion_entity_spec.rb @@ -13,8 +13,8 @@ describe SuggestionEntity do subject { entity.as_json } it 'exposes correct attributes' do - expect(subject).to include(:id, :from_original_line, :to_original_line, :from_line, - :to_line, :appliable, :applied, :from_content, :to_content) + expect(subject).to include(:id, :from_line, :to_line, :appliable, + :applied, :from_content, :to_content) end it 'exposes current user abilities' do diff --git a/spec/services/ci/destroy_pipeline_service_spec.rb b/spec/services/ci/destroy_pipeline_service_spec.rb index d896f990470..bff2b3179fb 100644 --- a/spec/services/ci/destroy_pipeline_service_spec.rb +++ b/spec/services/ci/destroy_pipeline_service_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' describe ::Ci::DestroyPipelineService do - let(:project) { create(:project) } - let!(:pipeline) { create(:ci_pipeline, project: project) } + let(:project) { create(:project, :repository) } + let!(:pipeline) { create(:ci_pipeline, :success, project: project, sha: project.commit.id) } subject { described_class.new(project, user).execute(pipeline) } @@ -17,6 +17,17 @@ describe ::Ci::DestroyPipelineService do expect { pipeline.reload }.to raise_error(ActiveRecord::RecordNotFound) end + it 'clears the cache', :use_clean_rails_memory_store_caching do + create(:commit_status, :success, pipeline: pipeline, ref: pipeline.ref) + + expect(project.pipeline_status.has_status?).to be_truthy + + subject + + # Need to use find to avoid memoization + expect(Project.find(project.id).pipeline_status.has_status?).to be_falsey + end + it 'does not log an audit event' do expect { subject }.not_to change { SecurityEvent.count } end diff --git a/spec/services/clusters/applications/create_service_spec.rb b/spec/services/clusters/applications/create_service_spec.rb index cbdef008b07..20555873503 100644 --- a/spec/services/clusters/applications/create_service_spec.rb +++ b/spec/services/clusters/applications/create_service_spec.rb @@ -150,7 +150,7 @@ describe Clusters::Applications::CreateService do where(:application, :association, :allowed, :pre_create_helm) do 'helm' | :application_helm | true | false 'ingress' | :application_ingress | true | true - 'runner' | :application_runner | false | true + 'runner' | :application_runner | true | true 'jupyter' | :application_jupyter | false | true 'prometheus' | :application_prometheus | false | true end diff --git a/spec/services/error_tracking/list_issues_service_spec.rb b/spec/services/error_tracking/list_issues_service_spec.rb index 9d4fc62f923..3a8f3069911 100644 --- a/spec/services/error_tracking/list_issues_service_spec.rb +++ b/spec/services/error_tracking/list_issues_service_spec.rb @@ -53,7 +53,10 @@ describe ErrorTracking::ListIssuesService do before do allow(error_tracking_setting) .to receive(:list_sentry_issues) - .and_return(error: 'Sentry response status code: 401') + .and_return( + error: 'Sentry response status code: 401', + error_type: ErrorTracking::ProjectErrorTrackingSetting::SENTRY_API_ERROR_TYPE_NON_20X_RESPONSE + ) end it 'returns the error' do @@ -64,6 +67,25 @@ describe ErrorTracking::ListIssuesService do ) end end + + context 'when list_sentry_issues returns error with http_status' do + before do + allow(error_tracking_setting) + .to receive(:list_sentry_issues) + .and_return( + error: 'Sentry API response is missing keys. key not found: "id"', + error_type: ErrorTracking::ProjectErrorTrackingSetting::SENTRY_API_ERROR_TYPE_MISSING_KEYS + ) + end + + it 'returns the error with correct http_status' do + expect(result).to eq( + status: :error, + http_status: :internal_server_error, + message: 'Sentry API response is missing keys. key not found: "id"' + ) + end + end end context 'with unauthorized user' do diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git/branch_push_service_spec.rb index e8fce951155..d0e2169b4a6 100644 --- a/spec/services/git_push_service_spec.rb +++ b/spec/services/git/branch_push_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe GitPushService, services: true do +describe Git::BranchPushService, services: true do include RepoHelpers set(:user) { create(:user) } diff --git a/spec/services/git_tag_push_service_spec.rb b/spec/services/git/tag_push_service_spec.rb index 2699f6e7bcd..e151db5827f 100644 --- a/spec/services/git_tag_push_service_spec.rb +++ b/spec/services/git/tag_push_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe GitTagPushService do +describe Git::TagPushService do include RepoHelpers include GitHelpers diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb index 715b1168bfb..d50412b6d2c 100644 --- a/spec/services/issues/create_service_spec.rb +++ b/spec/services/issues/create_service_spec.rb @@ -138,6 +138,20 @@ describe Issues::CreateService do end end + context 'when duplicate label titles are given' do + let(:label) { create(:label, project: project) } + + let(:opts) do + { title: 'Title', + description: 'Description', + labels: [label.title, label.title] } + end + + it 'assigns the label once' do + expect(issue.labels).to contain_exactly(label) + end + end + it 'executes issue hooks when issue is not confidential' do opts = { title: 'Title', description: 'Description', confidential: false } diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index f1684209729..1c8a4b608d5 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -592,6 +592,16 @@ describe Issues::UpdateService, :mailer do expect(result.label_ids).not_to include(label.id) end end + + context 'when duplicate label titles are given' do + let(:params) do + { labels: [label3.title, label3.title] } + end + + it 'assigns the label once' do + expect(result.labels).to contain_exactly(label3) + end + end end context 'updating asssignee_id' do diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb index a04a4d5fc36..55e7b46248b 100644 --- a/spec/services/merge_requests/create_service_spec.rb +++ b/spec/services/merge_requests/create_service_spec.rb @@ -173,7 +173,7 @@ describe MergeRequests::CreateService do end end - describe 'Merge request pipelines' do + describe 'Pipelines for merge requests' do before do stub_ci_pipeline_yaml_file(YAML.dump(config)) end @@ -189,12 +189,46 @@ describe MergeRequests::CreateService do } end - it 'creates a merge request pipeline and sets it as a head pipeline' do + it 'creates a detached merge request pipeline and sets it as a head pipeline' do expect(merge_request).to be_persisted merge_request.reload expect(merge_request.merge_request_pipelines.count).to eq(1) - expect(merge_request.actual_head_pipeline).to be_merge_request_event + expect(merge_request.actual_head_pipeline).to be_detached_merge_request_pipeline + end + + context 'when merge request is submitted from forked project' do + let(:target_project) { fork_project(project, nil, repository: true) } + + let(:opts) do + { + title: 'Awesome merge_request', + source_branch: 'feature', + target_branch: 'master', + target_project_id: target_project.id + } + end + + before do + target_project.add_developer(assignee) + target_project.add_maintainer(user) + end + + it 'create legacy detached merge request pipeline for fork merge request' do + expect(merge_request.actual_head_pipeline) + .to be_legacy_detached_merge_request_pipeline + end + end + + context 'when ci_use_merge_request_ref feature flag is false' do + before do + stub_feature_flags(ci_use_merge_request_ref: false) + end + + it 'create legacy detached merge request pipeline for non-fork merge request' do + expect(merge_request.actual_head_pipeline) + .to be_legacy_detached_merge_request_pipeline + end end context 'when there are no commits between source branch and target branch' do @@ -207,7 +241,7 @@ describe MergeRequests::CreateService do } end - it 'does not create a merge request pipeline' do + it 'does not create a detached merge request pipeline' do expect(merge_request).to be_persisted merge_request.reload @@ -225,7 +259,7 @@ describe MergeRequests::CreateService do merge_request end - it 'sets the latest merge request pipeline as the head pipeline' do + it 'sets the latest detached merge request pipeline as the head pipeline' do expect(merge_request.actual_head_pipeline).to be_merge_request_event end end @@ -235,7 +269,7 @@ describe MergeRequests::CreateService do stub_feature_flags(ci_merge_request_pipeline: false) end - it 'does not create a merge request pipeline' do + it 'does not create a detached merge request pipeline' do expect(merge_request).to be_persisted merge_request.reload @@ -254,7 +288,7 @@ describe MergeRequests::CreateService do } end - it 'does not create a merge request pipeline' do + it 'does not create a detached merge request pipeline' do expect(merge_request).to be_persisted merge_request.reload diff --git a/spec/services/merge_requests/ff_merge_service_spec.rb b/spec/services/merge_requests/ff_merge_service_spec.rb index fe673de46aa..1430e12a07e 100644 --- a/spec/services/merge_requests/ff_merge_service_spec.rb +++ b/spec/services/merge_requests/ff_merge_service_spec.rb @@ -72,7 +72,7 @@ describe MergeRequests::FfMergeService do it 'logs and saves error if there is an PreReceiveError exception' do error_message = 'error message' - allow(service).to receive(:repository).and_raise(Gitlab::Git::PreReceiveError, error_message) + allow(service).to receive(:repository).and_raise(Gitlab::Git::PreReceiveError, "GitLab: #{error_message}") allow(service).to receive(:execute_hooks) service.execute(merge_request) diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb index ede79b87bcc..887ec17171e 100644 --- a/spec/services/merge_requests/merge_service_spec.rb +++ b/spec/services/merge_requests/merge_service_spec.rb @@ -239,7 +239,7 @@ describe MergeRequests::MergeService do it 'logs and saves error if there is an PreReceiveError exception' do error_message = 'error message' - allow(service).to receive(:repository).and_raise(Gitlab::Git::PreReceiveError, error_message) + allow(service).to receive(:repository).and_raise(Gitlab::Git::PreReceiveError, "GitLab: #{error_message}") allow(service).to receive(:execute_hooks) service.execute(merge_request) diff --git a/spec/services/merge_requests/merge_to_ref_service_spec.rb b/spec/services/merge_requests/merge_to_ref_service_spec.rb index fabca8f6b4a..a3b48abae26 100644 --- a/spec/services/merge_requests/merge_to_ref_service_spec.rb +++ b/spec/services/merge_requests/merge_to_ref_service_spec.rb @@ -40,15 +40,6 @@ describe MergeRequests::MergeToRefService do end shared_examples_for 'successfully evaluates pre-condition checks' do - it 'returns error when feature is disabled' do - stub_feature_flags(merge_to_tmp_merge_ref_path: false) - - result = service.execute(merge_request) - - expect(result[:status]).to eq(:error) - expect(result[:message]).to eq('Feature is not enabled') - end - it 'returns an error when the failing to process the merge' do allow(project.repository).to receive(:merge_to_ref).and_return(nil) @@ -180,6 +171,17 @@ describe MergeRequests::MergeToRefService do it { expect(todo).not_to be_done } end + context 'when merge request is WIP state' do + it 'fails to merge' do + merge_request = create(:merge_request, title: 'WIP: The feature') + + result = service.execute(merge_request) + + expect(result[:status]).to eq(:error) + expect(result[:message]).to eq("Merge request is not mergeable to #{merge_request.merge_ref_path}") + end + end + it 'returns error when user has no authorization to admin the merge request' do unauthorized_user = create(:user) project.add_reporter(unauthorized_user) diff --git a/spec/services/merge_requests/migrate_external_diffs_service_spec.rb b/spec/services/merge_requests/migrate_external_diffs_service_spec.rb new file mode 100644 index 00000000000..40ac747e66f --- /dev/null +++ b/spec/services/merge_requests/migrate_external_diffs_service_spec.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe MergeRequests::MigrateExternalDiffsService do + let(:merge_request) { create(:merge_request) } + let(:diff) { merge_request.merge_request_diff } + + describe '.enqueue!', :sidekiq do + around do |example| + Sidekiq::Testing.fake! { example.run } + end + + it 'enqueues nothing if external diffs are disabled' do + expect(diff).not_to be_stored_externally + + expect { described_class.enqueue! } + .not_to change { MigrateExternalDiffsWorker.jobs.count } + end + + it 'enqueues eligible in-database diffs if external diffs are enabled' do + expect(diff).not_to be_stored_externally + + stub_external_diffs_setting(enabled: true) + + expect { described_class.enqueue! } + .to change { MigrateExternalDiffsWorker.jobs.count } + .by(1) + end + end + + describe '#execute' do + it 'migrates an in-database diff to the external store' do + expect(diff).not_to be_stored_externally + + stub_external_diffs_setting(enabled: true) + + described_class.new(diff).execute + + expect(diff).to be_stored_externally + end + end +end diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index 43ceb1dcbee..25cbac6d7ee 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -97,6 +97,15 @@ describe MergeRequests::RefreshService do } end + it 'outdates MR suggestions' do + expect_next_instance_of(Suggestions::OutdateService) do |service| + expect(service).to receive(:execute).with(@merge_request).and_call_original + expect(service).to receive(:execute).with(@another_merge_request).and_call_original + end + + refresh_service.execute(@oldrev, @newrev, 'refs/heads/master') + end + context 'when source branch ref does not exists' do before do DeleteBranchService.new(@project, @user).execute(@merge_request.source_branch) @@ -132,7 +141,7 @@ describe MergeRequests::RefreshService do end end - describe 'Merge request pipelines' do + describe 'Pipelines for merge requests' do before do stub_ci_pipeline_yaml_file(YAML.dump(config)) end @@ -150,7 +159,7 @@ describe MergeRequests::RefreshService do } end - it 'create merge request pipeline with commits' do + it 'create detached merge request pipeline with commits' do expect { subject } .to change { @merge_request.merge_request_pipelines.count }.by(1) .and change { @fork_merge_request.merge_request_pipelines.count }.by(1) @@ -161,7 +170,34 @@ describe MergeRequests::RefreshService do expect(@another_merge_request.has_commits?).to be_falsy end - context "when branch pipeline was created before a merge request pipline has been created" do + it 'create detached merge request pipeline for non-fork merge request' do + subject + + expect(@merge_request.merge_request_pipelines.first) + .to be_detached_merge_request_pipeline + end + + it 'create legacy detached merge request pipeline for fork merge request' do + subject + + expect(@fork_merge_request.merge_request_pipelines.first) + .to be_legacy_detached_merge_request_pipeline + end + + context 'when ci_use_merge_request_ref feature flag is false' do + before do + stub_feature_flags(ci_use_merge_request_ref: false) + end + + it 'create legacy detached merge request pipeline for non-fork merge request' do + subject + + expect(@merge_request.merge_request_pipelines.first) + .to be_legacy_detached_merge_request_pipeline + end + end + + context "when branch pipeline was created before a detaced merge request pipeline has been created" do before do create(:ci_pipeline, project: @merge_request.source_project, sha: @merge_request.diff_head_sha, @@ -171,7 +207,7 @@ describe MergeRequests::RefreshService do subject end - it 'sets the latest merge request pipeline as a head pipeline' do + it 'sets the latest detached merge request pipeline as a head pipeline' do @merge_request.reload expect(@merge_request.actual_head_pipeline).to be_merge_request_event end @@ -184,7 +220,7 @@ describe MergeRequests::RefreshService do end context "when MergeRequestUpdateWorker is retried by an exception" do - it 'does not re-create a duplicate merge request pipeline' do + it 'does not re-create a duplicate detached merge request pipeline' do expect do service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/master') end.to change { @merge_request.merge_request_pipelines.count }.by(1) @@ -200,7 +236,7 @@ describe MergeRequests::RefreshService do stub_feature_flags(ci_merge_request_pipeline: false) end - it 'does not create a merge request pipeline' do + it 'does not create a detached merge request pipeline' do expect { subject } .not_to change { @merge_request.merge_request_pipelines.count } end @@ -217,7 +253,7 @@ describe MergeRequests::RefreshService do } end - it 'does not create a merge request pipeline' do + it 'does not create a detached merge request pipeline' do expect { subject } .not_to change { @merge_request.merge_request_pipelines.count } end @@ -329,14 +365,16 @@ describe MergeRequests::RefreshService do context 'push to fork repo source branch' do let(:refresh_service) { service.new(@fork_project, @user) } - context 'open fork merge request' do - before do - allow(refresh_service).to receive(:execute_hooks) - refresh_service.execute(@oldrev, @newrev, 'refs/heads/master') - reload_mrs - end + def refresh + allow(refresh_service).to receive(:execute_hooks) + refresh_service.execute(@oldrev, @newrev, 'refs/heads/master') + reload_mrs + end + context 'open fork merge request' do it 'executes hooks with update action' do + refresh + expect(refresh_service).to have_received(:execute_hooks) .with(@fork_merge_request, 'update', old_rev: @oldrev) @@ -347,21 +385,30 @@ describe MergeRequests::RefreshService do expect(@build_failed_todo).to be_pending expect(@fork_build_failed_todo).to be_pending end + + it 'outdates opened forked MR suggestions' do + expect_next_instance_of(Suggestions::OutdateService) do |service| + expect(service).to receive(:execute).with(@fork_merge_request).and_call_original + end + + refresh + end end context 'closed fork merge request' do before do @fork_merge_request.close! - allow(refresh_service).to receive(:execute_hooks) - refresh_service.execute(@oldrev, @newrev, 'refs/heads/master') - reload_mrs end it 'do not execute hooks with update action' do + refresh + expect(refresh_service).not_to have_received(:execute_hooks) end it 'updates merge request to closed state' do + refresh + expect(@merge_request.notes).to be_empty expect(@merge_request).to be_open expect(@fork_merge_request.notes).to be_empty diff --git a/spec/services/projects/after_import_service_spec.rb b/spec/services/projects/after_import_service_spec.rb index 4dd6c6dab86..765b4ffae8f 100644 --- a/spec/services/projects/after_import_service_spec.rb +++ b/spec/services/projects/after_import_service_spec.rb @@ -13,7 +13,7 @@ describe Projects::AfterImportService do describe '#execute' do before do allow(Projects::HousekeepingService) - .to receive(:new).with(project).and_return(housekeeping_service) + .to receive(:new).with(project, :gc).and_return(housekeeping_service) allow(housekeeping_service) .to receive(:execute).and_yield diff --git a/spec/services/projects/housekeeping_service_spec.rb b/spec/services/projects/housekeeping_service_spec.rb index 18ecef1c0a1..12ae9105627 100644 --- a/spec/services/projects/housekeeping_service_spec.rb +++ b/spec/services/projects/housekeeping_service_spec.rb @@ -88,6 +88,19 @@ describe Projects::HousekeepingService do expect(project.pushes_since_gc).to eq(1) end end + + it 'runs the task specifically requested' do + housekeeping = described_class.new(project, :gc) + + allow(housekeeping).to receive(:try_obtain_lease).and_return(:gc_uuid) + allow(housekeeping).to receive(:lease_key).and_return(:gc_lease_key) + + expect(GitGarbageCollectWorker).to receive(:perform_async).with(project.id, :gc, :gc_lease_key, :gc_uuid).twice + + 2.times do + housekeeping.execute + end + end end describe '#needed?' do diff --git a/spec/services/projects/participants_service_spec.rb b/spec/services/projects/participants_service_spec.rb index 6040f9100f8..4b6d0c51363 100644 --- a/spec/services/projects/participants_service_spec.rb +++ b/spec/services/projects/participants_service_spec.rb @@ -2,29 +2,56 @@ require 'spec_helper' describe Projects::ParticipantsService do describe '#groups' do + set(:user) { create(:user) } + set(:project) { create(:project, :public) } + let(:service) { described_class.new(project, user) } + + it 'avoids N+1 queries' do + group_1 = create(:group) + group_1.add_owner(user) + + service.groups # Run general application warmup queries + control_count = ActiveRecord::QueryRecorder.new { service.groups }.count + + group_2 = create(:group) + group_2.add_owner(user) + + expect { service.groups }.not_to exceed_query_limit(control_count) + end + + it 'returns correct user counts for groups' do + group_1 = create(:group) + group_1.add_owner(user) + group_1.add_owner(create(:user)) + + group_2 = create(:group) + group_2.add_owner(user) + create(:group_member, :access_request, group: group_2, user: create(:user)) + + expect(service.groups).to contain_exactly( + a_hash_including(name: group_1.full_name, count: 2), + a_hash_including(name: group_2.full_name, count: 1) + ) + end + describe 'avatar_url' do - let(:project) { create(:project, :public) } let(:group) { create(:group, avatar: fixture_file_upload('spec/fixtures/dk.png')) } - let(:user) { create(:user) } - let!(:group_member) { create(:group_member, group: group, user: user) } - it 'should return an url for the avatar' do - participants = described_class.new(project, user) - groups = participants.groups + before do + group.add_owner(user) + end - expect(groups.size).to eq 1 - expect(groups.first[:avatar_url]).to eq("/uploads/-/system/group/avatar/#{group.id}/dk.png") + it 'should return an url for the avatar' do + expect(service.groups.size).to eq 1 + expect(service.groups.first[:avatar_url]).to eq("/uploads/-/system/group/avatar/#{group.id}/dk.png") end it 'should return an url for the avatar with relative url' do stub_config_setting(relative_url_root: '/gitlab') stub_config_setting(url: Settings.send(:build_gitlab_url)) - participants = described_class.new(project, user) - groups = participants.groups - - expect(groups.size).to eq 1 - expect(groups.first[:avatar_url]).to eq("/gitlab/uploads/-/system/group/avatar/#{group.id}/dk.png") + expect(service.groups.size).to eq 1 + expect(service.groups.first[:avatar_url]).to eq("/gitlab/uploads/-/system/group/avatar/#{group.id}/dk.png") end end end diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb index ea33d156c8a..8b0f9c8ade2 100644 --- a/spec/services/quick_actions/interpret_service_spec.rb +++ b/spec/services/quick_actions/interpret_service_spec.rb @@ -15,6 +15,7 @@ describe QuickActions::InterpretService do let(:service) { described_class.new(project, developer) } before do + stub_licensed_features(multiple_issue_assignees: false) project.add_developer(developer) end diff --git a/spec/services/releases/destroy_service_spec.rb b/spec/services/releases/destroy_service_spec.rb index dd5b8708f36..28663ca8853 100644 --- a/spec/services/releases/destroy_service_spec.rb +++ b/spec/services/releases/destroy_service_spec.rb @@ -28,13 +28,11 @@ describe Releases::DestroyService do end end - context 'when tag is not found' do + context 'when tag does not exist in the repository' do let(:tag) { 'v1.1.1' } - it 'returns an error' do - is_expected.to include(status: :error, - message: 'Tag does not exist', - http_status: 404) + it 'removes the orphaned release' do + expect { subject }.to change { project.releases.count }.by(-1) end end diff --git a/spec/services/suggestions/apply_service_spec.rb b/spec/services/suggestions/apply_service_spec.rb index fe85b5c9065..80b5dcac6c7 100644 --- a/spec/services/suggestions/apply_service_spec.rb +++ b/spec/services/suggestions/apply_service_spec.rb @@ -5,6 +5,41 @@ require 'spec_helper' describe Suggestions::ApplyService do include ProjectForksHelper + shared_examples 'successfully creates commit and updates suggestion' do + def apply(suggestion) + result = subject.execute(suggestion) + expect(result[:status]).to eq(:success) + end + + it 'updates the file with the new contents' do + apply(suggestion) + + blob = project.repository.blob_at_branch(merge_request.source_branch, + position.new_path) + + expect(blob.data).to eq(expected_content) + end + + it 'updates suggestion applied and commit_id columns' do + expect { apply(suggestion) } + .to change(suggestion, :applied) + .from(false).to(true) + .and change(suggestion, :commit_id) + .from(nil) + end + + it 'created commit has users email and name' do + apply(suggestion) + + commit = project.repository.commit + + expect(user.commit_email).not_to eq(user.email) + expect(commit.author_email).to eq(user.commit_email) + expect(commit.committer_email).to eq(user.commit_email) + expect(commit.author_name).to eq(user.name) + end + end + let(:project) { create(:project, :repository) } let(:user) { create(:user, :commit_email) } @@ -17,9 +52,8 @@ describe Suggestions::ApplyService do end let(:suggestion) do - create(:suggestion, note: diff_note, - from_content: " raise RuntimeError, \"System commands must be given as an array of strings\"\n", - to_content: " raise RuntimeError, 'Explosion'\n # explosion?\n") + create(:suggestion, :content_from_repo, note: diff_note, + to_content: " raise RuntimeError, 'Explosion'\n # explosion?\n") end subject { described_class.new(user) } @@ -84,39 +118,7 @@ describe Suggestions::ApplyService do project.add_maintainer(user) end - it 'updates the file with the new contents' do - subject.execute(suggestion) - - blob = project.repository.blob_at_branch(merge_request.source_branch, - position.new_path) - - expect(blob.data).to eq(expected_content) - end - - it 'returns success status' do - result = subject.execute(suggestion) - - expect(result[:status]).to eq(:success) - end - - it 'updates suggestion applied and commit_id columns' do - expect { subject.execute(suggestion) } - .to change(suggestion, :applied) - .from(false).to(true) - .and change(suggestion, :commit_id) - .from(nil) - end - - it 'created commit has users email and name' do - subject.execute(suggestion) - - commit = project.repository.commit - - expect(user.commit_email).not_to eq(user.email) - expect(commit.author_email).to eq(user.commit_email) - expect(commit.committer_email).to eq(user.commit_email) - expect(commit.author_name).to eq(user.name) - end + it_behaves_like 'successfully creates commit and updates suggestion' context 'when it fails to apply because the file was changed' do it 'returns error message' do @@ -212,11 +214,13 @@ describe Suggestions::ApplyService do end def apply_suggestion(suggestion) - suggestion.note.reload + suggestion.reload merge_request.reload merge_request.clear_memoized_shas result = subject.execute(suggestion) + expect(result[:status]).to eq(:success) + refresh = MergeRequests::RefreshService.new(project, user) refresh.execute(merge_request.diff_head_sha, suggestion.commit_id, @@ -241,7 +245,7 @@ describe Suggestions::ApplyService do suggestion_2_changes = { old_line: 24, new_line: 31, - from_content: " @cmd_output << stderr.read\n", + from_content: " @cmd_output << stderr.read\n", to_content: "# v2 change\n", path: path } @@ -368,7 +372,18 @@ describe Suggestions::ApplyService do result = subject.execute(suggestion) - expect(result).to eq(message: 'The file was not found', + expect(result).to eq(message: 'Suggestion is not appliable', + status: :error) + end + end + + context 'suggestion is eligible to be outdated' do + it 'returns error message' do + expect(suggestion).to receive(:outdated?) { true } + + result = subject.execute(suggestion) + + expect(result).to eq(message: 'Suggestion is not appliable', status: :error) end end diff --git a/spec/services/suggestions/create_service_spec.rb b/spec/services/suggestions/create_service_spec.rb index 1b4b15b8eaa..ce4990a34a4 100644 --- a/spec/services/suggestions/create_service_spec.rb +++ b/spec/services/suggestions/create_service_spec.rb @@ -40,6 +40,14 @@ describe Suggestions::CreateService do ```thing this is not a suggestion, it's a thing ``` + + ```suggestion:-3+2 + # multi-line suggestion 1 + ``` + + ```suggestion:-5 + # multi-line suggestion 1 + ``` MARKDOWN end @@ -54,7 +62,7 @@ describe Suggestions::CreateService do end it 'does not try to parse suggestions' do - expect(Banzai::SuggestionsParser).not_to receive(:parse) + expect(Gitlab::Diff::SuggestionsParser).not_to receive(:parse) subject.execute end @@ -71,7 +79,7 @@ describe Suggestions::CreateService do it 'does not try to parse suggestions' do allow(note).to receive(:on_text?) { false } - expect(Banzai::SuggestionsParser).not_to receive(:parse) + expect(Gitlab::Diff::SuggestionsParser).not_to receive(:parse) subject.execute end @@ -87,7 +95,9 @@ describe Suggestions::CreateService do end it 'creates no suggestion when diff file is not found' do - expect(note).to receive(:latest_diff_file) { nil } + expect_next_instance_of(DiffNote) do |diff_note| + expect(diff_note).to receive(:latest_diff_file).twice { nil } + end expect { subject.execute }.not_to change(Suggestion, :count) end @@ -101,43 +111,44 @@ describe Suggestions::CreateService do note: markdown) end - context 'single line suggestions' do - it 'persists suggestion records' do - expect { subject.execute } - .to change { note.suggestions.count } - .from(0) - .to(2) - end + let(:expected_suggestions) do + Gitlab::Diff::SuggestionsParser.parse(markdown, + project: note.project, + position: note.position) + end - it 'persists original from_content lines and suggested lines' do - subject.execute + it 'persists suggestion records' do + expect { subject.execute }.to change { note.suggestions.count } + .from(0).to(expected_suggestions.size) + end - suggestions = note.suggestions.order(:relative_order) + it 'persists suggestions data correctly' do + subject.execute - suggestion_1 = suggestions.first - suggestion_2 = suggestions.last + suggestions = note.suggestions.order(:relative_order) - expect(suggestion_1).to have_attributes(from_content: " vars = {\n", - to_content: " foo\n bar\n") + suggestions.zip(expected_suggestions) do |suggestion, expected_suggestion| + expected_data = expected_suggestion.to_hash - expect(suggestion_2).to have_attributes(from_content: " vars = {\n", - to_content: " xpto\n baz\n") + expect(suggestion.from_content).to eq(expected_data[:from_content]) + expect(suggestion.to_content).to eq(expected_data[:to_content]) + expect(suggestion.lines_above).to eq(expected_data[:lines_above]) + expect(suggestion.lines_below).to eq(expected_data[:lines_below]) end + end - context 'outdated position note' do - let!(:outdated_diff) { merge_request.merge_request_diff } - let!(:latest_diff) { merge_request.create_merge_request_diff } - let(:outdated_position) { build_position(diff_refs: outdated_diff.diff_refs) } - let(:position) { build_position(diff_refs: latest_diff.diff_refs) } + context 'outdated position note' do + let!(:outdated_diff) { merge_request.merge_request_diff } + let!(:latest_diff) { merge_request.create_merge_request_diff } + let(:outdated_position) { build_position(diff_refs: outdated_diff.diff_refs) } + let(:position) { build_position(diff_refs: latest_diff.diff_refs) } - it 'uses the correct position when creating the suggestion' do - expect(note.position) - .to receive(:diff_file) - .with(project_with_repo.repository) - .and_call_original + it 'uses the correct position when creating the suggestion' do + expect(Gitlab::Diff::SuggestionsParser).to receive(:parse) + .with(note.note, project: note.project, position: note.position) + .and_call_original - subject.execute - end + subject.execute end end end diff --git a/spec/services/suggestions/outdate_service_spec.rb b/spec/services/suggestions/outdate_service_spec.rb new file mode 100644 index 00000000000..bcc627013d8 --- /dev/null +++ b/spec/services/suggestions/outdate_service_spec.rb @@ -0,0 +1,102 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Suggestions::OutdateService do + describe '#execute' do + let(:merge_request) { create(:merge_request) } + let(:project) { merge_request.target_project } + let(:user) { merge_request.author } + let(:file_path) { 'files/ruby/popen.rb' } + let(:branch_name) { project.default_branch } + let(:diff_file) { suggestion.diff_file } + let(:position) { build_position(file_path, comment_line) } + let(:note) do + create(:diff_note_on_merge_request, noteable: merge_request, + position: position, + project: project) + end + + def build_position(path, line) + Gitlab::Diff::Position.new(old_path: path, + new_path: path, + old_line: nil, + new_line: line, + diff_refs: merge_request.diff_refs) + end + + def commit_changes(file_path, new_content) + params = { + file_path: file_path, + commit_message: "Update File", + file_content: new_content, + start_project: project, + start_branch: project.default_branch, + branch_name: branch_name + } + + Files::UpdateService.new(project, user, params).execute + end + + def update_file_line(diff_file, change_line, content) + new_lines = diff_file.new_blob.data.lines + new_lines[change_line..change_line] = content + result = commit_changes(diff_file.file_path, new_lines.join) + newrev = result[:result] + + expect(result[:status]).to eq(:success) + expect(newrev).to be_present + + # Ensure all memoized data is cleared in order + # to generate the new merge_request_diff. + MergeRequest.find(merge_request.id).reload_diff(user) + + note.reload + end + + before do + project.add_maintainer(user) + end + + subject { described_class.new.execute(merge_request) } + + context 'when there is a change within multi-line suggestion range' do + let(:comment_line) { 9 } + let(:lines_above) { 8 } # suggesting to change lines 1..9 + let(:change_line) { 2 } # line 2 is within the range + let!(:suggestion) do + create(:suggestion, :content_from_repo, note: note, lines_above: lines_above) + end + + it 'updates the outdatable suggestion record' do + update_file_line(diff_file, change_line, "# foo\nbar\n") + + # Make sure note is still active + expect(note.active?).to be(true) + + expect { subject }.to change { suggestion.reload.outdated } + .from(false).to(true) + end + end + + context 'when there is no change within multi-line suggestion range' do + let(:comment_line) { 9 } + let(:lines_above) { 3 } # suggesting to change lines 6..9 + let(:change_line) { 2 } # line 2 is not within the range + let!(:suggestion) do + create(:suggestion, :content_from_repo, note: note, lines_above: lines_above) + end + + subject { described_class.new.execute(merge_request) } + + it 'does not outdates suggestion record' do + update_file_line(diff_file, change_line, "# foo\nbar\n") + + # Make sure note is still active + expect(note.active?).to be(true) + + expect { subject }.not_to change { suggestion.reload.outdated }.from(false) + end + end + end +end diff --git a/spec/services/tags/create_service_spec.rb b/spec/services/tags/create_service_spec.rb index 0cbe57352be..e112cdc8881 100644 --- a/spec/services/tags/create_service_spec.rb +++ b/spec/services/tags/create_service_spec.rb @@ -41,7 +41,7 @@ describe Tags::CreateService do it 'returns an error' do expect(repository).to receive(:add_tag) .with(user, 'v1.1.0', 'master', 'Foo') - .and_raise(Gitlab::Git::PreReceiveError, 'something went wrong') + .and_raise(Gitlab::Git::PreReceiveError, 'GitLab: something went wrong') response = service.execute('v1.1.0', 'master', 'Foo') diff --git a/spec/services/tags/destroy_service_spec.rb b/spec/services/tags/destroy_service_spec.rb index 7c8c1dd0d3a..a541d300595 100644 --- a/spec/services/tags/destroy_service_spec.rb +++ b/spec/services/tags/destroy_service_spec.rb @@ -7,11 +7,27 @@ describe Tags::DestroyService do let(:service) { described_class.new(project, user) } describe '#execute' do + subject { service.execute(tag_name) } + it 'removes the tag' do expect(repository).to receive(:before_remove_tag) expect(service).to receive(:success) service.execute('v1.1.0') end + + context 'when there is an associated release on the tag' do + let(:tag) { repository.tags.first } + let(:tag_name) { tag.name } + + before do + project.add_maintainer(user) + create(:release, tag: tag_name, project: project) + end + + it 'destroys the release' do + expect { subject }.to change { project.releases.count }.by(-1) + end + end end end diff --git a/spec/services/verify_pages_domain_service_spec.rb b/spec/services/verify_pages_domain_service_spec.rb index d974cc0226f..ddf9d2b4917 100644 --- a/spec/services/verify_pages_domain_service_spec.rb +++ b/spec/services/verify_pages_domain_service_spec.rb @@ -9,88 +9,130 @@ describe VerifyPagesDomainService do subject(:service) { described_class.new(domain) } describe '#execute' do - context 'verification code recognition (verified domain)' do - where(:domain_sym, :code_sym) do - :domain | :verification_code - :domain | :keyed_verification_code + where(:domain_sym, :code_sym) do + :domain | :verification_code + :domain | :keyed_verification_code - :verification_domain | :verification_code - :verification_domain | :keyed_verification_code - end - - with_them do - set(:domain) { create(:pages_domain) } + :verification_domain | :verification_code + :verification_domain | :keyed_verification_code + end - let(:domain_name) { domain.send(domain_sym) } - let(:verification_code) { domain.send(code_sym) } + with_them do + let(:domain_name) { domain.send(domain_sym) } + let(:verification_code) { domain.send(code_sym) } + shared_examples 'verifies and enables the domain' do it 'verifies and enables the domain' do - stub_resolver(domain_name => ['something else', verification_code]) - expect(service.execute).to eq(status: :success) + expect(domain).to be_verified expect(domain).to be_enabled end + end - it 'verifies and enables when the code is contained partway through a TXT record' do - stub_resolver(domain_name => "something #{verification_code} else") + shared_examples 'successful enablement and verification' do + context 'when txt record contains verification code' do + before do + stub_resolver(domain_name => ['something else', verification_code]) + end - expect(service.execute).to eq(status: :success) - expect(domain).to be_verified - expect(domain).to be_enabled + include_examples 'verifies and enables the domain' end - it 'does not verify when the code is not present' do - stub_resolver(domain_name => 'something else') - - expect(service.execute).to eq(error_status) + context 'when txt record contains verification code with other text' do + before do + stub_resolver(domain_name => "something #{verification_code} else") + end - expect(domain).not_to be_verified - expect(domain).to be_enabled + include_examples 'verifies and enables the domain' end end - context 'verified domain' do - set(:domain) { create(:pages_domain) } + context 'when domain is disabled(or new)' do + let(:domain) { create(:pages_domain, :disabled) } - it 'unverifies (but does not disable) when the right code is not present' do - stub_resolver(domain.domain => 'something else') + include_examples 'successful enablement and verification' - expect(service.execute).to eq(error_status) - expect(domain).not_to be_verified - expect(domain).to be_enabled + shared_examples 'unverifies and disables domain' do + it 'unverifies and disables domain' do + expect(service.execute).to eq(error_status) + + expect(domain).not_to be_verified + expect(domain).not_to be_enabled + end end - it 'unverifies (but does not disable) when no records are present' do - stub_resolver + context 'when txt record does not contain verification code' do + before do + stub_resolver(domain_name => 'something else') + end - expect(service.execute).to eq(error_status) - expect(domain).not_to be_verified - expect(domain).to be_enabled + include_examples 'unverifies and disables domain' + end + + context 'when no txt records are present' do + before do + stub_resolver + end + + include_examples 'unverifies and disables domain' end end - context 'expired domain' do - set(:domain) { create(:pages_domain, :expired) } + context 'when domain is verified' do + let(:domain) { create(:pages_domain) } - it 'verifies and enables when the right code is present' do - stub_resolver(domain.domain => domain.keyed_verification_code) + include_examples 'successful enablement and verification' - expect(service.execute).to eq(status: :success) + context 'when txt record does not contain verification code' do + before do + stub_resolver(domain_name => 'something else') + end - expect(domain).to be_verified - expect(domain).to be_enabled + it 'unverifies but does not disable domain' do + expect(service.execute).to eq(error_status) + expect(domain).not_to be_verified + expect(domain).to be_enabled + end end - it 'disables when the right code is not present' do - error_status[:message] += '. It is now disabled.' + context 'when no txt records are present' do + before do + stub_resolver + end - stub_resolver + it 'unverifies but does not disable domain' do + expect(service.execute).to eq(error_status) + expect(domain).not_to be_verified + expect(domain).to be_enabled + end + end + end - expect(service.execute).to eq(error_status) + context 'when domain is expired' do + let(:domain) { create(:pages_domain, :expired) } - expect(domain).not_to be_verified - expect(domain).not_to be_enabled + context 'when the right code is present' do + before do + stub_resolver(domain_name => domain.keyed_verification_code) + end + + include_examples 'verifies and enables the domain' + end + + context 'when the right code is not present' do + before do + stub_resolver + end + + it 'disables domain' do + error_status[:message] += '. It is now disabled.' + + expect(service.execute).to eq(error_status) + + expect(domain).not_to be_verified + expect(domain).not_to be_enabled + end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index e8d7b18bf04..60db3e1bc46 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -66,6 +66,7 @@ RSpec.configure do |config| metadata[:type] = match[1].singularize.to_sym if match end + config.include LicenseHelpers config.include ActiveJob::TestHelper config.include ActiveSupport::Testing::TimeHelpers config.include CycleAnalyticsHelpers @@ -96,6 +97,7 @@ RSpec.configure do |config| config.include MigrationsHelpers, :migration config.include RedisHelpers config.include Rails.application.routes.url_helpers, type: :routing + config.include PolicyHelpers, type: :policy if ENV['CI'] # This includes the first try, i.e. tests will be run 4 times before failing. diff --git a/spec/support/api/milestones_shared_examples.rb b/spec/support/api/milestones_shared_examples.rb index 5f709831ce1..63b719be03e 100644 --- a/spec/support/api/milestones_shared_examples.rb +++ b/spec/support/api/milestones_shared_examples.rb @@ -72,6 +72,15 @@ shared_examples_for 'group and project milestones' do |route_definition| expect(json_response.first['id']).to eq closed_milestone.id end + it 'returns a milestone by title' do + get api(route, user), params: { title: 'version2' } + + expect(response).to have_gitlab_http_status(200) + expect(json_response.size).to eq(1) + expect(json_response.first['title']).to eq milestone.title + expect(json_response.first['id']).to eq milestone.id + end + it 'returns a milestone by searching for title' do get api(route, user), params: { search: 'version2' } diff --git a/spec/support/features/issuable_quick_actions_shared_examples.rb b/spec/support/features/issuable_quick_actions_shared_examples.rb deleted file mode 100644 index 2a883ce1074..00000000000 --- a/spec/support/features/issuable_quick_actions_shared_examples.rb +++ /dev/null @@ -1,389 +0,0 @@ -# Specifications for behavior common to all objects with executable attributes. -# It takes a `issuable_type`, and expect an `issuable`. - -shared_examples 'issuable record that supports quick actions in its description and notes' do |issuable_type| - include Spec::Support::Helpers::Features::NotesHelpers - - let(:maintainer) { create(:user) } - let(:project) do - case issuable_type - when :merge_request - create(:project, :public, :repository) - when :issue - create(:project, :public) - end - end - let!(:milestone) { create(:milestone, project: project, title: 'ASAP') } - let!(:label_bug) { create(:label, project: project, title: 'bug') } - let!(:label_feature) { create(:label, project: project, title: 'feature') } - let(:new_url_opts) { {} } - - before do - project.add_maintainer(maintainer) - - gitlab_sign_in(maintainer) - end - - after do - # Ensure all outstanding Ajax requests are complete to avoid database deadlocks - wait_for_requests - end - - describe "new #{issuable_type}", :js do - context 'with commands in the description' do - it "creates the #{issuable_type} and interpret commands accordingly" do - case issuable_type - when :merge_request - visit public_send("namespace_project_new_merge_request_path", project.namespace, project, new_url_opts) - when :issue - visit public_send("new_namespace_project_issue_path", project.namespace, project, new_url_opts) - end - fill_in "#{issuable_type}_title", with: 'bug 345' - fill_in "#{issuable_type}_description", with: "bug description\n/label ~bug\n/milestone %\"ASAP\"" - click_button "Submit #{issuable_type}".humanize - - issuable = project.public_send(issuable_type.to_s.pluralize).first - - expect(issuable.description).to eq "bug description" - expect(issuable.labels).to eq [label_bug] - expect(issuable.milestone).to eq milestone - expect(page).to have_content 'bug 345' - expect(page).to have_content 'bug description' - end - end - end - - describe "note on #{issuable_type}", :js do - before do - visit public_send("project_#{issuable_type}_path", project, issuable) - end - - context 'with a note containing commands' do - it 'creates a note without the commands and interpret the commands accordingly' do - assignee = create(:user, username: 'bob') - add_note("Awesome!\n\n/assign @bob\n\n/label ~bug\n\n/milestone %\"ASAP\"") - - expect(page).to have_content 'Awesome!' - expect(page).not_to have_content '/assign @bob' - expect(page).not_to have_content '/label ~bug' - expect(page).not_to have_content '/milestone %"ASAP"' - - wait_for_requests - issuable.reload - note = issuable.notes.user.first - - expect(note.note).to eq "Awesome!" - expect(issuable.assignees).to eq [assignee] - expect(issuable.labels).to eq [label_bug] - expect(issuable.milestone).to eq milestone - end - - it 'removes the quick action from note and explains it in the preview' do - preview_note("Awesome!\n\n/close") - - expect(page).to have_content 'Awesome!' - expect(page).not_to have_content '/close' - issuable_name = issuable.is_a?(Issue) ? 'issue' : 'merge request' - expect(page).to have_content "Closes this #{issuable_name}." - end - end - - context 'with a note containing only commands' do - it 'does not create a note but interpret the commands accordingly' do - assignee = create(:user, username: 'bob') - add_note("/assign @bob\n\n/label ~bug\n\n/milestone %\"ASAP\"") - - expect(page).not_to have_content '/assign @bob' - expect(page).not_to have_content '/label ~bug' - expect(page).not_to have_content '/milestone %"ASAP"' - expect(page).to have_content 'Commands applied' - - issuable.reload - - expect(issuable.notes.user).to be_empty - expect(issuable.assignees).to eq [assignee] - expect(issuable.labels).to eq [label_bug] - expect(issuable.milestone).to eq milestone - end - end - - context "with a note closing the #{issuable_type}" do - before do - expect(issuable).to be_open - end - - context "when current user can close #{issuable_type}" do - it "closes the #{issuable_type}" do - add_note("/close") - - expect(page).not_to have_content '/close' - expect(page).to have_content 'Commands applied' - - expect(issuable.reload).to be_closed - end - end - - context "when current user cannot close #{issuable_type}" do - before do - guest = create(:user) - project.add_guest(guest) - - gitlab_sign_out - gitlab_sign_in(guest) - visit public_send("project_#{issuable_type}_path", project, issuable) - end - - it "does not close the #{issuable_type}" do - add_note("/close") - - expect(page).not_to have_content 'Commands applied' - - expect(issuable).to be_open - end - end - end - - context "with a note reopening the #{issuable_type}" do - before do - issuable.close - expect(issuable).to be_closed - end - - context "when current user can reopen #{issuable_type}" do - it "reopens the #{issuable_type}" do - add_note("/reopen") - - expect(page).not_to have_content '/reopen' - expect(page).to have_content 'Commands applied' - - expect(issuable.reload).to be_open - end - end - - context "when current user cannot reopen #{issuable_type}" do - before do - guest = create(:user) - project.add_guest(guest) - - gitlab_sign_out - gitlab_sign_in(guest) - visit public_send("project_#{issuable_type}_path", project, issuable) - end - - it "does not reopen the #{issuable_type}" do - add_note("/reopen") - - expect(page).not_to have_content 'Commands applied' - - expect(issuable).to be_closed - end - end - end - - context "with a note changing the #{issuable_type}'s title" do - context "when current user can change title of #{issuable_type}" do - it "reopens the #{issuable_type}" do - add_note("/title Awesome new title") - - expect(page).not_to have_content '/title' - expect(page).to have_content 'Commands applied' - - expect(issuable.reload.title).to eq 'Awesome new title' - end - end - - context "when current user cannot change title of #{issuable_type}" do - before do - guest = create(:user) - project.add_guest(guest) - - gitlab_sign_out - gitlab_sign_in(guest) - visit public_send("project_#{issuable_type}_path", project, issuable) - end - - it "does not change the #{issuable_type} title" do - add_note("/title Awesome new title") - - expect(page).not_to have_content 'Commands applied' - - expect(issuable.reload.title).not_to eq 'Awesome new title' - end - end - end - - context "with a note marking the #{issuable_type} as todo" do - it "creates a new todo for the #{issuable_type}" do - add_note("/todo") - - expect(page).not_to have_content '/todo' - expect(page).to have_content 'Commands applied' - - todos = TodosFinder.new(maintainer).execute - todo = todos.first - - expect(todos.size).to eq 1 - expect(todo).to be_pending - expect(todo.target).to eq issuable - expect(todo.author).to eq maintainer - expect(todo.user).to eq maintainer - end - end - - context "with a note marking the #{issuable_type} as done" do - before do - TodoService.new.mark_todo(issuable, maintainer) - end - - it "creates a new todo for the #{issuable_type}" do - todos = TodosFinder.new(maintainer).execute - todo = todos.first - - expect(todos.size).to eq 1 - expect(todos.first).to be_pending - expect(todo.target).to eq issuable - expect(todo.author).to eq maintainer - expect(todo.user).to eq maintainer - - add_note("/done") - - expect(page).not_to have_content '/done' - expect(page).to have_content 'Commands applied' - - expect(todo.reload).to be_done - end - end - - context "with a note subscribing to the #{issuable_type}" do - it "creates a new todo for the #{issuable_type}" do - expect(issuable.subscribed?(maintainer, project)).to be_falsy - - add_note("/subscribe") - - expect(page).not_to have_content '/subscribe' - expect(page).to have_content 'Commands applied' - - expect(issuable.subscribed?(maintainer, project)).to be_truthy - end - end - - context "with a note unsubscribing to the #{issuable_type} as done" do - before do - issuable.subscribe(maintainer, project) - end - - it "creates a new todo for the #{issuable_type}" do - expect(issuable.subscribed?(maintainer, project)).to be_truthy - - add_note("/unsubscribe") - - expect(page).not_to have_content '/unsubscribe' - expect(page).to have_content 'Commands applied' - - expect(issuable.subscribed?(maintainer, project)).to be_falsy - end - end - - context "with a note assigning the #{issuable_type} to the current user" do - it "assigns the #{issuable_type} to the current user" do - add_note("/assign me") - - expect(page).not_to have_content '/assign me' - expect(page).to have_content 'Commands applied' - - expect(issuable.reload.assignees).to eq [maintainer] - end - end - - context "with a note locking the #{issuable_type} discussion" do - before do - issuable.update(discussion_locked: false) - expect(issuable).not_to be_discussion_locked - end - - context "when current user can lock #{issuable_type} discussion" do - it "locks the #{issuable_type} discussion" do - add_note("/lock") - - expect(page).not_to have_content '/lock' - expect(page).to have_content 'Commands applied' - - expect(issuable.reload).to be_discussion_locked - end - end - - context "when current user cannot lock #{issuable_type}" do - before do - guest = create(:user) - project.add_guest(guest) - - gitlab_sign_out - sign_in(guest) - visit public_send("project_#{issuable_type}_path", project, issuable) - end - - it "does not lock the #{issuable_type} discussion" do - add_note("/lock") - - expect(page).not_to have_content 'Commands applied' - - expect(issuable).not_to be_discussion_locked - end - end - end - - context "with a note unlocking the #{issuable_type} discussion" do - before do - issuable.update(discussion_locked: true) - expect(issuable).to be_discussion_locked - end - - context "when current user can unlock #{issuable_type} discussion" do - it "unlocks the #{issuable_type} discussion" do - add_note("/unlock") - - expect(page).not_to have_content '/unlock' - expect(page).to have_content 'Commands applied' - - expect(issuable.reload).not_to be_discussion_locked - end - end - - context "when current user cannot unlock #{issuable_type}" do - before do - guest = create(:user) - project.add_guest(guest) - - gitlab_sign_out - sign_in(guest) - visit public_send("project_#{issuable_type}_path", project, issuable) - end - - it "does not unlock the #{issuable_type} discussion" do - add_note("/unlock") - - expect(page).not_to have_content 'Commands applied' - - expect(issuable).to be_discussion_locked - end - end - end - end - - describe "preview of note on #{issuable_type}", :js do - it 'removes quick actions from note and explains them' do - create(:user, username: 'bob') - - visit public_send("project_#{issuable_type}_path", project, issuable) - - page.within('.js-main-target-form') do - fill_in 'note[note]', with: "Awesome!\n/assign @bob " - click_on 'Preview' - - expect(page).to have_content 'Awesome!' - expect(page).not_to have_content '/assign @bob' - expect(page).to have_content 'Assigns @bob.' - end - end - end -end diff --git a/spec/support/helpers/cycle_analytics_helpers.rb b/spec/support/helpers/cycle_analytics_helpers.rb index ecefdc23811..33648292037 100644 --- a/spec/support/helpers/cycle_analytics_helpers.rb +++ b/spec/support/helpers/cycle_analytics_helpers.rb @@ -23,7 +23,7 @@ module CycleAnalyticsHelpers return if skip_push_handler - GitPushService.new(project, + Git::BranchPushService.new(project, user, oldrev: oldrev, newrev: commit_shas.last, diff --git a/spec/support/helpers/javascript_fixtures_helpers.rb b/spec/support/helpers/javascript_fixtures_helpers.rb index cceb179d53e..9cae8f934db 100644 --- a/spec/support/helpers/javascript_fixtures_helpers.rb +++ b/spec/support/helpers/javascript_fixtures_helpers.rb @@ -24,7 +24,7 @@ module JavaScriptFixturesHelpers # def clean_frontend_fixtures(directory_name) full_directory_name = File.expand_path(directory_name, fixture_root_path) - Dir[File.expand_path('*.html.raw', full_directory_name)].each do |file_name| + Dir[File.expand_path('*.html', full_directory_name)].each do |file_name| FileUtils.rm(file_name) end end diff --git a/spec/support/helpers/license_helper.rb b/spec/support/helpers/license_helper.rb new file mode 100644 index 00000000000..4aaad55a8ef --- /dev/null +++ b/spec/support/helpers/license_helper.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +# Placeholder module for EE implementation needed for CE specs to be run in EE codebase +module LicenseHelpers + def stub_licensed_features(features) + # do nothing + end +end diff --git a/spec/support/helpers/policy_helpers.rb b/spec/support/helpers/policy_helpers.rb new file mode 100644 index 00000000000..3d780eb5fb1 --- /dev/null +++ b/spec/support/helpers/policy_helpers.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module PolicyHelpers + def expect_allowed(*permissions) + permissions.each { |p| is_expected.to be_allowed(p) } + end + + def expect_disallowed(*permissions) + permissions.each { |p| is_expected.not_to be_allowed(p) } + end +end diff --git a/spec/support/helpers/stub_worker.rb b/spec/support/helpers/stub_worker.rb new file mode 100644 index 00000000000..58b7ee93dff --- /dev/null +++ b/spec/support/helpers/stub_worker.rb @@ -0,0 +1,9 @@ +# Inspired by https://github.com/ljkbennett/stub_env/blob/master/lib/stub_env/helpers.rb +module StubWorker + def stub_worker(queue:) + Class.new do + include Sidekiq::Worker + sidekiq_options queue: queue + end + end +end diff --git a/spec/support/matchers/issuable_matchers.rb b/spec/support/matchers/issuable_matchers.rb index f5d9a97051a..62f510b0fbd 100644 --- a/spec/support/matchers/issuable_matchers.rb +++ b/spec/support/matchers/issuable_matchers.rb @@ -1,4 +1,4 @@ -RSpec::Matchers.define :have_header_with_correct_id_and_link do |level, text, id, parent = ".wiki"| +RSpec::Matchers.define :have_header_with_correct_id_and_link do |level, text, id, parent = ".md"| match do |actual| node = find("#{parent} h#{level} a#user-content-#{id}") diff --git a/spec/support/shared_context/policies/project_policy_shared_context.rb b/spec/support/shared_context/policies/project_policy_shared_context.rb new file mode 100644 index 00000000000..8bcd26ec0cd --- /dev/null +++ b/spec/support/shared_context/policies/project_policy_shared_context.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true + +RSpec.shared_context 'ProjectPolicy context' do + set(:guest) { create(:user) } + set(:reporter) { create(:user) } + set(:developer) { create(:user) } + set(:maintainer) { create(:user) } + set(:owner) { create(:user) } + set(:admin) { create(:admin) } + let(:project) { create(:project, :public, namespace: owner.namespace) } + + let(:base_guest_permissions) do + %i[ + read_project read_board read_list read_wiki read_issue + read_project_for_iids read_issue_iid read_label + read_milestone read_project_snippet read_project_member read_note + create_project create_issue create_note upload_file create_merge_request_in + award_emoji read_release + ] + end + + let(:base_reporter_permissions) do + %i[ + download_code fork_project create_project_snippet update_issue + admin_issue admin_label admin_list read_commit_status read_build + read_container_image read_pipeline read_environment read_deployment + read_merge_request download_wiki_code read_sentry_issue + ] + end + + let(:team_member_reporter_permissions) do + %i[build_download_code build_read_container_image] + end + + let(:developer_permissions) do + %i[ + admin_milestone admin_merge_request update_merge_request create_commit_status + update_commit_status create_build update_build create_pipeline + update_pipeline create_merge_request_from create_wiki push_code + resolve_note create_container_image update_container_image + create_environment create_deployment create_release update_release + ] + end + + let(:base_maintainer_permissions) do + %i[ + push_to_delete_protected_branch update_project_snippet update_environment + update_deployment admin_project_snippet admin_project_member admin_note admin_wiki admin_project + admin_commit_status admin_build admin_container_image + admin_pipeline admin_environment admin_deployment destroy_release add_cluster + daily_statistics + ] + end + + let(:public_permissions) do + %i[ + download_code fork_project read_commit_status read_pipeline + read_container_image build_download_code build_read_container_image + download_wiki_code read_release + ] + end + + let(:base_owner_permissions) do + %i[ + change_namespace change_visibility_level rename_project remove_project + archive_project remove_fork_project destroy_merge_request destroy_issue + set_issue_iid set_issue_created_at set_note_created_at + ] + end + + # Used in EE specs + let(:additional_guest_permissions) { [] } + let(:additional_reporter_permissions) { [] } + let(:additional_maintainer_permissions) { [] } + let(:additional_owner_permissions) { [] } + + let(:guest_permissions) { base_guest_permissions + additional_guest_permissions } + let(:reporter_permissions) { base_reporter_permissions + additional_reporter_permissions } + let(:maintainer_permissions) { base_maintainer_permissions + additional_maintainer_permissions } + let(:owner_permissions) { base_owner_permissions + additional_owner_permissions } + + before do + project.add_guest(guest) + project.add_maintainer(maintainer) + project.add_developer(developer) + project.add_reporter(reporter) + end +end diff --git a/spec/support/shared_contexts/policies/group_policy_shared_context.rb b/spec/support/shared_contexts/policies/group_policy_shared_context.rb new file mode 100644 index 00000000000..b4808ac0068 --- /dev/null +++ b/spec/support/shared_contexts/policies/group_policy_shared_context.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +RSpec.shared_context 'GroupPolicy context' do + let(:guest) { create(:user) } + let(:reporter) { create(:user) } + let(:developer) { create(:user) } + let(:maintainer) { create(:user) } + let(:owner) { create(:user) } + let(:admin) { create(:admin) } + let(:group) { create(:group, :private) } + + let(:guest_permissions) do + %i[ + read_label read_group upload_file read_namespace read_group_activity + read_group_issues read_group_boards read_group_labels read_group_milestones + read_group_merge_requests + ] + end + let(:reporter_permissions) { [:admin_label] } + let(:developer_permissions) { [:admin_milestone] } + let(:maintainer_permissions) do + %i[ + create_projects + read_cluster create_cluster update_cluster admin_cluster add_cluster + ] + end + let(:owner_permissions) do + [ + :admin_group, + :admin_namespace, + :admin_group_member, + :change_visibility_level, + :set_note_created_at, + (Gitlab::Database.postgresql? ? :create_subgroup : nil) + ].compact + end + + before do + group.add_guest(guest) + group.add_reporter(reporter) + group.add_developer(developer) + group.add_maintainer(maintainer) + group.add_owner(owner) + end + + subject { described_class.new(current_user, group) } +end diff --git a/spec/support/shared_examples/policies/project_policy_shared_examples.rb b/spec/support/shared_examples/policies/project_policy_shared_examples.rb new file mode 100644 index 00000000000..7a71e2ee370 --- /dev/null +++ b/spec/support/shared_examples/policies/project_policy_shared_examples.rb @@ -0,0 +1,231 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'archived project policies' do + let(:feature_write_abilities) do + described_class::READONLY_FEATURES_WHEN_ARCHIVED.flat_map do |feature| + described_class.create_update_admin_destroy(feature) + end + additional_reporter_permissions + additional_maintainer_permissions + end + + let(:other_write_abilities) do + %i[ + create_merge_request_in + create_merge_request_from + push_to_delete_protected_branch + push_code + request_access + upload_file + resolve_note + award_emoji + ] + end + + context 'when the project is archived' do + before do + project.archived = true + end + + it 'disables write actions on all relevant project features' do + expect_disallowed(*feature_write_abilities) + end + + it 'disables some other important write actions' do + expect_disallowed(*other_write_abilities) + end + + it 'does not disable other abilities' do + expect_allowed(*(regular_abilities - feature_write_abilities - other_write_abilities)) + end + end +end + +RSpec.shared_examples 'project policies as anonymous' do + context 'abilities for public projects' do + context 'when a project has pending invites' do + let(:group) { create(:group, :public) } + let(:project) { create(:project, :public, namespace: group) } + let(:user_permissions) { [:create_merge_request_in, :create_project, :create_issue, :create_note, :upload_file, :award_emoji] } + let(:anonymous_permissions) { guest_permissions - user_permissions } + + subject { described_class.new(nil, project) } + + before do + create(:group_member, :invited, group: group) + end + + it 'does not grant owner access' do + expect_allowed(*anonymous_permissions) + expect_disallowed(*user_permissions) + end + + it_behaves_like 'archived project policies' do + let(:regular_abilities) { anonymous_permissions } + end + end + end + + context 'abilities for non-public projects' do + let(:project) { create(:project, namespace: owner.namespace) } + + subject { described_class.new(nil, project) } + + it { is_expected.to be_banned } + end +end + +RSpec.shared_examples 'project policies as guest' do + subject { described_class.new(guest, project) } + + context 'abilities for non-public projects' do + let(:project) { create(:project, namespace: owner.namespace) } + let(:reporter_public_build_permissions) do + reporter_permissions - [:read_build, :read_pipeline] + end + + it do + expect_allowed(*guest_permissions) + expect_disallowed(*reporter_public_build_permissions) + expect_disallowed(*team_member_reporter_permissions) + expect_disallowed(*developer_permissions) + expect_disallowed(*maintainer_permissions) + expect_disallowed(*owner_permissions) + end + + it_behaves_like 'archived project policies' do + let(:regular_abilities) { guest_permissions } + end + + context 'public builds enabled' do + it do + expect_allowed(*guest_permissions) + expect_allowed(:read_build, :read_pipeline) + end + end + + context 'when public builds disabled' do + before do + project.update(public_builds: false) + end + + it do + expect_allowed(*guest_permissions) + expect_disallowed(:read_build, :read_pipeline) + end + end + + context 'when builds are disabled' do + before do + project.project_feature.update(builds_access_level: ProjectFeature::DISABLED) + end + + it do + expect_disallowed(:read_build) + expect_allowed(:read_pipeline) + end + end + end +end + +RSpec.shared_examples 'project policies as reporter' do + context 'abilities for non-public projects' do + let(:project) { create(:project, namespace: owner.namespace) } + + subject { described_class.new(reporter, project) } + + it do + expect_allowed(*guest_permissions) + expect_allowed(*reporter_permissions) + expect_allowed(*team_member_reporter_permissions) + expect_disallowed(*developer_permissions) + expect_disallowed(*maintainer_permissions) + expect_disallowed(*owner_permissions) + end + + it_behaves_like 'archived project policies' do + let(:regular_abilities) { reporter_permissions } + end + end +end + +RSpec.shared_examples 'project policies as developer' do + context 'abilities for non-public projects' do + let(:project) { create(:project, namespace: owner.namespace) } + subject { described_class.new(developer, project) } + + it do + expect_allowed(*guest_permissions) + expect_allowed(*reporter_permissions) + expect_allowed(*team_member_reporter_permissions) + expect_allowed(*developer_permissions) + expect_disallowed(*maintainer_permissions) + expect_disallowed(*owner_permissions) + end + + it_behaves_like 'archived project policies' do + let(:regular_abilities) { developer_permissions } + end + end +end + +RSpec.shared_examples 'project policies as maintainer' do + context 'abilities for non-public projects' do + let(:project) { create(:project, namespace: owner.namespace) } + + subject { described_class.new(maintainer, project) } + + it do + expect_allowed(*guest_permissions) + expect_allowed(*reporter_permissions) + expect_allowed(*team_member_reporter_permissions) + expect_allowed(*developer_permissions) + expect_allowed(*maintainer_permissions) + expect_disallowed(*owner_permissions) + end + + it_behaves_like 'archived project policies' do + let(:regular_abilities) { maintainer_permissions } + end + end +end + +RSpec.shared_examples 'project policies as owner' do + context 'abilities for non-public projects' do + let(:project) { create(:project, namespace: owner.namespace) } + + subject { described_class.new(owner, project) } + + it do + expect_allowed(*guest_permissions) + expect_allowed(*reporter_permissions) + expect_allowed(*team_member_reporter_permissions) + expect_allowed(*developer_permissions) + expect_allowed(*maintainer_permissions) + expect_allowed(*owner_permissions) + end + + it_behaves_like 'archived project policies' do + let(:regular_abilities) { owner_permissions } + end + end +end + +RSpec.shared_examples 'project policies as admin' do + context 'abilities for non-public projects' do + let(:project) { create(:project, namespace: owner.namespace) } + + subject { described_class.new(admin, project) } + + it do + expect_allowed(*guest_permissions) + expect_allowed(*reporter_permissions) + expect_disallowed(*team_member_reporter_permissions) + expect_allowed(*developer_permissions) + expect_allowed(*maintainer_permissions) + expect_allowed(*owner_permissions) + end + + it_behaves_like 'archived project policies' do + let(:regular_abilities) { owner_permissions } + end + end +end diff --git a/spec/support/shared_examples/quick_actions/commit/tag_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/commit/tag_quick_action_shared_examples.rb new file mode 100644 index 00000000000..4604d867507 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/commit/tag_quick_action_shared_examples.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +shared_examples 'tag quick action' do +end diff --git a/spec/support/shared_examples/quick_actions/issuable/assign_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/assign_quick_action_shared_examples.rb new file mode 100644 index 00000000000..d97da6be192 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/assign_quick_action_shared_examples.rb @@ -0,0 +1,110 @@ +# frozen_string_literal: true + +shared_examples 'assign quick action' do |issuable_type| + before do + project.add_maintainer(maintainer) + gitlab_sign_in(maintainer) + end + + context "new #{issuable_type}", :js do + before do + case issuable_type + when :merge_request + visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) + wait_for_all_requests + when :issue + visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) + wait_for_all_requests + end + end + + it "creates the #{issuable_type} and interprets assign quick action accordingly" do + assignee = create(:user, username: 'bob') + + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/assign @bob" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).first + + expect(issuable.description).to eq 'bug description' + expect(issuable.assignees).to eq [assignee] + expect(page).to have_content 'bug 345' + expect(page).to have_content 'bug description' + end + + it "creates the #{issuable_type} and interprets assign quick action accordingly" do + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/assign me" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).first + + expect(issuable.description).to eq 'bug description' + expect(issuable.assignees).to eq [maintainer] + expect(page).to have_content 'bug 345' + expect(page).to have_content 'bug description' + end + end + + context "post note to existing #{issuable_type}" do + before do + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it 'creates the note and interprets the assign quick action accordingly' do + assignee = create(:user, username: 'bob') + add_note("Awesome!\n\n/assign @bob") + + expect(page).to have_content 'Awesome!' + expect(page).not_to have_content '/assign @bob' + + wait_for_requests + issuable.reload + note = issuable.notes.user.first + + expect(note.note).to eq 'Awesome!' + expect(issuable.assignees).to eq [assignee] + end + + it "assigns the #{issuable_type} to the current user" do + add_note("/assign me") + + expect(page).not_to have_content '/assign me' + expect(page).to have_content 'Commands applied' + + expect(issuable.reload.assignees).to eq [maintainer] + end + end + + context "preview of note on #{issuable_type}", :js do + it 'explains assign quick action to bob' do + create(:user, username: 'bob') + + visit public_send("project_#{issuable_type}_path", project, issuable) + + page.within('.js-main-target-form') do + fill_in 'note[note]', with: "Awesome!\n/assign @bob " + click_on 'Preview' + + expect(page).not_to have_content '/assign @bob' + expect(page).to have_content 'Awesome!' + expect(page).to have_content 'Assigns @bob.' + end + end + + it 'explains assign quick action to me' do + visit public_send("project_#{issuable_type}_path", project, issuable) + + page.within('.js-main-target-form') do + fill_in 'note[note]', with: "Awesome!\n/assign me" + click_on 'Preview' + + expect(page).not_to have_content '/assign me' + expect(page).to have_content 'Awesome!' + expect(page).to have_content "Assigns @#{maintainer.username}." + end + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issuable/award_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/award_quick_action_shared_examples.rb new file mode 100644 index 00000000000..74cbfa3f4b4 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/award_quick_action_shared_examples.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +shared_examples 'award quick action' do |issuable_type| + before do + project.add_maintainer(maintainer) + gitlab_sign_in(maintainer) + end + + context "new #{issuable_type}", :js do + before do + case issuable_type + when :merge_request + visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) + wait_for_all_requests + when :issue + visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) + wait_for_all_requests + end + end + + it "creates the #{issuable_type} and interprets award quick action accordingly" do + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/award :100:" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).first + + expect(issuable.description).to eq 'bug description' + expect(issuable).to be_opened + expect(page).to have_content 'bug 345' + expect(page).to have_content 'bug description' + expect(issuable.award_emoji).to eq [] + end + end + + context "post note to existing #{issuable_type}" do + before do + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + expect(issuable.award_emoji).to eq [] + end + + it 'creates the note and interprets the award quick action accordingly' do + add_note("/award :100:") + + wait_for_requests + expect(page).not_to have_content '/award' + expect(page).to have_content 'Commands applied' + expect(issuable.reload.award_emoji.last.name).to eq('100') + end + end + + context "preview of note on #{issuable_type}", :js do + it 'explains label quick action' do + visit public_send("project_#{issuable_type}_path", project, issuable) + + preview_note('/award :100:') + + expect(page).not_to have_content '/award' + expect(page).to have_selector "gl-emoji[data-name='100']" + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb new file mode 100644 index 00000000000..e0d0b790a0e --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +shared_examples 'close quick action' do |issuable_type| + before do + project.add_maintainer(maintainer) + gitlab_sign_in(maintainer) + end + + context "new #{issuable_type}", :js do + before do + case issuable_type + when :merge_request + visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) + wait_for_all_requests + when :issue + visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) + wait_for_all_requests + end + end + + it "creates the #{issuable_type} and interprets close quick action accordingly" do + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/close" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).first + + expect(issuable.description).to eq 'bug description' + expect(issuable).to be_opened + expect(page).to have_content 'bug 345' + expect(page).to have_content 'bug description' + end + end + + context "post note to existing #{issuable_type}" do + before do + expect(issuable).to be_opened + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it 'creates the note and interprets the close quick action accordingly' do + add_note("this is done, close\n\n/close") + + wait_for_requests + expect(page).not_to have_content '/close' + expect(page).to have_content 'this is done, close' + + issuable.reload + note = issuable.notes.user.first + + expect(note.note).to eq 'this is done, close' + expect(issuable).to be_closed + end + + context "when current user cannot close #{issuable_type}" do + before do + guest = create(:user) + project.add_guest(guest) + + gitlab_sign_out + gitlab_sign_in(guest) + visit public_send("project_#{issuable_type}_path", project, issuable) + end + + it "does not close the #{issuable_type}" do + add_note('/close') + + expect(page).not_to have_content 'Commands applied' + expect(issuable).to be_open + end + end + end + + context "preview of note on #{issuable_type}", :js do + it 'explains close quick action' do + visit public_send("project_#{issuable_type}_path", project, issuable) + + page.within('.js-main-target-form') do + fill_in 'note[note]', with: "this is done, close\n/close" + click_on 'Preview' + + expect(page).not_to have_content '/close' + expect(page).to have_content 'this is done, close' + expect(page).to have_content "Closes this #{issuable_type.to_s.humanize.downcase}." + end + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issuable/copy_metadata_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/copy_metadata_quick_action_shared_examples.rb new file mode 100644 index 00000000000..1e1e3c7bc95 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/copy_metadata_quick_action_shared_examples.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true + +shared_examples 'copy_metadata quick action' do |issuable_type| + before do + project.add_maintainer(maintainer) + gitlab_sign_in(maintainer) + end + + context "new #{issuable_type}", :js do + before do + case issuable_type + when :merge_request + visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) + wait_for_all_requests + when :issue + visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) + wait_for_all_requests + end + end + + it "creates the #{issuable_type} and interprets copy_metadata quick action accordingly" do + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/copy_metadata #{source_issuable.to_reference(project)}" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).last + + expect(page).to have_content 'bug 345' + expect(page).to have_content 'bug description' + issuable.reload + expect(issuable.description).to eq 'bug description' + expect(issuable.milestone).to eq milestone + expect(issuable.labels).to match_array([label_bug, label_feature]) + end + end + + context "post note to existing #{issuable_type}" do + before do + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it 'creates the note and interprets copy_metadata quick action accordingly' do + add_note("/copy_metadata #{source_issuable.to_reference(project)}") + + wait_for_requests + expect(page).not_to have_content '/copy_metadata' + expect(page).to have_content 'Commands applied' + issuable.reload + expect(issuable.milestone).to eq milestone + expect(issuable.labels).to match_array([label_bug, label_feature]) + end + + context "when current user cannot copy_metadata" do + before do + guest = create(:user) + project.add_guest(guest) + + gitlab_sign_out + gitlab_sign_in(guest) + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it 'does not copy_metadata' do + add_note("/copy_metadata #{source_issuable.to_reference(project)}") + + wait_for_requests + expect(page).not_to have_content '/copy_metadata' + expect(page).not_to have_content 'Commands applied' + issuable.reload + expect(issuable.milestone).not_to eq milestone + expect(issuable.labels).to eq [] + end + end + end + + context "preview of note on #{issuable_type}", :js do + it 'explains copy_metadata quick action' do + visit public_send("project_#{issuable_type}_path", project, issuable) + + preview_note("/copy_metadata #{source_issuable.to_reference(project)}") + + expect(page).not_to have_content '/copy_metadata' + expect(page).to have_content "Copy labels and milestone from #{source_issuable.to_reference(project)}." + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issuable/done_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/done_quick_action_shared_examples.rb new file mode 100644 index 00000000000..8a72bbc13bf --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/done_quick_action_shared_examples.rb @@ -0,0 +1,103 @@ +# frozen_string_literal: true + +shared_examples 'done quick action' do |issuable_type| + before do + project.add_maintainer(maintainer) + gitlab_sign_in(maintainer) + end + + context "new #{issuable_type}", :js do + before do + case issuable_type + when :merge_request + visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) + wait_for_all_requests + when :issue + visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) + wait_for_all_requests + end + end + + it "creates the #{issuable_type} and interprets done quick action accordingly" do + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/done" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).first + + expect(issuable.description).to eq 'bug description' + expect(issuable).to be_opened + expect(page).to have_content 'bug 345' + expect(page).to have_content 'bug description' + + todos = TodosFinder.new(maintainer).execute + expect(todos.size).to eq 0 + end + end + + context "post note to existing #{issuable_type}" do + before do + TodoService.new.mark_todo(issuable, maintainer) + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it 'creates the note and interprets the done quick action accordingly' do + todos = TodosFinder.new(maintainer).execute + todo = todos.first + expect(todo.reload).to be_pending + + expect(todos.size).to eq 1 + expect(todo.target).to eq issuable + expect(todo.author).to eq maintainer + expect(todo.user).to eq maintainer + + add_note('/done') + + wait_for_requests + expect(page).not_to have_content '/done' + expect(page).to have_content 'Commands applied' + expect(todo.reload).to be_done + end + + context "when current user cannot mark #{issuable_type} todo as done" do + before do + guest = create(:user) + project.add_guest(guest) + + gitlab_sign_out + gitlab_sign_in(guest) + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it "does not set the #{issuable_type} todo as done" do + todos = TodosFinder.new(maintainer).execute + todo = todos.first + expect(todo.reload).to be_pending + + expect(todos.size).to eq 1 + expect(todo.target).to eq issuable + expect(todo.author).to eq maintainer + expect(todo.user).to eq maintainer + + add_note('/done') + + expect(page).not_to have_content 'Commands applied' + expect(todo.reload).to be_pending + end + end + end + + context "preview of note on #{issuable_type}", :js do + it 'explains done quick action' do + TodoService.new.mark_todo(issuable, maintainer) + visit public_send("project_#{issuable_type}_path", project, issuable) + + preview_note('/done') + + expect(page).not_to have_content '/done' + expect(page).to have_content "Marks todo as done." + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issuable/estimate_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/estimate_quick_action_shared_examples.rb new file mode 100644 index 00000000000..648755d7e55 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/estimate_quick_action_shared_examples.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +shared_examples 'estimate quick action' do |issuable_type| + before do + project.add_maintainer(maintainer) + gitlab_sign_in(maintainer) + end + + context "new #{issuable_type}", :js do + before do + case issuable_type + when :merge_request + visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) + wait_for_all_requests + when :issue + visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) + wait_for_all_requests + end + end + + it "creates the #{issuable_type} and interprets estimate quick action accordingly" do + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/estimate 1d 2h 3m" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).first + + expect(issuable.description).to eq 'bug description' + expect(page).to have_content 'bug 345' + expect(page).to have_content 'bug description' + expect(issuable.time_estimate).to eq 36180 + end + end + + context "post note to existing #{issuable_type}" do + before do + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it 'creates the note and interprets the estimate quick action accordingly' do + add_note("/estimate 1d 2h 3m") + + wait_for_requests + expect(page).not_to have_content '/estimate' + expect(page).to have_content 'Commands applied' + expect(issuable.reload.time_estimate).to eq 36180 + end + + context "when current user cannot set estimate to #{issuable_type}" do + before do + guest = create(:user) + project.add_guest(guest) + + gitlab_sign_out + gitlab_sign_in(guest) + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it 'does not set estimate' do + add_note("/estimate ~bug ~feature") + + wait_for_requests + expect(page).not_to have_content '/estimate' + expect(issuable.reload.time_estimate).to eq 0 + end + end + end + + context "preview of note on #{issuable_type}", :js do + it 'explains estimate quick action' do + visit public_send("project_#{issuable_type}_path", project, issuable) + + preview_note('/estimate 1d 2h 3m') + + expect(page).not_to have_content '/estimate' + expect(page).to have_content 'Sets time estimate to 1d 2h 3m.' + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issuable/label_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/label_quick_action_shared_examples.rb new file mode 100644 index 00000000000..9066e382b70 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/label_quick_action_shared_examples.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +shared_examples 'label quick action' do |issuable_type| + before do + project.add_maintainer(maintainer) + gitlab_sign_in(maintainer) + end + + context "new #{issuable_type}", :js do + before do + case issuable_type + when :merge_request + visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) + wait_for_all_requests + when :issue + visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) + wait_for_all_requests + end + end + + it "creates the #{issuable_type} and interprets label quick action accordingly" do + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/label ~bug ~feature" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).first + + expect(issuable.description).to eq 'bug description' + expect(issuable).to be_opened + expect(page).to have_content 'bug 345' + expect(page).to have_content 'bug description' + expect(issuable.labels).to match_array([label_bug, label_feature]) + end + end + + context "post note to existing #{issuable_type}" do + before do + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + expect(issuable.labels).to eq [] + end + + it 'creates the note and interprets the label quick action accordingly' do + add_note("/label ~bug ~feature") + + wait_for_requests + expect(page).not_to have_content '/label' + expect(page).to have_content 'Commands applied' + expect(issuable.reload.labels).to match_array([label_bug, label_feature]) + end + + context "when current user cannot set label to #{issuable_type}" do + before do + guest = create(:user) + project.add_guest(guest) + + gitlab_sign_out + gitlab_sign_in(guest) + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it 'does not set label' do + add_note("/label ~bug ~feature") + + wait_for_requests + expect(page).not_to have_content '/label' + expect(issuable.labels).to eq [] + end + end + end + + context "preview of note on #{issuable_type}", :js do + it 'explains label quick action' do + visit public_send("project_#{issuable_type}_path", project, issuable) + + preview_note('/label ~bug ~feature') + + expect(page).not_to have_content '/label' + expect(page).to have_content 'Adds bug feature labels.' + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issuable/lock_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/lock_quick_action_shared_examples.rb new file mode 100644 index 00000000000..d3197f2a459 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/lock_quick_action_shared_examples.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +shared_examples 'lock quick action' do |issuable_type| + before do + project.add_maintainer(maintainer) + gitlab_sign_in(maintainer) + end + + context "new #{issuable_type}", :js do + before do + case issuable_type + when :merge_request + visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) + wait_for_all_requests + when :issue + visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) + wait_for_all_requests + end + end + + it "creates the #{issuable_type} and interprets lock quick action accordingly" do + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/lock" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).first + + expect(issuable.description).to eq 'bug description' + expect(issuable).to be_opened + expect(page).to have_content 'bug 345' + expect(page).to have_content 'bug description' + expect(issuable).not_to be_discussion_locked + end + end + + context "post note to existing #{issuable_type}" do + before do + issuable.update(discussion_locked: false) + expect(issuable).not_to be_discussion_locked + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it 'creates the note and interprets the lock quick action accordingly' do + add_note('/lock') + + wait_for_requests + expect(page).not_to have_content '/lock' + expect(page).to have_content 'Commands applied' + expect(issuable.reload).to be_discussion_locked + end + + context "when current user cannot lock to #{issuable_type}" do + before do + guest = create(:user) + project.add_guest(guest) + + gitlab_sign_out + gitlab_sign_in(guest) + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it "does not lock the #{issuable_type}" do + add_note('/lock') + + wait_for_requests + expect(page).not_to have_content '/lock' + expect(issuable).not_to be_discussion_locked + end + end + end + + context "preview of note on #{issuable_type}", :js do + it 'explains lock quick action' do + visit public_send("project_#{issuable_type}_path", project, issuable) + + preview_note('/lock') + + expect(page).not_to have_content '/lock' + expect(page).to have_content "Locks the discussion" + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issuable/milestone_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/milestone_quick_action_shared_examples.rb new file mode 100644 index 00000000000..7f16ce93b6a --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/milestone_quick_action_shared_examples.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +shared_examples 'milestone quick action' do |issuable_type| + before do + project.add_maintainer(maintainer) + gitlab_sign_in(maintainer) + end + + context "new #{issuable_type}", :js do + before do + case issuable_type + when :merge_request + visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) + wait_for_all_requests + when :issue + visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) + wait_for_all_requests + end + end + + it "creates the #{issuable_type} and interprets milestone quick action accordingly" do + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/milestone %\"ASAP\"" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).first + + expect(issuable.description).to eq 'bug description' + expect(issuable).to be_opened + expect(page).to have_content 'bug 345' + expect(page).to have_content 'bug description' + expect(issuable.milestone).to eq milestone + end + end + + context "post note to existing #{issuable_type}" do + before do + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + expect(issuable.milestone).to be_nil + end + + it 'creates the note and interprets the milestone quick action accordingly' do + add_note("/milestone %\"ASAP\"") + + wait_for_requests + expect(page).not_to have_content '/milestone' + expect(page).to have_content 'Commands applied' + expect(issuable.reload.milestone).to eq milestone + end + + context "when current user cannot set milestone to #{issuable_type}" do + before do + guest = create(:user) + project.add_guest(guest) + + gitlab_sign_out + gitlab_sign_in(guest) + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it 'does not set milestone' do + add_note('/milestone') + + wait_for_requests + expect(page).not_to have_content '/milestone' + expect(issuable.reload.milestone).to be_nil + end + end + end + + context "preview of note on #{issuable_type}", :js do + it 'explains milestone quick action' do + visit public_send("project_#{issuable_type}_path", project, issuable) + + preview_note("/milestone %\"ASAP\"") + + expect(page).not_to have_content '/milestone' + expect(page).to have_content 'Sets the milestone to %ASAP' + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issuable/relabel_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/relabel_quick_action_shared_examples.rb new file mode 100644 index 00000000000..643ae77516a --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/relabel_quick_action_shared_examples.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true + +shared_examples 'relabel quick action' do |issuable_type| + before do + project.add_maintainer(maintainer) + gitlab_sign_in(maintainer) + end + + context "new #{issuable_type}", :js do + before do + case issuable_type + when :merge_request + visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) + wait_for_all_requests + when :issue + visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) + wait_for_all_requests + end + end + + it "creates the #{issuable_type} and interprets relabel quick action accordingly" do + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/label ~bug /relabel ~feature" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).first + + expect(issuable.description).to eq 'bug description' + expect(issuable).to be_opened + expect(page).to have_content 'bug 345' + expect(page).to have_content 'bug description' + expect(issuable.labels).to eq [label_bug, label_feature] + end + end + + context "post note to existing #{issuable_type}" do + before do + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + issuable.update(labels: [label_bug]) + end + + it 'creates the note and interprets the relabel quick action accordingly' do + add_note('/relabel ~feature') + + wait_for_requests + expect(page).not_to have_content '/relabel' + expect(page).to have_content 'Commands applied' + expect(issuable.reload.labels).to match_array([label_feature]) + end + + it 'creates the note and interprets the relabel quick action with empty param' do + add_note('/relabel') + + wait_for_requests + expect(page).not_to have_content '/relabel' + expect(page).to have_content 'Commands applied' + expect(issuable.reload.labels).to match_array([label_bug]) + end + + context "when current user cannot relabel to #{issuable_type}" do + before do + guest = create(:user) + project.add_guest(guest) + + gitlab_sign_out + gitlab_sign_in(guest) + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it 'does not relabel' do + add_note('/relabel ~feature') + + wait_for_requests + expect(page).not_to have_content '/relabel' + expect(issuable.labels).to match_array([label_bug]) + end + end + end + + context "preview of note on #{issuable_type}", :js do + before do + issuable.update(labels: [label_bug]) + visit public_send("project_#{issuable_type}_path", project, issuable) + end + + it 'explains relabel all quick action' do + preview_note('/relabel ~feature') + + expect(page).not_to have_content '/relabel' + expect(page).to have_content 'Replaces all labels with feature label.' + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issuable/remove_estimate_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/remove_estimate_quick_action_shared_examples.rb new file mode 100644 index 00000000000..24f6f8d5bf4 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/remove_estimate_quick_action_shared_examples.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +shared_examples 'remove_estimate quick action' do |issuable_type| + before do + project.add_maintainer(maintainer) + gitlab_sign_in(maintainer) + end + + context "new #{issuable_type}", :js do + before do + case issuable_type + when :merge_request + visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) + wait_for_all_requests + when :issue + visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) + wait_for_all_requests + end + end + + it "creates the #{issuable_type} and interprets estimate quick action accordingly" do + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/remove_estimate" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).first + + expect(issuable.description).to eq 'bug description' + expect(page).to have_content 'bug 345' + expect(page).to have_content 'bug description' + expect(issuable.time_estimate).to eq 0 + end + end + + context "post note to existing #{issuable_type}" do + before do + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + issuable.update_attribute(:time_estimate, 36180) + end + + it 'creates the note and interprets the remove_estimate quick action accordingly' do + add_note("/remove_estimate") + + wait_for_requests + expect(page).not_to have_content '/remove_estimate' + expect(page).to have_content 'Commands applied' + expect(issuable.reload.time_estimate).to eq 0 + end + + context "when current user cannot remove_estimate" do + before do + guest = create(:user) + project.add_guest(guest) + + gitlab_sign_out + gitlab_sign_in(guest) + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it 'does not remove_estimate' do + add_note('/remove_estimate') + + wait_for_requests + expect(page).not_to have_content '/remove_estimate' + expect(issuable.reload.time_estimate).to eq 36180 + end + end + end + + context "preview of note on #{issuable_type}", :js do + it 'explains remove_estimate quick action' do + visit public_send("project_#{issuable_type}_path", project, issuable) + + preview_note('/remove_estimate') + + expect(page).not_to have_content '/remove_estimate' + expect(page).to have_content 'Removes time estimate.' + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issuable/remove_milestone_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/remove_milestone_quick_action_shared_examples.rb new file mode 100644 index 00000000000..edd92d5cdbc --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/remove_milestone_quick_action_shared_examples.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +shared_examples 'remove_milestone quick action' do |issuable_type| + before do + project.add_maintainer(maintainer) + gitlab_sign_in(maintainer) + end + + context "new #{issuable_type}", :js do + before do + case issuable_type + when :merge_request + visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) + wait_for_all_requests + when :issue + visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) + wait_for_all_requests + end + end + + it "creates the #{issuable_type} and interprets remove_milestone quick action accordingly" do + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/remove_milestone" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).first + + expect(issuable.description).to eq 'bug description' + expect(issuable).to be_opened + expect(page).to have_content 'bug 345' + expect(page).to have_content 'bug description' + expect(issuable.milestone).to be_nil + end + end + + context "post note to existing #{issuable_type}" do + before do + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + issuable.update(milestone: milestone) + expect(issuable.milestone).to eq(milestone) + end + + it 'creates the note and interprets the remove_milestone quick action accordingly' do + add_note("/remove_milestone") + + wait_for_requests + expect(page).not_to have_content '/remove_milestone' + expect(page).to have_content 'Commands applied' + expect(issuable.reload.milestone).to be_nil + end + + context "when current user cannot remove milestone to #{issuable_type}" do + before do + guest = create(:user) + project.add_guest(guest) + + gitlab_sign_out + gitlab_sign_in(guest) + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it 'does not remove milestone' do + add_note('/remove_milestone') + + wait_for_requests + expect(page).not_to have_content '/remove_milestone' + expect(issuable.reload.milestone).to eq(milestone) + end + end + end + + context "preview of note on #{issuable_type}", :js do + it 'explains remove_milestone quick action' do + issuable.update(milestone: milestone) + expect(issuable.milestone).to eq(milestone) + visit public_send("project_#{issuable_type}_path", project, issuable) + + preview_note("/remove_milestone") + + expect(page).not_to have_content '/remove_milestone' + expect(page).to have_content 'Removes %ASAP milestone.' + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issuable/remove_time_spent_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/remove_time_spent_quick_action_shared_examples.rb new file mode 100644 index 00000000000..6d5894b2318 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/remove_time_spent_quick_action_shared_examples.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +shared_examples 'remove_time_spent quick action' do |issuable_type| + before do + project.add_maintainer(maintainer) + gitlab_sign_in(maintainer) + end + + context "new #{issuable_type}", :js do + before do + case issuable_type + when :merge_request + visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) + wait_for_all_requests + when :issue + visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) + wait_for_all_requests + end + end + + it "creates the #{issuable_type} and interprets remove_time_spent quick action accordingly" do + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/remove_time_spent" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).first + + expect(issuable.description).to eq 'bug description' + expect(page).to have_content 'bug 345' + expect(page).to have_content 'bug description' + expect(issuable.total_time_spent).to eq 0 + end + end + + context "post note to existing #{issuable_type}" do + before do + issuable.update!(spend_time: { duration: 36180, user_id: maintainer.id }) + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it 'creates the note and interprets the remove_time_spent quick action accordingly' do + add_note("/remove_time_spent") + + wait_for_requests + expect(page).not_to have_content '/remove_time_spent' + expect(page).to have_content 'Commands applied' + expect(issuable.reload.total_time_spent).to eq 0 + end + + context "when current user cannot set remove_time_spent time" do + before do + guest = create(:user) + project.add_guest(guest) + + gitlab_sign_out + gitlab_sign_in(guest) + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it 'does not set remove_time_spent time' do + add_note("/remove_time_spent") + + wait_for_requests + expect(page).not_to have_content '/remove_time_spent' + expect(issuable.reload.total_time_spent).to eq 36180 + end + end + end + + context "preview of note on #{issuable_type}", :js do + it 'explains remove_time_spent quick action' do + visit public_send("project_#{issuable_type}_path", project, issuable) + + preview_note('/remove_time_spent') + + expect(page).not_to have_content '/remove_time_spent' + expect(page).to have_content 'Removes spent time.' + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issuable/reopen_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/reopen_quick_action_shared_examples.rb new file mode 100644 index 00000000000..af173e93bb5 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/reopen_quick_action_shared_examples.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +shared_examples 'reopen quick action' do |issuable_type| + before do + project.add_maintainer(maintainer) + gitlab_sign_in(maintainer) + end + + context "new #{issuable_type}", :js do + before do + case issuable_type + when :merge_request + visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) + wait_for_all_requests + when :issue + visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) + wait_for_all_requests + end + end + + it "creates the #{issuable_type} and interprets reopen quick action accordingly" do + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/reopen" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).first + + expect(issuable.description).to eq 'bug description' + expect(issuable).to be_opened + expect(page).to have_content 'bug 345' + expect(page).to have_content 'bug description' + end + end + + context "post note to existing #{issuable_type}" do + before do + issuable.close + expect(issuable).to be_closed + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it 'creates the note and interprets the reopen quick action accordingly' do + add_note('/reopen') + + wait_for_requests + expect(page).not_to have_content '/reopen' + expect(page).to have_content 'Commands applied' + + issuable.reload + expect(issuable).to be_opened + end + + context "when current user cannot reopen #{issuable_type}" do + before do + guest = create(:user) + project.add_guest(guest) + + gitlab_sign_out + gitlab_sign_in(guest) + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it "does not reopen the #{issuable_type}" do + add_note('/reopen') + + expect(page).not_to have_content 'Commands applied' + expect(issuable).to be_closed + end + end + end + + context "preview of note on #{issuable_type}", :js do + it 'explains reopen quick action' do + issuable.close + expect(issuable).to be_closed + visit public_send("project_#{issuable_type}_path", project, issuable) + + preview_note('/reopen') + + expect(page).not_to have_content '/reopen' + expect(page).to have_content "Reopens this #{issuable_type.to_s.humanize.downcase}." + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issuable/shrug_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/shrug_quick_action_shared_examples.rb new file mode 100644 index 00000000000..0a526808585 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/shrug_quick_action_shared_examples.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +shared_examples 'shrug quick action' do |issuable_type| + before do + project.add_maintainer(maintainer) + gitlab_sign_in(maintainer) + end + + context "new #{issuable_type}", :js do + before do + case issuable_type + when :merge_request + visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) + wait_for_all_requests + when :issue + visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) + wait_for_all_requests + end + end + + it "creates the #{issuable_type} and interprets shrug quick action accordingly" do + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/shrug oops" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).first + + expect(issuable.description).to eq "bug description\noops ¯\\_(ツ)_/¯" + expect(page).to have_content 'bug 345' + expect(page).to have_content "bug description\noops ¯\\_(ツ)_/¯" + end + end + + context "post note to existing #{issuable_type}" do + before do + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it 'creates the note and interprets shrug quick action accordingly' do + add_note("/shrug oops") + + wait_for_requests + expect(page).not_to have_content '/shrug oops' + expect(page).to have_content "oops ¯\\_(ツ)_/¯" + expect(issuable.notes.last.note).to eq "oops ¯\\_(ツ)_/¯" + end + end + + context "preview of note on #{issuable_type}", :js do + it 'explains shrug quick action' do + visit public_send("project_#{issuable_type}_path", project, issuable) + + preview_note('/shrug oops') + + expect(page).not_to have_content '/shrug' + expect(page).to have_content "oops ¯\\_(ツ)_/¯" + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issuable/spend_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/spend_quick_action_shared_examples.rb new file mode 100644 index 00000000000..97b4885eba0 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/spend_quick_action_shared_examples.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +shared_examples 'spend quick action' do |issuable_type| + before do + project.add_maintainer(maintainer) + gitlab_sign_in(maintainer) + end + + context "new #{issuable_type}", :js do + before do + case issuable_type + when :merge_request + visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) + wait_for_all_requests + when :issue + visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) + wait_for_all_requests + end + end + + it "creates the #{issuable_type} and interprets spend quick action accordingly" do + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/spend 1d 2h 3m" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).first + + expect(issuable.description).to eq 'bug description' + expect(page).to have_content 'bug 345' + expect(page).to have_content 'bug description' + expect(issuable.total_time_spent).to eq 36180 + end + end + + context "post note to existing #{issuable_type}" do + before do + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it 'creates the note and interprets the spend quick action accordingly' do + add_note("/spend 1d 2h 3m") + + wait_for_requests + expect(page).not_to have_content '/spend' + expect(page).to have_content 'Commands applied' + expect(issuable.reload.total_time_spent).to eq 36180 + end + + context "when current user cannot set spend time" do + before do + guest = create(:user) + project.add_guest(guest) + + gitlab_sign_out + gitlab_sign_in(guest) + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it 'does not set spend time' do + add_note("/spend 1s 2h 3m") + + wait_for_requests + expect(page).not_to have_content '/spend' + expect(issuable.reload.total_time_spent).to eq 0 + end + end + end + + context "preview of note on #{issuable_type}", :js do + it 'explains spend quick action' do + visit public_send("project_#{issuable_type}_path", project, issuable) + + preview_note('/spend 1d 2h 3m') + + expect(page).not_to have_content '/spend' + expect(page).to have_content 'Adds 1d 2h 3m spent time.' + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issuable/subscribe_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/subscribe_quick_action_shared_examples.rb new file mode 100644 index 00000000000..15aefd511a5 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/subscribe_quick_action_shared_examples.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +shared_examples 'subscribe quick action' do |issuable_type| + before do + project.add_maintainer(maintainer) + gitlab_sign_in(maintainer) + end + + context "new #{issuable_type}", :js do + before do + case issuable_type + when :merge_request + visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) + wait_for_all_requests + when :issue + visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) + wait_for_all_requests + end + end + + it "creates the #{issuable_type} and interprets subscribe quick action accordingly" do + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/subscribe" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).first + + expect(issuable.description).to eq 'bug description' + expect(issuable).to be_opened + expect(page).to have_content 'bug 345' + expect(page).to have_content 'bug description' + expect(issuable.subscribed?(maintainer, project)).to be_truthy + end + end + + context "post note to existing #{issuable_type}" do + before do + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + expect(issuable.subscribed?(maintainer, project)).to be_falsy + end + + it 'creates the note and interprets the subscribe quick action accordingly' do + add_note('/subscribe') + + wait_for_requests + expect(page).not_to have_content '/subscribe' + expect(page).to have_content 'Commands applied' + expect(issuable.subscribed?(maintainer, project)).to be_truthy + end + + context "when current user cannot subscribe to #{issuable_type}" do + before do + guest = create(:user) + project.add_guest(guest) + + gitlab_sign_out + gitlab_sign_in(guest) + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it "does not subscribe to the #{issuable_type}" do + add_note('/subscribe') + + wait_for_requests + expect(page).not_to have_content '/subscribe' + expect(page).to have_content 'Commands applied' + expect(issuable.subscribed?(maintainer, project)).to be_falsy + end + end + end + + context "preview of note on #{issuable_type}", :js do + it 'explains subscribe quick action' do + visit public_send("project_#{issuable_type}_path", project, issuable) + + preview_note('/subscribe') + + expect(page).not_to have_content '/subscribe' + expect(page).to have_content "Subscribes to this #{issuable_type.to_s.humanize.downcase}" + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issuable/tableflip_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/tableflip_quick_action_shared_examples.rb new file mode 100644 index 00000000000..ef831e39872 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/tableflip_quick_action_shared_examples.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +shared_examples 'tableflip quick action' do |issuable_type| + before do + project.add_maintainer(maintainer) + gitlab_sign_in(maintainer) + end + + context "new #{issuable_type}", :js do + before do + case issuable_type + when :merge_request + visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) + wait_for_all_requests + when :issue + visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) + wait_for_all_requests + end + end + + it "creates the #{issuable_type} and interprets tableflip quick action accordingly" do + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/tableflip oops" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).first + + expect(issuable.description).to eq "bug description\noops (╯°□°)╯︵ ┻━┻" + expect(page).to have_content 'bug 345' + expect(page).to have_content "bug description\noops (╯°□°)╯︵ ┻━┻" + end + end + + context "post note to existing #{issuable_type}" do + before do + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it 'creates the note and interprets tableflip quick action accordingly' do + add_note("/tableflip oops") + + wait_for_requests + expect(page).not_to have_content '/tableflip oops' + expect(page).to have_content "oops (╯°□°)╯︵ ┻━┻" + expect(issuable.notes.last.note).to eq "oops (╯°□°)╯︵ ┻━┻" + end + end + + context "preview of note on #{issuable_type}", :js do + it 'explains tableflip quick action' do + visit public_send("project_#{issuable_type}_path", project, issuable) + + preview_note('/tableflip oops') + + expect(page).not_to have_content '/tableflip' + expect(page).to have_content "oops (╯°□°)╯︵ ┻━┻" + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issuable/time_tracking_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/time_tracking_quick_action_shared_examples.rb new file mode 100644 index 00000000000..ed904c8d539 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/time_tracking_quick_action_shared_examples.rb @@ -0,0 +1,98 @@ +# frozen_string_literal: true + +shared_examples 'issuable time tracker' do |issuable_type| + before do + project.add_maintainer(maintainer) + gitlab_sign_in(maintainer) + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + after do + wait_for_requests + end + + it 'renders the sidebar component empty state' do + page.within '.time-tracking-no-tracking-pane' do + expect(page).to have_content 'No estimate or time spent' + end + end + + it 'updates the sidebar component when estimate is added' do + submit_time('/estimate 3w 1d 1h') + + wait_for_requests + page.within '.time-tracking-estimate-only-pane' do + expect(page).to have_content '3w 1d 1h' + end + end + + it 'updates the sidebar component when spent is added' do + submit_time('/spend 3w 1d 1h') + + wait_for_requests + page.within '.time-tracking-spend-only-pane' do + expect(page).to have_content '3w 1d 1h' + end + end + + it 'shows the comparison when estimate and spent are added' do + submit_time('/estimate 3w 1d 1h') + submit_time('/spend 3w 1d 1h') + + wait_for_requests + page.within '.time-tracking-comparison-pane' do + expect(page).to have_content '3w 1d 1h' + end + end + + it 'updates the sidebar component when estimate is removed' do + submit_time('/estimate 3w 1d 1h') + submit_time('/remove_estimate') + + page.within '.time-tracking-component-wrap' do + expect(page).to have_content 'No estimate or time spent' + end + end + + it 'updates the sidebar component when spent is removed' do + submit_time('/spend 3w 1d 1h') + submit_time('/remove_time_spent') + + page.within '.time-tracking-component-wrap' do + expect(page).to have_content 'No estimate or time spent' + end + end + + it 'shows the help state when icon is clicked' do + page.within '.time-tracking-component-wrap' do + find('.help-button').click + expect(page).to have_content 'Track time with quick actions' + expect(page).to have_content 'Learn more' + end + end + + it 'hides the help state when close icon is clicked' do + page.within '.time-tracking-component-wrap' do + find('.help-button').click + find('.close-help-button').click + + expect(page).not_to have_content 'Track time with quick actions' + expect(page).not_to have_content 'Learn more' + end + end + + it 'displays the correct help url' do + page.within '.time-tracking-component-wrap' do + find('.help-button').click + + expect(find_link('Learn more')[:href]).to have_content('/help/workflow/time_tracking.md') + end + end +end + +def submit_time(quick_action) + fill_in 'note[note]', with: quick_action + find('.js-comment-submit-button').click + wait_for_requests +end diff --git a/spec/support/shared_examples/quick_actions/issuable/title_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/title_quick_action_shared_examples.rb new file mode 100644 index 00000000000..93a69093dde --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/title_quick_action_shared_examples.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +shared_examples 'title quick action' do |issuable_type| + before do + project.add_maintainer(maintainer) + gitlab_sign_in(maintainer) + end + + context "new #{issuable_type}", :js do + before do + case issuable_type + when :merge_request + visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) + wait_for_all_requests + when :issue + visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) + wait_for_all_requests + end + end + + it "creates the #{issuable_type} and interprets title quick action accordingly" do + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/title new title" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).first + + expect(issuable.description).to eq 'bug description' + expect(issuable).to be_opened + expect(issuable.title).to eq 'bug 345' + expect(page).to have_content 'bug 345' + expect(page).to have_content 'bug description' + end + end + + context "post note to existing #{issuable_type}" do + before do + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it 'creates the note and interprets the title quick action accordingly' do + add_note('/title New title') + + wait_for_requests + expect(page).not_to have_content '/title new title' + expect(page).to have_content 'Commands applied' + expect(page).to have_content 'New title' + + issuable.reload + expect(issuable.title).to eq 'New title' + end + + context "when current user cannot set title #{issuable_type}" do + before do + guest = create(:user) + project.add_guest(guest) + + gitlab_sign_out + gitlab_sign_in(guest) + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it "does not set title to the #{issuable_type}" do + add_note('/title New title') + + expect(page).not_to have_content 'Commands applied' + expect(issuable.title).not_to eq 'New title' + end + end + end + + context "preview of note on #{issuable_type}", :js do + it 'explains title quick action' do + visit public_send("project_#{issuable_type}_path", project, issuable) + + preview_note('/title New title') + wait_for_requests + + expect(page).not_to have_content '/title New title' + expect(page).to have_content 'Changes the title to "New title".' + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issuable/todo_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/todo_quick_action_shared_examples.rb new file mode 100644 index 00000000000..cccc28127ce --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/todo_quick_action_shared_examples.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +shared_examples 'todo quick action' do |issuable_type| + before do + project.add_maintainer(maintainer) + gitlab_sign_in(maintainer) + end + + context "new #{issuable_type}", :js do + before do + case issuable_type + when :merge_request + visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) + wait_for_all_requests + when :issue + visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) + wait_for_all_requests + end + end + + it "creates the #{issuable_type} and interprets todo quick action accordingly" do + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/todo" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).first + + expect(issuable.description).to eq 'bug description' + expect(issuable).to be_opened + expect(page).to have_content 'bug 345' + expect(page).to have_content 'bug description' + + todos = TodosFinder.new(maintainer).execute + expect(todos.size).to eq 0 + end + end + + context "post note to existing #{issuable_type}" do + before do + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it 'creates the note and interprets the todo quick action accordingly' do + add_note('/todo') + + wait_for_requests + expect(page).not_to have_content '/todo' + expect(page).to have_content 'Commands applied' + + todos = TodosFinder.new(maintainer).execute + todo = todos.first + + expect(todos.size).to eq 1 + expect(todo).to be_pending + expect(todo.target).to eq issuable + expect(todo.author).to eq maintainer + expect(todo.user).to eq maintainer + end + + context "when current user cannot add todo #{issuable_type}" do + before do + guest = create(:user) + project.add_guest(guest) + + gitlab_sign_out + gitlab_sign_in(guest) + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it "does not add todo the #{issuable_type}" do + add_note('/todo') + + expect(page).not_to have_content 'Commands applied' + todos = TodosFinder.new(maintainer).execute + expect(todos.size).to eq 0 + end + end + end + + context "preview of note on #{issuable_type}", :js do + it 'explains todo quick action' do + visit public_send("project_#{issuable_type}_path", project, issuable) + + preview_note('/todo') + + expect(page).not_to have_content '/todo' + expect(page).to have_content "Adds a todo." + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issuable/unassign_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/unassign_quick_action_shared_examples.rb new file mode 100644 index 00000000000..0b1a52bc860 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/unassign_quick_action_shared_examples.rb @@ -0,0 +1,120 @@ +# frozen_string_literal: true + +shared_examples 'unassign quick action' do |issuable_type| + before do + project.add_maintainer(maintainer) + gitlab_sign_in(maintainer) + end + + context "new #{issuable_type}", :js do + before do + case issuable_type + when :merge_request + visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) + wait_for_all_requests + when :issue + visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) + wait_for_all_requests + end + end + + it "creates the #{issuable_type} and interprets unassign quick action accordingly" do + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/unassign @bob" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).first + + expect(issuable.description).to eq 'bug description' + expect(issuable.assignees).to eq [] + expect(page).to have_content 'bug 345' + expect(page).to have_content 'bug description' + end + + it "creates the #{issuable_type} and interprets unassign quick action accordingly" do + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/unassign me" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).first + + expect(issuable.description).to eq 'bug description' + expect(issuable.assignees).to eq [] + expect(page).to have_content 'bug 345' + expect(page).to have_content 'bug description' + end + end + + context "post note to existing #{issuable_type}" do + before do + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it 'creates the note and interprets the unassign quick action accordingly' do + assignee = create(:user, username: 'bob') + issuable.update(assignee_ids: [assignee.id]) + expect(issuable.assignees).to eq [assignee] + + add_note("Awesome!\n\n/unassign @bob") + + expect(page).to have_content 'Awesome!' + expect(page).not_to have_content '/unassign @bob' + + wait_for_requests + issuable.reload + note = issuable.notes.user.first + + expect(note.note).to eq 'Awesome!' + expect(issuable.assignees).to eq [] + end + + it "unassigns the #{issuable_type} from current user" do + issuable.update(assignee_ids: [maintainer.id]) + expect(issuable.reload.assignees).to eq [maintainer] + expect(issuable.assignees).to eq [maintainer] + + add_note("/unassign me") + + expect(page).not_to have_content '/unassign me' + expect(page).to have_content 'Commands applied' + + expect(issuable.reload.assignees).to eq [] + end + end + + context "preview of note on #{issuable_type}", :js do + it 'explains unassign quick action: from bob' do + assignee = create(:user, username: 'bob') + issuable.update(assignee_ids: [assignee.id]) + expect(issuable.assignees).to eq [assignee] + + visit public_send("project_#{issuable_type}_path", project, issuable) + + page.within('.js-main-target-form') do + fill_in 'note[note]', with: "Awesome!\n/unassign @bob " + click_on 'Preview' + + expect(page).not_to have_content '/unassign @bob' + expect(page).to have_content 'Awesome!' + expect(page).to have_content 'Removes assignee @bob.' + end + end + + it 'explains unassign quick action: from me' do + issuable.update(assignee_ids: [maintainer.id]) + expect(issuable.assignees).to eq [maintainer] + + visit public_send("project_#{issuable_type}_path", project, issuable) + + page.within('.js-main-target-form') do + fill_in 'note[note]', with: "Awesome!\n/unassign me" + click_on 'Preview' + + expect(page).not_to have_content '/unassign me' + expect(page).to have_content 'Awesome!' + expect(page).to have_content "Removes assignee @#{maintainer.username}." + end + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issuable/unlabel_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/unlabel_quick_action_shared_examples.rb new file mode 100644 index 00000000000..1a1ee05841f --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/unlabel_quick_action_shared_examples.rb @@ -0,0 +1,102 @@ +# frozen_string_literal: true + +shared_examples 'unlabel quick action' do |issuable_type| + before do + project.add_maintainer(maintainer) + gitlab_sign_in(maintainer) + end + + context "new #{issuable_type}", :js do + before do + case issuable_type + when :merge_request + visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) + wait_for_all_requests + when :issue + visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) + wait_for_all_requests + end + end + + it "creates the #{issuable_type} and interprets unlabel quick action accordingly" do + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/label ~bug /unlabel" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).first + + expect(issuable.description).to eq 'bug description' + expect(issuable).to be_opened + expect(page).to have_content 'bug 345' + expect(page).to have_content 'bug description' + expect(issuable.labels).to eq [label_bug] + end + end + + context "post note to existing #{issuable_type}" do + before do + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + issuable.update(labels: [label_bug, label_feature]) + end + + it 'creates the note and interprets the unlabel all quick action accordingly' do + add_note("/unlabel") + + wait_for_requests + expect(page).not_to have_content '/unlabel' + expect(page).to have_content 'Commands applied' + expect(issuable.reload.labels).to eq [] + end + + it 'creates the note and interprets the unlabel some quick action accordingly' do + add_note("/unlabel ~bug") + + wait_for_requests + expect(page).not_to have_content '/unlabel' + expect(page).to have_content 'Commands applied' + expect(issuable.reload.labels).to match_array([label_feature]) + end + + context "when current user cannot unlabel to #{issuable_type}" do + before do + guest = create(:user) + project.add_guest(guest) + + gitlab_sign_out + gitlab_sign_in(guest) + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it 'does not unlabel' do + add_note("/unlabel") + + wait_for_requests + expect(page).not_to have_content '/unlabel' + expect(issuable.labels).to match_array([label_bug, label_feature]) + end + end + end + + context "preview of note on #{issuable_type}", :js do + before do + issuable.update(labels: [label_bug, label_feature]) + visit public_send("project_#{issuable_type}_path", project, issuable) + end + + it 'explains unlabel all quick action' do + preview_note('/unlabel') + + expect(page).not_to have_content '/unlabel' + expect(page).to have_content 'Removes all labels.' + end + + it 'explains unlabel some quick action' do + preview_note('/unlabel ~bug') + + expect(page).not_to have_content '/unlabel' + expect(page).to have_content 'Removes bug label.' + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issuable/unlock_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/unlock_quick_action_shared_examples.rb new file mode 100644 index 00000000000..998ff99b32e --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/unlock_quick_action_shared_examples.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true + +shared_examples 'unlock quick action' do |issuable_type| + before do + project.add_maintainer(maintainer) + gitlab_sign_in(maintainer) + end + + context "new #{issuable_type}", :js do + before do + case issuable_type + when :merge_request + visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) + wait_for_all_requests + when :issue + visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) + wait_for_all_requests + end + end + + it "creates the #{issuable_type} and interprets unlock quick action accordingly" do + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/unlock" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).first + + expect(issuable.description).to eq 'bug description' + expect(issuable).to be_opened + expect(page).to have_content 'bug 345' + expect(page).to have_content 'bug description' + expect(issuable).not_to be_discussion_locked + end + end + + context "post note to existing #{issuable_type}" do + before do + issuable.update(discussion_locked: true) + expect(issuable).to be_discussion_locked + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it 'creates the note and interprets the unlock quick action accordingly' do + add_note('/unlock') + + wait_for_requests + expect(page).not_to have_content '/unlock' + expect(page).to have_content 'Commands applied' + expect(issuable.reload).not_to be_discussion_locked + end + + context "when current user cannot unlock to #{issuable_type}" do + before do + guest = create(:user) + project.add_guest(guest) + + gitlab_sign_out + gitlab_sign_in(guest) + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it "does not lock the #{issuable_type}" do + add_note('/unlock') + + wait_for_requests + expect(page).not_to have_content '/unlock' + expect(issuable).to be_discussion_locked + end + end + end + + context "preview of note on #{issuable_type}", :js do + it 'explains unlock quick action' do + issuable.update(discussion_locked: true) + expect(issuable).to be_discussion_locked + + visit public_send("project_#{issuable_type}_path", project, issuable) + + preview_note('/unlock') + + expect(page).not_to have_content '/unlock' + expect(page).to have_content 'Unlocks the discussion' + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issuable/unsubscribe_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/unsubscribe_quick_action_shared_examples.rb new file mode 100644 index 00000000000..bd92f133889 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issuable/unsubscribe_quick_action_shared_examples.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +shared_examples 'unsubscribe quick action' do |issuable_type| + before do + project.add_maintainer(maintainer) + gitlab_sign_in(maintainer) + end + + context "new #{issuable_type}", :js do + before do + case issuable_type + when :merge_request + visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts) + wait_for_all_requests + when :issue + visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts) + wait_for_all_requests + end + end + + it "creates the #{issuable_type} and interprets unsubscribe quick action accordingly" do + fill_in "#{issuable_type}_title", with: 'bug 345' + fill_in "#{issuable_type}_description", with: "bug description\n/unsubscribe" + click_button "Submit #{issuable_type}".humanize + + issuable = project.public_send(issuable_type.to_s.pluralize).first + + expect(issuable.description).to eq 'bug description' + expect(issuable).to be_opened + expect(page).to have_content 'bug 345' + expect(page).to have_content 'bug description' + expect(issuable.subscribed?(maintainer, project)).to be_truthy + end + end + + context "post note to existing #{issuable_type}" do + before do + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + issuable.subscribe(maintainer, project) + expect(issuable.subscribed?(maintainer, project)).to be_truthy + end + + it 'creates the note and interprets the unsubscribe quick action accordingly' do + add_note('/unsubscribe') + + wait_for_requests + expect(page).not_to have_content '/unsubscribe' + expect(page).to have_content 'Commands applied' + expect(issuable.subscribed?(maintainer, project)).to be_falsey + end + + context "when current user cannot unsubscribe to #{issuable_type}" do + before do + guest = create(:user) + project.add_guest(guest) + + gitlab_sign_out + gitlab_sign_in(guest) + visit public_send("project_#{issuable_type}_path", project, issuable) + wait_for_all_requests + end + + it "does not unsubscribe to the #{issuable_type}" do + add_note('/unsubscribe') + + wait_for_requests + expect(page).not_to have_content '/unsubscribe' + expect(issuable.subscribed?(maintainer, project)).to be_truthy + end + end + end + + context "preview of note on #{issuable_type}", :js do + it 'explains unsubscribe quick action' do + visit public_send("project_#{issuable_type}_path", project, issuable) + issuable.subscribe(maintainer, project) + expect(issuable.subscribed?(maintainer, project)).to be_truthy + + preview_note('/unsubscribe') + + expect(page).not_to have_content '/unsubscribe' + expect(page).to have_content "Unsubscribes from this #{issuable_type.to_s.humanize.downcase}." + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issue/board_move_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/board_move_quick_action_shared_examples.rb new file mode 100644 index 00000000000..6edd20bb024 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issue/board_move_quick_action_shared_examples.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +shared_examples 'board_move quick action' do +end diff --git a/spec/support/shared_examples/quick_actions/issue/confidential_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/confidential_quick_action_shared_examples.rb new file mode 100644 index 00000000000..c68e5aee842 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issue/confidential_quick_action_shared_examples.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +shared_examples 'confidential quick action' do +end diff --git a/spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb new file mode 100644 index 00000000000..5bfc3bb222f --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +shared_examples 'create_merge_request quick action' do +end diff --git a/spec/support/shared_examples/quick_actions/issue/due_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/due_quick_action_shared_examples.rb new file mode 100644 index 00000000000..db3ecccc339 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issue/due_quick_action_shared_examples.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +shared_examples 'due quick action not available' do + it 'does not set the due date' do + add_note('/due 2016-08-28') + + expect(page).not_to have_content 'Commands applied' + expect(page).not_to have_content '/due 2016-08-28' + end +end + +shared_examples 'due quick action available and date can be added' do + it 'sets the due date accordingly' do + add_note('/due 2016-08-28') + + expect(page).not_to have_content '/due 2016-08-28' + expect(page).to have_content 'Commands applied' + + visit project_issue_path(project, issue) + + page.within '.due_date' do + expect(page).to have_content 'Aug 28, 2016' + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issue/duplicate_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/duplicate_quick_action_shared_examples.rb new file mode 100644 index 00000000000..24576fe0021 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issue/duplicate_quick_action_shared_examples.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +shared_examples 'duplicate quick action' do +end diff --git a/spec/support/shared_examples/quick_actions/issue/move_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/move_quick_action_shared_examples.rb new file mode 100644 index 00000000000..953e67b0423 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issue/move_quick_action_shared_examples.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +shared_examples 'move quick action' do +end diff --git a/spec/support/shared_examples/quick_actions/issue/remove_due_date_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/remove_due_date_quick_action_shared_examples.rb new file mode 100644 index 00000000000..5904164fcfc --- /dev/null +++ b/spec/support/shared_examples/quick_actions/issue/remove_due_date_quick_action_shared_examples.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +shared_examples 'remove_due_date action not available' do + it 'does not remove the due date' do + add_note("/remove_due_date") + + expect(page).not_to have_content 'Commands applied' + expect(page).not_to have_content '/remove_due_date' + end +end + +shared_examples 'remove_due_date action available and due date can be removed' do + it 'removes the due date accordingly' do + add_note('/remove_due_date') + + expect(page).not_to have_content '/remove_due_date' + expect(page).to have_content 'Commands applied' + + visit project_issue_path(project, issue) + + page.within '.due_date' do + expect(page).to have_content 'No due date' + end + end +end diff --git a/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb new file mode 100644 index 00000000000..31d88183f0d --- /dev/null +++ b/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +shared_examples 'merge quick action' do +end diff --git a/spec/support/shared_examples/quick_actions/merge_request/target_branch_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/merge_request/target_branch_quick_action_shared_examples.rb new file mode 100644 index 00000000000..ccb4a85325b --- /dev/null +++ b/spec/support/shared_examples/quick_actions/merge_request/target_branch_quick_action_shared_examples.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +shared_examples 'target_branch quick action' do +end diff --git a/spec/support/shared_examples/quick_actions/merge_request/wip_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/merge_request/wip_quick_action_shared_examples.rb new file mode 100644 index 00000000000..6abb12b41b2 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/merge_request/wip_quick_action_shared_examples.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +shared_examples 'wip quick action' do +end diff --git a/spec/uploaders/object_storage_spec.rb b/spec/uploaders/object_storage_spec.rb index 533e9d87ea6..9ce9a353913 100644 --- a/spec/uploaders/object_storage_spec.rb +++ b/spec/uploaders/object_storage_spec.rb @@ -375,7 +375,7 @@ describe ObjectStorage do describe '#fog_public' do subject { uploader.fog_public } - it { is_expected.to eq(false) } + it { is_expected.to eq(nil) } end describe '.workhorse_authorize' do diff --git a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb index 2c60ccfb754..c6c10001bc5 100644 --- a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb +++ b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb @@ -113,4 +113,56 @@ describe 'layouts/nav/sidebar/_project' do end end end + + describe 'ci/cd settings tab' do + before do + project.update!(archived: project_archived) + end + + context 'when project is archived' do + let(:project_archived) { true } + + it 'does not show the ci/cd settings tab' do + render + + expect(rendered).not_to have_link('CI / CD', href: project_settings_ci_cd_path(project)) + end + end + + context 'when project is active' do + let(:project_archived) { false } + + it 'shows the ci/cd settings tab' do + render + + expect(rendered).to have_link('CI / CD', href: project_settings_ci_cd_path(project)) + end + end + end + + describe 'operations settings tab' do + before do + project.update!(archived: project_archived) + end + + context 'when project is archived' do + let(:project_archived) { true } + + it 'does not show the operations settings tab' do + render + + expect(rendered).not_to have_link('Operations', href: project_settings_operations_path(project)) + end + end + + context 'when project is active' do + let(:project_archived) { false } + + it 'shows the operations settings tab' do + render + + expect(rendered).to have_link('Operations', href: project_settings_operations_path(project)) + end + end + end end diff --git a/spec/workers/admin_email_worker_spec.rb b/spec/workers/admin_email_worker_spec.rb index 27687f069ea..f72b932423f 100644 --- a/spec/workers/admin_email_worker_spec.rb +++ b/spec/workers/admin_email_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe AdminEmailWorker do diff --git a/spec/workers/archive_trace_worker_spec.rb b/spec/workers/archive_trace_worker_spec.rb index 7244ad4f199..368ed3f3db1 100644 --- a/spec/workers/archive_trace_worker_spec.rb +++ b/spec/workers/archive_trace_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ArchiveTraceWorker do diff --git a/spec/workers/authorized_projects_worker_spec.rb b/spec/workers/authorized_projects_worker_spec.rb index d095138f6b7..4c02278de64 100644 --- a/spec/workers/authorized_projects_worker_spec.rb +++ b/spec/workers/authorized_projects_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe AuthorizedProjectsWorker do diff --git a/spec/workers/background_migration_worker_spec.rb b/spec/workers/background_migration_worker_spec.rb index 3bd072e7125..746c858609f 100644 --- a/spec/workers/background_migration_worker_spec.rb +++ b/spec/workers/background_migration_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe BackgroundMigrationWorker, :sidekiq, :clean_gitlab_redis_shared_state do diff --git a/spec/workers/build_coverage_worker_spec.rb b/spec/workers/build_coverage_worker_spec.rb index ba20488f663..25686ae68ca 100644 --- a/spec/workers/build_coverage_worker_spec.rb +++ b/spec/workers/build_coverage_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe BuildCoverageWorker do diff --git a/spec/workers/build_finished_worker_spec.rb b/spec/workers/build_finished_worker_spec.rb index ccb26849e67..33f327d4a0c 100644 --- a/spec/workers/build_finished_worker_spec.rb +++ b/spec/workers/build_finished_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe BuildFinishedWorker do diff --git a/spec/workers/build_hooks_worker_spec.rb b/spec/workers/build_hooks_worker_spec.rb index 97654a93f5c..59b252a8be3 100644 --- a/spec/workers/build_hooks_worker_spec.rb +++ b/spec/workers/build_hooks_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe BuildHooksWorker do diff --git a/spec/workers/build_success_worker_spec.rb b/spec/workers/build_success_worker_spec.rb index 5eb9709ded9..065aeaf2b65 100644 --- a/spec/workers/build_success_worker_spec.rb +++ b/spec/workers/build_success_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe BuildSuccessWorker do diff --git a/spec/workers/build_trace_sections_worker_spec.rb b/spec/workers/build_trace_sections_worker_spec.rb index 45243f45547..97fc0a2da0c 100644 --- a/spec/workers/build_trace_sections_worker_spec.rb +++ b/spec/workers/build_trace_sections_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe BuildTraceSectionsWorker do diff --git a/spec/workers/ci/archive_traces_cron_worker_spec.rb b/spec/workers/ci/archive_traces_cron_worker_spec.rb index 478fb7d2c0f..eca6cf5235f 100644 --- a/spec/workers/ci/archive_traces_cron_worker_spec.rb +++ b/spec/workers/ci/archive_traces_cron_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::ArchiveTracesCronWorker do diff --git a/spec/workers/ci/build_schedule_worker_spec.rb b/spec/workers/ci/build_schedule_worker_spec.rb index 4a3fe84d7f7..647f9763fed 100644 --- a/spec/workers/ci/build_schedule_worker_spec.rb +++ b/spec/workers/ci/build_schedule_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Ci::BuildScheduleWorker do diff --git a/spec/workers/cluster_provision_worker_spec.rb b/spec/workers/cluster_provision_worker_spec.rb index da32f29fec0..9cc2ad12bfc 100644 --- a/spec/workers/cluster_provision_worker_spec.rb +++ b/spec/workers/cluster_provision_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ClusterProvisionWorker do diff --git a/spec/workers/cluster_wait_for_ingress_ip_address_worker_spec.rb b/spec/workers/cluster_wait_for_ingress_ip_address_worker_spec.rb index 2e2e9afd25a..a9ffdfb085e 100644 --- a/spec/workers/cluster_wait_for_ingress_ip_address_worker_spec.rb +++ b/spec/workers/cluster_wait_for_ingress_ip_address_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ClusterWaitForIngressIpAddressWorker do diff --git a/spec/workers/concerns/application_worker_spec.rb b/spec/workers/concerns/application_worker_spec.rb index 901d77178bc..ae5244e2f62 100644 --- a/spec/workers/concerns/application_worker_spec.rb +++ b/spec/workers/concerns/application_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ApplicationWorker do diff --git a/spec/workers/concerns/cluster_queue_spec.rb b/spec/workers/concerns/cluster_queue_spec.rb index 4118b9aa194..732d55dfbde 100644 --- a/spec/workers/concerns/cluster_queue_spec.rb +++ b/spec/workers/concerns/cluster_queue_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ClusterQueue do diff --git a/spec/workers/concerns/cronjob_queue_spec.rb b/spec/workers/concerns/cronjob_queue_spec.rb index c042a52f41f..cf4d47b7500 100644 --- a/spec/workers/concerns/cronjob_queue_spec.rb +++ b/spec/workers/concerns/cronjob_queue_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe CronjobQueue do diff --git a/spec/workers/concerns/gitlab/github_import/notify_upon_death_spec.rb b/spec/workers/concerns/gitlab/github_import/notify_upon_death_spec.rb index 4b9aa9a7ef8..200cdffd560 100644 --- a/spec/workers/concerns/gitlab/github_import/notify_upon_death_spec.rb +++ b/spec/workers/concerns/gitlab/github_import/notify_upon_death_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::NotifyUponDeath do diff --git a/spec/workers/concerns/gitlab/github_import/object_importer_spec.rb b/spec/workers/concerns/gitlab/github_import/object_importer_spec.rb index 9c187bead0a..51b685b5792 100644 --- a/spec/workers/concerns/gitlab/github_import/object_importer_spec.rb +++ b/spec/workers/concerns/gitlab/github_import/object_importer_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::ObjectImporter do diff --git a/spec/workers/concerns/gitlab/github_import/queue_spec.rb b/spec/workers/concerns/gitlab/github_import/queue_spec.rb index a96f583aff7..d262bc2e05c 100644 --- a/spec/workers/concerns/gitlab/github_import/queue_spec.rb +++ b/spec/workers/concerns/gitlab/github_import/queue_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::Queue do diff --git a/spec/workers/concerns/gitlab/github_import/rescheduling_methods_spec.rb b/spec/workers/concerns/gitlab/github_import/rescheduling_methods_spec.rb index 8de4059c4ae..294eacf09ab 100644 --- a/spec/workers/concerns/gitlab/github_import/rescheduling_methods_spec.rb +++ b/spec/workers/concerns/gitlab/github_import/rescheduling_methods_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::ReschedulingMethods do diff --git a/spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb b/spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb index d85a87f2cb0..f9081a875b5 100644 --- a/spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb +++ b/spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::StageMethods do diff --git a/spec/workers/concerns/pipeline_background_queue_spec.rb b/spec/workers/concerns/pipeline_background_queue_spec.rb index 24c0a3c6a20..78ceafb359f 100644 --- a/spec/workers/concerns/pipeline_background_queue_spec.rb +++ b/spec/workers/concerns/pipeline_background_queue_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PipelineBackgroundQueue do diff --git a/spec/workers/concerns/pipeline_queue_spec.rb b/spec/workers/concerns/pipeline_queue_spec.rb index a312b307fce..eedfceb8bf0 100644 --- a/spec/workers/concerns/pipeline_queue_spec.rb +++ b/spec/workers/concerns/pipeline_queue_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PipelineQueue do diff --git a/spec/workers/concerns/project_import_options_spec.rb b/spec/workers/concerns/project_import_options_spec.rb index 3699fd83a9a..c5fbcfb5fb0 100644 --- a/spec/workers/concerns/project_import_options_spec.rb +++ b/spec/workers/concerns/project_import_options_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProjectImportOptions do diff --git a/spec/workers/concerns/repository_check_queue_spec.rb b/spec/workers/concerns/repository_check_queue_spec.rb index d2eeecfc9a8..55ed71f124c 100644 --- a/spec/workers/concerns/repository_check_queue_spec.rb +++ b/spec/workers/concerns/repository_check_queue_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe RepositoryCheckQueue do diff --git a/spec/workers/concerns/waitable_worker_spec.rb b/spec/workers/concerns/waitable_worker_spec.rb index ce38cde9208..37fadd6ac02 100644 --- a/spec/workers/concerns/waitable_worker_spec.rb +++ b/spec/workers/concerns/waitable_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe WaitableWorker do diff --git a/spec/workers/create_gpg_signature_worker_spec.rb b/spec/workers/create_gpg_signature_worker_spec.rb index f5479e57260..ae09b4b77f1 100644 --- a/spec/workers/create_gpg_signature_worker_spec.rb +++ b/spec/workers/create_gpg_signature_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe CreateGpgSignatureWorker do diff --git a/spec/workers/create_note_diff_file_worker_spec.rb b/spec/workers/create_note_diff_file_worker_spec.rb index 0ac946a1232..e35aaa7d593 100644 --- a/spec/workers/create_note_diff_file_worker_spec.rb +++ b/spec/workers/create_note_diff_file_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe CreateNoteDiffFileWorker do diff --git a/spec/workers/create_pipeline_worker_spec.rb b/spec/workers/create_pipeline_worker_spec.rb index 02cb0f46cb4..62a17da80c0 100644 --- a/spec/workers/create_pipeline_worker_spec.rb +++ b/spec/workers/create_pipeline_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe CreatePipelineWorker do diff --git a/spec/workers/delete_diff_files_worker_spec.rb b/spec/workers/delete_diff_files_worker_spec.rb index e0edd313922..9f8b20df48e 100644 --- a/spec/workers/delete_diff_files_worker_spec.rb +++ b/spec/workers/delete_diff_files_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe DeleteDiffFilesWorker do diff --git a/spec/workers/delete_merged_branches_worker_spec.rb b/spec/workers/delete_merged_branches_worker_spec.rb index 39009d9e4b2..a218ca921d9 100644 --- a/spec/workers/delete_merged_branches_worker_spec.rb +++ b/spec/workers/delete_merged_branches_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe DeleteMergedBranchesWorker do diff --git a/spec/workers/delete_user_worker_spec.rb b/spec/workers/delete_user_worker_spec.rb index 06d9e125105..c963b886e62 100644 --- a/spec/workers/delete_user_worker_spec.rb +++ b/spec/workers/delete_user_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe DeleteUserWorker do diff --git a/spec/workers/deployments/success_worker_spec.rb b/spec/workers/deployments/success_worker_spec.rb index ba7d45eca01..1c68922b03d 100644 --- a/spec/workers/deployments/success_worker_spec.rb +++ b/spec/workers/deployments/success_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Deployments::SuccessWorker do diff --git a/spec/workers/detect_repository_languages_worker_spec.rb b/spec/workers/detect_repository_languages_worker_spec.rb index ff3878fbc8e..dbf32555985 100644 --- a/spec/workers/detect_repository_languages_worker_spec.rb +++ b/spec/workers/detect_repository_languages_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe DetectRepositoryLanguagesWorker do diff --git a/spec/workers/email_receiver_worker_spec.rb b/spec/workers/email_receiver_worker_spec.rb index 045135255d6..f8a31fcdee6 100644 --- a/spec/workers/email_receiver_worker_spec.rb +++ b/spec/workers/email_receiver_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe EmailReceiverWorker, :mailer do diff --git a/spec/workers/emails_on_push_worker_spec.rb b/spec/workers/emails_on_push_worker_spec.rb index 05b4fb49ea3..0f87df89c29 100644 --- a/spec/workers/emails_on_push_worker_spec.rb +++ b/spec/workers/emails_on_push_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe EmailsOnPushWorker, :mailer do diff --git a/spec/workers/every_sidekiq_worker_spec.rb b/spec/workers/every_sidekiq_worker_spec.rb index ebe02373275..8fddd8540ef 100644 --- a/spec/workers/every_sidekiq_worker_spec.rb +++ b/spec/workers/every_sidekiq_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'Every Sidekiq worker' do diff --git a/spec/workers/expire_build_artifacts_worker_spec.rb b/spec/workers/expire_build_artifacts_worker_spec.rb index 27995cf1611..74d6b5605d1 100644 --- a/spec/workers/expire_build_artifacts_worker_spec.rb +++ b/spec/workers/expire_build_artifacts_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ExpireBuildArtifactsWorker do diff --git a/spec/workers/expire_build_instance_artifacts_worker_spec.rb b/spec/workers/expire_build_instance_artifacts_worker_spec.rb index e1a56c72162..bdb5a3801d9 100644 --- a/spec/workers/expire_build_instance_artifacts_worker_spec.rb +++ b/spec/workers/expire_build_instance_artifacts_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ExpireBuildInstanceArtifactsWorker do diff --git a/spec/workers/expire_job_cache_worker_spec.rb b/spec/workers/expire_job_cache_worker_spec.rb index 1b614342a18..6ac285ca944 100644 --- a/spec/workers/expire_job_cache_worker_spec.rb +++ b/spec/workers/expire_job_cache_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ExpireJobCacheWorker do diff --git a/spec/workers/expire_pipeline_cache_worker_spec.rb b/spec/workers/expire_pipeline_cache_worker_spec.rb index 54c9a69d329..c98b69ef76c 100644 --- a/spec/workers/expire_pipeline_cache_worker_spec.rb +++ b/spec/workers/expire_pipeline_cache_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ExpirePipelineCacheWorker do diff --git a/spec/workers/git_garbage_collect_worker_spec.rb b/spec/workers/git_garbage_collect_worker_spec.rb index 4895a968d6e..2459638c3e6 100644 --- a/spec/workers/git_garbage_collect_worker_spec.rb +++ b/spec/workers/git_garbage_collect_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'fileutils' require 'spec_helper' diff --git a/spec/workers/gitlab/github_import/advance_stage_worker_spec.rb b/spec/workers/gitlab/github_import/advance_stage_worker_spec.rb index fc7aafbc0c9..b1647d8c7df 100644 --- a/spec/workers/gitlab/github_import/advance_stage_worker_spec.rb +++ b/spec/workers/gitlab/github_import/advance_stage_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::AdvanceStageWorker, :clean_gitlab_redis_shared_state do diff --git a/spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb b/spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb index 5b1c6b6010a..42d69ff6166 100644 --- a/spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb +++ b/spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::ImportDiffNoteWorker do diff --git a/spec/workers/gitlab/github_import/import_issue_worker_spec.rb b/spec/workers/gitlab/github_import/import_issue_worker_spec.rb index ab070d6d081..06a573e16b7 100644 --- a/spec/workers/gitlab/github_import/import_issue_worker_spec.rb +++ b/spec/workers/gitlab/github_import/import_issue_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::ImportIssueWorker do diff --git a/spec/workers/gitlab/github_import/import_note_worker_spec.rb b/spec/workers/gitlab/github_import/import_note_worker_spec.rb index 3a30f06bb2d..5110c3ff11b 100644 --- a/spec/workers/gitlab/github_import/import_note_worker_spec.rb +++ b/spec/workers/gitlab/github_import/import_note_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::ImportNoteWorker do diff --git a/spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb b/spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb index 3cccd7cab21..d46e381fc51 100644 --- a/spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb +++ b/spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::ImportPullRequestWorker do diff --git a/spec/workers/gitlab/github_import/refresh_import_jid_worker_spec.rb b/spec/workers/gitlab/github_import/refresh_import_jid_worker_spec.rb index 7ff133f1049..fa4ded8e42f 100644 --- a/spec/workers/gitlab/github_import/refresh_import_jid_worker_spec.rb +++ b/spec/workers/gitlab/github_import/refresh_import_jid_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::RefreshImportJidWorker do diff --git a/spec/workers/gitlab/github_import/stage/finish_import_worker_spec.rb b/spec/workers/gitlab/github_import/stage/finish_import_worker_spec.rb index 91e0cddb5d8..35a856802c2 100644 --- a/spec/workers/gitlab/github_import/stage/finish_import_worker_spec.rb +++ b/spec/workers/gitlab/github_import/stage/finish_import_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::Stage::FinishImportWorker do diff --git a/spec/workers/gitlab/github_import/stage/import_base_data_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_base_data_worker_spec.rb index ad6154cc4a4..0c7fc2a164e 100644 --- a/spec/workers/gitlab/github_import/stage/import_base_data_worker_spec.rb +++ b/spec/workers/gitlab/github_import/stage/import_base_data_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::Stage::ImportBaseDataWorker do diff --git a/spec/workers/gitlab/github_import/stage/import_issues_and_diff_notes_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_issues_and_diff_notes_worker_spec.rb index ab347f5b75b..5d96f562c30 100644 --- a/spec/workers/gitlab/github_import/stage/import_issues_and_diff_notes_worker_spec.rb +++ b/spec/workers/gitlab/github_import/stage/import_issues_and_diff_notes_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::Stage::ImportIssuesAndDiffNotesWorker do diff --git a/spec/workers/gitlab/github_import/stage/import_lfs_objects_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_lfs_objects_worker_spec.rb index b19884d7991..e7c9dabb292 100644 --- a/spec/workers/gitlab/github_import/stage/import_lfs_objects_worker_spec.rb +++ b/spec/workers/gitlab/github_import/stage/import_lfs_objects_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::Stage::ImportLfsObjectsWorker do diff --git a/spec/workers/gitlab/github_import/stage/import_notes_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_notes_worker_spec.rb index 94cff9e4e80..90590a45900 100644 --- a/spec/workers/gitlab/github_import/stage/import_notes_worker_spec.rb +++ b/spec/workers/gitlab/github_import/stage/import_notes_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::Stage::ImportNotesWorker do diff --git a/spec/workers/gitlab/github_import/stage/import_pull_requests_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_pull_requests_worker_spec.rb index 1fbb073a34a..15d485f1018 100644 --- a/spec/workers/gitlab/github_import/stage/import_pull_requests_worker_spec.rb +++ b/spec/workers/gitlab/github_import/stage/import_pull_requests_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::Stage::ImportPullRequestsWorker do diff --git a/spec/workers/gitlab/github_import/stage/import_repository_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_repository_worker_spec.rb index adab535ac05..6d47d73b92e 100644 --- a/spec/workers/gitlab/github_import/stage/import_repository_worker_spec.rb +++ b/spec/workers/gitlab/github_import/stage/import_repository_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::GithubImport::Stage::ImportRepositoryWorker do diff --git a/spec/workers/gitlab_shell_worker_spec.rb b/spec/workers/gitlab_shell_worker_spec.rb index 6b222af454d..0758cfc4ee2 100644 --- a/spec/workers/gitlab_shell_worker_spec.rb +++ b/spec/workers/gitlab_shell_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe GitlabShellWorker do diff --git a/spec/workers/gitlab_usage_ping_worker_spec.rb b/spec/workers/gitlab_usage_ping_worker_spec.rb index 49b4e04dc7c..aff5d112cdd 100644 --- a/spec/workers/gitlab_usage_ping_worker_spec.rb +++ b/spec/workers/gitlab_usage_ping_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe GitlabUsagePingWorker do diff --git a/spec/workers/group_destroy_worker_spec.rb b/spec/workers/group_destroy_worker_spec.rb index a170c84ab12..90a4150a31a 100644 --- a/spec/workers/group_destroy_worker_spec.rb +++ b/spec/workers/group_destroy_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe GroupDestroyWorker do diff --git a/spec/workers/hashed_storage/migrator_worker_spec.rb b/spec/workers/hashed_storage/migrator_worker_spec.rb index a85f820a3eb..a318cdd003e 100644 --- a/spec/workers/hashed_storage/migrator_worker_spec.rb +++ b/spec/workers/hashed_storage/migrator_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe HashedStorage::MigratorWorker do diff --git a/spec/workers/hashed_storage/project_migrate_worker_spec.rb b/spec/workers/hashed_storage/project_migrate_worker_spec.rb index 340e722aa7e..f266c7dbe8c 100644 --- a/spec/workers/hashed_storage/project_migrate_worker_spec.rb +++ b/spec/workers/hashed_storage/project_migrate_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe HashedStorage::ProjectMigrateWorker, :clean_gitlab_redis_shared_state do diff --git a/spec/workers/invalid_gpg_signature_update_worker_spec.rb b/spec/workers/invalid_gpg_signature_update_worker_spec.rb index 5972696515b..4f727469ea8 100644 --- a/spec/workers/invalid_gpg_signature_update_worker_spec.rb +++ b/spec/workers/invalid_gpg_signature_update_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe InvalidGpgSignatureUpdateWorker do diff --git a/spec/workers/issue_due_scheduler_worker_spec.rb b/spec/workers/issue_due_scheduler_worker_spec.rb index 2710267d384..61ad8330840 100644 --- a/spec/workers/issue_due_scheduler_worker_spec.rb +++ b/spec/workers/issue_due_scheduler_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe IssueDueSchedulerWorker do diff --git a/spec/workers/mail_scheduler/issue_due_worker_spec.rb b/spec/workers/mail_scheduler/issue_due_worker_spec.rb index 1026ae5b4bf..fa17775e9f2 100644 --- a/spec/workers/mail_scheduler/issue_due_worker_spec.rb +++ b/spec/workers/mail_scheduler/issue_due_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe MailScheduler::IssueDueWorker do diff --git a/spec/workers/mail_scheduler/notification_service_worker_spec.rb b/spec/workers/mail_scheduler/notification_service_worker_spec.rb index 5cfba01850c..0729c5f9ffb 100644 --- a/spec/workers/mail_scheduler/notification_service_worker_spec.rb +++ b/spec/workers/mail_scheduler/notification_service_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe MailScheduler::NotificationServiceWorker do diff --git a/spec/workers/merge_worker_spec.rb b/spec/workers/merge_worker_spec.rb index b57c275c770..138a99abde6 100644 --- a/spec/workers/merge_worker_spec.rb +++ b/spec/workers/merge_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe MergeWorker do diff --git a/spec/workers/migrate_external_diffs_worker_spec.rb b/spec/workers/migrate_external_diffs_worker_spec.rb new file mode 100644 index 00000000000..88d48cad14b --- /dev/null +++ b/spec/workers/migrate_external_diffs_worker_spec.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe MigrateExternalDiffsWorker do + let(:worker) { described_class.new } + let(:diff) { create(:merge_request).merge_request_diff } + + describe '#perform' do + it 'migrates the listed diff' do + expect_next_instance_of(MergeRequests::MigrateExternalDiffsService) do |instance| + expect(instance.diff).to eq(diff) + expect(instance).to receive(:execute) + end + + worker.perform(diff.id) + end + + it 'does nothing if the diff is missing' do + diff.destroy + + worker.perform(diff.id) + end + end +end diff --git a/spec/workers/namespaceless_project_destroy_worker_spec.rb b/spec/workers/namespaceless_project_destroy_worker_spec.rb index 2f21a1321e1..4fbda37e268 100644 --- a/spec/workers/namespaceless_project_destroy_worker_spec.rb +++ b/spec/workers/namespaceless_project_destroy_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe NamespacelessProjectDestroyWorker do diff --git a/spec/workers/new_issue_worker_spec.rb b/spec/workers/new_issue_worker_spec.rb index baa8ddb59e5..88a75ce5b70 100644 --- a/spec/workers/new_issue_worker_spec.rb +++ b/spec/workers/new_issue_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe NewIssueWorker do diff --git a/spec/workers/new_merge_request_worker_spec.rb b/spec/workers/new_merge_request_worker_spec.rb index c3f29a40d58..d078ddd07d9 100644 --- a/spec/workers/new_merge_request_worker_spec.rb +++ b/spec/workers/new_merge_request_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe NewMergeRequestWorker do diff --git a/spec/workers/new_note_worker_spec.rb b/spec/workers/new_note_worker_spec.rb index 575361c93d4..2966a201a62 100644 --- a/spec/workers/new_note_worker_spec.rb +++ b/spec/workers/new_note_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe NewNoteWorker do diff --git a/spec/workers/pages_domain_verification_cron_worker_spec.rb b/spec/workers/pages_domain_verification_cron_worker_spec.rb index 8f780428c82..9b479da1cb6 100644 --- a/spec/workers/pages_domain_verification_cron_worker_spec.rb +++ b/spec/workers/pages_domain_verification_cron_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PagesDomainVerificationCronWorker do diff --git a/spec/workers/pages_domain_verification_worker_spec.rb b/spec/workers/pages_domain_verification_worker_spec.rb index 372fc95ab4a..2f23dcb487f 100644 --- a/spec/workers/pages_domain_verification_worker_spec.rb +++ b/spec/workers/pages_domain_verification_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PagesDomainVerificationWorker do diff --git a/spec/workers/pipeline_hooks_worker_spec.rb b/spec/workers/pipeline_hooks_worker_spec.rb index 035e329839f..60df08f40da 100644 --- a/spec/workers/pipeline_hooks_worker_spec.rb +++ b/spec/workers/pipeline_hooks_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PipelineHooksWorker do diff --git a/spec/workers/pipeline_metrics_worker_spec.rb b/spec/workers/pipeline_metrics_worker_spec.rb index 896f9e6e7f2..6beecbcd114 100644 --- a/spec/workers/pipeline_metrics_worker_spec.rb +++ b/spec/workers/pipeline_metrics_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PipelineMetricsWorker do diff --git a/spec/workers/pipeline_notification_worker_spec.rb b/spec/workers/pipeline_notification_worker_spec.rb index eb539ffd893..98b0f139fe2 100644 --- a/spec/workers/pipeline_notification_worker_spec.rb +++ b/spec/workers/pipeline_notification_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PipelineNotificationWorker, :mailer do diff --git a/spec/workers/pipeline_process_worker_spec.rb b/spec/workers/pipeline_process_worker_spec.rb index 86e9d7f6684..d33cf72e51e 100644 --- a/spec/workers/pipeline_process_worker_spec.rb +++ b/spec/workers/pipeline_process_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PipelineProcessWorker do diff --git a/spec/workers/pipeline_schedule_worker_spec.rb b/spec/workers/pipeline_schedule_worker_spec.rb index ff408427926..f23910d23be 100644 --- a/spec/workers/pipeline_schedule_worker_spec.rb +++ b/spec/workers/pipeline_schedule_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PipelineScheduleWorker do diff --git a/spec/workers/pipeline_success_worker_spec.rb b/spec/workers/pipeline_success_worker_spec.rb index d1c84adda6f..4cbe384b47a 100644 --- a/spec/workers/pipeline_success_worker_spec.rb +++ b/spec/workers/pipeline_success_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PipelineSuccessWorker do diff --git a/spec/workers/pipeline_update_worker_spec.rb b/spec/workers/pipeline_update_worker_spec.rb index 0b456cfd0da..0225e4a9601 100644 --- a/spec/workers/pipeline_update_worker_spec.rb +++ b/spec/workers/pipeline_update_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PipelineUpdateWorker do diff --git a/spec/workers/plugin_worker_spec.rb b/spec/workers/plugin_worker_spec.rb index 9238a8199bc..ca6c9986131 100644 --- a/spec/workers/plugin_worker_spec.rb +++ b/spec/workers/plugin_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PluginWorker do diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb index caae46a3175..66958a4c116 100644 --- a/spec/workers/post_receive_spec.rb +++ b/spec/workers/post_receive_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PostReceive do @@ -33,8 +35,8 @@ describe PostReceive do describe "#process_project_changes" do context 'empty changes' do it "does not call any PushService but runs after project hooks" do - expect(GitPushService).not_to receive(:new) - expect(GitTagPushService).not_to receive(:new) + expect(Git::BranchPushService).not_to receive(:new) + expect(Git::TagPushService).not_to receive(:new) expect_next_instance_of(SystemHooksService) { |service| expect(service).to receive(:execute_hooks) } described_class.new.perform(gl_repository, key_id, "") @@ -45,8 +47,8 @@ describe PostReceive do let!(:key_id) { "" } it 'returns false' do - expect(GitPushService).not_to receive(:new) - expect(GitTagPushService).not_to receive(:new) + expect(Git::BranchPushService).not_to receive(:new) + expect(Git::TagPushService).not_to receive(:new) expect(described_class.new.perform(gl_repository, key_id, base64_changes)).to be false end @@ -60,9 +62,9 @@ describe PostReceive do context "branches" do let(:changes) { "123456 789012 refs/heads/tést" } - it "calls GitPushService" do - expect_any_instance_of(GitPushService).to receive(:execute).and_return(true) - expect_any_instance_of(GitTagPushService).not_to receive(:execute) + it "calls Git::BranchPushService" do + expect_any_instance_of(Git::BranchPushService).to receive(:execute).and_return(true) + expect_any_instance_of(Git::TagPushService).not_to receive(:execute) described_class.new.perform(gl_repository, key_id, base64_changes) end end @@ -70,9 +72,9 @@ describe PostReceive do context "tags" do let(:changes) { "123456 789012 refs/tags/tag" } - it "calls GitTagPushService" do - expect_any_instance_of(GitPushService).not_to receive(:execute) - expect_any_instance_of(GitTagPushService).to receive(:execute).and_return(true) + it "calls Git::TagPushService" do + expect_any_instance_of(Git::BranchPushService).not_to receive(:execute) + expect_any_instance_of(Git::TagPushService).to receive(:execute).and_return(true) described_class.new.perform(gl_repository, key_id, base64_changes) end end @@ -81,8 +83,8 @@ describe PostReceive do let(:changes) { "123456 789012 refs/merge-requests/123" } it "does not call any of the services" do - expect_any_instance_of(GitPushService).not_to receive(:execute) - expect_any_instance_of(GitTagPushService).not_to receive(:execute) + expect_any_instance_of(Git::BranchPushService).not_to receive(:execute) + expect_any_instance_of(Git::TagPushService).not_to receive(:execute) described_class.new.perform(gl_repository, key_id, base64_changes) end end @@ -125,7 +127,7 @@ describe PostReceive do allow_any_instance_of(Gitlab::DataBuilder::Repository).to receive(:update).and_return(fake_hook_data) # silence hooks so we can isolate allow_any_instance_of(Key).to receive(:post_create_hook).and_return(true) - allow_any_instance_of(GitPushService).to receive(:execute).and_return(true) + allow_any_instance_of(Git::BranchPushService).to receive(:execute).and_return(true) end it 'calls SystemHooksService' do diff --git a/spec/workers/process_commit_worker_spec.rb b/spec/workers/process_commit_worker_spec.rb index 2d071c181c2..47bac63511e 100644 --- a/spec/workers/process_commit_worker_spec.rb +++ b/spec/workers/process_commit_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProcessCommitWorker do diff --git a/spec/workers/project_cache_worker_spec.rb b/spec/workers/project_cache_worker_spec.rb index b9b5445562f..a7353227043 100644 --- a/spec/workers/project_cache_worker_spec.rb +++ b/spec/workers/project_cache_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProjectCacheWorker do diff --git a/spec/workers/project_destroy_worker_spec.rb b/spec/workers/project_destroy_worker_spec.rb index 6132f145f8d..ec40900a5b7 100644 --- a/spec/workers/project_destroy_worker_spec.rb +++ b/spec/workers/project_destroy_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProjectDestroyWorker do diff --git a/spec/workers/project_export_worker_spec.rb b/spec/workers/project_export_worker_spec.rb index 8899969c178..8065087796c 100644 --- a/spec/workers/project_export_worker_spec.rb +++ b/spec/workers/project_export_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ProjectExportWorker do diff --git a/spec/workers/propagate_service_template_worker_spec.rb b/spec/workers/propagate_service_template_worker_spec.rb index af1fb80a51d..fb4ced77832 100644 --- a/spec/workers/propagate_service_template_worker_spec.rb +++ b/spec/workers/propagate_service_template_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PropagateServiceTemplateWorker do diff --git a/spec/workers/prune_old_events_worker_spec.rb b/spec/workers/prune_old_events_worker_spec.rb index ea2b6ae229e..f1eef1923bf 100644 --- a/spec/workers/prune_old_events_worker_spec.rb +++ b/spec/workers/prune_old_events_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PruneOldEventsWorker do diff --git a/spec/workers/prune_web_hook_logs_worker_spec.rb b/spec/workers/prune_web_hook_logs_worker_spec.rb index b3ec71d4a00..e57334967fd 100644 --- a/spec/workers/prune_web_hook_logs_worker_spec.rb +++ b/spec/workers/prune_web_hook_logs_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe PruneWebHookLogsWorker do diff --git a/spec/workers/reactive_caching_worker_spec.rb b/spec/workers/reactive_caching_worker_spec.rb index 3da851de067..2395e6ec947 100644 --- a/spec/workers/reactive_caching_worker_spec.rb +++ b/spec/workers/reactive_caching_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe ReactiveCachingWorker do diff --git a/spec/workers/rebase_worker_spec.rb b/spec/workers/rebase_worker_spec.rb index 900332ed6b3..578b8cf7451 100644 --- a/spec/workers/rebase_worker_spec.rb +++ b/spec/workers/rebase_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe RebaseWorker, '#perform' do diff --git a/spec/workers/remote_mirror_notification_worker_spec.rb b/spec/workers/remote_mirror_notification_worker_spec.rb index e3db10ed645..5182f67b4af 100644 --- a/spec/workers/remote_mirror_notification_worker_spec.rb +++ b/spec/workers/remote_mirror_notification_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe RemoteMirrorNotificationWorker, :mailer do diff --git a/spec/workers/remove_expired_group_links_worker_spec.rb b/spec/workers/remove_expired_group_links_worker_spec.rb index 689bc3d27b4..10d9aa37dee 100644 --- a/spec/workers/remove_expired_group_links_worker_spec.rb +++ b/spec/workers/remove_expired_group_links_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe RemoveExpiredGroupLinksWorker do diff --git a/spec/workers/remove_expired_members_worker_spec.rb b/spec/workers/remove_expired_members_worker_spec.rb index 058fdf4c009..69a5725bb35 100644 --- a/spec/workers/remove_expired_members_worker_spec.rb +++ b/spec/workers/remove_expired_members_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe RemoveExpiredMembersWorker do diff --git a/spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb b/spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb index 57f83c1dbe9..0e21933a9a5 100644 --- a/spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb +++ b/spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe RemoveUnreferencedLfsObjectsWorker do diff --git a/spec/workers/repository_check/batch_worker_spec.rb b/spec/workers/repository_check/batch_worker_spec.rb index 50b93fce2dc..051c6a5d141 100644 --- a/spec/workers/repository_check/batch_worker_spec.rb +++ b/spec/workers/repository_check/batch_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe RepositoryCheck::BatchWorker do diff --git a/spec/workers/repository_check/clear_worker_spec.rb b/spec/workers/repository_check/clear_worker_spec.rb index 1c49415d46c..7ad9e287204 100644 --- a/spec/workers/repository_check/clear_worker_spec.rb +++ b/spec/workers/repository_check/clear_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe RepositoryCheck::ClearWorker do diff --git a/spec/workers/repository_check/dispatch_worker_spec.rb b/spec/workers/repository_check/dispatch_worker_spec.rb index 7877429aa8f..03efb6a0a80 100644 --- a/spec/workers/repository_check/dispatch_worker_spec.rb +++ b/spec/workers/repository_check/dispatch_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe RepositoryCheck::DispatchWorker do diff --git a/spec/workers/repository_check/single_repository_worker_spec.rb b/spec/workers/repository_check/single_repository_worker_spec.rb index f11875cffd1..65e1c5e9d5d 100644 --- a/spec/workers/repository_check/single_repository_worker_spec.rb +++ b/spec/workers/repository_check/single_repository_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' require 'fileutils' diff --git a/spec/workers/repository_cleanup_worker_spec.rb b/spec/workers/repository_cleanup_worker_spec.rb index 3adae0b6cfa..e58664cf22a 100644 --- a/spec/workers/repository_cleanup_worker_spec.rb +++ b/spec/workers/repository_cleanup_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe RepositoryCleanupWorker do diff --git a/spec/workers/repository_fork_worker_spec.rb b/spec/workers/repository_fork_worker_spec.rb index 31bfe88d0bd..26fd67adfaa 100644 --- a/spec/workers/repository_fork_worker_spec.rb +++ b/spec/workers/repository_fork_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe RepositoryForkWorker do diff --git a/spec/workers/repository_import_worker_spec.rb b/spec/workers/repository_import_worker_spec.rb index 87ac4bc05c1..b8767af8eee 100644 --- a/spec/workers/repository_import_worker_spec.rb +++ b/spec/workers/repository_import_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe RepositoryImportWorker do diff --git a/spec/workers/repository_remove_remote_worker_spec.rb b/spec/workers/repository_remove_remote_worker_spec.rb index 6ddb653d142..6eba5c50960 100644 --- a/spec/workers/repository_remove_remote_worker_spec.rb +++ b/spec/workers/repository_remove_remote_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe RepositoryRemoveRemoteWorker do diff --git a/spec/workers/repository_update_remote_mirror_worker_spec.rb b/spec/workers/repository_update_remote_mirror_worker_spec.rb index b582a3650b6..4de51ecb3e9 100644 --- a/spec/workers/repository_update_remote_mirror_worker_spec.rb +++ b/spec/workers/repository_update_remote_mirror_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe RepositoryUpdateRemoteMirrorWorker do diff --git a/spec/workers/run_pipeline_schedule_worker_spec.rb b/spec/workers/run_pipeline_schedule_worker_spec.rb index 481a84837f9..690af22f4dc 100644 --- a/spec/workers/run_pipeline_schedule_worker_spec.rb +++ b/spec/workers/run_pipeline_schedule_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe RunPipelineScheduleWorker do diff --git a/spec/workers/schedule_migrate_external_diffs_worker_spec.rb b/spec/workers/schedule_migrate_external_diffs_worker_spec.rb new file mode 100644 index 00000000000..9d6fecc9f4e --- /dev/null +++ b/spec/workers/schedule_migrate_external_diffs_worker_spec.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ScheduleMigrateExternalDiffsWorker do + include ExclusiveLeaseHelpers + + let(:worker) { described_class.new } + + describe '#perform' do + it 'triggers a scan for diffs to migrate' do + expect(MergeRequests::MigrateExternalDiffsService).to receive(:enqueue!) + + worker.perform + end + + it 'will not run if the lease is already taken' do + stub_exclusive_lease_taken('schedule_migrate_external_diffs_worker', timeout: 2.hours) + + expect(MergeRequests::MigrateExternalDiffsService).not_to receive(:enqueue!) + + worker.perform + end + end +end diff --git a/spec/workers/stage_update_worker_spec.rb b/spec/workers/stage_update_worker_spec.rb index 7bc76c79464..429d42bac29 100644 --- a/spec/workers/stage_update_worker_spec.rb +++ b/spec/workers/stage_update_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe StageUpdateWorker do diff --git a/spec/workers/stuck_ci_jobs_worker_spec.rb b/spec/workers/stuck_ci_jobs_worker_spec.rb index e09b8e5b964..72de62f1188 100644 --- a/spec/workers/stuck_ci_jobs_worker_spec.rb +++ b/spec/workers/stuck_ci_jobs_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe StuckCiJobsWorker do diff --git a/spec/workers/stuck_import_jobs_worker_spec.rb b/spec/workers/stuck_import_jobs_worker_spec.rb index e94d2be9850..dcb8e59ed28 100644 --- a/spec/workers/stuck_import_jobs_worker_spec.rb +++ b/spec/workers/stuck_import_jobs_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe StuckImportJobsWorker do diff --git a/spec/workers/stuck_merge_jobs_worker_spec.rb b/spec/workers/stuck_merge_jobs_worker_spec.rb index 5aaff27a6b2..09efed6d2cf 100644 --- a/spec/workers/stuck_merge_jobs_worker_spec.rb +++ b/spec/workers/stuck_merge_jobs_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe StuckMergeJobsWorker do diff --git a/spec/workers/system_hook_push_worker_spec.rb b/spec/workers/system_hook_push_worker_spec.rb index b1d446ed25f..890a622d11a 100644 --- a/spec/workers/system_hook_push_worker_spec.rb +++ b/spec/workers/system_hook_push_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe SystemHookPushWorker do diff --git a/spec/workers/todos_destroyer/confidential_issue_worker_spec.rb b/spec/workers/todos_destroyer/confidential_issue_worker_spec.rb index 9d7c0b8f560..18876b71615 100644 --- a/spec/workers/todos_destroyer/confidential_issue_worker_spec.rb +++ b/spec/workers/todos_destroyer/confidential_issue_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe TodosDestroyer::ConfidentialIssueWorker do diff --git a/spec/workers/todos_destroyer/entity_leave_worker_spec.rb b/spec/workers/todos_destroyer/entity_leave_worker_spec.rb index 955447906aa..cb14fac0910 100644 --- a/spec/workers/todos_destroyer/entity_leave_worker_spec.rb +++ b/spec/workers/todos_destroyer/entity_leave_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe TodosDestroyer::EntityLeaveWorker do diff --git a/spec/workers/todos_destroyer/group_private_worker_spec.rb b/spec/workers/todos_destroyer/group_private_worker_spec.rb index fcc38989ced..d9a240136d5 100644 --- a/spec/workers/todos_destroyer/group_private_worker_spec.rb +++ b/spec/workers/todos_destroyer/group_private_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe TodosDestroyer::GroupPrivateWorker do diff --git a/spec/workers/todos_destroyer/private_features_worker_spec.rb b/spec/workers/todos_destroyer/private_features_worker_spec.rb index 9599f5ee071..abd04acc3bd 100644 --- a/spec/workers/todos_destroyer/private_features_worker_spec.rb +++ b/spec/workers/todos_destroyer/private_features_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe TodosDestroyer::PrivateFeaturesWorker do diff --git a/spec/workers/todos_destroyer/project_private_worker_spec.rb b/spec/workers/todos_destroyer/project_private_worker_spec.rb index 15d926fa9d5..c1bb0438ec3 100644 --- a/spec/workers/todos_destroyer/project_private_worker_spec.rb +++ b/spec/workers/todos_destroyer/project_private_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe TodosDestroyer::ProjectPrivateWorker do diff --git a/spec/workers/trending_projects_worker_spec.rb b/spec/workers/trending_projects_worker_spec.rb index c3c6fdcf2d5..6e524085662 100644 --- a/spec/workers/trending_projects_worker_spec.rb +++ b/spec/workers/trending_projects_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe TrendingProjectsWorker do diff --git a/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb b/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb index d20d926f5a0..c4af829a5e2 100644 --- a/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb +++ b/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe UpdateHeadPipelineForMergeRequestWorker do diff --git a/spec/workers/update_merge_requests_worker_spec.rb b/spec/workers/update_merge_requests_worker_spec.rb index 0b553db0ca4..486dade454a 100644 --- a/spec/workers/update_merge_requests_worker_spec.rb +++ b/spec/workers/update_merge_requests_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe UpdateMergeRequestsWorker do diff --git a/spec/workers/upload_checksum_worker_spec.rb b/spec/workers/upload_checksum_worker_spec.rb index 9e50ce15871..7202c8001b4 100644 --- a/spec/workers/upload_checksum_worker_spec.rb +++ b/spec/workers/upload_checksum_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe UploadChecksumWorker do diff --git a/spec/workers/wait_for_cluster_creation_worker_spec.rb b/spec/workers/wait_for_cluster_creation_worker_spec.rb index 0e92b298178..850eba263a7 100644 --- a/spec/workers/wait_for_cluster_creation_worker_spec.rb +++ b/spec/workers/wait_for_cluster_creation_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe WaitForClusterCreationWorker do diff --git a/yarn.lock b/yarn.lock index 7123650b003..846e29862f1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -638,12 +638,12 @@ lodash "^4.17.11" to-fast-properties "^2.0.0" -"@gitlab/csslab@^1.8.0": - version "1.8.0" - resolved "https://registry.yarnpkg.com/@gitlab/csslab/-/csslab-1.8.0.tgz#54a2457fdc80f006665f0e578a5532780954ccfa" - integrity sha512-RZylRElufH1kwsBQlIDaVcrcXMyD5IEGrU6ABUd8W3LG8/F9jJ4Y3Ys7EPTpK/qFJyx86AutTtFGRxRNlMx85w== +"@gitlab/csslab@^1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@gitlab/csslab/-/csslab-1.9.0.tgz#22fca5b1a30cbd9ca46fc6f9485ecbaba4dc300c" + integrity sha512-Zjayzokm7E2wgxUR/pxIMocdiBB5XHt2PEemdzD8qD+aQmMpMxSyIEMQk5Jq0Wgv+Rd5WXTolTw3kmb9l8ZeJg== dependencies: - bootstrap "4.1.3" + bootstrap "^4.1.3" "@gitlab/eslint-config@^1.4.0": version "1.4.0" @@ -658,10 +658,10 @@ eslint-plugin-promise "^4.0.1" eslint-plugin-vue "^5.0.0" -"@gitlab/svgs@^1.54.0": - version "1.54.0" - resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.54.0.tgz#00320e845efd46716042cde0c348b990d4908daf" - integrity sha512-DR17iy8TM5IbXEacqiDP0p8SuC/J8EL+98xbfVz5BKvRsPHpeZJQNlBF/petIV5d+KWM5A9v3GZTY7uMU7z/JQ== +"@gitlab/svgs@^1.57.0": + version "1.57.0" + resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.57.0.tgz#969ac7bf16337d5de3808fee6fb5c13eefd99478" + integrity sha512-AAVvPDaxCsojmOyVVTyaOcob+bPhtYJ+GbtmmNNCHg2dXYDAEgy3+TYzAfV5fQ08TCZ9DPiKEjDIi2ODh0x/8g== "@gitlab/ui@^3.0.0": version "3.0.0" @@ -707,10 +707,10 @@ resolved "https://registry.yarnpkg.com/@types/events/-/events-1.2.0.tgz#81a6731ce4df43619e5c8c945383b3e62a89ea86" integrity sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA== -"@types/glob@^5": - version "5.0.35" - resolved "https://registry.yarnpkg.com/@types/glob/-/glob-5.0.35.tgz#1ae151c802cece940443b5ac246925c85189f32a" - integrity sha512-wc+VveszMLyMWFvXLkloixT4n0harUIVZjnpzztaZ0nKLuul7Z32iMt2fUFGAaZ4y1XWjFRMtCI5ewvyh4aIeg== +"@types/glob@5 - 7": + version "7.1.1" + resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575" + integrity sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w== dependencies: "@types/events" "*" "@types/minimatch" "*" @@ -791,7 +791,7 @@ resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.0.tgz#8b63ab7f1aa5321248aad5ac890a485656dcea4d" integrity sha512-te5lMAWii1uEJ4FwLjzdlbw3+n0FZNOvFXHxQDKeT0dilh7HOzdMzV2TrJVUzq8ep7J4Na8OUYPRLSQkJHAlrg== -"@vue/component-compiler-utils@^2.0.0", "@vue/component-compiler-utils@^2.4.0": +"@vue/component-compiler-utils@^2.4.0", "@vue/component-compiler-utils@^2.5.1": version "2.6.0" resolved "https://registry.yarnpkg.com/@vue/component-compiler-utils/-/component-compiler-utils-2.6.0.tgz#aa46d2a6f7647440b0b8932434d22f12371e543b" integrity sha512-IHjxt7LsOFYc0DkTncB7OXJL7UzwOLPPQCfEUNyxL2qt+tF12THV+EO33O1G2Uk4feMSWua3iD39Itszx0f0bw== @@ -1356,6 +1356,11 @@ async-each@^1.0.0: resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" integrity sha1-GdOGodntxufByF04iu28xW0zYC0= +async-foreach@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/async-foreach/-/async-foreach-0.1.3.tgz#36121f845c0578172de419a97dbeb1d16ec34542" + integrity sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI= + async-limiter@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" @@ -1619,6 +1624,13 @@ blob@0.0.4: resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.4.tgz#bcf13052ca54463f30f9fc7e95b9a47630a94921" integrity sha1-vPEwUspURj8w+fx+lbmkdjCpSSE= +block-stream@*: + version "0.0.9" + resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a" + integrity sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo= + dependencies: + inherits "~2.0.0" + bluebird@^3.1.1, bluebird@^3.3.0, bluebird@^3.5.1, bluebird@^3.5.3, bluebird@~3.5.0: version "3.5.3" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.3.tgz#7d01c6f9616c9a51ab0f8c549a79dfe6ec33efa7" @@ -1669,10 +1681,10 @@ bootstrap-vue@^2.0.0-rc.11: popper.js "^1.12.9" vue-functional-data-merge "^2.0.5" -bootstrap@4.1.3, bootstrap@^4.1.1: - version "4.1.3" - resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.1.3.tgz#0eb371af2c8448e8c210411d0cb824a6409a12be" - integrity sha512-rDFIzgXcof0jDyjNosjv4Sno77X4KuPeFxG2XZZv1/Kc8DRVGVADdoQyyOVDwPqL36DDmtCQbrpMCqvpPLJQ0w== +bootstrap@4.3.1, bootstrap@^4.1.1, bootstrap@^4.1.3: + version "4.3.1" + resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.3.1.tgz#280ca8f610504d99d7b6b4bfc4b68cec601704ac" + integrity sha512-rXqOmH1VilAt2DyPzluTi2blhk17bO7ef+zLLPlWvG494pDxcM234pJ8wTc/6R40UWizAIIMgxjvxZg5kmsbag== boxen@^1.2.1: version "1.3.0" @@ -1957,6 +1969,14 @@ callsites@^3.0.0: resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.0.0.tgz#fb7eb569b72ad7a45812f93fd9430a3e410b3dd3" integrity sha512-tWnkwu9YEq2uzlBDI4RcLn8jrFvF9AOi8PxDNU3hZZjJcjkcRAq3vCI+vZcg1SuxISDYe86k9VZFwAxDiJGoAw== +camelcase-keys@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7" + integrity sha1-MIvur/3ygRkFHvodkyITyRuPkuc= + dependencies: + camelcase "^2.0.0" + map-obj "^1.0.0" + camelcase-keys@^4.0.0: version "4.2.0" resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-4.2.0.tgz#a2aa5fb1af688758259c32c141426d78923b9b77" @@ -1966,6 +1986,16 @@ camelcase-keys@^4.0.0: map-obj "^2.0.0" quick-lru "^1.0.0" +camelcase@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" + integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8= + +camelcase@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" + integrity sha1-MvxLn82vhF/N9+c7uXysImHwqwo= + camelcase@^4.0.0, camelcase@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" @@ -2010,7 +2040,7 @@ ccount@^1.0.0: resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.0.3.tgz#f1cec43f332e2ea5a569fd46f9f5bde4e6102aff" integrity sha512-Jt9tIBkRc9POUof7QA/VwWd+58fKkEEfI+/t1/eOlxKM7ZhrczNzMFefge7Ai+39y1pR/pP6cI19guHy3FSLmw== -chalk@1.1.3, chalk@^1.0.0, chalk@^1.1.3: +chalk@1.1.3, chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= @@ -2060,7 +2090,7 @@ chardet@^0.5.0: resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.5.0.tgz#fe3ac73c00c3d865ffcc02a0682e2c20b6a06029" integrity sha512-9ZTaoBaePSCFvNlNGrsyI8ZVACP2svUtq0DkM7t4K2ClAa96sqOIRjAzDTc8zXzFt1cZR46rRzLTiHFSJ+Qw0g== -"charenc@>= 0.0.1": +"charenc@>= 0.0.1", charenc@~0.0.1: version "0.0.2" resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc= @@ -2204,6 +2234,15 @@ clipboard@^1.5.5, clipboard@^1.7.1: select "^1.1.2" tiny-emitter "^2.0.0" +cliui@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" + integrity sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0= + dependencies: + string-width "^1.0.1" + strip-ansi "^3.0.1" + wrap-ansi "^2.0.0" + cliui@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.0.0.tgz#743d4650e05f36d1ed2575b59638d87322bfbbcc" @@ -2572,6 +2611,14 @@ cropper@^2.3.0: dependencies: jquery ">= 1.9.1" +cross-spawn@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-3.0.1.tgz#1256037ecb9f0c5f79e3d6ef135e30770184b982" + integrity sha1-ElYDfsufDF9549bvE14wdwGEuYI= + dependencies: + lru-cache "^4.0.1" + which "^1.2.9" + cross-spawn@^5.0.1: version "5.1.0" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" @@ -2592,7 +2639,7 @@ cross-spawn@^6.0.0, cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" -"crypt@>= 0.0.1": +"crypt@>= 0.0.1", crypt@~0.0.1: version "0.0.2" resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs= @@ -2695,6 +2742,11 @@ custom-event@~1.0.0: resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425" integrity sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU= +custom-jquery-matchers@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/custom-jquery-matchers/-/custom-jquery-matchers-2.1.0.tgz#e5988fa9715c416b0986b372563f872d9e91e024" + integrity sha512-f2vQrncnwqbeDne9ag/BRjrmG9BxIEKnZ9Z8TyG5l9B8wwyINWd7LvuHDlTkMPqLTd3ghcvqFoTsxjhOP371JQ== + cyclist@~0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640" @@ -3029,7 +3081,7 @@ decamelize-keys@^1.0.0: decamelize "^1.1.0" map-obj "^1.0.0" -decamelize@^1.1.0, decamelize@^1.1.1, decamelize@^1.2.0: +decamelize@^1.1.0, decamelize@^1.1.1, decamelize@^1.1.2, decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= @@ -4414,6 +4466,16 @@ fsevents@^1.2.2, fsevents@^1.2.3: nan "^2.9.2" node-pre-gyp "^0.10.0" +fstream@^1.0.0, fstream@^1.0.2: + version "1.0.11" + resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171" + integrity sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE= + dependencies: + graceful-fs "^4.1.2" + inherits "~2.0.0" + mkdirp ">=0.5 0" + rimraf "2" + function-bind@^1.0.2, function-bind@^1.1.0, function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" @@ -4443,11 +4505,23 @@ gauge@~2.7.3: strip-ansi "^3.0.1" wide-align "^1.1.0" +gaze@^1.0.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/gaze/-/gaze-1.1.3.tgz#c441733e13b927ac8c0ff0b4c3b033f28812924a" + integrity sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g== + dependencies: + globule "^1.0.0" + get-caller-file@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5" integrity sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U= +get-stdin@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" + integrity sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4= + get-stdin@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b" @@ -4477,27 +4551,27 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" -gettext-extractor-vue@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/gettext-extractor-vue/-/gettext-extractor-vue-4.0.1.tgz#69d2737eb8f1938803ffcf9317133ed59fb2372f" - integrity sha512-UnkWVO5jQQrs17L7HSlKj3O7U8C4+AQFzE05MK/I+JkMZdQdB6JMjA0IK0c4GObSlkgx4aiCCG6zWqIBnDR95w== +gettext-extractor-vue@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/gettext-extractor-vue/-/gettext-extractor-vue-4.0.2.tgz#16e1cdbdaf37e5bdf3cb0aff63685bdc5e74e906" + integrity sha512-tnTAU1TdQFREv4Q4hfBDuB329eugeFsYmV7lE9U1jkZEyxcf4oPgimLHNZVNaEUg4+JJwhB8B9HIeqbcbSW32g== dependencies: bluebird "^3.5.1" glob "^7.1.2" - vue-template-compiler "^2.5.0" + vue-template-compiler "^2.5.20" -gettext-extractor@^3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/gettext-extractor/-/gettext-extractor-3.3.2.tgz#d5172ba8d175678bd40a5abe7f908fa2a9d9473b" - integrity sha1-1RcrqNF1Z4vUClq+f5CPoqnZRzs= +gettext-extractor@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/gettext-extractor/-/gettext-extractor-3.4.3.tgz#882679cefc71888eb6e69297e6b2dc14c0384fef" + integrity sha512-YSNdTCHmzm58Rc21thtXj7jRIOlqINftM3XbtvNK28C88i35EnEB89iOeV9Vetv7wcb/wiPAtcq/6iSnt2pMyw== dependencies: - "@types/glob" "^5" + "@types/glob" "5 - 7" "@types/parse5" "^5" css-selector-parser "^1.3" glob "5 - 7" parse5 "^5" pofile "^1" - typescript "^2" + typescript "2 - 3" glob-parent@^3.1.0: version "3.1.0" @@ -4512,7 +4586,7 @@ glob-to-regexp@^0.3.0: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs= -"glob@5 - 7", glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3: +"glob@5 - 7", glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@~7.1.1: version "7.1.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== @@ -4629,6 +4703,15 @@ globjoin@^0.1.4: resolved "https://registry.yarnpkg.com/globjoin/-/globjoin-0.1.4.tgz#2f4494ac8919e3767c5cbb691e9f463324285d43" integrity sha1-L0SUrIkZ43Z8XLtpHp9GMyQoXUM= +globule@^1.0.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/globule/-/globule-1.2.1.tgz#5dffb1b191f22d20797a9369b49eab4e9839696d" + integrity sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ== + dependencies: + glob "~7.1.1" + lodash "~4.17.10" + minimatch "~3.0.2" + gonzales-pe@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/gonzales-pe/-/gonzales-pe-4.2.3.tgz#41091703625433285e0aee3aa47829fc1fbeb6f2" @@ -5098,6 +5181,18 @@ imurmurhash@^0.1.4: resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= +in-publish@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/in-publish/-/in-publish-2.0.0.tgz#e20ff5e3a2afc2690320b6dc552682a9c7fadf51" + integrity sha1-4g/146KvwmkDILbcVSaCqcf631E= + +indent-string@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" + integrity sha1-ji1INIdCEhtKghi3oTfppSBJ3IA= + dependencies: + repeating "^2.0.0" + indent-string@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.2.0.tgz#4a5fd6d27cc332f37e5419a504dbb837105c9289" @@ -5121,7 +5216,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3: +inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= @@ -5271,7 +5366,7 @@ is-binary-path@^1.0.0: dependencies: binary-extensions "^1.0.0" -is-buffer@^1.1.5: +is-buffer@^1.1.5, is-buffer@~1.1.1: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== @@ -5364,6 +5459,13 @@ is-extglob@^2.1.0, is-extglob@^2.1.1: resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= +is-finite@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa" + integrity sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko= + dependencies: + number-is-nan "^1.0.0" + is-fullwidth-code-point@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" @@ -5532,6 +5634,11 @@ is-typedarray@~1.0.0: resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= +is-utf8@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" + integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI= + is-whitespace-character@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/is-whitespace-character/-/is-whitespace-character-1.0.2.tgz#ede53b4c6f6fb3874533751ec9280d01928d03ed" @@ -6090,6 +6197,11 @@ jquery.waitforimages@^2.2.0: resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.3.1.tgz#958ce29e81c9790f31be7792df5d4d95fc57fbca" integrity sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg== +js-base64@^2.1.8: + version "2.5.1" + resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.5.1.tgz#1efa39ef2c5f7980bb1784ade4a8af2de3291121" + integrity sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw== + js-beautify@^1.8.8: version "1.8.9" resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.8.9.tgz#08e3c05ead3ecfbd4f512c3895b1cda76c87d523" @@ -6493,6 +6605,17 @@ linkify-it@^2.0.0: dependencies: uc.micro "^1.0.1" +load-json-file@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" + integrity sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA= + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + pinkie-promise "^2.0.0" + strip-bom "^2.0.0" + load-json-file@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" @@ -6543,12 +6666,17 @@ locate-path@^3.0.0: p-locate "^3.0.0" path-exists "^3.0.0" +lodash.assign@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" + integrity sha1-DZnzzNem0mHRm9rrkkUAXShYCOc= + lodash.camelcase@4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= -lodash.clonedeep@^4.5.0: +lodash.clonedeep@^4.3.2, lodash.clonedeep@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= @@ -6603,7 +6731,7 @@ lodash.upperfirst@4.3.1: resolved "https://registry.yarnpkg.com/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz#1365edf431480481ef0d1c68957a5ed99d49f7ce" integrity sha1-E2Xt9DFIBIHvDRxolXpe2Z1J984= -lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.3.0, lodash@^4.5.0: +lodash@^4.0.0, lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.3.0, lodash@^4.5.0, lodash@~4.17.10: version "4.17.11" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== @@ -6720,7 +6848,7 @@ map-cache@^0.2.2: resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= -map-obj@^1.0.0: +map-obj@^1.0.0, map-obj@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0= @@ -6776,6 +6904,15 @@ md5.js@^1.3.4: hash-base "^3.0.0" inherits "^2.0.1" +md5@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/md5/-/md5-2.2.1.tgz#53ab38d5fe3c8891ba465329ea23fac0540126f9" + integrity sha1-U6s41f48iJG6RlMp6iP6wFQBJvk= + dependencies: + charenc "~0.0.1" + crypt "~0.0.1" + is-buffer "~1.1.1" + mdast-util-compact@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/mdast-util-compact/-/mdast-util-compact-1.0.2.tgz#c12ebe16fffc84573d3e19767726de226e95f649" @@ -6822,6 +6959,22 @@ memory-fs@^0.4.0, memory-fs@~0.4.1: errno "^0.1.3" readable-stream "^2.0.1" +meow@^3.7.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" + integrity sha1-cstmi0JSKCkKu/qFaJJYcwioAfs= + dependencies: + camelcase-keys "^2.0.0" + decamelize "^1.1.2" + loud-rejection "^1.0.0" + map-obj "^1.0.1" + minimist "^1.1.3" + normalize-package-data "^2.3.4" + object-assign "^4.0.1" + read-pkg-up "^1.0.1" + redent "^1.0.0" + trim-newlines "^1.0.0" + meow@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/meow/-/meow-5.0.0.tgz#dfc73d63a9afc714a5e371760eb5c88b91078aa4" @@ -6954,7 +7107,7 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= -"minimatch@2 || 3", minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4: +"minimatch@2 || 3", minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4, minimatch@~3.0.2: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== @@ -6979,7 +7132,7 @@ minimist@1.1.x: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.1.3.tgz#3bedfd91a92d39016fcfaa1c681e8faa1a1efda8" integrity sha1-O+39kaktOQFvz6ocaB6Pqhoe/ag= -minimist@1.2.0, minimist@^1.1.1, minimist@^1.2.0: +minimist@1.2.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= @@ -7023,7 +7176,7 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" -mkdirp@0.5.x, mkdirp@0.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: +mkdirp@0.5.x, mkdirp@0.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: version "0.5.1" resolved "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= @@ -7092,10 +7245,10 @@ mute-stream@0.0.7: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= -nan@^2.9.2: - version "2.10.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f" - integrity sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA== +nan@^2.10.0, nan@^2.9.2: + version "2.13.1" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.13.1.tgz#a15bee3790bde247e8f38f1d446edcdaeb05f2dd" + integrity sha512-I6YB/YEuDeUZMmhscXKxGgZlFnhsn5y0hgOZBadkzfTRrZBtJDZeg6eQf7PYMIEclwmorTKK8GztsyOUSVBREA== nanomatch@^1.2.9: version "1.2.9" @@ -7157,6 +7310,24 @@ node-forge@0.6.33: resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.6.33.tgz#463811879f573d45155ad6a9f43dc296e8e85ebc" integrity sha1-RjgRh59XPUUVWtap9D3ClujoXrw= +node-gyp@^3.8.0: + version "3.8.0" + resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.8.0.tgz#540304261c330e80d0d5edce253a68cb3964218c" + integrity sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA== + dependencies: + fstream "^1.0.0" + glob "^7.0.3" + graceful-fs "^4.1.2" + mkdirp "^0.5.0" + nopt "2 || 3" + npmlog "0 || 1 || 2 || 3 || 4" + osenv "0" + request "^2.87.0" + rimraf "2" + semver "~5.3.0" + tar "^2.0.0" + which "1" + node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" @@ -7229,6 +7400,31 @@ node-releases@^1.1.3: dependencies: semver "^5.3.0" +node-sass@^4.11.0: + version "4.11.0" + resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.11.0.tgz#183faec398e9cbe93ba43362e2768ca988a6369a" + integrity sha512-bHUdHTphgQJZaF1LASx0kAviPH7sGlcyNhWade4eVIpFp6tsn7SV8xNMTbsQFpEV9VXpnwTTnNYlfsZXgGgmkA== + dependencies: + async-foreach "^0.1.3" + chalk "^1.1.1" + cross-spawn "^3.0.0" + gaze "^1.0.0" + get-stdin "^4.0.1" + glob "^7.0.3" + in-publish "^2.0.0" + lodash.assign "^4.2.0" + lodash.clonedeep "^4.3.2" + lodash.mergewith "^4.6.0" + meow "^3.7.0" + mkdirp "^0.5.1" + nan "^2.10.0" + node-gyp "^3.8.0" + npmlog "^4.0.0" + request "^2.88.0" + sass-graph "^2.2.4" + stdout-stream "^1.4.0" + "true-case-path" "^1.0.2" + nodemon@^1.18.9: version "1.18.9" resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-1.18.9.tgz#90b467efd3b3c81b9453380aeb2a2cba535d0ead" @@ -7245,7 +7441,7 @@ nodemon@^1.18.9: undefsafe "^2.0.2" update-notifier "^2.5.0" -nopt@3.x: +"nopt@2 || 3", nopt@3.x: version "3.0.6" resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" integrity sha1-xkZdvwirzU2zWTF/eaxopkayj/k= @@ -7328,7 +7524,7 @@ npm-run-path@^2.0.0: dependencies: path-key "^2.0.0" -npmlog@^4.0.2: +"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.0, npmlog@^4.0.2: version "4.1.2" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== @@ -7541,6 +7737,13 @@ os-homedir@^1.0.0: resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= +os-locale@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" + integrity sha1-IPnxeuKe00XoveWDsT0gCYA8FNk= + dependencies: + lcid "^1.0.0" + os-locale@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-2.1.0.tgz#42bc2900a6b5b8bd17376c8e882b65afccf24bf2" @@ -7564,7 +7767,7 @@ os-tmpdir@^1.0.0, os-tmpdir@~1.0.2: resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= -osenv@^0.1.4: +osenv@0, osenv@^0.1.4: version "0.1.5" resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== @@ -7802,6 +8005,15 @@ path-to-regexp@0.1.7: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= +path-type@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" + integrity sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE= + dependencies: + graceful-fs "^4.1.2" + pify "^2.0.0" + pinkie-promise "^2.0.0" + path-type@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" @@ -7914,10 +8126,10 @@ pofile@^1: resolved "https://registry.yarnpkg.com/pofile/-/pofile-1.0.11.tgz#35aff58c17491d127a07336d5522ebc9df57c954" integrity sha512-Vy9eH1dRD9wHjYt/QqXcTz+RnX/zg53xK+KljFSX30PvdDMb2z+c6uDUeblUGqqJgz3QFsdlA0IJvHziPmWtQg== -popper.js@^1.12.9, popper.js@^1.14.3: - version "1.14.3" - resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.14.3.tgz#1438f98d046acf7b4d78cd502bf418ac64d4f095" - integrity sha1-FDj5jQRqz3tNeM1QK/QYrGTU8JU= +popper.js@^1.12.9, popper.js@^1.14.7: + version "1.14.7" + resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.14.7.tgz#e31ec06cfac6a97a53280c3e55e4e0c860e7738e" + integrity sha512-4q1hNvoUre/8srWsH7hnoSJ5xVmIL4qgz+s4qf2TnJIMyZFUFMGH+9vE7mXynAlHSZ/NdTmmow86muD0myUkVQ== portfinder@^1.0.9: version "1.0.13" @@ -8463,6 +8675,14 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.2.7: minimist "^1.2.0" strip-json-comments "~2.0.1" +read-pkg-up@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" + integrity sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI= + dependencies: + find-up "^1.0.0" + read-pkg "^1.0.0" + read-pkg-up@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" @@ -8487,6 +8707,15 @@ read-pkg-up@^4.0.0: find-up "^3.0.0" read-pkg "^3.0.0" +read-pkg@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28" + integrity sha1-9f+qXs0pyzHAR0vKfXVra7KePyg= + dependencies: + load-json-file "^1.0.0" + normalize-package-data "^2.3.2" + path-type "^1.0.0" + read-pkg@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" @@ -8556,6 +8785,14 @@ realpath-native@^1.0.0, realpath-native@^1.0.2: dependencies: util.promisify "^1.0.0" +redent@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" + integrity sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94= + dependencies: + indent-string "^2.1.0" + strip-indent "^1.0.1" + redent@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/redent/-/redent-2.0.0.tgz#c1b2007b42d57eb1389079b3c8333639d5e1ccaa" @@ -8745,6 +8982,13 @@ repeat-string@^1.5.4, repeat-string@^1.6.1: resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= +repeating@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" + integrity sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo= + dependencies: + is-finite "^1.0.0" + replace-ext@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb" @@ -8766,7 +9010,7 @@ request-promise-native@^1.0.5: stealthy-require "^1.1.0" tough-cookie ">=2.3.3" -request@^2.87.0: +request@^2.87.0, request@^2.88.0: version "2.88.0" resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== @@ -8894,7 +9138,7 @@ rfdc@^1.1.2: resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.1.2.tgz#e6e72d74f5dc39de8f538f65e00c36c18018e349" integrity sha512-92ktAgvZhBzYTIK0Mja9uen5q5J3NRVMoDkJL2VMwq6SXjVCgqvQeVP2XAaUY6HT+XpQYeLSjb3UoitBryKmdA== -rimraf@2.6.3, rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.6.1, rimraf@^2.6.2: +rimraf@2, rimraf@2.6.3, rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.6.1, rimraf@^2.6.2: version "2.6.3" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== @@ -9002,6 +9246,16 @@ sanitize-html@^1.16.1: srcset "^1.0.0" xtend "^4.0.0" +sass-graph@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-2.2.4.tgz#13fbd63cd1caf0908b9fd93476ad43a51d1e0b49" + integrity sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k= + dependencies: + glob "^7.0.0" + lodash "^4.0.0" + scss-tokenizer "^0.2.3" + yargs "^7.0.0" + sax@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" @@ -9033,6 +9287,14 @@ scope-css@^1.0.5: slugify "^1.3.1" strip-css-comments "^3.0.0" +scss-tokenizer@^0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz#8eb06db9a9723333824d3f5530641149847ce5d1" + integrity sha1-jrBtualyMzOCTT9VMGQRSYR85dE= + dependencies: + js-base64 "^2.1.8" + source-map "^0.4.2" + select-hose@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" @@ -9067,6 +9329,11 @@ semver-diff@^2.0.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== +semver@~5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" + integrity sha1-myzl094C0XxgEq0yaqa00M9U+U8= + send@0.16.2: version "0.16.2" resolved "https://registry.yarnpkg.com/send/-/send-0.16.2.tgz#6ecca1e0f8c156d141597559848df64730a6bbc1" @@ -9384,6 +9651,13 @@ source-map@0.5.0: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.0.tgz#0fe96503ac86a5adb5de63f4e412ae4872cdbe86" integrity sha1-D+llA6yGpa213mP05BKuSHLNvoY= +source-map@^0.4.2: + version "0.4.4" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" + integrity sha1-66T12pwNyZneaAMti092FzZSA2s= + dependencies: + amdefine ">=0.0.4" + source-map@^0.5.0, source-map@^0.5.6: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" @@ -9521,6 +9795,13 @@ statuses@~1.3.1: resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" integrity sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4= +stdout-stream@^1.4.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/stdout-stream/-/stdout-stream-1.4.1.tgz#5ac174cdd5cd726104aa0c0b2bd83815d8d535de" + integrity sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA== + dependencies: + readable-stream "^2.0.1" + stealthy-require@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" @@ -9655,6 +9936,13 @@ strip-ansi@^5.0.0: dependencies: ansi-regex "^4.0.0" +strip-bom@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" + integrity sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4= + dependencies: + is-utf8 "^0.2.0" + strip-bom@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" @@ -9672,6 +9960,13 @@ strip-eof@^1.0.0: resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= +strip-indent@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2" + integrity sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI= + dependencies: + get-stdin "^4.0.1" + strip-indent@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-2.0.0.tgz#5ef8db295d01e6ed6cbf7aab96998d7822527b68" @@ -9705,10 +10000,10 @@ stylelint-error-string-formatter@^1.0.1: resolved "https://registry.yarnpkg.com/stylelint-error-string-formatter/-/stylelint-error-string-formatter-1.0.1.tgz#366387825d6fb59569e8c5c3f5682398733756f9" integrity sha512-8zy0UsdnQZKVDwjWMQX36b30TaNMGcM2FzBcK9cshpXerpJ3AvF2/zw7FJ3Efm6DFviTBVsxR14F3FnDFhCxJw== -stylelint-scss@^3.5.3: - version "3.5.3" - resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-3.5.3.tgz#e158b3061eeec26d7f6088f346998a797432f3c8" - integrity sha512-QESQUOY1ldU5tlJTTM3Megz/QtJ39S58ByjZ7dZobGDq9qMjy5jbC7PDUasrv/T7pB1UbpPojpxX9K1OR7IPEg== +stylelint-scss@^3.5.4: + version "3.5.4" + resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-3.5.4.tgz#ff3ee989ac48f5c4f57313523b5ace059ffd6cc2" + integrity sha512-hEdEOfFXVqxWcUbenBONW/cAw5cJcEDasY8tGwKNAAn1GDHoZO1ATdWpr+iIk325mPGIQqVb1sUxsRxuL70trw== dependencies: lodash "^4.17.11" postcss-media-query-parser "^0.2.3" @@ -9847,6 +10142,15 @@ tapable@^1.0.0, tapable@^1.1.0: resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.0.tgz#0d076a172e3d9ba088fd2272b2668fb8d194b78c" integrity sha512-IlqtmLVaZA2qab8epUXbVWRn3aB1imbDMJtjB3nu4X0NqPkcY/JH9ZtCBWKHWPxs8Svi9tyo8w2dBoi07qZbBA== +tar@^2.0.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1" + integrity sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE= + dependencies: + block-stream "*" + fstream "^1.0.2" + inherits "2" + tar@^4: version "4.4.4" resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.4.tgz#ec8409fae9f665a4355cc3b4087d0820232bb8cd" @@ -10107,6 +10411,11 @@ tr46@^1.0.1: dependencies: punycode "^2.1.0" +trim-newlines@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" + integrity sha1-WIeWa7WCpFA6QetST301ARgVphM= + trim-newlines@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-2.0.0.tgz#b403d0b91be50c331dfc4b82eeceb22c3de16d20" @@ -10132,6 +10441,13 @@ trough@^1.0.0: resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.3.tgz#e29bd1614c6458d44869fc28b255ab7857ef7c24" integrity sha512-fwkLWH+DimvA4YCy+/nvJd61nWQQ2liO/nF/RjkTpiOGi+zxZzVkhb1mvbHIIW4b/8nDsYI8uTmAlc0nNkRMOw== +"true-case-path@^1.0.2": + version "1.0.3" + resolved "https://registry.yarnpkg.com/true-case-path/-/true-case-path-1.0.3.tgz#f813b5a8c86b40da59606722b144e3225799f47d" + integrity sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew== + dependencies: + glob "^7.1.2" + tryer@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.0.tgz#027b69fa823225e551cace3ef03b11f6ab37c1d7" @@ -10201,10 +10517,10 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript@^2: - version "2.9.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.2.tgz#1cbf61d05d6b96269244eb6a3bce4bd914e0f00c" - integrity sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w== +"typescript@2 - 3": + version "3.3.4000" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.3.4000.tgz#76b0f89cfdbf97827e1112d64f283f1151d6adf0" + integrity sha512-jjOcCZvpkl2+z7JFn0yBOoLQyLoIkNZAs/fYJkUG6VKy6zLPHJGfQJYFHzibB6GJaF/8QrcECtlQ5cpvRHSMEA== uc.micro@^1.0.1, uc.micro@^1.0.5: version "1.0.5" @@ -10627,12 +10943,12 @@ vue-jest@^4.0.0-beta.2: source-map "^0.5.6" ts-jest "^23.10.5" -vue-loader@^15.4.2: - version "15.4.2" - resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.4.2.tgz#812bb26e447dd3b84c485eb634190d914ce125e2" - integrity sha512-nVV27GNIA9MeoD8yQ3dkUzwlAaAsWeYSWZHsu/K04KCD339lW0Jv2sJWsjj3721SP7sl2lYdPmjcHgkWQSp5bg== +vue-loader@^15.4.2, vue-loader@^15.7.0: + version "15.7.0" + resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.7.0.tgz#27275aa5a3ef4958c5379c006dd1436ad04b25b3" + integrity sha512-x+NZ4RIthQOxcFclEcs8sXGEWqnZHodL2J9Vq+hUz+TDZzBaDIh1j3d9M2IUlTjtrHTZy4uMuRdTi8BGws7jLA== dependencies: - "@vue/component-compiler-utils" "^2.0.0" + "@vue/component-compiler-utils" "^2.5.1" hash-sum "^1.0.2" loader-utils "^1.1.0" vue-hot-reload-api "^2.3.0" @@ -10658,10 +10974,10 @@ vue-style-loader@^4.1.0: hash-sum "^1.0.2" loader-utils "^1.0.2" -vue-template-compiler@^2.5.0, vue-template-compiler@^2.5.21: - version "2.5.21" - resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.5.21.tgz#a57ceb903177e8f643560a8d639a0f8db647054a" - integrity sha512-Vmk5Cv7UcmI99B9nXJEkaK262IQNnHp5rJYo+EwYpe2epTAXqcVyExhV6pk8jTkxQK2vRc8v8KmZBAwdmUZvvw== +vue-template-compiler@^2.5.20, vue-template-compiler@^2.6.10: + version "2.6.10" + resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.6.10.tgz#323b4f3495f04faa3503337a82f5d6507799c9cc" + integrity sha512-jVZkw4/I/HT5ZMvRnhv78okGusqe0+qH2A0Em0Cp8aq78+NK9TII263CDVz2QXZsIT+yyV/gZc/j/vlwa+Epyg== dependencies: de-indent "^1.0.2" he "^1.1.0" @@ -10671,20 +10987,20 @@ vue-template-es2015-compiler@^1.9.0: resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825" integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw== -vue-virtual-scroll-list@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/vue-virtual-scroll-list/-/vue-virtual-scroll-list-1.2.5.tgz#bcbd010f7cdb035eba8958ebf807c6214d9a167a" - integrity sha1-vL0BD3zbA166iVjr+AfGIU2aFno= +vue-virtual-scroll-list@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/vue-virtual-scroll-list/-/vue-virtual-scroll-list-1.3.1.tgz#efcb83d3a3dcc69cd886fa4de1130a65493e8f76" + integrity sha512-PMTxiK9/P1LtgoWWw4n1QnmDDkYqIdWWCNdt1L4JD9g6rwDgnsGsSV10bAnd5n7DQLHGWHjRex+zAbjXWT8t0g== -vue@^2.5.21: - version "2.5.21" - resolved "https://registry.yarnpkg.com/vue/-/vue-2.5.21.tgz#3d33dcd03bb813912ce894a8303ab553699c4a85" - integrity sha512-Aejvyyfhn0zjVeLvXd70h4hrE4zZDx1wfZqia6ekkobLmUZ+vNFQer53B4fu0EjWBSiqApxPejzkO1Znt3joxQ== +vue@^2.5.21, vue@^2.6.10: + version "2.6.10" + resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.10.tgz#a72b1a42a4d82a721ea438d1b6bf55e66195c637" + integrity sha512-ImThpeNU9HbdZL3utgMCq0oiMzAkt1mcgy3/E6zWC/G6AaQoeuFdsl9nDhTDU3X1R6FK7nsIUuRACVcjI+A2GQ== -vuex@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/vuex/-/vuex-3.0.1.tgz#e761352ebe0af537d4bb755a9b9dc4be3df7efd2" - integrity sha512-wLoqz0B7DSZtgbWL1ShIBBCjv22GV5U+vcBFox658g6V0s4wZV9P4YjCNyoHSyIBpj1f29JBoNQIqD82cR4O3w== +vuex@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/vuex/-/vuex-3.1.0.tgz#634b81515cf0cfe976bd1ffe9601755e51f843b9" + integrity sha512-mdHeHT/7u4BncpUZMlxNaIdcN/HIt1GsGG5LKByArvYG/v6DvHcOxvDCts+7SRdCoIRGllK8IMZvQtQXLppDYg== w3c-hr-time@^1.0.1: version "1.0.1" @@ -10910,12 +11226,17 @@ whatwg-url@^7.0.0: tr46 "^1.0.1" webidl-conversions "^4.0.2" +which-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" + integrity sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8= + which-module@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= -which@^1.1.1, which@^1.2.1, which@^1.2.12, which@^1.2.14, which@^1.2.9, which@^1.3.0, which@^1.3.1: +which@1, which@^1.1.1, which@^1.2.1, which@^1.2.12, which@^1.2.14, which@^1.2.9, which@^1.3.0, which@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== @@ -11111,6 +11432,13 @@ yargs-parser@^11.1.1: camelcase "^5.0.0" decamelize "^1.2.0" +yargs-parser@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-5.0.0.tgz#275ecf0d7ffe05c77e64e7c86e4cd94bf0e1228a" + integrity sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo= + dependencies: + camelcase "^3.0.0" + yargs-parser@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-8.1.0.tgz#f1376a33b6629a5d063782944da732631e966950" @@ -11172,6 +11500,25 @@ yargs@^12.0.2, yargs@^12.0.4: y18n "^3.2.1 || ^4.0.0" yargs-parser "^11.1.1" +yargs@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-7.1.0.tgz#6ba318eb16961727f5d284f8ea003e8d6154d0c8" + integrity sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg= + dependencies: + camelcase "^3.0.0" + cliui "^3.2.0" + decamelize "^1.1.1" + get-caller-file "^1.0.1" + os-locale "^1.4.0" + read-pkg-up "^1.0.1" + require-directory "^2.1.1" + require-main-filename "^1.0.1" + set-blocking "^2.0.0" + string-width "^1.0.2" + which-module "^1.0.0" + y18n "^3.2.1" + yargs-parser "^5.0.0" + yarn-deduplicate@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/yarn-deduplicate/-/yarn-deduplicate-1.1.1.tgz#19b4a87654b66f55bf3a4bd6b153b4e4ab1b6e6d" |